#[cfg(test)]
mod tests {
use std::cell::Cell;
#[cfg(feature = "sandbox")]
use crate::SandboxConfig;
#[cfg(feature = "serde")]
use crate::lua_api::Value;
use crate::{
LuaUserData, SafeOption, Stdlib,
lua_api::{Function, Lua, Table},
lua_methods,
};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[cfg(feature = "serde")]
#[derive(Debug, PartialEq, Serialize, Deserialize)]
struct ApiConfig {
host: String,
port: u16,
tags: Vec<String>,
}
#[derive(LuaUserData)]
struct ApiCounter {
pub count: i64,
}
#[lua_methods]
impl ApiCounter {
pub fn inc(&mut self, delta: i64) {
self.count += delta;
}
pub fn get(&self) -> i64 {
self.count
}
}
#[test]
fn eval_and_typed_globals_work() {
let mut lua = Lua::new(SafeOption::default());
lua.open_stdlib(Stdlib::All).unwrap();
lua.set_global("name", "Lua").unwrap();
let result: String = lua.eval("return 'hello ' .. name").unwrap();
assert_eq!(result, "hello Lua");
}
#[test]
fn register_and_call_typed_function() {
let mut lua = Lua::new(SafeOption::default());
lua.open_stdlib(Stdlib::All).unwrap();
lua.register_function("sum", |a: i64, b: i64| a + b)
.unwrap();
let result: i64 = lua.eval("return sum(20, 22)").unwrap();
assert_eq!(result, 42);
}
#[test]
fn call_global_for_lua_defined_function() {
let mut lua = Lua::new(SafeOption::default());
lua.open_stdlib(Stdlib::All).unwrap();
lua.load("function mul(a, b) return a * b end")
.exec()
.unwrap();
let result: i64 = lua.call_global1("mul", (6, 7)).unwrap();
assert_eq!(result, 42);
}
#[test]
fn high_level_collect_garbage_works() {
let mut lua = Lua::new(SafeOption::default());
lua.open_stdlib(Stdlib::All).unwrap();
lua.load(
r#"
local t = {}
for i = 1, 200 do
t[i] = { index = i, payload = string.rep("x", 32) }
end
t = nil
"#,
)
.exec()
.unwrap();
lua.collect_garbage().unwrap();
let answer: i64 = lua.eval("return 40 + 2").unwrap();
assert_eq!(answer, 42);
}
#[test]
fn safe_table_round_trip() {
let mut lua = Lua::new(SafeOption::default());
let table = lua.create_table_with_capacity(0, 2).unwrap();
lua.table_set(&table, "host", "localhost").unwrap();
lua.table_set(&table, "port", 8080_i64).unwrap();
lua.set_global_table("config", &table).unwrap();
let config = lua.get_table("config").unwrap().unwrap();
let host: String = config.get("host").unwrap();
let port: i64 = config.get("port").unwrap();
assert_eq!(host, "localhost");
assert_eq!(port, 8080);
}
#[test]
fn globals_and_generic_table_api_feel_like_mlua() {
let mut lua = Lua::new(SafeOption::default());
let globals = lua.globals();
globals.set("host", "localhost").unwrap();
globals.set("port", 8080_i64).unwrap();
assert!(globals.contains_key("host").unwrap());
assert_eq!(globals.get::<String>("host").unwrap(), "localhost");
assert_eq!(globals.raw_get::<i64>("port").unwrap(), 8080);
}
#[test]
fn create_table_from_and_sequence_from_work() {
let mut lua = Lua::new(SafeOption::default());
let config = lua
.create_table_from([("host", "localhost"), ("mode", "dev")])
.unwrap();
let seq = lua.create_sequence_from([10_i64, 20_i64, 30_i64]).unwrap();
assert_eq!(config.get::<String>("host").unwrap(), "localhost");
assert_eq!(config.pairs::<String, String>().unwrap().len(), 2);
assert_eq!(seq.sequence_values::<i64>().unwrap(), vec![10, 20, 30]);
}
#[test]
fn create_function_and_convert_helpers_work() {
let mut lua = Lua::new(SafeOption::default());
lua.open_stdlib(Stdlib::All).unwrap();
let double = lua.create_function(|x: i64| x * 2).unwrap();
lua.globals().set("double", double.clone()).unwrap();
let packed = lua.pack("42").unwrap();
let unpacked: String = lua.unpack(packed).unwrap();
let converted: i64 = lua.convert(123_i64).unwrap();
let result: i64 = lua.eval("return double(21)").unwrap();
assert_eq!(unpacked, "42");
assert_eq!(converted, 123);
assert_eq!(result, 42);
}
#[test]
fn table_objectlike_helpers_work() {
let mut lua = Lua::new(SafeOption::default());
lua.open_stdlib(Stdlib::All).unwrap();
let obj: Table = lua
.load(
r#"
return {
nested = { answer = 42 },
add = function(a, b) return a + b end,
scale = function(self, x) return self.factor * x end,
factor = 3,
}
"#,
)
.eval()
.unwrap();
assert_eq!(obj.get_path::<i64>(&["nested", "answer"]).unwrap(), 42);
assert_eq!(obj.call_function::<_, i64>("add", (20, 22)).unwrap(), 42);
assert_eq!(obj.call_method1::<_, i64>("scale", 14_i64).unwrap(), 42);
}
#[test]
fn safe_value_handle_supports_string_and_downcasts() {
let mut lua = Lua::new(SafeOption::default());
let string_value = lua.pack("hello").unwrap();
let table = lua.create_table_from([("answer", 42_i64)]).unwrap();
let table_value = lua.pack(table).unwrap();
let userdata = lua.create_userdata(ApiCounter { count: 1 }).unwrap();
let userdata_value = lua.pack(userdata.clone()).unwrap();
assert_eq!(string_value.type_name(), "string");
assert_eq!(string_value.as_string().unwrap(), "hello");
assert_eq!(string_value.to_string_lossy(), "hello");
assert_eq!(
string_value.as_string_handle().unwrap().as_str(),
Some("hello")
);
let table = table_value.as_table().unwrap();
assert_eq!(table.get::<i64>("answer").unwrap(), 42);
let counter = userdata_value.as_userdata::<ApiCounter>().unwrap();
assert_eq!(counter.get().unwrap().count, 1);
let converted: String = string_value.get().unwrap();
assert_eq!(converted, "hello");
}
#[test]
fn high_level_userdata_api_works() {
let mut lua = Lua::new(SafeOption::default());
lua.open_stdlib(Stdlib::All).unwrap();
let type_table = lua.register_type::<ApiCounter>("Counter").unwrap();
assert!(type_table.raw_len().is_ok());
let counter = lua.create_userdata(ApiCounter { count: 1 }).unwrap();
lua.globals().set("counter", counter.clone()).unwrap();
lua.load("counter:inc(41)").exec().unwrap();
assert_eq!(counter.get().unwrap().count, 42);
assert_eq!(lua.load("return counter:get()").eval::<i64>().unwrap(), 42);
}
#[test]
fn borrowed_userdata_api_works() {
let mut lua = Lua::new(SafeOption::default());
lua.open_stdlib(Stdlib::All).unwrap();
let mut counter = ApiCounter { count: 2 };
let borrowed = unsafe { lua.create_userdata_ref(&mut counter).unwrap() };
lua.globals().set("borrowed", borrowed.clone()).unwrap();
lua.load("borrowed:inc(40)").exec().unwrap();
assert_eq!(counter.count, 42);
assert_eq!(borrowed.get().unwrap().count, 42);
}
#[test]
fn scope_supports_non_static_functions() {
let mut lua = Lua::new(SafeOption::default());
lua.open_stdlib(Stdlib::All).unwrap();
let base = 40_i64;
lua.scope(|scope| {
let add_base = scope.create_function_with(&base, |base: &i64, x: i64| x + *base)?;
scope.globals().set("add_base", &add_base)?;
let result: i64 = scope.load("return add_base(2)").eval()?;
assert_eq!(result, 42);
Ok(())
})
.unwrap();
assert!(lua.load("return add_base(1)").eval::<i64>().is_err());
}
#[test]
fn scope_supports_borrowed_userdata() {
let mut lua = Lua::new(SafeOption::default());
lua.open_stdlib(Stdlib::All).unwrap();
let mut counter = ApiCounter { count: 1 };
lua.scope(|scope| {
let mut borrowed = scope.create_userdata_ref(&mut counter)?;
scope.globals().set("borrowed", &borrowed)?;
let count: i64 = scope.load("return borrowed.count").eval()?;
assert_eq!(count, 1);
let called: i64 = scope
.load("borrowed:inc(41); return borrowed:get()")
.eval()?;
assert_eq!(called, 42);
let reassigned: i64 = scope
.load("borrowed.count = borrowed.count + 1; return borrowed.count")
.eval()?;
assert_eq!(reassigned, 43);
borrowed.get_mut()?.inc(41);
assert_eq!(borrowed.get()?.count, 84);
Ok(())
})
.unwrap();
assert_eq!(counter.count, 84);
assert!(lua.load("return borrowed:get()").eval::<i64>().is_err());
assert!(lua.load("borrowed.count = 1").exec().is_err());
}
#[test]
fn scope_function_with_borrowed_state_works() {
let mut lua = Lua::new(SafeOption::default());
lua.open_stdlib(Stdlib::All).unwrap();
let total = Cell::new(0_i64);
lua.scope(|scope| {
let push = scope.create_function_with(&total, |total: &Cell<i64>, delta: i64| {
total.set(total.get() + delta);
total.get()
})?;
scope.globals().set("push_total", &push)?;
let value: i64 = scope
.load("return push_total(19) + push_total(23)")
.eval()?;
assert_eq!(value, 61);
Ok(())
})
.unwrap();
assert_eq!(total.get(), 42);
}
#[test]
fn scope_function_mut_with_borrowed_state_works() {
let mut lua = Lua::new(SafeOption::default());
lua.open_stdlib(Stdlib::All).unwrap();
let mut total = 0_i64;
lua.scope(|scope| {
let push =
scope.create_function_mut_with(&mut total, |total: &mut i64, delta: i64| {
*total += delta;
*total
})?;
scope.globals().set("push_total_mut", &push)?;
let value: i64 = scope
.load("return push_total_mut(19) + push_total_mut(23)")
.eval()?;
assert_eq!(value, 61);
Ok(())
})
.unwrap();
assert_eq!(total, 42);
}
#[test]
fn scope_function_with_borrowed_reference_works() {
let mut lua = Lua::new(SafeOption::default());
lua.open_stdlib(Stdlib::All).unwrap();
let base = 40_i64;
lua.scope(|scope| {
let add_base = scope.create_function_with(&base, |base: &i64, x: i64| x + *base)?;
scope.globals().set("add_base", &add_base)?;
let result: i64 = scope.load("return add_base(2)").eval()?;
assert_eq!(result, 42);
Ok(())
})
.unwrap();
}
#[test]
fn chunk_builder_exec_eval_and_into_function_work() {
let mut lua = Lua::new(SafeOption::default());
lua.open_stdlib(Stdlib::All).unwrap();
lua.load("answer = 41").set_name("init.lua").exec().unwrap();
let answer: i64 = lua.load("return answer + 1").eval().unwrap();
let add = lua
.load("local a, b = ...; return a + b")
.set_name("adder.lua")
.into_function()
.unwrap();
assert_eq!(answer, 42);
assert_eq!(add.call1::<_, i64>((20, 22)).unwrap(), 42);
}
#[tokio::test]
async fn high_level_async_api_exec_and_call_work() {
let mut lua = Lua::new(SafeOption::default());
lua.open_stdlib(Stdlib::All).unwrap();
lua.register_async_function("double_async", |x: i64| async move { Ok(x * 2) })
.unwrap();
lua.load(
r#"
function add_async(a, b)
return double_async(a + b)
end
"#,
)
.exec()
.unwrap();
let chunk_value: i64 = lua
.load("return double_async(21)")
.eval_async()
.await
.unwrap();
let global_value: i64 = lua
.call_async_global1("add_async", (20_i64, 1_i64))
.await
.unwrap();
let compiled: Function = lua
.load("return function(x) return double_async(x) end")
.eval()
.unwrap();
let function_value: i64 = lua.call_async1(&compiled, 21_i64).await.unwrap();
assert_eq!(chunk_value, 42);
assert_eq!(global_value, 42);
assert_eq!(function_value, 42);
}
#[cfg(feature = "sandbox")]
#[test]
fn high_level_sandbox_api_supports_injected_globals_and_isolation() {
let mut lua = Lua::new(SafeOption::default());
lua.open_stdlib(Stdlib::All).unwrap();
lua.register_function("greet", |name: String| format!("hello, {name}"))
.unwrap();
let mut config = SandboxConfig::default();
lua.sandbox_capture_global(&mut config, "greet").unwrap();
let value: String = lua
.load_sandboxed(
r#"
sandbox_value = 41
return greet("sandbox")
"#,
&config,
)
.eval()
.unwrap();
assert_eq!(value, "hello, sandbox");
assert!(lua.get_global::<i64>("sandbox_value").unwrap().is_none());
}
#[test]
fn table_and_function_convert_from_lua() {
let mut lua = Lua::new(SafeOption::default());
lua.open_stdlib(Stdlib::All).unwrap();
let table: Table = lua
.load("return { host = 'localhost', port = 8080 }")
.eval()
.unwrap();
let function: Function = lua
.load("return function(x) return x * 2 end")
.eval()
.unwrap();
assert_eq!(table.get::<String>("host").unwrap(), "localhost");
assert_eq!(table.get::<i64>("port").unwrap(), 8080);
assert_eq!(function.call1::<_, i64>(21).unwrap(), 42);
}
#[test]
fn high_level_lua_install_library_works() {
let mut lua = Lua::new(SafeOption::default());
lua.open_stdlib(Stdlib::All).unwrap();
let module = crate::LibraryModule::new("hostlib").with_function("answer", |l| {
l.push_value(crate::LuaValue::integer(42))?;
Ok(1)
});
lua.install_library(module).unwrap();
let answer: i64 = lua.load("return hostlib.answer()").eval().unwrap();
assert_eq!(answer, 42);
}
#[cfg(feature = "serde")]
#[test]
fn table_serde_json_round_trip_works() {
let mut lua = Lua::new(SafeOption::default());
let table = lua
.create_table_from([("host", "localhost"), ("port", "8080")])
.unwrap();
table
.set(
"nested",
lua.create_sequence_from([1_i64, 2_i64, 3_i64]).unwrap(),
)
.unwrap();
let json = table.to_json_value().unwrap();
assert_eq!(
json,
serde_json::json!({
"host": "localhost",
"port": "8080",
"nested": [1, 2, 3]
})
);
let encoded = serde_json::to_value(&table).unwrap();
assert_eq!(encoded, json);
}
#[cfg(feature = "serde")]
#[test]
fn table_from_json_and_to_serde_work() {
let mut lua = Lua::new(SafeOption::default());
let table = Table::from_json_value(
&mut lua,
&serde_json::json!({
"host": "127.0.0.1",
"port": 8080,
"tags": ["dev", "edge"]
}),
)
.unwrap();
let config: ApiConfig = table.to_serde().unwrap();
assert_eq!(
config,
ApiConfig {
host: "127.0.0.1".to_string(),
port: 8080,
tags: vec!["dev".to_string(), "edge".to_string()],
}
);
}
#[cfg(feature = "serde")]
#[test]
fn table_from_serde_works() {
let mut lua = Lua::new(SafeOption::default());
let input = ApiConfig {
host: "localhost".to_string(),
port: 3000,
tags: vec!["api".to_string(), "beta".to_string()],
};
let table = Table::from_serde(&mut lua, &input).unwrap();
assert_eq!(table.get::<String>("host").unwrap(), "localhost");
assert_eq!(table.get::<i64>("port").unwrap(), 3000);
assert_eq!(
table
.get::<Table>("tags")
.unwrap()
.sequence_values::<String>()
.unwrap(),
vec!["api".to_string(), "beta".to_string()]
);
}
#[cfg(feature = "serde")]
#[test]
fn value_serde_scalar_round_trip_works() {
let mut lua = Lua::new(SafeOption::default());
let value = lua.pack(42_i64).unwrap();
assert_eq!(value.to_json_value().unwrap(), serde_json::json!(42));
assert_eq!(serde_json::to_value(&value).unwrap(), serde_json::json!(42));
let decoded: i64 = value.to_serde().unwrap();
assert_eq!(decoded, 42);
}
#[cfg(feature = "serde")]
#[test]
fn value_from_json_and_from_serde_work() {
let mut lua = Lua::new(SafeOption::default());
let from_json = Value::from_json_value(
&mut lua,
&serde_json::json!({
"host": "127.0.0.1",
"port": 8081,
"tags": ["prod", "edge"]
}),
)
.unwrap();
let config: ApiConfig = from_json.to_serde().unwrap();
assert_eq!(
config,
ApiConfig {
host: "127.0.0.1".to_string(),
port: 8081,
tags: vec!["prod".to_string(), "edge".to_string()],
}
);
let from_serde = Value::from_serde(
&mut lua,
&ApiConfig {
host: "localhost".to_string(),
port: 3001,
tags: vec!["api".to_string()],
},
)
.unwrap();
let table = from_serde.as_table().unwrap();
assert_eq!(table.get::<String>("host").unwrap(), "localhost");
assert_eq!(table.get::<i64>("port").unwrap(), 3001);
assert_eq!(
table
.get::<Table>("tags")
.unwrap()
.sequence_values::<String>()
.unwrap(),
vec!["api".to_string()]
);
}
#[test]
fn test_userdata() {
#[derive(Clone, Debug, LuaUserData)]
struct RustStruct {
a: i32,
b: i32,
}
#[lua_methods]
impl RustStruct {}
let mut l = Lua::new(SafeOption::default());
let t = l.create_table().unwrap();
t.set(1, RustStruct { a: 1, b: 2 }).unwrap();
let seq = t.sequence_values::<RustStruct>().unwrap();
assert_eq!(seq.len(), 1);
assert_eq!(seq[0].a, 1);
assert_eq!(seq[0].b, 2);
}
}