neovim/test/functional/helpers.lua

452 lines
10 KiB
Lua

require('coxpcall')
local ffi = require('ffi')
local lfs = require('lfs')
local assert = require('luassert')
local Loop = require('nvim.loop')
local MsgpackStream = require('nvim.msgpack_stream')
local AsyncSession = require('nvim.async_session')
local Session = require('nvim.session')
local nvim_prog = os.getenv('NVIM_PROG') or 'build/bin/nvim'
local nvim_argv = {nvim_prog, '-u', 'NONE', '-i', 'NONE', '-N',
'--cmd', 'set shortmess+=I background=light noswapfile noautoindent laststatus=1 undodir=. directory=. viewdir=. backupdir=.',
'--embed'}
-- Formulate a path to the directory containing nvim. We use this to
-- help run test executables. It helps to keep the tests working, even
-- when the build is not in the default location.
local nvim_dir = nvim_prog:gsub("[/\\][^/\\]+$", "")
if nvim_dir == nvim_prog then
nvim_dir = "."
end
-- Nvim "Unit Under Test" http://en.wikipedia.org/wiki/Device_under_test
local NvimUUT = {}
NvimUUT.__index = NvimUUT
local prepend_argv
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=.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}
end
if prepend_argv then
local new_nvim_argv = {}
local len = #prepend_argv
for i = 1, len do
new_nvim_argv[i] = prepend_argv[i]
end
for i = 1, #nvim_argv do
new_nvim_argv[i + len] = nvim_argv[i]
end
nvim_argv = new_nvim_argv
end
local session, loop_running, last_error
local function set_session(s)
session = s
end
local function request(method, ...)
local status, rv = session:request(method, ...)
if not status then
if loop_running then
last_error = rv[2]
session:stop()
else
error(rv[2])
end
end
return rv
end
local function next_message()
return session:next_message()
end
local function call_and_stop_on_error(...)
local status, result = copcall(...) -- luacheck: ignore
if not status then
session:stop()
last_error = result
return ''
end
return result
end
local function run(request_cb, notification_cb, setup_cb, timeout)
local on_request, on_notification, on_setup
if request_cb then
function on_request(method, args)
return call_and_stop_on_error(request_cb, method, args)
end
end
if notification_cb then
function on_notification(method, args)
call_and_stop_on_error(notification_cb, method, args)
end
end
if setup_cb then
function on_setup()
call_and_stop_on_error(setup_cb)
end
end
loop_running = true
session:run(on_request, on_notification, on_setup, timeout)
loop_running = false
if last_error then
local err = last_error
last_error = nil
error(err)
end
end
local function stop()
session:stop()
end
local function nvim_command(cmd)
request('vim_command', cmd)
end
local function nvim_eval(expr)
return request('vim_eval', expr)
end
local function nvim_call(name, ...)
return request('vim_call_function', name, {...})
end
local function nvim_feed(input)
while #input > 0 do
local written = request('vim_input', input)
input = input:sub(written + 1)
end
end
local function dedent(str)
-- find minimum common indent across lines
local indent = nil
for line in str:gmatch('[^\n]+') do
local line_indent = line:match('^%s+') or ''
if indent == nil or #line_indent < #indent then
indent = line_indent
end
end
if #indent == 0 then
-- no minimum common indent
return str
end
-- create a pattern for the indent
indent = indent:gsub('%s', '%%s')
-- strip it from the first line
str = str:gsub('^'..indent, '')
-- strip it from the remaining lines
str = str:gsub('[\n]'..indent, '\n')
return str
end
local function feed(...)
for _, v in ipairs({...}) do
nvim_feed(dedent(v))
end
end
local function rawfeed(...)
for _, v in ipairs({...}) do
nvim_feed(dedent(v))
end
end
local function merge_args(...)
local i = 1
local argv = {}
for anum = 1,select('#', ...) do
local args = select(anum, ...)
if args then
for _, arg in ipairs(args) do
argv[i] = arg
i = i + 1
end
end
end
return argv
end
local function spawn(argv, merge)
local loop = Loop.new()
local msgpack_stream = MsgpackStream.new(loop)
local async_session = AsyncSession.new(msgpack_stream)
local sess = Session.new(async_session)
loop:spawn(merge and merge_args(prepend_argv, argv) or argv)
return sess
end
local function clear(extra_cmd)
if session then
session:exit(0)
end
local args = {unpack(nvim_argv)}
if extra_cmd ~= nil then
table.insert(args, '--cmd')
table.insert(args, extra_cmd)
end
session = spawn(args)
end
local function insert(...)
nvim_feed('i')
for _, v in ipairs({...}) do
local escaped = v:gsub('<', '<lt>')
rawfeed(escaped)
end
nvim_feed('<ESC>')
end
local function execute(...)
for _, v in ipairs({...}) do
if v:sub(1, 1) ~= '/' then
-- not a search command, prefix with colon
nvim_feed(':')
end
nvim_feed(v:gsub('<', '<lt>'))
nvim_feed('<CR>')
end
end
-- Dedent the given text and write it to the file name.
local function write_file(name, text, dont_dedent)
local file = io.open(name, 'w')
if not dont_dedent then
text = dedent(text)
end
file:write(text)
file:flush()
file:close()
end
local function source(code)
local tmpname = os.tmpname()
if ffi.os == 'OSX' and string.match(tmpname, '^/tmp') then
tmpname = '/private'..tmpname
end
write_file(tmpname, code)
nvim_command('source '..tmpname)
os.remove(tmpname)
return tmpname
end
local function eq(expected, actual)
return assert.are.same(expected, actual)
end
local function neq(expected, actual)
return assert.are_not.same(expected, actual)
end
local function ok(expr)
assert.is_true(expr)
end
local function nvim(method, ...)
return request('vim_'..method, ...)
end
local function nvim_async(method, ...)
session:notify('vim_'..method, ...)
end
local function buffer(method, ...)
return request('buffer_'..method, ...)
end
local function window(method, ...)
return request('window_'..method, ...)
end
local function tabpage(method, ...)
return request('tabpage_'..method, ...)
end
local function curbuf(method, ...)
local buf = nvim('get_current_buffer')
if not method then
return buf
end
return buffer(method, buf, ...)
end
local function wait()
session:request('vim_eval', '1')
end
local function curbuf_contents()
-- Before inspecting the buffer, execute 'vim_eval' to wait until all
-- previously sent keys are processed(vim_eval is a deferred function, and
-- only processed after all input)
wait()
return table.concat(curbuf('get_line_slice', 0, -1, true, true), '\n')
end
local function curwin(method, ...)
local win = nvim('get_current_window')
if not method then
return win
end
return window(method, win, ...)
end
local function curtab(method, ...)
local tab = nvim('get_current_tabpage')
if not method then
return tab
end
return tabpage(method, tab, ...)
end
local function expect(contents)
return eq(dedent(contents), curbuf_contents())
end
local function os_is_windows()
return nvim_eval('has("win32")') == 1
end
local function rmdir(path)
if lfs.attributes(path, 'mode') ~= 'directory' then
return nil
end
for file in lfs.dir(path) do
if file == '.' or file == '..' then
goto continue
end
local ret, err = os.remove(path..'/'..file)
if not ret then
error('os.remove: '..err)
return nil
end
::continue::
end
local ret, err = os.remove(path)
if not ret then
error('os.remove: '..err)
end
return ret
end
local exc_exec = function(cmd)
nvim_command(([[
try
execute "%s"
catch
let g:__exception = v:exception
endtry
]]):format(cmd:gsub('\n', '\\n'):gsub('[\\"]', '\\%0')))
local ret = nvim_eval('get(g:, "__exception", 0)')
nvim_command('unlet! g:__exception')
return ret
end
local function redir_exec(cmd)
nvim_command(([[
redir => g:__output
silent! execute "%s"
redir END
]]):format(cmd:gsub('\n', '\\n'):gsub('[\\"]', '\\%0')))
local ret = nvim_eval('get(g:, "__output", 0)')
nvim_command('unlet! g:__output')
return ret
end
local function create_callindex(func)
local table = {}
setmetatable(table, {
__index = function(tbl, arg1)
local ret = function(...) return func(arg1, ...) end
tbl[arg1] = ret
return ret
end,
})
return table
end
local funcs = create_callindex(nvim_call)
local meths = create_callindex(nvim)
local bufmeths = create_callindex(buffer)
local winmeths = create_callindex(window)
local tabmeths = create_callindex(tabpage)
local curbufmeths = create_callindex(curbuf)
local curwinmeths = create_callindex(curwin)
local curtabmeths = create_callindex(curtab)
return {
prepend_argv = prepend_argv,
clear = clear,
spawn = spawn,
dedent = dedent,
source = source,
rawfeed = rawfeed,
insert = insert,
feed = feed,
execute = execute,
eval = nvim_eval,
call = nvim_call,
command = nvim_command,
request = request,
next_message = next_message,
run = run,
stop = stop,
eq = eq,
neq = neq,
expect = expect,
ok = ok,
nvim = nvim,
nvim_async = nvim_async,
nvim_prog = nvim_prog,
nvim_dir = nvim_dir,
buffer = buffer,
window = window,
tabpage = tabpage,
curbuf = curbuf,
curwin = curwin,
curtab = curtab,
curbuf_contents = curbuf_contents,
wait = wait,
set_session = set_session,
write_file = write_file,
os_is_windows = os_is_windows,
rmdir = rmdir,
mkdir = lfs.mkdir,
exc_exec = exc_exec,
redir_exec = redir_exec,
merge_args = merge_args,
funcs = funcs,
meths = meths,
bufmeths = bufmeths,
winmeths = winmeths,
tabmeths = tabmeths,
curbufmeths = curbufmeths,
curwinmeths = curwinmeths,
curtabmeths = curtabmeths,
}