if (...) ~= nil and (...).type == "group" then return end -- prevent recursive harness call local ok = pcall(require, "lb:task") if not ok then error("lua test harness requires lb:task module") end local ok, fs = pcall(require, "lb:fs") if not ok then error("lua test harness requires lb:fs module") end local global = _G local colors = { reset = "\x1b[0m", pass = "\x1b[32;1m", fail = "\x1b[31;1m", } local icons = { check = "\u{2713}", cross = "\u{00d7}", chevron = "\u{203a}", } local function color(name, s) return colors[name] .. s .. colors.reset end local function create_test(name, f, group) local test = { type = "test", name = name or "", group = group, state = "pending", f = f } local fenv = setmetatable({}, { __index = global }) setfenv(f, fenv) return test end local function create_group(name, f, parent) local group = { type = "group", name = name or "", parent = parent, items = {} } local fenv = setmetatable({ describe = function(name, f) local item = create_group(name, f, group) table.insert(group.items, item) return item end, test = function(name, f) local item = create_test(name, f, group) table.insert(group.items, item) return item end, }, { __index = global }) setfenv(f, fenv) f(group) return group end local function name_test(test) local name = test.name local group = test.group while group ~= nil do if group.name ~= "" then name = string.format("%s %s %s", group.name, icons.chevron, name) end group = group.parent end return name end local function trace(msg) return color("fail", msg) .. debug.traceback("", 2):sub(("\nstack traceback:"):len() + 1) end local function run_test(test) local ok, res = xpcall(test.f, trace, test) if ok then test.state = "pass" print("", color("pass", "PASS") .. " " .. name_test(test)) else test.state = "fail" print("", color("fail", "FAIL") .. " " .. name_test(test) .. "\n") print(res .. "\n") end return test end local function start(cx, item) if item.type == "test" then table.insert(cx.tasks, spawn(run_test, item)) elseif item.type == "group" then for _, item in ipairs(item.items) do start(cx, item) end end end local function main(item) local cx = { tasks = {} } local pass, fail = 0, 0 start(cx, item) for _, task in ipairs(cx.tasks) do if task:await().state == "pass" then pass = pass + 1 else fail = fail + 1 end end if fail == 0 then print("", color("pass", string.format("%s %d tests passed", icons.check, pass))) return 0 end print( "", color("pass", string.format("%s %d tests passed", icons.check, pass)) .. ", " .. color("fail", string.format("%s %d tests failed", icons.cross, fail)) ) return 1 -- report error to cargo end return main(create_group("", function() local function glob(path, pat) for entry in fs:glob_dir(path, pat) do local path = entry:path() local f, err = loadfile(path) if not f then error(err) end describe(path, f) end end glob("tests", "**/*.lua") glob("crates", "*/tests/**/*.lua") end))