// ============================================================================ // smoke_test.cpp — 插件化架构烟雾测试 // ============================================================================ // 测试: 核心初始化、插件加载、服务查询、file_io、session 功能 // ============================================================================ #include "dstalk/dstalk_host.h" #include #include #include #include #include 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( 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; std::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( 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; std::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( 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( 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( 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"; } std::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; }