From 40829cdfc61696ca11b16c0d421556a1c04db7a7 Mon Sep 17 00:00:00 2001 From: luaneko Date: Fri, 27 Jun 2025 03:11:49 +1000 Subject: [PATCH] Implement basic lua test harness --- .luarc.json | 6 +++- Cargo.toml | 4 +++ src/main.rs | 17 ++++----- tests/main.lua | 97 ++++++++++++++++++++++++++++++++++++++++++++++++++ tests/main.rs | 44 +++++++++++++++++++++++ 5 files changed, 157 insertions(+), 11 deletions(-) create mode 100644 tests/main.lua create mode 100644 tests/main.rs diff --git a/.luarc.json b/.luarc.json index 00c3492..394b1fb 100644 --- a/.luarc.json +++ b/.luarc.json @@ -1,5 +1,9 @@ { "$schema": "https://raw.githubusercontent.com/LuaLS/vscode-lua/master/setting/schema.json", "runtime.version": "LuaJIT", - "diagnostics.disable": ["redefined-local", "lowercase-global"] + "diagnostics.disable": [ + "undefined-global", + "redefined-local", + "lowercase-global" + ] } diff --git a/Cargo.toml b/Cargo.toml index 6a1d926..be681a2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,6 +30,10 @@ repository.workspace = true dev.panic = "abort" release.panic = "abort" +[[test]] +name = "main" +harness = false + [features] default = ["task", "fs", "net"] task = ["lb/task"] diff --git a/src/main.rs b/src/main.rs index 2f17ad4..b4b21d1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -219,17 +219,14 @@ fn init_tokio(args: &Args) -> tokio::runtime::Runtime { fn init_lua(args: &Args) -> lb::runtime::Runtime { let mut rt = { let mut rt = lb::runtime::Builder::new(); - rt.unhandled_error(error_cb); luby::open(&mut rt); if args.dump.iter().find(|s| *s == "cdef").is_some() { - print!("{}", rt.registry()); // for debugging + print!("{}", rt.registry()); // for cdef debugging } - rt - } - .build() - .unwrap(); + rt.unhandled_error(error_cb).build().unwrap() + }; for arg in args.jit.iter() { 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)) { - cx.report_error(&err); - } else if let Err(err) = cx.call_async(0, 0).await { - cx.report_error(&err); + if let Err(ref err) = cx.load(&luajit::Chunk::new(chunk).path(path)) { + cx.report_error(err); + } else if let Err(ref err) = cx.call_async(0, 0).await { + cx.report_error(err); } } diff --git a/tests/main.lua b/tests/main.lua new file mode 100644 index 0000000..079ac1f --- /dev/null +++ b/tests/main.lua @@ -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)) diff --git a/tests/main.rs b/tests/main.rs new file mode 100644 index 0000000..3218d22 --- /dev/null +++ b/tests/main.rs @@ -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); +}