testutil.lua 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917
  1. local ffi = require('ffi')
  2. local formatc = require('test.unit.formatc')
  3. local Set = require('test.unit.set')
  4. local Preprocess = require('test.unit.preprocess')
  5. local t_global = require('test.testutil')
  6. local paths = t_global.paths
  7. local assert = require('luassert')
  8. local say = require('say')
  9. local check_cores = t_global.check_cores
  10. local dedent = t_global.dedent
  11. local neq = t_global.neq
  12. local map = vim.tbl_map
  13. local eq = t_global.eq
  14. local trim = vim.trim
  15. -- add some standard header locations
  16. for _, p in ipairs(paths.include_paths) do
  17. Preprocess.add_to_include_path(p)
  18. end
  19. -- add some nonstandard header locations
  20. if paths.apple_sysroot ~= '' then
  21. Preprocess.add_apple_sysroot(paths.apple_sysroot)
  22. end
  23. local child_pid = nil --- @type integer?
  24. --- @generic F: function
  25. --- @param func F
  26. --- @return F
  27. local function only_separate(func)
  28. return function(...)
  29. if child_pid ~= 0 then
  30. error('This function must be run in a separate process only')
  31. end
  32. return func(...)
  33. end
  34. end
  35. --- @class ChildCall
  36. --- @field func function
  37. --- @field args any[]
  38. --- @class ChildCallLog
  39. --- @field func string
  40. --- @field args any[]
  41. --- @field ret any?
  42. local child_calls_init = {} --- @type ChildCall[]
  43. local child_calls_mod = nil --- @type ChildCall[]
  44. local child_calls_mod_once = nil --- @type ChildCall[]?
  45. local function child_call(func, ret)
  46. return function(...)
  47. local child_calls = child_calls_mod or child_calls_init
  48. if child_pid ~= 0 then
  49. child_calls[#child_calls + 1] = { func = func, args = { ... } }
  50. return ret
  51. else
  52. return func(...)
  53. end
  54. end
  55. end
  56. -- Run some code at the start of the child process, before running the test
  57. -- itself. Is supposed to be run in `before_each`.
  58. --- @param func function
  59. local function child_call_once(func, ...)
  60. if child_pid ~= 0 then
  61. child_calls_mod_once[#child_calls_mod_once + 1] = { func = func, args = { ... } }
  62. else
  63. func(...)
  64. end
  65. end
  66. local child_cleanups_mod_once = nil --- @type ChildCall[]?
  67. -- Run some code at the end of the child process, before exiting. Is supposed to
  68. -- be run in `before_each` because `after_each` is run after child has exited.
  69. local function child_cleanup_once(func, ...)
  70. local child_cleanups = child_cleanups_mod_once
  71. if child_pid ~= 0 then
  72. child_cleanups[#child_cleanups + 1] = { func = func, args = { ... } }
  73. else
  74. func(...)
  75. end
  76. end
  77. -- Unittests are run from debug nvim binary in lua interpreter mode.
  78. local libnvim = ffi.C
  79. local lib = setmetatable({}, {
  80. __index = only_separate(function(_, idx)
  81. return libnvim[idx]
  82. end),
  83. __newindex = child_call(function(_, idx, val)
  84. libnvim[idx] = val
  85. end),
  86. })
  87. local init = only_separate(function()
  88. for _, c in ipairs(child_calls_init) do
  89. c.func(unpack(c.args))
  90. end
  91. libnvim.event_init()
  92. libnvim.early_init(nil)
  93. if child_calls_mod then
  94. for _, c in ipairs(child_calls_mod) do
  95. c.func(unpack(c.args))
  96. end
  97. end
  98. if child_calls_mod_once then
  99. for _, c in ipairs(child_calls_mod_once) do
  100. c.func(unpack(c.args))
  101. end
  102. child_calls_mod_once = nil
  103. end
  104. end)
  105. local deinit = only_separate(function()
  106. if child_cleanups_mod_once then
  107. for _, c in ipairs(child_cleanups_mod_once) do
  108. c.func(unpack(c.args))
  109. end
  110. child_cleanups_mod_once = nil
  111. end
  112. end)
  113. -- a Set that keeps around the lines we've already seen
  114. local cdefs_init = Set:new()
  115. local cdefs_mod = nil
  116. local imported = Set:new()
  117. local pragma_pack_id = 1
  118. -- some things are just too complex for the LuaJIT C parser to digest. We
  119. -- usually don't need them anyway.
  120. --- @param body string
  121. local function filter_complex_blocks(body)
  122. local result = {} --- @type string[]
  123. for line in body:gmatch('[^\r\n]+') do
  124. if
  125. not (
  126. string.find(line, '(^)', 1, true) ~= nil
  127. or string.find(line, '_ISwupper', 1, true)
  128. or string.find(line, '_Float')
  129. or string.find(line, '__s128')
  130. or string.find(line, '__u128')
  131. or string.find(line, 'msgpack_zone_push_finalizer')
  132. or string.find(line, 'msgpack_unpacker_reserve_buffer')
  133. or string.find(line, 'value_init_')
  134. or string.find(line, 'UUID_NULL') -- static const uuid_t UUID_NULL = {...}
  135. or string.find(line, 'inline _Bool')
  136. -- used by musl libc headers on 32-bit arches via __REDIR marco
  137. or string.find(line, '__typeof__')
  138. -- used by macOS headers
  139. or string.find(line, 'typedef enum : ')
  140. or string.find(line, 'mach_vm_range_recipe')
  141. )
  142. then
  143. -- Remove GCC's extension keyword which is just used to disable warnings.
  144. line = string.gsub(line, '__extension__', '')
  145. -- HACK: remove bitfields from specific structs as luajit can't seem to handle them.
  146. if line:find('struct VTermState') then
  147. line = string.gsub(line, 'state : 8;', 'state;')
  148. end
  149. if line:find('VTermStringFragment') then
  150. line = string.gsub(line, 'size_t.*len : 30;', 'size_t len;')
  151. end
  152. result[#result + 1] = line
  153. end
  154. end
  155. return table.concat(result, '\n')
  156. end
  157. local cdef = ffi.cdef
  158. local cimportstr
  159. local previous_defines_init = [[
  160. typedef struct { char bytes[16]; } __attribute__((aligned(16))) __uint128_t;
  161. typedef struct { char bytes[16]; } __attribute__((aligned(16))) __float128;
  162. ]]
  163. local preprocess_cache_init = {} --- @type table<string,string>
  164. local previous_defines_mod = ''
  165. local preprocess_cache_mod = nil --- @type table<string,string>
  166. local function is_child_cdefs()
  167. return os.getenv('NVIM_TEST_MAIN_CDEFS') ~= '1'
  168. end
  169. -- use this helper to import C files, you can pass multiple paths at once,
  170. -- this helper will return the C namespace of the nvim library.
  171. local function cimport(...)
  172. local previous_defines --- @type string
  173. local preprocess_cache --- @type table<string,string>
  174. local cdefs
  175. if is_child_cdefs() and preprocess_cache_mod then
  176. preprocess_cache = preprocess_cache_mod
  177. previous_defines = previous_defines_mod
  178. cdefs = cdefs_mod
  179. else
  180. preprocess_cache = preprocess_cache_init
  181. previous_defines = previous_defines_init
  182. cdefs = cdefs_init
  183. end
  184. for _, path in ipairs({ ... }) do
  185. if not (path:sub(1, 1) == '/' or path:sub(1, 1) == '.' or path:sub(2, 2) == ':') then
  186. path = './' .. path
  187. end
  188. if not preprocess_cache[path] then
  189. local body --- @type string
  190. body, previous_defines = Preprocess.preprocess(previous_defines, path)
  191. -- format it (so that the lines are "unique" statements), also filter out
  192. -- Objective-C blocks
  193. if os.getenv('NVIM_TEST_PRINT_I') == '1' then
  194. local lnum = 0
  195. for line in body:gmatch('[^\n]+') do
  196. lnum = lnum + 1
  197. print(lnum, line)
  198. end
  199. end
  200. body = formatc(body)
  201. body = filter_complex_blocks(body)
  202. -- add the formatted lines to a set
  203. local new_cdefs = Set:new()
  204. for line in body:gmatch('[^\r\n]+') do
  205. line = trim(line)
  206. -- give each #pragma pack a unique id, so that they don't get removed
  207. -- if they are inserted into the set
  208. -- (they are needed in the right order with the struct definitions,
  209. -- otherwise luajit has wrong memory layouts for the structs)
  210. if line:match('#pragma%s+pack') then
  211. --- @type string
  212. line = line .. ' // ' .. pragma_pack_id
  213. pragma_pack_id = pragma_pack_id + 1
  214. end
  215. new_cdefs:add(line)
  216. end
  217. -- subtract the lines we've already imported from the new lines, then add
  218. -- the new unique lines to the old lines (so they won't be imported again)
  219. new_cdefs:diff(cdefs)
  220. cdefs:union(new_cdefs)
  221. -- request a sorted version of the new lines (same relative order as the
  222. -- original preprocessed file) and feed that to the LuaJIT ffi
  223. local new_lines = new_cdefs:to_table()
  224. if os.getenv('NVIM_TEST_PRINT_CDEF') == '1' then
  225. for lnum, line in ipairs(new_lines) do
  226. print(lnum, line)
  227. end
  228. end
  229. body = table.concat(new_lines, '\n')
  230. preprocess_cache[path] = body
  231. end
  232. cimportstr(preprocess_cache, path)
  233. end
  234. return lib
  235. end
  236. local function cimport_immediate(...)
  237. local saved_pid = child_pid
  238. child_pid = 0
  239. local err, emsg = pcall(cimport, ...)
  240. child_pid = saved_pid
  241. if not err then
  242. io.stderr:write(tostring(emsg) .. '\n')
  243. assert(false)
  244. else
  245. return lib
  246. end
  247. end
  248. --- @param preprocess_cache table<string,string[]>
  249. --- @param path string
  250. local function _cimportstr(preprocess_cache, path)
  251. if imported:contains(path) then
  252. return lib
  253. end
  254. local body = preprocess_cache[path]
  255. if body == '' then
  256. return lib
  257. end
  258. cdef(body)
  259. imported:add(path)
  260. return lib
  261. end
  262. if is_child_cdefs() then
  263. cimportstr = child_call(_cimportstr, lib)
  264. else
  265. cimportstr = _cimportstr
  266. end
  267. local function alloc_log_new()
  268. local log = {
  269. log = {}, --- @type ChildCallLog[]
  270. lib = cimport('./src/nvim/memory.h'), --- @type table<string,function>
  271. original_functions = {}, --- @type table<string,function>
  272. null = { ['\0:is_null'] = true },
  273. }
  274. local allocator_functions = { 'malloc', 'free', 'calloc', 'realloc' }
  275. function log:save_original_functions()
  276. for _, funcname in ipairs(allocator_functions) do
  277. if not self.original_functions[funcname] then
  278. self.original_functions[funcname] = self.lib['mem_' .. funcname]
  279. end
  280. end
  281. end
  282. log.save_original_functions = child_call(log.save_original_functions)
  283. function log:set_mocks()
  284. for _, k in ipairs(allocator_functions) do
  285. do
  286. local kk = k
  287. self.lib['mem_' .. k] = function(...)
  288. --- @type ChildCallLog
  289. local log_entry = { func = kk, args = { ... } }
  290. self.log[#self.log + 1] = log_entry
  291. if kk == 'free' then
  292. self.original_functions[kk](...)
  293. else
  294. log_entry.ret = self.original_functions[kk](...)
  295. end
  296. for i, v in ipairs(log_entry.args) do
  297. if v == nil then
  298. -- XXX This thing thinks that {NULL} ~= {NULL}.
  299. log_entry.args[i] = self.null
  300. end
  301. end
  302. if self.hook then
  303. self:hook(log_entry)
  304. end
  305. if log_entry.ret then
  306. return log_entry.ret
  307. end
  308. end
  309. end
  310. end
  311. end
  312. log.set_mocks = child_call(log.set_mocks)
  313. function log:clear()
  314. self.log = {}
  315. end
  316. function log:check(exp)
  317. eq(exp, self.log)
  318. self:clear()
  319. end
  320. function log:clear_tmp_allocs(clear_null_frees)
  321. local toremove = {} --- @type integer[]
  322. local allocs = {} --- @type table<string,integer>
  323. for i, v in ipairs(self.log) do
  324. if v.func == 'malloc' or v.func == 'calloc' then
  325. allocs[tostring(v.ret)] = i
  326. elseif v.func == 'realloc' or v.func == 'free' then
  327. if allocs[tostring(v.args[1])] then
  328. toremove[#toremove + 1] = allocs[tostring(v.args[1])]
  329. if v.func == 'free' then
  330. toremove[#toremove + 1] = i
  331. end
  332. elseif clear_null_frees and v.args[1] == self.null then
  333. toremove[#toremove + 1] = i
  334. end
  335. if v.func == 'realloc' then
  336. allocs[tostring(v.ret)] = i
  337. end
  338. end
  339. end
  340. table.sort(toremove)
  341. for i = #toremove, 1, -1 do
  342. table.remove(self.log, toremove[i])
  343. end
  344. end
  345. function log:setup()
  346. log:save_original_functions()
  347. log:set_mocks()
  348. end
  349. function log:before_each() end
  350. function log:after_each() end
  351. log:setup()
  352. return log
  353. end
  354. -- take a pointer to a C-allocated string and return an interned
  355. -- version while also freeing the memory
  356. local function internalize(cdata, len)
  357. ffi.gc(cdata, ffi.C.free)
  358. return ffi.string(cdata, len)
  359. end
  360. local cstr = ffi.typeof('char[?]')
  361. local function to_cstr(string)
  362. return cstr(#string + 1, string)
  363. end
  364. cimport_immediate('./test/unit/fixtures/posix.h')
  365. local sc = {}
  366. function sc.fork()
  367. return tonumber(ffi.C.fork())
  368. end
  369. function sc.pipe()
  370. local ret = ffi.new('int[2]', { -1, -1 })
  371. ffi.errno(0)
  372. local res = ffi.C.pipe(ret)
  373. if res ~= 0 then
  374. local err = ffi.errno(0)
  375. assert(res == 0, ('pipe() error: %u: %s'):format(err, ffi.string(ffi.C.strerror(err))))
  376. end
  377. assert(ret[0] ~= -1 and ret[1] ~= -1)
  378. return ret[0], ret[1]
  379. end
  380. --- @return string
  381. function sc.read(rd, len)
  382. local ret = ffi.new('char[?]', len, { 0 })
  383. local total_bytes_read = 0
  384. ffi.errno(0)
  385. while total_bytes_read < len do
  386. local bytes_read =
  387. tonumber(ffi.C.read(rd, ffi.cast('void*', ret + total_bytes_read), len - total_bytes_read))
  388. if bytes_read == -1 then
  389. local err = ffi.errno(0)
  390. if err ~= ffi.C.kPOSIXErrnoEINTR then
  391. assert(false, ('read() error: %u: %s'):format(err, ffi.string(ffi.C.strerror(err))))
  392. end
  393. elseif bytes_read == 0 then
  394. break
  395. else
  396. total_bytes_read = total_bytes_read + bytes_read
  397. end
  398. end
  399. return ffi.string(ret, total_bytes_read)
  400. end
  401. function sc.write(wr, s)
  402. local wbuf = to_cstr(s)
  403. local total_bytes_written = 0
  404. ffi.errno(0)
  405. while total_bytes_written < #s do
  406. local bytes_written = tonumber(
  407. ffi.C.write(wr, ffi.cast('void*', wbuf + total_bytes_written), #s - total_bytes_written)
  408. )
  409. if bytes_written == -1 then
  410. local err = ffi.errno(0)
  411. if err ~= ffi.C.kPOSIXErrnoEINTR then
  412. assert(
  413. false,
  414. ("write() error: %u: %s ('%s')"):format(err, ffi.string(ffi.C.strerror(err)), s)
  415. )
  416. end
  417. elseif bytes_written == 0 then
  418. break
  419. else
  420. total_bytes_written = total_bytes_written + bytes_written
  421. end
  422. end
  423. return total_bytes_written
  424. end
  425. sc.close = ffi.C.close
  426. --- @param pid integer
  427. --- @return integer
  428. function sc.wait(pid)
  429. ffi.errno(0)
  430. local stat_loc = ffi.new('int[1]', { 0 })
  431. while true do
  432. local r = ffi.C.waitpid(pid, stat_loc, ffi.C.kPOSIXWaitWUNTRACED)
  433. if r == -1 then
  434. local err = ffi.errno(0)
  435. if err == ffi.C.kPOSIXErrnoECHILD then
  436. break
  437. elseif err ~= ffi.C.kPOSIXErrnoEINTR then
  438. assert(false, ('waitpid() error: %u: %s'):format(err, ffi.string(ffi.C.strerror(err))))
  439. end
  440. else
  441. assert(r == pid)
  442. end
  443. end
  444. return stat_loc[0]
  445. end
  446. sc.exit = ffi.C._exit
  447. --- @param lst string[]
  448. --- @return string
  449. local function format_list(lst)
  450. local ret = {} --- @type string[]
  451. for _, v in ipairs(lst) do
  452. ret[#ret + 1] = assert:format({ v, n = 1 })[1]
  453. end
  454. return table.concat(ret, ', ')
  455. end
  456. if os.getenv('NVIM_TEST_PRINT_SYSCALLS') == '1' then
  457. for k_, v_ in pairs(sc) do
  458. (function(k, v)
  459. sc[k] = function(...)
  460. local rets = { v(...) }
  461. io.stderr:write(('%s(%s) = %s\n'):format(k, format_list({ ... }), format_list(rets)))
  462. return unpack(rets)
  463. end
  464. end)(k_, v_)
  465. end
  466. end
  467. local function just_fail(_)
  468. return false
  469. end
  470. say:set('assertion.just_fail.positive', '%s')
  471. say:set('assertion.just_fail.negative', '%s')
  472. assert:register(
  473. 'assertion',
  474. 'just_fail',
  475. just_fail,
  476. 'assertion.just_fail.positive',
  477. 'assertion.just_fail.negative'
  478. )
  479. local hook_fnamelen = 30
  480. local hook_sfnamelen = 30
  481. local hook_numlen = 5
  482. local hook_msglen = 1 + 1 + 1 + (1 + hook_fnamelen) + (1 + hook_sfnamelen) + (1 + hook_numlen) + 1
  483. local tracehelp = dedent([[
  484. Trace: either in the format described below or custom debug output starting
  485. with `>`. Latter lines still have the same width in byte.
  486. ┌ Trace type: _r_eturn from function , function _c_all, _l_ine executed,
  487. │ _t_ail return, _C_ount (should not actually appear),
  488. │ _s_aved from previous run for reference, _>_ for custom debug
  489. │ output.
  490. │┏ Function type: _L_ua function, _C_ function, _m_ain part of chunk,
  491. │┃ function that did _t_ail call.
  492. │┃┌ Function name type: _g_lobal, _l_ocal, _m_ethod, _f_ield, _u_pvalue,
  493. │┃│ space for unknown.
  494. │┃│ ┏ Source file name ┌ Function name ┏ Line
  495. │┃│ ┃ (trunc to 30 bytes, no .lua) │ (truncated to last 30 bytes) ┃ number
  496. CWN SSSSSSSSSSSSSSSSSSSSSSSSSSSSSS:FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF:LLLLL\n
  497. ]])
  498. local function child_sethook(wr)
  499. local trace_level_str = os.getenv('NVIM_TEST_TRACE_LEVEL')
  500. local trace_level = 0
  501. if trace_level_str and trace_level_str ~= '' then
  502. --- @type number
  503. trace_level = assert(tonumber(trace_level_str))
  504. end
  505. if trace_level <= 0 then
  506. return
  507. end
  508. local trace_only_c = trace_level <= 1
  509. --- @type debuginfo?, string?, integer
  510. local prev_info, prev_reason, prev_lnum
  511. --- @param reason string
  512. --- @param lnum integer
  513. --- @param use_prev boolean
  514. local function hook(reason, lnum, use_prev)
  515. local info = nil --- @type debuginfo?
  516. if use_prev then
  517. info = prev_info
  518. elseif reason ~= 'tail return' then -- tail return
  519. info = debug.getinfo(2, 'nSl')
  520. end
  521. if trace_only_c and (not info or info.what ~= 'C') and not use_prev then
  522. --- @cast info -nil
  523. if info.source:sub(-9) == '_spec.lua' then
  524. prev_info = info
  525. prev_reason = 'saved'
  526. prev_lnum = lnum
  527. end
  528. return
  529. end
  530. if trace_only_c and not use_prev and prev_reason then
  531. hook(prev_reason, prev_lnum, true)
  532. prev_reason = nil
  533. end
  534. local whatchar = ' '
  535. local namewhatchar = ' '
  536. local funcname = ''
  537. local source = ''
  538. local msgchar = reason:sub(1, 1)
  539. if reason == 'count' then
  540. msgchar = 'C'
  541. end
  542. if info then
  543. funcname = (info.name or ''):sub(1, hook_fnamelen)
  544. whatchar = info.what:sub(1, 1)
  545. namewhatchar = info.namewhat:sub(1, 1)
  546. if namewhatchar == '' then
  547. namewhatchar = ' '
  548. end
  549. source = info.source
  550. if source:sub(1, 1) == '@' then
  551. if source:sub(-4, -1) == '.lua' then
  552. source = source:sub(1, -5)
  553. end
  554. source = source:sub(-hook_sfnamelen, -1)
  555. end
  556. lnum = lnum or info.currentline
  557. end
  558. -- assert(-1 <= lnum and lnum <= 99999)
  559. local lnum_s = lnum == -1 and 'nknwn' or ('%u'):format(lnum)
  560. --- @type string
  561. local msg = ( -- lua does not support %*
  562. ''
  563. .. msgchar
  564. .. whatchar
  565. .. namewhatchar
  566. .. ' '
  567. .. source
  568. .. (' '):rep(hook_sfnamelen - #source)
  569. .. ':'
  570. .. funcname
  571. .. (' '):rep(hook_fnamelen - #funcname)
  572. .. ':'
  573. .. ('0'):rep(hook_numlen - #lnum_s)
  574. .. lnum_s
  575. .. '\n'
  576. )
  577. -- eq(hook_msglen, #msg)
  578. sc.write(wr, msg)
  579. end
  580. debug.sethook(hook, 'crl')
  581. end
  582. local trace_end_msg = ('E%s\n'):format((' '):rep(hook_msglen - 2))
  583. --- @type function
  584. local _debug_log
  585. local debug_log = only_separate(function(...)
  586. return _debug_log(...)
  587. end)
  588. local function itp_child(wr, func)
  589. --- @param s string
  590. _debug_log = function(s)
  591. s = s:sub(1, hook_msglen - 2)
  592. sc.write(wr, '>' .. s .. (' '):rep(hook_msglen - 2 - #s) .. '\n')
  593. end
  594. local status, result = pcall(init)
  595. if status then
  596. collectgarbage('stop')
  597. child_sethook(wr)
  598. status, result = pcall(func)
  599. debug.sethook()
  600. end
  601. sc.write(wr, trace_end_msg)
  602. if not status then
  603. local emsg = tostring(result)
  604. if #emsg > 99999 then
  605. emsg = emsg:sub(1, 99999)
  606. end
  607. sc.write(wr, ('-\n%05u\n%s'):format(#emsg, emsg))
  608. deinit()
  609. else
  610. sc.write(wr, '+\n')
  611. deinit()
  612. end
  613. collectgarbage('restart')
  614. collectgarbage()
  615. sc.write(wr, '$\n')
  616. sc.close(wr)
  617. sc.exit(status and 0 or 1)
  618. end
  619. local function check_child_err(rd)
  620. local trace = {} --- @type string[]
  621. local did_traceline = false
  622. local maxtrace = tonumber(os.getenv('NVIM_TEST_MAXTRACE')) or 1024
  623. while true do
  624. local traceline = sc.read(rd, hook_msglen)
  625. if #traceline ~= hook_msglen then
  626. if #traceline == 0 then
  627. break
  628. else
  629. trace[#trace + 1] = 'Partial read: <' .. trace .. '>\n'
  630. end
  631. end
  632. if traceline == trace_end_msg then
  633. did_traceline = true
  634. break
  635. end
  636. trace[#trace + 1] = traceline
  637. if #trace > maxtrace then
  638. table.remove(trace, 1)
  639. end
  640. end
  641. local res = sc.read(rd, 2)
  642. if #res == 2 then
  643. local err = ''
  644. if res ~= '+\n' then
  645. eq('-\n', res)
  646. local len_s = sc.read(rd, 5)
  647. local len = tonumber(len_s)
  648. neq(0, len)
  649. if os.getenv('NVIM_TEST_TRACE_ON_ERROR') == '1' and #trace ~= 0 then
  650. --- @type string
  651. err = '\nTest failed, trace:\n' .. tracehelp
  652. for _, traceline in ipairs(trace) do
  653. --- @type string
  654. err = err .. traceline
  655. end
  656. end
  657. --- @type string
  658. err = err .. sc.read(rd, len + 1)
  659. end
  660. local eres = sc.read(rd, 2)
  661. if eres ~= '$\n' then
  662. if #trace == 0 then
  663. err = '\nTest crashed, no trace available (check NVIM_TEST_TRACE_LEVEL)\n'
  664. else
  665. err = '\nTest crashed, trace:\n' .. tracehelp
  666. for i = 1, #trace do
  667. err = err .. trace[i]
  668. end
  669. end
  670. if not did_traceline then
  671. --- @type string
  672. err = err .. '\nNo end of trace occurred'
  673. end
  674. local cc_err, cc_emsg = pcall(check_cores, paths.test_luajit_prg, true)
  675. if not cc_err then
  676. --- @type string
  677. err = err .. '\ncheck_cores failed: ' .. cc_emsg
  678. end
  679. end
  680. if err ~= '' then
  681. assert.just_fail(err)
  682. end
  683. end
  684. end
  685. local function itp_parent(rd, pid, allow_failure, location)
  686. local ok, emsg = pcall(check_child_err, rd)
  687. local status = sc.wait(pid)
  688. sc.close(rd)
  689. if not ok then
  690. if allow_failure then
  691. io.stderr:write('Errorred out (' .. status .. '):\n' .. tostring(emsg) .. '\n')
  692. os.execute([[
  693. sh -c "source ci/common/test.sh
  694. check_core_dumps --delete \"]] .. paths.test_luajit_prg .. [[\""]])
  695. else
  696. error(tostring(emsg) .. '\nexit code: ' .. status)
  697. end
  698. elseif status ~= 0 then
  699. if not allow_failure then
  700. error('child process errored out with status ' .. status .. '!\n\n' .. location)
  701. end
  702. end
  703. end
  704. local function gen_itp(it)
  705. child_calls_mod = {}
  706. child_calls_mod_once = {}
  707. child_cleanups_mod_once = {}
  708. preprocess_cache_mod = map(function(v)
  709. return v
  710. end, preprocess_cache_init)
  711. previous_defines_mod = previous_defines_init
  712. cdefs_mod = cdefs_init:copy()
  713. local function itp(name, func, allow_failure)
  714. if allow_failure and os.getenv('NVIM_TEST_RUN_FAILING_TESTS') ~= '1' then
  715. -- FIXME Fix tests with this true
  716. return
  717. end
  718. -- Pre-emptively calculating error location, wasteful, ugh!
  719. -- But the way this code messes around with busted implies the real location is strictly
  720. -- not available in the parent when an actual error occurs. so we have to do this here.
  721. local location = debug.traceback()
  722. it(name, function()
  723. local rd, wr = sc.pipe()
  724. child_pid = sc.fork()
  725. if child_pid == 0 then
  726. sc.close(rd)
  727. itp_child(wr, func)
  728. else
  729. sc.close(wr)
  730. local saved_child_pid = child_pid
  731. child_pid = nil
  732. itp_parent(rd, saved_child_pid, allow_failure, location)
  733. end
  734. end)
  735. end
  736. return itp
  737. end
  738. local function cppimport(path)
  739. return cimport(paths.test_source_path .. '/test/includes/pre/' .. path)
  740. end
  741. cimport(
  742. './src/nvim/types_defs.h',
  743. './src/nvim/main.h',
  744. './src/nvim/os/time.h',
  745. './src/nvim/os/fs.h'
  746. )
  747. local function conv_enum(etab, eval)
  748. local n = tonumber(eval)
  749. return etab[n] or n
  750. end
  751. local function array_size(arr)
  752. return ffi.sizeof(arr) / ffi.sizeof(arr[0])
  753. end
  754. local function kvi_size(kvi)
  755. return array_size(kvi.init_array)
  756. end
  757. local function kvi_init(kvi)
  758. kvi.capacity = kvi_size(kvi)
  759. kvi.items = kvi.init_array
  760. return kvi
  761. end
  762. local function kvi_destroy(kvi)
  763. if kvi.items ~= kvi.init_array then
  764. lib.xfree(kvi.items)
  765. end
  766. end
  767. local function kvi_new(ct)
  768. return kvi_init(ffi.new(ct))
  769. end
  770. local function make_enum_conv_tab(m, values, skip_pref, set_cb)
  771. child_call_once(function()
  772. local ret = {}
  773. for _, v in ipairs(values) do
  774. local str_v = v
  775. if v:sub(1, #skip_pref) == skip_pref then
  776. str_v = v:sub(#skip_pref + 1)
  777. end
  778. ret[tonumber(m[v])] = str_v
  779. end
  780. set_cb(ret)
  781. end)
  782. end
  783. local function ptr2addr(ptr)
  784. return tonumber(ffi.cast('intptr_t', ffi.cast('void *', ptr)))
  785. end
  786. local s = ffi.new('char[64]', { 0 })
  787. local function ptr2key(ptr)
  788. ffi.C.snprintf(s, ffi.sizeof(s), '%p', ffi.cast('void *', ptr))
  789. return ffi.string(s)
  790. end
  791. --- @class test.unit.testutil.module
  792. local M = {
  793. cimport = cimport,
  794. cppimport = cppimport,
  795. internalize = internalize,
  796. ffi = ffi,
  797. lib = lib,
  798. cstr = cstr,
  799. to_cstr = to_cstr,
  800. NULL = ffi.cast('void*', 0),
  801. OK = 1,
  802. FAIL = 0,
  803. alloc_log_new = alloc_log_new,
  804. gen_itp = gen_itp,
  805. only_separate = only_separate,
  806. child_call_once = child_call_once,
  807. child_cleanup_once = child_cleanup_once,
  808. sc = sc,
  809. conv_enum = conv_enum,
  810. array_size = array_size,
  811. kvi_destroy = kvi_destroy,
  812. kvi_size = kvi_size,
  813. kvi_init = kvi_init,
  814. kvi_new = kvi_new,
  815. make_enum_conv_tab = make_enum_conv_tab,
  816. ptr2addr = ptr2addr,
  817. ptr2key = ptr2key,
  818. debug_log = debug_log,
  819. }
  820. --- @class test.unit.testutil: test.unit.testutil.module, test.testutil
  821. M = vim.tbl_extend('error', M, t_global)
  822. return M