-
Notifications
You must be signed in to change notification settings - Fork 0
/
atest.h
240 lines (217 loc) · 7.75 KB
/
atest.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
//
//
// C mimimalist unit testing framework
// Alex Vedeneev (c) 2023 MIT License
//
//
#ifndef __ATEST_H
#define __ATEST_H
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <float.h>
#ifndef NAN
#error "NAN is undefined on this platform"
#endif
#ifndef FLT_EPSILON
#error "FLT_EPSILON is undefined on this platform"
#endif
#define ATEST_AMSG_MAX_LEN 512 // max length for aassertf() string formatting
//
// atest.h settings, you can alter them in main() function, for example replace out_stream by fopen() file
//
struct __ATestContext_s {
FILE* out_stream; // by default uses stdout, however you can replace it by any FILE* stream
int tests_run; // number of tests run
int tests_failed; // number of tests failed
int verbosity; // verbosity level: 0 - only stats, 1 - short test results (. or F) + stats, 2 - all tests results, 3 - include logs
char _str_buf[ATEST_AMSG_MAX_LEN]; // Internal buffer for aassertf() string formatting
};
extern struct __ATestContext_s __ATestContext;
/*
*
* ASSERTS
*
*/
//
// atassert( condition ) - simple assert, failes when condition false
//
#define atassert(A) do {\
if(!(A)) { \
return __ATEST_LOG_ERR(#A);} \
} while(0);
//
// atassertf( condition, format_string, ...) - assert with printf() style formatting error message
//
#define atassertf(A, M, ...) do { \
if(!(A)) { \
snprintf(__ATestContext._str_buf, ATEST_AMSG_MAX_LEN-1, __ATEST_LOG_ERR(M), ##__VA_ARGS__); \
return __ATestContext._str_buf; } \
} while(0);
//
// atassert_eqs(char * actual, char * expected) - compares strings via strcmp(), (NULL friendly)
//
#define atassert_eqs(ac, ex) do {\
int __at_assert_passed = 1; \
if ((ac) == NULL && (ex) != NULL) {__at_assert_passed = 0; } \
else if ((ac) != NULL && (ex) == NULL) {__at_assert_passed = 0;} \
else if ((ac) == NULL && (ex) == NULL) {__at_assert_passed = 1; } \
else if (strcmp((ac), (ex)) != 0) {__at_assert_passed = 0; } \
if (__at_assert_passed == 0){\
snprintf(__ATestContext._str_buf, ATEST_AMSG_MAX_LEN-1, __ATEST_LOG_ERR("'%s' != '%s'"), (char*)(ac), (char*)(ex)); \
return __ATestContext._str_buf; } \
} while(0);
//
// atassert_eqi(int actual, int expected) - compares equality of integers
//
#define atassert_eqi(ac, ex) do {\
if ((ac) != (ex)) {\
snprintf(__ATestContext._str_buf, ATEST_AMSG_MAX_LEN-1, __ATEST_LOG_ERR("%d != %d"), (ac), (ex)); \
return __ATestContext._str_buf; } \
} while(0);
//
// atassert_eqf(double actual, double expected) - compares equiality of floating point numbers, NAN friendly, i.e. atassert_eqf(NAN, NAN) -- passes
//
#define atassert_eqf(ac, ex) do {\
int __at_assert_passed = 1; \
if (isnan(ac) && !isnan(ex)) __at_assert_passed = 0; \
else if (!isnan(ac) && isnan(ex)) __at_assert_passed = 0; \
else if (isnan(ac), isnan(ex)) __at_assert_passed = 1; \
else if ( fabs( (ac) - (ex) ) > (double)FLT_EPSILON) __at_assert_passed = 0; \
if (__at_assert_passed == 0) {\
snprintf(__ATestContext._str_buf, ATEST_AMSG_MAX_LEN-1, __ATEST_LOG_ERR("%f != %f (diff: %0.10f epsilon: %0.10f)"), (ac), (ex), ((ac)-(ex)), (double)FLT_EPSILON); \
return __ATestContext._str_buf; } \
} while(0);
//
// atlogf() - simple log message with additional formatting and information about what file and line of code was caller of the alogf()
//
#define atlogf(M, ...) if(__ATestContext.verbosity >= 3) {fprintf(__atest_stream, __ATEST_LOG(M), ##__VA_ARGS__);}
/*
*
* TEST FUNCTIONS
*
*/
//
// ATEST_SETUP_F() is mandatory for every test file,
// only one instance of this function is allowed.
//
// This function is launched before every test is run.
//
// ATEST_SETUP_F() may return pointer to atest_shutdown function, or NULL if no such function.
//
// // Bacic function, no setup and no shutdown
// ATEST_SETUP_F(void)
// {
// return NULL;
// }
//
// // Shutting down after every tests
// void shutdown(void){
// printf("atest_shutdown()\n");
// }
//
// ATEST_SETUP_F(void)
// {
// printf("atest_setup()\n");
// return &shutdown;
// }
typedef void (*atest_shutdown_ptr)(void);
atest_shutdown_ptr __atest_setup_func();
#define ATEST_SETUP_F \
struct __ATestContext_s __ATestContext = { \
.out_stream = NULL, \
.tests_run = 0, \
.tests_failed = 0, \
.verbosity = 3, \
}; \
atest_shutdown_ptr __atest_setup_func
//
// Typical test function template
// MUST return NULL on test success, or char* with error message when failed!
//
#define ATEST_F(TESTNAME) \
char * TESTNAME()
//
// To be used in main() test launching function
//
// ATEST_F(test_ok){ .... tests code here ..}
//
// int main(...) { ...
// ATEST_RUN(test_ok);
// }
#define ATEST_RUN(TESTNAME) do { \
atest_shutdown_ptr shutdown_fun_p = __atest_setup_func(); \
char * result = (TESTNAME()); \
__ATestContext.tests_run++; \
if (result == NULL) { \
if (__ATestContext.verbosity > 0) { \
if (__ATestContext.verbosity == 1) {fprintf(__atest_stream, ".");} \
else {fprintf(__atest_stream, "[PASS] %s\n", #TESTNAME);} \
}\
} \
else { \
if (__ATestContext.verbosity > 0) { \
if (__ATestContext.verbosity == 1) {fprintf(__atest_stream, "F");} \
else {fprintf(__atest_stream, "[FAIL] %s (%s)\n", result, #TESTNAME);} \
}\
__ATestContext.tests_failed++; \
} \
if (shutdown_fun_p != NULL) {(*shutdown_fun_p)();}\
} while(0)
//
// Prints current test header
//
#define ATEST_PRINT_HEAD() do {\
setbuf(__atest_stream, NULL); \
if (__ATestContext.verbosity > 0) { \
fprintf(__atest_stream, "-------------------------------------\n"); \
fprintf(__atest_stream, "Running Tests: %s\n", __FILE__); \
fprintf(__atest_stream, "-------------------------------------\n\n"); \
} \
fflush(0); \
} while(0)
//
// Prints current tests stats total / passed / failed
//
#define ATEST_PRINT_FOOTER() do {\
if (__ATestContext.verbosity > 0) { \
fprintf(__atest_stream, "\n-------------------------------------\n"); \
fprintf(__atest_stream, "Total: %d Passed: %d Failed: %d\n", __ATestContext.tests_run, __ATestContext.tests_run - __ATestContext.tests_failed, __ATestContext.tests_failed); \
fprintf(__atest_stream, "-------------------------------------\n"); \
} else {\
fprintf(__atest_stream, "[%s] %-40s [%2d/%2d]\n", \
__ATestContext.tests_failed == 0 ? "PASS" : "FAIL", \
__FILE__, \
__ATestContext.tests_run - __ATestContext.tests_failed,\
__ATestContext.tests_run); \
} \
} while (0)
//
// Utility macro for returning main() exit code based on test failed/run, if no tests run it's an error too
//
#define ATEST_PARSE_MAINARGS(argc, argv) do { \
if (argc == 1) break; \
if (argc > 2){ \
fprintf(__atest_stream, "Too many arguments: use test_name_exec vvv\n"); \
return 1; } \
char a; int i = 0; \
int _has_quiet = 0; int _verb = 0;\
while ((a = argv[1][i]) != '\0') { \
if (a == 'q') _has_quiet = 1; \
if (a == 'v') _verb++; \
i++; \
} \
__ATestContext.verbosity = _has_quiet ? 0 : _verb; \
} while(0);
#define ATEST_EXITCODE() (-1 ? __ATestContext.tests_run == 0 : __ATestContext.tests_failed)
/*
*
* Utility non public macros
*
*/
#define __atest_stream (__ATestContext.out_stream == NULL ? stdout : __ATestContext.out_stream)
#define __STRINGIZE_DETAIL(x) #x
#define __STRINGIZE(x) __STRINGIZE_DETAIL(x)
#define __ATEST_LOG_ERR(msg) ("(" __FILE__ ":" __STRINGIZE(__LINE__) "): " msg)
#define __ATEST_LOG(msg) ("[ LOG] (" __FILE__ ":" __STRINGIZE(__LINE__) "): " msg)
#endif // !__ATEST_H