Implement basic lua test harness

This commit is contained in:
lumi 2025-06-27 03:11:49 +10:00
parent db4faea821
commit 40829cdfc6
Signed by: luaneko
GPG Key ID: 406809B8763FF07A
5 changed files with 157 additions and 11 deletions

View File

@ -1,5 +1,9 @@
{ {
"$schema": "https://raw.githubusercontent.com/LuaLS/vscode-lua/master/setting/schema.json", "$schema": "https://raw.githubusercontent.com/LuaLS/vscode-lua/master/setting/schema.json",
"runtime.version": "LuaJIT", "runtime.version": "LuaJIT",
"diagnostics.disable": ["redefined-local", "lowercase-global"] "diagnostics.disable": [
"undefined-global",
"redefined-local",
"lowercase-global"
]
} }

View File

@ -30,6 +30,10 @@ repository.workspace = true
dev.panic = "abort" dev.panic = "abort"
release.panic = "abort" release.panic = "abort"
[[test]]
name = "main"
harness = false
[features] [features]
default = ["task", "fs", "net"] default = ["task", "fs", "net"]
task = ["lb/task"] task = ["lb/task"]

View File

@ -219,17 +219,14 @@ fn init_tokio(args: &Args) -> tokio::runtime::Runtime {
fn init_lua(args: &Args) -> lb::runtime::Runtime { fn init_lua(args: &Args) -> lb::runtime::Runtime {
let mut rt = { let mut rt = {
let mut rt = lb::runtime::Builder::new(); let mut rt = lb::runtime::Builder::new();
rt.unhandled_error(error_cb);
luby::open(&mut rt); luby::open(&mut rt);
if args.dump.iter().find(|s| *s == "cdef").is_some() { if args.dump.iter().find(|s| *s == "cdef").is_some() {
print!("{}", rt.registry()); // for debugging print!("{}", rt.registry()); // for cdef debugging
} }
rt rt.unhandled_error(error_cb).build().unwrap()
} };
.build()
.unwrap();
for arg in args.jit.iter() { for arg in args.jit.iter() {
let mut s = rt.guard(); let mut s = rt.guard();
@ -281,10 +278,10 @@ async fn main_async(args: Args, cx: &mut lb::runtime::Context) -> Result<(), Exi
} }
}; };
if let Err(err) = cx.load(&luajit::Chunk::new(chunk).path(path)) { if let Err(ref err) = cx.load(&luajit::Chunk::new(chunk).path(path)) {
cx.report_error(&err); cx.report_error(err);
} else if let Err(err) = cx.call_async(0, 0).await { } else if let Err(ref err) = cx.call_async(0, 0).await {
cx.report_error(&err); cx.report_error(err);
} }
} }

97
tests/main.lua Normal file
View File

@ -0,0 +1,97 @@
if (...) ~= nil and (...).type == "group" then return end -- prevent recursive main call
local fs = require("lb:fs")
local global = _G
local colors = {
reset = "\x1b[0m",
pass = "\x1b[32;1m",
fail = "\x1b[31;1m",
}
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 = group.name .. " " .. 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 run(item)
local cx = { tasks = {} }
local pass = true
start(cx, item)
for _, task in ipairs(cx.tasks) do
if task:await().state ~= "pass" then pass = false end
end
if pass then return 0 end -- report status to cargo
return 1
end
return run(create_group("", function()
for entry in fs:glob("{tests,crates/*/tests}/**/*.lua") do
local path = entry:path():sub(3)
local f, err = loadfile(path)
if not f then error(err) end
describe(path, f)
end
end))

44
tests/main.rs Normal file
View File

@ -0,0 +1,44 @@
use luajit::Chunk;
use owo_colors::OwoColorize;
use std::{
fs,
process::{self, ExitCode},
};
fn main() -> ExitCode {
let tokio = tokio::runtime::Runtime::new().unwrap();
let lua = {
let mut rt = lb::runtime::Builder::new();
luby::open(&mut rt);
rt.unhandled_error(error_cb).build().unwrap()
};
let path = "tests/main.lua";
let main = lua.spawn(async move |s| {
if let Err(ref err) = s.load(Chunk::new(fs::read(path).unwrap()).path(path)) {
s.report_error(err);
} else if let Err(ref err) = s.call_async(0, 1).await {
s.report_error(err);
}
if s.slot(1).integer().unwrap_or(1) == 0 {
ExitCode::SUCCESS
} else {
ExitCode::FAILURE
}
});
tokio.block_on(async move {
lua.await;
main.await.unwrap()
})
}
fn error_cb(err: &luajit::Error) {
match err.trace() {
Some(trace) => eprintln!("{}\n{trace}", err.red().bold()),
None => eprintln!("{}", err.red().bold()),
}
process::exit(1);
}