]> eyrie.org Git - kerberos/krb5-strength.git/blob - tests/runtests.c
Add a test suite
[kerberos/krb5-strength.git] / tests / runtests.c
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      * Consume the rest of the test output, close the output descriptor,
654      * retrieve the exit status, and pass that information to test_analyze()
655      * for eventual output.
656      */
657     while (fgets(buffer, sizeof(buffer), output))
658         ;
659     fclose(output);
660     child = waitpid(testpid, &ts->status, 0);
661     if (child == (pid_t) -1) {
662         if (!ts->reported) {
663             puts("ABORTED");
664             fflush(stdout);
665         }
666         sysdie("waitpid for %u failed", (unsigned int) testpid);
667     }
668     if (ts->all_skipped)
669         ts->aborted = 0;
670     status = test_analyze(ts);
671
672     /* Convert missing tests to failed tests. */
673     for (i = 0; i < ts->count; i++) {
674         if (ts->results[i] == TEST_INVALID) {
675             ts->failed++;
676             ts->results[i] = TEST_FAIL;
677             status = 0;
678         }
679     }
680     return status;
681 }
682
683
684 /* Summarize a list of test failures. */
685 static void
686 test_fail_summary(const struct testlist *fails)
687 {
688     struct testset *ts;
689     int i, chars, total, first, last;
690
691     puts(header);
692
693     /* Failed Set                 Fail/Total (%) Skip Stat  Failing (25)
694        -------------------------- -------------- ---- ----  -------------- */
695     for (; fails; fails = fails->next) {
696         ts = fails->ts;
697         total = ts->count - ts->skipped;
698         printf("%-26.26s %4d/%-4d %3.0f%% %4d ", ts->file, ts->failed,
699                total, total ? (ts->failed * 100.0) / total : 0,
700                ts->skipped);
701         if (WIFEXITED(ts->status))
702             printf("%4d  ", WEXITSTATUS(ts->status));
703         else
704             printf("  --  ");
705         if (ts->aborted) {
706             puts("aborted");
707             continue;
708         }
709         chars = 0;
710         first = 0;
711         last = 0;
712         for (i = 0; i < ts->count; i++) {
713             if (ts->results[i] == TEST_FAIL) {
714                 if (first && i == last)
715                     last = i + 1;
716                 else {
717                     if (first)
718                         chars += test_print_range(first, last, chars, 20);
719                     first = i + 1;
720                     last = i + 1;
721                 }
722             }
723         }
724         if (first)
725             test_print_range(first, last, chars, 20);
726         putchar('\n');
727     }
728 }
729
730
731 /*
732  * Given the name of a test, a pointer to the testset struct, and the source
733  * and build directories, find the test.  We try first relative to the current
734  * directory, then in the build directory (if not NULL), then in the source
735  * directory.  In each of those directories, we first try a "-t" extension and
736  * then a ".t" extension.  When we find an executable program, we fill in the
737  * path member of the testset struct.  If none of those paths are executable,
738  * just fill in the name of the test with "-t" appended.
739  *
740  * The caller is responsible for freeing the path member of the testset
741  * struct.
742  */
743 static void
744 find_test(const char *name, struct testset *ts, const char *source,
745           const char *build)
746 {
747     char *path;
748     const char *bases[] = { ".", build, source, NULL };
749     int i;
750
751     for (i = 0; bases[i] != NULL; i++) {
752         path = xmalloc(strlen(bases[i]) + strlen(name) + 4);
753         sprintf(path, "%s/%s-t", bases[i], name);
754         if (access(path, X_OK) != 0)
755             path[strlen(path) - 2] = '.';
756         if (access(path, X_OK) == 0)
757             break;
758         free(path);
759         path = NULL;
760     }
761     if (path == NULL) {
762         path = xmalloc(strlen(name) + 3);
763         sprintf(path, "%s-t", name);
764     }
765     ts->path = path;
766 }
767
768
769 /*
770  * Run a batch of tests from a given file listing each test on a line by
771  * itself.  Takes two additional parameters: the root of the source directory
772  * and the root of the build directory.  Test programs will be first searched
773  * for in the current directory, then the build directory, then the source
774  * directory.  The file must be rewindable.  Returns true iff all tests
775  * passed.
776  */
777 static int
778 test_batch(const char *testlist, const char *source, const char *build)
779 {
780     FILE *tests;
781     size_t length, i;
782     size_t longest = 0;
783     char buffer[BUFSIZ];
784     int line;
785     struct testset ts, *tmp;
786     struct timeval start, end;
787     struct rusage stats;
788     struct testlist *failhead = 0;
789     struct testlist *failtail = 0;
790     int total = 0;
791     int passed = 0;
792     int skipped = 0;
793     int failed = 0;
794     int aborted = 0;
795
796     /*
797      * Open our file of tests to run and scan it, checking for lines that
798      * are too long and searching for the longest line.
799      */
800     tests = fopen(testlist, "r");
801     if (!tests)
802         sysdie("can't open %s", testlist);
803     line = 0;
804     while (fgets(buffer, sizeof(buffer), tests)) {
805         line++;
806         length = strlen(buffer) - 1;
807         if (buffer[length] != '\n') {
808             fprintf(stderr, "%s:%d: line too long\n", testlist, line);
809             exit(1);
810         }
811         if (length > longest)
812             longest = length;
813     }
814     if (fseek(tests, 0, SEEK_SET) == -1)
815         sysdie("can't rewind %s", testlist);
816
817     /*
818      * Add two to longest and round up to the nearest tab stop.  This is how
819      * wide the column for printing the current test name will be.
820      */
821     longest += 2;
822     if (longest % 8)
823         longest += 8 - (longest % 8);
824
825     /* Start the wall clock timer. */
826     gettimeofday(&start, NULL);
827
828     /*
829      * Now, plow through our tests again, running each one.  Check line
830      * length again out of paranoia.
831      */
832     line = 0;
833     while (fgets(buffer, sizeof(buffer), tests)) {
834         line++;
835         length = strlen(buffer) - 1;
836         if (buffer[length] != '\n') {
837             fprintf(stderr, "%s:%d: line too long\n", testlist, line);
838             exit(1);
839         }
840         buffer[length] = '\0';
841         fputs(buffer, stdout);
842         for (i = length; i < longest; i++)
843             putchar('.');
844         if (isatty(STDOUT_FILENO))
845             fflush(stdout);
846         memset(&ts, 0, sizeof(ts));
847         ts.file = xstrdup(buffer);
848         find_test(buffer, &ts, source, build);
849         ts.reason = NULL;
850         if (test_run(&ts)) {
851             free(ts.file);
852             free(ts.path);
853             if (ts.reason != NULL)
854                 free(ts.reason);
855         } else {
856             tmp = xmalloc(sizeof(struct testset));
857             memcpy(tmp, &ts, sizeof(struct testset));
858             if (!failhead) {
859                 failhead = xmalloc(sizeof(struct testset));
860                 failhead->ts = tmp;
861                 failhead->next = 0;
862                 failtail = failhead;
863             } else {
864                 failtail->next = xmalloc(sizeof(struct testset));
865                 failtail = failtail->next;
866                 failtail->ts = tmp;
867                 failtail->next = 0;
868             }
869         }
870         aborted += ts.aborted;
871         total += ts.count + ts.all_skipped;
872         passed += ts.passed;
873         skipped += ts.skipped + ts.all_skipped;
874         failed += ts.failed;
875     }
876     total -= skipped;
877
878     /* Stop the timer and get our child resource statistics. */
879     gettimeofday(&end, NULL);
880     getrusage(RUSAGE_CHILDREN, &stats);
881
882     /* Print out our final results. */
883     if (failhead)
884         test_fail_summary(failhead);
885     putchar('\n');
886     if (aborted != 0) {
887         if (aborted == 1)
888             printf("Aborted %d test set", aborted);
889         else
890             printf("Aborted %d test sets", aborted);
891         printf(", passed %d/%d tests", passed, total);
892     }
893     else if (failed == 0)
894         fputs("All tests successful", stdout);
895     else
896         printf("Failed %d/%d tests, %.2f%% okay", failed, total,
897                (total - failed) * 100.0 / total);
898     if (skipped != 0) {
899         if (skipped == 1)
900             printf(", %d test skipped", skipped);
901         else
902             printf(", %d tests skipped", skipped);
903     }
904     puts(".");
905     printf("Files=%d,  Tests=%d", line, total);
906     printf(",  %.2f seconds", tv_diff(&end, &start));
907     printf(" (%.2f usr + %.2f sys = %.2f CPU)\n",
908            tv_seconds(&stats.ru_utime), tv_seconds(&stats.ru_stime),
909            tv_sum(&stats.ru_utime, &stats.ru_stime));
910     return (failed == 0 && aborted == 0);
911 }
912
913
914 /*
915  * Run a single test case.  This involves just running the test program after
916  * having done the environment setup and finding the test program.
917  */
918 static void
919 test_single(const char *program, const char *source, const char *build)
920 {
921     struct testset ts;
922
923     memset(&ts, 0, sizeof(ts));
924     find_test(program, &ts, source, build);
925     if (execl(ts.path, ts.path, (char *) 0) == -1)
926         sysdie("cannot exec %s", ts.path);
927 }
928
929
930 /*
931  * Main routine.  Set the SOURCE and BUILD environment variables and then,
932  * given a file listing tests, run each test listed.
933  */
934 int
935 main(int argc, char *argv[])
936 {
937     int option;
938     int single = 0;
939     char *setting;
940     const char *list;
941     const char *source = SOURCE;
942     const char *build = BUILD;
943
944     while ((option = getopt(argc, argv, "b:os:")) != EOF) {
945         switch (option) {
946         case 'b':
947             build = optarg;
948             break;
949         case 'o':
950             single = 1;
951             break;
952         case 's':
953             source = optarg;
954             break;
955         default:
956             exit(1);
957         }
958     }
959     argc -= optind;
960     argv += optind;
961     if (argc != 1) {
962         fprintf(stderr, "Usage: runtests <test-list>\n");
963         exit(1);
964     }
965
966     if (source != NULL) {
967         setting = xmalloc(strlen("SOURCE=") + strlen(source) + 1);
968         sprintf(setting, "SOURCE=%s", source);
969         if (putenv(setting) != 0)
970             sysdie("cannot set SOURCE in the environment");
971     }
972     if (build != NULL) {
973         setting = xmalloc(strlen("BUILD=") + strlen(build) + 1);
974         sprintf(setting, "BUILD=%s", build);
975         if (putenv(setting) != 0)
976             sysdie("cannot set BUILD in the environment");
977     }
978
979     if (single) {
980         test_single(argv[0], source, build);
981         exit(0);
982     } else {
983         list = strrchr(argv[0], '/');
984         if (list == NULL)
985             list = argv[0];
986         else
987             list++;
988         printf(banner, list);
989         exit(test_batch(argv[0], source, build) ? 0 : 1);
990     }
991 }