diff options
Diffstat (limited to 'test/functional/helpers.lua')
| -rw-r--r-- | test/functional/helpers.lua | 367 |
1 files changed, 239 insertions, 128 deletions
diff --git a/test/functional/helpers.lua b/test/functional/helpers.lua index 42ed1800d3..9895281773 100644 --- a/test/functional/helpers.lua +++ b/test/functional/helpers.lua @@ -8,39 +8,45 @@ local Session = require('nvim.session') local TcpStream = require('nvim.tcp_stream') local SocketStream = require('nvim.socket_stream') local ChildProcessStream = require('nvim.child_process_stream') +local Paths = require('test.config.paths') local check_cores = global_helpers.check_cores local check_logs = global_helpers.check_logs -local neq = global_helpers.neq +local dedent = global_helpers.dedent local eq = global_helpers.eq -local ok = global_helpers.ok -local map = global_helpers.map +local expect_err = global_helpers.expect_err local filter = global_helpers.filter -local dedent = global_helpers.dedent +local map = global_helpers.map +local matches = global_helpers.matches +local neq = global_helpers.neq +local ok = global_helpers.ok +local read_file = global_helpers.read_file +local sleep = global_helpers.sleep +local table_flatten = global_helpers.table_flatten +local write_file = global_helpers.write_file local start_dir = lfs.currentdir() -- XXX: NVIM_PROG takes precedence, QuickBuild sets it. -local nvim_prog = os.getenv('NVIM_PROG') or os.getenv('NVIM_PRG') or 'build/bin/nvim' +local nvim_prog = ( + os.getenv('NVIM_PROG') + or os.getenv('NVIM_PRG') + or Paths.test_build_dir .. '/bin/nvim' +) -- Default settings for the test session. local nvim_set = 'set shortmess+=I background=light noswapfile noautoindent' ..' laststatus=1 undodir=. directory=. viewdir=. backupdir=.' ..' belloff= noshowcmd noruler nomore' local nvim_argv = {nvim_prog, '-u', 'NONE', '-i', 'NONE', '-N', '--cmd', nvim_set, '--embed'} - -local mpack = require('mpack') - -local tmpname = global_helpers.tmpname -local uname = global_helpers.uname - --- Formulate a path to the directory containing nvim. We use this to --- help run test executables. It helps to keep the tests working, even --- when the build is not in the default location. +-- Directory containing nvim. local nvim_dir = nvim_prog:gsub("[/\\][^/\\]+$", "") if nvim_dir == nvim_prog then nvim_dir = "." end +local mpack = require('mpack') +local tmpname = global_helpers.tmpname +local uname = global_helpers.uname local prepend_argv if os.getenv('VALGRIND') then @@ -76,8 +82,8 @@ end local session, loop_running, last_error -local function set_session(s) - if session then +local function set_session(s, keep) + if session and not keep then session:close() end session = s @@ -96,8 +102,64 @@ local function request(method, ...) return rv end -local function next_message() - return session:next_message() +local function next_msg(timeout) + return session:next_message(timeout and timeout or 10000) +end + +local function expect_twostreams(msgs1, msgs2) + local pos1, pos2 = 1, 1 + while pos1 <= #msgs1 or pos2 <= #msgs2 do + local msg = next_msg() + if pos1 <= #msgs1 and pcall(eq, msgs1[pos1], msg) then + pos1 = pos1 + 1 + elseif pos2 <= #msgs2 then + eq(msgs2[pos2], msg) + pos2 = pos2 + 1 + else + -- already failed, but show the right error message + eq(msgs1[pos1], msg) + end + end +end + +-- Expects a sequence of next_msg() results. If multiple sequences are +-- passed they are tried until one succeeds, in order of shortest to longest. +local function expect_msg_seq(...) + if select('#', ...) < 1 then + error('need at least 1 argument') + end + local seqs = {...} + table.sort(seqs, function(a, b) -- Sort ascending, by (shallow) length. + return #a < #b + end) + + local actual_seq = {} + local final_error = '' + local function cat_err(err1, err2) + if err1 == nil then + return err2 + end + return string.format('%s\n%s\n%s', err1, string.rep('=', 78), err2) + end + for anum = 1, #seqs do + local expected_seq = seqs[anum] + -- Collect enough messages to compare the next expected sequence. + while #actual_seq < #expected_seq do + local msg = next_msg(10000) -- Big timeout for ASAN/valgrind. + if msg == nil then + error(cat_err(final_error, + string.format('got %d messages, expected %d', + #actual_seq, #expected_seq))) + end + table.insert(actual_seq, msg) + end + local status, result = pcall(eq, expected_seq, actual_seq) + if status then + return result + end + final_error = cat_err(final_error, result) + end + error(final_error) end local function call_and_stop_on_error(...) @@ -174,7 +236,7 @@ local os_name = (function() end)() local function iswin() - return os_name() == 'windows' + return package.config:sub(1,1) == '\\' end -- Executes a VimL function. @@ -245,13 +307,12 @@ local function retry(max, max_ms, fn) if status then return result end + luv.update_time() -- Update cached value of luv.now() (libuv: uv_now()). if (max and tries >= max) or (luv.now() - start_time > timeout) then - if type(result) == "string" then - result = "\nretry() attempts: "..tostring(tries).."\n"..result - end - error(result) + error("\nretry() attempts: "..tostring(tries).."\n"..tostring(result)) end tries = tries + 1 + luv.sleep(20) -- Avoid hot loop... end end @@ -305,7 +366,7 @@ end -- Executes an ex-command by user input. Because nvim_input() is used, VimL -- errors will not manifest as client (lua) errors. Use command() for that. -local function execute(...) +local function feed_command(...) for _, v in ipairs({...}) do if v:sub(1, 1) ~= '/' then -- not a search command, prefix with colon @@ -316,40 +377,30 @@ local function execute(...) end end --- Dedent the given text and write it to the file name. -local function write_file(name, text, dont_dedent) - local file = io.open(name, 'w') - if not dont_dedent then - text = dedent(text) - end - file:write(text) - file:flush() - file:close() -end - -local function read_file(name) - local file = io.open(name, 'r') - if not file then - return nil - end - local ret = file:read('*a') - file:close() - return ret -end - +local sourced_fnames = {} local function source(code) local fname = tmpname() write_file(fname, code) nvim_command('source '..fname) - os.remove(fname) + -- DO NOT REMOVE FILE HERE. + -- do_source() has a habit of checking whether files are “same” by using inode + -- and device IDs. If you run two source() calls in quick succession there is + -- a good chance that underlying filesystem will reuse the inode, making files + -- appear as “symlinks” to do_source when it checks FileIDs. With current + -- setup linux machines (both QB, travis and mine(ZyX-I) with XFS) do reuse + -- inodes, Mac OS machines (again, both QB and travis) do not. + -- + -- Files appearing as “symlinks” mean that both the first and the second + -- source() calls will use same SID, which may fail some tests which check for + -- exact numbers after `<SNR>` in e.g. function names. + sourced_fnames[#sourced_fnames + 1] = fname return fname end local function set_shell_powershell() source([[ - set shell=powershell shellquote=\" shellpipe=\| shellredir=> - set shellcmdflag=\ -ExecutionPolicy\ RemoteSigned\ -Command - let &shellxquote=' ' + set shell=powershell shellquote=( shellpipe=\| shellredir=> shellxquote= + let &shellcmdflag = '-NoLogo -NoProfile -ExecutionPolicy RemoteSigned -Command Remove-Item -Force alias:sleep;' ]]) end @@ -385,14 +436,9 @@ local function curbuf(method, ...) end local function wait() - -- Execute 'vim_eval' (a deferred function) to block + -- Execute 'nvim_eval' (a deferred function) to block -- until all pending input is processed. - session:request('vim_eval', '1') -end - --- sleeps the test runner (_not_ the nvim instance) -local function sleep(ms) - run(nil, nil, nil, ms) + session:request('nvim_eval', '1') end local function curbuf_contents() @@ -425,21 +471,27 @@ end local function do_rmdir(path) if lfs.attributes(path, 'mode') ~= 'directory' then - return nil + return -- Don't complain. end for file in lfs.dir(path) do if file ~= '.' and file ~= '..' then local abspath = path..'/'..file if lfs.attributes(abspath, 'mode') == 'directory' then - local ret = do_rmdir(abspath) -- recurse - if not ret then - return nil - end + do_rmdir(abspath) -- recurse else local ret, err = os.remove(abspath) if not ret then - error('os.remove: '..err) - return nil + if not session then + error('os.remove: '..err) + else + -- Try Nvim delete(): it handles `readonly` attribute on Windows, + -- and avoids Lua cross-version/platform incompatibilities. + if -1 == nvim_call('delete', abspath) then + local hint = (os_name() == 'windows' + and ' (hint: try :%bwipeout! before rmdir())' or '') + error('delete() failed'..hint..': '..abspath) + end + end end end end @@ -448,7 +500,6 @@ local function do_rmdir(path) if not ret then error('lfs.rmdir('..path..'): '..err) end - return ret end local function rmdir(path) @@ -480,17 +531,6 @@ local exc_exec = function(cmd) return ret end -local function redir_exec(cmd) - nvim_command(([[ - redir => g:__output - silent! execute "%s" - redir END - ]]):format(cmd:gsub('\n', '\\n'):gsub('[\\"]', '\\%0'))) - local ret = nvim_eval('get(g:, "__output", 0)') - nvim_command('unlet! g:__output') - return ret -end - local function create_callindex(func) local table = {} setmetatable(table, { @@ -550,84 +590,155 @@ local curbufmeths = create_callindex(curbuf) local curwinmeths = create_callindex(curwin) local curtabmeths = create_callindex(curtab) +local function redir_exec(cmd) + meths.set_var('__redir_exec_cmd', cmd) + nvim_command([[ + redir => g:__redir_exec_output + silent! execute g:__redir_exec_cmd + redir END + ]]) + local ret = meths.get_var('__redir_exec_output') + meths.del_var('__redir_exec_output') + meths.del_var('__redir_exec_cmd') + return ret +end + local function get_pathsep() - return funcs.fnamemodify('.', ':p'):sub(-1) + return iswin() and '\\' or '/' end -local M = { - prepend_argv = prepend_argv, +local function pathroot() + local pathsep = package.config:sub(1,1) + return iswin() and (nvim_dir:sub(1,2)..pathsep) or '/' +end + +-- Returns a valid, platform-independent $NVIM_LISTEN_ADDRESS. +-- Useful for communicating with child instances. +local function new_pipename() + -- HACK: Start a server temporarily, get the name, then stop it. + local pipename = nvim_eval('serverstart()') + funcs.serverstop(pipename) + return pipename +end + +local function missing_provider(provider) + if provider == 'ruby' or provider == 'node' then + local prog = funcs['provider#' .. provider .. '#Detect']() + return prog == '' and (provider .. ' not detected') or false + elseif provider == 'python' or provider == 'python3' then + local py_major_version = (provider == 'python3' and 3 or 2) + local errors = funcs['provider#pythonx#Detect'](py_major_version)[2] + return errors ~= '' and errors or false + else + assert(false, 'Unknown provider: ' .. provider) + end +end + +local function alter_slashes(obj) + if not iswin() then + return obj + end + if type(obj) == 'string' then + local ret = obj:gsub('/', '\\') + return ret + elseif type(obj) == 'table' then + local ret = {} + for k, v in pairs(obj) do + ret[k] = alter_slashes(v) + end + return ret + else + assert(false, 'Could only alter slashes for tables of strings and strings') + end +end + +local module = { + NIL = mpack.NIL, + alter_slashes = alter_slashes, + buffer = buffer, + bufmeths = bufmeths, + call = nvim_call, clear = clear, + command = nvim_command, connect = connect, - retry = retry, - spawn = spawn, + curbuf = curbuf, + curbuf_contents = curbuf_contents, + curbufmeths = curbufmeths, + curtab = curtab, + curtabmeths = curtabmeths, + curwin = curwin, + curwinmeths = curwinmeths, dedent = dedent, - source = source, - rawfeed = rawfeed, - insert = insert, - iswin = iswin, - feed = feed, - execute = execute, - eval = nvim_eval, - call = nvim_call, - command = nvim_command, - request = request, - next_message = next_message, - run = run, - stop = stop, eq = eq, - neq = neq, + eval = nvim_eval, + exc_exec = exc_exec, expect = expect, expect_any = expect_any, - ok = ok, - map = map, + expect_err = expect_err, + expect_msg_seq = expect_msg_seq, + expect_twostreams = expect_twostreams, + feed = feed, + feed_command = feed_command, filter = filter, + funcs = funcs, + get_pathsep = get_pathsep, + insert = insert, + iswin = iswin, + map = map, + matches = matches, + merge_args = merge_args, + meth_pcall = meth_pcall, + meths = meths, + missing_provider = missing_provider, + mkdir = lfs.mkdir, + neq = neq, + new_pipename = new_pipename, + next_msg = next_msg, nvim = nvim, + nvim_argv = nvim_argv, nvim_async = nvim_async, + nvim_dir = nvim_dir, nvim_prog = nvim_prog, nvim_set = nvim_set, - nvim_dir = nvim_dir, - buffer = buffer, - window = window, - tabpage = tabpage, - curbuf = curbuf, - curwin = curwin, - curtab = curtab, - curbuf_contents = curbuf_contents, - wait = wait, - sleep = sleep, - set_session = set_session, - write_file = write_file, - read_file = read_file, + ok = ok, os_name = os_name, - rmdir = rmdir, - mkdir = lfs.mkdir, - exc_exec = exc_exec, - redir_exec = redir_exec, - merge_args = merge_args, - funcs = funcs, - meths = meths, - bufmeths = bufmeths, - winmeths = winmeths, - tabmeths = tabmeths, - uimeths = uimeths, - curbufmeths = curbufmeths, - curwinmeths = curwinmeths, - curtabmeths = curtabmeths, + pathroot = pathroot, pending_win32 = pending_win32, - skip_fragile = skip_fragile, + prepend_argv = prepend_argv, + rawfeed = rawfeed, + read_file = read_file, + redir_exec = redir_exec, + request = request, + retry = retry, + rmdir = rmdir, + run = run, + set_session = set_session, set_shell_powershell = set_shell_powershell, + skip_fragile = skip_fragile, + sleep = sleep, + source = source, + spawn = spawn, + stop = stop, + table_flatten = table_flatten, + tabmeths = tabmeths, + tabpage = tabpage, tmpname = tmpname, - meth_pcall = meth_pcall, - NIL = mpack.NIL, - get_pathsep = get_pathsep, + uimeths = uimeths, + wait = wait, + window = window, + winmeths = winmeths, + write_file = write_file, } return function(after_each) if after_each then after_each(function() + for _, fname in ipairs(sourced_fnames) do + os.remove(fname) + end check_logs() check_cores('build/bin/nvim') end) end - return M + return module end |
