Files
LuaCangjia_api/test/test_lua_cj_api.cpp
JianFeeeee 42e8b7676d feat: 实现loadFunction预加载函数能力 + AI辅助编程标识
- 新增 loadFunction/callFunction/unloadFunction 预加载函数 API
- 预加载语义:加载文件→顶层执行一次→返回函数存入Lua Registry→可多次调用
- 与管道模式(load/runScript)存储完全解耦:funcs[]+Registry vs pkgs[]+虚拟栈
- 新增错误码 5023/5024/5025
- 新增单元测试:仓颉侧 8 个 + C++ GTest 侧 11 个
- 本机以 Cangjie 1.1.0 实编译并通过功能运行验证
- README/doc 增加 AI 辅助编程标识及预加载函数模式说明
2026-08-23 12:03:04 +08:00

493 lines
16 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() {
char tmpl[] = "/tmp/lua_cj_test_XXXXXX";
char *dir = mkdtemp(tmpl);
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;
}
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;
std::string temp_dir;
void SetUp() override {
temp_dir = create_temp_dir();
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();
}
void TearDown() override {
if (runner) {
free_lua(runner);
runner = nullptr;
}
std::string cmd = "rm -rf " + temp_dir;
system(cmd.c_str());
}
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) {
int ret = load_lib(runner, get_script_path("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 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);
}
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);
}
// 测试卸载库
TEST_F(LuaCjApiTest, UnloadLib) {
ASSERT_EQ(load_lib(runner, get_script_path("mylib.lua").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) {
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");
}
// 测试带参数的脚本
TEST_F(LuaCjApiTest, RunScriptWithArg) {
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");
}
// 测试脚本编译期语法错误按加载错误上报
TEST_F(LuaCjApiTest, RunScriptSyntaxError) {
int ret = run(runner, get_script_path("syntax_error.lua").c_str(), NULL);
EXPECT_EQ(ret, -1);
EXPECT_EQ(get_errno(), NAPI_LOAD_FILE_ERROR);
char* res = getresult(runner);
EXPECT_NE(strstr(res, "'do' expected"), nullptr);
}
// 测试脚本运行时错误
TEST_F(LuaCjApiTest, RunScriptRuntimeError) {
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);
EXPECT_NE(strstr(res, "oops"), nullptr);
}
// 测试 cleanup 重置状态
TEST_F(LuaCjApiTest, Cleanup) {
ASSERT_EQ(run(runner, get_script_path("set_global.lua").c_str(), nullptr), 0);
int ret = cleanup(runner);
EXPECT_EQ(ret, 0);
ret = run(runner, get_script_path("get_global.lua").c_str(), nullptr);
EXPECT_EQ(ret, 0);
char* res = getresult(runner);
EXPECT_STREQ(res, "nil");
}
// 测试跨调用数据传递
TEST_F(LuaCjApiTest, CrossCallDataPassing) {
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");
}
// 测试多次加载卸载后栈索引正确
TEST_F(LuaCjApiTest, MultipleLoadUnload) {
for (int i = 0; i < 5; ++i) {
char name[20];
snprintf(name, sizeof(name), "L%d", i);
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);
}
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];
snprintf(libname, sizeof(libname), "L%d", j);
std::string script = "return require '" + std::string(libname) + "'";
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];
snprintf(expected, sizeof(expected), "%d", j);
EXPECT_STREQ(getresult(runner), expected);
}
}
}
}
// ==================== 新增测试:针对仓颉封装覆盖的功能 ====================
// 测试 I/O 重定向Print
TEST_F(LuaCjApiTest, RedirectPrint) {
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";
std::string content = read_file("output");
EXPECT_NE(content.find("Hello Cangjie"), std::string::npos);
}
// 测试 I/O 重定向io.write
TEST_F(LuaCjApiTest, RedirectIoWrite) {
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";
std::string content = read_file("output");
EXPECT_NE(content.find("Data from lua"), std::string::npos);
}
// 测试 I/O 重定向io.read
TEST_F(LuaCjApiTest, RedirectIoRead) {
std::string input_file = temp_dir + "/input";
FILE* f = fopen(input_file.c_str(), "w");
ASSERT_NE(f, nullptr);
fprintf(f, "MockInputData");
fclose(f);
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);
free_lua(runner);
runner = nullptr;
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);
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);
EXPECT_STREQ(res, lib_path.c_str());
}
// ==================== 新增测试doString 功能 ====================
// 测试简单表达式
TEST_F(LuaCjApiTest, DoStringSimple) {
int ret = dostring(runner, "return 1 + 1");
EXPECT_EQ(ret, 0);
char* res = getresult(runner);
EXPECT_STREQ(res, "2");
}
// 测试全局变量设置与读取
TEST_F(LuaCjApiTest, DoStringWithGlobal) {
int ret = dostring(runner, "g = 42; return g");
EXPECT_EQ(ret, 0);
char* res = getresult(runner);
EXPECT_STREQ(res, "42");
}
// 测试语法错误
TEST_F(LuaCjApiTest, DoStringSyntaxError) {
int ret = dostring(runner, "if true then");
EXPECT_EQ(ret, -1);
EXPECT_EQ(get_errno(), NAPI_ERROR_FUNCS);
char* res = getresult(runner);
EXPECT_NE(strstr(res, "'end' expected"), nullptr);
}
// 测试无返回值
TEST_F(LuaCjApiTest, DoStringNoReturn) {
int ret = dostring(runner, "x = 10");
EXPECT_EQ(ret, -1);
EXPECT_EQ(get_errno(), NAPI_SCRIPT_BAD_RET);
}
// 测试返回 nil
TEST_F(LuaCjApiTest, DoStringNilReturn) {
int ret = dostring(runner, "return nil");
EXPECT_EQ(ret, -1);
EXPECT_EQ(get_errno(), NAPI_SCRIPT_BAD_RET);
}
// ==================== loadfunction / callfunction 预加载测试 ====================
// 基础预加载 + 调用
TEST_F(LuaCjApiTest, LoadFunctionAndCall) {
int ret = loadfunction(runner, get_script_path("func_add.lua").c_str(), "add");
EXPECT_EQ(ret, 0);
ret = callfunction(runner, "add", "hello");
EXPECT_EQ(ret, 0);
EXPECT_STREQ(getresult(runner), "result: hello (call #1)");
}
// 同一函数多次调用:顶层只执行一次,计数器持续累加
TEST_F(LuaCjApiTest, CallFunctionMultipleTimes) {
ASSERT_EQ(loadfunction(runner, get_script_path("func_add.lua").c_str(), "add"), 0);
EXPECT_EQ(callfunction(runner, "add", "a"), 0);
EXPECT_STREQ(getresult(runner), "result: a (call #1)");
EXPECT_EQ(callfunction(runner, "add", "b"), 0);
EXPECT_STREQ(getresult(runner), "result: b (call #2)");
EXPECT_EQ(callfunction(runner, "add", "c"), 0);
EXPECT_STREQ(getresult(runner), "result: c (call #3)");
}
// 无参函数多次调用
TEST_F(LuaCjApiTest, CallFunctionNoArgMultipleTimes) {
ASSERT_EQ(loadfunction(runner, get_script_path("func_multi_call.lua").c_str(), "multi"), 0);
for (int i = 0; i < 5; i++) {
EXPECT_EQ(callfunction(runner, "multi", NULL), 0);
EXPECT_STREQ(getresult(runner), "multi:init-done");
}
}
// 调用未预加载的函数
TEST_F(LuaCjApiTest, CallFunctionNotFound) {
int ret = callfunction(runner, "nonexistent", NULL);
EXPECT_EQ(ret, -1);
EXPECT_EQ(get_errno(), NAPI_FUNCTION_NOT_FOUND);
}
// 预加载不存在的文件
TEST_F(LuaCjApiTest, LoadFunctionFileNotFound) {
int ret = loadfunction(runner, "/no/such/file.lua", "bad");
EXPECT_EQ(ret, -1);
EXPECT_EQ(get_errno(), NAPI_LOAD_FILE_ERROR);
}
// 预加载顶层未返回函数的文件
TEST_F(LuaCjApiTest, LoadFunctionNotAFunction) {
int ret = loadfunction(runner, get_script_path("func_not_a_function.lua").c_str(), "bad");
EXPECT_EQ(ret, -1);
EXPECT_EQ(get_errno(), NAPI_FUNCTION_NOT_VALID);
}
// 预加载同名函数
TEST_F(LuaCjApiTest, LoadFunctionDuplicate) {
ASSERT_EQ(loadfunction(runner, get_script_path("func_add.lua").c_str(), "dup"), 0);
int ret = loadfunction(runner, get_script_path("func_add.lua").c_str(), "dup");
EXPECT_EQ(ret, -1);
EXPECT_EQ(get_errno(), NAPI_FUNCTION_NOT_FOUND);
}
// 运行时错误
TEST_F(LuaCjApiTest, CallFunctionRuntimeError) {
ASSERT_EQ(loadfunction(runner, get_script_path("func_runtime_error.lua").c_str(), "bad"), 0);
int ret = callfunction(runner, "bad", NULL);
EXPECT_EQ(ret, -1);
EXPECT_EQ(get_errno(), NAPI_SCRIPT_ERROR);
}
// unloadfunction 后再调用应报 5023且可重新加载
TEST_F(LuaCjApiTest, CallFunctionWithUnloadFunction) {
ASSERT_EQ(loadfunction(runner, get_script_path("func_add.lua").c_str(), "add"), 0);
EXPECT_EQ(callfunction(runner, "add", "before"), 0);
EXPECT_STREQ(getresult(runner), "result: before (call #1)");
EXPECT_EQ(unloadfunction(runner, "add"), 0);
int ret = callfunction(runner, "add", NULL);
EXPECT_EQ(ret, -1);
EXPECT_EQ(get_errno(), NAPI_FUNCTION_NOT_FOUND);
// 卸载后可重新预加载,计数器重置
EXPECT_EQ(loadfunction(runner, get_script_path("func_add.lua").c_str(), "add"), 0);
EXPECT_EQ(callfunction(runner, "add", "after"), 0);
EXPECT_STREQ(getresult(runner), "result: after (call #1)");
}
// 预加载与管道模式共存,互不干扰:
// 预加载函数走独立的 funcs[]+Registry管道走 pkgs[] 栈
TEST_F(LuaCjApiTest, LoadFunctionAndPipelineNoInterference) {
// 预加载一个函数(独立于管道栈)
ASSERT_EQ(loadfunction(runner, get_script_path("func_add.lua").c_str(), "add"), 0);
EXPECT_EQ(callfunction(runner, "add", "x"), 0);
EXPECT_STREQ(getresult(runner), "result: x (call #1)");
// 管道模式照常pkgs 栈)
ASSERT_EQ(load_lib(runner, get_script_path("pipeline_add_suffix.lua").c_str(), "add_suffix"), 0);
ASSERT_EQ(load_lib(runner, get_script_path("pipeline_to_upper.lua").c_str(), "to_upper"), 0);
ASSERT_EQ(load_lib(runner, get_script_path("pipeline_add_prefix.lua").c_str(), "add_prefix"), 0);
int ret = run(runner, get_script_path("pipeline_generate_data.lua").c_str(), "hello");
EXPECT_EQ(ret, 0);
EXPECT_STREQ(getresult(runner), "hello");
ret = run(runner, nullptr, nullptr);
EXPECT_EQ(ret, 0);
EXPECT_STREQ(getresult(runner), "[PREFIX] hello");
ret = run(runner, nullptr, nullptr);
EXPECT_EQ(ret, 0);
EXPECT_STREQ(getresult(runner), "[PREFIX] HELLO");
ret = run(runner, nullptr, nullptr);
EXPECT_EQ(ret, 0);
EXPECT_STREQ(getresult(runner), "[PREFIX] HELLO [SUFFIX]");
// 管道跑完后,预加载函数仍可调用,计数器继续
EXPECT_EQ(callfunction(runner, "add", "y"), 0);
EXPECT_STREQ(getresult(runner), "result: y (call #2)");
}
// 预加载数量上限:默认 MAX_LUA_FUNC=20
TEST_F(LuaCjApiTest, LoadFunctionOverflow) {
for (int i = 0; i < MAX_LUA_FUNC; i++) {
char name[20];
snprintf(name, sizeof(name), "func%d", i);
std::string content = "return function() return '" + std::to_string(i) + "' end";
std::string path = temp_dir + "/func" + std::to_string(i) + ".lua";
FILE* f = fopen(path.c_str(), "w");
ASSERT_NE(f, nullptr);
fprintf(f, "%s", content.c_str());
fclose(f);
int ret = loadfunction(runner, path.c_str(), name);
EXPECT_EQ(ret, 0);
}
// 第 21 个应报 5024
std::string path = temp_dir + "/overflow.lua";
FILE* f = fopen(path.c_str(), "w");
ASSERT_NE(f, nullptr);
fprintf(f, "return function() return 'overflow' end");
fclose(f);
int ret = loadfunction(runner, path.c_str(), "overflow");
EXPECT_EQ(ret, -1);
EXPECT_EQ(get_errno(), NAPI_LOAD_FUNCTION_OVER);
}
// cleanup 后预加载函数应失效
TEST_F(LuaCjApiTest, CallFunctionAfterCleanup) {
ASSERT_EQ(loadfunction(runner, get_script_path("func_add.lua").c_str(), "add"), 0);
EXPECT_EQ(callfunction(runner, "add", "x"), 0);
EXPECT_EQ(cleanup(runner), 0);
int ret = callfunction(runner, "add", NULL);
EXPECT_EQ(ret, -1);
EXPECT_EQ(get_errno(), NAPI_FUNCTION_NOT_FOUND);
}
int main(int argc, char **argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}