test: move exec_lua logic to separate module

By making it a separate module, the embedded Nvim session can require
this module directly instead of setup code sending over the module via
RPC.

Also make exec_lua wrap _G.print so messages can be seen
in the test output immediately as the exec_lua returns.
This commit is contained in:
Lewis Russell
2024-11-18 17:15:05 +00:00
committed by Lewis Russell
parent 01026ba47b
commit 534544cbf7
2 changed files with 149 additions and 121 deletions

View File

@@ -800,81 +800,6 @@ function M.exec_capture(code)
return M.api.nvim_exec2(code, { output = true }).output
end
--- @param f function
--- @return table<string,any>
local function get_upvalues(f)
local i = 1
local upvalues = {} --- @type table<string,any>
while true do
local n, v = debug.getupvalue(f, i)
if not n then
break
end
upvalues[n] = v
i = i + 1
end
return upvalues
end
--- @param f function
--- @param upvalues table<string,any>
local function set_upvalues(f, upvalues)
local i = 1
while true do
local n = debug.getupvalue(f, i)
if not n then
break
end
if upvalues[n] then
debug.setupvalue(f, i, upvalues[n])
end
i = i + 1
end
end
--- @type fun(f: function): table<string,any>
_G.__get_upvalues = nil
--- @type fun(f: function, upvalues: table<string,any>)
_G.__set_upvalues = nil
--- @param self table<string,function>
--- @param bytecode string
--- @param upvalues table<string,any>
--- @param ... any[]
--- @return any[] result
--- @return table<string,any> upvalues
local function exec_lua_handler(self, bytecode, upvalues, ...)
local f = assert(loadstring(bytecode))
self.set_upvalues(f, upvalues)
local ret = { f(...) } --- @type any[]
--- @type table<string,any>
local new_upvalues = self.get_upvalues(f)
do -- Check return value types for better error messages
local invalid_types = {
['thread'] = true,
['function'] = true,
['userdata'] = true,
}
for k, v in pairs(ret) do
if invalid_types[type(v)] then
error(
string.format(
"Return index %d with value '%s' of type '%s' cannot be serialized over RPC",
k,
tostring(v),
type(v)
)
)
end
end
end
return ret, new_upvalues
end
--- Execute Lua code in the wrapped Nvim session.
---
--- When `code` is passed as a function, it is converted into Lua byte code.
@@ -921,52 +846,7 @@ function M.exec_lua(code, ...)
end
assert(session, 'no Nvim session')
if not session.exec_lua_setup then
assert(
session:request(
'nvim_exec_lua',
[[
_G.__test_exec_lua = {
get_upvalues = loadstring((select(1,...))),
set_upvalues = loadstring((select(2,...))),
handler = loadstring((select(3,...)))
}
setmetatable(_G.__test_exec_lua, { __index = _G.__test_exec_lua })
]],
{ string.dump(get_upvalues), string.dump(set_upvalues), string.dump(exec_lua_handler) }
)
)
session.exec_lua_setup = true
end
local stat, rv = session:request(
'nvim_exec_lua',
'return { _G.__test_exec_lua:handler(...) }',
{ string.dump(code), get_upvalues(code), ... }
)
if not stat then
error(rv[2])
end
--- @type any[], table<string,any>
local ret, upvalues = unpack(rv)
-- Update upvalues
if next(upvalues) then
local caller = debug.getinfo(2)
local f = caller.func
-- On PUC-Lua, if the function is a tail call, then func will be nil.
-- In this case we need to use the current function.
if not f then
assert(caller.source == '=(tail call)')
f = debug.getinfo(1).func
end
set_upvalues(f, upvalues)
end
return unpack(ret, 1, table.maxn(ret))
return require('test.functional.testnvim.exec_lua')(session, 2, code, ...)
end
function M.get_pathsep()