Files
dstalk/tests/host_api_test.cpp
XiuChengWu f2da0f2ed4 Add metadata validation script and module documentation
- Introduced a new Python script `check_agents_metadata.py` for validating agent metadata, including YAML parsing, rating ranges, and cross-references.
- Added usage instructions and exit codes for the script.
- Created a new markdown file `模块目录和功能说明.md` to outline the directory structure and functionality of the modules.
- Added a text file `说明此文件不可AI修改.txt` to specify that certain files should not be modified by AI, including important information about the `dstalk` framework and its modules.
2026-05-31 00:00:58 +08:00

202 lines
8.8 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.
/*
* @file host_api_test.cpp
* @brief Host API unit tests: service registration, event bus, config store,
* alloc/free, logging, init/shutdown lifecycle.
* Host API 单元测试服务注册、事件总线、配置存储、alloc/free、日志、init/shutdown 生命周期。
* Copyright (c) 2026 dstalk contributors. GPLv3.
*/
#include <cstdarg>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <string>
// 引入 ServiceRegistry 实现做纯单元测试 / Include ServiceRegistry impl for pure unit tests
#include "service_registry.hpp"
#include "dstalk/dstalk_host.h"
// ---- 轻量断言 ----
static int g_failures = 0;
// Lightweight assertion helper: increments g_failures counter on failure
#define TCHECK(cond, msg) do { \
if (cond) { \
std::cout << "[OK] " << (msg) << "\n"; \
} else { \
std::cerr << "[FAIL] " << (msg) << "\n"; \
g_failures++; \
} \
} while (0)
// Helper: creates a temporary config.toml pointing to a non-existent plugin dir,
// so dstalk_init loads no external plugins during tests.
// 辅助函数:创建临时 config.toml 指向不存在的插件目录,使 dstalk_init 在测试时不加载任何外部插件。
static std::string make_temp_config(const std::string& tag) {
auto dir = std::filesystem::temp_directory_path() / ("dstalk-host-api-" + tag);
std::filesystem::create_directories(dir);
auto config_path = dir / "config.toml";
{
std::ofstream c(config_path);
// 指向不存在的插件目录,避免加载任何 .dll / Point to nonexistent plugin dir, avoid loading any .dll
c << "plugin_dir = \"__no_such_plugins_dir__\"\n";
}
return config_path.string();
}
// Host API 单元测试:覆盖注册/查询重复、版本不匹配、双重 init 防护、alloc/free 边界、日志级别、shutdown 后查询。
// Host API unit tests: covers register/query duplicates, version mismatch,
// double-init guard, alloc/free edge cases, logging levels, and post-shutdown query.
int main()
{
std::cout << "=== dstalk host_api unit tests ===\n\n";
// ====================================================================
// Test 1: register_service 重复注册 同名+同版本 → 应返回 -2
// Test 1: register_service duplicate same-name+same-version -> should return -2
// ====================================================================
{
dstalk::ServiceRegistry reg;
void* dummy_vtable = reinterpret_cast<void*>(0x1);
int r1 = reg.register_service("echo", 1, dummy_vtable);
TCHECK(r1 == 0, "register_service(\"echo\",1) first call returns 0");
int r2 = reg.register_service("echo", 1, dummy_vtable);
TCHECK(r2 == -2,
"register_service(\"echo\",1) duplicate same-version returns -2");
}
// ====================================================================
// Test 2: register_service 同名+不同版本 → 应返回 -2
// 名称已占用,与版本无关
// Test 2: register_service same-name+different-version -> should return -2
// Name already taken, regardless of version
// ====================================================================
{
dstalk::ServiceRegistry reg;
void* dummy_vtable = reinterpret_cast<void*>(0x1);
int r1 = reg.register_service("calc", 1, dummy_vtable);
TCHECK(r1 == 0, "register_service(\"calc\",1) first call returns 0");
int r2 = reg.register_service("calc", 99, dummy_vtable);
TCHECK(r2 == -2,
"register_service(\"calc\",99) diff-version duplicate returns -2");
}
// ====================================================================
// Test 3: query_service 不存在的 name → nullptr
// Test 3: query_service nonexistent name -> nullptr
// ====================================================================
{
dstalk::ServiceRegistry reg;
void* q = reg.query_service("ghost_service", 1);
TCHECK(q == nullptr, "query_service(\"ghost_service\",1) returns nullptr");
}
// ====================================================================
// Test 4: query_service 错误版本号 → nullptr
// 注册 v=1, 查询 min_version=2 → 不满足 → nullptr
// Test 4: query_service wrong version -> nullptr
// Registered v=1, query min_version=2 -> unsatisfied -> nullptr
// ====================================================================
{
dstalk::ServiceRegistry reg;
void* dummy_vtable = reinterpret_cast<void*>(0x2);
reg.register_service("solo", 1, dummy_vtable);
void* q = reg.query_service("solo", 2);
TCHECK(q == nullptr, "query_service(\"solo\",2) with only v1 available returns nullptr");
// 确证以正确版本查询能拿到 / Confirm correct version query works
void* q2 = reg.query_service("solo", 1);
TCHECK(q2 == dummy_vtable, "query_service(\"solo\",1) with v1 available returns vtable");
}
// ====================================================================
// Test 5: dstalk_init 多次调用 → 第二次应返回 -1 (幂等拒绝)
// Test 5: dstalk_init multiple calls -> second should return -1 (idempotent guard)
// ====================================================================
{
std::string cfg = make_temp_config("init-twice");
int r1 = dstalk_init(cfg.c_str());
TCHECK(r1 == 0, "dstalk_init first call returns 0");
int r2 = dstalk_init(cfg.c_str());
TCHECK(r2 == -1, "dstalk_init second call returns -1 (idempotent guard)");
dstalk_shutdown();
}
// ====================================================================
// Test 6: alloc(0) / free(nullptr) 行为
// malloc(0) 可返回 null 或合法指针; 两者都可 free
// free(nullptr) 是安全空操作
// Test 6: alloc(0) / free(nullptr) behavior
// malloc(0) may return null or valid pointer; both are free-able
// free(nullptr) is a safe no-op
// ====================================================================
{
void* p = dstalk_alloc(0);
std::cout << "[OK] dstalk_alloc(0) returned " << p
<< " (null or valid, both acceptable)\n";
dstalk_free(p);
std::cout << "[OK] dstalk_free(alloc(0)) did not crash\n";
dstalk_free(nullptr);
std::cout << "[OK] dstalk_free(nullptr) did not crash\n";
}
// ====================================================================
// Test 7: log 各 level 不崩溃 (DEBUG / INFO / WARN / ERROR)
// Test 7: log at each level no crash (DEBUG / INFO / WARN / ERROR)
// ====================================================================
{
dstalk_log(DSTALK_LOG_DEBUG, "host_api_test: debug level message");
std::cout << "[OK] dstalk_log(DEBUG) no crash\n";
dstalk_log(DSTALK_LOG_INFO, "host_api_test: info level message");
std::cout << "[OK] dstalk_log(INFO) no crash\n";
dstalk_log(DSTALK_LOG_WARN, "host_api_test: warn level message");
std::cout << "[OK] dstalk_log(WARN) no crash\n";
dstalk_log(DSTALK_LOG_ERROR, "host_api_test: error level message");
std::cout << "[OK] dstalk_log(ERROR) no crash\n";
// 带格式参数 / With format args
dstalk_log(DSTALK_LOG_INFO, "formatted: %s %d", "answer", 42);
std::cout << "[OK] dstalk_log with format args no crash\n";
}
// ====================================================================
// Test 8: dstalk_shutdown 后 query_service → nullptr
// g_service_registry 已被 delete 置空
// Test 8: query_service after dstalk_shutdown -> nullptr
// g_service_registry has been deleted and nulled
// ====================================================================
{
std::string cfg = make_temp_config("after-shutdown");
dstalk_init(cfg.c_str());
dstalk_shutdown();
void* q = dstalk_service_query("any_service", 1);
TCHECK(q == nullptr, "dstalk_service_query after shutdown returns nullptr");
}
// ====================================================================
// 结果 / Result
// ====================================================================
std::cout << "\n";
if (g_failures == 0) {
std::cout << "=== All host_api tests passed ===\n";
return 0;
} else {
std::cerr << "=== " << g_failures << " host_api test(s) FAILED ===\n";
return 1;
}
}