Pure formatting, no behaviour change. PKG landed .clang-format (Google base, 4-space indent, 100 cols); this brings util/ and the test harness into conformance so `clang-format --dry-run -Werror` is clean. Build and all six test binaries unchanged and green. Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01HPPSGhiArbvQgwC2DNiURS
155 lines
5.8 KiB
C++
155 lines
5.8 KiB
C++
// 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 <cstdio>
|
|
#include <exception>
|
|
#include <string>
|
|
#include <string_view>
|
|
#include <type_traits>
|
|
#include <vector>
|
|
|
|
namespace vt {
|
|
|
|
struct Case {
|
|
const char *name;
|
|
void (*fn)();
|
|
};
|
|
|
|
inline std::vector<Case> ®istry() {
|
|
static std::vector<Case> 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<int>(expr.size()),
|
|
expr.data());
|
|
if (!detail.empty())
|
|
std::fprintf(stderr, " [%.*s]", static_cast<int>(detail.size()), detail.data());
|
|
std::fprintf(stderr, "\n");
|
|
if (fatal) {
|
|
stats().current_fatal = true;
|
|
throw FatalAbort{};
|
|
}
|
|
}
|
|
|
|
template <class T>
|
|
std::string show(const T &v) {
|
|
if constexpr (std::is_same_v<T, std::string> || std::is_same_v<T, std::string_view> ||
|
|
std::is_same_v<T, const char *>) {
|
|
return std::string("\"") + std::string(v) + "\"";
|
|
} else if constexpr (std::is_same_v<T, bool>) {
|
|
return v ? "true" : "false";
|
|
} else if constexpr (std::is_arithmetic_v<T> || std::is_enum_v<T>) {
|
|
return std::to_string(static_cast<long long>(
|
|
static_cast<std::conditional_t<std::is_enum_v<T>, 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
|