chiark / gitweb /
REORG Delete everything that's not innduct or build system or changed for innduct
[innduct.git] / tests / runtests.c
diff --git a/tests/runtests.c b/tests/runtests.c
deleted file mode 100644 (file)
index 5dca51f..0000000
+++ /dev/null
@@ -1,690 +0,0 @@
-/* $Id: runtests.c 7578 2006-09-11 23:03:12Z eagle $
-
-   Run a set of tests, reporting results.
-
-   Copyright 2000, 2001 Russ Allbery <rra@stanford.edu>
-
-   Please note that this file is maintained separately from INN by the above
-   author (which is why the coding style is slightly different).  Any fixes
-   added to the INN tree should also be reported to the above author if
-   necessary.
-
-   Permission is hereby granted, free of charge, to any person obtaining a
-   copy of this software and associated documentation files (the
-   "Software"), to deal in the Software without restriction, including
-   without limitation the rights to use, copy, modify, merge, publish,
-   distribute, sublicense, and/or sell copies of the Software, and to
-   permit persons to whom the Software is furnished to do so, subject to
-   the following conditions:
-
-   The above copyright notice and this permission notice shall be included
-   in all copies or substantial portions of the Software.
-
-   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
-   OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-   MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
-   IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
-   CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
-   TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
-   SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
-   Usage:
-
-        runtests <test-list>
-
-   Expects a list of executables located in the given file, one line per
-   executable.  For each one, runs it as part of a test suite, reporting
-   results.  Test output should start with a line containing the number of
-   tests (numbered from 1 to this number), and then each line should be in
-   the following format:
-
-        ok <number>
-        not ok <number>
-        ok <number> # skip
-
-   where <number> is the number of the test.  ok indicates success, not ok
-   indicates failure, and "# skip" indicates the test was skipped for some
-   reason (maybe because it doesn't apply to this platform).
-
-   This file is completely stand-alone by intention.  As stated more
-   formally in the license above, you are welcome to include it in your
-   packages as a test suite driver.  It requires ANSI C (__FILE__, __LINE__,
-   void, const, stdarg.h, string.h) and POSIX (fcntl.h, unistd.h, pid_t) and
-   won't compile out of the box on SunOS without adjustments to include
-   strings.h instead.  This is intentionally not fixed using autoconf so
-   that this file will not have a dependency on autoconf (although you're
-   welcome to fix it for your project if you want).  Since it doesn't matter
-   as much that the test suite for the software package be utterly portable
-   to older systems, this file should be portable enough for most purposes.
-
-   Any bug reports, bug fixes, and improvements are very much welcome and
-   should be sent to the e-mail address above. */
-
-#include "config.h"
-#include "clibrary.h"
-#include "portable/wait.h"
-#include "portable/time.h"
-#include <ctype.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <stdarg.h>
-#include <sys/stat.h>
-
-/* sys/time.h must be included before sys/resource.h on some platforms. */
-#include <sys/resource.h>
-
-/* Test status codes. */
-enum test_status {
-    TEST_FAIL,
-    TEST_PASS,
-    TEST_SKIP,
-    TEST_INVALID
-};
-
-/* Error exit statuses for test processes. */
-#define CHILDERR_DUP    100     /* Couldn't redirect stderr or stdout. */
-#define CHILDERR_EXEC   101     /* Couldn't exec child process. */
-#define CHILDERR_STDERR 102     /* Couldn't open stderr file. */
-
-/* Structure to hold data for a set of tests. */
-struct testset {
-    const char *file;           /* The file name of the test. */
-    int count;                  /* Expected count of tests. */
-    int current;                /* The last seen test number. */
-    int passed;                 /* Count of passing tests. */
-    int failed;                 /* Count of failing lists. */
-    int skipped;                /* Count of skipped tests (passed). */
-    enum test_status *results;  /* Table of results by test number. */
-    int aborted;                /* Whether the set as aborted. */
-    int reported;               /* Whether the results were reported. */
-    int status;                 /* The exit status of the test. */
-};
-
-/* Structure to hold a linked list of test sets. */
-struct testlist {
-    struct testset *ts;
-    struct testlist *next;
-};
-
-/* Header used for test output.  %s is replaced by the file name of the list
-   of tests. */
-static const char banner[] = "\n\
-Running all tests listed in %s.  If any tests fail, run the failing\n\
-test program by hand to see more details.  The test program will have the\n\
-same name as the test set but with \".t\" appended.\n\n";
-
-/* Header for reports of failed tests. */
-static const char header[] = "\n\
-Failed Set                 Fail/Total (%) Skip Stat  Failing Tests\n\
--------------------------- -------------- ---- ----  ------------------------";
-
-/* Include the file name and line number in malloc failures. */
-#define xmalloc(size)   x_malloc((size), __FILE__, __LINE__)
-#define xstrdup(p)      x_strdup((p), __FILE__, __LINE__)
-
-/* Internal prototypes. */
-static void sysdie(const char *format, ...);
-static void *x_malloc(size_t, const char *file, int line);
-static char *x_strdup(const char *, const char *file, int line);
-static int test_analyze(struct testset *);
-static int test_batch(const char *testlist);
-static void test_checkline(const char *line, struct testset *);
-static void test_fail_summary(const struct testlist *);
-static int test_init(const char *line, struct testset *);
-static int test_print_range(int first, int last, int chars, int limit);
-static void test_summarize(struct testset *, int status);
-static pid_t test_start(const char *path, int *fd);
-static double tv_diff(const struct timeval *, const struct timeval *);
-static double tv_seconds(const struct timeval *);
-static double tv_sum(const struct timeval *, const struct timeval *);
-
-
-/* Report a fatal error, including the results of strerror, and exit. */
-static void
-sysdie(const char *format, ...)
-{
-    int oerrno;
-    va_list args;
-
-    oerrno = errno;
-    fflush(stdout);
-    fprintf(stderr, "runtests: ");
-    va_start(args, format);
-    vfprintf(stderr, format, args);
-    va_end(args);
-    fprintf(stderr, ": %s\n", strerror(oerrno));
-    exit(1);
-}
-
-
-/* Allocate memory, reporting a fatal error and exiting on failure. */
-static void *
-x_malloc(size_t size, const char *file, int line)
-{
-    void *p;
-
-    p = malloc(size);
-    if (!p)
-        sysdie("failed to malloc %lu bytes at %s line %d",
-               (unsigned long) size, file, line);
-    return p;
-}
-
-
-/* Copy a string, reporting a fatal error and exiting on failure. */
-static char *
-x_strdup(const char *s, const char *file, int line)
-{
-    char *p;
-    size_t len;
-
-    len = strlen(s) + 1;
-    p = malloc(len);
-    if (!p)
-        sysdie("failed to strdup %lu bytes at %s line %d",
-               (unsigned long) len, file, line);
-    memcpy(p, s, len);
-    return p;
-}
-
-
-/* Given a struct timeval, return the number of seconds it represents as a
-   double.  Use difftime() to convert a time_t to a double. */
-static double
-tv_seconds(const struct timeval *tv)
-{
-    return difftime(tv->tv_sec, 0) + tv->tv_usec * 1e-6;
-}
-
-/* Given two struct timevals, return the difference in seconds. */
-static double
-tv_diff(const struct timeval *tv1, const struct timeval *tv0)
-{
-    return tv_seconds(tv1) - tv_seconds(tv0);
-}
-
-/* Given two struct timevals, return the sum in seconds as a double. */
-static double
-tv_sum(const struct timeval *tv1, const struct timeval *tv2)
-{
-    return tv_seconds(tv1) + tv_seconds(tv2);
-}
-
-
-/* Read the first line of test output, which should contain the range of
-   test numbers, and initialize the testset structure.  Assume it was zeroed
-   before being passed in.  Return true if initialization succeeds, false
-   otherwise. */
-static int
-test_init(const char *line, struct testset *ts)
-{
-    int i;
-
-    /* Prefer a simple number of tests, but if the count is given as a range
-       such as 1..10, accept that too for compatibility with Perl's
-       Test::Harness. */
-    while (isspace((unsigned char)(*line))) line++;
-    if (!strncmp(line, "1..", 3)) line += 3;
-
-    /* Get the count, check it for validity, and initialize the struct. */
-    i = atoi(line);
-    if (i <= 0) {
-        puts("invalid test count");
-        ts->aborted = 1;
-        ts->reported = 1;
-        return 0;
-    }
-    ts->count = i;
-    ts->results = xmalloc(ts->count * sizeof(enum test_status));
-    for (i = 0; i < ts->count; i++) ts->results[i] = TEST_INVALID;
-    return 1;
-}
-
-
-/* Start a program, connecting its stdout to a pipe on our end and its
-   stderr to /dev/null, and storing the file descriptor to read from in the
-   two argument.  Returns the PID of the new process.  Errors are fatal. */
-static pid_t
-test_start(const char *path, int *fd)
-{
-    int fds[2], errfd;
-    pid_t child;
-
-    if (pipe(fds) == -1) sysdie("can't create pipe");
-    child = fork();
-    if (child == (pid_t) -1) {
-        sysdie("can't fork");
-    } else if (child == 0) {
-        /* In child.  Set up our stdout and stderr. */
-        errfd = open("/dev/null", O_WRONLY);
-        if (errfd < 0) _exit(CHILDERR_STDERR);
-        if (dup2(errfd, 2) == -1) _exit(CHILDERR_DUP);
-        close(fds[0]);
-        if (dup2(fds[1], 1) == -1) _exit(CHILDERR_DUP);
-
-        /* Now, exec our process. */
-        if (execl(path, path, (char *) 0) == -1) _exit(CHILDERR_EXEC);
-    } else {
-        /* In parent.  Close the extra file descriptor. */
-        close(fds[1]);
-    }
-    *fd = fds[0];
-    return child;
-}
-
-
-/* Given a single line of output from a test, parse it and return the
-   success status of that test.  Anything printed to stdout not matching the
-   form /^(not )?ok \d+/ is ignored.  Sets ts->current to the test number
-   that just reported status. */
-static void
-test_checkline(const char *line, struct testset *ts)
-{
-    enum test_status status = TEST_PASS;
-    int current;
-
-    /* If the given line isn't newline-terminated, it was too big for an
-       fgets(), which means ignore it. */
-    if (line[strlen(line) - 1] != '\n') return;
-
-    /* Parse the line, ignoring something we can't parse. */
-    if (!strncmp(line, "not ", 4)) {
-        status = TEST_FAIL;
-        line += 4;
-    }
-    if (strncmp(line, "ok ", 3)) return;
-    line += 3;
-    current = atoi(line);
-    if (current == 0) return;
-    if (current < 0 || current > ts->count) {
-        printf("invalid test number %d\n", current);
-        ts->aborted = 1;
-        ts->reported = 1;
-        return;
-    }
-    while (isspace((unsigned char)(*line))) line++;
-    while (isdigit((unsigned char)(*line))) line++;
-    while (isspace((unsigned char)(*line))) line++;
-    if (*line == '#') {
-        line++;
-        while (isspace((unsigned char)(*line))) line++;
-        if (!strncmp(line, "skip", 4)) status = TEST_SKIP;
-    }
-
-    /* Make sure that the test number is in range and not a duplicate. */
-    if (ts->results[current - 1] != TEST_INVALID) {
-        printf("duplicate test number %d\n", current);
-        ts->aborted = 1;
-        ts->reported = 1;
-        return;
-    }
-
-    /* Good results.  Increment our various counters. */
-    switch (status) {
-        case TEST_PASS: ts->passed++;   break;
-        case TEST_FAIL: ts->failed++;   break;
-        case TEST_SKIP: ts->skipped++;  break;
-        default:                        break;
-    }
-    ts->current = current;
-    ts->results[current - 1] = status;
-}
-
-
-/* Print out a range of test numbers, returning the number of characters it
-   took up.  Add a comma and a space before the range if chars indicates
-   that something has already been printed on the line, and print
-   ... instead if chars plus the space needed would go over the limit (use a
-   limit of 0 to disable this. */
-static int
-test_print_range(int first, int last, int chars, int limit)
-{
-    int needed = 0;
-    int out = 0;
-    int n;
-
-    if (chars > 0) {
-        needed += 2;
-        if (!limit || chars <= limit) out += printf(", ");
-    }
-    for (n = first; n > 0; n /= 10)
-        needed++;
-    if (last > first) {
-        for (n = last; n > 0; n /= 10)
-            needed++;
-        needed++;
-    }
-    if (limit && chars + needed > limit) {
-        if (chars <= limit) out += printf("...");
-    } else {
-        if (last > first) out += printf("%d-", first);
-        out += printf("%d", last);
-    }
-    return out;
-}
-
-
-/* Summarize a single test set.  The second argument is 0 if the set exited
-   cleanly, a positive integer representing the exit status if it exited
-   with a non-zero status, and a negative integer representing the signal
-   that terminated it if it was killed by a signal. */
-static void
-test_summarize(struct testset *ts, int status)
-{
-    int i;
-    int missing = 0;
-    int failed = 0;
-    int first = 0;
-    int last = 0;
-
-    if (ts->aborted) {
-        fputs("aborted", stdout);
-        if (ts->count > 0)
-            printf(", passed %d/%d", ts->passed, ts->count - ts->skipped);
-    } else {
-        for (i = 0; i < ts->count; i++) {
-            if (ts->results[i] == TEST_INVALID) {
-                if (missing == 0) fputs("MISSED ", stdout);
-                if (first && i == last) {
-                    last = i + 1;
-                } else {
-                    if (first) {
-                        test_print_range(first, last, missing - 1, 0);
-                    }
-                    missing++;
-                    first = i + 1;
-                    last = i + 1;
-                }
-            }
-        }
-        if (first) test_print_range(first, last, missing - 1, 0);
-        first = 0;
-        last = 0;
-        for (i = 0; i < ts->count; i++) {
-            if (ts->results[i] == TEST_FAIL) {
-                if (missing && !failed) fputs("; ", stdout);
-                if (failed == 0) fputs("FAILED ", stdout);
-                if (first && i == last) {
-                    last = i + 1;
-                } else {
-                    if (first) {
-                        test_print_range(first, last, failed - 1, 0);
-                    }
-                    failed++;
-                    first = i + 1;
-                    last = i + 1;
-                }
-            }
-        }
-        if (first) test_print_range(first, last, failed - 1, 0);
-        if (!missing && !failed) {
-            fputs(!status ? "ok" : "dubious", stdout);
-            if (ts->skipped > 0) printf(" (skipped %d tests)", ts->skipped);
-        }
-    }
-    if (status > 0) {
-        printf(" (exit status %d)", status);
-    } else if (status < 0) {
-        printf(" (killed by signal %d%s)", -status,
-               WCOREDUMP(ts->status) ? ", core dumped" : "");
-    }
-    putchar('\n');
-}
-
-
-/* Given a test set, analyze the results, classify the exit status, handle a
-   few special error messages, and then pass it along to test_summarize()
-   for the regular output. */
-static int
-test_analyze(struct testset *ts)
-{
-    if (ts->reported) return 0;
-    if (WIFEXITED(ts->status) && WEXITSTATUS(ts->status) != 0) {
-        switch (WEXITSTATUS(ts->status)) {
-        case CHILDERR_DUP:
-            if (!ts->reported) puts("can't dup file descriptors");
-            break;
-        case CHILDERR_EXEC:
-            if (!ts->reported) puts("execution failed (not found?)");
-            break;
-        case CHILDERR_STDERR:
-            if (!ts->reported) puts("can't open /dev/null");
-            break;
-        default:
-            test_summarize(ts, WEXITSTATUS(ts->status));
-            break;
-        }
-        return 0;
-    } else if (WIFSIGNALED(ts->status)) {
-        test_summarize(ts, -WTERMSIG(ts->status));
-        return 0;
-    } else {
-        test_summarize(ts, 0);
-        return (ts->failed == 0);
-    }
-}
-
-
-/* Runs a single test set, accumulating and then reporting the results.
-   Returns true if the test set was successfully run and all tests passed,
-   false otherwise. */
-static int
-test_run(struct testset *ts)
-{
-    pid_t testpid, child;
-    int outfd, i, status;
-    FILE *output;
-    char buffer[BUFSIZ];
-    char *file;
-
-    /* Initialize the test and our data structures, flagging this set in
-       error if the initialization fails. */
-    file = xmalloc(strlen(ts->file) + 3);
-    strcpy(file, ts->file);
-    strcat(file, ".t");
-    testpid = test_start(file, &outfd);
-    free(file);
-    output = fdopen(outfd, "r");
-    if (!output) sysdie("fdopen failed");
-    if (!fgets(buffer, sizeof(buffer), output)) ts->aborted = 1;
-    if (!ts->aborted && !test_init(buffer, ts)) {
-        while (fgets(buffer, sizeof(buffer), output))
-            ;
-        ts->aborted = 1;
-    }
-
-    /* Pass each line of output to test_checkline(). */
-    while (!ts->aborted && fgets(buffer, sizeof(buffer), output))
-        test_checkline(buffer, ts);
-    if (ferror(output)) ts->aborted = 1;
-
-    /* Close the output descriptor, retrieve the exit status, and pass that
-       information to test_analyze() for eventual output. */
-    fclose(output);
-    child = waitpid(testpid, &ts->status, 0);
-    if (child == (pid_t) -1)
-        sysdie("waitpid for %u failed", (unsigned int) testpid);
-    status = test_analyze(ts);
-
-    /* Convert missing tests to failed tests. */
-    for (i = 0; i < ts->count; i++) {
-        if (ts->results[i] == TEST_INVALID) {
-            ts->failed++;
-            ts->results[i] = TEST_FAIL;
-            status = 0;
-        }
-    }
-    return status;
-}
-
-
-/* Summarize a list of test failures. */
-static void
-test_fail_summary(const struct testlist *fails)
-{
-    struct testset *ts;
-    int i, chars, total, first, last;
-
-    puts(header);
-
-    /* Failed Set                 Fail/Total (%) Skip Stat  Failing (25)
-       -------------------------- -------------- ---- ----  -------------- */
-    for (; fails; fails = fails->next) {
-        ts = fails->ts;
-        total = ts->count - ts->skipped;
-        printf("%-26.26s %4d/%-4d %3.0f%% %4d ", ts->file, ts->failed,
-               total, total ? (ts->failed * 100.0) / total : 0,
-               ts->skipped);
-        if (WIFEXITED(ts->status)) {
-            printf("%4d  ", WEXITSTATUS(ts->status));
-        } else {
-            printf("  --  ");
-        }
-        if (ts->aborted) {
-            puts("aborted");
-            continue;
-        }
-        chars = 0;
-        first = 0;
-        last = 0;
-        for (i = 0; i < ts->count; i++) {
-            if (ts->results[i] == TEST_FAIL) {
-                if (first && i == last) {
-                    last = i + 1;
-                } else {
-                    if (first)
-                        chars += test_print_range(first, last, chars, 20);
-                    first = i + 1;
-                    last = i + 1;
-                }
-            }
-        }
-        if (first) test_print_range(first, last, chars, 20);
-        putchar('\n');
-    }
-}
-
-
-/* Run a batch of tests from a given file listing each test on a line by
-   itself.  The file must be rewindable.  Returns true iff all tests
-   passed. */
-static int
-test_batch(const char *testlist)
-{
-    FILE *tests;
-    size_t length, i;
-    size_t longest = 0;
-    char buffer[BUFSIZ];
-    int line;
-    struct testset ts, *tmp;
-    struct timeval start, end;
-    struct rusage stats;
-    struct testlist *failhead = 0;
-    struct testlist *failtail = 0;
-    int total = 0;
-    int passed = 0;
-    int skipped = 0;
-    int failed = 0;
-    int aborted = 0;
-
-    /* Open our file of tests to run and scan it, checking for lines that
-       are too long and searching for the longest line. */
-    tests = fopen(testlist, "r");
-    if (!tests) sysdie("can't open %s", testlist);
-    line = 0;
-    while (fgets(buffer, sizeof(buffer), tests)) {
-        line++;
-        length = strlen(buffer) - 1;
-        if (buffer[length] != '\n') {
-            fprintf(stderr, "%s:%d: line too long\n", testlist, line);
-            exit(1);
-        }
-        if (length > longest) longest = length;
-    }
-    if (fseek(tests, 0, SEEK_SET) == -1)
-        sysdie("can't rewind %s", testlist);
-
-    /* Add two to longest and round up to the nearest tab stop.  This is how
-       wide the column for printing the current test name will be. */
-    longest += 2;
-    if (longest % 8) longest += 8 - (longest % 8);
-
-    /* Start the wall clock timer. */
-    gettimeofday(&start, NULL);
-
-    /* Now, plow through our tests again, running each one.  Check line
-       length again out of paranoia. */
-    line = 0;
-    while (fgets(buffer, sizeof(buffer), tests)) {
-        line++;
-        length = strlen(buffer) - 1;
-        if (buffer[length] != '\n') {
-            fprintf(stderr, "%s:%d: line too long\n", testlist, line);
-            exit(1);
-        }
-        buffer[length] = '\0';
-        fputs(buffer, stdout);
-        for (i = length; i < longest; i++) putchar('.');
-        memset(&ts, 0, sizeof(ts));
-        ts.file = xstrdup(buffer);
-        if (!test_run(&ts)) {
-            tmp = xmalloc(sizeof(struct testset));
-            memcpy(tmp, &ts, sizeof(struct testset));
-            if (!failhead) {
-                failhead = xmalloc(sizeof(struct testset));
-                failhead->ts = tmp;
-                failhead->next = 0;
-                failtail = failhead;
-            } else {
-                failtail->next = xmalloc(sizeof(struct testset));
-                failtail = failtail->next;
-                failtail->ts = tmp;
-                failtail->next = 0;
-            }
-        }
-        aborted += ts.aborted;
-        total += ts.count;
-        passed += ts.passed;
-        skipped += ts.skipped;
-        failed += ts.failed;
-    }
-    total -= skipped;
-
-    /* Stop the timer and get our child resource statistics. */
-    gettimeofday(&end, NULL);
-    getrusage(RUSAGE_CHILDREN, &stats);
-
-    /* Print out our final results. */
-    if (failhead) test_fail_summary(failhead);
-    putchar('\n');
-    if (aborted) {
-        printf("Aborted %d test sets, passed %d/%d tests.\n", aborted,
-               passed, total);
-    } else if (failed == 0) {
-        fputs("All tests successful", stdout);
-        if (skipped) printf(", %d tests skipped", skipped);
-        puts(".");
-    } else {
-        printf("Failed %d/%d tests, %.2f%% okay.\n", failed, total,
-               (total - failed) * 100.0 / total);
-    }
-    printf("Files=%d,  Tests=%d", line, total);
-    printf(",  %.2f seconds", tv_diff(&end, &start));
-    printf(" (%.2f usr + %.2f sys = %.2f CPU)\n",
-           tv_seconds(&stats.ru_utime), tv_seconds(&stats.ru_stime),
-           tv_sum(&stats.ru_utime, &stats.ru_stime));
-    return !(failed || aborted);
-}
-
-
-/* Main routine.  Given a file listing tests, run each test listed. */
-int
-main(int argc, char *argv[])
-{
-    if (argc != 2) {
-        fprintf(stderr, "Usage: runtests <test-list>\n");
-        exit(1);
-    }
-    printf(banner, argv[1]);
-    exit(test_batch(argv[1]) ? 0 : 1);
-}