-
Notifications
You must be signed in to change notification settings - Fork 0
/
tinytest.h
380 lines (332 loc) · 13.3 KB
/
tinytest.h
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
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
#ifndef TINY_TEST_H
#define TINY_TEST_H
//------------------------------------------------------------------------------
//
// TinyTest information
//
//------------------------------------------------------------------------------
#define TINY_TEST_NAME "TinyTest"
#define TINY_TEST_VERSION "0.4.0"
//------------------------------------------------------------------------------
//
// You can define following macros by your own to customize TinyTest:
// - TINY_TEST_MAX_TESTS - maximum number of tests
// - TINY_TEST_PRINTF(format, ...) - printing function
//
//------------------------------------------------------------------------------
#ifndef TINY_TEST_MAX_TESTS
#define TINY_TEST_MAX_TESTS 1000
#endif
#ifndef TINY_TEST_PRINTF
#include <stdio.h>
#define TINY_TEST_PRINTF(format, ...) printf(format, __VA_ARGS__)
#endif
//------------------------------------------------------------------------------
//
// Logs
//
//------------------------------------------------------------------------------
/*
* Set color formatting for the text.
*/
#define TINY_COLOR(color, text) color text "\x1b[0m"
#define TINY_DEFAULT "\x1b[0m"
#define TINY_GRAY "\x1b[90m"
#define TINY_RED "\x1b[91m"
#define TINY_GREEN "\x1b[92m"
#define TINY_YELLOW "\x1b[93m"
#define TINY_BLUE "\x1b[94m"
#define TINY_MAGENTA "\x1b[95m"
#define TINY_CYAN "\x1b[96m"
/*
* Print log.
*
* Arguments:
* - color - log color
* - format - printf format
* - args... [opt] - arguments
*/
#define TINY_LOG(color, ...) \
_TT_CHOOSE_WRAPPER(_TT_TINY_LOG, _TT_AT_LEAST_1_ARG(__VA_ARGS__), color, __VA_ARGS__)
//------------------------------------------------------------------------------
//
// Tests
//
//------------------------------------------------------------------------------
/*
* Create a basic test.
* It's automatically added to the tests queue (see: TINY_TEST_RUN_ALL). It can
* be also run as a separate single tests (see: TINY_TEST_RUN_TEST).
*
* Arguments:
* - test_name
*/
#define TINY_TEST(test_name) \
static void test_name(tinytest::TestResult&); \
_TT_APPEND_TEST(test_name, test_name); \
static void test_name(tinytest::TestResult& _tt_result)
/*
* Create a subtest.
* To run it, it has to be called from an other test. It is used to divide
* a test to smaller independent parts.
*
* Arguments:
* - subtest_name
* - subtest_args...
*/
#define TINY_SUBTEST(...) \
_TT_CHOOSE_WRAPPER(_TT_TINY_SUBTEST, _TT_AT_LEAST_1_ARG(__VA_ARGS__), __VA_ARGS__)
/*
* Run a subtest.
* It can be used only in a test body.
*
* Arguments:
* - subtest_name
* - subtest_args...
*/
#define TINY_RUN_SUBTEST(...) \
_TT_CHOOSE_WRAPPER(_TT_TINY_RUN_SUBTEST, _TT_AT_LEAST_1_ARG(__VA_ARGS__), __VA_ARGS__)
/*
* Create a parametrized test.
*
* Arguments:
* - ptest_name
* - ptest_args_printf_format
* - ptest_args...
*/
#define TINY_PTEST(ptest_name, ptest_args_printf_format, ...) \
static const char* _tt_ptest_args_format_##ptest_name = ptest_args_printf_format; \
static void ptest_name(tinytest::TestResult& _tt_result, __VA_ARGS__)
/*
* Create a test that is an instance of the given ptest.
*
* Arguments:
* - ptest_name
* - ptest_args...
*/
#define TINY_PTEST_INSTANCE(ptest_name, ...) \
static void _TT_CONCAT(_tt_ptest_instance_##ptest_name, __LINE__)(tinytest::TestResult&); \
_TT_APPEND_TEST(ptest_name, _TT_CONCAT(_tt_ptest_instance_##ptest_name, __LINE__), _tt_ptest_args_format_##ptest_name, __VA_ARGS__); \
static void _TT_CONCAT(_tt_ptest_instance_##ptest_name, __LINE__)(tinytest::TestResult& result) { \
ptest_name(result, __VA_ARGS__); \
}
/*
* Test force fail
*
* Arguments:
* - format - message printf format
* - args... [opt] - arguments
*/
#define TINY_FAIL(...) \
do { \
TINY_LOG(TINY_RED, __VA_ARGS__); \
_tt_result.passed = false; \
} while (false)
/*
* Check if actual value is equal expected value.
*
* Arguments:
* - expected - expected value
* - actual - actual value
*/
#define TINY_CHECK(expected, actual) \
do { \
++_tt_result.checks; \
if ((expected) != (actual)) { \
TINY_FAIL("values are different (expected = %d, actual = %d)", (expected), (actual)); \
++_tt_result.failed_checks; \
} \
} while (false)
/*
* Check if actual value is not differ from expected value by more then epsilon.
*
* Arguments:
* - expected - expected value
* - actual - actual value
* - epsilon - maximum acceptable difference
*/
#define TINY_CHECK_EPS(expected, actual, epsilon) \
do { \
++_tt_result.checks; \
if (_TT_FABS((expected) - (actual)) > (epsilon)) { \
TINY_FAIL("values differ by more then %f (expected = %f, actual = %f)", (epsilon), (expected), (actual)); \
++_tt_result.failed_checks; \
} \
} while (false)
/*
* Check if every value in the actual array is equal to corresponding value
* in the expected array.
*
* Arguments:
* - expected - expected array pointer
* - actual - actual array pointer
* - elements - number of array elements
*/
#define TINY_CHECK_ARRAY(expected, actual, elements) \
do { \
++_tt_result.checks; \
bool failed = false; \
for (unsigned i = 0; i < (unsigned)(elements); ++i) \
if ((expected)[i] != (actual)[i]) { \
TINY_FAIL("memories differ at %u-th position (expected = %d, actual = %d)", i, (expected)[i], (actual)[i]); \
failed = true; \
} \
if (failed) \
++_tt_result.failed_checks; \
} while (false)
/*
* Check if every value in the actual array is not differ from corresponding value
* in the expected array by more then epsilon.
*
* Arguments:
* - expected - expected array pointer
* - actual - actual array pointer
* - epsilon - maximum acceptable difference
* - elements - number of array elements
*/
#define TINY_CHECK_ARRAY_EPS(expected, actual, elements, epsilon) \
do { \
++_tt_result.checks; \
bool failed = false; \
for (unsigned i = 0; i < (unsigned)(elements); ++i) \
if (_TT_FABS((expected)[i] - (actual)[i]) > (epsilon)) { \
TINY_FAIL("memories differ at %u-th position by more then %f (expected = %f, actual = %f)", i, (epsilon), (expected)[i], (actual)[i]); \
failed = true; \
} \
if (failed) \
++_tt_result.failed_checks; \
} while (false)
/*
* Run test.
*
* Arguments:
* - test_name - test name
*/
#define TINY_TEST_RUN_TEST(test_name) \
tinytest::run_test(_tt_test_##test_name)
/*
* Run all tests in the test queue.
*/
#define TINY_TEST_RUN_ALL() \
tinytest::run_all_tests()
//------------------------------------------------------------------------------
//
// IMPLEMENTATION DETAILS
//
// It's not important for you if you only want to use TinyTest.
//
//------------------------------------------------------------------------------
// Floating point absolute value
#define _TT_FABS(a) ((a) < 0 ? -(a) : (a))
// Workaround for unsupported optional arguments in variadic macros. Works if
// number of arguments passed in __VA_ARGS__ is not greater then 100.
#define _TT_AT_LEAST_1_ARG_INNER(_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, \
ARG, ...) ARG
#define _TT_AT_LEAST_1_ARG(...) \
_TT_AT_LEAST_1_ARG_INNER(__VA_ARGS__, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, \
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, \
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, \
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, \
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0)
#define _TT_CHOOSE_WRAPPER_INNER(name, more_then_one, ...) \
name##_##more_then_one(__VA_ARGS__)
#define _TT_CHOOSE_WRAPPER(name, version, ...) \
_TT_CHOOSE_WRAPPER_INNER(name, version, __VA_ARGS__)
#define _TT_CONCAT_INNER(a, b) a ## b
#define _TT_CONCAT(a, b) _TT_CONCAT_INNER(a, b)
#define _TT_TINY_LOG_0(color, format) TINY_TEST_PRINTF("[ ] " TINY_COLOR(color, "Line #%d: " format "\n"), __LINE__)
#define _TT_TINY_LOG_1(color, format, ...) TINY_TEST_PRINTF("[ ] " TINY_COLOR(color, "Line #%d: " format "\n"), __LINE__, __VA_ARGS__)
#define _TT_TINY_SUBTEST_0(subtest_name) static void subtest_name(tinytest::TestResult& _tt_result)
#define _TT_TINY_SUBTEST_1(subtest_name, ...) static void subtest_name(tinytest::TestResult& _tt_result, __VA_ARGS__)
#define _TT_TINY_RUN_SUBTEST_0(subtest_name) subtest_name(_tt_result)
#define _TT_TINY_RUN_SUBTEST_1(subtest_name, ...) subtest_name(_tt_result, __VA_ARGS__)
#define _TT_APPEND_TEST_0(test_name, test_body) \
static void _tt_test_##test_body(tinytest::TestResult&); \
static tinytest::TestAppender _tt_appender_##test_body(_tt_test_##test_body); \
static void _tt_test_##test_body(tinytest::TestResult& result) { \
TINY_TEST_PRINTF("[ TEST ] " #test_name " -- " __FILE__ ":%d\n", __LINE__); \
test_body(result); \
if (result.passed) \
TINY_TEST_PRINTF("[------] " TINY_COLOR(TINY_GREEN, "Passed (%u/%u)\n"), result.checks, result.checks); \
else \
TINY_TEST_PRINTF("[------] " TINY_COLOR(TINY_RED, "Failed (%u/%u)\n"), result.failed_checks, result.checks); \
}
#define _TT_APPEND_TEST_1(test_name, test_body, test_args_format, ...) \
static void _tt_test_##test_body(tinytest::TestResult&); \
static tinytest::TestAppender _tt_appender_##test_body(_tt_test_##test_body); \
static void _tt_test_##test_body(tinytest::TestResult& result) { \
TINY_TEST_PRINTF("[ TEST ] " #test_name "%c", '('); \
TINY_TEST_PRINTF(test_args_format, __VA_ARGS__); \
TINY_TEST_PRINTF(") -- " __FILE__ ":%d\n", __LINE__); \
test_body(result); \
if (result.passed) \
TINY_TEST_PRINTF("[------] " TINY_COLOR(TINY_GREEN, "Passed (%u/%u)\n"), result.checks, result.checks); \
else \
TINY_TEST_PRINTF("[------] " TINY_COLOR(TINY_RED, "Failed (%u/%u)\n"), result.failed_checks, result.checks); \
}
#define _TT_APPEND_TEST(test_name, ...) _TT_CHOOSE_WRAPPER(_TT_APPEND_TEST, _TT_AT_LEAST_1_ARG(__VA_ARGS__), test_name, __VA_ARGS__)
struct tinytest {
struct TestResult {
bool passed;
unsigned checks;
unsigned failed_checks;
};
typedef void(*TestBody)(TestResult&);
struct TestAppender {
TestAppender(TestBody test_body) {
*tinytest::all_tests_it++ = test_body;
}
};
static TestResult run_test(TestBody body) {
TestResult result = {true, 0, 0};
body(result);
return result;
}
static bool run_all_tests() {
TINY_TEST_PRINTF(
"================================================================================\n"
TINY_TEST_NAME " v" TINY_TEST_VERSION "\n"
"================================================================================%c",
'\n');
unsigned passed = 0;
unsigned failed = 0;
unsigned total_checks = 0;
unsigned total_failed_checks = 0;
for (const TestBody* it = all_tests; it != all_tests_it; ++it) {
if (it != all_tests)
TINY_TEST_PRINTF("%c", '\n');
TestResult result = {true, 0, 0};
(*it)(result);
total_checks += result.checks;
total_failed_checks += result.failed_checks;
if (result.passed)
++passed;
else
++failed;
}
TINY_TEST_PRINTF(
"================================================================================\n"
TINY_COLOR(TINY_GREEN, "Passed %u (%u/%u)\n")
TINY_COLOR(TINY_RED, "Failed %u (%u/%u)\n")
"================================================================================\n",
passed, total_checks - total_failed_checks, total_checks,
failed, total_failed_checks, total_checks);
if (failed == 0)
TINY_TEST_PRINTF(TINY_COLOR(TINY_GREEN, "All tests passed!\n%c"), '\n');
else
TINY_TEST_PRINTF(TINY_COLOR(TINY_RED, "%d %s failed!\n\n"), failed, (failed == 1 ? "test" : "tests"));
return failed == 0;
}
private:
static TestBody all_tests[TINY_TEST_MAX_TESTS];
static TestBody* all_tests_it;
};
tinytest::TestBody tinytest::all_tests[TINY_TEST_MAX_TESTS] = {};
tinytest::TestBody* tinytest::all_tests_it = tinytest::all_tests;
#endif