package lua551runner import std.unittest.* // cjlint-ignore G.PKG.01 import std.fs.* // cjlint-ignore G.PKG.01 // cjlint-ignore G.NAM.05 let SCRIPTS_DIR = "./test/scripts" // cjlint-ignore G.NAM.05 let GENERATED_SCRIPTS_DIR = "./test/generated_scripts" func scriptPath(name: String): String { SCRIPTS_DIR + "/" + name } func generatedScriptPath(name: String): String { GENERATED_SCRIPTS_DIR + "/" + name } func ensureGeneratedScriptsDir(): Unit { let dir = Path(GENERATED_SCRIPTS_DIR) if (!exists(dir)) { Directory.create(dir, recursive: true) } } // ==================== // 1. 基础运行与参数测试 // ==================== @Test func testRunScript(): Unit { let runner = LuaRunner() let path = scriptPath("simple_return.lua") let res = runner.runScript(path, "") @Expect(res, "hello") } @Test func testRunScriptWithArg(): Unit { let runner = LuaRunner() let path = scriptPath("args_test.lua") let res = runner.runScript(path, "world") @Expect(res, "world") } // ==================== // 2. 异常处理测试 // ==================== @Test func testRunScriptSyntaxError(): Unit { let runner = LuaRunner() let path = scriptPath("syntax_error.lua") try { runner.runScript(path, "") fail("Should throw syntax error") } catch (e: LuaError) { @Expect(e.code, 5001) } } @Test func testRunScriptRuntimeError(): Unit { let runner = LuaRunner() let path = scriptPath("runtime_error.lua") try { runner.runScript(path, "") fail("Should throw runtime error") } catch (e: LuaError) { @Expect(e.code, 5003) } } // ==================== // 3. 库加载测试 // ==================== @Test func testLoadLib(): Unit { let runner = LuaRunner() let path = scriptPath("mylib.lua") runner.load(path, "mylib") } @Test func testLoadLibFileNotFound(): Unit { let runner = LuaRunner() try { runner.load("/no/such/file.lua", "badlib") fail("Should throw file not found error") } catch (e: LuaError) { @Expect(e.code, 5001) } } @Test func testUnloadLib(): Unit { let runner = LuaRunner() let path = scriptPath("mylib.lua") runner.load(path, "mylib") runner.unload("mylib") } @Test func testUnloadLibNotFound(): Unit { let runner = LuaRunner() try { runner.unload("nosuchlib") fail("Should throw exception") } catch (e: LuaError) { @Expect(e.code, 5005) } } // ==================== // 4. 状态管理与数据交互 // ==================== @Test func testCleanup(): Unit { let runner = LuaRunner() let setPath = scriptPath("set_global.lua") let getPath = scriptPath("get_global.lua") runner.runScript(setPath, "") runner.clear() let res = runner.runScript(getPath, "") @Expect(res, "nil") } @Test func testCrossCallDataPassing(): Unit { let runner = LuaRunner() let setPath = scriptPath("cross_call_set.lua") let getPath = scriptPath("cross_call_get.lua") runner.runScript(setPath, "") let res = runner.runScript(getPath, "") @Expect(res, "1,2,3") } @Test func testPipelineDocumentedFlow(): Unit { let runner = LuaRunner() runner.load(scriptPath("pipeline_add_suffix.lua"), "add_suffix") .load(scriptPath("pipeline_to_upper.lua"), "to_upper") .load(scriptPath("pipeline_add_prefix.lua"), "add_prefix") let initialData = runner.runScript(scriptPath("pipeline_generate_data.lua"), "hello") @Expect(initialData, "hello") let afterA = runner.runScript("", "") @Expect(afterA, "[PREFIX] hello") let afterB = runner.runScript("", "") @Expect(afterB, "[PREFIX] HELLO") let finalResult = runner.runScript("", "") @Expect(finalResult, "[PREFIX] HELLO [SUFFIX]") } @Test func testPipelineStepByStepResults(): Unit { let runner = LuaRunner() runner.load(scriptPath("pipeline_add_suffix.lua"), "add_suffix") .load(scriptPath("pipeline_to_upper.lua"), "to_upper") .load(scriptPath("pipeline_add_prefix.lua"), "add_prefix") let initialData = runner.runScript(scriptPath("pipeline_generate_data.lua"), "hello") @Expect(initialData, "hello") let afterPrefix = runner.runScript("", "") @Expect(afterPrefix, "[PREFIX] hello") let afterUpper = runner.runScript("", "") @Expect(afterUpper, "[PREFIX] HELLO") let afterSuffix = runner.runScript("", "") @Expect(afterSuffix, "[PREFIX] HELLO [SUFFIX]") } @Test func testLoadLibOverflow(): Unit { ensureGeneratedScriptsDir() let runner = LuaRunner() for (i in 0..20) { let name = "overflow_lib_${i}" let path = generatedScriptPath(name + ".lua") File.writeTo(path, ("return \"" + name + "\"").toArray()) runner.load(path, name) } let overflowName = "overflow_lib_20" let overflowPath = generatedScriptPath(overflowName + ".lua") File.writeTo(overflowPath, "return \"overflow\"".toArray()) try { runner.load(overflowPath, overflowName) fail("Should throw overflow error") } catch (e: LuaError) { @Expect(e.code, 5008) } } @Test func testPackagePathConfig(): Unit { let runner = LuaRunner(pkgpath: SCRIPTS_DIR + "/pkgmods/?.lua") let res = runner.runScript(scriptPath("pkgpath_require_mylib.lua"), "") @Expect(res, SCRIPTS_DIR + "/pkgmods/mylib.lua") } @Test func testPackagePathInitLuaPattern(): Unit { let runner = LuaRunner(pkgpath: SCRIPTS_DIR + "/pkgmods_init/?/init.lua") let res = runner.runScript(scriptPath("pkgpath_require_nested.lua"), "") @Expect(res, SCRIPTS_DIR + "/pkgmods_init/nestedpkg/init.lua") } // ==================== // 5. I/O 重定向测试 // ==================== @Test func testRedirectPrint(): Unit { let runner = LuaRunner(pathio: "/tmp") let path = scriptPath("print_test.lua") runner.runScript(path, "") } @Test func testRedirectIoWrite(): Unit { let runner = LuaRunner(pathio: "/tmp") let path = scriptPath("io_write_test.lua") runner.runScript(path, "") } @Test func testRedirectIoRead(): Unit { // 1. 准备输入文件 let inputFile = "/tmp/input" // [修正点]:移除了不存在的 option 参数。 // File.writeTo 默认行为即为 CreateOrTruncate: // 文件存在会截断(覆盖),不存在则创建。 File.writeTo(inputFile, "MockInputData".toArray()) // 2. 运行测试 let runner = LuaRunner(pathio: "/tmp") let path = scriptPath("io_read_test.lua") let res = runner.runScript(path, "") @Expect(res, "MockInputData") } // ==================== // 6. doString 测试 // ==================== @Test func testDoStringSimple(): Unit { let runner = LuaRunner() let res = runner.doString("return 1 + 1") @Expect(res, "2") } @Test func testDoStringWithGlobal(): Unit { let runner = LuaRunner() let res = runner.doString("g = 42; return g") @Expect(res, "42") } @Test func testDoStringNoReturn(): Unit { let runner = LuaRunner() try { runner.doString("x = 10") fail("Should throw bad ret error") } catch (e: LuaError) { @Expect(e.code, 5004) } } @Test func testDoStringSyntaxError(): Unit { let runner = LuaRunner() try { runner.doString("if true then") fail("Should throw syntax error") } catch (e: LuaError) { @Expect(e.code, 5011) } } @Test func testDoStringNilReturn(): Unit { let runner = LuaRunner() try { runner.doString("return nil") fail("Should throw bad ret error") } catch (e: LuaError) { @Expect(e.code, 5004) } } // ==================== // 7. 错误码与回调 // ==================== @Test func testErrorCodesMapping(): Unit { @Expect(LuaError.getErrorMessage(5001), "File load error (path, permission, or compile-time syntax issue)") @Expect(LuaError.getErrorMessage(5002), "Lua state machine initialization failed or corrupted") @Expect(LuaError.getErrorMessage(5003), "Script internal error, check result for details") @Expect(LuaError.getErrorMessage(5004), "Script return value type not supported") @Expect(LuaError.getErrorMessage(5013), "Function not found") @Expect(LuaError.getErrorMessage(5016), "Result type not supported for requested conversion") @Expect(LuaError.getErrorMessage(9999), "Unknown error") } @Test func testDefaultIOCallback(): Unit { let result = unsafe { defaultIO() } @Expect(result, 0) } // ==================== // 8. loadFunction / callFunction 预加载测试 // ==================== @Test func testLoadFunctionAndCall(): Unit { let runner = LuaRunner() runner.loadFunction(scriptPath("func_add.lua"), "add") let res = runner.callFunction("add", "hello") // 闭包带计数器,第一次调用应返回 call #1 @Expect(res, "result: hello (call #1)") } @Test func testCallFunctionMultipleTimes(): Unit { let runner = LuaRunner() runner.loadFunction(scriptPath("func_multi_call.lua"), "multi") let r1 = runner.callFunction("multi", "") @Expect(r1, "multi:init-done") let r2 = runner.callFunction("multi", "") @Expect(r2, "multi:init-done") let r3 = runner.callFunction("multi", "") @Expect(r3, "multi:init-done") let r4 = runner.callFunction("multi", "") @Expect(r4, "multi:init-done") } @Test func testCallFunctionCallCount(): Unit { let runner = LuaRunner() runner.loadFunction(scriptPath("func_add.lua"), "add") @Expect(runner.callFunction("add", "a"), "result: a (call #1)") @Expect(runner.callFunction("add", "b"), "result: b (call #2)") @Expect(runner.callFunction("add", "c"), "result: c (call #3)") } @Test func testCallFunctionNotFound(): Unit { let runner = LuaRunner() try { runner.callFunction("nonexistent", "") fail("Should throw function not found error") } catch (e: LuaError) { @Expect(e.code, 5013) } } @Test func testLoadFunctionFileNotFound(): Unit { let runner = LuaRunner() try { runner.loadFunction("/no/such/file.lua", "bad") fail("Should throw file not found error") } catch (e: LuaError) { @Expect(e.code, 5001) } } @Test func testLoadFunctionNotAFunction(): Unit { let runner = LuaRunner() try { runner.loadFunction(scriptPath("func_not_a_function.lua"), "bad") fail("Should throw function not valid error") } catch (e: LuaError) { @Expect(e.code, 5015) } } @Test func testLoadFunctionDuplicate(): Unit { let runner = LuaRunner() runner.loadFunction(scriptPath("func_add.lua"), "dup") try { runner.loadFunction(scriptPath("func_add.lua"), "dup") fail("Should throw on duplicate name") } catch (e: LuaError) { @Expect(e.code, 5013) } } @Test func testCallFunctionRuntimeError(): Unit { let runner = LuaRunner() runner.loadFunction(scriptPath("func_runtime_error.lua"), "bad") try { runner.callFunction("bad", "") fail("Should throw runtime error") } catch (e: LuaError) { @Expect(e.code, 5003) } } @Test func testCallFunctionWithUnloadFunction(): Unit { let runner = LuaRunner() runner.loadFunction(scriptPath("func_add.lua"), "add") @Expect(runner.callFunction("add", "before"), "result: before (call #1)") runner.unloadFunction("add") try { runner.callFunction("add", "") fail("Should throw function not found after unloadFunction") } catch (e: LuaError) { @Expect(e.code, 5013) } // 卸载后可重新加载同名函数 runner.loadFunction(scriptPath("func_add.lua"), "add") @Expect(runner.callFunction("add", "after"), "result: after (call #1)") } @Test func testLoadFunctionAndPipelineNoInterference(): Unit { let runner = LuaRunner() // 预加载函数(用独立 func 脚本) runner.loadFunction(scriptPath("func_add.lua"), "add") @Expect(runner.callFunction("add", "x"), "result: x (call #1)") // 管道模式(用 pipeline 脚本,走 pkgs 栈) runner.load(scriptPath("pipeline_add_suffix.lua"), "add_suffix") .load(scriptPath("pipeline_to_upper.lua"), "to_upper") .load(scriptPath("pipeline_add_prefix.lua"), "add_prefix") let initialData = runner.runScript(scriptPath("pipeline_generate_data.lua"), "hello") @Expect(initialData, "hello") @Expect(runner.runScript("", ""), "[PREFIX] hello") @Expect(runner.runScript("", ""), "[PREFIX] HELLO") @Expect(runner.runScript("", ""), "[PREFIX] HELLO [SUFFIX]") // 管道结束后,预加载函数仍可调用,且计数器继续 @Expect(runner.callFunction("add", "y"), "result: y (call #2)") } @Test func testLoadFunctionAndCleanCoexists(): Unit { let runner = LuaRunner() runner.loadFunction(scriptPath("func_add.lua"), "add") @Expect(runner.callFunction("add", "x"), "result: x (call #1)") runner.clear() try { runner.callFunction("add", "") fail("Should throw after clean") } catch (e: LuaError) { @Expect(e.code, 5013) } } @Test func testLoadFunctionOverflow(): Unit { ensureGeneratedScriptsDir() let runner = LuaRunner() for (i in 0..20) { let name = "overflow_func_${i}" let path = generatedScriptPath(name + ".lua") File.writeTo(path, ("return function() return \"" + name + "\" end").toArray()) try { runner.loadFunction(path, name) } catch (e: LuaError) { @Expect(e.code, 5014) break } } } // ==================== // 9. typed interop 测试(v0.2.2) // ==================== @Test func testTypedCallInt(): Unit { let runner = LuaRunner() runner.loadFunction(scriptPath("func_typed_add.lua"), "inc") let res = runner.callFunctionInt("inc", 41) @Expect(res, 42) // 字符串形式同步可用(向后兼容) @Expect(runner.result(), "42") } @Test func testTypedCallNum(): Unit { let runner = LuaRunner() runner.loadFunction(scriptPath("func_typed_mul.lua"), "mul") let res = runner.callFunctionNum("mul", 4.0) @Expect(res, 10.0) } @Test func testTypedCallBool(): Unit { let runner = LuaRunner() runner.loadFunction(scriptPath("func_typed_not.lua"), "lnot") @Expect(runner.callFunctionBool("lnot", true), false) @Expect(runner.callFunctionBool("lnot", false), true) } @Test func testTypedRoundTrip(): Unit { let runner = LuaRunner() runner.loadFunction(scriptPath("func_typed_echo.lua"), "echo") // int 往返保真 @Expect(runner.callFunctionInt("echo", -123456789), -123456789) @Expect(runner.callFunctionInt("echo", 9007199254740993), 9007199254740993) // num 往返保真 @Expect(runner.callFunctionNum("echo", 3.14159), 3.14159) // bool 往返保真 @Expect(runner.callFunctionBool("echo", true), true) // string 走原 API,类型标记同步为 STR(4) let s = runner.callFunction("echo", "hello typed") @Expect(s, "hello typed") @Expect(runner.resultType(), 4) } @Test func testTypedTypeMismatch(): Unit { let runner = LuaRunner() runner.loadFunction(scriptPath("func_typed_void.lua"), "forty_two") // 函数返回 int 42,用 Num 方式可读(int→num 兼容) @Expect(runner.callFunctionNum("forty_two", 0.0), 42.0) // 返回 string 的函数不能用 Int 读 → 5016 runner.loadFunction(scriptPath("func_add.lua"), "add") try { runner.callFunctionInt("add", 1) fail("Should throw result type error") } catch (e: LuaError) { @Expect(e.code, 5016) } // 返回 nil 的函数不能用 Int 读 → 5016 runner.loadFunction(scriptPath("func_typed_nil.lua"), "nilret") try { runner.callFunctionInt("nilret", 1) fail("Should throw on nil result") } catch (e: LuaError) { @Expect(e.code, 5016) } } @Test func testTypedRunVariants(): Unit { ensureGeneratedScriptsDir() // 注意:typed run 变体是独立调用,脚本需直接返回值(非返回函数的预加载式文件) let echoPath = generatedScriptPath("typed_echo_arg.lua") File.writeTo(echoPath, "return (...)".toArray()) let runner = LuaRunner() @Expect(runner.runScriptInt(echoPath, 777), 777) @Expect(runner.runScriptNum(echoPath, 2.5), 2.5) @Expect(runner.runScriptBool(echoPath, true), true) } @Test func testTypedNotFound(): Unit { let runner = LuaRunner() try { runner.callFunctionInt("nonexistent", 1) fail("Should throw function not found") } catch (e: LuaError) { @Expect(e.code, 5013) } }