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
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
|
// Stolen from hyprutils
#pragma once
#include <cmath>
#include <string>
// TODO: localize these global variables
inline std::string HIS = "";
inline std::string WLDISPLAY = "";
namespace Colors {
constexpr const char* RED = "\x1b[31m";
constexpr const char* GREEN = "\x1b[32m";
constexpr const char* YELLOW = "\x1b[33m";
constexpr const char* BLUE = "\x1b[34m";
constexpr const char* MAGENTA = "\x1b[35m";
constexpr const char* CYAN = "\x1b[36m";
constexpr const char* RESET = "\x1b[0m";
};
// =================================
// TEST CASES DEFINITION
// =================================
class CTestCase {
public:
CTestCase() = default;
CTestCase(const CTestCase&) = delete; // Test cases probably should not be copied
bool failed = false;
virtual ~CTestCase() = default;
// TODO: `test` will be protected
virtual void test() = 0;
};
#define TEST_CASE(name) \
namespace { \
class TestCase_##name : public CTestCase { \
public: \
void test() override; \
}; \
} \
\
static TestCase_##name test_case_##name{}; \
static auto register_test_case_##name = [] { \
/* `TEST_CASES_STORAGE` must be defined by the caller */ \
TEST_CASES_STORAGE.emplace(#name, test_case_##name); \
return 1; \
}(); \
\
void TestCase_##name::test()
#define SUBTEST(name, ...) \
namespace { \
class Subtest_##name { \
public: \
bool failed = false; \
\
void main(__VA_ARGS__); \
}; \
} \
\
void Subtest_##name::main(__VA_ARGS__)
#define CALL_SUBTEST(name, ...) \
do { \
auto subtest_##name = Subtest_##name{}; \
subtest_##name.main(__VA_ARGS__); \
if (subtest_##name.failed) { \
NLog::log("{}Subtest {}({}) failed", Colors::RED, #name, #__VA_ARGS__); \
this->failed = true; \
return; \
} \
} while (0)
// =================================
// IN-TEST MACROS
// =================================
/// Marks the test as failed without terminating it
#define MARK_TEST_FAILED_SILENT() this->failed = true
/// Prints a failure message and makrs the test as failed without terminating it
#define MARK_TEST_FAILED(fmt, ...) \
do { \
NLog::log("{}Failed:{} " fmt ". Source: {}@{}.", Colors::RED, Colors::RESET __VA_OPT__(, ) __VA_ARGS__, __FILE__, __LINE__); \
MARK_TEST_FAILED_SILENT(); \
} while (0)
/// Terminates the test execution and marks it as failed
#define FAIL_TEST_SILENT() \
do { \
MARK_TEST_FAILED_SILENT(); \
return; \
} while (0)
/// Prints a failure message, terminates the test execution, and marks it as failed
#define FAIL_TEST(fmt, ...) \
do { \
MARK_TEST_FAILED(fmt, __VA_ARGS__); \
return; \
} while (0)
#define LOG_OK(fmt, ...) \
do { \
NLog::log("{}OK:{} " fmt ". Source: {}@{}", Colors::GREEN, Colors::RESET, __VA_ARGS__, __FILE__, __LINE__); \
} while (0)
// In case of failure:
// - All the `EXPECT*` macros will print a message and mark the test as failed without terminating its execution;
// - All the `ASSERT*` macros will print a message, mark the test as failed, and terminate it.
//
// `OK` acts like `ASSERT_OK`.
#define EXPECT_MAX_DELTA(expr, desired, delta) \
if (const auto RESULT = expr; std::abs(RESULT - (desired)) > delta) { \
MARK_TEST_FAILED("{}, expected max delta of {}, got delta {} ({} - {})", #expr, delta, (RESULT - (desired)), RESULT, desired); \
} else { \
LOG_OK("{}. Got {}", #expr, (RESULT - (desired))); \
}
#define EXPECT(expr, val) \
do { \
if (const auto RESULT = expr; RESULT != (val)) { \
MARK_TEST_FAILED("{}, expected {}, got {}", #expr, val, RESULT); \
} else { \
LOG_OK("{}. Got {}", #expr, val); \
} \
} while (0)
#define EXPECT_NOT(expr, val) \
if (const auto RESULT = expr; RESULT == (val)) { \
MARK_TEST_FAILED("{}, expected not {}, got {}", #expr, val, RESULT); \
} else { \
LOG_OK("{}. Got {}", #expr, val); \
}
#define EXPECT_VECTOR2D(expr, val) \
do { \
const auto& RESULT = expr; \
const auto& ASSERTED = val; \
if (!(std::abs(RESULT.x - ASSERTED.x) < 1e-6 && std::abs(RESULT.y - ASSERTED.y) < 1e-6)) { \
MARK_TEST_FAILED("{}, expected [{}, {}], got [{}, {}]", #expr, ASSERTED.x, ASSERTED.y, RESULT.x, RESULT.y); \
} else { \
LOG_OK("{}. Got [{}, {}].", #expr, RESULT.x, RESULT.y); \
} \
} while (0)
// String check macros below use a special error message layout, putting the Source reference before `haystack`,
// since `haystack` may be a large multi-line string. Thus, they use bare `NLog::log` + `MARK_TEST_FAILED_SILENT`.
#define EXPECT_CONTAINS(haystack, needle) \
if (const auto ASSERTED = needle; !std::string{haystack}.contains(ASSERTED)) { \
NLog::log("{}Failed: {}{} should contain {} but doesn't. Source: {}@{}. Haystack is:\n{}", Colors::RED, Colors::RESET, #haystack, #needle, __FILE__, __LINE__, \
std::string{haystack}); \
MARK_TEST_FAILED_SILENT(); \
} else { \
LOG_OK("{} contains {}.", #haystack, ASSERTED); \
}
#define EXPECT_NOT_CONTAINS(haystack, needle) \
if (std::string{haystack}.contains(needle)) { \
NLog::log("{}Failed: {}{} shouldn't contain {} but does. Source: {}@{}. Haystack is:\n{}", Colors::RED, Colors::RESET, #haystack, #needle, __FILE__, __LINE__, \
std::string{haystack}); \
MARK_TEST_FAILED_SILENT(); \
} else { \
LOG_OK("{} doesn't contain {}.", #haystack, #needle); \
}
#define EXPECT_STARTS_WITH(str, what) \
if (!std::string{str}.starts_with(what)) { \
NLog::log("{}Failed: {}{} should start with {} but doesn't. Source: {}@{}. String is:\n{}", Colors::RED, Colors::RESET, #str, #what, __FILE__, __LINE__, \
std::string{str}); \
MARK_TEST_FAILED_SILENT(); \
} else { \
LOG_OK("{} starts with {}.", #str, #what); \
}
#define EXPECT_COUNT_STRING(str, what, no) \
if (Tests::countOccurrences(str, what) != no) { \
NLog::log("{}Failed: {}{} should contain {} {} times, but doesn't. Source: {}@{}. String is:\n{}", Colors::RED, Colors::RESET, #str, #what, no, __FILE__, __LINE__, \
std::string{str}); \
MARK_TEST_FAILED_SILENT(); \
} else { \
LOG_OK("{} contains {} {} times.", #str, #what, no); \
}
#define EXPECT_OK(x) EXPECT(x, "ok")
#define ASSERT_MAX_DELTA(expr, desired, delta) \
do { \
EXPECT_MAX_DELTA(expr, desired, delta); \
if (this->failed) \
return; \
} while (0)
#define ASSERT(expr, val) \
do { \
EXPECT(expr, val); \
if (this->failed) \
return; \
} while (0)
#define ASSERT_NOT(expr, val) \
do { \
EXPECT_NOT(expr, val); \
if (this->failed) \
return; \
} while (0)
#define ASSERT_VECTOR2D(expr, val) \
do { \
EXPECT_VECTOR2D(expr, val); \
if (this->failed) \
return; \
} while (0)
#define ASSERT_CONTAINS(haystack, needle) \
do { \
EXPECT_CONTAINS(haystack, needle); \
if (this->failed) \
return; \
} while (0)
#define ASSERT_NOT_CONTAINS(haystack, needle) \
do { \
EXPECT_NOT_CONTAINS(haystack, needle); \
if (this->failed) \
return; \
} while (0)
#define ASSERT_STARTS_WITH(str, what) \
do { \
EXPECT_STARTS_WITH(str, what); \
if (this->failed) \
return; \
} while (0)
#define ASSERT_COUNT_STRING(str, what, no) \
do { \
EXPECT_COUNT_STRING(str, what, no); \
if (this->failed) \
return; \
} while (0)
#define OK(x) ASSERT(x, "ok")
|