]> eyrie.org Git - kerberos/krb5-strength.git/blob - tests/runtests.c
2b7959bfc854d9751774b1d4d7c58966ea900904
[kerberos/krb5-strength.git] / tests / runtests.c
1 /*
2  * Run a set of tests, reporting results.
3  *
4  * Usage:
5  *
6  *      runtests [-b <build-dir>] [-s <source-dir>] <test-list>
7  *      runtests -o [-b <build-dir>] [-s <source-dir>] <test>
8  *
9  * In the first case, expects a list of executables located in the given file,
10  * one line per executable.  For each one, runs it as part of a test suite,
11  * reporting results.  Test output should start with a line containing the
12  * number of tests (numbered from 1 to this number), optionally preceded by
13  * "1..", although that line may be given anywhere in the output.  Each
14  * additional line should be in the following format:
15  *
16  *      ok <number>
17  *      not ok <number>
18  *      ok <number> # skip
19  *      not ok <number> # todo
20  *
21  * where <number> is the number of the test.  An optional comment is permitted
22  * after the number if preceded by whitespace.  ok indicates success, not ok
23  * indicates failure.  "# skip" and "# todo" are a special cases of a comment,
24  * and must start with exactly that formatting.  They indicate the test was
25  * skipped for some reason (maybe because it doesn't apply to this platform)
26  * or is testing something known to currently fail.  The text following either
27  * "# skip" or "# todo" and whitespace is the reason.
28  *
29  * As a special case, the first line of the output may be in the form:
30  *
31  *      1..0 # skip some reason
32  *
33  * which indicates that this entire test case should be skipped and gives a
34  * reason.
35  *
36  * Any other lines are ignored, although for compliance with the TAP protocol
37  * all lines other than the ones in the above format should be sent to
38  * standard error rather than standard output and start with #.
39  *
40  * This is a subset of TAP as documented in Test::Harness::TAP or
41  * TAP::Parser::Grammar, which comes with Perl.
42  *
43  * If the -o option is given, instead run a single test and display all of its
44  * output.  This is intended for use with failing tests so that the person
45  * running the test suite can get more details about what failed.
46  *
47  * If built with the C preprocessor symbols SOURCE and BUILD defined, C TAP
48  * Harness will export those values in the environment so that tests can find
49  * the source and build directory and will look for tests under both
50  * directories.  These paths can also be set with the -b and -s command-line
51  * options, which will override anything set at build time.
52  *
53  * Any bug reports, bug fixes, and improvements are very much welcome and
54  * should be sent to the e-mail address below.  This program is part of C TAP
55  * Harness <http://www.eyrie.org/~eagle/software/c-tap-harness/>.
56  *
57  * Copyright 2000, 2001, 2004, 2006, 2007, 2008, 2009, 2010, 2011
58  *     Russ Allbery <eagle@eyrie.org>
59  *
60  * Permission is hereby granted, free of charge, to any person obtaining a
61  * copy of this software and associated documentation files (the "Software"),
62  * to deal in the Software without restriction, including without limitation
63  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
64  * and/or sell copies of the Software, and to permit persons to whom the
65  * Software is furnished to do so, subject to the following conditions:
66  *
67  * The above copyright notice and this permission notice shall be included in
68  * all copies or substantial portions of the Software.
69  *
70  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
71  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
72  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
73  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
74  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
75  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
76  * DEALINGS IN THE SOFTWARE.
77 */
78
79 /* Required for fdopen(), getopt(), and putenv(). */
80 #if defined(__STRICT_ANSI__) || defined(PEDANTIC)
81 # ifndef _XOPEN_SOURCE
82 #  define _XOPEN_SOURCE 500
83 # endif
84 #endif
85
86 #include <ctype.h>
87 #include <errno.h>
88 #include <fcntl.h>
89 #include <stdarg.h>
90 #include <stddef.h>
91 #include <stdio.h>
92 #include <stdlib.h>
93 #include <string.h>
94 #include <strings.h>
95 #include <sys/stat.h>
96 #include <sys/time.h>
97 #include <sys/types.h>
98 #include <sys/wait.h>
99 #include <time.h>
100 #include <unistd.h>
101
102 /* sys/time.h must be included before sys/resource.h on some platforms. */
103 #include <sys/resource.h>
104
105 /* AIX doesn't have WCOREDUMP. */
106 #ifndef WCOREDUMP
107 # define WCOREDUMP(status) ((unsigned)(status) & 0x80)
108 #endif
109
110 /*
111  * Used for iterating through arrays.  Returns the number of elements in the
112  * array (useful for a < upper bound in a for loop).
113  */
114 #define ARRAY_SIZE(array) (sizeof(array) / sizeof((array)[0]))
115
116 /*
117  * The source and build versions of the tests directory.  This is used to set
118  * the SOURCE and BUILD environment variables and find test programs, if set.
119  * Normally, this should be set as part of the build process to the test
120  * subdirectories of $(abs_top_srcdir) and $(abs_top_builddir) respectively.
121  */
122 #ifndef SOURCE
123 # define SOURCE NULL
124 #endif
125 #ifndef BUILD
126 # define BUILD NULL
127 #endif
128
129 /* Test status codes. */
130 enum test_status {
131     TEST_FAIL,
132     TEST_PASS,
133     TEST_SKIP,
134     TEST_INVALID
135 };
136
137 /* Indicates the state of our plan. */
138 enum plan_status {
139     PLAN_INIT,                  /* Nothing seen yet. */
140     PLAN_FIRST,                 /* Plan seen before any tests. */
141     PLAN_PENDING,               /* Test seen and no plan yet. */
142     PLAN_FINAL                  /* Plan seen after some tests. */
143 };
144
145 /* Error exit statuses for test processes. */
146 #define CHILDERR_DUP    100     /* Couldn't redirect stderr or stdout. */
147 #define CHILDERR_EXEC   101     /* Couldn't exec child process. */
148 #define CHILDERR_STDERR 102     /* Couldn't open stderr file. */
149
150 /* Structure to hold data for a set of tests. */
151 struct testset {
152     char *file;                 /* The file name of the test. */
153     char *path;                 /* The path to the test program. */
154     enum plan_status plan;      /* The status of our plan. */
155     unsigned long count;        /* Expected count of tests. */
156     unsigned long current;      /* The last seen test number. */
157     unsigned int length;        /* The length of the last status message. */
158     unsigned long passed;       /* Count of passing tests. */
159     unsigned long failed;       /* Count of failing lists. */
160     unsigned long skipped;      /* Count of skipped tests (passed). */
161     unsigned long allocated;    /* The size of the results table. */
162     enum test_status *results;  /* Table of results by test number. */
163     unsigned int aborted;       /* Whether the set was aborted. */
164     int reported;               /* Whether the results were reported. */
165     int status;                 /* The exit status of the test. */
166     unsigned int all_skipped;   /* Whether all tests were skipped. */
167     char *reason;               /* Why all tests were skipped. */
168 };
169
170 /* Structure to hold a linked list of test sets. */
171 struct testlist {
172     struct testset *ts;
173     struct testlist *next;
174 };
175
176 /*
177  * Usage message.  Should be used as a printf format with four arguments: the
178  * path to runtests, given three times, and the usage_description.  This is
179  * split into variables to satisfy the pedantic ISO C90 limit on strings.
180  */
181 static const char usage_message[] = "\
182 Usage: %s [-b <build-dir>] [-s <source-dir>] <test> ...\n\
183        %s [-b <build-dir>] [-s <source-dir>] -l <test-list>\n\
184        %s -o [-b <build-dir>] [-s <source-dir>] <test>\n\
185 \n%s";
186 static const char usage_extra[] = "\
187 Options:\n\
188     -b <build-dir>      Set the build directory to <build-dir>\n\
189     -l <list>           Take the list of tests to run from <test-list>\n\
190     -o                  Run a single test rather than a list of tests\n\
191     -s <source-dir>     Set the source directory to <source-dir>\n\
192 \n\
193 runtests normally runs each test listed on the command line.  With the -l\n\
194 option, it instead runs every test listed in a file.  With the -o option,\n\
195 it instead runs a single test and shows its complete output.\n";
196
197 /*
198  * Header used for test output.  %s is replaced by the file name of the list
199  * of tests.
200  */
201 static const char banner[] = "\n\
202 Running all tests listed in %s.  If any tests fail, run the failing\n\
203 test program with runtests -o to see more details.\n\n";
204
205 /* Header for reports of failed tests. */
206 static const char header[] = "\n\
207 Failed Set                 Fail/Total (%) Skip Stat  Failing Tests\n\
208 -------------------------- -------------- ---- ----  ------------------------";
209
210 /* Include the file name and line number in malloc failures. */
211 #define xcalloc(n, size)  x_calloc((n), (size), __FILE__, __LINE__)
212 #define xmalloc(size)     x_malloc((size), __FILE__, __LINE__)
213 #define xrealloc(p, size) x_realloc((p), (size), __FILE__, __LINE__)
214 #define xstrdup(p)        x_strdup((p), __FILE__, __LINE__)
215
216 /*
217  * __attribute__ is available in gcc 2.5 and later, but only with gcc 2.7
218  * could you use the __format__ form of the attributes, which is what we use
219  * (to avoid confusion with other macros).
220  */
221 #ifndef __attribute__
222 # if __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 7)
223 #  define __attribute__(spec)   /* empty */
224 # endif
225 #endif
226
227 /*
228  * We use __alloc_size__, but it was only available in fairly recent versions
229  * of GCC.  Suppress warnings about the unknown attribute if GCC is too old.
230  * We know that we're GCC at this point, so we can use the GCC variadic macro
231  * extension, which will still work with versions of GCC too old to have C99
232  * variadic macro support.
233  */
234 #if !defined(__attribute__) && !defined(__alloc_size__)
235 # if __GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 3)
236 #  define __alloc_size__(spec, args...) /* empty */
237 # endif
238 #endif
239
240 /*
241  * LLVM and Clang pretend to be GCC but don't support all of the __attribute__
242  * settings that GCC does.  For them, suppress warnings about unknown
243  * attributes on declarations.  This unfortunately will affect the entire
244  * compilation context, but there's no push and pop available.
245  */
246 #if !defined(__attribute__) && (defined(__llvm__) || defined(__clang__))
247 # pragma GCC diagnostic ignored "-Wattributes"
248 #endif
249
250 /* Declare internal functions that benefit from compiler attributes. */
251 static void sysdie(const char *, ...)
252     __attribute__((__nonnull__, __noreturn__, __format__(printf, 1, 2)));
253 static void *x_calloc(size_t, size_t, const char *, int)
254     __attribute__((__alloc_size__(1, 2), __malloc__, __nonnull__));
255 static void *x_malloc(size_t, const char *, int)
256     __attribute__((__alloc_size__(1), __malloc__, __nonnull__));
257 static void *x_realloc(void *, size_t, const char *, int)
258     __attribute__((__alloc_size__(2), __malloc__, __nonnull__(3)));
259 static char *x_strdup(const char *, const char *, int)
260     __attribute__((__malloc__, __nonnull__));
261
262
263 /*
264  * Report a fatal error, including the results of strerror, and exit.
265  */
266 static void
267 sysdie(const char *format, ...)
268 {
269     int oerrno;
270     va_list args;
271
272     oerrno = errno;
273     fflush(stdout);
274     fprintf(stderr, "runtests: ");
275     va_start(args, format);
276     vfprintf(stderr, format, args);
277     va_end(args);
278     fprintf(stderr, ": %s\n", strerror(oerrno));
279     exit(1);
280 }
281
282
283 /*
284  * Allocate zeroed memory, reporting a fatal error and exiting on failure.
285  */
286 static void *
287 x_calloc(size_t n, size_t size, const char *file, int line)
288 {
289     void *p;
290
291     n = (n > 0) ? n : 1;
292     size = (size > 0) ? size : 1;
293     p = calloc(n, size);
294     if (p == NULL)
295         sysdie("failed to calloc %lu bytes at %s line %d",
296                (unsigned long) size, file, line);
297     return p;
298 }
299
300
301 /*
302  * Allocate memory, reporting a fatal error and exiting on failure.
303  */
304 static void *
305 x_malloc(size_t size, const char *file, int line)
306 {
307     void *p;
308
309     p = malloc(size);
310     if (p == NULL)
311         sysdie("failed to malloc %lu bytes at %s line %d",
312                (unsigned long) size, file, line);
313     return p;
314 }
315
316
317 /*
318  * Reallocate memory, reporting a fatal error and exiting on failure.
319  */
320 static void *
321 x_realloc(void *p, size_t size, const char *file, int line)
322 {
323     p = realloc(p, size);
324     if (p == NULL)
325         sysdie("failed to realloc %lu bytes at %s line %d",
326                (unsigned long) size, file, line);
327     return p;
328 }
329
330
331 /*
332  * Copy a string, reporting a fatal error and exiting on failure.
333  */
334 static char *
335 x_strdup(const char *s, const char *file, int line)
336 {
337     char *p;
338     size_t len;
339
340     len = strlen(s) + 1;
341     p = malloc(len);
342     if (p == NULL)
343         sysdie("failed to strdup %lu bytes at %s line %d",
344                (unsigned long) len, file, line);
345     memcpy(p, s, len);
346     return p;
347 }
348
349
350 /*
351  * Given a struct timeval, return the number of seconds it represents as a
352  * double.  Use difftime() to convert a time_t to a double.
353  */
354 static double
355 tv_seconds(const struct timeval *tv)
356 {
357     return difftime(tv->tv_sec, 0) + tv->tv_usec * 1e-6;
358 }
359
360
361 /*
362  * Given two struct timevals, return the difference in seconds.
363  */
364 static double
365 tv_diff(const struct timeval *tv1, const struct timeval *tv0)
366 {
367     return tv_seconds(tv1) - tv_seconds(tv0);
368 }
369
370
371 /*
372  * Given two struct timevals, return the sum in seconds as a double.
373  */
374 static double
375 tv_sum(const struct timeval *tv1, const struct timeval *tv2)
376 {
377     return tv_seconds(tv1) + tv_seconds(tv2);
378 }
379
380
381 /*
382  * Given a pointer to a string, skip any leading whitespace and return a
383  * pointer to the first non-whitespace character.
384  */
385 static const char *
386 skip_whitespace(const char *p)
387 {
388     while (isspace((unsigned char)(*p)))
389         p++;
390     return p;
391 }
392
393
394 /*
395  * Start a program, connecting its stdout to a pipe on our end and its stderr
396  * to /dev/null, and storing the file descriptor to read from in the two
397  * argument.  Returns the PID of the new process.  Errors are fatal.
398  */
399 static pid_t
400 test_start(const char *path, int *fd)
401 {
402     int fds[2], errfd;
403     pid_t child;
404
405     if (pipe(fds) == -1) {
406         puts("ABORTED");
407         fflush(stdout);
408         sysdie("can't create pipe");
409     }
410     child = fork();
411     if (child == (pid_t) -1) {
412         puts("ABORTED");
413         fflush(stdout);
414         sysdie("can't fork");
415     } else if (child == 0) {
416         /* In child.  Set up our stdout and stderr. */
417         errfd = open("/dev/null", O_WRONLY);
418         if (errfd < 0)
419             _exit(CHILDERR_STDERR);
420         if (dup2(errfd, 2) == -1)
421             _exit(CHILDERR_DUP);
422         close(fds[0]);
423         if (dup2(fds[1], 1) == -1)
424             _exit(CHILDERR_DUP);
425
426         /* Now, exec our process. */
427         if (execl(path, path, (char *) 0) == -1)
428             _exit(CHILDERR_EXEC);
429     } else {
430         /* In parent.  Close the extra file descriptor. */
431         close(fds[1]);
432     }
433     *fd = fds[0];
434     return child;
435 }
436
437
438 /*
439  * Back up over the output saying what test we were executing.
440  */
441 static void
442 test_backspace(struct testset *ts)
443 {
444     unsigned int i;
445
446     if (!isatty(STDOUT_FILENO))
447         return;
448     for (i = 0; i < ts->length; i++)
449         putchar('\b');
450     for (i = 0; i < ts->length; i++)
451         putchar(' ');
452     for (i = 0; i < ts->length; i++)
453         putchar('\b');
454     ts->length = 0;
455 }
456
457
458 /*
459  * Read the plan line of test output, which should contain the range of test
460  * numbers.  We may initialize the testset structure here if we haven't yet
461  * seen a test.  Return true if initialization succeeded and the test should
462  * continue, false otherwise.
463  */
464 static int
465 test_plan(const char *line, struct testset *ts)
466 {
467     unsigned long i;
468     long n;
469
470     /*
471      * Accept a plan without the leading 1.. for compatibility with older
472      * versions of runtests.  This will only be allowed if we've not yet seen
473      * a test result.
474      */
475     line = skip_whitespace(line);
476     if (strncmp(line, "1..", 3) == 0)
477         line += 3;
478
479     /*
480      * Get the count, check it for validity, and initialize the struct.  If we
481      * have something of the form "1..0 # skip foo", the whole file was
482      * skipped; record that.  If we do skip the whole file, zero out all of
483      * our statistics, since they're no longer relevant.  strtol is called
484      * with a second argument to advance the line pointer past the count to
485      * make it simpler to detect the # skip case.
486      */
487     n = strtol(line, (char **) &line, 10);
488     if (n == 0) {
489         line = skip_whitespace(line);
490         if (*line == '#') {
491             line = skip_whitespace(line + 1);
492             if (strncasecmp(line, "skip", 4) == 0) {
493                 line = skip_whitespace(line + 4);
494                 if (*line != '\0') {
495                     ts->reason = xstrdup(line);
496                     ts->reason[strlen(ts->reason) - 1] = '\0';
497                 }
498                 ts->all_skipped = 1;
499                 ts->aborted = 1;
500                 ts->count = 0;
501                 ts->passed = 0;
502                 ts->skipped = 0;
503                 ts->failed = 0;
504                 return 0;
505             }
506         }
507     }
508     if (n <= 0) {
509         puts("ABORTED (invalid test count)");
510         ts->aborted = 1;
511         ts->reported = 1;
512         return 0;
513     }
514     if (ts->plan == PLAN_INIT && ts->allocated == 0) {
515         ts->count = n;
516         ts->allocated = n;
517         ts->plan = PLAN_FIRST;
518         ts->results = xmalloc(ts->count * sizeof(enum test_status));
519         for (i = 0; i < ts->count; i++)
520             ts->results[i] = TEST_INVALID;
521     } else if (ts->plan == PLAN_PENDING) {
522         if ((unsigned long) n < ts->count) {
523             test_backspace(ts);
524             printf("ABORTED (invalid test number %lu)\n", ts->count);
525             ts->aborted = 1;
526             ts->reported = 1;
527             return 0;
528         }
529         ts->count = n;
530         if ((unsigned long) n > ts->allocated) {
531             ts->results = xrealloc(ts->results, n * sizeof(enum test_status));
532             for (i = ts->allocated; i < ts->count; i++)
533                 ts->results[i] = TEST_INVALID;
534             ts->allocated = n;
535         }
536         ts->plan = PLAN_FINAL;
537     }
538     return 1;
539 }
540
541
542 /*
543  * Given a single line of output from a test, parse it and return the success
544  * status of that test.  Anything printed to stdout not matching the form
545  * /^(not )?ok \d+/ is ignored.  Sets ts->current to the test number that just
546  * reported status.
547  */
548 static void
549 test_checkline(const char *line, struct testset *ts)
550 {
551     enum test_status status = TEST_PASS;
552     const char *bail;
553     char *end;
554     long number;
555     unsigned long i, current;
556     int outlen;
557
558     /* Before anything, check for a test abort. */
559     bail = strstr(line, "Bail out!");
560     if (bail != NULL) {
561         bail = skip_whitespace(bail + strlen("Bail out!"));
562         if (*bail != '\0') {
563             size_t length;
564
565             length = strlen(bail);
566             if (bail[length - 1] == '\n')
567                 length--;
568             test_backspace(ts);
569             printf("ABORTED (%.*s)\n", (int) length, bail);
570             ts->reported = 1;
571         }
572         ts->aborted = 1;
573         return;
574     }
575
576     /*
577      * If the given line isn't newline-terminated, it was too big for an
578      * fgets(), which means ignore it.
579      */
580     if (line[strlen(line) - 1] != '\n')
581         return;
582
583     /* If the line begins with a hash mark, ignore it. */
584     if (line[0] == '#')
585         return;
586
587     /* If we haven't yet seen a plan, look for one. */
588     if (ts->plan == PLAN_INIT && isdigit((unsigned char)(*line))) {
589         if (!test_plan(line, ts))
590             return;
591     } else if (strncmp(line, "1..", 3) == 0) {
592         if (ts->plan == PLAN_PENDING) {
593             if (!test_plan(line, ts))
594                 return;
595         } else {
596             test_backspace(ts);
597             puts("ABORTED (multiple plans)");
598             ts->aborted = 1;
599             ts->reported = 1;
600             return;
601         }
602     }
603
604     /* Parse the line, ignoring something we can't parse. */
605     if (strncmp(line, "not ", 4) == 0) {
606         status = TEST_FAIL;
607         line += 4;
608     }
609     if (strncmp(line, "ok", 2) != 0)
610         return;
611     line = skip_whitespace(line + 2);
612     errno = 0;
613     number = strtol(line, &end, 10);
614     if (errno != 0 || end == line)
615         number = ts->current + 1;
616     current = number;
617     if (number <= 0 || (current > ts->count && ts->plan == PLAN_FIRST)) {
618         test_backspace(ts);
619         printf("ABORTED (invalid test number %lu)\n", current);
620         ts->aborted = 1;
621         ts->reported = 1;
622         return;
623     }
624
625     /* We have a valid test result.  Tweak the results array if needed. */
626     if (ts->plan == PLAN_INIT || ts->plan == PLAN_PENDING) {
627         ts->plan = PLAN_PENDING;
628         if (current > ts->count)
629             ts->count = current;
630         if (current > ts->allocated) {
631             unsigned long n;
632
633             n = (ts->allocated == 0) ? 32 : ts->allocated * 2;
634             if (n < current)
635                 n = current;
636             ts->results = xrealloc(ts->results, n * sizeof(enum test_status));
637             for (i = ts->allocated; i < n; i++)
638                 ts->results[i] = TEST_INVALID;
639             ts->allocated = n;
640         }
641     }
642
643     /*
644      * Handle directives.  We should probably do something more interesting
645      * with unexpected passes of todo tests.
646      */
647     while (isdigit((unsigned char)(*line)))
648         line++;
649     line = skip_whitespace(line);
650     if (*line == '#') {
651         line = skip_whitespace(line + 1);
652         if (strncasecmp(line, "skip", 4) == 0)
653             status = TEST_SKIP;
654         if (strncasecmp(line, "todo", 4) == 0)
655             status = (status == TEST_FAIL) ? TEST_SKIP : TEST_FAIL;
656     }
657
658     /* Make sure that the test number is in range and not a duplicate. */
659     if (ts->results[current - 1] != TEST_INVALID) {
660         test_backspace(ts);
661         printf("ABORTED (duplicate test number %lu)\n", current);
662         ts->aborted = 1;
663         ts->reported = 1;
664         return;
665     }
666
667     /* Good results.  Increment our various counters. */
668     switch (status) {
669         case TEST_PASS: ts->passed++;   break;
670         case TEST_FAIL: ts->failed++;   break;
671         case TEST_SKIP: ts->skipped++;  break;
672         case TEST_INVALID:              break;
673     }
674     ts->current = current;
675     ts->results[current - 1] = status;
676     if (isatty(STDOUT_FILENO)) {
677         test_backspace(ts);
678         if (ts->plan == PLAN_PENDING)
679             outlen = printf("%lu/?", current);
680         else
681             outlen = printf("%lu/%lu", current, ts->count);
682         ts->length = (outlen >= 0) ? outlen : 0;
683         fflush(stdout);
684     }
685 }
686
687
688 /*
689  * Print out a range of test numbers, returning the number of characters it
690  * took up.  Takes the first number, the last number, the number of characters
691  * already printed on the line, and the limit of number of characters the line
692  * can hold.  Add a comma and a space before the range if chars indicates that
693  * something has already been printed on the line, and print ... instead if
694  * chars plus the space needed would go over the limit (use a limit of 0 to
695  * disable this).
696  */
697 static unsigned int
698 test_print_range(unsigned long first, unsigned long last, unsigned int chars,
699                  unsigned int limit)
700 {
701     unsigned int needed = 0;
702     unsigned long n;
703
704     for (n = first; n > 0; n /= 10)
705         needed++;
706     if (last > first) {
707         for (n = last; n > 0; n /= 10)
708             needed++;
709         needed++;
710     }
711     if (chars > 0)
712         needed += 2;
713     if (limit > 0 && chars + needed > limit) {
714         needed = 0;
715         if (chars <= limit) {
716             if (chars > 0) {
717                 printf(", ");
718                 needed += 2;
719             }
720             printf("...");
721             needed += 3;
722         }
723     } else {
724         if (chars > 0)
725             printf(", ");
726         if (last > first)
727             printf("%lu-", first);
728         printf("%lu", last);
729     }
730     return needed;
731 }
732
733
734 /*
735  * Summarize a single test set.  The second argument is 0 if the set exited
736  * cleanly, a positive integer representing the exit status if it exited
737  * with a non-zero status, and a negative integer representing the signal
738  * that terminated it if it was killed by a signal.
739  */
740 static void
741 test_summarize(struct testset *ts, int status)
742 {
743     unsigned long i;
744     unsigned long missing = 0;
745     unsigned long failed = 0;
746     unsigned long first = 0;
747     unsigned long last = 0;
748
749     if (ts->aborted) {
750         fputs("ABORTED", stdout);
751         if (ts->count > 0)
752             printf(" (passed %lu/%lu)", ts->passed, ts->count - ts->skipped);
753     } else {
754         for (i = 0; i < ts->count; i++) {
755             if (ts->results[i] == TEST_INVALID) {
756                 if (missing == 0)
757                     fputs("MISSED ", stdout);
758                 if (first && i == last)
759                     last = i + 1;
760                 else {
761                     if (first)
762                         test_print_range(first, last, missing - 1, 0);
763                     missing++;
764                     first = i + 1;
765                     last = i + 1;
766                 }
767             }
768         }
769         if (first)
770             test_print_range(first, last, missing - 1, 0);
771         first = 0;
772         last = 0;
773         for (i = 0; i < ts->count; i++) {
774             if (ts->results[i] == TEST_FAIL) {
775                 if (missing && !failed)
776                     fputs("; ", stdout);
777                 if (failed == 0)
778                     fputs("FAILED ", stdout);
779                 if (first && i == last)
780                     last = i + 1;
781                 else {
782                     if (first)
783                         test_print_range(first, last, failed - 1, 0);
784                     failed++;
785                     first = i + 1;
786                     last = i + 1;
787                 }
788             }
789         }
790         if (first)
791             test_print_range(first, last, failed - 1, 0);
792         if (!missing && !failed) {
793             fputs(!status ? "ok" : "dubious", stdout);
794             if (ts->skipped > 0) {
795                 if (ts->skipped == 1)
796                     printf(" (skipped %lu test)", ts->skipped);
797                 else
798                     printf(" (skipped %lu tests)", ts->skipped);
799             }
800         }
801     }
802     if (status > 0)
803         printf(" (exit status %d)", status);
804     else if (status < 0)
805         printf(" (killed by signal %d%s)", -status,
806                WCOREDUMP(ts->status) ? ", core dumped" : "");
807     putchar('\n');
808 }
809
810
811 /*
812  * Given a test set, analyze the results, classify the exit status, handle a
813  * few special error messages, and then pass it along to test_summarize() for
814  * the regular output.  Returns true if the test set ran successfully and all
815  * tests passed or were skipped, false otherwise.
816  */
817 static int
818 test_analyze(struct testset *ts)
819 {
820     if (ts->reported)
821         return 0;
822     if (ts->all_skipped) {
823         if (ts->reason == NULL)
824             puts("skipped");
825         else
826             printf("skipped (%s)\n", ts->reason);
827         return 1;
828     } else if (WIFEXITED(ts->status) && WEXITSTATUS(ts->status) != 0) {
829         switch (WEXITSTATUS(ts->status)) {
830         case CHILDERR_DUP:
831             if (!ts->reported)
832                 puts("ABORTED (can't dup file descriptors)");
833             break;
834         case CHILDERR_EXEC:
835             if (!ts->reported)
836                 puts("ABORTED (execution failed -- not found?)");
837             break;
838         case CHILDERR_STDERR:
839             if (!ts->reported)
840                 puts("ABORTED (can't open /dev/null)");
841             break;
842         default:
843             test_summarize(ts, WEXITSTATUS(ts->status));
844             break;
845         }
846         return 0;
847     } else if (WIFSIGNALED(ts->status)) {
848         test_summarize(ts, -WTERMSIG(ts->status));
849         return 0;
850     } else if (ts->plan != PLAN_FIRST && ts->plan != PLAN_FINAL) {
851         puts("ABORTED (no valid test plan)");
852         ts->aborted = 1;
853         return 0;
854     } else {
855         test_summarize(ts, 0);
856         return (ts->failed == 0);
857     }
858 }
859
860
861 /*
862  * Runs a single test set, accumulating and then reporting the results.
863  * Returns true if the test set was successfully run and all tests passed,
864  * false otherwise.
865  */
866 static int
867 test_run(struct testset *ts)
868 {
869     pid_t testpid, child;
870     int outfd, status;
871     unsigned long i;
872     FILE *output;
873     char buffer[BUFSIZ];
874
875     /* Run the test program. */
876     testpid = test_start(ts->path, &outfd);
877     output = fdopen(outfd, "r");
878     if (!output) {
879         puts("ABORTED");
880         fflush(stdout);
881         sysdie("fdopen failed");
882     }
883
884     /* Pass each line of output to test_checkline(). */
885     while (!ts->aborted && fgets(buffer, sizeof(buffer), output))
886         test_checkline(buffer, ts);
887     if (ferror(output) || ts->plan == PLAN_INIT)
888         ts->aborted = 1;
889     test_backspace(ts);
890
891     /*
892      * Consume the rest of the test output, close the output descriptor,
893      * retrieve the exit status, and pass that information to test_analyze()
894      * for eventual output.
895      */
896     while (fgets(buffer, sizeof(buffer), output))
897         ;
898     fclose(output);
899     child = waitpid(testpid, &ts->status, 0);
900     if (child == (pid_t) -1) {
901         if (!ts->reported) {
902             puts("ABORTED");
903             fflush(stdout);
904         }
905         sysdie("waitpid for %u failed", (unsigned int) testpid);
906     }
907     if (ts->all_skipped)
908         ts->aborted = 0;
909     status = test_analyze(ts);
910
911     /* Convert missing tests to failed tests. */
912     for (i = 0; i < ts->count; i++) {
913         if (ts->results[i] == TEST_INVALID) {
914             ts->failed++;
915             ts->results[i] = TEST_FAIL;
916             status = 0;
917         }
918     }
919     return status;
920 }
921
922
923 /* Summarize a list of test failures. */
924 static void
925 test_fail_summary(const struct testlist *fails)
926 {
927     struct testset *ts;
928     unsigned int chars;
929     unsigned long i, first, last, total;
930
931     puts(header);
932
933     /* Failed Set                 Fail/Total (%) Skip Stat  Failing (25)
934        -------------------------- -------------- ---- ----  -------------- */
935     for (; fails; fails = fails->next) {
936         ts = fails->ts;
937         total = ts->count - ts->skipped;
938         printf("%-26.26s %4lu/%-4lu %3.0f%% %4lu ", ts->file, ts->failed,
939                total, total ? (ts->failed * 100.0) / total : 0,
940                ts->skipped);
941         if (WIFEXITED(ts->status))
942             printf("%4d  ", WEXITSTATUS(ts->status));
943         else
944             printf("  --  ");
945         if (ts->aborted) {
946             puts("aborted");
947             continue;
948         }
949         chars = 0;
950         first = 0;
951         last = 0;
952         for (i = 0; i < ts->count; i++) {
953             if (ts->results[i] == TEST_FAIL) {
954                 if (first != 0 && i == last)
955                     last = i + 1;
956                 else {
957                     if (first != 0)
958                         chars += test_print_range(first, last, chars, 19);
959                     first = i + 1;
960                     last = i + 1;
961                 }
962             }
963         }
964         if (first != 0)
965             test_print_range(first, last, chars, 19);
966         putchar('\n');
967     }
968 }
969
970
971 /*
972  * Check whether a given file path is a valid test.  Currently, this checks
973  * whether it is executable and is a regular file.  Returns true or false.
974  */
975 static int
976 is_valid_test(const char *path)
977 {
978     struct stat st;
979
980     if (access(path, X_OK) < 0)
981         return 0;
982     if (stat(path, &st) < 0)
983         return 0;
984     if (!S_ISREG(st.st_mode))
985         return 0;
986     return 1;
987 }
988
989
990 /*
991  * Given the name of a test, a pointer to the testset struct, and the source
992  * and build directories, find the test.  We try first relative to the current
993  * directory, then in the build directory (if not NULL), then in the source
994  * directory.  In each of those directories, we first try a "-t" extension and
995  * then a ".t" extension.  When we find an executable program, we return the
996  * path to that program.  If none of those paths are executable, just fill in
997  * the name of the test as is.
998  *
999  * The caller is responsible for freeing the path member of the testset
1000  * struct.
1001  */
1002 static char *
1003 find_test(const char *name, const char *source, const char *build)
1004 {
1005     char *path;
1006     const char *bases[3], *suffix, *base;
1007     unsigned int i, j;
1008     const char *suffixes[3] = { "-t", ".t", "" };
1009
1010     /* Possible base directories. */
1011     bases[0] = ".";
1012     bases[1] = build;
1013     bases[2] = source;
1014
1015     /* Try each suffix with each base. */
1016     for (i = 0; i < ARRAY_SIZE(suffixes); i++) {
1017         suffix = suffixes[i];
1018         for (j = 0; j < ARRAY_SIZE(bases); j++) {
1019             base = bases[j];
1020             if (base == NULL)
1021                 continue;
1022             path = xmalloc(strlen(base) + strlen(name) + strlen(suffix) + 2);
1023             sprintf(path, "%s/%s%s", base, name, suffix);
1024             if (is_valid_test(path))
1025                 return path;
1026             free(path);
1027             path = NULL;
1028         }
1029     }
1030     if (path == NULL)
1031         path = xstrdup(name);
1032     return path;
1033 }
1034
1035
1036 /*
1037  * Read a list of tests from a file, returning the list of tests as a struct
1038  * testlist.  Reports an error to standard error and exits if the list of
1039  * tests cannot be read.
1040  */
1041 static struct testlist *
1042 read_test_list(const char *filename)
1043 {
1044     FILE *file;
1045     unsigned int line;
1046     size_t length;
1047     char buffer[BUFSIZ];
1048     struct testlist *listhead, *current;
1049
1050     /* Create the initial container list that will hold our results. */
1051     listhead = xmalloc(sizeof(struct testlist));
1052     listhead->ts = NULL;
1053     listhead->next = NULL;
1054     current = NULL;
1055
1056     /*
1057      * Open our file of tests to run and read it line by line, creating a new
1058      * struct testlist and struct testset for each line.
1059      */
1060     file = fopen(filename, "r");
1061     if (file == NULL)
1062         sysdie("can't open %s", filename);
1063     line = 0;
1064     while (fgets(buffer, sizeof(buffer), file)) {
1065         line++;
1066         length = strlen(buffer) - 1;
1067         if (buffer[length] != '\n') {
1068             fprintf(stderr, "%s:%u: line too long\n", filename, line);
1069             exit(1);
1070         }
1071         buffer[length] = '\0';
1072         if (current == NULL)
1073             current = listhead;
1074         else {
1075             current->next = xmalloc(sizeof(struct testlist));
1076             current = current->next;
1077             current->next = NULL;
1078         }
1079         current->ts = xcalloc(1, sizeof(struct testset));
1080         current->ts->plan = PLAN_INIT;
1081         current->ts->file = xstrdup(buffer);
1082         current->ts->reason = NULL;
1083     }
1084     fclose(file);
1085
1086     /* Return the results. */
1087     return listhead;
1088 }
1089
1090
1091 /*
1092  * Build a list of tests from command line arguments.  Takes the argv and argc
1093  * representing the command line arguments and returns a newly allocated test
1094  * list.  The caller is responsible for freeing.
1095  */
1096 static struct testlist *
1097 build_test_list(char *argv[], int argc)
1098 {
1099     int i;
1100     struct testlist *listhead, *current;
1101
1102     /* Create the initial container list that will hold our results. */
1103     listhead = xmalloc(sizeof(struct testlist));
1104     listhead->ts = NULL;
1105     listhead->next = NULL;
1106     current = NULL;
1107
1108     /* Walk the list of arguments and create test sets for them. */
1109     for (i = 0; i < argc; i++) {
1110         if (current == NULL)
1111             current = listhead;
1112         else {
1113             current->next = xmalloc(sizeof(struct testlist));
1114             current = current->next;
1115             current->next = NULL;
1116         }
1117         current->ts = xcalloc(1, sizeof(struct testset));
1118         current->ts->plan = PLAN_INIT;
1119         current->ts->file = xstrdup(argv[i]);
1120         current->ts->reason = NULL;
1121     }
1122
1123     /* Return the results. */
1124     return listhead;
1125 }
1126
1127
1128 /* Free a struct testset. */
1129 static void
1130 free_testset(struct testset *ts)
1131 {
1132     free(ts->file);
1133     free(ts->path);
1134     free(ts->results);
1135     if (ts->reason != NULL)
1136         free(ts->reason);
1137     free(ts);
1138 }
1139
1140
1141 /*
1142  * Run a batch of tests.  Takes two additional parameters: the root of the
1143  * source directory and the root of the build directory.  Test programs will
1144  * be first searched for in the current directory, then the build directory,
1145  * then the source directory.  Returns true iff all tests passed, and always
1146  * frees the test list that's passed in.
1147  */
1148 static int
1149 test_batch(struct testlist *tests, const char *source, const char *build)
1150 {
1151     size_t length;
1152     unsigned int i;
1153     unsigned int longest = 0;
1154     unsigned int count = 0;
1155     struct testset *ts;
1156     struct timeval start, end;
1157     struct rusage stats;
1158     struct testlist *failhead = NULL;
1159     struct testlist *failtail = NULL;
1160     struct testlist *current, *next;
1161     int succeeded;
1162     unsigned long total = 0;
1163     unsigned long passed = 0;
1164     unsigned long skipped = 0;
1165     unsigned long failed = 0;
1166     unsigned long aborted = 0;
1167
1168     /* Walk the list of tests to find the longest name. */
1169     for (current = tests; current != NULL; current = current->next) {
1170         length = strlen(current->ts->file);
1171         if (length > longest)
1172             longest = length;
1173     }
1174
1175     /*
1176      * Add two to longest and round up to the nearest tab stop.  This is how
1177      * wide the column for printing the current test name will be.
1178      */
1179     longest += 2;
1180     if (longest % 8)
1181         longest += 8 - (longest % 8);
1182
1183     /* Start the wall clock timer. */
1184     gettimeofday(&start, NULL);
1185
1186     /* Now, plow through our tests again, running each one. */
1187     for (current = tests; current != NULL; current = current->next) {
1188         ts = current->ts;
1189
1190         /* Print out the name of the test file. */
1191         fputs(ts->file, stdout);
1192         for (i = strlen(ts->file); i < longest; i++)
1193             putchar('.');
1194         if (isatty(STDOUT_FILENO))
1195             fflush(stdout);
1196
1197         /* Run the test. */
1198         ts->path = find_test(ts->file, source, build);
1199         succeeded = test_run(ts);
1200         fflush(stdout);
1201
1202         /* Record cumulative statistics. */
1203         aborted += ts->aborted;
1204         total += ts->count + ts->all_skipped;
1205         passed += ts->passed;
1206         skipped += ts->skipped + ts->all_skipped;
1207         failed += ts->failed;
1208         count++;
1209
1210         /* If the test fails, we shuffle it over to the fail list. */
1211         if (!succeeded) {
1212             if (failhead == NULL) {
1213                 failhead = xmalloc(sizeof(struct testset));
1214                 failtail = failhead;
1215             } else {
1216                 failtail->next = xmalloc(sizeof(struct testset));
1217                 failtail = failtail->next;
1218             }
1219             failtail->ts = ts;
1220             failtail->next = NULL;
1221         }
1222     }
1223     total -= skipped;
1224
1225     /* Stop the timer and get our child resource statistics. */
1226     gettimeofday(&end, NULL);
1227     getrusage(RUSAGE_CHILDREN, &stats);
1228
1229     /* Summarize the failures and free the failure list. */
1230     if (failhead != NULL) {
1231         test_fail_summary(failhead);
1232         while (failhead != NULL) {
1233             next = failhead->next;
1234             free(failhead);
1235             failhead = next;
1236         }
1237     }
1238
1239     /* Free the memory used by the test lists. */
1240     while (tests != NULL) {
1241         next = tests->next;
1242         free_testset(tests->ts);
1243         free(tests);
1244         tests = next;
1245     }
1246
1247     /* Print out the final test summary. */
1248     putchar('\n');
1249     if (aborted != 0) {
1250         if (aborted == 1)
1251             printf("Aborted %lu test set", aborted);
1252         else
1253             printf("Aborted %lu test sets", aborted);
1254         printf(", passed %lu/%lu tests", passed, total);
1255     }
1256     else if (failed == 0)
1257         fputs("All tests successful", stdout);
1258     else
1259         printf("Failed %lu/%lu tests, %.2f%% okay", failed, total,
1260                (total - failed) * 100.0 / total);
1261     if (skipped != 0) {
1262         if (skipped == 1)
1263             printf(", %lu test skipped", skipped);
1264         else
1265             printf(", %lu tests skipped", skipped);
1266     }
1267     puts(".");
1268     printf("Files=%u,  Tests=%lu", count, total);
1269     printf(",  %.2f seconds", tv_diff(&end, &start));
1270     printf(" (%.2f usr + %.2f sys = %.2f CPU)\n",
1271            tv_seconds(&stats.ru_utime), tv_seconds(&stats.ru_stime),
1272            tv_sum(&stats.ru_utime, &stats.ru_stime));
1273     return (failed == 0 && aborted == 0);
1274 }
1275
1276
1277 /*
1278  * Run a single test case.  This involves just running the test program after
1279  * having done the environment setup and finding the test program.
1280  */
1281 static void
1282 test_single(const char *program, const char *source, const char *build)
1283 {
1284     char *path;
1285
1286     path = find_test(program, source, build);
1287     if (execl(path, path, (char *) 0) == -1)
1288         sysdie("cannot exec %s", path);
1289 }
1290
1291
1292 /*
1293  * Main routine.  Set the SOURCE and BUILD environment variables and then,
1294  * given a file listing tests, run each test listed.
1295  */
1296 int
1297 main(int argc, char *argv[])
1298 {
1299     int option;
1300     int status = 0;
1301     int single = 0;
1302     char *source_env = NULL;
1303     char *build_env = NULL;
1304     const char *shortlist;
1305     const char *list = NULL;
1306     const char *source = SOURCE;
1307     const char *build = BUILD;
1308     struct testlist *tests;
1309
1310     while ((option = getopt(argc, argv, "b:hl:os:")) != EOF) {
1311         switch (option) {
1312         case 'b':
1313             build = optarg;
1314             break;
1315         case 'h':
1316             printf(usage_message, argv[0], argv[0], argv[0], usage_extra);
1317             exit(0);
1318             break;
1319         case 'l':
1320             list = optarg;
1321             break;
1322         case 'o':
1323             single = 1;
1324             break;
1325         case 's':
1326             source = optarg;
1327             break;
1328         default:
1329             exit(1);
1330         }
1331     }
1332     argv += optind;
1333     argc -= optind;
1334     if ((list == NULL && argc < 1) || (list != NULL && argc > 0)) {
1335         fprintf(stderr, usage_message, argv[0], argv[0], argv[0], usage_extra);
1336         exit(1);
1337     }
1338
1339     /* Set SOURCE and BUILD environment variables. */
1340     if (source != NULL) {
1341         source_env = xmalloc(strlen("SOURCE=") + strlen(source) + 1);
1342         sprintf(source_env, "SOURCE=%s", source);
1343         if (putenv(source_env) != 0)
1344             sysdie("cannot set SOURCE in the environment");
1345     }
1346     if (build != NULL) {
1347         build_env = xmalloc(strlen("BUILD=") + strlen(build) + 1);
1348         sprintf(build_env, "BUILD=%s", build);
1349         if (putenv(build_env) != 0)
1350             sysdie("cannot set BUILD in the environment");
1351     }
1352
1353     /* Run the tests as instructed. */
1354     if (single)
1355         test_single(argv[0], source, build);
1356     else if (list != NULL) {
1357         shortlist = strrchr(list, '/');
1358         if (shortlist == NULL)
1359             shortlist = list;
1360         else
1361             shortlist++;
1362         printf(banner, shortlist);
1363         tests = read_test_list(list);
1364         status = test_batch(tests, source, build) ? 0 : 1;
1365     } else {
1366         tests = build_test_list(argv, argc);
1367         status = test_batch(tests, source, build) ? 0 : 1;
1368     }
1369
1370     /* For valgrind cleanliness, free all our memory. */
1371     if (source_env != NULL) {
1372         putenv((char *) "SOURCE=");
1373         free(source_env);
1374     }
1375     if (build_env != NULL) {
1376         putenv((char *) "BUILD=");
1377         free(build_env);
1378     }
1379     exit(status);
1380 }