summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rwxr-xr-xtests/docs/pod-spelling-t11
-rwxr-xr-xtests/docs/pod-t10
-rw-r--r--tests/runtests.c700
-rw-r--r--tests/tap/basic.c586
-rw-r--r--tests/tap/basic.h79
-rw-r--r--tests/tap/kerberos.c105
-rw-r--r--tests/tap/kerberos.h26
-rw-r--r--tests/tap/libtap.sh4
-rw-r--r--tests/tap/macros.h9
-rw-r--r--tests/tap/messages.c8
-rw-r--r--tests/tap/perl/Test/RRA.pm71
-rw-r--r--tests/tap/perl/Test/RRA/Automake.pm2
-rw-r--r--tests/tap/perl/Test/RRA/Config.pm13
-rw-r--r--tests/tap/process.c350
-rw-r--r--tests/tap/process.h31
-rw-r--r--tests/util/messages-krb5-t.c24
-rwxr-xr-xtests/util/xmalloc-t121
-rw-r--r--tests/util/xmalloc.c43
18 files changed, 1667 insertions, 526 deletions
diff --git a/tests/docs/pod-spelling-t b/tests/docs/pod-spelling-t
index 1a02af8..7b61c86 100755
--- a/tests/docs/pod-spelling-t
+++ b/tests/docs/pod-spelling-t
@@ -1,14 +1,12 @@
#!/usr/bin/perl
#
# Checks all POD files in the tree for spelling errors using Test::Spelling.
-# This test is disabled unless RRA_MAINTAINER_TESTS is set, since spelling
-# dictionaries vary too much between environments.
#
# 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 <eagle@eyrie.org>
-# Copyright 2012, 2013
+# Copyright 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,11 +34,12 @@ use warnings;
use lib "$ENV{SOURCE}/tap/perl";
use Test::More;
-use Test::RRA qw(skip_unless_maintainer use_prereq);
+use Test::RRA qw(skip_unless_author use_prereq);
use Test::RRA::Automake qw(automake_setup perl_dirs);
-# Only run this test for the maintainer.
-skip_unless_maintainer('Spelling tests');
+# Only run this test for the module author since the required stopwords are
+# too sensitive to the exact spell-checking program and dictionary.
+skip_unless_author('Spelling tests');
# Load prerequisite modules.
use_prereq('Test::Spelling');
diff --git a/tests/docs/pod-t b/tests/docs/pod-t
index 6918271..53f9925 100755
--- a/tests/docs/pod-t
+++ b/tests/docs/pod-t
@@ -1,4 +1,4 @@
-#!/usr/bin/perl -w
+#!/usr/bin/perl
#
# Check all POD documents in the tree, except for any embedded Perl module
# distribution, for POD formatting errors.
@@ -7,7 +7,7 @@
# which can be found at <http://www.eyrie.org/~eagle/software/rra-c-util/>.
#
# Written by Russ Allbery <eagle@eyrie.org>
-# Copyright 2012, 2013
+# Copyright 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
@@ -35,9 +35,13 @@ use warnings;
use lib "$ENV{SOURCE}/tap/perl";
use Test::More;
-use Test::RRA qw(use_prereq);
+use Test::RRA qw(skip_unless_automated use_prereq);
use Test::RRA::Automake qw(automake_setup perl_dirs);
+# Skip this test for normal user installs, since we normally pre-generate all
+# of the documentation and the end user doesn't care.
+skip_unless_automated('POD syntax tests');
+
# Load prerequisite modules.
use_prereq('Test::Pod');
diff --git a/tests/runtests.c b/tests/runtests.c
index 3756aa6..a9d2373 100644
--- a/tests/runtests.c
+++ b/tests/runtests.c
@@ -54,8 +54,8 @@
* 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
- * Russ Allbery <eagle@eyrie.org>
+ * Copyright 2000, 2001, 2004, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013,
+ * 2014 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"),
@@ -86,7 +86,9 @@
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
+#include <limits.h>
#include <stdarg.h>
+#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -101,12 +103,29 @@
/* sys/time.h must be included before sys/resource.h on some platforms. */
#include <sys/resource.h>
-/* AIX doesn't have WCOREDUMP. */
+/* AIX 6.1 (and possibly later) doesn't have WCOREDUMP. */
#ifndef WCOREDUMP
-# define WCOREDUMP(status) ((unsigned)(status) & 0x80)
+# define WCOREDUMP(status) ((unsigned)(status) & 0x80)
#endif
/*
+ * POSIX requires that these be defined in <unistd.h>, but they're not always
+ * available. If one of them has been defined, all the rest almost certainly
+ * have.
+ */
+#ifndef STDIN_FILENO
+# define STDIN_FILENO 0
+# define STDOUT_FILENO 1
+# define STDERR_FILENO 2
+#endif
+
+/*
+ * Used for iterating through arrays. Returns the number of elements in the
+ * array (useful for a < upper bound in a for loop).
+ */
+#define ARRAY_SIZE(array) (sizeof(array) / sizeof((array)[0]))
+
+/*
* 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
@@ -138,7 +157,8 @@ enum plan_status {
/* Error exit statuses for test processes. */
#define CHILDERR_DUP 100 /* Couldn't redirect stderr or stdout. */
#define CHILDERR_EXEC 101 /* Couldn't exec child process. */
-#define CHILDERR_STDERR 102 /* Couldn't open stderr file. */
+#define CHILDERR_STDIN 102 /* Couldn't open stdin file. */
+#define CHILDERR_STDERR 103 /* Couldn't open stderr file. */
/* Structure to hold data for a set of tests. */
struct testset {
@@ -153,7 +173,7 @@ 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. */
- unsigned int aborted; /* Whether the set as aborted. */
+ unsigned int aborted; /* Whether the set was aborted. */
int reported; /* Whether the results were reported. */
int status; /* The exit status of the test. */
unsigned int all_skipped; /* Whether all tests were skipped. */
@@ -167,21 +187,25 @@ struct testlist {
};
/*
- * Usage message. Should be used as a printf format with two arguments: the
- * path to runtests, given twice.
+ * Usage message. Should be used as a printf format with four arguments: the
+ * path to runtests, given three times, and the usage_description. This is
+ * split into variables to satisfy the pedantic ISO C90 limit on strings.
*/
static const char usage_message[] = "\
-Usage: %s [-b <build-dir>] [-s <source-dir>] <test-list>\n\
+Usage: %s [-b <build-dir>] [-s <source-dir>] <test> ...\n\
+ %s [-b <build-dir>] [-s <source-dir>] -l <test-list>\n\
%s -o [-b <build-dir>] [-s <source-dir>] <test>\n\
-\n\
+\n%s";
+static const char usage_extra[] = "\
Options:\n\
-b <build-dir> Set the build directory to <build-dir>\n\
+ -l <list> Take the list of tests to run from <test-list>\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";
+runtests normally runs each test listed on the command line. With the -l\n\
+option, it instead runs every test listed in a file. With the -o option,\n\
+it instead runs a single test and shows its complete output.\n";
/*
* Header used for test output. %s is replaced by the file name of the list
@@ -197,9 +221,57 @@ Failed Set Fail/Total (%) Skip Stat Failing Tests\n\
-------------------------- -------------- ---- ---- ------------------------";
/* Include the file name and line number in malloc failures. */
-#define xmalloc(size) x_malloc((size), __FILE__, __LINE__)
-#define xrealloc(p, size) x_realloc((p), (size), __FILE__, __LINE__)
-#define xstrdup(p) x_strdup((p), __FILE__, __LINE__)
+#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 xreallocarray(p, n, size) \
+ x_reallocarray((p), (n), (size), __FILE__, __LINE__)
+
+/*
+ * __attribute__ is available in gcc 2.5 and later, but only with gcc 2.7
+ * could you use the __format__ form of the attributes, which is what we use
+ * (to avoid confusion with other macros).
+ */
+#ifndef __attribute__
+# if __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 7)
+# define __attribute__(spec) /* empty */
+# endif
+#endif
+
+/*
+ * We use __alloc_size__, but it was only available in fairly recent versions
+ * of GCC. Suppress warnings about the unknown attribute if GCC is too old.
+ * We know that we're GCC at this point, so we can use the GCC variadic macro
+ * extension, which will still work with versions of GCC too old to have C99
+ * variadic macro support.
+ */
+#if !defined(__attribute__) && !defined(__alloc_size__)
+# if __GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 3)
+# define __alloc_size__(spec, args...) /* 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
+ * attributes on declarations. This unfortunately will affect the entire
+ * compilation context, but there's no push and pop available.
+ */
+#if !defined(__attribute__) && (defined(__llvm__) || defined(__clang__))
+# pragma GCC diagnostic ignored "-Wattributes"
+#endif
+
+/* Declare internal functions that benefit from compiler attributes. */
+static void sysdie(const char *, ...)
+ __attribute__((__nonnull__, __noreturn__, __format__(printf, 1, 2)));
+static void *x_calloc(size_t, size_t, const char *, int)
+ __attribute__((__alloc_size__(1, 2), __malloc__, __nonnull__));
+static void *x_malloc(size_t, const char *, int)
+ __attribute__((__alloc_size__(1), __malloc__, __nonnull__));
+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__));
/*
@@ -223,6 +295,24 @@ sysdie(const char *format, ...)
/*
+ * Allocate zeroed memory, reporting a fatal error and exiting on failure.
+ */
+static void *
+x_calloc(size_t n, size_t size, const char *file, int line)
+{
+ void *p;
+
+ n = (n > 0) ? n : 1;
+ size = (size > 0) ? size : 1;
+ p = calloc(n, size);
+ if (p == NULL)
+ sysdie("failed to calloc %lu bytes at %s line %d",
+ (unsigned long) size, file, line);
+ return p;
+}
+
+
+/*
* Allocate memory, reporting a fatal error and exiting on failure.
*/
static void *
@@ -240,14 +330,26 @@ x_malloc(size_t size, const char *file, int line)
/*
* Reallocate memory, reporting a fatal error and exiting on failure.
+ *
+ * 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. And we should not be allocating
+ * anything anywhere near that large.
+ *
+ * (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.)
*/
static void *
-x_realloc(void *p, size_t size, const char *file, int line)
+x_reallocarray(void *p, size_t n, size_t size, const char *file, int line)
{
- p = realloc(p, size);
+ if (n > 0 && UINT_MAX / n <= size)
+ sysdie("realloc too large at %s line %d", file, line);
+ p = realloc(p, n * size);
if (p == NULL)
sysdie("failed to realloc %lu bytes at %s line %d",
- (unsigned long) size, file, line);
+ (unsigned long) (n * size), file, line);
return p;
}
@@ -272,6 +374,55 @@ x_strdup(const char *s, const char *file, int line)
/*
+ * 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. We
+ * aren't guaranteed to have SIZE_MAX, so use UINT_MAX as an acceptable
+ * substitute (see the x_nrealloc comments).
+ */
+ va_start(args, first);
+ for (string = first; string != NULL; string = va_arg(args, const char *)) {
+ if (length >= UINT_MAX - strlen(string)) {
+ errno = EINVAL;
+ sysdie("strings too long in concat");
+ }
+ length += strlen(string);
+ }
+ va_end(args);
+ length++;
+
+ /* Create the string. */
+ result = xmalloc(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;
+}
+
+
+/*
* Given a struct timeval, return the number of seconds it represents as a
* double. Use difftime() to convert a time_t to a double.
*/
@@ -323,36 +474,62 @@ skip_whitespace(const char *p)
static pid_t
test_start(const char *path, int *fd)
{
- int fds[2], errfd;
+ int fds[2], infd, errfd;
pid_t child;
+ /* Create a pipe used to capture the output from the test program. */
if (pipe(fds) == -1) {
puts("ABORTED");
fflush(stdout);
sysdie("can't create pipe");
}
+
+ /* Fork a child process, massage the file descriptors, and exec. */
child = fork();
- if (child == (pid_t) -1) {
+ switch (child) {
+ case -1:
puts("ABORTED");
fflush(stdout);
sysdie("can't fork");
- } else if (child == 0) {
- /* In child. Set up our stdout and stderr. */
+
+ /* In the child. Set up our standard output. */
+ case 0:
+ close(fds[0]);
+ close(STDOUT_FILENO);
+ if (dup2(fds[1], STDOUT_FILENO) < 0)
+ _exit(CHILDERR_DUP);
+ close(fds[1]);
+
+ /* Point standard input at /dev/null. */
+ close(STDIN_FILENO);
+ infd = open("/dev/null", O_RDONLY);
+ if (infd < 0)
+ _exit(CHILDERR_STDIN);
+ if (infd != STDIN_FILENO) {
+ if (dup2(infd, STDIN_FILENO) < 0)
+ _exit(CHILDERR_DUP);
+ close(infd);
+ }
+
+ /* Point standard error at /dev/null. */
+ close(STDERR_FILENO);
errfd = open("/dev/null", O_WRONLY);
if (errfd < 0)
_exit(CHILDERR_STDERR);
- if (dup2(errfd, 2) == -1)
- _exit(CHILDERR_DUP);
- close(fds[0]);
- if (dup2(fds[1], 1) == -1)
- _exit(CHILDERR_DUP);
+ if (errfd != STDERR_FILENO) {
+ if (dup2(errfd, STDERR_FILENO) < 0)
+ _exit(CHILDERR_DUP);
+ close(errfd);
+ }
/* Now, exec our process. */
if (execl(path, path, (char *) 0) == -1)
_exit(CHILDERR_EXEC);
- } else {
- /* In parent. Close the extra file descriptor. */
+
+ /* In parent. Close the extra file descriptor. */
+ default:
close(fds[1]);
+ break;
}
*fd = fds[0];
return child;
@@ -380,6 +557,40 @@ test_backspace(struct testset *ts)
/*
+ * Allocate or resize the array of test results to be large enough to contain
+ * the test number in.
+ */
+static void
+resize_results(struct testset *ts, unsigned long n)
+{
+ unsigned long i;
+ size_t s;
+
+ /* If there's already enough space, return quickly. */
+ if (n <= ts->allocated)
+ return;
+
+ /*
+ * If no space has been allocated, do the initial allocation. Otherwise,
+ * resize. Start with 32 test cases and then add 1024 with each resize to
+ * try to reduce the number of reallocations.
+ */
+ if (ts->allocated == 0) {
+ s = (n > 32) ? n : 32;
+ ts->results = xcalloc(s, sizeof(enum test_status));
+ } else {
+ s = (n > ts->allocated + 1024) ? n : ts->allocated + 1024;
+ ts->results = xreallocarray(ts->results, s, sizeof(enum test_status));
+ }
+
+ /* Set the results for the newly-allocated test array. */
+ for (i = ts->allocated; i < s; i++)
+ ts->results[i] = TEST_INVALID;
+ ts->allocated = s;
+}
+
+
+/*
* Read the plan line of test output, which should contain the range of test
* numbers. We may initialize the testset structure here if we haven't yet
* seen a test. Return true if initialization succeeded and the test should
@@ -388,7 +599,6 @@ test_backspace(struct testset *ts)
static int
test_plan(const char *line, struct testset *ts)
{
- unsigned long i;
long n;
/*
@@ -401,12 +611,14 @@ test_plan(const char *line, struct testset *ts)
line += 3;
/*
- * 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
+ * Get the count and check it for validity.
+ *
+ * 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. strtol is called
- * with a second argument to advance the line pointer past the count to
- * make it simpler to detect the # skip case.
+ * 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) {
@@ -435,29 +647,30 @@ test_plan(const char *line, struct testset *ts)
ts->reported = 1;
return 0;
}
- if (ts->plan == PLAN_INIT && ts->allocated == 0) {
- ts->count = n;
- ts->allocated = n;
+
+ /*
+ * If we are doing lazy planning, check the plan against the largest test
+ * number that we saw and fail now if we saw a check outside the plan
+ * range.
+ */
+ if (ts->plan == PLAN_PENDING && (unsigned long) n < ts->count) {
+ test_backspace(ts);
+ printf("ABORTED (invalid test number %lu)\n", ts->count);
+ ts->aborted = 1;
+ ts->reported = 1;
+ return 0;
+ }
+
+ /*
+ * Otherwise, allocated or resize the results if needed and update count,
+ * and then record that we've seen a plan.
+ */
+ resize_results(ts, n);
+ ts->count = n;
+ if (ts->plan == PLAN_INIT)
ts->plan = PLAN_FIRST;
- ts->results = xmalloc(ts->count * sizeof(enum test_status));
- for (i = 0; i < ts->count; i++)
- ts->results[i] = TEST_INVALID;
- } else if (ts->plan == PLAN_PENDING) {
- if ((unsigned long) n < ts->count) {
- printf("ABORTED (invalid test number %lu)\n", ts->count);
- ts->aborted = 1;
- ts->reported = 1;
- return 0;
- }
- ts->count = n;
- if ((unsigned long) n > ts->allocated) {
- ts->results = xrealloc(ts->results, n * sizeof(enum test_status));
- for (i = ts->allocated; i < ts->count; i++)
- ts->results[i] = TEST_INVALID;
- ts->allocated = n;
- }
+ else if (ts->plan == PLAN_PENDING)
ts->plan = PLAN_FINAL;
- }
return 1;
}
@@ -475,7 +688,7 @@ test_checkline(const char *line, struct testset *ts)
const char *bail;
char *end;
long number;
- unsigned long i, current;
+ unsigned long current;
int outlen;
/* Before anything, check for a test abort. */
@@ -516,6 +729,7 @@ test_checkline(const char *line, struct testset *ts)
if (!test_plan(line, ts))
return;
} else {
+ test_backspace(ts);
puts("ABORTED (multiple plans)");
ts->aborted = 1;
ts->reported = 1;
@@ -547,19 +761,9 @@ test_checkline(const char *line, struct testset *ts)
/* We have a valid test result. Tweak the results array if needed. */
if (ts->plan == PLAN_INIT || ts->plan == PLAN_PENDING) {
ts->plan = PLAN_PENDING;
+ resize_results(ts, current);
if (current > ts->count)
ts->count = current;
- if (current > ts->allocated) {
- unsigned long n;
-
- n = (ts->allocated == 0) ? 32 : ts->allocated * 2;
- if (n < current)
- n = current;
- ts->results = xrealloc(ts->results, n * sizeof(enum test_status));
- for (i = ts->allocated; i < n; i++)
- ts->results[i] = TEST_INVALID;
- ts->allocated = n;
- }
}
/*
@@ -595,9 +799,12 @@ test_checkline(const char *line, struct testset *ts)
}
ts->current = current;
ts->results[current - 1] = status;
- test_backspace(ts);
if (isatty(STDOUT_FILENO)) {
- outlen = printf("%lu/%lu", current, ts->count);
+ test_backspace(ts);
+ if (ts->plan == PLAN_PENDING)
+ outlen = printf("%lu/?", current);
+ else
+ outlen = printf("%lu/%lu", current, ts->count);
ts->length = (outlen >= 0) ? outlen : 0;
fflush(stdout);
}
@@ -754,6 +961,7 @@ test_analyze(struct testset *ts)
if (!ts->reported)
puts("ABORTED (execution failed -- not found?)");
break;
+ case CHILDERR_STDIN:
case CHILDERR_STDERR:
if (!ts->reported)
puts("ABORTED (can't open /dev/null)");
@@ -883,109 +1091,203 @@ test_fail_summary(const struct testlist *fails)
if (first != 0)
test_print_range(first, last, chars, 19);
putchar('\n');
- free(ts->file);
- free(ts->path);
- free(ts->results);
- if (ts->reason != NULL)
- free(ts->reason);
- free(ts);
}
}
/*
+ * Check whether a given file path is a valid test. Currently, this checks
+ * whether it is executable and is a regular file. Returns true or false.
+ */
+static int
+is_valid_test(const char *path)
+{
+ struct stat st;
+
+ if (access(path, X_OK) < 0)
+ return 0;
+ if (stat(path, &st) < 0)
+ return 0;
+ if (!S_ISREG(st.st_mode))
+ return 0;
+ return 1;
+}
+
+
+/*
* Given the name of a test, a pointer to the testset struct, and the source
* and build directories, find the test. We try first relative to the current
* directory, then in the build directory (if not NULL), then in the source
* directory. In each of those directories, we first try a "-t" extension and
- * then a ".t" extension. When we find an executable program, we fill in the
- * path member of the testset struct. If none of those paths are executable,
- * just fill in the name of the test with "-t" appended.
+ * then a ".t" extension. When we find an executable program, we return the
+ * path to that program. If none of those paths are executable, just fill in
+ * the name of the test as is.
*
* The caller is responsible for freeing the path member of the testset
* struct.
*/
-static void
-find_test(const char *name, struct testset *ts, const char *source,
- const char *build)
+static char *
+find_test(const char *name, const char *source, const char *build)
{
char *path;
- const char *bases[4];
- unsigned int i;
+ const char *bases[3], *suffix, *base;
+ unsigned int i, j;
+ const char *suffixes[3] = { "-t", ".t", "" };
+ /* Possible base directories. */
bases[0] = ".";
bases[1] = build;
bases[2] = source;
- bases[3] = NULL;
- for (i = 0; i < 3; i++) {
- if (bases[i] == NULL)
- continue;
- path = xmalloc(strlen(bases[i]) + strlen(name) + 4);
- sprintf(path, "%s/%s-t", bases[i], name);
- if (access(path, X_OK) != 0)
- path[strlen(path) - 2] = '.';
- if (access(path, X_OK) == 0)
- break;
- free(path);
- path = NULL;
+ /* Try each suffix with each base. */
+ for (i = 0; i < ARRAY_SIZE(suffixes); i++) {
+ suffix = suffixes[i];
+ for (j = 0; j < ARRAY_SIZE(bases); j++) {
+ base = bases[j];
+ if (base == NULL)
+ continue;
+ path = concat(base, "/", name, suffix, (const char *) 0);
+ if (is_valid_test(path))
+ return path;
+ free(path);
+ path = NULL;
+ }
}
- if (path == NULL) {
- path = xmalloc(strlen(name) + 3);
- sprintf(path, "%s-t", name);
+ if (path == NULL)
+ path = xstrdup(name);
+ return path;
+}
+
+
+/*
+ * Read a list of tests from a file, returning the list of tests as a struct
+ * testlist. 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)
+{
+ FILE *file;
+ unsigned int line;
+ size_t length;
+ char buffer[BUFSIZ];
+ struct testlist *listhead, *current;
+
+ /* Create the initial container list that will hold our results. */
+ listhead = xcalloc(1, sizeof(struct testlist));
+ current = NULL;
+
+ /*
+ * Open our file of tests to run and read it line by line, creating a new
+ * struct testlist and struct testset for each line.
+ */
+ file = fopen(filename, "r");
+ if (file == NULL)
+ sysdie("can't open %s", filename);
+ line = 0;
+ while (fgets(buffer, sizeof(buffer), file)) {
+ line++;
+ length = strlen(buffer) - 1;
+ if (buffer[length] != '\n') {
+ fprintf(stderr, "%s:%u: line too long\n", filename, line);
+ exit(1);
+ }
+ buffer[length] = '\0';
+ if (current == NULL)
+ current = listhead;
+ else {
+ current->next = xcalloc(1, sizeof(struct testlist));
+ current = current->next;
+ }
+ current->ts = xcalloc(1, sizeof(struct testset));
+ current->ts->plan = PLAN_INIT;
+ current->ts->file = xstrdup(buffer);
}
- ts->path = path;
+ fclose(file);
+
+ /* Return the results. */
+ return listhead;
}
/*
- * Run a batch of tests from a given file listing each test on a line by
- * itself. Takes two additional parameters: the root of the source directory
- * and the root of the build directory. Test programs will be first searched
- * for in the current directory, then the build directory, then the source
- * directory. The file must be rewindable. Returns true iff all tests
- * passed.
+ * Build a list of tests from command line arguments. Takes the argv and argc
+ * representing the command line arguments and returns a newly allocated test
+ * list. The caller is responsible for freeing.
+ */
+static struct testlist *
+build_test_list(char *argv[], int argc)
+{
+ int i;
+ struct testlist *listhead, *current;
+
+ /* Create the initial container list that will hold our results. */
+ listhead = xcalloc(1, sizeof(struct testlist));
+ current = NULL;
+
+ /* Walk the list of arguments and create test sets for them. */
+ for (i = 0; i < argc; i++) {
+ if (current == NULL)
+ current = listhead;
+ else {
+ current->next = xcalloc(1, sizeof(struct testlist));
+ current = current->next;
+ }
+ current->ts = xcalloc(1, sizeof(struct testset));
+ current->ts->plan = PLAN_INIT;
+ current->ts->file = xstrdup(argv[i]);
+ }
+
+ /* Return the results. */
+ return listhead;
+}
+
+
+/* Free a struct testset. */
+static void
+free_testset(struct testset *ts)
+{
+ free(ts->file);
+ free(ts->path);
+ free(ts->results);
+ free(ts->reason);
+ free(ts);
+}
+
+
+/*
+ * Run a batch of tests. Takes two additional parameters: the root of the
+ * source directory and the root of the build directory. Test programs will
+ * be first searched for in the current directory, then the build directory,
+ * then the source directory. Returns true iff all tests passed, and always
+ * frees the test list that's passed in.
*/
static int
-test_batch(const char *testlist, const char *source, const char *build)
+test_batch(struct testlist *tests, const char *source, const char *build)
{
- FILE *tests;
- unsigned int length, i;
+ size_t length;
+ unsigned int i;
unsigned int longest = 0;
- char buffer[BUFSIZ];
- unsigned int line;
- struct testset ts, *tmp;
+ unsigned int count = 0;
+ struct testset *ts;
struct timeval start, end;
struct rusage stats;
struct testlist *failhead = NULL;
struct testlist *failtail = NULL;
- struct testlist *next;
+ struct testlist *current, *next;
+ int succeeded;
unsigned long total = 0;
unsigned long passed = 0;
unsigned long skipped = 0;
unsigned long failed = 0;
unsigned long aborted = 0;
- /*
- * Open our file of tests to run and scan it, checking for lines that
- * are too long and searching for the longest line.
- */
- tests = fopen(testlist, "r");
- if (!tests)
- sysdie("can't open %s", testlist);
- line = 0;
- while (fgets(buffer, sizeof(buffer), tests)) {
- line++;
- length = strlen(buffer) - 1;
- if (buffer[length] != '\n') {
- fprintf(stderr, "%s:%u: line too long\n", testlist, line);
- exit(1);
- }
+ /* Walk the list of tests to find the longest name. */
+ for (current = tests; current != NULL; current = current->next) {
+ length = strlen(current->ts->file);
if (length > longest)
longest = length;
}
- if (fseek(tests, 0, SEEK_SET) == -1)
- sysdie("can't rewind %s", testlist);
/*
* Add two to longest and round up to the nearest tab stop. This is how
@@ -998,64 +1300,50 @@ test_batch(const char *testlist, const char *source, const char *build)
/* Start the wall clock timer. */
gettimeofday(&start, NULL);
- /*
- * Now, plow through our tests again, running each one. Check line
- * length again out of paranoia.
- */
- line = 0;
- while (fgets(buffer, sizeof(buffer), tests)) {
- line++;
- length = strlen(buffer) - 1;
- if (buffer[length] != '\n') {
- fprintf(stderr, "%s:%u: line too long\n", testlist, line);
- exit(1);
- }
- buffer[length] = '\0';
- fputs(buffer, stdout);
- for (i = length; i < longest; i++)
+ /* Now, plow through our tests again, running each one. */
+ for (current = tests; current != NULL; current = current->next) {
+ ts = current->ts;
+
+ /* Print out the name of the test file. */
+ fputs(ts->file, stdout);
+ for (i = strlen(ts->file); i < longest; i++)
putchar('.');
if (isatty(STDOUT_FILENO))
fflush(stdout);
- memset(&ts, 0, sizeof(ts));
- ts.plan = PLAN_INIT;
- ts.file = xstrdup(buffer);
- find_test(buffer, &ts, source, build);
- ts.reason = NULL;
- if (test_run(&ts)) {
- free(ts.file);
- free(ts.path);
- free(ts.results);
- if (ts.reason != NULL)
- free(ts.reason);
- } else {
- tmp = xmalloc(sizeof(struct testset));
- memcpy(tmp, &ts, sizeof(struct testset));
- if (!failhead) {
+
+ /* Run the test. */
+ ts->path = find_test(ts->file, source, build);
+ succeeded = test_run(ts);
+ fflush(stdout);
+
+ /* Record cumulative statistics. */
+ aborted += ts->aborted;
+ total += ts->count + ts->all_skipped;
+ passed += ts->passed;
+ skipped += ts->skipped + ts->all_skipped;
+ failed += ts->failed;
+ count++;
+
+ /* If the test fails, we shuffle it over to the fail list. */
+ if (!succeeded) {
+ if (failhead == NULL) {
failhead = xmalloc(sizeof(struct testset));
- failhead->ts = tmp;
- failhead->next = NULL;
failtail = failhead;
} else {
failtail->next = xmalloc(sizeof(struct testset));
failtail = failtail->next;
- failtail->ts = tmp;
- failtail->next = NULL;
}
+ failtail->ts = ts;
+ failtail->next = NULL;
}
- aborted += ts.aborted;
- total += ts.count + ts.all_skipped;
- passed += ts.passed;
- skipped += ts.skipped + ts.all_skipped;
- failed += ts.failed;
}
total -= skipped;
- fclose(tests);
/* Stop the timer and get our child resource statistics. */
gettimeofday(&end, NULL);
getrusage(RUSAGE_CHILDREN, &stats);
- /* Print out our final results. */
+ /* Summarize the failures and free the failure list. */
if (failhead != NULL) {
test_fail_summary(failhead);
while (failhead != NULL) {
@@ -1064,6 +1352,16 @@ test_batch(const char *testlist, const char *source, const char *build)
failhead = next;
}
}
+
+ /* Free the memory used by the test lists. */
+ while (tests != NULL) {
+ next = tests->next;
+ free_testset(tests->ts);
+ free(tests);
+ tests = next;
+ }
+
+ /* Print out the final test summary. */
putchar('\n');
if (aborted != 0) {
if (aborted == 1)
@@ -1084,7 +1382,7 @@ test_batch(const char *testlist, const char *source, const char *build)
printf(", %lu tests skipped", skipped);
}
puts(".");
- printf("Files=%u, Tests=%lu", line, total);
+ printf("Files=%u, Tests=%lu", count, total);
printf(", %.2f seconds", tv_diff(&end, &start));
printf(" (%.2f usr + %.2f sys = %.2f CPU)\n",
tv_seconds(&stats.ru_utime), tv_seconds(&stats.ru_stime),
@@ -1100,12 +1398,11 @@ test_batch(const char *testlist, const char *source, const char *build)
static void
test_single(const char *program, const char *source, const char *build)
{
- struct testset ts;
+ char *path;
- memset(&ts, 0, sizeof(ts));
- find_test(program, &ts, source, build);
- if (execl(ts.path, ts.path, (char *) 0) == -1)
- sysdie("cannot exec %s", ts.path);
+ path = find_test(program, source, build);
+ if (execl(path, path, (char *) 0) == -1)
+ sysdie("cannot exec %s", path);
}
@@ -1121,19 +1418,24 @@ main(int argc, char *argv[])
int single = 0;
char *source_env = NULL;
char *build_env = NULL;
- const char *list;
+ const char *shortlist;
+ const char *list = NULL;
const char *source = SOURCE;
const char *build = BUILD;
+ struct testlist *tests;
- while ((option = getopt(argc, argv, "b:hos:")) != EOF) {
+ while ((option = getopt(argc, argv, "b:hl:os:")) != EOF) {
switch (option) {
case 'b':
build = optarg;
break;
case 'h':
- printf(usage_message, argv[0], argv[0]);
+ printf(usage_message, argv[0], argv[0], argv[0], usage_extra);
exit(0);
break;
+ case 'l':
+ list = optarg;
+ break;
case 'o':
single = 1;
break;
@@ -1144,39 +1446,43 @@ main(int argc, char *argv[])
exit(1);
}
}
- if (argc - optind != 1) {
- fprintf(stderr, usage_message, argv[0], argv[0]);
+ argv += optind;
+ argc -= optind;
+ if ((list == NULL && argc < 1) || (list != NULL && argc > 0)) {
+ fprintf(stderr, usage_message, argv[0], argv[0], argv[0], usage_extra);
exit(1);
}
- argc -= optind;
- argv += optind;
+ /* Set SOURCE and BUILD environment variables. */
if (source != NULL) {
- source_env = xmalloc(strlen("SOURCE=") + strlen(source) + 1);
- sprintf(source_env, "SOURCE=%s", source);
+ source_env = concat("SOURCE=", source, (const char *) 0);
if (putenv(source_env) != 0)
sysdie("cannot set SOURCE in the environment");
}
if (build != NULL) {
- build_env = xmalloc(strlen("BUILD=") + strlen(build) + 1);
- sprintf(build_env, "BUILD=%s", build);
+ build_env = concat("BUILD=", build, (const char *) 0);
if (putenv(build_env) != 0)
sysdie("cannot set BUILD in the environment");
}
+ /* Run the tests as instructed. */
if (single)
test_single(argv[0], source, build);
- else {
- list = strrchr(argv[0], '/');
- if (list == NULL)
- list = argv[0];
+ else if (list != NULL) {
+ shortlist = strrchr(list, '/');
+ if (shortlist == NULL)
+ shortlist = list;
else
- list++;
- printf(banner, list);
- status = test_batch(argv[0], source, build) ? 0 : 1;
+ shortlist++;
+ printf(banner, shortlist);
+ tests = read_test_list(list);
+ status = test_batch(tests, source, build) ? 0 : 1;
+ } else {
+ tests = build_test_list(argv, argc);
+ status = test_batch(tests, source, build) ? 0 : 1;
}
- /* For valgrind cleanliness. */
+ /* For valgrind cleanliness, free all our memory. */
if (source_env != NULL) {
putenv((char *) "SOURCE=");
free(source_env);
diff --git a/tests/tap/basic.c b/tests/tap/basic.c
index 70ee093..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 <eagle@eyrie.org>
- * 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 c55f662..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 <eagle@eyrie.org>
- * 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 6f593f8..578a858 100644
--- a/tests/tap/kerberos.c
+++ b/tests/tap/kerberos.c
@@ -15,7 +15,7 @@
* which can be found at <http://www.eyrie.org/~eagle/software/rra-c-util/>.
*
* Written by Russ Allbery <eagle@eyrie.org>
- * Copyright 2006, 2007, 2009, 2010, 2011, 2012
+ * 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 25c44a6..8be0add 100644
--- a/tests/tap/kerberos.h
+++ b/tests/tap/kerberos.h
@@ -5,7 +5,7 @@
* which can be found at <http://www.eyrie.org/~eagle/software/rra-c-util/>.
*
* Written by Russ Allbery <eagle@eyrie.org>
- * Copyright 2006, 2007, 2009, 2011, 2012
+ * 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/libtap.sh b/tests/tap/libtap.sh
index 1b02939..9731032 100644
--- a/tests/tap/libtap.sh
+++ b/tests/tap/libtap.sh
@@ -11,7 +11,7 @@
#
# Written by Russ Allbery <eagle@eyrie.org>
# Copyright 2009, 2010, 2011, 2012 Russ Allbery <eagle@eyrie.org>
-# Copyright 2006, 2007, 2008
+# 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 368f95e..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 <eagle@eyrie.org>
+ * 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 3754d18..45b0566 100644
--- a/tests/tap/messages.c
+++ b/tests/tap/messages.c
@@ -9,7 +9,7 @@
* which can be found at <http://www.eyrie.org/~eagle/software/rra-c-util/>.
*
* Copyright 2002, 2004, 2005 Russ Allbery <eagle@eyrie.org>
- * Copyright 2006, 2007, 2009, 2012
+ * 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/perl/Test/RRA.pm b/tests/tap/perl/Test/RRA.pm
index 49c0d06..bb7de7d 100644
--- a/tests/tap/perl/Test/RRA.pm
+++ b/tests/tap/perl/Test/RRA.pm
@@ -10,7 +10,7 @@
# which can be found at <http://www.eyrie.org/~eagle/software/rra-c-util/>.
#
# Written by Russ Allbery <eagle@eyrie.org>
-# Copyright 2013
+# 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 = '5.00';
+ $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
@@ -143,10 +161,14 @@ 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('Perl6::Slurp', 'slurp');
@@ -166,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 ...])
@@ -192,7 +225,7 @@ 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
@@ -220,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 b80a8fe..a064ed9 100644
--- a/tests/tap/perl/Test/RRA/Automake.pm
+++ b/tests/tap/perl/Test/RRA/Automake.pm
@@ -87,7 +87,7 @@ BEGIN {
# 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 = '5.00';
+ $VERSION = '5.05';
}
# Perl directories to skip globally for perl_dirs. We ignore the perl
diff --git a/tests/tap/perl/Test/RRA/Config.pm b/tests/tap/perl/Test/RRA/Config.pm
index 5c3df7b..3e77650 100644
--- a/tests/tap/perl/Test/RRA/Config.pm
+++ b/tests/tap/perl/Test/RRA/Config.pm
@@ -31,12 +31,13 @@ BEGIN {
@EXPORT_OK = qw(
$COVERAGE_LEVEL @COVERAGE_SKIP_TESTS @CRITIC_IGNORE $LIBRARY_PATH
$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 = '5.00';
+ $VERSION = '5.05';
}
# If BUILD or SOURCE are set in the environment, look for data/perl.conf under
@@ -65,6 +66,7 @@ our $MINIMUM_VERSION = '5.008';
our %MINIMUM_VERSION;
our @POD_COVERAGE_EXCLUDE;
our @STRICT_IGNORE;
+our @STRICT_PREREQ;
# Load the configuration.
if (!do($PATH)) {
@@ -163,6 +165,13 @@ 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
@@ -174,7 +183,7 @@ 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
diff --git a/tests/tap/process.c b/tests/tap/process.c
index ac60aae..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 <eagle@eyrie.org>
- * Copyright 2002, 2004, 2005 Russ Allbery <eagle@eyrie.org>
- * Copyright 2009, 2010, 2011
+ * 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 ed90345..8137d5d 100644
--- a/tests/tap/process.h
+++ b/tests/tap/process.h
@@ -5,7 +5,7 @@
* which can be found at <http://www.eyrie.org/~eagle/software/rra-c-util/>.
*
* Written by Russ Allbery <eagle@eyrie.org>
- * Copyright 2009, 2010
+ * 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/util/messages-krb5-t.c b/tests/util/messages-krb5-t.c
index 8e9daf1..c6de5a5 100644
--- a/tests/util/messages-krb5-t.c
+++ b/tests/util/messages-krb5-t.c
@@ -5,7 +5,7 @@
* which can be found at <http://www.eyrie.org/~eagle/software/rra-c-util/>.
*
* Written by Russ Allbery <eagle@eyrie.org>
- * Copyright 2010, 2011
+ * Copyright 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
@@ -28,17 +28,31 @@
*/
#include <config.h>
-#include <portable/krb5.h>
+#ifdef HAVE_KRB5
+# include <portable/krb5.h>
+#endif
#include <portable/system.h>
#include <tests/tap/basic.h>
#include <tests/tap/process.h>
#include <util/macros.h>
-#include <util/messages-krb5.h>
+#ifdef HAVE_KRB5
+# include <util/messages-krb5.h>
+#endif
#include <util/messages.h>
#include <util/xmalloc.h>
+/* Skip the whole test if not built with Kerberos support. */
+#ifndef HAVE_KRB5
+int
+main(void)
+{
+ skip_all("not built with Kerberos support");
+ return 0;
+}
+#else
+
/*
* Test functions.
*/
@@ -116,5 +130,9 @@ main(void)
message_handlers_die(0);
is_function_output(test_die, NULL, 1, "", "warn_krb5 with no handlers");
+ krb5_free_error_message(ctx, message);
+ krb5_free_context(ctx);
return 0;
}
+
+#endif /* HAVE_KRB5 */
diff --git a/tests/util/xmalloc-t b/tests/util/xmalloc-t
index 74e4bbd..d52c448 100755
--- a/tests/util/xmalloc-t
+++ b/tests/util/xmalloc-t
@@ -6,7 +6,7 @@
# which can be found at <http://www.eyrie.org/~eagle/software/rra-c-util/>.
#
# Written by Russ Allbery <eagle@eyrie.org>
-# Copyright 2000, 2001, 2006 Russ Allbery <eagle@eyrie.org>
+# Copyright 2000, 2001, 2006, 2014 Russ Allbery <eagle@eyrie.org>
# Copyright 2008, 2009, 2010, 2012
# The Board of Trustees of the Leland Stanford Junior University
#
@@ -59,31 +59,33 @@ ok_xmalloc () {
# failures in automated testing have been problems with the assumptions around
# memory allocation or problems with the test suite, not problems with the
# underlying xmalloc code.
-if [ -z "$RRA_MAINTAINER_TESTS" ] ; then
- skip_all 'xmalloc tests only run for maintainer'
+if [ -z "$AUTHOR_TESTING" ] ; then
+ skip_all 'xmalloc tests only run for author'
fi
# Total tests.
-plan 36
+plan 41
# First run the tests expected to succeed.
-ok_xmalloc "malloc small" 0 "" "m" "21" "0"
-ok_xmalloc "malloc large" 0 "" "m" "5500000" "0"
-ok_xmalloc "malloc zero" 0 "" "m" "0" "0"
-ok_xmalloc "realloc small" 0 "" "r" "21" "0"
-ok_xmalloc "realloc large" 0 "" "r" "5500000" "0"
-ok_xmalloc "strdup small" 0 "" "s" "21" "0"
-ok_xmalloc "strdup large" 0 "" "s" "5500000" "0"
-ok_xmalloc "strndup small" 0 "" "n" "21" "0"
-ok_xmalloc "strndup large" 0 "" "n" "5500000" "0"
-ok_xmalloc "calloc small" 0 "" "c" "24" "0"
-ok_xmalloc "calloc large" 0 "" "c" "5500000" "0"
-ok_xmalloc "asprintf small" 0 "" "a" "24" "0"
-ok_xmalloc "asprintf large" 0 "" "a" "5500000" "0"
-ok_xmalloc "vasprintf small" 0 "" "v" "24" "0"
-ok_xmalloc "vasprintf large" 0 "" "v" "5500000" "0"
+ok_xmalloc "malloc small" 0 "" "m" "21" "0"
+ok_xmalloc "malloc large" 0 "" "m" "30000000" "0"
+ok_xmalloc "malloc zero" 0 "" "m" "0" "0"
+ok_xmalloc "realloc small" 0 "" "r" "21" "0"
+ok_xmalloc "realloc large" 0 "" "r" "30000000" "0"
+ok_xmalloc "reallocarray small" 0 "" "y" "20" "0"
+ok_xmalloc "reallocarray large" 0 "" "y" "30000000" "0"
+ok_xmalloc "strdup small" 0 "" "s" "21" "0"
+ok_xmalloc "strdup large" 0 "" "s" "30000000" "0"
+ok_xmalloc "strndup small" 0 "" "n" "21" "0"
+ok_xmalloc "strndup large" 0 "" "n" "30000000" "0"
+ok_xmalloc "calloc small" 0 "" "c" "24" "0"
+ok_xmalloc "calloc large" 0 "" "c" "30000000" "0"
+ok_xmalloc "asprintf small" 0 "" "a" "24" "0"
+ok_xmalloc "asprintf large" 0 "" "a" "30000000" "0"
+ok_xmalloc "vasprintf small" 0 "" "v" "24" "0"
+ok_xmalloc "vasprintf large" 0 "" "v" "30000000" "0"
-# Now limit our memory to 5.5MB and then try the large ones again, all of
+# Now limit our memory to 30MB and then try the large ones again, all of
# which should fail.
#
# The exact memory limits used here are essentially black magic. They need to
@@ -91,53 +93,60 @@ ok_xmalloc "vasprintf large" 0 "" "v" "5500000" "0"
# but not so large that we can't reasonably expect to allocate that much
# memory normally. The amount of memory required varies a lot based on what
# shared libraries are loaded, and if it's too small, all memory allocations
-# fail. 5.5MB seems to work reasonably well on both Solaris and Linux.
+# fail. 30MB seems to work reasonably well on both Solaris and Linux, even
+# when the program is linked with additional libraries.
#
# We assume that there are enough miscellaneous allocations that an allocation
# exactly as large as the limit will always fail.
ok_xmalloc "malloc fail" 1 \
- "failed to malloc 5500000 bytes at xmalloc.c line 38" \
- "m" "5500000" "5500000"
+ "failed to malloc 30000000 bytes at xmalloc.c line 38" \
+ "m" "30000000" "30000000"
ok_xmalloc "realloc fail" 1 \
- "failed to realloc 5500000 bytes at xmalloc.c line 66" \
- "r" "5500000" "5500000"
+ "failed to realloc 30000000 bytes at xmalloc.c line 66" \
+ "r" "30000000" "30000000"
+ok_xmalloc "reallocarray fail" 1 \
+ "failed to reallocarray 30000000 bytes at xmalloc.c line 96" \
+ "y" "30000000" "30000000"
ok_xmalloc "strdup fail" 1 \
- "failed to strdup 5500000 bytes at xmalloc.c line 97" \
- "s" "5500000" "5500000"
+ "failed to strdup 30000000 bytes at xmalloc.c line 127" \
+ "s" "30000000" "30000000"
ok_xmalloc "strndup fail" 1 \
- "failed to strndup 5500000 bytes at xmalloc.c line 143" \
- "n" "5500000" "5500000"
+ "failed to strndup 30000000 bytes at xmalloc.c line 173" \
+ "n" "30000000" "30000000"
ok_xmalloc "calloc fail" 1 \
- "failed to calloc 5500000 bytes at xmalloc.c line 167" \
- "c" "5500000" "5500000"
+ "failed to calloc 30000000 bytes at xmalloc.c line 197" \
+ "c" "30000000" "30000000"
ok_xmalloc "asprintf fail" 1 \
- "failed to asprintf 5500000 bytes at xmalloc.c line 191" \
- "a" "5500000" "5500000"
+ "failed to asprintf 30000000 bytes at xmalloc.c line 221" \
+ "a" "30000000" "30000000"
ok_xmalloc "vasprintf fail" 1 \
- "failed to vasprintf 5500000 bytes at xmalloc.c line 210" \
- "v" "5500000" "5500000"
+ "failed to vasprintf 30000000 bytes at xmalloc.c line 240" \
+ "v" "30000000" "30000000"
# Check our custom error handler.
-ok_xmalloc "malloc custom" 1 "malloc 5500000 xmalloc.c 38" \
- "M" "5500000" "5500000"
-ok_xmalloc "realloc custom" 1 "realloc 5500000 xmalloc.c 66" \
- "R" "5500000" "5500000"
-ok_xmalloc "strdup custom" 1 "strdup 5500000 xmalloc.c 97" \
- "S" "5500000" "5500000"
-ok_xmalloc "strndup custom" 1 "strndup 5500000 xmalloc.c 143" \
- "N" "5500000" "5500000"
-ok_xmalloc "calloc custom" 1 "calloc 5500000 xmalloc.c 167" \
- "C" "5500000" "5500000"
-ok_xmalloc "asprintf custom" 1 "asprintf 5500000 xmalloc.c 191" \
- "A" "5500000" "5500000"
-ok_xmalloc "vasprintf custom" 1 "vasprintf 5500000 xmalloc.c 210" \
- "V" "5500000" "5500000"
+ok_xmalloc "malloc custom" 1 "malloc 30000000 xmalloc.c 38" \
+ "M" "30000000" "30000000"
+ok_xmalloc "realloc custom" 1 "realloc 30000000 xmalloc.c 66" \
+ "R" "30000000" "30000000"
+ok_xmalloc "reallocarray custom" 1 "reallocarray 30000000 xmalloc.c 96" \
+ "Y" "30000000" "30000000"
+ok_xmalloc "strdup custom" 1 "strdup 30000000 xmalloc.c 127" \
+ "S" "30000000" "30000000"
+ok_xmalloc "strndup custom" 1 "strndup 30000000 xmalloc.c 173" \
+ "N" "30000000" "30000000"
+ok_xmalloc "calloc custom" 1 "calloc 30000000 xmalloc.c 197" \
+ "C" "30000000" "30000000"
+ok_xmalloc "asprintf custom" 1 "asprintf 30000000 xmalloc.c 221" \
+ "A" "30000000" "30000000"
+ok_xmalloc "vasprintf custom" 1 "vasprintf 30000000 xmalloc.c 240" \
+ "V" "30000000" "30000000"
# Check the smaller ones again just for grins.
-ok_xmalloc "malloc retry" 0 "" "m" "21" "5500000"
-ok_xmalloc "realloc retry" 0 "" "r" "32" "5500000"
-ok_xmalloc "strdup retry" 0 "" "s" "64" "5500000"
-ok_xmalloc "strndup retry" 0 "" "n" "20" "5500000"
-ok_xmalloc "calloc retry" 0 "" "c" "24" "5500000"
-ok_xmalloc "asprintf retry" 0 "" "a" "30" "5500000"
-ok_xmalloc "vasprintf retry" 0 "" "v" "35" "5500000"
+ok_xmalloc "malloc retry" 0 "" "m" "21" "30000000"
+ok_xmalloc "realloc retry" 0 "" "r" "32" "30000000"
+ok_xmalloc "reallocarray retry" 0 "" "y" "32" "30000000"
+ok_xmalloc "strdup retry" 0 "" "s" "64" "30000000"
+ok_xmalloc "strndup retry" 0 "" "n" "20" "30000000"
+ok_xmalloc "calloc retry" 0 "" "c" "24" "30000000"
+ok_xmalloc "asprintf retry" 0 "" "a" "30" "30000000"
+ok_xmalloc "vasprintf retry" 0 "" "v" "35" "30000000"
diff --git a/tests/util/xmalloc.c b/tests/util/xmalloc.c
index 6614586..e222612 100644
--- a/tests/util/xmalloc.c
+++ b/tests/util/xmalloc.c
@@ -5,7 +5,7 @@
* which can be found at <http://www.eyrie.org/~eagle/software/rra-c-util/>.
*
* Copyright 2000, 2001, 2006 Russ Allbery <eagle@eyrie.org>
- * Copyright 2008, 2012
+ * Copyright 2008, 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
@@ -110,6 +110,36 @@ test_realloc(size_t size)
/*
+ * Like test_realloc, but test allocating an array instead. Returns true on
+ * success, false on any failure.
+ */
+static int
+test_reallocarray(size_t n, size_t size)
+{
+ char *buffer;
+ size_t i;
+
+ buffer = xmalloc(10);
+ if (buffer == NULL)
+ return 0;
+ memset(buffer, 1, 10);
+ buffer = xreallocarray(buffer, n, size);
+ if (buffer == NULL)
+ return 0;
+ if (n > 0 && size > 0)
+ memset(buffer + 10, 2, (n * size) - 10);
+ for (i = 0; i < 10; i++)
+ if (buffer[i] != 1)
+ return 0;
+ for (i = 10; i < n * size; i++)
+ if (buffer[i] != 2)
+ return 0;
+ free(buffer);
+ return 1;
+}
+
+
+/*
* Generate a string of the size indicated, call xstrdup on it, and then
* ensure the result matches. Returns true on success, false on any failure.
*/
@@ -322,6 +352,7 @@ main(int argc, char *argv[])
#if HAVE_SETRLIMIT && defined(RLIMIT_AS)
struct rlimit rl;
void *tmp;
+ size_t test_size;
rl.rlim_cur = limit;
rl.rlim_max = limit;
@@ -329,11 +360,14 @@ main(int argc, char *argv[])
syswarn("Can't set data limit to %lu", (unsigned long) limit);
exit(2);
}
- if (size < limit || code == 'r') {
- tmp = malloc(code == 'r' ? 10 : size);
+ if (size < limit || code == 'r' || code == 'y') {
+ test_size = (code == 'r' || code == 'y') ? 10 : size;
+ if (test_size == 0)
+ test_size = 1;
+ tmp = malloc(test_size);
if (tmp == NULL) {
syswarn("Can't allocate initial memory of %lu (limit %lu)",
- (unsigned long) size, (unsigned long) limit);
+ (unsigned long) test_size, (unsigned long) limit);
exit(2);
}
free(tmp);
@@ -348,6 +382,7 @@ main(int argc, char *argv[])
case 'c': exit(test_calloc(size) ? willfail : 1);
case 'm': exit(test_malloc(size) ? willfail : 1);
case 'r': exit(test_realloc(size) ? willfail : 1);
+ case 'y': exit(test_reallocarray(4, size / 4) ? willfail : 1);
case 's': exit(test_strdup(size) ? willfail : 1);
case 'n': exit(test_strndup(size) ? willfail : 1);
case 'a': exit(test_asprintf(size) ? willfail : 1);