local M = {} local stats = { passed = 0, failed = 0, errors = {} } local defers = {} local label = "?" local started = false local color_on = vim.env.TEST_COLOR == "1" local function color(code, str) if color_on then return string.format("\27[%sm%s\27[0m", code, str) end return str end local function red(s) return color("31", s) end local function green(s) return color("32", s) end local function ensure_started() if started then return end started = true io.stdout:write(string.format("-> %s ", label)) io.stdout:flush() end ---Begin a new test file. Resets per-file stats and the cleanup queue ---and stages the per-file header for the next `M.test` call. ---@param path string function M.start_file(path) label = path started = false stats = { passed = 0, failed = 0, errors = {} } defers = {} end ---Print the per-file summary and return the counts so the runner can ---accumulate totals across files. ---@return integer passed ---@return integer failed function M.report() ensure_started() if stats.failed > 0 then io.stdout:write(" " .. red("FAIL") .. "\n") for _, e in ipairs(stats.errors) do io.stdout:write( string.format( " %s %s\n %s\n", red("FAIL"), e.name, e.err ) ) end else io.stdout:write(" " .. green("OK") .. "\n") end return stats.passed, stats.failed end ---Queue a function to run when the current `M.test` completes (whether ---it passed or failed). Deferred calls run in LIFO order. ---@param fn fun() function M.defer(fn) table.insert(defers, fn) end ---@param name string ---@param fn fun() function M.test(name, fn) ensure_started() local saved_cwd = vim.fn.getcwd() local ok, err = pcall(fn) while #defers > 0 do pcall(table.remove(defers)) end if vim.fn.getcwd() ~= saved_cwd then pcall(vim.cmd.cd, saved_cwd) end if ok then stats.passed = stats.passed + 1 io.stdout:write(".") else stats.failed = stats.failed + 1 table.insert(stats.errors, { name = name, err = err }) io.stdout:write(red("F")) end io.stdout:flush() end local function fmt_value(v) if type(v) == "string" then return string.format("%q", v) end return vim.inspect(v) end ---@param actual any ---@param expected any ---@param msg string? function M.eq(actual, expected, msg) if not vim.deep_equal(actual, expected) then error( string.format( "%s\n expected: %s\n actual: %s", msg or "values differ", fmt_value(expected), fmt_value(actual) ), 2 ) end end ---@param val any ---@param msg string? function M.truthy(val, msg) if not val then error(msg or ("expected truthy, got " .. tostring(val)), 2) end end ---@param val any ---@param msg string? function M.falsy(val, msg) if val then error(msg or ("expected falsy, got " .. tostring(val)), 2) end end ---@param dir string ---@param path string ---@param content string function M.write(dir, path, content) local full = vim.fs.joinpath(dir, path) vim.fn.mkdir(vim.fs.dirname(full), "p") local f = assert(io.open(full --[[@as string]], "w")) f:write(content) f:close() end return M