Files
LuaCangjia_api/test/test_lua_cj_api.cpp
JianFeeeee cc40a49ae6 feat: 适配 LuaJIT 2.1 (luajit_2.1 分支)
- lib/lua 替换为完整 LuaJIT 2.1 仓库(Makefile+src+dynasm, 官方结构)
- 构建系统改造: lib/lua/CMakeLists.txt 改为调用 LuaJIT 自带 make
  (BUILDMODE=static CFLAGS=-fPIC) 产出 libluajit.a; 顶层链接该静态库
  并加 -Wl,-E 导出符号(静态链 LuaJIT 必需)
- 补充 lua.hpp 转发头兼容桥接层 'lua/lua.hpp' include 路径
- 桥接层沿用 5.1 方案: Registry 存 iopath / luaL_tolstring 兜底 /
  typed interop 无 integer 子类型(LUA_VERSION_NUM=501)
- [关键修复] lua_remove 栈越界 bug(各版本共有, LuaJIT 下显性崩溃):
  * unload_lib: ref 记录的是 gettop+1, 实际移除应用 ref-1
  * clean(): 原逐个 remove 循环恒越界且多次移除后索引偏移,
    属死代码(末尾 settop(L,0) 已清栈), 直接删除
  该 bug 在 Lua 5.4 中被隐式容忍, 在 LuaJIT 的 GC finalizer 中
  表现为 lj_gc_finalize_cdata 段错误
- 测试跨版本断言同步(自 5.1.5)
- 验证: GTest 44/44 通过
2026-08-25 11:57:32 +08:00

680 lines
23 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(), LUAERR_LOAD_FILE);
}
// 测试超过最大库数量限制
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(), LUAERR_LIB_OVERFLOW);
}
// 测试卸载库
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(), LUAERR_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(), LUAERR_LOAD_FILE);
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(), LUAERR_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);
// 跨版本语义适配:
// - Lua 5.3 的 require 返回 1 个值模块本身run() 读栈顶得模块返回值 "LoadedViaPkgPath"
// - Lua 5.4+ 的 require 返回 2 个值(模块 + loader datarun() 读栈顶得 loader data文件路径
// 两者都表明 require 经 pkgpath 成功定位并加载了模块
EXPECT_TRUE(strcmp(res, lib_path.c_str()) == 0 || strcmp(res, "LoadedViaPkgPath") == 0)
<< "unexpected require result: " << (res ? res : "(null)");
}
// ==================== 新增测试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(), LUAERR_CALLBACK);
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(), LUAERR_SCRIPT_BAD_RET);
}
// 测试返回 nil
TEST_F(LuaCjApiTest, DoStringNilReturn) {
int ret = dostring(runner, "return nil");
EXPECT_EQ(ret, -1);
EXPECT_EQ(get_errno(), LUAERR_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(), LUAERR_FUNCTION_NOT_FOUND);
}
// 预加载不存在的文件
TEST_F(LuaCjApiTest, LoadFunctionFileNotFound) {
int ret = loadfunction(runner, "/no/such/file.lua", "bad");
EXPECT_EQ(ret, -1);
EXPECT_EQ(get_errno(), LUAERR_LOAD_FILE);
}
// 预加载顶层未返回函数的文件
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(), LUAERR_FUNCTION_INVALID);
}
// 预加载同名函数
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(), LUAERR_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(), LUAERR_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(), LUAERR_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(), LUAERR_FUNC_OVERFLOW);
}
// 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(), LUAERR_FUNCTION_NOT_FOUND);
}
// ==================== typed interop 测试v0.2.2====================
// 版本自适应Lua 5.3+ 有 integer 子类型CJT_INT5.2 及以下所有数字均为
// numberCJT_NUM。数值断言用 INT_OR_NUM 宏兼容两种情况。
#if LUA_VERSION_NUM >= 503
#define EXPECT_INT_OR_NUM(type) EXPECT_EQ(type, CJT_INT)
#else
// 5.2 及以下:整型结果也报为 CJT_NUM且 lua_tostring(10.0) 输出 "10"
#define EXPECT_INT_OR_NUM(type) EXPECT_EQ(type, CJT_NUM)
#endif
// 整型参数直通Lua 收到 integer返回 integer+1仓颉侧读回 Int64
TEST_F(LuaCjApiTest, TypedCallInt) {
ASSERT_EQ(loadfunction(runner, get_script_path("func_typed_add.lua").c_str(), "inc"), 0);
EXPECT_EQ(callfunction_int(runner, "inc", 41), 0);
EXPECT_INT_OR_NUM(result_type(runner));
EXPECT_EQ(result_int(runner), 42);
// 字符串形式同步可用(向后兼容)
EXPECT_STREQ(getresult(runner), "42");
}
// 浮点参数直通:返回 number*2.5,读回 Float64
TEST_F(LuaCjApiTest, TypedCallNum) {
ASSERT_EQ(loadfunction(runner, get_script_path("func_typed_mul.lua").c_str(), "mul"), 0);
EXPECT_EQ(callfunction_num(runner, "mul", 4.0), 0);
EXPECT_EQ(result_type(runner), CJT_NUM);
EXPECT_DOUBLE_EQ(result_num(runner), 10.0);
#if LUA_VERSION_NUM >= 503
EXPECT_STREQ(getresult(runner), "10.0");
#else
// 5.2 的 %.14g 格式化把 10.0 输出为 "10"
EXPECT_STREQ(getresult(runner), "10");
#endif
}
// 布尔参数直通:返回逻辑非,读回 Bool
TEST_F(LuaCjApiTest, TypedCallBool) {
ASSERT_EQ(loadfunction(runner, get_script_path("func_typed_not.lua").c_str(), "lnot"), 0);
EXPECT_EQ(callfunction_bool(runner, "lnot", 1), 0);
EXPECT_EQ(result_type(runner), CJT_BOOL);
EXPECT_EQ(result_bool(runner), 0);
EXPECT_STREQ(getresult(runner), "false");
EXPECT_EQ(callfunction_bool(runner, "lnot", 0), 0);
EXPECT_EQ(result_type(runner), CJT_BOOL);
EXPECT_EQ(result_bool(runner), 1);
EXPECT_STREQ(getresult(runner), "true");
}
// 无参调用 + 整数返回
TEST_F(LuaCjApiTest, TypedCallVoid) {
ASSERT_EQ(loadfunction(runner, get_script_path("func_typed_void.lua").c_str(), "forty_two"), 0);
EXPECT_EQ(callfunction_void(runner, "forty_two"), 0);
EXPECT_INT_OR_NUM(result_type(runner));
EXPECT_EQ(result_int(runner), 42);
}
// 回声函数:验证各类型往返保真(类型不丢失)
TEST_F(LuaCjApiTest, TypedEchoRoundTrip) {
ASSERT_EQ(loadfunction(runner, get_script_path("func_typed_echo.lua").c_str(), "echo"), 0);
// int 往返
EXPECT_EQ(callfunction_int(runner, "echo", -123456789LL), 0);
EXPECT_INT_OR_NUM(result_type(runner));
EXPECT_EQ(result_int(runner), -123456789LL);
// 大整数(超过 double 精确表示范围)往返:
// 仅 Lua 5.3+integer 类型可保真5.2 及以下经 double 必然丢精度
#if LUA_VERSION_NUM >= 503
EXPECT_EQ(callfunction_int(runner, "echo", 9007199254740993LL), 0);
EXPECT_INT_OR_NUM(result_type(runner));
EXPECT_EQ(result_int(runner), 9007199254740993LL);
#else
EXPECT_EQ(callfunction_int(runner, "echo", 9007199254740993LL), 0);
EXPECT_EQ(result_type(runner), CJT_NUM);
// 经 double 往返,精度丢失为可预期值
EXPECT_EQ(result_int(runner), (long long)(double)9007199254740993LL);
#endif
// num 往返
EXPECT_EQ(callfunction_num(runner, "echo", 3.14159), 0);
EXPECT_EQ(result_type(runner), CJT_NUM);
EXPECT_DOUBLE_EQ(result_num(runner), 3.14159);
// bool 往返
EXPECT_EQ(callfunction_bool(runner, "echo", 1), 0);
EXPECT_EQ(result_type(runner), CJT_BOOL);
EXPECT_EQ(result_bool(runner), 1);
// string 往返(走原 callfunction
EXPECT_EQ(callfunction(runner, "echo", "hello typed"), 0);
EXPECT_EQ(result_type(runner), CJT_STR);
EXPECT_STREQ(getresult(runner), "hello typed");
}
// nil 返回值识别
TEST_F(LuaCjApiTest, TypedNilResult) {
ASSERT_EQ(loadfunction(runner, get_script_path("func_typed_nil.lua").c_str(), "nilret"), 0);
EXPECT_EQ(callfunction_void(runner, "nilret"), 0);
EXPECT_EQ(result_type(runner), CJT_NIL);
EXPECT_EQ(result_int(runner), 0);
EXPECT_EQ(result_bool(runner), 0);
}
// typed 调用不存在的函数仍报 5023
TEST_F(LuaCjApiTest, TypedCallNotFound) {
EXPECT_EQ(callfunction_int(runner, "nonexistent", 1), -1);
EXPECT_EQ(get_errno(), LUAERR_FUNCTION_NOT_FOUND);
EXPECT_EQ(callfunction_void(runner, "nonexistent"), -1);
EXPECT_EQ(get_errno(), LUAERR_FUNCTION_NOT_FOUND);
}
// runScript 的 typed 变体args_echo.lua 直接返回入参
TEST_F(LuaCjApiTest, TypedRunVariants) {
std::string path = temp_dir + "/args_echo.lua";
FILE* f = fopen(path.c_str(), "w");
ASSERT_NE(f, nullptr);
fprintf(f, "return (...)");
fclose(f);
// int
EXPECT_EQ(run_int(runner, path.c_str(), 777), 0);
EXPECT_INT_OR_NUM(result_type(runner));
EXPECT_EQ(result_int(runner), 777);
// num
EXPECT_EQ(run_num(runner, path.c_str(), 2.5), 0);
EXPECT_EQ(result_type(runner), CJT_NUM);
EXPECT_DOUBLE_EQ(result_num(runner), 2.5);
// bool
EXPECT_EQ(run_bool(runner, path.c_str(), 1), 0);
EXPECT_EQ(result_type(runner), CJT_BOOL);
EXPECT_EQ(result_bool(runner), 1);
// 无参无返回时栈顶为空reslt 应为 "nil"
std::string void_path = temp_dir + "/no_return.lua";
f = fopen(void_path.c_str(), "w");
ASSERT_NE(f, nullptr);
fprintf(f, "local x = 1");
fclose(f);
EXPECT_EQ(run_void(runner, void_path.c_str()), 0);
EXPECT_EQ(result_type(runner), CJT_NIL);
}
// typed 与字符串 API 混用互不干扰typed 结果同步到字符串缓冲区
TEST_F(LuaCjApiTest, TypedAndStringCoexist) {
ASSERT_EQ(loadfunction(runner, get_script_path("func_add.lua").c_str(), "add"), 0);
ASSERT_EQ(loadfunction(runner, get_script_path("func_typed_add.lua").c_str(), "inc"), 0);
// 字符串 API
EXPECT_EQ(callfunction(runner, "add", "a"), 0);
EXPECT_STREQ(getresult(runner), "result: a (call #1)");
EXPECT_EQ(result_type(runner), CJT_STR);
// typed API
EXPECT_EQ(callfunction_int(runner, "inc", 100), 0);
EXPECT_EQ(result_int(runner), 101);
// 字符串函数的计数器不受影响
EXPECT_EQ(callfunction(runner, "add", "b"), 0);
EXPECT_STREQ(getresult(runner), "result: b (call #2)");
}
// cleanup 后 typed 状态重置为 NIL
TEST_F(LuaCjApiTest, TypedStateAfterCleanup) {
ASSERT_EQ(loadfunction(runner, get_script_path("func_typed_void.lua").c_str(), "v"), 0);
EXPECT_EQ(callfunction_void(runner, "v"), 0);
EXPECT_INT_OR_NUM(result_type(runner));
EXPECT_EQ(cleanup(runner), 0);
int ret = callfunction_void(runner, "v");
EXPECT_EQ(ret, -1);
EXPECT_EQ(get_errno(), LUAERR_FUNCTION_NOT_FOUND);
}
int main(int argc, char **argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}