use std::io::Result as IoResult; use std::result::Result as StdResult; use mlua::{Error, FromLua, IntoLua, Lua, MultiValue, NavigateError, Require, Result, TextRequirer, Value}; fn run_require(lua: &Lua, path: impl IntoLua) -> Result { lua.load(r#"return require(...)"#).call(path) } fn run_require_pcall(lua: &Lua, path: impl IntoLua) -> Result { lua.load(r#"return pcall(require, ...)"#).call(path) } #[track_caller] fn get_value(value: &Value, key: impl IntoLua) -> V { value.as_table().unwrap().get(key).unwrap() } #[track_caller] fn get_str(value: &Value, key: impl IntoLua) -> String { get_value(value, key) } #[test] fn test_require_errors() { let lua = Lua::new(); // RequireAbsolutePath let res = run_require(&lua, "/an/absolute/path"); assert!(res.is_err()); assert!( (res.unwrap_err().to_string()).contains("require path must start with a valid prefix: ./, ../, or @") ); // RequireUnprefixedPath let res = run_require(&lua, "an/unprefixed/path"); assert!(res.is_err()); assert!( (res.unwrap_err().to_string()).contains("require path must start with a valid prefix: ./, ../, or @") ); // Pass non-string to require let res = run_require(&lua, true); assert!(res.is_err()); assert!( (res.unwrap_err().to_string()) .contains("bad argument #1 to 'require' (string expected, got boolean)") ); // Require from loadstring let res = lua .load(r#"return loadstring("require('./a/relative/path')")()"#) .eval::(); assert!(res.is_err()); assert!((res.unwrap_err().to_string()).contains("require is not supported in this context")); // RequireAliasThatDoesNotExist let res = run_require(&lua, "@this.alias.does.not.exist"); assert!(res.is_err()); assert!((res.unwrap_err().to_string()).contains("@this.alias.does.not.exist is not a valid alias")); // IllegalAlias let res = run_require(&lua, "@"); assert!(res.is_err()); assert!((res.unwrap_err().to_string()).contains("@ is not a valid alias")); // Test throwing mlua::Error struct MyRequire(TextRequirer); impl Require for MyRequire { fn is_require_allowed(&self, chunk_name: &str) -> bool { self.0.is_require_allowed(chunk_name) } fn reset(&mut self, _chunk_name: &str) -> StdResult<(), NavigateError> { Err(Error::runtime("test error"))? } fn jump_to_alias(&mut self, path: &str) -> StdResult<(), NavigateError> { self.0.jump_to_alias(path) } fn to_parent(&mut self) -> StdResult<(), NavigateError> { self.0.to_parent() } fn to_child(&mut self, name: &str) -> StdResult<(), NavigateError> { self.0.to_child(name) } fn has_module(&self) -> bool { self.0.has_module() } fn cache_key(&self) -> String { self.0.cache_key() } fn has_config(&self) -> bool { self.0.has_config() } fn config(&self) -> IoResult> { self.0.config() } fn loader(&self, lua: &Lua) -> Result { self.0.loader(lua) } } let require = lua .create_require_function(MyRequire(TextRequirer::new())) .unwrap(); lua.globals().set("require", require).unwrap(); let res = lua.load(r#"return require('./a/relative/path')"#).exec(); assert!((res.unwrap_err().to_string()).contains("test error")); } #[test] fn test_require_without_config() { let lua = Lua::new(); // RequireSimpleRelativePath let res = run_require(&lua, "./tests/luau/require/without_config/dependency").unwrap(); assert_eq!("result from dependency", get_str(&res, 1)); // RequireSimpleRelativePathWithinPcall let res = run_require_pcall(&lua, "./tests/luau/require/without_config/dependency").unwrap(); assert!(res[0].as_boolean().unwrap()); assert_eq!("result from dependency", get_str(&res[1], 1)); // RequireRelativeToRequiringFile let res = run_require(&lua, "./tests/luau/require/without_config/module").unwrap(); assert_eq!("result from dependency", get_str(&res, 1)); assert_eq!("required into module", get_str(&res, 2)); // RequireLua let res = run_require(&lua, "./tests/luau/require/without_config/lua_dependency").unwrap(); assert_eq!("result from lua_dependency", get_str(&res, 1)); // RequireInitLuau let res = run_require(&lua, "./tests/luau/require/without_config/luau").unwrap(); assert_eq!("result from init.luau", get_str(&res, 1)); // RequireInitLua let res = run_require(&lua, "./tests/luau/require/without_config/lua").unwrap(); assert_eq!("result from init.lua", get_str(&res, 1)); // RequireSubmoduleUsingSelfIndirectly let res = run_require(&lua, "./tests/luau/require/without_config/nested_module_requirer").unwrap(); assert_eq!("result from submodule", get_str(&res, 1)); // RequireSubmoduleUsingSelfDirectly let res = run_require(&lua, "./tests/luau/require/without_config/nested").unwrap(); assert_eq!("result from submodule", get_str(&res, 1)); // CannotRequireInitLuauDirectly let res = run_require(&lua, "./tests/luau/require/without_config/nested/init"); assert!(res.is_err()); assert!((res.unwrap_err().to_string()).contains("could not resolve child component \"init\"")); // RequireNestedInits let res = run_require(&lua, "./tests/luau/require/without_config/nested_inits_requirer").unwrap(); assert_eq!("result from nested_inits/init", get_str(&res, 1)); assert_eq!("required into module", get_str(&res, 2)); // RequireWithFileAmbiguity let res = run_require( &lua, "./tests/luau/require/without_config/ambiguous_file_requirer", ); assert!(res.is_err()); assert!( (res.unwrap_err().to_string()) .contains("could not resolve child component \"dependency\" (ambiguous)") ); // RequireWithDirectoryAmbiguity let res = run_require( &lua, "./tests/luau/require/without_config/ambiguous_directory_requirer", ); assert!(res.is_err()); assert!( (res.unwrap_err().to_string()) .contains("could not resolve child component \"dependency\" (ambiguous)") ); // CheckCachedResult let res = run_require(&lua, "./tests/luau/require/without_config/validate_cache").unwrap(); assert!(res.is_table()); } fn test_require_with_config_inner(r#type: &str) { let lua = Lua::new(); let base_path = format!("./tests/luau/require/{type}"); // RequirePathWithAlias let res = run_require(&lua, format!("{base_path}/src/alias_requirer")).unwrap(); assert_eq!("result from dependency", get_str(&res, 1)); // RequirePathWithAlias (case-insensitive) let res2 = run_require(&lua, format!("{base_path}/src/alias_requirer_uc")).unwrap(); assert_eq!("result from dependency", get_str(&res2, 1)); assert_eq!(res.to_pointer(), res2.to_pointer()); // RequirePathWithParentAlias let res = run_require(&lua, format!("{base_path}/src/parent_alias_requirer")).unwrap(); assert_eq!("result from other_dependency", get_str(&res, 1)); // RequirePathWithAliasPointingToDirectory let res = run_require(&lua, format!("{base_path}/src/directory_alias_requirer")).unwrap(); assert_eq!("result from subdirectory_dependency", get_str(&res, 1)); // RequireChainedAliasesSuccess let res = run_require( &lua, format!("{base_path}/chained_aliases/subdirectory/successful_requirer"), ) .unwrap(); assert_eq!("result from inner_dependency", get_str(&get_value(&res, 1), 1)); assert_eq!("result from outer_dependency", get_str(&get_value(&res, 2), 1)); // RequireChainedAliasesFailureCyclic let res = run_require( &lua, format!("{base_path}/chained_aliases/subdirectory/failing_requirer_cyclic"), ); assert!(res.is_err()); let err_msg = "error requiring module \"@cyclicentry\": detected alias cycle (@cyclic1 -> @cyclic2 -> @cyclic3 -> @cyclic1)"; assert!(res.unwrap_err().to_string().contains(err_msg)); // RequireChainedAliasesFailureMissing let res = run_require( &lua, format!("{base_path}/chained_aliases/subdirectory/failing_requirer_missing"), ); assert!(res.is_err()); let err_msg = "error requiring module \"@brokenchain\": @missing is not a valid alias"; assert!(res.unwrap_err().to_string().contains(err_msg)); } #[test] fn test_require_with_config() { test_require_with_config_inner("with_config"); } #[test] fn test_require_with_config_luau() { test_require_with_config_inner("with_config_luau"); } #[cfg(all(feature = "async", not(windows)))] #[tokio::test] async fn test_async_require() -> Result<()> { let lua = Lua::new(); let temp_dir = tempfile::tempdir().unwrap(); let temp_path = temp_dir.path().join("async_chunk.luau"); std::fs::write( &temp_path, r#" sleep_ms(10) return "result_after_async_sleep" "#, ) .unwrap(); lua.globals().set( "sleep_ms", lua.create_async_function(|_, ms: u64| async move { tokio::time::sleep(std::time::Duration::from_millis(ms)).await; Ok(()) })?, )?; lua.globals().set("tmp_dir", temp_dir.path().to_str().unwrap())?; lua.globals().set( "curr_dir_components", std::env::current_dir().unwrap().components().count(), )?; lua.load( r#" local path_to_root = string.rep("/..", curr_dir_components - 1) local result = require(`.{path_to_root}{tmp_dir}/async_chunk`) assert(result == "result_after_async_sleep") "#, ) .exec_async() .await }