#include #include #include #include #include #include #include #include #include #include // 新增:用于异常处理 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 *dir = mkdtemp(tmpl); // 修正点:检查 mkdtemp 是否成功,防止传入 NULL 给 std::string 导致崩溃 if (dir == nullptr) { perror("mkdtemp failed"); throw std::runtime_error("Failed to create temp directory"); } return std::string(dir); } // 全局计数器,用于验证回调是否被调用 static int callback_invoke_count = 0; // 模拟输入/输出回调 static int mock_callback() { callback_invoke_count++; return 0; } class LuaCjApiTest : public ::testing::Test { protected: void* runner; std::string temp_dir; void SetUp() override { temp_dir = create_temp_dir(); callback_invoke_count = 0; // 重置计数器 // 修改:传入 temp_dir 作为 pathio,确保 I/O 重定向路径有效 // 仓颉层封装中 pathio 和 pkgpath 都是可配置的,这里测试基础情况 runner = init_lua_runner(temp_dir.c_str(), NULL, mock_callback, mock_callback); ASSERT_NE(runner, nullptr) << "init_lua_runner failed, errno=" << get_errno(); } void TearDown() override { if (runner) { free_lua(runner); runner = nullptr; } 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); std::string str((std::istreambuf_iterator(t)), std::istreambuf_iterator()); return str; } }; // 基础生命周期测试 TEST_F(LuaCjApiTest, InitFree) { SUCCEED(); } // 测试加载库 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"); EXPECT_EQ(ret, 0); } // 测试加载不存在的文件 TEST_F(LuaCjApiTest, LoadLibFileNotFound) { int ret = load_lib(runner, "/no/such/file.lua", "bad"); EXPECT_EQ(ret, -1); EXPECT_EQ(get_errno(), NAPI_LOAD_FILE_ERROR); } // 测试超过最大库数量限制 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; 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"; int ret = load_lib(runner, path.c_str(), "extra"); EXPECT_EQ(ret, -1); EXPECT_EQ(get_errno(), NAPI_LUALIB_LOAD_OVER_STACK); } // 测试卸载库 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); int ret = unload_lib(runner, "mylib"); EXPECT_EQ(ret, 0); } // 测试卸载未加载的库 TEST_F(LuaCjApiTest, UnloadLibNotFound) { int ret = unload_lib(runner, "nosuch"); EXPECT_EQ(ret, -1); EXPECT_EQ(get_errno(), NAPI_UNLOADLIB_FAIL); } // 测试运行脚本 TEST_F(LuaCjApiTest, RunScript) { write_lua("test.lua", "return 'hello'"); int ret = run(runner, (temp_dir + "/test.lua").c_str(), nullptr); EXPECT_EQ(ret, 0); char* res = getresult(runner); EXPECT_STREQ(res, "hello"); } // 测试带参数的脚本 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"); EXPECT_EQ(ret, 0); char* res = getresult(runner); EXPECT_STREQ(res, "world"); } // 测试脚本语法错误 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); EXPECT_EQ(ret, -1); EXPECT_EQ(get_errno(), NAPI_SCRIPT_ERROR); char* res = getresult(runner); EXPECT_NE(strstr(res, "'do' expected"), nullptr); } // 测试脚本运行时错误 TEST_F(LuaCjApiTest, RunScriptRuntimeError) { write_lua("bad.lua", "error('oops')"); int ret = run(runner, (temp_dir + "/bad.lua").c_str(), nullptr); EXPECT_EQ(ret, -1); EXPECT_EQ(get_errno(), NAPI_SCRIPT_ERROR); char* res = getresult(runner); EXPECT_NE(strstr(res, "oops"), nullptr); } // 测试 cleanup 重置状态 TEST_F(LuaCjApiTest, Cleanup) { write_lua("set.lua", "g = 123"); ASSERT_EQ(run(runner, (temp_dir + "/set.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); EXPECT_EQ(ret, 0); char* res = getresult(runner); EXPECT_STREQ(res, "nil"); } // 测试跨调用数据传递 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); char* res = getresult(runner); EXPECT_STREQ(res, "1,2,3"); } // 测试多次加载卸载后栈索引正确 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; ASSERT_EQ(load_lib(runner, path.c_str(), name), 0); } for (int i = 4; i >= 0; --i) { char name[20]; snprintf(name, sizeof(name), "L%d", i); ASSERT_EQ(unload_lib(runner, name), 0); for (int j = 0; j < i; ++j) { char libname[20], scriptname[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); EXPECT_EQ(ret, -1) << "Failed for lib " << libname << " after unloading " << name; if (ret == 0) { char expected[10]; snprintf(expected, sizeof(expected), "%d", j); EXPECT_STREQ(getresult(runner), expected); } } } } // ==================== 新增测试:针对仓颉封装覆盖的功能 ==================== // 测试 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); 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); EXPECT_EQ(ret, 0); EXPECT_GT(callback_invoke_count, 0) << "Output callback should be invoked by io.write"; std::string content = read_file("output"); EXPECT_NE(content.find("Data from lua"), std::string::npos); } // 测试 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); 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); EXPECT_EQ(ret, 0) << "Require failed, maybe pkgpath not set correctly. Errno: " << get_errno(); char* res = getresult(runner); EXPECT_STREQ(res, "LoadedViaPkgPath"); } int main(int argc, char **argv) { ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); }