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