1 /*
2 libtap - Write tests in C
3 Copyright 2012 Jake Gelbman <gelbman@gmail.com>
4 This file is licensed under the LGPL
5 */
6
7 #define _DEFAULT_SOURCE 1
8
9 #include <stdio.h>
10 #include <stdlib.h>
11 #include <stdarg.h>
12 #include <string.h>
13 #include "tap.h"
14
15 static int expected_tests = NO_PLAN;
16 static int failed_tests;
17 static int current_test;
18 static char *todo_mesg;
19
20 static char *
vstrdupf(const char * fmt,va_list args)21 vstrdupf (const char *fmt, va_list args) {
22 char *str;
23 int size;
24 va_list args2;
25 va_copy(args2, args);
26 if (!fmt)
27 fmt = "";
28 size = vsnprintf(NULL, 0, fmt, args2) + 2;
29 str = malloc(size);
30 if (!str) {
31 perror("malloc error");
32 exit(1);
33 }
34 vsprintf(str, fmt, args);
35 va_end(args2);
36 return str;
37 }
38
39 void
tap_plan(int tests,const char * fmt,...)40 tap_plan (int tests, const char *fmt, ...) {
41 expected_tests = tests;
42 if (tests == SKIP_ALL) {
43 char *why;
44 va_list args;
45 va_start(args, fmt);
46 why = vstrdupf(fmt, args);
47 va_end(args);
48 printf("1..0 ");
49 diag("SKIP %s\n", why);
50 exit(0);
51 }
52 if (tests != NO_PLAN) {
53 printf("1..%d\n", tests);
54 }
55 }
56
57 int
vok_at_loc(const char * file,int line,int test,const char * fmt,va_list args)58 vok_at_loc (const char *file, int line, int test, const char *fmt,
59 va_list args)
60 {
61 char *name = vstrdupf(fmt, args);
62 if (!test) {
63 printf("not ");
64 }
65 printf("ok %d", ++current_test);
66 if (*name)
67 printf(" - %s", name);
68 if (todo_mesg) {
69 printf(" # TODO");
70 if (*todo_mesg)
71 printf(" %s", todo_mesg);
72 }
73 printf("\n");
74 if (!test) {
75 printf("# Failed ");
76 if (todo_mesg)
77 printf("(TODO) ");
78 printf("test ");
79 if (*name)
80 printf("'%s'\n# ", name);
81 printf("at %s line %d.\n", file, line);
82 if (!todo_mesg)
83 failed_tests++;
84 }
85 free(name);
86 return test;
87 }
88
89 int
ok_at_loc(const char * file,int line,int test,const char * fmt,...)90 ok_at_loc (const char *file, int line, int test, const char *fmt, ...) {
91 va_list args;
92 va_start(args, fmt);
93 vok_at_loc(file, line, test, fmt, args);
94 va_end(args);
95 return test;
96 }
97
98 static int
mystrcmp(const char * a,const char * b)99 mystrcmp (const char *a, const char *b) {
100 return a == b ? 0 : !a ? -1 : !b ? 1 : strcmp(a, b);
101 }
102
103 #define eq(a, b) (!mystrcmp(a, b))
104 #define ne(a, b) (mystrcmp(a, b))
105
106 int
is_at_loc(const char * file,int line,const char * got,const char * expected,const char * fmt,...)107 is_at_loc (const char *file, int line, const char *got, const char *expected,
108 const char *fmt, ...)
109 {
110 int test = eq(got, expected);
111 va_list args;
112 va_start(args, fmt);
113 vok_at_loc(file, line, test, fmt, args);
114 va_end(args);
115 if (!test) {
116 diag(" got: '%s'", got);
117 diag(" expected: '%s'", expected);
118 }
119 return test;
120 }
121
122 int
isnt_at_loc(const char * file,int line,const char * got,const char * expected,const char * fmt,...)123 isnt_at_loc (const char *file, int line, const char *got, const char *expected,
124 const char *fmt, ...)
125 {
126 int test = ne(got, expected);
127 va_list args;
128 va_start(args, fmt);
129 vok_at_loc(file, line, test, fmt, args);
130 va_end(args);
131 if (!test) {
132 diag(" got: '%s'", got);
133 diag(" expected: anything else");
134 }
135 return test;
136 }
137
138 int
cmp_ok_at_loc(const char * file,int line,int a,const char * op,int b,const char * fmt,...)139 cmp_ok_at_loc (const char *file, int line, int a, const char *op, int b,
140 const char *fmt, ...)
141 {
142 int test = eq(op, "||") ? a || b
143 : eq(op, "&&") ? a && b
144 : eq(op, "|") ? a | b
145 : eq(op, "^") ? a ^ b
146 : eq(op, "&") ? a & b
147 : eq(op, "==") ? a == b
148 : eq(op, "!=") ? a != b
149 : eq(op, "<") ? a < b
150 : eq(op, ">") ? a > b
151 : eq(op, "<=") ? a <= b
152 : eq(op, ">=") ? a >= b
153 : eq(op, "<<") ? a << b
154 : eq(op, ">>") ? a >> b
155 : eq(op, "+") ? a + b
156 : eq(op, "-") ? a - b
157 : eq(op, "*") ? a * b
158 : eq(op, "/") ? a / b
159 : eq(op, "%") ? a % b
160 : diag("unrecognized operator '%s'", op);
161 va_list args;
162 va_start(args, fmt);
163 vok_at_loc(file, line, test, fmt, args);
164 va_end(args);
165 if (!test) {
166 diag(" %d", a);
167 diag(" %s", op);
168 diag(" %d", b);
169 }
170 return test;
171 }
172
173 static int
find_mem_diff(const char * a,const char * b,size_t n,size_t * offset)174 find_mem_diff (const char *a, const char *b, size_t n, size_t *offset) {
175 size_t i;
176 if (a == b)
177 return 0;
178 if (!a || !b)
179 return 2;
180 for (i = 0; i < n; i++) {
181 if (a[i] != b[i]) {
182 *offset = i;
183 return 1;
184 }
185 }
186 return 0;
187 }
188
189 int
cmp_mem_at_loc(const char * file,int line,const void * got,const void * expected,size_t n,const char * fmt,...)190 cmp_mem_at_loc (const char *file, int line, const void *got,
191 const void *expected, size_t n, const char *fmt, ...)
192 {
193 size_t offset;
194 int diff = find_mem_diff(got, expected, n, &offset);
195 va_list args;
196 va_start(args, fmt);
197 vok_at_loc(file, line, !diff, fmt, args);
198 va_end(args);
199 if (diff == 1) {
200 diag(" Difference starts at offset %d", offset);
201 diag(" got: 0x%02x", ((unsigned char *)got)[offset]);
202 diag(" expected: 0x%02x", ((unsigned char *)expected)[offset]);
203 }
204 else if (diff == 2) {
205 diag(" got: %s", got ? "not NULL" : "NULL");
206 diag(" expected: %s", expected ? "not NULL" : "NULL");
207 }
208 return !diff;
209 }
210
211 int
diag(const char * fmt,...)212 diag (const char *fmt, ...) {
213 va_list args;
214 char *mesg, *line;
215 int i;
216 va_start(args, fmt);
217 if (!fmt)
218 return 0;
219 mesg = vstrdupf(fmt, args);
220 line = mesg;
221 for (i = 0; *line; i++) {
222 char c = mesg[i];
223 if (!c || c == '\n') {
224 mesg[i] = '\0';
225 printf("# %s\n", line);
226 if (!c)
227 break;
228 mesg[i] = c;
229 line = mesg + i + 1;
230 }
231 }
232 free(mesg);
233 va_end(args);
234 return 0;
235 }
236
237 int
exit_status()238 exit_status () {
239 int retval = 0;
240 if (expected_tests == NO_PLAN) {
241 printf("1..%d\n", current_test);
242 }
243 else if (current_test != expected_tests) {
244 diag("Looks like you planned %d test%s but ran %d.",
245 expected_tests, expected_tests > 1 ? "s" : "", current_test);
246 retval = 2;
247 }
248 if (failed_tests) {
249 diag("Looks like you failed %d test%s of %d run.",
250 failed_tests, failed_tests > 1 ? "s" : "", current_test);
251 retval = 1;
252 }
253 return retval;
254 }
255
256 int
bail_out(int ignore,const char * fmt,...)257 bail_out (int ignore, const char *fmt, ...) {
258 va_list args;
259 va_start(args, fmt);
260 printf("Bail out! ");
261 vprintf(fmt, args);
262 printf("\n");
263 va_end(args);
264 exit(255);
265 return 0;
266 }
267
268 void
tap_skip(int n,const char * fmt,...)269 tap_skip (int n, const char *fmt, ...) {
270 char *why;
271 va_list args;
272 va_start(args, fmt);
273 why = vstrdupf(fmt, args);
274 va_end(args);
275 while (n --> 0) {
276 printf("ok %d ", ++current_test);
277 diag("skip %s\n", why);
278 }
279 free(why);
280 }
281
282 void
tap_todo(int ignore,const char * fmt,...)283 tap_todo (int ignore, const char *fmt, ...) {
284 va_list args;
285 va_start(args, fmt);
286 todo_mesg = vstrdupf(fmt, args);
287 va_end(args);
288 }
289
290 void
tap_end_todo()291 tap_end_todo () {
292 free(todo_mesg);
293 todo_mesg = NULL;
294 }
295
296 #ifndef _WIN32
297 #include <sys/mman.h>
298 #include <sys/param.h>
299 #include <regex.h>
300
301 #if defined __APPLE__ || defined BSD
302 #define MAP_ANONYMOUS MAP_ANON
303 #endif
304
305 /* Create a shared memory int to keep track of whether a piece of code executed
306 dies. to be used in the dies_ok and lives_ok macros. */
307 int
tap_test_died(int status)308 tap_test_died (int status) {
309 static int *test_died = NULL;
310 int prev;
311 if (!test_died) {
312 test_died = mmap(0, sizeof (int), PROT_READ | PROT_WRITE,
313 MAP_SHARED | MAP_ANONYMOUS, -1, 0);
314 *test_died = 0;
315 }
316 prev = *test_died;
317 *test_died = status;
318 return prev;
319 }
320
321 int
like_at_loc(int for_match,const char * file,int line,const char * got,const char * expected,const char * fmt,...)322 like_at_loc (int for_match, const char *file, int line, const char *got,
323 const char *expected, const char *fmt, ...)
324 {
325 int test;
326 regex_t re;
327 va_list args;
328 int err = regcomp(&re, expected, REG_EXTENDED);
329 if (err) {
330 char errbuf[256];
331 regerror(err, &re, errbuf, sizeof errbuf);
332 fprintf(stderr, "Unable to compile regex '%s': %s at %s line %d\n",
333 expected, errbuf, file, line);
334 exit(255);
335 }
336 err = regexec(&re, got, 0, NULL, 0);
337 regfree(&re);
338 test = for_match ? !err : err;
339 va_start(args, fmt);
340 vok_at_loc(file, line, test, fmt, args);
341 va_end(args);
342 if (!test) {
343 if (for_match) {
344 diag(" '%s'", got);
345 diag(" doesn't match: '%s'", expected);
346 }
347 else {
348 diag(" '%s'", got);
349 diag(" matches: '%s'", expected);
350 }
351 }
352 return test;
353 }
354 #endif
355