local M = {} local stats = { passed = 0, failed = 0, errors = {} } local pending_cleanup = {} 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 ", vim.env.TEST_FILE_LABEL or "?") ) io.stdout:flush() end ---Tear down repos created during the current test. Stops fs watchers first, ---drains any scheduled callbacks, wipes buffers tied to the repo, then nukes ---the directory. ---@param dir string function M.cleanup(dir) pcall(vim.cmd.cd, "/tmp") pcall(function() require("git.repo").stop_all() end) vim.wait(60) for _, b in ipairs(vim.api.nvim_list_bufs()) do local name = vim.api.nvim_buf_get_name(b) if name:find(dir, 1, true) or name:match("^git[a-z]*://") then pcall(vim.api.nvim_buf_delete, b, { force = true }) end end vim.fn.delete(dir, "rf") end ---@param name string ---@param fn fun() function M.test(name, fn) ensure_started() local ok, err = pcall(fn) while #pending_cleanup > 0 do local dir = table.remove(pending_cleanup) pcall(M.cleanup, dir) 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 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 io.stdout:write( string.format("RESULTS %d %d\n", stats.passed, stats.failed) ) if stats.failed > 0 then vim.cmd("cquit 1") end 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 ... string local function git(dir, ...) local r = vim.system({ "git", ... }, { cwd = dir, text = true }):wait() if r.code ~= 0 then error( string.format( "git %s failed: %s", table.concat({ ... }, " "), vim.trim(r.stderr or "") ), 2 ) end return r 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, "w")) f:write(content) f:close() end ---Create a temporary git repo and seed it with files committed on `main`. ---The directory is auto-cleaned at the end of the current `M.test(...)`. ---@param files table? path -> content ---@return string dir ---@return fun(...): vim.SystemCompleted runner function M.make_repo(files) local dir = vim.fn.tempname() vim.fn.mkdir(dir, "p") git(dir, "init", "-q", "-b", "main") git(dir, "config", "user.email", "t@t.com") git(dir, "config", "user.name", "t") if files and next(files) then for path, content in pairs(files) do M.write(dir, path, content) end git(dir, "add", ".") git(dir, "commit", "-q", "-m", "init") end table.insert(pending_cleanup, dir) return dir, function(...) return git(dir, ...) end end return M