diff options
author | Russ Allbery <eagle@eyrie.org> | 2018-06-03 16:58:02 -0700 |
---|---|---|
committer | Russ Allbery <eagle@eyrie.org> | 2018-06-03 16:58:02 -0700 |
commit | edf31eba414d9a105791c076fb1444a78d210dff (patch) | |
tree | 2bac18fa3b71593e616061a0fbcbfdd6ab26a255 /tests/runtests.c | |
parent | 4b3f858ef567c0d12511e7fea2a56f08f2729635 (diff) | |
parent | 68c4b05c268cd6e358cc41c8feb44bc2c7fcb898 (diff) |
New upstream version 1.4
Diffstat (limited to 'tests/runtests.c')
-rw-r--r-- | tests/runtests.c | 331 |
1 files changed, 262 insertions, 69 deletions
diff --git a/tests/runtests.c b/tests/runtests.c index 42a73ea..af15a5c 100644 --- a/tests/runtests.c +++ b/tests/runtests.c @@ -1,6 +1,37 @@ /* * Run a set of tests, reporting results. * + * Test suite driver that runs a set of tests implementing a subset of the + * Test Anything Protocol (TAP) and reports the results. + * + * Any bug reports, bug fixes, and improvements are very much welcome and + * should be sent to the e-mail address below. This program is part of C TAP + * Harness <https://www.eyrie.org/~eagle/software/c-tap-harness/>. + * + * Copyright 2000-2001, 2004, 2006-2018 Russ Allbery <eagle@eyrie.org> + * + * 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. + * + * SPDX-License-Identifier: MIT + */ + +/* * Usage: * * runtests [-hv] [-b <build-dir>] [-s <source-dir>] -l <test-list> @@ -8,9 +39,10 @@ * runtests -o [-h] [-b <build-dir>] [-s <source-dir>] <test> * * 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. In the second case, use the same infrastructure, but - * run only the tests listed on the command line. + * one line per executable, possibly followed by a space-separated list of + * options. For each one, runs it as part of a test suite, reporting results. + * In the second case, use the same infrastructure, but run only the tests + * listed on the command line. * * Test output should start with a line containing the number of tests * (numbered from 1 to this number), optionally preceded by "1..", although @@ -48,41 +80,16 @@ * 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. + * If built with the C preprocessor symbols C_TAP_SOURCE and C_TAP_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. * * If the -v option is given, or the C_TAP_VERBOSE environment variable is set, * display the full output of each test as it runs rather than showing a * summary of the results of each test. - * - * Any bug reports, bug fixes, and improvements are very much welcome and - * 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, 2011, 2012, 2013, - * 2014, 2015 Russ Allbery <eagle@eyrie.org> - * - * 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. -*/ + */ /* Required for fdopen(), getopt(), and putenv(). */ #if defined(__STRICT_ANSI__) || defined(PEDANTIC) @@ -135,15 +142,17 @@ /* * The source and build versions of the tests directory. This is used to set - * the SOURCE and BUILD environment variables and find test programs, if set. - * Normally, this should be set as part of the build process to the test - * subdirectories of $(abs_top_srcdir) and $(abs_top_builddir) respectively. + * the C_TAP_SOURCE and C_TAP_BUILD environment variables (and the SOURCE and + * BUILD environment variables set for backward compatibility) and find test + * programs, if set. Normally, this should be set as part of the build + * process to the test subdirectories of $(abs_top_srcdir) and + * $(abs_top_builddir) respectively. */ -#ifndef SOURCE -# define SOURCE NULL +#ifndef C_TAP_SOURCE +# define C_TAP_SOURCE NULL #endif -#ifndef BUILD -# define BUILD NULL +#ifndef C_TAP_BUILD +# define C_TAP_BUILD NULL #endif /* Test status codes. */ @@ -177,7 +186,7 @@ enum plan_status { /* Structure to hold data for a set of tests. */ struct testset { char *file; /* The file name of the test. */ - char *path; /* The path to the test program. */ + char **command; /* The argv vector to run the command. */ enum plan_status plan; /* The status of our plan. */ unsigned long count; /* Expected count of tests. */ unsigned long current; /* The last seen test number. */ @@ -188,7 +197,7 @@ struct testset { unsigned long allocated; /* The size of the results table. */ enum test_status *results; /* Table of results by test number. */ unsigned int aborted; /* Whether the set was aborted. */ - int reported; /* Whether the results were reported. */ + unsigned int reported; /* Whether the results were reported. */ int status; /* The exit status of the test. */ unsigned int all_skipped; /* Whether all tests were skipped. */ char *reason; /* Why all tests were skipped. */ @@ -240,6 +249,7 @@ Failed Set Fail/Total (%) Skip Stat Failing Tests\n\ #define xcalloc(n, size) x_calloc((n), (size), __FILE__, __LINE__) #define xmalloc(size) x_malloc((size), __FILE__, __LINE__) #define xstrdup(p) x_strdup((p), __FILE__, __LINE__) +#define xstrndup(p, size) x_strndup((p), (size), __FILE__, __LINE__) #define xreallocarray(p, n, size) \ x_reallocarray((p), (n), (size), __FILE__, __LINE__) @@ -280,6 +290,8 @@ Failed Set Fail/Total (%) Skip Stat Failing Tests\n\ #endif /* Declare internal functions that benefit from compiler attributes. */ +static void die(const char *, ...) + __attribute__((__nonnull__, __noreturn__, __format__(printf, 1, 2))); static void sysdie(const char *, ...) __attribute__((__nonnull__, __noreturn__, __format__(printf, 1, 2))); static void *x_calloc(size_t, size_t, const char *, int) @@ -290,6 +302,26 @@ static void *x_reallocarray(void *, size_t, size_t, const char *, int) __attribute__((__alloc_size__(2, 3), __malloc__, __nonnull__(4))); static char *x_strdup(const char *, const char *, int) __attribute__((__malloc__, __nonnull__)); +static char *x_strndup(const char *, size_t, const char *, int) + __attribute__((__malloc__, __nonnull__)); + + +/* + * Report a fatal error and exit. + */ +static void +die(const char *format, ...) +{ + va_list args; + + fflush(stdout); + fprintf(stderr, "runtests: "); + va_start(args, format); + vfprintf(stderr, format, args); + va_end(args); + fprintf(stderr, "\n"); + exit(1); +} /* @@ -392,6 +424,35 @@ x_strdup(const char *s, const char *file, int line) /* + * Copy the first n characters of a string, reporting a fatal error and + * existing on failure. + * + * Avoid using the system strndup function since it may not exist (on Mac OS + * X, for example), and there's no need to introduce another portability + * requirement. + */ +char * +x_strndup(const char *s, size_t size, const char *file, int line) +{ + const char *p; + size_t len; + char *copy; + + /* Don't assume that the source string is nul-terminated. */ + for (p = s; (size_t) (p - s) < size && *p != '\0'; p++) + ; + len = (size_t) (p - s); + copy = malloc(len + 1); + if (copy == NULL) + sysdie("failed to strndup %lu bytes at %s line %d", + (unsigned long) len, file, line); + memcpy(copy, s, len); + copy[len] = '\0'; + return copy; +} + + +/* * Form a new string by concatenating multiple strings. The arguments must be * terminated by (const char *) 0. * @@ -447,7 +508,7 @@ concat(const char *first, ...) static double tv_seconds(const struct timeval *tv) { - return difftime(tv->tv_sec, 0) + tv->tv_usec * 1e-6; + return difftime(tv->tv_sec, 0) + (double) tv->tv_usec * 1e-6; } @@ -485,12 +546,25 @@ skip_whitespace(const char *p) /* + * Given a pointer to a string, skip any non-whitespace characters and return + * a pointer to the first whitespace character, or to the end of the string. + */ +static const char * +skip_non_whitespace(const char *p) +{ + while (*p != '\0' && !isspace((unsigned char)(*p))) + p++; + return p; +} + + +/* * 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) +test_start(char *const *command, int *fd) { int fds[2], infd, errfd; pid_t child; @@ -541,8 +615,9 @@ test_start(const char *path, int *fd) } /* Now, exec our process. */ - if (execl(path, path, (char *) 0) == -1) + if (execv(command[0], command) == -1) _exit(CHILDERR_EXEC); + break; /* In parent. Close the extra file descriptor. */ default: @@ -1035,7 +1110,7 @@ test_run(struct testset *ts, enum test_verbose verbose) char buffer[BUFSIZ]; /* Run the test program. */ - testpid = test_start(ts->path, &outfd); + testpid = test_start(ts->command, &outfd); output = fdopen(outfd, "r"); if (!output) { puts("ABORTED"); @@ -1097,6 +1172,7 @@ test_fail_summary(const struct testlist *fails) struct testset *ts; unsigned int chars; unsigned long i, first, last, total; + double failed; puts(header); @@ -1105,8 +1181,9 @@ test_fail_summary(const struct testlist *fails) for (; fails; fails = fails->next) { ts = fails->ts; total = ts->count - ts->skipped; + failed = (double) ts->failed; printf("%-26.26s %4lu/%-4lu %3.0f%% %4lu ", ts->file, ts->failed, - total, total ? (ts->failed * 100.0) / total : 0, + total, total ? (failed * 100.0) / (double) total : 0, ts->skipped); if (WIFEXITED(ts->status)) printf("%4d ", WEXITSTATUS(ts->status)); @@ -1203,19 +1280,111 @@ find_test(const char *name, const char *source, const char *build) /* + * Parse a single line of a test list and store the test name and command to + * execute it in the given testset struct. + * + * Normally, each line is just the name of the test, which is located in the + * test directory and turned into a command to run. However, each line may + * have whitespace-separated options, which change the command that's run. + * Current supported options are: + * + * valgrind + * Run the test under valgrind if C_TAP_VALGRIND is set. The contents + * of that environment variable are taken as the valgrind command (with + * options) to run. The command is parsed with a simple split on + * whitespace and no quoting is supported. + * + * libtool + * If running under valgrind, use libtool to invoke valgrind. This avoids + * running valgrind on the wrapper shell script generated by libtool. If + * set, C_TAP_LIBTOOL must be set to the full path to the libtool program + * to use to run valgrind and thus the test. Ignored if the test isn't + * being run under valgrind. + */ +static void +parse_test_list_line(const char *line, struct testset *ts, const char *source, + const char *build) +{ + const char *p, *end, *option, *libtool; + const char *valgrind = NULL; + unsigned int use_libtool = 0; + unsigned int use_valgrind = 0; + size_t len, i; + + /* Determine the name of the test. */ + p = skip_non_whitespace(line); + ts->file = xstrndup(line, p - line); + + /* Check if any test options are set. */ + p = skip_whitespace(p); + while (*p != '\0') { + end = skip_non_whitespace(p); + if (strncmp(p, "libtool", end - p) == 0) { + use_libtool = 1; + p = end; + } else if (strncmp(p, "valgrind", end - p) == 0) { + valgrind = getenv("C_TAP_VALGRIND"); + use_valgrind = (valgrind != NULL); + p = end; + } else { + option = xstrndup(p, end - p); + die("unknown test list option %s", option); + } + p = skip_whitespace(end); + } + + /* Construct the argv to run the test. First, find the length. */ + len = 1; + if (use_valgrind && valgrind != NULL) { + p = skip_whitespace(valgrind); + while (*p != '\0') { + len++; + p = skip_whitespace(skip_non_whitespace(p)); + } + if (use_libtool) + len += 2; + } + + /* Now, build the command. */ + ts->command = xcalloc(len + 1, sizeof(char *)); + i = 0; + if (use_valgrind && valgrind != NULL) { + if (use_libtool) { + libtool = getenv("C_TAP_LIBTOOL"); + if (libtool == NULL) + die("valgrind with libtool requested, but C_TAP_LIBTOOL is not" + " set"); + ts->command[i++] = xstrdup(libtool); + ts->command[i++] = xstrdup("--mode=execute"); + } + p = skip_whitespace(valgrind); + while (*p != '\0') { + end = skip_non_whitespace(p); + ts->command[i++] = xstrndup(p, end - p); + p = skip_whitespace(end); + } + } + if (i != len - 1) + die("internal error while constructing command line"); + ts->command[i++] = find_test(ts->file, source, build); + ts->command[i] = NULL; +} + + +/* * Read a list of tests from a file, returning the list of tests as a struct * testlist, or NULL if there were no tests (such as a file containing only * comments). Reports an error to standard error and exits if the list of * tests cannot be read. */ static struct testlist * -read_test_list(const char *filename) +read_test_list(const char *filename, const char *source, const char *build) { FILE *file; unsigned int line; size_t length; char buffer[BUFSIZ]; - const char *testname; + const char *start; struct testlist *listhead, *current; /* Create the initial container list that will hold our results. */ @@ -1240,10 +1409,10 @@ read_test_list(const char *filename) buffer[length] = '\0'; /* Skip comments, leading spaces, and blank lines. */ - testname = skip_whitespace(buffer); - if (strlen(testname) == 0) + start = skip_whitespace(buffer); + if (strlen(start) == 0) continue; - if (testname[0] == '#') + if (start[0] == '#') continue; /* Allocate the new testset structure. */ @@ -1255,7 +1424,9 @@ read_test_list(const char *filename) } current->ts = xcalloc(1, sizeof(struct testset)); current->ts->plan = PLAN_INIT; - current->ts->file = xstrdup(testname); + + /* Parse the line and store the results in the testset struct. */ + parse_test_list_line(start, current->ts, source, build); } fclose(file); @@ -1277,7 +1448,7 @@ read_test_list(const char *filename) * freeing. */ static struct testlist * -build_test_list(char *argv[], int argc) +build_test_list(char *argv[], int argc, const char *source, const char *build) { int i; struct testlist *listhead, *current; @@ -1297,6 +1468,9 @@ build_test_list(char *argv[], int argc) current->ts = xcalloc(1, sizeof(struct testset)); current->ts->plan = PLAN_INIT; current->ts->file = xstrdup(argv[i]); + current->ts->command = xcalloc(2, sizeof(char *)); + current->ts->command[0] = find_test(current->ts->file, source, build); + current->ts->command[1] = NULL; } /* If there were no tests, current is still NULL. */ @@ -1314,8 +1488,12 @@ build_test_list(char *argv[], int argc) static void free_testset(struct testset *ts) { + size_t i; + free(ts->file); - free(ts->path); + for (i = 0; ts->command[i] != NULL; i++) + free(ts->command[i]); + free(ts->command); free(ts->results); free(ts->reason); free(ts); @@ -1330,8 +1508,7 @@ free_testset(struct testset *ts) * frees the test list that's passed in. */ static int -test_batch(struct testlist *tests, const char *source, const char *build, - enum test_verbose verbose) +test_batch(struct testlist *tests, enum test_verbose verbose) { size_t length, i; size_t longest = 0; @@ -1382,7 +1559,6 @@ test_batch(struct testlist *tests, const char *source, const char *build, fflush(stdout); /* Run the test. */ - ts->path = find_test(ts->file, source, build); succeeded = test_run(ts, verbose); fflush(stdout); if (verbose) @@ -1446,7 +1622,7 @@ test_batch(struct testlist *tests, const char *source, const char *build, fputs("All tests successful", stdout); else printf("Failed %lu/%lu tests, %.2f%% okay", failed, total, - (total - failed) * 100.0 / total); + (double) (total - failed) * 100.0 / (double) total); if (skipped != 0) { if (skipped == 1) printf(", %lu test skipped", skipped); @@ -1479,8 +1655,9 @@ test_single(const char *program, const char *source, const char *build) /* - * Main routine. Set the SOURCE and BUILD environment variables and then, - * given a file listing tests, run each test listed. + * Main routine. Set the C_TAP_SOURCE, C_TAP_BUILD, SOURCE, and BUILD + * environment variables and then, given a file listing tests, run each test + * listed. */ int main(int argc, char *argv[]) @@ -1489,13 +1666,15 @@ main(int argc, char *argv[]) int status = 0; int single = 0; enum test_verbose verbose = CONCISE; + char *c_tap_source_env = NULL; + char *c_tap_build_env = NULL; char *source_env = NULL; char *build_env = NULL; const char *program; const char *shortlist; const char *list = NULL; - const char *source = SOURCE; - const char *build = BUILD; + const char *source = C_TAP_SOURCE; + const char *build = C_TAP_BUILD; struct testlist *tests; program = argv[0]; @@ -1537,13 +1716,23 @@ main(int argc, char *argv[]) if (getenv("C_TAP_VERBOSE") != NULL) verbose = VERBOSE; - /* Set SOURCE and BUILD environment variables. */ + /* + * Set C_TAP_SOURCE and C_TAP_BUILD environment variables. Also set + * SOURCE and BUILD for backward compatibility, although we're trying to + * migrate to the ones with a C_TAP_* prefix. + */ if (source != NULL) { + c_tap_source_env = concat("C_TAP_SOURCE=", source, (const char *) 0); + if (putenv(c_tap_source_env) != 0) + sysdie("cannot set C_TAP_SOURCE in the environment"); source_env = concat("SOURCE=", source, (const char *) 0); if (putenv(source_env) != 0) sysdie("cannot set SOURCE in the environment"); } if (build != NULL) { + c_tap_build_env = concat("C_TAP_BUILD=", build, (const char *) 0); + if (putenv(c_tap_build_env) != 0) + sysdie("cannot set C_TAP_BUILD in the environment"); build_env = concat("BUILD=", build, (const char *) 0); if (putenv(build_env) != 0) sysdie("cannot set BUILD in the environment"); @@ -1559,20 +1748,24 @@ main(int argc, char *argv[]) else shortlist++; printf(banner, shortlist); - tests = read_test_list(list); - status = test_batch(tests, source, build, verbose) ? 0 : 1; + tests = read_test_list(list, source, build); + status = test_batch(tests, verbose) ? 0 : 1; } else { - tests = build_test_list(argv, argc); - status = test_batch(tests, source, build, verbose) ? 0 : 1; + tests = build_test_list(argv, argc, source, build); + status = test_batch(tests, verbose) ? 0 : 1; } /* For valgrind cleanliness, free all our memory. */ if (source_env != NULL) { + putenv((char *) "C_TAP_SOURCE="); putenv((char *) "SOURCE="); + free(c_tap_source_env); free(source_env); } if (build_env != NULL) { + putenv((char *) "C_TAP_BUILD="); putenv((char *) "BUILD="); + free(c_tap_build_env); free(build_env); } exit(status); |