// A ~100-line test harness. // // Pulling in GTest would mean either a system package we cannot assume or a // FetchContent download on every clean build. The tests here need registration, // assertions and a non-zero exit code; that is all this provides. #pragma once #include #include #include #include #include #include namespace ovgtest { struct Case { std::string name; std::function fn; }; std::vector ®istry(); struct Registrar { Registrar(std::string name, std::function fn); }; // Thrown by the CHECK macros; caught per-case by the runner. struct Failure { std::string message; }; // Thrown by SKIP: the environment cannot support the test (e.g. a sandbox that // transparently accepts every outbound connection, so nothing can be // blackholed). Reported distinctly from a pass so it cannot hide a regression. struct Skipped { std::string reason; }; [[noreturn]] void fail(const char *file, int line, const std::string &what); [[noreturn]] void skip(const std::string &reason); // Absolute path to tests/data, injected by CMake. std::string data_path(const std::string &leaf); int run_all(int argc, char **argv); // Best-effort stringification: streamable types get printed, everything else // (IpAddress, enums without an operator<<, ...) degrades to a placeholder // rather than failing to compile. template concept Streamable = requires(std::ostream &os, const T &v) { os << v; }; template std::string to_str(const T &v) { if constexpr (Streamable) { std::ostringstream os; os << v; return os.str(); } else { return ""; } } inline std::string to_str(bool v) { return v ? "true" : "false"; } inline std::string to_str(const std::string &v) { return "\"" + v + "\""; } } // namespace ovgtest #define OVG_TEST(name) \ static void name(); \ static ::ovgtest::Registrar ovg_reg_##name(#name, name); \ static void name() #define SKIP(reason) ::ovgtest::skip(reason) #define CHECK(cond) \ do { \ if (!(cond)) ::ovgtest::fail(__FILE__, __LINE__, "CHECK(" #cond ")"); \ } while (0) #define CHECK_EQ(a, b) \ do { \ const auto &ovg_a = (a); \ const auto &ovg_b = (b); \ if (!(ovg_a == ovg_b)) \ ::ovgtest::fail(__FILE__, __LINE__, \ "CHECK_EQ(" #a ", " #b ")\n left = " + \ ::ovgtest::to_str(ovg_a) + \ "\n right = " + ::ovgtest::to_str(ovg_b)); \ } while (0) #define CHECK_NE(a, b) \ do { \ if ((a) == (b)) \ ::ovgtest::fail(__FILE__, __LINE__, "CHECK_NE(" #a ", " #b ")"); \ } while (0) #define CHECK_LT(a, b) \ do { \ const auto &ovg_a = (a); \ const auto &ovg_b = (b); \ if (!(ovg_a < ovg_b)) \ ::ovgtest::fail(__FILE__, __LINE__, \ "CHECK_LT(" #a ", " #b ")\n left = " + \ ::ovgtest::to_str(ovg_a) + \ "\n right = " + ::ovgtest::to_str(ovg_b)); \ } while (0) #define CHECK_GT(a, b) CHECK_LT(b, a) #define CHECK_NEAR(a, b, tol) \ do { \ const double ovg_d = static_cast(a) - static_cast(b); \ if (ovg_d > (tol) || -ovg_d > (tol)) \ ::ovgtest::fail(__FILE__, __LINE__, \ "CHECK_NEAR(" #a ", " #b ")\n left = " + \ ::ovgtest::to_str(static_cast(a)) + \ "\n right = " + \ ::ovgtest::to_str(static_cast(b))); \ } while (0)