// vtest.hpp — provisional micro test harness for lane CORE. // // PROVISIONAL. PKG/QA owns the framework decision (see core/docs/pkg-requests-m1.md P2); // this exists only so CORE isn't blocked. API is intentionally minimal so the swap to // GoogleTest/Catch2/doctest is mechanical: // // VT_TEST(name) { ... } -- define a test // VT_CHECK(cond) -- non-fatal assertion // VT_REQUIRE(cond) -- fatal (aborts this test) // VT_CHECK_EQ(a, b) / VT_CHECK_NE -- comparison with values printed on failure // VT_FAIL("msg") -- unconditional non-fatal failure // // Link one translation unit that also defines VT_MAIN before including this header, or // just link vtest_main.cpp. Each test binary returns non-zero if any check failed. #ifndef VDM_TESTS_VTEST_HPP #define VDM_TESTS_VTEST_HPP #include #include #include #include #include #include namespace vt { struct Case { const char *name; void (*fn)(); }; inline std::vector ®istry() { static std::vector r; return r; } struct Stats { int checks = 0; int failures = 0; bool current_fatal = false; }; inline Stats &stats() { static Stats s; return s; } struct FatalAbort : std::exception {}; struct Registrar { Registrar(const char *name, void (*fn)()) { registry().push_back({name, fn}); } }; inline void report(const char *file, int line, std::string_view expr, std::string_view detail, bool fatal) { stats().failures++; std::fprintf(stderr, " FAIL %s:%d %.*s", file, line, static_cast(expr.size()), expr.data()); if (!detail.empty()) std::fprintf(stderr, " [%.*s]", static_cast(detail.size()), detail.data()); std::fprintf(stderr, "\n"); if (fatal) { stats().current_fatal = true; throw FatalAbort{}; } } template std::string show(const T &v) { if constexpr (std::is_same_v || std::is_same_v || std::is_same_v) { return std::string("\"") + std::string(v) + "\""; } else if constexpr (std::is_same_v) { return v ? "true" : "false"; } else if constexpr (std::is_arithmetic_v || std::is_enum_v) { return std::to_string(static_cast( static_cast, long long, T>>(v))); } else { return ""; } } inline int run_all() { int failed_cases = 0; for (const auto &c : registry()) { int before = stats().failures; stats().current_fatal = false; std::fprintf(stderr, "[ RUN ] %s\n", c.name); try { c.fn(); } catch (const FatalAbort &) { // reported already } catch (const std::exception &e) { stats().failures++; std::fprintf(stderr, " FAIL uncaught exception: %s\n", e.what()); } catch (...) { stats().failures++; std::fprintf(stderr, " FAIL uncaught non-std exception\n"); } bool ok = stats().failures == before; std::fprintf(stderr, "[ %s ] %s\n", ok ? "PASS" : "FAIL", c.name); if (!ok) failed_cases++; } std::fprintf(stderr, "\n%zu cases, %d failed, %d checks, %d check failures\n", registry().size(), failed_cases, stats().checks, stats().failures); return failed_cases == 0 ? 0 : 1; } } // namespace vt #define VT_TEST(NAME) \ static void NAME##_impl(); \ static ::vt::Registrar NAME##_reg(#NAME, &NAME##_impl); \ static void NAME##_impl() #define VT_CHECK(COND) \ do { \ ::vt::stats().checks++; \ if (!(COND)) \ ::vt::report(__FILE__, __LINE__, #COND, {}, /*fatal=*/false); \ } while (0) #define VT_REQUIRE(COND) \ do { \ ::vt::stats().checks++; \ if (!(COND)) \ ::vt::report(__FILE__, __LINE__, #COND, {}, /*fatal=*/true); \ } while (0) #define VT_CHECK_EQ(A, B) \ do { \ ::vt::stats().checks++; \ auto &&_a = (A); \ auto &&_b = (B); \ if (!(_a == _b)) \ ::vt::report(__FILE__, __LINE__, #A " == " #B, \ ::vt::show(_a) + " vs " + ::vt::show(_b), false); \ } while (0) #define VT_CHECK_NE(A, B) \ do { \ ::vt::stats().checks++; \ auto &&_a = (A); \ auto &&_b = (B); \ if (!(_a != _b)) \ ::vt::report(__FILE__, __LINE__, #A " != " #B, \ ::vt::show(_a) + " vs " + ::vt::show(_b), false); \ } while (0) #define VT_FAIL(MSG) \ ::vt::report(__FILE__, __LINE__, "VT_FAIL", (MSG), /*fatal=*/false) #endif // VDM_TESTS_VTEST_HPP