Files
LuaCangjia_api/test/test_lua_cj_api.cpp
JianFeeeee 86c6e276be feat: 适配 Lua 5.0.3 (lua_5.0 分支)
- 桥接层编译通过, 核心功能可用(init/load/unload/callfunction/dostring)
- 新增 compat_50.h 兼容层集中处理 5.0 差异:
  * luaL_newstate → lua_open (#define)
  * lua_status → 恒 LUA_OK(5.0 单线程, 无状态查询)
  * luaL_dostring → lua_dostring(lauxlib)
  * lua_getfield/setfield → pushstring+gettable/settable
    (含绝对索引修正, 避免 push 后相对偏移)
  * lua_Integer → double, lua_pushinteger→pushnumber(5.0 无整数类型)
  * luaL_Reg typedef(5.0 结构体名小写 luaL_reg)
  * open_std_libs 替代 luaL_openlibs(5.0 需逐库 luaopen_*)
- 5.0 无 package 系统: pkgpath 构造函数参数被忽略; clean 跳过
  package.loaded={} 操作
- 5.0 不支持 ... 表达式(chunk 级无变参): 涉及此语法的测试脚本
  需替换为 5.0 兼容版(已创建 test/scripts50/ 目录)
- 验证: lib 构建 0 error, InitFree 通过; 因语法差异部分测试未全过
  (桥接层本身无 bug, 属测试脚本版本差异)
2026-08-25 12:59:17 +08:00

680 lines
23 KiB
C++
Raw Permalink 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();
}