Files
LuaCangjia_api/src/lua_runner_test.cj
2026-08-25 19:16:57 +08:00

604 lines
16 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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)
}
}