1 | /* |
---|
2 | * Run a set of tests, reporting results. |
---|
3 | * |
---|
4 | * Usage: |
---|
5 | * |
---|
6 | * runtests <test-list> |
---|
7 | * |
---|
8 | * Expects a list of executables located in the given file, one line per |
---|
9 | * executable. For each one, runs it as part of a test suite, reporting |
---|
10 | * results. Test output should start with a line containing the number of |
---|
11 | * tests (numbered from 1 to this number), and then each line should be in the |
---|
12 | * following format: |
---|
13 | * |
---|
14 | * ok <number> |
---|
15 | * not ok <number> |
---|
16 | * ok <number> # skip |
---|
17 | * |
---|
18 | * where <number> is the number of the test. ok indicates success, not ok |
---|
19 | * indicates failure, and "# skip" indicates the test was skipped for some |
---|
20 | * reason (maybe because it doesn't apply to this platform). This is a subset |
---|
21 | * of TAP as documented in Test::Harness::TAP, which comes with Perl. |
---|
22 | * |
---|
23 | * Any bug reports, bug fixes, and improvements are very much welcome and |
---|
24 | * should be sent to the e-mail address below. |
---|
25 | * |
---|
26 | * Copyright 2000, 2001, 2004, 2006, 2007, 2008, 2009 |
---|
27 | * Russ Allbery <rra@stanford.edu> |
---|
28 | * |
---|
29 | * Permission is hereby granted, free of charge, to any person obtaining a |
---|
30 | * copy of this software and associated documentation files (the "Software"), |
---|
31 | * to deal in the Software without restriction, including without limitation |
---|
32 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, |
---|
33 | * and/or sell copies of the Software, and to permit persons to whom the |
---|
34 | * Software is furnished to do so, subject to the following conditions: |
---|
35 | * |
---|
36 | * The above copyright notice and this permission notice shall be included in |
---|
37 | * all copies or substantial portions of the Software. |
---|
38 | * |
---|
39 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
---|
40 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
---|
41 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL |
---|
42 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
---|
43 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
---|
44 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
---|
45 | * DEALINGS IN THE SOFTWARE. |
---|
46 | */ |
---|
47 | |
---|
48 | #include <ctype.h> |
---|
49 | #include <errno.h> |
---|
50 | #include <fcntl.h> |
---|
51 | #include <stdarg.h> |
---|
52 | #include <stdio.h> |
---|
53 | #include <stdlib.h> |
---|
54 | #include <string.h> |
---|
55 | #include <sys/stat.h> |
---|
56 | #include <sys/time.h> |
---|
57 | #include <sys/types.h> |
---|
58 | #include <sys/wait.h> |
---|
59 | #include <time.h> |
---|
60 | #include <unistd.h> |
---|
61 | |
---|
62 | /* sys/time.h must be included before sys/resource.h on some platforms. */ |
---|
63 | #include <sys/resource.h> |
---|
64 | |
---|
65 | /* AIX doesn't have WCOREDUMP. */ |
---|
66 | #ifndef WCOREDUMP |
---|
67 | # define WCOREDUMP(status) ((unsigned)(status) & 0x80) |
---|
68 | #endif |
---|
69 | |
---|
70 | /* |
---|
71 | * The source and build versions of the tests directory. This is used to set |
---|
72 | * the SOURCE and BUILD environment variables and find test programs, if set. |
---|
73 | * Normally, this should be set as part of the build process to the test |
---|
74 | * subdirectories of $(abs_top_srcdir) and $(abs_top_builddir) respectively. |
---|
75 | */ |
---|
76 | #ifndef SOURCE |
---|
77 | # define SOURCE NULL |
---|
78 | #endif |
---|
79 | #ifndef BUILD |
---|
80 | # define BUILD NULL |
---|
81 | #endif |
---|
82 | |
---|
83 | /* Test status codes. */ |
---|
84 | enum test_status { |
---|
85 | TEST_FAIL, |
---|
86 | TEST_PASS, |
---|
87 | TEST_SKIP, |
---|
88 | TEST_INVALID |
---|
89 | }; |
---|
90 | |
---|
91 | /* Error exit statuses for test processes. */ |
---|
92 | #define CHILDERR_DUP 100 /* Couldn't redirect stderr or stdout. */ |
---|
93 | #define CHILDERR_EXEC 101 /* Couldn't exec child process. */ |
---|
94 | #define CHILDERR_STDERR 102 /* Couldn't open stderr file. */ |
---|
95 | |
---|
96 | /* Structure to hold data for a set of tests. */ |
---|
97 | struct testset { |
---|
98 | char *file; /* The file name of the test. */ |
---|
99 | char *path; /* The path to the test program. */ |
---|
100 | int count; /* Expected count of tests. */ |
---|
101 | int current; /* The last seen test number. */ |
---|
102 | int length; /* The length of the last status message. */ |
---|
103 | int passed; /* Count of passing tests. */ |
---|
104 | int failed; /* Count of failing lists. */ |
---|
105 | int skipped; /* Count of skipped tests (passed). */ |
---|
106 | enum test_status *results; /* Table of results by test number. */ |
---|
107 | int aborted; /* Whether the set as aborted. */ |
---|
108 | int reported; /* Whether the results were reported. */ |
---|
109 | int status; /* The exit status of the test. */ |
---|
110 | int all_skipped; /* Whether all tests were skipped. */ |
---|
111 | char *reason; /* Why all tests were skipped. */ |
---|
112 | }; |
---|
113 | |
---|
114 | /* Structure to hold a linked list of test sets. */ |
---|
115 | struct testlist { |
---|
116 | struct testset *ts; |
---|
117 | struct testlist *next; |
---|
118 | }; |
---|
119 | |
---|
120 | /* |
---|
121 | * Header used for test output. %s is replaced by the file name of the list |
---|
122 | * of tests. |
---|
123 | */ |
---|
124 | static const char banner[] = "\n\ |
---|
125 | Running all tests listed in %s. If any tests fail, run the failing\n\ |
---|
126 | test program with runtests -o to see more details.\n\n"; |
---|
127 | |
---|
128 | /* Header for reports of failed tests. */ |
---|
129 | static const char header[] = "\n\ |
---|
130 | Failed Set Fail/Total (%) Skip Stat Failing Tests\n\ |
---|
131 | -------------------------- -------------- ---- ---- ------------------------"; |
---|
132 | |
---|
133 | /* Include the file name and line number in malloc failures. */ |
---|
134 | #define xmalloc(size) x_malloc((size), __FILE__, __LINE__) |
---|
135 | #define xstrdup(p) x_strdup((p), __FILE__, __LINE__) |
---|
136 | |
---|
137 | |
---|
138 | /* |
---|
139 | * Report a fatal error, including the results of strerror, and exit. |
---|
140 | */ |
---|
141 | static void |
---|
142 | sysdie(const char *format, ...) |
---|
143 | { |
---|
144 | int oerrno; |
---|
145 | va_list args; |
---|
146 | |
---|
147 | oerrno = errno; |
---|
148 | fflush(stdout); |
---|
149 | fprintf(stderr, "runtests: "); |
---|
150 | va_start(args, format); |
---|
151 | vfprintf(stderr, format, args); |
---|
152 | va_end(args); |
---|
153 | fprintf(stderr, ": %s\n", strerror(oerrno)); |
---|
154 | exit(1); |
---|
155 | } |
---|
156 | |
---|
157 | |
---|
158 | /* |
---|
159 | * Allocate memory, reporting a fatal error and exiting on failure. |
---|
160 | */ |
---|
161 | static void * |
---|
162 | x_malloc(size_t size, const char *file, int line) |
---|
163 | { |
---|
164 | void *p; |
---|
165 | |
---|
166 | p = malloc(size); |
---|
167 | if (!p) |
---|
168 | sysdie("failed to malloc %lu bytes at %s line %d", |
---|
169 | (unsigned long) size, file, line); |
---|
170 | return p; |
---|
171 | } |
---|
172 | |
---|
173 | |
---|
174 | /* |
---|
175 | * Copy a string, reporting a fatal error and exiting on failure. |
---|
176 | */ |
---|
177 | static char * |
---|
178 | x_strdup(const char *s, const char *file, int line) |
---|
179 | { |
---|
180 | char *p; |
---|
181 | size_t len; |
---|
182 | |
---|
183 | len = strlen(s) + 1; |
---|
184 | p = malloc(len); |
---|
185 | if (!p) |
---|
186 | sysdie("failed to strdup %lu bytes at %s line %d", |
---|
187 | (unsigned long) len, file, line); |
---|
188 | memcpy(p, s, len); |
---|
189 | return p; |
---|
190 | } |
---|
191 | |
---|
192 | |
---|
193 | /* |
---|
194 | * Given a struct timeval, return the number of seconds it represents as a |
---|
195 | * double. Use difftime() to convert a time_t to a double. |
---|
196 | */ |
---|
197 | static double |
---|
198 | tv_seconds(const struct timeval *tv) |
---|
199 | { |
---|
200 | return difftime(tv->tv_sec, 0) + tv->tv_usec * 1e-6; |
---|
201 | } |
---|
202 | |
---|
203 | |
---|
204 | /* |
---|
205 | * Given two struct timevals, return the difference in seconds. |
---|
206 | */ |
---|
207 | static double |
---|
208 | tv_diff(const struct timeval *tv1, const struct timeval *tv0) |
---|
209 | { |
---|
210 | return tv_seconds(tv1) - tv_seconds(tv0); |
---|
211 | } |
---|
212 | |
---|
213 | |
---|
214 | /* |
---|
215 | * Given two struct timevals, return the sum in seconds as a double. |
---|
216 | */ |
---|
217 | static double |
---|
218 | tv_sum(const struct timeval *tv1, const struct timeval *tv2) |
---|
219 | { |
---|
220 | return tv_seconds(tv1) + tv_seconds(tv2); |
---|
221 | } |
---|
222 | |
---|
223 | |
---|
224 | /* |
---|
225 | * Given a pointer to a string, skip any leading whitespace and return a |
---|
226 | * pointer to the first non-whitespace character. |
---|
227 | */ |
---|
228 | static const char * |
---|
229 | skip_whitespace(const char *p) |
---|
230 | { |
---|
231 | while (isspace((unsigned char)(*p))) |
---|
232 | p++; |
---|
233 | return p; |
---|
234 | } |
---|
235 | |
---|
236 | |
---|
237 | /* |
---|
238 | * Read the first line of test output, which should contain the range of |
---|
239 | * test numbers, and initialize the testset structure. Assume it was zeroed |
---|
240 | * before being passed in. Return true if initialization succeeds, false |
---|
241 | * otherwise. |
---|
242 | */ |
---|
243 | static int |
---|
244 | test_init(const char *line, struct testset *ts) |
---|
245 | { |
---|
246 | int i; |
---|
247 | |
---|
248 | /* |
---|
249 | * Prefer a simple number of tests, but if the count is given as a range |
---|
250 | * such as 1..10, accept that too for compatibility with Perl's |
---|
251 | * Test::Harness. |
---|
252 | */ |
---|
253 | line = skip_whitespace(line); |
---|
254 | if (strncmp(line, "1..", 3) == 0) |
---|
255 | line += 3; |
---|
256 | |
---|
257 | /* |
---|
258 | * Get the count, check it for validity, and initialize the struct. If we |
---|
259 | * have something of the form "1..0 # skip foo", the whole file was |
---|
260 | * skipped; record that. |
---|
261 | */ |
---|
262 | i = strtol(line, (char **) &line, 10); |
---|
263 | if (i == 0) { |
---|
264 | line = skip_whitespace(line); |
---|
265 | if (*line == '#') { |
---|
266 | line = skip_whitespace(line + 1); |
---|
267 | if (strncasecmp(line, "skip", 4) == 0) { |
---|
268 | line = skip_whitespace(line + 4); |
---|
269 | if (*line != '\0') { |
---|
270 | ts->reason = xstrdup(line); |
---|
271 | ts->reason[strlen(ts->reason) - 1] = '\0'; |
---|
272 | } |
---|
273 | ts->all_skipped = 1; |
---|
274 | ts->aborted = 1; |
---|
275 | return 0; |
---|
276 | } |
---|
277 | } |
---|
278 | } |
---|
279 | if (i <= 0) { |
---|
280 | puts("ABORTED (invalid test count)"); |
---|
281 | ts->aborted = 1; |
---|
282 | ts->reported = 1; |
---|
283 | return 0; |
---|
284 | } |
---|
285 | ts->count = i; |
---|
286 | ts->results = xmalloc(ts->count * sizeof(enum test_status)); |
---|
287 | for (i = 0; i < ts->count; i++) |
---|
288 | ts->results[i] = TEST_INVALID; |
---|
289 | return 1; |
---|
290 | } |
---|
291 | |
---|
292 | |
---|
293 | /* |
---|
294 | * Start a program, connecting its stdout to a pipe on our end and its stderr |
---|
295 | * to /dev/null, and storing the file descriptor to read from in the two |
---|
296 | * argument. Returns the PID of the new process. Errors are fatal. |
---|
297 | */ |
---|
298 | static pid_t |
---|
299 | test_start(const char *path, int *fd) |
---|
300 | { |
---|
301 | int fds[2], errfd; |
---|
302 | pid_t child; |
---|
303 | |
---|
304 | if (pipe(fds) == -1) { |
---|
305 | puts("ABORTED"); |
---|
306 | fflush(stdout); |
---|
307 | sysdie("can't create pipe"); |
---|
308 | } |
---|
309 | child = fork(); |
---|
310 | if (child == (pid_t) -1) { |
---|
311 | puts("ABORTED"); |
---|
312 | fflush(stdout); |
---|
313 | sysdie("can't fork"); |
---|
314 | } else if (child == 0) { |
---|
315 | /* In child. Set up our stdout and stderr. */ |
---|
316 | errfd = open("/dev/null", O_WRONLY); |
---|
317 | if (errfd < 0) |
---|
318 | _exit(CHILDERR_STDERR); |
---|
319 | if (dup2(errfd, 2) == -1) |
---|
320 | _exit(CHILDERR_DUP); |
---|
321 | close(fds[0]); |
---|
322 | if (dup2(fds[1], 1) == -1) |
---|
323 | _exit(CHILDERR_DUP); |
---|
324 | |
---|
325 | /* Now, exec our process. */ |
---|
326 | if (execl(path, path, (char *) 0) == -1) |
---|
327 | _exit(CHILDERR_EXEC); |
---|
328 | } else { |
---|
329 | /* In parent. Close the extra file descriptor. */ |
---|
330 | close(fds[1]); |
---|
331 | } |
---|
332 | *fd = fds[0]; |
---|
333 | return child; |
---|
334 | } |
---|
335 | |
---|
336 | |
---|
337 | /* |
---|
338 | * Back up over the output saying what test we were executing. |
---|
339 | */ |
---|
340 | static void |
---|
341 | test_backspace(struct testset *ts) |
---|
342 | { |
---|
343 | int i; |
---|
344 | |
---|
345 | if (!isatty(STDOUT_FILENO)) |
---|
346 | return; |
---|
347 | for (i = 0; i < ts->length; i++) |
---|
348 | putchar('\b'); |
---|
349 | for (i = 0; i < ts->length; i++) |
---|
350 | putchar(' '); |
---|
351 | for (i = 0; i < ts->length; i++) |
---|
352 | putchar('\b'); |
---|
353 | ts->length = 0; |
---|
354 | } |
---|
355 | |
---|
356 | |
---|
357 | /* |
---|
358 | * Given a single line of output from a test, parse it and return the success |
---|
359 | * status of that test. Anything printed to stdout not matching the form |
---|
360 | * /^(not )?ok \d+/ is ignored. Sets ts->current to the test number that just |
---|
361 | * reported status. |
---|
362 | */ |
---|
363 | static void |
---|
364 | test_checkline(const char *line, struct testset *ts) |
---|
365 | { |
---|
366 | enum test_status status = TEST_PASS; |
---|
367 | const char *bail; |
---|
368 | char *end; |
---|
369 | int current; |
---|
370 | |
---|
371 | /* Before anything, check for a test abort. */ |
---|
372 | bail = strstr(line, "Bail out!"); |
---|
373 | if (bail != NULL) { |
---|
374 | bail = skip_whitespace(bail + strlen("Bail out!")); |
---|
375 | if (*bail != '\0') { |
---|
376 | int length; |
---|
377 | |
---|
378 | length = strlen(bail); |
---|
379 | if (bail[length - 1] == '\n') |
---|
380 | length--; |
---|
381 | test_backspace(ts); |
---|
382 | printf("ABORTED (%.*s)\n", length, bail); |
---|
383 | ts->reported = 1; |
---|
384 | } |
---|
385 | ts->aborted = 1; |
---|
386 | return; |
---|
387 | } |
---|
388 | |
---|
389 | /* |
---|
390 | * If the given line isn't newline-terminated, it was too big for an |
---|
391 | * fgets(), which means ignore it. |
---|
392 | */ |
---|
393 | if (line[strlen(line) - 1] != '\n') |
---|
394 | return; |
---|
395 | |
---|
396 | /* Parse the line, ignoring something we can't parse. */ |
---|
397 | if (strncmp(line, "not ", 4) == 0) { |
---|
398 | status = TEST_FAIL; |
---|
399 | line += 4; |
---|
400 | } |
---|
401 | if (strncmp(line, "ok", 2) != 0) |
---|
402 | return; |
---|
403 | line = skip_whitespace(line + 2); |
---|
404 | errno = 0; |
---|
405 | current = strtol(line, &end, 10); |
---|
406 | if (errno != 0 || end == line) |
---|
407 | current = ts->current + 1; |
---|
408 | if (current <= 0 || current > ts->count) { |
---|
409 | test_backspace(ts); |
---|
410 | printf("ABORTED (invalid test number %d)\n", current); |
---|
411 | ts->aborted = 1; |
---|
412 | ts->reported = 1; |
---|
413 | return; |
---|
414 | } |
---|
415 | |
---|
416 | /* |
---|
417 | * Handle directives. We should probably do something more interesting |
---|
418 | * with unexpected passes of todo tests. |
---|
419 | */ |
---|
420 | while (isdigit((unsigned char)(*line))) |
---|
421 | line++; |
---|
422 | line = skip_whitespace(line); |
---|
423 | if (*line == '#') { |
---|
424 | line = skip_whitespace(line + 1); |
---|
425 | if (strncasecmp(line, "skip", 4) == 0) |
---|
426 | status = TEST_SKIP; |
---|
427 | if (strncasecmp(line, "todo", 4) == 0) |
---|
428 | status = (status == TEST_FAIL) ? TEST_SKIP : TEST_FAIL; |
---|
429 | } |
---|
430 | |
---|
431 | /* Make sure that the test number is in range and not a duplicate. */ |
---|
432 | if (ts->results[current - 1] != TEST_INVALID) { |
---|
433 | test_backspace(ts); |
---|
434 | printf("ABORTED (duplicate test number %d)\n", current); |
---|
435 | ts->aborted = 1; |
---|
436 | ts->reported = 1; |
---|
437 | return; |
---|
438 | } |
---|
439 | |
---|
440 | /* Good results. Increment our various counters. */ |
---|
441 | switch (status) { |
---|
442 | case TEST_PASS: ts->passed++; break; |
---|
443 | case TEST_FAIL: ts->failed++; break; |
---|
444 | case TEST_SKIP: ts->skipped++; break; |
---|
445 | default: break; |
---|
446 | } |
---|
447 | ts->current = current; |
---|
448 | ts->results[current - 1] = status; |
---|
449 | test_backspace(ts); |
---|
450 | if (isatty(STDOUT_FILENO)) { |
---|
451 | ts->length = printf("%d/%d", current, ts->count); |
---|
452 | fflush(stdout); |
---|
453 | } |
---|
454 | } |
---|
455 | |
---|
456 | |
---|
457 | /* |
---|
458 | * Print out a range of test numbers, returning the number of characters it |
---|
459 | * took up. Add a comma and a space before the range if chars indicates that |
---|
460 | * something has already been printed on the line, and print ... instead if |
---|
461 | * chars plus the space needed would go over the limit (use a limit of 0 to |
---|
462 | * disable this. |
---|
463 | */ |
---|
464 | static int |
---|
465 | test_print_range(int first, int last, int chars, int limit) |
---|
466 | { |
---|
467 | int needed = 0; |
---|
468 | int out = 0; |
---|
469 | int n; |
---|
470 | |
---|
471 | if (chars > 0) { |
---|
472 | needed += 2; |
---|
473 | if (!limit || chars <= limit) out += printf(", "); |
---|
474 | } |
---|
475 | for (n = first; n > 0; n /= 10) |
---|
476 | needed++; |
---|
477 | if (last > first) { |
---|
478 | for (n = last; n > 0; n /= 10) |
---|
479 | needed++; |
---|
480 | needed++; |
---|
481 | } |
---|
482 | if (limit && chars + needed > limit) { |
---|
483 | if (chars <= limit) |
---|
484 | out += printf("..."); |
---|
485 | } else { |
---|
486 | if (last > first) |
---|
487 | out += printf("%d-", first); |
---|
488 | out += printf("%d", last); |
---|
489 | } |
---|
490 | return out; |
---|
491 | } |
---|
492 | |
---|
493 | |
---|
494 | /* |
---|
495 | * Summarize a single test set. The second argument is 0 if the set exited |
---|
496 | * cleanly, a positive integer representing the exit status if it exited |
---|
497 | * with a non-zero status, and a negative integer representing the signal |
---|
498 | * that terminated it if it was killed by a signal. |
---|
499 | */ |
---|
500 | static void |
---|
501 | test_summarize(struct testset *ts, int status) |
---|
502 | { |
---|
503 | int i; |
---|
504 | int missing = 0; |
---|
505 | int failed = 0; |
---|
506 | int first = 0; |
---|
507 | int last = 0; |
---|
508 | |
---|
509 | if (ts->aborted) { |
---|
510 | fputs("ABORTED", stdout); |
---|
511 | if (ts->count > 0) |
---|
512 | printf(" (passed %d/%d)", ts->passed, ts->count - ts->skipped); |
---|
513 | } else { |
---|
514 | for (i = 0; i < ts->count; i++) { |
---|
515 | if (ts->results[i] == TEST_INVALID) { |
---|
516 | if (missing == 0) |
---|
517 | fputs("MISSED ", stdout); |
---|
518 | if (first && i == last) |
---|
519 | last = i + 1; |
---|
520 | else { |
---|
521 | if (first) |
---|
522 | test_print_range(first, last, missing - 1, 0); |
---|
523 | missing++; |
---|
524 | first = i + 1; |
---|
525 | last = i + 1; |
---|
526 | } |
---|
527 | } |
---|
528 | } |
---|
529 | if (first) |
---|
530 | test_print_range(first, last, missing - 1, 0); |
---|
531 | first = 0; |
---|
532 | last = 0; |
---|
533 | for (i = 0; i < ts->count; i++) { |
---|
534 | if (ts->results[i] == TEST_FAIL) { |
---|
535 | if (missing && !failed) |
---|
536 | fputs("; ", stdout); |
---|
537 | if (failed == 0) |
---|
538 | fputs("FAILED ", stdout); |
---|
539 | if (first && i == last) |
---|
540 | last = i + 1; |
---|
541 | else { |
---|
542 | if (first) |
---|
543 | test_print_range(first, last, failed - 1, 0); |
---|
544 | failed++; |
---|
545 | first = i + 1; |
---|
546 | last = i + 1; |
---|
547 | } |
---|
548 | } |
---|
549 | } |
---|
550 | if (first) |
---|
551 | test_print_range(first, last, failed - 1, 0); |
---|
552 | if (!missing && !failed) { |
---|
553 | fputs(!status ? "ok" : "dubious", stdout); |
---|
554 | if (ts->skipped > 0) { |
---|
555 | if (ts->skipped == 1) |
---|
556 | printf(" (skipped %d test)", ts->skipped); |
---|
557 | else |
---|
558 | printf(" (skipped %d tests)", ts->skipped); |
---|
559 | } |
---|
560 | } |
---|
561 | } |
---|
562 | if (status > 0) |
---|
563 | printf(" (exit status %d)", status); |
---|
564 | else if (status < 0) |
---|
565 | printf(" (killed by signal %d%s)", -status, |
---|
566 | WCOREDUMP(ts->status) ? ", core dumped" : ""); |
---|
567 | putchar('\n'); |
---|
568 | } |
---|
569 | |
---|
570 | |
---|
571 | /* |
---|
572 | * Given a test set, analyze the results, classify the exit status, handle a |
---|
573 | * few special error messages, and then pass it along to test_summarize() |
---|
574 | * for the regular output. |
---|
575 | */ |
---|
576 | static int |
---|
577 | test_analyze(struct testset *ts) |
---|
578 | { |
---|
579 | if (ts->reported) |
---|
580 | return 0; |
---|
581 | if (ts->all_skipped) { |
---|
582 | if (ts->reason == NULL) |
---|
583 | puts("skipped"); |
---|
584 | else |
---|
585 | printf("skipped (%s)\n", ts->reason); |
---|
586 | return 1; |
---|
587 | } else if (WIFEXITED(ts->status) && WEXITSTATUS(ts->status) != 0) { |
---|
588 | switch (WEXITSTATUS(ts->status)) { |
---|
589 | case CHILDERR_DUP: |
---|
590 | if (!ts->reported) |
---|
591 | puts("ABORTED (can't dup file descriptors)"); |
---|
592 | break; |
---|
593 | case CHILDERR_EXEC: |
---|
594 | if (!ts->reported) |
---|
595 | puts("ABORTED (execution failed -- not found?)"); |
---|
596 | break; |
---|
597 | case CHILDERR_STDERR: |
---|
598 | if (!ts->reported) |
---|
599 | puts("ABORTED (can't open /dev/null)"); |
---|
600 | break; |
---|
601 | default: |
---|
602 | test_summarize(ts, WEXITSTATUS(ts->status)); |
---|
603 | break; |
---|
604 | } |
---|
605 | return 0; |
---|
606 | } else if (WIFSIGNALED(ts->status)) { |
---|
607 | test_summarize(ts, -WTERMSIG(ts->status)); |
---|
608 | return 0; |
---|
609 | } else { |
---|
610 | test_summarize(ts, 0); |
---|
611 | return (ts->failed == 0); |
---|
612 | } |
---|
613 | } |
---|
614 | |
---|
615 | |
---|
616 | /* |
---|
617 | * Runs a single test set, accumulating and then reporting the results. |
---|
618 | * Returns true if the test set was successfully run and all tests passed, |
---|
619 | * false otherwise. |
---|
620 | */ |
---|
621 | static int |
---|
622 | test_run(struct testset *ts) |
---|
623 | { |
---|
624 | pid_t testpid, child; |
---|
625 | int outfd, i, status; |
---|
626 | FILE *output; |
---|
627 | char buffer[BUFSIZ]; |
---|
628 | |
---|
629 | /* |
---|
630 | * Initialize the test and our data structures, flagging this set in error |
---|
631 | * if the initialization fails. |
---|
632 | */ |
---|
633 | testpid = test_start(ts->path, &outfd); |
---|
634 | output = fdopen(outfd, "r"); |
---|
635 | if (!output) { |
---|
636 | puts("ABORTED"); |
---|
637 | fflush(stdout); |
---|
638 | sysdie("fdopen failed"); |
---|
639 | } |
---|
640 | if (!fgets(buffer, sizeof(buffer), output)) |
---|
641 | ts->aborted = 1; |
---|
642 | if (!ts->aborted && !test_init(buffer, ts)) |
---|
643 | ts->aborted = 1; |
---|
644 | |
---|
645 | /* Pass each line of output to test_checkline(). */ |
---|
646 | while (!ts->aborted && fgets(buffer, sizeof(buffer), output)) |
---|
647 | test_checkline(buffer, ts); |
---|
648 | if (ferror(output)) |
---|
649 | ts->aborted = 1; |
---|
650 | test_backspace(ts); |
---|
651 | |
---|
652 | /* |
---|
653 | * Close the output descriptor, retrieve the exit status, and pass that |
---|
654 | * information to test_analyze() for eventual output. |
---|
655 | */ |
---|
656 | fclose(output); |
---|
657 | child = waitpid(testpid, &ts->status, 0); |
---|
658 | if (child == (pid_t) -1) { |
---|
659 | if (!ts->reported) { |
---|
660 | puts("ABORTED"); |
---|
661 | fflush(stdout); |
---|
662 | } |
---|
663 | sysdie("waitpid for %u failed", (unsigned int) testpid); |
---|
664 | } |
---|
665 | if (ts->all_skipped) |
---|
666 | ts->aborted = 0; |
---|
667 | status = test_analyze(ts); |
---|
668 | |
---|
669 | /* Convert missing tests to failed tests. */ |
---|
670 | for (i = 0; i < ts->count; i++) { |
---|
671 | if (ts->results[i] == TEST_INVALID) { |
---|
672 | ts->failed++; |
---|
673 | ts->results[i] = TEST_FAIL; |
---|
674 | status = 0; |
---|
675 | } |
---|
676 | } |
---|
677 | return status; |
---|
678 | } |
---|
679 | |
---|
680 | |
---|
681 | /* Summarize a list of test failures. */ |
---|
682 | static void |
---|
683 | test_fail_summary(const struct testlist *fails) |
---|
684 | { |
---|
685 | struct testset *ts; |
---|
686 | int i, chars, total, first, last; |
---|
687 | |
---|
688 | puts(header); |
---|
689 | |
---|
690 | /* Failed Set Fail/Total (%) Skip Stat Failing (25) |
---|
691 | -------------------------- -------------- ---- ---- -------------- */ |
---|
692 | for (; fails; fails = fails->next) { |
---|
693 | ts = fails->ts; |
---|
694 | total = ts->count - ts->skipped; |
---|
695 | printf("%-26.26s %4d/%-4d %3.0f%% %4d ", ts->file, ts->failed, |
---|
696 | total, total ? (ts->failed * 100.0) / total : 0, |
---|
697 | ts->skipped); |
---|
698 | if (WIFEXITED(ts->status)) |
---|
699 | printf("%4d ", WEXITSTATUS(ts->status)); |
---|
700 | else |
---|
701 | printf(" -- "); |
---|
702 | if (ts->aborted) { |
---|
703 | puts("aborted"); |
---|
704 | continue; |
---|
705 | } |
---|
706 | chars = 0; |
---|
707 | first = 0; |
---|
708 | last = 0; |
---|
709 | for (i = 0; i < ts->count; i++) { |
---|
710 | if (ts->results[i] == TEST_FAIL) { |
---|
711 | if (first && i == last) |
---|
712 | last = i + 1; |
---|
713 | else { |
---|
714 | if (first) |
---|
715 | chars += test_print_range(first, last, chars, 20); |
---|
716 | first = i + 1; |
---|
717 | last = i + 1; |
---|
718 | } |
---|
719 | } |
---|
720 | } |
---|
721 | if (first) |
---|
722 | test_print_range(first, last, chars, 20); |
---|
723 | putchar('\n'); |
---|
724 | } |
---|
725 | } |
---|
726 | |
---|
727 | |
---|
728 | /* |
---|
729 | * Given the name of a test, a pointer to the testset struct, and the source |
---|
730 | * and build directories, find the test. We try first relative to the current |
---|
731 | * directory, then in the build directory (if not NULL), then in the source |
---|
732 | * directory. In each of those directories, we first try a "-t" extension and |
---|
733 | * then a ".t" extension. When we find an executable program, we fill in the |
---|
734 | * path member of the testset struct. If none of those paths are executable, |
---|
735 | * just fill in the name of the test with "-t" appended. |
---|
736 | * |
---|
737 | * The caller is responsible for freeing the path member of the testset |
---|
738 | * struct. |
---|
739 | */ |
---|
740 | static void |
---|
741 | find_test(const char *name, struct testset *ts, const char *source, |
---|
742 | const char *build) |
---|
743 | { |
---|
744 | char *path; |
---|
745 | const char *bases[] = { ".", build, source, NULL }; |
---|
746 | int i; |
---|
747 | |
---|
748 | for (i = 0; bases[i] != NULL; i++) { |
---|
749 | path = xmalloc(strlen(bases[i]) + strlen(name) + 4); |
---|
750 | sprintf(path, "%s/%s-t", bases[i], name); |
---|
751 | if (access(path, X_OK) != 0) |
---|
752 | path[strlen(path) - 2] = '.'; |
---|
753 | if (access(path, X_OK) == 0) |
---|
754 | break; |
---|
755 | free(path); |
---|
756 | path = NULL; |
---|
757 | } |
---|
758 | if (path == NULL) { |
---|
759 | path = xmalloc(strlen(name) + 3); |
---|
760 | sprintf(path, "%s-t", name); |
---|
761 | } |
---|
762 | ts->path = path; |
---|
763 | } |
---|
764 | |
---|
765 | |
---|
766 | /* |
---|
767 | * Run a batch of tests from a given file listing each test on a line by |
---|
768 | * itself. Takes two additional parameters: the root of the source directory |
---|
769 | * and the root of the build directory. Test programs will be first searched |
---|
770 | * for in the current directory, then the build directory, then the source |
---|
771 | * directory. The file must be rewindable. Returns true iff all tests |
---|
772 | * passed. |
---|
773 | */ |
---|
774 | static int |
---|
775 | test_batch(const char *testlist, const char *source, const char *build) |
---|
776 | { |
---|
777 | FILE *tests; |
---|
778 | size_t length, i; |
---|
779 | size_t longest = 0; |
---|
780 | char buffer[BUFSIZ]; |
---|
781 | int line; |
---|
782 | struct testset ts, *tmp; |
---|
783 | struct timeval start, end; |
---|
784 | struct rusage stats; |
---|
785 | struct testlist *failhead = 0; |
---|
786 | struct testlist *failtail = 0; |
---|
787 | int total = 0; |
---|
788 | int passed = 0; |
---|
789 | int skipped = 0; |
---|
790 | int failed = 0; |
---|
791 | int aborted = 0; |
---|
792 | |
---|
793 | /* |
---|
794 | * Open our file of tests to run and scan it, checking for lines that |
---|
795 | * are too long and searching for the longest line. |
---|
796 | */ |
---|
797 | tests = fopen(testlist, "r"); |
---|
798 | if (!tests) |
---|
799 | sysdie("can't open %s", testlist); |
---|
800 | line = 0; |
---|
801 | while (fgets(buffer, sizeof(buffer), tests)) { |
---|
802 | line++; |
---|
803 | length = strlen(buffer) - 1; |
---|
804 | if (buffer[length] != '\n') { |
---|
805 | fprintf(stderr, "%s:%d: line too long\n", testlist, line); |
---|
806 | exit(1); |
---|
807 | } |
---|
808 | if (length > longest) |
---|
809 | longest = length; |
---|
810 | } |
---|
811 | if (fseek(tests, 0, SEEK_SET) == -1) |
---|
812 | sysdie("can't rewind %s", testlist); |
---|
813 | |
---|
814 | /* |
---|
815 | * Add two to longest and round up to the nearest tab stop. This is how |
---|
816 | * wide the column for printing the current test name will be. |
---|
817 | */ |
---|
818 | longest += 2; |
---|
819 | if (longest % 8) |
---|
820 | longest += 8 - (longest % 8); |
---|
821 | |
---|
822 | /* Start the wall clock timer. */ |
---|
823 | gettimeofday(&start, NULL); |
---|
824 | |
---|
825 | /* |
---|
826 | * Now, plow through our tests again, running each one. Check line |
---|
827 | * length again out of paranoia. |
---|
828 | */ |
---|
829 | line = 0; |
---|
830 | while (fgets(buffer, sizeof(buffer), tests)) { |
---|
831 | line++; |
---|
832 | length = strlen(buffer) - 1; |
---|
833 | if (buffer[length] != '\n') { |
---|
834 | fprintf(stderr, "%s:%d: line too long\n", testlist, line); |
---|
835 | exit(1); |
---|
836 | } |
---|
837 | buffer[length] = '\0'; |
---|
838 | fputs(buffer, stdout); |
---|
839 | for (i = length; i < longest; i++) |
---|
840 | putchar('.'); |
---|
841 | if (isatty(STDOUT_FILENO)) |
---|
842 | fflush(stdout); |
---|
843 | memset(&ts, 0, sizeof(ts)); |
---|
844 | ts.file = xstrdup(buffer); |
---|
845 | find_test(buffer, &ts, source, build); |
---|
846 | ts.reason = NULL; |
---|
847 | if (test_run(&ts)) { |
---|
848 | free(ts.file); |
---|
849 | free(ts.path); |
---|
850 | if (ts.reason != NULL) |
---|
851 | free(ts.reason); |
---|
852 | } else { |
---|
853 | tmp = xmalloc(sizeof(struct testset)); |
---|
854 | memcpy(tmp, &ts, sizeof(struct testset)); |
---|
855 | if (!failhead) { |
---|
856 | failhead = xmalloc(sizeof(struct testset)); |
---|
857 | failhead->ts = tmp; |
---|
858 | failhead->next = 0; |
---|
859 | failtail = failhead; |
---|
860 | } else { |
---|
861 | failtail->next = xmalloc(sizeof(struct testset)); |
---|
862 | failtail = failtail->next; |
---|
863 | failtail->ts = tmp; |
---|
864 | failtail->next = 0; |
---|
865 | } |
---|
866 | } |
---|
867 | aborted += ts.aborted; |
---|
868 | total += ts.count + ts.all_skipped; |
---|
869 | passed += ts.passed; |
---|
870 | skipped += ts.skipped + ts.all_skipped; |
---|
871 | failed += ts.failed; |
---|
872 | } |
---|
873 | total -= skipped; |
---|
874 | |
---|
875 | /* Stop the timer and get our child resource statistics. */ |
---|
876 | gettimeofday(&end, NULL); |
---|
877 | getrusage(RUSAGE_CHILDREN, &stats); |
---|
878 | |
---|
879 | /* Print out our final results. */ |
---|
880 | if (failhead) |
---|
881 | test_fail_summary(failhead); |
---|
882 | putchar('\n'); |
---|
883 | if (aborted != 0) { |
---|
884 | if (aborted == 1) |
---|
885 | printf("Aborted %d test set", aborted); |
---|
886 | else |
---|
887 | printf("Aborted %d test sets", aborted); |
---|
888 | printf(", passed %d/%d tests", passed, total); |
---|
889 | } |
---|
890 | else if (failed == 0) |
---|
891 | fputs("All tests successful", stdout); |
---|
892 | else |
---|
893 | printf("Failed %d/%d tests, %.2f%% okay", failed, total, |
---|
894 | (total - failed) * 100.0 / total); |
---|
895 | if (skipped != 0) { |
---|
896 | if (skipped == 1) |
---|
897 | printf(", %d test skipped", skipped); |
---|
898 | else |
---|
899 | printf(", %d tests skipped", skipped); |
---|
900 | } |
---|
901 | puts("."); |
---|
902 | printf("Files=%d, Tests=%d", line, total); |
---|
903 | printf(", %.2f seconds", tv_diff(&end, &start)); |
---|
904 | printf(" (%.2f usr + %.2f sys = %.2f CPU)\n", |
---|
905 | tv_seconds(&stats.ru_utime), tv_seconds(&stats.ru_stime), |
---|
906 | tv_sum(&stats.ru_utime, &stats.ru_stime)); |
---|
907 | return (failed == 0 && aborted == 0); |
---|
908 | } |
---|
909 | |
---|
910 | |
---|
911 | /* |
---|
912 | * Run a single test case. This involves just running the test program after |
---|
913 | * having done the environment setup and finding the test program. |
---|
914 | */ |
---|
915 | static void |
---|
916 | test_single(const char *program, const char *source, const char *build) |
---|
917 | { |
---|
918 | struct testset ts; |
---|
919 | |
---|
920 | memset(&ts, 0, sizeof(ts)); |
---|
921 | find_test(program, &ts, source, build); |
---|
922 | if (execl(ts.path, ts.path, (char *) 0) == -1) |
---|
923 | sysdie("cannot exec %s", ts.path); |
---|
924 | } |
---|
925 | |
---|
926 | |
---|
927 | /* |
---|
928 | * Main routine. Set the SOURCE and BUILD environment variables and then, |
---|
929 | * given a file listing tests, run each test listed. |
---|
930 | */ |
---|
931 | int |
---|
932 | main(int argc, char *argv[]) |
---|
933 | { |
---|
934 | int option; |
---|
935 | int single = 0; |
---|
936 | char *setting; |
---|
937 | const char *list; |
---|
938 | const char *source = SOURCE; |
---|
939 | const char *build = BUILD; |
---|
940 | |
---|
941 | while ((option = getopt(argc, argv, "b:os:")) != EOF) { |
---|
942 | switch (option) { |
---|
943 | case 'b': |
---|
944 | build = optarg; |
---|
945 | break; |
---|
946 | case 'o': |
---|
947 | single = 1; |
---|
948 | break; |
---|
949 | case 's': |
---|
950 | source = optarg; |
---|
951 | break; |
---|
952 | default: |
---|
953 | exit(1); |
---|
954 | } |
---|
955 | } |
---|
956 | argc -= optind; |
---|
957 | argv += optind; |
---|
958 | if (argc != 1) { |
---|
959 | fprintf(stderr, "Usage: runtests <test-list>\n"); |
---|
960 | exit(1); |
---|
961 | } |
---|
962 | |
---|
963 | if (source != NULL) { |
---|
964 | setting = xmalloc(strlen("SOURCE=") + strlen(source) + 1); |
---|
965 | sprintf(setting, "SOURCE=%s", source); |
---|
966 | if (putenv(setting) != 0) |
---|
967 | sysdie("cannot set SOURCE in the environment"); |
---|
968 | } |
---|
969 | if (build != NULL) { |
---|
970 | setting = xmalloc(strlen("BUILD=") + strlen(build) + 1); |
---|
971 | sprintf(setting, "BUILD=%s", build); |
---|
972 | if (putenv(setting) != 0) |
---|
973 | sysdie("cannot set BUILD in the environment"); |
---|
974 | } |
---|
975 | |
---|
976 | if (single) { |
---|
977 | test_single(argv[0], source, build); |
---|
978 | exit(0); |
---|
979 | } else { |
---|
980 | list = strrchr(argv[0], '/'); |
---|
981 | if (list == NULL) |
---|
982 | list = argv[0]; |
---|
983 | else |
---|
984 | list++; |
---|
985 | printf(banner, list); |
---|
986 | exit(test_batch(argv[0], source, build) ? 0 : 1); |
---|
987 | } |
---|
988 | } |
---|