neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

runtest.vim (20635B)


      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.
     52 silent! while 0
     53  qa!
     54 silent! endwhile
     55 
     56 " In the GUI we can always change the screen size.
     57 if has('gui_running')
     58  if has('gui_gtk')
     59    " to keep screendump size unchanged
     60    set guifont=Monospace\ 10
     61  endif
     62  set columns=80 lines=25
     63 endif
     64 
     65 " Check that the screen size is at least 24 x 80 characters.
     66 if &lines < 24 || &columns < 80
     67  let error = 'Screen size too small! Tests require at least 24 lines with 80 characters, got ' .. &lines .. ' lines with ' .. &columns .. ' characters'
     68  echoerr error
     69  split test.log
     70  $put =error
     71  write
     72  split messages
     73  call append(line('$'), '')
     74  call append(line('$'), 'From ' . expand('%') . ':')
     75  call append(line('$'), error)
     76  write
     77  qa!
     78 endif
     79 
     80 if has('reltime')
     81  let s:run_start_time = reltime()
     82 
     83  if !filereadable('starttime')
     84    " first test, store the overall test starting time
     85    let s:test_start_time = localtime()
     86    call writefile([string(s:test_start_time)], 'starttime')
     87  else
     88    " second or later test, read the overall test starting time
     89    let s:test_start_time = readfile('starttime')[0]->str2nr()
     90  endif
     91 endif
     92 
     93 " Always use forward slashes.
     94 set shellslash
     95 
     96 " Common with all tests on all systems.
     97 source setup.vim
     98 
     99 " Needed for RunningWithValgrind().
    100 source shared.vim
    101 
    102 " Needed for the various Check commands
    103 source check.vim
    104 
    105 " For consistency run all tests with 'nocompatible' set.
    106 " This also enables use of line continuation.
    107 set nocp viminfo+=nviminfo
    108 
    109 " Use utf-8 by default, instead of whatever the system default happens to be.
    110 " Individual tests can overrule this at the top of the file and use
    111 " g:orig_encoding if needed.
    112 let g:orig_encoding = &encoding
    113 set encoding=utf-8
    114 
    115 " REDIR_TEST_TO_NULL has a very permissive SwapExists autocommand which is for
    116 " the test_name.vim file itself. Replace it here with a more restrictive one,
    117 " so we still catch mistakes.
    118 if has("win32")
    119  " replace any '/' directory separators by '\\'
    120  let s:test_script_fname = substitute(expand('%'), '/', '\\', 'g')
    121 else
    122  let s:test_script_fname = expand('%')
    123 endif
    124 au! SwapExists * call HandleSwapExists()
    125 func HandleSwapExists()
    126  if exists('g:ignoreSwapExists')
    127    if type(g:ignoreSwapExists) == v:t_string
    128      let v:swapchoice = g:ignoreSwapExists
    129    endif
    130    return
    131  endif
    132  " Ignore finding a swap file for the test script (the user might be
    133  " editing it and do ":make test_name") and the output file.
    134  " Report finding another swap file and chose 'q' to avoid getting stuck.
    135  if expand('<afile>') == 'messages' || expand('<afile>') =~ s:test_script_fname
    136    let v:swapchoice = 'e'
    137  else
    138    call assert_report('Unexpected swap file: ' .. v:swapname)
    139    let v:swapchoice = 'q'
    140  endif
    141 endfunc
    142 
    143 " Avoid stopping at the "hit enter" prompt
    144 set nomore
    145 
    146 " Output all messages in English.
    147 lang mess C
    148 
    149 " Nvim: append runtime from build dir, which contains the generated doc/tags.
    150 let &runtimepath ..= ',' .. expand($BUILD_DIR) .. '/runtime/'
    151 " Nvim: append libdir from build dir, which contains the bundled TS parsers.
    152 let &runtimepath ..= ',' .. expand($BUILD_DIR) .. '/lib/nvim/'
    153 
    154 let s:t_bold = &t_md
    155 let s:t_normal = &t_me
    156 if has('win32')
    157  " avoid prompt that is long or contains a line break
    158  let $PROMPT = '$P$G'
    159 endif
    160 
    161 if has('mac')
    162  " In macOS, when starting a shell in a terminal, a bash deprecation warning
    163  " message is displayed. This breaks the terminal test. Disable the warning
    164  " message.
    165  let $BASH_SILENCE_DEPRECATION_WARNING = 1
    166 endif
    167 
    168 
    169 " Prepare for calling test_garbagecollect_now().
    170 " Also avoids some delays in Insert mode completion.
    171 let v:testing = 1
    172 
    173 let s:has_ffi = luaeval('pcall(require, "ffi")')
    174 if s:has_ffi
    175  lua << trim EOF
    176    require('ffi').cdef([[
    177      int starting;
    178      bool test_disable_char_avail;
    179    ]])
    180  EOF
    181 endif
    182 
    183 " This can emulate test_override('starting') and test_override('char_avail')
    184 " if LuaJIT FFI is enabled.
    185 " Other flags are not supported.
    186 func Ntest_override(name, val)
    187  if a:name !=# 'starting' && a:name != 'char_avail' && a:name !=# 'ALL'
    188    throw 'Unexpected use of Ntest_override()'
    189  endif
    190  if !s:has_ffi
    191    throw 'Skipped: missing LuaJIT FFI'
    192  endif
    193 
    194  if a:name ==# 'starting' || a:name ==# 'ALL'
    195    if a:val
    196      if !exists('s:save_starting')
    197        let s:save_starting = luaeval('require("ffi").C.starting')
    198      endif
    199      lua require("ffi").C.starting = 0
    200    elseif exists('s:save_starting')
    201      exe 'lua require("ffi").C.starting =' s:save_starting
    202      unlet s:save_starting
    203    endif
    204  endif
    205 
    206  if a:name ==# 'char_avail' || a:name ==# 'ALL'
    207    exe 'lua require("ffi").C.test_disable_char_avail =' a:val
    208  endif
    209 endfunc
    210 
    211 " roughly equivalent to test_setmouse() in Vim
    212 func Ntest_setmouse(row, col)
    213  call nvim_input_mouse('move', '', '', 0, a:row - 1, a:col - 1)
    214  if state('m') == ''
    215    call getchar(0)
    216  endif
    217 endfunc
    218 
    219 " roughly equivalent to term_wait() in Vim
    220 func Nterm_wait(buf, time = 10)
    221  execute $'sleep {a:time}m'
    222 endfunc
    223 
    224 " Support function: get the alloc ID by name.
    225 func GetAllocId(name)
    226  exe 'split ' . s:srcdir . '/alloc.h'
    227  let top = search('typedef enum')
    228  if top == 0
    229    call add(v:errors, 'typedef not found in alloc.h')
    230  endif
    231  let lnum = search('aid_' . a:name . ',')
    232  if lnum == 0
    233    call add(v:errors, 'Alloc ID ' . a:name . ' not defined')
    234  endif
    235  close
    236  return lnum - top - 1
    237 endfunc
    238 
    239 " Get the list of swap files in the current directory.
    240 func s:GetSwapFileList()
    241  let save_dir = &directory
    242  let &directory = '.'
    243  let files = swapfilelist()
    244  let &directory = save_dir
    245 
    246  " remove a match with runtest.vim
    247  let idx = indexof(files, 'v:val =~ "runtest.vim."')
    248  if idx >= 0
    249    call remove(files, idx)
    250  endif
    251 
    252  return files
    253 endfunc
    254 
    255 " A previous (failed) test run may have left swap files behind.  Delete them
    256 " before running tests again, they might interfere.
    257 for name in s:GetSwapFileList()
    258  call delete(name)
    259 endfor
    260 unlet! name
    261 
    262 
    263 " Invoked when a test takes too much time.
    264 func TestTimeout(id)
    265  split test.log
    266  call append(line('$'), '')
    267 
    268  let text = 'Test timed out: ' .. g:testfunc
    269  if g:timeout_start > 0
    270    let text ..= strftime(' after %s seconds', localtime() - g:timeout_start)
    271  endif
    272  call append(line('$'), text)
    273  write
    274  call add(v:errors, text)
    275 
    276  cquit! 42
    277 endfunc
    278 let g:timeout_start = 0
    279 
    280 func RunTheTest(test)
    281  let prefix = ''
    282  if has('reltime')
    283    let prefix = strftime('%M:%S', localtime() - s:test_start_time) .. ' '
    284    let g:func_start = reltime()
    285  endif
    286  echo prefix .. 'Executing ' .. a:test
    287 
    288  if has('timers')
    289    " No test should take longer than 45 seconds.  If it takes longer we
    290    " assume we are stuck and need to break out.
    291    let test_timeout_timer =
    292          \ timer_start(RunningWithValgrind() ? 90000 : 45000, 'TestTimeout')
    293    let g:timeout_start = localtime()
    294  endif
    295 
    296  " Avoid stopping at the "hit enter" prompt
    297  set nomore
    298 
    299  " Avoid a three second wait when a message is about to be overwritten by the
    300  " mode message.
    301  set noshowmode
    302 
    303  " Some tests wipe out buffers.  To be consistent, always wipe out all
    304  " buffers.
    305  %bwipe!
    306 
    307  " The test may change the current directory. Save and restore the
    308  " directory after executing the test.
    309  let save_cwd = getcwd()
    310 
    311  " Align Nvim defaults to Vim.
    312  source setup.vim
    313 
    314  if exists("*SetUp")
    315    try
    316      call SetUp()
    317    catch
    318      call add(v:errors, 'Caught exception in SetUp() before ' . a:test . ': ' . v:exception . ' @ ' . v:throwpoint)
    319    endtry
    320  endif
    321 
    322  let skipped = v:false
    323 
    324  au VimLeavePre * call EarlyExit(g:testfunc)
    325  if a:test =~ 'Test_nocatch_'
    326    " Function handles errors itself.  This avoids skipping commands after the
    327    " error.
    328    let g:skipped_reason = ''
    329    exe 'call ' . a:test
    330    if g:skipped_reason != ''
    331      call add(s:messages, '    Skipped')
    332      call add(s:skipped, 'SKIPPED ' . a:test . ': ' . g:skipped_reason)
    333      let skipped = v:true
    334    endif
    335  elseif !s:has_ffi && execute('func ' .. a:test[:-3])->match("\n[ 0-9]*call Ntest_override(") >= 0
    336    call add(s:messages, '    Skipped')
    337    call add(s:skipped, 'SKIPPED ' . a:test . ': missing LuaJIT FFI' . )
    338    let skipped = v:true
    339  else
    340    try
    341      exe 'call ' . a:test
    342    catch /^\cskipped/
    343      call add(s:messages, '    Skipped')
    344      call add(s:skipped, 'SKIPPED ' . a:test . ': ' . substitute(v:exception, '^\S*\s\+', '',  ''))
    345      let skipped = v:true
    346    catch
    347      call add(v:errors, 'Caught exception in ' . a:test . ': ' . v:exception . ' @ ' . v:throwpoint)
    348    endtry
    349  endif
    350  au! VimLeavePre
    351 
    352  if a:test =~ '_terminal_'
    353    " Terminal tests sometimes hang, give extra information
    354    echoconsole 'After executing ' .. a:test
    355  endif
    356 
    357  " In case 'insertmode' was set and something went wrong, make sure it is
    358  " reset to avoid trouble with anything else.
    359  set noinsertmode
    360 
    361  if exists("*TearDown")
    362    try
    363      call TearDown()
    364    catch
    365      call add(v:errors, 'Caught exception in TearDown() after ' . a:test . ': ' . v:exception . ' @ ' . v:throwpoint)
    366    endtry
    367  endif
    368 
    369  if has('timers')
    370    call timer_stop(test_timeout_timer)
    371    let g:timeout_start = 0
    372  endif
    373 
    374  " Clear any autocommands and put back the catch-all for SwapExists.
    375  au!
    376  au SwapExists * call HandleSwapExists()
    377 
    378  " Close any stray popup windows
    379  if has('popupwin')
    380    call popup_clear()
    381  endif
    382 
    383  " Close any extra tab pages and windows and make the current one not modified.
    384  while tabpagenr('$') > 1
    385    let winid = win_getid()
    386    quit!
    387    if winid == win_getid()
    388      echoerr 'Could not quit window'
    389      break
    390    endif
    391  endwhile
    392 
    393  while 1
    394    let wincount = winnr('$')
    395    if wincount == 1
    396      break
    397    endif
    398    bwipe!
    399    if wincount == winnr('$')
    400      " Did not manage to close a window.
    401      only!
    402      break
    403    endif
    404  endwhile
    405 
    406  exe 'cd ' . save_cwd
    407 
    408  if a:test =~ '_terminal_'
    409    " Terminal tests sometimes hang, give extra information
    410    echoconsole 'Finished ' . a:test
    411  endif
    412 
    413  let message = 'Executed ' . a:test
    414  if has('reltime')
    415    let message ..= repeat(' ', 50 - len(message))
    416    let time = reltime(g:func_start)
    417    if reltimefloat(time) > 0.1
    418      let message = s:t_bold .. message
    419    endif
    420    let message ..= ' in ' .. reltimestr(time) .. ' seconds'
    421    if reltimefloat(time) > 0.1
    422      let message ..= s:t_normal
    423    endif
    424  endif
    425  call add(s:messages, message)
    426  let s:done += 1
    427 
    428  " close any split windows
    429  while winnr('$') > 1
    430    noswapfile bwipe!
    431  endwhile
    432 
    433  " May be editing some buffer, wipe it out.  Then we may end up in another
    434  " buffer, continue until we end up in an empty no-name buffer without a swap
    435  " file.
    436  while bufname() != '' || execute('swapname') !~ 'No swap file'
    437    let bn = bufnr()
    438 
    439    noswapfile bwipe!
    440 
    441    if bn == bufnr()
    442      " avoid getting stuck in the same buffer
    443      break
    444    endif
    445  endwhile
    446 
    447  if !skipped
    448    " Check if the test has left any swap files behind.  Delete them before
    449    " running tests again, they might interfere.
    450    let swapfiles = s:GetSwapFileList()
    451    if len(swapfiles) > 0
    452      call add(s:messages, "Found swap files: " .. string(swapfiles))
    453      for name in swapfiles
    454        call delete(name)
    455      endfor
    456    endif
    457  endif
    458 endfunc
    459 
    460 function Delete_Xtest_Files()
    461  for file in glob('X*', v:false, v:true)
    462    if file ==? 'XfakeHOME'
    463      " Clean up files created by setup.vim
    464      call delete('XfakeHOME', 'rf')
    465      continue
    466    endif
    467    " call add(v:errors, file .. " exists when it shouldn't, trying to delete it!")
    468    call delete(file)
    469    if !empty(glob(file, v:false, v:true))
    470      " call add(v:errors, file .. " still exists after trying to delete it!")
    471      if has('unix')
    472        call system('rm -rf  ' .. file)
    473      endif
    474    endif
    475  endfor
    476 endfunc
    477 
    478 func AfterTheTest(func_name)
    479  if len(v:errors) > 0
    480    if match(s:may_fail_list, '^' .. a:func_name) >= 0
    481      let s:fail_expected += 1
    482      call add(s:errors_expected, 'Found errors in ' . g:testfunc . ':')
    483      call extend(s:errors_expected, v:errors)
    484    else
    485      let s:fail += 1
    486      call add(s:errors, 'Found errors in ' . g:testfunc . ':')
    487      call extend(s:errors, v:errors)
    488    endif
    489    let v:errors = []
    490  endif
    491 endfunc
    492 
    493 func EarlyExit(test)
    494  " It's OK for the test we use to test the quit detection.
    495  if a:test != 'Test_zz_quit_detected()'
    496    call add(v:errors, v:errmsg)
    497    call add(v:errors, 'Test caused Vim to exit: ' . a:test)
    498  endif
    499 
    500  call FinishTesting()
    501 endfunc
    502 
    503 " This function can be called by a test if it wants to abort testing.
    504 func FinishTesting()
    505  call AfterTheTest('')
    506  call Delete_Xtest_Files()
    507 
    508  " Don't write viminfo on exit.
    509  set viminfo=
    510 
    511  if s:fail == 0 && s:fail_expected == 0
    512    " Success, create the .res file so that make knows it's done.
    513    exe 'split ' . fnamemodify(g:testname, ':r') . '.res'
    514    write
    515  endif
    516 
    517  if len(s:errors) > 0
    518    " Append errors to test.log
    519    split test.log
    520    call append(line('$'), '')
    521    call append(line('$'), 'From ' . g:testname . ':')
    522    call append(line('$'), s:errors)
    523    write
    524  endif
    525 
    526  if s:done == 0
    527    if s:filtered > 0
    528      if $TEST_FILTER != ''
    529        let message = "NO tests match $TEST_FILTER: '" .. $TEST_FILTER .. "'"
    530      else
    531        let message = "ALL tests match $TEST_SKIP_PAT: '" .. $TEST_SKIP_PAT .. "'"
    532      endif
    533    else
    534      let message = 'NO tests executed'
    535    endif
    536  else
    537    if s:filtered > 0
    538      call add(s:messages, "Filtered " .. s:filtered .. " tests with $TEST_FILTER and $TEST_SKIP_PAT")
    539    endif
    540    let message = 'Executed ' . s:done . (s:done > 1 ? ' tests' : ' test')
    541  endif
    542  if s:done > 0 && has('reltime')
    543    let message = s:t_bold .. message .. repeat(' ', 40 - len(message))
    544    let message ..= ' in ' .. reltimestr(reltime(s:run_start_time)) .. ' seconds'
    545    let message ..= s:t_normal
    546  endif
    547  echo message
    548  call add(s:messages, message)
    549  if s:fail > 0
    550    let message = s:fail . ' FAILED:'
    551    echo message
    552    call add(s:messages, message)
    553    call extend(s:messages, s:errors)
    554  endif
    555  if s:fail_expected > 0
    556    let message = s:fail_expected . ' FAILED (matching $TEST_MAY_FAIL):'
    557    echo message
    558    call add(s:messages, message)
    559    call extend(s:messages, s:errors_expected)
    560  endif
    561 
    562  " Add SKIPPED messages
    563  call extend(s:messages, s:skipped)
    564 
    565  " Append messages to the file "messages"
    566  split messages
    567  call append(line('$'), '')
    568  call append(line('$'), 'From ' . g:testname . ':')
    569  call append(line('$'), s:messages)
    570  write
    571 
    572  qall!
    573 endfunc
    574 
    575 " Source the test script.  First grab the file name, in case the script
    576 " navigates away.  g:testname can be used by the tests.
    577 let g:testname = expand('%')
    578 let s:done = 0
    579 let s:fail = 0
    580 let s:fail_expected = 0
    581 let s:errors = []
    582 let s:errors_expected = []
    583 let s:messages = []
    584 let s:skipped = []
    585 if expand('%') =~ 'test_vimscript.vim'
    586  " this test has intentional errors, don't use try/catch.
    587  source %
    588 else
    589  try
    590    source %
    591  catch /^\cskipped/
    592    call add(s:messages, '    Skipped')
    593    call add(s:skipped, 'SKIPPED ' . expand('%') . ': ' . substitute(v:exception, '^\S*\s\+', '',  ''))
    594  catch
    595    let s:fail += 1
    596    call add(s:errors, 'Caught exception: ' . v:exception . ' @ ' . v:throwpoint)
    597  endtry
    598 endif
    599 
    600 " Delete the .res file, it may change behavior for completion
    601 call delete(fnamemodify(g:testname, ':r') .. '.res')
    602 
    603 " Locate Test_ functions and execute them.
    604 redir @q
    605 silent function /^Test_
    606 redir END
    607 let s:tests = split(substitute(@q, 'function \(\k*()\)', '\1', 'g'))
    608 
    609 " If there is an extra argument filter the function names against it.
    610 if argc() > 1
    611  let s:tests = filter(s:tests, 'v:val =~ argv(1)')
    612 endif
    613 
    614 " If the environment variable $TEST_FILTER is set then filter the function
    615 " names against it.
    616 let s:filtered = 0
    617 if $TEST_FILTER != ''
    618  let s:filtered = len(s:tests)
    619  let s:tests = filter(s:tests, 'v:val =~ $TEST_FILTER')
    620  let s:filtered -= len(s:tests)
    621 endif
    622 
    623 let s:may_fail_list = []
    624 if $TEST_MAY_FAIL != ''
    625  " Split the list at commas and add () to make it match g:testfunc.
    626  let s:may_fail_list = split($TEST_MAY_FAIL, ',')->map({i, v -> v .. '()'})
    627 endif
    628 
    629 " Execute the tests in alphabetical order.
    630 for g:testfunc in sort(s:tests)
    631  if $TEST_SKIP_PAT != '' && g:testfunc =~ $TEST_SKIP_PAT
    632    call add(s:messages, g:testfunc .. ' matches $TEST_SKIP_PAT')
    633    let s:filtered += 1
    634    continue
    635  endif
    636 
    637  " Silence, please!
    638  set belloff=all
    639  let prev_error = ''
    640  let total_errors = []
    641  let g:run_nr = 1
    642 
    643  " A test can set g:test_is_flaky to retry running the test.
    644  let g:test_is_flaky = 0
    645 
    646  let g:check_screendump_called = v:false
    647 
    648  " A test can set g:max_run_nr to change the max retry count.
    649  let g:max_run_nr = 5
    650  if has('mac')
    651    let g:max_run_nr = 10
    652  endif
    653 
    654  " By default, give up if the same error occurs.  A test can set
    655  " g:giveup_same_error to 0 to not give up on the same error and keep trying.
    656  let g:giveup_same_error = 1
    657 
    658  let starttime = strftime("%H:%M:%S")
    659  call RunTheTest(g:testfunc)
    660 
    661  " Repeat a flaky test.  Give up when:
    662  " - $TEST_NO_RETRY is not empty
    663  " - it fails again with the same message
    664  " - it fails five times (with a different message)
    665  if len(v:errors) > 0
    666        \ && $TEST_NO_RETRY == ''
    667        \ && g:test_is_flaky
    668    while 1
    669      call add(s:messages, 'Found errors in ' .. g:testfunc .. ':')
    670      call extend(s:messages, v:errors)
    671 
    672      let endtime = strftime("%H:%M:%S")
    673      if has('reltime')
    674        let suffix = $' in{reltimestr(reltime(g:func_start))} seconds'
    675      else
    676        let suffix = ''
    677      endif
    678      call add(total_errors, $'Run {g:run_nr}, {starttime} - {endtime}{suffix}:')
    679      call extend(total_errors, v:errors)
    680 
    681      if g:run_nr >= g:max_run_nr || g:giveup_same_error && prev_error == v:errors[0]
    682        call add(total_errors, 'Flaky test failed too often, giving up')
    683        let v:errors = total_errors
    684        break
    685      endif
    686 
    687      call add(s:messages, 'Flaky test failed, running it again')
    688 
    689      " Flakiness is often caused by the system being very busy.  Sleep a
    690      " couple of seconds to have a higher chance of succeeding the second
    691      " time.
    692      let delay = g:run_nr * 2
    693      exe 'sleep' delay
    694 
    695      let prev_error = v:errors[0]
    696      let v:errors = []
    697      let g:run_nr += 1
    698 
    699      let starttime = strftime("%H:%M:%S")
    700      call RunTheTest(g:testfunc)
    701 
    702      if len(v:errors) == 0
    703        " Test passed on rerun.
    704        break
    705      endif
    706    endwhile
    707  endif
    708 
    709  call AfterTheTest(g:testfunc)
    710 endfor
    711 
    712 call FinishTesting()
    713 
    714 " vim: shiftwidth=2 sts=2 expandtab