Resync test harness with C TAP Harness 1.7
authorRuss Allbery <rra@stanford.edu>
Thu, 28 Apr 2011 20:00:43 +0000 (13:00 -0700)
committerDerrick Brashear <shadow@dementia.org>
Fri, 29 Apr 2011 03:24:34 +0000 (20:24 -0700)
Includes the following upstream changes:

Add a more complete usage message to runtests and add support for a -h
command-line flag to display the usage message.

is_double() now takes a third argument, an epsilon.  Two numbers are
considered equal if their absolute difference is less than epsilon.
is_double() also now treats wanted and seen values of NaN (not a
number) as equal.  Thanks to PICCA Frédéric-Emmanuel for the proposed
changes.

The ok_program function in the shell libtap.sh library no longer
strips text after a colon and a space from the program output if the
expected status is non-zero.  Instead, if program output may contain
system-specific error messages after a colon and a space, put the new
function strip_colon_error before the program to do this stripping.
Thanks to Carsten Hey for the idea.

strip_colon_error is now smarter about preserving an initial word
ending in a colon (which is generally the program name) while still
stripping error messages later in the line.

The test_file_path function in the shell libtap.sh library now always
returns the empty string, rather than possible absolute paths starting
at /, if $BUILD and $SOURCE are not set.

Flush standard error in the C TAP library before printing results for
more deterministic output.  Thanks to Carsten Hey for the idea.

All of C TAP Harness now compiles with gcc -ansi -pedantic and should
be fully C89-compatible.  Note that either C99 or SUSv3 is required to
build C TAP Harness.  (This should not be a problem on any modern
platform.)  Based on work by Carsten Hey.

Simplify and improve output formatting in the summary of failing tests
in some edge cases.

Add explicit license statements to the files meant to be copied into
other packages rather than referring to LICENSE.

Add a test_file_path() function to the basic C and shell TAP
libraries, which searches the build and source directories for a
particular file and returns the full path.  This is a utility function
that can be used to find test data files.

Change-Id: I3ef84218f0e3a8b75f550c8b629b058330659b31
Reviewed-on: http://gerrit.openafs.org/4589
Reviewed-by: Derrick Brashear <shadow@dementia.org>
Tested-by: BuildBot <buildbot@rampaginggeek.com>

tests/HOWTO
tests/runtests.c
tests/tap/basic.c
tests/tap/basic.h
tests/tap/libtap.sh

index 5d3e620..5d38748 100644 (file)
@@ -71,10 +71,17 @@ Writing TAP Tests
     One of the special features of C TAP Harness is the environment that
     it sets up for your test cases.  If your test program is called under
     the runtests driver, the environment variables SOURCE and BUILD will
-    be set to the top of the source tree and the top of the build tree,
-    respectively.  You can use those environment variables to locate
-    additional test data, programs and libraries built as part of your
-    software build, and other supporting information needed by tests.
+    be set to the top of the test directory in the source tree and the top
+    of the build tree, respectively.  You can use those environment
+    variables to locate additional test data, programs and libraries built
+    as part of your software build, and other supporting information
+    needed by tests.
+
+    The C and shell TAP libraries support a test_file_path() function,
+    which looks for a file under the build tree and then under the source
+    tree, using the BUILD and SOURCE environment variables, and return the
+    full path to the file.  This can be used to locate supporting data
+    files.
 
   Perl
 
@@ -89,12 +96,11 @@ Writing TAP Tests
     of Test::More for all the details and lots of examples.
 
     C TAP Harness can run Perl test scripts directly and interpret the
-    results correctly, and similarly the Perl Test::Harness module can run
-    TAP tests written in other languages using, for example, the TAP
-    library that comes with C TAP Harness.  However, the "prove" tool that
-    comes with Perl and runs tests makes some Perl-specific assumptions
-    that aren't always appropriate for packages that aren't written in
-    Perl.
+    results correctly, and similarly the Perl Test::Harness module and
+    prove command can run TAP tests written in other languages using, for
+    example, the TAP library that comes with C TAP Harness.  You can, if
+    you wish, use the library that comes with C TAP Harness but use prove
+    instead of runtests for running the test suite.
 
   C
 
@@ -110,6 +116,7 @@ Writing TAP Tests
 
     Here's a complete example test program that uses the C TAP library:
 
+        #include <stddef.h>
         #include <tap/basic.h>
 
         int
@@ -219,3 +226,23 @@ Writing TAP Tests
     rest of its arguments as the program to run.  That program is run with
     standard error and standard output combined, and then its exit status
     and output are tested against the provided values.
+
+    A utility function, strip_colon_error, is provided that runs the
+    command given as its arguments and strips text following a colon and a
+    space from the output (unless there is no whitespace on the line
+    before the colon and the space, normally indicating a prefix of the
+    program name).  This function can be used to wrap commands that are
+    expected to fail with output that has a system- or locale-specific
+    error message appended, such as the output of strerror().
+
+License
+
+    This file is part of the documentation of C TAP Harness, which can be
+    found at <http://www.eyrie.org/~eagle/software/c-tap-harness/>.
+
+    Copyright 2010 Russ Allbery <rra@stanford.edu>
+
+    Copying and distribution of this file, with or without modification,
+    are permitted in any medium without royalty provided the copyright
+    notice and this notice are preserved.  This file is offered as-is,
+    without any warranty.
index af057a3..1bcf051 100644 (file)
@@ -3,14 +3,15 @@
  *
  * Usage:
  *
- *      runtests <test-list>
+ *      runtests [-b <build-dir>] [-s <source-dir>] <test-list>
+ *      runtests -o [-b <build-dir>] [-s <source-dir>] <test>
  *
- * 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), optionally preceded by "1..",
- * although that line may be given anywhere in the output.  Each additional
- * line should be in the following format:
+ * In the first case, 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), optionally preceded by
+ * "1..", although that line may be given anywhere in the output.  Each
+ * additional line should be in the following format:
  *
  *      ok <number>
  *      not ok <number>
  * This is a subset of TAP as documented in Test::Harness::TAP or
  * TAP::Parser::Grammar, which comes with Perl.
  *
+ * If the -o option is given, instead run a single test and display all of its
+ * output.  This is intended for use with failing tests so that the person
+ * running the test suite can get more details about what failed.
+ *
+ * If built with the C preprocessor symbols SOURCE and BUILD defined, C TAP
+ * Harness will export those values in the environment so that tests can find
+ * the source and build directory and will look for tests under both
+ * directories.  These paths can also be set with the -b and -s command-line
+ * options, which will override anything set at build time.
+ *
  * Any bug reports, bug fixes, and improvements are very much welcome and
- * should be sent to the e-mail address below.
+ * should be sent to the e-mail address below.  This program is part of C TAP
+ * Harness <http://www.eyrie.org/~eagle/software/c-tap-harness/>.
  *
- * Copyright 2000, 2001, 2004, 2006, 2007, 2008, 2009, 2010
+ * Copyright 2000, 2001, 2004, 2006, 2007, 2008, 2009, 2010, 2011
  *     Russ Allbery <rra@stanford.edu>
  *
  * Permission is hereby granted, free of charge, to any person obtaining a
  * DEALINGS IN THE SOFTWARE.
 */
 
+/* Required for fdopen(), getopt(), and putenv(). */
+#ifndef _XOPEN_SOURCE
+# define _XOPEN_SOURCE 500
+#endif
+
 #include <ctype.h>
 #include <errno.h>
 #include <fcntl.h>
@@ -71,6 +88,7 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#include <strings.h>
 #include <sys/stat.h>
 #include <sys/time.h>
 #include <sys/types.h>
@@ -133,10 +151,10 @@ struct testset {
     unsigned long skipped;      /* Count of skipped tests (passed). */
     unsigned long allocated;    /* The size of the results table. */
     enum test_status *results;  /* Table of results by test number. */
-    int aborted;                /* Whether the set as aborted. */
+    unsigned int aborted;       /* Whether the set as aborted. */
     int reported;               /* Whether the results were reported. */
     int status;                 /* The exit status of the test. */
-    int all_skipped;            /* Whether all tests were skipped. */
+    unsigned int all_skipped;   /* Whether all tests were skipped. */
     char *reason;               /* Why all tests were skipped. */
 };
 
@@ -147,6 +165,23 @@ struct testlist {
 };
 
 /*
+ * Usage message.  Should be used as a printf format with two arguments: the
+ * path to runtests, given twice.
+ */
+static const char usage_message[] = "\
+Usage: %s [-b <build-dir>] [-s <source-dir>] <test-list>\n\
+       %s -o [-b <build-dir>] [-s <source-dir>] <test>\n\
+\n\
+Options:\n\
+    -b <build-dir>      Set the build directory to <build-dir>\n\
+    -o                  Run a single test rather than a list of tests\n\
+    -s <source-dir>     Set the source directory to <source-dir>\n\
+\n\
+runtests normally runs each test listed in a file whose path is given as\n\
+its command-line argument.  With the -o option, it instead runs a single\n\
+test and shows its complete output.\n";
+
+/*
  * Header used for test output.  %s is replaced by the file name of the list
  * of tests.
  */
@@ -367,7 +402,9 @@ test_plan(const char *line, struct testset *ts)
      * Get the count, check it for validity, and initialize the struct.  If we
      * have something of the form "1..0 # skip foo", the whole file was
      * skipped; record that.  If we do skip the whole file, zero out all of
-     * our statistics, since they're no longer relevant.
+     * our statistics, since they're no longer relevant.  strtol is called
+     * with a second argument to advance the line pointer past the count to
+     * make it simpler to detect the # skip case.
      */
     n = strtol(line, (char **) &line, 10);
     if (n == 0) {
@@ -437,6 +474,7 @@ test_checkline(const char *line, struct testset *ts)
     char *end;
     long number;
     unsigned long i, current;
+    int outlen;
 
     /* Before anything, check for a test abort. */
     bail = strstr(line, "Bail out!");
@@ -449,7 +487,7 @@ test_checkline(const char *line, struct testset *ts)
             if (bail[length - 1] == '\n')
                 length--;
             test_backspace(ts);
-            printf("ABORTED (%.*s)\n", (int)length, bail);
+            printf("ABORTED (%.*s)\n", (int) length, bail);
             ts->reported = 1;
         }
         ts->aborted = 1;
@@ -551,13 +589,14 @@ test_checkline(const char *line, struct testset *ts)
         case TEST_PASS: ts->passed++;   break;
         case TEST_FAIL: ts->failed++;   break;
         case TEST_SKIP: ts->skipped++;  break;
-        default:                        break;
+        case TEST_INVALID:              break;
     }
     ts->current = current;
     ts->results[current - 1] = status;
     test_backspace(ts);
     if (isatty(STDOUT_FILENO)) {
-        ts->length = printf("%lu/%lu", current, ts->count);
+        outlen = printf("%lu/%lu", current, ts->count);
+        ts->length = (outlen >= 0) ? outlen : 0;
         fflush(stdout);
     }
 }
@@ -565,23 +604,20 @@ test_checkline(const char *line, struct testset *ts)
 
 /*
  * 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
+ * took up.  Takes the first number, the last number, the number of characters
+ * already printed on the line, and the limit of number of characters the line
+ * can hold.  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.
+ * disable this).
  */
 static unsigned int
 test_print_range(unsigned long first, unsigned long last, unsigned int chars,
                  unsigned int limit)
 {
     unsigned int needed = 0;
-    unsigned int out = 0;
     unsigned long n;
 
-    if (chars > 0) {
-        needed += 2;
-        if (!limit || chars <= limit) out += printf(", ");
-    }
     for (n = first; n > 0; n /= 10)
         needed++;
     if (last > first) {
@@ -589,15 +625,26 @@ test_print_range(unsigned long first, unsigned long last, unsigned int chars,
             needed++;
         needed++;
     }
-    if (limit && chars + needed > limit) {
-        if (chars <= limit)
-            out += printf("...");
+    if (chars > 0)
+        needed += 2;
+    if (limit > 0 && chars + needed > limit) {
+        needed = 0;
+        if (chars <= limit) {
+            if (chars > 0) {
+                printf(", ");
+                needed += 2;
+            }
+            printf("...");
+            needed += 3;
+        }
     } else {
+        if (chars > 0)
+            printf(", ");
         if (last > first)
-            out += printf("%lu-", first);
-        out += printf("%lu", last);
+            printf("%lu-", first);
+        printf("%lu", last);
     }
-    return out;
+    return needed;
 }
 
 
@@ -825,14 +872,14 @@ test_fail_summary(const struct testlist *fails)
                     last = i + 1;
                 else {
                     if (first != 0)
-                        chars += test_print_range(first, last, chars, 20);
+                        chars += test_print_range(first, last, chars, 19);
                     first = i + 1;
                     last = i + 1;
                 }
             }
         }
         if (first != 0)
-            test_print_range(first, last, chars, 20);
+            test_print_range(first, last, chars, 19);
         putchar('\n');
         free(ts->file);
         free(ts->path);
@@ -861,9 +908,14 @@ find_test(const char *name, struct testset *ts, const char *source,
           const char *build)
 {
     char *path;
-    const char *bases[] = { ".", build, source, NULL };
+    const char *bases[4];
     unsigned int i;
 
+    bases[0] = ".";
+    bases[1] = build;
+    bases[2] = source;
+    bases[3] = NULL;
+
     for (i = 0; bases[i] != NULL; i++) {
         path = xmalloc(strlen(bases[i]) + strlen(name) + 4);
         sprintf(path, "%s/%s-t", bases[i], name);
@@ -1066,11 +1118,15 @@ main(int argc, char *argv[])
     const char *source = SOURCE;
     const char *build = BUILD;
 
-    while ((option = getopt(argc, argv, "b:os:")) != EOF) {
+    while ((option = getopt(argc, argv, "b:hos:")) != EOF) {
         switch (option) {
         case 'b':
             build = optarg;
             break;
+        case 'h':
+            printf(usage_message, argv[0], argv[0]);
+            exit(0);
+            break;
         case 'o':
             single = 1;
             break;
@@ -1081,12 +1137,12 @@ main(int argc, char *argv[])
             exit(1);
         }
     }
-    argc -= optind;
-    argv += optind;
-    if (argc != 1) {
-        fprintf(stderr, "Usage: runtests <test-list>\n");
+    if (argc - optind != 1) {
+        fprintf(stderr, usage_message, argv[0], argv[0]);
         exit(1);
     }
+    argc -= optind;
+    argv += optind;
 
     if (source != NULL) {
         setting = xmalloc(strlen("SOURCE=") + strlen(source) + 1);
index ac40e4f..8027a31 100644 (file)
@@ -1,24 +1,47 @@
 /*
  * Some utility routines for writing tests.
  *
- * Herein are a variety of utility routines for writing tests.  All routines
- * of the form ok*() take a test number and some number of appropriate
- * arguments, check to be sure the results match the expected output using the
- * arguments, and print out something appropriate for that test number.  Other
- * utility routines help in constructing more complex tests.
+ * Here are a variety of utility routines for writing tests compatible with
+ * the TAP protocol.  All routines of the form ok() or is*() take a test
+ * number and some number of appropriate arguments, check to be sure the
+ * results match the expected output using the arguments, and print out
+ * something appropriate for that test number.  Other utility routines help in
+ * constructing more complex tests, skipping tests, or setting up the TAP
+ * output format.
+ *
+ * This file is part of C TAP Harness.  The current version plus supporting
+ * documentation is at <http://www.eyrie.org/~eagle/software/c-tap-harness/>.
  *
  * Copyright 2009, 2010 Russ Allbery <rra@stanford.edu>
- * Copyright 2006, 2007, 2008
- *     Board of Trustees, Leland Stanford Jr. University
- * Copyright (c) 2004, 2005, 2006
- *     by Internet Systems Consortium, Inc. ("ISC")
- * Copyright (c) 1991, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001,
- *     2002, 2003 by The Internet Software Consortium and Rich Salz
+ * Copyright 2001, 2002, 2004, 2005, 2006, 2007, 2008
+ *     The Board of Trustees of the Leland Stanford Junior University
+ *
+ * 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.
  *
- * See LICENSE for licensing terms.
+ * 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.
  */
 
+/* Required for isnan() and isinf(). */
+#ifndef _XOPEN_SOURCE
+# define _XOPEN_SOURCE 600
+#endif
+
 #include <errno.h>
+#include <math.h>
 #include <stdarg.h>
 #include <stdio.h>
 #include <stdlib.h>
@@ -65,6 +88,7 @@ finish(void)
 
     if (_planned == 0 && !_lazy)
         return;
+    fflush(stderr);
     if (_process != 0 && getpid() == _process) {
         if (_lazy) {
             printf("1..%lu\n", highest);
@@ -97,6 +121,7 @@ plan(unsigned long count)
     if (setvbuf(stdout, NULL, _IOLBF, BUFSIZ) != 0)
         fprintf(stderr, "# cannot set stdout to line buffered: %s\n",
                 strerror(errno));
+    fflush(stderr);
     printf("1..%lu\n", count);
     testnum = 1;
     _planned = count;
@@ -129,6 +154,7 @@ plan_lazy(void)
 void
 skip_all(const char *format, ...)
 {
+    fflush(stderr);
     printf("1..0 # skip");
     if (format != NULL) {
         va_list args;
@@ -161,6 +187,7 @@ print_desc(const char *format, va_list args)
 void
 ok(int success, const char *format, ...)
 {
+    fflush(stderr);
     printf("%sok %lu", success ? "" : "not ", testnum++);
     if (!success)
         _failed++;
@@ -181,6 +208,7 @@ ok(int success, const char *format, ...)
 void
 okv(int success, const char *format, va_list args)
 {
+    fflush(stderr);
     printf("%sok %lu", success ? "" : "not ", testnum++);
     if (!success)
         _failed++;
@@ -196,6 +224,7 @@ okv(int success, const char *format, va_list args)
 void
 skip(const char *reason, ...)
 {
+    fflush(stderr);
     printf("ok %lu # skip", testnum++);
     if (reason != NULL) {
         va_list args;
@@ -217,6 +246,7 @@ ok_block(unsigned long count, int status, const char *format, ...)
 {
     unsigned long i;
 
+    fflush(stderr);
     for (i = 0; i < count; i++) {
         printf("%sok %lu", status ? "" : "not ", testnum++);
         if (!status)
@@ -241,6 +271,7 @@ skip_block(unsigned long count, const char *reason, ...)
 {
     unsigned long i;
 
+    fflush(stderr);
     for (i = 0; i < count; i++) {
         printf("ok %lu # skip", testnum++);
         if (reason != NULL) {
@@ -263,6 +294,7 @@ skip_block(unsigned long count, const char *reason, ...)
 void
 is_int(long wanted, long seen, const char *format, ...)
 {
+    fflush(stderr);
     if (wanted == seen)
         printf("ok %lu", testnum++);
     else {
@@ -292,6 +324,7 @@ is_string(const char *wanted, const char *seen, const char *format, ...)
         wanted = "(null)";
     if (seen == NULL)
         seen = "(null)";
+    fflush(stderr);
     if (strcmp(wanted, seen) == 0)
         printf("ok %lu", testnum++);
     else {
@@ -312,12 +345,15 @@ is_string(const char *wanted, const char *seen, const char *format, ...)
 
 /*
  * Takes an expected double and a seen double and assumes the test passes if
- * those two numbers match.
+ * those two numbers are within delta of each other.
  */
 void
-is_double(double wanted, double seen, const char *format, ...)
+is_double(double wanted, double seen, double epsilon, const char *format, ...)
 {
-    if (wanted == seen)
+    fflush(stderr);
+    if ((isnan(wanted) && isnan(seen))
+        || (isinf(wanted) && isinf(seen) && wanted == seen)
+        || fabs(wanted - seen) <= epsilon)
         printf("ok %lu", testnum++);
     else {
         printf("# wanted: %g\n#   seen: %g\n", wanted, seen);
@@ -342,6 +378,7 @@ is_double(double wanted, double seen, const char *format, ...)
 void
 is_hex(unsigned long wanted, unsigned long seen, const char *format, ...)
 {
+    fflush(stderr);
     if (wanted == seen)
         printf("ok %lu", testnum++);
     else {
@@ -369,6 +406,7 @@ bail(const char *format, ...)
 {
     va_list args;
 
+    fflush(stderr);
     fflush(stdout);
     printf("Bail out! ");
     va_start(args, format);
@@ -388,6 +426,7 @@ sysbail(const char *format, ...)
     va_list args;
     int oerrno = errno;
 
+    fflush(stderr);
     fflush(stdout);
     printf("Bail out! ");
     va_start(args, format);
@@ -406,6 +445,7 @@ diag(const char *format, ...)
 {
     va_list args;
 
+    fflush(stderr);
     fflush(stdout);
     printf("# ");
     va_start(args, format);
@@ -424,6 +464,7 @@ sysdiag(const char *format, ...)
     va_list args;
     int oerrno = errno;
 
+    fflush(stderr);
     fflush(stdout);
     printf("# ");
     va_start(args, format);
@@ -431,3 +472,53 @@ sysdiag(const char *format, ...)
     va_end(args);
     printf(": %s\n", strerror(oerrno));
 }
+
+
+/*
+ * Locate a test file.  Given the partial path to a file, look under BUILD and
+ * then SOURCE for the file and return the full path to the file.  Returns
+ * NULL if the file doesn't exist.  A non-NULL return should be freed with
+ * test_file_path_free().
+ *
+ * This function uses sprintf because it attempts to be independent of all
+ * other portability layers.  The use immediately after a memory allocation
+ * should be safe without using snprintf or strlcpy/strlcat.
+ */
+char *
+test_file_path(const char *file)
+{
+    char *base;
+    char *path = NULL;
+    size_t length;
+    const char *envs[] = { "BUILD", "SOURCE", NULL };
+    int i;
+
+    for (i = 0; envs[i] != NULL; i++) {
+        base = getenv(envs[i]);
+        if (base == NULL)
+            continue;
+        length = strlen(base) + 1 + strlen(file) + 1;
+        path = malloc(length);
+        if (path == NULL)
+            sysbail("cannot allocate memory");
+        sprintf(path, "%s/%s", base, file);
+        if (access(path, R_OK) == 0)
+            break;
+        free(path);
+        path = NULL;
+    }
+    return path;
+}
+
+
+/*
+ * Free a path returned from test_file_path().  This function exists primarily
+ * for Windows, where memory must be freed from the same library domain that
+ * it was allocated from.
+ */
+void
+test_file_path_free(char *path)
+{
+    if (path != NULL)
+        free(path);
+}
index f4a7b5d..a3475d9 100644 (file)
@@ -1,15 +1,30 @@
 /*
  * Basic utility routines for the TAP protocol.
  *
+ * This file is part of C TAP Harness.  The current version plus supporting
+ * documentation is at <http://www.eyrie.org/~eagle/software/c-tap-harness/>.
+ *
  * Copyright 2009, 2010 Russ Allbery <rra@stanford.edu>
- * Copyright 2006, 2007, 2008
- *     Board of Trustees, Leland Stanford Jr. University
- * Copyright (c) 2004, 2005, 2006
- *     by Internet Systems Consortium, Inc. ("ISC")
- * Copyright (c) 1991, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001,
- *     2002, 2003 by The Internet Software Consortium and Rich Salz
+ * Copyright 2001, 2002, 2004, 2005, 2006, 2007, 2008
+ *     The Board of Trustees of the Leland Stanford Junior University
+ *
+ * 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:
  *
- * See LICENSE for licensing terms.
+ * 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.
  */
 
 #ifndef TAP_BASIC_H
@@ -93,8 +108,9 @@ void skip_block(unsigned long count, const char *reason, ...)
 /* Check an expected value against a seen value. */
 void is_int(long wanted, long seen, const char *format, ...)
     __attribute__((__format__(printf, 3, 4)));
-void is_double(double wanted, double seen, const char *format, ...)
-    __attribute__((__format__(printf, 3, 4)));
+void is_double(double wanted, double seen, double epsilon,
+               const char *format, ...)
+    __attribute__((__format__(printf, 4, 5)));
 void is_string(const char *wanted, const char *seen, const char *format, ...)
     __attribute__((__format__(printf, 3, 4)));
 void is_hex(unsigned long wanted, unsigned long seen, const char *format, ...)
@@ -112,6 +128,14 @@ void diag(const char *format, ...)
 void sysdiag(const char *format, ...)
     __attribute__((__nonnull__, __format__(printf, 1, 2)));
 
+/*
+ * Find a test file under BUILD or SOURCE, returning the full path.  The
+ * returned path should be freed with test_file_path_free().
+ */
+char *test_file_path(const char *file)
+    __attribute__((__malloc__, __nonnull__));
+void test_file_path_free(char *path);
+
 END_DECLS
 
 #endif /* TAP_BASIC_H */
index 0e50d80..bf2d191 100644 (file)
@@ -1,10 +1,31 @@
 # Shell function library for test cases.
 #
+# This file provides a TAP-compatible shell function library useful for
+# writing test cases.  It is part of C TAP Harness, which can be found at
+# <http://www.eyrie.org/~eagle/software/c-tap-harness/>.
+#
 # Written by Russ Allbery <rra@stanford.edu>
 # Copyright 2009, 2010 Russ Allbery <rra@stanford.edu>
-# Copyright 2006, 2007, 2008 Board of Trustees, Leland Stanford Jr. University
+# Copyright 2006, 2007, 2008
+#     The Board of Trustees of the Leland Stanford Junior University
+#
+# 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:
 #
-# See LICENSE for licensing terms.
+# 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.
 
 # Print out the number of test cases we expect to run.
 plan () {
@@ -126,11 +147,23 @@ skip_block () {
     done
 }
 
+# Portable variant of printf '%s\n' "$*".  In the majority of cases, this
+# function is slower than printf, because the latter is often implemented
+# as a builtin command.  The value of the variable IFS is ignored.
+puts () {
+    cat << EOH
+$@
+EOH
+}
+
 # Run a program expected to succeed, and print ok if it does and produces the
 # correct output.  Takes the description, expected exit status, the expected
-# output, the command to run, and then any arguments for that command.  Strip
-# a colon and everything after it off the output if the expected status is
-# non-zero, since this is probably a system-specific error message.
+# output, the command to run, and then any arguments for that command.
+# Standard output and standard error are combined when analyzing the output of
+# the command.
+#
+# If the command may contain system-specific error messages in its output,
+# add strip_colon_error before the command to post-process its output.
 ok_program () {
     local desc w_status w_output output status
     desc="$1"
@@ -141,9 +174,6 @@ ok_program () {
     shift
     output=`"$@" 2>&1`
     status=$?
-    if [ "$w_status" -ne 0 ] ; then
-        output=`echo "$output" | sed 's/^\([^:]*\):.*/\1/'`
-    fi
     if [ $status = $w_status ] && [ x"$output" = x"$w_output" ] ; then
         ok "$desc" true
     else
@@ -153,6 +183,20 @@ ok_program () {
     fi
 }
 
+# Strip a colon and everything after it off the output of a command, as long
+# as that colon comes after at least one whitespace character.  (This is done
+# to avoid stripping the name of the program from the start of an error
+# message.)  This is used to remove system-specific error messages (coming
+# from strerror, for example).
+strip_colon_error() {
+    local output status
+    output=`"$@" 2>&1`
+    status=$?
+    output=`puts "$output" | sed 's/^\([^ ]* [^:]*\):.*/\1/'`
+    puts "$output"
+    return $status
+}
+
 # Bail out with an error message.
 bail () {
     echo 'Bail out!' "$@"
@@ -163,3 +207,16 @@ bail () {
 diag () {
     echo '#' "$@"
 }
+
+# Search for the given file first in $BUILD and then in $SOURCE and echo the
+# path where the file was found, or the empty string if the file wasn't
+# found.
+test_file_path () {
+    if [ -n "$BUILD" ] && [ -f "$BUILD/$1" ] ; then
+        puts "$BUILD/$1"
+    elif [ -n "$SOURCE" ] && [ -f "$SOURCE/$1" ] ; then
+        puts "$SOURCE/$1"
+    else
+        echo ''
+    fi
+}