1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
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
|