diff options
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 +} | 
