diff options
Diffstat (limited to 'test/functional/helpers.lua')
-rw-r--r-- | test/functional/helpers.lua | 616 |
1 files changed, 346 insertions, 270 deletions
diff --git a/test/functional/helpers.lua b/test/functional/helpers.lua index dcaaa664b9..d1d26919a0 100644 --- a/test/functional/helpers.lua +++ b/test/functional/helpers.lua @@ -1,4 +1,4 @@ -local luv = require('luv') +local uv = vim.uv local global_helpers = require('test.helpers') local Session = require('test.client.session') @@ -10,65 +10,76 @@ local check_cores = global_helpers.check_cores local check_logs = global_helpers.check_logs local dedent = global_helpers.dedent local eq = global_helpers.eq -local filter = global_helpers.tbl_filter local is_os = global_helpers.is_os -local map = global_helpers.tbl_map local ok = global_helpers.ok -local sleep = global_helpers.sleep -local tbl_contains = global_helpers.tbl_contains +local sleep = uv.sleep local fail = global_helpers.fail local module = {} -local start_dir = luv.cwd() local runtime_set = 'set runtimepath^=./build/lib/nvim/' -module.nvim_prog = ( - os.getenv('NVIM_PRG') - or global_helpers.test_build_dir .. '/bin/nvim' -) +module.nvim_prog = (os.getenv('NVIM_PRG') or global_helpers.paths.test_build_dir .. '/bin/nvim') -- Default settings for the test session. module.nvim_set = ( - 'set shortmess+=IS background=light noswapfile noautoindent startofline' - ..' laststatus=1 undodir=. directory=. viewdir=. backupdir=.' - ..' belloff= wildoptions-=pum joinspaces noshowcmd noruler nomore redrawdebug=invalid') + 'set shortmess+=IS background=light termguicolors noswapfile noautoindent startofline' + .. ' laststatus=1 undodir=. directory=. viewdir=. backupdir=.' + .. ' belloff= wildoptions-=pum joinspaces noshowcmd noruler nomore redrawdebug=invalid' +) module.nvim_argv = { - module.nvim_prog, '-u', 'NONE', '-i', 'NONE', + module.nvim_prog, + '-u', + 'NONE', + '-i', + 'NONE', -- XXX: find treesitter parsers. - '--cmd', runtime_set, - '--cmd', module.nvim_set, - '--cmd', 'mapclear', - '--cmd', 'mapclear!', - '--embed'} + '--cmd', + runtime_set, + '--cmd', + module.nvim_set, + -- Remove default mappings. + '--cmd', + 'mapclear | mapclear!', + -- Make screentest work after changing to the new default color scheme + -- Source 'vim' color scheme without side effects + -- TODO: rewrite tests + '--cmd', + 'lua dofile("runtime/colors/vim.lua")', + '--cmd', + 'unlet g:colors_name', + '--embed', +} -- Directory containing nvim. -module.nvim_dir = module.nvim_prog:gsub("[/\\][^/\\]+$", "") +module.nvim_dir = module.nvim_prog:gsub('[/\\][^/\\]+$', '') if module.nvim_dir == module.nvim_prog then - module.nvim_dir = "." + module.nvim_dir = '.' end -local prepend_argv +local prepend_argv --- @type string[]? if os.getenv('VALGRIND') then local log_file = os.getenv('VALGRIND_LOG') or 'valgrind-%p.log' - prepend_argv = {'valgrind', '-q', '--tool=memcheck', - '--leak-check=yes', '--track-origins=yes', - '--show-possibly-lost=no', - '--suppressions=src/.valgrind.supp', - '--log-file='..log_file} + prepend_argv = { + 'valgrind', + '-q', + '--tool=memcheck', + '--leak-check=yes', + '--track-origins=yes', + '--show-possibly-lost=no', + '--suppressions=src/.valgrind.supp', + '--log-file=' .. log_file, + } if os.getenv('GDB') then table.insert(prepend_argv, '--vgdb=yes') table.insert(prepend_argv, '--vgdb-error=0') end elseif os.getenv('GDB') then - local gdbserver_port = '7777' - if os.getenv('GDBSERVER_PORT') then - gdbserver_port = os.getenv('GDBSERVER_PORT') - end - prepend_argv = {'gdbserver', 'localhost:'..gdbserver_port} + local gdbserver_port = os.getenv('GDBSERVER_PORT') or '7777' + prepend_argv = { 'gdbserver', 'localhost:' .. gdbserver_port } end if prepend_argv then - local new_nvim_argv = {} + local new_nvim_argv = {} --- @type string[] local len = #prepend_argv for i = 1, len do new_nvim_argv[i] = prepend_argv[i] @@ -80,12 +91,15 @@ if prepend_argv then module.prepend_argv = prepend_argv end -local session, loop_running, last_error, method_error +local session --- @type test.Session? +local loop_running --- @type boolean? +local last_error --- @type string? +local method_error --- @type string? if not is_os('win') then - local sigpipe_handler = luv.new_signal() - luv.signal_start(sigpipe_handler, "sigpipe", function() - print("warning: got SIGPIPE signal. Likely related to a crash in nvim") + local sigpipe_handler = assert(uv.new_signal()) + uv.signal_start(sigpipe_handler, 'sigpipe', function() + print('warning: got SIGPIPE signal. Likely related to a crash in nvim') end) end @@ -97,10 +111,15 @@ function module.set_session(s) session = s end +--- @param method string +--- @param ... any +--- @return any function module.request(method, ...) + assert(session) local status, rv = session:request(method, ...) if not status then if loop_running then + --- @type string last_error = rv[2] session:stop() else @@ -110,12 +129,18 @@ function module.request(method, ...) return rv end +--- @param method string +--- @param ... any +--- @return any function module.request_lua(method, ...) return module.exec_lua([[return vim.api[...](select(2, ...))]], method, ...) end +--- @param timeout? integer +--- @return string? function module.next_msg(timeout) - return session:next_message(timeout and timeout or 10000) + assert(session) + return session:next_message(timeout or 10000) end function module.expect_twostreams(msgs1, msgs2) @@ -149,15 +174,16 @@ function module.expect_msg_seq(...) error('need at least 1 argument') end local arg1 = select(1, ...) - if (arg1['seqs'] and select('#', ...) > 1) or type(arg1) ~= 'table' then + if (arg1['seqs'] and select('#', ...) > 1) or type(arg1) ~= 'table' then error('invalid args') end local ignore = arg1['ignore'] and arg1['ignore'] or {} - local seqs = arg1['seqs'] and arg1['seqs'] or {...} + --- @type string[] + local seqs = arg1['seqs'] and arg1['seqs'] or { ... } if type(ignore) ~= 'table' then error("'ignore' arg must be a list of strings") end - table.sort(seqs, function(a, b) -- Sort ascending, by (shallow) length. + table.sort(seqs, function(a, b) -- Sort ascending, by (shallow) length. return #a < #b end) @@ -170,7 +196,7 @@ function module.expect_msg_seq(...) end return string.format('%s\n%s\n%s', err1, string.rep('=', 78), err2) end - local msg_timeout = module.load_adjust(10000) -- Big timeout for ASAN/valgrind. + local msg_timeout = module.load_adjust(10000) -- Big timeout for ASAN/valgrind. for anum = 1, #seqs do local expected_seq = seqs[anum] -- Collect enough messages to compare the next expected sequence. @@ -178,10 +204,18 @@ function module.expect_msg_seq(...) local msg = module.next_msg(msg_timeout) local msg_type = msg and msg[2] or nil if msg == nil then - error(cat_err(final_error, - string.format('got %d messages (ignored %d), expected %d', - #actual_seq, nr_ignored, #expected_seq))) - elseif tbl_contains(ignore, msg_type) then + error( + cat_err( + final_error, + string.format( + 'got %d messages (ignored %d), expected %d', + #actual_seq, + nr_ignored, + #expected_seq + ) + ) + ) + elseif vim.tbl_contains(ignore, msg_type) then nr_ignored = nr_ignored + 1 else table.insert(actual_seq, msg) @@ -192,8 +226,9 @@ function module.expect_msg_seq(...) return result end local message = result - if type(result) == "table" then + if type(result) == 'table' then -- 'eq' returns several things + --- @type string message = result.message end final_error = cat_err(final_error, message) @@ -202,7 +237,7 @@ function module.expect_msg_seq(...) end local function call_and_stop_on_error(lsession, ...) - local status, result = Session.safe_pcall(...) -- luacheck: ignore + local status, result = Session.safe_pcall(...) -- luacheck: ignore if not status then lsession:stop() last_error = result @@ -215,8 +250,16 @@ function module.set_method_error(err) method_error = err end +--- @param lsession test.Session +--- @param request_cb function? +--- @param notification_cb function? +--- @param setup_cb function? +--- @param timeout integer +--- @return {[1]: integer, [2]: string} function module.run_session(lsession, request_cb, notification_cb, setup_cb, timeout) - local on_request, on_notification, on_setup + local on_request --- @type function? + local on_notification --- @type function? + local on_setup --- @type function? if request_cb then function on_request(method, args) @@ -254,29 +297,24 @@ function module.run_session(lsession, request_cb, notification_cb, setup_cb, tim end function module.run(request_cb, notification_cb, setup_cb, timeout) + assert(session) return module.run_session(session, request_cb, notification_cb, setup_cb, timeout) end function module.stop() - session:stop() + assert(session):stop() end function module.nvim_prog_abs() -- system(['build/bin/nvim']) does not work for whatever reason. It must -- be executable searched in $PATH or something starting with / or ./. if module.nvim_prog:match('[/\\]') then - return module.request('nvim_call_function', 'fnamemodify', {module.nvim_prog, ':p'}) + return module.request('nvim_call_function', 'fnamemodify', { module.nvim_prog, ':p' }) else return module.nvim_prog end end --- Executes an ex-command. Vimscript errors manifest as client (lua) errors, but --- v:errmsg will not be updated. -function module.command(cmd) - module.request('nvim_command', cmd) -end - -- Use for commands which expect nvim to quit. -- The first argument can also be a timeout. function module.expect_exit(fn_or_timeout, ...) @@ -284,37 +322,33 @@ function module.expect_exit(fn_or_timeout, ...) if type(fn_or_timeout) == 'function' then eq(eof_err_msg, module.pcall_err(fn_or_timeout, ...)) else - eq(eof_err_msg, module.pcall_err(function(timeout, fn, ...) - fn(...) - while session:next_message(timeout) do - end - if session.eof_err then - error(session.eof_err[2]) - end - end, fn_or_timeout, ...)) + eq( + eof_err_msg, + module.pcall_err(function(timeout, fn, ...) + fn(...) + assert(session) + while session:next_message(timeout) do + end + if session.eof_err then + error(session.eof_err[2]) + end + end, fn_or_timeout, ...) + ) end end --- Evaluates a Vimscript expression. --- Fails on Vimscript error, but does not update v:errmsg. -function module.eval(expr) - return module.request('nvim_eval', expr) -end - --- Executes a Vimscript function via RPC. --- Fails on Vimscript error, but does not update v:errmsg. -function module.call(name, ...) - return module.request('nvim_call_function', name, {...}) -end - --- Executes a Vimscript function via Lua. --- Fails on Vimscript error, but does not update v:errmsg. +--- Executes a Vimscript function via Lua. +--- Fails on Vimscript error, but does not update v:errmsg. +--- @param name string +--- @param ... any +--- @return any function module.call_lua(name, ...) return module.exec_lua([[return vim.call(...)]], name, ...) end --- Sends user input to Nvim. --- Does not fail on Vimscript error, but v:errmsg will be updated. +--- Sends user input to Nvim. +--- Does not fail on Vimscript error, but v:errmsg will be updated. +--- @param input string local function nvim_feed(input) while #input > 0 do local written = module.request('nvim_input', input) @@ -326,22 +360,27 @@ local function nvim_feed(input) end end +--- @param ... string function module.feed(...) - for _, v in ipairs({...}) do + for _, v in ipairs({ ... }) do nvim_feed(dedent(v)) end end +--- @param ... string function module.rawfeed(...) - for _, v in ipairs({...}) do + for _, v in ipairs({ ... }) do nvim_feed(dedent(v)) end end +---@param ... string[]? +---@return string[] function module.merge_args(...) local i = 1 - local argv = {} - for anum = 1,select('#', ...) do + local argv = {} --- @type string[] + for anum = 1, select('#', ...) do + --- @type string[]? local args = select(anum, ...) if args then for _, arg in ipairs(args) do @@ -353,41 +392,44 @@ function module.merge_args(...) return argv end --- Removes Nvim startup args from `args` matching items in `args_rm`. --- --- - Special case: "-u", "-i", "--cmd" are treated specially: their "values" are also removed. --- - Special case: "runtimepath" will remove only { '--cmd', 'set runtimepath^=…', } --- --- Example: --- args={'--headless', '-u', 'NONE'} --- args_rm={'--cmd', '-u'} --- Result: --- {'--headless'} --- --- All matching cases are removed. --- --- Example: --- args={'--cmd', 'foo', '-N', '--cmd', 'bar'} --- args_rm={'--cmd', '-u'} --- Result: --- {'-N'} +--- Removes Nvim startup args from `args` matching items in `args_rm`. +--- +--- - Special case: "-u", "-i", "--cmd" are treated specially: their "values" are also removed. +--- - Special case: "runtimepath" will remove only { '--cmd', 'set runtimepath^=…', } +--- +--- Example: +--- args={'--headless', '-u', 'NONE'} +--- args_rm={'--cmd', '-u'} +--- Result: +--- {'--headless'} +--- +--- All matching cases are removed. +--- +--- Example: +--- args={'--cmd', 'foo', '-N', '--cmd', 'bar'} +--- args_rm={'--cmd', '-u'} +--- Result: +--- {'-N'} +--- @param args string[] +--- @param args_rm string[] +--- @return string[] local function remove_args(args, args_rm) - local new_args = {} - local skip_following = {'-u', '-i', '-c', '--cmd', '-s', '--listen'} + local new_args = {} --- @type string[] + local skip_following = { '-u', '-i', '-c', '--cmd', '-s', '--listen' } if not args_rm or #args_rm == 0 then - return {unpack(args)} + return { unpack(args) } end for _, v in ipairs(args_rm) do assert(type(v) == 'string') end local last = '' for _, arg in ipairs(args) do - if tbl_contains(skip_following, last) then + if vim.tbl_contains(skip_following, last) then last = '' - elseif tbl_contains(args_rm, arg) then + elseif vim.tbl_contains(args_rm, arg) then last = arg - elseif arg == runtime_set and tbl_contains(args_rm, 'runtimepath') then - table.remove(new_args) -- Remove the preceding "--cmd". + elseif arg == runtime_set and vim.tbl_contains(args_rm, 'runtimepath') then + table.remove(new_args) -- Remove the preceding "--cmd". last = '' else table.insert(new_args, arg) @@ -400,40 +442,48 @@ function module.check_close() if not session then return end - local start_time = luv.now() + local start_time = uv.now() session:close() - luv.update_time() -- Update cached value of luv.now() (libuv: uv_now()). - local end_time = luv.now() + uv.update_time() -- Update cached value of luv.now() (libuv: uv_now()). + local end_time = uv.now() local delta = end_time - start_time if delta > 500 then - print("nvim took " .. delta .. " milliseconds to exit after last test\n".. - "This indicates a likely problem with the test even if it passed!\n") + print( + 'nvim took ' + .. delta + .. ' milliseconds to exit after last test\n' + .. 'This indicates a likely problem with the test even if it passed!\n' + ) io.stdout:flush() end session = nil end ---- @param io_extra used for stdin_fd, see :help ui-option +--- @param argv string[] +--- @param merge boolean? +--- @param env string[]? +--- @param keep boolean +--- @param io_extra uv.uv_pipe_t? used for stdin_fd, see :help ui-option +--- @return test.Session function module.spawn(argv, merge, env, keep, io_extra) if not keep then module.check_close() end - local child_stream = ChildProcessStream.spawn( - merge and module.merge_args(prepend_argv, argv) or argv, - env, io_extra) + local child_stream = + ChildProcessStream.spawn(merge and module.merge_args(prepend_argv, argv) or argv, env, io_extra) return Session.new(child_stream) end -- Creates a new Session connected by domain socket (named pipe) or TCP. function module.connect(file_or_address) - local addr, port = string.match(file_or_address, "(.*):(%d+)") - local stream = (addr and port) and SocketStream.connect(addr, port) or - SocketStream.open(file_or_address) + local addr, port = string.match(file_or_address, '(.*):(%d+)') + local stream = (addr and port) and SocketStream.connect(addr, port) + or SocketStream.open(file_or_address) return Session.new(stream) end --- Starts a new global Nvim session. +-- Starts (and returns) a new global Nvim session. -- -- Parameters are interpreted as startup args, OR a map with these keys: -- args: List: Args appended to the default `nvim_argv` set. @@ -447,36 +497,49 @@ end -- clear{args={'-e'}, args_rm={'-i'}, env={TERM=term}} function module.clear(...) module.set_session(module.spawn_argv(false, ...)) + return module.get_session() end --- same params as clear, but does returns the session instead --- of replacing the default session +--- same params as clear, but does returns the session instead +--- of replacing the default session +--- @return test.Session function module.spawn_argv(keep, ...) local argv, env, io_extra = module.new_argv(...) return module.spawn(argv, nil, env, keep, io_extra) end --- Builds an argument list for use in clear(). --- ----@see clear() for parameters. +--- @class test.new_argv.Opts +--- @field args? string[] +--- @field args_rm? string[] +--- @field env? table<string,string> +--- @field io_extra? uv.uv_pipe_t + +--- Builds an argument list for use in clear(). +--- +--- @see clear() for parameters. +--- @param ... string +--- @return string[] +--- @return string[]? +--- @return uv.uv_pipe_t? function module.new_argv(...) - local args = {unpack(module.nvim_argv)} + local args = { unpack(module.nvim_argv) } table.insert(args, '--headless') if _G._nvim_test_id then -- Set the server name to the test-id for logging. #8519 table.insert(args, '--listen') table.insert(args, _G._nvim_test_id) end - local new_args - local io_extra - local env = nil + local new_args --- @type string[] + local io_extra --- @type uv.uv_pipe_t? + local env --- @type string[]? + --- @type test.new_argv.Opts|string local opts = select(1, ...) if type(opts) ~= 'table' then - new_args = {...} + new_args = { ... } else args = remove_args(args, opts.args_rm) if opts.env then - local env_opt = {} + local env_opt = {} --- @type table<string,string> for k, v in pairs(opts.env) do assert(type(k) == 'string') assert(type(v) == 'string') @@ -515,19 +578,21 @@ function module.new_argv(...) return args, env, io_extra end +--- @param ... string function module.insert(...) nvim_feed('i') - for _, v in ipairs({...}) do + for _, v in ipairs({ ... }) do local escaped = v:gsub('<', '<lt>') module.rawfeed(escaped) end nvim_feed('<ESC>') end --- Executes an ex-command by user input. Because nvim_input() is used, Vimscript --- errors will not manifest as client (lua) errors. Use command() for that. +--- Executes an ex-command by user input. Because nvim_input() is used, Vimscript +--- errors will not manifest as client (lua) errors. Use command() for that. +--- @param ... string function module.feed_command(...) - for _, v in ipairs({...}) do + for _, v in ipairs({ ... }) do if v:sub(1, 1) ~= '/' then -- not a search command, prefix with colon nvim_feed(':') @@ -543,7 +608,7 @@ function module.source(code) end function module.has_powershell() - return module.eval('executable("'..(is_os('win') and 'powershell' or 'pwsh')..'")') == 1 + return module.eval('executable("' .. (is_os('win') and 'powershell' or 'pwsh') .. '")') == 1 end --- Sets Nvim shell to powershell. @@ -557,102 +622,68 @@ function module.set_shell_powershell(fake) assert(found) end local shell = found and (is_os('win') and 'powershell' or 'pwsh') or module.testprg('pwsh-test') - local cmd = 'Remove-Item -Force '..table.concat(is_os('win') - and {'alias:cat', 'alias:echo', 'alias:sleep', 'alias:sort', 'alias:tee'} - or {'alias:echo'}, ',')..';' + local cmd = 'Remove-Item -Force ' + .. table.concat( + is_os('win') and { 'alias:cat', 'alias:echo', 'alias:sleep', 'alias:sort', 'alias:tee' } + or { 'alias:echo' }, + ',' + ) + .. ';' module.exec([[ - let &shell = ']]..shell..[[' + let &shell = ']] .. shell .. [[' set shellquote= shellxquote= let &shellcmdflag = '-NoLogo -NoProfile -ExecutionPolicy RemoteSigned -Command ' let &shellcmdflag .= '[Console]::InputEncoding=[Console]::OutputEncoding=[System.Text.UTF8Encoding]::new();' let &shellcmdflag .= '$PSDefaultParameterValues[''Out-File:Encoding'']=''utf8'';' - let &shellcmdflag .= ']]..cmd..[[' + let &shellcmdflag .= ']] .. cmd .. [[' let &shellredir = '2>&1 | %%{ "$_" } | Out-File %s; exit $LastExitCode' let &shellpipe = '2>&1 | %%{ "$_" } | tee %s; exit $LastExitCode' ]]) return found end +---@param func function +---@return table<string,function> function module.create_callindex(func) - local table = {} - setmetatable(table, { + return setmetatable({}, { + --- @param tbl table<any,function> + --- @param arg1 string + --- @return function __index = function(tbl, arg1) - local ret = function(...) return func(arg1, ...) end + local ret = function(...) + return func(arg1, ...) + end tbl[arg1] = ret return ret end, }) - return table end -local function ui(method, ...) - return module.request('nvim_ui_'..method, ...) +--- @param method string +--- @param ... any +function module.nvim_async(method, ...) + assert(session):notify(method, ...) end -function module.nvim_async(method, ...) - session:notify('nvim_'..method, ...) +--- Executes a Vimscript function via RPC. +--- Fails on Vimscript error, but does not update v:errmsg. +--- @param name string +--- @param ... any +--- @return any +function module.call(name, ...) + return module.request('nvim_call_function', name, { ... }) end module.async_meths = module.create_callindex(module.nvim_async) -module.uimeths = module.create_callindex(ui) - -local function create_api(request, call) - local m = {} - function m.nvim(method, ...) - return request('nvim_'..method, ...) - end - - function m.buffer(method, ...) - return request('nvim_buf_'..method, ...) - end - - function m.window(method, ...) - return request('nvim_win_'..method, ...) - end - - function m.tabpage(method, ...) - return request('nvim_tabpage_'..method, ...) - end - - function m.curbuf(method, ...) - if not method then - return m.nvim('get_current_buf') - end - return m.buffer(method, 0, ...) - end - - function m.curwin(method, ...) - if not method then - return m.nvim('get_current_win') - end - return m.window(method, 0, ...) - end - - function m.curtab(method, ...) - if not method then - return m.nvim('get_current_tabpage') - end - return m.tabpage(method, 0, ...) - end - - m.funcs = module.create_callindex(call) - m.meths = module.create_callindex(m.nvim) - m.bufmeths = module.create_callindex(m.buffer) - m.winmeths = module.create_callindex(m.window) - m.tabmeths = module.create_callindex(m.tabpage) - m.curbufmeths = module.create_callindex(m.curbuf) - m.curwinmeths = module.create_callindex(m.curwin) - m.curtabmeths = module.create_callindex(m.curtab) - - return m -end module.rpc = { - api = create_api(module.request, module.call), + fn = module.create_callindex(module.call), + api = module.create_callindex(module.request), } module.lua = { - api = create_api(module.request_lua, module.call_lua), + fn = module.create_callindex(module.call_lua), + api = module.create_callindex(module.request_lua), } module.describe_lua_and_rpc = function(describe) @@ -668,24 +699,37 @@ module.describe_lua_and_rpc = function(describe) end end -for name, fn in pairs(module.rpc.api) do - module[name] = fn +--- add for typing. The for loop after will overwrite this +module.api = vim.api +module.fn = vim.fn + +for name, fns in pairs(module.rpc) do + --- @diagnostic disable-next-line:no-unknown + module[name] = fns end +-- Executes an ex-command. Vimscript errors manifest as client (lua) errors, but +-- v:errmsg will not be updated. +module.command = module.api.nvim_command + +-- Evaluates a Vimscript expression. +-- Fails on Vimscript error, but does not update v:errmsg. +module.eval = module.api.nvim_eval + function module.poke_eventloop() -- Execute 'nvim_eval' (a deferred function) to -- force at least one main_loop iteration - session:request('nvim_eval', '1') + module.api.nvim_eval('1') end function module.buf_lines(bufnr) - return module.exec_lua("return vim.api.nvim_buf_get_lines((...), 0, -1, false)", bufnr) + return module.exec_lua('return vim.api.nvim_buf_get_lines((...), 0, -1, false)', bufnr) end ---@see buf_lines() function module.curbuf_contents() - module.poke_eventloop() -- Before inspecting the buffer, do whatever. - return table.concat(module.curbuf('get_lines', 0, -1, true), '\n') + module.poke_eventloop() -- Before inspecting the buffer, do whatever. + return table.concat(module.api.nvim_buf_get_lines(0, 0, -1, true), '\n') end function module.expect(contents) @@ -697,18 +741,21 @@ function module.expect_any(contents) return ok(nil ~= string.find(module.curbuf_contents(), contents, 1, true)) end +--- @param expected any[] +--- @param received any[] +--- @param kind string +--- @return any function module.expect_events(expected, received, kind) - local inspect = require'vim.inspect' if not pcall(eq, expected, received) then - local msg = 'unexpected '..kind..' received.\n\n' + local msg = 'unexpected ' .. kind .. ' received.\n\n' msg = msg .. 'received events:\n' for _, e in ipairs(received) do - msg = msg .. ' ' .. inspect(e) .. ';\n' + msg = msg .. ' ' .. vim.inspect(e) .. ';\n' end msg = msg .. '\nexpected events:\n' for _, e in ipairs(expected) do - msg = msg .. ' ' .. inspect(e) .. ';\n' + msg = msg .. ' ' .. vim.inspect(e) .. ';\n' end fail(msg) end @@ -723,18 +770,23 @@ end -- Asserts that buffer is loaded and visible in the current tabpage. function module.assert_visible(bufnr, visible) assert(type(visible) == 'boolean') - eq(visible, module.bufmeths.is_loaded(bufnr)) + eq(visible, module.api.nvim_buf_is_loaded(bufnr)) if visible then - assert(-1 ~= module.funcs.bufwinnr(bufnr), - 'expected buffer to be visible in current tabpage: '..tostring(bufnr)) + assert( + -1 ~= module.fn.bufwinnr(bufnr), + 'expected buffer to be visible in current tabpage: ' .. tostring(bufnr) + ) else - assert(-1 == module.funcs.bufwinnr(bufnr), - 'expected buffer NOT visible in current tabpage: '..tostring(bufnr)) + assert( + -1 == module.fn.bufwinnr(bufnr), + 'expected buffer NOT visible in current tabpage: ' .. tostring(bufnr) + ) end end +--- @param path string local function do_rmdir(path) - local stat = luv.fs_stat(path) + local stat = uv.fs_stat(path) if stat == nil then return end @@ -743,44 +795,45 @@ local function do_rmdir(path) end for file in vim.fs.dir(path) do if file ~= '.' and file ~= '..' then - local abspath = path..'/'..file + local abspath = path .. '/' .. file if global_helpers.isdir(abspath) then - do_rmdir(abspath) -- recurse + do_rmdir(abspath) -- recurse else local ret, err = os.remove(abspath) if not ret then if not session then - error('os.remove: '..err) + error('os.remove: ' .. err) else -- Try Nvim delete(): it handles `readonly` attribute on Windows, -- and avoids Lua cross-version/platform incompatibilities. if -1 == module.call('delete', abspath) then - local hint = (is_os('win') - and ' (hint: try :%bwipeout! before rmdir())' or '') - error('delete() failed'..hint..': '..abspath) + local hint = (is_os('win') and ' (hint: try :%bwipeout! before rmdir())' or '') + error('delete() failed' .. hint .. ': ' .. abspath) end end end end end end - local ret, err = luv.fs_rmdir(path) + local ret, err = uv.fs_rmdir(path) if not ret then - error('luv.fs_rmdir('..path..'): '..err) + error('luv.fs_rmdir(' .. path .. '): ' .. err) end end +local start_dir = uv.cwd() + function module.rmdir(path) local ret, _ = pcall(do_rmdir, path) if not ret and is_os('win') then -- Maybe "Permission denied"; try again after changing the nvim -- process to the top-level directory. - module.command([[exe 'cd '.fnameescape(']]..start_dir.."')") + module.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. + if not ret then -- Try again. sleep(1000) do_rmdir(path) end @@ -799,44 +852,49 @@ function module.exc_exec(cmd) return ret end +--- @param cond boolean +--- @param reason string +--- @return boolean function module.skip(cond, reason) if cond then + --- @type fun(reason: string) local pending = getfenv(2).pending pending(reason or 'FIXME') return true - else - return false end + return false end -- Calls pending() and returns `true` if the system is too slow to -- run fragile or expensive tests. Else returns `false`. function module.skip_fragile(pending_fn, cond) - if pending_fn == nil or type(pending_fn) ~= type(function()end) then - error("invalid pending_fn") + 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) + 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) + elseif os.getenv('TEST_SKIP_FRAGILE') then + pending_fn('skipped (TEST_SKIP_FRAGILE)', function() end) return true end return false end function module.exec(code) - module.meths.exec2(code, {}) + module.api.nvim_exec2(code, {}) end +--- @param code string +--- @return string function module.exec_capture(code) - return module.meths.exec2(code, { output = true }).output + return module.api.nvim_exec2(code, { output = true }).output end --- @param code string --- @return any function module.exec_lua(code, ...) - return module.meths.exec_lua(code, {...}) + return module.api.nvim_exec_lua(code, { ... }) end function module.get_pathsep() @@ -845,8 +903,8 @@ end --- Gets the filesystem root dir, namely "/" or "C:/". function module.pathroot() - local pathsep = package.config:sub(1,1) - return is_os('win') and (module.nvim_dir:sub(1,2)..pathsep) or '/' + local pathsep = package.config:sub(1, 1) + return is_os('win') and (module.nvim_dir:sub(1, 2) .. pathsep) or '/' end --- Gets the full `…/build/bin/{name}` path of a test program produced by @@ -868,26 +926,32 @@ end function module.new_pipename() -- HACK: Start a server temporarily, get the name, then stop it. local pipename = module.eval('serverstart()') - module.funcs.serverstop(pipename) + module.fn.serverstop(pipename) -- Remove the pipe so that trying to connect to it without a server listening -- will be an error instead of a hang. os.remove(pipename) return pipename end +--- @param provider string +--- @return string|boolean? function module.missing_provider(provider) - if provider == 'ruby' or provider == 'node' or provider == 'perl' then - local e = module.funcs['provider#'..provider..'#Detect']()[2] + if provider == 'ruby' or provider == 'perl' then + --- @type string? + local e = module.exec_lua("return {require('vim.provider." .. provider .. "').detect()}")[2] return e ~= '' and e or false - elseif provider == 'python' or provider == 'python3' then - local py_major_version = (provider == 'python3' and 3 or 2) - local e = module.funcs['provider#pythonx#Detect'](py_major_version)[2] + elseif provider == 'node' then + --- @type string? + local e = module.fn['provider#node#Detect']()[2] return e ~= '' and e or false - else - assert(false, 'Unknown provider: '..provider) + elseif provider == 'python' then + return module.exec_lua([[return {require('vim.provider.python').detect_by_module('neovim')}]])[2] end + assert(false, 'Unknown provider: ' .. provider) end +--- @param obj string|table +--- @return any function module.alter_slashes(obj) if not is_os('win') then return obj @@ -896,14 +960,14 @@ function module.alter_slashes(obj) local ret = obj:gsub('/', '\\') return ret elseif type(obj) == 'table' then - local ret = {} + --- @cast obj table<any,any> + local ret = {} --- @type table<any,any> for k, v in pairs(obj) do ret[k] = module.alter_slashes(v) end return ret - else - assert(false, 'expected string or table of strings, got '..type(obj)) end + assert(false, 'expected string or table of strings, got ' .. type(obj)) end local load_factor = 1 @@ -913,19 +977,26 @@ if global_helpers.is_ci() then module.request('nvim_command', 'source test/old/testdir/load.vim') load_factor = module.request('nvim_eval', 'g:test_load_factor') end + +--- @param num number +--- @return number function module.load_adjust(num) return math.ceil(num * load_factor) end +--- @param ctx table<string,any> +--- @return table function module.parse_context(ctx) - local parsed = {} - for _, item in ipairs({'regs', 'jumps', 'bufs', 'gvars'}) do - parsed[item] = filter(function(v) + local parsed = {} --- @type table<string,any> + for _, item in ipairs({ 'regs', 'jumps', 'bufs', 'gvars' }) do + --- @param v any + parsed[item] = vim.tbl_filter(function(v) return type(v) == 'table' end, module.call('msgpackparse', ctx[item])) end parsed['bufs'] = parsed['bufs'][1] - return map(function(v) + --- @param v any + return vim.tbl_map(function(v) if #v == 0 then return nil end @@ -935,25 +1006,30 @@ end function module.add_builddir_to_rtp() -- Add runtime from build dir for doc/tags (used with :help). - module.command(string.format([[set rtp+=%s/runtime]], module.test_build_dir)) + module.command(string.format([[set rtp+=%s/runtime]], module.paths.test_build_dir)) end --- Kill process with given pid +--- Kill (reap) a process by PID. +--- @param pid string +--- @return boolean? function module.os_kill(pid) - return os.execute((is_os('win') - and 'taskkill /f /t /pid '..pid..' > nul' - or 'kill -9 '..pid..' > /dev/null')) + return os.execute( + ( + is_os('win') and 'taskkill /f /t /pid ' .. pid .. ' > nul' + or 'kill -9 ' .. pid .. ' > /dev/null' + ) + ) end --- Create folder with non existing parents +--- Create folder with non existing parents +--- @param path string +--- @return boolean? function module.mkdir_p(path) - return os.execute((is_os('win') - and 'mkdir '..path - or 'mkdir -p '..path)) + return os.execute((is_os('win') and 'mkdir ' .. path or 'mkdir -p ' .. path)) end --- @class test.functional.helpers: test.helpers -module = global_helpers.tbl_extend('error', module, global_helpers) +module = vim.tbl_extend('error', module, global_helpers) --- @return test.functional.helpers return function(after_each) @@ -964,7 +1040,7 @@ return function(after_each) if session then local msg = session:next_message(0) if msg then - if msg[1] == "notification" and msg[2] == "nvim_error_event" then + if msg[1] == 'notification' and msg[2] == 'nvim_error_event' then error(msg[3][2]) end end |