xref: /vim-8.2.3635/src/testdir/runtest.vim (revision f3caeb63)
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  echoconsole '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    let g:skipped_reason = ''
200    exe 'call ' . a:test
201    if g:skipped_reason != ''
202      call add(s:messages, '    Skipped')
203      call add(s:skipped, 'SKIPPED ' . a:test . ': ' . g:skipped_reason)
204    endif
205  else
206    try
207      au VimLeavePre * call EarlyExit(g:testfunc)
208      exe 'call ' . a:test
209      au! VimLeavePre
210    catch /^\cskipped/
211      call add(s:messages, '    Skipped')
212      call add(s:skipped, 'SKIPPED ' . a:test . ': ' . substitute(v:exception, '^\S*\s\+', '',  ''))
213    catch
214      call add(v:errors, 'Caught exception in ' . a:test . ': ' . v:exception . ' @ ' . v:throwpoint)
215    endtry
216  endif
217
218  " In case 'insertmode' was set and something went wrong, make sure it is
219  " reset to avoid trouble with anything else.
220  set noinsertmode
221
222  if exists("*TearDown")
223    try
224      call TearDown()
225    catch
226      call add(v:errors, 'Caught exception in TearDown() after ' . a:test . ': ' . v:exception . ' @ ' . v:throwpoint)
227    endtry
228  endif
229
230  " Clear any autocommands and put back the catch-all for SwapExists.
231  au!
232  au SwapExists * call HandleSwapExists()
233
234  " Check for and close any stray popup windows.
235  if has('popupwin')
236    call assert_equal([], popup_list())
237    call popup_clear(1)
238  endif
239
240  " Close any extra tab pages and windows and make the current one not modified.
241  while tabpagenr('$') > 1
242    let winid = win_getid()
243    quit!
244    if winid == win_getid()
245      echoerr 'Could not quit window'
246      break
247    endif
248  endwhile
249
250  while 1
251    let wincount = winnr('$')
252    if wincount == 1
253      break
254    endif
255    bwipe!
256    if wincount == winnr('$')
257      " Did not manage to close a window.
258      only!
259      break
260    endif
261  endwhile
262
263  exe 'cd ' . save_cwd
264
265  let message = 'Executed ' . a:test
266  if has('reltime')
267    let message ..= repeat(' ', 50 - len(message))
268    let time = reltime(func_start)
269    if has('float') && reltimefloat(time) > 0.1
270      let message = s:t_bold .. message
271    endif
272    let message ..= ' in ' .. reltimestr(time) .. ' seconds'
273    if has('float') && reltimefloat(time) > 0.1
274      let message ..= s:t_normal
275    endif
276  endif
277  call add(s:messages, message)
278  let s:done += 1
279endfunc
280
281func AfterTheTest(func_name)
282  if len(v:errors) > 0
283    if match(s:may_fail_list, '^' .. a:func_name) >= 0
284      let s:fail_expected += 1
285      call add(s:errors_expected, 'Found errors in ' . g:testfunc . ':')
286      call extend(s:errors_expected, v:errors)
287    else
288      let s:fail += 1
289      call add(s:errors, 'Found errors in ' . g:testfunc . ':')
290      call extend(s:errors, v:errors)
291    endif
292    let v:errors = []
293  endif
294endfunc
295
296func EarlyExit(test)
297  " It's OK for the test we use to test the quit detection.
298  if a:test != 'Test_zz_quit_detected()'
299    call add(v:errors, 'Test caused Vim to exit: ' . a:test)
300  endif
301
302  call FinishTesting()
303endfunc
304
305" This function can be called by a test if it wants to abort testing.
306func FinishTesting()
307  call AfterTheTest('')
308
309  " Don't write viminfo on exit.
310  set viminfo=
311
312  " Clean up files created by setup.vim
313  call delete('XfakeHOME', 'rf')
314
315  if s:fail == 0 && s:fail_expected == 0
316    " Success, create the .res file so that make knows it's done.
317    exe 'split ' . fnamemodify(g:testname, ':r') . '.res'
318    write
319  endif
320
321  if len(s:errors) > 0
322    " Append errors to test.log
323    split test.log
324    call append(line('$'), '')
325    call append(line('$'), 'From ' . g:testname . ':')
326    call append(line('$'), s:errors)
327    write
328  endif
329
330  if s:done == 0
331    if s:filtered > 0
332      let message = "NO tests match $TEST_FILTER: '" .. $TEST_FILTER .. "'"
333    else
334      let message = 'NO tests executed'
335    endif
336  else
337    if s:filtered > 0
338      call add(s:messages, "Filtered " .. s:filtered .. " tests with $TEST_FILTER")
339    endif
340    let message = 'Executed ' . s:done . (s:done > 1 ? ' tests' : ' test')
341  endif
342  if s:done > 0 && has('reltime')
343    let message = s:t_bold .. message .. repeat(' ', 40 - len(message))
344    let message ..= ' in ' .. reltimestr(reltime(s:start_time)) .. ' seconds'
345    let message ..= s:t_normal
346  endif
347  echo message
348  call add(s:messages, message)
349  if s:fail > 0
350    let message = s:fail . ' FAILED:'
351    echo message
352    call add(s:messages, message)
353    call extend(s:messages, s:errors)
354  endif
355  if s:fail_expected > 0
356    let message = s:fail_expected . ' FAILED (matching $TEST_MAY_FAIL):'
357    echo message
358    call add(s:messages, message)
359    call extend(s:messages, s:errors_expected)
360  endif
361
362  " Add SKIPPED messages
363  call extend(s:messages, s:skipped)
364
365  " Append messages to the file "messages"
366  split messages
367  call append(line('$'), '')
368  call append(line('$'), 'From ' . g:testname . ':')
369  call append(line('$'), s:messages)
370  write
371
372  qall!
373endfunc
374
375" Source the test script.  First grab the file name, in case the script
376" navigates away.  g:testname can be used by the tests.
377let g:testname = expand('%')
378let s:done = 0
379let s:fail = 0
380let s:fail_expected = 0
381let s:errors = []
382let s:errors_expected = []
383let s:messages = []
384let s:skipped = []
385if expand('%') =~ 'test_vimscript.vim'
386  " this test has intentional errors, don't use try/catch.
387  source %
388else
389  try
390    source %
391  catch /^\cskipped/
392    call add(s:messages, '    Skipped')
393    call add(s:skipped, 'SKIPPED ' . expand('%') . ': ' . substitute(v:exception, '^\S*\s\+', '',  ''))
394  catch
395    let s:fail += 1
396    call add(s:errors, 'Caught exception: ' . v:exception . ' @ ' . v:throwpoint)
397  endtry
398endif
399
400" Names of flaky tests.
401let s:flaky_tests = [
402      \ 'Test_BufWrite_lockmarks()',
403      \ 'Test_autocmd_SafeState()',
404      \ 'Test_bufunload_all()',
405      \ 'Test_client_server()',
406      \ 'Test_close_and_exit_cb()',
407      \ 'Test_close_output_buffer()',
408      \ 'Test_collapse_buffers()',
409      \ 'Test_cwd()',
410      \ 'Test_diff_screen()',
411      \ 'Test_exit_callback_interval()',
412      \ 'Test_map_timeout_with_timer_interrupt()',
413      \ 'Test_out_cb()',
414      \ 'Test_pipe_through_sort_all()',
415      \ 'Test_pipe_through_sort_some()',
416      \ 'Test_popup_and_window_resize()',
417      \ 'Test_quoteplus()',
418      \ 'Test_quotestar()',
419      \ 'Test_reltime()',
420      \ 'Test_state()',
421      \ 'Test_terminal_composing_unicode()',
422      \ 'Test_terminal_does_not_truncate_last_newlines()',
423      \ 'Test_terminal_no_cmd()',
424      \ 'Test_terminal_noblock()',
425      \ 'Test_terminal_redir_file()',
426      \ 'Test_termwinscroll()',
427      \ 'Test_timer_oneshot()',
428      \ 'Test_timer_paused()',
429      \ 'Test_timer_repeat_many()',
430      \ 'Test_timer_repeat_three()',
431      \ 'Test_timer_stop_all_in_callback()',
432      \ 'Test_timer_stop_in_callback()',
433      \ 'Test_timer_with_partial_callback()',
434      \ ]
435
436" Locate Test_ functions and execute them.
437redir @q
438silent function /^Test_
439redir END
440let s:tests = split(substitute(@q, '\(function\|def\) \(\k*()\)', '\2', 'g'))
441
442" If there is an extra argument filter the function names against it.
443if argc() > 1
444  let s:tests = filter(s:tests, 'v:val =~ argv(1)')
445endif
446
447" If the environment variable $TEST_FILTER is set then filter the function
448" names against it.
449let s:filtered = 0
450if $TEST_FILTER != ''
451  let s:filtered = len(s:tests)
452  let s:tests = filter(s:tests, 'v:val =~ $TEST_FILTER')
453  let s:filtered -= len(s:tests)
454endif
455
456let s:may_fail_list = []
457if $TEST_MAY_FAIL != ''
458  " Split the list at commas and add () to make it match g:testfunc.
459  let s:may_fail_list = split($TEST_MAY_FAIL, ',')->map({i, v -> v .. '()'})
460endif
461
462" Execute the tests in alphabetical order.
463for g:testfunc in sort(s:tests)
464  " Silence, please!
465  set belloff=all
466  let prev_error = ''
467  let total_errors = []
468  let g:run_nr = 1
469
470  " A test can set g:test_is_flaky to retry running the test.
471  let g:test_is_flaky = 0
472
473  call RunTheTest(g:testfunc)
474
475  " Repeat a flaky test.  Give up when:
476  " - $TEST_NO_RETRY is not empty
477  " - it fails again with the same message
478  " - it fails five times (with a different message)
479  if len(v:errors) > 0
480        \ && $TEST_NO_RETRY == ''
481        \ && (index(s:flaky_tests, g:testfunc) >= 0
482        \      || g:test_is_flaky)
483    while 1
484      call add(s:messages, 'Found errors in ' . g:testfunc . ':')
485      call extend(s:messages, v:errors)
486
487      call add(total_errors, 'Run ' . g:run_nr . ':')
488      call extend(total_errors, v:errors)
489
490      if g:run_nr == 5 || prev_error == v:errors[0]
491        call add(total_errors, 'Flaky test failed too often, giving up')
492        let v:errors = total_errors
493        break
494      endif
495
496      call add(s:messages, 'Flaky test failed, running it again')
497
498      " Flakiness is often caused by the system being very busy.  Sleep a
499      " couple of seconds to have a higher chance of succeeding the second
500      " time.
501      sleep 2
502
503      let prev_error = v:errors[0]
504      let v:errors = []
505      let g:run_nr += 1
506
507      call RunTheTest(g:testfunc)
508
509      if len(v:errors) == 0
510        " Test passed on rerun.
511        break
512      endif
513    endwhile
514  endif
515
516  call AfterTheTest(g:testfunc)
517endfor
518
519call FinishTesting()
520
521" vim: shiftwidth=2 sts=2 expandtab
522