xref: /vim-8.2.3635/src/testdir/runtest.vim (revision a0122dcd)
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_ function.  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" If the environment variable $TEST_FILTER is set then only test functions
11" matching this pattern are executed.  E.g. for sh/bash:
12"     export TEST_FILTER=Test_channel
13" For csh:
14"     setenv TEST_FILTER Test_channel
15"
16" While working on a test you can make $TEST_NO_RETRY non-empty to not retry:
17"     export TEST_NO_RETRY=yes
18"
19" To ignore failure for tests that are known to fail in a certain environment,
20" set $TEST_MAY_FAIL to a comma separated list of function names.  E.g. for
21" sh/bash:
22"     export TEST_MAY_FAIL=Test_channel_one,Test_channel_other
23" The failure report will then not be included in the test.log file and
24" "make test" will not fail.
25"
26" The test script may contain anything, only functions that start with
27" "Test_" are special.  These will be invoked and should contain assert
28" functions.  See test_assert.vim for an example.
29"
30" It is possible to source other files that contain "Test_" functions.  This
31" can speed up testing, since Vim does not need to restart.  But be careful
32" that the tests do not interfere with each other.
33"
34" If an error cannot be detected properly with an assert function add the
35" error to the v:errors list:
36"   call add(v:errors, 'test foo failed: Cannot find xyz')
37"
38" If preparation for each Test_ function is needed, define a SetUp function.
39" It will be called before each Test_ function.
40"
41" If cleanup after each Test_ function is needed, define a TearDown function.
42" It will be called after each Test_ function.
43"
44" When debugging a test it can be useful to add messages to v:errors:
45"	call add(v:errors, "this happened")
46
47
48" Without the +eval feature we can't run these tests, bail out.
49silent! while 0
50  qa!
51silent! endwhile
52
53" In the GUI we can always change the screen size.
54if has('gui_running')
55  set columns=80 lines=25
56endif
57
58" Check that the screen size is at least 24 x 80 characters.
59if &lines < 24 || &columns < 80
60  let error = 'Screen size too small! Tests require at least 24 lines with 80 characters, got ' .. &lines .. ' lines with ' .. &columns .. ' characters'
61  echoerr error
62  split test.log
63  $put =error
64  write
65  split messages
66  call append(line('$'), '')
67  call append(line('$'), 'From ' . expand('%') . ':')
68  call append(line('$'), error)
69  write
70  qa!
71endif
72
73if has('reltime')
74  let s:start_time = reltime()
75endif
76
77" Common with all tests on all systems.
78source setup.vim
79
80" For consistency run all tests with 'nocompatible' set.
81" This also enables use of line continuation.
82set nocp viminfo+=nviminfo
83
84" Use utf-8 by default, instead of whatever the system default happens to be.
85" Individual tests can overrule this at the top of the file and use
86" g:orig_encoding if needed.
87let g:orig_encoding = &encoding
88set encoding=utf-8
89
90" REDIR_TEST_TO_NULL has a very permissive SwapExists autocommand which is for
91" the test_name.vim file itself. Replace it here with a more restrictive one,
92" so we still catch mistakes.
93let s:test_script_fname = expand('%')
94au! SwapExists * call HandleSwapExists()
95func HandleSwapExists()
96  if exists('g:ignoreSwapExists')
97    return
98  endif
99  " Ignore finding a swap file for the test script (the user might be
100  " editing it and do ":make test_name") and the output file.
101  " Report finding another swap file and chose 'q' to avoid getting stuck.
102  if expand('<afile>') == 'messages' || expand('<afile>') =~ s:test_script_fname
103    let v:swapchoice = 'e'
104  else
105    call assert_report('Unexpected swap file: ' .. v:swapname)
106    let v:swapchoice = 'q'
107  endif
108endfunc
109
110" Avoid stopping at the "hit enter" prompt
111set nomore
112
113" Output all messages in English.
114lang mess C
115
116" suppress menu translation
117if has('gui_running') && exists('did_install_default_menus')
118  source $VIMRUNTIME/delmenu.vim
119  set langmenu=none
120  source $VIMRUNTIME/menu.vim
121endif
122
123" Always use forward slashes.
124set shellslash
125
126let s:srcdir = expand('%:p:h:h')
127
128if has('win32')
129  " avoid prompt that is long or contains a line break
130  let $PROMPT = '$P$G'
131  " On MS-Windows t_md and t_me are Vim specific escape sequences.
132  let s:t_bold = "\x1b[1m"
133  let s:t_normal = "\x1b[m"
134else
135  let s:t_bold = &t_md
136  let s:t_normal = &t_me
137endif
138
139if has('mac')
140  " In MacOS, when starting a shell in a terminal, a bash deprecation warning
141  " message is displayed. This breaks the terminal test. Disable the warning
142  " message.
143  let $BASH_SILENCE_DEPRECATION_WARNING = 1
144endif
145
146" Prepare for calling test_garbagecollect_now().
147let v:testing = 1
148
149" Support function: get the alloc ID by name.
150function GetAllocId(name)
151  exe 'split ' . s:srcdir . '/alloc.h'
152  let top = search('typedef enum')
153  if top == 0
154    call add(v:errors, 'typedef not found in alloc.h')
155  endif
156  let lnum = search('aid_' . a:name . ',')
157  if lnum == 0
158    call add(v:errors, 'Alloc ID ' . a:name . ' not defined')
159  endif
160  close
161  return lnum - top - 1
162endfunc
163
164func RunTheTest(test)
165  echo 'Executing ' . a:test
166  if has('reltime')
167    let func_start = reltime()
168  endif
169
170  " Avoid stopping at the "hit enter" prompt
171  set nomore
172
173  " Avoid a three second wait when a message is about to be overwritten by the
174  " mode message.
175  set noshowmode
176
177  " Clear any overrides.
178  call test_override('ALL', 0)
179
180  " Some tests wipe out buffers.  To be consistent, always wipe out all
181  " buffers.
182  %bwipe!
183
184  " The test may change the current directory. Save and restore the
185  " directory after executing the test.
186  let save_cwd = getcwd()
187
188  if exists("*SetUp")
189    try
190      call SetUp()
191    catch
192      call add(v:errors, 'Caught exception in SetUp() before ' . a:test . ': ' . v:exception . ' @ ' . v:throwpoint)
193    endtry
194  endif
195
196  if a:test =~ 'Test_nocatch_'
197    " Function handles errors itself.  This avoids skipping commands after the
198    " error.
199    exe 'call ' . a:test
200  else
201    try
202      au VimLeavePre * call EarlyExit(g:testfunc)
203      exe 'call ' . a:test
204      au! VimLeavePre
205    catch /^\cskipped/
206      call add(s:messages, '    Skipped')
207      call add(s:skipped, 'SKIPPED ' . a:test . ': ' . substitute(v:exception, '^\S*\s\+', '',  ''))
208    catch
209      call add(v:errors, 'Caught exception in ' . a:test . ': ' . v:exception . ' @ ' . v:throwpoint)
210    endtry
211  endif
212
213  " In case 'insertmode' was set and something went wrong, make sure it is
214  " reset to avoid trouble with anything else.
215  set noinsertmode
216
217  if exists("*TearDown")
218    try
219      call TearDown()
220    catch
221      call add(v:errors, 'Caught exception in TearDown() after ' . a:test . ': ' . v:exception . ' @ ' . v:throwpoint)
222    endtry
223  endif
224
225  " Clear any autocommands and put back the catch-all for SwapExists.
226  au!
227  au SwapExists * call HandleSwapExists()
228
229  " Check for and close any stray popup windows.
230  if has('popupwin')
231    call assert_equal([], popup_list())
232    call popup_clear(1)
233  endif
234
235  " Close any extra tab pages and windows and make the current one not modified.
236  while tabpagenr('$') > 1
237    let winid = win_getid()
238    quit!
239    if winid == win_getid()
240      echoerr 'Could not quit window'
241      break
242    endif
243  endwhile
244
245  while 1
246    let wincount = winnr('$')
247    if wincount == 1
248      break
249    endif
250    bwipe!
251    if wincount == winnr('$')
252      " Did not manage to close a window.
253      only!
254      break
255    endif
256  endwhile
257
258  exe 'cd ' . save_cwd
259
260  let message = 'Executed ' . a:test
261  if has('reltime')
262    let message ..= repeat(' ', 50 - len(message))
263    let time = reltime(func_start)
264    if has('float') && reltimefloat(time) > 0.1
265      let message = s:t_bold .. message
266    endif
267    let message ..= ' in ' .. reltimestr(time) .. ' seconds'
268    if has('float') && reltimefloat(time) > 0.1
269      let message ..= s:t_normal
270    endif
271  endif
272  call add(s:messages, message)
273  let s:done += 1
274endfunc
275
276func AfterTheTest(func_name)
277  if len(v:errors) > 0
278    if match(s:may_fail_list, '^' .. a:func_name) >= 0
279      let s:fail_expected += 1
280      call add(s:errors_expected, 'Found errors in ' . g:testfunc . ':')
281      call extend(s:errors_expected, v:errors)
282    else
283      let s:fail += 1
284      call add(s:errors, 'Found errors in ' . g:testfunc . ':')
285      call extend(s:errors, v:errors)
286    endif
287    let v:errors = []
288  endif
289endfunc
290
291func EarlyExit(test)
292  " It's OK for the test we use to test the quit detection.
293  if a:test != 'Test_zz_quit_detected()'
294    call add(v:errors, 'Test caused Vim to exit: ' . a:test)
295  endif
296
297  call FinishTesting()
298endfunc
299
300" This function can be called by a test if it wants to abort testing.
301func FinishTesting()
302  call AfterTheTest('')
303
304  " Don't write viminfo on exit.
305  set viminfo=
306
307  " Clean up files created by setup.vim
308  call delete('XfakeHOME', 'rf')
309
310  if s:fail == 0 && s:fail_expected == 0
311    " Success, create the .res file so that make knows it's done.
312    exe 'split ' . fnamemodify(g:testname, ':r') . '.res'
313    write
314  endif
315
316  if len(s:errors) > 0
317    " Append errors to test.log
318    split test.log
319    call append(line('$'), '')
320    call append(line('$'), 'From ' . g:testname . ':')
321    call append(line('$'), s:errors)
322    write
323  endif
324
325  if s:done == 0
326    if s:filtered > 0
327      let message = "NO tests match $TEST_FILTER: '" .. $TEST_FILTER .. "'"
328    else
329      let message = 'NO tests executed'
330    endif
331  else
332    if s:filtered > 0
333      call add(s:messages, "Filtered " .. s:filtered .. " tests with $TEST_FILTER")
334    endif
335    let message = 'Executed ' . s:done . (s:done > 1 ? ' tests' : ' test')
336  endif
337  if s:done > 0 && has('reltime')
338    let message = s:t_bold .. message .. repeat(' ', 40 - len(message))
339    let message ..= ' in ' .. reltimestr(reltime(s:start_time)) .. ' seconds'
340    let message ..= s:t_normal
341  endif
342  echo message
343  call add(s:messages, message)
344  if s:fail > 0
345    let message = s:fail . ' FAILED:'
346    echo message
347    call add(s:messages, message)
348    call extend(s:messages, s:errors)
349  endif
350  if s:fail_expected > 0
351    let message = s:fail_expected . ' FAILED (matching $TEST_MAY_FAIL):'
352    echo message
353    call add(s:messages, message)
354    call extend(s:messages, s:errors_expected)
355  endif
356
357  " Add SKIPPED messages
358  call extend(s:messages, s:skipped)
359
360  " Append messages to the file "messages"
361  split messages
362  call append(line('$'), '')
363  call append(line('$'), 'From ' . g:testname . ':')
364  call append(line('$'), s:messages)
365  write
366
367  qall!
368endfunc
369
370" Source the test script.  First grab the file name, in case the script
371" navigates away.  g:testname can be used by the tests.
372let g:testname = expand('%')
373let s:done = 0
374let s:fail = 0
375let s:fail_expected = 0
376let s:errors = []
377let s:errors_expected = []
378let s:messages = []
379let s:skipped = []
380if expand('%') =~ 'test_vimscript.vim'
381  " this test has intentional errors, don't use try/catch.
382  source %
383else
384  try
385    source %
386  catch /^\cskipped/
387    call add(s:messages, '    Skipped')
388    call add(s:skipped, 'SKIPPED ' . expand('%') . ': ' . substitute(v:exception, '^\S*\s\+', '',  ''))
389  catch
390    let s:fail += 1
391    call add(s:errors, 'Caught exception: ' . v:exception . ' @ ' . v:throwpoint)
392  endtry
393endif
394
395" Names of flaky tests.
396let s:flaky_tests = [
397      \ 'Test_BufWrite_lockmarks()',
398      \ 'Test_autocmd_SafeState()',
399      \ 'Test_bufunload_all()',
400      \ 'Test_client_server()',
401      \ 'Test_close_and_exit_cb()',
402      \ 'Test_close_output_buffer()',
403      \ 'Test_collapse_buffers()',
404      \ 'Test_cwd()',
405      \ 'Test_diff_screen()',
406      \ 'Test_exit_callback_interval()',
407      \ 'Test_map_timeout_with_timer_interrupt()',
408      \ 'Test_out_cb()',
409      \ 'Test_pipe_through_sort_all()',
410      \ 'Test_pipe_through_sort_some()',
411      \ 'Test_popup_and_window_resize()',
412      \ 'Test_quoteplus()',
413      \ 'Test_quotestar()',
414      \ 'Test_reltime()',
415      \ 'Test_state()',
416      \ 'Test_terminal_composing_unicode()',
417      \ 'Test_terminal_does_not_truncate_last_newlines()',
418      \ 'Test_terminal_no_cmd()',
419      \ 'Test_terminal_noblock()',
420      \ 'Test_terminal_redir_file()',
421      \ 'Test_termwinscroll()',
422      \ 'Test_timer_oneshot()',
423      \ 'Test_timer_paused()',
424      \ 'Test_timer_repeat_many()',
425      \ 'Test_timer_repeat_three()',
426      \ 'Test_timer_stop_all_in_callback()',
427      \ 'Test_timer_stop_in_callback()',
428      \ 'Test_timer_with_partial_callback()',
429      \ ]
430
431" Locate Test_ functions and execute them.
432redir @q
433silent function /^Test_
434redir END
435let s:tests = split(substitute(@q, '\(function\|def\) \(\k*()\)', '\2', 'g'))
436
437" If there is an extra argument filter the function names against it.
438if argc() > 1
439  let s:tests = filter(s:tests, 'v:val =~ argv(1)')
440endif
441
442" If the environment variable $TEST_FILTER is set then filter the function
443" names against it.
444let s:filtered = 0
445if $TEST_FILTER != ''
446  let s:filtered = len(s:tests)
447  let s:tests = filter(s:tests, 'v:val =~ $TEST_FILTER')
448  let s:filtered -= len(s:tests)
449endif
450
451let s:may_fail_list = []
452if $TEST_MAY_FAIL != ''
453  " Split the list at commas and add () to make it match g:testfunc.
454  let s:may_fail_list = split($TEST_MAY_FAIL, ',')->map({i, v -> v .. '()'})
455endif
456
457" Execute the tests in alphabetical order.
458for g:testfunc in sort(s:tests)
459  " Silence, please!
460  set belloff=all
461  let prev_error = ''
462  let total_errors = []
463  let g:run_nr = 1
464
465  " A test can set g:test_is_flaky to retry running the test.
466  let g:test_is_flaky = 0
467
468  call RunTheTest(g:testfunc)
469
470  " Repeat a flaky test.  Give up when:
471  " - $TEST_NO_RETRY is not empty
472  " - it fails again with the same message
473  " - it fails five times (with a different message)
474  if len(v:errors) > 0
475        \ && $TEST_NO_RETRY == ''
476        \ && (index(s:flaky_tests, g:testfunc) >= 0
477        \      || g:test_is_flaky)
478    while 1
479      call add(s:messages, 'Found errors in ' . g:testfunc . ':')
480      call extend(s:messages, v:errors)
481
482      call add(total_errors, 'Run ' . g:run_nr . ':')
483      call extend(total_errors, v:errors)
484
485      if g:run_nr == 5 || prev_error == v:errors[0]
486        call add(total_errors, 'Flaky test failed too often, giving up')
487        let v:errors = total_errors
488        break
489      endif
490
491      call add(s:messages, 'Flaky test failed, running it again')
492
493      " Flakiness is often caused by the system being very busy.  Sleep a
494      " couple of seconds to have a higher chance of succeeding the second
495      " time.
496      sleep 2
497
498      let prev_error = v:errors[0]
499      let v:errors = []
500      let g:run_nr += 1
501
502      call RunTheTest(g:testfunc)
503
504      if len(v:errors) == 0
505        " Test passed on rerun.
506        break
507      endif
508    endwhile
509  endif
510
511  call AfterTheTest(g:testfunc)
512endfor
513
514call FinishTesting()
515
516" vim: shiftwidth=2 sts=2 expandtab
517