/** * @file UnitTestFramework.hpp * @brief 极简单元测试框架(无异常、无 RTTI,与项目本体 ABI 一致)。 * * 设计取舍: * * **不引入第三方框架**(Catch2 / gtest 会带来几 MB 的静态库与异常依赖); * * 项目本体用 `-fno-exceptions` 构建,因此断言失败只能"记录 + 继续", * 不允许 `throw`; * * 每个 `.cpp` 用 `JDI_TEST(名字)` 注册用例,`Main.cpp` 按注册顺序执行。 * * 输出约定(供 CI / Agent 读取): * * 逐用例一行 `[RUN ] `,失败时一行 `[FAIL] (file:line)`; * * 结尾一行 `SUMMARY checks= failed= result=`。 * ⚠️ 所有输出保持 **ASCII**:控制台代码页为 GBK,中文只在注释里出现。 */ #ifndef __UNITTESTFRAMEWORK_HPP__ #define __UNITTESTFRAMEWORK_HPP__ #include #include #include #include namespace jdi { namespace test { /** @brief 一个注册在案的测试用例。 */ struct TestCase { const char* name = ""; /**< 用例名(打印用,建议 `模块.行为`)。 */ void (*function)() = nullptr; /**< 用例体。 */ }; /** @brief 全局用例表。 */ inline std::vector& Registry() { static std::vector registry; return registry; } /** @brief 断言总数。 */ inline int& TotalChecks() { static int value = 0; return value; } /** @brief 失败断言数。 */ inline int& FailedChecks() { static int value = 0; return value; } /** @brief 当前用例是否已失败(用于给出汇总提示)。 */ inline bool& CaseFailed() { static bool value = false; return value; } /** @brief 注册用例(由 `JDI_TEST` 宏在静态初始化期调用)。 */ inline bool Register(const char* name, void (*function)()) { Registry().push_back(TestCase{name, function}); return true; } /** @brief 记录一次布尔断言。 */ inline void Check(bool ok, const char* expression, const char* file, int line) { ++TotalChecks(); if (!ok) { ++FailedChecks(); CaseFailed() = true; std::printf(" [FAIL] %s (%s:%d)\n", expression, file, line); std::fflush(stdout); } } /** @brief 记录一次"带说明"的布尔断言(说明只用于失败时定位)。 */ inline void CheckWithNote(bool ok, const char* note, const char* file, int line) { ++TotalChecks(); if (!ok) { ++FailedChecks(); CaseFailed() = true; std::printf(" [FAIL] %s (%s:%d)\n", note, file, line); std::fflush(stdout); } } /** @brief 记录一次整数相等断言(失败时打印实际值/期望值)。 */ inline void CheckEqual(const long long actual, const long long expected, const char* note, const char* file, int line) { ++TotalChecks(); if (actual != expected) { ++FailedChecks(); CaseFailed() = true; std::printf(" [FAIL] %s: actual=%lld expected=%lld (%s:%d)\n", note, actual, expected, file, line); std::fflush(stdout); } } /** @brief 记录一次浮点近似断言。 */ inline void CheckNear(const double actual, const double expected, const double tolerance, const char* note, const char* file, int line) { const bool bothNaN = (actual != actual) && (expected != expected); const double delta = (actual > expected) ? (actual - expected) : (expected - actual); ++TotalChecks(); if (!bothNaN && !(delta <= tolerance)) { ++FailedChecks(); CaseFailed() = true; std::printf(" [FAIL] %s: actual=%.17g expected=%.17g tolerance=%.3g (%s:%d)\n", note, actual, expected, tolerance, file, line); std::fflush(stdout); } } /** @brief 记录一次"逐位相同"断言(用于数值语义回归)。 */ inline void CheckBitEqual(const double actual, const double expected, const char* note, const char* file, int line) { std::uint64_t a = 0; std::uint64_t b = 0; static_assert(sizeof(a) == sizeof(actual), "double must be 8 bytes"); __builtin_memcpy(&a, &actual, sizeof(a)); __builtin_memcpy(&b, &expected, sizeof(b)); ++TotalChecks(); if (a != b) { ++FailedChecks(); CaseFailed() = true; std::printf(" [FAIL] %s: actual=%.17g (0x%016llX) expected=%.17g (0x%016llX) (%s:%d)\n", note, actual, static_cast(a), expected, static_cast(b), file, line); std::fflush(stdout); } } /** @brief 记录一次宽字符串相等断言。 */ inline void CheckWideEqual(const std::wstring& actual, const std::wstring& expected, const char* note, const char* file, int line) { ++TotalChecks(); if (actual != expected) { ++FailedChecks(); CaseFailed() = true; std::printf(" [FAIL] %s: actual=\"%ls\" expected=\"%ls\" (%s:%d)\n", note, actual.c_str(), expected.c_str(), file, line); std::fflush(stdout); } } } // namespace test } // namespace jdi /** @brief 定义一个测试用例(自动注册)。 */ #define JDI_TEST(case_name) \ static void case_name(); \ [[maybe_unused]] static const bool case_name##_registered_ = \ ::jdi::test::Register(#case_name, &case_name); \ static void case_name() /** @brief 布尔断言。 */ #define JDI_CHECK(expression) ::jdi::test::Check((expression), #expression, __FILE__, __LINE__) /** @brief 带说明的布尔断言(说明为裸字符串)。 */ #define JDI_REQUIRE(note, expression) \ ::jdi::test::CheckWithNote((expression), (note), __FILE__, __LINE__) /** @brief 整数相等断言。 */ #define JDI_CHECK_EQ(actual, expected) \ ::jdi::test::CheckEqual(static_cast(actual), static_cast(expected), \ #actual " == " #expected, __FILE__, __LINE__) /** @brief 浮点近似断言。 */ #define JDI_CHECK_NEAR(actual, expected, tolerance) \ ::jdi::test::CheckNear((actual), (expected), (tolerance), #actual " ~ " #expected, __FILE__, __LINE__) /** @brief 浮点逐位相等断言(数值语义回归专用)。 */ #define JDI_CHECK_BIT_EQUAL(actual, expected) \ ::jdi::test::CheckBitEqual((actual), (expected), #actual " bit== " #expected, __FILE__, __LINE__) /** @brief 宽字符串相等断言。 */ #define JDI_CHECK_WIDE_EQ(actual, expected) \ ::jdi::test::CheckWideEqual((actual), (expected), #actual " == " #expected, __FILE__, __LINE__) #endif // __UNITTESTFRAMEWORK_HPP__