Run all lua examples in docs as doctests

This commit is contained in:
2025-06-30 22:01:00 +10:00
parent 81cd901ea6
commit da2598b534
3 changed files with 65 additions and 29 deletions

View File

@@ -12,6 +12,7 @@ local color = {
reset = "\x1b[0m",
pass = "\x1b[32;1m", -- green
fail = "\x1b[31;1m", -- red
faint = "\x1b[2;39;49m", -- faint
}
local icon = {
@@ -24,6 +25,12 @@ local function style(name, s)
return ("%s%s%s"):format(color[name], s, color.reset)
end
local function rjust(s, w)
if w == nil then w = 12 end
if #s >= w then return s end
return (" "):rep(w - #s) .. s
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, __newindex = global })
@@ -67,13 +74,13 @@ local function trace(msg)
end
local function run_test(test)
local ok, res = xpcall(test.f, trace, test)
local ok, trace = xpcall(test.f, trace, test)
if ok then
test.state = "pass"
print("", ("%s %s"):format(style("pass", "PASS"), name_test(test)))
print(("%s %s"):format(style("pass", rjust("PASS")), name_test(test)))
else
test.state = "fail"
print("", ("%s %s\n\n%s\n"):format(style("fail", "FAIL"), name_test(test), res))
print(("%s %s\n\n%s\n"):format(style("fail", rjust("!!! FAIL")), name_test(test), trace))
end
collectgarbage() -- gc after each test to test destructors
return test
@@ -118,28 +125,24 @@ local function main(item)
end
end
local elapsed = time:elapsed_secs()
local code = 1
local retcode
if fail == 0 then
print("", style("pass", ("%s %d tests passed"):format(icon.check, pass)))
code = 0
print(style("pass", ("\t%s %d tests passed"):format(icon.check, pass)))
retcode = 0
else
print(
"",
("%s, %s"):format(
("\t%s, %s"):format(
style("pass", ("%s %d tests passed"):format(icon.check, pass)),
style("fail", ("%s %d tests failed"):format(icon.cross, fail))
)
)
retcode = 1
end
if elapsed < 1000 then
print("", ("%s completed in %.2f ms"):format(icon.chevron, elapsed * 1000))
else
print("", ("%s completed in %.2f s"):format(icon.chevron, elapsed))
end
print(style("faint", ("\t%s completed in %.2fs"):format(icon.chevron, elapsed)))
cx = nil
collectgarbage()
check_refs()
return code -- report error to cargo
check_refs() -- check that all refs were properly unref'ed in destructors
return retcode -- report error to cargo
end
return main(create_group("", function()
@@ -152,6 +155,34 @@ return main(create_group("", function()
end
end
local function include_doctest(path, pat)
for entry in fs.glob_dir(path, pat) do
local line, doctest = 0, nil
for s in fs.read(entry:path()):gmatch("([^\n]*)\n?") do
line = line + 1
local prefix = s:match("^%s*///")
s = prefix and s:sub(#prefix + 1)
if s and not s:match("^%s*```%s*$") then
if s:match("^%s*```lua$") then
doctest = { line = line, col = #prefix + 2 }
elseif doctest then
table.insert(doctest, (" "):rep(#prefix) .. s)
end
else
if doctest then
local name = ("%s:%d:%d"):format(entry:path(), doctest.line, doctest.col)
local f, err = loadstring(table.concat(doctest, "\n"), "@" .. name)
if not f then error(err) end
test(("%s %s"):format(name, style("faint", "(doctest)")), f)
end
doctest = nil
end
end
end
end
include("tests", "**/*.lua")
include("crates", "*/tests/**/*.lua")
include_doctest("src", "**/*.rs")
include_doctest("crates", "*/src/**/*.rs")
end))