aboutsummaryrefslogtreecommitdiff
path: root/test/unit/helpers.lua
diff options
context:
space:
mode:
authorJosh Rahm <joshuarahm@gmail.com>2023-11-29 21:52:58 +0000
committerJosh Rahm <joshuarahm@gmail.com>2023-11-29 21:52:58 +0000
commit931bffbda3668ddc609fc1da8f9eb576b170aa52 (patch)
treed8c1843a95da5ea0bb4acc09f7e37843d9995c86 /test/unit/helpers.lua
parent142d9041391780ac15b89886a54015fdc5c73995 (diff)
parent4a8bf24ac690004aedf5540fa440e788459e5e34 (diff)
downloadrneovim-931bffbda3668ddc609fc1da8f9eb576b170aa52.tar.gz
rneovim-931bffbda3668ddc609fc1da8f9eb576b170aa52.tar.bz2
rneovim-931bffbda3668ddc609fc1da8f9eb576b170aa52.zip
Merge remote-tracking branch 'upstream/master' into userreguserreg
Diffstat (limited to 'test/unit/helpers.lua')
-rw-r--r--test/unit/helpers.lua344
1 files changed, 199 insertions, 145 deletions
diff --git a/test/unit/helpers.lua b/test/unit/helpers.lua
index 14d8eda357..8d581dac49 100644
--- a/test/unit/helpers.lua
+++ b/test/unit/helpers.lua
@@ -14,20 +14,15 @@ local map = global_helpers.tbl_map
local eq = global_helpers.eq
local trim = global_helpers.trim
--- C constants.
-local NULL = ffi.cast('void*', 0)
-
-local OK = 1
-local FAIL = 0
-
-local cimport
-
-- add some standard header locations
for _, p in ipairs(Paths.include_paths) do
Preprocess.add_to_include_path(p)
end
-local child_pid = nil
+local child_pid = nil --- @type integer
+--- @generic F: function
+--- @param func F
+--- @return F
local function only_separate(func)
return function(...)
if child_pid ~= 0 then
@@ -36,9 +31,20 @@ local function only_separate(func)
return func(...)
end
end
-local child_calls_init = {}
-local child_calls_mod = nil
-local child_calls_mod_once = nil
+
+--- @class ChildCall
+--- @field func function
+--- @field args any[]
+
+--- @class ChildCallLog
+--- @field func string
+--- @field args any[]
+--- @field ret any?
+
+local child_calls_init = {} --- @type ChildCall[]
+local child_calls_mod = nil --- @type ChildCall[]
+local child_calls_mod_once = nil --- @type ChildCall[]?
+
local function child_call(func, ret)
return function(...)
local child_calls = child_calls_mod or child_calls_init
@@ -53,16 +59,16 @@ end
-- Run some code at the start of the child process, before running the test
-- itself. Is supposed to be run in `before_each`.
+--- @param func function
local function child_call_once(func, ...)
if child_pid ~= 0 then
- child_calls_mod_once[#child_calls_mod_once + 1] = {
- func=func, args={...}}
+ child_calls_mod_once[#child_calls_mod_once + 1] = { func = func, args = {...} }
else
func(...)
end
end
-local child_cleanups_mod_once = nil
+local child_cleanups_mod_once = nil --- @type ChildCall[]?
-- Run some code at the end of the child process, before exiting. Is supposed to
-- be run in `before_each` because `after_each` is run after child has exited.
@@ -75,7 +81,8 @@ local function child_cleanup_once(func, ...)
end
end
-local libnvim = nil
+-- Unittests are run from debug nvim binary in lua interpreter mode.
+local libnvim = ffi.C
local lib = setmetatable({}, {
__index = only_separate(function(_, idx)
@@ -87,13 +94,9 @@ local lib = setmetatable({}, {
})
local init = only_separate(function()
- -- load neovim shared library
- libnvim = ffi.load(Paths.test_libnvim_path)
for _, c in ipairs(child_calls_init) do
c.func(unpack(c.args))
end
- libnvim.time_init()
- libnvim.fs_init()
libnvim.event_init()
libnvim.early_init(nil)
if child_calls_mod then
@@ -126,15 +129,19 @@ local pragma_pack_id = 1
-- some things are just too complex for the LuaJIT C parser to digest. We
-- usually don't need them anyway.
+--- @param body string
local function filter_complex_blocks(body)
- local result = {}
+ local result = {} --- @type string[]
for line in body:gmatch("[^\r\n]+") do
if not (string.find(line, "(^)", 1, true) ~= nil
or string.find(line, "_ISwupper", 1, true)
or string.find(line, "_Float")
+ or string.find(line, "__s128")
+ or string.find(line, "__u128")
or string.find(line, "msgpack_zone_push_finalizer")
or string.find(line, "msgpack_unpacker_reserve_buffer")
+ or string.find(line, "value_init_")
or string.find(line, "UUID_NULL") -- static const uuid_t UUID_NULL = {...}
or string.find(line, "inline _Bool")) then
result[#result + 1] = line
@@ -149,19 +156,25 @@ local cdef = ffi.cdef
local cimportstr
-local previous_defines_init = ''
-local preprocess_cache_init = {}
+local previous_defines_init = [[
+typedef struct { char bytes[16]; } __attribute__((aligned(16))) __uint128_t;
+typedef struct { char bytes[16]; } __attribute__((aligned(16))) __float128;
+]]
+
+local preprocess_cache_init = {} --- @type table<string,string>
local previous_defines_mod = ''
-local preprocess_cache_mod = nil
+local preprocess_cache_mod = nil --- @type table<string,string>
local function is_child_cdefs()
- return (os.getenv('NVIM_TEST_MAIN_CDEFS') ~= '1')
+ return os.getenv('NVIM_TEST_MAIN_CDEFS') ~= '1'
end
-- use this helper to import C files, you can pass multiple paths at once,
-- this helper will return the C namespace of the nvim library.
-cimport = function(...)
- local previous_defines, preprocess_cache, cdefs
+local function cimport(...)
+ local previous_defines --- @type string
+ local preprocess_cache --- @type table<string,string>
+ local cdefs
if is_child_cdefs() and preprocess_cache_mod then
preprocess_cache = preprocess_cache_mod
previous_defines = previous_defines_mod
@@ -177,7 +190,7 @@ cimport = function(...)
path = './' .. path
end
if not preprocess_cache[path] then
- local body
+ local body --- @type string
body, previous_defines = Preprocess.preprocess(previous_defines, path)
-- format it (so that the lines are "unique" statements), also filter out
-- Objective-C blocks
@@ -199,6 +212,7 @@ cimport = function(...)
-- (they are needed in the right order with the struct definitions,
-- otherwise luajit has wrong memory layouts for the sturcts)
if line:match("#pragma%s+pack") then
+ --- @type string
line = line .. " // " .. pragma_pack_id
pragma_pack_id = pragma_pack_id + 1
end
@@ -226,20 +240,21 @@ cimport = function(...)
return lib
end
-local cimport_immediate = function(...)
+local function cimport_immediate(...)
local saved_pid = child_pid
child_pid = 0
local err, emsg = pcall(cimport, ...)
child_pid = saved_pid
if not err then
- emsg = tostring(emsg)
- io.stderr:write(emsg .. '\n')
+ io.stderr:write(tostring(emsg) .. '\n')
assert(false)
else
return lib
end
end
+--- @param preprocess_cache table<string,string[]>
+--- @param path string
local function _cimportstr(preprocess_cache, path)
if imported:contains(path) then
return lib
@@ -262,12 +277,14 @@ end
local function alloc_log_new()
local log = {
- log={},
- lib=cimport('./src/nvim/memory.h'),
- original_functions={},
+ log={}, --- @type ChildCallLog[]
+ lib=cimport('./src/nvim/memory.h'), --- @type table<string,function>
+ original_functions={}, --- @type table<string,function>
null={['\0:is_null']=true},
}
+
local allocator_functions = {'malloc', 'free', 'calloc', 'realloc'}
+
function log:save_original_functions()
for _, funcname in ipairs(allocator_functions) do
if not self.original_functions[funcname] then
@@ -275,13 +292,16 @@ local function alloc_log_new()
end
end
end
+
log.save_original_functions = child_call(log.save_original_functions)
+
function log:set_mocks()
for _, k in ipairs(allocator_functions) do
do
local kk = k
self.lib['mem_' .. k] = function(...)
- local log_entry = {func=kk, args={...}}
+ --- @type ChildCallLog
+ local log_entry = { func = kk, args = {...} }
self.log[#self.log + 1] = log_entry
if kk == 'free' then
self.original_functions[kk](...)
@@ -302,17 +322,21 @@ local function alloc_log_new()
end
end
end
+
log.set_mocks = child_call(log.set_mocks)
+
function log:clear()
self.log = {}
end
+
function log:check(exp)
eq(exp, self.log)
self:clear()
end
+
function log:clear_tmp_allocs(clear_null_frees)
- local toremove = {}
- local allocs = {}
+ local toremove = {} --- @type integer[]
+ local allocs = {} --- @type table<string,integer>
for i, v in ipairs(self.log) do
if v.func == 'malloc' or v.func == 'calloc' then
allocs[tostring(v.ret)] = i
@@ -335,26 +359,20 @@ local function alloc_log_new()
table.remove(self.log, toremove[i])
end
end
- function log:restore_original_functions()
- -- Do nothing: set mocks live in a separate process
- return
- --[[
- [ for k, v in pairs(self.original_functions) do
- [ self.lib['mem_' .. k] = v
- [ end
- ]]
- end
+
function log:setup()
log:save_original_functions()
log:set_mocks()
end
+
function log:before_each()
- return
end
+
function log:after_each()
- log:restore_original_functions()
end
+
log:setup()
+
return log
end
@@ -371,98 +389,109 @@ local function to_cstr(string)
end
cimport_immediate('./test/unit/fixtures/posix.h')
-local sc = {
- fork = function()
- return tonumber(ffi.C.fork())
- end,
- pipe = function()
- local ret = ffi.new('int[2]', {-1, -1})
- ffi.errno(0)
- local res = ffi.C.pipe(ret)
- if (res ~= 0) then
+
+local sc = {}
+
+function sc.fork()
+ return tonumber(ffi.C.fork())
+end
+
+function sc.pipe()
+ local ret = ffi.new('int[2]', {-1, -1})
+ ffi.errno(0)
+ local res = ffi.C.pipe(ret)
+ if (res ~= 0) then
+ local err = ffi.errno(0)
+ assert(res == 0, ("pipe() error: %u: %s"):format(
+ err, ffi.string(ffi.C.strerror(err))))
+ end
+ assert(ret[0] ~= -1 and ret[1] ~= -1)
+ return ret[0], ret[1]
+end
+
+--- @return string
+function sc.read(rd, len)
+ local ret = ffi.new('char[?]', len, {0})
+ local total_bytes_read = 0
+ ffi.errno(0)
+ while total_bytes_read < len do
+ local bytes_read = tonumber(ffi.C.read(
+ rd,
+ ffi.cast('void*', ret + total_bytes_read),
+ len - total_bytes_read))
+ if bytes_read == -1 then
local err = ffi.errno(0)
- assert(res == 0, ("pipe() error: %u: %s"):format(
- err, ffi.string(ffi.C.strerror(err))))
- end
- assert(ret[0] ~= -1 and ret[1] ~= -1)
- return ret[0], ret[1]
- end,
- read = function(rd, len)
- local ret = ffi.new('char[?]', len, {0})
- local total_bytes_read = 0
- ffi.errno(0)
- while total_bytes_read < len do
- local bytes_read = tonumber(ffi.C.read(
- rd,
- ffi.cast('void*', ret + total_bytes_read),
- len - total_bytes_read))
- if bytes_read == -1 then
- local err = ffi.errno(0)
- if err ~= ffi.C.kPOSIXErrnoEINTR then
- assert(false, ("read() error: %u: %s"):format(
- err, ffi.string(ffi.C.strerror(err))))
- end
- elseif bytes_read == 0 then
- break
- else
- total_bytes_read = total_bytes_read + bytes_read
+ if err ~= ffi.C.kPOSIXErrnoEINTR then
+ assert(false, ("read() error: %u: %s"):format(
+ err, ffi.string(ffi.C.strerror(err))))
end
+ elseif bytes_read == 0 then
+ break
+ else
+ total_bytes_read = total_bytes_read + bytes_read
end
- return ffi.string(ret, total_bytes_read)
- end,
- write = function(wr, s)
- local wbuf = to_cstr(s)
- local total_bytes_written = 0
- ffi.errno(0)
- while total_bytes_written < #s do
- local bytes_written = tonumber(ffi.C.write(
- wr,
- ffi.cast('void*', wbuf + total_bytes_written),
- #s - total_bytes_written))
- if bytes_written == -1 then
- local err = ffi.errno(0)
- if err ~= ffi.C.kPOSIXErrnoEINTR then
- assert(false, ("write() error: %u: %s ('%s')"):format(
- err, ffi.string(ffi.C.strerror(err)), s))
- end
- elseif bytes_written == 0 then
- break
- else
- total_bytes_written = total_bytes_written + bytes_written
+ end
+ return ffi.string(ret, total_bytes_read)
+end
+
+function sc.write(wr, s)
+ local wbuf = to_cstr(s)
+ local total_bytes_written = 0
+ ffi.errno(0)
+ while total_bytes_written < #s do
+ local bytes_written = tonumber(ffi.C.write(
+ wr,
+ ffi.cast('void*', wbuf + total_bytes_written),
+ #s - total_bytes_written))
+ if bytes_written == -1 then
+ local err = ffi.errno(0)
+ if err ~= ffi.C.kPOSIXErrnoEINTR then
+ assert(false, ("write() error: %u: %s ('%s')"):format(
+ err, ffi.string(ffi.C.strerror(err)), s))
end
+ elseif bytes_written == 0 then
+ break
+ else
+ total_bytes_written = total_bytes_written + bytes_written
end
- return total_bytes_written
- end,
- close = ffi.C.close,
- wait = function(pid)
- ffi.errno(0)
- local stat_loc = ffi.new('int[1]', {0})
- while true do
- local r = ffi.C.waitpid(pid, stat_loc, ffi.C.kPOSIXWaitWUNTRACED)
- if r == -1 then
- local err = ffi.errno(0)
- if err == ffi.C.kPOSIXErrnoECHILD then
- break
- elseif err ~= ffi.C.kPOSIXErrnoEINTR then
- assert(false, ("waitpid() error: %u: %s"):format(
- err, ffi.string(ffi.C.strerror(err))))
- end
- else
- assert(r == pid)
+ end
+ return total_bytes_written
+end
+
+sc.close = ffi.C.close
+
+--- @param pid integer
+--- @return integer
+function sc.wait(pid)
+ ffi.errno(0)
+ local stat_loc = ffi.new('int[1]', {0})
+ while true do
+ local r = ffi.C.waitpid(pid, stat_loc, ffi.C.kPOSIXWaitWUNTRACED)
+ if r == -1 then
+ local err = ffi.errno(0)
+ if err == ffi.C.kPOSIXErrnoECHILD then
+ break
+ elseif err ~= ffi.C.kPOSIXErrnoEINTR then
+ assert(false, ("waitpid() error: %u: %s"):format(
+ err, ffi.string(ffi.C.strerror(err))))
end
+ else
+ assert(r == pid)
end
- return stat_loc[0]
- end,
- exit = ffi.C._exit,
-}
+ end
+ return stat_loc[0]
+end
+sc.exit = ffi.C._exit
+
+--- @param lst string[]
+--- @return string
local function format_list(lst)
- local ret = ''
+ local ret = {} --- @type string[]
for _, v in ipairs(lst) do
- if ret ~= '' then ret = ret .. ', ' end
- ret = ret .. assert:format({v, n=1})[1]
+ ret[#ret+1] = assert:format({v, n=1})[1]
end
- return ret
+ return table.concat(ret, ', ')
end
if os.getenv('NVIM_TEST_PRINT_SYSCALLS') == '1' then
@@ -510,19 +539,26 @@ local tracehelp = dedent([[
]])
local function child_sethook(wr)
- local trace_level = os.getenv('NVIM_TEST_TRACE_LEVEL')
- if not trace_level or trace_level == '' then
- trace_level = 0
- else
- trace_level = tonumber(trace_level)
+ local trace_level_str = os.getenv('NVIM_TEST_TRACE_LEVEL')
+ local trace_level = 0
+ if trace_level_str and trace_level_str ~= '' then
+ --- @type number
+ trace_level = assert(tonumber(trace_level_str))
end
+
if trace_level <= 0 then
return
end
+
local trace_only_c = trace_level <= 1
+ --- @type debuginfo?, string?, integer
local prev_info, prev_reason, prev_lnum
+
+ --- @param reason string
+ --- @param lnum integer
+ --- @param use_prev boolean
local function hook(reason, lnum, use_prev)
- local info = nil
+ local info = nil --- @type debuginfo?
if use_prev then
info = prev_info
elseif reason ~= 'tail return' then -- tail return
@@ -530,6 +566,7 @@ local function child_sethook(wr)
end
if trace_only_c and (not info or info.what ~= 'C') and not use_prev then
+ --- @cast info -nil
if info.source:sub(-9) == '_spec.lua' then
prev_info = info
prev_reason = 'saved'
@@ -570,12 +607,8 @@ local function child_sethook(wr)
end
-- assert(-1 <= lnum and lnum <= 99999)
- local lnum_s
- if lnum == -1 then
- lnum_s = 'nknwn'
- else
- lnum_s = ('%u'):format(lnum)
- end
+ local lnum_s = lnum == -1 and 'nknwn' or ('%u'):format(lnum)
+ --- @type string
local msg = ( -- lua does not support %*
''
.. msgchar
@@ -597,6 +630,7 @@ end
local trace_end_msg = ('E%s\n'):format((' '):rep(hook_msglen - 2))
+--- @type function
local _debug_log
local debug_log = only_separate(function(...)
@@ -604,6 +638,7 @@ local debug_log = only_separate(function(...)
end)
local function itp_child(wr, func)
+ --- @param s string
_debug_log = function(s)
s = s:sub(1, hook_msglen - 2)
sc.write(wr, '>' .. s .. (' '):rep(hook_msglen - 2 - #s) .. '\n')
@@ -635,7 +670,7 @@ local function itp_child(wr, func)
end
local function check_child_err(rd)
- local trace = {}
+ local trace = {} --- @type string[]
local did_traceline = false
local maxtrace = tonumber(os.getenv('NVIM_TEST_MAXTRACE')) or 1024
while true do
@@ -665,11 +700,14 @@ local function check_child_err(rd)
local len = tonumber(len_s)
neq(0, len)
if os.getenv('NVIM_TEST_TRACE_ON_ERROR') == '1' and #trace ~= 0 then
+ --- @type string
err = '\nTest failed, trace:\n' .. tracehelp
for _, traceline in ipairs(trace) do
+ --- @type string
err = err .. traceline
end
end
+ --- @type string
err = err .. sc.read(rd, len + 1)
end
local eres = sc.read(rd, 2)
@@ -683,10 +721,12 @@ local function check_child_err(rd)
end
end
if not did_traceline then
+ --- @type string
err = err .. '\nNo end of trace occurred'
end
local cc_err, cc_emsg = pcall(check_cores, Paths.test_luajit_prg, true)
if not cc_err then
+ --- @type string
err = err .. '\ncheck_cores failed: ' .. cc_emsg
end
end
@@ -811,6 +851,17 @@ local function ptr2key(ptr)
return ffi.string(s)
end
+local function is_asan()
+ cimport('./src/nvim/version.h')
+ local status, res = pcall(function() return lib.version_cflags end)
+ if status then
+ return ffi.string(res):match('-fsanitize=[a-z,]*address')
+ else
+ return false
+ end
+end
+
+--- @class test.unit.helpers.module
local module = {
cimport = cimport,
cppimport = cppimport,
@@ -819,9 +870,9 @@ local module = {
lib = lib,
cstr = cstr,
to_cstr = to_cstr,
- NULL = NULL,
- OK = OK,
- FAIL = FAIL,
+ NULL = ffi.cast('void*', 0),
+ OK = 1,
+ FAIL = 0,
alloc_log_new = alloc_log_new,
gen_itp = gen_itp,
only_separate = only_separate,
@@ -838,8 +889,11 @@ local module = {
ptr2addr = ptr2addr,
ptr2key = ptr2key,
debug_log = debug_log,
+ is_asan = is_asan,
}
+--- @class test.unit.helpers: test.unit.helpers.module, test.helpers
module = global_helpers.tbl_extend('error', module, global_helpers)
+
return function()
return module
end