Files
LuaCangjia_api/test/test_lua_cj_api.cpp

322 lines
10 KiB
C++
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.

#include <gtest/gtest.h>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <unistd.h>
#include <sys/stat.h>
#include <string>
#include <fstream>
#include <streambuf>
#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 *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<char>(t)),
std::istreambuf_iterator<char>());
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();
}