diff options
author | Russ Allbery <rra@stanford.edu> | 2010-02-09 18:40:22 -0800 |
---|---|---|
committer | Russ Allbery <rra@stanford.edu> | 2010-02-09 18:40:22 -0800 |
commit | c02942ddc12408f0e5b9d828cddf240519d1fe93 (patch) | |
tree | 62f80e0ba359c1a13cee7daee228e3e00011a723 /tests/tap | |
parent | d05f66dbff10b525d37f60ee01d5b9f94bf5192e (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.c | 356 | ||||
-rw-r--r-- | tests/tap/basic.h | 98 | ||||
-rw-r--r-- | tests/tap/kerberos.c | 164 | ||||
-rw-r--r-- | tests/tap/kerberos.h | 32 | ||||
-rw-r--r-- | tests/tap/kerberos.sh | 48 | ||||
-rw-r--r-- | tests/tap/libtap.sh | 148 | ||||
-rw-r--r-- | tests/tap/messages.c | 80 | ||||
-rw-r--r-- | tests/tap/messages.h | 35 | ||||
-rw-r--r-- | tests/tap/process.c | 100 | ||||
-rw-r--r-- | tests/tap/process.h | 37 | ||||
-rw-r--r-- | tests/tap/remctl.sh | 46 |
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 +} |