use omnilua::{Lua, LuaVersion, Value};
const ALL: [LuaVersion; 5] = [
LuaVersion::V51,
LuaVersion::V52,
LuaVersion::V53,
LuaVersion::V54,
LuaVersion::V55,
];
fn eval_str(version: LuaVersion, code: &str) -> Vec<u8> {
let lua = Lua::new_versioned(version);
match lua.load(code).eval::<Value>() {
Ok(Value::String(s)) => s
.as_bytes()
.unwrap_or_else(|e| panic!("string bytes under {version:?} for `{code}`: {e:?}"))
.to_vec(),
Ok(other) => panic!("`{code}` under {version:?} returned {other:?}, expected a string"),
Err(e) => panic!("eval of `{code}` failed under {version:?}: {e:?}"),
}
}
fn eval_int(version: LuaVersion, code: &str) -> i64 {
let lua = Lua::new_versioned(version);
match lua.load(code).eval::<Value>() {
Ok(Value::Integer(i)) => i,
Ok(Value::Number(n)) if n.fract() == 0.0 => n as i64,
Ok(other) => panic!("`{code}` under {version:?} returned {other:?}, expected an integer"),
Err(e) => panic!("eval of `{code}` failed under {version:?}: {e:?}"),
}
}
fn eval_value(version: LuaVersion, code: &str) -> Value {
let lua = Lua::new_versioned(version);
lua.load(code)
.eval::<Value>()
.unwrap_or_else(|e| panic!("eval of `{code}` failed under {version:?}: {e:?}"))
}
fn assert_true(version: LuaVersion, code: &str) {
match eval_value(version, code) {
Value::Boolean(true) => {}
other => panic!("`{code}` under {version:?} returned {other:?}, expected true"),
}
}
#[test]
fn config_string_is_version_exact() {
assert_eq!(eval_str(LuaVersion::V51, "return package.config"), b"/\n;\n?\n!\n-");
assert_eq!(eval_int(LuaVersion::V51, "return #package.config"), 9);
for v in [LuaVersion::V52, LuaVersion::V53, LuaVersion::V54, LuaVersion::V55] {
assert_eq!(eval_str(v, "return package.config"), b"/\n;\n?\n!\n-\n", "{v:?}");
assert_eq!(eval_int(v, "return #package.config"), 10, "{v:?}");
}
for v in ALL {
assert_eq!(eval_str(v, "return package.config:sub(1,1)"), b"/", "{v:?}");
}
}
#[test]
fn require_preload_returns_module_and_caches() {
for v in ALL {
let probe = "\
package.preload['mymod'] = function() return {answer = 42} end \
local m = require('mymod') \
return m.answer == 42 \
and m == package.loaded['mymod'] \
and type(package.preload['mymod']) == 'function'";
assert_true(v, probe);
}
}
#[test]
fn require_caches_loader_runs_once() {
for v in ALL {
let probe = "\
local n = 0 \
package.preload['m'] = function() n = n + 1; return {id = n} end \
local a = require('m') \
local b = require('m') \
return a == b and a.id == 1 and n == 1";
assert_true(v, probe);
}
}
#[test]
fn require_preseeded_loaded_short_circuits_searchers() {
for v in ALL {
assert_eq!(
eval_str(v, "package.loaded['pre'] = 'CACHED'; return require('pre')"),
b"CACHED",
"{v:?}"
);
}
}
#[test]
fn require_second_value_is_loader_data_only_5_4_plus() {
let probe = "\
package.preload['m'] = function() return 'MOD' end \
local a, b = require('m') \
return b";
for v in [LuaVersion::V51, LuaVersion::V52, LuaVersion::V53] {
assert!(
matches!(eval_value(v, probe), Value::Nil),
"{v:?}: require's 2nd value must be nil pre-5.4, got {:?}",
eval_value(v, probe)
);
}
for v in [LuaVersion::V54, LuaVersion::V55] {
assert_eq!(eval_str(v, probe), b":preload:", "{v:?}");
}
}
#[test]
fn preload_loader_argument_count_is_one_on_5_1_two_after() {
let probe = "\
package.preload['m'] = function(...) return select('#', ...) end \
return (require('m'))";
assert_eq!(eval_int(LuaVersion::V51, probe), 1, "5.1 passes the name only");
for v in [LuaVersion::V52, LuaVersion::V53, LuaVersion::V54, LuaVersion::V55] {
assert_eq!(eval_int(v, probe), 2, "{v:?} passes name + loader data");
assert_eq!(
eval_str(
v,
"package.preload['m'] = function(n) return n end; return (require('m'))"
),
b"m",
"{v:?}"
);
}
assert_eq!(
eval_str(
LuaVersion::V51,
"package.preload['m'] = function(n) return n end; return (require('m'))"
),
b"m"
);
}
#[test]
fn require_missing_module_error_is_full_four_searcher_trace() {
let setup_and_require = "\
package.path = './?.lua'; package.cpath = './?.so' \
local ok, err = pcall(require, 'no.such.mod') \
assert(not ok) \
return err";
let expected: &[u8] = b"module 'no.such.mod' not found:\n\
\tno field package.preload['no.such.mod']\n\
\tno file './no/such/mod.lua'\n\
\tno file './no/such/mod.so'\n\
\tno file './no.so'";
for v in ALL {
assert_eq!(eval_str(v, setup_and_require), expected, "{v:?}");
}
}
#[test]
fn require_missing_dotless_module_trace_has_no_croot_line() {
let probe = "\
package.path = './?.lua'; package.cpath = './?.so' \
local ok, err = pcall(require, 'solo') \
assert(not ok) \
return err";
let expected: &[u8] = b"module 'solo' not found:\n\
\tno field package.preload['solo']\n\
\tno file './solo.lua'\n\
\tno file './solo.so'";
for v in ALL {
assert_eq!(eval_str(v, probe), expected, "{v:?}");
}
}
#[test]
fn require_preload_not_found_line_wording() {
for v in ALL {
let probe = "\
local ok, err = pcall(require, 'absent_xyz') \
assert(not ok) \
return err:match(\"no field package%.preload%['absent_xyz'%]\") ~= nil";
assert_true(v, probe);
let probe2 = "\
local ok, err = pcall(require, 'absent_xyz') \
return err:sub(1, 30) == \"module 'absent_xyz' not found:\"";
assert_true(v, probe2);
}
}
#[test]
fn searchpath_absent_on_5_1_present_after() {
assert_eq!(eval_str(LuaVersion::V51, "return type(package.searchpath)"), b"nil");
for v in [LuaVersion::V52, LuaVersion::V53, LuaVersion::V54, LuaVersion::V55] {
assert_eq!(eval_str(v, "return type(package.searchpath)"), b"function", "{v:?}");
}
}
#[test]
fn searchpath_failure_returns_nil_not_false() {
for v in [LuaVersion::V52, LuaVersion::V53, LuaVersion::V54, LuaVersion::V55] {
assert!(
matches!(eval_value(v, "return (package.searchpath('x', 'noq'))"), Value::Nil),
"{v:?}: searchpath fail value must be nil, got {:?}",
eval_value(v, "return (package.searchpath('x', 'noq'))")
);
}
let probe = "local _, err = package.searchpath('x', 'noq'); return err";
for v in [LuaVersion::V52, LuaVersion::V53] {
assert_eq!(eval_str(v, probe), b"\n\tno file 'noq'", "{v:?}");
}
for v in [LuaVersion::V54, LuaVersion::V55] {
assert_eq!(eval_str(v, probe), b"no file 'noq'", "{v:?}");
}
}
#[test]
fn searchpath_error_message_leading_separator_is_5_2_5_3_only() {
let probe = "\
local _, err = package.searchpath('a.b.c', './?.lua;/x/?.lua') \
return err";
for v in [LuaVersion::V52, LuaVersion::V53] {
assert_eq!(
eval_str(v, probe),
b"\n\tno file './a/b/c.lua'\n\tno file '/x/a/b/c.lua'",
"{v:?}"
);
}
for v in [LuaVersion::V54, LuaVersion::V55] {
assert_eq!(
eval_str(v, probe),
b"no file './a/b/c.lua'\n\tno file '/x/a/b/c.lua'",
"{v:?}"
);
}
}
#[test]
fn searchpath_separator_and_rep_logic() {
for v in [LuaVersion::V52, LuaVersion::V53, LuaVersion::V54, LuaVersion::V55] {
let probe = "\
local _, err = package.searchpath('a.b', './?.lua') \
return err:match(\"no file './a/b%.lua'\") ~= nil";
assert_true(v, probe);
let probe2 = "\
local _, err = package.searchpath('a.b', './?.lua', '') \
return err:match(\"no file './a%.b%.lua'\") ~= nil";
assert_true(v, probe2);
let probe3 = "\
local _, err = package.searchpath('a/b', './?.lua', '/', '_') \
return err:match(\"no file './a_b%.lua'\") ~= nil";
assert_true(v, probe3);
}
}
#[test]
fn searcher_list_name_matrix() {
assert_eq!(eval_str(LuaVersion::V51, "return type(package.loaders)"), b"table");
assert_eq!(eval_str(LuaVersion::V51, "return type(package.searchers)"), b"nil");
assert_eq!(eval_str(LuaVersion::V52, "return type(package.loaders)"), b"table");
assert_eq!(eval_str(LuaVersion::V52, "return type(package.searchers)"), b"table");
assert_true(LuaVersion::V52, "return package.loaders == package.searchers");
for v in [LuaVersion::V53, LuaVersion::V54, LuaVersion::V55] {
assert_eq!(eval_str(v, "return type(package.searchers)"), b"table", "{v:?}");
assert_eq!(eval_str(v, "return type(package.loaders)"), b"nil", "{v:?}");
}
for v in ALL {
let list = if matches!(v, LuaVersion::V51) { "loaders" } else { "searchers" };
let probe = format!("return type(package.{list}[1])");
assert_eq!(eval_str(v, &probe), b"function", "{v:?}");
}
}
#[test]
fn module_and_seeall_roster_matrix() {
for v in [LuaVersion::V51, LuaVersion::V52] {
assert_eq!(eval_str(v, "return type(module)"), b"function", "{v:?}");
assert_eq!(eval_str(v, "return type(package.seeall)"), b"function", "{v:?}");
}
for v in [LuaVersion::V53, LuaVersion::V54, LuaVersion::V55] {
assert_eq!(eval_str(v, "return type(module)"), b"nil", "{v:?}");
assert_eq!(eval_str(v, "return type(package.seeall)"), b"nil", "{v:?}");
}
}
#[test]
fn module_seeall_creates_and_registers_module() {
let probe = "\
module('foo', package.seeall) \
return _NAME .. ',' .. tostring(_M == foo) .. ',' \
.. tostring(package.loaded.foo == foo) .. ',' .. tostring(print ~= nil)";
assert_eq!(eval_str(LuaVersion::V51, probe), b"foo,true,true,true");
}