Compare commits

..

6 Commits

Author SHA1 Message Date
5c257b0f74
Update luajit crate docs 2025-06-30 06:24:37 +10:00
1808bee82a
Refactor and overhaul luajit crate 2025-06-30 05:59:54 +10:00
7768c5ec56
Add basic timing library 2025-06-29 18:06:56 +10:00
fcdee34b42
Fix doc errors 2025-06-29 17:58:34 +10:00
5846220e35
Make bacon watch lua files too 2025-06-29 17:48:56 +10:00
b0efc9f783
Report test complete time 2025-06-28 21:31:42 +10:00
18 changed files with 1335 additions and 616 deletions

2
Cargo.lock generated
View File

@ -1053,6 +1053,7 @@ dependencies = [
"derive_more", "derive_more",
"globset", "globset",
"luaffi", "luaffi",
"luaify",
"luajit", "luajit",
"sysexits", "sysexits",
"tempfile", "tempfile",
@ -1168,7 +1169,6 @@ dependencies = [
"bstr", "bstr",
"luaffi", "luaffi",
"luajit-sys", "luajit-sys",
"thiserror",
] ]
[[package]] [[package]]

View File

@ -35,8 +35,9 @@ name = "main"
harness = false harness = false
[features] [features]
default = ["task", "fs", "net"] default = ["task", "time", "fs", "net"]
task = ["lb/task"] task = ["lb/task"]
time = ["lb/time"]
fs = ["lb/fs"] fs = ["lb/fs"]
net = ["lb/net"] net = ["lb/net"]
tokio-console = ["dep:console-subscriber"] tokio-console = ["dep:console-subscriber"]

View File

@ -1,3 +1,11 @@
default_job = "test"
[jobs.test]
command = ["cargo", "test"]
watch = ["*.lua"]
need_stdout = true
background = false
[jobs.doc] [jobs.doc]
command = ["cargo", "doc", "--workspace"] command = ["cargo", "doc", "--workspace"]

View File

@ -10,6 +10,7 @@ repository.workspace = true
[features] [features]
runtime = ["tokio/rt"] runtime = ["tokio/rt"]
task = ["tokio/rt", "tokio/time"] task = ["tokio/rt", "tokio/time"]
time = []
fs = ["tokio/fs", "dep:walkdir", "dep:globset", "dep:tempfile"] fs = ["tokio/fs", "dep:walkdir", "dep:globset", "dep:tempfile"]
net = ["tokio/net", "tokio/io-util"] net = ["tokio/net", "tokio/io-util"]
@ -17,6 +18,7 @@ net = ["tokio/net", "tokio/io-util"]
derive_more = { version = "2.0.1", features = ["full"] } derive_more = { version = "2.0.1", features = ["full"] }
globset = { version = "0.4.16", optional = true } globset = { version = "0.4.16", optional = true }
luaffi = { path = "../luaffi" } luaffi = { path = "../luaffi" }
luaify = { path = "../luaify" }
luajit = { path = "../luajit" } luajit = { path = "../luajit" }
sysexits = "0.9.0" sysexits = "0.9.0"
tempfile = { version = "3.20.0", optional = true } tempfile = { version = "3.20.0", optional = true }

View File

@ -11,7 +11,7 @@ use luaffi::{cdef, metatype};
/// Items exported by the `lb:chan` library. /// Items exported by the `lb:chan` library.
/// ///
/// This library can be acquired by calling /// This library can be acquired by calling
/// [`require("lb:chan")`](https://www.lua.org/manual/5.1/manual.html#pdf-require) in Lua. /// [`require("lb:chan")`](https://www.lua.org/manual/5.1/manual.html#pdf-require).
/// ///
/// ```lua /// ```lua
/// local chan = require("lb:chan"); /// local chan = require("lb:chan");

View File

@ -22,14 +22,14 @@ use thiserror::Error;
/// Errors that can be thrown by this library. /// Errors that can be thrown by this library.
/// ///
/// Functions which return this error will **throw** in Lua. The error message can be caught by /// Functions which return this error will **throw**. The error message can be caught by using
/// using [`pcall(f, ...)`](https://www.lua.org/manual/5.1/manual.html#pdf-pcall). /// [`pcall(f, ...)`](https://www.lua.org/manual/5.1/manual.html#pdf-pcall).
#[derive(Debug, Error)] #[derive(Debug, Error)]
pub enum Error { pub enum Error {
/// Attempt to access an object while it is being modified. /// Attempt to access an object while it is being modified.
#[error("cannot access object while it is being modified")] #[error("cannot access object while it is being modified")]
Borrow(#[from] BorrowError), Borrow(#[from] BorrowError),
/// Attempt to modify an object while it is in use /// Attempt to modify an object while it is in use.
#[error("cannot modify object while it is in use")] #[error("cannot modify object while it is in use")]
BorrowMut(#[from] BorrowMutError), BorrowMut(#[from] BorrowMutError),
/// I/O error. /// I/O error.
@ -48,7 +48,7 @@ type Result<T> = std::result::Result<T, Error>;
/// Items exported by the `lb:fs` library. /// Items exported by the `lb:fs` library.
/// ///
/// This library can be acquired by calling /// This library can be acquired by calling
/// [`require("lb:fs")`](https://www.lua.org/manual/5.1/manual.html#pdf-require) in Lua. /// [`require("lb:fs")`](https://www.lua.org/manual/5.1/manual.html#pdf-require).
/// ///
/// ```lua /// ```lua
/// local fs = require("lb:fs"); /// local fs = require("lb:fs");

View File

@ -1,5 +1,4 @@
//! luby standard library //! luby standard library
#![warn(missing_docs)]
#[cfg(feature = "task")] #[cfg(feature = "task")]
pub mod chan; pub mod chan;
#[cfg(feature = "fs")] #[cfg(feature = "fs")]
@ -10,3 +9,5 @@ pub mod net;
pub mod runtime; pub mod runtime;
#[cfg(feature = "task")] #[cfg(feature = "task")]
pub mod task; pub mod task;
#[cfg(feature = "time")]
pub mod time;

View File

@ -28,14 +28,14 @@ use tokio::{
/// Errors that can be thrown by this library. /// Errors that can be thrown by this library.
/// ///
/// Functions which return this error will **throw** in Lua. The error message can be caught by /// Functions which return this error will **throw**. The error message can be caught by using
/// using [`pcall(f, ...)`](https://www.lua.org/manual/5.1/manual.html#pdf-pcall). /// [`pcall(f, ...)`](https://www.lua.org/manual/5.1/manual.html#pdf-pcall).
#[derive(Debug, Error)] #[derive(Debug, Error)]
pub enum Error { pub enum Error {
/// Attempt to access an object while it is being modified. /// Attempt to access an object while it is being modified.
#[error("cannot access object while it is being modified")] #[error("cannot access object while it is being modified")]
Borrow(#[from] BorrowError), Borrow(#[from] BorrowError),
/// Attempt to modify an object while it is in use /// Attempt to modify an object while it is in use.
#[error("cannot modify object while it is in use")] #[error("cannot modify object while it is in use")]
BorrowMut(#[from] BorrowMutError), BorrowMut(#[from] BorrowMutError),
/// I/O error. /// I/O error.
@ -54,7 +54,7 @@ type Result<T> = std::result::Result<T, Error>;
/// Items exported by the `lb:net` library. /// Items exported by the `lb:net` library.
/// ///
/// This library can be acquired by calling /// This library can be acquired by calling
/// [`require("lb:net")`](https://www.lua.org/manual/5.1/manual.html#pdf-require) in Lua. /// [`require("lb:net")`](https://www.lua.org/manual/5.1/manual.html#pdf-require).
/// ///
/// ```lua /// ```lua
/// local net = require("lb:net"); /// local net = require("lb:net");
@ -501,9 +501,9 @@ impl lb_ipaddr {
/// Socket address, which is an IP address with a port number. /// Socket address, which is an IP address with a port number.
/// ///
/// This represents a combination of an IP address and a port, such as `127.0.0.1:8080` or /// This represents an IP address with a prescribed port, such as `127.0.0.1:8080` or `[::1]:443`.
/// `[::1]:443`. It is used to specify endpoints for network connections and listeners, and can be /// It is used to specify endpoints for network connections and listeners, and can be constructed by
/// constructed by [`socketaddr`](lb_libnet::socketaddr). /// [`socketaddr`](lb_netlib::socketaddr).
#[derive(Debug, Clone, Copy, PartialEq, Eq, From, FromStr)] #[derive(Debug, Clone, Copy, PartialEq, Eq, From, FromStr)]
#[cdef] #[cdef]
pub struct lb_socketaddr(#[opaque] SocketAddr); pub struct lb_socketaddr(#[opaque] SocketAddr);

View File

@ -1,7 +1,8 @@
#![doc(hidden)] #![doc(hidden)]
use derive_more::{Deref, DerefMut}; use derive_more::{Deref, DerefMut};
use luaffi::{Module, Registry}; use luaffi::{Module, Registry};
use luajit::{Chunk, State}; use luaify::luaify_chunk;
use luajit::{Chunk, Index, NewTable, State};
use std::rc::Rc; use std::rc::Rc;
use tokio::{ use tokio::{
task::{JoinHandle, LocalSet, futures::TaskLocalFuture, spawn_local}, task::{JoinHandle, LocalSet, futures::TaskLocalFuture, spawn_local},
@ -13,6 +14,7 @@ pub type ErrorFn = dyn Fn(&luajit::Error);
pub struct Builder { pub struct Builder {
registry: Registry, registry: Registry,
report_err: Rc<ErrorFn>, report_err: Rc<ErrorFn>,
prohibit_globals: bool,
} }
impl Builder { impl Builder {
@ -23,6 +25,7 @@ impl Builder {
Some(trace) => eprintln!("unhandled lua error: {err}\n{trace}"), Some(trace) => eprintln!("unhandled lua error: {err}\n{trace}"),
None => eprintln!("unhandled lua error: {err}"), None => eprintln!("unhandled lua error: {err}"),
}), }),
prohibit_globals: false,
} }
} }
@ -35,19 +38,42 @@ impl Builder {
self self
} }
pub fn prohibit_globals(&mut self, enabled: bool) -> &mut Self {
self.prohibit_globals = enabled;
self
}
pub fn module<T: Module>(&mut self) -> &mut Self { pub fn module<T: Module>(&mut self) -> &mut Self {
self.registry.preload::<T>(); self.registry.preload::<T>();
self self
} }
pub fn build(&self) -> luajit::Result<Runtime> { pub fn build(&self) -> luajit::Result<Runtime> {
let mut state = State::new()?;
let chunk = Chunk::new(self.registry.build()).with_path("[luby]");
state.eval(&chunk, 0, Some(0))?;
if self.prohibit_globals {
let mut s = state.guard();
s.eval(
&Chunk::new(luaify_chunk!({
return |self, key, value| {
error(("undeclared local variable '%s'").format(key), 2);
};
})),
0,
Some(1),
)
.unwrap();
s.push(NewTable::new());
(s.push("__index"), s.push_idx(-3), s.set(-3));
(s.push("__newindex"), s.push_idx(-3), s.set(-3));
s.set_metatable(Index::globals());
}
Ok(Runtime { Ok(Runtime {
cx: Context { cx: Context {
state: { state,
let mut s = State::new()?;
s.eval(Chunk::new(self.registry.build()).path("[luby]"), 0, 0)?;
s
},
report_err: self.report_err.clone(), report_err: self.report_err.clone(),
}, },
tasks: LocalSet::new(), tasks: LocalSet::new(),
@ -97,7 +123,7 @@ pub struct Context {
impl Context { impl Context {
pub fn new_thread(&self) -> Self { pub fn new_thread(&self) -> Self {
Self { Self {
state: self.state.new_thread(), state: State::new_thread(&self.state),
report_err: self.report_err.clone(), report_err: self.report_err.clone(),
} }
} }

View File

@ -12,14 +12,13 @@ use luaffi::{
marker::{function, many}, marker::{function, many},
metatype, metatype,
}; };
use luajit::LUA_MULTRET;
use std::{cell::RefCell, ffi::c_int, time::Duration}; use std::{cell::RefCell, ffi::c_int, time::Duration};
use tokio::{task::JoinHandle, time::sleep}; use tokio::{task::JoinHandle, time::sleep};
/// Items exported by the `lb:task` library. /// Items exported by the `lb:task` library.
/// ///
/// This library can be acquired by calling /// This library can be acquired by calling
/// [`require("lb:task")`](https://www.lua.org/manual/5.1/manual.html#pdf-require) in Lua. /// [`require("lb:task")`](https://www.lua.org/manual/5.1/manual.html#pdf-require).
/// ///
/// ```lua /// ```lua
/// local task = require("lb:task"); /// local task = require("lb:task");
@ -59,12 +58,12 @@ impl lb_tasklib {
extern "Lua-C" fn __spawn(spawn_ref: c_int, handle_ref: c_int) -> lb_task { extern "Lua-C" fn __spawn(spawn_ref: c_int, handle_ref: c_int) -> lb_task {
let handle = spawn(async move |cx| { let handle = spawn(async move |cx| {
// SAFETY: handle_ref is always unique, created in Self::spawn above. // SAFETY: handle_ref is always unique, created in Self::spawn above.
let state = unsafe { cx.new_ref_unchecked(spawn_ref) }; let state = unsafe { luajit::Ref::from_raw(cx, spawn_ref) };
let mut s = cx.guard(); let mut s = cx.guard();
s.resize(0); s.resize(0);
s.push(state); // this drops the state table ref, but the table is still on the stack s.push(state); // this drops the state table ref, but the table is still on the stack
let narg = s.unpack(1, 1, None) - 1; // unpack the function and its args from the state table let narg = s.unpack(1, 1, None) - 1; // unpack the function and its args from the state table
match s.call_async(narg, LUA_MULTRET).await { match s.call_async(narg, None).await {
Ok(nret) => { Ok(nret) => {
s.pack(1, nret); // pack the return values back into the state table s.pack(1, nret); // pack the return values back into the state table
} }

30
crates/lb/src/time.rs Normal file
View File

@ -0,0 +1,30 @@
use luaffi::{cdef, metatype};
#[cdef(module = "lb:time")]
pub struct lb_timelib;
#[metatype]
impl lb_timelib {
#[new]
extern "Lua-C" fn new() -> Self {
Self
}
pub extern "Lua-C" fn instant() -> lb_instant {
lb_instant::new(std::time::Instant::now())
}
}
#[cdef]
pub struct lb_instant(#[opaque] std::time::Instant);
#[metatype]
impl lb_instant {
fn new(instant: std::time::Instant) -> Self {
Self(instant)
}
pub extern "Lua-C" fn elapsed(&self) -> f64 {
self.0.elapsed().as_secs_f64()
}
}

View File

@ -1,3 +1,6 @@
//! # luaify
//!
//! A Rust for generating Lua code from Rust syntax.
use crate::{ use crate::{
generate::{generate, generate_chunk}, generate::{generate, generate_chunk},
transform::{transform, transform_chunk}, transform::{transform, transform_chunk},

View File

@ -16,4 +16,3 @@ bitflags = { version = "2.9.1", features = ["std"] }
bstr = "1.12.0" bstr = "1.12.0"
luaffi = { path = "../luaffi" } luaffi = { path = "../luaffi" }
luajit-sys = { path = "../luajit-sys" } luajit-sys = { path = "../luajit-sys" }
thiserror = "2.0.12"

File diff suppressed because it is too large Load Diff

View File

@ -6,6 +6,8 @@ pub use lb::fs;
pub use lb::net; pub use lb::net;
#[cfg(feature = "task")] #[cfg(feature = "task")]
pub use lb::task; pub use lb::task;
#[cfg(feature = "time")]
pub use lb::time;
#[doc(hidden)] #[doc(hidden)]
pub fn open(#[allow(unused)] rt: &mut lb::runtime::Builder) { pub fn open(#[allow(unused)] rt: &mut lb::runtime::Builder) {
@ -13,6 +15,8 @@ pub fn open(#[allow(unused)] rt: &mut lb::runtime::Builder) {
rt.module::<task::lb_tasklib>(); rt.module::<task::lb_tasklib>();
#[cfg(feature = "task")] #[cfg(feature = "task")]
rt.module::<chan::lb_chanlib>(); rt.module::<chan::lb_chanlib>();
#[cfg(feature = "time")]
rt.module::<time::lb_timelib>();
#[cfg(feature = "fs")] #[cfg(feature = "fs")]
rt.module::<fs::lb_fslib>(); rt.module::<fs::lb_fslib>();
#[cfg(feature = "net")] #[cfg(feature = "net")]

View File

@ -1,4 +1,5 @@
use clap::Parser; use clap::Parser;
use luajit::Chunk;
use mimalloc::MiMalloc; use mimalloc::MiMalloc;
use owo_colors::OwoColorize; use owo_colors::OwoColorize;
use std::{backtrace::Backtrace, fmt::Display, num::NonZero, panic, process, thread}; use std::{backtrace::Backtrace, fmt::Display, num::NonZero, panic, process, thread};
@ -78,12 +79,21 @@ struct Args {
#[clap(long, short = 'j', help_heading = "Runtime", value_name = "CMD=FLAGS")] #[clap(long, short = 'j', help_heading = "Runtime", value_name = "CMD=FLAGS")]
jit: Vec<String>, jit: Vec<String>,
/// Allow global variables.
#[clap(
long,
help_heading = "Runtime",
value_name = "ENABLED",
default_value_t = true
)]
allow_globals: bool,
/// Number of worker threads. /// Number of worker threads.
#[clap( #[clap(
long, long,
short = 'T', short = 'T',
help_heading = "Runtime", help_heading = "Runtime",
value_name = "THREADS", value_name = "COUNT",
default_value_t = Self::threads() default_value_t = Self::threads()
)] )]
threads: NonZero<usize>, threads: NonZero<usize>,
@ -92,14 +102,14 @@ struct Args {
#[clap( #[clap(
long, long,
help_heading = "Runtime", help_heading = "Runtime",
value_name = "THREADS", value_name = "COUNT",
default_value_t = Self::blocking_threads() default_value_t = Self::blocking_threads()
)] )]
blocking_threads: NonZero<usize>, blocking_threads: NonZero<usize>,
/// Enable tokio-console integration. /// Enable tokio-console integration.
#[cfg(feature = "tokio-console")] #[cfg(feature = "tokio-console")]
#[clap(long, help_heading = "Debugging")] #[clap(long, help_heading = "Debugging", value_name = "ENABLED")]
enable_console: bool, enable_console: bool,
/// tokio-console publish address. /// tokio-console publish address.
@ -229,27 +239,32 @@ fn init_lua(args: &Args) -> lb::runtime::Runtime {
print!("{}", rt.registry()); // for cdef debugging print!("{}", rt.registry()); // for cdef debugging
} }
rt.unhandled_error(error_cb).build().unwrap() rt.unhandled_error(error_cb)
.prohibit_globals(!args.allow_globals)
.build()
.unwrap()
}; };
for arg in args.jit.iter() { for arg in args.jit.iter() {
let mut s = rt.guard(); let mut s = rt.guard();
let res = if let Some((cmd, flags)) = parse_jitlib_cmd(arg) let res = if let Some((cmd, flags)) = parse_jitlib_cmd(arg)
&& let Ok(_) = s.require(format!("jit.{cmd}"), 1) && let Ok(_) = s.require(format!("jit.{cmd}"), Some(1))
{ {
(s.push("start"), s.get(-2), s.push(flags)); (s.push("start"), s.get(-2));
s.call(1, 0) // require("jit.{cmd}").start(flags) s.push(flags);
s.call(1, Some(0)) // require("jit.{cmd}").start(flags)
} else { } else {
s.require("jit", 1).unwrap(); s.require("jit", Some(1)).unwrap();
match arg.as_str() { match arg.as_str() {
cmd @ ("on" | "off" | "flush") => { cmd @ ("on" | "off" | "flush") => {
(s.push(cmd), s.get(-2)); (s.push(cmd), s.get(-2));
s.call(0, 0) // require("jit").[on/off/flush]() s.call(0, Some(0)) // require("jit").[on/off/flush]()
} }
flags => { flags => {
(s.push("opt"), s.get(-2)); (s.push("opt"), s.get(-2));
(s.push("start"), s.get(-2), s.push(flags)); (s.push("start"), s.get(-2));
s.call(1, 0) // require("jit").opt.start(flags) s.push(flags);
s.call(1, Some(0)) // require("jit").opt.start(flags)
} }
} }
}; };
@ -282,9 +297,9 @@ async fn main_async(args: Args, cx: &mut lb::runtime::Context) -> ExitCode {
} }
}; };
if let Err(ref err) = cx.load(&luajit::Chunk::new(chunk).path(path)) { if let Err(ref err) = cx.load(&Chunk::new(chunk).with_path(path)) {
cx.report_error(err); cx.report_error(err);
} else if let Err(ref err) = cx.call_async(0, 0).await { } else if let Err(ref err) = cx.call_async(0, Some(0)).await {
cx.report_error(err); cx.report_error(err);
} }
} }

View File

@ -1,30 +1,32 @@
if (...) ~= nil and (...).type == "group" then return end -- prevent recursive harness call if (...) ~= nil and (...).type == "group" then return end -- prevent recursive harness call
local ok = pcall(require, "lb:task") local ok = pcall(require, "lb:task")
if not ok then error("lua test harness requires lb:task module") end if not ok then error("lua test harness requires 'lb:task'") end
local ok, time = pcall(require, "lb:time")
if not ok then error("lua test harness requires 'lb:time'") end
local ok, fs = pcall(require, "lb:fs") local ok, fs = pcall(require, "lb:fs")
if not ok then error("lua test harness requires lb:fs module") end if not ok then error("lua test harness requires 'lb:fs'") end
local global = _G local global = _G
local colors = { local color = {
reset = "\x1b[0m", reset = "\x1b[0m",
pass = "\x1b[32;1m", pass = "\x1b[32;1m", -- green
fail = "\x1b[31;1m", fail = "\x1b[31;1m", -- red
} }
local icons = { local icon = {
check = "\u{2713}", check = "\u{2713}",
cross = "\u{00d7}", cross = "\u{00d7}",
chevron = "\u{203a}", chevron = "\u{203a}",
} }
local function color(name, s) local function style(name, s)
return ("%s %s %s"):format(colors[name], s, colors.reset) return ("%s%s%s"):format(color[name], s, color.reset)
end end
local function create_test(name, f, group) local function create_test(name, f, group)
local test = { type = "test", name = name or "", group = group, state = "pending", f = f } local test = { type = "test", name = name or "", group = group, state = "pending", f = f }
local fenv = setmetatable({}, { __index = global }) local fenv = setmetatable({}, { __index = global, __newindex = global })
setfenv(f, fenv) setfenv(f, fenv)
return test return test
end end
@ -43,7 +45,7 @@ local function create_group(name, f, parent)
table.insert(group.items, item) table.insert(group.items, item)
return item return item
end, end,
}, { __index = global }) }, { __index = global, __newindex = global })
setfenv(f, fenv) setfenv(f, fenv)
f(group) f(group)
@ -54,24 +56,24 @@ local function name_test(test)
local name = test.name local name = test.name
local group = test.group local group = test.group
while group ~= nil do while group ~= nil do
if group.name ~= "" then name = ("%s %s %s"):format(group.name, icons.chevron, name) end if group.name ~= "" then name = ("%s %s %s"):format(group.name, icon.chevron, name) end
group = group.parent group = group.parent
end end
return name return name
end end
local function trace(msg) local function trace(msg)
return color("fail", msg) .. debug.traceback("", 2):sub(("\nstack traceback:"):len() + 1) return style("fail", msg) .. debug.traceback("", 2):sub(("\nstack traceback:"):len() + 1)
end end
local function run_test(test) local function run_test(test)
local ok, res = xpcall(test.f, trace, test) local ok, res = xpcall(test.f, trace, test)
if ok then if ok then
test.state = "pass" test.state = "pass"
print("", ("%s %s"):format(color("pass", "PASS"), name_test(test))) print("", ("%s %s"):format(style("pass", "PASS"), name_test(test)))
else else
test.state = "fail" test.state = "fail"
print("", ("%s %s\n\n%s\n"):format(color("fail", "FAIL"), name_test(test), res)) print("", ("%s %s\n\n%s\n"):format(style("fail", "FAIL"), name_test(test), res))
end end
collectgarbage() -- gc after each test to test destructors collectgarbage() -- gc after each test to test destructors
return test return test
@ -87,7 +89,7 @@ local function start(cx, item)
end end
end end
local function check_unrefs() local function check_refs()
-- ensure all refs were properly unref'ed -- ensure all refs were properly unref'ed
local registry = debug.getregistry() local registry = debug.getregistry()
local count = #registry local count = #registry
@ -106,7 +108,7 @@ end
local function main(item) local function main(item)
local cx = { tasks = {} } local cx = { tasks = {} }
local pass, fail = 0, 0 local time, pass, fail = time.instant(), 0, 0
start(cx, item) start(cx, item)
for _, task in ipairs(cx.tasks) do for _, task in ipairs(cx.tasks) do
if task:await().state == "pass" then if task:await().state == "pass" then
@ -115,22 +117,28 @@ local function main(item)
fail = fail + 1 fail = fail + 1
end end
end end
local elapsed = time:elapsed()
local code = 1 local code = 1
if fail == 0 then if fail == 0 then
print("", color("pass", ("%s %d tests passed"):format(icons.check, pass))) print("", style("pass", ("%s %d tests passed"):format(icon.check, pass)))
code = 0 code = 0
else else
print( print(
"", "",
("%s, %s"):format( ("%s, %s"):format(
color("pass", ("%s %d tests passed"):format(icons.check, pass)), style("pass", ("%s %d tests passed"):format(icon.check, pass)),
color("fail", ("%s %d tests failed"):format(icons.cross, fail)) style("fail", ("%s %d tests failed"):format(icon.cross, fail))
) )
) )
end 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
cx = nil cx = nil
collectgarbage() collectgarbage()
check_unrefs() check_refs()
return code -- report error to cargo return code -- report error to cargo
end end

View File

@ -10,14 +10,17 @@ fn main() -> ExitCode {
let lua = { let lua = {
let mut rt = lb::runtime::Builder::new(); let mut rt = lb::runtime::Builder::new();
luby::open(&mut rt); luby::open(&mut rt);
rt.unhandled_error(error_cb).build().unwrap() rt.unhandled_error(error_cb)
.prohibit_globals(true)
.build()
.unwrap()
}; };
let path = "tests/main.lua"; let path = "tests/main.lua";
let main = lua.spawn(async move |s| { let main = lua.spawn(async move |s| {
if let Err(ref err) = s.load(Chunk::new(fs::read(path).unwrap()).path(path)) { if let Err(ref err) = s.load(&Chunk::new(fs::read(path).unwrap()).with_path(path)) {
s.report_error(err); s.report_error(err);
} else if let Err(ref err) = s.call_async(0, 1).await { } else if let Err(ref err) = s.call_async(0, Some(1)).await {
s.report_error(err); s.report_error(err);
} }