use lua_rs_runtime::{Lua, LuaVersion};
fn run(version: LuaVersion, code: &str) -> Result<String, String> {
let lua = Lua::new_versioned(version);
let wrapper = format!(
"local f, e = load([==[\n{code}\n]==])\n\
if not f then return 'E\\0' .. e end\n\
local ok, r = pcall(f)\n\
if not ok then return 'E\\0' .. tostring(r) end\n\
return 'V\\0' .. tostring(r)"
);
let out: String = lua
.load(&wrapper)
.eval()
.unwrap_or_else(|e| panic!("harness failure for `{code}`: {e:?}"));
if let Some(v) = out.strip_prefix("V\0") {
Ok(v.to_string())
} else if let Some(e) = out.strip_prefix("E\0") {
Err(e.to_string())
} else {
panic!("harness: unexpected output `{out}` for `{code}`")
}
}
fn eq(version: LuaVersion, code: &str, expected: &str) {
match run(version, code) {
Ok(got) => assert_eq!(got, expected, "code: {code}"),
Err(e) => panic!("code `{code}` errored (`{e}`), expected `{expected}`"),
}
}
fn err_contains(version: LuaVersion, code: &str, needle: &str) {
match run(version, code) {
Ok(got) => panic!("code `{code}` returned `{got}`, expected error containing `{needle}`"),
Err(e) => assert!(e.contains(needle), "code `{code}` error `{e}` lacked `{needle}`"),
}
}
#[test]
fn v55_global_enforcement() {
eq(LuaVersion::V55, "y = 3; return y", "3");
eq(LuaVersion::V55, "global a; a = 5; return a", "5");
err_contains(LuaVersion::V55, "global a; a = 1; zz = 2", "variable 'zz' not declared");
err_contains(
LuaVersion::V55,
"global f; local function g() return nope end return g()",
"variable 'nope' not declared",
);
}
#[test]
fn v55_global_block_scoped() {
eq(LuaVersion::V55, "do global Y; Y = 1 end; return Y", "1");
eq(LuaVersion::V55, "if true then global Z; Z = 1 end; w = 2; return w", "2");
}
#[test]
fn v55_global_initializer_stored() {
eq(LuaVersion::V55, "do global x = 7 end; return x", "7");
eq(LuaVersion::V55, "do global a, b = 10, 20 end; return a + b", "30");
}
#[test]
fn v55_const_global_rejects_assignment() {
err_contains(
LuaVersion::V55,
"global x <const> = 1; x = 2",
"attempt to assign to const variable 'x'",
);
}
#[test]
fn v55_global_is_a_valid_identifier() {
eq(LuaVersion::V55, "local global = 5; return global", "5");
eq(LuaVersion::V55, "global = 7; return global", "7");
}
#[test]
fn v55_for_control_var_readonly() {
err_contains(LuaVersion::V55, "for i = 1, 3 do i = 10 end", "attempt to assign to const variable 'i'");
err_contains(
LuaVersion::V55,
"for k, v in pairs({1, 2}) do k = 10 end",
"attempt to assign to const variable 'k'",
);
eq(LuaVersion::V55, "local s = 0; for i = 1, 3 do s = s + i end; return s", "6");
eq(LuaVersion::V55, "for k, v in pairs({7}) do v = 9 end; return 'ok'", "ok");
}
#[test]
fn v55_float_tostring_round_trips() {
eq(LuaVersion::V55, "return 1/3", "0.33333333333333331");
eq(LuaVersion::V55, "return 3.14", "3.14");
eq(LuaVersion::V55, "return 0.1 + 0.2", "0.30000000000000004");
eq(LuaVersion::V55, "return 2^53", "9007199254740992.0");
eq(LuaVersion::V55, "return 1e16", "1e+16");
eq(LuaVersion::V55, "return 1.0", "1.0");
}
#[test]
fn v55_table_create_present() {
eq(LuaVersion::V55, "return type(table.create)", "function");
}
#[test]
fn v53_bit32_surface() {
eq(LuaVersion::V53, "return bit32.band(6, 3)", "2");
eq(LuaVersion::V53, "return bit32.btest(6, 3)", "true");
eq(LuaVersion::V53, "return bit32.extract(0xF0, 4, 4)", "15");
eq(LuaVersion::V53, "return bit32.replace(0, 5, 0, 4)", "5");
eq(LuaVersion::V53, "return bit32.arshift(-8, 1)", "4294967292");
eq(LuaVersion::V53, "return bit32.lrotate(1, 1)", "2");
eq(LuaVersion::V53, "return bit32.rrotate(1, 1)", "2147483648");
}
#[test]
fn v53_string_coercion_is_float() {
eq(LuaVersion::V53, "return math.type('0x10' + 0)", "float");
eq(LuaVersion::V54, "return math.type('0x10' + 0)", "integer");
}
#[test]
fn v53_removed_builtins_absent() {
eq(LuaVersion::V53, "return type(warn)", "nil");
eq(LuaVersion::V53, "return type(coroutine.close)", "nil");
eq(LuaVersion::V53, "return type(bit32)", "table");
eq(LuaVersion::V53, "return type(table.create)", "nil");
eq(LuaVersion::V53, "return type(math.type)", "function");
}
#[test]
fn v53_rejects_attribute_syntax() {
err_contains(LuaVersion::V53, "local x <const> = 1; return x", "unexpected symbol");
}
#[test]
fn v54_unchanged() {
eq(LuaVersion::V54, "return 1/3", "0.33333333333333"); eq(LuaVersion::V54, "return 2^53", "9.007199254741e+15");
eq(LuaVersion::V54, "return 3.14", "3.14");
eq(LuaVersion::V54, "return type(warn)", "function");
eq(LuaVersion::V54, "return type(coroutine.close)", "function");
eq(LuaVersion::V54, "return type(bit32)", "nil");
eq(LuaVersion::V54, "local x <const> = 42; return x", "42");
err_contains(LuaVersion::V54, "local x <const> = 1; x = 2", "attempt to assign to const variable 'x'");
eq(LuaVersion::V54, "local global = 8; return global", "8");
eq(LuaVersion::V54, "for i = 1, 1 do i = 10 end; return 'ok'", "ok");
}