Use the same version as the current macvim
This commit is contained in:
parent
de3c513b24
commit
d458d06bc5
12 changed files with 733 additions and 4 deletions
359
tests/lib/run_test.vim
Normal file
359
tests/lib/run_test.vim
Normal file
|
|
@ -0,0 +1,359 @@
|
|||
" This script is sourced while editing the .vim file with the tests.
|
||||
" When the script is successful the .res file will be created.
|
||||
" Errors are appended to the test.log file.
|
||||
"
|
||||
" To execute only specific test functions, add a second argument. It will be
|
||||
" matched against the names of the Test_ funtion. E.g.:
|
||||
" ../vim -u NONE -S runtest.vim test_channel.vim open_delay
|
||||
" The output can be found in the "messages" file.
|
||||
"
|
||||
" The test script may contain anything, only functions that start with
|
||||
" "Test_" are special. These will be invoked and should contain assert
|
||||
" functions. See test_assert.vim for an example.
|
||||
"
|
||||
" It is possible to source other files that contain "Test_" functions. This
|
||||
" can speed up testing, since Vim does not need to restart. But be careful
|
||||
" that the tests do not interfere with each other.
|
||||
"
|
||||
" If an error cannot be detected properly with an assert function add the
|
||||
" error to the v:errors list:
|
||||
" call add(v:errors, 'test foo failed: Cannot find xyz')
|
||||
"
|
||||
" If preparation for each Test_ function is needed, define a SetUp function.
|
||||
" It will be called before each Test_ function.
|
||||
"
|
||||
" If cleanup after each Test_ function is needed, define a TearDown function.
|
||||
" It will be called after each Test_ function.
|
||||
"
|
||||
" When debugging a test it can be useful to add messages to v:errors:
|
||||
" call add(v:errors, "this happened")
|
||||
|
||||
set rtp=$VIM/vimfiles,$VIMRUNTIME,$VIM/vimfiles/after
|
||||
if has('packages')
|
||||
let &packpath = &rtp
|
||||
endif
|
||||
|
||||
call ch_logfile( 'debuglog', 'w' )
|
||||
|
||||
" For consistency run all tests with 'nocompatible' set.
|
||||
" This also enables use of line continuation.
|
||||
set nocp viminfo+=nviminfo
|
||||
|
||||
" Use utf-8 by default, instead of whatever the system default happens to be.
|
||||
" Individual tests can overrule this at the top of the file.
|
||||
set encoding=utf-8
|
||||
|
||||
" Avoid stopping at the "hit enter" prompt
|
||||
set nomore
|
||||
|
||||
" Output all messages in English.
|
||||
lang mess C
|
||||
|
||||
" Always use forward slashes.
|
||||
set shellslash
|
||||
|
||||
func RunTheTest(test)
|
||||
echo 'Executing ' . a:test
|
||||
|
||||
" Avoid stopping at the "hit enter" prompt
|
||||
set nomore
|
||||
|
||||
" Avoid a three second wait when a message is about to be overwritten by the
|
||||
" mode message.
|
||||
set noshowmode
|
||||
|
||||
" Clear any overrides.
|
||||
call test_override('ALL', 0)
|
||||
|
||||
" Some tests wipe out buffers. To be consistent, always wipe out all
|
||||
" buffers.
|
||||
%bwipe!
|
||||
|
||||
" The test may change the current directory. Save and restore the
|
||||
" directory after executing the test.
|
||||
let save_cwd = getcwd()
|
||||
|
||||
if exists("*SetUp_" . a:test)
|
||||
try
|
||||
exe 'call SetUp_' . a:test
|
||||
catch
|
||||
call add(v:errors,
|
||||
\ 'Caught exception in SetUp_' . a:test . ' before '
|
||||
\ . a:test
|
||||
\ . ': '
|
||||
\ . v:exception
|
||||
\ . ' @ '
|
||||
\ . g:testpath
|
||||
\ . ':'
|
||||
\ . v:throwpoint)
|
||||
endtry
|
||||
endif
|
||||
|
||||
if exists("*SetUp")
|
||||
try
|
||||
call SetUp()
|
||||
catch
|
||||
call add(v:errors,
|
||||
\ 'Caught exception in SetUp() before '
|
||||
\ . a:test
|
||||
\ . ': '
|
||||
\ . v:exception
|
||||
\ . ' @ '
|
||||
\ . g:testpath
|
||||
\ . ':'
|
||||
\ . v:throwpoint)
|
||||
endtry
|
||||
endif
|
||||
|
||||
call add(s:messages, 'Executing ' . a:test)
|
||||
let s:done += 1
|
||||
|
||||
if a:test =~ 'Test_nocatch_'
|
||||
" Function handles errors itself. This avoids skipping commands after the
|
||||
" error.
|
||||
exe 'call ' . a:test
|
||||
else
|
||||
try
|
||||
let s:test = a:test
|
||||
let s:testid = g:testpath . ':' . a:test
|
||||
au VimLeavePre * call EarlyExit(s:test)
|
||||
exe 'call ' . a:test
|
||||
au! VimLeavePre
|
||||
catch /^\cskipped/
|
||||
call add(s:messages, ' Skipped')
|
||||
call add(s:skipped,
|
||||
\ 'SKIPPED ' . a:test
|
||||
\ . ': '
|
||||
\ . substitute(v:exception, '^\S*\s\+', '', ''))
|
||||
catch
|
||||
call add(v:errors,
|
||||
\ 'Caught exception in ' . a:test
|
||||
\ . ': '
|
||||
\ . v:exception
|
||||
\ . ' @ '
|
||||
\ . g:testpath
|
||||
\ . ':'
|
||||
\ . v:throwpoint)
|
||||
endtry
|
||||
endif
|
||||
|
||||
" In case 'insertmode' was set and something went wrong, make sure it is
|
||||
" reset to avoid trouble with anything else.
|
||||
set noinsertmode
|
||||
|
||||
if exists("*TearDown")
|
||||
try
|
||||
call TearDown()
|
||||
catch
|
||||
call add(v:errors,
|
||||
\ 'Caught exception in TearDown() after ' . a:test
|
||||
\ . ': '
|
||||
\ . v:exception
|
||||
\ . ' @ '
|
||||
\ . g:testpath
|
||||
\ . ':'
|
||||
\ . v:throwpoint)
|
||||
endtry
|
||||
endif
|
||||
|
||||
if exists("*TearDown_" . a:test)
|
||||
try
|
||||
exe 'call TearDown_' . a:test
|
||||
catch
|
||||
call add(v:errors,
|
||||
\ 'Caught exception in TearDown_' . a:test . ' after ' . a:test
|
||||
\ . ': '
|
||||
\ . v:exception
|
||||
\ . ' @ '
|
||||
\ . g:testpath
|
||||
\ . ':'
|
||||
\ . v:throwpoint)
|
||||
endtry
|
||||
endif
|
||||
|
||||
" Clear any autocommands
|
||||
au!
|
||||
|
||||
" Close any extra tab pages and windows and make the current one not modified.
|
||||
while tabpagenr('$') > 1
|
||||
quit!
|
||||
endwhile
|
||||
|
||||
while 1
|
||||
let wincount = winnr('$')
|
||||
if wincount == 1
|
||||
break
|
||||
endif
|
||||
bwipe!
|
||||
if wincount == winnr('$')
|
||||
" Did not manage to close a window.
|
||||
only!
|
||||
break
|
||||
endif
|
||||
endwhile
|
||||
|
||||
exe 'cd ' . save_cwd
|
||||
endfunc
|
||||
|
||||
func AfterTheTest()
|
||||
if len(v:errors) > 0
|
||||
let s:fail += 1
|
||||
call add(s:errors, 'Found errors in ' . s:testid . ':')
|
||||
call extend(s:errors, v:errors)
|
||||
let v:errors = []
|
||||
endif
|
||||
endfunc
|
||||
|
||||
func EarlyExit(test)
|
||||
" It's OK for the test we use to test the quit detection.
|
||||
if a:test != 'Test_zz_quit_detected()'
|
||||
call add(v:errors, 'Test caused Vim to exit: ' . a:test)
|
||||
endif
|
||||
|
||||
call FinishTesting()
|
||||
endfunc
|
||||
|
||||
" This function can be called by a test if it wants to abort testing.
|
||||
func FinishTesting()
|
||||
call AfterTheTest()
|
||||
|
||||
" Don't write viminfo on exit.
|
||||
set viminfo=
|
||||
|
||||
if s:fail == 0
|
||||
" Success, create the .res file so that make knows it's done.
|
||||
exe 'split ' . fnamemodify(g:testname, ':r') . '.res'
|
||||
write
|
||||
endif
|
||||
|
||||
if len(s:errors) > 0
|
||||
" Append errors to test.log
|
||||
split test.log
|
||||
call append(line('$'), '')
|
||||
call append(line('$'), 'From ' . g:testpath . ':')
|
||||
call append(line('$'), s:errors)
|
||||
write
|
||||
endif
|
||||
|
||||
if s:done == 0
|
||||
let message = 'NO tests executed'
|
||||
else
|
||||
let message = 'Executed ' . s:done . (s:done > 1 ? ' tests' : ' test')
|
||||
endif
|
||||
echo message
|
||||
call add(s:messages, message)
|
||||
if s:fail > 0
|
||||
let message = s:fail . ' FAILED:'
|
||||
echo message
|
||||
call add(s:messages, message)
|
||||
call extend(s:messages, s:errors)
|
||||
endif
|
||||
|
||||
" Add SKIPPED messages
|
||||
call extend(s:messages, s:skipped)
|
||||
|
||||
" Append messages to the file "messages"
|
||||
split messages
|
||||
call append(line('$'), '')
|
||||
call append(line('$'), 'From ' . g:testpath . ':')
|
||||
call append(line('$'), s:messages)
|
||||
write
|
||||
|
||||
if s:fail > 0
|
||||
cquit!
|
||||
else
|
||||
qall!
|
||||
endif
|
||||
endfunc
|
||||
|
||||
" Source the test script. First grab the file name, in case the script
|
||||
" navigates away. g:testname can be used by the tests.
|
||||
let g:testname = expand('%')
|
||||
let g:testpath = expand('%:p')
|
||||
let s:done = 0
|
||||
let s:fail = 0
|
||||
let s:errors = []
|
||||
let s:messages = []
|
||||
let s:skipped = []
|
||||
try
|
||||
source %
|
||||
catch
|
||||
let s:fail += 1
|
||||
call add(s:errors,
|
||||
\ 'Caught exception: ' .
|
||||
\ v:exception .
|
||||
\ ' @ ' . v:throwpoint)
|
||||
endtry
|
||||
|
||||
" Names of flaky tests.
|
||||
let s:flaky_tests = []
|
||||
|
||||
" Pattern indicating a common flaky test failure.
|
||||
let s:flaky_errors_re = '__does_not_match__'
|
||||
|
||||
" Locate Test_ functions and execute them.
|
||||
redir @q
|
||||
silent function /^Test_
|
||||
redir END
|
||||
let s:tests = split(substitute(@q, 'function \(\k*()\)', '\1', 'g'))
|
||||
|
||||
" If there is an extra argument filter the function names against it.
|
||||
if argc() > 1
|
||||
let s:tests = filter(s:tests, 'v:val =~ argv(1)')
|
||||
endif
|
||||
|
||||
" Execute the tests in alphabetical order.
|
||||
for s:test in sort(s:tests)
|
||||
" Silence, please!
|
||||
set belloff=all
|
||||
let prev_error = ''
|
||||
let total_errors = []
|
||||
let run_nr = 1
|
||||
|
||||
call RunTheTest(s:test)
|
||||
|
||||
" Repeat a flaky test. Give up when:
|
||||
" - it fails again with the same message
|
||||
" - it fails five times (with a different mesage)
|
||||
if len(v:errors) > 0
|
||||
\ && (index(s:flaky_tests, s:test) >= 0
|
||||
\ || v:errors[0] =~ s:flaky_errors_re)
|
||||
while 1
|
||||
call add(s:messages, 'Found errors in ' . s:testid . ':')
|
||||
call extend(s:messages, v:errors)
|
||||
|
||||
call add(total_errors, 'Run ' . run_nr . ':')
|
||||
call extend(total_errors, v:errors)
|
||||
|
||||
if run_nr == 5 || prev_error == v:errors[0]
|
||||
call add(total_errors, 'Flaky test failed too often, giving up')
|
||||
let v:errors = total_errors
|
||||
break
|
||||
endif
|
||||
|
||||
call add(s:messages, 'Flaky test failed, running it again')
|
||||
|
||||
" Flakiness is often caused by the system being very busy. Sleep a
|
||||
" couple of seconds to have a higher chance of succeeding the second
|
||||
" time.
|
||||
sleep 2
|
||||
|
||||
let prev_error = v:errors[0]
|
||||
let v:errors = []
|
||||
let run_nr += 1
|
||||
|
||||
call RunTheTest(s:test)
|
||||
|
||||
if len(v:errors) == 0
|
||||
" Test passed on rerun.
|
||||
break
|
||||
endif
|
||||
endwhile
|
||||
endif
|
||||
|
||||
call AfterTheTest()
|
||||
endfor
|
||||
|
||||
call FinishTesting()
|
||||
|
||||
" vim: shiftwidth=2 sts=2 expandtab
|
||||
171
tests/lib/screendump.vim
Normal file
171
tests/lib/screendump.vim
Normal file
|
|
@ -0,0 +1,171 @@
|
|||
" Functions shared by tests making screen dumps.
|
||||
|
||||
" Only load this script once.
|
||||
if exists('*CanRunVimInTerminal')
|
||||
finish
|
||||
endif
|
||||
|
||||
" For most tests we need to be able to run terminal Vim with 256 colors. On
|
||||
" MS-Windows the console only has 16 colors and the GUI can't run in a
|
||||
" terminal.
|
||||
func CanRunVimInTerminal()
|
||||
return has('terminal') && !has('win32')
|
||||
endfunc
|
||||
|
||||
" Skip the rest if there is no terminal feature at all.
|
||||
if !has('terminal')
|
||||
finish
|
||||
endif
|
||||
|
||||
source lib/shared.vim
|
||||
|
||||
" Run Vim with "arguments" in a new terminal window.
|
||||
" By default uses a size of 20 lines and 75 columns.
|
||||
" Returns the buffer number of the terminal.
|
||||
"
|
||||
" Options is a dictionary, these items are recognized:
|
||||
" "rows" - height of the terminal window (max. 20)
|
||||
" "cols" - width of the terminal window (max. 78)
|
||||
" "statusoff" - number of lines the status is offset from default
|
||||
func RunVimInTerminal(arguments, options)
|
||||
" If Vim doesn't exit a swap file remains, causing other tests to fail.
|
||||
" Remove it here.
|
||||
call delete(".swp")
|
||||
|
||||
if exists('$COLORFGBG')
|
||||
" Clear $COLORFGBG to avoid 'background' being set to "dark", which will
|
||||
" only be corrected if the response to t_RB is received, which may be too
|
||||
" late.
|
||||
let $COLORFGBG = ''
|
||||
endif
|
||||
|
||||
" Make a horizontal and vertical split, so that we can get exactly the right
|
||||
" size terminal window. Works only when the current window is full width.
|
||||
call assert_equal(&columns, winwidth(0))
|
||||
split
|
||||
vsplit
|
||||
|
||||
" Always do this with 256 colors and a light background.
|
||||
set t_Co=256 background=light
|
||||
hi Normal ctermfg=NONE ctermbg=NONE
|
||||
|
||||
" Make the window 20 lines high and 75 columns, unless told otherwise.
|
||||
let rows = get(a:options, 'rows', 20)
|
||||
let cols = get(a:options, 'cols', 75)
|
||||
let statusoff = get(a:options, 'statusoff', 1)
|
||||
|
||||
let cmd = GetVimCommandClean()
|
||||
|
||||
" Add -v to have gvim run in the terminal (if possible)
|
||||
let cmd .= ' -v ' . a:arguments
|
||||
let buf = term_start(cmd, {
|
||||
\ 'curwin': 1,
|
||||
\ 'term_rows': rows,
|
||||
\ 'term_cols': cols,
|
||||
\ })
|
||||
if &termwinsize == ''
|
||||
" in the GUI we may end up with a different size, try to set it.
|
||||
if term_getsize(buf) != [rows, cols]
|
||||
call term_setsize(buf, rows, cols)
|
||||
endif
|
||||
call assert_equal([rows, cols], term_getsize(buf))
|
||||
else
|
||||
let rows = term_getsize(buf)[0]
|
||||
let cols = term_getsize(buf)[1]
|
||||
endif
|
||||
|
||||
" Wait for "All" or "Top" of the ruler to be shown in the last line or in
|
||||
" the status line of the last window. This can be quite slow (e.g. when
|
||||
" using valgrind).
|
||||
" If it fails then show the terminal contents for debugging.
|
||||
try
|
||||
call WaitFor({-> len(term_getline(buf, rows)) >= cols - 1 || len(term_getline(buf, rows - statusoff)) >= cols - 1})
|
||||
catch /timed out after/
|
||||
let lines = map(range(1, rows), {key, val -> term_getline(buf, val)})
|
||||
call assert_report('RunVimInTerminal() failed, screen contents: ' . join(lines, "<NL>"))
|
||||
endtry
|
||||
|
||||
return buf
|
||||
endfunc
|
||||
|
||||
" Stop a Vim running in terminal buffer "buf".
|
||||
func StopVimInTerminal(buf)
|
||||
call assert_equal("running", term_getstatus(a:buf))
|
||||
|
||||
" CTRL-O : works both in Normal mode and Insert mode to start a command line.
|
||||
" In Command-line it's inserted, the CTRL-U removes it again.
|
||||
call term_sendkeys(a:buf, "\<C-O>:\<C-U>qa!\<cr>")
|
||||
|
||||
call WaitForAssert({-> assert_equal("finished", term_getstatus(a:buf))})
|
||||
only!
|
||||
endfunc
|
||||
|
||||
" Verify that Vim running in terminal buffer "buf" matches the screen dump.
|
||||
" "options" is passed to term_dumpwrite().
|
||||
" The file name used is "dumps/{filename}.dump".
|
||||
" Optionally an extra argument can be passed which is prepended to the error
|
||||
" message. Use this when using the same dump file with different options.
|
||||
" Will wait for up to a second for the screen dump to match.
|
||||
" Returns non-zero when verification fails.
|
||||
func VerifyScreenDump(buf, filename, options, ...)
|
||||
let reference = 'dumps/' . a:filename . '.dump'
|
||||
let testfile = 'failed/' . a:filename . '.dump'
|
||||
|
||||
" Redraw to execute the code that updates the screen. Otherwise we get the
|
||||
" text and attributes only from the internal buffer.
|
||||
redraw
|
||||
|
||||
let did_mkdir = 0
|
||||
if !isdirectory('failed')
|
||||
let did_mkdir = 1
|
||||
call mkdir('failed')
|
||||
endif
|
||||
|
||||
let i = 0
|
||||
while 1
|
||||
" leave some time for updating the original window
|
||||
sleep 10m
|
||||
call delete(testfile)
|
||||
call term_dumpwrite(a:buf, testfile, a:options)
|
||||
let testdump = readfile(testfile)
|
||||
if filereadable(reference)
|
||||
let refdump = readfile(reference)
|
||||
else
|
||||
" Must be a new screendump, always fail
|
||||
let refdump = []
|
||||
endif
|
||||
if refdump == testdump
|
||||
call delete(testfile)
|
||||
if did_mkdir
|
||||
call delete('failed', 'd')
|
||||
endif
|
||||
break
|
||||
endif
|
||||
if i == 100
|
||||
" Leave the failed dump around for inspection.
|
||||
if filereadable(reference)
|
||||
let msg = 'See dump file difference: call term_dumpdiff("' . testfile . '", "' . reference . '")'
|
||||
if a:0 == 1
|
||||
let msg = a:1 . ': ' . msg
|
||||
endif
|
||||
if len(testdump) != len(refdump)
|
||||
let msg = msg . '; line count is ' . len(testdump) . ' instead of ' . len(refdump)
|
||||
endif
|
||||
else
|
||||
let msg = 'See new dump file: call term_dumpload("' . testfile . '")'
|
||||
endif
|
||||
for i in range(len(refdump))
|
||||
if i >= len(testdump)
|
||||
break
|
||||
endif
|
||||
if testdump[i] != refdump[i]
|
||||
let msg = msg . '; difference in line ' . (i + 1) . ': "' . testdump[i] . '"'
|
||||
endif
|
||||
endfor
|
||||
call assert_report(msg)
|
||||
return 1
|
||||
endif
|
||||
let i += 1
|
||||
endwhile
|
||||
return 0
|
||||
endfunc
|
||||
358
tests/lib/shared.vim
Normal file
358
tests/lib/shared.vim
Normal file
|
|
@ -0,0 +1,358 @@
|
|||
|
||||
" Functions shared by several tests.
|
||||
|
||||
" Only load this script once.
|
||||
if exists('*WaitFor')
|
||||
finish
|
||||
endif
|
||||
|
||||
" Get the name of the Python executable.
|
||||
" Also keeps it in s:python.
|
||||
func PythonProg()
|
||||
" This test requires the Python command to run the test server.
|
||||
" This most likely only works on Unix and Windows.
|
||||
if has('unix')
|
||||
" We also need the job feature or the pkill command to make sure the server
|
||||
" can be stopped.
|
||||
if !(executable('python') && (has('job') || executable('pkill')))
|
||||
return ''
|
||||
endif
|
||||
let s:python = 'python'
|
||||
elseif has('win32')
|
||||
" Use Python Launcher for Windows (py.exe) if available.
|
||||
if executable('py.exe')
|
||||
let s:python = 'py.exe'
|
||||
elseif executable('python.exe')
|
||||
let s:python = 'python.exe'
|
||||
else
|
||||
return ''
|
||||
endif
|
||||
else
|
||||
return ''
|
||||
endif
|
||||
return s:python
|
||||
endfunc
|
||||
|
||||
" Run "cmd". Returns the job if using a job.
|
||||
func RunCommand(cmd)
|
||||
let job = 0
|
||||
if has('job')
|
||||
let job = job_start(a:cmd, {"stoponexit": "hup"})
|
||||
call job_setoptions(job, {"stoponexit": "kill"})
|
||||
elseif has('win32')
|
||||
exe 'silent !start cmd /c start "test_channel" ' . a:cmd
|
||||
else
|
||||
exe 'silent !' . a:cmd . '&'
|
||||
endif
|
||||
return job
|
||||
endfunc
|
||||
|
||||
" Read the port number from the Xportnr file.
|
||||
func GetPort()
|
||||
let l = []
|
||||
" with 200 it sometimes failed
|
||||
for i in range(400)
|
||||
try
|
||||
let l = readfile("Xportnr")
|
||||
catch
|
||||
endtry
|
||||
if len(l) >= 1
|
||||
break
|
||||
endif
|
||||
sleep 10m
|
||||
endfor
|
||||
call delete("Xportnr")
|
||||
|
||||
if len(l) == 0
|
||||
" Can't make the connection, give up.
|
||||
return 0
|
||||
endif
|
||||
return l[0]
|
||||
endfunc
|
||||
|
||||
" Run a Python server for "cmd" and call "testfunc".
|
||||
" Always kills the server before returning.
|
||||
func RunServer(cmd, testfunc, args)
|
||||
" The Python program writes the port number in Xportnr.
|
||||
call delete("Xportnr")
|
||||
|
||||
if len(a:args) == 1
|
||||
let arg = ' ' . a:args[0]
|
||||
else
|
||||
let arg = ''
|
||||
endif
|
||||
let pycmd = s:python . " " . a:cmd . arg
|
||||
|
||||
try
|
||||
let g:currentJob = RunCommand(pycmd)
|
||||
|
||||
" Wait for up to 2 seconds for the port number to be there.
|
||||
let port = GetPort()
|
||||
if port == 0
|
||||
call assert_false(1, "Can't start " . a:cmd)
|
||||
return
|
||||
endif
|
||||
|
||||
call call(function(a:testfunc), [port])
|
||||
catch
|
||||
call assert_false(1, 'Caught exception: "' . v:exception . '" in ' . v:throwpoint)
|
||||
finally
|
||||
call s:kill_server(a:cmd)
|
||||
endtry
|
||||
endfunc
|
||||
|
||||
func s:kill_server(cmd)
|
||||
if has('job')
|
||||
if exists('g:currentJob')
|
||||
call job_stop(g:currentJob)
|
||||
unlet g:currentJob
|
||||
endif
|
||||
elseif has('win32')
|
||||
let cmd = substitute(a:cmd, ".py", '', '')
|
||||
call system('taskkill /IM ' . s:python . ' /T /F /FI "WINDOWTITLE eq ' . cmd . '"')
|
||||
else
|
||||
call system("pkill -f " . a:cmd)
|
||||
endif
|
||||
endfunc
|
||||
|
||||
" Wait for up to five seconds for "expr" to become true. "expr" can be a
|
||||
" stringified expression to evaluate, or a funcref without arguments.
|
||||
" Using a lambda works best. Example:
|
||||
" call WaitFor({-> status == "ok"})
|
||||
"
|
||||
" A second argument can be used to specify a different timeout in msec.
|
||||
"
|
||||
" When successful the time slept is returned.
|
||||
" When running into the timeout an exception is thrown, thus the function does
|
||||
" not return.
|
||||
func WaitFor(expr, ...)
|
||||
let timeout = get(a:000, 0, 5000)
|
||||
let slept = s:WaitForCommon(a:expr, v:null, timeout)
|
||||
if slept < 0
|
||||
throw 'WaitFor() timed out after ' . timeout . ' msec'
|
||||
endif
|
||||
return slept
|
||||
endfunc
|
||||
|
||||
" Wait for up to five seconds for "assert" to return zero. "assert" must be a
|
||||
" (lambda) function containing one assert function. Example:
|
||||
" call WaitForAssert({-> assert_equal("dead", job_status(job)})
|
||||
"
|
||||
" A second argument can be used to specify a different timeout in msec.
|
||||
"
|
||||
" Return zero for success, one for failure (like the assert function).
|
||||
func WaitForAssert(assert, ...)
|
||||
let timeout = get(a:000, 0, 5000)
|
||||
if s:WaitForCommon(v:null, a:assert, timeout) < 0
|
||||
return 1
|
||||
endif
|
||||
return 0
|
||||
endfunc
|
||||
|
||||
" Common implementation of WaitFor() and WaitForAssert().
|
||||
" Either "expr" or "assert" is not v:null
|
||||
" Return the waiting time for success, -1 for failure.
|
||||
func s:WaitForCommon(expr, assert, timeout)
|
||||
" using reltime() is more accurate, but not always available
|
||||
let slept = 0
|
||||
if has('reltime')
|
||||
let start = reltime()
|
||||
endif
|
||||
|
||||
while 1
|
||||
if type(a:expr) == v:t_func
|
||||
let success = a:expr()
|
||||
elseif type(a:assert) == v:t_func
|
||||
let success = a:assert() == 0
|
||||
else
|
||||
let success = eval(a:expr)
|
||||
endif
|
||||
if success
|
||||
return slept
|
||||
endif
|
||||
|
||||
if slept >= a:timeout
|
||||
break
|
||||
endif
|
||||
if type(a:assert) == v:t_func
|
||||
" Remove the error added by the assert function.
|
||||
call remove(v:errors, -1)
|
||||
endif
|
||||
|
||||
sleep 10m
|
||||
if has('reltime')
|
||||
let slept = float2nr(reltimefloat(reltime(start)) * 1000)
|
||||
else
|
||||
let slept += 10
|
||||
endif
|
||||
endwhile
|
||||
|
||||
return -1 " timed out
|
||||
endfunc
|
||||
|
||||
|
||||
" Wait for up to a given milliseconds.
|
||||
" With the +timers feature this waits for key-input by getchar(), Resume()
|
||||
" feeds key-input and resumes process. Return time waited in milliseconds.
|
||||
" Without +timers it uses simply :sleep.
|
||||
func Standby(msec)
|
||||
if has('timers')
|
||||
let start = reltime()
|
||||
let g:_standby_timer = timer_start(a:msec, function('s:feedkeys'))
|
||||
call getchar()
|
||||
return float2nr(reltimefloat(reltime(start)) * 1000)
|
||||
else
|
||||
execute 'sleep ' a:msec . 'm'
|
||||
return a:msec
|
||||
endif
|
||||
endfunc
|
||||
|
||||
func Resume()
|
||||
if exists('g:_standby_timer')
|
||||
call timer_stop(g:_standby_timer)
|
||||
call s:feedkeys(0)
|
||||
unlet g:_standby_timer
|
||||
endif
|
||||
endfunc
|
||||
|
||||
func s:feedkeys(timer)
|
||||
call feedkeys('x', 'nt')
|
||||
endfunc
|
||||
|
||||
" Get $VIMPROG to run Vim executable.
|
||||
" The Makefile writes it as the first line in the "vimcmd" file.
|
||||
func GetVimProg()
|
||||
if !filereadable('vimcmd')
|
||||
" Assume the script was sourced instead of running "make".
|
||||
return '../vim'
|
||||
endif
|
||||
return readfile('vimcmd')[0]
|
||||
endfunc
|
||||
|
||||
let g:valgrind_cnt = 1
|
||||
|
||||
" Get the command to run Vim, with -u NONE and --not-a-term arguments.
|
||||
" If there is an argument use it instead of "NONE".
|
||||
func GetVimCommand(...)
|
||||
if !filereadable('vimcmd')
|
||||
echo 'Cannot read the "vimcmd" file, falling back to ../vim.'
|
||||
let lines = ['../vim']
|
||||
else
|
||||
let lines = readfile('vimcmd')
|
||||
endif
|
||||
if a:0 == 0
|
||||
let name = 'NONE'
|
||||
else
|
||||
let name = a:1
|
||||
endif
|
||||
" For Unix Makefile writes the command to use in the second line of the
|
||||
" "vimcmd" file, including environment options.
|
||||
" Other Makefiles just write the executable in the first line, so fall back
|
||||
" to that if there is no second line or it is empty.
|
||||
if len(lines) > 1 && lines[1] != ''
|
||||
let cmd = lines[1]
|
||||
else
|
||||
let cmd = lines[0]
|
||||
endif
|
||||
|
||||
let cmd = substitute(cmd, '-u \f\+', '-u ' . name, '')
|
||||
if cmd !~ '-u '. name
|
||||
let cmd = cmd . ' -u ' . name
|
||||
endif
|
||||
let cmd .= ' --not-a-term'
|
||||
let cmd = substitute(cmd, 'VIMRUNTIME=.*VIMRUNTIME;', '', '')
|
||||
|
||||
" If using valgrind, make sure every run uses a different log file.
|
||||
if cmd =~ 'valgrind.*--log-file='
|
||||
let cmd = substitute(cmd, '--log-file=\(^\s*\)', '--log-file=\1.' . g:valgrind_cnt, '')
|
||||
let g:valgrind_cnt += 1
|
||||
endif
|
||||
|
||||
return cmd
|
||||
endfunc
|
||||
|
||||
" Get the command to run Vim, with --clean.
|
||||
func GetVimCommandClean()
|
||||
let cmd = GetVimCommand()
|
||||
let cmd = substitute(cmd, '-u NONE', '--clean', '')
|
||||
let cmd = substitute(cmd, '--not-a-term', '', '')
|
||||
|
||||
" Optionally run Vim under valgrind
|
||||
" let cmd = 'valgrind --tool=memcheck --leak-check=yes --num-callers=25 --log-file=valgrind ' . cmd
|
||||
|
||||
return cmd
|
||||
endfunc
|
||||
|
||||
" Run Vim, using the "vimcmd" file and "-u NORC".
|
||||
" "before" is a list of Vim commands to be executed before loading plugins.
|
||||
" "after" is a list of Vim commands to be executed after loading plugins.
|
||||
" Plugins are not loaded, unless 'loadplugins' is set in "before".
|
||||
" Return 1 if Vim could be executed.
|
||||
func RunVim(before, after, arguments)
|
||||
return RunVimPiped(a:before, a:after, a:arguments, '')
|
||||
endfunc
|
||||
|
||||
func RunVimPiped(before, after, arguments, pipecmd)
|
||||
let cmd = GetVimCommand()
|
||||
let args = ''
|
||||
if len(a:before) > 0
|
||||
call writefile(a:before, 'Xbefore.vim')
|
||||
let args .= ' --cmd "so Xbefore.vim"'
|
||||
endif
|
||||
if len(a:after) > 0
|
||||
call writefile(a:after, 'Xafter.vim')
|
||||
let args .= ' -S Xafter.vim'
|
||||
endif
|
||||
|
||||
exe "silent !" . a:pipecmd . cmd . args . ' ' . a:arguments
|
||||
|
||||
if len(a:before) > 0
|
||||
call delete('Xbefore.vim')
|
||||
endif
|
||||
if len(a:after) > 0
|
||||
call delete('Xafter.vim')
|
||||
endif
|
||||
return 1
|
||||
endfunc
|
||||
|
||||
func CanRunGui()
|
||||
return has('gui') && ($DISPLAY != "" || has('gui_running'))
|
||||
endfunc
|
||||
|
||||
func WorkingClipboard()
|
||||
if !has('clipboard')
|
||||
return 0
|
||||
endif
|
||||
if has('x11')
|
||||
return $DISPLAY != ""
|
||||
endif
|
||||
return 1
|
||||
endfunc
|
||||
|
||||
" Get line "lnum" as displayed on the screen.
|
||||
" Trailing white space is trimmed.
|
||||
func! Screenline(lnum)
|
||||
let chars = []
|
||||
for c in range(1, winwidth(0))
|
||||
call add(chars, nr2char(screenchar(a:lnum, c)))
|
||||
endfor
|
||||
let line = join(chars, '')
|
||||
return matchstr(line, '^.\{-}\ze\s*$')
|
||||
endfunc
|
||||
|
||||
" Stops the shell running in terminal "buf".
|
||||
func Stop_shell_in_terminal(buf)
|
||||
call term_sendkeys(a:buf, "exit\r")
|
||||
let job = term_getjob(a:buf)
|
||||
call WaitFor({-> job_status(job) == "dead"})
|
||||
endfunc
|
||||
|
||||
" Gets the text of a terminal line, using term_scrape()
|
||||
func Get_terminal_text(bufnr, row)
|
||||
let list = term_scrape(a:bufnr, a:row)
|
||||
let text = ''
|
||||
for item in list
|
||||
let text .= item.chars
|
||||
endfor
|
||||
return text
|
||||
endfunc
|
||||
Loading…
Add table
Add a link
Reference in a new issue