chiark / gitweb /
debugging for thing that crashed
[innduct.git] / tests / runtests.c
1 /* $Id: runtests.c 7578 2006-09-11 23:03:12Z eagle $
2
3    Run a set of tests, reporting results.
4
5    Copyright 2000, 2001 Russ Allbery <rra@stanford.edu>
6
7    Please note that this file is maintained separately from INN by the above
8    author (which is why the coding style is slightly different).  Any fixes
9    added to the INN tree should also be reported to the above author if
10    necessary.
11
12    Permission is hereby granted, free of charge, to any person obtaining a
13    copy of this software and associated documentation files (the
14    "Software"), to deal in the Software without restriction, including
15    without limitation the rights to use, copy, modify, merge, publish,
16    distribute, sublicense, and/or sell copies of the Software, and to
17    permit persons to whom the Software is furnished to do so, subject to
18    the following conditions:
19
20    The above copyright notice and this permission notice shall be included
21    in all copies or substantial portions of the Software.
22
23    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
24    OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
25    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
26    IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
27    CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
28    TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
29    SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
30
31    Usage:
32
33         runtests <test-list>
34
35    Expects a list of executables located in the given file, one line per
36    executable.  For each one, runs it as part of a test suite, reporting
37    results.  Test output should start with a line containing the number of
38    tests (numbered from 1 to this number), and then each line should be in
39    the following format:
40
41         ok <number>
42         not ok <number>
43         ok <number> # skip
44
45    where <number> is the number of the test.  ok indicates success, not ok
46    indicates failure, and "# skip" indicates the test was skipped for some
47    reason (maybe because it doesn't apply to this platform).
48
49    This file is completely stand-alone by intention.  As stated more
50    formally in the license above, you are welcome to include it in your
51    packages as a test suite driver.  It requires ANSI C (__FILE__, __LINE__,
52    void, const, stdarg.h, string.h) and POSIX (fcntl.h, unistd.h, pid_t) and
53    won't compile out of the box on SunOS without adjustments to include
54    strings.h instead.  This is intentionally not fixed using autoconf so
55    that this file will not have a dependency on autoconf (although you're
56    welcome to fix it for your project if you want).  Since it doesn't matter
57    as much that the test suite for the software package be utterly portable
58    to older systems, this file should be portable enough for most purposes.
59
60    Any bug reports, bug fixes, and improvements are very much welcome and
61    should be sent to the e-mail address above. */
62
63 #include "config.h"
64 #include "clibrary.h"
65 #include "portable/wait.h"
66 #include "portable/time.h"
67 #include <ctype.h>
68 #include <errno.h>
69 #include <fcntl.h>
70 #include <stdarg.h>
71 #include <sys/stat.h>
72
73 /* sys/time.h must be included before sys/resource.h on some platforms. */
74 #include <sys/resource.h>
75
76 /* Test status codes. */
77 enum test_status {
78     TEST_FAIL,
79     TEST_PASS,
80     TEST_SKIP,
81     TEST_INVALID
82 };
83
84 /* Error exit statuses for test processes. */
85 #define CHILDERR_DUP    100     /* Couldn't redirect stderr or stdout. */
86 #define CHILDERR_EXEC   101     /* Couldn't exec child process. */
87 #define CHILDERR_STDERR 102     /* Couldn't open stderr file. */
88
89 /* Structure to hold data for a set of tests. */
90 struct testset {
91     const char *file;           /* The file name of the test. */
92     int count;                  /* Expected count of tests. */
93     int current;                /* The last seen test number. */
94     int passed;                 /* Count of passing tests. */
95     int failed;                 /* Count of failing lists. */
96     int skipped;                /* Count of skipped tests (passed). */
97     enum test_status *results;  /* Table of results by test number. */
98     int aborted;                /* Whether the set as aborted. */
99     int reported;               /* Whether the results were reported. */
100     int status;                 /* The exit status of the test. */
101 };
102
103 /* Structure to hold a linked list of test sets. */
104 struct testlist {
105     struct testset *ts;
106     struct testlist *next;
107 };
108
109 /* Header used for test output.  %s is replaced by the file name of the list
110    of tests. */
111 static const char banner[] = "\n\
112 Running all tests listed in %s.  If any tests fail, run the failing\n\
113 test program by hand to see more details.  The test program will have the\n\
114 same name as the test set but with \".t\" appended.\n\n";
115
116 /* Header for reports of failed tests. */
117 static const char header[] = "\n\
118 Failed Set                 Fail/Total (%) Skip Stat  Failing Tests\n\
119 -------------------------- -------------- ---- ----  ------------------------";
120
121 /* Include the file name and line number in malloc failures. */
122 #define xmalloc(size)   x_malloc((size), __FILE__, __LINE__)
123 #define xstrdup(p)      x_strdup((p), __FILE__, __LINE__)
124
125 /* Internal prototypes. */
126 static void sysdie(const char *format, ...);
127 static void *x_malloc(size_t, const char *file, int line);
128 static char *x_strdup(const char *, const char *file, int line);
129 static int test_analyze(struct testset *);
130 static int test_batch(const char *testlist);
131 static void test_checkline(const char *line, struct testset *);
132 static void test_fail_summary(const struct testlist *);
133 static int test_init(const char *line, struct testset *);
134 static int test_print_range(int first, int last, int chars, int limit);
135 static void test_summarize(struct testset *, int status);
136 static pid_t test_start(const char *path, int *fd);
137 static double tv_diff(const struct timeval *, const struct timeval *);
138 static double tv_seconds(const struct timeval *);
139 static double tv_sum(const struct timeval *, const struct timeval *);
140
141
142 /* Report a fatal error, including the results of strerror, and exit. */
143 static void
144 sysdie(const char *format, ...)
145 {
146     int oerrno;
147     va_list args;
148
149     oerrno = errno;
150     fflush(stdout);
151     fprintf(stderr, "runtests: ");
152     va_start(args, format);
153     vfprintf(stderr, format, args);
154     va_end(args);
155     fprintf(stderr, ": %s\n", strerror(oerrno));
156     exit(1);
157 }
158
159
160 /* Allocate memory, reporting a fatal error and exiting on failure. */
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 /* Copy a string, reporting a fatal error and exiting on failure. */
175 static char *
176 x_strdup(const char *s, const char *file, int line)
177 {
178     char *p;
179     size_t len;
180
181     len = strlen(s) + 1;
182     p = malloc(len);
183     if (!p)
184         sysdie("failed to strdup %lu bytes at %s line %d",
185                (unsigned long) len, file, line);
186     memcpy(p, s, len);
187     return p;
188 }
189
190
191 /* Given a struct timeval, return the number of seconds it represents as a
192    double.  Use difftime() to convert a time_t to a double. */
193 static double
194 tv_seconds(const struct timeval *tv)
195 {
196     return difftime(tv->tv_sec, 0) + tv->tv_usec * 1e-6;
197 }
198
199 /* Given two struct timevals, return the difference in seconds. */
200 static double
201 tv_diff(const struct timeval *tv1, const struct timeval *tv0)
202 {
203     return tv_seconds(tv1) - tv_seconds(tv0);
204 }
205
206 /* Given two struct timevals, return the sum in seconds as a double. */
207 static double
208 tv_sum(const struct timeval *tv1, const struct timeval *tv2)
209 {
210     return tv_seconds(tv1) + tv_seconds(tv2);
211 }
212
213
214 /* Read the first line of test output, which should contain the range of
215    test numbers, and initialize the testset structure.  Assume it was zeroed
216    before being passed in.  Return true if initialization succeeds, false
217    otherwise. */
218 static int
219 test_init(const char *line, struct testset *ts)
220 {
221     int i;
222
223     /* Prefer a simple number of tests, but if the count is given as a range
224        such as 1..10, accept that too for compatibility with Perl's
225        Test::Harness. */
226     while (isspace((unsigned char)(*line))) line++;
227     if (!strncmp(line, "1..", 3)) line += 3;
228
229     /* Get the count, check it for validity, and initialize the struct. */
230     i = atoi(line);
231     if (i <= 0) {
232         puts("invalid test count");
233         ts->aborted = 1;
234         ts->reported = 1;
235         return 0;
236     }
237     ts->count = i;
238     ts->results = xmalloc(ts->count * sizeof(enum test_status));
239     for (i = 0; i < ts->count; i++) ts->results[i] = TEST_INVALID;
240     return 1;
241 }
242
243
244 /* Start a program, connecting its stdout to a pipe on our end and its
245    stderr to /dev/null, and storing the file descriptor to read from in the
246    two argument.  Returns the PID of the new process.  Errors are fatal. */
247 static pid_t
248 test_start(const char *path, int *fd)
249 {
250     int fds[2], errfd;
251     pid_t child;
252
253     if (pipe(fds) == -1) sysdie("can't create pipe");
254     child = fork();
255     if (child == (pid_t) -1) {
256         sysdie("can't fork");
257     } else if (child == 0) {
258         /* In child.  Set up our stdout and stderr. */
259         errfd = open("/dev/null", O_WRONLY);
260         if (errfd < 0) _exit(CHILDERR_STDERR);
261         if (dup2(errfd, 2) == -1) _exit(CHILDERR_DUP);
262         close(fds[0]);
263         if (dup2(fds[1], 1) == -1) _exit(CHILDERR_DUP);
264
265         /* Now, exec our process. */
266         if (execl(path, path, (char *) 0) == -1) _exit(CHILDERR_EXEC);
267     } else {
268         /* In parent.  Close the extra file descriptor. */
269         close(fds[1]);
270     }
271     *fd = fds[0];
272     return child;
273 }
274
275
276 /* Given a single line of output from a test, parse it and return the
277    success status of that test.  Anything printed to stdout not matching the
278    form /^(not )?ok \d+/ is ignored.  Sets ts->current to the test number
279    that just reported status. */
280 static void
281 test_checkline(const char *line, struct testset *ts)
282 {
283     enum test_status status = TEST_PASS;
284     int current;
285
286     /* If the given line isn't newline-terminated, it was too big for an
287        fgets(), which means ignore it. */
288     if (line[strlen(line) - 1] != '\n') return;
289
290     /* Parse the line, ignoring something we can't parse. */
291     if (!strncmp(line, "not ", 4)) {
292         status = TEST_FAIL;
293         line += 4;
294     }
295     if (strncmp(line, "ok ", 3)) return;
296     line += 3;
297     current = atoi(line);
298     if (current == 0) return;
299     if (current < 0 || current > ts->count) {
300         printf("invalid test number %d\n", current);
301         ts->aborted = 1;
302         ts->reported = 1;
303         return;
304     }
305     while (isspace((unsigned char)(*line))) line++;
306     while (isdigit((unsigned char)(*line))) line++;
307     while (isspace((unsigned char)(*line))) line++;
308     if (*line == '#') {
309         line++;
310         while (isspace((unsigned char)(*line))) line++;
311         if (!strncmp(line, "skip", 4)) status = TEST_SKIP;
312     }
313
314     /* Make sure that the test number is in range and not a duplicate. */
315     if (ts->results[current - 1] != TEST_INVALID) {
316         printf("duplicate test number %d\n", current);
317         ts->aborted = 1;
318         ts->reported = 1;
319         return;
320     }
321
322     /* Good results.  Increment our various counters. */
323     switch (status) {
324         case TEST_PASS: ts->passed++;   break;
325         case TEST_FAIL: ts->failed++;   break;
326         case TEST_SKIP: ts->skipped++;  break;
327         default:                        break;
328     }
329     ts->current = current;
330     ts->results[current - 1] = status;
331 }
332
333
334 /* Print out a range of test numbers, returning the number of characters it
335    took up.  Add a comma and a space before the range if chars indicates
336    that something has already been printed on the line, and print
337    ... instead if chars plus the space needed would go over the limit (use a
338    limit of 0 to disable this. */
339 static int
340 test_print_range(int first, int last, int chars, int limit)
341 {
342     int needed = 0;
343     int out = 0;
344     int n;
345
346     if (chars > 0) {
347         needed += 2;
348         if (!limit || chars <= limit) out += printf(", ");
349     }
350     for (n = first; n > 0; n /= 10)
351         needed++;
352     if (last > first) {
353         for (n = last; n > 0; n /= 10)
354             needed++;
355         needed++;
356     }
357     if (limit && chars + needed > limit) {
358         if (chars <= limit) out += printf("...");
359     } else {
360         if (last > first) out += printf("%d-", first);
361         out += printf("%d", last);
362     }
363     return out;
364 }
365
366
367 /* Summarize a single test set.  The second argument is 0 if the set exited
368    cleanly, a positive integer representing the exit status if it exited
369    with a non-zero status, and a negative integer representing the signal
370    that terminated it if it was killed by a signal. */
371 static void
372 test_summarize(struct testset *ts, int status)
373 {
374     int i;
375     int missing = 0;
376     int failed = 0;
377     int first = 0;
378     int last = 0;
379
380     if (ts->aborted) {
381         fputs("aborted", stdout);
382         if (ts->count > 0)
383             printf(", passed %d/%d", ts->passed, ts->count - ts->skipped);
384     } else {
385         for (i = 0; i < ts->count; i++) {
386             if (ts->results[i] == TEST_INVALID) {
387                 if (missing == 0) fputs("MISSED ", stdout);
388                 if (first && i == last) {
389                     last = i + 1;
390                 } else {
391                     if (first) {
392                         test_print_range(first, last, missing - 1, 0);
393                     }
394                     missing++;
395                     first = i + 1;
396                     last = i + 1;
397                 }
398             }
399         }
400         if (first) test_print_range(first, last, missing - 1, 0);
401         first = 0;
402         last = 0;
403         for (i = 0; i < ts->count; i++) {
404             if (ts->results[i] == TEST_FAIL) {
405                 if (missing && !failed) fputs("; ", stdout);
406                 if (failed == 0) fputs("FAILED ", stdout);
407                 if (first && i == last) {
408                     last = i + 1;
409                 } else {
410                     if (first) {
411                         test_print_range(first, last, failed - 1, 0);
412                     }
413                     failed++;
414                     first = i + 1;
415                     last = i + 1;
416                 }
417             }
418         }
419         if (first) test_print_range(first, last, failed - 1, 0);
420         if (!missing && !failed) {
421             fputs(!status ? "ok" : "dubious", stdout);
422             if (ts->skipped > 0) printf(" (skipped %d tests)", ts->skipped);
423         }
424     }
425     if (status > 0) {
426         printf(" (exit status %d)", status);
427     } else if (status < 0) {
428         printf(" (killed by signal %d%s)", -status,
429                WCOREDUMP(ts->status) ? ", core dumped" : "");
430     }
431     putchar('\n');
432 }
433
434
435 /* Given a test set, analyze the results, classify the exit status, handle a
436    few special error messages, and then pass it along to test_summarize()
437    for the regular output. */
438 static int
439 test_analyze(struct testset *ts)
440 {
441     if (ts->reported) return 0;
442     if (WIFEXITED(ts->status) && WEXITSTATUS(ts->status) != 0) {
443         switch (WEXITSTATUS(ts->status)) {
444         case CHILDERR_DUP:
445             if (!ts->reported) puts("can't dup file descriptors");
446             break;
447         case CHILDERR_EXEC:
448             if (!ts->reported) puts("execution failed (not found?)");
449             break;
450         case CHILDERR_STDERR:
451             if (!ts->reported) puts("can't open /dev/null");
452             break;
453         default:
454             test_summarize(ts, WEXITSTATUS(ts->status));
455             break;
456         }
457         return 0;
458     } else if (WIFSIGNALED(ts->status)) {
459         test_summarize(ts, -WTERMSIG(ts->status));
460         return 0;
461     } else {
462         test_summarize(ts, 0);
463         return (ts->failed == 0);
464     }
465 }
466
467
468 /* Runs a single test set, accumulating and then reporting the results.
469    Returns true if the test set was successfully run and all tests passed,
470    false otherwise. */
471 static int
472 test_run(struct testset *ts)
473 {
474     pid_t testpid, child;
475     int outfd, i, status;
476     FILE *output;
477     char buffer[BUFSIZ];
478     char *file;
479
480     /* Initialize the test and our data structures, flagging this set in
481        error if the initialization fails. */
482     file = xmalloc(strlen(ts->file) + 3);
483     strcpy(file, ts->file);
484     strcat(file, ".t");
485     testpid = test_start(file, &outfd);
486     free(file);
487     output = fdopen(outfd, "r");
488     if (!output) sysdie("fdopen failed");
489     if (!fgets(buffer, sizeof(buffer), output)) ts->aborted = 1;
490     if (!ts->aborted && !test_init(buffer, ts)) {
491         while (fgets(buffer, sizeof(buffer), output))
492             ;
493         ts->aborted = 1;
494     }
495
496     /* Pass each line of output to test_checkline(). */
497     while (!ts->aborted && fgets(buffer, sizeof(buffer), output))
498         test_checkline(buffer, ts);
499     if (ferror(output)) ts->aborted = 1;
500
501     /* Close the output descriptor, retrieve the exit status, and pass that
502        information to test_analyze() for eventual output. */
503     fclose(output);
504     child = waitpid(testpid, &ts->status, 0);
505     if (child == (pid_t) -1)
506         sysdie("waitpid for %u failed", (unsigned int) testpid);
507     status = test_analyze(ts);
508
509     /* Convert missing tests to failed tests. */
510     for (i = 0; i < ts->count; i++) {
511         if (ts->results[i] == TEST_INVALID) {
512             ts->failed++;
513             ts->results[i] = TEST_FAIL;
514             status = 0;
515         }
516     }
517     return status;
518 }
519
520
521 /* Summarize a list of test failures. */
522 static void
523 test_fail_summary(const struct testlist *fails)
524 {
525     struct testset *ts;
526     int i, chars, total, first, last;
527
528     puts(header);
529
530     /* Failed Set                 Fail/Total (%) Skip Stat  Failing (25)
531        -------------------------- -------------- ---- ----  -------------- */
532     for (; fails; fails = fails->next) {
533         ts = fails->ts;
534         total = ts->count - ts->skipped;
535         printf("%-26.26s %4d/%-4d %3.0f%% %4d ", ts->file, ts->failed,
536                total, total ? (ts->failed * 100.0) / total : 0,
537                ts->skipped);
538         if (WIFEXITED(ts->status)) {
539             printf("%4d  ", WEXITSTATUS(ts->status));
540         } else {
541             printf("  --  ");
542         }
543         if (ts->aborted) {
544             puts("aborted");
545             continue;
546         }
547         chars = 0;
548         first = 0;
549         last = 0;
550         for (i = 0; i < ts->count; i++) {
551             if (ts->results[i] == TEST_FAIL) {
552                 if (first && i == last) {
553                     last = i + 1;
554                 } else {
555                     if (first)
556                         chars += test_print_range(first, last, chars, 20);
557                     first = i + 1;
558                     last = i + 1;
559                 }
560             }
561         }
562         if (first) test_print_range(first, last, chars, 20);
563         putchar('\n');
564     }
565 }
566
567
568 /* Run a batch of tests from a given file listing each test on a line by
569    itself.  The file must be rewindable.  Returns true iff all tests
570    passed. */
571 static int
572 test_batch(const char *testlist)
573 {
574     FILE *tests;
575     size_t length, i;
576     size_t longest = 0;
577     char buffer[BUFSIZ];
578     int line;
579     struct testset ts, *tmp;
580     struct timeval start, end;
581     struct rusage stats;
582     struct testlist *failhead = 0;
583     struct testlist *failtail = 0;
584     int total = 0;
585     int passed = 0;
586     int skipped = 0;
587     int failed = 0;
588     int aborted = 0;
589
590     /* Open our file of tests to run and scan it, checking for lines that
591        are too long and searching for the longest line. */
592     tests = fopen(testlist, "r");
593     if (!tests) sysdie("can't open %s", testlist);
594     line = 0;
595     while (fgets(buffer, sizeof(buffer), tests)) {
596         line++;
597         length = strlen(buffer) - 1;
598         if (buffer[length] != '\n') {
599             fprintf(stderr, "%s:%d: line too long\n", testlist, line);
600             exit(1);
601         }
602         if (length > longest) longest = length;
603     }
604     if (fseek(tests, 0, SEEK_SET) == -1)
605         sysdie("can't rewind %s", testlist);
606
607     /* Add two to longest and round up to the nearest tab stop.  This is how
608        wide the column for printing the current test name will be. */
609     longest += 2;
610     if (longest % 8) longest += 8 - (longest % 8);
611
612     /* Start the wall clock timer. */
613     gettimeofday(&start, NULL);
614
615     /* Now, plow through our tests again, running each one.  Check line
616        length again out of paranoia. */
617     line = 0;
618     while (fgets(buffer, sizeof(buffer), tests)) {
619         line++;
620         length = strlen(buffer) - 1;
621         if (buffer[length] != '\n') {
622             fprintf(stderr, "%s:%d: line too long\n", testlist, line);
623             exit(1);
624         }
625         buffer[length] = '\0';
626         fputs(buffer, stdout);
627         for (i = length; i < longest; i++) putchar('.');
628         memset(&ts, 0, sizeof(ts));
629         ts.file = xstrdup(buffer);
630         if (!test_run(&ts)) {
631             tmp = xmalloc(sizeof(struct testset));
632             memcpy(tmp, &ts, sizeof(struct testset));
633             if (!failhead) {
634                 failhead = xmalloc(sizeof(struct testset));
635                 failhead->ts = tmp;
636                 failhead->next = 0;
637                 failtail = failhead;
638             } else {
639                 failtail->next = xmalloc(sizeof(struct testset));
640                 failtail = failtail->next;
641                 failtail->ts = tmp;
642                 failtail->next = 0;
643             }
644         }
645         aborted += ts.aborted;
646         total += ts.count;
647         passed += ts.passed;
648         skipped += ts.skipped;
649         failed += ts.failed;
650     }
651     total -= skipped;
652
653     /* Stop the timer and get our child resource statistics. */
654     gettimeofday(&end, NULL);
655     getrusage(RUSAGE_CHILDREN, &stats);
656
657     /* Print out our final results. */
658     if (failhead) test_fail_summary(failhead);
659     putchar('\n');
660     if (aborted) {
661         printf("Aborted %d test sets, passed %d/%d tests.\n", aborted,
662                passed, total);
663     } else if (failed == 0) {
664         fputs("All tests successful", stdout);
665         if (skipped) printf(", %d tests skipped", skipped);
666         puts(".");
667     } else {
668         printf("Failed %d/%d tests, %.2f%% okay.\n", failed, total,
669                (total - failed) * 100.0 / total);
670     }
671     printf("Files=%d,  Tests=%d", line, total);
672     printf(",  %.2f seconds", tv_diff(&end, &start));
673     printf(" (%.2f usr + %.2f sys = %.2f CPU)\n",
674            tv_seconds(&stats.ru_utime), tv_seconds(&stats.ru_stime),
675            tv_sum(&stats.ru_utime, &stats.ru_stime));
676     return !(failed || aborted);
677 }
678
679
680 /* Main routine.  Given a file listing tests, run each test listed. */
681 int
682 main(int argc, char *argv[])
683 {
684     if (argc != 2) {
685         fprintf(stderr, "Usage: runtests <test-list>\n");
686         exit(1);
687     }
688     printf(banner, argv[1]);
689     exit(test_batch(argv[1]) ? 0 : 1);
690 }