diff options
| author | Murilo Ijanc <[email protected]> | 2026-07-26 12:28:10 -0300 |
|---|---|---|
| committer | Murilo Ijanc <[email protected]> | 2026-07-26 12:28:10 -0300 |
| commit | b71faa6e95a0736f1ff171e006b171467b65db90 (patch) | |
| tree | 7580c3ab515e8f50726dc68dbc240995ff4a1a4c /tests/framework.hpp | |
| download | cpp-b71faa6e95a0736f1ff171e006b171467b65db90.tar.gz | |
Initial importv0.1.0
Diffstat (limited to 'tests/framework.hpp')
| -rw-r--r-- | tests/framework.hpp | 94 |
1 files changed, 94 insertions, 0 deletions
diff --git a/tests/framework.hpp b/tests/framework.hpp new file mode 100644 index 0000000..e3751cf --- /dev/null +++ b/tests/framework.hpp @@ -0,0 +1,94 @@ +#ifndef CEPIS_TESTS_FRAMEWORK_HPP +#define CEPIS_TESTS_FRAMEWORK_HPP + +#include <cmath> +#include <functional> +#include <sstream> +#include <stdexcept> +#include <string> + +namespace cepis::testing { + +/// Registers a test case. `CEPIS_TEST` does this for you. +void register_test(const char* name, std::function<void()> body); + +/// Runs every registered test. Returns the process exit code. +int run_all(); + +/// Thrown by the assertion helpers; caught by the runner. +class AssertionFailure : public std::runtime_error { + public: + explicit AssertionFailure(const std::string& message) : std::runtime_error(message) {} +}; + +struct Registrar { + Registrar(const char* name, std::function<void()> body) { register_test(name, std::move(body)); } +}; + +[[noreturn]] inline void fail(const std::string& detail, const char* file, int line) { + std::ostringstream message; + message << file << ':' << line << ": " << detail; + throw AssertionFailure(message.str()); +} + +inline void check_true(bool value, const char* text, const char* file, int line) { + if (!value) { + fail(std::string("expected ") + text, file, line); + } +} + +/// Comparison lives in a function rather than a macro so that the arguments are +/// bound as parameters: a `CHECK_EQ` on a member of a temporary would otherwise +/// compare against a reference that has already dangled. +template <typename Actual, typename Expected> +void check_equal(const Actual& actual, const Expected& expected, const char* text, const char* file, + int line) { + if (actual == expected) { + return; + } + std::ostringstream detail; + detail << "expected " << text << ", got [" << actual << "] vs [" << expected << ']'; + fail(detail.str(), file, line); +} + +inline void check_near(double actual, double expected, double tolerance, const char* text, + const char* file, int line) { + if (std::fabs(actual - expected) <= tolerance) { + return; + } + std::ostringstream detail; + detail << "expected " << text << ", got " << actual << " vs " << expected; + fail(detail.str(), file, line); +} + +} // namespace cepis::testing + +#define CEPIS_TEST(test_name) \ + static void test_name(); \ + static const ::cepis::testing::Registrar registrar_##test_name(#test_name, &(test_name)); \ + static void test_name() + +#define CHECK(condition) ::cepis::testing::check_true((condition), #condition, __FILE__, __LINE__) + +#define CHECK_EQ(actual, expected) \ + ::cepis::testing::check_equal((actual), (expected), #actual " == " #expected, __FILE__, __LINE__) + +#define CHECK_NEAR(actual, expected, tolerance) \ + ::cepis::testing::check_near((actual), (expected), (tolerance), #actual " ~= " #expected, \ + __FILE__, __LINE__) + +#define CHECK_THROWS(expression, exception_type) \ + do { \ + bool cepis_threw = false; \ + try { \ + (void)(expression); \ + } catch (const exception_type&) { \ + cepis_threw = true; \ + } \ + if (!cepis_threw) { \ + ::cepis::testing::fail("expected " #expression " to throw " #exception_type, __FILE__, \ + __LINE__); \ + } \ + } while (false) + +#endif // CEPIS_TESTS_FRAMEWORK_HPP |