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