local uv = vim.uv
local t = require('test.testutil')
---@type test.harness
local harness = require('test.harness')
local Session = require('test.client.session')
local uv_stream = require('test.client.uv_stream')
local SocketStream = uv_stream.SocketStream
local ProcStream = uv_stream.ProcStream
local check_cores = t.check_cores
local check_logs = t.check_logs
local dedent = t.dedent
local eq = t.eq
local is_os = t.is_os
local ok = t.ok
local sleep = uv.sleep
--- Functions executing in the current nvim session/process being tested.
local M = {}
M.runtime_set = 'set runtimepath^=' .. t.paths.test_build_dir .. '/lib/nvim'
M.nvim_prog = (os.getenv('NVIM_PRG') or t.paths.test_build_dir .. '/bin/nvim')
-- Default settings for the test session.
M.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 shada=!,'100,<50,s10,h"
.. [[ statusline=%<%f\ %{%nvim_eval_statusline('%h%w%m%r',\ {'maxwidth':\ 30}).width\ >\ 0\ ?\ '%h%w%m%r\ '\ :\ ''%}%=%{%\ &showcmdloc\ ==\ 'statusline'\ ?\ '%-10.S\ '\ :\ ''\ %}%{%\ exists('b:keymap_name')\ ?\ '<'..b:keymap_name..'>\ '\ :\ ''\ %}%{%\ &ruler\ ?\ &rulerformat\ :\ ''\ %}]]
.. ' rulerformat=%18(%l,%c%V%=%P%)'
)
M.nvim_argv = {
M.nvim_prog,
'-u',
'NONE',
'-i',
'NONE',
-- XXX: find treesitter parsers.
'--cmd',
M.runtime_set,
'--cmd',
M.nvim_set,
-- Remove default user commands and mappings.
'--cmd',
'comclear | 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',
}
if os.getenv('OSV_PORT') then
table.insert(M.nvim_argv, '--cmd')
table.insert(
M.nvim_argv,
string.format(
"lua require('osv').launch({ port = %s, blocking = true })",
os.getenv('OSV_PORT')
)
)
end
-- Directory containing nvim.
M.nvim_dir = M.nvim_prog:gsub('[/\\][^/\\]+$', '')
if M.nvim_dir == M.nvim_prog then
M.nvim_dir = '.'
end
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,
}
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 = os.getenv('GDBSERVER_PORT') or '7777'
prepend_argv = { 'gdbserver', 'localhost:' .. gdbserver_port }
end
if prepend_argv then
local new_nvim_argv = {} --- @type string[]
local len = #prepend_argv
for i = 1, len do
new_nvim_argv[i] = prepend_argv[i]
end
for i = 1, #M.nvim_argv do
new_nvim_argv[i + len] = M.nvim_argv[i]
end
M.nvim_argv = new_nvim_argv
M.prepend_argv = prepend_argv
end
local session --- @type test.Session?
local sessions = {} --- @type table<test.Session, true>
local sigpipe_handler --- @type uv.uv_signal_t?
local loop_running --- @type boolean?
local last_error --- @type string?
local method_error --- @type string?
if not is_os('win') then
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
function M.get_session()
return session
end
function M.set_session(s)
session = s
end
--- @param method string
--- @param ... any
--- @return any
function M.request(method, ...)
assert(session, 'no Nvim session')
assert(not session.eof_err, 'RPC request after Nvim EOF')
local status, rv = session:request(method, ...)
if not status then
if loop_running then
--- @type string
last_error = rv[2]
session:stop()
else
error(rv[2])
end
end
return rv
end
--- @param method string
--- @param ... any
--- @return any
function M.request_lua(method, ...)
return M.exec_lua([[return vim.api[...](select(2, ...))]], method, ...)
end
--- @param timeout? integer
--- @return string?
function M.next_msg(timeout)
assert(session)
return session:next_message(timeout or 10000)
end
function M.expect_twostreams(msgs1, msgs2)
local pos1, pos2 = 1, 1
while pos1 <= #msgs1 or pos2 <= #msgs2 do
local msg = M.next_msg()
if pos1 <= #msgs1 and pcall(eq, msgs1[pos1], msg) then
pos1 = pos1 + 1
elseif pos2 <= #msgs2 then
eq(msgs2[pos2], msg)
pos2 = pos2 + 1
else
-- already failed, but show the right error message
eq(msgs1[pos1], msg)
end
end
end
-- Expects a sequence of next_msg() results. If multiple sequences are
-- passed they are tried until one succeeds, in order of shortest to longest.
--
-- Can be called with positional args (list of sequences only):
-- expect_msg_seq(seq1, seq2, ...)
-- or keyword args:
-- expect_msg_seq{ignore={...}, seqs={seq1, seq2, ...}}
--
-- ignore: List of ignored event names.
-- seqs: List of one or more potential event sequences.
function M.expect_msg_seq(...)
if select(
|