#pragma once #include #include #include #include #include #include #include #include #include using plot::f32; using plot::f64; using plot::u32; using plot::u64; namespace plottest { struct Case { const char* name; std::function fn; }; inline std::vector& cases() { static std::vector c; return c; } struct Reg { Reg(const char* n, std::function f) { cases().push_back({n, std::move(f)}); } }; struct Failure { std::string msg; }; /// Compares two doubles within `eps`. `PLOT_CHECK_NEAR(a, b)` leaves it to the /// default below; pass an explicit tolerance for computed values. inline constexpr double kNearEps = 1e-9; inline void check_near(double a, double b, double eps, const char* file, int line, const char* lhs, const char* rhs) { if (!(std::fabs(a - b) <= eps)) { throw Failure{std::string(file) + ":" + std::to_string(line) + " CHECK_NEAR(" + lhs + ", " + rhs + ") got " + std::to_string(a) + " vs " + std::to_string(b)}; } } inline void check_near(double a, double b, const char* file, int line, const char* lhs, const char* rhs) { check_near(a, b, kNearEps, file, line, lhs, rhs); } inline int run_all() { int failed = 0; for (auto& c : cases()) { try { c.fn(); std::printf("[ ok ] %s\n", c.name); } catch (const Failure& f) { std::printf("[FAIL] %s: %s\n", c.name, f.msg.c_str()); ++failed; } } std::printf("%zu cases, %d failed\n", cases().size(), failed); return failed == 0 ? 0 : 1; } } // namespace plottest #define PLOT_TEST(name) \ static void name(); \ static ::plottest::Reg reg_##name(#name, name); \ static void name() #define PLOT_CHECK(cond) \ do { \ if (!(cond)) \ throw ::plottest::Failure{std::string(__FILE__) + ":" + std::to_string(__LINE__) + \ " CHECK(" #cond ")"}; \ } while (0) #define PLOT_CHECK_NEAR(a, b, ...) \ ::plottest::check_near((a), (b) __VA_OPT__(,) __VA_ARGS__, __FILE__, __LINE__, #a, #b)