mirror of
https://gitcode.com/JianFeeeee/LuaCangjia_api.git
synced 2026-09-20 00:48:47 +00:00
604 lines
16 KiB
Plaintext
604 lines
16 KiB
Plaintext
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)
|
||
}
|
||
}
|