]> eyrie.org Git - kerberos/krb5-sync.git/commitdiff
Update to rra-c-util 4.12 (to be) and C TAP Harness 2.3
authorRuss Allbery <eagle@eyrie.org>
Thu, 5 Dec 2013 05:02:57 +0000 (21:02 -0800)
committerRuss Allbery <eagle@eyrie.org>
Thu, 5 Dec 2013 05:02:57 +0000 (21:02 -0800)
Update to rra-c-util 4.12:

* Better error messages from xasprintf on failure to format output.
* Check return status of vsnprintf properly.
* Significant improvements to POD tests.
* Avoid leaking a dummy symbol from the portability layer.
* Probe for Kerberos headers with file existence checks.

Update to C TAP Harness 2.3:

* runtests now treats the command line as a list of tests by default.
* The full test executable path can now be passed to runtests -o.
* Improved harness output for tests with lazy plans.
* Improved harness output to a terminal for some abort cases.
* Flush harness output after each test even when not on a terminal.
* bail and sysbail now exit with status 255 to match Test::More.
* Suppress lazy plans and test summaries if the test failed with bail.
* Add warn_unused_result gcc attributes to relevant functions.

28 files changed:
Makefile.am
NEWS
README
m4/lib-helper.m4
portable/asprintf.c
portable/dummy.c
portable/krb5.h
portable/system.h
tests/data/perl.conf [new file with mode: 0644]
tests/docs/pod-spelling-t
tests/docs/pod-t
tests/runtests.c
tests/tap/basic.c
tests/tap/basic.h
tests/tap/libtap.sh
tests/tap/macros.h
tests/tap/perl/Test/RRA.pm [new file with mode: 0644]
tests/tap/perl/Test/RRA/Automake.pm [new file with mode: 0644]
tests/tap/perl/Test/RRA/Config.pm [new file with mode: 0644]
tests/tap/process.c
tests/util/messages-krb5-t.c
tests/util/xmalloc.c
util/messages-krb5.c
util/messages-krb5.h
util/messages.c
util/messages.h
util/xmalloc.c
util/xmalloc.h

index c5d0cbda2cf2ea05399ba4a7f34a77ed43a55783..01328003201e5316f8c22a0c346f8b92bc7365ca 100644 (file)
@@ -139,4 +139,4 @@ tests_util_messages_t_LDADD = tests/tap/libtap.a util/libutil.la \
 tests_util_xmalloc_LDADD = util/libutil.la portable/libportable.la
 
 check-local: $(check_PROGRAMS)
-       cd tests && ./runtests $(abs_top_srcdir)/tests/TESTS
+       cd tests && ./runtests -l $(abs_top_srcdir)/tests/TESTS
diff --git a/NEWS b/NEWS
index e2e8cea49609f779fbc8b873b41d04586bd7ea3d..13274e89cde4c57d7ef861f12b42ea129b33ab94 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -63,6 +63,25 @@ krb5-sync 3.0 (unreleased)
     longer exist by the time we get to them.  This makes krb5-sync-backend
     more robust against multiple copies running at the same time.
 
+    Update to rra-c-util 4.12:
+
+    * Better error messages from xasprintf on failure to format output.
+    * Check return status of vsnprintf properly.
+    * Significant improvements to POD tests.
+    * Avoid leaking a dummy symbol from the portability layer.
+    * Probe for Kerberos headers with file existence checks.
+
+    Update to C TAP Harness 2.3:
+
+    * runtests now treats the command line as a list of tests by default.
+    * The full test executable path can now be passed to runtests -o.
+    * Improved harness output for tests with lazy plans.
+    * Improved harness output to a terminal for some abort cases.
+    * Flush harness output after each test even when not on a terminal.
+    * bail and sysbail now exit with status 255 to match Test::More.
+    * Suppress lazy plans and test summaries if the test failed with bail.
+    * Add warn_unused_result gcc attributes to relevant functions.
+
 krb5-sync 2.3 (2012-09-18)
 
     When handling password changes from MIT Kerberos, quietly ignore
diff --git a/README b/README
index 6e867dbb881b8410e874aacab602c8c05fdb5404..44b9d7228bda6642c33d7e0d0ebdce3c3c24898e 100644 (file)
--- a/README
+++ b/README
@@ -89,6 +89,12 @@ REQUIREMENTS
   updates, you will also need to know the server to which to do LDAP
   queries (generally, this is one of the Domain Controllers).
 
+  To run the full test suite, Perl 5.6.2 or later is required.  The
+  following additional Perl modules will be used if present:
+
+      Test::Pod
+      Test::Spelling
+
   To bootstrap from a Git checkout, or if you change the Automake files
   and need to regenerate Makefile.in, you will need Automake 1.11 or
   later.  For bootstrap or if you change configure.ac or any of the m4
index e4fbd1f59bd03d00893e941565493f4cda4a9873..0b2e85ca99e650a27ba844563b751003e082566c 100644 (file)
@@ -124,7 +124,7 @@ AC_DEFUN([RRA_LIB_HELPER_WITH_OPTIONAL],
         [Location of $2 headers and libraries])],
     [AS_IF([test x"$withval" = xno],
         [rra_use_$3=false],
-        [AS_IF([test x"$withval" != yes], [rra_$3[]_root="$withval"])
+        [AS_IF([test x"$withval" != xyes], [rra_$3[]_root="$withval"])
          rra_use_$3=true])])
  AC_ARG_WITH([$1][-include],
     [AS_HELP_STRING([--with-][$1][-include=DIR],
index 7bdfd0d547c25b0b405779e5f3285d7a22a8dc0f..eb2b713e55b3fd5f023f07c870aed588e813288f 100644 (file)
@@ -21,6 +21,8 @@
 #include <config.h>
 #include <portable/system.h>
 
+#include <errno.h>
+
 /*
  * If we're running the test suite, rename the functions to avoid conflicts
  * with the system versions.
@@ -33,6 +35,7 @@ int test_asprintf(char **, const char *, ...)
 int test_vasprintf(char **, const char *, va_list);
 #endif
 
+
 int
 asprintf(char **strp, const char *fmt, ...)
 {
@@ -45,11 +48,12 @@ asprintf(char **strp, const char *fmt, ...)
     return status;
 }
 
+
 int
 vasprintf(char **strp, const char *fmt, va_list args)
 {
     va_list args_copy;
-    int status, needed;
+    int status, needed, oerrno;
 
     va_copy(args_copy, args);
     needed = vsnprintf(NULL, 0, fmt, args_copy);
@@ -65,8 +69,10 @@ vasprintf(char **strp, const char *fmt, va_list args)
     if (status >= 0)
         return status;
     else {
+        oerrno = errno;
         free(*strp);
         *strp = NULL;
+        errno = oerrno;
         return status;
     }
 }
index f2ac91765a35139ea702c69d9c9fdfe6456e93d2..890bc0cc3ea8332bd1184d1765992071730fff57 100644 (file)
  * work.
  */
 
-/* Prototype to avoid gcc warnings. */
-int portable_dummy(void);
+#include <portable/macros.h>
+
+/* Prototype to avoid gcc warnings and set visibility. */
+int portable_dummy(void) __attribute__((__visibility__("hidden")));
 
 int
 portable_dummy(void)
index 135640fd74e9a81a861d2686bc2321e05bebe3d4..9496b0e615e95eae5cd8cfdcdc98ab355b7d6798 100644 (file)
@@ -66,6 +66,31 @@ void krb5_appdefault_string(krb5_context, const char *, const krb5_data *,
                             const char *, const char *, char **);
 #endif
 
+/*
+ * MIT-specific.  The Heimdal documentation says to use free(), but that
+ * doesn't actually make sense since the memory is allocated inside the
+ * Kerberos library.  Use krb5_xfree instead.
+ */
+#ifndef HAVE_KRB5_FREE_DEFAULT_REALM
+# define krb5_free_default_realm(c, r) krb5_xfree(r)
+#endif
+
+/*
+ * Heimdal: krb5_xfree, MIT: krb5_free_string, older MIT uses free().  Note
+ * that we can incorrectly allocate in the library and call free() if
+ * krb5_free_string is not available but something we use that API for is
+ * available, such as krb5_appdefaults_*, but there isn't anything we can
+ * really do about it.
+ */
+#ifndef HAVE_KRB5_FREE_STRING
+# ifdef HAVE_KRB5_XFREE
+#  define krb5_free_string(c, s) krb5_xfree(s)
+# else
+#  define krb5_free_string(c, s) free(s)
+# endif
+#endif
+
+
 /* Heimdal: krb5_xfree, MIT: krb5_free_unparsed_name. */
 #ifdef HAVE_KRB5_XFREE
 # define krb5_free_unparsed_name(c, p) krb5_xfree(p)
index fe8ea4f8aca4d0aaeaea0cd35599a03f1f3b95fd..5345da360c4a6f921bd7e15b624d119542d1dd2e 100644 (file)
@@ -8,9 +8,9 @@
  *     #include <sys/types.h>
  *     #include <stdarg.h>
  *     #include <stdbool.h>
+ *     #include <stddef.h>
  *     #include <stdio.h>
  *     #include <stdlib.h>
- *     #include <stddef.h>
  *     #include <stdint.h>
  *     #include <string.h>
  *     #include <strings.h>
 #include <portable/macros.h>
 
 /* A set of standard ANSI C headers.  We don't care about pre-ANSI systems. */
+#if HAVE_INTTYPES_H
+# include <inttypes.h>
+#endif
 #include <stdarg.h>
 #include <stddef.h>
+#if HAVE_STDINT_H
+# include <stdint.h>
+#endif
 #include <stdio.h>
 #include <stdlib.h>
-#include <sys/types.h>
 #include <string.h>
 #if HAVE_STRINGS_H
 # include <strings.h>
 #endif
-#if HAVE_INTTYPES_H
-# include <inttypes.h>
-#endif
-#if HAVE_STDINT_H
-# include <stdint.h>
-#endif
+#include <sys/types.h>
 #if HAVE_UNISTD_H
 # include <unistd.h>
 #endif
diff --git a/tests/data/perl.conf b/tests/data/perl.conf
new file mode 100644 (file)
index 0000000..fc004f0
--- /dev/null
@@ -0,0 +1,7 @@
+# Configuration for Perl tests.  -*- perl -*-
+
+# Default minimum version requirement for included Perl scripts.
+$MINIMUM_VERSION = '5.006';
+
+# File must end with this line.
+1;
index 624c7a5e0819017d4eb2451edd97da7288dc7915..1a02af8efca52224fc3d294c092373b3f67c84ad 100755 (executable)
@@ -1,79 +1,52 @@
-#!/usr/bin/perl -w
+#!/usr/bin/perl
 #
-# Check for spelling errors in POD documentation
+# Checks all POD files in the tree for spelling errors using Test::Spelling.
+# This test is disabled unless RRA_MAINTAINER_TESTS is set, since spelling
+# dictionaries vary too much between environments.
 #
-# Checks all POD files in the tree for spelling problems using Pod::Spell and
-# either aspell or ispell.  aspell is preferred.  This test is disabled unless
-# RRA_MAINTAINER_TESTS is set, since spelling dictionaries vary too much
-# between environments.
+# The canonical version of this file is maintained in the rra-c-util package,
+# which can be found at <http://www.eyrie.org/~eagle/software/rra-c-util/>.
 #
-# Copyright 2008, 2009, 2011 Russ Allbery <eagle@eyrie.org>
+# Written by Russ Allbery <eagle@eyrie.org>
+# Copyright 2012, 2013
+#     The Board of Trustees of the Leland Stanford Junior University
 #
-# See LICENSE for licensing terms.
+# 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.
 
+use 5.006;
 use strict;
-use Test::More;
+use warnings;
 
-# Skip all spelling tests unless the maintainer environment variable is set.
-plan skip_all => 'spelling tests only run for maintainer'
-    unless $ENV{RRA_MAINTAINER_TESTS};
+use lib "$ENV{SOURCE}/tap/perl";
+
+use Test::More;
+use Test::RRA qw(skip_unless_maintainer use_prereq);
+use Test::RRA::Automake qw(automake_setup perl_dirs);
 
-# Load required Perl modules.
-eval 'use Test::Pod 1.00';
-plan skip_all => 'Test::Pod 1.00 required for testing POD' if $@;
-eval 'use Pod::Spell';
-plan skip_all => 'Pod::Spell required to test POD spelling' if $@;
+# Only run this test for the maintainer.
+skip_unless_maintainer('Spelling tests');
 
-# Locate a spell-checker.  hunspell is not currently supported due to its lack
-# of support for contractions (at least in the version in Debian).
-my @spell;
-my %options = (aspell => [ qw(-d en_US --home-dir=./ list) ],
-               ispell => [ qw(-d american -l -p /dev/null) ]);
-SEARCH: for my $program (qw/aspell ispell/) {
-    for my $dir (split ':', $ENV{PATH}) {
-        if (-x "$dir/$program") {
-            @spell = ("$dir/$program", @{ $options{$program} });
-        }
-        last SEARCH if @spell;
-    }
-}
-plan skip_all => 'aspell or ispell required to test POD spelling'
-    unless @spell;
+# Load prerequisite modules.
+use_prereq('Test::Spelling');
 
-# Prerequisites are satisfied, so we're going to do some testing.  Figure out
-# what POD files we have and from that develop our plan.
-$| = 1;
-my @pod = map {
-    my $pod = "$ENV{SOURCE}/../" . $_;
-    $pod =~ s,[^/.][^/]*/../,,g;
-    $pod;
-} qw(tools/krb5-sync-backend tools/krb5-sync.pod);
-plan tests => scalar @pod;
+# Set up Automake testing.
+automake_setup();
 
-# Finally, do the checks.
-for my $pod (@pod) {
-    my $child = open (CHILD, '-|');
-    if (not defined $child) {
-        BAIL_OUT ("cannot fork: $!");
-    } elsif ($child == 0) {
-        my $pid = open (SPELL, '|-', @spell)
-            or BAIL_OUT ("cannot run @spell: $!");
-        open (POD, '<', $pod) or BAIL_OUT ("cannot open $pod: $!");
-        my $parser = Pod::Spell->new;
-        $parser->parse_from_filehandle (\*POD, \*SPELL);
-        close POD;
-        close SPELL;
-        exit ($? >> 8);
-    } else {
-        my @words = <CHILD>;
-        close CHILD;
-      SKIP: {
-            skip "@spell failed for $pod", 1 unless $? == 0;
-            for (@words) {
-                s/^\s+//;
-                s/\s+$//;
-            }
-            is ("@words", '', $pod);
-        }
-    }
-}
+# Run the tests.
+all_pod_files_spelling_ok(perl_dirs());
index 47aa62f48f7bd2bca31905cf8199df5e56d09f57..69182713f88b4f9b2fb91de357b166c9bad4c389 100755 (executable)
@@ -1,21 +1,48 @@
 #!/usr/bin/perl -w
 #
-# Test formatting of POD documentation.
+# Check all POD documents in the tree, except for any embedded Perl module
+# distribution, for POD formatting errors.
+#
+# The canonical version of this file is maintained in the rra-c-util package,
+# which can be found at <http://www.eyrie.org/~eagle/software/rra-c-util/>.
 #
 # Written by Russ Allbery <eagle@eyrie.org>
-# Copyright 2008, 2009, 2011
+# Copyright 2012, 2013
 #     The Board of Trustees of the Leland Stanford Junior University
 #
-# See LICENSE for licensing terms.
+# 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.
 
+use 5.006;
 use strict;
+use warnings;
+
+use lib "$ENV{SOURCE}/tap/perl";
+
 use Test::More;
+use Test::RRA qw(use_prereq);
+use Test::RRA::Automake qw(automake_setup perl_dirs);
+
+# Load prerequisite modules.
+use_prereq('Test::Pod');
 
-my @podfiles = qw(tools/krb5-sync-backend tools/krb5-sync.pod);
+# Set up Automake testing.
+automake_setup();
 
-eval 'use Test::Pod 1.00';
-plan skip_all => "Test::Pod 1.00 required for testing POD" if $@;
-my $srcdir = "$ENV{SOURCE}";
-$srcdir =~ s,[^/]+/*$,,;
-chdir "$srcdir" or die "$0: cannot chdir to $srcdir: $!\n";
-all_pod_files_ok (@podfiles);
+# Run the tests.
+all_pod_files_ok(perl_dirs());
index 3756aa650ab576215b15b16d330a634dd0e9614f..2b7959bfc854d9751774b1d4d7c58966ea900904 100644 (file)
@@ -87,6 +87,7 @@
 #include <errno.h>
 #include <fcntl.h>
 #include <stdarg.h>
+#include <stddef.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 
 /* AIX doesn't have WCOREDUMP. */
 #ifndef WCOREDUMP
-# define WCOREDUMP(status)      ((unsigned)(status) & 0x80)
+# define WCOREDUMP(status) ((unsigned)(status) & 0x80)
 #endif
 
+/*
+ * Used for iterating through arrays.  Returns the number of elements in the
+ * array (useful for a < upper bound in a for loop).
+ */
+#define ARRAY_SIZE(array) (sizeof(array) / sizeof((array)[0]))
+
 /*
  * The source and build versions of the tests directory.  This is used to set
  * the SOURCE and BUILD environment variables and find test programs, if set.
@@ -153,7 +160,7 @@ struct testset {
     unsigned long skipped;      /* Count of skipped tests (passed). */
     unsigned long allocated;    /* The size of the results table. */
     enum test_status *results;  /* Table of results by test number. */
-    unsigned int aborted;       /* Whether the set as aborted. */
+    unsigned int aborted;       /* Whether the set was aborted. */
     int reported;               /* Whether the results were reported. */
     int status;                 /* The exit status of the test. */
     unsigned int all_skipped;   /* Whether all tests were skipped. */
@@ -167,21 +174,25 @@ struct testlist {
 };
 
 /*
- * Usage message.  Should be used as a printf format with two arguments: the
- * path to runtests, given twice.
+ * Usage message.  Should be used as a printf format with four arguments: the
+ * path to runtests, given three times, and the usage_description.  This is
+ * split into variables to satisfy the pedantic ISO C90 limit on strings.
  */
 static const char usage_message[] = "\
-Usage: %s [-b <build-dir>] [-s <source-dir>] <test-list>\n\
+Usage: %s [-b <build-dir>] [-s <source-dir>] <test> ...\n\
+       %s [-b <build-dir>] [-s <source-dir>] -l <test-list>\n\
        %s -o [-b <build-dir>] [-s <source-dir>] <test>\n\
-\n\
+\n%s";
+static const char usage_extra[] = "\
 Options:\n\
     -b <build-dir>      Set the build directory to <build-dir>\n\
+    -l <list>           Take the list of tests to run from <test-list>\n\
     -o                  Run a single test rather than a list of tests\n\
     -s <source-dir>     Set the source directory to <source-dir>\n\
 \n\
-runtests normally runs each test listed in a file whose path is given as\n\
-its command-line argument.  With the -o option, it instead runs a single\n\
-test and shows its complete output.\n";
+runtests normally runs each test listed on the command line.  With the -l\n\
+option, it instead runs every test listed in a file.  With the -o option,\n\
+it instead runs a single test and shows its complete output.\n";
 
 /*
  * Header used for test output.  %s is replaced by the file name of the list
@@ -197,10 +208,57 @@ Failed Set                 Fail/Total (%) Skip Stat  Failing Tests\n\
 -------------------------- -------------- ---- ----  ------------------------";
 
 /* Include the file name and line number in malloc failures. */
+#define xcalloc(n, size)  x_calloc((n), (size), __FILE__, __LINE__)
 #define xmalloc(size)     x_malloc((size), __FILE__, __LINE__)
 #define xrealloc(p, size) x_realloc((p), (size), __FILE__, __LINE__)
 #define xstrdup(p)        x_strdup((p), __FILE__, __LINE__)
 
+/*
+ * __attribute__ is available in gcc 2.5 and later, but only with gcc 2.7
+ * could you use the __format__ form of the attributes, which is what we use
+ * (to avoid confusion with other macros).
+ */
+#ifndef __attribute__
+# if __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 7)
+#  define __attribute__(spec)   /* empty */
+# endif
+#endif
+
+/*
+ * We use __alloc_size__, but it was only available in fairly recent versions
+ * of GCC.  Suppress warnings about the unknown attribute if GCC is too old.
+ * We know that we're GCC at this point, so we can use the GCC variadic macro
+ * extension, which will still work with versions of GCC too old to have C99
+ * variadic macro support.
+ */
+#if !defined(__attribute__) && !defined(__alloc_size__)
+# if __GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 3)
+#  define __alloc_size__(spec, args...) /* empty */
+# endif
+#endif
+
+/*
+ * LLVM and Clang pretend to be GCC but don't support all of the __attribute__
+ * settings that GCC does.  For them, suppress warnings about unknown
+ * attributes on declarations.  This unfortunately will affect the entire
+ * compilation context, but there's no push and pop available.
+ */
+#if !defined(__attribute__) && (defined(__llvm__) || defined(__clang__))
+# pragma GCC diagnostic ignored "-Wattributes"
+#endif
+
+/* Declare internal functions that benefit from compiler attributes. */
+static void sysdie(const char *, ...)
+    __attribute__((__nonnull__, __noreturn__, __format__(printf, 1, 2)));
+static void *x_calloc(size_t, size_t, const char *, int)
+    __attribute__((__alloc_size__(1, 2), __malloc__, __nonnull__));
+static void *x_malloc(size_t, const char *, int)
+    __attribute__((__alloc_size__(1), __malloc__, __nonnull__));
+static void *x_realloc(void *, size_t, const char *, int)
+    __attribute__((__alloc_size__(2), __malloc__, __nonnull__(3)));
+static char *x_strdup(const char *, const char *, int)
+    __attribute__((__malloc__, __nonnull__));
+
 
 /*
  * Report a fatal error, including the results of strerror, and exit.
@@ -222,6 +280,24 @@ sysdie(const char *format, ...)
 }
 
 
+/*
+ * Allocate zeroed memory, reporting a fatal error and exiting on failure.
+ */
+static void *
+x_calloc(size_t n, size_t size, const char *file, int line)
+{
+    void *p;
+
+    n = (n > 0) ? n : 1;
+    size = (size > 0) ? size : 1;
+    p = calloc(n, size);
+    if (p == NULL)
+        sysdie("failed to calloc %lu bytes at %s line %d",
+               (unsigned long) size, file, line);
+    return p;
+}
+
+
 /*
  * Allocate memory, reporting a fatal error and exiting on failure.
  */
@@ -444,6 +520,7 @@ test_plan(const char *line, struct testset *ts)
             ts->results[i] = TEST_INVALID;
     } else if (ts->plan == PLAN_PENDING) {
         if ((unsigned long) n < ts->count) {
+            test_backspace(ts);
             printf("ABORTED (invalid test number %lu)\n", ts->count);
             ts->aborted = 1;
             ts->reported = 1;
@@ -516,6 +593,7 @@ test_checkline(const char *line, struct testset *ts)
             if (!test_plan(line, ts))
                 return;
         } else {
+            test_backspace(ts);
             puts("ABORTED (multiple plans)");
             ts->aborted = 1;
             ts->reported = 1;
@@ -595,9 +673,12 @@ test_checkline(const char *line, struct testset *ts)
     }
     ts->current = current;
     ts->results[current - 1] = status;
-    test_backspace(ts);
     if (isatty(STDOUT_FILENO)) {
-        outlen = printf("%lu/%lu", current, ts->count);
+        test_backspace(ts);
+        if (ts->plan == PLAN_PENDING)
+            outlen = printf("%lu/?", current);
+        else
+            outlen = printf("%lu/%lu", current, ts->count);
         ts->length = (outlen >= 0) ? outlen : 0;
         fflush(stdout);
     }
@@ -883,109 +964,213 @@ test_fail_summary(const struct testlist *fails)
         if (first != 0)
             test_print_range(first, last, chars, 19);
         putchar('\n');
-        free(ts->file);
-        free(ts->path);
-        free(ts->results);
-        if (ts->reason != NULL)
-            free(ts->reason);
-        free(ts);
     }
 }
 
 
+/*
+ * Check whether a given file path is a valid test.  Currently, this checks
+ * whether it is executable and is a regular file.  Returns true or false.
+ */
+static int
+is_valid_test(const char *path)
+{
+    struct stat st;
+
+    if (access(path, X_OK) < 0)
+        return 0;
+    if (stat(path, &st) < 0)
+        return 0;
+    if (!S_ISREG(st.st_mode))
+        return 0;
+    return 1;
+}
+
+
 /*
  * Given the name of a test, a pointer to the testset struct, and the source
  * and build directories, find the test.  We try first relative to the current
  * directory, then in the build directory (if not NULL), then in the source
  * directory.  In each of those directories, we first try a "-t" extension and
- * then a ".t" extension.  When we find an executable program, we fill in the
- * path member of the testset struct.  If none of those paths are executable,
- * just fill in the name of the test with "-t" appended.
+ * then a ".t" extension.  When we find an executable program, we return the
+ * path to that program.  If none of those paths are executable, just fill in
+ * the name of the test as is.
  *
  * The caller is responsible for freeing the path member of the testset
  * struct.
  */
-static void
-find_test(const char *name, struct testset *ts, const char *source,
-          const char *build)
+static char *
+find_test(const char *name, const char *source, const char *build)
 {
     char *path;
-    const char *bases[4];
-    unsigned int i;
+    const char *bases[3], *suffix, *base;
+    unsigned int i, j;
+    const char *suffixes[3] = { "-t", ".t", "" };
 
+    /* Possible base directories. */
     bases[0] = ".";
     bases[1] = build;
     bases[2] = source;
-    bases[3] = NULL;
 
-    for (i = 0; i < 3; i++) {
-        if (bases[i] == NULL)
-            continue;
-        path = xmalloc(strlen(bases[i]) + strlen(name) + 4);
-        sprintf(path, "%s/%s-t", bases[i], name);
-        if (access(path, X_OK) != 0)
-            path[strlen(path) - 2] = '.';
-        if (access(path, X_OK) == 0)
-            break;
-        free(path);
-        path = NULL;
+    /* Try each suffix with each base. */
+    for (i = 0; i < ARRAY_SIZE(suffixes); i++) {
+        suffix = suffixes[i];
+        for (j = 0; j < ARRAY_SIZE(bases); j++) {
+            base = bases[j];
+            if (base == NULL)
+                continue;
+            path = xmalloc(strlen(base) + strlen(name) + strlen(suffix) + 2);
+            sprintf(path, "%s/%s%s", base, name, suffix);
+            if (is_valid_test(path))
+                return path;
+            free(path);
+            path = NULL;
+        }
+    }
+    if (path == NULL)
+        path = xstrdup(name);
+    return path;
+}
+
+
+/*
+ * Read a list of tests from a file, returning the list of tests as a struct
+ * testlist.  Reports an error to standard error and exits if the list of
+ * tests cannot be read.
+ */
+static struct testlist *
+read_test_list(const char *filename)
+{
+    FILE *file;
+    unsigned int line;
+    size_t length;
+    char buffer[BUFSIZ];
+    struct testlist *listhead, *current;
+
+    /* Create the initial container list that will hold our results. */
+    listhead = xmalloc(sizeof(struct testlist));
+    listhead->ts = NULL;
+    listhead->next = NULL;
+    current = NULL;
+
+    /*
+     * Open our file of tests to run and read it line by line, creating a new
+     * struct testlist and struct testset for each line.
+     */
+    file = fopen(filename, "r");
+    if (file == NULL)
+        sysdie("can't open %s", filename);
+    line = 0;
+    while (fgets(buffer, sizeof(buffer), file)) {
+        line++;
+        length = strlen(buffer) - 1;
+        if (buffer[length] != '\n') {
+            fprintf(stderr, "%s:%u: line too long\n", filename, line);
+            exit(1);
+        }
+        buffer[length] = '\0';
+        if (current == NULL)
+            current = listhead;
+        else {
+            current->next = xmalloc(sizeof(struct testlist));
+            current = current->next;
+            current->next = NULL;
+        }
+        current->ts = xcalloc(1, sizeof(struct testset));
+        current->ts->plan = PLAN_INIT;
+        current->ts->file = xstrdup(buffer);
+        current->ts->reason = NULL;
     }
-    if (path == NULL) {
-        path = xmalloc(strlen(name) + 3);
-        sprintf(path, "%s-t", name);
+    fclose(file);
+
+    /* Return the results. */
+    return listhead;
+}
+
+
+/*
+ * Build a list of tests from command line arguments.  Takes the argv and argc
+ * representing the command line arguments and returns a newly allocated test
+ * list.  The caller is responsible for freeing.
+ */
+static struct testlist *
+build_test_list(char *argv[], int argc)
+{
+    int i;
+    struct testlist *listhead, *current;
+
+    /* Create the initial container list that will hold our results. */
+    listhead = xmalloc(sizeof(struct testlist));
+    listhead->ts = NULL;
+    listhead->next = NULL;
+    current = NULL;
+
+    /* Walk the list of arguments and create test sets for them. */
+    for (i = 0; i < argc; i++) {
+        if (current == NULL)
+            current = listhead;
+        else {
+            current->next = xmalloc(sizeof(struct testlist));
+            current = current->next;
+            current->next = NULL;
+        }
+        current->ts = xcalloc(1, sizeof(struct testset));
+        current->ts->plan = PLAN_INIT;
+        current->ts->file = xstrdup(argv[i]);
+        current->ts->reason = NULL;
     }
-    ts->path = path;
+
+    /* Return the results. */
+    return listhead;
+}
+
+
+/* Free a struct testset. */
+static void
+free_testset(struct testset *ts)
+{
+    free(ts->file);
+    free(ts->path);
+    free(ts->results);
+    if (ts->reason != NULL)
+        free(ts->reason);
+    free(ts);
 }
 
 
 /*
- * Run a batch of tests from a given file listing each test on a line by
- * itself.  Takes two additional parameters: the root of the source directory
- * and the root of the build directory.  Test programs will be first searched
- * for in the current directory, then the build directory, then the source
- * directory.  The file must be rewindable.  Returns true iff all tests
- * passed.
+ * Run a batch of tests.  Takes two additional parameters: the root of the
+ * source directory and the root of the build directory.  Test programs will
+ * be first searched for in the current directory, then the build directory,
+ * then the source directory.  Returns true iff all tests passed, and always
+ * frees the test list that's passed in.
  */
 static int
-test_batch(const char *testlist, const char *source, const char *build)
+test_batch(struct testlist *tests, const char *source, const char *build)
 {
-    FILE *tests;
-    unsigned int length, i;
+    size_t length;
+    unsigned int i;
     unsigned int longest = 0;
-    char buffer[BUFSIZ];
-    unsigned int line;
-    struct testset ts, *tmp;
+    unsigned int count = 0;
+    struct testset *ts;
     struct timeval start, end;
     struct rusage stats;
     struct testlist *failhead = NULL;
     struct testlist *failtail = NULL;
-    struct testlist *next;
+    struct testlist *current, *next;
+    int succeeded;
     unsigned long total = 0;
     unsigned long passed = 0;
     unsigned long skipped = 0;
     unsigned long failed = 0;
     unsigned long aborted = 0;
 
-    /*
-     * Open our file of tests to run and scan it, checking for lines that
-     * are too long and searching for the longest line.
-     */
-    tests = fopen(testlist, "r");
-    if (!tests)
-        sysdie("can't open %s", testlist);
-    line = 0;
-    while (fgets(buffer, sizeof(buffer), tests)) {
-        line++;
-        length = strlen(buffer) - 1;
-        if (buffer[length] != '\n') {
-            fprintf(stderr, "%s:%u: line too long\n", testlist, line);
-            exit(1);
-        }
+    /* Walk the list of tests to find the longest name. */
+    for (current = tests; current != NULL; current = current->next) {
+        length = strlen(current->ts->file);
         if (length > longest)
             longest = length;
     }
-    if (fseek(tests, 0, SEEK_SET) == -1)
-        sysdie("can't rewind %s", testlist);
 
     /*
      * Add two to longest and round up to the nearest tab stop.  This is how
@@ -998,64 +1183,50 @@ test_batch(const char *testlist, const char *source, const char *build)
     /* Start the wall clock timer. */
     gettimeofday(&start, NULL);
 
-    /*
-     * Now, plow through our tests again, running each one.  Check line
-     * length again out of paranoia.
-     */
-    line = 0;
-    while (fgets(buffer, sizeof(buffer), tests)) {
-        line++;
-        length = strlen(buffer) - 1;
-        if (buffer[length] != '\n') {
-            fprintf(stderr, "%s:%u: line too long\n", testlist, line);
-            exit(1);
-        }
-        buffer[length] = '\0';
-        fputs(buffer, stdout);
-        for (i = length; i < longest; i++)
+    /* Now, plow through our tests again, running each one. */
+    for (current = tests; current != NULL; current = current->next) {
+        ts = current->ts;
+
+        /* Print out the name of the test file. */
+        fputs(ts->file, stdout);
+        for (i = strlen(ts->file); i < longest; i++)
             putchar('.');
         if (isatty(STDOUT_FILENO))
             fflush(stdout);
-        memset(&ts, 0, sizeof(ts));
-        ts.plan = PLAN_INIT;
-        ts.file = xstrdup(buffer);
-        find_test(buffer, &ts, source, build);
-        ts.reason = NULL;
-        if (test_run(&ts)) {
-            free(ts.file);
-            free(ts.path);
-            free(ts.results);
-            if (ts.reason != NULL)
-                free(ts.reason);
-        } else {
-            tmp = xmalloc(sizeof(struct testset));
-            memcpy(tmp, &ts, sizeof(struct testset));
-            if (!failhead) {
+
+        /* Run the test. */
+        ts->path = find_test(ts->file, source, build);
+        succeeded = test_run(ts);
+        fflush(stdout);
+
+        /* Record cumulative statistics. */
+        aborted += ts->aborted;
+        total += ts->count + ts->all_skipped;
+        passed += ts->passed;
+        skipped += ts->skipped + ts->all_skipped;
+        failed += ts->failed;
+        count++;
+
+        /* If the test fails, we shuffle it over to the fail list. */
+        if (!succeeded) {
+            if (failhead == NULL) {
                 failhead = xmalloc(sizeof(struct testset));
-                failhead->ts = tmp;
-                failhead->next = NULL;
                 failtail = failhead;
             } else {
                 failtail->next = xmalloc(sizeof(struct testset));
                 failtail = failtail->next;
-                failtail->ts = tmp;
-                failtail->next = NULL;
             }
+            failtail->ts = ts;
+            failtail->next = NULL;
         }
-        aborted += ts.aborted;
-        total += ts.count + ts.all_skipped;
-        passed += ts.passed;
-        skipped += ts.skipped + ts.all_skipped;
-        failed += ts.failed;
     }
     total -= skipped;
-    fclose(tests);
 
     /* Stop the timer and get our child resource statistics. */
     gettimeofday(&end, NULL);
     getrusage(RUSAGE_CHILDREN, &stats);
 
-    /* Print out our final results. */
+    /* Summarize the failures and free the failure list. */
     if (failhead != NULL) {
         test_fail_summary(failhead);
         while (failhead != NULL) {
@@ -1064,6 +1235,16 @@ test_batch(const char *testlist, const char *source, const char *build)
             failhead = next;
         }
     }
+
+    /* Free the memory used by the test lists. */
+    while (tests != NULL) {
+        next = tests->next;
+        free_testset(tests->ts);
+        free(tests);
+        tests = next;
+    }
+
+    /* Print out the final test summary. */
     putchar('\n');
     if (aborted != 0) {
         if (aborted == 1)
@@ -1084,7 +1265,7 @@ test_batch(const char *testlist, const char *source, const char *build)
             printf(", %lu tests skipped", skipped);
     }
     puts(".");
-    printf("Files=%u,  Tests=%lu", line, total);
+    printf("Files=%u,  Tests=%lu", count, total);
     printf(",  %.2f seconds", tv_diff(&end, &start));
     printf(" (%.2f usr + %.2f sys = %.2f CPU)\n",
            tv_seconds(&stats.ru_utime), tv_seconds(&stats.ru_stime),
@@ -1100,12 +1281,11 @@ test_batch(const char *testlist, const char *source, const char *build)
 static void
 test_single(const char *program, const char *source, const char *build)
 {
-    struct testset ts;
+    char *path;
 
-    memset(&ts, 0, sizeof(ts));
-    find_test(program, &ts, source, build);
-    if (execl(ts.path, ts.path, (char *) 0) == -1)
-        sysdie("cannot exec %s", ts.path);
+    path = find_test(program, source, build);
+    if (execl(path, path, (char *) 0) == -1)
+        sysdie("cannot exec %s", path);
 }
 
 
@@ -1121,19 +1301,24 @@ main(int argc, char *argv[])
     int single = 0;
     char *source_env = NULL;
     char *build_env = NULL;
-    const char *list;
+    const char *shortlist;
+    const char *list = NULL;
     const char *source = SOURCE;
     const char *build = BUILD;
+    struct testlist *tests;
 
-    while ((option = getopt(argc, argv, "b:hos:")) != EOF) {
+    while ((option = getopt(argc, argv, "b:hl:os:")) != EOF) {
         switch (option) {
         case 'b':
             build = optarg;
             break;
         case 'h':
-            printf(usage_message, argv[0], argv[0]);
+            printf(usage_message, argv[0], argv[0], argv[0], usage_extra);
             exit(0);
             break;
+        case 'l':
+            list = optarg;
+            break;
         case 'o':
             single = 1;
             break;
@@ -1144,13 +1329,14 @@ main(int argc, char *argv[])
             exit(1);
         }
     }
-    if (argc - optind != 1) {
-        fprintf(stderr, usage_message, argv[0], argv[0]);
+    argv += optind;
+    argc -= optind;
+    if ((list == NULL && argc < 1) || (list != NULL && argc > 0)) {
+        fprintf(stderr, usage_message, argv[0], argv[0], argv[0], usage_extra);
         exit(1);
     }
-    argc -= optind;
-    argv += optind;
 
+    /* Set SOURCE and BUILD environment variables. */
     if (source != NULL) {
         source_env = xmalloc(strlen("SOURCE=") + strlen(source) + 1);
         sprintf(source_env, "SOURCE=%s", source);
@@ -1164,19 +1350,24 @@ main(int argc, char *argv[])
             sysdie("cannot set BUILD in the environment");
     }
 
+    /* Run the tests as instructed. */
     if (single)
         test_single(argv[0], source, build);
-    else {
-        list = strrchr(argv[0], '/');
-        if (list == NULL)
-            list = argv[0];
+    else if (list != NULL) {
+        shortlist = strrchr(list, '/');
+        if (shortlist == NULL)
+            shortlist = list;
         else
-            list++;
-        printf(banner, list);
-        status = test_batch(argv[0], source, build) ? 0 : 1;
+            shortlist++;
+        printf(banner, shortlist);
+        tests = read_test_list(list);
+        status = test_batch(tests, source, build) ? 0 : 1;
+    } else {
+        tests = build_test_list(argv, argc);
+        status = test_batch(tests, source, build) ? 0 : 1;
     }
 
-    /* For valgrind cleanliness. */
+    /* For valgrind cleanliness, free all our memory. */
     if (source_env != NULL) {
         putenv((char *) "SOURCE=");
         free(source_env);
index 70ee093c385fdbf650e8edb2fc35c51bda1fcfba..bb51606b639b08350820bad6936386437b713c01 100644 (file)
@@ -12,8 +12,8 @@
  * This file is part of C TAP Harness.  The current version plus supporting
  * documentation is at <http://www.eyrie.org/~eagle/software/c-tap-harness/>.
  *
- * Copyright 2009, 2010, 2011, 2012 Russ Allbery <eagle@eyrie.org>
- * Copyright 2001, 2002, 2004, 2005, 2006, 2007, 2008, 2011, 2012
+ * Copyright 2009, 2010, 2011, 2012, 2013 Russ Allbery <eagle@eyrie.org>
+ * Copyright 2001, 2002, 2004, 2005, 2006, 2007, 2008, 2011, 2012, 2013
  *     The Board of Trustees of the Leland Stanford Junior University
  *
  * Permission is hereby granted, free of charge, to any person obtaining a
@@ -58,7 +58,7 @@
 
 /*
  * The test count.  Always contains the number that will be used for the next
- * test status.
+ * test status.  This is exported to callers of the library.
  */
 unsigned long testnum = 1;
 
@@ -66,53 +66,133 @@ unsigned long testnum = 1;
  * Status information stored so that we can give a test summary at the end of
  * the test case.  We store the planned final test and the count of failures.
  * We can get the highest test count from testnum.
- *
- * We also store the PID of the process that called plan() and only summarize
- * results when that process exits, so as to not misreport results in forked
- * processes.
- *
- * If _lazy is true, we're doing lazy planning and will print out the plan
- * based on the last test number at the end of testing.
  */
 static unsigned long _planned = 0;
 static unsigned long _failed  = 0;
+
+/*
+ * Store the PID of the process that called plan() and only summarize
+ * results when that process exits, so as to not misreport results in forked
+ * processes.
+ */
 static pid_t _process = 0;
+
+/*
+ * If true, we're doing lazy planning and will print out the plan based on the
+ * last test number at the end of testing.
+ */
 static int _lazy = 0;
 
+/*
+ * If true, the test was aborted by calling bail().  Currently, this is only
+ * used to ensure that we pass a false value to any cleanup functions even if
+ * all tests to that point have passed.
+ */
+static int _aborted = 0;
+
+/*
+ * Registered cleanup functions.  These are stored as a linked list and run in
+ * registered order by finish when the test program exits.  Each function is
+ * passed a boolean value indicating whether all tests were successful.
+ */
+struct cleanup_func {
+    test_cleanup_func func;
+    struct cleanup_func *next;
+};
+static struct cleanup_func *cleanup_funcs = NULL;
+
+/*
+ * Print a specified prefix and then the test description.  Handles turning
+ * the argument list into a va_args structure suitable for passing to
+ * print_desc, which has to be done in a macro.  Assumes that format is the
+ * argument immediately before the variadic arguments.
+ */
+#define PRINT_DESC(prefix, format)              \
+    do {                                        \
+        if (format != NULL) {                   \
+            va_list args;                       \
+            if (prefix != NULL)                 \
+                printf("%s", prefix);           \
+            va_start(args, format);             \
+            vprintf(format, args);              \
+            va_end(args);                       \
+        }                                       \
+    } while (0)
+
 
 /*
  * Our exit handler.  Called on completion of the test to report a summary of
  * results provided we're still in the original process.  This also handles
  * printing out the plan if we used plan_lazy(), although that's suppressed if
- * we never ran a test (due to an early bail, for example).
+ * we never ran a test (due to an early bail, for example), and running any
+ * registered cleanup functions.
  */
 static void
 finish(void)
 {
+    int success;
+    struct cleanup_func *current;
     unsigned long highest = testnum - 1;
 
-    if (_planned == 0 && !_lazy)
-        return;
-    fflush(stderr);
-    if (_process != 0 && getpid() == _process) {
-        if (_lazy && highest > 0) {
-            printf("1..%lu\n", highest);
-            _planned = highest;
+    /*
+     * Don't do anything except free the cleanup functions if we aren't the
+     * primary process (the process in which plan or plan_lazy was called).
+     */
+    if (_process != 0 && getpid() != _process) {
+        while (cleanup_funcs != NULL) {
+            current = cleanup_funcs;
+            cleanup_funcs = cleanup_funcs->next;
+            free(current);
         }
-        if (_planned > highest)
-            printf("# Looks like you planned %lu test%s but only ran %lu\n",
-                   _planned, (_planned > 1 ? "s" : ""), highest);
-        else if (_planned < highest)
-            printf("# Looks like you planned %lu test%s but ran %lu extra\n",
-                   _planned, (_planned > 1 ? "s" : ""), highest - _planned);
-        else if (_failed > 0)
-            printf("# Looks like you failed %lu test%s of %lu\n", _failed,
-                   (_failed > 1 ? "s" : ""), _planned);
-        else if (_planned > 1)
-            printf("# All %lu tests successful or skipped\n", _planned);
-        else
-            printf("# %lu test successful or skipped\n", _planned);
+        return;
     }
+
+    /*
+     * Determine whether all tests were successful, which is needed before
+     * calling cleanup functions since we pass that fact to the functions.
+     */
+    if (_planned == 0 && _lazy)
+        _planned = highest;
+    success = (!_aborted && _planned == highest && _failed == 0);
+
+    /*
+     * If there are any registered cleanup functions, we run those first.  We
+     * always run them, even if we didn't run a test.
+     */
+    while (cleanup_funcs != NULL) {
+        cleanup_funcs->func(success);
+        current = cleanup_funcs;
+        cleanup_funcs = cleanup_funcs->next;
+        free(current);
+    }
+
+    /* Don't do anything further if we never planned a test. */
+    if (_planned == 0)
+        return;
+
+    /* If we're aborting due to bail, don't print summaries. */
+    if (_aborted)
+        return;
+
+    /* Print out the lazy plan if needed. */
+    fflush(stderr);
+    if (_lazy && _planned > 0)
+        printf("1..%lu\n", _planned);
+
+    /* Print out a summary of the results. */
+    if (_planned > highest)
+        diag("Looks like you planned %lu test%s but only ran %lu", _planned,
+             (_planned > 1 ? "s" : ""), highest);
+    else if (_planned < highest)
+        diag("Looks like you planned %lu test%s but ran %lu extra", _planned,
+             (_planned > 1 ? "s" : ""), highest - _planned);
+    else if (_failed > 0)
+        diag("Looks like you failed %lu test%s of %lu", _failed,
+             (_failed > 1 ? "s" : ""), _planned);
+    else if (_planned != 1)
+        diag("All %lu tests successful or skipped", _planned);
+    else
+        diag("%lu test successful or skipped", _planned);
 }
 
 
@@ -124,14 +204,16 @@ void
 plan(unsigned long count)
 {
     if (setvbuf(stdout, NULL, _IOLBF, BUFSIZ) != 0)
-        fprintf(stderr, "# cannot set stdout to line buffered: %s\n",
-                strerror(errno));
+        sysdiag("cannot set stdout to line buffered");
     fflush(stderr);
     printf("1..%lu\n", count);
     testnum = 1;
     _planned = count;
     _process = getpid();
-    atexit(finish);
+    if (atexit(finish) != 0) {
+        sysdiag("cannot register exit handler");
+        diag("cleanups will not be run");
+    }
 }
 
 
@@ -143,12 +225,12 @@ void
 plan_lazy(void)
 {
     if (setvbuf(stdout, NULL, _IOLBF, BUFSIZ) != 0)
-        fprintf(stderr, "# cannot set stdout to line buffered: %s\n",
-                strerror(errno));
+        sysdiag("cannot set stdout to line buffered");
     testnum = 1;
     _process = getpid();
     _lazy = 1;
-    atexit(finish);
+    if (atexit(finish) != 0)
+        sysbail("cannot register exit handler to display plan");
 }
 
 
@@ -161,30 +243,12 @@ skip_all(const char *format, ...)
 {
     fflush(stderr);
     printf("1..0 # skip");
-    if (format != NULL) {
-        va_list args;
-
-        putchar(' ');
-        va_start(args, format);
-        vprintf(format, args);
-        va_end(args);
-    }
+    PRINT_DESC(" ", format);
     putchar('\n');
     exit(0);
 }
 
 
-/*
- * Print the test description.
- */
-static void
-print_desc(const char *format, va_list args)
-{
-    printf(" - ");
-    vprintf(format, args);
-}
-
-
 /*
  * Takes a boolean success value and assumes the test passes if that value
  * is true and fails if that value is false.
@@ -196,13 +260,7 @@ ok(int success, const char *format, ...)
     printf("%sok %lu", success ? "" : "not ", testnum++);
     if (!success)
         _failed++;
-    if (format != NULL) {
-        va_list args;
-
-        va_start(args, format);
-        print_desc(format, args);
-        va_end(args);
-    }
+    PRINT_DESC(" - ", format);
     putchar('\n');
 }
 
@@ -217,8 +275,10 @@ okv(int success, const char *format, va_list args)
     printf("%sok %lu", success ? "" : "not ", testnum++);
     if (!success)
         _failed++;
-    if (format != NULL)
-        print_desc(format, args);
+    if (format != NULL) {
+        printf(" - ");
+        vprintf(format, args);
+    }
     putchar('\n');
 }
 
@@ -231,14 +291,7 @@ skip(const char *reason, ...)
 {
     fflush(stderr);
     printf("ok %lu # skip", testnum++);
-    if (reason != NULL) {
-        va_list args;
-
-        va_start(args, reason);
-        putchar(' ');
-        vprintf(reason, args);
-        va_end(args);
-    }
+    PRINT_DESC(" ", reason);
     putchar('\n');
 }
 
@@ -256,13 +309,7 @@ ok_block(unsigned long count, int status, const char *format, ...)
         printf("%sok %lu", status ? "" : "not ", testnum++);
         if (!status)
             _failed++;
-        if (format != NULL) {
-            va_list args;
-
-            va_start(args, format);
-            print_desc(format, args);
-            va_end(args);
-        }
+        PRINT_DESC(" - ", format);
         putchar('\n');
     }
 }
@@ -279,14 +326,7 @@ skip_block(unsigned long count, const char *reason, ...)
     fflush(stderr);
     for (i = 0; i < count; i++) {
         printf("ok %lu # skip", testnum++);
-        if (reason != NULL) {
-            va_list args;
-
-            va_start(args, reason);
-            putchar(' ');
-            vprintf(reason, args);
-            va_end(args);
-        }
+        PRINT_DESC(" ", reason);
         putchar('\n');
     }
 }
@@ -303,17 +343,12 @@ is_int(long wanted, long seen, const char *format, ...)
     if (wanted == seen)
         printf("ok %lu", testnum++);
     else {
-        printf("# wanted: %ld\n#   seen: %ld\n", wanted, seen);
+        diag("wanted: %ld", wanted);
+        diag("  seen: %ld", seen);
         printf("not ok %lu", testnum++);
         _failed++;
     }
-    if (format != NULL) {
-        va_list args;
-
-        va_start(args, format);
-        print_desc(format, args);
-        va_end(args);
-    }
+    PRINT_DESC(" - ", format);
     putchar('\n');
 }
 
@@ -333,17 +368,12 @@ is_string(const char *wanted, const char *seen, const char *format, ...)
     if (strcmp(wanted, seen) == 0)
         printf("ok %lu", testnum++);
     else {
-        printf("# wanted: %s\n#   seen: %s\n", wanted, seen);
+        diag("wanted: %s", wanted);
+        diag("  seen: %s", seen);
         printf("not ok %lu", testnum++);
         _failed++;
     }
-    if (format != NULL) {
-        va_list args;
-
-        va_start(args, format);
-        print_desc(format, args);
-        va_end(args);
-    }
+    PRINT_DESC(" - ", format);
     putchar('\n');
 }
 
@@ -359,18 +389,12 @@ is_hex(unsigned long wanted, unsigned long seen, const char *format, ...)
     if (wanted == seen)
         printf("ok %lu", testnum++);
     else {
-        printf("# wanted: %lx\n#   seen: %lx\n", (unsigned long) wanted,
-               (unsigned long) seen);
+        diag("wanted: %lx", (unsigned long) wanted);
+        diag("  seen: %lx", (unsigned long) seen);
         printf("not ok %lu", testnum++);
         _failed++;
     }
-    if (format != NULL) {
-        va_list args;
-
-        va_start(args, format);
-        print_desc(format, args);
-        va_end(args);
-    }
+    PRINT_DESC(" - ", format);
     putchar('\n');
 }
 
@@ -383,6 +407,7 @@ bail(const char *format, ...)
 {
     va_list args;
 
+    _aborted = 1;
     fflush(stderr);
     fflush(stdout);
     printf("Bail out! ");
@@ -390,7 +415,7 @@ bail(const char *format, ...)
     vprintf(format, args);
     va_end(args);
     printf("\n");
-    exit(1);
+    exit(255);
 }
 
 
@@ -403,6 +428,7 @@ sysbail(const char *format, ...)
     va_list args;
     int oerrno = errno;
 
+    _aborted = 1;
     fflush(stderr);
     fflush(stdout);
     printf("Bail out! ");
@@ -410,7 +436,7 @@ sysbail(const char *format, ...)
     vprintf(format, args);
     va_end(args);
     printf(": %s\n", strerror(oerrno));
-    exit(1);
+    exit(255);
 }
 
 
@@ -627,3 +653,22 @@ test_tmpdir_free(char *path)
     if (path != NULL)
         free(path);
 }
+
+
+/*
+ * Register a cleanup function that is called when testing ends.  All such
+ * registered functions will be run by finish.
+ */
+void
+test_cleanup_register(test_cleanup_func func)
+{
+    struct cleanup_func *cleanup, **last;
+
+    cleanup = bmalloc(sizeof(struct cleanup_func));
+    cleanup->func = func;
+    cleanup->next = NULL;
+    last = &cleanup_funcs;
+    while (*last != NULL)
+        last = &(*last)->next;
+    *last = cleanup;
+}
index c55f6621eaa7525fa5f0ba069187f0b97a72fa80..92d348a15a0c6af752e66d796b0cd57e331a44ad 100644 (file)
@@ -4,7 +4,7 @@
  * This file is part of C TAP Harness.  The current version plus supporting
  * documentation is at <http://www.eyrie.org/~eagle/software/c-tap-harness/>.
  *
- * Copyright 2009, 2010, 2011, 2012 Russ Allbery <eagle@eyrie.org>
+ * Copyright 2009, 2010, 2011, 2012, 2013 Russ Allbery <eagle@eyrie.org>
  * Copyright 2001, 2002, 2004, 2005, 2006, 2007, 2008, 2011, 2012
  *     The Board of Trustees of the Leland Stanford Junior University
  *
@@ -32,7 +32,7 @@
 
 #include <tests/tap/macros.h>
 #include <stdarg.h>             /* va_list */
-#include <sys/types.h>          /* size_t */
+#include <stddef.h>             /* size_t */
 
 /*
  * Used for iterating through arrays.  ARRAY_SIZE returns the number of
@@ -55,7 +55,7 @@ extern unsigned long testnum;
 void plan(unsigned long count);
 
 /*
- * Prepare for lazy planning, in which the plan will be  printed automatically
+ * Prepare for lazy planning, in which the plan will be printed automatically
  * at the end of the test program.
  */
 void plan_lazy(void);
@@ -103,22 +103,22 @@ void sysdiag(const char *format, ...)
 
 /* Allocate memory, reporting a fatal error with bail on failure. */
 void *bcalloc(size_t, size_t)
-    __attribute__((__alloc_size__(1, 2), __malloc__));
+    __attribute__((__alloc_size__(1, 2), __malloc__, __warn_unused_result__));
 void *bmalloc(size_t)
-    __attribute__((__alloc_size__(1), __malloc__));
+    __attribute__((__alloc_size__(1), __malloc__, __warn_unused_result__));
 void *brealloc(void *, size_t)
-    __attribute__((__alloc_size__(2), __malloc__));
+    __attribute__((__alloc_size__(2), __malloc__, __warn_unused_result__));
 char *bstrdup(const char *)
-    __attribute__((__malloc__, __nonnull__));
+    __attribute__((__malloc__, __nonnull__, __warn_unused_result__));
 char *bstrndup(const char *, size_t)
-    __attribute__((__malloc__, __nonnull__));
+    __attribute__((__malloc__, __nonnull__, __warn_unused_result__));
 
 /*
  * Find a test file under BUILD or SOURCE, returning the full path.  The
  * returned path should be freed with test_file_path_free().
  */
 char *test_file_path(const char *file)
-    __attribute__((__malloc__, __nonnull__));
+    __attribute__((__malloc__, __nonnull__, __warn_unused_result__));
 void test_file_path_free(char *path);
 
 /*
@@ -126,9 +126,20 @@ void test_file_path_free(char *path);
  * returned path should be freed with test_tmpdir_free.
  */
 char *test_tmpdir(void)
-    __attribute__((__malloc__));
+    __attribute__((__malloc__, __warn_unused_result__));
 void test_tmpdir_free(char *path);
 
+/*
+ * Register a cleanup function that is called when testing ends.  All such
+ * registered functions will be run during atexit handling (and are therefore
+ * subject to all the same constraints and caveats as atexit functions).  The
+ * function must return void and will be passed one argument, an int that will
+ * be true if the test completed successfully and false otherwise.
+ */
+typedef void (*test_cleanup_func)(int);
+void test_cleanup_register(test_cleanup_func)
+    __attribute__((__nonnull__));
+
 END_DECLS
 
 #endif /* TAP_BASIC_H */
index 1b0293931f48566c3374965aabf6e1683e5b170c..9731032addeb85b2770248d7725ca07a0d6cdeeb 100644 (file)
@@ -11,7 +11,7 @@
 #
 # Written by Russ Allbery <eagle@eyrie.org>
 # Copyright 2009, 2010, 2011, 2012 Russ Allbery <eagle@eyrie.org>
-# Copyright 2006, 2007, 2008
+# Copyright 2006, 2007, 2008, 2013
 #     The Board of Trustees of the Leland Stanford Junior University
 #
 # Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -204,7 +204,7 @@ strip_colon_error() {
 # Bail out with an error message.
 bail () {
     echo 'Bail out!' "$@"
-    exit 1
+    exit 255
 }
 
 # Output a diagnostic on standard error, preceded by the required # mark.
index 368f95e6d03f90bb7c4e7ccc61e8a339f2db3ba8..04cc420ddc76b7b08cd89fc00693251d8b9362d5 100644 (file)
@@ -8,7 +8,7 @@
  * This file is part of C TAP Harness.  The current version plus supporting
  * documentation is at <http://www.eyrie.org/~eagle/software/c-tap-harness/>.
  *
- * Copyright 2008, 2012 Russ Allbery <eagle@eyrie.org>
+ * Copyright 2008, 2012, 2013 Russ Allbery <eagle@eyrie.org>
  *
  * Permission is hereby granted, free of charge, to any person obtaining a
  * copy of this software and associated documentation files (the "Software"),
 # endif
 #endif
 
+/* Suppress __warn_unused_result__ if gcc is too old. */
+#if !defined(__attribute__) && !defined(__warn_unused_result__)
+# if __GNUC__ < 3 || (__GNUC__ == 3 && __GNUC_MINOR__ < 4)
+#  define __warn_unused_result__ /* empty */
+# endif
+#endif
+
 /*
  * LLVM and Clang pretend to be GCC but don't support all of the __attribute__
  * settings that GCC does.  For them, suppress warnings about unknown
diff --git a/tests/tap/perl/Test/RRA.pm b/tests/tap/perl/Test/RRA.pm
new file mode 100644 (file)
index 0000000..5223680
--- /dev/null
@@ -0,0 +1,223 @@
+# 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 <eagle@eyrie.org>
+# 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.11';
+}
+
+# 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) {
+        my $name = length($version) > 0 ? "$module $version" : $module;
+        plan skip_all => "$name 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 <eagle@eyrie.org>
+
+=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 (file)
index 0000000..3a30313
--- /dev/null
@@ -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 <eagle@eyrie.org>
+# 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.11';
+}
+
+# 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 <eagle@eyrie.org>
+
+=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 (file)
index 0000000..38d96a2
--- /dev/null
@@ -0,0 +1,209 @@
+# 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 @STRICT_IGNORE
+    );
+
+    # 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.11';
+}
+
+# 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;
+our @STRICT_IGNORE;
+
+# 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 subdirectory
+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 F<.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.
+
+=item @STRICT_IGNORE
+
+Additional directories to ignore when doing recursive Test::Strict testing
+for C<use strict> and C<use warnings>.  The contents of this directory
+must be either top-level directory names or directory names starting with
+F<tests/>.
+
+=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 <eagle@eyrie.org>
+
+=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
+
+perlcritic(1), Test::MinimumVersion(3), Test::RRA(3),
+Test::RRA::Automake(3), Test::Strict(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
index 2a42f8da33bf66882a25397ed0859db314b00a9f..ac60aae87b2cd96c347f096003068c6fbce6e63a 100644 (file)
@@ -102,6 +102,7 @@ run_child_function(test_function_type function, void *data, int *status,
         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. */
index 8e9daf10aeda9b877f9cf73eda1b32b654f11d7b..8015c4e62943f952667f0923f0d8525a99a9a873 100644 (file)
@@ -5,7 +5,7 @@
  * which can be found at <http://www.eyrie.org/~eagle/software/rra-c-util/>.
  *
  * Written by Russ Allbery <eagle@eyrie.org>
- * Copyright 2010, 2011
+ * Copyright 2010, 2011, 2013
  *     The Board of Trustees of the Leland Stanford Junior University
  *
  * Permission is hereby granted, free of charge, to any person obtaining a
@@ -116,5 +116,7 @@ main(void)
     message_handlers_die(0);
     is_function_output(test_die, NULL, 1, "", "warn_krb5 with no handlers");
 
+    krb5_free_error_message(ctx, message);
+    krb5_free_context(ctx);
     return 0;
 }
index 66145860f6edaacdd78ac4b9083d8718b02cf13f..a415614cdd6b52e3f18834c995518f82d1d2256c 100644 (file)
@@ -5,7 +5,7 @@
  * which can be found at <http://www.eyrie.org/~eagle/software/rra-c-util/>.
  *
  * Copyright 2000, 2001, 2006 Russ Allbery <eagle@eyrie.org>
- * Copyright 2008, 2012
+ * Copyright 2008, 2012, 2013
  *     The Board of Trustees of the Leland Stanford Junior University
  *
  * Permission is hereby granted, free of charge, to any person obtaining a
@@ -322,6 +322,7 @@ main(int argc, char *argv[])
 #if HAVE_SETRLIMIT && defined(RLIMIT_AS)
         struct rlimit rl;
         void *tmp;
+        size_t test_size;
 
         rl.rlim_cur = limit;
         rl.rlim_max = limit;
@@ -330,10 +331,13 @@ main(int argc, char *argv[])
             exit(2);
         }
         if (size < limit || code == 'r') {
-            tmp = malloc(code == 'r' ? 10 : size);
+            test_size = code == 'r' ? 10 : size;
+            if (test_size == 0)
+                test_size = 1;
+            tmp = malloc(test_size);
             if (tmp == NULL) {
                 syswarn("Can't allocate initial memory of %lu (limit %lu)",
-                        (unsigned long) size, (unsigned long) limit);
+                        (unsigned long) test_size, (unsigned long) limit);
                 exit(2);
             }
             free(tmp);
index b05858663e33026eb675ebb3d1e0a8a5f8129c9e..961ea1d18fec80e2a9f922243b29b52b647b6b28 100644 (file)
@@ -9,7 +9,7 @@
  * which can be found at <http://www.eyrie.org/~eagle/software/rra-c-util/>.
  *
  * Written by Russ Allbery <eagle@eyrie.org>
- * Copyright 2006, 2007, 2008, 2009, 2010
+ * Copyright 2006, 2007, 2008, 2009, 2010, 2013
  *     The Board of Trustees of the Leland Stanford Junior University
  *
  * Permission is hereby granted, free of charge, to any person obtaining a
@@ -35,7 +35,6 @@
 #include <portable/krb5.h>
 #include <portable/system.h>
 
-#include <util/macros.h>
 #include <util/messages.h>
 #include <util/messages-krb5.h>
 #include <util/xmalloc.h>
index a9072bf0a40e7b1fdb6277bccb8c0bc929ea1dc7..3fc08620bf89e2b5765d9ecf5af15e29c9effbf2 100644 (file)
@@ -5,7 +5,7 @@
  * which can be found at <http://www.eyrie.org/~eagle/software/rra-c-util/>.
  *
  * Written by Russ Allbery <eagle@eyrie.org>
- * Copyright 2006, 2007, 2008, 2009, 2010
+ * Copyright 2006, 2007, 2008, 2009, 2010, 2013
  *     The Board of Trustees of the Leland Stanford Junior University
  *
  * Permission is hereby granted, free of charge, to any person obtaining a
 #define UTIL_MESSAGES_KRB5_H 1
 
 #include <config.h>
+#include <portable/krb5.h>
 #include <portable/macros.h>
 
-#include <krb5.h>
-#include <sys/types.h>
-
 BEGIN_DECLS
 
 /* Default to a hidden visibility for all util functions. */
index 9ec3ba855860445c720e6aa36560e93a124108ce..769166fc4c01b4cd0cb33a13092ce129d0b205e4 100644 (file)
@@ -54,7 +54,7 @@
  * which can be found at <http://www.eyrie.org/~eagle/software/rra-c-util/>.
  *
  * Written by Russ Allbery <eagle@eyrie.org>
- * Copyright 2008, 2009, 2010
+ * Copyright 2008, 2009, 2010, 2013
  *     The Board of Trustees of the Leland Stanford Junior University
  * Copyright (c) 2004, 2005, 2006
  *     by Internet Systems Consortium, Inc. ("ISC")
@@ -204,6 +204,7 @@ static void
 message_log_syslog(int pri, size_t len, const char *fmt, va_list args, int err)
 {
     char *buffer;
+    int status;
 
     buffer = malloc(len + 1);
     if (buffer == NULL) {
@@ -211,7 +212,12 @@ message_log_syslog(int pri, size_t len, const char *fmt, va_list args, int err)
                 (unsigned long) len + 1, __FILE__, __LINE__, strerror(errno));
         exit(message_fatal_cleanup ? (*message_fatal_cleanup)() : 1);
     }
-    vsnprintf(buffer, len + 1, fmt, args);
+    status = vsnprintf(buffer, len + 1, fmt, args);
+    if (status < 0) {
+        warn("failed to format output with vsnprintf in syslog handler");
+        free(buffer);
+        return;
+    }
 #ifdef _WIN32
     {
         HANDLE eventlog;
index 463137c4a6f6c5e0c08d21f61a8282f23e90acdf..9ea1a8b4272647ba20e8f148af8427b3d65e38ab 100644 (file)
@@ -4,7 +4,7 @@
  * 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 2008, 2010
+ * Copyright 2008, 2010, 2013
  *     The Board of Trustees of the Leland Stanford Junior University
  * Copyright (c) 2004, 2005, 2006
  *     by Internet Systems Consortium, Inc. ("ISC")
@@ -34,6 +34,7 @@
 #include <portable/macros.h>
 
 #include <stdarg.h>
+#include <stddef.h>
 
 BEGIN_DECLS
 
index a78e31a43061cfc9bcfee35837fe6a3cfbf4194d..5bfd5558589f2290a8c221a5747194a950dfac5c 100644 (file)
@@ -58,7 +58,7 @@
  * 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 2012
+ * Copyright 2012, 2013
  *     The Board of Trustees of the Leland Stanford Junior University
  * Copyright (c) 2004, 2005, 2006
  *     by Internet Systems Consortium, Inc. ("ISC")
@@ -84,8 +84,6 @@
 #include <config.h>
 #include <portable/system.h>
 
-#include <errno.h>
-
 #include <util/messages.h>
 #include <util/xmalloc.h>
 
 void
 xmalloc_fail(const char *function, size_t size, const char *file, int line)
 {
-    sysdie("failed to %s %lu bytes at %s line %d", function,
-           (unsigned long) size, file, line);
+    if (size == 0)
+        sysdie("failed to format output with %s at %s line %d", function,
+               file, line);
+    else
+        sysdie("failed to %s %lu bytes at %s line %d", function,
+               (unsigned long) size, file, line);
 }
 
 /* Assign to this variable to choose a handler other than the default. */
@@ -208,7 +210,8 @@ x_vasprintf(char **strp, const char *fmt, va_list args, const char *file,
         va_copy(args_copy, args);
         status = vsnprintf(NULL, 0, fmt, args_copy);
         va_end(args_copy);
-        (*xmalloc_error_handler)("vasprintf", status + 1, file, line);
+        status = (status < 0) ? 0 : status + 1;
+        (*xmalloc_error_handler)("vasprintf", status, file, line);
         va_copy(args_copy, args);
         status = vasprintf(strp, fmt, args_copy);
         va_end(args_copy);
@@ -231,7 +234,8 @@ x_asprintf(char **strp, const char *file, int line, const char *fmt, ...)
         va_copy(args_copy, args);
         status = vsnprintf(NULL, 0, fmt, args_copy);
         va_end(args_copy);
-        (*xmalloc_error_handler)("asprintf", status + 1, file, line);
+        status = (status < 0) ? 0 : status + 1;
+        (*xmalloc_error_handler)("asprintf", status, file, line);
         va_copy(args_copy, args);
         status = vasprintf(strp, fmt, args_copy);
         va_end(args_copy);
@@ -252,7 +256,8 @@ x_asprintf(char **strp, const char *fmt, ...)
         va_copy(args_copy, args);
         status = vsnprintf(NULL, 0, fmt, args_copy);
         va_end(args_copy);
-        (*xmalloc_error_handler)("asprintf", status + 1, __FILE__, __LINE__);
+        status = (status < 0) ? 0 : status + 1;
+        (*xmalloc_error_handler)("asprintf", status, __FILE__, __LINE__);
         va_copy(args_copy, args);
         status = vasprintf(strp, fmt, args_copy);
         va_end(args_copy);
index 55a0b91480d73c356e0995e5e5174e56fe1b4843..ac4d79629c0d302376ab01768b58526f1102a9a5 100644 (file)
@@ -4,7 +4,7 @@
  * 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 2010, 2012
+ * Copyright 2010, 2012, 2013
  *     The Board of Trustees of the Leland Stanford Junior University
  * Copyright (c) 2004, 2005, 2006
  *     by Internet Systems Consortium, Inc. ("ISC")
@@ -33,7 +33,8 @@
 #include <config.h>
 #include <portable/macros.h>
 
-#include <sys/types.h>
+#include <stdarg.h>
+#include <stddef.h>
 
 /*
  * The functions are actually macros so that we can pick up the file and line
@@ -96,7 +97,11 @@ void x_asprintf(char **, const char *, ...)
     __attribute__((__nonnull__, __format__(printf, 2, 3)));
 #endif
 
-/* Failure handler takes the function, the size, the file, and the line. */
+/*
+ * Failure handler takes the function, the size, the file, and the line.  The
+ * size will be zero if the failure was due to some failure in snprintf
+ * instead of a memory allocation failure.
+ */
 typedef void (*xmalloc_handler_type)(const char *, size_t, const char *, int);
 
 /* The default error handler. */