xref: /vim-8.2.3635/src/testdir/runtest.vim (revision 4b2c8047)
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.
96if has("win32")
97  " replace any '/' directory separators by '\\'
98  let s:test_script_fname = substitute(expand('%'), '/', '\\', 'g')
99else
100  let s:test_script_fname = expand('%')
101endif
102au! SwapExists * call HandleSwapExists()
103func HandleSwapExists()
104  if exists('g:ignoreSwapExists')
105    return
106  endif
107  " Ignore finding a swap file for the test script (the user might be
108  " editing it and do ":make test_name") and the output file.
109  " Report finding another swap file and chose 'q' to avoid getting stuck.
110  if expand('<afile>') == 'messages' || expand('<afile>') =~ s:test_script_fname
111    let v:swapchoice = 'e'
112  else
113    call assert_report('Unexpected swap file: ' .. v:swapname)
114    let v:swapchoice = 'q'
115  endif
116endfunc
117
118" Avoid stopping at the "hit enter" prompt
119set nomore
120
121" Output all messages in English.
122lang mess C
123
124" suppress menu translation
125if has('gui_running') && exists('did_install_default_menus')
126  source $VIMRUNTIME/delmenu.vim
127  set langmenu=none
128  source $VIMRUNTIME/menu.vim
129endif
130
131" Always use forward slashes.
132set shellslash
133
134let s:srcdir = expand('%:p:h:h')
135
136if has('win32')
137  " avoid prompt that is long or contains a line break
138  let $PROMPT = '$P$G'
139  " On MS-Windows t_md and t_me are Vim specific escape sequences.
140  let s:t_bold = "\x1b[1m"
141  let s:t_normal = "\x1b[m"
142else
143  let s:t_bold = &t_md
144  let s:t_normal = &t_me
145endif
146
147if has('mac')
148  " In MacOS, when starting a shell in a terminal, a bash deprecation warning
149  " message is displayed. This breaks the terminal test. Disable the warning
150  " message.
151  let $BASH_SILENCE_DEPRECATION_WARNING = 1
152endif
153
154" Prepare for calling test_garbagecollect_now().
155let v:testing = 1
156
157" Support function: get the alloc ID by name.
158function GetAllocId(name)
159  exe 'split ' . s:srcdir . '/alloc.h'
160  let top = search('typedef enum')
161  if top == 0
162    call add(v:errors, 'typedef not found in alloc.h')
163  endif
164  let lnum = search('aid_' . a:name . ',')
165  if lnum == 0
166    call add(v:errors, 'Alloc ID ' . a:name . ' not defined')
167  endif
168  close
169  return lnum - top - 1
170endfunc
171
172func RunTheTest(test)
173  echoconsole 'Executing ' . a:test
174  if has('reltime')
175    let func_start = reltime()
176  endif
177
178  " Avoid stopping at the "hit enter" prompt
179  set nomore
180
181  " Avoid a three second wait when a message is about to be overwritten by the
182  " mode message.
183  set noshowmode
184
185  " Clear any overrides.
186  call test_override('ALL', 0)
187
188  " Some tests wipe out buffers.  To be consistent, always wipe out all
189  " buffers.
190  %bwipe!
191
192  " The test may change the current directory. Save and restore the
193  " directory after executing the test.
194  let save_cwd = getcwd()
195
196  if exists("*SetUp")
197    try
198      call SetUp()
199    catch
200      call add(v:errors, 'Caught exception in SetUp() before ' . a:test . ': ' . v:exception . ' @ ' . v:throwpoint)
201    endtry
202  endif
203
204  if a:test =~ 'Test_nocatch_'
205    " Function handles errors itself.  This avoids skipping commands after the
206    " error.
207    let g:skipped_reason = ''
208    exe 'call ' . a:test
209    if g:skipped_reason != ''
210      call add(s:messages, '    Skipped')
211      call add(s:skipped, 'SKIPPED ' . a:test . ': ' . g:skipped_reason)
212    endif
213  else
214    try
215      au VimLeavePre * call EarlyExit(g:testfunc)
216      exe 'call ' . a:test
217      au! VimLeavePre
218    catch /^\cskipped/
219      call add(s:messages, '    Skipped')
220      call add(s:skipped, 'SKIPPED ' . a:test . ': ' . substitute(v:exception, '^\S*\s\+', '',  ''))
221    catch
222      call add(v:errors, 'Caught exception in ' . a:test . ': ' . v:exception . ' @ ' . v:throwpoint)
223    endtry
224  endif
225
226  " In case 'insertmode' was set and something went wrong, make sure it is
227  " reset to avoid trouble with anything else.
228  set noinsertmode
229
230  if exists("*TearDown")
231    try
232      call TearDown()
233    catch
234      call add(v:errors, 'Caught exception in TearDown() after ' . a:test . ': ' . v:exception . ' @ ' . v:throwpoint)
235    endtry
236  endif
237
238  " Clear any autocommands and put back the catch-all for SwapExists.
239  au!
240  au SwapExists * call HandleSwapExists()
241
242  " Check for and close any stray popup windows.
243  if has('popupwin')
244    call assert_equal([], popup_list())
245    call popup_clear(1)
246  endif
247
248  " Close any extra tab pages and windows and make the current one not modified.
249  while tabpagenr('$') > 1
250    let winid = win_getid()
251    quit!
252    if winid == win_getid()
253      echoerr 'Could not quit window'
254      break
255    endif
256  endwhile
257
258  while 1
259    let wincount = winnr('$')
260    if wincount == 1
261      break
262    endif
263    bwipe!
264    if wincount == winnr('$')
265      " Did not manage to close a window.
266      only!
267      break
268    endif
269  endwhile
270
271  exe 'cd ' . save_cwd
272
273  let message = 'Executed ' . a:test
274  if has('reltime')
275    let message ..= repeat(' ', 50 - len(message))
276    let time = reltime(func_start)
277    if has('float') && reltimefloat(time) > 0.1
278      let message = s:t_bold .. message
279    endif
280    let message ..= ' in ' .. reltimestr(time) .. ' seconds'
281    if has('float') && reltimefloat(time) > 0.1
282      let message ..= s:t_normal
283    endif
284  endif
285  call add(s:messages, message)
286  let s:done += 1
287endfunc
288
289func AfterTheTest(func_name)
290  if len(v:errors) > 0
291    if match(s:may_fail_list, '^' .. a:func_name) >= 0
292      let s:fail_expected += 1
293      call add(s:errors_expected, 'Found errors in ' . g:testfunc . ':')
294      call extend(s:errors_expected, v:errors)
295    else
296      let s:fail += 1
297      call add(s:errors, 'Found errors in ' . g:testfunc . ':')
298      call extend(s:errors, v:errors)
299    endif
300    let v:errors = []
301  endif
302endfunc
303
304func EarlyExit(test)
305  " It's OK for the test we use to test the quit detection.
306  if a:test != 'Test_zz_quit_detected()'
307    call add(v:errors, 'Test caused Vim to exit: ' . a:test)
308  endif
309
310  call FinishTesting()
311endfunc
312
313" This function can be called by a test if it wants to abort testing.
314func FinishTesting()
315  call AfterTheTest('')
316
317  " Don't write viminfo on exit.
318  set viminfo=
319
320  " Clean up files created by setup.vim
321  call delete('XfakeHOME', 'rf')
322
323  if s:fail == 0 && s:fail_expected == 0
324    " Success, create the .res file so that make knows it's done.
325    exe 'split ' . fnamemodify(g:testname, ':r') . '.res'
326    write
327  endif
328
329  if len(s:errors) > 0
330    " Append errors to test.log
331    split test.log
332    call append(line('$'), '')
333    call append(line('$'), 'From ' . g:testname . ':')
334    call append(line('$'), s:errors)
335    write
336  endif
337
338  if s:done == 0
339    if s:filtered > 0
340      if $TEST_FILTER != ''
341        let message = "NO tests match $TEST_FILTER: '" .. $TEST_FILTER .. "'"
342      else
343        let message = "ALL tests match $TEST_SKIP_PAT: '" .. $TEST_SKIP_PAT .. "'"
344      endif
345    else
346      let message = 'NO tests executed'
347    endif
348  else
349    if s:filtered > 0
350      call add(s:messages, "Filtered " .. s:filtered .. " tests with $TEST_FILTER and $TEST_SKIP_PAT")
351    endif
352    let message = 'Executed ' . s:done . (s:done > 1 ? ' tests' : ' test')
353  endif
354  if s:done > 0 && has('reltime')
355    let message = s:t_bold .. message .. repeat(' ', 40 - len(message))
356    let message ..= ' in ' .. reltimestr(reltime(s:start_time)) .. ' seconds'
357    let message ..= s:t_normal
358  endif
359  echo message
360  call add(s:messages, message)
361  if s:fail > 0
362    let message = s:fail . ' FAILED:'
363    echo message
364    call add(s:messages, message)
365    call extend(s:messages, s:errors)
366  endif
367  if s:fail_expected > 0
368    let message = s:fail_expected . ' FAILED (matching $TEST_MAY_FAIL):'
369    echo message
370    call add(s:messages, message)
371    call extend(s:messages, s:errors_expected)
372  endif
373
374  " Add SKIPPED messages
375  call extend(s:messages, s:skipped)
376
377  " Append messages to the file "messages"
378  split messages
379  call append(line('$'), '')
380  call append(line('$'), 'From ' . g:testname . ':')
381  call append(line('$'), s:messages)
382  write
383
384  qall!
385endfunc
386
387" Source the test script.  First grab the file name, in case the script
388" navigates away.  g:testname can be used by the tests.
389let g:testname = expand('%')
390let s:done = 0
391let s:fail = 0
392let s:fail_expected = 0
393let s:errors = []
394let s:errors_expected = []
395let s:messages = []
396let s:skipped = []
397if expand('%') =~ 'test_vimscript.vim'
398  " this test has intentional errors, don't use try/catch.
399  source %
400else
401  try
402    source %
403  catch /^\cskipped/
404    call add(s:messages, '    Skipped')
405    call add(s:skipped, 'SKIPPED ' . expand('%') . ': ' . substitute(v:exception, '^\S*\s\+', '',  ''))
406  catch
407    let s:fail += 1
408    call add(s:errors, 'Caught exception: ' . v:exception . ' @ ' . v:throwpoint)
409  endtry
410endif
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  if $TEST_SKIP_PAT != '' && g:testfunc =~ $TEST_SKIP_PAT
441    call add(s:messages, g:testfunc .. ' matches $TEST_SKIP_PAT')
442    let s:filtered += 1
443    continue
444  endif
445
446  " Silence, please!
447  set belloff=all
448  let prev_error = ''
449  let total_errors = []
450  let g:run_nr = 1
451
452  " A test can set g:test_is_flaky to retry running the test.
453  let g:test_is_flaky = 0
454
455  call RunTheTest(g:testfunc)
456
457  " Repeat a flaky test.  Give up when:
458  " - $TEST_NO_RETRY is not empty
459  " - it fails again with the same message
460  " - it fails five times (with a different message)
461  if len(v:errors) > 0
462        \ && $TEST_NO_RETRY == ''
463        \ && g:test_is_flaky
464    while 1
465      call add(s:messages, 'Found errors in ' . g:testfunc . ':')
466      call extend(s:messages, v:errors)
467
468      call add(total_errors, 'Run ' . g:run_nr . ':')
469      call extend(total_errors, v:errors)
470
471      if g:run_nr == 5 || prev_error == v:errors[0]
472        call add(total_errors, 'Flaky test failed too often, giving up')
473        let v:errors = total_errors
474        break
475      endif
476
477      call add(s:messages, 'Flaky test failed, running it again')
478
479      " Flakiness is often caused by the system being very busy.  Sleep a
480      " couple of seconds to have a higher chance of succeeding the second
481      " time.
482      sleep 2
483
484      let prev_error = v:errors[0]
485      let v:errors = []
486      let g:run_nr += 1
487
488      call RunTheTest(g:testfunc)
489
490      if len(v:errors) == 0
491        " Test passed on rerun.
492        break
493      endif
494    endwhile
495  endif
496
497  call AfterTheTest(g:testfunc)
498endfor
499
500call FinishTesting()
501
502" vim: shiftwidth=2 sts=2 expandtab
503