use std::sync::Arc;
use luaur_rt::{
Error, Function, Lua, MetaMethod, Result, UserData, UserDataFields, UserDataMethods, Variadic,
};
#[test]
fn test_methods() -> Result<()> {
struct MyUserData(i64);
impl UserData for MyUserData {
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
methods.add_method("get_value", |_, data, ()| Ok(data.0));
methods.add_method_mut("set_value", |_, data, args: i64| {
data.0 = args;
Ok(())
});
}
}
let lua = Lua::new();
let globals = lua.globals();
let userdata = lua.create_userdata(MyUserData(42))?;
globals.set("userdata", &userdata)?;
lua.load(
r#"
function get_it()
return userdata:get_value()
end
function set_it(i)
return userdata:set_value(i)
end
"#,
)
.exec()?;
let get = globals.get::<Function>("get_it")?;
let set = globals.get::<Function>("set_it")?;
assert_eq!(get.call::<i64>(())?, 42);
userdata.borrow_mut::<MyUserData>()?.0 = 64;
assert_eq!(get.call::<i64>(())?, 64);
set.call::<()>(100)?;
assert_eq!(get.call::<i64>(())?, 100);
Ok(())
}
#[test]
fn test_userdata() -> Result<()> {
use std::any::TypeId;
struct UserData1(i64);
struct UserData2(Box<i64>);
impl UserData for UserData1 {}
impl UserData for UserData2 {}
let lua = Lua::new();
let userdata1 = lua.create_userdata(UserData1(1))?;
let userdata2 = lua.create_userdata(UserData2(Box::new(2)))?;
assert!(userdata1.is::<UserData1>());
assert!(userdata1.type_id() == Some(TypeId::of::<UserData1>()));
assert!(!userdata1.is::<UserData2>());
assert!(userdata2.is::<UserData2>());
assert!(!userdata2.is::<UserData1>());
assert!(userdata2.type_id() == Some(TypeId::of::<UserData2>()));
assert_eq!(userdata1.borrow::<UserData1>()?.0, 1);
assert_eq!(*userdata2.borrow::<UserData2>()?.0, 2);
Ok(())
}
#[test]
fn test_userdata_take() -> Result<()> {
#[derive(Debug)]
struct MyUserdata(Arc<i64>);
impl UserData for MyUserdata {
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
methods.add_method("num", |_, this, ()| Ok(*this.0))
}
}
let lua = Lua::new();
let rc = Arc::new(18);
let userdata = lua.create_userdata(MyUserdata(rc.clone()))?;
lua.globals().set("userdata", &userdata)?;
assert_eq!(Arc::strong_count(&rc), 2);
{
let _value = userdata.borrow::<MyUserdata>()?;
match userdata.take::<MyUserdata>() {
Err(Error::UserDataBorrowMutError) => {}
r => panic!("expected `UserDataBorrowMutError` error, got {:?}", r),
}
}
let value = userdata.take::<MyUserdata>()?;
assert_eq!(*value.0, 18);
drop(value);
assert_eq!(Arc::strong_count(&rc), 1);
match userdata.borrow::<MyUserdata>() {
Err(Error::UserDataDestructed) => {}
r => panic!("expected `UserDataDestructed` error, got {:?}", r),
}
match lua.load("userdata:num()").exec() {
Err(Error::CallbackError { ref cause, .. }) => match cause.as_ref() {
Error::UserDataDestructed => {}
err => panic!("expected `UserDataDestructed`, got {:?}", err),
},
r => panic!("improper return for destructed userdata: {:?}", r),
}
assert!(!userdata.is::<MyUserdata>());
Ok(())
}
#[test]
fn test_fields() -> Result<()> {
let lua = Lua::new();
let globals = lua.globals();
#[derive(Copy, Clone)]
struct MyUserData(i64);
impl UserData for MyUserData {
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
fields.add_field("static", "constant");
fields.add_field_method_get("val", |_, data| Ok(data.0));
fields.add_field_method_set("val", |_, data, val| {
data.0 = val;
Ok(())
});
fields.add_field_function_get("val_fget", |lua, ud| {
lua.create_function(move |_, ()| Ok(ud.borrow::<MyUserData>()?.0))
});
}
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
methods.add_method("dummy", |_, _, ()| Ok(()));
}
}
globals.set("ud", lua.create_userdata(MyUserData(7))?)?;
lua.load(
r#"
assert(ud.static == "constant")
assert(ud.val == 7)
ud.val = 10
assert(ud.val == 10)
assert(ud:val_fget() == 10)
ud:dummy()
"#,
)
.exec()?;
Ok(())
}
#[test]
fn test_method_variadic() -> Result<()> {
struct MyUserData(i64);
impl UserData for MyUserData {
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
methods.add_method("get", |_, data, ()| Ok(data.0));
methods.add_method_mut("add", |_, data, vals: Variadic<i64>| {
data.0 += vals.into_iter().sum::<i64>();
Ok(())
});
}
}
let lua = Lua::new();
let globals = lua.globals();
globals.set("userdata", lua.create_userdata(MyUserData(0))?)?;
lua.load("userdata:add(1, 5, -10)").exec()?;
let total: i64 = lua.load("return userdata:get()").eval()?;
assert_eq!(total, -4);
Ok(())
}
#[test]
fn test_metamethods() -> Result<()> {
struct MyUserData(i64);
impl UserData for MyUserData {
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
methods.add_method("get", |_, data, ()| Ok(data.0));
methods.add_meta_method(MetaMethod::Add, |_, data, other: i64| Ok(data.0 + other));
methods.add_meta_method("__sub", |_, data, other: i64| Ok(data.0 - other));
methods.add_meta_method(MetaMethod::ToString, |_, data, ()| {
Ok(format!("MyUserData({})", data.0))
});
}
}
let lua = Lua::new();
let globals = lua.globals();
globals.set("userdata1", lua.create_userdata(MyUserData(7))?)?;
assert_eq!(lua.load("return userdata1 + 3").eval::<i64>()?, 10);
assert_eq!(lua.load("return userdata1 - 2").eval::<i64>()?, 5);
assert_eq!(lua.load("return userdata1:get()").eval::<i64>()?, 7);
assert_eq!(
lua.load("return tostring(userdata1)").eval::<String>()?,
"MyUserData(7)"
);
Ok(())
}
#[test]
fn test_functions() -> Result<()> {
struct MyUserData(i64);
impl UserData for MyUserData {
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
methods.add_method("get_value", |_, data, ()| Ok(data.0));
methods.add_function("get_constant", |_, ()| Ok(7));
}
}
let lua = Lua::new();
let globals = lua.globals();
globals.set("userdata", lua.create_userdata(MyUserData(42))?)?;
lua.load(
r#"
function get_it()
return userdata:get_value()
end
function get_constant()
return userdata.get_constant()
end
"#,
)
.exec()?;
assert_eq!(globals.get::<Function>("get_it")?.call::<i64>(())?, 42);
assert_eq!(globals.get::<Function>("get_constant")?.call::<i64>(())?, 7);
Ok(())
}
#[test]
fn test_gc_userdata_access_after_collect() -> Result<()> {
struct MyUserdata {
id: u8,
}
impl UserData for MyUserdata {
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
methods.add_method("access", |_, this, ()| {
assert_eq!(this.id, 123);
Ok(this.id)
});
}
}
let lua = Lua::new();
let ud = lua.create_userdata(MyUserdata { id: 123 })?;
lua.globals().set("userdata", ud.clone())?;
lua.gc_collect()?;
let id: u8 = lua.load("return userdata:access()").eval()?;
assert_eq!(id, 123);
Ok(())
}
#[test]
fn test_userdata_drop_runs_destructor() -> Result<()> {
use std::sync::atomic::{AtomicBool, Ordering};
struct Tracked(Arc<AtomicBool>);
impl UserData for Tracked {}
impl Drop for Tracked {
fn drop(&mut self) {
self.0.store(true, Ordering::SeqCst);
}
}
let dropped = Arc::new(AtomicBool::new(false));
let lua = Lua::new();
lua.globals()
.set("ud", lua.create_userdata(Tracked(dropped.clone()))?)?;
assert!(!dropped.load(Ordering::SeqCst));
lua.load("ud = nil").exec()?;
lua.gc_collect()?;
lua.gc_collect()?;
assert!(
dropped.load(Ordering::SeqCst),
"userdata destructor should have run"
);
Ok(())
}