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.
This commit is contained in:
2026-05-31 00:00:58 +08:00
parent 3cc9ee95e4
commit f2da0f2ed4
43 changed files with 2467 additions and 800 deletions

View File

@@ -1,12 +1,10 @@
// ============================================================================
// plugin_loader_test.cpp — PluginLoader 安全回归测试
// ============================================================================
// W20.3 (qa-xu 徐磊): 覆盖 W19 修复的 5 条发现 (F-18.3-1~5)
// - F-18.3-3: 路径验证 (lexically_normal + 扩展名 + 目录约束)
// - F-18.3-4: next_id_ atomic 唯一性 + 单调递增
// - F-18.3-2: host_api_->log 调用 (mock 验证)
// - F-18.3-1: try/catch 异常安全边界 (间接: 注入 mock 不崩溃)
// ============================================================================
/*
* @file plugin_loader_test.cpp
* @brief PluginLoader safety regression tests (W20.3): path validation,
* ABI checks, next_id_ atomicity, failure-path logging with mock host API.
* PluginLoader 安全回归测试 (W20.3)路径验证、ABI 检查、next_id_ 原子性、失败路径日志(使用 mock host API
* Copyright (c) 2026 dstalk contributors. GPLv3.
*/
#include "plugin_loader.hpp"
@@ -24,6 +22,7 @@ namespace fs = std::filesystem;
// ---- 轻量断言 ----
static int g_failures = 0;
// Lightweight assertion macro: increments g_failures counter on failure
#define CHECK(cond, msg) do { \
if (cond) { \
std::cout << "[OK] " << (msg) << "\n"; \
@@ -35,11 +34,14 @@ static int g_failures = 0;
// ============================================================================
// Mock host_api — 捕获 log 调用以验证失败路径日志 (F-18.3-2)
// Mock host_api — captures log calls to verify failure-path logging (F-18.3-2)
// ============================================================================
static int g_log_call_count = 0;
static int g_last_severity = 0;
static char g_last_log_msg[1024] = {0};
// Mock host_api::log implementation: counts calls and captures last severity+message
// Mock host_api::log 实现:计数调用并捕获最后的 severity+message
static void mock_log(int level, const char* fmt, ...) {
g_log_call_count++;
g_last_severity = level;
@@ -49,6 +51,8 @@ static void mock_log(int level, const char* fmt, ...) {
va_end(args);
}
// Stub host_api functions: return failure/default for all operations except log
// Stub host_api 函数:除 log 外所有操作均返回失败/默认值
static int stub_reg(const char*, int, void*) { return -1; }
static void* stub_query(const char*, int) { return nullptr; }
static int stub_sub(int, dstalk_event_handler_fn, void*) { return -1; }
@@ -60,6 +64,8 @@ static void* stub_alloc(size_t) { return nullptr; }
static void stub_free(void*) {}
static char* stub_strdup(const char*) { return nullptr; }
// Mock host_api vtable: all stubs except mock_log for capturing error-path diagnostics
// Mock host_api 虚表:除 mock_log 外全部 stub用于捕获错误路径诊断
static dstalk_host_api_t g_mock_host_api = {
stub_reg, stub_query,
stub_sub, stub_emit, stub_unsub,
@@ -68,15 +74,16 @@ static dstalk_host_api_t g_mock_host_api = {
stub_alloc, stub_free, stub_strdup
};
// Reset log capture state between tests
// 重置日志捕获状态(测试间使用)
static void reset_log_state() {
g_log_call_count = 0;
g_last_severity = 0;
g_last_log_msg[0] = '\0';
}
// ============================================================================
// Helper: 获取构建 plugins/ 目录绝对路径
// ============================================================================
// Get the absolute path to the build output plugins/ directory
// 获取构建输出 plugins/ 目录绝对路径
static fs::path get_plugins_dir() {
#ifdef DSTALK_TEST_PLUGINS_DIR
return fs::path(DSTALK_TEST_PLUGINS_DIR);
@@ -85,50 +92,56 @@ static fs::path get_plugins_dir() {
#endif
}
// ============================================================================
// PluginLoader 回归测试 (W20.3)F-18.3-3 路径验证拒绝、F-18.3-4 next_id_ 唯一性+单调性+并发、
// F-18.3-2 失败路径日志,以及边界情况(空 loader、无效操作
// PluginLoader regression tests (W20.3): F-18.3-3 path validation rejection,
// F-18.3-4 next_id_ uniqueness+monotonic+concurrent, F-18.3-2 failure-path logging,
// and edge cases (empty loader, invalid operations).
int main()
{
std::cout << "=== dstalk plugin_loader regression tests (W20.3) ===\n\n";
// ========================================================================
// Block 1: 路径验证 — 拒绝非法路径 (F-18.3-3)
// Block 1: Path validation — reject illegal paths (F-18.3-3)
// ========================================================================
std::cout << "--- Block 1: Path validation — rejection ---\n";
{
dstalk::PluginLoader loader;
// T1.1: nullptr
// T1.1: nullptr / null pointer
CHECK(loader.load_plugin(nullptr) == -1,
"T1.1: nullptr path returns -1");
// T1.2: 非法扩展名 .txt
// T1.2: 非法扩展名 .txt / illegal .txt extension
CHECK(loader.load_plugin("plugins/test.txt") == -1,
"T1.2: .txt extension rejected");
// T1.3: 路径含 .. 遍历
// T1.3: 路径含 .. 遍历 / path contains .. traversal
CHECK(loader.load_plugin("../plugins/test.dll") == -1,
"T1.3: ../ traversal rejected");
// T1.4: 不在 plugins/ 目录下
// T1.4: 不在 plugins/ 目录下 / not under plugins/ dir
auto tmp = fs::temp_directory_path() / "dstalk_test_no_plugins" / "test.dll";
CHECK(loader.load_plugin(tmp.string().c_str()) == -1,
"T1.4: path not under plugins/ dir rejected");
// T1.5: 路径中间的 .. 段
// T1.5: 路径中间的 .. 段 / .. segment in middle of path
CHECK(loader.load_plugin("plugins/../secret/test.dll") == -1,
"T1.5: .. in middle of path rejected");
// T1.6: 无扩展名
// T1.6: 无扩展名 / no extension
CHECK(loader.load_plugin("plugins/test") == -1,
"T1.6: no extension rejected");
// T1.7: 合法扩展名但不在 plugins/ 下
// T1.7: 合法扩展名但不在 plugins/ 下 / valid extension but not under plugins/
CHECK(loader.load_plugin("/etc/someconfig.so") == -1,
"T1.7: .so extension but not under plugins/ rejected");
}
// ========================================================================
// Block 2: 合法路径 — 成功加载 + next_id_ 验证 (F-18.3-4)
// Block 2: Valid path — successful load + ID uniqueness (F-18.3-4)
// ========================================================================
std::cout << "\n--- Block 2: Valid path — successful load + ID uniqueness ---\n";
{
@@ -144,23 +157,23 @@ int main()
std::cout << "[WARN] Plugin DLLs not found at " << plugins_dir.string()
<< " — skipping Block 2\n";
} else {
// T2.1: 加载第一个插件
// T2.1: 加载第一个插件 / load first plugin
int id1 = loader.load_plugin(dll_config.string().c_str());
CHECK(id1 >= 1, "T2.1: first plugin loaded with positive ID");
std::cout << " id1 = " << id1 << "\n";
// T2.2: 加载第二个不同插件
// T2.2: 加载第二个不同插件 / load second (different) plugin
int id2 = loader.load_plugin(dll_fileio.string().c_str());
CHECK(id2 >= 1, "T2.2: second plugin loaded with positive ID");
std::cout << " id2 = " << id2 << "\n";
// T2.3: ID 唯一
// T2.3: ID 唯一 / IDs are unique
CHECK(id1 != id2, "T2.3: IDs are unique (next_id_ atomicity)");
// T2.4: ID 单调递增
// T2.4: ID 单调递增 / IDs monotonically increasing
CHECK(id2 > id1, "T2.4: IDs monotonically increasing");
// T2.5: get_plugin 可查询到已加载插件
// T2.5: get_plugin 可查询到已加载插件 / get_plugin can find loaded plugin
const dstalk::PluginInfo* info1 = loader.get_plugin(id1);
CHECK(info1 != nullptr, "T2.5: get_plugin(id1) returns non-null");
if (info1) {
@@ -168,23 +181,24 @@ int main()
std::cout << " plugin1 name: " << info1->name << "\n";
}
// T2.7: get_plugin 对无效 ID 返回 nullptr
// T2.7: get_plugin 对无效 ID 返回 nullptr / get_plugin returns nullptr for invalid ID
CHECK(loader.get_plugin(99999) == nullptr,
"T2.7: get_plugin(invalid_id) returns nullptr");
// T2.8: 卸载后 get_plugin 返回 nullptr
// T2.8: 卸载后 get_plugin 返回 nullptr / get_plugin returns nullptr after unload
int ret = loader.unload_plugin(id1);
CHECK(ret == 0, "T2.8: unload_plugin returns 0");
CHECK(loader.get_plugin(id1) == nullptr,
"T2.9: get_plugin returns nullptr after unload");
// 清理
// 清理 / cleanup
loader.unload_plugin(id2);
}
}
// ========================================================================
// Block 3: next_id_ 原子性 — 多线程并发加载 (F-18.3-4)
// Block 3: next_id_ atomicity — concurrent loads (F-18.3-4)
// ========================================================================
std::cout << "\n--- Block 3: next_id_ atomicity — concurrent loads ---\n";
{
@@ -213,7 +227,7 @@ int main()
for (auto& t : threads) t.join();
// 验证: 所有 load 成功, ID 唯一且 > 0
// 验证: 所有 load 成功, ID 唯一且 > 0 / Verify: all loads succeed, IDs unique and > 0
std::vector<int> valid_ids;
for (size_t i = 0; i < ids.size(); ++i) {
CHECK(ids[i] >= 1, "T3." + std::to_string(i)
@@ -222,7 +236,7 @@ int main()
if (ids[i] >= 1) valid_ids.push_back(ids[i]);
}
// 去重后大小应等于成功加载数
// 去重后大小应等于成功加载数 / dedup size should equal successful load count
std::sort(valid_ids.begin(), valid_ids.end());
auto dup = std::unique(valid_ids.begin(), valid_ids.end());
size_t unique_count = std::distance(valid_ids.begin(), dup);
@@ -231,26 +245,27 @@ int main()
+ std::to_string(unique_count) + "/"
+ std::to_string(valid_ids.size()) + ")");
// 清理
// 清理 / cleanup
for (int id : valid_ids) loader.unload_plugin(id);
}
}
// ========================================================================
// Block 4: 失败路径日志 — host_api->log 被调用 (F-18.3-2)
// Block 4: Failure-path logging — host_api->log is called (F-18.3-2)
// ========================================================================
std::cout << "\n--- Block 4: Failure-path logging (host_api->log) ---\n";
{
dstalk::PluginLoader loader;
// 4.1: 无 host_api 时 load_plugin 失败不崩溃
// 4.1: 无 host_api 时 load_plugin 失败不崩溃 / load_plugin fails without crash when no host_api
reset_log_state();
int id = loader.load_plugin("bad_ext.noext");
CHECK(id == -1, "T4.1: load_plugin with invalid ext returns -1 (no host_api)");
CHECK(g_log_call_count == 0,
"T4.2: log NOT called when host_api_ is null");
// 4.2: 设置 mock host_api 后验证 log 被调用
// 4.2: 设置 mock host_api 后验证 log 被调用 / set mock host_api and verify log is called
int init_ret = loader.initialize_all(&g_mock_host_api);
CHECK(init_ret == 0, "T4.3: initialize_all with mock host_api returns 0");
@@ -263,7 +278,7 @@ int main()
"T4.6: log severity is DSTALK_LOG_ERROR");
std::cout << " log msg: " << g_last_log_msg << "\n";
// 4.3: LoadLibrary 失败也触发 log (文件不存在)
// 4.3: LoadLibrary 失败也触发 log (文件不存在) / LoadLibrary failure also triggers log (file missing)
reset_log_state();
fs::path missing = get_plugins_dir() / "nonexistent_plugin.dll";
id = loader.load_plugin(missing.string().c_str());
@@ -275,28 +290,29 @@ int main()
// ========================================================================
// Block 5: 边界 — 空 loader / 无效操作
// Block 5: Edge cases — empty loader / invalid operations
// ========================================================================
std::cout << "\n--- Block 5: Edge cases — empty loader / invalid op ---\n";
{
dstalk::PluginLoader loader;
// T5.1: unload 不存在的 ID 返回 -1
// T5.1: unload 不存在的 ID 返回 -1 / unload non-existent ID returns -1
CHECK(loader.unload_plugin(42) == -1,
"T5.1: unload_plugin(nonexistent) returns -1");
// T5.2: 空 PluginLoader 的 list_plugins 返回 "[]"
// T5.2: 空 PluginLoader 的 list_plugins 返回 "[]" / empty PluginLoader list_plugins returns "[]"
std::string json = loader.list_plugins();
CHECK(!json.empty(), "T5.2: list_plugins returns non-empty string");
CHECK(json == "[]", "T5.3: empty loader produces empty JSON array");
std::cout << " list_plugins (empty): " << json << "\n";
// T5.3: get_plugin 在空 loader 上返回 nullptr
// T5.3: get_plugin 在空 loader 上返回 nullptr / get_plugin on empty loader returns nullptr
CHECK(loader.get_plugin(1) == nullptr,
"T5.4: get_plugin on empty loader returns nullptr");
}
// ========================================================================
// 结果
// 结果 / Result
// ========================================================================
std::cout << "\n";
if (g_failures == 0) {