diff options
| author | Mehmet Samet Duman <yongdohyun@projecttick.org> | 2026-04-04 12:41:27 +0300 |
|---|---|---|
| committer | Mehmet Samet Duman <yongdohyun@projecttick.org> | 2026-04-04 12:41:27 +0300 |
| commit | 4f2d36194b4f299aa7509d815c07121039ea833b (patch) | |
| tree | f3ded014bad3a4c76ff6a22b8726ebaab68c3d13 /mnv/src/testdir/util/shared.mnv | |
| parent | 5b578e70c314723a3cde5c9bfc2be0bf1dadc93b (diff) | |
| download | Project-Tick-4f2d36194b4f299aa7509d815c07121039ea833b.tar.gz Project-Tick-4f2d36194b4f299aa7509d815c07121039ea833b.zip | |
NOISSUE change uvim folder name to mnv
Signed-off-by: Mehmet Samet Duman <yongdohyun@projecttick.org>
Diffstat (limited to 'mnv/src/testdir/util/shared.mnv')
| -rw-r--r-- | mnv/src/testdir/util/shared.mnv | 476 |
1 files changed, 476 insertions, 0 deletions
diff --git a/mnv/src/testdir/util/shared.mnv b/mnv/src/testdir/util/shared.mnv new file mode 100644 index 0000000000..1ea367392b --- /dev/null +++ b/mnv/src/testdir/util/shared.mnv @@ -0,0 +1,476 @@ +" Functions shared by several tests. + +" Only load this script once. +if exists('*PythonProg') + finish +endif + +source util/view_util.mnv + +" When 'term' is changed some status requests may be sent. The responses may +" interfere with what is being tested. A short sleep is used to process any of +" those responses first. +func WaitForResponses() + sleep 50m +endfunc + +" 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 !(has('job') || executable('pkill')) + return '' + endif + if executable('python3') + let s:python = 'python3' + elseif executable('python') + let s:python = 'python' + else + return '' + end + elseif has('win32') + " Use Python Launcher for Windows (py.exe) if available. + " NOTE: if you get a "Python was not found" error, disable the Python + " shortcuts in "Windows menu / Settings / Manage App Execution Aliases". + 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) + " Running an external command can occasionally be slow or fail. + let g:test_is_flaky = 1 + + 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 /D /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, with 400 is rarily failed + for i in range(600) + 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 some time for the port number to be there. + let port = GetPort() + if port == 0 + call assert_report(strftime("%H:%M:%S") .. " Can't start " .. a:cmd) + return + endif + + call call(function(a:testfunc), [port]) + catch /E901.*Address family for hostname not supported/ + throw 'Skipped: Invalid network setup ("' .. v:exception .. '" in ' .. v:throwpoint .. ')' + catch + call assert_report('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 + +" Callback function to be invoked by a child terminal job. The parent could +" then wait for the notification using WaitForChildNotification() +let g:child_notification = 0 +func Tapi_notify_parent(bufnum, arglist) + let g:child_notification = 1 +endfunc + +" Generates a command that we can pass to a terminal job that it uses to +" notify us. Argument 'escape' will specify whether to escape the double +" quote. +func TermNotifyParentCmd(escape) + call assert_false(has("win32"), 'Windows does not support terminal API right now. Use another method to synchronize timing.') + let cmd = '\033]51;["call", "Tapi_notify_parent", []]\007' + if a:escape + return escape(cmd, '"') + endif + return cmd +endfunc + +" Wait for a child process to notify us. This allows us to sequence events in +" conjunction with the child. Currently the only supported notification method +" is for a terminal job to call Tapi_notify_parent() using terminal API. +func WaitForChildNotification(...) + let timeout = get(a:000, 0, 5000) + call WaitFor({-> g:child_notification == 1}, timeout) + let g:child_notification = 0 +endfunc + +" Wait for the cursor position in a terminal buffer to fall in range and for +" specific line contents to be matchable against expected patterns. +" +" The waited-for cursor position [lnum, cnum] can be discovered by searching +" for the only instance of non-"|"-delimited ">" in an already verified +" screendump file, with every "|.\+|" cell entry counted as a single column. +" Use 2-tuples (aka pairs) with (lnum, pattern) as optional arguments for +" additional points of synchronisation. Assuming that buffer lines are drawn +" from top to bottom, consider pairing up the bottom line number with its +" corresponding whole line pattern; if nothing will be written on the bottom +" line, or if other lines will be further updated before verification, then +" prefer a more recently-updated line (or lines) for matching. +func WaitForTermCurPosAndLinesToMatch(bnum, cpos, timeout = g:test_timeout, ...) + if empty(term_getstatus(a:bnum)) + throw 'Skipped: Not a terminal buffer' + endif + + if a:0 > 0 && indexof(a:000, {_, ps -> len(ps) != 2}) < 0 + return WaitFor({b, c, ps -> {-> slice(term_getcursor(b), 0, 2) == c && indexof(ps, {b_ -> {_, p -> term_getline(b_, p[0]) !~# p[1]}}(b)) < 0}}(a:bnum, a:cpos, a:000), a:timeout) + else + return WaitFor({b, c -> {-> slice(term_getcursor(b), 0, 2) == c}}(a:bnum, a:cpos), a:timeout) + 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 g: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 exists('*reltimefloat') + 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 1m + if exists('*reltimefloat') + let slept = float2nr(reltimefloat(reltime(start)) * 1000) + else + let slept += 1 + 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') && exists('*reltimefloat') + 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 the name of the MNV executable that we expect has been build in the src +" directory. +func s:GetJustBuildMNVExe() + if has("win32") + if !filereadable('..\mnv.exe') && filereadable('..\mnvd.exe') + " looks like the debug executable was intentionally build, so use it + return '..\mnvd.exe' + endif + return '..\mnv.exe' + endif + return '../mnv' +endfunc + +" Get $MNVPROG to run the MNV executable. +" The Makefile writes it as the first line in the "mnvcmd" file. +" Falls back to the MNV executable in the src directory. +func GetMNVProg() + if filereadable('mnvcmd') + return readfile('mnvcmd')[0] + endif + echo 'Cannot read the "mnvcmd" file, falling back to ../mnv.' + + " Probably the script was sourced instead of running "make". + " We assume MNV was just build in the src directory then. + return s:GetJustBuildMNVExe() +endfunc + +let g:valgrind_cnt = 1 + +" Get the command to run MNV, with -u NONE and --not-a-term arguments. +" If there is an argument use it instead of "NONE". +func GetMNVCommand(...) + if filereadable('mnvcmd') + let lines = readfile('mnvcmd') + else + echo 'Cannot read the "mnvcmd" file, falling back to ../mnv.' + let lines = [s:GetJustBuildMNVExe()] + 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 + " "mnvcmd" 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 .= ' --gui-dialog-file guidialogfile' + " remove any environment variables + let cmd = substitute(cmd, '[A-Z_]\+=\S\+ *', '', 'g') + + " 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 + +" Return one when it looks like the tests are run with valgrind, which means +" that everything is much slower. +func RunningWithValgrind() + return GetMNVCommand() =~ '\<valgrind\>' +endfunc + +func RunningAsan() + return exists("$ASAN_OPTIONS") +endfunc + +func ValgrindOrAsan() + return RunningWithValgrind() || RunningAsan() +endfun + +const g:test_timeout = (ValgrindOrAsan()) ? 5000 * 4 : 5000 + +" Get the command to run MNV, with --clean instead of "-u NONE". +func GetMNVCommandClean() + let cmd = GetMNVCommand() + let cmd = substitute(cmd, '-u NONE', '--clean', '') + let cmd = substitute(cmd, '--not-a-term', '', '') + + " Force using utf-8, MNV may pick up something else from the environment. + let cmd ..= ' --cmd "set enc=utf8" ' + + " Optionally run MNV under valgrind + " let cmd = 'valgrind --tool=memcheck --leak-check=yes --num-callers=25 --log-file=valgrind ' . cmd + + return cmd +endfunc + +" Get the command to run MNV, with --clean, and force to run in terminal so it +" won't start a new GUI. +func GetMNVCommandCleanTerm() + " Add -v to have gmnv run in the terminal (if possible) + return GetMNVCommandClean() .. ' -v ' +endfunc + +" Run MNV, using the "mnvcmd" file and "-u NORC". +" "before" is a list of MNV commands to be executed before loading plugins. +" "after" is a list of MNV commands to be executed after loading plugins. +" Plugins are not loaded, unless 'loadplugins' is set in "before". +" Return 1 if MNV could be executed. +func RunMNV(before, after, arguments) + return RunMNVPiped(a:before, a:after, a:arguments, '') +endfunc + +func RunMNVPiped(before, after, arguments, pipecmd) + let cmd = GetMNVCommand() + let args = '' + if len(a:before) > 0 + call writefile(a:before, 'Xbefore.mnv') + let args .= ' --cmd "so Xbefore.mnv"' + endif + if len(a:after) > 0 + call writefile(a:after, 'Xafter.mnv') + let args .= ' -S Xafter.mnv' + endif + + " Optionally run MNV under valgrind + " let cmd = 'valgrind --tool=memcheck --leak-check=yes --num-callers=25 --log-file=valgrind ' . cmd + + exe "silent !" .. a:pipecmd .. ' ' .. cmd .. args .. ' ' .. a:arguments + + if len(a:before) > 0 + call delete('Xbefore.mnv') + endif + if len(a:after) > 0 + call delete('Xafter.mnv') + endif + return 1 +endfunc + +func IsRoot() + if !has('unix') + return v:false + elseif $USER == 'root' || system('id -un') =~ '\<root\>' + return v:true + endif + return v:false +endfunc + +" Get all messages but drop the maintainer entry. +func GetMessages() + redir => result + redraw | messages + redir END + let msg_list = split(result, "\n") + if msg_list->len() > 0 && msg_list[0] =~ 'Messages maintainer:' + return msg_list[1:] + endif + return msg_list +endfunc + +" Run the list of commands in 'cmds' and look for 'errstr' in exception. +" Note that assert_fails() cannot be used in some places and this function +" can be used. +func AssertException(cmds, errstr) + let save_exception = '' + try + for cmd in a:cmds + exe cmd + endfor + catch + let save_exception = v:exception + endtry + call assert_match(a:errstr, save_exception) +endfunc + +" mnv: shiftwidth=2 sts=2 expandtab |
