sots-engine/tests/game_sim/check.h

72 lines
2.8 KiB
C++

// Minimal test helpers: CHECK macros, a scripted RNG, and a summary exit code.
#pragma once
#include <cmath>
#include <cstdint>
#include <cstdio>
#include <string>
#include <vector>
#include "game/sim/rng.h"
namespace simtest {
inline int& failures() { static int n = 0; return n; }
inline int& checks() { static int n = 0; return n; }
inline void report(bool ok, const char* expr, const char* file, int line, const std::string& detail) {
++checks();
if (ok) return;
++failures();
std::fprintf(stderr, "FAIL %s:%d %s%s%s\n", file, line, expr, detail.empty() ? "" : " -- ", detail.c_str());
}
template <class A, class B>
inline std::string pair_detail(const A& a, const B& b) {
return "got " + std::to_string(a) + ", expected " + std::to_string(b);
}
inline bool near(double a, double b, double eps = 1e-9) { return std::fabs(a - b) <= eps; }
inline int finish(const char* name) {
std::printf("%s: %d checks, %d failures\n", name, checks(), failures());
return failures() == 0 ? 0 : 1;
}
// RNG that replays scripted values and counts draws; exhausting the script is a failure.
struct ScriptedRng final : sots::sim::IRandom {
std::vector<float> floats;
std::vector<std::uint32_t> ints;
std::size_t fi = 0, ii = 0;
explicit ScriptedRng(std::vector<float> f = {}, std::vector<std::uint32_t> i = {})
: floats(std::move(f)), ints(std::move(i)) {}
float NextFloat() override {
if (fi >= floats.size()) { std::fprintf(stderr, "ScriptedRng: float script exhausted\n"); ++failures(); return 0.f; }
return floats[fi++];
}
std::uint32_t NextIntInclusive(std::uint32_t n) override {
if (ii >= ints.size()) { std::fprintf(stderr, "ScriptedRng: int script exhausted\n"); ++failures(); return 0; }
const std::uint32_t v = ints[ii++];
return n == 0xffffffffu ? v : v % (n + 1); // scripted value folded into [0, n]
}
std::size_t floatDraws() const { return fi; }
std::size_t intDraws() const { return ii; }
};
// Each operand is evaluated exactly once (operands may consume RNG draws).
template <class A, class B>
inline void check_eq(const A& a, const B& b, const char* expr, const char* file, int line) {
report(a == b, expr, file, line, pair_detail(a, b));
}
template <class A, class B>
inline void check_near(const A& a, const B& b, double eps, const char* expr, const char* file, int line) {
report(near(static_cast<double>(a), static_cast<double>(b), eps), expr, file, line, pair_detail(a, b));
}
} // namespace simtest
#define CHECK(expr) ::simtest::report((expr), #expr, __FILE__, __LINE__, "")
#define CHECK_EQ(a, b) ::simtest::check_eq((a), (b), #a " == " #b, __FILE__, __LINE__)
#define CHECK_NEAR(a, b, eps) ::simtest::check_near((a), (b), (eps), #a " ~= " #b, __FILE__, __LINE__)