/* * @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 #include #include #include #include #include #include #include // 引入 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(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(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(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; } }