diff options
Diffstat (limited to 'test/functional/helpers.lua')
-rw-r--r-- | test/functional/helpers.lua | 246 |
1 files changed, 178 insertions, 68 deletions
diff --git a/test/functional/helpers.lua b/test/functional/helpers.lua index 13a0cff137..da334d4ac6 100644 --- a/test/functional/helpers.lua +++ b/test/functional/helpers.lua @@ -8,6 +8,7 @@ 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 @@ -16,30 +17,30 @@ 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() -- 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' + ..' 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 @@ -75,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 @@ -99,6 +100,22 @@ local function next_message() return session:next_message() end +local function expect_twostreams(msgs1, msgs2) + local pos1, pos2 = 1, 1 + while pos1 <= #msgs1 or pos2 <= #msgs2 do + local msg = next_message() + 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 + local function call_and_stop_on_error(...) local status, result = copcall(...) -- luacheck: ignore if not status then @@ -173,7 +190,7 @@ local os_name = (function() end)() local function iswin() - return os_name() == 'windows' + return package.config:sub(1,1) == '\\' end -- Executes a VimL function. @@ -191,28 +208,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 == nil or #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)) @@ -267,12 +262,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(...) @@ -325,7 +321,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 @@ -339,7 +335,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) @@ -357,18 +360,30 @@ local function read_file(name) 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 + set shellcmdflag=\ -NoLogo\ -NoProfile\ -ExecutionPolicy\ RemoteSigned\ -Command let &shellxquote=' ' ]]) end @@ -405,14 +420,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() @@ -445,21 +467,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 @@ -468,7 +496,6 @@ local function do_rmdir(path) if not ret then error('lfs.rmdir('..path..'): '..err) end - return ret end local function rmdir(path) @@ -500,17 +527,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, { @@ -570,7 +586,91 @@ local curbufmeths = create_callindex(curbuf) local curwinmeths = create_callindex(curwin) local curtabmeths = create_callindex(curtab) -local M = { +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 + +-- 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' 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 function hexdump(str) + local len = string.len( str ) + local dump = "" + local hex = "" + local asc = "" + + for i = 1, len do + if 1 == i % 8 then + dump = dump .. hex .. asc .. "\n" + hex = string.format( "%04x: ", i - 1 ) + asc = "" + end + + local ord = string.byte( str, i ) + hex = hex .. string.format( "%02x ", ord ) + if ord >= 32 and ord <= 126 then + asc = asc .. string.char( ord ) + else + asc = asc .. "." + end + end + + return dump .. hex + .. string.rep( " ", 8 - len % 8 ) .. asc + +end + +local module = { prepend_argv = prepend_argv, clear = clear, connect = connect, @@ -582,12 +682,13 @@ local M = { insert = insert, iswin = iswin, feed = feed, - execute = execute, + feed_command = feed_command, eval = nvim_eval, call = nvim_call, command = nvim_command, request = request, next_message = next_message, + expect_twostreams = expect_twostreams, run = run, stop = stop, eq = eq, @@ -600,6 +701,7 @@ local M = { nvim = nvim, nvim_async = nvim_async, nvim_prog = nvim_prog, + nvim_argv = nvim_argv, nvim_set = nvim_set, nvim_dir = nvim_dir, buffer = buffer, @@ -635,14 +737,22 @@ local M = { tmpname = tmpname, meth_pcall = meth_pcall, NIL = mpack.NIL, + get_pathsep = get_pathsep, + missing_provider = missing_provider, + alter_slashes = alter_slashes, + hexdump = hexdump, + new_pipename = new_pipename, } 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 |