use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc;
use luaur_rt::{Compiler, Error, Lua, Result, Table, Value, Vector, VmState};
#[test]
fn test_version() -> Result<()> {
let lua = Lua::new();
assert!(lua.globals().get::<String>("_VERSION")?.starts_with("Luau"));
Ok(())
}
#[test]
fn test_vectors() -> Result<()> {
let lua = Lua::new();
let v: Vector = lua
.load("vector.create(1, 2, 3) + vector.create(3, 2, 1)")
.eval()?;
assert_eq!(v, [4.0, 4.0, 4.0]);
let v: [f64; 3] = lua.load("vector.create(1, 2, 3)").eval()?;
assert!(v == [1.0, 2.0, 3.0]);
lua.load(
r#"
local v = vector.create(1, 2, 3)
assert(v.x == 1)
assert(v.y == 2)
assert(v.z == 3)
"#,
)
.exec()?;
lua.load(
r#"
local v = vector.create(1, 2, 3)
assert(v.x == 1)
assert(v.y == 2)
assert(v.z == 3)
"#,
)
.set_compiler(Compiler::new().set_vector_ctor("vector"))
.exec()?;
Ok(())
}
#[test]
fn test_vector_metatable() -> Result<()> {
let lua = Lua::new();
let vector_mt = lua
.load(
r#"
{
__index = {
new = vector.create,
product = function(a, b)
return vector.create(a.x * b.x, a.y * b.y, a.z * b.z)
end
}
}
"#,
)
.eval::<Table>()?;
vector_mt.set_metatable(Some(vector_mt.clone()))?;
lua.set_type_metatable::<Vector>(Some(vector_mt.clone()));
lua.globals().set("Vector3", vector_mt)?;
let compiler = Compiler::new()
.set_vector_ctor("Vector3.new")
.set_vector_type("Vector3");
lua.load(
r#"
local v = Vector3.new(1, 2, 3)
local v2 = v:product(Vector3.new(2, 3, 4))
assert(v2.x == 2 and v2.y == 6 and v2.z == 12)
"#,
)
.set_compiler(compiler)
.exec()?;
Ok(())
}
#[test]
fn test_vector_roundtrip() -> Result<()> {
let lua = Lua::new();
let v = lua.create_vector(1.5, -2.0, 3.25);
assert_eq!(v.x(), 1.5);
assert_eq!(v.y(), -2.0);
assert_eq!(v.z(), 3.25);
let echo = lua.create_function(|_, v: Vector| Ok(v))?;
let back: Vector = echo.call(v)?;
assert_eq!(back, v);
assert_eq!(back, [1.5, -2.0, 3.25]);
let described: String = lua
.create_function(|_, v: Vector| Ok(format!("{},{},{}", v.x(), v.y(), v.z())))?
.call(lua.create_vector(4.0, 5.0, 6.0))?;
assert_eq!(described, "4,5,6");
Ok(())
}
#[test]
fn test_load_from_rust() -> Result<()> {
let lua = Lua::new();
let f = lua.load("return 123").into_function()?;
assert_eq!(f.call::<i32>(())?, 123);
Ok(())
}
#[test]
fn test_readonly_table() -> Result<()> {
let lua = Lua::new();
let t = lua.create_sequence_from([1])?;
assert!(!t.is_readonly());
t.set_readonly(true);
assert!(t.is_readonly());
fn check_readonly_error<T: std::fmt::Debug>(res: Result<T>) {
match res {
Err(Error::RuntimeError(e)) if e.contains("attempt to modify a readonly table") => {}
r => panic!("expected readonly RuntimeError, got {r:?}"),
}
}
check_readonly_error(t.set("key", "value"));
check_readonly_error(t.raw_set("key", "value"));
check_readonly_error(t.raw_insert(1, "value"));
check_readonly_error(t.raw_remove(1));
check_readonly_error(t.push("value"));
check_readonly_error(t.pop::<Value>());
check_readonly_error(t.raw_push("value"));
check_readonly_error(t.raw_pop::<Value>());
check_readonly_error(t.set_metatable(None));
t.set_readonly(false);
t.set("key", "value")?;
assert_eq!(t.get::<String>("key")?, "value");
Ok(())
}
#[test]
fn test_readonly_table_reads_still_work() -> Result<()> {
let lua = Lua::new();
let t = lua.create_sequence_from([10, 20, 30])?;
t.set_readonly(true);
assert_eq!(t.get::<i64>(1)?, 10);
assert_eq!(t.raw_get::<i64>(2)?, 20);
assert_eq!(t.raw_len(), 3);
assert_eq!(
t.sequence_values::<i64>().collect::<Result<Vec<_>>>()?,
vec![10, 20, 30]
);
Ok(())
}
#[test]
fn test_metatable_via_lua() -> Result<()> {
let lua = Lua::new();
let base = lua
.load(
r#"
return {
__index = {
greet = function() return "hi" end,
}
}
"#,
)
.eval::<Table>()?;
let t = lua.create_table();
t.set_metatable(Some(base))?;
let greeting: String = lua
.load("local t = ...; return t.greet()")
.into_function()?
.call(t)?;
assert_eq!(greeting, "hi");
Ok(())
}
#[test]
fn test_sandbox() -> Result<()> {
let lua = Lua::new();
lua.sandbox(true)?;
lua.load("global = 123").exec()?;
let n: i32 = lua.load("return global").eval()?;
assert_eq!(n, 123);
assert_eq!(lua.globals().get::<Option<i32>>("global")?, Some(123));
let f = lua.create_function(|lua, ()| lua.globals().get::<i32>("global"))?;
let co = lua.create_thread(f.clone())?;
assert_eq!(co.resume::<Option<i32>>(())?, Some(123));
let co = lua.create_thread(f)?;
co.sandbox()?;
assert_eq!(co.resume::<Option<i32>>(())?, Some(123));
lua.sandbox(false)?;
assert_eq!(lua.globals().get::<Option<i32>>("global")?, None);
let table = lua.globals().get::<Table>("table")?;
table.set("test", "test")?;
Ok(())
}
#[test]
fn test_sandbox_safeenv() -> Result<()> {
let lua = Lua::new();
lua.sandbox(true)?;
lua.globals().set("state", lua.create_table())?;
lua.set_safeenv(false);
lua.load("state.a = 123").exec()?;
let a: i32 = lua.load("state.a = 321; return state.a").eval()?;
assert_eq!(a, 321);
Ok(())
}
#[test]
fn test_sandbox_threads() -> Result<()> {
let lua = Lua::new();
let f = lua.create_function(|lua, v: Value| lua.globals().set("global", v))?;
let co = lua.create_thread(f.clone())?;
co.resume::<()>(321)?;
assert_eq!(lua.globals().get::<Option<i32>>("global")?, Some(321));
let co = lua.create_thread(f.clone())?;
co.sandbox()?;
co.resume::<()>(123)?;
assert_eq!(lua.globals().get::<Option<i32>>("global")?, Some(321));
co.reset(f)?;
co.resume::<()>(111)?;
assert_eq!(lua.globals().get::<Option<i32>>("global")?, Some(111));
Ok(())
}
#[test]
fn test_interrupts() -> Result<()> {
let lua = Lua::new();
let interrupts_count = Arc::new(AtomicU64::new(0));
let interrupts_count2 = interrupts_count.clone();
lua.set_interrupt(move |_| {
interrupts_count2.fetch_add(1, Ordering::Relaxed);
Ok(VmState::Continue)
});
let f = lua
.load(
r#"
local x = 2 + 3
local y = x * 63
local z = string.len(x..", "..y)
"#,
)
.into_function()?;
f.call::<()>(())?;
assert!(interrupts_count.load(Ordering::Relaxed) > 0);
let yield_count = Arc::new(AtomicU64::new(0));
let yield_count2 = yield_count.clone();
lua.set_interrupt(move |_| {
if yield_count2.fetch_add(1, Ordering::Relaxed) == 1 {
return Ok(VmState::Yield);
}
Ok(VmState::Continue)
});
let co = lua.create_thread(
lua.load(
r#"
local a = {1, 2, 3}
local b = 0
for _, x in ipairs(a) do b += x end
return b
"#,
)
.into_function()?,
)?;
co.resume::<()>(())?;
assert!(co.is_resumable());
let result: i32 = co.resume(())?;
assert_eq!(result, 6);
assert_eq!(yield_count.load(Ordering::Relaxed), 7);
assert!(co.is_finished());
yield_count.store(0, Ordering::Relaxed);
let co = lua.create_thread(lua.create_function(|lua, arg: Value| {
(lua.load("return (function(x) return x end)(...)")).call::<Value>(arg)
})?)?;
let res = co.resume::<String>("abc")?;
assert_eq!(res, "abc".to_string());
assert_eq!(yield_count.load(Ordering::Relaxed), 3);
lua.set_interrupt(|_| Err(Error::runtime("error from interrupt")));
match f.call::<()>(()) {
Err(Error::RuntimeError(ref msg)) => assert_eq!(msg, "error from interrupt"),
res => panic!("expected `RuntimeError` with a specific message, got {res:?}"),
}
lua.remove_interrupt();
Ok(())
}
#[test]
fn test_fflags() {
assert!(Lua::set_fflag("UnknownFlag", true).is_err());
}
#[test]
fn test_memory_category() -> Result<()> {
let lua = Lua::new();
lua.set_memory_category("main").unwrap();
let err = lua.set_memory_category("invalid$");
assert!(err.is_err());
for i in 0..254 {
let name = format!("category_{}", i);
lua.set_memory_category(&name).unwrap();
}
let err = lua.set_memory_category("category_254");
assert!(err.is_err());
Ok(())
}
#[test]
fn test_integer_type() -> Result<()> {
let lua = Lua::new();
let echo = lua.create_function(|_, n: i64| Ok(n))?;
assert_eq!(echo.call::<i64>(42i64)?, 42);
assert_eq!(echo.call::<i64>(-42i64)?, -42);
let n: i64 = lua.load("return 42").eval()?;
assert_eq!(n, 42);
let n: i64 = lua.load("return -42").eval()?;
assert_eq!(n, -42);
Ok(())
}
#[test]
fn test_chunk_call() -> Result<()> {
let lua = Lua::new();
let sum: i64 = lua.load("local a, b = ...; return a + b").call((2, 3))?;
assert_eq!(sum, 5);
lua.load("_G.touched = true").call::<()>(())?;
assert_eq!(lua.globals().get::<bool>("touched")?, true);
Ok(())
}
#[test]
fn test_typeof_error_deferred() -> Result<()> {
let lua = Lua::new();
let err = Error::runtime("just a test error");
let res = lua.load("return typeof(...)").call::<String>(err)?;
assert_eq!(res, "string"); Ok(())
}
#[test]
fn test_heap_dump_deferred() {
let lua = Lua::new();
lua.set_memory_category("test_category").unwrap();
let _t = lua.create_table();
assert!(lua.memory_category_bytes("main").unwrap_or(0) > 0);
assert!(lua.memory_category_bytes("test_category").is_some());
}