use crate::*;
#[test]
fn test_call_lua_function() {
let mut vm = LuaVM::new(SafeOption::default());
vm.open_stdlib(Stdlib::All).unwrap();
vm.execute("function add(a, b) return a + b end").unwrap();
let func = vm.get_global("add").unwrap().unwrap();
let result: i64 = vm.call1(func, (3, 4)).unwrap();
assert_eq!(result, 7);
}
#[test]
fn test_call_lua_function_raw() {
let mut vm = LuaVM::new(SafeOption::default());
vm.open_stdlib(Stdlib::All).unwrap();
vm.execute("function add(a, b) return a + b end").unwrap();
let func = vm.get_global("add").unwrap().unwrap();
let results = vm
.call_raw(func, vec![LuaValue::integer(3), LuaValue::integer(4)])
.unwrap();
assert_eq!(results[0].as_integer(), Some(7));
}
#[test]
fn test_call_global() {
let mut vm = LuaVM::new(SafeOption::default());
vm.open_stdlib(Stdlib::All).unwrap();
vm.execute("function greet(name) return 'Hello, ' .. name end")
.unwrap();
let result: String = vm.call1_global("greet", "World").unwrap();
assert_eq!(result, "Hello, World");
}
#[test]
fn test_call_global_not_found() {
let mut vm = LuaVM::new(SafeOption::default());
let result: LuaResult<Vec<LuaValue>> = vm.call_global("nonexistent", ());
assert!(result.is_err());
}
#[test]
fn test_register_function() {
let mut vm = LuaVM::new(SafeOption::default());
vm.open_stdlib(Stdlib::All).unwrap();
vm.register_function("rust_add", |state| {
let a = state.get_arg(1).and_then(|v| v.as_integer()).unwrap_or(0);
let b = state.get_arg(2).and_then(|v| v.as_integer()).unwrap_or(0);
state.push_value(LuaValue::integer(a + b))?;
Ok(1)
})
.unwrap();
let results = vm.execute("return rust_add(10, 20)").unwrap();
assert_eq!(results[0].as_integer(), Some(30));
}
#[test]
fn test_register_function_typed() {
let mut vm = LuaVM::new(SafeOption::default());
vm.open_stdlib(Stdlib::All).unwrap();
vm.register_function_typed("rust_add_typed", |a: i64, b: i64| a + b)
.unwrap();
let results = vm.execute("return rust_add_typed(10, 20)").unwrap();
assert_eq!(results[0].as_integer(), Some(30));
}
#[test]
fn test_register_function_typed_userdata_ref() {
#[derive(Debug)]
struct Counter {
count: i64,
}
let mut vm = LuaVM::new(SafeOption::default());
vm.open_stdlib(Stdlib::All).unwrap();
let counter = vm.push_any(Counter { count: 1 }).unwrap();
vm.set_global("counter", counter).unwrap();
vm.register_function_typed(
"increment_typed",
|mut counter: UserDataRef<Counter>, delta: i64| {
let counter_ref = counter.get_mut().unwrap();
counter_ref.count += delta;
counter_ref.count
},
)
.unwrap();
let results = vm.execute("return increment_typed(counter, 9)").unwrap();
assert_eq!(results[0].as_integer(), Some(10));
let counter: UserDataRef<Counter> = vm.get_global_as("counter").unwrap().unwrap();
assert_eq!(counter.get().unwrap().count, 10);
}
#[test]
fn test_register_function_typed_high_arity() {
let mut vm = LuaVM::new(SafeOption::default());
vm.open_stdlib(Stdlib::All).unwrap();
vm.register_function_typed(
"sum8",
|a: i64, b: i64, c: i64, d: i64, e: i64, f: i64, g: i64, h: i64| {
a + b + c + d + e + f + g + h
},
)
.unwrap();
let results = vm.execute("return sum8(1, 2, 3, 4, 5, 6, 7, 8)").unwrap();
assert_eq!(results[0].as_integer(), Some(36));
}
#[test]
fn test_load_and_call() {
let mut vm = LuaVM::new(SafeOption::default());
vm.open_stdlib(Stdlib::All).unwrap();
let func = vm.load("return 42").unwrap();
let result: i64 = vm.call1(func, ()).unwrap();
assert_eq!(result, 42);
}
#[test]
fn test_load_with_name() {
let mut vm = LuaVM::new(SafeOption::default());
vm.open_stdlib(Stdlib::All).unwrap();
let func = vm.load_with_name("return 'hello'", "@my_script").unwrap();
let result: String = vm.call1(func, ()).unwrap();
assert_eq!(result, "hello");
}
#[test]
fn test_load_does_not_execute() {
let mut vm = LuaVM::new(SafeOption::default());
vm.open_stdlib(Stdlib::All).unwrap();
let _func = vm.load("x = 999").unwrap();
let x = vm.get_global("x").unwrap();
assert!(x.is_none());
}
#[cfg(feature = "shared-proto")]
#[test]
fn test_load_marks_chunk_short_strings_shared() {
let mut vm = LuaVM::new(SafeOption::default());
vm.open_stdlib(Stdlib::All).unwrap();
let func = vm
.load("local key = '0123456789abcdefghijklmnopqr'; return key")
.unwrap();
let function = func.as_function_ptr().unwrap();
let chunk = function.as_ref().data.chunk();
let key = chunk
.constants
.iter()
.copied()
.find(|value| value.as_str() == Some("0123456789abcdefghijklmnopqr"))
.unwrap();
assert!(key.is_short_string());
assert!(key.as_string_ptr().unwrap().as_ref().header.is_shared());
assert!(function.as_ref().data.proto().as_ref().header.is_shared());
}
#[cfg(feature = "shared-proto")]
#[test]
fn test_shared_proto_survives_vm_drop() {
let proto = {
let mut vm = LuaVM::new(SafeOption::default());
vm.open_stdlib(Stdlib::All).unwrap();
let func = vm
.load("local key = '0123456789abcdefghijklmnopqr'; return key")
.unwrap();
func.as_function_ptr().unwrap().as_ref().data.proto()
};
assert!(proto.as_ref().header.is_shared());
let key = proto
.as_ref()
.data
.constants
.iter()
.copied()
.find(|value| value.as_str() == Some("0123456789abcdefghijklmnopqr"))
.unwrap();
assert!(key.as_string_ptr().unwrap().as_ref().header.is_shared());
}
#[cfg(feature = "shared-proto")]
#[test]
fn test_shared_proto_reuses_same_file_across_vms() {
use std::io::Write;
let path = std::env::temp_dir().join("lua_rs_shared_proto_cache.lua");
{
let mut file = std::fs::File::create(&path).unwrap();
writeln!(file, "return 'shared-proto-cache'").unwrap();
}
let proto1 = {
let mut vm = LuaVM::new(SafeOption::default());
vm.open_stdlib(Stdlib::All).unwrap();
vm.load_proto_from_file(path.to_str().unwrap()).unwrap()
};
let proto2 = {
let mut vm = LuaVM::new(SafeOption::default());
vm.open_stdlib(Stdlib::All).unwrap();
vm.load_proto_from_file(path.to_str().unwrap()).unwrap()
};
assert_eq!(proto1, proto2);
std::fs::remove_file(&path).ok();
}
#[cfg(feature = "shared-proto")]
#[test]
fn test_shared_proto_reloads_when_file_changes() {
use std::io::Write;
let path = std::env::temp_dir().join("lua_rs_shared_proto_reload.lua");
{
let mut file = std::fs::File::create(&path).unwrap();
writeln!(file, "return 1").unwrap();
}
let proto1 = {
let mut vm = LuaVM::new(SafeOption::default());
vm.open_stdlib(Stdlib::All).unwrap();
vm.load_proto_from_file(path.to_str().unwrap()).unwrap()
};
{
let mut file = std::fs::File::create(&path).unwrap();
writeln!(file, "return 123456789").unwrap();
}
let proto2 = {
let mut vm = LuaVM::new(SafeOption::default());
vm.open_stdlib(Stdlib::All).unwrap();
vm.load_proto_from_file(path.to_str().unwrap()).unwrap()
};
assert_ne!(proto1, proto2);
std::fs::remove_file(&path).ok();
}
#[test]
fn test_table_builder_named_keys() {
let mut vm = LuaVM::new(SafeOption::default());
vm.open_stdlib(Stdlib::All).unwrap();
let host = vm.create_string("localhost").unwrap();
let table = TableBuilder::new()
.set("host", host)
.set("port", LuaValue::integer(8080))
.build(&mut vm)
.unwrap();
let host_key = vm.create_string("host").unwrap();
let port_key = vm.create_string("port").unwrap();
assert_eq!(
vm.raw_get(&table, &host_key).unwrap().as_str(),
Some("localhost")
);
assert_eq!(
vm.raw_get(&table, &port_key).unwrap().as_integer(),
Some(8080)
);
}
#[test]
fn test_table_builder_array() {
let mut vm = LuaVM::new(SafeOption::default());
vm.open_stdlib(Stdlib::All).unwrap();
let table = TableBuilder::new()
.push(LuaValue::integer(10))
.push(LuaValue::integer(20))
.push(LuaValue::integer(30))
.build(&mut vm)
.unwrap();
vm.set_global("arr", table).unwrap();
let results = vm.execute("return #arr, arr[1], arr[2], arr[3]").unwrap();
assert_eq!(results[0].as_integer(), Some(3));
assert_eq!(results[1].as_integer(), Some(10));
assert_eq!(results[2].as_integer(), Some(20));
assert_eq!(results[3].as_integer(), Some(30));
}
#[test]
fn test_table_builder_mixed() {
let mut vm = LuaVM::new(SafeOption::default());
vm.open_stdlib(Stdlib::All).unwrap();
let name = vm.create_string("test").unwrap();
let table = TableBuilder::new()
.push(LuaValue::integer(1))
.push(LuaValue::integer(2))
.set("name", name)
.build(&mut vm)
.unwrap();
vm.set_global("t", table).unwrap();
let results = vm.execute("return t[1], t[2], t.name").unwrap();
assert_eq!(results[0].as_integer(), Some(1));
assert_eq!(results[1].as_integer(), Some(2));
assert_eq!(results[2].as_str(), Some("test"));
}
#[test]
fn test_lua_error_display() {
let err = lua_vm::LuaError::RuntimeError;
assert_eq!(format!("{}", err), "Runtime Error");
}
#[test]
fn test_lua_error_is_std_error() {
fn assert_error<E: std::error::Error>(_: &E) {}
let err = lua_vm::LuaError::RuntimeError;
assert_error(&err);
}
#[test]
fn test_lua_full_error() {
let mut vm = LuaVM::new(SafeOption::default());
vm.open_stdlib(Stdlib::All).unwrap();
match vm.execute("error('boom')") {
Err(e) => {
let full = vm.into_full_error(e);
assert_eq!(full.kind(), lua_vm::LuaError::RuntimeError);
assert!(
full.message().contains("boom"),
"message should contain 'boom': {}",
full
);
let display = format!("{}", full);
assert!(display.contains("boom"));
}
Ok(_) => panic!("expected error"),
}
}
#[test]
fn test_lua_full_error_is_std_error() {
fn assert_error<E: std::error::Error>(_: &E) {}
let full = lua_vm::lua_error::LuaFullError {
kind: lua_vm::LuaError::CompileError,
message: "syntax error".to_string(),
};
assert_error(&full);
}
#[test]
fn test_get_global_as_integer() {
let mut vm = LuaVM::new(SafeOption::default());
vm.execute("x = 42").unwrap();
let x: i64 = vm.get_global_as::<i64>("x").unwrap().unwrap();
assert_eq!(x, 42);
}
#[test]
fn test_get_global_as_string() {
let mut vm = LuaVM::new(SafeOption::default());
vm.open_stdlib(Stdlib::All).unwrap();
vm.execute("name = 'Alice'").unwrap();
let name: String = vm.get_global_as::<String>("name").unwrap().unwrap();
assert_eq!(name, "Alice");
}
#[test]
fn test_get_global_as_bool() {
let mut vm = LuaVM::new(SafeOption::default());
vm.execute("flag = true").unwrap();
let flag: bool = vm.get_global_as::<bool>("flag").unwrap().unwrap();
assert!(flag);
}
#[test]
fn test_get_global_as_none() {
let mut vm = LuaVM::new(SafeOption::default());
let result = vm.get_global_as::<i64>("nonexistent").unwrap();
assert!(result.is_none());
}
#[test]
fn test_open_stdlibs() {
let mut vm = LuaVM::new(SafeOption::default());
vm.open_stdlibs(&[Stdlib::Math, Stdlib::String, Stdlib::Table])
.unwrap();
let results = vm.execute("return math.abs(-5)").unwrap();
assert_eq!(results[0].as_integer(), Some(5));
let results = vm.execute("return string.upper('hello')").unwrap();
assert_eq!(results[0].as_str(), Some("HELLO"));
}
#[test]
fn test_table_pairs() {
let mut vm = LuaVM::new(SafeOption::default());
vm.open_stdlib(Stdlib::All).unwrap();
let table = TableBuilder::new()
.set("a", LuaValue::integer(1))
.set("b", LuaValue::integer(2))
.build(&mut vm)
.unwrap();
let pairs = vm.table_pairs(&table).unwrap();
assert_eq!(pairs.len(), 2);
let keys: Vec<_> = pairs.iter().map(|(k, _)| k.as_str().unwrap()).collect();
assert!(keys.contains(&"a"));
assert!(keys.contains(&"b"));
}
#[test]
fn test_table_length() {
let mut vm = LuaVM::new(SafeOption::default());
vm.open_stdlib(Stdlib::All).unwrap();
let table = TableBuilder::new()
.push(LuaValue::integer(10))
.push(LuaValue::integer(20))
.push(LuaValue::integer(30))
.build(&mut vm)
.unwrap();
let len = vm.table_length(&table).unwrap();
assert_eq!(len, 3);
}
#[test]
fn test_dofile() {
use std::io::Write;
let dir = std::env::temp_dir();
let path = dir.join("lua_rs_test_dofile.lua");
{
let mut f = std::fs::File::create(&path).unwrap();
writeln!(f, "return 1 + 2").unwrap();
}
let mut vm = LuaVM::new(SafeOption::default());
vm.open_stdlib(Stdlib::All).unwrap();
let results = vm.dofile(path.to_str().unwrap()).unwrap();
assert_eq!(results[0].as_integer(), Some(3));
std::fs::remove_file(&path).ok();
}
#[test]
fn test_dofile_not_found() {
let mut vm = LuaVM::new(SafeOption::default());
let result = vm.dofile("nonexistent_file_12345.lua");
assert!(result.is_err());
}
#[test]
fn test_lua_state_load_proxy() {
let mut vm = LuaVM::new(SafeOption::default());
vm.open_stdlib(Stdlib::All).unwrap();
vm.register_function("test_load", |state| {
let func = state.load("return 77")?;
state.push_value(func)?;
Ok(1)
})
.unwrap();
let results = vm.execute("local f = test_load(); return f()").unwrap();
assert_eq!(results[0].as_integer(), Some(77));
}
#[test]
fn test_lua_state_call_global_proxy() {
let mut vm = LuaVM::new(SafeOption::default());
vm.open_stdlib(Stdlib::All).unwrap();
vm.execute("function double(x) return x * 2 end").unwrap();
vm.register_function("test_call_global", |state| {
let results = state.call_global("double", vec![LuaValue::integer(21)])?;
state.push_value(results[0])?;
Ok(1)
})
.unwrap();
let results = vm.execute("return test_call_global()").unwrap();
assert_eq!(results[0].as_integer(), Some(42));
}
#[test]
fn test_execute_error_recovery() {
let mut vm = LuaVM::new(SafeOption::default());
vm.open_stdlib(Stdlib::All).unwrap();
let err = vm.execute("error('boom')");
assert!(err.is_err());
let results = vm.execute("return 1 + 2").unwrap();
assert_eq!(results[0].as_integer(), Some(3));
}
#[test]
fn test_execute_error_preserves_globals() {
let mut vm = LuaVM::new(SafeOption::default());
vm.open_stdlib(Stdlib::All).unwrap();
vm.execute("x = 42").unwrap();
let err = vm.execute("error('fail')");
assert!(err.is_err());
let results = vm.execute("return x").unwrap();
assert_eq!(results[0].as_integer(), Some(42));
}
#[test]
fn test_call_global_error_recovery() {
let mut vm = LuaVM::new(SafeOption::default());
vm.open_stdlib(Stdlib::All).unwrap();
vm.execute("function bad() error('nope') end").unwrap();
vm.execute("function good() return 99 end").unwrap();
let err: LuaResult<Vec<LuaValue>> = vm.call_global("bad", ());
assert!(err.is_err());
let results: Vec<LuaValue> = vm.call_global("good", ()).unwrap();
assert_eq!(results[0].as_integer(), Some(99));
}
#[test]
fn test_multiple_errors_recovery() {
let mut vm = LuaVM::new(SafeOption::default());
vm.open_stdlib(Stdlib::All).unwrap();
for i in 0..5 {
let err = vm.execute(&format!("error('error {}')", i));
assert!(err.is_err());
}
let results = vm.execute("return 'still alive'").unwrap();
assert_eq!(results[0].as_str(), Some("still alive"));
}
#[test]
fn test_error_message_available_after_recovery() {
let mut vm = LuaVM::new(SafeOption::default());
vm.open_stdlib(Stdlib::All).unwrap();
let err = vm.execute("error('specific error message')").unwrap_err();
let msg = vm.get_error_message(err);
assert!(
msg.contains("specific error message"),
"expected message to contain 'specific error message', got: {}",
msg
);
let results = vm.execute("return true").unwrap();
assert_eq!(results[0].as_boolean(), Some(true));
}
#[test]
fn test_deep_call_error_recovery() {
let mut vm = LuaVM::new(SafeOption::default());
vm.open_stdlib(Stdlib::All).unwrap();
vm.execute(
r#"
function a() return b() end
function b() return c() end
function c() error('deep error') end
"#,
)
.unwrap();
let err: LuaResult<Vec<LuaValue>> = vm.call_global("a", ());
assert!(err.is_err());
let results = vm.execute("return 1 + 1").unwrap();
assert_eq!(results[0].as_integer(), Some(2));
vm.execute("function simple() return 42 end").unwrap();
let results: Vec<LuaValue> = vm.call_global("simple", ()).unwrap();
assert_eq!(results[0].as_integer(), Some(42));
}