1" This script is sourced while editing the .vim file with the tests. 2" When the script is successful the .res file will be created. 3" Errors are appended to the test.log file. 4" 5" To execute only specific test functions, add a second argument. It will be 6" matched against the names of the Test_ funtion. E.g.: 7" ../vim -u NONE -S runtest.vim test_channel.vim open_delay 8" The output can be found in the "messages" file. 9" 10" The test script may contain anything, only functions that start with 11" "Test_" are special. These will be invoked and should contain assert 12" functions. See test_assert.vim for an example. 13" 14" It is possible to source other files that contain "Test_" functions. This 15" can speed up testing, since Vim does not need to restart. But be careful 16" that the tests do not interfere with each other. 17" 18" If an error cannot be detected properly with an assert function add the 19" error to the v:errors list: 20" call add(v:errors, 'test foo failed: Cannot find xyz') 21" 22" If preparation for each Test_ function is needed, define a SetUp function. 23" It will be called before each Test_ function. 24" 25" If cleanup after each Test_ function is needed, define a TearDown function. 26" It will be called after each Test_ function. 27" 28" When debugging a test it can be useful to add messages to v:errors: 29" call add(v:errors, "this happened") 30 31 32" Without the +eval feature we can't run these tests, bail out. 33so small.vim 34 35" Check that the screen size is at least 24 x 80 characters. 36if &lines < 24 || &columns < 80 37 let error = 'Screen size too small! Tests require at least 24 lines with 80 characters' 38 echoerr error 39 split test.log 40 $put =error 41 write 42 split messages 43 call append(line('$'), error) 44 write 45 qa! 46endif 47 48if has('reltime') 49 let s:start_time = reltime() 50endif 51 52" Common with all tests on all systems. 53source setup.vim 54 55" For consistency run all tests with 'nocompatible' set. 56" This also enables use of line continuation. 57set nocp viminfo+=nviminfo 58 59" Use utf-8 by default, instead of whatever the system default happens to be. 60" Individual tests can overrule this at the top of the file and use 61" g:orig_encoding if needed. 62let g:orig_encoding = &encoding 63set encoding=utf-8 64 65" REDIR_TEST_TO_NULL has a very permissive SwapExists autocommand which is for 66" the test_name.vim file itself. Replace it here with a more restrictive one, 67" so we still catch mistakes. 68let s:test_script_fname = expand('%') 69au! SwapExists * call HandleSwapExists() 70func HandleSwapExists() 71 " Ignore finding a swap file for the test script (the user might be 72 " editing it and do ":make test_name") and the output file. 73 " Report finding another swap file and chose 'q' to avoid getting stuck. 74 if expand('<afile>') == 'messages' || expand('<afile>') =~ s:test_script_fname 75 let v:swapchoice = 'e' 76 else 77 call assert_report('Unexpected swap file: ' .. v:swapname) 78 let v:swapchoice = 'q' 79 endif 80endfunc 81 82" Avoid stopping at the "hit enter" prompt 83set nomore 84 85" Output all messages in English. 86lang mess C 87 88" Always use forward slashes. 89set shellslash 90 91let s:srcdir = expand('%:p:h:h') 92 93" Prepare for calling test_garbagecollect_now(). 94let v:testing = 1 95 96" Support function: get the alloc ID by name. 97function GetAllocId(name) 98 exe 'split ' . s:srcdir . '/alloc.h' 99 let top = search('typedef enum') 100 if top == 0 101 call add(v:errors, 'typedef not found in alloc.h') 102 endif 103 let lnum = search('aid_' . a:name . ',') 104 if lnum == 0 105 call add(v:errors, 'Alloc ID ' . a:name . ' not defined') 106 endif 107 close 108 return lnum - top - 1 109endfunc 110 111func RunTheTest(test) 112 echo 'Executing ' . a:test 113 if has('reltime') 114 let func_start = reltime() 115 endif 116 117 " Avoid stopping at the "hit enter" prompt 118 set nomore 119 120 " Avoid a three second wait when a message is about to be overwritten by the 121 " mode message. 122 set noshowmode 123 124 " Clear any overrides. 125 call test_override('ALL', 0) 126 127 " Some tests wipe out buffers. To be consistent, always wipe out all 128 " buffers. 129 %bwipe! 130 131 " The test may change the current directory. Save and restore the 132 " directory after executing the test. 133 let save_cwd = getcwd() 134 135 if exists("*SetUp") 136 try 137 call SetUp() 138 catch 139 call add(v:errors, 'Caught exception in SetUp() before ' . a:test . ': ' . v:exception . ' @ ' . v:throwpoint) 140 endtry 141 endif 142 143 if a:test =~ 'Test_nocatch_' 144 " Function handles errors itself. This avoids skipping commands after the 145 " error. 146 exe 'call ' . a:test 147 else 148 try 149 let s:test = a:test 150 au VimLeavePre * call EarlyExit(s:test) 151 exe 'call ' . a:test 152 au! VimLeavePre 153 catch /^\cskipped/ 154 call add(s:messages, ' Skipped') 155 call add(s:skipped, 'SKIPPED ' . a:test . ': ' . substitute(v:exception, '^\S*\s\+', '', '')) 156 catch 157 call add(v:errors, 'Caught exception in ' . a:test . ': ' . v:exception . ' @ ' . v:throwpoint) 158 endtry 159 endif 160 161 " In case 'insertmode' was set and something went wrong, make sure it is 162 " reset to avoid trouble with anything else. 163 set noinsertmode 164 165 if exists("*TearDown") 166 try 167 call TearDown() 168 catch 169 call add(v:errors, 'Caught exception in TearDown() after ' . a:test . ': ' . v:exception . ' @ ' . v:throwpoint) 170 endtry 171 endif 172 173 " Clear any autocommands 174 au! 175 au SwapExists * call HandleSwapExists() 176 177 " Close any stray popup windows 178 if has('textprop') 179 call popup_clear() 180 endif 181 182 " Close any extra tab pages and windows and make the current one not modified. 183 while tabpagenr('$') > 1 184 quit! 185 endwhile 186 187 while 1 188 let wincount = winnr('$') 189 if wincount == 1 190 break 191 endif 192 bwipe! 193 if wincount == winnr('$') 194 " Did not manage to close a window. 195 only! 196 break 197 endif 198 endwhile 199 200 exe 'cd ' . save_cwd 201 202 let message = 'Executed ' . a:test 203 if has('reltime') 204 let message ..= ' in ' .. reltimestr(reltime(func_start)) .. ' seconds' 205 endif 206 call add(s:messages, message) 207 let s:done += 1 208endfunc 209 210func AfterTheTest() 211 if len(v:errors) > 0 212 let s:fail += 1 213 call add(s:errors, 'Found errors in ' . s:test . ':') 214 call extend(s:errors, v:errors) 215 let v:errors = [] 216 endif 217endfunc 218 219func EarlyExit(test) 220 " It's OK for the test we use to test the quit detection. 221 if a:test != 'Test_zz_quit_detected()' 222 call add(v:errors, 'Test caused Vim to exit: ' . a:test) 223 endif 224 225 call FinishTesting() 226endfunc 227 228" This function can be called by a test if it wants to abort testing. 229func FinishTesting() 230 call AfterTheTest() 231 232 " Don't write viminfo on exit. 233 set viminfo= 234 235 " Clean up files created by setup.vim 236 call delete('XfakeHOME', 'rf') 237 238 if s:fail == 0 239 " Success, create the .res file so that make knows it's done. 240 exe 'split ' . fnamemodify(g:testname, ':r') . '.res' 241 write 242 endif 243 244 if len(s:errors) > 0 245 " Append errors to test.log 246 split test.log 247 call append(line('$'), '') 248 call append(line('$'), 'From ' . g:testname . ':') 249 call append(line('$'), s:errors) 250 write 251 endif 252 253 if s:done == 0 254 let message = 'NO tests executed' 255 else 256 let message = 'Executed ' . s:done . (s:done > 1 ? ' tests' : ' test') 257 endif 258 if has('reltime') 259 let message ..= ' in ' .. reltimestr(reltime(s:start_time)) .. ' seconds' 260 endif 261 echo message 262 call add(s:messages, message) 263 if s:fail > 0 264 let message = s:fail . ' FAILED:' 265 echo message 266 call add(s:messages, message) 267 call extend(s:messages, s:errors) 268 endif 269 270 " Add SKIPPED messages 271 call extend(s:messages, s:skipped) 272 273 " Append messages to the file "messages" 274 split messages 275 call append(line('$'), '') 276 call append(line('$'), 'From ' . g:testname . ':') 277 call append(line('$'), s:messages) 278 write 279 280 qall! 281endfunc 282 283" Source the test script. First grab the file name, in case the script 284" navigates away. g:testname can be used by the tests. 285let g:testname = expand('%') 286let s:done = 0 287let s:fail = 0 288let s:errors = [] 289let s:messages = [] 290let s:skipped = [] 291if expand('%') =~ 'test_vimscript.vim' 292 " this test has intentional s:errors, don't use try/catch. 293 source % 294else 295 try 296 source % 297 catch /^\cskipped/ 298 call add(s:messages, ' Skipped') 299 call add(s:skipped, 'SKIPPED ' . expand('%') . ': ' . substitute(v:exception, '^\S*\s\+', '', '')) 300 catch 301 let s:fail += 1 302 call add(s:errors, 'Caught exception: ' . v:exception . ' @ ' . v:throwpoint) 303 endtry 304endif 305 306" Names of flaky tests. 307let s:flaky_tests = [ 308 \ 'Test_call()', 309 \ 'Test_channel_handler()', 310 \ 'Test_client_server()', 311 \ 'Test_close_and_exit_cb()', 312 \ 'Test_close_callback()', 313 \ 'Test_close_handle()', 314 \ 'Test_close_lambda()', 315 \ 'Test_close_output_buffer()', 316 \ 'Test_close_partial()', 317 \ 'Test_collapse_buffers()', 318 \ 'Test_communicate()', 319 \ 'Test_cwd()', 320 \ 'Test_diff_screen()', 321 \ 'Test_exit_callback()', 322 \ 'Test_exit_callback_interval()', 323 \ 'Test_nb_basic()', 324 \ 'Test_oneshot()', 325 \ 'Test_open_delay()', 326 \ 'Test_out_cb()', 327 \ 'Test_paused()', 328 \ 'Test_pipe_through_sort_all()', 329 \ 'Test_pipe_through_sort_some()', 330 \ 'Test_popup_and_window_resize()', 331 \ 'Test_quoteplus()', 332 \ 'Test_quotestar()', 333 \ 'Test_raw_one_time_callback()', 334 \ 'Test_reltime()', 335 \ 'Test_repeat_three()', 336 \ 'Test_server_crash()', 337 \ 'Test_terminal_ansicolors_default()', 338 \ 'Test_terminal_ansicolors_func()', 339 \ 'Test_terminal_ansicolors_global()', 340 \ 'Test_terminal_composing_unicode()', 341 \ 'Test_terminal_does_not_truncate_last_newlines()', 342 \ 'Test_terminal_env()', 343 \ 'Test_terminal_hide_buffer()', 344 \ 'Test_terminal_make_change()', 345 \ 'Test_terminal_no_cmd()', 346 \ 'Test_terminal_noblock()', 347 \ 'Test_terminal_redir_file()', 348 \ 'Test_terminal_response_to_control_sequence()', 349 \ 'Test_terminal_scrollback()', 350 \ 'Test_terminal_split_quit()', 351 \ 'Test_terminal_termwinkey()', 352 \ 'Test_terminal_termwinsize_mininmum()', 353 \ 'Test_terminal_termwinsize_option_fixed()', 354 \ 'Test_terminal_termwinsize_option_zero()', 355 \ 'Test_terminal_tmap()', 356 \ 'Test_terminal_wall()', 357 \ 'Test_terminal_wipe_buffer()', 358 \ 'Test_terminal_wqall()', 359 \ 'Test_two_channels()', 360 \ 'Test_unlet_handle()', 361 \ 'Test_with_partial_callback()', 362 \ 'Test_zero_reply()', 363 \ 'Test_zz1_terminal_in_gui()', 364 \ ] 365 366" Pattern indicating a common flaky test failure. 367let s:flaky_errors_re = 'StopVimInTerminal\|VerifyScreenDump' 368 369" Locate Test_ functions and execute them. 370redir @q 371silent function /^Test_ 372redir END 373let s:tests = split(substitute(@q, 'function \(\k*()\)', '\1', 'g')) 374 375" If there is an extra argument filter the function names against it. 376if argc() > 1 377 let s:tests = filter(s:tests, 'v:val =~ argv(1)') 378endif 379 380" Execute the tests in alphabetical order. 381for s:test in sort(s:tests) 382 " Silence, please! 383 set belloff=all 384 let prev_error = '' 385 let total_errors = [] 386 let run_nr = 1 387 388 call RunTheTest(s:test) 389 390 " Repeat a flaky test. Give up when: 391 " - it fails again with the same message 392 " - it fails five times (with a different mesage) 393 if len(v:errors) > 0 394 \ && (index(s:flaky_tests, s:test) >= 0 395 \ || v:errors[0] =~ s:flaky_errors_re) 396 while 1 397 call add(s:messages, 'Found errors in ' . s:test . ':') 398 call extend(s:messages, v:errors) 399 400 call add(total_errors, 'Run ' . run_nr . ':') 401 call extend(total_errors, v:errors) 402 403 if run_nr == 5 || prev_error == v:errors[0] 404 call add(total_errors, 'Flaky test failed too often, giving up') 405 let v:errors = total_errors 406 break 407 endif 408 409 call add(s:messages, 'Flaky test failed, running it again') 410 411 " Flakiness is often caused by the system being very busy. Sleep a 412 " couple of seconds to have a higher chance of succeeding the second 413 " time. 414 sleep 2 415 416 let prev_error = v:errors[0] 417 let v:errors = [] 418 let run_nr += 1 419 420 call RunTheTest(s:test) 421 422 if len(v:errors) == 0 423 " Test passed on rerun. 424 break 425 endif 426 endwhile 427 endif 428 429 call AfterTheTest() 430endfor 431 432call FinishTesting() 433 434" vim: shiftwidth=2 sts=2 expandtab 435