mirror of
https://github.com/neovim/neovim.git
synced 2026-07-31 04:39:07 +00:00
test: improve Nvim EOF handling (for Windows CI) #40841
Problem:
Mysterious Windows CI failure points to resource exhaustion, but we have
no visibility/instrumentation:
RUN T610 nvim_set_keymap, nvim_del_keymap can set mappings with special characters, lhs: <S-Left>, rhs: <S-Left>: 93.70 ms OK
RUN T611 nvim_set_keymap, nvim_del_keymap can set mappings with special characters, lhs: <S-Left>, rhs: <F12><F2><Tab>: 49.48 ms FAIL
…\testnvim.lua:144: EOF was received from Nvim. Likely the Nvim process crashed.
stack traceback:
D:/a/neovim/neovim/test/functional/testnvim.lua:144: in function 'nvim_set_keymap'
…/api/keymap_spec.lua:769: in function <…/api/keymap_spec.lua:768>
RUN T612 nvim_set_keymap, nvim_del_keymap can set mappings with special characters, lhs: <S-Left>, rhs: <Space><Tab>: 18.49 ms FAIL
…\testnvim.lua:144: EOF was received from Nvim. Likely the Nvim process crashed.
stack traceback:
D:/a/neovim/neovim/test/functional/testnvim.lua:144: in function 'nvim_set_keymap'
…/api/keymap_spec.lua:769: in function <.../build/Xtest_xdg_api/test/functional/api/keymap_spec.lua:768>
Solution:
Append the child exit-code and signal to the "EOF … crashed" message.
Example:
Nvim EOF (crash?) exit code: 42 (0x0000002A)
On the next failing Windows run, the first crash line will classify the failure:
- 0xC0000005 → access violation (nvim bug)
- 0xC0000374 → heap corruption (nvim bug)
- spawn/resource error or "clean" exit-code → system resource exhaustion
This commit is contained in:
@@ -14,7 +14,9 @@ local RpcStream = require('test.client.rpc_stream')
|
||||
--- @field private _timer uv.uv_timer_t
|
||||
--- @field private _is_running boolean true during `Session:run()` scope.
|
||||
--- @field data table Arbitrary user data.
|
||||
local Session = {}
|
||||
local Session = {
|
||||
eof_err_msg = 'Nvim EOF (crash?)',
|
||||
}
|
||||
Session.__index = Session
|
||||
if package.loaded['jit'] then
|
||||
-- luajit pcall is already coroutine safe
|
||||
@@ -227,15 +229,10 @@ function Session:_run(request_cb, notification_cb, timeout)
|
||||
self._prepare:stop()
|
||||
end)
|
||||
end
|
||||
local got_eof = false
|
||||
self._rpc_stream:read_start(request_cb, notification_cb, function()
|
||||
uv.stop()
|
||||
|
||||
--- @diagnostic disable-next-line: invisible
|
||||
local stderr = self._rpc_stream._stream.stderr --[[@as string?]]
|
||||
-- See if `ProcStream.stderr` has anything useful.
|
||||
stderr = '' ~= ((stderr or ''):match('^%s*(.*%S)') or '') and ' stderr:\n' .. stderr or ''
|
||||
|
||||
self.eof_err = { 1, 'EOF was received from Nvim. Likely the Nvim process crashed.' .. stderr }
|
||||
got_eof = true
|
||||
end)
|
||||
local ret, err, _ = uv.run()
|
||||
if ret == nil then
|
||||
@@ -244,6 +241,38 @@ function Session:_run(request_cb, notification_cb, timeout)
|
||||
self._prepare:stop()
|
||||
self._timer:stop()
|
||||
self._rpc_stream:read_stop()
|
||||
if got_eof then
|
||||
self.eof_err = { 1, self:_eof_msg() }
|
||||
end
|
||||
end
|
||||
|
||||
--- Message shown on unexpected stdout EOF.
|
||||
--- @private
|
||||
--- @return string
|
||||
function Session:_eof_msg()
|
||||
local msg = self.eof_err_msg
|
||||
--- @diagnostic disable-next-line: access-invisible
|
||||
local proc = self._rpc_stream._stream --[[@as test.ProcStream]]
|
||||
|
||||
-- ProcStream (spawned child) has stderr + exit-code; SocketStream (a socket peer) does not.
|
||||
if proc.stderr ~= nil then
|
||||
-- stdout EOF and process-exit are separate libuv events. Wait for up to 1s.
|
||||
vim.wait(1000, function()
|
||||
return proc.status ~= nil or proc.signal ~= nil
|
||||
end)
|
||||
if proc.status ~= nil then
|
||||
msg = ('%s exit code: %d (0x%08X)'):format(msg, proc.status, proc.status % 2 ^ 32)
|
||||
end
|
||||
if proc.signal ~= nil and proc.signal ~= 0 then
|
||||
msg = ('%s signal: %d'):format(msg, proc.signal)
|
||||
end
|
||||
end
|
||||
|
||||
local stderr = proc.stderr --[[@as string?]]
|
||||
if '' ~= ((stderr or ''):match('^%s*(.*%S)') or '') then
|
||||
msg = msg .. ' stderr:\n' .. stderr
|
||||
end
|
||||
return msg
|
||||
end
|
||||
|
||||
--- Nvim msgpack-RPC session.
|
||||
|
||||
@@ -9,7 +9,6 @@ local SocketStream = uv_stream.SocketStream
|
||||
local ProcStream = uv_stream.ProcStream
|
||||
|
||||
local check_cores = t.check_cores
|
||||
local pcall_err = t.pcall_err
|
||||
local check_logs = t.check_logs
|
||||
local dedent = t.dedent
|
||||
local eq = t.eq
|
||||
@@ -133,7 +132,7 @@ end
|
||||
--- @return any
|
||||
function M.request(method, ...)
|
||||
assert(session, 'no Nvim session')
|
||||
assert(not session.eof_err, 'sending request after EOF from Nvim')
|
||||
assert(not session.eof_err, 'RPC request after Nvim EOF')
|
||||
local status, rv = session:request(method, ...)
|
||||
if not status then
|
||||
if loop_running then
|
||||
@@ -331,12 +330,11 @@ end
|
||||
-- Use for commands which expect nvim to quit.
|
||||
-- The first argument can also be a timeout.
|
||||
function M.expect_exit(fn_or_timeout, ...)
|
||||
local eof_err_msg = 'EOF was received from Nvim. Likely the Nvim process crashed.'
|
||||
if type(fn_or_timeout) == 'function' then
|
||||
t.matches(vim.pesc(eof_err_msg), t.pcall_err(fn_or_timeout, ...))
|
||||
t.matches(vim.pesc(Session.eof_err_msg), t.pcall_err(fn_or_timeout, ...))
|
||||
else
|
||||
t.matches(
|
||||
vim.pesc(eof_err_msg),
|
||||
vim.pesc(Session.eof_err_msg),
|
||||
t.pcall_err(function(timeout, fn, ...)
|
||||
fn(...)
|
||||
assert(session)
|
||||
|
||||
Reference in New Issue
Block a user