修复描述性错误,完善测试

This commit is contained in:
2026-03-21 22:53:03 +08:00
parent 8998ad6900
commit 8e9deb2865
34 changed files with 202 additions and 95 deletions

View File

@ -1,12 +1,14 @@
# Lua Runner for Cangjie
[![License](https://img.shields.io/badge/License-GPL%20v3-blue.svg)](LICENSE)
[![License](https://img.shields.io/badge/License-LGPL%20v3-blue.svg)](LICENSE)
[![Lua](https://img.shields.io/badge/Lua-5.4-blue)](https://www.lua.org/)
[![Cangjie](https://img.shields.io/badge/Cangjie-SDK-orange)](https://cangjie-lang.cn/)
Lua Runner for Cangjie 是一个专为仓颉语言设计的轻量级、高性能 Lua 脚本执行引擎。它通过 C FFI 桥接 C++,提供了稳定且易用的 Lua 虚拟机管理能力。
**注:本项目是开发原生鸿蒙应用时产生的副产物,当前版本依然存在局限性与不足,请详细检查后再使用。**
安全与发布说明:仓库中不应提交任何中心仓访问令牌、私有凭据或其他敏感配置。发布前请使用本地环境变量、用户级配置或 CI 密钥注入方式提供认证信息,并检查待上传制品中不包含明文凭据。
除了基础的脚本嵌入功能外,该引擎的核心特色在于支持一种独特的 **“栈式管道执行模式”**,能够实现脚本间的隐式参数传递,非常适合构建数据处理管道、游戏脚本系统或插件化架构。同时,它提供了完善的异常处理机制和灵活的 I/O 重定向功能。
## 特性
@ -180,7 +182,12 @@ println(finalResult) // 最终输出: [PREFIX] HELLO [SUFFIX]
- **容量限制**:内部使用固定数组管理加载的库,上限为 **20 个**,超过将报错 `5016`。
- **I/O 性能**I/O 重定向依赖于文件系统交换,相对于纯内存交互存在微小的性能开销。
## 第三方组件与合规说明
- 本项目仓库内包含 Lua 5.4.8 源码副本,用于构建底层原生运行时。
- Lua 5.4.8 使用 MIT License其原始版权与许可声明可在 `lib/lua/` 对应源码中查看。
- 本项目自身以 `LGPL-3.0-or-later` 方式发布,使用或分发时请同时遵守项目本身以及所包含第三方组件的许可证要求。
## 许可证
本项目基于 GPLv3 协议开源。这意味着您可以自由地使用、修改和分发本软件,但任何衍生作品也必须以相同的 GPLv3 协议开源。详情请参阅 [LICENSE](LICENSE) 文件
本项目基于 LGPL-3.0-or-later 协议开源。仓库同时包含 Lua 5.4.8 的 MIT Licensed 源码副本,用于构建底层原生运行时。分发和集成时,请一并检查 [LICENSE](LICENSE) 以及 `lib/lua/` 中随源码附带的许可声明。

View File

@ -3,7 +3,7 @@ name = "luarunner"
version = "0.0.1"
description = "Lite and High performance LuaRunner"
cjc-version = "1.1.0"
license = "GPLv3"
license = "LGPL-3.0-or-later"
output-type ="dynamic"
[dependencies]

View File

@ -4,6 +4,22 @@ import std.unittest.*
import std.fs.*
let scriptsDir = "./test/scripts"
let generatedScriptsDir = "./test/generated_scripts"
func scriptPath(name: String): String {
scriptsDir + "/" + name
}
func generatedScriptPath(name: String): String {
generatedScriptsDir + "/" + name
}
func ensureGeneratedScriptsDir(): Unit {
let dir = Path(generatedScriptsDir)
if (!exists(dir)) {
Directory.create(dir, recursive: true)
}
}
// ====================
// 1. 基础运行与参数测试
@ -12,7 +28,7 @@ let scriptsDir = "./test/scripts"
@Test
func testRunScript(): Unit {
let runner = LuaRunner()
let path = scriptsDir + "/simple_return.lua"
let path = scriptPath("simple_return.lua")
let res = runner.runScript(path, "")
@Expect(res, "hello")
}
@ -20,7 +36,7 @@ func testRunScript(): Unit {
@Test
func testRunScriptWithArg(): Unit {
let runner = LuaRunner()
let path = scriptsDir + "/args_test.lua"
let path = scriptPath("args_test.lua")
let res = runner.runScript(path, "world")
@Expect(res, "world")
}
@ -32,7 +48,7 @@ func testRunScriptWithArg(): Unit {
@Test
func testRunScriptSyntaxError(): Unit {
let runner = LuaRunner()
let path = scriptsDir + "/syntax_error.lua"
let path = scriptPath("syntax_error.lua")
try {
runner.runScript(path, "")
fail("Should throw syntax error")
@ -44,7 +60,7 @@ func testRunScriptSyntaxError(): Unit {
@Test
func testRunScriptRuntimeError(): Unit {
let runner = LuaRunner()
let path = scriptsDir + "/runtime_error.lua"
let path = scriptPath("runtime_error.lua")
try {
runner.runScript(path, "")
fail("Should throw runtime error")
@ -60,7 +76,7 @@ func testRunScriptRuntimeError(): Unit {
@Test
func testLoadLib(): Unit {
let runner = LuaRunner()
let path = scriptsDir + "/mylib.lua"
let path = scriptPath("mylib.lua")
runner.load(path, "mylib")
}
@ -78,7 +94,7 @@ func testLoadLibFileNotFound(): Unit {
@Test
func testUnloadLib(): Unit {
let runner = LuaRunner()
let path = scriptsDir + "/mylib.lua"
let path = scriptPath("mylib.lua")
runner.load(path, "mylib")
runner.unload("mylib")
}
@ -101,8 +117,8 @@ func testUnloadLibNotFound(): Unit {
@Test
func testCleanup(): Unit {
let runner = LuaRunner()
let setPath = scriptsDir + "/set_global.lua"
let getPath = scriptsDir + "/get_global.lua"
let setPath = scriptPath("set_global.lua")
let getPath = scriptPath("get_global.lua")
runner.runScript(setPath, "")
runner.clear()
@ -113,14 +129,92 @@ func testCleanup(): Unit {
@Test
func testCrossCallDataPassing(): Unit {
let runner = LuaRunner()
let setPath = scriptsDir + "/cross_call_set.lua"
let getPath = scriptsDir + "/cross_call_get.lua"
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, 5016)
}
}
@Test
func testPackagePathConfig(): Unit {
let runner = LuaRunner(pkgpath: scriptsDir + "/pkgmods/?.lua")
let res = runner.runScript(scriptPath("pkgpath_require_mylib.lua"), "")
@Expect(res, "LoadedViaPkgPath")
}
@Test
func testPackagePathInitLuaPattern(): Unit {
let runner = LuaRunner(pkgpath: scriptsDir + "/pkgmods_init/?/init.lua")
let res = runner.runScript(scriptPath("pkgpath_require_nested.lua"), "")
@Expect(res, "LoadedViaInit")
}
// ====================
// 5. I/O 重定向测试
// ====================
@ -128,14 +222,14 @@ func testCrossCallDataPassing(): Unit {
@Test
func testRedirectPrint(): Unit {
let runner = LuaRunner(pathio: "/tmp")
let path = scriptsDir + "/print_test.lua"
let path = scriptPath("print_test.lua")
runner.runScript(path, "")
}
@Test
func testRedirectIoWrite(): Unit {
let runner = LuaRunner(pathio: "/tmp")
let path = scriptsDir + "/io_write_test.lua"
let path = scriptPath("io_write_test.lua")
runner.runScript(path, "")
}
@ -151,7 +245,7 @@ func testRedirectIoRead(): Unit {
// 2. 运行测试
let runner = LuaRunner(pathio: "/tmp")
let path = scriptsDir + "/io_read_test.lua"
let path = scriptPath("io_read_test.lua")
let res = runner.runScript(path, "")
@Expect(res, "MockInputData")

View File

@ -3,6 +3,9 @@ project(LuaCjApiTest)
set(CMAKE_CXX_STANDARD 17)
set(SCRIPT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/scripts" CACHE PATH "Path to Lua test scripts")
add_compile_definitions(SCRIPT_DIR="${SCRIPT_DIR}")
# ==================== 核心修改 ====================
# 1. 使用 add_subdirectory 直接包含 lib 源码目录
# 这会自动继承 lib 中定义的 luacjapi 目标及其依赖路径

View File

@ -0,0 +1 @@
return "overflow_lib_0"

View File

@ -0,0 +1 @@
return "overflow_lib_1"

View File

@ -0,0 +1 @@
return "overflow_lib_10"

View File

@ -0,0 +1 @@
return "overflow_lib_11"

View File

@ -0,0 +1 @@
return "overflow_lib_12"

View File

@ -0,0 +1 @@
return "overflow_lib_13"

View File

@ -0,0 +1 @@
return "overflow_lib_14"

View File

@ -0,0 +1 @@
return "overflow_lib_15"

View File

@ -0,0 +1 @@
return "overflow_lib_16"

View File

@ -0,0 +1 @@
return "overflow_lib_17"

View File

@ -0,0 +1 @@
return "overflow_lib_18"

View File

@ -0,0 +1 @@
return "overflow_lib_19"

View File

@ -0,0 +1 @@
return "overflow_lib_2"

View File

@ -0,0 +1 @@
return "overflow"

View File

@ -0,0 +1 @@
return "overflow_lib_3"

View File

@ -0,0 +1 @@
return "overflow_lib_4"

View File

@ -0,0 +1 @@
return "overflow_lib_5"

View File

@ -0,0 +1 @@
return "overflow_lib_6"

View File

@ -0,0 +1 @@
return "overflow_lib_7"

View File

@ -0,0 +1 @@
return "overflow_lib_8"

View File

@ -0,0 +1 @@
return "overflow_lib_9"

View File

@ -0,0 +1,2 @@
local input = ...
return "[PREFIX] " .. input

View File

@ -0,0 +1,2 @@
local input = ...
return input .. " [SUFFIX]"

View File

@ -0,0 +1,2 @@
local input = ...
return input

View File

@ -0,0 +1,2 @@
local input = ...
return string.upper(input)

View File

@ -0,0 +1 @@
return "LoadedViaPkgPath"

View File

@ -0,0 +1 @@
return "LoadedViaInit"

View File

@ -0,0 +1 @@
return require("mylib")

View File

@ -0,0 +1 @@
return require("nestedpkg")

View File

@ -7,22 +7,17 @@
#include <string>
#include <fstream>
#include <streambuf>
#include <stdexcept> // 新增:用于异常处理
#include <stdexcept>
extern "C" {
#include "lua_cj_api.h"
#include "errors.h"
}
// 辅助函数:创建临时目录
static std::string create_temp_dir() {
// 修正点:
// 1. mkdtemp 要求模板字符串最后6个字符必须是 'XXXXXX'
// 2. 使用 /tmp 目录确保路径存在,避免 "test/" 目录不存在导致的失败
char tmpl[] = "/tmp/lua_cj_test_XXXXXX";
char tmpl[] = "/tmp/lua_cj_test_XXXXXX";
char *dir = mkdtemp(tmpl);
// 修正点:检查 mkdtemp 是否成功,防止传入 NULL 给 std::string 导致崩溃
if (dir == nullptr) {
perror("mkdtemp failed");
throw std::runtime_error("Failed to create temp directory");
@ -31,15 +26,17 @@ static std::string create_temp_dir() {
return std::string(dir);
}
// 全局计数器,用于验证回调是否被调用
static int callback_invoke_count = 0;
// 模拟输入/输出回调
static int mock_callback() {
callback_invoke_count++;
return 0;
}
static std::string get_script_path(const std::string& script_name) {
return std::string(SCRIPT_DIR) + "/" + script_name;
}
class LuaCjApiTest : public ::testing::Test {
protected:
void* runner;
@ -47,10 +44,7 @@ protected:
void SetUp() override {
temp_dir = create_temp_dir();
callback_invoke_count = 0; // 重置计数器
// 修改:传入 temp_dir 作为 pathio确保 I/O 重定向路径有效
// 仓颉层封装中 pathio 和 pkgpath 都是可配置的,这里测试基础情况
callback_invoke_count = 0;
runner = init_lua_runner(temp_dir.c_str(), NULL, mock_callback, mock_callback);
ASSERT_NE(runner, nullptr) << "init_lua_runner failed, errno=" << get_errno();
}
@ -63,16 +57,7 @@ protected:
std::string cmd = "rm -rf " + temp_dir;
system(cmd.c_str());
}
void write_lua(const std::string& filename, const std::string& content) {
std::string path = temp_dir + "/" + filename;
FILE* f = fopen(path.c_str(), "w");
ASSERT_NE(f, nullptr);
fprintf(f, "%s", content.c_str());
fclose(f);
}
// 辅助函数:读取文件内容
std::string read_file(const std::string& filename) {
std::string path = temp_dir + "/" + filename;
std::ifstream t(path);
@ -89,8 +74,7 @@ TEST_F(LuaCjApiTest, InitFree) {
// 测试加载库
TEST_F(LuaCjApiTest, LoadLib) {
write_lua("mylib.lua", "function foo() return 42 end");
int ret = load_lib(runner, (temp_dir + "/mylib.lua").c_str(), "mylib");
int ret = load_lib(runner, get_script_path("mylib.lua").c_str(), "mylib");
EXPECT_EQ(ret, 0);
}
@ -106,14 +90,20 @@ TEST_F(LuaCjApiTest, LoadLibOverflow) {
for (int i = 0; i < MAX_LUA_LIB; ++i) {
char name[20];
snprintf(name, sizeof(name), "lib%d", i);
std::string fname = std::string(name) + ".lua";
write_lua(fname, "return {}");
std::string path = temp_dir + "/" + fname;
std::string content = "return " + std::to_string(i);
std::string path = temp_dir + "/" + std::string(name) + ".lua";
FILE* f = fopen(path.c_str(), "w");
ASSERT_NE(f, nullptr);
fprintf(f, "%s", content.c_str());
fclose(f);
int ret = load_lib(runner, path.c_str(), name);
EXPECT_EQ(ret, 0);
}
write_lua("extra.lua", "return {}");
std::string path = temp_dir + "/extra.lua";
FILE* f = fopen(path.c_str(), "w");
ASSERT_NE(f, nullptr);
fprintf(f, "return {}");
fclose(f);
int ret = load_lib(runner, path.c_str(), "extra");
EXPECT_EQ(ret, -1);
EXPECT_EQ(get_errno(), NAPI_LUALIB_LOAD_OVER_STACK);
@ -121,9 +111,7 @@ TEST_F(LuaCjApiTest, LoadLibOverflow) {
// 测试卸载库
TEST_F(LuaCjApiTest, UnloadLib) {
write_lua("mylib.lua", "function foo() end");
std::string path = temp_dir + "/mylib.lua";
ASSERT_EQ(load_lib(runner, path.c_str(), "mylib"), 0);
ASSERT_EQ(load_lib(runner, get_script_path("mylib.lua").c_str(), "mylib"), 0);
int ret = unload_lib(runner, "mylib");
EXPECT_EQ(ret, 0);
}
@ -137,8 +125,7 @@ TEST_F(LuaCjApiTest, UnloadLibNotFound) {
// 测试运行脚本
TEST_F(LuaCjApiTest, RunScript) {
write_lua("test.lua", "return 'hello'");
int ret = run(runner, (temp_dir + "/test.lua").c_str(), nullptr);
int ret = run(runner, get_script_path("simple_return.lua").c_str(), nullptr);
EXPECT_EQ(ret, 0);
char* res = getresult(runner);
EXPECT_STREQ(res, "hello");
@ -146,12 +133,7 @@ TEST_F(LuaCjApiTest, RunScript) {
// 测试带参数的脚本
TEST_F(LuaCjApiTest, RunScriptWithArg) {
// 修正点Lua 中 ... 不能作为变量名。
// 使用 select(1, ...) 或者直接 return ...
// 这里使用 "return (...)" 返回第一个参数
write_lua("test.lua", "return (...)");
int ret = run(runner, (temp_dir + "/test.lua").c_str(), "world");
int ret = run(runner, get_script_path("args_test.lua").c_str(), "world");
EXPECT_EQ(ret, 0);
char* res = getresult(runner);
EXPECT_STREQ(res, "world");
@ -160,8 +142,7 @@ TEST_F(LuaCjApiTest, RunScriptWithArg) {
// 测试脚本语法错误
TEST_F(LuaCjApiTest, RunScriptSyntaxError) {
write_lua("bad.lua", "for i=1,10 print(i) end");
int ret = run(runner, (temp_dir + "/bad.lua").c_str(), NULL);
int ret = run(runner, get_script_path("syntax_error.lua").c_str(), NULL);
EXPECT_EQ(ret, -1);
EXPECT_EQ(get_errno(), NAPI_SCRIPT_ERROR);
char* res = getresult(runner);
@ -170,8 +151,7 @@ TEST_F(LuaCjApiTest, RunScriptSyntaxError) {
// 测试脚本运行时错误
TEST_F(LuaCjApiTest, RunScriptRuntimeError) {
write_lua("bad.lua", "error('oops')");
int ret = run(runner, (temp_dir + "/bad.lua").c_str(), nullptr);
int ret = run(runner, get_script_path("runtime_error.lua").c_str(), nullptr);
EXPECT_EQ(ret, -1);
EXPECT_EQ(get_errno(), NAPI_SCRIPT_ERROR);
char* res = getresult(runner);
@ -180,12 +160,10 @@ TEST_F(LuaCjApiTest, RunScriptRuntimeError) {
// 测试 cleanup 重置状态
TEST_F(LuaCjApiTest, Cleanup) {
write_lua("set.lua", "g = 123");
ASSERT_EQ(run(runner, (temp_dir + "/set.lua").c_str(), nullptr), 0);
ASSERT_EQ(run(runner, get_script_path("set_global.lua").c_str(), nullptr), 0);
int ret = cleanup(runner);
EXPECT_EQ(ret, 0);
write_lua("get.lua", "return tostring(g or 'nil')");
ret = run(runner, (temp_dir + "/get.lua").c_str(), nullptr);
ret = run(runner, get_script_path("get_global.lua").c_str(), nullptr);
EXPECT_EQ(ret, 0);
char* res = getresult(runner);
EXPECT_STREQ(res, "nil");
@ -193,10 +171,8 @@ TEST_F(LuaCjApiTest, Cleanup) {
// 测试跨调用数据传递
TEST_F(LuaCjApiTest, CrossCallDataPassing) {
write_lua("set.lua", "data = {1,2,3}");
ASSERT_EQ(run(runner, (temp_dir + "/set.lua").c_str(), nullptr), 0);
write_lua("get.lua", "return table.concat(data, ',')");
ASSERT_EQ(run(runner, (temp_dir + "/get.lua").c_str(), nullptr), 0);
ASSERT_EQ(run(runner, get_script_path("cross_call_set.lua").c_str(), nullptr), 0);
ASSERT_EQ(run(runner, get_script_path("cross_call_get.lua").c_str(), nullptr), 0);
char* res = getresult(runner);
EXPECT_STREQ(res, "1,2,3");
}
@ -207,9 +183,11 @@ TEST_F(LuaCjApiTest, MultipleLoadUnload) {
for (int i = 0; i < 5; ++i) {
char name[20];
snprintf(name, sizeof(name), "L%d", i);
std::string fname = std::string(name) + ".lua";
write_lua(fname, "return " + std::to_string(i));
std::string path = temp_dir + "/" + fname;
std::string path = temp_dir + "/" + std::string(name) + ".lua";
FILE* f = fopen(path.c_str(), "w");
ASSERT_NE(f, nullptr);
fprintf(f, "return %d", i);
fclose(f);
ASSERT_EQ(load_lib(runner, path.c_str(), name), 0);
}
@ -219,12 +197,15 @@ TEST_F(LuaCjApiTest, MultipleLoadUnload) {
ASSERT_EQ(unload_lib(runner, name), 0);
for (int j = 0; j < i; ++j) {
char libname[20], scriptname[20];
char libname[20];
snprintf(libname, sizeof(libname), "L%d", j);
snprintf(scriptname, sizeof(scriptname), "check%d.lua", j);
std::string script = "return require '" + std::string(libname) + "'";
write_lua(scriptname, script);
int ret = run(runner, (temp_dir + "/" + scriptname).c_str(), nullptr);
std::string scriptname = temp_dir + "/check" + std::to_string(j) + ".lua";
FILE* f = fopen(scriptname.c_str(), "w");
ASSERT_NE(f, nullptr);
fprintf(f, "%s", script.c_str());
fclose(f);
int ret = run(runner, scriptname.c_str(), nullptr);
EXPECT_EQ(ret, -1) << "Failed for lib " << libname << " after unloading " << name;
if (ret == 0) {
char expected[10];
@ -239,22 +220,18 @@ TEST_F(LuaCjApiTest, MultipleLoadUnload) {
// 测试 I/O 重定向Print
TEST_F(LuaCjApiTest, RedirectPrint) {
write_lua("print_test.lua", "print('Hello Cangjie')");
int ret = run(runner, (temp_dir + "/print_test.lua").c_str(), nullptr);
int ret = run(runner, get_script_path("print_test.lua").c_str(), nullptr);
EXPECT_EQ(ret, 0);
// 验证:回调函数应该被调用
EXPECT_GT(callback_invoke_count, 0) << "Output callback should be invoked by print";
// 验证temp_dir 下应生成 output 文件
std::string content = read_file("output");
EXPECT_NE(content.find("Hello Cangjie"), std::string::npos);
}
// 测试 I/O 重定向io.write
TEST_F(LuaCjApiTest, RedirectIoWrite) {
write_lua("write_test.lua", "io.write('Data from lua')");
int ret = run(runner, (temp_dir + "/write_test.lua").c_str(), nullptr);
int ret = run(runner, get_script_path("io_write_test.lua").c_str(), nullptr);
EXPECT_EQ(ret, 0);
EXPECT_GT(callback_invoke_count, 0) << "Output callback should be invoked by io.write";
@ -264,51 +241,44 @@ TEST_F(LuaCjApiTest, RedirectIoWrite) {
// 测试 I/O 重定向io.read
TEST_F(LuaCjApiTest, RedirectIoRead) {
// 准备:模拟外部向 input 文件写入数据
std::string input_file = temp_dir + "/input";
FILE* f = fopen(input_file.c_str(), "w");
ASSERT_NE(f, nullptr);
fprintf(f, "MockInputData");
fclose(f);
// Lua 脚本读取输入并返回
write_lua("read_test.lua", "local s = io.read('*a'); return s");
int ret = run(runner, (temp_dir + "/read_test.lua").c_str(), nullptr);
int ret = run(runner, get_script_path("io_read_test.lua").c_str(), nullptr);
EXPECT_EQ(ret, 0);
// 验证:输入回调被调用
EXPECT_GT(callback_invoke_count, 0) << "Input callback should be invoked by io.read";
// 验证:结果正确
char* res = getresult(runner);
EXPECT_STREQ(res, "MockInputData");
}
// 测试 pkgpath 配置是否生效
TEST_F(LuaCjApiTest, PackagePathConfig) {
// 创建子目录存放库
std::string lib_dir = temp_dir + "/mypkgs";
mkdir(lib_dir.c_str(), 0755);
// 在子目录创建库
std::string lib_path = lib_dir + "/mylib.lua";
FILE* f = fopen(lib_path.c_str(), "w");
fprintf(f, "return 'LoadedViaPkgPath'");
fclose(f);
// 销毁旧 runner
free_lua(runner);
runner = nullptr;
// 创建新 runner配置 pkgpath
std::string pkg_path_pattern = lib_dir + "/?.lua";
runner = init_lua_runner(temp_dir.c_str(), pkg_path_pattern.c_str(), mock_callback, mock_callback);
ASSERT_NE(runner, nullptr);
// 脚本直接 require不指定路径
write_lua("main.lua", "return require 'mylib'");
int ret = run(runner, (temp_dir + "/main.lua").c_str(), nullptr);
std::string main_script = temp_dir + "/main.lua";
f = fopen(main_script.c_str(), "w");
fprintf(f, "return require 'mylib'");
fclose(f);
int ret = run(runner, main_script.c_str(), nullptr);
EXPECT_EQ(ret, 0) << "Require failed, maybe pkgpath not set correctly. Errno: " << get_errno();
char* res = getresult(runner);