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