Files
dstalk/tests/smoke_test.cpp
XiuChengWu 5766938524
Some checks failed
CI / Determine matrix (push) Has been cancelled
CI / ${{ matrix.os }} / ${{ matrix.build_type }} (push) Has been cancelled
Wave 5+6: plugin ABI hardening, build modernization, ABI/security docs
Wave 5 (9 parallel agents):
- W1.1 atomic diag callback + DLL handle release on shutdown (lin)
- W2.1 unify cross-DLL heap discipline (host->alloc/free/strdup) (chen)
- W2.2 secure_zero api_key on shutdown for deepseek/anthropic (cao)
- W3 CMake modernization: target-based cxx_std_20, dstalk_boost_config
  INTERFACE lib, root-level RUNTIME_OUTPUT_DIRECTORY (hu)
- W4 GitHub Actions CI with dynamic Linux/Windows matrix (ma)
- W5.1 SSE buffer_body to cut peak memory ~67% on 32K streams (zhou)
- W6.1 LSP JSON-RPC frame parser hardened against header reordering (sun)
- W7 smoke test: copy plugin DLLs post-build + Boost.JSON src.hpp fix
  for full 9-plugin load coverage (wang)
- W8.1 README slimmed 398->92, Diataxis docs/ skeleton (deng)

Wave 6 (6 parallel agents):
- W9.1 docs/explanation: architecture + plugin-lifecycle (deng)
- W9.3 log credential leak audit (0 vulns, audit trail in
  docs/explanation/security-logging.md) (cao)
- W9.4 docs/reference/plugin-abi.md - 7-point ABI contract (lin)
- W9.6 CLI /history command + status integration (zhao)
- W9.8 plugin_loader fault tolerance: per-plugin failure no longer
  aborts dstalk_init (huang)
- W9.10 host_api unit tests: tests/host_api_test.cpp, 8 cases (liu)

CEO oversight (preexisting bugs fixed during Wave 5 verification):
- lsp_plugin.cpp:449 forward decl mismatch (int vs void)
- tools_plugin.cpp:109 missing forward decl

Multi-agent collaboration framework:
- agents/WORKFLOW.md: 6-stage protocol, two-tier governance,
  prompt template, technical constraints registry

Build: cmake --build 0 error / 0 warning. Tests: 2/2 100% pass.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-27 05:39:10 +08:00

431 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.
// ============================================================================
// smoke_test.cpp — 插件化架构烟雾测试
// ============================================================================
// 测试: 核心初始化、插件加载、服务查询、file_io、session 功能
// ============================================================================
#include "dstalk/dstalk_host.h"
#include <cstring>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <string>
int main()
{
const auto dir = std::filesystem::temp_directory_path() / "dstalk-smoke-test";
std::filesystem::create_directories(dir);
// 写一个配置文件用于初始化
const auto config_path = dir / "config.toml";
{
std::ofstream config(config_path);
config << "[api]\n"
<< "provider = \"deepseek\"\n"
<< "base_url = \"https://api.deepseek.com/v1\"\n"
<< "api_key = \"test-key\"\n"
<< "model = \"deepseek-v4-pro\"\n";
}
// 初始化主机(会自动扫描 plugins/ 加载插件)
if (dstalk_init(config_path.string().c_str()) != 0) {
std::cerr << "dstalk_init failed\n";
return 1;
}
std::cout << "[OK] dstalk_init succeeded\n";
// 验证插件列表
{
char* list_json = nullptr;
int ret = dstalk_plugin_list(&list_json);
if (ret == 0 && list_json) {
std::cout << "[OK] plugins loaded: " << list_json << "\n";
dstalk_free(list_json);
} else {
std::cerr << "[WARN] dstalk_plugin_list returned: " << ret << "\n";
}
}
// 测试服务查询: file_io
auto* file_io = static_cast<const dstalk_file_io_service_t*>(
dstalk_service_query("file_io", 1));
if (file_io) {
std::cout << "[OK] file_io service found\n";
// 测试写入
const auto file_path = dir / "sample.txt";
constexpr const char* sample_content = "hello dstalk\nquote=\"yes\" tab=\t slash=\\";
if (file_io->write(file_path.string().c_str(), sample_content) == 0) {
std::cout << "[OK] file_io->write succeeded\n";
} else {
std::cerr << "[FAIL] file_io->write failed\n";
dstalk_shutdown();
return 1;
}
// 测试读取
char* content = nullptr;
if (file_io->read(file_path.string().c_str(), &content) == 0 && content) {
bool ok = std::strcmp(content, sample_content) == 0;
dstalk_free(content);
if (ok) {
std::cout << "[OK] file_io->read content matches\n";
} else {
std::cerr << "[FAIL] file_io->read content mismatch\n";
dstalk_shutdown();
return 1;
}
} else {
std::cerr << "[FAIL] file_io->read failed\n";
dstalk_shutdown();
return 1;
}
} else {
std::cerr << "[WARN] file_io service not found (plugin may not be in plugins/ dir)\n";
}
// 测试服务查询: session
auto* session = static_cast<const dstalk_session_service_t*>(
dstalk_service_query("session", 1));
if (session) {
std::cout << "[OK] session service found\n";
// 测试 session save/load
const auto session_path = dir / "session.jsonl";
const auto saved_path = dir / "session-saved.jsonl";
constexpr const char* session_content =
"{\"role\":\"user\",\"content\":\"line\\n\\\"quote\\\"\\\\slash\"}\n"
"{\"role\":\"assistant\",\"content\":\"ok\\tready\"}\n";
if (file_io) {
file_io->write(session_path.string().c_str(), session_content);
}
if (session->load(session_path.string().c_str()) == 0) {
std::cout << "[OK] session->load succeeded\n";
} else {
std::cerr << "[FAIL] session->load failed\n";
dstalk_shutdown();
return 1;
}
if (session->save(saved_path.string().c_str()) == 0) {
std::cout << "[OK] session->save succeeded\n";
} else {
std::cerr << "[FAIL] session->save failed\n";
dstalk_shutdown();
return 1;
}
// 验证保存的内容
if (file_io) {
char* saved = nullptr;
if (file_io->read(saved_path.string().c_str(), &saved) == 0 && saved) {
bool session_ok = std::strcmp(saved, session_content) == 0;
dstalk_free(saved);
if (session_ok) {
std::cout << "[OK] session content matches after save/load\n";
} else {
std::cerr << "[FAIL] session content mismatch after save/load\n";
dstalk_shutdown();
return 1;
}
}
}
// 测试 token 计数
int tokens = session->token_count();
std::cout << "[OK] session->token_count: " << tokens << "\n";
// 测试 history
int count = 0;
session->history(&count);
std::cout << "[OK] session->history count: " << count << "\n";
// 测试 clear
session->clear();
session->history(&count);
if (count == 0) {
std::cout << "[OK] session->clear succeeded\n";
}
} else {
std::cerr << "[WARN] session service not found\n";
}
// 测试服务查询: ai可能因为没有真实 API key 而失败,但服务应存在)
const char* ai_provider = dstalk_config_get("ai.provider");
if (!ai_provider) ai_provider = "ai.deepseek";
auto* ai = static_cast<const dstalk_ai_service_t*>(
dstalk_service_query(ai_provider, 1));
if (ai) {
std::cout << "[OK] ai service found\n";
} else {
std::cerr << "[WARN] ai service not found\n";
}
// 测试服务查询: config
auto* config_svc = static_cast<const dstalk_config_service_t*>(
dstalk_service_query("config", 1));
if (config_svc) {
std::cout << "[OK] config service found\n";
const char* val = config_svc->get("api.model");
if (val) {
std::cout << "[OK] config->get(\"api.model\"): " << val << "\n";
}
} else {
std::cerr << "[WARN] config service not found\n";
}
// 测试 dstalk_config_get主机级配置 API
const char* model = dstalk_config_get("api.model");
if (model) {
std::cout << "[OK] dstalk_config_get(\"api.model\"): " << model << "\n";
}
// 测试 dstalk_log
dstalk_log(DSTALK_LOG_INFO, "Smoke test completed successfully");
// ========================================================================
// 扩展测试块 C2: null-safety / 转义边界 / tools 调用链 / session 健壮性
// ========================================================================
std::cout << "\n--- Extended Smoke Tests (C2) ---\n";
// 提前查询 tools 服务,供后续测试块使用
auto* tools = static_cast<const dstalk_tools_service_t*>(
dstalk_service_query("tools", 1));
// ---- 1. Null-safety 测试 ----
// 对所有服务 API 传 null 参数,验证不崩溃且返回错误
std::cout << "\n[Block] Null-safety tests\n";
if (file_io) {
char* dummy = nullptr;
int ret = file_io->read(nullptr, &dummy);
if (ret != 0) {
std::cout << "[OK] file_io->read(nullptr, ...) returned error (" << ret << ")\n";
} else {
std::cerr << "[FAIL] file_io->read(nullptr, ...) should return error\n";
}
ret = file_io->write(nullptr, "test_content");
if (ret != 0) {
std::cout << "[OK] file_io->write(nullptr, ...) returned error (" << ret << ")\n";
} else {
std::cerr << "[FAIL] file_io->write(nullptr, ...) should return error\n";
}
// read 的 content 参数也为 null
ret = file_io->read("dummy_path", nullptr);
if (ret != 0) {
std::cout << "[OK] file_io->read(path, nullptr) returned error (" << ret << ")\n";
} else {
std::cerr << "[FAIL] file_io->read(path, nullptr) should return error\n";
}
// write 的 content 参数为 null
ret = file_io->write("dummy_path", nullptr);
if (ret != 0) {
std::cout << "[OK] file_io->write(path, nullptr) returned error (" << ret << ")\n";
} else {
std::cerr << "[FAIL] file_io->write(path, nullptr) should return error\n";
}
} else {
std::cerr << "[WARN] file_io service not available for null-safety tests\n";
}
if (session) {
session->add(nullptr);
std::cout << "[OK] session->add(nullptr) did not crash\n";
int ret = session->save(nullptr);
if (ret != 0) {
std::cout << "[OK] session->save(nullptr) returned error (" << ret << ")\n";
} else {
std::cerr << "[FAIL] session->save(nullptr) should return error\n";
}
ret = session->load(nullptr);
if (ret != 0) {
std::cout << "[OK] session->load(nullptr) returned error (" << ret << ")\n";
} else {
std::cerr << "[FAIL] session->load(nullptr) should return error\n";
}
} else {
std::cerr << "[WARN] session service not available for null-safety tests\n";
}
if (tools) {
char* result = tools->execute(nullptr, nullptr);
if (result) {
// 实现返回了错误字符串(如 {"error":"tool name is null"}),未崩溃
std::cout << "[OK] tools->execute(nullptr, nullptr) did not crash"
<< " (returned: " << result << ")\n";
dstalk_free(result);
} else {
std::cout << "[OK] tools->execute(nullptr, nullptr) returned null without crash\n";
}
} else {
std::cerr << "[WARN] tools service not available for null-safety tests\n";
}
if (config_svc) {
const char* val = config_svc->get(nullptr);
if (val == nullptr) {
std::cout << "[OK] config->get(nullptr) returned nullptr\n";
} else {
std::cerr << "[FAIL] config->get(nullptr) should return nullptr\n";
}
int ret = config_svc->set(nullptr, nullptr);
if (ret != 0) {
std::cout << "[OK] config->set(nullptr, nullptr) returned error (" << ret << ")\n";
} else {
std::cerr << "[FAIL] config->set(nullptr, nullptr) should return error\n";
}
// set 的 value 为 null
ret = config_svc->set("some.key", nullptr);
if (ret != 0) {
std::cout << "[OK] config->set(key, nullptr) returned error (" << ret << ")\n";
} else {
std::cerr << "[FAIL] config->set(key, nullptr) should return error\n";
}
} else {
std::cerr << "[WARN] config service not available for null-safety tests\n";
}
// ---- 2. 转义边界测试 ----
// 写入含特殊字符的内容,读回后验证内容一致
std::cout << "\n[Block] Escape boundary tests\n";
if (file_io) {
// 构造包含各种特殊字节的内容:
// - 实际换行符 (0x0A)
// - 实际双引号 (0x22)
// - 实际反斜杠 (0x5C)
// - 实际制表符 (0x09)
// - 以及字面上的 \n \" \\ \t 转义序列文本
constexpr const char* escape_content =
"line1\nline2\n"
"quote=\"yes\"\n"
"backslash=\\path\n"
"tab=\there\n"
"literal-escapes: newline=\\n quote=\\\" backslash=\\\\ tab=\\t\n"
"endswithbackslash\\\\\n"
"mixed\\t\\\"quoted\\\"\\\\path\n";
const auto escape_path = dir / "escape_test.txt";
if (file_io->write(escape_path.string().c_str(), escape_content) == 0) {
std::cout << "[OK] escape content write succeeded\n";
char* read_back = nullptr;
if (file_io->read(escape_path.string().c_str(), &read_back) == 0 && read_back) {
bool match = (std::strcmp(read_back, escape_content) == 0);
if (match) {
std::cout << "[OK] escape content round-trip matches"
<< " (length=" << std::strlen(escape_content) << ")\n";
} else {
std::cerr << "[FAIL] escape content round-trip mismatch\n"
<< " expected length: " << std::strlen(escape_content) << "\n"
<< " got length: " << std::strlen(read_back) << "\n";
}
dstalk_free(read_back);
} else {
std::cerr << "[FAIL] escape content read-back failed\n";
}
} else {
std::cerr << "[FAIL] escape content write failed\n";
}
} else {
std::cerr << "[WARN] file_io service not available for escape tests\n";
}
// ---- 3. Tools 调用链测试 ----
// 通过 tools->execute("file_read", ...) 验证内置工具可正确调用 file_io
std::cout << "\n[Block] Tools call chain tests\n";
if (tools && file_io) {
// 准备测试文件
const auto chain_path = dir / "tool_chain_test.txt";
constexpr const char* chain_content = "tools-chain-ok\n";
file_io->write(chain_path.string().c_str(), chain_content);
// 用 generic_string() 获取正斜杠路径,避免 JSON 中反斜杠转义问题
std::string generic_path = chain_path.generic_string();
std::string args_json = "{\"path\":\"" + generic_path + "\"}";
char* result = tools->execute("file_read", args_json.c_str());
if (result) {
std::cout << "[OK] tools->execute(\"file_read\", ...) returned result\n";
// 验证返回的 JSON 中包含原始文件内容
if (std::strstr(result, "tools-chain-ok")) {
std::cout << "[OK] tools->execute chain correctly called file_io\n";
} else {
std::cout << "[WARN] tools->execute result does not contain expected content: "
<< result << "\n";
}
dstalk_free(result);
} else {
std::cout << "[WARN] tools->execute(\"file_read\", ...) returned null"
<< " (tool may not be registered)\n";
}
// 额外测试:查询 tools 返回的工具列表
char* tools_json = tools->get_tools_json();
if (tools_json) {
std::cout << "[OK] tools->get_tools_json() returned: " << tools_json << "\n";
dstalk_free(tools_json);
} else {
std::cout << "[WARN] tools->get_tools_json() returned null\n";
}
} else {
std::cerr << "[WARN] tools or file_io service not available for chain tests\n";
}
// ---- 4. Session 健壮性测试 ----
// session->add(nullptr) 后验证 history 不变
// session->clear 后验证 token_count 为 0
std::cout << "\n[Block] Session robustness tests\n";
if (session) {
// 记录 add(nullptr) 前的 history 计数
int count_before = 0;
session->history(&count_before);
// 传 null 不应改变 history
session->add(nullptr);
int count_after = 0;
session->history(&count_after);
if (count_before == count_after) {
std::cout << "[OK] session->add(nullptr) did not change history count"
<< " (before=" << count_before << ", after=" << count_after << ")\n";
} else {
std::cerr << "[FAIL] session->add(nullptr) changed history count: "
<< count_before << " -> " << count_after << "\n";
}
// clear 后 token_count 应为 0
session->clear();
int tokens = session->token_count();
if (tokens == 0) {
std::cout << "[OK] session->token_count() == 0 after clear\n";
} else {
std::cerr << "[FAIL] session->token_count() == " << tokens
<< " after clear, expected 0\n";
}
} else {
std::cerr << "[WARN] session service not available for robustness tests\n";
}
// 清理
dstalk_shutdown();
std::cout << "[OK] dstalk_shutdown succeeded\n";
std::cout << "\n=== All smoke tests passed ===\n";
return 0;
}