use std::sync::atomic::{AtomicI64, AtomicU64, Ordering};
use std::sync::Arc;
use luaur_rt::{Error, Lua, Result, VmState};
#[test]
fn test_interrupt_observes_execution() -> Result<()> {
let lua = Lua::new();
let count = Arc::new(AtomicU64::new(0));
let count2 = count.clone();
lua.set_interrupt(move |_| {
count2.fetch_add(1, Ordering::Relaxed);
Ok(VmState::Continue)
});
lua.load(
r#"
local x = 2 + 3
local y = x * 63
local z = string.len(x..", "..y)
"#,
)
.exec()?;
lua.remove_interrupt();
assert!(
count.load(Ordering::Relaxed) > 0,
"interrupt observed execution"
);
Ok(())
}
#[test]
fn test_limit_execution_via_interrupt() -> Result<()> {
let lua = Lua::new();
let budget = Arc::new(AtomicI64::new(1000));
let budget2 = budget.clone();
lua.set_interrupt(move |_| {
if budget2.fetch_sub(1, Ordering::Relaxed) <= 1 {
Err(Error::runtime("time's up"))
} else {
Ok(VmState::Continue)
}
});
lua.globals().set("x", 0i64)?;
let err = lua
.load(
r#"
for i = 1, 1000000 do
x = x + 1
end
"#,
)
.exec()
.expect_err("execution budget should be exhausted");
match err {
Error::RuntimeError(msg) => assert_eq!(msg, "time's up"),
other => panic!("expected RuntimeError(\"time's up\"), got {other:?}"),
}
lua.remove_interrupt();
Ok(())
}
#[test]
fn test_error_within_interrupt() -> Result<()> {
let lua = Lua::new();
lua.set_interrupt(|_| Err(Error::runtime("Something happened in there!")));
let err = lua
.load("local x = 1; x = x + 1")
.exec()
.expect_err("error in interrupt didn't propagate");
match err {
Error::RuntimeError(msg) => assert_eq!(msg, "Something happened in there!"),
err => panic!("expected `RuntimeError` with a specific message, got {err:?}"),
}
lua.remove_interrupt();
Ok(())
}
#[test]
fn test_interrupt_removal() -> Result<()> {
let lua = Lua::new();
lua.set_interrupt(|_| Err(Error::runtime("this interrupt should've been removed")));
assert!(lua.load("local x = 1; x = x + 1").exec().is_err());
lua.remove_interrupt();
assert!(lua.load("local x = 1; x = x + 1").exec().is_ok());
Ok(())
}