use crate::lua_value::userdata_trait::LuaMethodProvider;
use crate::lua_vm::SafeOption;
use crate::{LuaUserData, LuaVM, LuaValue, Stdlib, UserDataRef};
#[derive(LuaUserData)]
struct ApiCounter {
pub count: i64,
}
#[test]
fn test_table_ref_basic() {
let mut vm = LuaVM::new(SafeOption::default());
let tbl = vm.create_table_ref(0, 4).unwrap();
let hello = vm.create_string("hello").unwrap();
tbl.set("greeting", hello).unwrap();
let val = tbl.get("greeting").unwrap();
assert_eq!(val.as_str(), Some("hello"));
tbl.seti(1, LuaValue::integer(42)).unwrap();
let val = tbl.geti(1).unwrap();
assert_eq!(val.as_integer(), Some(42));
let key = vm.create_string("key2").unwrap();
tbl.set_value(key, LuaValue::boolean(true)).unwrap();
let val = tbl.get_value(&key).unwrap();
assert_eq!(val.as_boolean(), Some(true));
}
#[test]
fn test_table_ref_get_as() {
let mut vm = LuaVM::new(SafeOption::default());
let tbl = vm.create_table_ref(0, 4).unwrap();
tbl.set("count", LuaValue::integer(99)).unwrap();
let count: i64 = tbl.get_as("count").unwrap();
assert_eq!(count, 99);
tbl.set("pi", LuaValue::float(3.15)).unwrap();
let pi: f64 = tbl.get_as("pi").unwrap();
assert!((pi - 3.15).abs() < 1e-10);
}
#[test]
fn test_table_ref_pairs_and_len() {
let mut vm = LuaVM::new(SafeOption::default());
let tbl = vm.create_table_ref(4, 0).unwrap();
tbl.seti(1, LuaValue::integer(10)).unwrap();
tbl.seti(2, LuaValue::integer(20)).unwrap();
tbl.seti(3, LuaValue::integer(30)).unwrap();
assert_eq!(tbl.len().unwrap(), 3);
let pairs = tbl.pairs().unwrap();
assert_eq!(pairs.len(), 3);
}
#[test]
fn test_table_ref_push() {
let mut vm = LuaVM::new(SafeOption::default());
let tbl = vm.create_table_ref(4, 0).unwrap();
tbl.push(LuaValue::integer(100)).unwrap();
tbl.push(LuaValue::integer(200)).unwrap();
assert_eq!(tbl.len().unwrap(), 2);
assert_eq!(tbl.geti(1).unwrap().as_integer(), Some(100));
assert_eq!(tbl.geti(2).unwrap().as_integer(), Some(200));
}
#[test]
fn test_table_ref_from_global() {
let mut vm = LuaVM::new(SafeOption::default());
vm.execute(
r#"
config = { host = "localhost", port = 8080 }
"#,
)
.unwrap();
let config = vm.get_global_table("config").unwrap().unwrap();
let host: String = config.get_as("host").unwrap();
assert_eq!(host, "localhost");
let port: i64 = config.get_as("port").unwrap();
assert_eq!(port, 8080);
config.set("port", LuaValue::integer(9090)).unwrap();
let results = vm.execute("return config.port").unwrap();
assert_eq!(results[0].as_integer(), Some(9090));
}
#[test]
fn test_table_ref_auto_drop() {
let mut vm = LuaVM::new(SafeOption::default());
let ref_id;
{
let tbl = vm.create_table_ref(0, 0).unwrap();
ref_id = tbl.ref_id();
tbl.set("x", LuaValue::integer(1)).unwrap();
}
let val = vm.get_ref_value_by_id(ref_id);
assert!(val.is_nil(), "Registry entry should be released on drop");
}
#[test]
fn test_function_ref_call() {
let mut vm = LuaVM::new(SafeOption::default());
vm.execute(
r#"
function add(a, b)
return a + b
end
"#,
)
.unwrap();
let add = vm.get_global_function("add").unwrap().unwrap();
let results = add
.call_raw(vec![LuaValue::integer(3), LuaValue::integer(4)])
.unwrap();
assert_eq!(results[0].as_integer(), Some(7));
}
#[test]
fn test_function_ref_call1() {
let mut vm = LuaVM::new(SafeOption::default());
vm.execute(
r#"
function greet(name)
return "Hello, " .. name
end
"#,
)
.unwrap();
let greet = vm.get_global_function("greet").unwrap().unwrap();
let name = vm.create_string("World").unwrap();
let result = greet.call1_raw(vec![name]).unwrap();
assert_eq!(result.as_str(), Some("Hello, World"));
}
#[test]
fn test_function_ref_call1_typed() {
let mut vm = LuaVM::new(SafeOption::default());
vm.execute(
r#"
function greet(name)
return "Hello, " .. name
end
"#,
)
.unwrap();
let greet = vm.get_global_function("greet").unwrap().unwrap();
let result: String = greet.call1("World").unwrap();
assert_eq!(result, "Hello, World");
}
#[test]
fn test_function_ref_call_typed_multi_return() {
let mut vm = LuaVM::new(SafeOption::default());
vm.execute(
r#"
function stats(a, b)
return a + b, a * b, a - b
end
"#,
)
.unwrap();
let stats = vm.get_global_function("stats").unwrap().unwrap();
let result: (i64, i64, i64) = stats.call((7, 3)).unwrap();
assert_eq!(result, (10, 21, 4));
}
#[test]
fn test_function_ref_call_typed_no_args() {
let mut vm = LuaVM::new(SafeOption::default());
vm.execute(
r#"
function ping()
return 42
end
"#,
)
.unwrap();
let ping = vm.get_global_function("ping").unwrap().unwrap();
let result: i64 = ping.call1(()).unwrap();
assert_eq!(result, 42);
}
#[test]
fn test_function_ref_multiple_calls() {
let mut vm = LuaVM::new(SafeOption::default());
vm.execute(
r#"
counter = 0
function inc()
counter = counter + 1
return counter
end
"#,
)
.unwrap();
let inc = vm.get_global_function("inc").unwrap().unwrap();
assert_eq!(inc.call1_raw(vec![]).unwrap().as_integer(), Some(1));
assert_eq!(inc.call1_raw(vec![]).unwrap().as_integer(), Some(2));
assert_eq!(inc.call1_raw(vec![]).unwrap().as_integer(), Some(3));
}
#[test]
fn test_function_ref_auto_drop() {
let mut vm = LuaVM::new(SafeOption::default());
vm.execute("function noop() end").unwrap();
let ref_id;
{
let f = vm.get_global_function("noop").unwrap().unwrap();
ref_id = f.ref_id();
}
let val = vm.get_ref_value_by_id(ref_id);
assert!(val.is_nil(), "Function ref should be released on drop");
}
#[test]
fn test_string_ref_basic() {
let mut vm = LuaVM::new(SafeOption::default());
let s = vm.create_string("hello world").unwrap();
let sref = vm.to_string_ref(s).unwrap();
assert_eq!(sref.as_str(), Some("hello world"));
assert_eq!(sref.byte_len(), 11);
assert_eq!(sref.to_string_lossy(), "hello world");
assert_eq!(format!("{}", sref), "hello world");
}
#[test]
fn test_string_ref_auto_drop() {
let mut vm = LuaVM::new(SafeOption::default());
let s = vm.create_string("test_drop").unwrap();
let ref_id;
{
let sref = vm.to_string_ref(s).unwrap();
ref_id = sref.ref_id();
}
let val = vm.get_ref_value_by_id(ref_id);
assert!(val.is_nil());
}
#[test]
fn test_any_ref_table() {
let mut vm = LuaVM::new(SafeOption::default());
let table = vm.create_table(0, 2).unwrap();
let any = vm.to_ref(table);
assert!(matches!(any.kind(), crate::lua_value::LuaValueKind::Table));
let tbl = any.as_table().unwrap();
tbl.set("x", LuaValue::integer(42)).unwrap();
assert_eq!(tbl.get("x").unwrap().as_integer(), Some(42));
}
#[test]
fn test_any_ref_function() {
let mut vm = LuaVM::new(SafeOption::default());
vm.execute("function f() return 99 end").unwrap();
let val = vm.get_global("f").unwrap().unwrap();
let any = vm.to_ref(val);
let func = any.as_function().unwrap();
let result = func.call1_raw(vec![]).unwrap();
assert_eq!(result.as_integer(), Some(99));
}
#[test]
fn test_any_ref_string() {
let mut vm = LuaVM::new(SafeOption::default());
let s = vm.create_string("any_string").unwrap();
let any = vm.to_ref(s);
let sref = any.as_string().unwrap();
assert_eq!(sref.as_str(), Some("any_string"));
}
#[test]
fn test_any_ref_wrong_type() {
let mut vm = LuaVM::new(SafeOption::default());
let table = vm.create_table(0, 0).unwrap();
let any = vm.to_ref(table);
assert!(any.as_function().is_none());
assert!(any.as_string().is_none());
assert!(any.as_table().is_some());
}
#[test]
fn test_any_ref_userdata_typed() {
let mut vm = LuaVM::new(SafeOption::default());
let userdata = vm
.create_userdata(crate::lua_value::LuaUserdata::new(ApiCounter { count: 5 }))
.unwrap();
let any = vm.to_ref(userdata);
let counter = any.as_userdata::<ApiCounter>().unwrap();
assert_eq!(counter.get().unwrap().count, 5);
assert_eq!(counter.type_name().unwrap(), "ApiCounter");
}
#[test]
fn test_userdata_ref_from_lua_and_mutate() {
let mut vm = LuaVM::new(SafeOption::default());
let userdata = vm
.create_userdata(crate::lua_value::LuaUserdata::new(ApiCounter { count: 11 }))
.unwrap();
vm.set_global("counter", userdata).unwrap();
let mut counter: UserDataRef<ApiCounter> = vm.get_global_as("counter").unwrap().unwrap();
assert_eq!(counter.get().unwrap().count, 11);
counter.get_mut().unwrap().count += 9;
let results = vm.execute("return counter.count").unwrap();
assert_eq!(results[0].as_integer(), Some(20));
}
#[test]
fn test_push_any_basic() {
let mut vm = LuaVM::new(SafeOption::default());
#[allow(dead_code)]
struct MyConfig {
name: String,
value: i32,
}
let config = MyConfig {
name: "test".to_string(),
value: 42,
};
let ud = vm.push_any(config).unwrap();
assert!(ud.is_userdata());
vm.set_global("my_config", ud).unwrap();
vm.open_stdlib(Stdlib::Basic).unwrap();
let results = vm.execute("return type(my_config)").unwrap();
assert_eq!(results[0].as_str(), Some("userdata"));
}
#[test]
fn test_push_any_downcast() {
let mut vm = LuaVM::new(SafeOption::default());
#[derive(Debug, PartialEq)]
struct Point {
x: f64,
y: f64,
}
let point = Point { x: 3.0, y: 4.0 };
let ud = vm.push_any(point).unwrap();
vm.set_global("pt", ud).unwrap();
let val = vm.get_global("pt").unwrap().unwrap();
let ud_data = val.as_userdata_mut().unwrap();
let recovered = ud_data.downcast_ref::<Point>().unwrap();
assert_eq!(recovered.x, 3.0);
assert_eq!(recovered.y, 4.0);
}
#[test]
fn test_push_any_in_callback() {
let mut vm = LuaVM::new(SafeOption::default());
struct Counter {
count: i32,
}
let counter = Counter { count: 0 };
let ud = vm.push_any(counter).unwrap();
vm.set_global("counter", ud).unwrap();
vm.register_function("increment", |state| {
let ud_val = state.get_arg(1).unwrap();
let ud = ud_val.as_userdata_mut().unwrap();
let c = ud.downcast_mut::<Counter>().unwrap();
c.count += 1;
state.push_value(LuaValue::integer(c.count as i64))?;
Ok(1)
})
.unwrap();
let results = vm.execute("return increment(counter)").unwrap();
assert_eq!(results[0].as_integer(), Some(1));
let results = vm.execute("return increment(counter)").unwrap();
assert_eq!(results[0].as_integer(), Some(2));
}
#[test]
fn test_userdata_builder_fields() {
use crate::lua_value::UserDataBuilder;
let mut vm = LuaVM::new(SafeOption::default());
vm.open_stdlib(Stdlib::Basic).unwrap();
struct Address {
ip: String,
port: u16,
}
let addr = Address {
ip: "127.0.0.1".to_owned(),
port: 8080,
};
let ud = UserDataBuilder::new(addr)
.set_type_name("Address")
.add_field_getter("ip", |a| crate::UdValue::Str(a.ip.clone()))
.add_field_getter("port", |a| crate::UdValue::Integer(a.port as i64))
.build(&mut vm)
.unwrap();
vm.set_global("addr", ud).unwrap();
let results = vm.execute("return addr.ip").unwrap();
assert_eq!(results[0].as_str(), Some("127.0.0.1"));
let results = vm.execute("return addr.port").unwrap();
assert_eq!(results[0].as_integer(), Some(8080));
}
#[test]
fn test_userdata_builder_tostring() {
use crate::lua_value::UserDataBuilder;
let mut vm = LuaVM::new(SafeOption::default());
vm.open_stdlib(Stdlib::Basic).unwrap();
struct Version {
major: u32,
minor: u32,
patch: u32,
}
let ver = Version {
major: 1,
minor: 2,
patch: 3,
};
let ud = UserDataBuilder::new(ver)
.set_type_name("Version")
.set_tostring(|v| format!("{}.{}.{}", v.major, v.minor, v.patch))
.build(&mut vm)
.unwrap();
vm.set_global("ver", ud).unwrap();
let results = vm.execute("return tostring(ver)").unwrap();
assert_eq!(results[0].as_str(), Some("1.2.3"));
}
#[test]
fn test_userdata_builder_setter() {
use crate::lua_value::UserDataBuilder;
let mut vm = LuaVM::new(SafeOption::default());
struct Config {
debug: bool,
}
let cfg = Config { debug: false };
let ud = UserDataBuilder::new(cfg)
.add_field_getter("debug", |c| crate::UdValue::Boolean(c.debug))
.add_field_setter("debug", |c, v| {
c.debug = v.to_bool();
Ok(())
})
.build(&mut vm)
.unwrap();
vm.set_global("cfg", ud).unwrap();
let results = vm.execute("return cfg.debug").unwrap();
assert_eq!(results[0].as_boolean(), Some(false));
vm.execute("cfg.debug = true").unwrap();
let results = vm.execute("return cfg.debug").unwrap();
assert_eq!(results[0].as_boolean(), Some(true));
}
#[test]
fn test_build_table_ref() {
use crate::TableBuilder;
let mut vm = LuaVM::new(SafeOption::default());
let name_str = vm.create_string("Alice").unwrap();
let tbl = vm
.build_table_ref(
TableBuilder::new()
.set("name", name_str)
.set("age", LuaValue::integer(30)),
)
.unwrap();
let name: String = tbl.get_as("name").unwrap();
assert_eq!(name, "Alice");
let age: i64 = tbl.get_as("age").unwrap();
assert_eq!(age, 30);
}
#[test]
fn test_to_ref_type_mismatch() {
let mut vm = LuaVM::new(SafeOption::default());
let table = vm.create_table(0, 0).unwrap();
assert!(vm.to_function_ref(table).is_none());
assert!(vm.to_table_ref(LuaValue::integer(42)).is_none());
}
#[test]
fn test_get_global_nonexistent() {
let mut vm = LuaVM::new(SafeOption::default());
assert!(vm.get_global_table("nonexistent").unwrap().is_none());
assert!(vm.get_global_function("nonexistent").unwrap().is_none());
}