diff options
author | Russ Allbery <eagle@eyrie.org> | 2014-07-16 13:43:17 -0700 |
---|---|---|
committer | Russ Allbery <eagle@eyrie.org> | 2014-07-16 13:43:17 -0700 |
commit | 6409733ee3b7b1910dc1c166a392cc628834146c (patch) | |
tree | e9460f8f2ca0f3676afeed2a9dcf549acfc39b53 /tests/tap | |
parent | 334ed844cbb5c8f7ea82a94c701a3016dd6950b9 (diff) | |
parent | f8963ceb19cd2b503b981f43a3f8c0f45649989f (diff) |
Imported Upstream version 1.1
Diffstat (limited to 'tests/tap')
-rw-r--r-- | tests/tap/basic.c | 586 | ||||
-rw-r--r-- | tests/tap/basic.h | 79 | ||||
-rw-r--r-- | tests/tap/kerberos.c | 107 | ||||
-rw-r--r-- | tests/tap/kerberos.h | 28 | ||||
-rw-r--r-- | tests/tap/kerberos.sh | 2 | ||||
-rw-r--r-- | tests/tap/libtap.sh | 8 | ||||
-rw-r--r-- | tests/tap/macros.h | 9 | ||||
-rw-r--r-- | tests/tap/messages.c | 10 | ||||
-rw-r--r-- | tests/tap/messages.h | 2 | ||||
-rw-r--r-- | tests/tap/perl/Test/RRA.pm | 82 | ||||
-rw-r--r-- | tests/tap/perl/Test/RRA/Automake.pm | 69 | ||||
-rw-r--r-- | tests/tap/perl/Test/RRA/Config.pm | 34 | ||||
-rw-r--r-- | tests/tap/process.c | 352 | ||||
-rw-r--r-- | tests/tap/process.h | 33 | ||||
-rw-r--r-- | tests/tap/remctl.sh | 2 | ||||
-rw-r--r-- | tests/tap/string.c | 2 | ||||
-rw-r--r-- | tests/tap/string.h | 2 |
17 files changed, 1123 insertions, 284 deletions
diff --git a/tests/tap/basic.c b/tests/tap/basic.c index e8196fc..92a749b 100644 --- a/tests/tap/basic.c +++ b/tests/tap/basic.c @@ -12,8 +12,8 @@ * 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, 2011, 2012 Russ Allbery <rra@stanford.edu> - * Copyright 2001, 2002, 2004, 2005, 2006, 2007, 2008, 2011, 2012 + * Copyright 2009, 2010, 2011, 2012, 2013, 2014 Russ Allbery <eagle@eyrie.org> + * Copyright 2001, 2002, 2004, 2005, 2006, 2007, 2008, 2011, 2012, 2013, 2014 * The Board of Trustees of the Leland Stanford Junior University * * Permission is hereby granted, free of charge, to any person obtaining a @@ -36,6 +36,7 @@ */ #include <errno.h> +#include <limits.h> #include <stdarg.h> #include <stdio.h> #include <stdlib.h> @@ -58,7 +59,7 @@ /* * The test count. Always contains the number that will be used for the next - * test status. + * test status. This is exported to callers of the library. */ unsigned long testnum = 1; @@ -66,72 +67,298 @@ unsigned long testnum = 1; * Status information stored so that we can give a test summary at the end of * the test case. We store the planned final test and the count of failures. * We can get the highest test count from testnum. - * - * We also store the PID of the process that called plan() and only summarize - * results when that process exits, so as to not misreport results in forked - * processes. - * - * If _lazy is true, we're doing lazy planning and will print out the plan - * based on the last test number at the end of testing. */ static unsigned long _planned = 0; static unsigned long _failed = 0; + +/* + * Store the PID of the process that called plan() and only summarize + * results when that process exits, so as to not misreport results in forked + * processes. + */ static pid_t _process = 0; + +/* + * If true, we're doing lazy planning and will print out the plan based on the + * last test number at the end of testing. + */ static int _lazy = 0; +/* + * If true, the test was aborted by calling bail(). Currently, this is only + * used to ensure that we pass a false value to any cleanup functions even if + * all tests to that point have passed. + */ +static int _aborted = 0; + +/* + * Registered cleanup functions. These are stored as a linked list and run in + * registered order by finish when the test program exits. Each function is + * passed a boolean value indicating whether all tests were successful. + */ +struct cleanup_func { + test_cleanup_func func; + struct cleanup_func *next; +}; +static struct cleanup_func *cleanup_funcs = NULL; + +/* + * Registered diag files. Any output found in these files will be printed out + * as if it were passed to diag() before any other output we do. This allows + * background processes to log to a file and have that output interleved with + * the test output. + */ +struct diag_file { + char *name; + FILE *file; + char *buffer; + size_t bufsize; + struct diag_file *next; +}; +static struct diag_file *diag_files = NULL; + +/* + * Print a specified prefix and then the test description. Handles turning + * the argument list into a va_args structure suitable for passing to + * print_desc, which has to be done in a macro. Assumes that format is the + * argument immediately before the variadic arguments. + */ +#define PRINT_DESC(prefix, format) \ + do { \ + if (format != NULL) { \ + va_list args; \ + if (prefix != NULL) \ + printf("%s", prefix); \ + va_start(args, format); \ + vprintf(format, args); \ + va_end(args); \ + } \ + } while (0) + + +/* + * Form a new string by concatenating multiple strings. The arguments must be + * terminated by (const char *) 0. + * + * This function only exists because we can't assume asprintf. We can't + * simulate asprintf with snprintf because we're only assuming SUSv3, which + * does not require that snprintf with a NULL buffer return the required + * length. When those constraints are relaxed, this should be ripped out and + * replaced with asprintf or a more trivial replacement with snprintf. + */ +static char * +concat(const char *first, ...) +{ + va_list args; + char *result; + const char *string; + size_t offset; + size_t length = 0; + + /* + * Find the total memory required. Ensure we don't overflow length. See + * the comment for breallocarray for why we're using UINT_MAX here. + */ + va_start(args, first); + for (string = first; string != NULL; string = va_arg(args, const char *)) { + if (length >= UINT_MAX - strlen(string)) + bail("strings too long in concat"); + length += strlen(string); + } + va_end(args); + length++; + + /* Create the string. */ + result = bmalloc(length); + va_start(args, first); + offset = 0; + for (string = first; string != NULL; string = va_arg(args, const char *)) { + memcpy(result + offset, string, strlen(string)); + offset += strlen(string); + } + va_end(args); + result[offset] = '\0'; + return result; +} + + +/* + * Check all registered diag_files for any output. We only print out the + * output if we see a complete line; otherwise, we wait for the next newline. + */ +static void +check_diag_files(void) +{ + struct diag_file *file; + fpos_t where; + size_t length; + int incomplete; + + /* + * Walk through each file and read each line of output available. The + * general scheme here used is as follows: try to read a line of output at + * a time. If we get NULL, check for EOF; on EOF, advance to the next + * file. + * + * If we get some data, see if it ends in a newline. If it doesn't end in + * a newline, we have one of two cases: our buffer isn't large enough, in + * which case we resize it and try again, or we have incomplete data in + * the file, in which case we rewind the file and will try again next + * time. + */ + for (file = diag_files; file != NULL; file = file->next) { + clearerr(file->file); + + /* Store the current position in case we have to rewind. */ + if (fgetpos(file->file, &where) < 0) + sysbail("cannot get position in %s", file->name); + + /* Continue until we get EOF or an incomplete line of data. */ + incomplete = 0; + while (!feof(file->file) && !incomplete) { + if (fgets(file->buffer, file->bufsize, file->file) == NULL) { + if (ferror(file->file)) + sysbail("cannot read from %s", file->name); + continue; + } + + /* + * See if the line ends in a newline. If not, see which error + * case we have. Use UINT_MAX as a substitute for SIZE_MAX (see + * the comment for breallocarray). + */ + length = strlen(file->buffer); + if (file->buffer[length - 1] != '\n') { + if (length < file->bufsize - 1) + incomplete = 1; + else { + if (file->bufsize >= UINT_MAX - BUFSIZ) + sysbail("line too long in %s", file->name); + file->bufsize += BUFSIZ; + file->buffer = brealloc(file->buffer, file->bufsize); + } + + /* + * On either incomplete lines or too small of a buffer, rewind + * and read the file again (on the next pass, if incomplete). + * It's simpler than trying to double-buffer the file. + */ + if (fsetpos(file->file, &where) < 0) + sysbail("cannot set position in %s", file->name); + continue; + } + + /* We saw a complete line. Print it out. */ + printf("# %s", file->buffer); + } + } +} + /* * Our exit handler. Called on completion of the test to report a summary of * results provided we're still in the original process. This also handles * printing out the plan if we used plan_lazy(), although that's suppressed if - * we never ran a test (due to an early bail, for example). + * we never ran a test (due to an early bail, for example), and running any + * registered cleanup functions. */ static void finish(void) { + int success, primary; + struct cleanup_func *current; unsigned long highest = testnum - 1; + struct diag_file *file, *tmp; + + /* Check for pending diag_file output. */ + check_diag_files(); + + /* Free the diag_files. */ + file = diag_files; + while (file != NULL) { + tmp = file; + file = file->next; + fclose(tmp->file); + free(tmp->name); + free(tmp->buffer); + free(tmp); + } + diag_files = NULL; + + /* + * Determine whether all tests were successful, which is needed before + * calling cleanup functions since we pass that fact to the functions. + */ + if (_planned == 0 && _lazy) + _planned = highest; + success = (!_aborted && _planned == highest && _failed == 0); + + /* + * If there are any registered cleanup functions, we run those first. We + * always run them, even if we didn't run a test. Don't do anything + * except free the diag_files and call cleanup functions if we aren't the + * primary process (the process in which plan or plan_lazy was called), + * and tell the cleanup functions that fact. + */ + primary = (_process == 0 || getpid() == _process); + while (cleanup_funcs != NULL) { + cleanup_funcs->func(success, primary); + current = cleanup_funcs; + cleanup_funcs = cleanup_funcs->next; + free(current); + } + if (!primary) + return; - if (_planned == 0 && !_lazy) + /* Don't do anything further if we never planned a test. */ + if (_planned == 0) return; + + /* If we're aborting due to bail, don't print summaries. */ + if (_aborted) + return; + + /* Print out the lazy plan if needed. */ fflush(stderr); - if (_process != 0 && getpid() == _process) { - if (_lazy && highest > 0) { - printf("1..%lu\n", highest); - _planned = highest; - } - if (_planned > highest) - printf("# Looks like you planned %lu test%s but only ran %lu\n", - _planned, (_planned > 1 ? "s" : ""), highest); - else if (_planned < highest) - printf("# Looks like you planned %lu test%s but ran %lu extra\n", - _planned, (_planned > 1 ? "s" : ""), highest - _planned); - else if (_failed > 0) - printf("# Looks like you failed %lu test%s of %lu\n", _failed, - (_failed > 1 ? "s" : ""), _planned); - else if (_planned > 1) - printf("# All %lu tests successful or skipped\n", _planned); - else - printf("# %lu test successful or skipped\n", _planned); - } + if (_lazy && _planned > 0) + printf("1..%lu\n", _planned); + + /* Print out a summary of the results. */ + if (_planned > highest) + diag("Looks like you planned %lu test%s but only ran %lu", _planned, + (_planned > 1 ? "s" : ""), highest); + else if (_planned < highest) + diag("Looks like you planned %lu test%s but ran %lu extra", _planned, + (_planned > 1 ? "s" : ""), highest - _planned); + else if (_failed > 0) + diag("Looks like you failed %lu test%s of %lu", _failed, + (_failed > 1 ? "s" : ""), _planned); + else if (_planned != 1) + diag("All %lu tests successful or skipped", _planned); + else + diag("%lu test successful or skipped", _planned); } /* * Initialize things. Turns on line buffering on stdout and then prints out - * the number of tests in the test suite. + * the number of tests in the test suite. We intentionally don't check for + * pending diag_file output here, since it should really come after the plan. */ void plan(unsigned long count) { if (setvbuf(stdout, NULL, _IOLBF, BUFSIZ) != 0) - fprintf(stderr, "# cannot set stdout to line buffered: %s\n", - strerror(errno)); + sysdiag("cannot set stdout to line buffered"); fflush(stderr); printf("1..%lu\n", count); testnum = 1; _planned = count; _process = getpid(); - atexit(finish); + if (atexit(finish) != 0) { + sysdiag("cannot register exit handler"); + diag("cleanups will not be run"); + } } @@ -143,83 +370,66 @@ void plan_lazy(void) { if (setvbuf(stdout, NULL, _IOLBF, BUFSIZ) != 0) - fprintf(stderr, "# cannot set stdout to line buffered: %s\n", - strerror(errno)); + sysdiag("cannot set stdout to line buffered"); testnum = 1; _process = getpid(); _lazy = 1; - atexit(finish); + if (atexit(finish) != 0) + sysbail("cannot register exit handler to display plan"); } /* * Skip the entire test suite and exits. Should be called instead of plan(), - * not after it, since it prints out a special plan line. + * not after it, since it prints out a special plan line. Ignore diag_file + * output here, since it's not clear if it's allowed before the plan. */ void skip_all(const char *format, ...) { fflush(stderr); printf("1..0 # skip"); - if (format != NULL) { - va_list args; - - putchar(' '); - va_start(args, format); - vprintf(format, args); - va_end(args); - } + PRINT_DESC(" ", format); putchar('\n'); exit(0); } /* - * Print the test description. - */ -static void -print_desc(const char *format, va_list args) -{ - printf(" - "); - vprintf(format, args); -} - - -/* * Takes a boolean success value and assumes the test passes if that value * is true and fails if that value is false. */ -void +int ok(int success, const char *format, ...) { fflush(stderr); + check_diag_files(); printf("%sok %lu", success ? "" : "not ", testnum++); if (!success) _failed++; - if (format != NULL) { - va_list args; - - va_start(args, format); - print_desc(format, args); - va_end(args); - } + PRINT_DESC(" - ", format); putchar('\n'); + return success; } /* * Same as ok(), but takes the format arguments as a va_list. */ -void +int okv(int success, const char *format, va_list args) { fflush(stderr); + check_diag_files(); printf("%sok %lu", success ? "" : "not ", testnum++); if (!success) _failed++; - if (format != NULL) - print_desc(format, args); + if (format != NULL) { + printf(" - "); + vprintf(format, args); + } putchar('\n'); + return success; } @@ -230,15 +440,9 @@ void skip(const char *reason, ...) { fflush(stderr); + check_diag_files(); printf("ok %lu # skip", testnum++); - if (reason != NULL) { - va_list args; - - va_start(args, reason); - putchar(' '); - vprintf(reason, args); - va_end(args); - } + PRINT_DESC(" ", reason); putchar('\n'); } @@ -246,25 +450,21 @@ skip(const char *reason, ...) /* * Report the same status on the next count tests. */ -void -ok_block(unsigned long count, int status, const char *format, ...) +int +ok_block(unsigned long count, int success, const char *format, ...) { unsigned long i; fflush(stderr); + check_diag_files(); for (i = 0; i < count; i++) { - printf("%sok %lu", status ? "" : "not ", testnum++); - if (!status) + printf("%sok %lu", success ? "" : "not ", testnum++); + if (!success) _failed++; - if (format != NULL) { - va_list args; - - va_start(args, format); - print_desc(format, args); - va_end(args); - } + PRINT_DESC(" - ", format); putchar('\n'); } + return success; } @@ -277,16 +477,10 @@ skip_block(unsigned long count, const char *reason, ...) unsigned long i; fflush(stderr); + check_diag_files(); for (i = 0; i < count; i++) { printf("ok %lu # skip", testnum++); - if (reason != NULL) { - va_list args; - - va_start(args, reason); - putchar(' '); - vprintf(reason, args); - va_end(args); - } + PRINT_DESC(" ", reason); putchar('\n'); } } @@ -296,25 +490,25 @@ skip_block(unsigned long count, const char *reason, ...) * Takes an expected integer and a seen integer and assumes the test passes * if those two numbers match. */ -void +int is_int(long wanted, long seen, const char *format, ...) { + int success; + fflush(stderr); - if (wanted == seen) + check_diag_files(); + success = (wanted == seen); + if (success) printf("ok %lu", testnum++); else { - printf("# wanted: %ld\n# seen: %ld\n", wanted, seen); + diag("wanted: %ld", wanted); + diag(" seen: %ld", seen); printf("not ok %lu", testnum++); _failed++; } - if (format != NULL) { - va_list args; - - va_start(args, format); - print_desc(format, args); - va_end(args); - } + PRINT_DESC(" - ", format); putchar('\n'); + return success; } @@ -322,29 +516,29 @@ is_int(long wanted, long seen, const char *format, ...) * Takes a string and what the string should be, and assumes the test passes * if those strings match (using strcmp). */ -void +int is_string(const char *wanted, const char *seen, const char *format, ...) { + int success; + if (wanted == NULL) wanted = "(null)"; if (seen == NULL) seen = "(null)"; fflush(stderr); - if (strcmp(wanted, seen) == 0) + check_diag_files(); + success = (strcmp(wanted, seen) == 0); + if (success) printf("ok %lu", testnum++); else { - printf("# wanted: %s\n# seen: %s\n", wanted, seen); + diag("wanted: %s", wanted); + diag(" seen: %s", seen); printf("not ok %lu", testnum++); _failed++; } - if (format != NULL) { - va_list args; - - va_start(args, format); - print_desc(format, args); - va_end(args); - } + PRINT_DESC(" - ", format); putchar('\n'); + return success; } @@ -352,26 +546,25 @@ is_string(const char *wanted, const char *seen, const char *format, ...) * Takes an expected unsigned long and a seen unsigned long and assumes the * test passes if the two numbers match. Otherwise, reports them in hex. */ -void +int is_hex(unsigned long wanted, unsigned long seen, const char *format, ...) { + int success; + fflush(stderr); - if (wanted == seen) + check_diag_files(); + success = (wanted == seen); + if (success) printf("ok %lu", testnum++); else { - printf("# wanted: %lx\n# seen: %lx\n", (unsigned long) wanted, - (unsigned long) seen); + diag("wanted: %lx", (unsigned long) wanted); + diag(" seen: %lx", (unsigned long) seen); printf("not ok %lu", testnum++); _failed++; } - if (format != NULL) { - va_list args; - - va_start(args, format); - print_desc(format, args); - va_end(args); - } + PRINT_DESC(" - ", format); putchar('\n'); + return success; } @@ -383,14 +576,16 @@ bail(const char *format, ...) { va_list args; + _aborted = 1; fflush(stderr); + check_diag_files(); fflush(stdout); printf("Bail out! "); va_start(args, format); vprintf(format, args); va_end(args); printf("\n"); - exit(1); + exit(255); } @@ -403,51 +598,110 @@ sysbail(const char *format, ...) va_list args; int oerrno = errno; + _aborted = 1; fflush(stderr); + check_diag_files(); fflush(stdout); printf("Bail out! "); va_start(args, format); vprintf(format, args); va_end(args); printf(": %s\n", strerror(oerrno)); - exit(1); + exit(255); } /* - * Report a diagnostic to stderr. + * Report a diagnostic to stderr. Always returns 1 to allow embedding in + * compound statements. */ -void +int diag(const char *format, ...) { va_list args; fflush(stderr); + check_diag_files(); fflush(stdout); printf("# "); va_start(args, format); vprintf(format, args); va_end(args); printf("\n"); + return 1; } /* - * Report a diagnostic to stderr, appending strerror(errno). + * Report a diagnostic to stderr, appending strerror(errno). Always returns 1 + * to allow embedding in compound statements. */ -void +int sysdiag(const char *format, ...) { va_list args; int oerrno = errno; fflush(stderr); + check_diag_files(); fflush(stdout); printf("# "); va_start(args, format); vprintf(format, args); va_end(args); printf(": %s\n", strerror(oerrno)); + return 1; +} + + +/* + * Register a new file for diag_file processing. + */ +void +diag_file_add(const char *name) +{ + struct diag_file *file, *prev; + + file = bcalloc(1, sizeof(struct diag_file)); + file->name = bstrdup(name); + file->file = fopen(file->name, "r"); + if (file->file == NULL) + sysbail("cannot open %s", name); + file->buffer = bmalloc(BUFSIZ); + file->bufsize = BUFSIZ; + if (diag_files == NULL) + diag_files = file; + else { + for (prev = diag_files; prev->next != NULL; prev = prev->next) + ; + prev->next = file; + } +} + + +/* + * Remove a file from diag_file processing. If the file is not found, do + * nothing, since there are some situations where it can be removed twice + * (such as if it's removed from a cleanup function, since cleanup functions + * are called after freeing all the diag_files). + */ +void +diag_file_remove(const char *name) +{ + struct diag_file *file; + struct diag_file **prev = &diag_files; + + for (file = diag_files; file != NULL; file = file->next) { + if (strcmp(file->name, name) == 0) { + *prev = file->next; + fclose(file->file); + free(file->name); + free(file->buffer); + free(file); + return; + } + prev = &file->next; + } } @@ -495,6 +749,32 @@ brealloc(void *p, size_t size) /* + * The same as brealloc, but determine the size by multiplying an element + * count by a size, similar to calloc. The multiplication is checked for + * integer overflow. + * + * We should technically use SIZE_MAX here for the overflow check, but + * SIZE_MAX is C99 and we're only assuming C89 + SUSv3, which does not + * guarantee that it exists. They do guarantee that UINT_MAX exists, and we + * can assume that UINT_MAX <= SIZE_MAX. + * + * (In theory, C89 and C99 permit size_t to be smaller than unsigned int, but + * I disbelieve in the existence of such systems and they will have to cope + * without overflow checks.) + */ +void * +breallocarray(void *p, size_t n, size_t size) +{ + if (n > 0 && UINT_MAX / n <= size) + bail("reallocarray too large"); + p = realloc(p, n * size); + if (p == NULL) + sysbail("failed to realloc %lu bytes", (unsigned long) (n * size)); + return p; +} + + +/* * Copy a string, reporting a fatal error with bail on failure. */ char * @@ -542,17 +822,12 @@ bstrndup(const char *s, size_t n) * 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; @@ -560,9 +835,7 @@ test_file_path(const char *file) base = getenv(envs[i]); if (base == NULL) continue; - length = strlen(base) + 1 + strlen(file) + 1; - path = bmalloc(length); - sprintf(path, "%s/%s", base, file); + path = concat(base, "/", file, (const char *) 0); if (access(path, R_OK) == 0) break; free(path); @@ -580,8 +853,7 @@ test_file_path(const char *file) void test_file_path_free(char *path) { - if (path != NULL) - free(path); + free(path); } @@ -600,14 +872,11 @@ test_tmpdir(void) { const char *build; char *path = NULL; - size_t length; build = getenv("BUILD"); if (build == NULL) build = "."; - length = strlen(build) + strlen("/tmp") + 1; - path = bmalloc(length); - sprintf(path, "%s/tmp", build); + path = concat(build, "/tmp", (const char *) 0); if (access(path, X_OK) < 0) if (mkdir(path, 0777) < 0) sysbail("error creating temporary directory %s", path); @@ -623,7 +892,26 @@ test_tmpdir(void) void test_tmpdir_free(char *path) { - rmdir(path); if (path != NULL) - free(path); + rmdir(path); + free(path); +} + + +/* + * Register a cleanup function that is called when testing ends. All such + * registered functions will be run by finish. + */ +void +test_cleanup_register(test_cleanup_func func) +{ + struct cleanup_func *cleanup, **last; + + cleanup = bmalloc(sizeof(struct cleanup_func)); + cleanup->func = func; + cleanup->next = NULL; + last = &cleanup_funcs; + while (*last != NULL) + last = &(*last)->next; + *last = cleanup; } diff --git a/tests/tap/basic.h b/tests/tap/basic.h index fa4adaf..c002df9 100644 --- a/tests/tap/basic.h +++ b/tests/tap/basic.h @@ -4,8 +4,8 @@ * 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, 2011, 2012 Russ Allbery <rra@stanford.edu> - * Copyright 2001, 2002, 2004, 2005, 2006, 2007, 2008, 2011, 2012 + * Copyright 2009, 2010, 2011, 2012, 2013, 2014 Russ Allbery <eagle@eyrie.org> + * Copyright 2001, 2002, 2004, 2005, 2006, 2007, 2008, 2011, 2012, 2014 * The Board of Trustees of the Leland Stanford Junior University * * Permission is hereby granted, free of charge, to any person obtaining a @@ -32,7 +32,7 @@ #include <tests/tap/macros.h> #include <stdarg.h> /* va_list */ -#include <sys/types.h> /* size_t */ +#include <stddef.h> /* size_t */ /* * Used for iterating through arrays. ARRAY_SIZE returns the number of @@ -55,7 +55,7 @@ extern unsigned long testnum; void plan(unsigned long count); /* - * Prepare for lazy planning, in which the plan will be printed automatically + * Prepare for lazy planning, in which the plan will be printed automatically * at the end of the test program. */ void plan_lazy(void); @@ -67,26 +67,33 @@ void skip_all(const char *format, ...) /* * Basic reporting functions. The okv() function is the same as ok() but * takes the test description as a va_list to make it easier to reuse the - * reporting infrastructure when writing new tests. + * reporting infrastructure when writing new tests. ok() and okv() return the + * value of the success argument. */ -void ok(int success, const char *format, ...) +int ok(int success, const char *format, ...) __attribute__((__format__(printf, 2, 3))); -void okv(int success, const char *format, va_list args); +int okv(int success, const char *format, va_list args); void skip(const char *reason, ...) __attribute__((__format__(printf, 1, 2))); -/* Report the same status on, or skip, the next count tests. */ -void ok_block(unsigned long count, int success, const char *format, ...) +/* + * Report the same status on, or skip, the next count tests. ok_block() + * returns the value of the success argument. + */ +int ok_block(unsigned long count, int success, const char *format, ...) __attribute__((__format__(printf, 3, 4))); void skip_block(unsigned long count, const char *reason, ...) __attribute__((__format__(printf, 2, 3))); -/* Check an expected value against a seen value. */ -void is_int(long wanted, long seen, const char *format, ...) +/* + * Check an expected value against a seen value. Returns true if the test + * passes and false if it fails. + */ +int is_int(long wanted, long seen, const char *format, ...) __attribute__((__format__(printf, 3, 4))); -void is_string(const char *wanted, const char *seen, const char *format, ...) +int 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, ...) +int is_hex(unsigned long wanted, unsigned long seen, const char *format, ...) __attribute__((__format__(printf, 3, 4))); /* Bail out with an error. sysbail appends strerror(errno). */ @@ -96,29 +103,43 @@ void sysbail(const char *format, ...) __attribute__((__noreturn__, __nonnull__, __format__(printf, 1, 2))); /* Report a diagnostic to stderr prefixed with #. */ -void diag(const char *format, ...) +int diag(const char *format, ...) __attribute__((__nonnull__, __format__(printf, 1, 2))); -void sysdiag(const char *format, ...) +int sysdiag(const char *format, ...) __attribute__((__nonnull__, __format__(printf, 1, 2))); +/* + * Register or unregister a file that contains supplementary diagnostics. + * Before any other output, all registered files will be read, line by line, + * and each line will be reported as a diagnostic as if it were passed to + * diag(). Nul characters are not supported in these files and will result in + * truncated output. + */ +void diag_file_add(const char *file) + __attribute__((__nonnull__)); +void diag_file_remove(const char *file) + __attribute__((__nonnull__)); + /* Allocate memory, reporting a fatal error with bail on failure. */ void *bcalloc(size_t, size_t) - __attribute__((__alloc_size__(1, 2), __malloc__)); + __attribute__((__alloc_size__(1, 2), __malloc__, __warn_unused_result__)); void *bmalloc(size_t) - __attribute__((__alloc_size__(1), __malloc__)); + __attribute__((__alloc_size__(1), __malloc__, __warn_unused_result__)); +void *breallocarray(void *, size_t, size_t) + __attribute__((__alloc_size__(2, 3), __malloc__, __warn_unused_result__)); void *brealloc(void *, size_t) - __attribute__((__alloc_size__(2), __malloc__)); + __attribute__((__alloc_size__(2), __malloc__, __warn_unused_result__)); char *bstrdup(const char *) - __attribute__((__malloc__, __nonnull__)); + __attribute__((__malloc__, __nonnull__, __warn_unused_result__)); char *bstrndup(const char *, size_t) - __attribute__((__malloc__, __nonnull__)); + __attribute__((__malloc__, __nonnull__, __warn_unused_result__)); /* * 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__)); + __attribute__((__malloc__, __nonnull__, __warn_unused_result__)); void test_file_path_free(char *path); /* @@ -126,9 +147,23 @@ void test_file_path_free(char *path); * returned path should be freed with test_tmpdir_free. */ char *test_tmpdir(void) - __attribute__((__malloc__)); + __attribute__((__malloc__, __warn_unused_result__)); void test_tmpdir_free(char *path); +/* + * Register a cleanup function that is called when testing ends. All such + * registered functions will be run during atexit handling (and are therefore + * subject to all the same constraints and caveats as atexit functions). + * + * The function must return void and will be passed two argument, an int that + * will be true if the test completed successfully and false otherwise, and an + * int that will be true if the cleanup function is run in the primary process + * (the one that called plan or plan_lazy) and false otherwise. + */ +typedef void (*test_cleanup_func)(int, int); +void test_cleanup_register(test_cleanup_func) + __attribute__((__nonnull__)); + END_DECLS #endif /* TAP_BASIC_H */ diff --git a/tests/tap/kerberos.c b/tests/tap/kerberos.c index 474cf4f..578a858 100644 --- a/tests/tap/kerberos.c +++ b/tests/tap/kerberos.c @@ -14,8 +14,8 @@ * The canonical version of this file is maintained in the rra-c-util package, * which can be found at <http://www.eyrie.org/~eagle/software/rra-c-util/>. * - * Written by Russ Allbery <rra@stanford.edu> - * Copyright 2006, 2007, 2009, 2010, 2011, 2012 + * Written by Russ Allbery <eagle@eyrie.org> + * Copyright 2006, 2007, 2009, 2010, 2011, 2012, 2013, 2014 * The Board of Trustees of the Leland Stanford Junior University * * Permission is hereby granted, free of charge, to any person obtaining a @@ -38,7 +38,7 @@ */ #include <config.h> -#ifdef HAVE_KERBEROS +#ifdef HAVE_KRB5 # include <portable/krb5.h> #endif #include <portable/system.h> @@ -47,6 +47,7 @@ #include <tests/tap/basic.h> #include <tests/tap/kerberos.h> +#include <tests/tap/macros.h> #include <tests/tap/process.h> #include <tests/tap/string.h> @@ -79,7 +80,7 @@ static char *tmpdir_conf = NULL; * Kerberos libraries available and one if we don't. Uses keytab to obtain * credentials, and fills in the cache member of the provided config struct. */ -#ifdef HAVE_KERBEROS +#ifdef HAVE_KRB5 static void kerberos_kinit(void) @@ -147,7 +148,7 @@ kerberos_kinit(void) free(krbtgt); } -#else /* !HAVE_KERBEROS */ +#else /* !HAVE_KRB5 */ static void kerberos_kinit(void) @@ -197,37 +198,27 @@ kerberos_kinit(void) bail("cannot get Kerberos tickets"); } -#endif /* !HAVE_KERBEROS */ +#endif /* !HAVE_KRB5 */ /* - * Clean up at the end of a test. This removes the ticket cache and resets - * and frees the memory allocated for the environment variables so that - * valgrind output on test suites is cleaner. + * Free all the memory associated with our Kerberos setup, but don't remove + * the ticket cache. This is used when cleaning up on exit from a non-primary + * process so that test programs that fork don't remove the ticket cache still + * used by the main program. */ -void -kerberos_cleanup(void) +static void +kerberos_free(void) { - char *path; - - if (tmpdir_ticket != NULL) { - basprintf(&path, "%s/krb5cc_test", tmpdir_ticket); - unlink(path); - free(path); - test_tmpdir_free(tmpdir_ticket); - tmpdir_ticket = NULL; - } + test_tmpdir_free(tmpdir_ticket); + tmpdir_ticket = NULL; if (config != NULL) { - if (config->keytab != NULL) { - test_file_path_free(config->keytab); - free(config->principal); - free(config->cache); - } - if (config->userprinc != NULL) { - free(config->userprinc); - free(config->username); - free(config->password); - } + test_file_path_free(config->keytab); + free(config->principal); + free(config->cache); + free(config->userprinc); + free(config->username); + free(config->password); free(config); config = NULL; } @@ -245,6 +236,42 @@ kerberos_cleanup(void) /* + * Clean up at the end of a test. This removes the ticket cache and resets + * and frees the memory allocated for the environment variables so that + * valgrind output on test suites is cleaner. Most of the work is done by + * kerberos_free, but this function also deletes the ticket cache. + */ +void +kerberos_cleanup(void) +{ + char *path; + + if (tmpdir_ticket != NULL) { + basprintf(&path, "%s/krb5cc_test", tmpdir_ticket); + unlink(path); + free(path); + } + kerberos_free(); +} + + +/* + * The cleanup handler for the TAP framework. Call kerberos_cleanup if we're + * in the primary process and kerberos_free if not. The first argument, which + * indicates whether the test succeeded or not, is ignored, since we need to + * do the same thing either way. + */ +static void +kerberos_cleanup_handler(int success UNUSED, int primary) +{ + if (primary) + kerberos_cleanup(); + else + kerberos_free(); +} + + +/* * Obtain Kerberos tickets for the principal specified in config/principal * using the keytab specified in config/keytab, both of which are presumed to * be in tests in either the build or the source tree. Also sets KRB5_KTNAME @@ -321,15 +348,13 @@ kerberos_setup(enum kerberos_needs needs) *config->realm = '\0'; config->realm++; } - if (path != NULL) - test_file_path_free(path); + test_file_path_free(path); /* - * Register the cleanup function as an atexit handler so that the caller - * doesn't have to worry about cleanup. + * Register the cleanup function so that the caller doesn't have to do + * explicit cleanup. */ - if (atexit(kerberos_cleanup) != 0) - sysdiag("cannot register cleanup function"); + test_cleanup_register(kerberos_cleanup_handler); /* Return the configuration. */ return config; @@ -357,10 +382,8 @@ kerberos_cleanup_conf(void) tmpdir_conf = NULL; } putenv((char *) "KRB5_CONFIG="); - if (krb5_config != NULL) { - free(krb5_config); - krb5_config = NULL; - } + free(krb5_config); + krb5_config = NULL; } @@ -401,7 +424,7 @@ kerberos_generate_conf(const char *realm) * The remaining functions in this file are only available if Kerberos * libraries are available. */ -#ifdef HAVE_KERBEROS +#ifdef HAVE_KRB5 /* @@ -485,4 +508,4 @@ kerberos_keytab_principal(krb5_context ctx, const char *path) return princ; } -#endif /* HAVE_KERBEROS */ +#endif /* HAVE_KRB5 */ diff --git a/tests/tap/kerberos.h b/tests/tap/kerberos.h index 31b6343..8be0add 100644 --- a/tests/tap/kerberos.h +++ b/tests/tap/kerberos.h @@ -4,8 +4,8 @@ * The canonical version of this file is maintained in the rra-c-util package, * which can be found at <http://www.eyrie.org/~eagle/software/rra-c-util/>. * - * Written by Russ Allbery <rra@stanford.edu> - * Copyright 2006, 2007, 2009, 2011, 2012 + * Written by Russ Allbery <eagle@eyrie.org> + * Copyright 2006, 2007, 2009, 2011, 2012, 2013, 2014 * The Board of Trustees of the Leland Stanford Junior University * * Permission is hereby granted, free of charge, to any person obtaining a @@ -33,7 +33,7 @@ #include <config.h> #include <tests/tap/macros.h> -#ifdef HAVE_KERBEROS +#ifdef HAVE_KRB5 # include <portable/krb5.h> #endif @@ -53,10 +53,10 @@ struct kerberos_config { * certain configuration information isn't available. */ enum kerberos_needs { - TAP_KRB_NEEDS_NONE, - TAP_KRB_NEEDS_KEYTAB, - TAP_KRB_NEEDS_PASSWORD, - TAP_KRB_NEEDS_BOTH + TAP_KRB_NEEDS_NONE = 0x00, + TAP_KRB_NEEDS_KEYTAB = 0x01, + TAP_KRB_NEEDS_PASSWORD = 0x02, + TAP_KRB_NEEDS_BOTH = 0x01 | 0x02 }; BEGIN_DECLS @@ -73,11 +73,11 @@ BEGIN_DECLS * the principal field will be NULL. If the files exist but loading them * fails, or authentication fails, kerberos_setup calls bail. * - * kerberos_cleanup will be set up to run from an atexit handler. This means - * that any child processes that should not remove the Kerberos ticket cache - * should call _exit instead of exit. The principal will be automatically - * freed when kerberos_cleanup is called or if kerberos_setup is called again. - * The caller doesn't need to worry about it. + * kerberos_cleanup will be run as a cleanup function normally, freeing all + * resources and cleaning up temporary files on process exit. It can, + * however, be called directly if for some reason the caller needs to delete + * the Kerberos environment again. However, normally the caller can just call + * kerberos_setup again. */ struct kerberos_config *kerberos_setup(enum kerberos_needs) __attribute__((__malloc__)); @@ -100,7 +100,7 @@ void kerberos_generate_conf(const char *realm); void kerberos_cleanup_conf(void); /* Thes interfaces are only available with native Kerberos support. */ -#ifdef HAVE_KERBEROS +#ifdef HAVE_KRB5 /* Bail out with an error, appending the Kerberos error message. */ void bail_krb5(krb5_context, krb5_error_code, const char *format, ...) @@ -118,7 +118,7 @@ void diag_krb5(krb5_context, krb5_error_code, const char *format, ...) krb5_principal kerberos_keytab_principal(krb5_context, const char *path) __attribute__((__nonnull__)); -#endif /* HAVE_KERBEROS */ +#endif /* HAVE_KRB5 */ END_DECLS diff --git a/tests/tap/kerberos.sh b/tests/tap/kerberos.sh index d2f174d..e970ae5 100644 --- a/tests/tap/kerberos.sh +++ b/tests/tap/kerberos.sh @@ -8,7 +8,7 @@ # The canonical version of this file is maintained in the rra-c-util package, # which can be found at <http://www.eyrie.org/~eagle/software/rra-c-util/>. # -# Written by Russ Allbery <rra@stanford.edu> +# Written by Russ Allbery <eagle@eyrie.org> # Copyright 2009, 2010, 2011, 2012 # The Board of Trustees of the Leland Stanford Junior University # diff --git a/tests/tap/libtap.sh b/tests/tap/libtap.sh index f9347d8..9731032 100644 --- a/tests/tap/libtap.sh +++ b/tests/tap/libtap.sh @@ -9,9 +9,9 @@ # 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, 2011, 2012 Russ Allbery <rra@stanford.edu> -# Copyright 2006, 2007, 2008 +# Written by Russ Allbery <eagle@eyrie.org> +# Copyright 2009, 2010, 2011, 2012 Russ Allbery <eagle@eyrie.org> +# Copyright 2006, 2007, 2008, 2013 # The Board of Trustees of the Leland Stanford Junior University # # Permission is hereby granted, free of charge, to any person obtaining a copy @@ -204,7 +204,7 @@ strip_colon_error() { # Bail out with an error message. bail () { echo 'Bail out!' "$@" - exit 1 + exit 255 } # Output a diagnostic on standard error, preceded by the required # mark. diff --git a/tests/tap/macros.h b/tests/tap/macros.h index 33fee42..04cc420 100644 --- a/tests/tap/macros.h +++ b/tests/tap/macros.h @@ -8,7 +8,7 @@ * 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 2008, 2012 Russ Allbery <rra@stanford.edu> + * Copyright 2008, 2012, 2013 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"), @@ -58,6 +58,13 @@ # endif #endif +/* Suppress __warn_unused_result__ if gcc is too old. */ +#if !defined(__attribute__) && !defined(__warn_unused_result__) +# if __GNUC__ < 3 || (__GNUC__ == 3 && __GNUC_MINOR__ < 4) +# define __warn_unused_result__ /* empty */ +# endif +#endif + /* * LLVM and Clang pretend to be GCC but don't support all of the __attribute__ * settings that GCC does. For them, suppress warnings about unknown diff --git a/tests/tap/messages.c b/tests/tap/messages.c index abc2c49..45b0566 100644 --- a/tests/tap/messages.c +++ b/tests/tap/messages.c @@ -8,8 +8,8 @@ * The canonical version of this file is maintained in the rra-c-util package, * which can be found at <http://www.eyrie.org/~eagle/software/rra-c-util/>. * - * Copyright 2002, 2004, 2005 Russ Allbery <rra@stanford.edu> - * Copyright 2006, 2007, 2009, 2012 + * Copyright 2002, 2004, 2005 Russ Allbery <eagle@eyrie.org> + * Copyright 2006, 2007, 2009, 2012, 2014 * The Board of Trustees of the Leland Stanford Junior University * * Permission is hereby granted, free of charge, to any person obtaining a @@ -75,10 +75,8 @@ message_log_buffer(int len UNUSED, const char *fmt, va_list args, void errors_capture(void) { - if (errors != NULL) { - free(errors); - errors = NULL; - } + free(errors); + errors = NULL; message_handlers_warn(1, message_log_buffer); message_handlers_notice(1, message_log_buffer); } diff --git a/tests/tap/messages.h b/tests/tap/messages.h index 0544f2d..985b9cd 100644 --- a/tests/tap/messages.h +++ b/tests/tap/messages.h @@ -4,7 +4,7 @@ * The canonical version of this file is maintained in the rra-c-util package, * which can be found at <http://www.eyrie.org/~eagle/software/rra-c-util/>. * - * Copyright 2002 Russ Allbery <rra@stanford.edu> + * Copyright 2002 Russ Allbery <eagle@eyrie.org> * Copyright 2006, 2007, 2009 * The Board of Trustees of the Leland Stanford Junior University * diff --git a/tests/tap/perl/Test/RRA.pm b/tests/tap/perl/Test/RRA.pm index 3035c7a..bb7de7d 100644 --- a/tests/tap/perl/Test/RRA.pm +++ b/tests/tap/perl/Test/RRA.pm @@ -9,8 +9,8 @@ # The canonical version of this file is maintained in the rra-c-util package, # which can be found at <http://www.eyrie.org/~eagle/software/rra-c-util/>. # -# Written by Russ Allbery <rra@stanford.edu> -# Copyright 2013 +# Written by Russ Allbery <eagle@eyrie.org> +# Copyright 2013, 2014 # The Board of Trustees of the Leland Stanford Junior University # # Permission is hereby granted, free of charge, to any person obtaining a @@ -51,29 +51,47 @@ our (@EXPORT_OK, @ISA, $VERSION); # consistency is good). BEGIN { @ISA = qw(Exporter); - @EXPORT_OK = qw(skip_unless_maintainer use_prereq); + @EXPORT_OK = qw(skip_unless_author skip_unless_automated use_prereq); # This version should match the corresponding rra-c-util release, but with # two digits for the minor version, including a leading zero if necessary, # so that it will sort properly. - $VERSION = '4.08'; + $VERSION = '5.05'; } -# Skip this test unless maintainer tests are requested. Takes a short -# description of what tests this script would perform, which is used in the -# skip message. Calls plan skip_all, which will terminate the program. +# Skip this test unless author tests are requested. Takes a short description +# of what tests this script would perform, which is used in the skip message. +# Calls plan skip_all, which will terminate the program. # # $description - Short description of the tests # # Returns: undef -sub skip_unless_maintainer { +sub skip_unless_author { my ($description) = @_; - if (!$ENV{RRA_MAINTAINER_TESTS}) { - plan skip_all => "$description only run for maintainer"; + if (!$ENV{AUTHOR_TESTING}) { + plan skip_all => "$description only run for author"; } return; } +# Skip this test unless doing automated testing or release testing. This is +# used for tests that should be run by CPAN smoke testing or during releases, +# but not for manual installs by end users. Takes a short description of what +# tests this script would perform, which is used in the skip message. Calls +# plan skip_all, which will terminate the program. +# +# $description - Short description of the tests +# +# Returns: undef +sub skip_unless_automated { + my ($description) = @_; + for my $env (qw(AUTOMATED_TESTING RELEASE_TESTING AUTHOR_TESTING)) { + return if $ENV{$env}; + } + plan skip_all => "$description normally skipped"; + return; +} + # Attempt to load a module and skip the test if the module could not be # loaded. If the module could be loaded, call its import function manually. # If the module could not be loaded, calls plan skip_all, which will terminate @@ -91,7 +109,7 @@ sub use_prereq { # If the first import looks like a version, pass it as a bare string. my $version = q{}; - if (@imports >= 1 && $imports[0] =~ m{ \A \d+ (?: [.]\d+ )* \z }xms) { + if (@imports >= 1 && $imports[0] =~ m{ \A \d+ (?: [.][\d_]+ )* \z }xms) { $version = shift(@imports); } @@ -118,7 +136,8 @@ sub use_prereq { # If the use failed for any reason, skip the test. if (!$result || $error) { - plan skip_all => "$module required for test"; + my $name = length($version) > 0 ? "$module $version" : $module; + plan skip_all => "$name required for test"; } # If the module set $SIG{__DIE__}, we cleared that via local. Restore it. @@ -142,13 +161,17 @@ Test::RRA - Support functions for Perl tests =head1 SYNOPSIS - use Test::RRA qw(skip_unless_maintainer use_prereq); + use Test::RRA + qw(skip_unless_author skip_unless_automated use_prereq); - # Skip this test unless maintainer tests are requested. - skip_unless_maintainer('Coding style tests'); + # Skip this test unless author tests are requested. + skip_unless_author('Coding style tests'); + + # Skip this test unless doing automated or release testing. + skip_unless_automated('POD syntax tests'); # Load modules, skipping the test if they're not available. - use_prereq('File::Slurp'); + use_prereq('Perl6::Slurp', 'slurp'); use_prereq('Test::Script::Run', '0.04'); =head1 DESCRIPTION @@ -165,12 +188,23 @@ script should be explicitly imported. =over 4 -=item skip_unless_maintainer(DESC) +=item skip_unless_author(DESC) -Checks whether RRA_MAINTAINER_TESTS is set in the environment and skips -the whole test (by calling C<plan skip_all> from Test::More) if it is not. +Checks whether AUTHOR_TESTING is set in the environment and skips the +whole test (by calling C<plan skip_all> from Test::More) if it is not. DESC is a description of the tests being skipped. A space and C<only run -for maintainer> will be appended to it and used as the skip reason. +for author> will be appended to it and used as the skip reason. + +=item skip_unless_automated(DESC) + +Checks whether AUTHOR_TESTING, AUTOMATED_TESTING, or RELEASE_TESTING are +set in the environment and skips the whole test (by calling C<plan +skip_all> from Test::More) if they are not. This should be used by tests +that should not run during end-user installs of the module, but which +should run as part of CPAN smoke testing and release testing. + +DESC is a description of the tests being skipped. A space and C<normally +skipped> will be appended to it and used as the skip reason. =item use_prereq(MODULE[, VERSION][, IMPORT ...]) @@ -187,11 +221,11 @@ value of an array. =head1 AUTHOR -Russ Allbery <rra@stanford.edu> +Russ Allbery <eagle@eyrie.org> =head1 COPYRIGHT AND LICENSE -Copyright 2013 The Board of Trustees of the Leland Stanford Junior +Copyright 2013, 2014 The Board of Trustees of the Leland Stanford Junior University Permission is hereby granted, free of charge, to any person obtaining a @@ -219,4 +253,8 @@ Test::More(3), Test::RRA::Automake(3), Test::RRA::Config(3) This module is maintained in the rra-c-util package. The current version is available from L<http://www.eyrie.org/~eagle/software/rra-c-util/>. +The functions to control when tests are run use environment variables +defined by the L<Lancaster +Consensus|https://github.com/Perl-Toolchain-Gang/toolchain-site/blob/master/lancaster-consensus.md>. + =cut diff --git a/tests/tap/perl/Test/RRA/Automake.pm b/tests/tap/perl/Test/RRA/Automake.pm index 5dde32d..a064ed9 100644 --- a/tests/tap/perl/Test/RRA/Automake.pm +++ b/tests/tap/perl/Test/RRA/Automake.pm @@ -13,7 +13,7 @@ # The canonical version of this file is maintained in the rra-c-util package, # which can be found at <http://www.eyrie.org/~eagle/software/rra-c-util/>. # -# Written by Russ Allbery <rra@stanford.edu> +# Written by Russ Allbery <eagle@eyrie.org> # Copyright 2013 # The Board of Trustees of the Leland Stanford Junior University # @@ -82,12 +82,12 @@ our (@EXPORT_OK, @ISA, $VERSION); # consistency is good). BEGIN { @ISA = qw(Exporter); - @EXPORT_OK = qw(automake_setup perl_dirs test_file_path); + @EXPORT_OK = qw(automake_setup perl_dirs test_file_path test_tmpdir); # This version should match the corresponding rra-c-util release, but with # two digits for the minor version, including a leading zero if necessary, # so that it will sort properly. - $VERSION = '4.08'; + $VERSION = '5.05'; } # Perl directories to skip globally for perl_dirs. We ignore the perl @@ -95,6 +95,11 @@ BEGIN { # distribution and has its own standalone test suite. my @GLOBAL_SKIP = qw(.git perl); +# The temporary directory created by test_tmpdir, if any. If this is set, +# attempt to remove the directory stored here on program exit (but ignore +# failure to do so). +my $TMPDIR; + # Perform initial test setup for running a Perl test in an Automake package. # This verifies that BUILD and SOURCE are set and then changes directory to # the SOURCE directory by default. Sets LD_LIBRARY_PATH if the $LIBRARY_PATH @@ -238,12 +243,53 @@ sub test_file_path { return; } +# Create a temporary directory for tests to use for transient files and return +# the path to that directory. The directory is automatically removed on +# program exit. The directory permissions use the current umask. Calls +# BAIL_OUT if the directory could not be created. +# +# Returns: Path to a writable temporary directory +sub test_tmpdir { + my $path; + + # If we already figured out what directory to use, reuse the same path. + # Otherwise, create a directory relative to BUILD if set. + if (defined($TMPDIR)) { + $path = $TMPDIR; + } else { + my $base = defined($ENV{BUILD}) ? $ENV{BUILD} : File::Spec->curdir; + $path = File::Spec->catdir($base, 'tmp'); + } + + # Create the directory if it doesn't exist. + if (!-d $path) { + if (!mkdir($path, 0777)) { + BAIL_OUT("cannot create directory $path: $!"); + } + } + + # Store the directory name for cleanup and return it. + $TMPDIR = $path; + return $path; +} + +# On program exit, remove $TMPDIR if set and if possible. Report errors with +# diag but otherwise ignore them. +END { + if (defined($TMPDIR) && -d $TMPDIR) { + local $! = undef; + if (!rmdir($TMPDIR)) { + diag("cannot remove temporary directory $TMPDIR: $!"); + } + } +} + 1; __END__ =for stopwords Allbery Automake Automake-aware Automake-based rra-c-util ARGS -subdirectories sublicense MERCHANTABILITY NONINFRINGEMENT +subdirectories sublicense MERCHANTABILITY NONINFRINGEMENT umask =head1 NAME @@ -320,11 +366,24 @@ checked for relative to the environment variable BUILD first, and then relative to SOURCE. test_file_path() returns the full path to FILE or calls BAIL_OUT if FILE could not be found. +=item test_tmpdir() + +Create a temporary directory for tests to use for transient files and +return the path to that directory. The directory is created relative to +the BUILD environment variable, which must be set. Permissions on the +directory are set using the current umask. test_tmpdir() returns the full +path to the temporary directory or calls BAIL_OUT if it could not be +created. + +The directory is automatically removed if possible on program exit. +Failure to remove the directory on exit is reported with diag() and +otherwise ignored. + =back =head1 AUTHOR -Russ Allbery <rra@stanford.edu> +Russ Allbery <eagle@eyrie.org> =head1 COPYRIGHT AND LICENSE diff --git a/tests/tap/perl/Test/RRA/Config.pm b/tests/tap/perl/Test/RRA/Config.pm index cfa3ad5..3e77650 100644 --- a/tests/tap/perl/Test/RRA/Config.pm +++ b/tests/tap/perl/Test/RRA/Config.pm @@ -30,13 +30,14 @@ BEGIN { @ISA = qw(Exporter); @EXPORT_OK = qw( $COVERAGE_LEVEL @COVERAGE_SKIP_TESTS @CRITIC_IGNORE $LIBRARY_PATH - $MINIMUM_VERSION %MINIMUM_VERSION @POD_COVERAGE_EXCLUDE + $MINIMUM_VERSION %MINIMUM_VERSION @POD_COVERAGE_EXCLUDE @STRICT_IGNORE + @STRICT_PREREQ ); # This version should match the corresponding rra-c-util release, but with # two digits for the minor version, including a leading zero if necessary, # so that it will sort properly. - $VERSION = '4.08'; + $VERSION = '5.05'; } # If BUILD or SOURCE are set in the environment, look for data/perl.conf under @@ -64,6 +65,8 @@ our $LIBRARY_PATH; our $MINIMUM_VERSION = '5.008'; our %MINIMUM_VERSION; our @POD_COVERAGE_EXCLUDE; +our @STRICT_IGNORE; +our @STRICT_PREREQ; # Load the configuration. if (!do($PATH)) { @@ -75,8 +78,8 @@ if (!do($PATH)) { __END__ =for stopwords -Allbery rra-c-util Automake perlcritic .libs namespace sublicense -MERCHANTABILITY NONINFRINGEMENT +Allbery rra-c-util Automake perlcritic .libs namespace subdirectory +sublicense MERCHANTABILITY NONINFRINGEMENT =head1 NAME @@ -130,7 +133,7 @@ directory names starting with F<tests/>. =item $LIBRARY_PATH -Add this directory (or a .libs subdirectory) relative to the top of the +Add this directory (or a F<.libs> subdirectory) relative to the top of the source tree to LD_LIBRARY_PATH when checking the syntax of Perl modules. This may be required to pick up libraries that are used by in-tree Perl modules so that Perl scripts can pass a syntax check. @@ -155,6 +158,20 @@ testing. Normally, all methods have to be documented in the POD for a Perl module, but methods matching any of these regexes will be considered private and won't require documentation. +=item @STRICT_IGNORE + +Additional directories to ignore when doing recursive Test::Strict testing +for C<use strict> and C<use warnings>. The contents of this directory +must be either top-level directory names or directory names starting with +F<tests/>. + +=item @STRICT_PREREQ + +A list of Perl modules that have to be available in order to do meaningful +Test::Strict testing. If any of the modules cannot be loaded via C<use>, +Test::Strict checking will be skipped. There is currently no way to +require specific versions of the modules. + =back No variables are exported by default, but the variables can be imported @@ -162,11 +179,11 @@ into the local namespace to avoid long variable names. =head1 AUTHOR -Russ Allbery <rra@stanford.edu> +Russ Allbery <eagle@eyrie.org> =head1 COPYRIGHT AND LICENSE -Copyright 2013 The Board of Trustees of the Leland Stanford Junior +Copyright 2013, 2014 The Board of Trustees of the Leland Stanford Junior University Permission is hereby granted, free of charge, to any person obtaining a @@ -189,7 +206,8 @@ DEALINGS IN THE SOFTWARE. =head1 SEE ALSO -Test::RRA(3), Test::RRA::Automake(3) +perlcritic(1), Test::MinimumVersion(3), Test::RRA(3), +Test::RRA::Automake(3), Test::Strict(3) This module is maintained in the rra-c-util package. The current version is available from L<http://www.eyrie.org/~eagle/software/rra-c-util/>. diff --git a/tests/tap/process.c b/tests/tap/process.c index 8ed4cfd..6461fb4 100644 --- a/tests/tap/process.c +++ b/tests/tap/process.c @@ -7,12 +7,15 @@ * runs a function in a subprocess and checks its output and exit status * against expected values. * + * Requires an Autoconf probe for sys/select.h and a replacement for a missing + * mkstemp. + * * The canonical version of this file is maintained in the rra-c-util package, * which can be found at <http://www.eyrie.org/~eagle/software/rra-c-util/>. * - * Written by Russ Allbery <rra@stanford.edu> - * Copyright 2002, 2004, 2005 Russ Allbery <rra@stanford.edu> - * Copyright 2009, 2010, 2011 + * Written by Russ Allbery <eagle@eyrie.org> + * Copyright 2002, 2004, 2005, 2013 Russ Allbery <eagle@eyrie.org> + * Copyright 2009, 2010, 2011, 2013, 2014 * The Board of Trustees of the Leland Stanford Junior University * * Permission is hereby granted, free of charge, to any person obtaining a @@ -37,12 +40,48 @@ #include <config.h> #include <portable/system.h> +#include <errno.h> +#include <fcntl.h> +#include <signal.h> +#ifdef HAVE_SYS_SELECT_H +# include <sys/select.h> +#endif +#include <sys/stat.h> +#include <sys/time.h> #include <sys/wait.h> #include <tests/tap/basic.h> #include <tests/tap/process.h> #include <tests/tap/string.h> +/* May be defined by the build system. */ +#ifndef PATH_FAKEROOT +# define PATH_FAKEROOT "" +#endif + +/* How long to wait for the process to start in seconds. */ +#define PROCESS_WAIT 10 + +/* + * Used to store information about a background process. This contains + * everything required to stop the process and clean up after it. + */ +struct process { + pid_t pid; /* PID of child process */ + char *pidfile; /* PID file to delete on process stop */ + char *tmpdir; /* Temporary directory for log file */ + char *logfile; /* Log file of process output */ + bool is_child; /* Whether we can waitpid for process */ + struct process *next; /* Next process in global list */ +}; + +/* + * Global list of started processes, which will be cleaned up automatically on + * program exit if they haven't been explicitly stopped with process_stop + * prior to that point. + */ +static struct process *processes = NULL; + /* * Given a function, an expected exit status, and expected output, runs that @@ -171,7 +210,312 @@ run_setup(const char *const argv[]) p = strchr(output, '\n'); if (p != NULL) *p = '\0'; - bail("%s", output); + if (output[0] != '\0') + bail("%s", output); + else + bail("setup command failed with no output"); } free(output); } + + +/* + * Free the resources associated with tracking a process, without doing + * anything to the process. This is kept separate so that we can free + * resources during shutdown in a non-primary process. + */ +static void +process_free(struct process *process) +{ + struct process **prev; + + /* Remove the process from the global list. */ + prev = &processes; + while (*prev != NULL && *prev != process) + prev = &(*prev)->next; + if (*prev == process) + *prev = process->next; + + /* Free resources. */ + free(process->pidfile); + free(process->logfile); + test_tmpdir_free(process->tmpdir); + free(process); +} + + +/* + * Kill a process and wait for it to exit. Returns the status of the process. + * Calls bail on a system failure or a failure of the process to exit. + * + * We are quite aggressive with error reporting here because child processes + * that don't exit or that don't exist often indicate some form of test + * failure. + */ +static int +process_kill(struct process *process) +{ + int result, i; + int status = -1; + struct timeval tv; + unsigned long pid = process->pid; + + /* If the process is not a child, just kill it and hope. */ + if (!process->is_child) { + if (kill(process->pid, SIGTERM) < 0 && errno != ESRCH) + sysbail("cannot send SIGTERM to process %lu", pid); + return 0; + } + + /* Check if the process has already exited. */ + result = waitpid(process->pid, &status, WNOHANG); + if (result < 0) + sysbail("cannot wait for child process %lu", pid); + else if (result > 0) + return status; + + /* + * Kill the process and wait for it to exit. I don't want to go to the + * work of setting up a SIGCHLD handler or a full event loop here, so we + * effectively poll every tenth of a second for process exit (and + * hopefully faster when it does since the SIGCHLD may interrupt our + * select, although we're racing with it. + */ + if (kill(process->pid, SIGTERM) < 0 && errno != ESRCH) + sysbail("cannot send SIGTERM to child process %lu", pid); + for (i = 0; i < PROCESS_WAIT * 10; i++) { + tv.tv_sec = 0; + tv.tv_usec = 100000; + select(0, NULL, NULL, NULL, &tv); + result = waitpid(process->pid, &status, WNOHANG); + if (result < 0) + sysbail("cannot wait for child process %lu", pid); + else if (result > 0) + return status; + } + + /* The process still hasn't exited. Bail. */ + bail("child process %lu did not exit on SIGTERM", pid); + + /* Not reached, but some compilers may get confused. */ + return status; +} + + +/* + * Stop a particular process given its process struct. This kills the + * process, waits for it to exit if possible (giving it at most five seconds), + * and then removes it from the global processes struct so that it isn't + * stopped again during global shutdown. + */ +void +process_stop(struct process *process) +{ + int status; + unsigned long pid = process->pid; + + /* Stop the process. */ + status = process_kill(process); + + /* Call diag to flush logs as well as provide exit status. */ + if (process->is_child) + diag("stopped process %lu (exit status %d)", pid, status); + else + diag("stopped process %lu", pid); + + /* Remove the log and PID file. */ + diag_file_remove(process->logfile); + unlink(process->pidfile); + unlink(process->logfile); + + /* Free resources. */ + process_free(process); +} + + +/* + * Stop all running processes. This is called as a cleanup handler during + * process shutdown. The first argument, which says whether the test was + * successful, is ignored, since the same actions should be performed + * regardless. The second argument says whether this is the primary process, + * in which case we do the full shutdown. Otherwise, we only free resources + * but don't stop the process. + */ +static void +process_stop_all(int success UNUSED, int primary) +{ + while (processes != NULL) { + if (primary) + process_stop(processes); + else + process_free(processes); + } +} + + +/* + * Read the PID of a process from a file. This is necessary when running + * under fakeroot to get the actual PID of the remctld process. + */ +static long +read_pidfile(const char *path) +{ + FILE *file; + char buffer[BUFSIZ]; + long pid; + + file = fopen(path, "r"); + if (file == NULL) + sysbail("cannot open %s", path); + if (fgets(buffer, sizeof(buffer), file) == NULL) + sysbail("cannot read from %s", path); + fclose(file); + pid = strtol(buffer, NULL, 10); + if (pid <= 0) + bail("cannot read PID from %s", path); + return pid; +} + + +/* + * Start a process and return its status information. The status information + * is also stored in the global processes linked list so that it can be + * stopped automatically on program exit. + * + * The boolean argument says whether to start the process under fakeroot. If + * true, PATH_FAKEROOT must be defined, generally by Autoconf. If it's not + * found, call skip_all. + * + * This is a helper function for process_start and process_start_fakeroot. + */ +static struct process * +process_start_internal(const char *const argv[], const char *pidfile, + bool fakeroot) +{ + size_t i; + int log_fd; + const char *name; + struct timeval tv; + struct process *process; + const char **fakeroot_argv = NULL; + const char *path_fakeroot = PATH_FAKEROOT; + + /* Check prerequisites. */ + if (fakeroot && path_fakeroot[0] == '\0') + skip_all("fakeroot not found"); + + /* Create the process struct and log file. */ + process = bcalloc(1, sizeof(struct process)); + process->pidfile = bstrdup(pidfile); + process->tmpdir = test_tmpdir(); + name = strrchr(argv[0], '/'); + if (name != NULL) + name++; + else + name = argv[0]; + basprintf(&process->logfile, "%s/%s.log.XXXXXX", process->tmpdir, name); + log_fd = mkstemp(process->logfile); + if (log_fd < 0) + sysbail("cannot create log file for %s", argv[0]); + + /* If using fakeroot, rewrite argv accordingly. */ + if (fakeroot) { + for (i = 0; argv[i] != NULL; i++) + ; + fakeroot_argv = bcalloc(2 + i + 1, sizeof(const char *)); + fakeroot_argv[0] = path_fakeroot; + fakeroot_argv[1] = "--"; + for (i = 0; argv[i] != NULL; i++) + fakeroot_argv[i + 2] = argv[i]; + fakeroot_argv[i + 2] = NULL; + argv = fakeroot_argv; + } + + /* + * Fork off the child process, redirect its standard output and standard + * error to the log file, and then exec the program. + */ + process->pid = fork(); + if (process->pid < 0) + sysbail("fork failed"); + else if (process->pid == 0) { + if (dup2(log_fd, STDOUT_FILENO) < 0) + sysbail("cannot redirect standard output"); + if (dup2(log_fd, STDERR_FILENO) < 0) + sysbail("cannot redirect standard error"); + close(log_fd); + if (execv(argv[0], (char *const *) argv) < 0) + sysbail("exec of %s failed", argv[0]); + } + close(log_fd); + free(fakeroot_argv); + + /* + * In the parent. Wait for the child to start by watching for the PID + * file to appear in 100ms intervals. + */ + for (i = 0; i < PROCESS_WAIT * 10 && access(pidfile, F_OK) != 0; i++) { + tv.tv_sec = 0; + tv.tv_usec = 100000; + select(0, NULL, NULL, NULL, &tv); + } + + /* + * If the PID file still hasn't appeared after ten seconds, attempt to + * kill the process and then bail. + */ + if (access(pidfile, F_OK) != 0) { + kill(process->pid, SIGTERM); + alarm(5); + waitpid(process->pid, NULL, 0); + alarm(0); + bail("cannot start %s", argv[0]); + } + + /* + * Read the PID back from the PID file. This usually isn't necessary for + * non-forking daemons, but always doing this makes this function general, + * and it's required when running under fakeroot. + */ + if (fakeroot) + process->pid = read_pidfile(pidfile); + process->is_child = !fakeroot; + + /* Register the log file as a source of diag messages. */ + diag_file_add(process->logfile); + + /* + * Add the process to our global list and set our cleanup handler if this + * is the first process we started. + */ + if (processes == NULL) + test_cleanup_register(process_stop_all); + process->next = processes; + processes = process; + + /* All done. */ + return process; +} + + +/* + * Start a process and return the opaque process struct. The process must + * create pidfile with its PID when startup is complete. + */ +struct process * +process_start(const char *const argv[], const char *pidfile) +{ + return process_start_internal(argv, pidfile, false); +} + + +/* + * Start a process under fakeroot and return the opaque process struct. If + * fakeroot is not available, calls skip_all. The process must create pidfile + * with its PID when startup is complete. + */ +struct process * +process_start_fakeroot(const char *const argv[], const char *pidfile) +{ + return process_start_internal(argv, pidfile, true); +} diff --git a/tests/tap/process.h b/tests/tap/process.h index df74b5f..8137d5d 100644 --- a/tests/tap/process.h +++ b/tests/tap/process.h @@ -4,8 +4,8 @@ * The canonical version of this file is maintained in the rra-c-util package, * which can be found at <http://www.eyrie.org/~eagle/software/rra-c-util/>. * - * Written by Russ Allbery <rra@stanford.edu> - * Copyright 2009, 2010 + * Written by Russ Allbery <eagle@eyrie.org> + * Copyright 2009, 2010, 2013 * The Board of Trustees of the Leland Stanford Junior University * * Permission is hereby granted, free of charge, to any person obtaining a @@ -33,6 +33,9 @@ #include <config.h> #include <tests/tap/macros.h> +/* Opaque data type for process_start and friends. */ +struct process; + BEGIN_DECLS /* @@ -60,6 +63,32 @@ void is_function_output(test_function_type, void *data, int status, void run_setup(const char *const argv[]) __attribute__((__nonnull__)); +/* + * process_start starts a process in the background, returning an opaque data + * struct that can be used to stop the process later. The standard output and + * standard error of the process will be sent to a log file registered with + * diag_file_add, so its output will be properly interleaved with the test + * case output. + * + * The process should create a PID file in the path given as the second + * argument when it's finished initialization. + * + * process_start_fakeroot is the same but starts the process under fakeroot. + * PATH_FAKEROOT must be defined (generally by Autoconf). If fakeroot is not + * found, process_start_fakeroot will call skip_all, so be sure to call this + * function before plan. + * + * process_stop can be called to explicitly stop the process. If it isn't + * called by the test program, it will be called automatically when the + * program exits. + */ +struct process *process_start(const char *const argv[], const char *pidfile) + __attribute__((__nonnull__)); +struct process *process_start_fakeroot(const char *const argv[], + const char *pidfile) + __attribute__((__nonnull__)); +void process_stop(struct process *); + END_DECLS #endif /* TAP_PROCESS_H */ diff --git a/tests/tap/remctl.sh b/tests/tap/remctl.sh index 2fd6681..0a511a0 100644 --- a/tests/tap/remctl.sh +++ b/tests/tap/remctl.sh @@ -8,7 +8,7 @@ # The canonical version of this file is maintained in the rra-c-util package, # which can be found at <http://www.eyrie.org/~eagle/software/rra-c-util/>. # -# Written by Russ Allbery <rra@stanford.edu> +# Written by Russ Allbery <eagle@eyrie.org> # Copyright 2009, 2012 # The Board of Trustees of the Leland Stanford Junior University # diff --git a/tests/tap/string.c b/tests/tap/string.c index f5c965c..6ed7e68 100644 --- a/tests/tap/string.c +++ b/tests/tap/string.c @@ -7,7 +7,7 @@ * The canonical version of this file is maintained in the rra-c-util package, * which can be found at <http://www.eyrie.org/~eagle/software/rra-c-util/>. * - * Copyright 2011, 2012 Russ Allbery <rra@stanford.edu> + * Copyright 2011, 2012 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"), diff --git a/tests/tap/string.h b/tests/tap/string.h index 2f699e4..cc51945 100644 --- a/tests/tap/string.h +++ b/tests/tap/string.h @@ -7,7 +7,7 @@ * The canonical version of this file is maintained in the rra-c-util package, * which can be found at <http://www.eyrie.org/~eagle/software/rra-c-util/>. * - * Copyright 2011, 2012 Russ Allbery <rra@stanford.edu> + * Copyright 2011, 2012 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"), |