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