diff options
Diffstat (limited to 'test/functional/helpers.lua')
-rw-r--r-- | test/functional/helpers.lua | 369 |
1 files changed, 238 insertions, 131 deletions
diff --git a/test/functional/helpers.lua b/test/functional/helpers.lua index 5eec3afe65..848f1ef477 100644 --- a/test/functional/helpers.lua +++ b/test/functional/helpers.lua @@ -8,35 +8,39 @@ 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 eq = global_helpers.eq local ok = global_helpers.ok +local map = global_helpers.map +local filter = global_helpers.filter +local dedent = global_helpers.dedent local start_dir = lfs.currentdir() -local nvim_prog = os.getenv('NVIM_PROG') or 'build/bin/nvim' +-- XXX: NVIM_PROG takes precedence, QuickBuild sets it. +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', 'set shortmess+=I background=light noswapfile noautoindent laststatus=1 undodir=. directory=. viewdir=. backupdir=.', - '--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. + '--cmd', nvim_set, '--embed'} +-- Directory containing nvim. local nvim_dir = nvim_prog:gsub("[/\\][^/\\]+$", "") if nvim_dir == nvim_prog then - nvim_dir = "." + nvim_dir = "." end --- Nvim "Unit Under Test" http://en.wikipedia.org/wiki/Device_under_test -local NvimUUT = {} -NvimUUT.__index = NvimUUT - +local mpack = require('mpack') +local tmpname = global_helpers.tmpname +local uname = global_helpers.uname local prepend_argv if os.getenv('VALGRIND') then @@ -72,8 +76,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 @@ -169,6 +173,10 @@ local os_name = (function() end) end)() +local function iswin() + return package.config:sub(1,1) == '\\' +end + -- Executes a VimL function. -- Fails on VimL error, but does not update v:errmsg. local function nvim_call(name, ...) @@ -184,28 +192,6 @@ local function nvim_feed(input) end end -local function dedent(str) - -- find minimum common indent across lines - local indent = nil - for line in str:gmatch('[^\n]+') do - local line_indent = line:match('^%s+') or '' - if indent == nil or #line_indent < #indent then - indent = line_indent - end - end - if #indent == 0 then - -- no minimum common indent - return str - end - -- create a pattern for the indent - indent = indent:gsub('%s', '[ \t]') - -- strip it from the first line - str = str:gsub('^'..indent, '') - -- strip it from the remaining lines - str = str:gsub('[\n]'..indent, '\n') - return str -end - local function feed(...) for _, v in ipairs({...}) do nvim_feed(dedent(v)) @@ -260,12 +246,13 @@ local function retry(max, max_ms, fn) return result end if (max and tries >= max) or (luv.now() - start_time > timeout) then - break + if type(result) == "string" then + result = "\nretry() attempts: "..tostring(tries).."\n"..result + end + error(result) end tries = tries + 1 end - -- Do not use pcall() for the final attempt, let the failure bubble up. - return fn() end local function clear(...) @@ -288,7 +275,9 @@ local function clear(...) 'NVIM_LOG_FILE', 'NVIM_RPLUGIN_MANIFEST', }) do - env_tbl[k] = os.getenv(k) + if not env_tbl[k] then + env_tbl[k] = os.getenv(k) + end end env = {} for k, v in pairs(env_tbl) do @@ -316,7 +305,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 @@ -330,7 +319,14 @@ 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 + if type(text) == 'table' then + -- Byte blob + local bytes = text + text = '' + for _, char in ipairs(bytes) do + text = ('%s%c'):format(text, char) + end + elseif not dont_dedent then text = dedent(text) end file:write(text) @@ -338,14 +334,44 @@ local function write_file(name, text, dont_dedent) 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=\ -NoLogo\ -NoProfile\ -ExecutionPolicy\ RemoteSigned\ -Command + let &shellxquote=' ' + ]]) +end + local function nvim(method, ...) return request('nvim_'..method, ...) end @@ -378,14 +404,21 @@ 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') + session:request('nvim_eval', '1') end -- sleeps the test runner (_not_ the nvim instance) local function sleep(ms) - run(nil, nil, nil, ms) + local function notification_cb(method, _) + if method == "redraw" then + error("Screen is attached; use screen:sleep() instead.") + end + return true + end + + run(nil, notification_cb, nil, ms) end local function curbuf_contents() @@ -411,23 +444,34 @@ local function expect(contents) return eq(dedent(contents), curbuf_contents()) end +local function expect_any(contents) + contents = dedent(contents) + return ok(nil ~= string.find(curbuf_contents(), contents, 1, true)) +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 @@ -436,7 +480,6 @@ local function do_rmdir(path) if not ret then error('lfs.rmdir('..path..'): '..err) end - return ret end local function rmdir(path) @@ -468,17 +511,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, { @@ -494,7 +526,6 @@ end -- Helper to skip tests. Returns true in Windows systems. -- pending_fn is pending() from busted local function pending_win32(pending_fn) - clear() if uname() == 'Windows' then if pending_fn ~= nil then pending_fn('FIXME: Windows', function() end) @@ -521,6 +552,14 @@ local function skip_fragile(pending_fn, cond) return false end +local function meth_pcall(...) + local ret = {pcall(...)} + if type(ret[2]) == 'string' then + ret[2] = ret[2]:gsub('^[^:]+:%d+: ', '') + end + return ret +end + local funcs = create_callindex(nvim_call) local meths = create_callindex(nvim) local uimeths = create_callindex(ui) @@ -531,66 +570,134 @@ 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) +end + +local function missing_provider(provider) + if provider == 'ruby' 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 = { + prepend_argv = prepend_argv, + clear = clear, + connect = connect, + retry = retry, + spawn = spawn, + dedent = dedent, + source = source, + rawfeed = rawfeed, + insert = insert, + iswin = iswin, + feed = feed, + feed_command = feed_command, + eval = nvim_eval, + call = nvim_call, + command = nvim_command, + request = request, + next_message = next_message, + run = run, + stop = stop, + eq = eq, + neq = neq, + expect = expect, + expect_any = expect_any, + ok = ok, + map = map, + filter = filter, + nvim = nvim, + nvim_async = nvim_async, + nvim_prog = nvim_prog, + nvim_argv = nvim_argv, + 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, + 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, + pending_win32 = pending_win32, + skip_fragile = skip_fragile, + set_shell_powershell = set_shell_powershell, + tmpname = tmpname, + meth_pcall = meth_pcall, + NIL = mpack.NIL, + get_pathsep = get_pathsep, + missing_provider = missing_provider, + alter_slashes = alter_slashes, +} + return function(after_each) if after_each then - after_each(check_logs) - end - return { - prepend_argv = prepend_argv, - clear = clear, - connect = connect, - retry = retry, - spawn = spawn, - dedent = dedent, - source = source, - rawfeed = rawfeed, - insert = insert, - 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, - expect = expect, - ok = ok, - nvim = nvim, - nvim_async = nvim_async, - nvim_prog = nvim_prog, - 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, - 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, - pending_win32 = pending_win32, - skip_fragile = skip_fragile, - tmpname = tmpname, - NIL = mpack.NIL, - } + after_each(function() + for _, fname in ipairs(sourced_fnames) do + os.remove(fname) + end + check_logs() + check_cores('build/bin/nvim') + end) + end + return module end |