use mlua::{Error, IntoLua, Lua, ObjectLike, Result, Table, Value};
#[test]
fn test_globals_set_get() -> Result<()> {
let lua = Lua::new();
let globals = lua.globals();
globals.set("foo", "bar")?;
globals.set("baz", "baf")?;
assert_eq!(globals.get::<String>("foo")?, "bar");
assert_eq!(globals.get::<String>("baz")?, "baf");
lua.load(r#"assert(foo == "bar")"#).exec().unwrap();
Ok(())
}
#[test]
fn test_table() -> Result<()> {
let lua = Lua::new();
let globals = lua.globals();
lua.load(
r#"
table1 = {1, 2, 3, 4, 5}
table2 = {}
table3 = {1, 2, nil, 4, 5}
"#,
)
.exec()?;
let table1 = globals.get::<Table>("table1")?;
assert_eq!(table1.len()?, 5);
assert!(!table1.is_empty());
assert_eq!(
table1.pairs().collect::<Result<Vec<(i64, i64)>>>()?,
vec![(1, 1), (2, 2), (3, 3), (4, 4), (5, 5)]
);
assert_eq!(
table1.sequence_values().collect::<Result<Vec<i64>>>()?,
vec![1, 2, 3, 4, 5]
);
assert_eq!(table1, [1, 2, 3, 4, 5]);
assert_eq!(table1, [1, 2, 3, 4, 5].as_slice());
assert_ne!(table1, [1, 2, 3, 4, 5, 6]);
assert_ne!(table1, [1, 2, 3, 4]);
let table2 = globals.get::<Table>("table2")?;
assert_eq!(table2.len()?, 0);
assert!(table2.is_empty());
assert_eq!(table2.pairs().collect::<Result<Vec<(i64, i64)>>>()?, vec![]);
assert_eq!(table2, [0; 0]);
assert_ne!(table2, [1]);
let table3 = globals.get::<Table>("table3")?;
assert_eq!(table3, [1, 2]);
assert_eq!(
table3.sequence_values().collect::<Result<Vec<i64>>>()?,
vec![1, 2]
);
Ok(())
}
#[test]
#[cfg(target_os = "linux")] fn test_table_with_large_capacity() {
let lua = Lua::new();
let t = lua.create_table_with_capacity(1 << 26, 1 << 26);
assert!(t.is_ok());
}
#[test]
fn test_table_push_pop() -> Result<()> {
let lua = Lua::new();
let table1 = lua.create_sequence_from([123])?;
table1.raw_push(321)?;
assert_eq!(table1, [123, 321]);
assert_eq!(table1.raw_pop::<i64>()?, 321);
assert_eq!(table1.raw_pop::<i64>()?, 123);
assert_eq!(table1.raw_pop::<Value>()?, Value::Nil); assert_eq!(table1.raw_len(), 0);
assert_eq!(table1, [0; 0]);
let table2 = lua
.load(
r#"
local proxy_table = {234}
table2 = setmetatable({}, {
__len = function() return #proxy_table end,
__index = proxy_table,
__newindex = proxy_table,
})
return table2
"#,
)
.eval::<Table>()?;
table2.push(345)?;
assert_eq!(table2.len()?, 2);
assert_eq!(
table2.sequence_values::<i64>().collect::<Result<Vec<_>>>()?,
vec![]
);
assert_eq!(table2.pop::<i64>()?, 345);
assert_eq!(table2.pop::<i64>()?, 234);
assert_eq!(table2.pop::<Value>()?, Value::Nil);
assert_eq!(table2.len()?, 0);
#[cfg(any(feature = "lua53", feature = "lua54", feature = "lua55"))]
{
let table = (lua.load("setmetatable({}, {__len = function() return math.maxinteger end})"))
.eval::<Table>()?;
assert!(
matches!(table.push(1), Err(Error::RuntimeError(msg)) if msg.contains("table length overflow"))
);
assert!(table.is_empty());
}
Ok(())
}
#[test]
fn test_table_pop_empty() -> Result<()> {
let lua = Lua::new();
let empty = lua.create_table()?;
let table = lua.create_table_from([(0, 42)])?;
assert_eq!(empty.raw_pop::<Value>()?, Value::Nil);
assert_eq!(table.raw_pop::<Value>()?, Value::Nil);
assert_eq!(table.raw_get::<i64>(0)?, 42);
table.set_metatable(Some(lua.create_table()?))?;
assert_eq!(table.pop::<Value>()?, Value::Nil);
assert_eq!(table.raw_get::<i64>(0)?, 42);
Ok(())
}
#[test]
fn test_table_insert_remove() -> Result<()> {
let lua = Lua::new();
let globals = lua.globals();
globals.set("table4", [1, 2, 3, 4, 5])?;
let table4 = globals.get::<Table>("table4")?;
assert_eq!(
table4.pairs().collect::<Result<Vec<(i64, i64)>>>()?,
vec![(1, 1), (2, 2), (3, 3), (4, 4), (5, 5)]
);
table4.raw_insert(4, 35)?;
table4.raw_insert(7, 7)?;
assert_eq!(
table4.pairs().collect::<Result<Vec<(i64, i64)>>>()?,
vec![(1, 1), (2, 2), (3, 3), (4, 35), (5, 4), (6, 5), (7, 7)]
);
table4.raw_remove(1)?;
assert_eq!(
table4.pairs().collect::<Result<Vec<(i64, i64)>>>()?,
vec![(1, 2), (2, 3), (3, 35), (4, 4), (5, 5), (6, 7)]
);
assert!(table4.raw_insert(0, "123").is_err());
Ok(())
}
#[test]
fn test_table_remove_metatable() -> Result<()> {
let lua = Lua::new();
let inner = lua.create_sequence_from([1, 2, 3, 4, 5])?;
let mt = lua.create_table()?;
mt.set("__index", &inner)?;
mt.set("__newindex", &inner)?;
mt.set("__len", {
let inner = inner.clone();
lua.create_function(move |_, ()| Ok(inner.raw_len()))?
})?;
let t = lua.create_table()?;
t.set_metatable(Some(mt))?;
t.remove(2)?; assert_eq!(t.len()?, 4);
assert_eq!(
inner.pairs().collect::<Result<Vec<(i64, i64)>>>()?,
vec![(1, 1), (2, 3), (3, 4), (4, 5)]
);
t.set("abc", "abcdef")?;
assert_eq!(inner.get::<String>("abc")?, "abcdef");
t.remove("abc")?;
assert_eq!(inner.get::<Value>("abc")?, Value::Nil);
Ok(())
}
#[test]
fn test_table_metatable_during_conversion() -> Result<()> {
struct SetMetatable<'a>(&'a Table, &'a Table);
impl IntoLua for SetMetatable<'_> {
fn into_lua(self, _: &Lua) -> Result<Value> {
self.0.set_metatable(Some(self.1.clone()))?;
Ok(Value::Integer(1))
}
}
let lua = Lua::new();
let inner = lua.create_sequence_from([42, 43])?;
let mt = lua.create_table_from([("__index", &inner), ("__newindex", &inner)])?;
mt.set(
"__len",
lua.create_function({
let inner = inner.clone();
move |_, ()| Ok(inner.raw_len())
})?,
)?;
let table = lua.create_table()?;
assert_eq!(table.get::<i64>(SetMetatable(&table, &mt))?, 42);
table.set_metatable(None)?;
table.set(1, SetMetatable(&table, &mt))?;
assert_eq!(inner.raw_get::<i64>(1)?, 1);
table.set_metatable(None)?;
table.push(SetMetatable(&table, &mt))?;
assert_eq!(inner.raw_get::<i64>(3)?, 1);
table.set_metatable(None)?;
table.remove(SetMetatable(&table, &mt))?;
assert_eq!(inner, [43, 1]);
assert!(table.is_empty());
Ok(())
}
#[test]
#[cfg(feature = "luau")]
fn test_table_readonly_during_conversion() -> Result<()> {
struct Freeze<'a>(&'a Table);
impl mlua::IntoLua for Freeze<'_> {
fn into_lua(self, _: &Lua) -> Result<Value> {
self.0.set_readonly(true);
Ok(Value::Integer(1))
}
}
let lua = Lua::new();
let table = lua.create_table()?;
let result = table.raw_set(1, Freeze(&table));
assert!(matches!(result, Err(Error::RuntimeError(err)) if err.contains("readonly")));
assert!(table.is_empty());
Ok(())
}
#[test]
fn test_table_clear() -> Result<()> {
let lua = Lua::new();
let t = lua.create_table()?;
#[cfg(feature = "luau")]
{
t.set_readonly(true);
assert!(matches!(
t.clear(),
Err(Error::RuntimeError(err)) if err.contains("attempt to modify a readonly table")
));
t.set_readonly(false);
}
t.push("abc")?;
t.push("bcd")?;
t.set("a", "1")?;
t.set("b", "2")?;
t.clear()?;
assert_eq!(t.len()?, 0);
assert_eq!(t.pairs::<Value, Value>().count(), 0);
let t2 = lua
.load(
r#"
setmetatable({1, 2, 3, a = "1"}, {
__index = function() error("index error") end,
__newindex = function() error("newindex error") end,
__len = function() error("len error") end,
__pairs = function() error("pairs error") end,
})
"#,
)
.eval::<Table>()?;
assert_eq!(t2.raw_len(), 3);
assert!(!t2.is_empty());
t2.clear()?;
assert_eq!(t2.raw_len(), 0);
assert!(t2.is_empty());
assert_eq!(t2.raw_get::<Value>("a")?, Value::Nil);
assert_ne!(t2.metatable(), None);
Ok(())
}
#[test]
fn test_table_sequence_from() -> Result<()> {
let lua = Lua::new();
let get_table = lua.create_function(|_, t: Table| Ok(t))?;
assert_eq!(get_table.call::<Table>(vec![1, 2, 3])?, [1, 2, 3]);
assert_eq!(get_table.call::<Table>([4, 5, 6])?, [4, 5, 6]);
assert_eq!(get_table.call::<Table>([7, 8, 9].as_slice())?, [7, 8, 9]);
Ok(())
}
#[test]
fn test_table_pairs() -> Result<()> {
let lua = Lua::new();
let table = lua
.load(
r#"
{
foo = "bar",
baz = "baf",
[123] = 456,
[789] = 101112,
5,
}
"#,
)
.eval::<Table>()?;
for (i, kv) in table.pairs::<String, Value>().enumerate() {
let (k, _v) = kv.unwrap();
match i {
0 => table.set("new_key", "new_value")?,
1 => {
table.set(k, Value::Nil)?;
lua.gc_collect()?;
}
_ => {}
}
}
Ok(())
}
#[test]
#[cfg(feature = "luau")]
fn test_table_iteration_after_clear() -> Result<()> {
let lua = Lua::new();
let table = lua.create_table_from([("key", 1)])?;
let mut pairs = table.pairs::<String, i32>();
pairs.next().unwrap()?;
table.clear()?;
assert!(pairs.next().is_none());
table.raw_set("key", 1)?;
table.for_each::<String, i32>(|_, _| table.clear())?;
table.raw_set("key", 2)?;
assert_eq!(table.pairs::<String, i32>().next().unwrap()?.1, 2);
Ok(())
}
#[test]
#[cfg(not(feature = "luau"))]
fn test_table_pairs_invalid_key() -> Result<()> {
let lua = Lua::new();
let table = lua.create_table_from([("key", true)])?;
let mut pairs = table.pairs::<Value, Value>();
pairs.next().unwrap()?;
table.clear()?;
for key in ["a", "b", "c"] {
table.raw_set(key, true)?;
}
assert!(matches!(pairs.next().unwrap(), Err(Error::RuntimeError(e)) if e.contains("invalid key")));
assert!(pairs.next().is_none());
Ok(())
}
#[test]
fn test_table_for_each() -> Result<()> {
let lua = Lua::new();
let table = lua
.load(
r#"
{
foo = "bar",
baz = "baf",
[123] = 456,
[789] = 101112,
5,
}
"#,
)
.eval::<Table>()?;
let mut i = 0;
table.for_each::<String, Value>(|k, _| {
if i == 0 {
table.set(k, Value::Nil)?;
lua.gc_collect()?;
}
i += 1;
Ok(())
})?;
assert_eq!(i, 5);
Ok(())
}
#[test]
fn test_table_for_each_value() -> Result<()> {
let lua = Lua::new();
let table = lua.load("{1, 2, 3, 4, 5, nil, 7}").eval::<Table>()?;
let mut sum = 0;
table.for_each_value::<i32>(|v| {
sum += v;
Ok(())
})?;
assert_eq!(sum, 1 + 2 + 3 + 4 + 5);
Ok(())
}
#[test]
#[cfg(not(feature = "luau"))]
fn test_table_for_each_invalid_key() -> Result<()> {
let lua = Lua::new();
let table = lua.create_table_from([("key", true)])?;
let result = table.for_each::<Value, Value>(|_, _| {
table.clear()?;
for key in ["a", "b", "c"] {
table.raw_set(key, true)?;
}
Ok(())
});
assert!(matches!(result, Err(Error::RuntimeError(e)) if e.contains("invalid key")));
Ok(())
}
#[test]
fn test_table_for_each_error() -> Result<()> {
let lua = Lua::new();
let table = lua.create_sequence_from([1, 2])?;
#[cfg(not(feature = "luau"))]
lua.set_hook(mlua::HookTriggers::ON_RETURNS, |_, _| {
Err(Error::runtime("return hook error"))
})?;
let mut calls = 0;
let result = table.for_each::<i64, i64>(|_, _| {
calls += 1;
Err(Error::runtime("callback error"))
});
assert!(matches!(result, Err(Error::RuntimeError(e)) if e == "callback error"));
assert_eq!(calls, 1);
assert!(matches!(
table.for_each::<i64, Table>(|_, _| unreachable!()),
Err(Error::FromLuaConversionError { .. })
));
#[cfg(panic = "unwind")]
{
use std::panic::{AssertUnwindSafe, catch_unwind};
let panic = catch_unwind(AssertUnwindSafe(|| {
table.for_each::<i64, i64>(|_, _| panic!("callback panic"))
}));
assert_eq!(panic.unwrap_err().downcast_ref::<&str>(), Some(&"callback panic"));
}
#[cfg(not(feature = "luau"))]
lua.remove_hook();
table.for_each::<i64, i64>(|k, v| {
assert_eq!(lua.load("return ...").call::<i64>(v)?, k);
Ok(())
})
}
#[test]
fn test_table_scope() -> Result<()> {
let lua = Lua::new();
let globals = lua.globals();
lua.load(
r#"
touter = {
tin = {1, 2, 3}
}
"#,
)
.exec()?;
let tin;
{
let touter = globals.get::<Table>("touter")?;
tin = touter.get::<Table>("tin")?;
}
assert_eq!(tin.get::<i64>(1)?, 1);
assert_eq!(tin.get::<i64>(2)?, 2);
assert_eq!(tin.get::<i64>(3)?, 3);
Ok(())
}
#[test]
fn test_metatable() -> Result<()> {
let lua = Lua::new();
let table = lua.create_table()?;
let metatable = lua.create_table()?;
metatable.set("__index", lua.create_function(|_, ()| Ok("index_value"))?)?;
table.set_metatable(Some(metatable))?;
assert_eq!(table.get::<String>("any_key")?, "index_value");
assert_eq!(table.raw_get::<Value>("any_key")?, Value::Nil);
table.set_metatable(None)?;
assert_eq!(table.get::<Value>("any_key")?, Value::Nil);
Ok(())
}
#[test]
fn test_table_equals() -> Result<()> {
let lua = Lua::new();
let tables = lua
.load(
r#"
local a = {}
local eq = function() return 42 end
return {
a, a, {},
setmetatable({}, {__eq = eq}),
setmetatable({}, {__eq = eq}),
setmetatable({}, {__eq = function() return false end}),
setmetatable({}, setmetatable({}, {__index = {__eq = eq}})),
}
"#,
)
.eval::<Vec<Table>>()?;
let equals = lua
.load("function(a, b) return a == b end")
.eval::<mlua::Function>()?;
for a in &tables {
for b in &tables {
assert_eq!(a.equals(b)?, equals.call::<bool>((a, b))?);
}
}
let mt = lua
.load("{__eq = function() error('comparison failed') end}")
.eval::<Table>()?;
tables[0].set_metatable(Some(mt.clone()))?;
tables[2].set_metatable(Some(mt))?;
assert!(matches!(
tables[0].equals(&tables[2]),
Err(Error::RuntimeError(_))
));
Ok(())
}
#[test]
fn test_table_pointer() -> Result<()> {
let lua = Lua::new();
let table1 = lua.create_table()?;
let table2 = lua.create_table()?;
assert_eq!(table1.to_pointer(), table1.clone().to_pointer());
assert_ne!(table1.to_pointer(), table2.to_pointer());
Ok(())
}
#[test]
fn test_table_error() -> Result<()> {
let lua = Lua::new();
let globals = lua.globals();
lua.load(
r#"
table = {}
setmetatable(table, {
__index = function()
error("lua error")
end,
__newindex = function()
error("lua error")
end,
__len = function()
error("lua error")
end
})
"#,
)
.exec()?;
let bad_table: Table = globals.get("table")?;
assert!(bad_table.set(1, 1).is_err());
assert!(bad_table.get::<i32>(1).is_err());
assert!(bad_table.len().is_err());
assert!(bad_table.raw_set(1, 1).is_ok());
assert!(bad_table.raw_get::<i32>(1).is_ok());
assert_eq!(bad_table.raw_len(), 1);
Ok(())
}
#[test]
fn test_table_fmt() -> Result<()> {
let lua = Lua::new();
let table = lua
.load(
r#"
local t = {1, 2, 3, a = 5, b = { 6 }}
t["special-<chars>"] = 10
t[9.2] = 9.2
t[1.99] = 1.99
t[true] = true
t[false] = false
return t
"#,
)
.eval::<Table>()?;
assert!(format!("{table:?}").starts_with("Table(Ref("));
assert_eq!(
format!("{table:#?}"),
"{\n [false] = false,\n [true] = true,\n [1] = 1,\n [1.99] = 1.99,\n [2] = 2,\n [3] = 3,\n [9.2] = 9.2,\n a = 5,\n b = {\n 6,\n },\n [\"special-<chars>\"] = 10,\n}"
);
let table2 = lua.create_table_from([("1", "first"), ("2", "second")])?;
assert_eq!(
format!("{table2:#?}"),
"{\n [\"1\"] = \"first\",\n [\"2\"] = \"second\",\n}"
);
Ok(())
}
#[test]
fn test_table_object_like() -> Result<()> {
let lua = Lua::new();
lua.load(
r#"
table = {a = 1, b = 2}
setmetatable(table, {
__call = function(t, key)
return "call_"..t[key]
end,
__tostring = function()
return "table object"
end
})
function table.func(key)
return "func_"..key
end
function table:method(key)
return "method_"..self[key]
end
"#,
)
.exec()?;
let table: Table = lua.globals().get("table")?;
<Table as ObjectLike>::set(&table, "c", 3)?;
assert_eq!(<Table as ObjectLike>::get::<i32>(&table, "c")?, 3);
assert_eq!(table.call::<String>("b")?, "call_2");
assert_eq!(table.call_function::<String>("func", "a")?, "func_a");
assert_eq!(table.call_method::<String>("method", "a")?, "method_1");
assert_eq!(table.to_string()?, "table object");
match table.call_method::<()>("non_existent", ()) {
Err(Error::RuntimeError(err)) => {
assert!(err.contains("attempt to call a nil value (function 'non_existent')"))
}
r => panic!("expected RuntimeError, got {r:?}"),
}
let table2 = lua.create_table()?;
assert!(matches!(table2.call::<()>(()), Err(Error::RuntimeError(_))));
Ok(())
}
#[test]
fn test_table_get_path() -> Result<()> {
let lua = Lua::new();
let table = lua
.load(
r#"
{
a = {
b = {
c = "hello",
d = 42
},
[1] = "first",
["special key"] = "special value"
},
abc = "top level",
x = {},
["🚀"] = "rocket",
[1] = {
["nested-key"] = {
[42] = {
final = "hello!",
},
},
["key\"with\"quotes"] = "value1",
["key'with'quotes"] = "value2",
["key\\with\\backslashes"] = "value3",
[-2] = "negative index",
},
}
"#,
)
.eval::<Table>()?;
assert_eq!(table.get_path::<String>(".a.b.c")?, "hello");
assert_eq!(table.get_path::<String>("a.b.c")?, "hello");
assert_eq!(table.get_path::<i32>("a.b.d")?, 42);
assert_eq!(table.get_path::<String>("abc")?, "top level");
assert_eq!(table.get_path::<String>("a[1]")?, "first");
assert_eq!(table.get_path::<String>("[1][-2]")?, "negative index");
assert_eq!(table.get_path::<String>("a[\"special key\"]")?, "special value");
assert_eq!(table.get_path::<String>("a['special key']")?, "special value");
assert_eq!(table.get_path::<String>(r#"[1]["key\"with\"quotes"]"#)?, "value1");
assert_eq!(table.get_path::<String>(r#"[1]['key"with"quotes']"#)?, "value1");
assert_eq!(table.get_path::<String>(r#"[1]['key\'with\'quotes']"#)?, "value2");
assert_eq!(
table.get_path::<String>(r#"[1]["key\\with\\backslashes"]"#)?,
"value3"
);
assert_eq!(table.get_path::<String>("[1].nested-key[42].final")?, "hello!");
assert_eq!(table.get_path::<String>("🚀")?, "rocket");
assert_eq!(table.get_path::<Table>("")?, table);
assert_eq!(table.get_path::<String>("a?.b.c")?, "hello");
assert_eq!(table.get_path::<Value>("x.y?.z")?, Value::Nil);
assert_eq!(table.get_path::<Value>("[1].nested-key[43]?.final")?, Value::Nil);
assert_eq!(table.get_path::<String>(" .a [\"b\"] .c ")?, "hello");
let err = table.get_path::<String>("abc.c").unwrap_err().to_string();
assert_eq!(err, "runtime error: attempt to index a string value with key 'c'");
Ok(())
}
#[test]
fn test_table_invalid_keys() -> Result<()> {
let lua = Lua::new();
let table = lua.create_table()?;
for key in [
Value::Nil,
Value::Number(f64::NAN),
#[cfg(all(feature = "luau", not(feature = "luau-vector4")))]
Value::Vector(mlua::Vector::new(f32::NAN, 0., 0.)),
] {
assert!(matches!(
table.raw_set(key.clone(), 1),
Err(Error::RuntimeError(_))
));
assert!(matches!(
lua.create_table_from([(key, 1)]),
Err(Error::RuntimeError(_))
));
}
Ok(())
}