summaryrefslogtreecommitdiff
path: root/tests/tap
diff options
context:
space:
mode:
authorRuss Allbery <rra@stanford.edu>2010-02-09 18:40:22 -0800
committerRuss Allbery <rra@stanford.edu>2010-02-09 18:40:22 -0800
commitc02942ddc12408f0e5b9d828cddf240519d1fe93 (patch)
tree62f80e0ba359c1a13cee7daee228e3e00011a723 /tests/tap
parentd05f66dbff10b525d37f60ee01d5b9f94bf5192e (diff)
Update to C TAP Harness 1.1 and rra-c-util 3.0 tests
* Update portable and util tests for C TAP Harness 1.1. * Remove the need for Autoconf substitution in test programs. * Support running a single test program with runtests -o. * Properly handle test cases that are skipped in their entirety. * Much improved C TAP library more closely matching Test::More. Rewrite client/basic-t to use the new test library functions and my current test case coding style.
Diffstat (limited to 'tests/tap')
-rw-r--r--tests/tap/basic.c356
-rw-r--r--tests/tap/basic.h98
-rw-r--r--tests/tap/kerberos.c164
-rw-r--r--tests/tap/kerberos.h32
-rw-r--r--tests/tap/kerberos.sh48
-rw-r--r--tests/tap/libtap.sh148
-rw-r--r--tests/tap/messages.c80
-rw-r--r--tests/tap/messages.h35
-rw-r--r--tests/tap/process.c100
-rw-r--r--tests/tap/process.h37
-rw-r--r--tests/tap/remctl.sh46
11 files changed, 1144 insertions, 0 deletions
diff --git a/tests/tap/basic.c b/tests/tap/basic.c
new file mode 100644
index 0000000..5ca9ff4
--- /dev/null
+++ b/tests/tap/basic.c
@@ -0,0 +1,356 @@
+/*
+ * Some utility routines for writing tests.
+ *
+ * Herein are a variety of utility routines for writing tests. All routines
+ * of the form ok*() take a test number and some number of appropriate
+ * arguments, check to be sure the results match the expected output using the
+ * arguments, and print out something appropriate for that test number. Other
+ * utility routines help in constructing more complex tests.
+ *
+ * Copyright 2009 Russ Allbery <rra@stanford.edu>
+ * Copyright 2006, 2007, 2008
+ * Board of Trustees, Leland Stanford Jr. University
+ * Copyright (c) 2004, 2005, 2006
+ * by Internet Systems Consortium, Inc. ("ISC")
+ * Copyright (c) 1991, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001,
+ * 2002, 2003 by The Internet Software Consortium and Rich Salz
+ *
+ * See LICENSE for licensing terms.
+ */
+
+#include <errno.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <tap/basic.h>
+
+/*
+ * The test count. Always contains the number that will be used for the next
+ * test status.
+ */
+int 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.
+ */
+static int _planned = 0;
+static int _failed = 0;
+static pid_t _process = 0;
+
+
+/*
+ * Our exit handler. Called on completion of the test to report a summary of
+ * results provided we're still in the original process.
+ */
+static void
+finish(void)
+{
+ int highest = testnum - 1;
+
+ if (_process != 0 && getpid() == _process && _planned > 0) {
+ if (_planned > highest)
+ printf("# Looks like you planned %d test%s but only ran %d\n",
+ _planned, (_planned > 1 ? "s" : ""), highest);
+ else if (_planned < highest)
+ printf("# Looks like you planned %d test%s but ran %d extra\n",
+ _planned, (_planned > 1 ? "s" : ""), highest - _planned);
+ else if (_failed > 0)
+ printf("# Looks like you failed %d test%s of %d\n", _failed,
+ (_failed > 1 ? "s" : ""), _planned);
+ else if (_planned > 1)
+ printf("# All %d tests successful or skipped\n", _planned);
+ else
+ printf("# %d test successful or skipped\n", _planned);
+ }
+}
+
+
+/*
+ * Initialize things. Turns on line buffering on stdout and then prints out
+ * the number of tests in the test suite.
+ */
+void
+plan(int count)
+{
+ if (setvbuf(stdout, NULL, _IOLBF, BUFSIZ) != 0)
+ fprintf(stderr, "# cannot set stdout to line buffered: %s\n",
+ strerror(errno));
+ printf("1..%d\n", count);
+ testnum = 1;
+ _planned = count;
+ _process = getpid();
+ atexit(finish);
+}
+
+
+/*
+ * Skip the entire test suite and exits. Should be called instead of plan(),
+ * not after it, since it prints out a special plan line.
+ */
+void
+skip_all(const char *format, ...)
+{
+ printf("1..0 # skip");
+ if (format != NULL) {
+ va_list args;
+
+ putchar(' ');
+ va_start(args, format);
+ vprintf(format, args);
+ va_end(args);
+ }
+ 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
+ok(int success, const char *format, ...)
+{
+ printf("%sok %d", success ? "" : "not ", testnum++);
+ if (!success)
+ _failed++;
+ if (format != NULL) {
+ va_list args;
+
+ va_start(args, format);
+ print_desc(format, args);
+ va_end(args);
+ }
+ putchar('\n');
+}
+
+
+/*
+ * Skip a test.
+ */
+void
+skip(const char *reason, ...)
+{
+ printf("ok %d # skip", testnum++);
+ if (reason != NULL) {
+ va_list args;
+
+ va_start(args, reason);
+ putchar(' ');
+ vprintf(reason, args);
+ va_end(args);
+ }
+ putchar('\n');
+}
+
+
+/*
+ * Report the same status on the next count tests.
+ */
+void
+ok_block(int count, int status, const char *format, ...)
+{
+ int i;
+
+ for (i = 0; i < count; i++) {
+ printf("%sok %d", status ? "" : "not ", testnum++);
+ if (!status)
+ _failed++;
+ if (format != NULL) {
+ va_list args;
+
+ va_start(args, format);
+ print_desc(format, args);
+ va_end(args);
+ }
+ putchar('\n');
+ }
+}
+
+
+/*
+ * Skip the next count tests.
+ */
+void
+skip_block(int count, const char *reason, ...)
+{
+ int i;
+
+ for (i = 0; i < count; i++) {
+ printf("ok %d # skip", testnum++);
+ if (reason != NULL) {
+ va_list args;
+
+ va_start(args, reason);
+ putchar(' ');
+ vprintf(reason, args);
+ va_end(args);
+ }
+ putchar('\n');
+ }
+}
+
+
+/*
+ * Takes an expected integer and a seen integer and assumes the test passes
+ * if those two numbers match.
+ */
+void
+is_int(int wanted, int seen, const char *format, ...)
+{
+ if (wanted == seen)
+ printf("ok %d", testnum++);
+ else {
+ printf("# wanted: %d\n# seen: %d\n", wanted, seen);
+ printf("not ok %d", testnum++);
+ _failed++;
+ }
+ if (format != NULL) {
+ va_list args;
+
+ va_start(args, format);
+ print_desc(format, args);
+ va_end(args);
+ }
+ putchar('\n');
+}
+
+
+/*
+ * Takes a string and what the string should be, and assumes the test passes
+ * if those strings match (using strcmp).
+ */
+void
+is_string(const char *wanted, const char *seen, const char *format, ...)
+{
+ if (wanted == NULL)
+ wanted = "(null)";
+ if (seen == NULL)
+ seen = "(null)";
+ if (strcmp(wanted, seen) == 0)
+ printf("ok %d", testnum++);
+ else {
+ printf("# wanted: %s\n# seen: %s\n", wanted, seen);
+ printf("not ok %d", testnum++);
+ _failed++;
+ }
+ if (format != NULL) {
+ va_list args;
+
+ va_start(args, format);
+ print_desc(format, args);
+ va_end(args);
+ }
+ putchar('\n');
+}
+
+
+/*
+ * Takes an expected double and a seen double and assumes the test passes if
+ * those two numbers match.
+ */
+void
+is_double(double wanted, double seen, const char *format, ...)
+{
+ if (wanted == seen)
+ printf("ok %d", testnum++);
+ else {
+ printf("# wanted: %g\n# seen: %g\n", wanted, seen);
+ printf("not ok %d", testnum++);
+ _failed++;
+ }
+ if (format != NULL) {
+ va_list args;
+
+ va_start(args, format);
+ print_desc(format, args);
+ va_end(args);
+ }
+ putchar('\n');
+}
+
+
+/*
+ * 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
+is_hex(unsigned long wanted, unsigned long seen, const char *format, ...)
+{
+ if (wanted == seen)
+ printf("ok %d", testnum++);
+ else {
+ printf("# wanted: %lx\n# seen: %lx\n", (unsigned long) wanted,
+ (unsigned long) seen);
+ printf("not ok %d", testnum++);
+ _failed++;
+ }
+ if (format != NULL) {
+ va_list args;
+
+ va_start(args, format);
+ print_desc(format, args);
+ va_end(args);
+ }
+ putchar('\n');
+}
+
+
+/*
+ * Bail out with an error.
+ */
+void
+bail(const char *format, ...)
+{
+ va_list args;
+
+ fflush(stdout);
+ printf("Bail out! ");
+ va_start(args, format);
+ vprintf(format, args);
+ va_end(args);
+ printf("\n");
+ exit(1);
+}
+
+
+/*
+ * Bail out with an error, appending strerror(errno).
+ */
+void
+sysbail(const char *format, ...)
+{
+ va_list args;
+ int oerrno = errno;
+
+ fflush(stdout);
+ printf("Bail out! ");
+ va_start(args, format);
+ vprintf(format, args);
+ va_end(args);
+ printf(": %s\n", strerror(oerrno));
+ exit(1);
+}
diff --git a/tests/tap/basic.h b/tests/tap/basic.h
new file mode 100644
index 0000000..efe94ba
--- /dev/null
+++ b/tests/tap/basic.h
@@ -0,0 +1,98 @@
+/*
+ * Basic utility routines for the TAP protocol.
+ *
+ * Copyright 2006, 2007, 2008
+ * Board of Trustees, Leland Stanford Jr. University
+ * Copyright (c) 2004, 2005, 2006
+ * by Internet Systems Consortium, Inc. ("ISC")
+ * Copyright (c) 1991, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001,
+ * 2002, 2003 by The Internet Software Consortium and Rich Salz
+ *
+ * See LICENSE for licensing terms.
+ */
+
+#ifndef TAP_BASIC_H
+#define TAP_BASIC_H 1
+
+#include <sys/types.h> /* pid_t */
+
+/*
+ * __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
+
+/*
+ * BEGIN_DECLS is used at the beginning of declarations so that C++
+ * compilers don't mangle their names. END_DECLS is used at the end.
+ */
+#undef BEGIN_DECLS
+#undef END_DECLS
+#ifdef __cplusplus
+# define BEGIN_DECLS extern "C" {
+# define END_DECLS }
+#else
+# define BEGIN_DECLS /* empty */
+# define END_DECLS /* empty */
+#endif
+
+/*
+ * Used for iterating through arrays. ARRAY_SIZE returns the number of
+ * elements in the array (useful for a < upper bound in a for loop) and
+ * ARRAY_END returns a pointer to the element past the end (ISO C99 makes it
+ * legal to refer to such a pointer as long as it's never dereferenced).
+ */
+#define ARRAY_SIZE(array) (sizeof(array) / sizeof((array)[0]))
+#define ARRAY_END(array) (&(array)[ARRAY_SIZE(array)])
+
+BEGIN_DECLS
+
+/*
+ * The test count. Always contains the number that will be used for the next
+ * test status.
+ */
+extern int testnum;
+
+/* Print out the number of tests and set standard output to line buffered. */
+void plan(int count);
+
+/* Skip the entire test suite. Call instead of plan. */
+void skip_all(const char *format, ...)
+ __attribute__((__noreturn__, __format__(printf, 1, 2)));
+
+/* Basic reporting functions. */
+void ok(int success, const char *format, ...)
+ __attribute__((__format__(printf, 2, 3)));
+void skip(const char *reason, ...)
+ __attribute__((__format__(printf, 1, 2)));
+
+/* Report the same status on, or skip, the next count tests. */
+void ok_block(int count, int success, const char *format, ...)
+ __attribute__((__format__(printf, 3, 4)));
+void skip_block(int count, const char *reason, ...)
+ __attribute__((__format__(printf, 2, 3)));
+
+/* Check an expected value against a seen value. */
+void is_int(int wanted, int seen, const char *format, ...)
+ __attribute__((__format__(printf, 3, 4)));
+void is_double(double wanted, double seen, const char *format, ...)
+ __attribute__((__format__(printf, 3, 4)));
+void is_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, ...)
+ __attribute__((__format__(printf, 3, 4)));
+
+/* Bail out with an error. sysbail appends strerror(errno). */
+void bail(const char *format, ...)
+ __attribute__((__noreturn__, __nonnull__, __format__(printf, 1, 2)));
+void sysbail(const char *format, ...)
+ __attribute__((__noreturn__, __nonnull__, __format__(printf, 1, 2)));
+
+END_DECLS
+
+#endif /* LIBTEST_H */
diff --git a/tests/tap/kerberos.c b/tests/tap/kerberos.c
new file mode 100644
index 0000000..700212e
--- /dev/null
+++ b/tests/tap/kerberos.c
@@ -0,0 +1,164 @@
+/*
+ * Utility functions for tests that use Kerberos.
+ *
+ * Currently only provides kerberos_setup(), which assumes a particular set of
+ * data files in either the SOURCE or BUILD directories and, using those,
+ * obtains Kerberos credentials, sets up a ticket cache, and sets the
+ * environment variable pointing to the Kerberos keytab to use for testing.
+ *
+ * Copyright 2006, 2007, 2009, 2010
+ * Board of Trustees, Leland Stanford Jr. University
+ *
+ * See LICENSE for licensing terms.
+ */
+
+#include <config.h>
+#include <portable/krb5.h>
+#include <portable/system.h>
+
+#include <tests/tap/basic.h>
+#include <tests/tap/kerberos.h>
+#include <util/concat.h>
+#include <util/xmalloc.h>
+
+
+/*
+ * Given the partial path to a file, look under BUILD and then SOURCE for the
+ * file and return the full path to the file in newly-allocated memory.
+ * Returns NULL if the file doesn't exist.
+ */
+static char *
+find_file(const char *file)
+{
+ char *base;
+ char *path = NULL;
+ const char *envs[] = { "BUILD", "SOURCE", NULL };
+ int i;
+
+ for (i = 0; envs[i] != NULL; i++) {
+ base = getenv(envs[i]);
+ if (base == NULL)
+ continue;
+ path = concatpath(base, file);
+ if (access(path, R_OK) == 0)
+ break;
+ free(path);
+ path = NULL;
+ }
+ return path;
+}
+
+
+/*
+ * Obtain Kerberos tickets for the principal specified in test.principal using
+ * the keytab specified in test.keytab, both of which are presumed to be in
+ * tests/data in either the build or the source tree.
+ *
+ * Returns the contents of test.principal in newly allocated memory or NULL if
+ * Kerberos tests are apparently not configured. If Kerberos tests are
+ * configured but something else fails, calls bail().
+ *
+ * The error handling here is not great. We should have a bail_krb5 that uses
+ * the same logic as messages-krb5.c, which hasn't yet been imported into
+ * rra-c-util.
+ */
+char *
+kerberos_setup(void)
+{
+ char *path, *krbtgt;
+ const char *build, *realm;
+ FILE *file;
+ char principal[BUFSIZ];
+ krb5_error_code code;
+ krb5_context ctx;
+ krb5_ccache ccache;
+ krb5_principal kprinc;
+ krb5_keytab keytab;
+ krb5_get_init_creds_opt *opts;
+ krb5_creds creds;
+
+ /* Read the principal name and find the keytab file. */
+ path = find_file("data/test.principal");
+ if (path == NULL)
+ return NULL;
+ file = fopen(path, "r");
+ if (file == NULL) {
+ free(path);
+ return NULL;
+ }
+ if (fgets(principal, sizeof(principal), file) == NULL) {
+ fclose(file);
+ bail("cannot read %s", path);
+ }
+ fclose(file);
+ if (principal[strlen(principal) - 1] != '\n')
+ bail("no newline in %s", path);
+ free(path);
+ principal[strlen(principal) - 1] = '\0';
+ path = find_file("data/test.keytab");
+ if (path == NULL)
+ return NULL;
+
+ /* Set the KRB5CCNAME and KRB5_KTNAME environment variables. */
+ build = getenv("BUILD");
+ if (build == NULL)
+ build = ".";
+ putenv(concat("KRB5CCNAME=", build, "/data/test.cache", (char *) 0));
+ putenv(concat("KRB5_KTNAME=", path, (char *) 0));
+
+ /* Now do the Kerberos initialization. */
+ code = krb5_init_context(&ctx);
+ if (code != 0)
+ bail("error initializing Kerberos");
+ code = krb5_cc_default(ctx, &ccache);
+ if (code != 0)
+ bail("error setting ticket cache");
+ code = krb5_parse_name(ctx, principal, &kprinc);
+ if (code != 0)
+ bail("error parsing principal %s", principal);
+ realm = krb5_principal_get_realm(ctx, kprinc);
+ krbtgt = concat("krbtgt/", realm, "@", realm, (char *) 0);
+ code = krb5_kt_resolve(ctx, path, &keytab);
+ if (code != 0)
+ bail("cannot open keytab %s", path);
+ code = krb5_get_init_creds_opt_alloc(ctx, &opts);
+ if (code != 0)
+ bail("cannot allocate credential options");
+ krb5_get_init_creds_opt_set_default_flags(ctx, NULL, realm, opts);
+ krb5_get_init_creds_opt_set_forwardable(opts, 0);
+ krb5_get_init_creds_opt_set_proxiable(opts, 0);
+ code = krb5_get_init_creds_keytab(ctx, &creds, kprinc, keytab, 0, krbtgt,
+ opts);
+ if (code != 0)
+ bail("cannot get Kerberos tickets");
+ code = krb5_cc_initialize(ctx, ccache, kprinc);
+ if (code != 0)
+ bail("error initializing ticket cache");
+ code = krb5_cc_store_cred(ctx, ccache, &creds);
+ if (code != 0)
+ bail("error storing credentials");
+ krb5_cc_close(ctx, ccache);
+ krb5_free_cred_contents(ctx, &creds);
+ krb5_kt_close(ctx, keytab);
+ krb5_free_principal(ctx, kprinc);
+ krb5_free_context(ctx);
+ free(krbtgt);
+ free(path);
+
+ return xstrdup(principal);
+}
+
+
+/*
+ * Clean up at the end of a test. Currently, all this does is remove the
+ * ticket cache.
+ */
+void
+kerberos_cleanup(void)
+{
+ char *path;
+
+ path = concatpath(getenv("BUILD"), "data/test.cache");
+ unlink(path);
+ free(path);
+}
diff --git a/tests/tap/kerberos.h b/tests/tap/kerberos.h
new file mode 100644
index 0000000..1c64f70
--- /dev/null
+++ b/tests/tap/kerberos.h
@@ -0,0 +1,32 @@
+/*
+ * Utility functions for tests that use Kerberos.
+ *
+ * Copyright 2006, 2007, 2009
+ * Board of Trustees, Leland Stanford Jr. University
+ *
+ * See LICENSE for licensing terms.
+ */
+
+#ifndef TAP_KERBEROS_H
+#define TAP_KERBEROS_H 1
+
+#include <config.h>
+#include <portable/macros.h>
+
+BEGIN_DECLS
+
+/*
+ * Set up Kerberos, returning the test principal in newly allocated memory if
+ * we were successful. If there is no principal in tests/data/test.principal
+ * or no keytab in tests/data/test.keytab, return NULL. Otherwise, on
+ * failure, calls bail().
+ */
+char *kerberos_setup(void)
+ __attribute__((__malloc__));
+
+/* Clean up at the end of a test. */
+void kerberos_cleanup(void);
+
+END_DECLS
+
+#endif /* !TAP_MESSAGES_H */
diff --git a/tests/tap/kerberos.sh b/tests/tap/kerberos.sh
new file mode 100644
index 0000000..da07e66
--- /dev/null
+++ b/tests/tap/kerberos.sh
@@ -0,0 +1,48 @@
+# Shell function library to initialize Kerberos credentials
+#
+# Written by Russ Allbery <rra@stanford.edu>
+# Copyright 2009 Board of Trustees, Leland Stanford Jr. University
+#
+# See LICENSE for licensing terms.
+
+# Set up Kerberos, including the ticket cache environment variable. Bail out
+# if not successful, return 0 if successful, and return 1 if Kerberos is not
+# configured. Sets the global principal variable to the principal to use.
+kerberos_setup () {
+ local keytab
+ keytab=''
+ for f in "$BUILD/data/test.keytab" "$SOURCE/data/test.keytab" ; do
+ if [ -r "$f" ] ; then
+ keytab="$f"
+ fi
+ done
+ principal=''
+ for f in "$BUILD/data/test.principal" "$SOURCE/data/test.principal" ; do
+ if [ -r "$f" ] ; then
+ principal=`cat "$BUILD/data/test.principal"`
+ fi
+ done
+ if [ -z "$keytab" ] || [ -z "$principal" ] ; then
+ return 1
+ fi
+ KRB5CCNAME="$BUILD/data/test.cache"; export KRB5CCNAME
+ kinit -k -t "$keytab" "$principal" >/dev/null </dev/null
+ status=$?
+ if [ $status != 0 ] ; then
+ kinit -t "$keytab" "$principal" >/dev/null </dev/null
+ status=$?
+ fi
+ if [ $status != 0 ] ; then
+ kinit -k -K "$keytab" "$principal" >/dev/null </dev/null
+ status=$?
+ fi
+ if [ $status != 0 ] ; then
+ bail "Can't get Kerberos tickets"
+ fi
+ return 0
+}
+
+# Clean up at the end of a test. Currently only removes the ticket cache.
+kerberos_cleanup () {
+ rm -f "$BUILD/data/test.cache"
+}
diff --git a/tests/tap/libtap.sh b/tests/tap/libtap.sh
new file mode 100644
index 0000000..1846840
--- /dev/null
+++ b/tests/tap/libtap.sh
@@ -0,0 +1,148 @@
+# Shell function library for test cases.
+#
+# Written by Russ Allbery <rra@stanford.edu>
+# Copyright 2009 Russ Allbery <rra@stanford.edu>
+# Copyright 2006, 2007, 2008 Board of Trustees, Leland Stanford Jr. University
+#
+# See LICENSE for licensing terms.
+
+# Print out the number of test cases we expect to run.
+plan () {
+ count=1
+ planned="$1"
+ failed=0
+ echo "1..$1"
+ trap finish 0
+}
+
+# Report the test status on exit.
+finish () {
+ local highest looks
+ highest=`expr "$count" - 1`
+ looks='# Looks like you'
+ if [ "$planned" -gt 0 ] ; then
+ if [ "$planned" -gt "$highest" ] ; then
+ if [ "$planned" -gt 1 ] ; then
+ echo "$looks planned $planned tests but only ran $highest"
+ else
+ echo "$looks planned $planned test but only ran $highest"
+ fi
+ elif [ "$planned" -lt "$highest" ] ; then
+ local extra
+ extra=`expr "$highest" - "$planned"`
+ if [ "$planned" -gt 1 ] ; then
+ echo "$looks planned $planned tests but ran $extra extra"
+ else
+ echo "$looks planned $planned test but ran $extra extra"
+ fi
+ elif [ "$failed" -gt 0 ] ; then
+ if [ "$failed" -gt 1 ] ; then
+ echo "$looks failed $failed tests of $planned"
+ else
+ echo "$looks failed $failed test of $planned"
+ fi
+ elif [ "$planned" -gt 1 ] ; then
+ echo "# All $planned tests successful or skipped"
+ else
+ echo "# $planned test successful or skipped"
+ fi
+ fi
+}
+
+# Skip the entire test suite. Should be run instead of plan.
+skip_all () {
+ local desc
+ desc="$1"
+ if [ -n "$desc" ] ; then
+ echo "1..0 # skip $desc"
+ else
+ echo "1..0 # skip"
+ fi
+ exit 0
+}
+
+# ok takes a test description and a command to run and prints success if that
+# command is successful, false otherwise. The count starts at 1 and is
+# updated each time ok is printed.
+ok () {
+ local desc
+ desc="$1"
+ if [ -n "$desc" ] ; then
+ desc=" - $desc"
+ fi
+ shift
+ if "$@" ; then
+ echo ok $count$desc
+ else
+ echo not ok $count$desc
+ failed=`expr $failed + 1`
+ fi
+ count=`expr $count + 1`
+}
+
+# Skip the next test. Takes the reason why the test is skipped.
+skip () {
+ echo "ok $count # skip $*"
+ count=`expr $count + 1`
+}
+
+# Report the same status on a whole set of tests. Takes the count of tests,
+# the description, and then the command to run to determine the status.
+ok_block () {
+ local end i desc
+ i=$count
+ end=`expr $count + $1`
+ shift
+ desc="$1"
+ shift
+ while [ "$i" -lt "$end" ] ; do
+ ok "$desc" "$@"
+ i=`expr $i + 1`
+ done
+}
+
+# Skip a whole set of tests. Takes the count and then the reason for skipping
+# the test.
+skip_block () {
+ local i end
+ i=$count
+ end=`expr $count + $1`
+ shift
+ while [ "$i" -lt "$end" ] ; do
+ skip "$@"
+ i=`expr $i + 1`
+ done
+}
+
+# Run a program expected to succeed, and print ok if it does and produces the
+# correct output. Takes the description, expected exit status, the expected
+# output, the command to run, and then any arguments for that command. Strip
+# a colon and everything after it off the output if the expected status is
+# non-zero, since this is probably a system-specific error message.
+ok_program () {
+ local desc w_status w_output output status
+ desc="$1"
+ shift
+ w_status="$1"
+ shift
+ w_output="$1"
+ shift
+ output=`"$@" 2>&1`
+ status=$?
+ if [ "$w_status" -ne 0 ] ; then
+ output=`echo "$output" | sed 's/^\([^:]* [^:]*\):.*/\1/'`
+ fi
+ if [ $status = $w_status ] && [ x"$output" = x"$w_output" ] ; then
+ ok "$desc" true
+ else
+ echo "# saw: ($status) $output"
+ echo "# not: ($w_status) $w_output"
+ ok "$desc" false
+ fi
+}
+
+# Bail out with an error message.
+bail () {
+ echo 'Bail out!' "$@"
+ exit 1
+}
diff --git a/tests/tap/messages.c b/tests/tap/messages.c
new file mode 100644
index 0000000..3bb9a1a
--- /dev/null
+++ b/tests/tap/messages.c
@@ -0,0 +1,80 @@
+/*
+ * Utility functions to test message handling.
+ *
+ * These functions set up a message handler to trap warn and notice output
+ * into a buffer that can be inspected later, allowing testing of error
+ * handling.
+ *
+ * Copyright 2006, 2007, 2009
+ * Board of Trustees, Leland Stanford Jr. University
+ * Copyright (c) 2004, 2005, 2006
+ * by Internet Systems Consortium, Inc. ("ISC")
+ * Copyright (c) 1991, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001,
+ * 2002, 2003 by The Internet Software Consortium and Rich Salz
+ *
+ * See LICENSE for licensing terms.
+ */
+
+#include <config.h>
+#include <portable/system.h>
+
+#include <tests/tap/messages.h>
+#include <util/concat.h>
+#include <util/macros.h>
+#include <util/messages.h>
+#include <util/xmalloc.h>
+
+/* A global buffer into which message_log_buffer stores error messages. */
+char *errors = NULL;
+
+
+/*
+ * An error handler that appends all errors to the errors global. Used by
+ * error_capture.
+ */
+static void
+message_log_buffer(int len, const char *fmt, va_list args, int error UNUSED)
+{
+ char *message;
+
+ message = xmalloc(len + 1);
+ vsnprintf(message, len + 1, fmt, args);
+ if (errors == NULL) {
+ errors = concat(message, "\n", (char *) 0);
+ } else {
+ char *new_errors;
+
+ new_errors = concat(errors, message, "\n", (char *) 0);
+ free(errors);
+ errors = new_errors;
+ }
+ free(message);
+}
+
+
+/*
+ * Turn on the capturing of errors. Errors will be stored in the global
+ * errors variable where they can be checked by the test suite. Capturing is
+ * turned off with errors_uncapture.
+ */
+void
+errors_capture(void)
+{
+ if (errors != NULL) {
+ free(errors);
+ errors = NULL;
+ }
+ message_handlers_warn(1, message_log_buffer);
+ message_handlers_notice(1, message_log_buffer);
+}
+
+
+/*
+ * Turn off the capturing of errors again.
+ */
+void
+errors_uncapture(void)
+{
+ message_handlers_warn(1, message_log_stderr);
+ message_handlers_notice(1, message_log_stdout);
+}
diff --git a/tests/tap/messages.h b/tests/tap/messages.h
new file mode 100644
index 0000000..2b9a7db
--- /dev/null
+++ b/tests/tap/messages.h
@@ -0,0 +1,35 @@
+/*
+ * Utility functions to test message handling.
+ *
+ * Copyright 2006, 2007, 2009
+ * Board of Trustees, Leland Stanford Jr. University
+ * Copyright (c) 2004, 2005, 2006
+ * by Internet Systems Consortium, Inc. ("ISC")
+ * Copyright (c) 1991, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001,
+ * 2002, 2003 by The Internet Software Consortium and Rich Salz
+ *
+ * See LICENSE for licensing terms.
+ */
+
+#ifndef TAP_MESSAGES_H
+#define TAP_MESSAGES_H 1
+
+#include <config.h>
+#include <portable/macros.h>
+
+/* A global buffer into which errors_capture stores errors. */
+extern char *errors;
+
+BEGIN_DECLS
+
+/*
+ * Turn on capturing of errors with errors_capture. Errors reported by warn
+ * will be stored in the global errors variable. Turn this off again with
+ * errors_uncapture. Caller is responsible for freeing errors when done.
+ */
+void errors_capture(void);
+void errors_uncapture(void);
+
+END_DECLS
+
+#endif /* !TAP_MESSAGES_H */
diff --git a/tests/tap/process.c b/tests/tap/process.c
new file mode 100644
index 0000000..16154c7
--- /dev/null
+++ b/tests/tap/process.c
@@ -0,0 +1,100 @@
+/*
+ * Utility functions for tests that use subprocesses.
+ *
+ * Provides utility functions for subprocess manipulation. Currently, only
+ * one utility function is provided: is_function_output, which runs a function
+ * in a subprocess and checks its output and exit status against expected
+ * values.
+ *
+ * Copyright 2009, 2010 Board of Trustees, Leland Stanford Jr. University
+ * Copyright (c) 2004, 2005, 2006
+ * by Internet Systems Consortium, Inc. ("ISC")
+ * Copyright (c) 1991, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001,
+ * 2002, 2003 by The Internet Software Consortium and Rich Salz
+ *
+ * See LICENSE for licensing terms.
+ */
+
+#include <config.h>
+#include <portable/system.h>
+
+#include <sys/wait.h>
+
+#include <tests/tap/basic.h>
+#include <tests/tap/process.h>
+#include <util/xmalloc.h>
+
+
+/*
+ * Given a function, an expected exit status, and expected output, runs that
+ * function in a subprocess, capturing stdout and stderr via a pipe, and
+ * compare the combination of stdout and stderr with the expected output and
+ * the exit status with the expected status. Expects the function to always
+ * exit (not die from a signal).
+ */
+void
+is_function_output(test_function_type function, int status, const char *output,
+ const char *format, ...)
+{
+ int fds[2];
+ pid_t child;
+ char *buf, *msg;
+ ssize_t count, ret, buflen;
+ int rval;
+ va_list args;
+
+ /* Flush stdout before we start to avoid odd forking issues. */
+ fflush(stdout);
+
+ /* Set up the pipe and call the function, collecting its output. */
+ if (pipe(fds) == -1)
+ sysbail("can't create pipe");
+ child = fork();
+ if (child == (pid_t) -1) {
+ sysbail("can't fork");
+ } else if (child == 0) {
+ /* In child. Set up our stdout and stderr. */
+ close(fds[0]);
+ if (dup2(fds[1], 1) == -1)
+ _exit(255);
+ if (dup2(fds[1], 2) == -1)
+ _exit(255);
+
+ /* Now, run the function and exit successfully if it returns. */
+ (*function)();
+ fflush(stdout);
+ _exit(0);
+ } else {
+ /*
+ * In the parent; close the extra file descriptor, read the output if
+ * any, and then collect the exit status.
+ */
+ close(fds[1]);
+ buflen = BUFSIZ;
+ buf = xmalloc(buflen);
+ count = 0;
+ do {
+ ret = read(fds[0], buf + count, buflen - count - 1);
+ if (ret > 0)
+ count += ret;
+ if (count >= buflen - 1) {
+ buflen += BUFSIZ;
+ buf = xrealloc(buf, buflen);
+ }
+ } while (ret > 0);
+ buf[count < 0 ? 0 : count] = '\0';
+ if (waitpid(child, &rval, 0) == (pid_t) -1)
+ sysbail("waitpid failed");
+ }
+
+ /* Now, check the results against what we expected. */
+ va_start(args, format);
+ if (xvasprintf(&msg, format, args) < 0)
+ bail("cannot format test description");
+ va_end(args);
+ ok(WIFEXITED(rval), "%s (exited)", msg);
+ is_int(status, WEXITSTATUS(rval), "%s (status)", msg);
+ is_string(output, buf, "%s (output)", msg);
+ free(buf);
+ free(msg);
+}
diff --git a/tests/tap/process.h b/tests/tap/process.h
new file mode 100644
index 0000000..b7d3b11
--- /dev/null
+++ b/tests/tap/process.h
@@ -0,0 +1,37 @@
+/*
+ * Utility functions for tests that use subprocesses.
+ *
+ * Copyright 2009, 2010 Board of Trustees, Leland Stanford Jr. University
+ * Copyright (c) 2004, 2005, 2006
+ * by Internet Systems Consortium, Inc. ("ISC")
+ * Copyright (c) 1991, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001,
+ * 2002, 2003 by The Internet Software Consortium and Rich Salz
+ *
+ * See LICENSE for licensing terms.
+ */
+
+#ifndef TAP_PROCESS_H
+#define TAP_PROCESS_H 1
+
+#include <config.h>
+#include <portable/macros.h>
+
+BEGIN_DECLS
+
+/*
+ * Run a function in a subprocess and check the exit status and expected
+ * output (stdout and stderr combined) against the provided values. Expects
+ * the function to always exit (not die from a signal).
+ *
+ * This reports as three separate tests: whether the function exited rather
+ * than was killed, whether the exit status was correct, and whether the
+ * output was correct.
+ */
+typedef void (*test_function_type)(void);
+void is_function_output(test_function_type, int status, const char *output,
+ const char *format, ...)
+ __attribute__((__format__(printf, 4, 5)));
+
+END_DECLS
+
+#endif /* TAP_PROCESS_H */
diff --git a/tests/tap/remctl.sh b/tests/tap/remctl.sh
new file mode 100644
index 0000000..b9667ef
--- /dev/null
+++ b/tests/tap/remctl.sh
@@ -0,0 +1,46 @@
+# Shell function library to start and stop remctld
+#
+# Written by Russ Allbery <rra@stanford.edu>
+# Copyright 2009 Board of Trustees, Leland Stanford Jr. University
+#
+# See LICENSE for licensing terms.
+
+# Start remctld. Takes the path to remctld, which may be found via configure,
+# and the path to the configuration file.
+remctld_start () {
+ local keytab principal
+ rm -f "$BUILD/data/remctld.pid"
+ keytab=''
+ for f in "$BUILD/data/test.keytab" "$SOURCE/data/test.keytab" ; do
+ if [ -r "$f" ] ; then
+ keytab="$f"
+ fi
+ done
+ principal=''
+ for f in "$BUILD/data/test.principal" "$SOURCE/data/test.principal" ; do
+ if [ -r "$f" ] ; then
+ principal=`cat "$BUILD/data/test.principal"`
+ fi
+ done
+ if [ -n "$VALGRIND" ] ; then
+ ( "$VALGRIND" --log-file=valgrind.%p --leak-check=full "$1" -m \
+ -p 14373 -s "$principal" -P "$BUILD/data/remctld.pid" -f "$2" -d \
+ -S -F -k "$keytab" &)
+ [ -f "$BUILD/data/remctld.pid" ] || sleep 5
+ else
+ ( "$1" -m -p 14373 -s "$principal" -P "$BUILD/data/remctld.pid" \
+ -f "$2" -d -S -F -k "$keytab" &)
+ fi
+ [ -f "$BUILD/data/remctld.pid" ] || sleep 1
+ if [ ! -f "$BUILD/data/remctld.pid" ] ; then
+ bail 'remctld did not start'
+ fi
+}
+
+# Stop remctld and clean up.
+remctld_stop () {
+ if [ -f "$BUILD/data/remctld.pid" ] ; then
+ kill -TERM `cat "$BUILD/data/remctld.pid"`
+ rm -f "$BUILD/data/remctld.pid"
+ fi
+}