diff options
Diffstat (limited to 'test/helpers.lua')
-rw-r--r-- | test/helpers.lua | 569 |
1 files changed, 225 insertions, 344 deletions
diff --git a/test/helpers.lua b/test/helpers.lua index f9405c011d..3d53aa3be9 100644 --- a/test/helpers.lua +++ b/test/helpers.lua @@ -1,31 +1,31 @@ -local shared = vim -local assert = require('luassert') +local luaassert = require('luassert') local busted = require('busted') -local luv = require('luv') +local uv = vim.uv local Paths = require('test.cmakeconfig.paths') -assert:set_parameter('TableFormatLevel', 100) +luaassert:set_parameter('TableFormatLevel', 100) local quote_me = '[^.%w%+%-%@%_%/]' -- complement (needn't quote) + +--- @param str string +--- @return string local function shell_quote(str) if string.find(str, quote_me) or str == '' then return '"' .. str:gsub('[$%%"\\]', '\\%0') .. '"' - else - return str end + return str end --- @class test.helpers local module = { - REMOVE_THIS = {}, + paths = Paths, } --- @param p string --- @return string local function relpath(p) p = vim.fs.normalize(p) - local cwd = luv.cwd() - return p:gsub("^" .. cwd) + return (p:gsub('^' .. uv.cwd, '')) end --- @param path string @@ -34,84 +34,73 @@ function module.isdir(path) if not path then return false end - local stat = luv.fs_stat(path) + local stat = uv.fs_stat(path) if not stat then return false end return stat.type == 'directory' end ---- @param path string ---- @return boolean -function module.isfile(path) - if not path then - return false - end - local stat = luv.fs_stat(path) - if not stat then - return false - end - return stat.type == 'file' -end - +--- @param ... string|string[] --- @return string function module.argss_to_cmd(...) - local cmd = '' + local cmd = {} --- @type string[] for i = 1, select('#', ...) do local arg = select(i, ...) if type(arg) == 'string' then - cmd = cmd .. ' ' ..shell_quote(arg) + cmd[#cmd + 1] = shell_quote(arg) else + --- @cast arg string[] for _, subarg in ipairs(arg) do - cmd = cmd .. ' ' .. shell_quote(subarg) + cmd[#cmd + 1] = shell_quote(subarg) end end end - return cmd + return table.concat(cmd, ' ') end function module.popen_r(...) return io.popen(module.argss_to_cmd(...), 'r') end --- sleeps the test runner (_not_ the nvim instance) -function module.sleep(ms) - luv.sleep(ms) -end - --- Calls fn() until it succeeds, up to `max` times or until `max_ms` --- milliseconds have passed. +--- Calls fn() until it succeeds, up to `max` times or until `max_ms` +--- milliseconds have passed. +--- @param max integer? +--- @param max_ms integer? +--- @param fn function +--- @return any function module.retry(max, max_ms, fn) - assert(max == nil or max > 0) - assert(max_ms == nil or max_ms > 0) + luaassert(max == nil or max > 0) + luaassert(max_ms == nil or max_ms > 0) local tries = 1 local timeout = (max_ms and max_ms or 10000) - local start_time = luv.now() + local start_time = uv.now() while true do + --- @type boolean, any local status, result = pcall(fn) if status then return result end - luv.update_time() -- Update cached value of luv.now() (libuv: uv_now()). - if (max and tries >= max) or (luv.now() - start_time > timeout) then - busted.fail(string.format("retry() attempts: %d\n%s", tries, tostring(result)), 2) + uv.update_time() -- Update cached value of luv.now() (libuv: uv_now()). + if (max and tries >= max) or (uv.now() - start_time > timeout) then + busted.fail(string.format('retry() attempts: %d\n%s', tries, tostring(result)), 2) end tries = tries + 1 - luv.sleep(20) -- Avoid hot loop... + uv.sleep(20) -- Avoid hot loop... end end local check_logs_useless_lines = { - ['Warning: noted but unhandled ioctl']=1, - ['could cause spurious value errors to appear']=2, - ['See README_MISSING_SYSCALL_OR_IOCTL for guidance']=3, + ['Warning: noted but unhandled ioctl'] = 1, + ['could cause spurious value errors to appear'] = 2, + ['See README_MISSING_SYSCALL_OR_IOCTL for guidance'] = 3, } function module.eq(expected, actual, context) - return assert.are.same(expected, actual, context) + return luaassert.are.same(expected, actual, context) end function module.neq(expected, actual, context) - return assert.are_not.same(expected, actual, context) + return luaassert.are_not.same(expected, actual, context) end --- Asserts that `cond` is true, or prints a message. @@ -120,20 +109,26 @@ end --- @param expected (any) description of expected result --- @param actual (any) description of actual result function module.ok(cond, expected, actual) - assert((not expected and not actual) or (expected and actual), 'if "expected" is given, "actual" is also required') + luaassert( + (not expected and not actual) or (expected and actual), + 'if "expected" is given, "actual" is also required' + ) local msg = expected and ('expected %s, got: %s'):format(expected, tostring(actual)) or nil - return assert(cond, msg) + return luaassert(cond, msg) end local function epicfail(state, arguments, _) state.failure_message = arguments[1] return false end -assert:register("assertion", "epicfail", epicfail) +luaassert:register('assertion', 'epicfail', epicfail) function module.fail(msg) - return assert.epicfail(msg) + return luaassert.epicfail(msg) end +--- @param pat string +--- @param actual string +--- @return boolean function module.matches(pat, actual) if nil ~= string.match(actual, pat) then return true @@ -146,37 +141,55 @@ end --- Retries for 1 second in case of filesystem delay. --- ---@param pat (string) Lua pattern to match lines in the log file ----@param logfile (string) Full path to log file (default=$NVIM_LOG_FILE) ----@param nrlines (number) Search up to this many log lines ----@param inverse (boolean) Assert that the pattern does NOT match. +---@param logfile? (string) Full path to log file (default=$NVIM_LOG_FILE) +---@param nrlines? (number) Search up to this many log lines +---@param inverse? (boolean) Assert that the pattern does NOT match. function module.assert_log(pat, logfile, nrlines, inverse) logfile = logfile or os.getenv('NVIM_LOG_FILE') or '.nvimlog' - assert(logfile ~= nil, 'no logfile') + luaassert(logfile ~= nil, 'no logfile') nrlines = nrlines or 10 inverse = inverse or false module.retry(nil, 1000, function() local lines = module.read_file_list(logfile, -nrlines) or {} - local msg = string.format('Pattern %q %sfound in log (last %d lines): %s:\n%s', - pat, (inverse and '' or 'not '), nrlines, logfile, ' '..table.concat(lines, '\n ')) - for _,line in ipairs(lines) do + local msg = string.format( + 'Pattern %q %sfound in log (last %d lines): %s:\n%s', + pat, + (inverse and '' or 'not '), + nrlines, + logfile, + ' ' .. table.concat(lines, '\n ') + ) + for _, line in ipairs(lines) do if line:match(pat) then - if inverse then error(msg) else return end + if inverse then + error(msg) + else + return + end end end - if not inverse then error(msg) end + if not inverse then + error(msg) + end end) end --- Asserts that `pat` does NOT match any line in the tail of `logfile`. --- --- @see assert_log +--- @param pat (string) Lua pattern to match lines in the log file +--- @param logfile? (string) Full path to log file (default=$NVIM_LOG_FILE) +--- @param nrlines? (number) Search up to this many log lines function module.assert_nolog(pat, logfile, nrlines) return module.assert_log(pat, logfile, nrlines, true) end +--- @param fn fun(...): any +--- @param ... any +--- @return boolean, any function module.pcall(fn, ...) - assert(type(fn) == 'function') + luaassert(type(fn) == 'function') local status, rv = pcall(fn, ...) if status then return status, rv @@ -186,9 +199,10 @@ function module.pcall(fn, ...) -- C:/long/path/foo.lua:186: Expected string, got number -- to: -- .../foo.lua:0: Expected string, got number - local errmsg = tostring(rv):gsub('([%s<])vim[/\\]([^%s:/\\]+):%d+', '%1\xffvim\xff%2:0') - :gsub('[^%s<]-[/\\]([^%s:/\\]+):%d+', '.../%1:0') - :gsub('\xffvim\xff', 'vim/') + local errmsg = tostring(rv) + :gsub('([%s<])vim[/\\]([^%s:/\\]+):%d+', '%1\xffvim\xff%2:0') + :gsub('[^%s<]-[/\\]([^%s:/\\]+):%d+', '.../%1:0') + :gsub('\xffvim\xff', 'vim/') -- Scrub numbers in paths/stacktraces: -- shared.lua:0: in function 'gsplit' @@ -221,8 +235,10 @@ end -- -- Match Lua pattern. -- matches('e[or]+$', pcall_err(function(a, b) error('some error') end, 'arg1', 'arg2')) -- +--- @param fn function +--- @return string function module.pcall_err_withfile(fn, ...) - assert(type(fn) == 'function') + luaassert(type(fn) == 'function') local status, rv = module.pcall(fn, ...) if status == true then error('expected failure, but got success') @@ -230,20 +246,31 @@ function module.pcall_err_withfile(fn, ...) return rv end +--- @param fn function +--- @param ... any +--- @return string function module.pcall_err_withtrace(fn, ...) local errmsg = module.pcall_err_withfile(fn, ...) - return errmsg:gsub('^%.%.%./helpers%.lua:0: ', '') - :gsub('^Error executing lua:- ' ,'') - :gsub('^%[string "<nvim>"%]:0: ' ,'') + return ( + errmsg + :gsub('^%.%.%./helpers%.lua:0: ', '') + :gsub('^Error executing lua:- ', '') + :gsub('^%[string "<nvim>"%]:0: ', '') + ) end -function module.pcall_err(...) - return module.remove_trace(module.pcall_err_withtrace(...)) +--- @param fn function +--- @param ... any +--- @return string +function module.pcall_err(fn, ...) + return module.remove_trace(module.pcall_err_withtrace(fn, ...)) end +--- @param s string +--- @return string function module.remove_trace(s) - return (s:gsub("\n%s*stack traceback:.*", "")) + return (s:gsub('\n%s*stack traceback:.*', '')) end -- initial_path: directory to recurse into @@ -251,12 +278,14 @@ end -- exc_re: exclude pattern(s) (string or table) function module.glob(initial_path, re, exc_re) exc_re = type(exc_re) == 'table' and exc_re or { exc_re } - local paths_to_check = {initial_path} - local ret = {} - local checked_files = {} + local paths_to_check = { initial_path } --- @type string[] + local ret = {} --- @type string[] + local checked_files = {} --- @type table<string,true> local function is_excluded(path) for _, pat in pairs(exc_re) do - if path:match(pat) then return true end + if path:match(pat) then + return true + end end return false end @@ -271,7 +300,7 @@ function module.glob(initial_path, re, exc_re) local full_path = cur_path .. '/' .. e local checked_path = full_path:sub(#initial_path + 1) if (not is_excluded(checked_path)) and e:sub(1, 1) ~= '.' then - local stat = luv.fs_stat(full_path) + local stat = uv.fs_stat(full_path) if stat then local check_key = stat.dev .. ':' .. tostring(stat.ino) if not checked_files[check_key] then @@ -296,9 +325,9 @@ function module.check_logs() for tail in vim.fs.dir(log_dir) do if tail:sub(1, 30) == 'valgrind-' or tail:find('san%.') then local file = log_dir .. '/' .. tail - local fd = io.open(file) + local fd = assert(io.open(file)) local start_msg = ('='):rep(20) .. ' File ' .. file .. ' ' .. ('='):rep(20) - local lines = {} + local lines = {} --- @type string[] local warning_line = 0 for line in fd:lines() do local cur_warning_line = check_logs_useless_lines[line] @@ -310,6 +339,7 @@ function module.check_logs() end fd:close() if #lines > 0 then + --- @type boolean?, file*? local status, f local out = io.stdout if os.getenv('SYMBOLIZER') then @@ -317,8 +347,9 @@ function module.check_logs() end out:write(start_msg .. '\n') if status then + assert(f) for line in f:lines() do - out:write('= '..line..'\n') + out:write('= ' .. line .. '\n') end f:close() else @@ -331,76 +362,67 @@ function module.check_logs() end end end - assert(0 == #runtime_errors, string.format( - 'Found runtime errors in logfile(s): %s', - table.concat(runtime_errors, ', '))) + luaassert( + 0 == #runtime_errors, + string.format('Found runtime errors in logfile(s): %s', table.concat(runtime_errors, ', ')) + ) end function module.sysname() - local platform = luv.os_uname() - if platform and platform.sysname then - return platform.sysname:lower() - end + return uv.os_uname().sysname:lower() end +--- @param s 'win'|'mac'|'freebsd'|'openbsd'|'bsd' +--- @return boolean function module.is_os(s) - if not (s == 'win' - or s == 'mac' - or s == 'freebsd' - or s == 'openbsd' - or s == 'bsd') then - error('unknown platform: '..tostring(s)) - end - return not not ((s == 'win' and (module.sysname():find('windows') or module.sysname():find('mingw'))) + if not (s == 'win' or s == 'mac' or s == 'freebsd' or s == 'openbsd' or s == 'bsd') then + error('unknown platform: ' .. tostring(s)) + end + return not not ( + (s == 'win' and (module.sysname():find('windows') or module.sysname():find('mingw'))) or (s == 'mac' and module.sysname() == 'darwin') or (s == 'freebsd' and module.sysname() == 'freebsd') or (s == 'openbsd' and module.sysname() == 'openbsd') - or (s == 'bsd' and module.sysname():find('bsd'))) + or (s == 'bsd' and module.sysname():find('bsd')) + ) end local function tmpdir_get() return os.getenv('TMPDIR') and os.getenv('TMPDIR') or os.getenv('TEMP') end --- Is temp directory `dir` defined local to the project workspace? +--- Is temp directory `dir` defined local to the project workspace? +--- @param dir string? +--- @return boolean local function tmpdir_is_local(dir) - return not not (dir and string.find(dir, 'Xtest')) + return not not (dir and dir:find('Xtest')) end +local tmpname_id = 0 +local tmpdir = tmpdir_get() + --- Creates a new temporary file for use by tests. -module.tmpname = (function() - local seq = 0 - local tmpdir = tmpdir_get() - return (function() - if tmpdir_is_local(tmpdir) then - -- Cannot control os.tmpname() dir, so hack our own tmpname() impl. - seq = seq + 1 - -- "…/Xtest_tmpdir/T42.7" - local fname = ('%s/%s.%d'):format(tmpdir, (_G._nvim_test_id or 'nvim-test'), seq) - io.open(fname, 'w'):close() - return fname - else - local fname = os.tmpname() - if module.is_os('win') and fname:sub(1, 2) == '\\s' then - -- In Windows tmpname() returns a filename starting with - -- special sequence \s, prepend $TEMP path - return tmpdir..fname - elseif fname:match('^/tmp') and module.is_os('mac') then - -- In OS X /tmp links to /private/tmp - return '/private'..fname - else - return fname - end - end - end) -end)() +function module.tmpname() + if tmpdir_is_local(tmpdir) then + -- Cannot control os.tmpname() dir, so hack our own tmpname() impl. + tmpname_id = tmpname_id + 1 + -- "…/Xtest_tmpdir/T42.7" + local fname = ('%s/%s.%d'):format(tmpdir, (_G._nvim_test_id or 'nvim-test'), tmpname_id) + io.open(fname, 'w'):close() + return fname + end -function module.hasenv(name) - local env = os.getenv(name) - if env and env ~= '' then - return env + local fname = os.tmpname() + if module.is_os('win') and fname:sub(1, 2) == '\\s' then + -- In Windows tmpname() returns a filename starting with + -- special sequence \s, prepend $TEMP path + return tmpdir .. fname + elseif module.is_os('mac') and fname:match('^/tmp') then + -- In OS X /tmp links to /private/tmp + return '/private' .. fname end - return nil + + return fname end local function deps_prefix() @@ -416,22 +438,27 @@ function module.check_cores(app, force) -- luacheck: ignore return end app = app or 'build/bin/nvim' -- luacheck: ignore + --- @type string, string?, string[] local initial_path, re, exc_re - local gdb_db_cmd = 'gdb -n -batch -ex "thread apply all bt full" "$_NVIM_TEST_APP" -c "$_NVIM_TEST_CORE"' + local gdb_db_cmd = + 'gdb -n -batch -ex "thread apply all bt full" "$_NVIM_TEST_APP" -c "$_NVIM_TEST_CORE"' local lldb_db_cmd = 'lldb -Q -o "bt all" -f "$_NVIM_TEST_APP" -c "$_NVIM_TEST_CORE"' local random_skip = false -- Workspace-local $TMPDIR, scrubbed and pattern-escaped. -- "./Xtest-tmpdir/" => "Xtest%-tmpdir" - local local_tmpdir = (tmpdir_is_local(tmpdir_get()) - and relpath(tmpdir_get()):gsub('^[ ./]+',''):gsub('%/+$',''):gsub('([^%w])', '%%%1') - or nil) - local db_cmd - if module.hasenv('NVIM_TEST_CORE_GLOB_DIRECTORY') then - initial_path = os.getenv('NVIM_TEST_CORE_GLOB_DIRECTORY') + local local_tmpdir = ( + tmpdir_is_local(tmpdir_get()) + and relpath(tmpdir_get()):gsub('^[ ./]+', ''):gsub('%/+$', ''):gsub('([^%w])', '%%%1') + or nil + ) + local db_cmd --- @type string + local test_glob_dir = os.getenv('NVIM_TEST_CORE_GLOB_DIRECTORY') + if test_glob_dir and test_glob_dir ~= '' then + initial_path = test_glob_dir re = os.getenv('NVIM_TEST_CORE_GLOB_RE') exc_re = { os.getenv('NVIM_TEST_CORE_EXC_RE'), local_tmpdir } db_cmd = os.getenv('NVIM_TEST_CORE_DB_CMD') or gdb_db_cmd - random_skip = os.getenv('NVIM_TEST_CORE_RANDOM_SKIP') + random_skip = os.getenv('NVIM_TEST_CORE_RANDOM_SKIP') ~= '' elseif module.is_os('mac') then initial_path = '/cores' re = nil @@ -444,7 +471,7 @@ function module.check_cores(app, force) -- luacheck: ignore else re = '/core[^/]*$' end - exc_re = { '^/%.deps$', '^/%'..deps_prefix()..'$', local_tmpdir, '^/%node_modules$' } + exc_re = { '^/%.deps$', '^/%' .. deps_prefix() .. '$', local_tmpdir, '^/%node_modules$' } db_cmd = gdb_db_cmd random_skip = true end @@ -457,7 +484,7 @@ function module.check_cores(app, force) -- luacheck: ignore local found_cores = 0 local out = io.stdout for _, core in ipairs(cores) do - local len = 80 - #core - #('Core file ') - 2 + local len = 80 - #core - #'Core file ' - 2 local esigns = ('='):rep(len / 2) out:write(('\n%s Core file %s %s\n'):format(esigns, core, esigns)) out:flush() @@ -471,7 +498,7 @@ function module.check_cores(app, force) -- luacheck: ignore end tests_skipped = 0 if found_cores > 0 then - error("crash detected (see above)") + error('crash detected (see above)') end end @@ -489,21 +516,28 @@ function module.repeated_read_cmd(...) return nil end +--- @generic T +--- @param orig T +--- @return T function module.shallowcopy(orig) if type(orig) ~= 'table' then return orig end - local copy = {} + --- @cast orig table<any,any> + local copy = {} --- @type table<any,any> for orig_key, orig_value in pairs(orig) do copy[orig_key] = orig_value end return copy end +--- @param d1 table<any,any> +--- @param d2 table<any,any> +--- @return table<any,any> function module.mergedicts_copy(d1, d2) local ret = module.shallowcopy(d1) for k, v in pairs(d2) do - if d2[k] == module.REMOVE_THIS then + if d2[k] == vim.NIL then ret[k] = nil elseif type(d1[k]) == 'table' and type(v) == 'table' then ret[k] = module.mergedicts_copy(d1[k], v) @@ -514,16 +548,18 @@ function module.mergedicts_copy(d1, d2) return ret end --- dictdiff: find a diff so that mergedicts_copy(d1, diff) is equal to d2 --- --- Note: does not do copies of d2 values used. +--- dictdiff: find a diff so that mergedicts_copy(d1, diff) is equal to d2 +--- +--- Note: does not do copies of d2 values used. +--- @param d1 table<any,any> +--- @param d2 table<any,any> function module.dictdiff(d1, d2) - local ret = {} + local ret = {} --- @type table<any,any> local hasdiff = false for k, v in pairs(d1) do if d2[k] == nil then hasdiff = true - ret[k] = module.REMOVE_THIS + ret[k] = vim.NIL elseif type(v) == type(d2[k]) then if type(v) == 'table' then local subdiff = module.dictdiff(v, d2[k]) @@ -554,17 +590,11 @@ function module.dictdiff(d1, d2) end end -function module.updated(d, d2) - for k, v in pairs(d2) do - d[k] = v - end - return d -end - -- Concat list-like tables. function module.concat_tables(...) - local ret = {} + local ret = {} --- @type table<any,any> for i = 1, select('#', ...) do + --- @type table<any,any> local tbl = select(i, ...) if tbl then for _, v in ipairs(tbl) do @@ -597,203 +627,48 @@ function module.dedent(str, leave_indent) -- create a pattern for the indent indent = indent:gsub('%s', '[ \t]') -- strip it from the first line - str = str:gsub('^'..indent, left_indent) + str = str:gsub('^' .. indent, left_indent) -- strip it from the remaining lines - str = str:gsub('[\n]'..indent, '\n' .. left_indent) + str = str:gsub('[\n]' .. indent, '\n' .. left_indent) return str end -local function format_float(v) - -- On windows exponent appears to have three digits and not two - local ret = ('%.6e'):format(v) - local l, f, es, e = ret:match('^(%-?%d)%.(%d+)e([+%-])0*(%d%d+)$') - return l .. '.' .. f .. 'e' .. es .. e -end - -local SUBTBL = { - '\\000', '\\001', '\\002', '\\003', '\\004', - '\\005', '\\006', '\\007', '\\008', '\\t', - '\\n', '\\011', '\\012', '\\r', '\\014', - '\\015', '\\016', '\\017', '\\018', '\\019', - '\\020', '\\021', '\\022', '\\023', '\\024', - '\\025', '\\026', '\\027', '\\028', '\\029', - '\\030', '\\031', -} - --- Formats Lua value `v`. --- --- TODO(justinmk): redundant with vim.inspect() ? --- --- "Nice table formatting similar to screen:snapshot_util()". --- Commit: 520c0b91a528 -function module.format_luav(v, indent, opts) - opts = opts or {} - local linesep = '\n' - local next_indent_arg = nil - local indent_shift = opts.indent_shift or ' ' - local next_indent - local nl = '\n' - if indent == nil then - indent = '' - linesep = '' - next_indent = '' - nl = ' ' - else - next_indent_arg = indent .. indent_shift - next_indent = indent .. indent_shift - end - local ret = '' - if type(v) == 'string' then - if opts.literal_strings then - ret = v - else - local quote = opts.dquote_strings and '"' or '\'' - ret = quote .. tostring(v):gsub( - opts.dquote_strings and '["\\]' or '[\'\\]', - '\\%0'):gsub( - '[%z\1-\31]', function(match) - return SUBTBL[match:byte() + 1] - end) .. quote - end - elseif type(v) == 'table' then - if v == module.REMOVE_THIS then - ret = 'REMOVE_THIS' - else - local processed_keys = {} - ret = '{' .. linesep - local non_empty = false - local format_luav = module.format_luav - for i, subv in ipairs(v) do - ret = ('%s%s%s,%s'):format(ret, next_indent, - format_luav(subv, next_indent_arg, opts), nl) - processed_keys[i] = true - non_empty = true - end - for k, subv in pairs(v) do - if not processed_keys[k] then - if type(k) == 'string' and k:match('^[a-zA-Z_][a-zA-Z0-9_]*$') then - ret = ret .. next_indent .. k .. ' = ' - else - ret = ('%s%s[%s] = '):format(ret, next_indent, - format_luav(k, nil, opts)) - end - ret = ret .. format_luav(subv, next_indent_arg, opts) .. ',' .. nl - non_empty = true - end - end - if nl == ' ' and non_empty then - ret = ret:sub(1, -3) - end - ret = ret .. indent .. '}' - end - elseif type(v) == 'number' then - if v % 1 == 0 then - ret = ('%d'):format(v) - else - ret = format_float(v) - end - elseif type(v) == 'nil' then - ret = 'nil' - elseif type(v) == 'boolean' then - ret = (v and 'true' or 'false') - else - print(type(v)) - -- Not implemented yet - assert(false) - end - return ret -end - --- Like Python repr(), "{!r}".format(s) --- --- Commit: 520c0b91a528 -function module.format_string(fmt, ...) - local i = 0 - local args = {...} - local function getarg() - i = i + 1 - return args[i] - end - local ret = fmt:gsub('%%[0-9*]*%.?[0-9*]*[cdEefgGiouXxqsr%%]', function(match) - local subfmt = match:gsub('%*', function() - return tostring(getarg()) - end) - local arg = nil - if subfmt:sub(-1) ~= '%' then - arg = getarg() - end - if subfmt:sub(-1) == 'r' or subfmt:sub(-1) == 'q' then - -- %r is like built-in %q, but it is supposed to single-quote strings and - -- not double-quote them, and also work not only for strings. - -- Builtin %q is replaced here as it gives invalid and inconsistent with - -- luajit results for e.g. "\e" on lua: luajit transforms that into `\27`, - -- lua leaves as-is. - arg = module.format_luav(arg, nil, {dquote_strings = (subfmt:sub(-1) == 'q')}) - subfmt = subfmt:sub(1, -2) .. 's' - end - if subfmt == '%e' then - return format_float(arg) - else - return subfmt:format(arg) - end - end) - return ret -end - function module.intchar2lua(ch) ch = tonumber(ch) return (20 <= ch and ch < 127) and ('%c'):format(ch) or ch end -local fixtbl_metatable = { - __newindex = function() - assert(false) - end, -} - -function module.fixtbl(tbl) - return setmetatable(tbl, fixtbl_metatable) -end - -function module.fixtbl_rec(tbl) - local fixtbl_rec = module.fixtbl_rec - for _, v in pairs(tbl) do - if type(v) == 'table' then - fixtbl_rec(v) - end - end - return module.fixtbl(tbl) -end - +--- @param str string +--- @return string function module.hexdump(str) local len = string.len(str) - local dump = "" - local hex = "" - local asc = "" + local dump = '' + local hex = '' + local asc = '' for i = 1, len do if 1 == i % 8 then - dump = dump .. hex .. asc .. "\n" - hex = string.format("%04x: ", i - 1) - asc = "" + dump = dump .. hex .. asc .. '\n' + hex = string.format('%04x: ', i - 1) + asc = '' end local ord = string.byte(str, i) - hex = hex .. string.format("%02x ", ord) + hex = hex .. string.format('%02x ', ord) if ord >= 32 and ord <= 126 then asc = asc .. string.char(ord) else - asc = asc .. "." + asc = asc .. '.' end end - return dump .. hex .. string.rep(" ", 8 - len % 8) .. asc + return dump .. hex .. string.rep(' ', 8 - len % 8) .. asc end --- Reads text lines from `filename` into a table. --- --- filename: path to file --- start: start line (1-indexed), negative means "lines before end" (tail) +--- Reads text lines from `filename` into a table. +--- @param filename string path to file +--- @param start? integer start line (1-indexed), negative means "lines before end" (tail) +--- @return string[]? function module.read_file_list(filename, start) local lnum = (start ~= nil and type(start) == 'number') and start or 1 local tail = (lnum < 0) @@ -805,16 +680,16 @@ function module.read_file_list(filename, start) -- There is no need to read more than the last 2MB of the log file, so seek -- to that. - local file_size = file:seek("end") + local file_size = file:seek('end') local offset = file_size - 2000000 if offset < 0 then offset = 0 end - file:seek("set", offset) + file:seek('set', offset) local lines = {} local i = 1 - local line = file:read("*l") + local line = file:read('*l') while line ~= nil do if i >= start then table.insert(lines, line) @@ -823,15 +698,15 @@ function module.read_file_list(filename, start) end end i = i + 1 - line = file:read("*l") + line = file:read('*l') end file:close() return lines end --- Reads the entire contents of `filename` into a string. --- --- filename: path to file +--- Reads the entire contents of `filename` into a string. +--- @param filename string +--- @return string? function module.read_file(filename) local file = io.open(filename, 'r') if not file then @@ -844,9 +719,10 @@ end -- Dedent the given text and write it to the file name. function module.write_file(name, text, no_dedent, append) - local file = io.open(name, (append and 'a' or 'w')) + local file = assert(io.open(name, (append and 'a' or 'w'))) if type(text) == 'table' then -- Byte blob + --- @type string[] local bytes = text text = '' for _, char in ipairs(bytes) do @@ -860,9 +736,11 @@ function module.write_file(name, text, no_dedent, append) file:close() end +--- @param name? 'cirrus'|'github' +--- @return boolean function module.is_ci(name) local any = (name == nil) - assert(any or name == 'github' or name == 'cirrus') + luaassert(any or name == 'github' or name == 'cirrus') local gh = ((any or name == 'github') and nil ~= os.getenv('GITHUB_ACTIONS')) local cirrus = ((any or name == 'cirrus') and nil ~= os.getenv('CIRRUS_CI')) return gh or cirrus @@ -875,24 +753,27 @@ function module.read_nvim_log(logfile, ci_rename) local is_ci = module.is_ci() local keep = is_ci and 100 or 10 local lines = module.read_file_list(logfile, -keep) or {} - local log = (('-'):rep(78)..'\n' - ..string.format('$NVIM_LOG_FILE: %s\n', logfile) - ..(#lines > 0 and '(last '..tostring(keep)..' lines)\n' or '(empty)\n')) - for _,line in ipairs(lines) do - log = log..line..'\n' - end - log = log..('-'):rep(78)..'\n' + local log = ( + ('-'):rep(78) + .. '\n' + .. string.format('$NVIM_LOG_FILE: %s\n', logfile) + .. (#lines > 0 and '(last ' .. tostring(keep) .. ' lines)\n' or '(empty)\n') + ) + for _, line in ipairs(lines) do + log = log .. line .. '\n' + end + log = log .. ('-'):rep(78) .. '\n' if is_ci and ci_rename then os.rename(logfile, logfile .. '.displayed') end return log end +--- @param path string +--- @return boolean? function module.mkdir(path) -- 493 is 0755 in decimal - return luv.fs_mkdir(path, 493) + return (uv.fs_mkdir(path, 493)) end -module = shared.tbl_extend('error', module, Paths, shared, require('test.deprecated')) - return module |