fix(tests): use uv.spawn instead of io.popen for unittest helpers

The old implementation of repeated_read_cmd would attempt to run the
command multiple times to handle racyness of async output. Code
like this should not be written. Instead, use the libuv event
loop to read until the process has exited and the pipe has been closed.

This causes some previous discarded errors to be propagated. Fix these
as well.
This commit is contained in:
bfredl
2025-05-20 12:10:37 +02:00
parent 272e041780
commit c81af9428c
4 changed files with 40 additions and 31 deletions

View File

@ -5,17 +5,6 @@ local Paths = require('test.cmakeconfig.paths')
luaassert:set_parameter('TableFormatLevel', 100) luaassert:set_parameter('TableFormatLevel', 100)
local quote_me = '[^.%w%+%-%@%_%/]' -- complement (needn't quote)
--- @param str string
--- @return string
local function shell_quote(str)
if string.find(str, quote_me) or str == '' then
return '"' .. str:gsub('[$%%"\\]', '\\%0') .. '"'
end
return str
end
--- Functions executing in the context of the test runner (not the current nvim test session). --- Functions executing in the context of the test runner (not the current nvim test session).
--- @class test.testutil --- @class test.testutil
local M = { local M = {
@ -66,19 +55,15 @@ function M.argss_to_cmd(...)
for i = 1, select('#', ...) do for i = 1, select('#', ...) do
local arg = select(i, ...) local arg = select(i, ...)
if type(arg) == 'string' then if type(arg) == 'string' then
cmd[#cmd + 1] = shell_quote(arg) cmd[#cmd + 1] = arg
else else
--- @cast arg string[] --- @cast arg string[]
for _, subarg in ipairs(arg) do for _, subarg in ipairs(arg) do
cmd[#cmd + 1] = shell_quote(subarg) cmd[#cmd + 1] = subarg
end end
end end
end end
return table.concat(cmd, ' ') return cmd
end
function M.popen_r(...)
return io.popen(M.argss_to_cmd(...), 'r')
end end
--- Calls fn() until it succeeds, up to `max` times or until `max_ms` --- Calls fn() until it succeeds, up to `max` times or until `max_ms`
@ -356,7 +341,7 @@ function M.check_logs()
local status, f local status, f
local out = io.stdout local out = io.stdout
if os.getenv('SYMBOLIZER') then if os.getenv('SYMBOLIZER') then
status, f = pcall(M.popen_r, os.getenv('SYMBOLIZER'), '-l', file) status, f = pcall(M.repeated_read_cmd, os.getenv('SYMBOLIZER'), '-l', file)
end end
out:write(start_msg .. '\n') out:write(start_msg .. '\n')
if status then if status then
@ -539,16 +524,37 @@ end
--- @return string? --- @return string?
function M.repeated_read_cmd(...) function M.repeated_read_cmd(...)
for _ = 1, 10 do local cmd = M.argss_to_cmd(...)
local stream = M.popen_r(...) local data = {}
local ret = stream:read('*a') local got_code = nil
stream:close() local stdout = assert(vim.uv.new_pipe(false))
if ret then local handle = assert(
return ret vim.uv.spawn(
cmd[1],
{ args = vim.list_slice(cmd, 2), stdio = { nil, stdout, 2 }, hide = true },
function(code, _signal)
got_code = code
end
)
)
stdout:read_start(function(err, chunk)
if err or chunk == nil then
stdout:read_stop()
stdout:close()
else
table.insert(data, chunk)
end end
end)
while not stdout:is_closing() or got_code == nil do
vim.uv.run('once')
end end
print('ERROR: Failed to execute ' .. M.argss_to_cmd(...) .. ': nil return after 10 attempts')
return nil if got_code ~= 0 then
error('command ' .. vim.inspect(cmd) .. 'unexpectedly exited with status ' .. got_code)
end
handle:close()
return table.concat(data)
end end
--- @generic T --- @generic T

View File

@ -18,7 +18,7 @@ local type_key = api_t.type_key
local obj2lua = api_t.obj2lua local obj2lua = api_t.obj2lua
local func_type = api_t.func_type local func_type = api_t.func_type
local api = cimport('./src/nvim/api/private/t.h', './src/nvim/api/private/converter.h') local api = cimport('./src/nvim/api/private/helpers.h', './src/nvim/api/private/converter.h')
describe('vim_to_object', function() describe('vim_to_object', function()
local vim_to_object = function(l) local vim_to_object = function(l)

View File

@ -13,8 +13,11 @@ local int_type = t_eval.int_type
local flt_type = t_eval.flt_type local flt_type = t_eval.flt_type
local type_key = t_eval.type_key local type_key = t_eval.type_key
local api = local api = cimport(
cimport('./src/nvim/api/private/defs.h', './src/nvim/api/private/t.h', './src/nvim/memory.h') './src/nvim/api/private/defs.h',
'./src/nvim/api/private/helpers.h',
'./src/nvim/memory.h'
)
local obj2lua local obj2lua

View File

@ -94,7 +94,7 @@ local vterm = t.cimport(
'./src/nvim/vterm/screen.h', './src/nvim/vterm/screen.h',
'./src/nvim/vterm/state.h', './src/nvim/vterm/state.h',
'./src/nvim/vterm/vterm.h', './src/nvim/vterm/vterm.h',
'./src/nvim/vterm/vterm_internal.h', './src/nvim/vterm/vterm_internal_defs.h',
'./test/unit/fixtures/vterm_test.h' './test/unit/fixtures/vterm_test.h'
) )