123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920 |
- local ffi = require('ffi')
- local formatc = require('test.unit.formatc')
- local Set = require('test.unit.set')
- local Preprocess = require('test.unit.preprocess')
- local t_global = require('test.testutil')
- local paths = t_global.paths
- local assert = require('luassert')
- local say = require('say')
- local check_cores = t_global.check_cores
- local dedent = t_global.dedent
- local neq = t_global.neq
- local map = vim.tbl_map
- local eq = t_global.eq
- local trim = vim.trim
- -- add some standard header locations
- for _, p in ipairs(paths.include_paths) do
- Preprocess.add_to_include_path(p)
- end
- 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
- error('This function must be run in a separate process only')
- end
- return func(...)
- end
- end
- --- @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
- if child_pid ~= 0 then
- child_calls[#child_calls + 1] = { func = func, args = { ... } }
- return ret
- else
- return func(...)
- end
- end
- 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 = { ... } }
- else
- func(...)
- end
- end
- 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.
- local function child_cleanup_once(func, ...)
- local child_cleanups = child_cleanups_mod_once
- if child_pid ~= 0 then
- child_cleanups[#child_cleanups + 1] = { func = func, args = { ... } }
- else
- func(...)
- end
- end
- -- Unittests are run from debug nvim binary in lua interpreter mode.
- local libnvim = ffi.C
- local lib = setmetatable({}, {
- __index = only_separate(function(_, idx)
- return libnvim[idx]
- end),
- __newindex = child_call(function(_, idx, val)
- libnvim[idx] = val
- end),
- })
- local init = only_separate(function()
- for _, c in ipairs(child_calls_init) do
- c.func(unpack(c.args))
- end
- libnvim.event_init()
- libnvim.early_init(nil)
- if child_calls_mod then
- for _, c in ipairs(child_calls_mod) do
- c.func(unpack(c.args))
- end
- end
- if child_calls_mod_once then
- for _, c in ipairs(child_calls_mod_once) do
- c.func(unpack(c.args))
- end
- child_calls_mod_once = nil
- end
- end)
- local deinit = only_separate(function()
- if child_cleanups_mod_once then
- for _, c in ipairs(child_cleanups_mod_once) do
- c.func(unpack(c.args))
- end
- child_cleanups_mod_once = nil
- end
- end)
- -- a Set that keeps around the lines we've already seen
- local cdefs_init = Set:new()
- local cdefs_mod = nil
- local imported = Set:new()
- 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 = {} --- @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')
- -- used by macOS headers
- or string.find(line, 'typedef enum : ')
- or string.find(line, 'mach_vm_range_recipe')
- )
- then
- -- HACK: remove bitfields from specific structs as luajit can't seem to handle them.
- if line:find('struct VTermState') then
- line = string.gsub(line, 'state : 8;', 'state;')
- end
- if line:find('VTermStringFragment') then
- line = string.gsub(line, 'size_t.*len : 30;', 'size_t len;')
- end
- result[#result + 1] = line
- end
- end
- return table.concat(result, '\n')
- end
- local cdef = ffi.cdef
- local cimportstr
- 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 --- @type table<string,string>
- local function is_child_cdefs()
- 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.
- 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
- cdefs = cdefs_mod
- else
- preprocess_cache = preprocess_cache_init
- previous_defines = previous_defines_init
- cdefs = cdefs_init
- end
- for _, path in ipairs({ ... }) do
- if not (path:sub(1, 1) == '/' or path:sub(1, 1) == '.' or path:sub(2, 2) == ':') then
- path = './' .. path
- end
- if not preprocess_cache[path] then
- 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
- if os.getenv('NVIM_TEST_PRINT_I') == '1' then
- local lnum = 0
- for line in body:gmatch('[^\n]+') do
- lnum = lnum + 1
- print(lnum, line)
- end
- end
- body = formatc(body)
- body = filter_complex_blocks(body)
- -- add the formatted lines to a set
- local new_cdefs = Set:new()
- for line in body:gmatch('[^\r\n]+') do
- line = trim(line)
- -- give each #pragma pack a unique id, so that they don't get removed
- -- if they are inserted into the set
- -- (they are needed in the right order with the struct definitions,
- -- otherwise luajit has wrong memory layouts for the structs)
- if line:match('#pragma%s+pack') then
- --- @type string
- line = line .. ' // ' .. pragma_pack_id
- pragma_pack_id = pragma_pack_id + 1
- end
- new_cdefs:add(line)
- end
- -- subtract the lines we've already imported from the new lines, then add
- -- the new unique lines to the old lines (so they won't be imported again)
- new_cdefs:diff(cdefs)
- cdefs:union(new_cdefs)
- -- request a sorted version of the new lines (same relative order as the
- -- original preprocessed file) and feed that to the LuaJIT ffi
- local new_lines = new_cdefs:to_table()
- if os.getenv('NVIM_TEST_PRINT_CDEF') == '1' then
- for lnum, line in ipairs(new_lines) do
- print(lnum, line)
- end
- end
- body = table.concat(new_lines, '\n')
- preprocess_cache[path] = body
- end
- cimportstr(preprocess_cache, path)
- end
- return lib
- end
- 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
- 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
- end
- local body = preprocess_cache[path]
- if body == '' then
- return lib
- end
- cdef(body)
- imported:add(path)
- return lib
- end
- if is_child_cdefs() then
- cimportstr = child_call(_cimportstr, lib)
- else
- cimportstr = _cimportstr
- end
- local function alloc_log_new()
- local log = {
- 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
- self.original_functions[funcname] = self.lib['mem_' .. funcname]
- 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(...)
- --- @type ChildCallLog
- local log_entry = { func = kk, args = { ... } }
- self.log[#self.log + 1] = log_entry
- if kk == 'free' then
- self.original_functions[kk](...)
- else
- log_entry.ret = self.original_functions[kk](...)
- end
- for i, v in ipairs(log_entry.args) do
- if v == nil then
- -- XXX This thing thinks that {NULL} ~= {NULL}.
- log_entry.args[i] = self.null
- end
- end
- if self.hook then
- self:hook(log_entry)
- end
- if log_entry.ret then
- return log_entry.ret
- end
- end
- 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 = {} --- @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
- elseif v.func == 'realloc' or v.func == 'free' then
- if allocs[tostring(v.args[1])] then
- toremove[#toremove + 1] = allocs[tostring(v.args[1])]
- if v.func == 'free' then
- toremove[#toremove + 1] = i
- end
- elseif clear_null_frees and v.args[1] == self.null then
- toremove[#toremove + 1] = i
- end
- if v.func == 'realloc' then
- allocs[tostring(v.ret)] = i
- end
- end
- end
- table.sort(toremove)
- for i = #toremove, 1, -1 do
- table.remove(self.log, toremove[i])
- end
- end
- function log:setup()
- log:save_original_functions()
- log:set_mocks()
- end
- function log:before_each() end
- function log:after_each() end
- log:setup()
- return log
- end
- -- take a pointer to a C-allocated string and return an interned
- -- version while also freeing the memory
- local function internalize(cdata, len)
- ffi.gc(cdata, ffi.C.free)
- return ffi.string(cdata, len)
- end
- local cstr = ffi.typeof('char[?]')
- local function to_cstr(string)
- return cstr(#string + 1, string)
- end
- cimport_immediate('./test/unit/fixtures/posix.h')
- 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)
- 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
- 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
- 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
- end
- return stat_loc[0]
- end
- sc.exit = ffi.C._exit
- --- @param lst string[]
- --- @return string
- local function format_list(lst)
- local ret = {} --- @type string[]
- for _, v in ipairs(lst) do
- ret[#ret + 1] = assert:format({ v, n = 1 })[1]
- end
- return table.concat(ret, ', ')
- end
- if os.getenv('NVIM_TEST_PRINT_SYSCALLS') == '1' then
- for k_, v_ in pairs(sc) do
- (function(k, v)
- sc[k] = function(...)
- local rets = { v(...) }
- io.stderr:write(('%s(%s) = %s\n'):format(k, format_list({ ... }), format_list(rets)))
- return unpack(rets)
- end
- end)(k_, v_)
- end
- end
- local function just_fail(_)
- return false
- end
- say:set('assertion.just_fail.positive', '%s')
- say:set('assertion.just_fail.negative', '%s')
- assert:register(
- 'assertion',
- 'just_fail',
- just_fail,
- 'assertion.just_fail.positive',
- 'assertion.just_fail.negative'
- )
- local hook_fnamelen = 30
- local hook_sfnamelen = 30
- local hook_numlen = 5
- local hook_msglen = 1 + 1 + 1 + (1 + hook_fnamelen) + (1 + hook_sfnamelen) + (1 + hook_numlen) + 1
- local tracehelp = dedent([[
- Trace: either in the format described below or custom debug output starting
- with `>`. Latter lines still have the same width in byte.
- ┌ Trace type: _r_eturn from function , function _c_all, _l_ine executed,
- │ _t_ail return, _C_ount (should not actually appear),
- │ _s_aved from previous run for reference, _>_ for custom debug
- │ output.
- │┏ Function type: _L_ua function, _C_ function, _m_ain part of chunk,
- │┃ function that did _t_ail call.
- │┃┌ Function name type: _g_lobal, _l_ocal, _m_ethod, _f_ield, _u_pvalue,
- │┃│ space for unknown.
- │┃│ ┏ Source file name ┌ Function name ┏ Line
- │┃│ ┃ (trunc to 30 bytes, no .lua) │ (truncated to last 30 bytes) ┃ number
- CWN SSSSSSSSSSSSSSSSSSSSSSSSSSSSSS:FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF:LLLLL\n
- ]])
- local function child_sethook(wr)
- 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 --- @type debuginfo?
- if use_prev then
- info = prev_info
- elseif reason ~= 'tail return' then -- tail return
- info = debug.getinfo(2, 'nSl')
- 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'
- prev_lnum = lnum
- end
- return
- end
- if trace_only_c and not use_prev and prev_reason then
- hook(prev_reason, prev_lnum, true)
- prev_reason = nil
- end
- local whatchar = ' '
- local namewhatchar = ' '
- local funcname = ''
- local source = ''
- local msgchar = reason:sub(1, 1)
- if reason == 'count' then
- msgchar = 'C'
- end
- if info then
- funcname = (info.name or ''):sub(1, hook_fnamelen)
- whatchar = info.what:sub(1, 1)
- namewhatchar = info.namewhat:sub(1, 1)
- if namewhatchar == '' then
- namewhatchar = ' '
- end
- source = info.source
- if source:sub(1, 1) == '@' then
- if source:sub(-4, -1) == '.lua' then
- source = source:sub(1, -5)
- end
- source = source:sub(-hook_sfnamelen, -1)
- end
- lnum = lnum or info.currentline
- end
- -- assert(-1 <= lnum and lnum <= 99999)
- local lnum_s = lnum == -1 and 'nknwn' or ('%u'):format(lnum)
- --- @type string
- local msg = ( -- lua does not support %*
- ''
- .. msgchar
- .. whatchar
- .. namewhatchar
- .. ' '
- .. source
- .. (' '):rep(hook_sfnamelen - #source)
- .. ':'
- .. funcname
- .. (' '):rep(hook_fnamelen - #funcname)
- .. ':'
- .. ('0'):rep(hook_numlen - #lnum_s)
- .. lnum_s
- .. '\n'
- )
- -- eq(hook_msglen, #msg)
- sc.write(wr, msg)
- end
- debug.sethook(hook, 'crl')
- end
- local trace_end_msg = ('E%s\n'):format((' '):rep(hook_msglen - 2))
- --- @type function
- local _debug_log
- local debug_log = only_separate(function(...)
- return _debug_log(...)
- 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')
- end
- local status, result = pcall(init)
- if status then
- collectgarbage('stop')
- child_sethook(wr)
- status, result = pcall(func)
- debug.sethook()
- end
- sc.write(wr, trace_end_msg)
- if not status then
- local emsg = tostring(result)
- if #emsg > 99999 then
- emsg = emsg:sub(1, 99999)
- end
- sc.write(wr, ('-\n%05u\n%s'):format(#emsg, emsg))
- deinit()
- else
- sc.write(wr, '+\n')
- deinit()
- end
- collectgarbage('restart')
- collectgarbage()
- sc.write(wr, '$\n')
- sc.close(wr)
- sc.exit(status and 0 or 1)
- end
- local function check_child_err(rd)
- local trace = {} --- @type string[]
- local did_traceline = false
- local maxtrace = tonumber(os.getenv('NVIM_TEST_MAXTRACE')) or 1024
- while true do
- local traceline = sc.read(rd, hook_msglen)
- if #traceline ~= hook_msglen then
- if #traceline == 0 then
- break
- else
- trace[#trace + 1] = 'Partial read: <' .. trace .. '>\n'
- end
- end
- if traceline == trace_end_msg then
- did_traceline = true
- break
- end
- trace[#trace + 1] = traceline
- if #trace > maxtrace then
- table.remove(trace, 1)
- end
- end
- local res = sc.read(rd, 2)
- if #res == 2 then
- local err = ''
- if res ~= '+\n' then
- eq('-\n', res)
- local len_s = sc.read(rd, 5)
- 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)
- if eres ~= '$\n' then
- if #trace == 0 then
- err = '\nTest crashed, no trace available (check NVIM_TEST_TRACE_LEVEL)\n'
- else
- err = '\nTest crashed, trace:\n' .. tracehelp
- for i = 1, #trace do
- err = err .. trace[i]
- 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
- if err ~= '' then
- assert.just_fail(err)
- end
- end
- end
- local function itp_parent(rd, pid, allow_failure, location)
- local ok, emsg = pcall(check_child_err, rd)
- local status = sc.wait(pid)
- sc.close(rd)
- if not ok then
- if allow_failure then
- io.stderr:write('Errorred out (' .. status .. '):\n' .. tostring(emsg) .. '\n')
- os.execute([[
- sh -c "source ci/common/test.sh
- check_core_dumps --delete \"]] .. paths.test_luajit_prg .. [[\""]])
- else
- error(tostring(emsg) .. '\nexit code: ' .. status)
- end
- elseif status ~= 0 then
- if not allow_failure then
- error('child process errored out with status ' .. status .. '!\n\n' .. location)
- end
- end
- end
- local function gen_itp(it)
- child_calls_mod = {}
- child_calls_mod_once = {}
- child_cleanups_mod_once = {}
- preprocess_cache_mod = map(function(v)
- return v
- end, preprocess_cache_init)
- previous_defines_mod = previous_defines_init
- cdefs_mod = cdefs_init:copy()
- local function itp(name, func, allow_failure)
- if allow_failure and os.getenv('NVIM_TEST_RUN_FAILING_TESTS') ~= '1' then
- -- FIXME Fix tests with this true
- return
- end
- -- Pre-emptively calculating error location, wasteful, ugh!
- -- But the way this code messes around with busted implies the real location is strictly
- -- not available in the parent when an actual error occurs. so we have to do this here.
- local location = debug.traceback()
- it(name, function()
- local rd, wr = sc.pipe()
- child_pid = sc.fork()
- if child_pid == 0 then
- sc.close(rd)
- itp_child(wr, func)
- else
- sc.close(wr)
- local saved_child_pid = child_pid
- child_pid = nil
- itp_parent(rd, saved_child_pid, allow_failure, location)
- end
- end)
- end
- return itp
- end
- local function cppimport(path)
- return cimport(paths.test_source_path .. '/test/includes/pre/' .. path)
- end
- cimport(
- './src/nvim/types_defs.h',
- './src/nvim/main.h',
- './src/nvim/os/time.h',
- './src/nvim/os/fs.h'
- )
- local function conv_enum(etab, eval)
- local n = tonumber(eval)
- return etab[n] or n
- end
- local function array_size(arr)
- return ffi.sizeof(arr) / ffi.sizeof(arr[0])
- end
- local function kvi_size(kvi)
- return array_size(kvi.init_array)
- end
- local function kvi_init(kvi)
- kvi.capacity = kvi_size(kvi)
- kvi.items = kvi.init_array
- return kvi
- end
- local function kvi_destroy(kvi)
- if kvi.items ~= kvi.init_array then
- lib.xfree(kvi.items)
- end
- end
- local function kvi_new(ct)
- return kvi_init(ffi.new(ct))
- end
- local function make_enum_conv_tab(m, values, skip_pref, set_cb)
- child_call_once(function()
- local ret = {}
- for _, v in ipairs(values) do
- local str_v = v
- if v:sub(1, #skip_pref) == skip_pref then
- str_v = v:sub(#skip_pref + 1)
- end
- ret[tonumber(m[v])] = str_v
- end
- set_cb(ret)
- end)
- end
- local function ptr2addr(ptr)
- return tonumber(ffi.cast('intptr_t', ffi.cast('void *', ptr)))
- end
- local s = ffi.new('char[64]', { 0 })
- local function ptr2key(ptr)
- ffi.C.snprintf(s, ffi.sizeof(s), '%p', ffi.cast('void *', 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.testutil.module
- local M = {
- cimport = cimport,
- cppimport = cppimport,
- internalize = internalize,
- ffi = ffi,
- lib = lib,
- cstr = cstr,
- to_cstr = to_cstr,
- NULL = ffi.cast('void*', 0),
- OK = 1,
- FAIL = 0,
- alloc_log_new = alloc_log_new,
- gen_itp = gen_itp,
- only_separate = only_separate,
- child_call_once = child_call_once,
- child_cleanup_once = child_cleanup_once,
- sc = sc,
- conv_enum = conv_enum,
- array_size = array_size,
- kvi_destroy = kvi_destroy,
- kvi_size = kvi_size,
- kvi_init = kvi_init,
- kvi_new = kvi_new,
- make_enum_conv_tab = make_enum_conv_tab,
- ptr2addr = ptr2addr,
- ptr2key = ptr2key,
- debug_log = debug_log,
- is_asan = is_asan,
- }
- --- @class test.unit.testutil: test.unit.testutil.module, test.testutil
- M = vim.tbl_extend('error', M, t_global)
- return M
|