diff options
Diffstat (limited to 'tests/tap')
| -rw-r--r-- | tests/tap/basic.c | 239 | ||||
| -rw-r--r-- | tests/tap/basic.h | 81 | ||||
| -rw-r--r-- | tests/tap/kerberos.c | 499 | ||||
| -rw-r--r-- | tests/tap/kerberos.h | 115 | ||||
| -rw-r--r-- | tests/tap/kerberos.sh | 64 | ||||
| -rw-r--r-- | tests/tap/libtap.sh | 192 | ||||
| -rw-r--r-- | tests/tap/macros.h | 88 | ||||
| -rw-r--r-- | tests/tap/messages.c | 49 | ||||
| -rw-r--r-- | tests/tap/messages.h | 30 | ||||
| -rw-r--r-- | tests/tap/perl/Test/RRA.pm | 222 | ||||
| -rw-r--r-- | tests/tap/perl/Test/RRA/Automake.pm | 362 | ||||
| -rw-r--r-- | tests/tap/perl/Test/RRA/Config.pm | 200 | ||||
| -rw-r--r-- | tests/tap/process.c | 125 | ||||
| -rw-r--r-- | tests/tap/process.h | 52 | ||||
| -rw-r--r-- | tests/tap/remctl.sh | 61 | ||||
| -rw-r--r-- | tests/tap/string.c | 65 | ||||
| -rw-r--r-- | tests/tap/string.h | 49 | 
17 files changed, 2171 insertions, 322 deletions
| diff --git a/tests/tap/basic.c b/tests/tap/basic.c index 829f91a..e8196fc 100644 --- a/tests/tap/basic.c +++ b/tests/tap/basic.c @@ -1,22 +1,38 @@  /*   * Some utility routines for writing tests.   * - * Herein are a variety of utility routines for writing tests.  All routines - * of the form ok() or is*() 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, skipping tests, - * or setting up the TAP output format. + * Here are a variety of utility routines for writing tests compatible with + * the TAP protocol.  All routines of the form ok() or is*() 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, skipping tests, reporting errors, setting + * up the TAP output format, or finding things in the test environment.   * - * Copyright 2009, 2010 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 + * This file is part of C TAP Harness.  The current version plus supporting + * documentation is at <http://www.eyrie.org/~eagle/software/c-tap-harness/>.   * - * See LICENSE for licensing terms. + * Copyright 2009, 2010, 2011, 2012 Russ Allbery <rra@stanford.edu> + * Copyright 2001, 2002, 2004, 2005, 2006, 2007, 2008, 2011, 2012 + *     The Board of Trustees of the Leland Stanford Junior University + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE.   */  #include <errno.h> @@ -24,12 +40,21 @@  #include <stdio.h>  #include <stdlib.h>  #include <string.h> +#ifdef _WIN32 +# include <direct.h> +#else +# include <sys/stat.h> +#endif  #include <sys/types.h> -#include <sys/time.h> -#include <sys/wait.h>  #include <unistd.h> -#include <tap/basic.h> +#include <tests/tap/basic.h> + +/* Windows provides mkdir and rmdir under different names. */ +#ifdef _WIN32 +# define mkdir(p, m) _mkdir(p) +# define rmdir(p)    _rmdir(p) +#endif  /*   * The test count.  Always contains the number that will be used for the next @@ -57,7 +82,9 @@ static int _lazy = 0;  /*   * Our exit handler.  Called on completion of the test to report a summary of - * results provided we're still in the original process. + * results provided we're still in the original process.  This also handles + * printing out the plan if we used plan_lazy(), although that's suppressed if + * we never ran a test (due to an early bail, for example).   */  static void  finish(void) @@ -66,8 +93,9 @@ finish(void)      if (_planned == 0 && !_lazy)          return; +    fflush(stderr);      if (_process != 0 && getpid() == _process) { -        if (_lazy) { +        if (_lazy && highest > 0) {              printf("1..%lu\n", highest);              _planned = highest;          } @@ -98,6 +126,7 @@ plan(unsigned long count)      if (setvbuf(stdout, NULL, _IOLBF, BUFSIZ) != 0)          fprintf(stderr, "# cannot set stdout to line buffered: %s\n",                  strerror(errno)); +    fflush(stderr);      printf("1..%lu\n", count);      testnum = 1;      _planned = count; @@ -130,6 +159,7 @@ plan_lazy(void)  void  skip_all(const char *format, ...)  { +    fflush(stderr);      printf("1..0 # skip");      if (format != NULL) {          va_list args; @@ -162,6 +192,7 @@ print_desc(const char *format, va_list args)  void  ok(int success, const char *format, ...)  { +    fflush(stderr);      printf("%sok %lu", success ? "" : "not ", testnum++);      if (!success)          _failed++; @@ -182,6 +213,7 @@ ok(int success, const char *format, ...)  void  okv(int success, const char *format, va_list args)  { +    fflush(stderr);      printf("%sok %lu", success ? "" : "not ", testnum++);      if (!success)          _failed++; @@ -197,6 +229,7 @@ okv(int success, const char *format, va_list args)  void  skip(const char *reason, ...)  { +    fflush(stderr);      printf("ok %lu # skip", testnum++);      if (reason != NULL) {          va_list args; @@ -218,6 +251,7 @@ ok_block(unsigned long count, int status, const char *format, ...)  {      unsigned long i; +    fflush(stderr);      for (i = 0; i < count; i++) {          printf("%sok %lu", status ? "" : "not ", testnum++);          if (!status) @@ -242,6 +276,7 @@ skip_block(unsigned long count, const char *reason, ...)  {      unsigned long i; +    fflush(stderr);      for (i = 0; i < count; i++) {          printf("ok %lu # skip", testnum++);          if (reason != NULL) { @@ -264,6 +299,7 @@ skip_block(unsigned long count, const char *reason, ...)  void  is_int(long wanted, long seen, const char *format, ...)  { +    fflush(stderr);      if (wanted == seen)          printf("ok %lu", testnum++);      else { @@ -293,6 +329,7 @@ is_string(const char *wanted, const char *seen, const char *format, ...)          wanted = "(null)";      if (seen == NULL)          seen = "(null)"; +    fflush(stderr);      if (strcmp(wanted, seen) == 0)          printf("ok %lu", testnum++);      else { @@ -312,37 +349,13 @@ is_string(const char *wanted, const char *seen, const char *format, ...)  /* - * 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 %lu", testnum++); -    else { -        printf("# wanted: %g\n#   seen: %g\n", wanted, seen); -        printf("not ok %lu", 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, ...)  { +    fflush(stderr);      if (wanted == seen)          printf("ok %lu", testnum++);      else { @@ -370,6 +383,7 @@ bail(const char *format, ...)  {      va_list args; +    fflush(stderr);      fflush(stdout);      printf("Bail out! ");      va_start(args, format); @@ -389,6 +403,7 @@ sysbail(const char *format, ...)      va_list args;      int oerrno = errno; +    fflush(stderr);      fflush(stdout);      printf("Bail out! ");      va_start(args, format); @@ -407,6 +422,7 @@ diag(const char *format, ...)  {      va_list args; +    fflush(stderr);      fflush(stdout);      printf("# ");      va_start(args, format); @@ -425,6 +441,7 @@ sysdiag(const char *format, ...)      va_list args;      int oerrno = errno; +    fflush(stderr);      fflush(stdout);      printf("# ");      va_start(args, format); @@ -435,6 +452,92 @@ sysdiag(const char *format, ...)  /* + * Allocate cleared memory, reporting a fatal error with bail on failure. + */ +void * +bcalloc(size_t n, size_t size) +{ +    void *p; + +    p = calloc(n, size); +    if (p == NULL) +        sysbail("failed to calloc %lu", (unsigned long)(n * size)); +    return p; +} + + +/* + * Allocate memory, reporting a fatal error with bail on failure. + */ +void * +bmalloc(size_t size) +{ +    void *p; + +    p = malloc(size); +    if (p == NULL) +        sysbail("failed to malloc %lu", (unsigned long) size); +    return p; +} + + +/* + * Reallocate memory, reporting a fatal error with bail on failure. + */ +void * +brealloc(void *p, size_t size) +{ +    p = realloc(p, size); +    if (p == NULL) +        sysbail("failed to realloc %lu bytes", (unsigned long) size); +    return p; +} + + +/* + * Copy a string, reporting a fatal error with bail on failure. + */ +char * +bstrdup(const char *s) +{ +    char *p; +    size_t len; + +    len = strlen(s) + 1; +    p = malloc(len); +    if (p == NULL) +        sysbail("failed to strdup %lu bytes", (unsigned long) len); +    memcpy(p, s, len); +    return p; +} + + +/* + * Copy up to n characters of a string, reporting a fatal error with bail on + * failure.  Don't use the system strndup function, since it may not exist and + * the TAP library doesn't assume any portability support. + */ +char * +bstrndup(const char *s, size_t n) +{ +    const char *p; +    char *copy; +    size_t length; + +    /* Don't assume that the source string is nul-terminated. */ +    for (p = s; (size_t) (p - s) < n && *p != '\0'; p++) +        ; +    length = p - s; +    copy = malloc(length + 1); +    if (p == NULL) +        sysbail("failed to strndup %lu bytes", (unsigned long) length); +    memcpy(copy, s, length); +    copy[length] = '\0'; +    return copy; +} + + +/*   * Locate a test file.  Given the partial path to a file, look under BUILD and   * then SOURCE for the file and return the full path to the file.  Returns   * NULL if the file doesn't exist.  A non-NULL return should be freed with @@ -458,9 +561,7 @@ test_file_path(const char *file)          if (base == NULL)              continue;          length = strlen(base) + 1 + strlen(file) + 1; -        path = malloc(length); -        if (path == NULL) -            sysbail("cannot allocate memory"); +        path = bmalloc(length);          sprintf(path, "%s/%s", base, file);          if (access(path, R_OK) == 0)              break; @@ -482,3 +583,47 @@ test_file_path_free(char *path)      if (path != NULL)          free(path);  } + + +/* + * Create a temporary directory, tmp, under BUILD if set and the current + * directory if it does not.  Returns the path to the temporary directory in + * newly allocated memory, and calls bail on any failure.  The return value + * should be freed with test_tmpdir_free. + * + * This function uses sprintf because it attempts to be independent of all + * other portability layers.  The use immediately after a memory allocation + * should be safe without using snprintf or strlcpy/strlcat. + */ +char * +test_tmpdir(void) +{ +    const char *build; +    char *path = NULL; +    size_t length; + +    build = getenv("BUILD"); +    if (build == NULL) +        build = "."; +    length = strlen(build) + strlen("/tmp") + 1; +    path = bmalloc(length); +    sprintf(path, "%s/tmp", build); +    if (access(path, X_OK) < 0) +        if (mkdir(path, 0777) < 0) +            sysbail("error creating temporary directory %s", path); +    return path; +} + + +/* + * Free a path returned from test_tmpdir() and attempt to remove the + * directory.  If we can't delete the directory, don't worry; something else + * that hasn't yet cleaned up may still be using it. + */ +void +test_tmpdir_free(char *path) +{ +    rmdir(path); +    if (path != NULL) +        free(path); +} diff --git a/tests/tap/basic.h b/tests/tap/basic.h index 9602db4..fa4adaf 100644 --- a/tests/tap/basic.h +++ b/tests/tap/basic.h @@ -1,47 +1,38 @@  /*   * Basic utility routines for the TAP protocol.   * - * Copyright 2009, 2010 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 + * This file is part of C TAP Harness.  The current version plus supporting + * documentation is at <http://www.eyrie.org/~eagle/software/c-tap-harness/>.   * - * See LICENSE for licensing terms. + * Copyright 2009, 2010, 2011, 2012 Russ Allbery <rra@stanford.edu> + * Copyright 2001, 2002, 2004, 2005, 2006, 2007, 2008, 2011, 2012 + *     The Board of Trustees of the Leland Stanford Junior University + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE.   */  #ifndef TAP_BASIC_H  #define TAP_BASIC_H 1 +#include <tests/tap/macros.h>  #include <stdarg.h>             /* va_list */ -#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 +#include <sys/types.h>          /* size_t */  /*   * Used for iterating through arrays.  ARRAY_SIZE returns the number of @@ -93,8 +84,6 @@ void skip_block(unsigned long count, const char *reason, ...)  /* Check an expected value against a seen value. */  void is_int(long wanted, long 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, ...) @@ -112,6 +101,18 @@ void diag(const char *format, ...)  void sysdiag(const char *format, ...)      __attribute__((__nonnull__, __format__(printf, 1, 2))); +/* Allocate memory, reporting a fatal error with bail on failure. */ +void *bcalloc(size_t, size_t) +    __attribute__((__alloc_size__(1, 2), __malloc__)); +void *bmalloc(size_t) +    __attribute__((__alloc_size__(1), __malloc__)); +void *brealloc(void *, size_t) +    __attribute__((__alloc_size__(2), __malloc__)); +char *bstrdup(const char *) +    __attribute__((__malloc__, __nonnull__)); +char *bstrndup(const char *, size_t) +    __attribute__((__malloc__, __nonnull__)); +  /*   * Find a test file under BUILD or SOURCE, returning the full path.  The   * returned path should be freed with test_file_path_free(). @@ -120,6 +121,14 @@ char *test_file_path(const char *file)      __attribute__((__malloc__, __nonnull__));  void test_file_path_free(char *path); +/* + * Create a temporary directory relative to BUILD and return the path.  The + * returned path should be freed with test_tmpdir_free. + */ +char *test_tmpdir(void) +    __attribute__((__malloc__)); +void test_tmpdir_free(char *path); +  END_DECLS  #endif /* TAP_BASIC_H */ diff --git a/tests/tap/kerberos.c b/tests/tap/kerberos.c index a17d980..474cf4f 100644 --- a/tests/tap/kerberos.c +++ b/tests/tap/kerberos.c @@ -1,47 +1,90 @@  /*   * 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. + * The core function is kerberos_setup, which loads Kerberos test + * configuration and returns a struct of information.  It also supports + * obtaining initial tickets from the configured keytab and setting up + * KRB5CCNAME and KRB5_KTNAME if a Kerberos keytab is present.  Also included + * are utility functions for setting up a krb5.conf file and reporting + * Kerberos errors or warnings during testing.   * - * Copyright 2006, 2007, 2009, 2010 - *     Board of Trustees, Leland Stanford Jr. University + * Some of the functionality here is only available if the Kerberos libraries + * are available.   * - * See LICENSE for licensing terms. + * The canonical version of this file is maintained in the rra-c-util package, + * which can be found at <http://www.eyrie.org/~eagle/software/rra-c-util/>. + * + * Written by Russ Allbery <rra@stanford.edu> + * Copyright 2006, 2007, 2009, 2010, 2011, 2012 + *     The Board of Trustees of the Leland Stanford Junior University + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE.   */  #include <config.h> -#include <portable/krb5.h> +#ifdef HAVE_KERBEROS +# include <portable/krb5.h> +#endif  #include <portable/system.h> +#include <sys/stat.h> +  #include <tests/tap/basic.h>  #include <tests/tap/kerberos.h> -#include <util/concat.h> -#include <util/xmalloc.h> +#include <tests/tap/process.h> +#include <tests/tap/string.h> + +/* + * Disable the requirement that format strings be literals, since it's easier + * to handle the possible patterns for kinit commands as an array. + */ +#pragma GCC diagnostic ignored "-Wformat-nonliteral"  /* - * 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(). + * These variables hold the allocated configuration struct, the environment to + * point to a different Kerberos ticket cache, keytab, and configuration file, + * and the temporary directories used.  We store them so that we can free them + * on exit for cleaner valgrind output, making it easier to find real memory + * leaks in the tested programs. + */ +static struct kerberos_config *config = NULL; +static char *krb5ccname = NULL; +static char *krb5_ktname = NULL; +static char *krb5_config = NULL; +static char *tmpdir_ticket = NULL; +static char *tmpdir_conf = NULL; + + +/* + * Obtain Kerberos tickets and fill in the principal config entry.   * - * 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. + * There are two implementations of this function, one if we have native + * Kerberos libraries available and one if we don't.  Uses keytab to obtain + * credentials, and fills in the cache member of the provided config struct.   */ -char * -kerberos_setup(void) +#ifdef HAVE_KERBEROS + +static void +kerberos_kinit(void)  { -    char *path, *krbtgt; -    const char *build, *realm; -    FILE *file; -    char principal[BUFSIZ]; +    char *name, *krbtgt;      krb5_error_code code;      krb5_context ctx;      krb5_ccache ccache; @@ -49,89 +92,397 @@ kerberos_setup(void)      krb5_keytab keytab;      krb5_get_init_creds_opt *opts;      krb5_creds creds; +    const char *realm; -    /* Read the principal name and find the keytab file. */ -    path = test_file_path("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 = test_file_path("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. */ +    /* +     * Determine the principal corresponding to that keytab.  We copy the +     * memory to ensure that it's allocated in the right memory domain on +     * systems where that may matter (like Windows). +     */      code = krb5_init_context(&ctx);      if (code != 0) -        bail("error initializing Kerberos"); +        bail_krb5(ctx, code, "error initializing Kerberos"); +    kprinc = kerberos_keytab_principal(ctx, config->keytab); +    code = krb5_unparse_name(ctx, kprinc, &name); +    if (code != 0) +        bail_krb5(ctx, code, "error unparsing name"); +    krb5_free_principal(ctx, kprinc); +    config->principal = bstrdup(name); +    krb5_free_unparsed_name(ctx, name); + +    /* Now do the Kerberos initialization. */      code = krb5_cc_default(ctx, &ccache);      if (code != 0) -        bail("error setting ticket cache"); -    code = krb5_parse_name(ctx, principal, &kprinc); +        bail_krb5(ctx, code, "error setting ticket cache"); +    code = krb5_parse_name(ctx, config->principal, &kprinc);      if (code != 0) -        bail("error parsing principal %s", principal); +        bail_krb5(ctx, code, "error parsing principal %s", config->principal);      realm = krb5_principal_get_realm(ctx, kprinc); -    krbtgt = concat("krbtgt/", realm, "@", realm, (char *) 0); -    code = krb5_kt_resolve(ctx, path, &keytab); +    basprintf(&krbtgt, "krbtgt/%s@%s", realm, realm); +    code = krb5_kt_resolve(ctx, config->keytab, &keytab);      if (code != 0) -        bail("cannot open keytab %s", path); +        bail_krb5(ctx, code, "cannot open keytab %s", config->keytab);      code = krb5_get_init_creds_opt_alloc(ctx, &opts);      if (code != 0) -        bail("cannot allocate credential options"); +        bail_krb5(ctx, code, "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"); +        bail_krb5(ctx, code, "cannot get Kerberos tickets");      code = krb5_cc_initialize(ctx, ccache, kprinc);      if (code != 0) -        bail("error initializing ticket cache"); +        bail_krb5(ctx, code, "error initializing ticket cache");      code = krb5_cc_store_cred(ctx, ccache, &creds);      if (code != 0) -        bail("error storing credentials"); +        bail_krb5(ctx, code, "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_get_init_creds_opt_free(ctx, opts);      krb5_free_context(ctx);      free(krbtgt); -    free(path); +} -    return xstrdup(principal); +#else /* !HAVE_KERBEROS */ + +static void +kerberos_kinit(void) +{ +    static const char * const format[] = { +        "kinit --no-afslog -k -t %s %s >/dev/null 2>&1 </dev/null", +        "kinit -k -t %s %s >/dev/null 2>&1 </dev/null", +        "kinit -t %s %s >/dev/null 2>&1 </dev/null", +        "kinit -k -K %s %s >/dev/null 2>&1 </dev/null" +    }; +    FILE *file; +    char *path; +    char principal[BUFSIZ], *command; +    size_t i; +    int status; + +    /* Read the principal corresponding to the keytab. */ +    path = test_file_path("config/principal"); +    if (path == NULL) { +        test_file_path_free(config->keytab); +        config->keytab = NULL; +        return; +    } +    file = fopen(path, "r"); +    if (file == NULL) { +        test_file_path_free(path); +        return; +    } +    test_file_path_free(path); +    if (fgets(principal, sizeof(principal), file) == NULL) +        bail("cannot read %s", path); +    fclose(file); +    if (principal[strlen(principal) - 1] != '\n') +        bail("no newline in %s", path); +    principal[strlen(principal) - 1] = '\0'; +    config->principal = bstrdup(principal); + +    /* Now do the Kerberos initialization. */ +    for (i = 0; i < ARRAY_SIZE(format); i++) { +        basprintf(&command, format[i], config->keytab, principal); +        status = system(command); +        free(command); +        if (status != -1 && WEXITSTATUS(status) == 0) +            break; +    } +    if (status == -1 || WEXITSTATUS(status) != 0) +        bail("cannot get Kerberos tickets");  } +#endif /* !HAVE_KERBEROS */ +  /* - * Clean up at the end of a test.  Currently, all this does is remove the - * ticket cache. + * Clean up at the end of a test.  This removes the ticket cache and resets + * and frees the memory allocated for the environment variables so that + * valgrind output on test suites is cleaner.   */  void  kerberos_cleanup(void)  {      char *path; -    path = concatpath(getenv("BUILD"), "data/test.cache"); -    unlink(path); -    free(path); +    if (tmpdir_ticket != NULL) { +        basprintf(&path, "%s/krb5cc_test", tmpdir_ticket); +        unlink(path); +        free(path); +        test_tmpdir_free(tmpdir_ticket); +        tmpdir_ticket = NULL; +    } +    if (config != NULL) { +        if (config->keytab != NULL) { +            test_file_path_free(config->keytab); +            free(config->principal); +            free(config->cache); +        } +        if (config->userprinc != NULL) { +            free(config->userprinc); +            free(config->username); +            free(config->password); +        } +        free(config); +        config = NULL; +    } +    if (krb5ccname != NULL) { +        putenv((char *) "KRB5CCNAME="); +        free(krb5ccname); +        krb5ccname = NULL; +    } +    if (krb5_ktname != NULL) { +        putenv((char *) "KRB5_KTNAME="); +        free(krb5_ktname); +        krb5_ktname = NULL; +    } +} + + +/* + * Obtain Kerberos tickets for the principal specified in config/principal + * using the keytab specified in config/keytab, both of which are presumed to + * be in tests in either the build or the source tree.  Also sets KRB5_KTNAME + * and KRB5CCNAME. + * + * Returns the contents of config/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. + */ +struct kerberos_config * +kerberos_setup(enum kerberos_needs needs) +{ +    char *path; +    char buffer[BUFSIZ]; +    FILE *file = NULL; + +    /* If we were called before, clean up after the previous run. */ +    if (config != NULL) +        kerberos_cleanup(); +    config = bcalloc(1, sizeof(struct kerberos_config)); + +    /* +     * If we have a config/keytab file, set the KRB5CCNAME and KRB5_KTNAME +     * environment variables and obtain initial tickets. +     */ +    config->keytab = test_file_path("config/keytab"); +    if (config->keytab == NULL) { +        if (needs == TAP_KRB_NEEDS_KEYTAB || needs == TAP_KRB_NEEDS_BOTH) +            skip_all("Kerberos tests not configured"); +    } else { +        tmpdir_ticket = test_tmpdir(); +        basprintf(&config->cache, "%s/krb5cc_test", tmpdir_ticket); +        basprintf(&krb5ccname, "KRB5CCNAME=%s/krb5cc_test", tmpdir_ticket); +        basprintf(&krb5_ktname, "KRB5_KTNAME=%s", config->keytab); +        putenv(krb5ccname); +        putenv(krb5_ktname); +        kerberos_kinit(); +    } + +    /* +     * If we have a config/password file, read it and fill out the relevant +     * members of our config struct. +     */ +    path = test_file_path("config/password"); +    if (path != NULL) +        file = fopen(path, "r"); +    if (file == NULL) { +        if (needs == TAP_KRB_NEEDS_PASSWORD || needs == TAP_KRB_NEEDS_BOTH) +            skip_all("Kerberos tests not configured"); +    } else { +        if (fgets(buffer, sizeof(buffer), file) == NULL) +            bail("cannot read %s", path); +        if (buffer[strlen(buffer) - 1] != '\n') +            bail("no newline in %s", path); +        buffer[strlen(buffer) - 1] = '\0'; +        config->userprinc = bstrdup(buffer); +        if (fgets(buffer, sizeof(buffer), file) == NULL) +            bail("cannot read password from %s", path); +        fclose(file); +        if (buffer[strlen(buffer) - 1] != '\n') +            bail("password too long in %s", path); +        buffer[strlen(buffer) - 1] = '\0'; +        config->password = bstrdup(buffer); + +        /* +         * Strip the realm from the principal and set realm and username. +         * This is not strictly correct; it doesn't cope with escaped @-signs +         * or enterprise names. +         */ +        config->username = bstrdup(config->userprinc); +        config->realm = strchr(config->username, '@'); +        if (config->realm == NULL) +            bail("test principal has no realm"); +        *config->realm = '\0'; +        config->realm++; +    } +    if (path != NULL) +        test_file_path_free(path); + +    /* +     * Register the cleanup function as an atexit handler so that the caller +     * doesn't have to worry about cleanup. +     */ +    if (atexit(kerberos_cleanup) != 0) +        sysdiag("cannot register cleanup function"); + +    /* Return the configuration. */ +    return config; +} + + +/* + * Clean up the krb5.conf file generated by kerberos_generate_conf and free + * the memory used to set the environment variable.  This doesn't fail if the + * file and variable are already gone, allowing it to be harmlessly run + * multiple times. + * + * Normally called via an atexit handler. + */ +void +kerberos_cleanup_conf(void) +{ +    char *path; + +    if (tmpdir_conf != NULL) { +        basprintf(&path, "%s/krb5.conf", tmpdir_conf); +        unlink(path); +        free(path); +        test_tmpdir_free(tmpdir_conf); +        tmpdir_conf = NULL; +    } +    putenv((char *) "KRB5_CONFIG="); +    if (krb5_config != NULL) { +        free(krb5_config); +        krb5_config = NULL; +    }  } + + +/* + * Generate a krb5.conf file for testing and set KRB5_CONFIG to point to it. + * The [appdefaults] section will be stripped out and the default realm will + * be set to the realm specified, if not NULL.  This will use config/krb5.conf + * in preference, so users can configure the tests by creating that file if + * the system file isn't suitable. + * + * Depends on data/generate-krb5-conf being present in the test suite. + */ +void +kerberos_generate_conf(const char *realm) +{ +    char *path; +    const char *argv[3]; + +    if (tmpdir_conf != NULL) +        kerberos_cleanup_conf(); +    path = test_file_path("data/generate-krb5-conf"); +    if (path == NULL) +        bail("cannot find generate-krb5-conf"); +    argv[0] = path; +    argv[1] = realm; +    argv[2] = NULL; +    run_setup(argv); +    test_file_path_free(path); +    tmpdir_conf = test_tmpdir(); +    basprintf(&krb5_config, "KRB5_CONFIG=%s/krb5.conf", tmpdir_conf); +    putenv(krb5_config); +    if (atexit(kerberos_cleanup_conf) != 0) +        sysdiag("cannot register cleanup function"); +} + + +/* + * The remaining functions in this file are only available if Kerberos + * libraries are available. + */ +#ifdef HAVE_KERBEROS + + +/* + * Report a Kerberos error and bail out. + */ +void +bail_krb5(krb5_context ctx, krb5_error_code code, const char *format, ...) +{ +    const char *k5_msg = NULL; +    char *message; +    va_list args; + +    if (ctx != NULL) +        k5_msg = krb5_get_error_message(ctx, code); +    va_start(args, format); +    bvasprintf(&message, format, args); +    va_end(args); +    if (k5_msg == NULL) +        bail("%s", message); +    else +        bail("%s: %s", message, k5_msg); +} + + +/* + * Report a Kerberos error as a diagnostic to stderr. + */ +void +diag_krb5(krb5_context ctx, krb5_error_code code, const char *format, ...) +{ +    const char *k5_msg = NULL; +    char *message; +    va_list args; + +    if (ctx != NULL) +        k5_msg = krb5_get_error_message(ctx, code); +    va_start(args, format); +    bvasprintf(&message, format, args); +    va_end(args); +    if (k5_msg == NULL) +        diag("%s", message); +    else +        diag("%s: %s", message, k5_msg); +    free(message); +    if (k5_msg != NULL) +        krb5_free_error_message(ctx, k5_msg); +} + + +/* + * Find the principal of the first entry of a keytab and return it.  The + * caller is responsible for freeing the result with krb5_free_principal. + * Exit on error. + */ +krb5_principal +kerberos_keytab_principal(krb5_context ctx, const char *path) +{ +    krb5_keytab keytab; +    krb5_kt_cursor cursor; +    krb5_keytab_entry entry; +    krb5_principal princ; +    krb5_error_code status; + +    status = krb5_kt_resolve(ctx, path, &keytab); +    if (status != 0) +        bail_krb5(ctx, status, "error opening %s", path); +    status = krb5_kt_start_seq_get(ctx, keytab, &cursor); +    if (status != 0) +        bail_krb5(ctx, status, "error reading %s", path); +    status = krb5_kt_next_entry(ctx, keytab, &entry, &cursor); +    if (status == 0) { +        status = krb5_copy_principal(ctx, entry.principal, &princ); +        if (status != 0) +            bail_krb5(ctx, status, "error copying principal from %s", path); +        krb5_kt_free_entry(ctx, &entry); +    } +    if (status != 0) +        bail("no principal found in keytab file %s", path); +    krb5_kt_end_seq_get(ctx, keytab, &cursor); +    krb5_kt_close(ctx, keytab); +    return princ; +} + +#endif /* HAVE_KERBEROS */ diff --git a/tests/tap/kerberos.h b/tests/tap/kerberos.h index 1c64f70..31b6343 100644 --- a/tests/tap/kerberos.h +++ b/tests/tap/kerberos.h @@ -1,32 +1,125 @@  /*   * Utility functions for tests that use Kerberos.   * - * Copyright 2006, 2007, 2009 - *     Board of Trustees, Leland Stanford Jr. University + * The canonical version of this file is maintained in the rra-c-util package, + * which can be found at <http://www.eyrie.org/~eagle/software/rra-c-util/>.   * - * See LICENSE for licensing terms. + * Written by Russ Allbery <rra@stanford.edu> + * Copyright 2006, 2007, 2009, 2011, 2012 + *     The Board of Trustees of the Leland Stanford Junior University + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE.   */  #ifndef TAP_KERBEROS_H  #define TAP_KERBEROS_H 1  #include <config.h> -#include <portable/macros.h> +#include <tests/tap/macros.h> + +#ifdef HAVE_KERBEROS +# include <portable/krb5.h> +#endif + +/* Holds the information parsed from the Kerberos test configuration. */ +struct kerberos_config { +    char *keytab;               /* Path to the keytab. */ +    char *principal;            /* Principal whose keys are in the keytab. */ +    char *cache;                /* Path to the Kerberos ticket cache. */ +    char *userprinc;            /* The fully-qualified principal. */ +    char *username;             /* The local (non-realm) part of principal. */ +    char *realm;                /* The realm part of the principal. */ +    char *password;             /* The password. */ +}; + +/* + * Whether to skip all tests (by calling skip_all) in kerberos_setup if + * certain configuration information isn't available. + */ +enum kerberos_needs { +    TAP_KRB_NEEDS_NONE, +    TAP_KRB_NEEDS_KEYTAB, +    TAP_KRB_NEEDS_PASSWORD, +    TAP_KRB_NEEDS_BOTH +};  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(). + * Set up Kerberos, returning the test configuration information.  This + * obtains Kerberos tickets from config/keytab, if one is present, and stores + * them in a Kerberos ticket cache, sets KRB5_KTNAME and KRB5CCNAME.  It also + * loads the principal and password from config/password, if it exists, and + * stores the principal, password, username, and realm in the returned struct. + * + * If there is no config/keytab file, KRB5_KTNAME and KRB5CCNAME won't be set + * and the keytab field will be NULL.  If there is no config/password file, + * the principal field will be NULL.  If the files exist but loading them + * fails, or authentication fails, kerberos_setup calls bail. + * + * kerberos_cleanup will be set up to run from an atexit handler.  This means + * that any child processes that should not remove the Kerberos ticket cache + * should call _exit instead of exit.  The principal will be automatically + * freed when kerberos_cleanup is called or if kerberos_setup is called again. + * The caller doesn't need to worry about it.   */ -char *kerberos_setup(void) +struct kerberos_config *kerberos_setup(enum kerberos_needs)      __attribute__((__malloc__)); - -/* Clean up at the end of a test. */  void kerberos_cleanup(void); +/* + * Generate a krb5.conf file for testing and set KRB5_CONFIG to point to it. + * The [appdefaults] section will be stripped out and the default realm will + * be set to the realm specified, if not NULL.  This will use config/krb5.conf + * in preference, so users can configure the tests by creating that file if + * the system file isn't suitable. + * + * Depends on data/generate-krb5-conf being present in the test suite. + * + * kerberos_cleanup_conf will clean up after this function, but usually + * doesn't need to be called directly since it's registered as an atexit + * handler. + */ +void kerberos_generate_conf(const char *realm); +void kerberos_cleanup_conf(void); + +/* Thes interfaces are only available with native Kerberos support. */ +#ifdef HAVE_KERBEROS + +/* Bail out with an error, appending the Kerberos error message. */ +void bail_krb5(krb5_context, krb5_error_code, const char *format, ...) +    __attribute__((__noreturn__, __nonnull__, __format__(printf, 3, 4))); + +/* Report a diagnostic with Kerberos error to stderr prefixed with #. */ +void diag_krb5(krb5_context, krb5_error_code, const char *format, ...) +    __attribute__((__nonnull__, __format__(printf, 3, 4))); + +/* + * Given a Kerberos context and the path to a keytab, retrieve the principal + * for the first entry in the keytab and return it.  Calls bail on failure. + * The returned principal should be freed with krb5_free_principal. + */ +krb5_principal kerberos_keytab_principal(krb5_context, const char *path) +    __attribute__((__nonnull__)); + +#endif /* HAVE_KERBEROS */ +  END_DECLS  #endif /* !TAP_MESSAGES_H */ diff --git a/tests/tap/kerberos.sh b/tests/tap/kerberos.sh index 904cae5..d2f174d 100644 --- a/tests/tap/kerberos.sh +++ b/tests/tap/kerberos.sh @@ -1,30 +1,61 @@  # Shell function library to initialize Kerberos credentials  # +# Note that while many of the functions in this library could benefit from +# using "local" to avoid possibly hammering global variables, Solaris /bin/sh +# doesn't support local and this library aspires to be portable to Solaris +# Bourne shell.  Instead, all private variables are prefixed with "tap_". +# +# The canonical version of this file is maintained in the rra-c-util package, +# which can be found at <http://www.eyrie.org/~eagle/software/rra-c-util/>. +#  # Written by Russ Allbery <rra@stanford.edu> -# Copyright 2009, 2010 Board of Trustees, Leland Stanford Jr. University +# Copyright 2009, 2010, 2011, 2012 +#     The Board of Trustees of the Leland Stanford Junior University +# +# Permission is hereby granted, free of charge, to any person obtaining a +# copy of this software and associated documentation files (the "Software"), +# to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, +# and/or sell copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software.  # -# See LICENSE for licensing terms. +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL +# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +# DEALINGS IN THE SOFTWARE. + +# We use test_tmpdir. +. "${SOURCE}/tap/libtap.sh"  # 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=`test_file_path data/test.keytab` -    principal=`test_file_path data/test.principal` +    tap_keytab=`test_file_path config/keytab` +    principal=`test_file_path config/principal`      principal=`cat "$principal" 2>/dev/null` -    if [ -z "$keytab" ] || [ -z "$principal" ] ; then +    if [ -z "$tap_keytab" ] || [ -z "$principal" ] ; then          return 1      fi -    KRB5CCNAME="$BUILD/data/test.cache"; export KRB5CCNAME -    kinit -k -t "$keytab" "$principal" >/dev/null </dev/null +    KRB5CCNAME=`test_tmpdir`/krb5cc_test; export KRB5CCNAME +    kinit --no-afslog -k -t "$tap_keytab" "$principal" >/dev/null </dev/null      status=$?      if [ $status != 0 ] ; then -        kinit -t "$keytab" "$principal" >/dev/null </dev/null +        kinit -k -t "$tap_keytab" "$principal" >/dev/null </dev/null +        status=$? +    fi +    if [ $status != 0 ] ; then +        kinit -t "$tap_keytab" "$principal" >/dev/null </dev/null          status=$?      fi      if [ $status != 0 ] ; then -        kinit -k -K "$keytab" "$principal" >/dev/null </dev/null +        kinit -k -K "$tap_keytab" "$principal" >/dev/null </dev/null          status=$?      fi      if [ $status != 0 ] ; then @@ -35,7 +66,8 @@ kerberos_setup () {  # Clean up at the end of a test.  Currently only removes the ticket cache.  kerberos_cleanup () { -    rm -f "$BUILD/data/test.cache" +    tap_tmp=`test_tmpdir` +    rm -f "$tap_tmp"/krb5cc_test  }  # List the contents of a keytab with enctypes and keys.  This adjusts for the @@ -44,11 +76,13 @@ kerberos_cleanup () {  # may just hang.  Takes the keytab to list and the file into which to save the  # output, and strips off the header containing the file name.  ktutil_list () { -    if klist -keK "$1" > ktutil-tmp 2>/dev/null ; then +    tap_tmp=`test_tmpdir` +    if klist -keK "$1" > "$tap_tmp"/ktutil-tmp 2>/dev/null ; then          :      else -        ktutil -k "$1" list --keys > ktutil-tmp < /dev/null 2>/dev/null +        ktutil -k "$1" list --keys > "$tap_tmp"/ktutil-tmp </dev/null \ +            2>/dev/null      fi -    sed -e '/Keytab name:/d' -e "/^[^ ]*:/d" ktutil-tmp > "$2" -    rm -f ktutil-tmp +    sed -e '/Keytab name:/d' -e "/^[^ ]*:/d" "$tap_tmp"/ktutil-tmp > "$2" +    rm -f "$tap_tmp"/ktutil-tmp  } diff --git a/tests/tap/libtap.sh b/tests/tap/libtap.sh index a9b46d4..f9347d8 100644 --- a/tests/tap/libtap.sh +++ b/tests/tap/libtap.sh @@ -1,10 +1,36 @@  # Shell function library for test cases.  # +# Note that while many of the functions in this library could benefit from +# using "local" to avoid possibly hammering global variables, Solaris /bin/sh +# doesn't support local and this library aspires to be portable to Solaris +# Bourne shell.  Instead, all private variables are prefixed with "tap_". +# +# This file provides a TAP-compatible shell function library useful for +# writing test cases.  It is part of C TAP Harness, which can be found at +# <http://www.eyrie.org/~eagle/software/c-tap-harness/>. +#  # Written by Russ Allbery <rra@stanford.edu> -# Copyright 2009, 2010 Russ Allbery <rra@stanford.edu> -# Copyright 2006, 2007, 2008 Board of Trustees, Leland Stanford Jr. University +# Copyright 2009, 2010, 2011, 2012 Russ Allbery <rra@stanford.edu> +# Copyright 2006, 2007, 2008 +#     The Board of Trustees of the Leland Stanford Junior University +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to +# deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +# sell copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions:  # -# See LICENSE for licensing terms. +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +# IN THE SOFTWARE.  # Print out the number of test cases we expect to run.  plan () { @@ -25,33 +51,35 @@ plan_lazy () {  # Report the test status on exit.  finish () { -    local highest looks -    highest=`expr "$count" - 1` +    tap_highest=`expr "$count" - 1`      if [ "$planned" = 0 ] ; then -        echo "1..$highest" -        planned="$highest" +        echo "1..$tap_highest" +        planned="$tap_highest"      fi -    looks='# Looks like you' +    tap_looks='# Looks like you'      if [ "$planned" -gt 0 ] ; then -        if [ "$planned" -gt "$highest" ] ; then +        if [ "$planned" -gt "$tap_highest" ] ; then              if [ "$planned" -gt 1 ] ; then -                echo "$looks planned $planned tests but only ran $highest" +                echo "$tap_looks planned $planned tests but only ran" \ +                    "$tap_highest"              else -                echo "$looks planned $planned test but only ran $highest" +                echo "$tap_looks planned $planned test but only ran" \ +                    "$tap_highest"              fi -        elif [ "$planned" -lt "$highest" ] ; then -            local extra -            extra=`expr "$highest" - "$planned"` +        elif [ "$planned" -lt "$tap_highest" ] ; then +            tap_extra=`expr "$tap_highest" - "$planned"`              if [ "$planned" -gt 1 ] ; then -                echo "$looks planned $planned tests but ran $extra extra" +                echo "$tap_looks planned $planned tests but ran" \ +                    "$tap_extra extra"              else -                echo "$looks planned $planned test but ran $extra extra" +                echo "$tap_looks planned $planned test but ran" \ +                    "$tap_extra extra"              fi          elif [ "$failed" -gt 0 ] ; then              if [ "$failed" -gt 1 ] ; then -                echo "$looks failed $failed tests of $planned" +                echo "$tap_looks failed $failed tests of $planned"              else -                echo "$looks failed $failed test of $planned" +                echo "$tap_looks failed $failed test of $planned"              fi          elif [ "$planned" -gt 1 ] ; then              echo "# All $planned tests successful or skipped" @@ -63,10 +91,9 @@ finish () {  # 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" +    tap_desc="$1" +    if [ -n "$tap_desc" ] ; then +        echo "1..0 # skip $tap_desc"      else          echo "1..0 # skip"      fi @@ -77,16 +104,15 @@ skip_all () {  # 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" +    tap_desc="$1" +    if [ -n "$tap_desc" ] ; then +        tap_desc=" - $tap_desc"      fi      shift      if "$@" ; then -        echo ok $count$desc +        echo ok "$count$tap_desc"      else -        echo not ok $count$desc +        echo not ok "$count$tap_desc"          failed=`expr $failed + 1`      fi      count=`expr $count + 1` @@ -101,58 +127,80 @@ skip () {  # 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" +    tap_i=$count +    tap_end=`expr $count + $1`      shift -    while [ "$i" -lt "$end" ] ; do -        ok "$desc" "$@" -        i=`expr $i + 1` +    while [ "$tap_i" -lt "$tap_end" ] ; do +        ok "$@" +        tap_i=`expr $tap_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` +    tap_i=$count +    tap_end=`expr $count + $1`      shift -    while [ "$i" -lt "$end" ] ; do +    while [ "$tap_i" -lt "$tap_end" ] ; do          skip "$@" -        i=`expr $i + 1` +        tap_i=`expr $tap_i + 1`      done  } +# Portable variant of printf '%s\n' "$*".  In the majority of cases, this +# function is slower than printf, because the latter is often implemented +# as a builtin command.  The value of the variable IFS is ignored. +# +# This macro must not be called via backticks inside double quotes, since this +# will result in bizarre escaping behavior and lots of extra backslashes on +# Solaris. +puts () { +    cat << EOH +$@ +EOH +} +  # 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. +# output, the command to run, and then any arguments for that command. +# Standard output and standard error are combined when analyzing the output of +# the command. +# +# If the command may contain system-specific error messages in its output, +# add strip_colon_error before the command to post-process its output.  ok_program () { -    local desc w_status w_output output status -    desc="$1" +    tap_desc="$1"      shift -    w_status="$1" +    tap_w_status="$1"      shift -    w_output="$1" +    tap_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 +    tap_output=`"$@" 2>&1` +    tap_status=$? +    if [ $tap_status = $tap_w_status ] \ +        && [ x"$tap_output" = x"$tap_w_output" ] ; then +        ok "$tap_desc" true      else -        echo "#  saw: ($status) $output" -        echo "#  not: ($w_status) $w_output" -        ok "$desc" false +        echo "#  saw: ($tap_status) $tap_output" +        echo "#  not: ($tap_w_status) $tap_w_output" +        ok "$tap_desc" false      fi  } +# Strip a colon and everything after it off the output of a command, as long +# as that colon comes after at least one whitespace character.  (This is done +# to avoid stripping the name of the program from the start of an error +# message.)  This is used to remove system-specific error messages (coming +# from strerror, for example). +strip_colon_error() { +    tap_output=`"$@" 2>&1` +    tap_status=$? +    tap_output=`puts "$tap_output" | sed 's/^\([^ ]* [^:]*\):.*/\1/'` +    puts "$tap_output" +    return $tap_status +} +  # Bail out with an error message.  bail () {      echo 'Bail out!' "$@" @@ -167,12 +215,32 @@ diag () {  # Search for the given file first in $BUILD and then in $SOURCE and echo the  # path where the file was found, or the empty string if the file wasn't  # found. +# +# This macro uses puts, so don't run it using backticks inside double quotes +# or bizarre quoting behavior will happen with Solaris sh.  test_file_path () { -    if [ -f "$BUILD/$1" ] ; then -        echo "$BUILD/$1" -    elif [ -f "$SOURCE/$1" ] ; then -        echo "$SOURCE/$1" +    if [ -n "$BUILD" ] && [ -f "$BUILD/$1" ] ; then +        puts "$BUILD/$1" +    elif [ -n "$SOURCE" ] && [ -f "$SOURCE/$1" ] ; then +        puts "$SOURCE/$1"      else          echo ''      fi  } + +# Create $BUILD/tmp for use by tests for storing temporary files and return +# the path (via standard output). +# +# This macro uses puts, so don't run it using backticks inside double quotes +# or bizarre quoting behavior will happen with Solaris sh. +test_tmpdir () { +    if [ -z "$BUILD" ] ; then +        tap_tmpdir="./tmp" +    else +        tap_tmpdir="$BUILD"/tmp +    fi +    if [ ! -d "$tap_tmpdir" ] ; then +        mkdir "$tap_tmpdir" || bail "Error creating $tap_tmpdir" +    fi +    puts "$tap_tmpdir" +} diff --git a/tests/tap/macros.h b/tests/tap/macros.h new file mode 100644 index 0000000..33fee42 --- /dev/null +++ b/tests/tap/macros.h @@ -0,0 +1,88 @@ +/* + * Helpful macros for TAP header files. + * + * This is not, strictly speaking, related to TAP, but any TAP add-on is + * probably going to need these macros, so define them in one place so that + * everyone can pull them in. + * + * This file is part of C TAP Harness.  The current version plus supporting + * documentation is at <http://www.eyrie.org/~eagle/software/c-tap-harness/>. + * + * Copyright 2008, 2012 Russ Allbery <rra@stanford.edu> + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef TAP_MACROS_H +#define TAP_MACROS_H 1 + +/* + * __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), and only with gcc 2.96 can you use + * the attribute __malloc__.  2.96 is very old, so don't bother trying to get + * the other attributes to work with GCC versions between 2.7 and 2.96. + */ +#ifndef __attribute__ +# if __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 96) +#  define __attribute__(spec)   /* empty */ +# endif +#endif + +/* + * We use __alloc_size__, but it was only available in fairly recent versions + * of GCC.  Suppress warnings about the unknown attribute if GCC is too old. + * We know that we're GCC at this point, so we can use the GCC variadic macro + * extension, which will still work with versions of GCC too old to have C99 + * variadic macro support. + */ +#if !defined(__attribute__) && !defined(__alloc_size__) +# if __GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 3) +#  define __alloc_size__(spec, args...) /* empty */ +# endif +#endif + +/* + * LLVM and Clang pretend to be GCC but don't support all of the __attribute__ + * settings that GCC does.  For them, suppress warnings about unknown + * attributes on declarations.  This unfortunately will affect the entire + * compilation context, but there's no push and pop available. + */ +#if !defined(__attribute__) && (defined(__llvm__) || defined(__clang__)) +# pragma GCC diagnostic ignored "-Wattributes" +#endif + +/* Used for unused parameters to silence gcc warnings. */ +#define UNUSED __attribute__((__unused__)) + +/* + * 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 + +#endif /* TAP_MACROS_H */ diff --git a/tests/tap/messages.c b/tests/tap/messages.c index 3bb9a1a..abc2c49 100644 --- a/tests/tap/messages.c +++ b/tests/tap/messages.c @@ -5,24 +5,39 @@   * 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 + * The canonical version of this file is maintained in the rra-c-util package, + * which can be found at <http://www.eyrie.org/~eagle/software/rra-c-util/>.   * - * See LICENSE for licensing terms. + * Copyright 2002, 2004, 2005 Russ Allbery <rra@stanford.edu> + * Copyright 2006, 2007, 2009, 2012 + *     The Board of Trustees of the Leland Stanford Junior University + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE.   */  #include <config.h>  #include <portable/system.h> +#include <tests/tap/macros.h>  #include <tests/tap/messages.h> -#include <util/concat.h> -#include <util/macros.h> +#include <tests/tap/string.h>  #include <util/messages.h> -#include <util/xmalloc.h>  /* A global buffer into which message_log_buffer stores error messages. */  char *errors = NULL; @@ -33,18 +48,18 @@ char *errors = NULL;   * error_capture.   */  static void -message_log_buffer(int len, const char *fmt, va_list args, int error UNUSED) +message_log_buffer(int len UNUSED, 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 { +    bvasprintf(&message, fmt, args); +    if (errors == NULL) +        basprintf(&errors, "%s\n", message); +    else {          char *new_errors; -        new_errors = concat(errors, message, "\n", (char *) 0); +        basprintf(&new_errors, "%s%s\n", errors, message);          free(errors);          errors = new_errors;      } diff --git a/tests/tap/messages.h b/tests/tap/messages.h index 2b9a7db..0544f2d 100644 --- a/tests/tap/messages.h +++ b/tests/tap/messages.h @@ -1,21 +1,37 @@  /*   * Utility functions to test message handling.   * + * The canonical version of this file is maintained in the rra-c-util package, + * which can be found at <http://www.eyrie.org/~eagle/software/rra-c-util/>. + * + * Copyright 2002 Russ Allbery <rra@stanford.edu>   * Copyright 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 + *     The Board of Trustees of the Leland Stanford Junior University + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software.   * - * See LICENSE for licensing terms. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE.   */  #ifndef TAP_MESSAGES_H  #define TAP_MESSAGES_H 1  #include <config.h> -#include <portable/macros.h> +#include <tests/tap/macros.h>  /* A global buffer into which errors_capture stores errors. */  extern char *errors; diff --git a/tests/tap/perl/Test/RRA.pm b/tests/tap/perl/Test/RRA.pm new file mode 100644 index 0000000..3035c7a --- /dev/null +++ b/tests/tap/perl/Test/RRA.pm @@ -0,0 +1,222 @@ +# Helper functions for test programs written in Perl. +# +# This module provides a collection of helper functions used by test programs +# written in Perl.  This is a general collection of functions that can be used +# by both C packages with Automake and by stand-alone Perl modules.  See +# Test::RRA::Automake for additional functions specifically for C Automake +# distributions. +# +# The canonical version of this file is maintained in the rra-c-util package, +# which can be found at <http://www.eyrie.org/~eagle/software/rra-c-util/>. +# +# Written by Russ Allbery <rra@stanford.edu> +# Copyright 2013 +#     The Board of Trustees of the Leland Stanford Junior University +# +# Permission is hereby granted, free of charge, to any person obtaining a +# copy of this software and associated documentation files (the "Software"), +# to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, +# and/or sell copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL +# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +# DEALINGS IN THE SOFTWARE. + +package Test::RRA; + +use 5.006; +use strict; +use warnings; + +use Exporter; +use Test::More; + +# For Perl 5.006 compatibility. +## no critic (ClassHierarchies::ProhibitExplicitISA) + +# Declare variables that should be set in BEGIN for robustness. +our (@EXPORT_OK, @ISA, $VERSION); + +# Set $VERSION and everything export-related in a BEGIN block for robustness +# against circular module loading (not that we load any modules, but +# consistency is good). +BEGIN { +    @ISA       = qw(Exporter); +    @EXPORT_OK = qw(skip_unless_maintainer use_prereq); + +    # This version should match the corresponding rra-c-util release, but with +    # two digits for the minor version, including a leading zero if necessary, +    # so that it will sort properly. +    $VERSION = '4.08'; +} + +# Skip this test unless maintainer tests are requested.  Takes a short +# description of what tests this script would perform, which is used in the +# skip message.  Calls plan skip_all, which will terminate the program. +# +# $description - Short description of the tests +# +# Returns: undef +sub skip_unless_maintainer { +    my ($description) = @_; +    if (!$ENV{RRA_MAINTAINER_TESTS}) { +        plan skip_all => "$description only run for maintainer"; +    } +    return; +} + +# Attempt to load a module and skip the test if the module could not be +# loaded.  If the module could be loaded, call its import function manually. +# If the module could not be loaded, calls plan skip_all, which will terminate +# the program. +# +# The special logic here is based on Test::More and is required to get the +# imports to happen in the caller's namespace. +# +# $module  - Name of the module to load +# @imports - Any arguments to import, possibly including a version +# +# Returns: undef +sub use_prereq { +    my ($module, @imports) = @_; + +    # If the first import looks like a version, pass it as a bare string. +    my $version = q{}; +    if (@imports >= 1 && $imports[0] =~ m{ \A \d+ (?: [.]\d+ )* \z }xms) { +        $version = shift(@imports); +    } + +    # Get caller information to put imports in the correct package. +    my ($package) = caller; + +    # Do the import with eval, and try to isolate it from the surrounding +    # context as much as possible.  Based heavily on Test::More::_eval. +    ## no critic (BuiltinFunctions::ProhibitStringyEval) +    ## no critic (ValuesAndExpressions::ProhibitImplicitNewlines) +    my ($result, $error, $sigdie); +    { +        local $@            = undef; +        local $!            = undef; +        local $SIG{__DIE__} = undef; +        $result = eval qq{ +            package $package; +            use $module $version \@imports; +            1; +        }; +        $error = $@; +        $sigdie = $SIG{__DIE__} || undef; +    } + +    # If the use failed for any reason, skip the test. +    if (!$result || $error) { +        plan skip_all => "$module required for test"; +    } + +    # If the module set $SIG{__DIE__}, we cleared that via local.  Restore it. +    ## no critic (Variables::RequireLocalizedPunctuationVars) +    if (defined($sigdie)) { +        $SIG{__DIE__} = $sigdie; +    } +    return; +} + +1; +__END__ + +=for stopwords +Allbery Allbery's DESC bareword sublicense MERCHANTABILITY NONINFRINGEMENT +rra-c-util + +=head1 NAME + +Test::RRA - Support functions for Perl tests + +=head1 SYNOPSIS + +    use Test::RRA qw(skip_unless_maintainer use_prereq); + +    # Skip this test unless maintainer tests are requested. +    skip_unless_maintainer('Coding style tests'); + +    # Load modules, skipping the test if they're not available. +    use_prereq('File::Slurp'); +    use_prereq('Test::Script::Run', '0.04'); + +=head1 DESCRIPTION + +This module collects utility functions that are useful for Perl test +scripts.  It assumes Russ Allbery's Perl module layout and test +conventions and will only be useful for other people if they use the +same conventions. + +=head1 FUNCTIONS + +None of these functions are imported by default.  The ones used by a +script should be explicitly imported. + +=over 4 + +=item skip_unless_maintainer(DESC) + +Checks whether RRA_MAINTAINER_TESTS is set in the environment and skips +the whole test (by calling C<plan skip_all> from Test::More) if it is not. +DESC is a description of the tests being skipped.  A space and C<only run +for maintainer> will be appended to it and used as the skip reason. + +=item use_prereq(MODULE[, VERSION][, IMPORT ...]) + +Attempts to load MODULE with the given VERSION and import arguments.  If +this fails for any reason, the test will be skipped (by calling C<plan +skip_all> from Test::More) with a skip reason saying that MODULE is +required for the test. + +VERSION will be passed to C<use> as a version bareword if it looks like a +version number.  The remaining IMPORT arguments will be passed as the +value of an array. + +=back + +=head1 AUTHOR + +Russ Allbery <rra@stanford.edu> + +=head1 COPYRIGHT AND LICENSE + +Copyright 2013 The Board of Trustees of the Leland Stanford Junior +University + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + +=head1 SEE ALSO + +Test::More(3), Test::RRA::Automake(3), Test::RRA::Config(3) + +This module is maintained in the rra-c-util package.  The current version +is available from L<http://www.eyrie.org/~eagle/software/rra-c-util/>. + +=cut diff --git a/tests/tap/perl/Test/RRA/Automake.pm b/tests/tap/perl/Test/RRA/Automake.pm new file mode 100644 index 0000000..5dde32d --- /dev/null +++ b/tests/tap/perl/Test/RRA/Automake.pm @@ -0,0 +1,362 @@ +# Helper functions for Perl test programs in Automake distributions. +# +# This module provides a collection of helper functions used by test programs +# written in Perl and included in C source distributions that use Automake. +# They embed knowledge of how I lay out my source trees and test suites with +# Autoconf and Automake.  They may be usable by others, but doing so will +# require closely following the conventions implemented by the rra-c-util +# utility collection. +# +# All the functions here assume that BUILD and SOURCE are set in the +# environment.  This is normally done via the C TAP Harness runtests wrapper. +# +# The canonical version of this file is maintained in the rra-c-util package, +# which can be found at <http://www.eyrie.org/~eagle/software/rra-c-util/>. +# +# Written by Russ Allbery <rra@stanford.edu> +# Copyright 2013 +#     The Board of Trustees of the Leland Stanford Junior University +# +# Permission is hereby granted, free of charge, to any person obtaining a +# copy of this software and associated documentation files (the "Software"), +# to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, +# and/or sell copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL +# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +# DEALINGS IN THE SOFTWARE. + +package Test::RRA::Automake; + +use 5.006; +use strict; +use warnings; + +# For Perl 5.006 compatibility. +## no critic (ClassHierarchies::ProhibitExplicitISA) + +use Exporter; +use File::Spec; +use Test::More; +use Test::RRA::Config qw($LIBRARY_PATH); + +# Used below for use lib calls. +my ($PERL_BLIB_ARCH, $PERL_BLIB_LIB); + +# Determine the path to the build tree of any embedded Perl module package in +# this source package.  We do this in a BEGIN block because we're going to use +# the results in a use lib command below. +BEGIN { +    $PERL_BLIB_ARCH = File::Spec->catdir(qw(perl blib arch)); +    $PERL_BLIB_LIB  = File::Spec->catdir(qw(perl blib lib)); + +    # If BUILD is set, we can come up with better values. +    if (defined($ENV{BUILD})) { +        my ($vol, $dirs) = File::Spec->splitpath($ENV{BUILD}, 1); +        my @dirs = File::Spec->splitdir($dirs); +        pop(@dirs); +        $PERL_BLIB_ARCH = File::Spec->catdir(@dirs, qw(perl blib arch)); +        $PERL_BLIB_LIB  = File::Spec->catdir(@dirs, qw(perl blib lib)); +    } +} + +# Prefer the modules built as part of our source package.  Otherwise, we may +# not find Perl modules while testing, or find the wrong versions. +use lib $PERL_BLIB_ARCH; +use lib $PERL_BLIB_LIB; + +# Declare variables that should be set in BEGIN for robustness. +our (@EXPORT_OK, @ISA, $VERSION); + +# Set $VERSION and everything export-related in a BEGIN block for robustness +# against circular module loading (not that we load any modules, but +# consistency is good). +BEGIN { +    @ISA       = qw(Exporter); +    @EXPORT_OK = qw(automake_setup perl_dirs test_file_path); + +    # This version should match the corresponding rra-c-util release, but with +    # two digits for the minor version, including a leading zero if necessary, +    # so that it will sort properly. +    $VERSION = '4.08'; +} + +# Perl directories to skip globally for perl_dirs.  We ignore the perl +# directory if it exists since, in my packages, it is treated as a Perl module +# distribution and has its own standalone test suite. +my @GLOBAL_SKIP = qw(.git perl); + +# Perform initial test setup for running a Perl test in an Automake package. +# This verifies that BUILD and SOURCE are set and then changes directory to +# the SOURCE directory by default.  Sets LD_LIBRARY_PATH if the $LIBRARY_PATH +# configuration option is set.  Calls BAIL_OUT if BUILD or SOURCE are missing +# or if anything else fails. +# +# $args_ref - Reference to a hash of arguments to configure behavior: +#   chdir_build - If set to a true value, changes to BUILD instead of SOURCE +# +# Returns: undef +sub automake_setup { +    my ($args_ref) = @_; + +    # Bail if BUILD or SOURCE are not set. +    if (!$ENV{BUILD}) { +        BAIL_OUT('BUILD not defined (run under runtests)'); +    } +    if (!$ENV{SOURCE}) { +        BAIL_OUT('SOURCE not defined (run under runtests)'); +    } + +    # BUILD or SOURCE will be the test directory.  Change to the parent. +    my $start = $args_ref->{chdir_build} ? $ENV{BUILD} : $ENV{SOURCE}; +    my ($vol, $dirs) = File::Spec->splitpath($start, 1); +    my @dirs = File::Spec->splitdir($dirs); +    pop(@dirs); +    if ($dirs[-1] eq File::Spec->updir) { +        pop(@dirs); +        pop(@dirs); +    } +    my $root = File::Spec->catpath($vol, File::Spec->catdir(@dirs), q{}); +    chdir($root) or BAIL_OUT("cannot chdir to $root: $!"); + +    # If BUILD is a subdirectory of SOURCE, add it to the global ignore list. +    my ($buildvol, $builddirs) = File::Spec->splitpath($ENV{BUILD}, 1); +    my @builddirs = File::Spec->splitdir($builddirs); +    pop(@builddirs); +    if ($buildvol eq $vol && @builddirs == @dirs + 1) { +        while (@dirs && $builddirs[0] eq $dirs[0]) { +            shift(@builddirs); +            shift(@dirs); +        } +        if (@builddirs == 1) { +            push(@GLOBAL_SKIP, $builddirs[0]); +        } +    } + +    # Set LD_LIBRARY_PATH if the $LIBRARY_PATH configuration option is set. +    ## no critic (Variables::RequireLocalizedPunctuationVars) +    if (defined($LIBRARY_PATH)) { +        @builddirs = File::Spec->splitdir($builddirs); +        pop(@builddirs); +        my $libdir = File::Spec->catdir(@builddirs, $LIBRARY_PATH); +        my $path = File::Spec->catpath($buildvol, $libdir, q{}); +        if (-d "$path/.libs") { +            $path .= '/.libs'; +        } +        if ($ENV{LD_LIBRARY_PATH}) { +            $ENV{LD_LIBRARY_PATH} .= ":$path"; +        } else { +            $ENV{LD_LIBRARY_PATH} = $path; +        } +    } +    return; +} + +# Returns a list of directories that may contain Perl scripts and that should +# be passed to Perl test infrastructure that expects a list of directories to +# recursively check.  The list will be all eligible top-level directories in +# the package except for the tests directory, which is broken out to one +# additional level.  Calls BAIL_OUT on any problems +# +# $args_ref - Reference to a hash of arguments to configure behavior: +#   skip - A reference to an array of directories to skip +# +# Returns: List of directories possibly containing Perl scripts to test +sub perl_dirs { +    my ($args_ref) = @_; + +    # Add the global skip list. +    my @skip = $args_ref->{skip} ? @{ $args_ref->{skip} } : (); +    push(@skip, @GLOBAL_SKIP); + +    # Separate directories to skip under tests from top-level directories. +    my @skip_tests = grep { m{ \A tests/ }xms } @skip; +    @skip = grep { !m{ \A tests }xms } @skip; +    for my $skip_dir (@skip_tests) { +        $skip_dir =~ s{ \A tests/ }{}xms; +    } + +    # Convert the skip lists into hashes for convenience. +    my %skip = map { $_ => 1 } @skip, 'tests'; +    my %skip_tests = map { $_ => 1 } @skip_tests; + +    # Build the list of top-level directories to test. +    opendir(my $rootdir, q{.}) or BAIL_OUT("cannot open .: $!"); +    my @dirs = grep { -d $_ && !$skip{$_} } readdir($rootdir); +    closedir($rootdir); +    @dirs = File::Spec->no_upwards(@dirs); + +    # Add the list of subdirectories of the tests directory. +    if (-d 'tests') { +        opendir(my $testsdir, q{tests}) or BAIL_OUT("cannot open tests: $!"); + +        # Skip if found in %skip_tests or if not a directory. +        my $is_skipped = sub { +            my ($dir) = @_; +            return 1 if $skip_tests{$dir}; +            $dir = File::Spec->catdir('tests', $dir); +            return -d $dir ? 0 : 1; +        }; + +        # Build the filtered list of subdirectories of tests. +        my @test_dirs = grep { !$is_skipped->($_) } readdir($testsdir); +        closedir($testsdir); +        @test_dirs = File::Spec->no_upwards(@test_dirs); + +        # Add the tests directory to the start of the directory name. +        push(@dirs, map { File::Spec->catdir('tests', $_) } @test_dirs); +    } +    return @dirs; +} + +# Find a configuration file for the test suite.  Searches relative to BUILD +# first and then SOURCE and returns whichever is found first.  Calls BAIL_OUT +# if the file could not be found. +# +# $file - Partial path to the file +# +# Returns: Full path to the file +sub test_file_path { +    my ($file) = @_; +  BASE: +    for my $base ($ENV{BUILD}, $ENV{SOURCE}) { +        next if !defined($base); +        if (-f "$base/$file") { +            return "$base/$file"; +        } +    } +    BAIL_OUT("cannot find $file"); +    return; +} + +1; +__END__ + +=for stopwords +Allbery Automake Automake-aware Automake-based rra-c-util ARGS +subdirectories sublicense MERCHANTABILITY NONINFRINGEMENT + +=head1 NAME + +Test::RRA::Automake - Automake-aware support functions for Perl tests + +=head1 SYNOPSIS + +    use Test::RRA::Automake qw(automake_setup perl_dirs test_file_path); +    automake_setup({ chdir_build => 1 }); + +    # Paths to directories that may contain Perl scripts. +    my @dirs = perl_dirs({ skip => [qw(lib)] }); + +    # Configuration for Kerberos tests. +    my $keytab = test_file_path('config/keytab'); + +=head1 DESCRIPTION + +This module collects utility functions that are useful for test scripts +written in Perl and included in a C Automake-based package.  They assume +the layout of a package that uses rra-c-util and C TAP Harness for the +test structure. + +Loading this module will also add the directories C<perl/blib/arch> and +C<perl/blib/lib> to the Perl library search path, relative to BUILD if +that environment variable is set.  This is harmless for C Automake +projects that don't contain an embedded Perl module, and for those +projects that do, this will allow subsequent C<use> calls to find modules +that are built as part of the package build process. + +The automake_setup() function should be called before calling any other +functions provided by this module. + +=head1 FUNCTIONS + +None of these functions are imported by default.  The ones used by a +script should be explicitly imported.  On failure, all of these functions +call BAIL_OUT (from Test::More). + +=over 4 + +=item automake_setup([ARGS]) + +Verifies that the BUILD and SOURCE environment variables are set and +then changes directory to the top of the source tree (which is one +directory up from the SOURCE path, since SOURCE points to the top of +the tests directory). + +If ARGS is given, it should be a reference to a hash of configuration +options.  Only one option is supported: C<chdir_build>.  If it is set +to a true value, automake_setup() changes directories to the top of +the build tree instead. + +=item perl_dirs([ARGS]) + +Returns a list of directories that may contain Perl scripts that should be +tested by test scripts that test all Perl in the source tree (such as +syntax or coding style checks).  The paths will be simple directory names +relative to the current directory or two-part directory names under the +F<tests> directory.  (Directories under F<tests> are broken out separately +since it's common to want to apply different policies to different +subdirectories of F<tests>.) + +If ARGS is given, it should be a reference to a hash of configuration +options.  Only one option is supported: C<skip>, whose value should be a +reference to an array of additional top-level directories or directories +starting with C<tests/> that should be skipped. + +=item test_file_path(FILE) + +Given FILE, which should be a relative path, locates that file relative to +the test directory in either the source or build tree.  FILE will be +checked for relative to the environment variable BUILD first, and then +relative to SOURCE.  test_file_path() returns the full path to FILE or +calls BAIL_OUT if FILE could not be found. + +=back + +=head1 AUTHOR + +Russ Allbery <rra@stanford.edu> + +=head1 COPYRIGHT AND LICENSE + +Copyright 2013 The Board of Trustees of the Leland Stanford Junior +University + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + +=head1 SEE ALSO + +Test::More(3), Test::RRA(3), Test::RRA::Config(3) + +The C TAP Harness test driver and libraries for TAP-based C testing are +available from L<http://www.eyrie.org/~eagle/software/c-tap-harness/>. + +This module is maintained in the rra-c-util package.  The current version +is available from L<http://www.eyrie.org/~eagle/software/rra-c-util/>. + +=cut diff --git a/tests/tap/perl/Test/RRA/Config.pm b/tests/tap/perl/Test/RRA/Config.pm new file mode 100644 index 0000000..cfa3ad5 --- /dev/null +++ b/tests/tap/perl/Test/RRA/Config.pm @@ -0,0 +1,200 @@ +# Configuration for Perl test cases. +# +# In order to reuse the same Perl test cases in multiple packages, I use a +# configuration file to store some package-specific data.  This module loads +# that configuration and provides the namespace for the configuration +# settings. +# +# The canonical version of this file is maintained in the rra-c-util package, +# which can be found at <http://www.eyrie.org/~eagle/software/rra-c-util/>. + +package Test::RRA::Config; + +use 5.006; +use strict; +use warnings; + +# For Perl 5.006 compatibility. +## no critic (ClassHierarchies::ProhibitExplicitISA) + +use Exporter; +use Test::More; + +# Declare variables that should be set in BEGIN for robustness. +our (@EXPORT_OK, @ISA, $VERSION); + +# Set $VERSION and everything export-related in a BEGIN block for robustness +# against circular module loading (not that we load any modules, but +# consistency is good). +BEGIN { +    @ISA       = qw(Exporter); +    @EXPORT_OK = qw( +      $COVERAGE_LEVEL @COVERAGE_SKIP_TESTS @CRITIC_IGNORE $LIBRARY_PATH +      $MINIMUM_VERSION %MINIMUM_VERSION @POD_COVERAGE_EXCLUDE +    ); + +    # This version should match the corresponding rra-c-util release, but with +    # two digits for the minor version, including a leading zero if necessary, +    # so that it will sort properly. +    $VERSION = '4.08'; +} + +# If BUILD or SOURCE are set in the environment, look for data/perl.conf under +# those paths for a C Automake package.  Otherwise, look in t/data/perl.conf +# for a standalone Perl module.  Don't use Test::RRA::Automake since it may +# not exist. +our $PATH; +for my $base ($ENV{BUILD}, $ENV{SOURCE}, 't') { +    next if !defined($base); +    my $path = "$base/data/perl.conf"; +    if (-r $path) { +        $PATH = $path; +        last; +    } +} +if (!defined($PATH)) { +    BAIL_OUT('cannot find data/perl.conf'); +} + +# Pre-declare all of our variables and set any defaults. +our $COVERAGE_LEVEL = 100; +our @COVERAGE_SKIP_TESTS; +our @CRITIC_IGNORE; +our $LIBRARY_PATH; +our $MINIMUM_VERSION = '5.008'; +our %MINIMUM_VERSION; +our @POD_COVERAGE_EXCLUDE; + +# Load the configuration. +if (!do($PATH)) { +    my $error = $@ || $! || 'loading file did not return true'; +    BAIL_OUT("cannot load data/perl.conf: $error"); +} + +1; +__END__ + +=for stopwords +Allbery rra-c-util Automake perlcritic .libs namespace sublicense +MERCHANTABILITY NONINFRINGEMENT + +=head1 NAME + +Test::RRA::Config - Perl test configuration + +=head1 SYNOPSIS + +    use Test::RRA::Config qw($MINIMUM_VERSION); +    print "Required Perl version is $MINIMUM_VERSION\n"; + +=head1 DESCRIPTION + +Test::RRA::Config encapsulates per-package configuration for generic Perl +test programs that are shared between multiple packages using the +rra-c-util infrastructure.  It handles locating and loading the test +configuration file for both C Automake packages and stand-alone Perl +modules. + +Test::RRA::Config looks for a file named F<data/perl.conf> relative to the +root of the test directory.  That root is taken from the environment +variables BUILD or SOURCE (in that order) if set, which will be the case +for C Automake packages using C TAP Harness.  If neither is set, it +expects the root of the test directory to be a directory named F<t> +relative to the current directory, which will be the case for stand-alone +Perl modules. + +The following variables are supported: + +=over 4 + +=item $COVERAGE_LEVEL + +The coverage level achieved by the test suite for Perl test coverage +testing using Test::Strict, as a percentage.  The test will fail if test +coverage less than this percentage is achieved.  If not given, defaults +to 100. + +=item @COVERAGE_SKIP_TESTS + +Directories under F<t> whose tests should be skipped when doing coverage +testing.  This can be tests that won't contribute to coverage or tests +that don't run properly under Devel::Cover for some reason (such as ones +that use taint checking).  F<docs> and F<style> will always be skipped +regardless of this setting. + +=item @CRITIC_IGNORE + +Additional directories to ignore when doing recursive perlcritic testing. +The contents of this directory must be either top-level directory names or +directory names starting with F<tests/>. + +=item $LIBRARY_PATH + +Add this directory (or a .libs subdirectory) relative to the top of the +source tree to LD_LIBRARY_PATH when checking the syntax of Perl modules. +This may be required to pick up libraries that are used by in-tree Perl +modules so that Perl scripts can pass a syntax check. + +=item $MINIMUM_VERSION + +Default minimum version requirement for included Perl scripts.  If not +given, defaults to 5.008. + +=item %MINIMUM_VERSION + +Minimum version exceptions for specific directories.  The keys should be +minimum versions of Perl to enforce.  The value for each key should be a +reference to an array of either top-level directory names or directory +names starting with F<tests/>.  All files in those directories will have +that minimum Perl version constraint imposed instead of $MINIMUM_VERSION. + +=item @POD_COVERAGE_EXCLUDE + +Regexes that match method names that should be excluded from POD coverage +testing.  Normally, all methods have to be documented in the POD for a +Perl module, but methods matching any of these regexes will be considered +private and won't require documentation. + +=back + +No variables are exported by default, but the variables can be imported +into the local namespace to avoid long variable names. + +=head1 AUTHOR + +Russ Allbery <rra@stanford.edu> + +=head1 COPYRIGHT AND LICENSE + +Copyright 2013 The Board of Trustees of the Leland Stanford Junior +University + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + +=head1 SEE ALSO + +Test::RRA(3), Test::RRA::Automake(3) + +This module is maintained in the rra-c-util package.  The current version +is available from L<http://www.eyrie.org/~eagle/software/rra-c-util/>. + +The C TAP Harness test driver and libraries for TAP-based C testing are +available from L<http://www.eyrie.org/~eagle/software/c-tap-harness/>. + +=cut diff --git a/tests/tap/process.c b/tests/tap/process.c index 16154c7..8ed4cfd 100644 --- a/tests/tap/process.c +++ b/tests/tap/process.c @@ -1,18 +1,37 @@  /*   * 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. + * Provides utility functions for subprocess manipulation.  Specifically, + * provides a function, run_setup, which runs a command and bails if it fails, + * using its error message as the bail output, and 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 + * The canonical version of this file is maintained in the rra-c-util package, + * which can be found at <http://www.eyrie.org/~eagle/software/rra-c-util/>.   * - * See LICENSE for licensing terms. + * Written by Russ Allbery <rra@stanford.edu> + * Copyright 2002, 2004, 2005 Russ Allbery <rra@stanford.edu> + * Copyright 2009, 2010, 2011 + *     The Board of Trustees of the Leland Stanford Junior University + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE.   */  #include <config.h> @@ -22,26 +41,24 @@  #include <tests/tap/basic.h>  #include <tests/tap/process.h> -#include <util/xmalloc.h> +#include <tests/tap/string.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). + * returns the function output in newly allocated memory.  Also captures the + * process exit status.   */ -void -is_function_output(test_function_type function, int status, const char *output, -                   const char *format, ...) +static void +run_child_function(test_function_type function, void *data, int *status, +                   char **output)  {      int fds[2];      pid_t child; -    char *buf, *msg; +    char *buf;      ssize_t count, ret, buflen;      int rval; -    va_list args;      /* Flush stdout before we start to avoid odd forking issues. */      fflush(stdout); @@ -61,7 +78,7 @@ is_function_output(test_function_type function, int status, const char *output,              _exit(255);          /* Now, run the function and exit successfully if it returns. */ -        (*function)(); +        (*function)(data);          fflush(stdout);          _exit(0);      } else { @@ -71,7 +88,7 @@ is_function_output(test_function_type function, int status, const char *output,           */          close(fds[1]);          buflen = BUFSIZ; -        buf = xmalloc(buflen); +        buf = bmalloc(buflen);          count = 0;          do {              ret = read(fds[0], buf + count, buflen - count - 1); @@ -79,18 +96,41 @@ is_function_output(test_function_type function, int status, const char *output,                  count += ret;              if (count >= buflen - 1) {                  buflen += BUFSIZ; -                buf = xrealloc(buf, buflen); +                buf = brealloc(buf, buflen);              }          } while (ret > 0);          buf[count < 0 ? 0 : count] = '\0';          if (waitpid(child, &rval, 0) == (pid_t) -1)              sysbail("waitpid failed"); +        close(fds[0]);      } +    /* Store the output and return. */ +    *status = rval; +    *output = buf; +} + + +/* + * Given a function, data to pass to that 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, void *data, int status, +                   const char *output, const char *format, ...) +{ +    char *buf, *msg; +    int rval; +    va_list args; + +    run_child_function(function, data, &rval, &buf); +      /* Now, check the results against what we expected. */      va_start(args, format); -    if (xvasprintf(&msg, format, args) < 0) -        bail("cannot format test description"); +    bvasprintf(&msg, format, args);      va_end(args);      ok(WIFEXITED(rval), "%s (exited)", msg);      is_int(status, WEXITSTATUS(rval), "%s (status)", msg); @@ -98,3 +138,40 @@ is_function_output(test_function_type function, int status, const char *output,      free(buf);      free(msg);  } + + +/* + * A helper function for run_setup.  This is a function to run an external + * command, suitable for passing into run_child_function.  The expected + * argument must be an argv array, with argv[0] being the command to run. + */ +static void +exec_command(void *data) +{ +    char *const *argv = data; + +    execvp(argv[0], argv); +} + + +/* + * Given a command expressed as an argv struct, with argv[0] the name or path + * to the command, run that command.  If it exits with a non-zero status, use + * the part of its output up to the first newline as the error message when + * calling bail. + */ +void +run_setup(const char *const argv[]) +{ +    char *output, *p; +    int status; + +    run_child_function(exec_command, (void *) argv, &status, &output); +    if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { +        p = strchr(output, '\n'); +        if (p != NULL) +            *p = '\0'; +        bail("%s", output); +    } +    free(output); +} diff --git a/tests/tap/process.h b/tests/tap/process.h index b7d3b11..df74b5f 100644 --- a/tests/tap/process.h +++ b/tests/tap/process.h @@ -1,36 +1,64 @@  /*   * 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 + * The canonical version of this file is maintained in the rra-c-util package, + * which can be found at <http://www.eyrie.org/~eagle/software/rra-c-util/>.   * - * See LICENSE for licensing terms. + * Written by Russ Allbery <rra@stanford.edu> + * Copyright 2009, 2010 + *     The Board of Trustees of the Leland Stanford Junior University + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE.   */  #ifndef TAP_PROCESS_H  #define TAP_PROCESS_H 1  #include <config.h> -#include <portable/macros.h> +#include <tests/tap/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). + * the function to always exit (not die from a signal).  data is optional data + * that's passed into the function as its only argument.   *   * 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))); +typedef void (*test_function_type)(void *); +void is_function_output(test_function_type, void *data, int status, +                        const char *output, const char *format, ...) +    __attribute__((__format__(printf, 5, 6), __nonnull__(1))); + +/* + * Run a setup program.  Takes the program to run and its arguments as an argv + * vector, where argv[0] must be either the full path to the program or the + * program name if the PATH should be searched.  If the program does not exit + * successfully, call bail, with the error message being the output from the + * program. + */ +void run_setup(const char *const argv[]) +    __attribute__((__nonnull__));  END_DECLS diff --git a/tests/tap/remctl.sh b/tests/tap/remctl.sh index 9e01bcf..2fd6681 100644 --- a/tests/tap/remctl.sh +++ b/tests/tap/remctl.sh @@ -1,40 +1,67 @@  # Shell function library to start and stop remctld  # +# Note that while many of the functions in this library could benefit from +# using "local" to avoid possibly hammering global variables, Solaris /bin/sh +# doesn't support local and this library aspires to be portable to Solaris +# Bourne shell.  Instead, all private variables are prefixed with "tap_". +# +# The canonical version of this file is maintained in the rra-c-util package, +# which can be found at <http://www.eyrie.org/~eagle/software/rra-c-util/>. +#  # Written by Russ Allbery <rra@stanford.edu> -# Copyright 2009 Board of Trustees, Leland Stanford Jr. University +# Copyright 2009, 2012 +#     The Board of Trustees of the Leland Stanford Junior University +# +# Permission is hereby granted, free of charge, to any person obtaining a +# copy of this software and associated documentation files (the "Software"), +# to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, +# and/or sell copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software.  # -# See LICENSE for licensing terms. +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL +# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +# DEALINGS IN THE SOFTWARE.  # 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=`test_file_path data/test.keytab` -    principal=`test_file_path data/test.principal` -    principal=`cat "$principal" 2>/dev/null` -    if [ -z "$keytab" ] || [ -z "$principal" ] ; then +    tap_pidfile=`test_tmpdir`/remctld.pid +    rm -f "$tap_pidfile" +    tap_keytab=`test_file_path config/keytab` +    tap_principal=`test_file_path config/principal` +    tap_principal=`cat "$tap_principal" 2>/dev/null` +    if [ -z "$tap_keytab" ] || [ -z "$tap_principal" ] ; then          return 1      fi      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" &) +          -p 14373 -s "$tap_principal" -P "$tap_pidfile" -f "$2" -d -S -F \ +          -k "$tap_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" &) +        ( "$1" -m -p 14373 -s "$tap_principal" -P "$tap_pidfile" -f "$2" \ +          -d -S -F -k "$tap_keytab" &)      fi -    [ -f "$BUILD/data/remctld.pid" ] || sleep 1 -    if [ ! -f "$BUILD/data/remctld.pid" ] ; then +    [ -f "$tap_pidfile" ] || sleep 1 +    [ -f "$tap_pidfile" ] || sleep 1 +    if [ ! -f "$tap_pidfile" ] ; 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" +    tap_pidfile=`test_tmpdir`/remctld.pid +    if [ -f "$tap_pidfile" ] ; then +        kill -TERM `cat "$tap_pidfile"` +        rm -f "$tap_pidfile"      fi  } diff --git a/tests/tap/string.c b/tests/tap/string.c new file mode 100644 index 0000000..f5c965c --- /dev/null +++ b/tests/tap/string.c @@ -0,0 +1,65 @@ +/* + * String utilities for the TAP protocol. + * + * Additional string utilities that can't be included with C TAP Harness + * because they rely on additional portability code from rra-c-util. + * + * The canonical version of this file is maintained in the rra-c-util package, + * which can be found at <http://www.eyrie.org/~eagle/software/rra-c-util/>. + * + * Copyright 2011, 2012 Russ Allbery <rra@stanford.edu> + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include <config.h> +#include <portable/system.h> + +#include <tests/tap/basic.h> +#include <tests/tap/string.h> + + +/* + * vsprintf into a newly allocated string, reporting a fatal error with bail + * on failure. + */ +void +bvasprintf(char **strp, const char *fmt, va_list args) +{ +    int status; + +    status = vasprintf(strp, fmt, args); +    if (status < 0) +        sysbail("failed to allocate memory for vasprintf"); +} + + +/* + * sprintf into a newly allocated string, reporting a fatal error with bail on + * failure. + */ +void +basprintf(char **strp, const char *fmt, ...) +{ +    va_list args; + +    va_start(args, fmt); +    bvasprintf(strp, fmt, args); +    va_end(args); +} diff --git a/tests/tap/string.h b/tests/tap/string.h new file mode 100644 index 0000000..2f699e4 --- /dev/null +++ b/tests/tap/string.h @@ -0,0 +1,49 @@ +/* + * String utilities for the TAP protocol. + * + * Additional string utilities that can't be included with C TAP Harness + * because they rely on additional portability code from rra-c-util. + * + * The canonical version of this file is maintained in the rra-c-util package, + * which can be found at <http://www.eyrie.org/~eagle/software/rra-c-util/>. + * + * Copyright 2011, 2012 Russ Allbery <rra@stanford.edu> + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef TAP_STRING_H +#define TAP_STRING_H 1 + +#include <config.h> +#include <tests/tap/macros.h> + +#include <stdarg.h>             /* va_list */ + +BEGIN_DECLS + +/* sprintf into an allocated string, calling bail on any failure. */ +void basprintf(char **, const char *, ...) +    __attribute__((__nonnull__, __format__(printf, 2, 3))); +void bvasprintf(char **, const char *, va_list) +    __attribute__((__nonnull__)); + +END_DECLS + +#endif /* !TAP_STRING_H */ | 
