diff options
Diffstat (limited to 'test/functional/helpers.lua')
-rw-r--r-- | test/functional/helpers.lua | 282 |
1 files changed, 218 insertions, 64 deletions
diff --git a/test/functional/helpers.lua b/test/functional/helpers.lua index 37b7bf664c..880cb6546c 100644 --- a/test/functional/helpers.lua +++ b/test/functional/helpers.lua @@ -1,9 +1,22 @@ require('coxpcall') +local luv = require('luv') local lfs = require('lfs') -local assert = require('luassert') -local ChildProcessStream = require('nvim.child_process_stream') +local global_helpers = require('test.helpers') + +-- nvim client: Found in .deps/usr/share/lua/<version>/nvim/ if "bundled". 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 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 start_dir = lfs.currentdir() local nvim_prog = os.getenv('NVIM_PROG') or 'build/bin/nvim' local nvim_argv = {nvim_prog, '-u', 'NONE', '-i', 'NONE', '-N', '--cmd', 'set shortmess+=I background=light noswapfile noautoindent laststatus=1 undodir=. directory=. viewdir=. backupdir=.', @@ -11,18 +24,17 @@ local nvim_argv = {nvim_prog, '-u', 'NONE', '-i', 'NONE', '-N', 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. 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 prepend_argv if os.getenv('VALGRIND') then @@ -30,7 +42,7 @@ if os.getenv('VALGRIND') then prepend_argv = {'valgrind', '-q', '--tool=memcheck', '--leak-check=yes', '--track-origins=yes', '--show-possibly-lost=no', - '--suppressions=.valgrind.supp', + '--suppressions=src/.valgrind.supp', '--log-file='..log_file} if os.getenv('GDB') then table.insert(prepend_argv, '--vgdb=yes') @@ -127,12 +139,16 @@ local function stop() session:stop() end +-- Executes an ex-command. VimL errors manifest as client (lua) errors, but +-- v:errmsg will not be updated. local function nvim_command(cmd) - request('vim_command', cmd) + request('nvim_command', cmd) end +-- Evaluates a VimL expression. +-- Fails on VimL error, but does not update v:errmsg. local function nvim_eval(expr) - return request('vim_eval', expr) + return request('nvim_eval', expr) end local os_name = (function() @@ -151,13 +167,21 @@ local os_name = (function() end) end)() +local function iswin() + return os_name() == 'windows' +end + +-- Executes a VimL function. +-- Fails on VimL error, but does not update v:errmsg. local function nvim_call(name, ...) - return request('vim_call_function', name, {...}) + return request('nvim_call_function', name, {...}) end +-- Sends user input to Nvim. +-- Does not fail on VimL error, but v:errmsg will be updated. local function nvim_feed(input) while #input > 0 do - local written = request('vim_input', input) + local written = request('nvim_input', input) input = input:sub(written + 1) end end @@ -171,7 +195,7 @@ local function dedent(str) indent = line_indent end end - if #indent == 0 then + if indent == nil or #indent == 0 then -- no minimum common indent return str end @@ -211,18 +235,76 @@ local function merge_args(...) return argv end -local function spawn(argv, merge) - local child_stream = ChildProcessStream.spawn(merge and merge_args(prepend_argv, argv) or argv) +local function spawn(argv, merge, env) + local child_stream = ChildProcessStream.spawn( + merge and merge_args(prepend_argv, argv) or argv, + env) return Session.new(child_stream) end -local function clear(extra_cmd) +-- Creates a new Session connected by domain socket (named pipe) or TCP. +local function connect(file_or_address) + local addr, port = string.match(file_or_address, "(.*):(%d+)") + local stream = (addr and port) and TcpStream.open(addr, port) or + SocketStream.open(file_or_address) + return Session.new(stream) +end + +-- Calls fn() until it succeeds, up to `max` times or until `max_ms` +-- milliseconds have passed. +local function retry(max, max_ms, fn) + local tries = 1 + local timeout = (max_ms and max_ms > 0) and max_ms or 10000 + local start_time = luv.now() + while true do + local status, result = pcall(fn) + if status then + return result + end + if (max and tries >= max) or (luv.now() - start_time > timeout) then + break + end + tries = tries + 1 + end + -- Do not use pcall() for the final attempt, let the failure bubble up. + return fn() +end + +local function clear(...) local args = {unpack(nvim_argv)} - if extra_cmd ~= nil then - table.insert(args, '--cmd') - table.insert(args, extra_cmd) + local new_args + local env = nil + local opts = select(1, ...) + if type(opts) == 'table' then + if opts.env then + local env_tbl = {} + for k, v in pairs(opts.env) do + assert(type(k) == 'string') + assert(type(v) == 'string') + env_tbl[k] = v + end + for _, k in ipairs({ + 'HOME', + 'ASAN_OPTIONS', + 'LD_LIBRARY_PATH', 'PATH', + 'NVIM_LOG_FILE', + 'NVIM_RPLUGIN_MANIFEST', + }) do + env_tbl[k] = os.getenv(k) + end + env = {} + for k, v in pairs(env_tbl) do + env[#env + 1] = k .. '=' .. v + end + end + new_args = opts.args or {} + else + new_args = {...} end - set_session(spawn(args)) + for _, arg in ipairs(new_args) do + table.insert(args, arg) + end + set_session(spawn(args, nil, env)) end local function insert(...) @@ -234,6 +316,8 @@ local function insert(...) nvim_feed('<ESC>') 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(...) for _, v in ipairs({...}) do if v:sub(1, 1) ~= '/' then @@ -257,108 +341,130 @@ local function write_file(name, text, dont_dedent) end local function source(code) - local tmpname = os.tmpname() - if os_name() == 'osx' and string.match(tmpname, '^/tmp') then - tmpname = '/private'..tmpname - end - write_file(tmpname, code) - nvim_command('source '..tmpname) - os.remove(tmpname) - return tmpname + local fname = tmpname() + write_file(fname, code) + nvim_command('source '..fname) + os.remove(fname) + return fname end -local function eq(expected, actual) - return assert.are.same(expected, actual) +local function set_shell_powershell() + source([[ + set shell=powershell shellquote=\" shellpipe=\| shellredir=> + set shellcmdflag=\ -ExecutionPolicy\ RemoteSigned\ -Command + let &shellxquote=' ' + ]]) end -local function neq(expected, actual) - return assert.are_not.same(expected, actual) +local function nvim(method, ...) + return request('nvim_'..method, ...) end -local function ok(expr) - assert.is_true(expr) -end - -local function nvim(method, ...) - return request('vim_'..method, ...) +local function ui(method, ...) + return request('nvim_ui_'..method, ...) end local function nvim_async(method, ...) - session:notify('vim_'..method, ...) + session:notify('nvim_'..method, ...) end local function buffer(method, ...) - return request('buffer_'..method, ...) + return request('nvim_buf_'..method, ...) end local function window(method, ...) - return request('window_'..method, ...) + return request('nvim_win_'..method, ...) end local function tabpage(method, ...) - return request('tabpage_'..method, ...) + return request('nvim_tabpage_'..method, ...) end local function curbuf(method, ...) - local buf = nvim('get_current_buffer') if not method then - return buf + return nvim('get_current_buf') end - return buffer(method, buf, ...) + return buffer(method, 0, ...) end local function wait() + -- Execute 'vim_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) +end + local function curbuf_contents() - -- Before inspecting the buffer, execute 'vim_eval' to wait until all - -- previously sent keys are processed(vim_eval is a deferred function, and - -- only processed after all input) - wait() + wait() -- Before inspecting the buffer, process all input. return table.concat(curbuf('get_lines', 0, -1, true), '\n') end local function curwin(method, ...) - local win = nvim('get_current_window') if not method then - return win + return nvim('get_current_win') end - return window(method, win, ...) + return window(method, 0, ...) end local function curtab(method, ...) - local tab = nvim('get_current_tabpage') if not method then - return tab + return nvim('get_current_tabpage') end - return tabpage(method, tab, ...) + return tabpage(method, 0, ...) end local function expect(contents) return eq(dedent(contents), curbuf_contents()) end -local function rmdir(path) +local function do_rmdir(path) if lfs.attributes(path, 'mode') ~= 'directory' then return nil end for file in lfs.dir(path) do if file ~= '.' and file ~= '..' then - local ret, err = os.remove(path..'/'..file) - if not ret then - error('os.remove: '..err) - return nil + local abspath = path..'/'..file + if lfs.attributes(abspath, 'mode') == 'directory' then + local ret = do_rmdir(abspath) -- recurse + if not ret then + return nil + end + else + local ret, err = os.remove(abspath) + if not ret then + error('os.remove: '..err) + return nil + end end end end - local ret, err = os.remove(path) + local ret, err = lfs.rmdir(path) if not ret then - error('os.remove: '..err) + error('lfs.rmdir('..path..'): '..err) end return ret end +local function rmdir(path) + local ret, _ = pcall(do_rmdir, path) + if not ret and os_name() == "windows" then + -- Maybe "Permission denied"; try again after changing the nvim + -- process to the top-level directory. + nvim_command([[exe 'cd '.fnameescape(']]..start_dir.."')") + ret, _ = pcall(do_rmdir, path) + end + -- During teardown, the nvim process may not exit quickly enough, then rmdir() + -- will fail (on Windows). + if not ret then -- Try again. + sleep(1000) + do_rmdir(path) + end +end + local exc_exec = function(cmd) nvim_command(([[ try @@ -395,8 +501,38 @@ local function create_callindex(func) return table end +-- Helper to skip tests. Returns true in Windows systems. +-- pending_fn is pending() from busted +local function pending_win32(pending_fn) + if uname() == 'Windows' then + if pending_fn ~= nil then + pending_fn('FIXME: Windows', function() end) + end + return true + else + return false + end +end + +-- Calls pending() and returns `true` if the system is too slow to +-- run fragile or expensive tests. Else returns `false`. +local function skip_fragile(pending_fn, cond) + if pending_fn == nil or type(pending_fn) ~= type(function()end) then + error("invalid pending_fn") + end + if cond then + pending_fn("skipped (test is fragile on this system)", function() end) + return true + elseif os.getenv("TEST_SKIP_FRAGILE") then + pending_fn("skipped (TEST_SKIP_FRAGILE)", function() end) + return true + end + return false +end + local funcs = create_callindex(nvim_call) local meths = create_callindex(nvim) +local uimeths = create_callindex(ui) local bufmeths = create_callindex(buffer) local winmeths = create_callindex(window) local tabmeths = create_callindex(tabpage) @@ -404,14 +540,17 @@ local curbufmeths = create_callindex(curbuf) local curwinmeths = create_callindex(curwin) local curtabmeths = create_callindex(curtab) -return { +local M = { prepend_argv = prepend_argv, clear = clear, + connect = connect, + retry = retry, spawn = spawn, dedent = dedent, source = source, rawfeed = rawfeed, insert = insert, + iswin = iswin, feed = feed, execute = execute, eval = nvim_eval, @@ -425,6 +564,8 @@ return { neq = neq, expect = expect, ok = ok, + map = map, + filter = filter, nvim = nvim, nvim_async = nvim_async, nvim_prog = nvim_prog, @@ -437,6 +578,7 @@ return { curtab = curtab, curbuf_contents = curbuf_contents, wait = wait, + sleep = sleep, set_session = set_session, write_file = write_file, os_name = os_name, @@ -450,8 +592,20 @@ return { bufmeths = bufmeths, winmeths = winmeths, tabmeths = tabmeths, + uimeths = uimeths, curbufmeths = curbufmeths, curwinmeths = curwinmeths, curtabmeths = curtabmeths, - NIL = mpack.NIL + pending_win32 = pending_win32, + skip_fragile = skip_fragile, + set_shell_powershell = set_shell_powershell, + tmpname = tmpname, + NIL = mpack.NIL, } + +return function(after_each) + if after_each then + after_each(check_logs) + end + return M +end |