summaryrefslogtreecommitdiffstats
path: root/tests/framework.hpp
diff options
context:
space:
mode:
authorMurilo Ijanc <[email protected]>2026-07-26 12:28:10 -0300
committerMurilo Ijanc <[email protected]>2026-07-26 12:28:10 -0300
commitb71faa6e95a0736f1ff171e006b171467b65db90 (patch)
tree7580c3ab515e8f50726dc68dbc240995ff4a1a4c /tests/framework.hpp
downloadcpp-b71faa6e95a0736f1ff171e006b171467b65db90.tar.gz
Initial importv0.1.0
Diffstat (limited to 'tests/framework.hpp')
-rw-r--r--tests/framework.hpp94
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