diff options
| author | Russ Allbery <eagle@eyrie.org> | 2014-07-16 13:46:50 -0700 | 
|---|---|---|
| committer | Russ Allbery <eagle@eyrie.org> | 2014-07-16 13:46:50 -0700 | 
| commit | 1796d631f0846ec98cd286bc4284898a7300ee78 (patch) | |
| tree | 6fd42de6dc858ef06c6d270410c32ec61f39e593 /tests | |
| parent | f5194217566a6f4cdeffbae551153feb1412210d (diff) | |
| parent | 6409733ee3b7b1910dc1c166a392cc628834146c (diff) | |
Merge tag 'upstream/1.1' into debian
Upstream version 1.1
Conflicts:
	NEWS
	README
	client/keytab.c
	perl/lib/Wallet/ACL.pm
	perl/sql/Wallet-Schema-0.08-PostgreSQL.sql
	perl/t/general/admin.t
	perl/t/verifier/ldap-attr.t
Change-Id: I1a1dc09b97c9258e61f1c8877d0837193c8ae2c6
Diffstat (limited to 'tests')
45 files changed, 1963 insertions, 605 deletions
| diff --git a/tests/HOWTO b/tests/HOWTO index 5d38748..b94985d 100644 --- a/tests/HOWTO +++ b/tests/HOWTO @@ -240,7 +240,7 @@ License      This file is part of the documentation of C TAP Harness, which can be      found at <http://www.eyrie.org/~eagle/software/c-tap-harness/>. -    Copyright 2010 Russ Allbery <rra@stanford.edu> +    Copyright 2010 Russ Allbery <eagle@eyrie.org>      Copying and distribution of this file, with or without modification,      are permitted in any medium without royalty provided the copyright diff --git a/tests/TESTS b/tests/TESTS index 807d944..d947e97 100644 --- a/tests/TESTS +++ b/tests/TESTS @@ -4,6 +4,8 @@ client/prompt  client/rekey  docs/pod  docs/pod-spelling +perl/minimum-version +perl/strict  portable/asprintf  portable/mkstemp  portable/setenv diff --git a/tests/client/basic-t.in b/tests/client/basic-t.in index 836f394..974b636 100644 --- a/tests/client/basic-t.in +++ b/tests/client/basic-t.in @@ -2,7 +2,7 @@  #  # Test suite for the wallet command-line client.  # -# Written by Russ Allbery <rra@stanford.edu> +# Written by Russ Allbery <eagle@eyrie.org>  # Copyright 2006, 2007, 2008, 2010  #     The Board of Trustees of the Leland Stanford Junior University  # diff --git a/tests/client/full-t.in b/tests/client/full-t.in index ebdba03..4861723 100644 --- a/tests/client/full-t.in +++ b/tests/client/full-t.in @@ -1,21 +1,23 @@ -#!/usr/bin/perl -w +#!/usr/bin/perl  #  # End-to-end tests for the wallet client.  # -# Written by Russ Allbery <rra@stanford.edu> -# Copyright 2008, 2010 +# Written by Russ Allbery <eagle@eyrie.org> +# Copyright 2008, 2010, 2014  #     The Board of Trustees of the Leland Stanford Junior University  #  # See LICENSE for licensing terms. +use strict; +use warnings; +  # Point to our server configuration.  This must be done before Wallet::Config  # is loaded, and it's pulled in as a prerequisite for Wallet::Admin.  BEGIN { $ENV{WALLET_CONFIG} = "$ENV{SOURCE}/data/wallet.conf" } -BEGIN { our $total = 59 } -use Test::More tests => $total; +use Test::More tests => 59; -use lib "$ENV{SOURCE}/../perl"; +use lib "$ENV{SOURCE}/../perl/lib";  use Wallet::Admin;  use lib "$ENV{SOURCE}/../perl/t/lib"; @@ -56,10 +58,10 @@ sub wallet {  chdir "$ENV{SOURCE}" or die "Cannot chdir to $ENV{SOURCE}: $!\n";  SKIP: { -    skip 'no keytab configuration', $total +    skip 'no keytab configuration', 59          unless -f "$ENV{BUILD}/config/keytab";      my $remctld = '@REMCTLD@'; -    skip 'remctld not found', $total unless $remctld; +    skip 'remctld not found', 59 unless $remctld;      # Spawn remctld and get local tickets.  Don't destroy the user's Kerberos      # ticket cache. @@ -188,8 +190,12 @@ SKIP: {      # All done.      remctld_stop;      $admin->destroy; -    unlink ('wallet-db', 'krb5cc_test', 'test-pid');      if (-d 'test-files') {          system ('rm', '-r', 'test-files');      }  } + +# Clean up the database and other test files at the end of the test. +END { +    unlink ('wallet-db', 'krb5cc_test', 'test-pid'); +} diff --git a/tests/client/prompt-t.in b/tests/client/prompt-t.in index 06991cc..686cc88 100644 --- a/tests/client/prompt-t.in +++ b/tests/client/prompt-t.in @@ -1,17 +1,19 @@ -#!/usr/bin/perl -w +#!/usr/bin/perl  #  # Password prompting tests for the wallet client.  # -# Written by Russ Allbery <rra@stanford.edu> -# Copyright 2008, 2010 +# Written by Russ Allbery <eagle@eyrie.org> +# Copyright 2008, 2010, 2014  #     The Board of Trustees of the Leland Stanford Junior University  #  # See LICENSE for licensing terms. -BEGIN { our $total = 5 } -use Test::More tests => $total; +use strict; +use warnings; -use lib "$ENV{SOURCE}/..//perl"; +use Test::More tests => 5; + +use lib "$ENV{SOURCE}/../perl/lib";  use Wallet::Admin;  use lib "$ENV{SOURCE}/../perl/t/lib"; @@ -21,12 +23,12 @@ use Util;  chdir "$ENV{SOURCE}" or die "Cannot chdir to $ENV{SOURCE}: $!\n";  SKIP: { -    skip 'no password configuration', $total +    skip 'no password configuration', 5          unless -f "$ENV{BUILD}/config/password";      my $remctld = '@REMCTLD@'; -    skip 'remctld not found', $total unless $remctld; +    skip 'remctld not found', 5 unless $remctld;      eval { require Expect }; -    skip 'Expect module not found', $total if $@; +    skip 'Expect module not found', 5 if $@;      # Disable sending of wallet's output to our standard output.  Do this      # twice to avoid Perl warnings. diff --git a/tests/client/rekey-t.in b/tests/client/rekey-t.in index c6d0e41..c93b8eb 100644 --- a/tests/client/rekey-t.in +++ b/tests/client/rekey-t.in @@ -2,7 +2,7 @@  #  # Test suite for the wallet-rekey command-line client.  # -# Written by Russ Allbery <rra@stanford.edu> +# Written by Russ Allbery <eagle@eyrie.org>  # Copyright 2006, 2007, 2008, 2010  #     The Board of Trustees of the Leland Stanford Junior University  # diff --git a/tests/data/cmd-fake b/tests/data/cmd-fake index 11791a6..f889edd 100755 --- a/tests/data/cmd-fake +++ b/tests/data/cmd-fake @@ -3,7 +3,7 @@  # This is a fake wallet backend that returns bogus data for verification by  # the client test suite.  It doesn't test any of the wallet server code.  # -# Written by Russ Allbery <rra@stanford.edu> +# Written by Russ Allbery <eagle@eyrie.org>  # Copyright 2007, 2008, 2010  #     The Board of Trustees of the Leland Stanford Junior University  # diff --git a/tests/data/cmd-wrapper b/tests/data/cmd-wrapper index 79b1943..b5b6d26 100755 --- a/tests/data/cmd-wrapper +++ b/tests/data/cmd-wrapper @@ -5,4 +5,4 @@  WALLET_CONFIG="$SOURCE/data/wallet.conf"  export WALLET_CONFIG -exec perl -I"$SOURCE/../perl" "$SOURCE/../server/wallet-backend" -q "$@" +exec perl -I"$SOURCE/../perl/lib" "$SOURCE/../server/wallet-backend" -q "$@" diff --git a/tests/data/fake-kadmin b/tests/data/fake-kadmin index c073ea5..ff90f88 100755 --- a/tests/data/fake-kadmin +++ b/tests/data/fake-kadmin @@ -1,13 +1,16 @@ -#!/usr/bin/perl -w +#!/usr/bin/perl  #  # Fake kadmin.local used to test the keytab backend.  # -# Written by Russ Allbery <rra@stanford.edu> -# Copyright 2007 +# Written by Russ Allbery <eagle@eyrie.org> +# Copyright 2007, 2014  #     The Board of Trustees of the Leland Stanford Junior University  #  # See LICENSE for licensing terms. +use strict; +use warnings; +  unless ($ARGV[0] eq '-q' && @ARGV == 2) {      die "invalid arguments\n";  } diff --git a/tests/docs/pod-spelling-t b/tests/docs/pod-spelling-t index e1a95cd..7b61c86 100755 --- a/tests/docs/pod-spelling-t +++ b/tests/docs/pod-spelling-t @@ -1,14 +1,12 @@  #!/usr/bin/perl  #  # 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.  #  # The canonical version of this file is maintained in the rra-c-util package,  # which can be found at <http://www.eyrie.org/~eagle/software/rra-c-util/>.  # -# Written by Russ Allbery <rra@stanford.edu> -# Copyright 2012, 2013 +# Written by Russ Allbery <eagle@eyrie.org> +# Copyright 2012, 2013, 2014  #     The Board of Trustees of the Leland Stanford Junior University  #  # Permission is hereby granted, free of charge, to any person obtaining a @@ -36,11 +34,12 @@ use warnings;  use lib "$ENV{SOURCE}/tap/perl";  use Test::More; -use Test::RRA qw(skip_unless_maintainer use_prereq); +use Test::RRA qw(skip_unless_author use_prereq);  use Test::RRA::Automake qw(automake_setup perl_dirs); -# Only run this test for the maintainer. -skip_unless_maintainer('Spelling tests'); +# Only run this test for the module author since the required stopwords are +# too sensitive to the exact spell-checking program and dictionary. +skip_unless_author('Spelling tests');  # Load prerequisite modules.  use_prereq('Test::Spelling'); diff --git a/tests/docs/pod-t b/tests/docs/pod-t index 2743287..53f9925 100755 --- a/tests/docs/pod-t +++ b/tests/docs/pod-t @@ -1,4 +1,4 @@ -#!/usr/bin/perl -w +#!/usr/bin/perl  #  # Check all POD documents in the tree, except for any embedded Perl module  # distribution, for POD formatting errors. @@ -6,8 +6,8 @@  # The canonical version of this file is maintained in the rra-c-util package,  # which can be found at <http://www.eyrie.org/~eagle/software/rra-c-util/>.  # -# Written by Russ Allbery <rra@stanford.edu> -# Copyright 2012, 2013 +# Written by Russ Allbery <eagle@eyrie.org> +# Copyright 2012, 2013, 2014  #     The Board of Trustees of the Leland Stanford Junior University  #  # Permission is hereby granted, free of charge, to any person obtaining a @@ -35,9 +35,13 @@ use warnings;  use lib "$ENV{SOURCE}/tap/perl";  use Test::More; -use Test::RRA qw(use_prereq); +use Test::RRA qw(skip_unless_automated use_prereq);  use Test::RRA::Automake qw(automake_setup perl_dirs); +# Skip this test for normal user installs, since we normally pre-generate all +# of the documentation and the end user doesn't care. +skip_unless_automated('POD syntax tests'); +  # Load prerequisite modules.  use_prereq('Test::Pod'); diff --git a/tests/perl/minimum-version-t b/tests/perl/minimum-version-t new file mode 100755 index 0000000..8c49327 --- /dev/null +++ b/tests/perl/minimum-version-t @@ -0,0 +1,69 @@ +#!/usr/bin/perl +# +# Check that too-new features of Perl are not being used. +# +# This version of the check script supports mapping various directories to +# different version numbers.  This allows a newer version of Perl to be +# required for internal tools than for public code. +# +# 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 2012, 2013, 2014 +#     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. + +use 5.006; +use strict; +use warnings; + +use lib "$ENV{SOURCE}/tap/perl"; + +use Test::More; +use Test::RRA qw(skip_unless_automated use_prereq); +use Test::RRA::Automake qw(automake_setup perl_dirs); +use Test::RRA::Config qw($MINIMUM_VERSION %MINIMUM_VERSION); + +# Skip for normal user installs since this doesn't affect functionality. +skip_unless_automated('Minimum version tests'); + +# Load prerequisite modules. +use_prereq('Test::MinimumVersion'); + +# Set up Automake testing. +automake_setup(); + +# For each exception case in %MINIMUM_VERSION, check the files that should +# have that minium version.  Sort for reproducible test order.  Also +# accumulate the list of directories we've already tested. +my @tested; +for my $version (sort keys %MINIMUM_VERSION) { +    my $paths_ref = $MINIMUM_VERSION{$version}; +    all_minimum_version_ok($version, { paths => $paths_ref, no_plan => 1 }); +    push(@tested, @{$paths_ref}); +} + +# Now, check anything that's left against the default minimum version. +my @paths = perl_dirs({ skip => [@tested] }); +all_minimum_version_ok($MINIMUM_VERSION, { paths => \@paths, no_plan => 1 }); + +# Tell the TAP harness that we're done. +done_testing(); diff --git a/tests/perl/strict-t b/tests/perl/strict-t new file mode 100755 index 0000000..2df6d58 --- /dev/null +++ b/tests/perl/strict-t @@ -0,0 +1,66 @@ +#!/usr/bin/perl +# +# Check Perl scripts for strict, warnings, and syntax. +# +# Checks all Perl scripts in the tree for problems uncovered by Test::Strict. +# This includes using strict and warnings for every script and ensuring they +# all pass a syntax check.  Currently, test suite coverage is not checked. +# +# 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 2012, 2013, 2014 +#     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. + +use 5.006; +use strict; +use warnings; + +use lib "$ENV{SOURCE}/tap/perl"; + +use Test::More; +use Test::RRA qw(skip_unless_automated use_prereq); +use Test::RRA::Automake qw(automake_setup perl_dirs); +use Test::RRA::Config qw(@STRICT_IGNORE @STRICT_PREREQ); + +# Skip for normal user installs since this doesn't affect functionality. +skip_unless_automated('Strictness tests'); + +# Load prerequisite modules. +use_prereq('Test::Strict'); + +# Check whether all prerequisites are available, and skip the test if any of +# them are not. +for my $module (@STRICT_PREREQ) { +    use_prereq($module); +} + +# Set up Automake testing.  This must be done after loading Test::Strict, +# since it wants to use FindBin to locate this script. +automake_setup(); + +# Run the actual tests.  We also want to check warnings. +$Test::Strict::TEST_WARNINGS = 1; +all_perl_files_ok(perl_dirs({ skip => [@STRICT_IGNORE] })); + +# Suppress "used only once" warnings. +END { $Test::Strict::TEST_WARNINGS = 0 } diff --git a/tests/portable/asprintf-t.c b/tests/portable/asprintf-t.c index 4513a85..c61c14a 100644 --- a/tests/portable/asprintf-t.c +++ b/tests/portable/asprintf-t.c @@ -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/>.   * - * Written by Russ Allbery <rra@stanford.edu> + * Written by Russ Allbery <eagle@eyrie.org>   *   * The authors hereby relinquish any claim to any copyright that they may have   * in this work, whether granted under contract or by operation of law or diff --git a/tests/portable/mkstemp-t.c b/tests/portable/mkstemp-t.c index 98c708e..20a83fc 100644 --- a/tests/portable/mkstemp-t.c +++ b/tests/portable/mkstemp-t.c @@ -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/>.   * - * Written by Russ Allbery <rra@stanford.edu> + * Written by Russ Allbery <eagle@eyrie.org>   *   * The authors hereby relinquish any claim to any copyright that they may have   * in this work, whether granted under contract or by operation of law or diff --git a/tests/portable/setenv-t.c b/tests/portable/setenv-t.c index a1aecb5..15ed1fd 100644 --- a/tests/portable/setenv-t.c +++ b/tests/portable/setenv-t.c @@ -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/>.   * - * Written by Russ Allbery <rra@stanford.edu> + * Written by Russ Allbery <eagle@eyrie.org>   *   * The authors hereby relinquish any claim to any copyright that they may have   * in this work, whether granted under contract or by operation of law or diff --git a/tests/portable/snprintf-t.c b/tests/portable/snprintf-t.c index 927de96..270d2e1 100644 --- a/tests/portable/snprintf-t.c +++ b/tests/portable/snprintf-t.c @@ -4,9 +4,9 @@   * The canonical version of this file is maintained in the rra-c-util package,   * which can be found at <http://www.eyrie.org/~eagle/software/rra-c-util/>.   * - * Written by Russ Allbery <rra@stanford.edu> + * Written by Russ Allbery <eagle@eyrie.org>   * Copyright 2000, 2001, 2002, 2003, 2004, 2005, 2006 - *     Russ Allbery <rra@stanford.edu> + *     Russ Allbery <eagle@eyrie.org>   * Copyright 2009, 2010   *     The Board of Trustees of the Leland Stanford Junior University   * Copyright 1995 Patrick Powell diff --git a/tests/portable/strlcat-t.c b/tests/portable/strlcat-t.c index 54d0d40..58aba58 100644 --- a/tests/portable/strlcat-t.c +++ b/tests/portable/strlcat-t.c @@ -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/>.   * - * Written by Russ Allbery <rra@stanford.edu> + * Written by Russ Allbery <eagle@eyrie.org>   *   * The authors hereby relinquish any claim to any copyright that they may have   * in this work, whether granted under contract or by operation of law or diff --git a/tests/portable/strlcpy-t.c b/tests/portable/strlcpy-t.c index 26aa8f2..6652a7c 100644 --- a/tests/portable/strlcpy-t.c +++ b/tests/portable/strlcpy-t.c @@ -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/>.   * - * Written by Russ Allbery <rra@stanford.edu> + * Written by Russ Allbery <eagle@eyrie.org>   *   * The authors hereby relinquish any claim to any copyright that they may have   * in this work, whether granted under contract or by operation of law or diff --git a/tests/runtests.c b/tests/runtests.c index 4249875..a9d2373 100644 --- a/tests/runtests.c +++ b/tests/runtests.c @@ -54,8 +54,8 @@   * should be sent to the e-mail address below.  This program is part of C TAP   * Harness <http://www.eyrie.org/~eagle/software/c-tap-harness/>.   * - * Copyright 2000, 2001, 2004, 2006, 2007, 2008, 2009, 2010, 2011 - *     Russ Allbery <rra@stanford.edu> + * Copyright 2000, 2001, 2004, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, + *     2014 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"), @@ -86,7 +86,9 @@  #include <ctype.h>  #include <errno.h>  #include <fcntl.h> +#include <limits.h>  #include <stdarg.h> +#include <stddef.h>  #include <stdio.h>  #include <stdlib.h>  #include <string.h> @@ -101,12 +103,29 @@  /* sys/time.h must be included before sys/resource.h on some platforms. */  #include <sys/resource.h> -/* AIX doesn't have WCOREDUMP. */ +/* AIX 6.1 (and possibly later) doesn't have WCOREDUMP. */  #ifndef WCOREDUMP -# define WCOREDUMP(status)      ((unsigned)(status) & 0x80) +# define WCOREDUMP(status) ((unsigned)(status) & 0x80)  #endif  /* + * POSIX requires that these be defined in <unistd.h>, but they're not always + * available.  If one of them has been defined, all the rest almost certainly + * have. + */ +#ifndef STDIN_FILENO +# define STDIN_FILENO  0 +# define STDOUT_FILENO 1 +# define STDERR_FILENO 2 +#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.   * Normally, this should be set as part of the build process to the test @@ -138,7 +157,8 @@ enum plan_status {  /* Error exit statuses for test processes. */  #define CHILDERR_DUP    100     /* Couldn't redirect stderr or stdout. */  #define CHILDERR_EXEC   101     /* Couldn't exec child process. */ -#define CHILDERR_STDERR 102     /* Couldn't open stderr file. */ +#define CHILDERR_STDIN  102     /* Couldn't open stdin file. */ +#define CHILDERR_STDERR 103     /* Couldn't open stderr file. */  /* Structure to hold data for a set of tests. */  struct testset { @@ -153,7 +173,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 +187,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,9 +221,57 @@ Failed Set                 Fail/Total (%) Skip Stat  Failing Tests\n\  -------------------------- -------------- ---- ----  ------------------------";  /* Include the file name and line number in malloc failures. */ -#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__) +#define xcalloc(n, size)      x_calloc((n), (size), __FILE__, __LINE__) +#define xmalloc(size)         x_malloc((size), __FILE__, __LINE__) +#define xstrdup(p)            x_strdup((p), __FILE__, __LINE__) +#define xreallocarray(p, n, size) \ +    x_reallocarray((p), (n), (size), __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_reallocarray(void *, size_t, size_t, const char *, int) +    __attribute__((__alloc_size__(2, 3), __malloc__, __nonnull__(4))); +static char *x_strdup(const char *, const char *, int) +    __attribute__((__malloc__, __nonnull__));  /* @@ -223,6 +295,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.   */  static void * @@ -240,14 +330,26 @@ x_malloc(size_t size, const char *file, int line)  /*   * Reallocate memory, reporting a fatal error and exiting on failure. + * + * We should technically use SIZE_MAX here for the overflow check, but + * SIZE_MAX is C99 and we're only assuming C89 + SUSv3, which does not + * guarantee that it exists.  They do guarantee that UINT_MAX exists, and we + * can assume that UINT_MAX <= SIZE_MAX.  And we should not be allocating + * anything anywhere near that large. + * + * (In theory, C89 and C99 permit size_t to be smaller than unsigned int, but + * I disbelieve in the existence of such systems and they will have to cope + * without overflow checks.)   */  static void * -x_realloc(void *p, size_t size, const char *file, int line) +x_reallocarray(void *p, size_t n, size_t size, const char *file, int line)  { -    p = realloc(p, size); +    if (n > 0 && UINT_MAX / n <= size) +        sysdie("realloc too large at %s line %d", file, line); +    p = realloc(p, n * size);      if (p == NULL)          sysdie("failed to realloc %lu bytes at %s line %d", -               (unsigned long) size, file, line); +               (unsigned long) (n * size), file, line);      return p;  } @@ -272,6 +374,55 @@ x_strdup(const char *s, const char *file, int line)  /* + * Form a new string by concatenating multiple strings.  The arguments must be + * terminated by (const char *) 0. + * + * This function only exists because we can't assume asprintf.  We can't + * simulate asprintf with snprintf because we're only assuming SUSv3, which + * does not require that snprintf with a NULL buffer return the required + * length.  When those constraints are relaxed, this should be ripped out and + * replaced with asprintf or a more trivial replacement with snprintf. + */ +static char * +concat(const char *first, ...) +{ +    va_list args; +    char *result; +    const char *string; +    size_t offset; +    size_t length = 0; + +    /* +     * Find the total memory required.  Ensure we don't overflow length.  We +     * aren't guaranteed to have SIZE_MAX, so use UINT_MAX as an acceptable +     * substitute (see the x_nrealloc comments). +     */ +    va_start(args, first); +    for (string = first; string != NULL; string = va_arg(args, const char *)) { +        if (length >= UINT_MAX - strlen(string)) { +            errno = EINVAL; +            sysdie("strings too long in concat"); +        } +        length += strlen(string); +    } +    va_end(args); +    length++; + +    /* Create the string. */ +    result = xmalloc(length); +    va_start(args, first); +    offset = 0; +    for (string = first; string != NULL; string = va_arg(args, const char *)) { +        memcpy(result + offset, string, strlen(string)); +        offset += strlen(string); +    } +    va_end(args); +    result[offset] = '\0'; +    return result; +} + + +/*   * Given a struct timeval, return the number of seconds it represents as a   * double.  Use difftime() to convert a time_t to a double.   */ @@ -323,36 +474,62 @@ skip_whitespace(const char *p)  static pid_t  test_start(const char *path, int *fd)  { -    int fds[2], errfd; +    int fds[2], infd, errfd;      pid_t child; +    /* Create a pipe used to capture the output from the test program. */      if (pipe(fds) == -1) {          puts("ABORTED");          fflush(stdout);          sysdie("can't create pipe");      } + +    /* Fork a child process, massage the file descriptors, and exec. */      child = fork(); -    if (child == (pid_t) -1) { +    switch (child) { +    case -1:          puts("ABORTED");          fflush(stdout);          sysdie("can't fork"); -    } else if (child == 0) { -        /* In child.  Set up our stdout and stderr. */ + +    /* In the child.  Set up our standard output. */ +    case 0: +        close(fds[0]); +        close(STDOUT_FILENO); +        if (dup2(fds[1], STDOUT_FILENO) < 0) +            _exit(CHILDERR_DUP); +        close(fds[1]); + +        /* Point standard input at /dev/null. */ +        close(STDIN_FILENO); +        infd = open("/dev/null", O_RDONLY); +        if (infd < 0) +            _exit(CHILDERR_STDIN); +        if (infd != STDIN_FILENO) { +            if (dup2(infd, STDIN_FILENO) < 0) +                _exit(CHILDERR_DUP); +            close(infd); +        } + +        /* Point standard error at /dev/null. */ +        close(STDERR_FILENO);          errfd = open("/dev/null", O_WRONLY);          if (errfd < 0)              _exit(CHILDERR_STDERR); -        if (dup2(errfd, 2) == -1) -            _exit(CHILDERR_DUP); -        close(fds[0]); -        if (dup2(fds[1], 1) == -1) -            _exit(CHILDERR_DUP); +        if (errfd != STDERR_FILENO) { +            if (dup2(errfd, STDERR_FILENO) < 0) +                _exit(CHILDERR_DUP); +            close(errfd); +        }          /* Now, exec our process. */          if (execl(path, path, (char *) 0) == -1)              _exit(CHILDERR_EXEC); -    } else { -        /* In parent.  Close the extra file descriptor. */ + +    /* In parent.  Close the extra file descriptor. */ +    default:          close(fds[1]); +        break;      }      *fd = fds[0];      return child; @@ -380,6 +557,40 @@ test_backspace(struct testset *ts)  /* + * Allocate or resize the array of test results to be large enough to contain + * the test number in. + */ +static void +resize_results(struct testset *ts, unsigned long n) +{ +    unsigned long i; +    size_t s; + +    /* If there's already enough space, return quickly. */ +    if (n <= ts->allocated) +        return; + +    /* +     * If no space has been allocated, do the initial allocation.  Otherwise, +     * resize.  Start with 32 test cases and then add 1024 with each resize to +     * try to reduce the number of reallocations. +     */ +    if (ts->allocated == 0) { +        s = (n > 32) ? n : 32; +        ts->results = xcalloc(s, sizeof(enum test_status)); +    } else { +        s = (n > ts->allocated + 1024) ? n : ts->allocated + 1024; +        ts->results = xreallocarray(ts->results, s, sizeof(enum test_status)); +    } + +    /* Set the results for the newly-allocated test array. */ +    for (i = ts->allocated; i < s; i++) +        ts->results[i] = TEST_INVALID; +    ts->allocated = s; +} + + +/*   * Read the plan line of test output, which should contain the range of test   * numbers.  We may initialize the testset structure here if we haven't yet   * seen a test.  Return true if initialization succeeded and the test should @@ -388,7 +599,6 @@ test_backspace(struct testset *ts)  static int  test_plan(const char *line, struct testset *ts)  { -    unsigned long i;      long n;      /* @@ -401,12 +611,14 @@ test_plan(const char *line, struct testset *ts)          line += 3;      /* -     * Get the count, check it for validity, and initialize the struct.  If we -     * have something of the form "1..0 # skip foo", the whole file was +     * Get the count and check it for validity. +     * +     * If we have something of the form "1..0 # skip foo", the whole file was       * skipped; record that.  If we do skip the whole file, zero out all of -     * our statistics, since they're no longer relevant.  strtol is called -     * with a second argument to advance the line pointer past the count to -     * make it simpler to detect the # skip case. +     * our statistics, since they're no longer relevant. +     * +     * strtol is called with a second argument to advance the line pointer +     * past the count to make it simpler to detect the # skip case.       */      n = strtol(line, (char **) &line, 10);      if (n == 0) { @@ -435,29 +647,30 @@ test_plan(const char *line, struct testset *ts)          ts->reported = 1;          return 0;      } -    if (ts->plan == PLAN_INIT && ts->allocated == 0) { -        ts->count = n; -        ts->allocated = n; + +    /* +     * If we are doing lazy planning, check the plan against the largest test +     * number that we saw and fail now if we saw a check outside the plan +     * range. +     */ +    if (ts->plan == PLAN_PENDING && (unsigned long) n < ts->count) { +        test_backspace(ts); +        printf("ABORTED (invalid test number %lu)\n", ts->count); +        ts->aborted = 1; +        ts->reported = 1; +        return 0; +    } + +    /* +     * Otherwise, allocated or resize the results if needed and update count, +     * and then record that we've seen a plan. +     */ +    resize_results(ts, n); +    ts->count = n; +    if (ts->plan == PLAN_INIT)          ts->plan = PLAN_FIRST; -        ts->results = xmalloc(ts->count * sizeof(enum test_status)); -        for (i = 0; i < ts->count; i++) -            ts->results[i] = TEST_INVALID; -    } else if (ts->plan == PLAN_PENDING) { -        if ((unsigned long) n < ts->count) { -            printf("ABORTED (invalid test number %lu)\n", ts->count); -            ts->aborted = 1; -            ts->reported = 1; -            return 0; -        } -        ts->count = n; -        if ((unsigned long) n > ts->allocated) { -            ts->results = xrealloc(ts->results, n * sizeof(enum test_status)); -            for (i = ts->allocated; i < ts->count; i++) -                ts->results[i] = TEST_INVALID; -            ts->allocated = n; -        } +    else if (ts->plan == PLAN_PENDING)          ts->plan = PLAN_FINAL; -    }      return 1;  } @@ -475,7 +688,7 @@ test_checkline(const char *line, struct testset *ts)      const char *bail;      char *end;      long number; -    unsigned long i, current; +    unsigned long current;      int outlen;      /* Before anything, check for a test abort. */ @@ -516,6 +729,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; @@ -547,19 +761,9 @@ test_checkline(const char *line, struct testset *ts)      /* We have a valid test result.  Tweak the results array if needed. */      if (ts->plan == PLAN_INIT || ts->plan == PLAN_PENDING) {          ts->plan = PLAN_PENDING; +        resize_results(ts, current);          if (current > ts->count)              ts->count = current; -        if (current > ts->allocated) { -            unsigned long n; - -            n = (ts->allocated == 0) ? 32 : ts->allocated * 2; -            if (n < current) -                n = current; -            ts->results = xrealloc(ts->results, n * sizeof(enum test_status)); -            for (i = ts->allocated; i < n; i++) -                ts->results[i] = TEST_INVALID; -            ts->allocated = n; -        }      }      /* @@ -595,9 +799,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);      } @@ -754,6 +961,7 @@ test_analyze(struct testset *ts)              if (!ts->reported)                  puts("ABORTED (execution failed -- not found?)");              break; +        case CHILDERR_STDIN:          case CHILDERR_STDERR:              if (!ts->reported)                  puts("ABORTED (can't open /dev/null)"); @@ -883,109 +1091,203 @@ 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 = concat(base, "/", name, suffix, (const char *) 0); +            if (is_valid_test(path)) +                return path; +            free(path); +            path = NULL; +        }      } -    if (path == NULL) { -        path = xmalloc(strlen(name) + 3); -        sprintf(path, "%s-t", name); +    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 = xcalloc(1, sizeof(struct testlist)); +    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 = xcalloc(1, sizeof(struct testlist)); +            current = current->next; +        } +        current->ts = xcalloc(1, sizeof(struct testset)); +        current->ts->plan = PLAN_INIT; +        current->ts->file = xstrdup(buffer);      } -    ts->path = path; +    fclose(file); + +    /* Return the results. */ +    return listhead;  }  /* - * 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. + * 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 = xcalloc(1, sizeof(struct testlist)); +    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 = xcalloc(1, sizeof(struct testlist)); +            current = current->next; +        } +        current->ts = xcalloc(1, sizeof(struct testset)); +        current->ts->plan = PLAN_INIT; +        current->ts->file = xstrdup(argv[i]); +    } + +    /* 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); +    free(ts->reason); +    free(ts); +} + + +/* + * 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 +1300,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 +1352,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 +1382,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 +1398,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 +1418,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,39 +1446,43 @@ 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); +        source_env = concat("SOURCE=", source, (const char *) 0);          if (putenv(source_env) != 0)              sysdie("cannot set SOURCE in the environment");      }      if (build != NULL) { -        build_env = xmalloc(strlen("BUILD=") + strlen(build) + 1); -        sprintf(build_env, "BUILD=%s", build); +        build_env = concat("BUILD=", build, (const char *) 0);          if (putenv(build_env) != 0)              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); diff --git a/tests/server/admin-t b/tests/server/admin-t index 6846609..f025d98 100755 --- a/tests/server/admin-t +++ b/tests/server/admin-t @@ -2,8 +2,8 @@  #  # Tests for the wallet-admin dispatch code.  # -# Written by Russ Allbery <rra@stanford.edu> -# Copyright 2008, 2009, 2010, 2011 +# Written by Russ Allbery <eagle@eyrie.org> +# Copyright 2008, 2009, 2010, 2011, 2014  #     The Board of Trustees of the Leland Stanford Junior University  #  # See LICENSE for licensing terms. @@ -140,9 +140,9 @@ seek (STDIN, 0, 0);  ($out, $err) = run_admin ('initialize', 'rra');  is ($err, "invalid admin principal rra\n", 'Initialize requires a principal');  is ($out, "new\n", ' and nothing was run'); -($out, $err) = run_admin ('initialize', 'rra@stanford.edu'); +($out, $err) = run_admin ('initialize', 'eagle@eyrie.org');  is ($err, '', 'Initialize succeeds with a principal'); -is ($out, "new\ninitialize rra\@stanford.edu\n", ' and runs the right code'); +is ($out, "new\ninitialize eagle\@eyrie.org\n", ' and runs the right code');  # Test register.  ($out, $err) = run_admin ('register', 'foo', 'foo', 'Foo::Bar'); @@ -170,9 +170,9 @@ is ($err, "some error\n", 'Error handling succeeds for destroy');  is ($out, "new\n"      . 'This will delete all data in the wallet database.'      . '  Are you sure (N/y)? ' . "destroy\n", ' and calls the right methods'); -($out, $err) = run_admin ('initialize', 'rra@stanford.edu'); +($out, $err) = run_admin ('initialize', 'eagle@eyrie.org');  is ($err, "some error\n", 'Error handling succeeds for initialize'); -is ($out, "new\ninitialize rra\@stanford.edu\n", +is ($out, "new\ninitialize eagle\@eyrie.org\n",      ' and calls the right methods');  ($out, $err) = run_admin ('register', 'object', 'foo', 'Foo::Object');  is ($err, "some error\n", 'Error handling succeeds for register object'); diff --git a/tests/server/backend-t b/tests/server/backend-t index 7e9287d..2ed8404 100755 --- a/tests/server/backend-t +++ b/tests/server/backend-t @@ -2,14 +2,14 @@  #  # Tests for the wallet-backend dispatch code.  # -# Written by Russ Allbery <rra@stanford.edu> -# Copyright 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 +# Written by Russ Allbery <eagle@eyrie.org> +# Copyright 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014  #     The Board of Trustees of the Leland Stanford Junior University  #  # See LICENSE for licensing terms.  use strict; -use Test::More tests => 1314; +use Test::More tests => 1311;  # Create a dummy class for Wallet::Server that prints what method was called  # with its arguments and returns data for testing. @@ -244,7 +244,7 @@ my %commands = (autocreate => [2, 2],                  comment    => [2, 3],                  create     => [2, 2],                  destroy    => [2, 2], -                expires    => [2, 4], +                expires    => [2, 3],                  get        => [2, 2],                  getacl     => [3, 3],                  getattr    => [3, 3], diff --git a/tests/server/keytab-t b/tests/server/keytab-t index a9f5450..94c1bd8 100755 --- a/tests/server/keytab-t +++ b/tests/server/keytab-t @@ -2,7 +2,7 @@  #  # Tests for the keytab-backend dispatch code.  # -# Written by Russ Allbery <rra@stanford.edu> +# Written by Russ Allbery <eagle@eyrie.org>  # Copyright 2006, 2007, 2010  #     The Board of Trustees of the Leland Stanford Junior University  # diff --git a/tests/server/report-t b/tests/server/report-t index 43ec9d1..ad05363 100755 --- a/tests/server/report-t +++ b/tests/server/report-t @@ -2,7 +2,7 @@  #  # Tests for the wallet-report dispatch code.  # -# Written by Russ Allbery <rra@stanford.edu> +# Written by Russ Allbery <eagle@eyrie.org>  # Copyright 2008, 2009, 2010  #     The Board of Trustees of the Leland Stanford Junior University  # diff --git a/tests/tap/basic.c b/tests/tap/basic.c index e8196fc..92a749b 100644 --- a/tests/tap/basic.c +++ b/tests/tap/basic.c @@ -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 <rra@stanford.edu> - * Copyright 2001, 2002, 2004, 2005, 2006, 2007, 2008, 2011, 2012 + * Copyright 2009, 2010, 2011, 2012, 2013, 2014 Russ Allbery <eagle@eyrie.org> + * Copyright 2001, 2002, 2004, 2005, 2006, 2007, 2008, 2011, 2012, 2013, 2014   *     The Board of Trustees of the Leland Stanford Junior University   *   * Permission is hereby granted, free of charge, to any person obtaining a @@ -36,6 +36,7 @@   */  #include <errno.h> +#include <limits.h>  #include <stdarg.h>  #include <stdio.h>  #include <stdlib.h> @@ -58,7 +59,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,72 +67,298 @@ 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; + +/* + * Registered diag files.  Any output found in these files will be printed out + * as if it were passed to diag() before any other output we do.  This allows + * background processes to log to a file and have that output interleved with + * the test output. + */ +struct diag_file { +    char *name; +    FILE *file; +    char *buffer; +    size_t bufsize; +    struct diag_file *next; +}; +static struct diag_file *diag_files = 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) + + +/* + * Form a new string by concatenating multiple strings.  The arguments must be + * terminated by (const char *) 0. + * + * This function only exists because we can't assume asprintf.  We can't + * simulate asprintf with snprintf because we're only assuming SUSv3, which + * does not require that snprintf with a NULL buffer return the required + * length.  When those constraints are relaxed, this should be ripped out and + * replaced with asprintf or a more trivial replacement with snprintf. + */ +static char * +concat(const char *first, ...) +{ +    va_list args; +    char *result; +    const char *string; +    size_t offset; +    size_t length = 0; + +    /* +     * Find the total memory required.  Ensure we don't overflow length.  See +     * the comment for breallocarray for why we're using UINT_MAX here. +     */ +    va_start(args, first); +    for (string = first; string != NULL; string = va_arg(args, const char *)) { +        if (length >= UINT_MAX - strlen(string)) +            bail("strings too long in concat"); +        length += strlen(string); +    } +    va_end(args); +    length++; + +    /* Create the string. */ +    result = bmalloc(length); +    va_start(args, first); +    offset = 0; +    for (string = first; string != NULL; string = va_arg(args, const char *)) { +        memcpy(result + offset, string, strlen(string)); +        offset += strlen(string); +    } +    va_end(args); +    result[offset] = '\0'; +    return result; +} + + +/* + * Check all registered diag_files for any output.  We only print out the + * output if we see a complete line; otherwise, we wait for the next newline. + */ +static void +check_diag_files(void) +{ +    struct diag_file *file; +    fpos_t where; +    size_t length; +    int incomplete; + +    /* +     * Walk through each file and read each line of output available.  The +     * general scheme here used is as follows: try to read a line of output at +     * a time.  If we get NULL, check for EOF; on EOF, advance to the next +     * file. +     * +     * If we get some data, see if it ends in a newline.  If it doesn't end in +     * a newline, we have one of two cases: our buffer isn't large enough, in +     * which case we resize it and try again, or we have incomplete data in +     * the file, in which case we rewind the file and will try again next +     * time. +     */ +    for (file = diag_files; file != NULL; file = file->next) { +        clearerr(file->file); + +        /* Store the current position in case we have to rewind. */ +        if (fgetpos(file->file, &where) < 0) +            sysbail("cannot get position in %s", file->name); + +        /* Continue until we get EOF or an incomplete line of data. */ +        incomplete = 0; +        while (!feof(file->file) && !incomplete) { +            if (fgets(file->buffer, file->bufsize, file->file) == NULL) { +                if (ferror(file->file)) +                    sysbail("cannot read from %s", file->name); +                continue; +            } + +            /* +             * See if the line ends in a newline.  If not, see which error +             * case we have.  Use UINT_MAX as a substitute for SIZE_MAX (see +             * the comment for breallocarray). +             */ +            length = strlen(file->buffer); +            if (file->buffer[length - 1] != '\n') { +                if (length < file->bufsize - 1) +                    incomplete = 1; +                else { +                    if (file->bufsize >= UINT_MAX - BUFSIZ) +                        sysbail("line too long in %s", file->name); +                    file->bufsize += BUFSIZ; +                    file->buffer = brealloc(file->buffer, file->bufsize); +                } + +                /* +                 * On either incomplete lines or too small of a buffer, rewind +                 * and read the file again (on the next pass, if incomplete). +                 * It's simpler than trying to double-buffer the file. +                 */ +                if (fsetpos(file->file, &where) < 0) +                    sysbail("cannot set position in %s", file->name); +                continue; +            } + +            /* We saw a complete line.  Print it out. */ +            printf("# %s", file->buffer); +        } +    } +} +  /*   * 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, primary; +    struct cleanup_func *current;      unsigned long highest = testnum - 1; +    struct diag_file *file, *tmp; + +    /* Check for pending diag_file output. */ +    check_diag_files(); + +    /* Free the diag_files. */ +    file = diag_files; +    while (file != NULL) { +        tmp = file; +        file = file->next; +        fclose(tmp->file); +        free(tmp->name); +        free(tmp->buffer); +        free(tmp); +    } +    diag_files = NULL; + +    /* +     * 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.  Don't do anything +     * except free the diag_files and call cleanup functions if we aren't the +     * primary process (the process in which plan or plan_lazy was called), +     * and tell the cleanup functions that fact. +     */ +    primary = (_process == 0 || getpid() == _process); +    while (cleanup_funcs != NULL) { +        cleanup_funcs->func(success, primary); +        current = cleanup_funcs; +        cleanup_funcs = cleanup_funcs->next; +        free(current); +    } +    if (!primary) +        return; -    if (_planned == 0 && !_lazy) +    /* 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 (_process != 0 && getpid() == _process) { -        if (_lazy && highest > 0) { -            printf("1..%lu\n", highest); -            _planned = highest; -        } -        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); -    } +    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);  }  /*   * Initialize things.  Turns on line buffering on stdout and then prints out - * the number of tests in the test suite. + * the number of tests in the test suite.  We intentionally don't check for + * pending diag_file output here, since it should really come after the plan.   */  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,83 +370,66 @@ 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");  }  /*   * Skip the entire test suite and exits.  Should be called instead of plan(), - * not after it, since it prints out a special plan line. + * not after it, since it prints out a special plan line.  Ignore diag_file + * output here, since it's not clear if it's allowed before the plan.   */  void  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.   */ -void +int  ok(int success, const char *format, ...)  {      fflush(stderr); +    check_diag_files();      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'); +    return success;  }  /*   * Same as ok(), but takes the format arguments as a va_list.   */ -void +int  okv(int success, const char *format, va_list args)  {      fflush(stderr); +    check_diag_files();      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'); +    return success;  } @@ -230,15 +440,9 @@ void  skip(const char *reason, ...)  {      fflush(stderr); +    check_diag_files();      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');  } @@ -246,25 +450,21 @@ skip(const char *reason, ...)  /*   * Report the same status on the next count tests.   */ -void -ok_block(unsigned long count, int status, const char *format, ...) +int +ok_block(unsigned long count, int success, const char *format, ...)  {      unsigned long i;      fflush(stderr); +    check_diag_files();      for (i = 0; i < count; i++) { -        printf("%sok %lu", status ? "" : "not ", testnum++); -        if (!status) +        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');      } +    return success;  } @@ -277,16 +477,10 @@ skip_block(unsigned long count, const char *reason, ...)      unsigned long i;      fflush(stderr); +    check_diag_files();      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');      }  } @@ -296,25 +490,25 @@ skip_block(unsigned long count, const char *reason, ...)   * Takes an expected integer and a seen integer and assumes the test passes   * if those two numbers match.   */ -void +int  is_int(long wanted, long seen, const char *format, ...)  { +    int success; +      fflush(stderr); -    if (wanted == seen) +    check_diag_files(); +    success = (wanted == seen); +    if (success)          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'); +    return success;  } @@ -322,29 +516,29 @@ is_int(long wanted, long seen, const char *format, ...)   * Takes a string and what the string should be, and assumes the test passes   * if those strings match (using strcmp).   */ -void +int  is_string(const char *wanted, const char *seen, const char *format, ...)  { +    int success; +      if (wanted == NULL)          wanted = "(null)";      if (seen == NULL)          seen = "(null)";      fflush(stderr); -    if (strcmp(wanted, seen) == 0) +    check_diag_files(); +    success = (strcmp(wanted, seen) == 0); +    if (success)          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'); +    return success;  } @@ -352,26 +546,25 @@ is_string(const char *wanted, const char *seen, const char *format, ...)   * Takes an expected unsigned long and a seen unsigned long and assumes the   * test passes if the two numbers match.  Otherwise, reports them in hex.   */ -void +int  is_hex(unsigned long wanted, unsigned long seen, const char *format, ...)  { +    int success; +      fflush(stderr); -    if (wanted == seen) +    check_diag_files(); +    success = (wanted == seen); +    if (success)          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'); +    return success;  } @@ -383,14 +576,16 @@ bail(const char *format, ...)  {      va_list args; +    _aborted = 1;      fflush(stderr); +    check_diag_files();      fflush(stdout);      printf("Bail out! ");      va_start(args, format);      vprintf(format, args);      va_end(args);      printf("\n"); -    exit(1); +    exit(255);  } @@ -403,51 +598,110 @@ sysbail(const char *format, ...)      va_list args;      int oerrno = errno; +    _aborted = 1;      fflush(stderr); +    check_diag_files();      fflush(stdout);      printf("Bail out! ");      va_start(args, format);      vprintf(format, args);      va_end(args);      printf(": %s\n", strerror(oerrno)); -    exit(1); +    exit(255);  }  /* - * Report a diagnostic to stderr. + * Report a diagnostic to stderr.  Always returns 1 to allow embedding in + * compound statements.   */ -void +int  diag(const char *format, ...)  {      va_list args;      fflush(stderr); +    check_diag_files();      fflush(stdout);      printf("# ");      va_start(args, format);      vprintf(format, args);      va_end(args);      printf("\n"); +    return 1;  }  /* - * Report a diagnostic to stderr, appending strerror(errno). + * Report a diagnostic to stderr, appending strerror(errno).  Always returns 1 + * to allow embedding in compound statements.   */ -void +int  sysdiag(const char *format, ...)  {      va_list args;      int oerrno = errno;      fflush(stderr); +    check_diag_files();      fflush(stdout);      printf("# ");      va_start(args, format);      vprintf(format, args);      va_end(args);      printf(": %s\n", strerror(oerrno)); +    return 1; +} + + +/* + * Register a new file for diag_file processing. + */ +void +diag_file_add(const char *name) +{ +    struct diag_file *file, *prev; + +    file = bcalloc(1, sizeof(struct diag_file)); +    file->name = bstrdup(name); +    file->file = fopen(file->name, "r"); +    if (file->file == NULL) +        sysbail("cannot open %s", name); +    file->buffer = bmalloc(BUFSIZ); +    file->bufsize = BUFSIZ; +    if (diag_files == NULL) +        diag_files = file; +    else { +        for (prev = diag_files; prev->next != NULL; prev = prev->next) +            ; +        prev->next = file; +    } +} + + +/* + * Remove a file from diag_file processing.  If the file is not found, do + * nothing, since there are some situations where it can be removed twice + * (such as if it's removed from a cleanup function, since cleanup functions + * are called after freeing all the diag_files). + */ +void +diag_file_remove(const char *name) +{ +    struct diag_file *file; +    struct diag_file **prev = &diag_files; + +    for (file = diag_files; file != NULL; file = file->next) { +        if (strcmp(file->name, name) == 0) { +            *prev = file->next; +            fclose(file->file); +            free(file->name); +            free(file->buffer); +            free(file); +            return; +        } +        prev = &file->next; +    }  } @@ -495,6 +749,32 @@ brealloc(void *p, size_t size)  /* + * The same as brealloc, but determine the size by multiplying an element + * count by a size, similar to calloc.  The multiplication is checked for + * integer overflow. + * + * We should technically use SIZE_MAX here for the overflow check, but + * SIZE_MAX is C99 and we're only assuming C89 + SUSv3, which does not + * guarantee that it exists.  They do guarantee that UINT_MAX exists, and we + * can assume that UINT_MAX <= SIZE_MAX. + * + * (In theory, C89 and C99 permit size_t to be smaller than unsigned int, but + * I disbelieve in the existence of such systems and they will have to cope + * without overflow checks.) + */ +void * +breallocarray(void *p, size_t n, size_t size) +{ +    if (n > 0 && UINT_MAX / n <= size) +        bail("reallocarray too large"); +    p = realloc(p, n * size); +    if (p == NULL) +        sysbail("failed to realloc %lu bytes", (unsigned long) (n * size)); +    return p; +} + + +/*   * Copy a string, reporting a fatal error with bail on failure.   */  char * @@ -542,17 +822,12 @@ bstrndup(const char *s, size_t n)   * then SOURCE for the file and return the full path to the file.  Returns   * NULL if the file doesn't exist.  A non-NULL return should be freed with   * test_file_path_free(). - * - * This function uses sprintf because it attempts to be independent of all - * other portability layers.  The use immediately after a memory allocation - * should be safe without using snprintf or strlcpy/strlcat.   */  char *  test_file_path(const char *file)  {      char *base;      char *path = NULL; -    size_t length;      const char *envs[] = { "BUILD", "SOURCE", NULL };      int i; @@ -560,9 +835,7 @@ test_file_path(const char *file)          base = getenv(envs[i]);          if (base == NULL)              continue; -        length = strlen(base) + 1 + strlen(file) + 1; -        path = bmalloc(length); -        sprintf(path, "%s/%s", base, file); +        path = concat(base, "/", file, (const char *) 0);          if (access(path, R_OK) == 0)              break;          free(path); @@ -580,8 +853,7 @@ test_file_path(const char *file)  void  test_file_path_free(char *path)  { -    if (path != NULL) -        free(path); +    free(path);  } @@ -600,14 +872,11 @@ test_tmpdir(void)  {      const char *build;      char *path = NULL; -    size_t length;      build = getenv("BUILD");      if (build == NULL)          build = "."; -    length = strlen(build) + strlen("/tmp") + 1; -    path = bmalloc(length); -    sprintf(path, "%s/tmp", build); +    path = concat(build, "/tmp", (const char *) 0);      if (access(path, X_OK) < 0)          if (mkdir(path, 0777) < 0)              sysbail("error creating temporary directory %s", path); @@ -623,7 +892,26 @@ test_tmpdir(void)  void  test_tmpdir_free(char *path)  { -    rmdir(path);      if (path != NULL) -        free(path); +        rmdir(path); +    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;  } diff --git a/tests/tap/basic.h b/tests/tap/basic.h index fa4adaf..c002df9 100644 --- a/tests/tap/basic.h +++ b/tests/tap/basic.h @@ -4,8 +4,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 <rra@stanford.edu> - * Copyright 2001, 2002, 2004, 2005, 2006, 2007, 2008, 2011, 2012 + * Copyright 2009, 2010, 2011, 2012, 2013, 2014 Russ Allbery <eagle@eyrie.org> + * Copyright 2001, 2002, 2004, 2005, 2006, 2007, 2008, 2011, 2012, 2014   *     The Board of Trustees of the Leland Stanford Junior University   *   * Permission is hereby granted, free of charge, to any person obtaining a @@ -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); @@ -67,26 +67,33 @@ void skip_all(const char *format, ...)  /*   * Basic reporting functions.  The okv() function is the same as ok() but   * takes the test description as a va_list to make it easier to reuse the - * reporting infrastructure when writing new tests. + * reporting infrastructure when writing new tests.  ok() and okv() return the + * value of the success argument.   */ -void ok(int success, const char *format, ...) +int ok(int success, const char *format, ...)      __attribute__((__format__(printf, 2, 3))); -void okv(int success, const char *format, va_list args); +int okv(int success, const char *format, va_list args);  void skip(const char *reason, ...)      __attribute__((__format__(printf, 1, 2))); -/* Report the same status on, or skip, the next count tests. */ -void ok_block(unsigned long count, int success, const char *format, ...) +/* + * Report the same status on, or skip, the next count tests.  ok_block() + * returns the value of the success argument. + */ +int ok_block(unsigned long count, int success, const char *format, ...)      __attribute__((__format__(printf, 3, 4)));  void skip_block(unsigned long count, const char *reason, ...)      __attribute__((__format__(printf, 2, 3))); -/* Check an expected value against a seen value. */ -void is_int(long wanted, long seen, const char *format, ...) +/* + * Check an expected value against a seen value.  Returns true if the test + * passes and false if it fails. + */ +int is_int(long wanted, long seen, const char *format, ...)      __attribute__((__format__(printf, 3, 4))); -void is_string(const char *wanted, const char *seen, const char *format, ...) +int is_string(const char *wanted, const char *seen, const char *format, ...)      __attribute__((__format__(printf, 3, 4))); -void is_hex(unsigned long wanted, unsigned long seen, const char *format, ...) +int is_hex(unsigned long wanted, unsigned long seen, const char *format, ...)      __attribute__((__format__(printf, 3, 4)));  /* Bail out with an error.  sysbail appends strerror(errno). */ @@ -96,29 +103,43 @@ void sysbail(const char *format, ...)      __attribute__((__noreturn__, __nonnull__, __format__(printf, 1, 2)));  /* Report a diagnostic to stderr prefixed with #. */ -void diag(const char *format, ...) +int diag(const char *format, ...)      __attribute__((__nonnull__, __format__(printf, 1, 2))); -void sysdiag(const char *format, ...) +int sysdiag(const char *format, ...)      __attribute__((__nonnull__, __format__(printf, 1, 2))); +/* + * Register or unregister a file that contains supplementary diagnostics. + * Before any other output, all registered files will be read, line by line, + * and each line will be reported as a diagnostic as if it were passed to + * diag().  Nul characters are not supported in these files and will result in + * truncated output. + */ +void diag_file_add(const char *file) +    __attribute__((__nonnull__)); +void diag_file_remove(const char *file) +    __attribute__((__nonnull__)); +  /* 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 *breallocarray(void *, size_t, size_t) +    __attribute__((__alloc_size__(2, 3), __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 +147,23 @@ 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 two argument, an int that + * will be true if the test completed successfully and false otherwise, and an + * int that will be true if the cleanup function is run in the primary process + * (the one that called plan or plan_lazy) and false otherwise. + */ +typedef void (*test_cleanup_func)(int, int); +void test_cleanup_register(test_cleanup_func) +    __attribute__((__nonnull__)); +  END_DECLS  #endif /* TAP_BASIC_H */ diff --git a/tests/tap/kerberos.c b/tests/tap/kerberos.c index 474cf4f..578a858 100644 --- a/tests/tap/kerberos.c +++ b/tests/tap/kerberos.c @@ -14,8 +14,8 @@   * The canonical version of this file is maintained in the rra-c-util package,   * which can be found at <http://www.eyrie.org/~eagle/software/rra-c-util/>.   * - * Written by Russ Allbery <rra@stanford.edu> - * Copyright 2006, 2007, 2009, 2010, 2011, 2012 + * Written by Russ Allbery <eagle@eyrie.org> + * Copyright 2006, 2007, 2009, 2010, 2011, 2012, 2013, 2014   *     The Board of Trustees of the Leland Stanford Junior University   *   * Permission is hereby granted, free of charge, to any person obtaining a @@ -38,7 +38,7 @@   */  #include <config.h> -#ifdef HAVE_KERBEROS +#ifdef HAVE_KRB5  # include <portable/krb5.h>  #endif  #include <portable/system.h> @@ -47,6 +47,7 @@  #include <tests/tap/basic.h>  #include <tests/tap/kerberos.h> +#include <tests/tap/macros.h>  #include <tests/tap/process.h>  #include <tests/tap/string.h> @@ -79,7 +80,7 @@ static char *tmpdir_conf = NULL;   * Kerberos libraries available and one if we don't.  Uses keytab to obtain   * credentials, and fills in the cache member of the provided config struct.   */ -#ifdef HAVE_KERBEROS +#ifdef HAVE_KRB5  static void  kerberos_kinit(void) @@ -147,7 +148,7 @@ kerberos_kinit(void)      free(krbtgt);  } -#else /* !HAVE_KERBEROS */ +#else /* !HAVE_KRB5 */  static void  kerberos_kinit(void) @@ -197,37 +198,27 @@ kerberos_kinit(void)          bail("cannot get Kerberos tickets");  } -#endif /* !HAVE_KERBEROS */ +#endif /* !HAVE_KRB5 */  /* - * Clean up at the end of a test.  This removes the ticket cache and resets - * and frees the memory allocated for the environment variables so that - * valgrind output on test suites is cleaner. + * Free all the memory associated with our Kerberos setup, but don't remove + * the ticket cache.  This is used when cleaning up on exit from a non-primary + * process so that test programs that fork don't remove the ticket cache still + * used by the main program.   */ -void -kerberos_cleanup(void) +static void +kerberos_free(void)  { -    char *path; - -    if (tmpdir_ticket != NULL) { -        basprintf(&path, "%s/krb5cc_test", tmpdir_ticket); -        unlink(path); -        free(path); -        test_tmpdir_free(tmpdir_ticket); -        tmpdir_ticket = NULL; -    } +    test_tmpdir_free(tmpdir_ticket); +    tmpdir_ticket = NULL;      if (config != NULL) { -        if (config->keytab != NULL) { -            test_file_path_free(config->keytab); -            free(config->principal); -            free(config->cache); -        } -        if (config->userprinc != NULL) { -            free(config->userprinc); -            free(config->username); -            free(config->password); -        } +        test_file_path_free(config->keytab); +        free(config->principal); +        free(config->cache); +        free(config->userprinc); +        free(config->username); +        free(config->password);          free(config);          config = NULL;      } @@ -245,6 +236,42 @@ kerberos_cleanup(void)  /* + * Clean up at the end of a test.  This removes the ticket cache and resets + * and frees the memory allocated for the environment variables so that + * valgrind output on test suites is cleaner.  Most of the work is done by + * kerberos_free, but this function also deletes the ticket cache. + */ +void +kerberos_cleanup(void) +{ +    char *path; + +    if (tmpdir_ticket != NULL) { +        basprintf(&path, "%s/krb5cc_test", tmpdir_ticket); +        unlink(path); +        free(path); +    } +    kerberos_free(); +} + + +/* + * The cleanup handler for the TAP framework.  Call kerberos_cleanup if we're + * in the primary process and kerberos_free if not.  The first argument, which + * indicates whether the test succeeded or not, is ignored, since we need to + * do the same thing either way. + */ +static void +kerberos_cleanup_handler(int success UNUSED, int primary) +{ +    if (primary) +        kerberos_cleanup(); +    else +        kerberos_free(); +} + + +/*   * Obtain Kerberos tickets for the principal specified in config/principal   * using the keytab specified in config/keytab, both of which are presumed to   * be in tests in either the build or the source tree.  Also sets KRB5_KTNAME @@ -321,15 +348,13 @@ kerberos_setup(enum kerberos_needs needs)          *config->realm = '\0';          config->realm++;      } -    if (path != NULL) -        test_file_path_free(path); +    test_file_path_free(path);      /* -     * Register the cleanup function as an atexit handler so that the caller -     * doesn't have to worry about cleanup. +     * Register the cleanup function so that the caller doesn't have to do +     * explicit cleanup.       */ -    if (atexit(kerberos_cleanup) != 0) -        sysdiag("cannot register cleanup function"); +    test_cleanup_register(kerberos_cleanup_handler);      /* Return the configuration. */      return config; @@ -357,10 +382,8 @@ kerberos_cleanup_conf(void)          tmpdir_conf = NULL;      }      putenv((char *) "KRB5_CONFIG="); -    if (krb5_config != NULL) { -        free(krb5_config); -        krb5_config = NULL; -    } +    free(krb5_config); +    krb5_config = NULL;  } @@ -401,7 +424,7 @@ kerberos_generate_conf(const char *realm)   * The remaining functions in this file are only available if Kerberos   * libraries are available.   */ -#ifdef HAVE_KERBEROS +#ifdef HAVE_KRB5  /* @@ -485,4 +508,4 @@ kerberos_keytab_principal(krb5_context ctx, const char *path)      return princ;  } -#endif /* HAVE_KERBEROS */ +#endif /* HAVE_KRB5 */ diff --git a/tests/tap/kerberos.h b/tests/tap/kerberos.h index 31b6343..8be0add 100644 --- a/tests/tap/kerberos.h +++ b/tests/tap/kerberos.h @@ -4,8 +4,8 @@   * The canonical version of this file is maintained in the rra-c-util package,   * which can be found at <http://www.eyrie.org/~eagle/software/rra-c-util/>.   * - * Written by Russ Allbery <rra@stanford.edu> - * Copyright 2006, 2007, 2009, 2011, 2012 + * Written by Russ Allbery <eagle@eyrie.org> + * Copyright 2006, 2007, 2009, 2011, 2012, 2013, 2014   *     The Board of Trustees of the Leland Stanford Junior University   *   * Permission is hereby granted, free of charge, to any person obtaining a @@ -33,7 +33,7 @@  #include <config.h>  #include <tests/tap/macros.h> -#ifdef HAVE_KERBEROS +#ifdef HAVE_KRB5  # include <portable/krb5.h>  #endif @@ -53,10 +53,10 @@ struct kerberos_config {   * certain configuration information isn't available.   */  enum kerberos_needs { -    TAP_KRB_NEEDS_NONE, -    TAP_KRB_NEEDS_KEYTAB, -    TAP_KRB_NEEDS_PASSWORD, -    TAP_KRB_NEEDS_BOTH +    TAP_KRB_NEEDS_NONE     = 0x00, +    TAP_KRB_NEEDS_KEYTAB   = 0x01, +    TAP_KRB_NEEDS_PASSWORD = 0x02, +    TAP_KRB_NEEDS_BOTH     = 0x01 | 0x02  };  BEGIN_DECLS @@ -73,11 +73,11 @@ BEGIN_DECLS   * the principal field will be NULL.  If the files exist but loading them   * fails, or authentication fails, kerberos_setup calls bail.   * - * kerberos_cleanup will be set up to run from an atexit handler.  This means - * that any child processes that should not remove the Kerberos ticket cache - * should call _exit instead of exit.  The principal will be automatically - * freed when kerberos_cleanup is called or if kerberos_setup is called again. - * The caller doesn't need to worry about it. + * kerberos_cleanup will be run as a cleanup function normally, freeing all + * resources and cleaning up temporary files on process exit.  It can, + * however, be called directly if for some reason the caller needs to delete + * the Kerberos environment again.  However, normally the caller can just call + * kerberos_setup again.   */  struct kerberos_config *kerberos_setup(enum kerberos_needs)      __attribute__((__malloc__)); @@ -100,7 +100,7 @@ void kerberos_generate_conf(const char *realm);  void kerberos_cleanup_conf(void);  /* Thes interfaces are only available with native Kerberos support. */ -#ifdef HAVE_KERBEROS +#ifdef HAVE_KRB5  /* Bail out with an error, appending the Kerberos error message. */  void bail_krb5(krb5_context, krb5_error_code, const char *format, ...) @@ -118,7 +118,7 @@ void diag_krb5(krb5_context, krb5_error_code, const char *format, ...)  krb5_principal kerberos_keytab_principal(krb5_context, const char *path)      __attribute__((__nonnull__)); -#endif /* HAVE_KERBEROS */ +#endif /* HAVE_KRB5 */  END_DECLS diff --git a/tests/tap/kerberos.sh b/tests/tap/kerberos.sh index d2f174d..e970ae5 100644 --- a/tests/tap/kerberos.sh +++ b/tests/tap/kerberos.sh @@ -8,7 +8,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/>.  # -# Written by Russ Allbery <rra@stanford.edu> +# Written by Russ Allbery <eagle@eyrie.org>  # Copyright 2009, 2010, 2011, 2012  #     The Board of Trustees of the Leland Stanford Junior University  # diff --git a/tests/tap/libtap.sh b/tests/tap/libtap.sh index f9347d8..9731032 100644 --- a/tests/tap/libtap.sh +++ b/tests/tap/libtap.sh @@ -9,9 +9,9 @@  # writing test cases.  It is part of C TAP Harness, which can be found at  # <http://www.eyrie.org/~eagle/software/c-tap-harness/>.  # -# Written by Russ Allbery <rra@stanford.edu> -# Copyright 2009, 2010, 2011, 2012 Russ Allbery <rra@stanford.edu> -# Copyright 2006, 2007, 2008 +# Written by Russ Allbery <eagle@eyrie.org> +# Copyright 2009, 2010, 2011, 2012 Russ Allbery <eagle@eyrie.org> +# 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. diff --git a/tests/tap/macros.h b/tests/tap/macros.h index 33fee42..04cc420 100644 --- a/tests/tap/macros.h +++ b/tests/tap/macros.h @@ -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 <rra@stanford.edu> + * 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"), @@ -58,6 +58,13 @@  # 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/messages.c b/tests/tap/messages.c index abc2c49..45b0566 100644 --- a/tests/tap/messages.c +++ b/tests/tap/messages.c @@ -8,8 +8,8 @@   * The canonical version of this file is maintained in the rra-c-util package,   * which can be found at <http://www.eyrie.org/~eagle/software/rra-c-util/>.   * - * Copyright 2002, 2004, 2005 Russ Allbery <rra@stanford.edu> - * Copyright 2006, 2007, 2009, 2012 + * Copyright 2002, 2004, 2005 Russ Allbery <eagle@eyrie.org> + * Copyright 2006, 2007, 2009, 2012, 2014   *     The Board of Trustees of the Leland Stanford Junior University   *   * Permission is hereby granted, free of charge, to any person obtaining a @@ -75,10 +75,8 @@ message_log_buffer(int len UNUSED, const char *fmt, va_list args,  void  errors_capture(void)  { -    if (errors != NULL) { -        free(errors); -        errors = NULL; -    } +    free(errors); +    errors = NULL;      message_handlers_warn(1, message_log_buffer);      message_handlers_notice(1, message_log_buffer);  } diff --git a/tests/tap/messages.h b/tests/tap/messages.h index 0544f2d..985b9cd 100644 --- a/tests/tap/messages.h +++ b/tests/tap/messages.h @@ -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 2002 Russ Allbery <rra@stanford.edu> + * Copyright 2002 Russ Allbery <eagle@eyrie.org>   * Copyright 2006, 2007, 2009   *     The Board of Trustees of the Leland Stanford Junior University   * diff --git a/tests/tap/perl/Test/RRA.pm b/tests/tap/perl/Test/RRA.pm index 3035c7a..bb7de7d 100644 --- a/tests/tap/perl/Test/RRA.pm +++ b/tests/tap/perl/Test/RRA.pm @@ -9,8 +9,8 @@  # The canonical version of this file is maintained in the rra-c-util package,  # which can be found at <http://www.eyrie.org/~eagle/software/rra-c-util/>.  # -# Written by Russ Allbery <rra@stanford.edu> -# Copyright 2013 +# Written by Russ Allbery <eagle@eyrie.org> +# Copyright 2013, 2014  #     The Board of Trustees of the Leland Stanford Junior University  #  # Permission is hereby granted, free of charge, to any person obtaining a @@ -51,29 +51,47 @@ our (@EXPORT_OK, @ISA, $VERSION);  # consistency is good).  BEGIN {      @ISA       = qw(Exporter); -    @EXPORT_OK = qw(skip_unless_maintainer use_prereq); +    @EXPORT_OK = qw(skip_unless_author skip_unless_automated use_prereq);      # This version should match the corresponding rra-c-util release, but with      # two digits for the minor version, including a leading zero if necessary,      # so that it will sort properly. -    $VERSION = '4.08'; +    $VERSION = '5.05';  } -# 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. +# Skip this test unless author 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 { +sub skip_unless_author {      my ($description) = @_; -    if (!$ENV{RRA_MAINTAINER_TESTS}) { -        plan skip_all => "$description only run for maintainer"; +    if (!$ENV{AUTHOR_TESTING}) { +        plan skip_all => "$description only run for author";      }      return;  } +# Skip this test unless doing automated testing or release testing.  This is +# used for tests that should be run by CPAN smoke testing or during releases, +# but not for manual installs by end users.  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_automated { +    my ($description) = @_; +    for my $env (qw(AUTOMATED_TESTING RELEASE_TESTING AUTHOR_TESTING)) { +        return if $ENV{$env}; +    } +    plan skip_all => "$description normally skipped"; +    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 @@ -91,7 +109,7 @@ sub use_prereq {      # 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) { +    if (@imports >= 1 && $imports[0] =~ m{ \A \d+ (?: [.][\d_]+ )* \z }xms) {          $version = shift(@imports);      } @@ -118,7 +136,8 @@ sub use_prereq {      # If the use failed for any reason, skip the test.      if (!$result || $error) { -        plan skip_all => "$module required for test"; +        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. @@ -142,13 +161,17 @@ Test::RRA - Support functions for Perl tests  =head1 SYNOPSIS -    use Test::RRA qw(skip_unless_maintainer use_prereq); +    use Test::RRA +      qw(skip_unless_author skip_unless_automated use_prereq); -    # Skip this test unless maintainer tests are requested. -    skip_unless_maintainer('Coding style tests'); +    # Skip this test unless author tests are requested. +    skip_unless_author('Coding style tests'); + +    # Skip this test unless doing automated or release testing. +    skip_unless_automated('POD syntax tests');      # Load modules, skipping the test if they're not available. -    use_prereq('File::Slurp'); +    use_prereq('Perl6::Slurp', 'slurp');      use_prereq('Test::Script::Run', '0.04');  =head1 DESCRIPTION @@ -165,12 +188,23 @@ script should be explicitly imported.  =over 4 -=item skip_unless_maintainer(DESC) +=item skip_unless_author(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. +Checks whether AUTHOR_TESTING 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. +for author> will be appended to it and used as the skip reason. + +=item skip_unless_automated(DESC) + +Checks whether AUTHOR_TESTING, AUTOMATED_TESTING, or RELEASE_TESTING are +set in the environment and skips the whole test (by calling C<plan +skip_all> from Test::More) if they are not.  This should be used by tests +that should not run during end-user installs of the module, but which +should run as part of CPAN smoke testing and release testing. + +DESC is a description of the tests being skipped.  A space and C<normally +skipped> will be appended to it and used as the skip reason.  =item use_prereq(MODULE[, VERSION][, IMPORT ...]) @@ -187,11 +221,11 @@ value of an array.  =head1 AUTHOR -Russ Allbery <rra@stanford.edu> +Russ Allbery <eagle@eyrie.org>  =head1 COPYRIGHT AND LICENSE -Copyright 2013 The Board of Trustees of the Leland Stanford Junior +Copyright 2013, 2014 The Board of Trustees of the Leland Stanford Junior  University  Permission is hereby granted, free of charge, to any person obtaining a @@ -219,4 +253,8 @@ 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/>. +The functions to control when tests are run use environment variables +defined by the L<Lancaster +Consensus|https://github.com/Perl-Toolchain-Gang/toolchain-site/blob/master/lancaster-consensus.md>. +  =cut diff --git a/tests/tap/perl/Test/RRA/Automake.pm b/tests/tap/perl/Test/RRA/Automake.pm index 5dde32d..a064ed9 100644 --- a/tests/tap/perl/Test/RRA/Automake.pm +++ b/tests/tap/perl/Test/RRA/Automake.pm @@ -13,7 +13,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/>.  # -# Written by Russ Allbery <rra@stanford.edu> +# Written by Russ Allbery <eagle@eyrie.org>  # Copyright 2013  #     The Board of Trustees of the Leland Stanford Junior University  # @@ -82,12 +82,12 @@ our (@EXPORT_OK, @ISA, $VERSION);  # consistency is good).  BEGIN {      @ISA       = qw(Exporter); -    @EXPORT_OK = qw(automake_setup perl_dirs test_file_path); +    @EXPORT_OK = qw(automake_setup perl_dirs test_file_path test_tmpdir);      # This version should match the corresponding rra-c-util release, but with      # two digits for the minor version, including a leading zero if necessary,      # so that it will sort properly. -    $VERSION = '4.08'; +    $VERSION = '5.05';  }  # Perl directories to skip globally for perl_dirs.  We ignore the perl @@ -95,6 +95,11 @@ BEGIN {  # distribution and has its own standalone test suite.  my @GLOBAL_SKIP = qw(.git perl); +# The temporary directory created by test_tmpdir, if any.  If this is set, +# attempt to remove the directory stored here on program exit (but ignore +# failure to do so). +my $TMPDIR; +  # 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 @@ -238,12 +243,53 @@ sub test_file_path {      return;  } +# Create a temporary directory for tests to use for transient files and return +# the path to that directory.  The directory is automatically removed on +# program exit.  The directory permissions use the current umask.  Calls +# BAIL_OUT if the directory could not be created. +# +# Returns: Path to a writable temporary directory +sub test_tmpdir { +    my $path; + +    # If we already figured out what directory to use, reuse the same path. +    # Otherwise, create a directory relative to BUILD if set. +    if (defined($TMPDIR)) { +        $path = $TMPDIR; +    } else { +        my $base = defined($ENV{BUILD}) ? $ENV{BUILD} : File::Spec->curdir; +        $path = File::Spec->catdir($base, 'tmp'); +    } + +    # Create the directory if it doesn't exist. +    if (!-d $path) { +        if (!mkdir($path, 0777)) { +            BAIL_OUT("cannot create directory $path: $!"); +        } +    } + +    # Store the directory name for cleanup and return it. +    $TMPDIR = $path; +    return $path; +} + +# On program exit, remove $TMPDIR if set and if possible.  Report errors with +# diag but otherwise ignore them. +END { +    if (defined($TMPDIR) && -d $TMPDIR) { +        local $! = undef; +        if (!rmdir($TMPDIR)) { +            diag("cannot remove temporary directory $TMPDIR: $!"); +        } +    } +} +  1;  __END__  =for stopwords  Allbery Automake Automake-aware Automake-based rra-c-util ARGS -subdirectories sublicense MERCHANTABILITY NONINFRINGEMENT +subdirectories sublicense MERCHANTABILITY NONINFRINGEMENT umask  =head1 NAME @@ -320,11 +366,24 @@ 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. +=item test_tmpdir() + +Create a temporary directory for tests to use for transient files and +return the path to that directory.  The directory is created relative to +the BUILD environment variable, which must be set.  Permissions on the +directory are set using the current umask.  test_tmpdir() returns the full +path to the temporary directory or calls BAIL_OUT if it could not be +created. + +The directory is automatically removed if possible on program exit. +Failure to remove the directory on exit is reported with diag() and +otherwise ignored. +  =back  =head1 AUTHOR -Russ Allbery <rra@stanford.edu> +Russ Allbery <eagle@eyrie.org>  =head1 COPYRIGHT AND LICENSE diff --git a/tests/tap/perl/Test/RRA/Config.pm b/tests/tap/perl/Test/RRA/Config.pm index cfa3ad5..3e77650 100644 --- a/tests/tap/perl/Test/RRA/Config.pm +++ b/tests/tap/perl/Test/RRA/Config.pm @@ -30,13 +30,14 @@ BEGIN {      @ISA       = qw(Exporter);      @EXPORT_OK = qw(        $COVERAGE_LEVEL @COVERAGE_SKIP_TESTS @CRITIC_IGNORE $LIBRARY_PATH -      $MINIMUM_VERSION %MINIMUM_VERSION @POD_COVERAGE_EXCLUDE +      $MINIMUM_VERSION %MINIMUM_VERSION @POD_COVERAGE_EXCLUDE @STRICT_IGNORE +      @STRICT_PREREQ      );      # This version should match the corresponding rra-c-util release, but with      # two digits for the minor version, including a leading zero if necessary,      # so that it will sort properly. -    $VERSION = '4.08'; +    $VERSION = '5.05';  }  # If BUILD or SOURCE are set in the environment, look for data/perl.conf under @@ -64,6 +65,8 @@ our $LIBRARY_PATH;  our $MINIMUM_VERSION = '5.008';  our %MINIMUM_VERSION;  our @POD_COVERAGE_EXCLUDE; +our @STRICT_IGNORE; +our @STRICT_PREREQ;  # Load the configuration.  if (!do($PATH)) { @@ -75,8 +78,8 @@ if (!do($PATH)) {  __END__  =for stopwords -Allbery rra-c-util Automake perlcritic .libs namespace sublicense -MERCHANTABILITY NONINFRINGEMENT +Allbery rra-c-util Automake perlcritic .libs namespace subdirectory +sublicense MERCHANTABILITY NONINFRINGEMENT  =head1 NAME @@ -130,7 +133,7 @@ directory names starting with F<tests/>.  =item $LIBRARY_PATH -Add this directory (or a .libs subdirectory) relative to the top of the +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. @@ -155,6 +158,20 @@ 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/>. + +=item @STRICT_PREREQ + +A list of Perl modules that have to be available in order to do meaningful +Test::Strict testing.  If any of the modules cannot be loaded via C<use>, +Test::Strict checking will be skipped.  There is currently no way to +require specific versions of the modules. +  =back  No variables are exported by default, but the variables can be imported @@ -162,11 +179,11 @@ into the local namespace to avoid long variable names.  =head1 AUTHOR -Russ Allbery <rra@stanford.edu> +Russ Allbery <eagle@eyrie.org>  =head1 COPYRIGHT AND LICENSE -Copyright 2013 The Board of Trustees of the Leland Stanford Junior +Copyright 2013, 2014 The Board of Trustees of the Leland Stanford Junior  University  Permission is hereby granted, free of charge, to any person obtaining a @@ -189,7 +206,8 @@ DEALINGS IN THE SOFTWARE.  =head1 SEE ALSO -Test::RRA(3), Test::RRA::Automake(3) +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/>. diff --git a/tests/tap/process.c b/tests/tap/process.c index 8ed4cfd..6461fb4 100644 --- a/tests/tap/process.c +++ b/tests/tap/process.c @@ -7,12 +7,15 @@   * runs a function in a subprocess and checks its output and exit status   * against expected values.   * + * Requires an Autoconf probe for sys/select.h and a replacement for a missing + * mkstemp. + *   * The canonical version of this file is maintained in the rra-c-util package,   * which can be found at <http://www.eyrie.org/~eagle/software/rra-c-util/>.   * - * Written by Russ Allbery <rra@stanford.edu> - * Copyright 2002, 2004, 2005 Russ Allbery <rra@stanford.edu> - * Copyright 2009, 2010, 2011 + * Written by Russ Allbery <eagle@eyrie.org> + * Copyright 2002, 2004, 2005, 2013 Russ Allbery <eagle@eyrie.org> + * Copyright 2009, 2010, 2011, 2013, 2014   *     The Board of Trustees of the Leland Stanford Junior University   *   * Permission is hereby granted, free of charge, to any person obtaining a @@ -37,12 +40,48 @@  #include <config.h>  #include <portable/system.h> +#include <errno.h> +#include <fcntl.h> +#include <signal.h> +#ifdef HAVE_SYS_SELECT_H +# include <sys/select.h> +#endif +#include <sys/stat.h> +#include <sys/time.h>  #include <sys/wait.h>  #include <tests/tap/basic.h>  #include <tests/tap/process.h>  #include <tests/tap/string.h> +/* May be defined by the build system. */ +#ifndef PATH_FAKEROOT +# define PATH_FAKEROOT "" +#endif + +/* How long to wait for the process to start in seconds. */ +#define PROCESS_WAIT 10 + +/* + * Used to store information about a background process.  This contains + * everything required to stop the process and clean up after it. + */ +struct process { +    pid_t pid;                  /* PID of child process */ +    char *pidfile;              /* PID file to delete on process stop */ +    char *tmpdir;               /* Temporary directory for log file */ +    char *logfile;              /* Log file of process output */ +    bool is_child;              /* Whether we can waitpid for process */ +    struct process *next;       /* Next process in global list */ +}; + +/* + * Global list of started processes, which will be cleaned up automatically on + * program exit if they haven't been explicitly stopped with process_stop + * prior to that point. + */ +static struct process *processes = NULL; +  /*   * Given a function, an expected exit status, and expected output, runs that @@ -171,7 +210,312 @@ run_setup(const char *const argv[])          p = strchr(output, '\n');          if (p != NULL)              *p = '\0'; -        bail("%s", output); +        if (output[0] != '\0') +            bail("%s", output); +        else +            bail("setup command failed with no output");      }      free(output);  } + + +/* + * Free the resources associated with tracking a process, without doing + * anything to the process.  This is kept separate so that we can free + * resources during shutdown in a non-primary process. + */ +static void +process_free(struct process *process) +{ +    struct process **prev; + +    /* Remove the process from the global list. */ +    prev = &processes; +    while (*prev != NULL && *prev != process) +        prev = &(*prev)->next; +    if (*prev == process) +        *prev = process->next; + +    /* Free resources. */ +    free(process->pidfile); +    free(process->logfile); +    test_tmpdir_free(process->tmpdir); +    free(process); +} + + +/* + * Kill a process and wait for it to exit.  Returns the status of the process. + * Calls bail on a system failure or a failure of the process to exit. + * + * We are quite aggressive with error reporting here because child processes + * that don't exit or that don't exist often indicate some form of test + * failure. + */ +static int +process_kill(struct process *process) +{ +    int result, i; +    int status = -1; +    struct timeval tv; +    unsigned long pid = process->pid; + +    /* If the process is not a child, just kill it and hope. */ +    if (!process->is_child) { +        if (kill(process->pid, SIGTERM) < 0 && errno != ESRCH) +            sysbail("cannot send SIGTERM to process %lu", pid); +        return 0; +    } + +    /* Check if the process has already exited. */ +    result = waitpid(process->pid, &status, WNOHANG); +    if (result < 0) +        sysbail("cannot wait for child process %lu", pid); +    else if (result > 0) +        return status; + +    /* +     * Kill the process and wait for it to exit.  I don't want to go to the +     * work of setting up a SIGCHLD handler or a full event loop here, so we +     * effectively poll every tenth of a second for process exit (and +     * hopefully faster when it does since the SIGCHLD may interrupt our +     * select, although we're racing with it. +     */ +    if (kill(process->pid, SIGTERM) < 0 && errno != ESRCH) +        sysbail("cannot send SIGTERM to child process %lu", pid); +    for (i = 0; i < PROCESS_WAIT * 10; i++) { +        tv.tv_sec = 0; +        tv.tv_usec = 100000; +        select(0, NULL, NULL, NULL, &tv); +        result = waitpid(process->pid, &status, WNOHANG); +        if (result < 0) +            sysbail("cannot wait for child process %lu", pid); +        else if (result > 0) +            return status; +    } + +    /* The process still hasn't exited.  Bail. */ +    bail("child process %lu did not exit on SIGTERM", pid); + +    /* Not reached, but some compilers may get confused. */ +    return status; +} + + +/* + * Stop a particular process given its process struct.  This kills the + * process, waits for it to exit if possible (giving it at most five seconds), + * and then removes it from the global processes struct so that it isn't + * stopped again during global shutdown. + */ +void +process_stop(struct process *process) +{ +    int status; +    unsigned long pid = process->pid; + +    /* Stop the process. */ +    status = process_kill(process); + +    /* Call diag to flush logs as well as provide exit status. */ +    if (process->is_child) +        diag("stopped process %lu (exit status %d)", pid, status); +    else +        diag("stopped process %lu", pid); + +    /* Remove the log and PID file. */ +    diag_file_remove(process->logfile); +    unlink(process->pidfile); +    unlink(process->logfile); + +    /* Free resources. */ +    process_free(process); +} + + +/* + * Stop all running processes.  This is called as a cleanup handler during + * process shutdown.  The first argument, which says whether the test was + * successful, is ignored, since the same actions should be performed + * regardless.  The second argument says whether this is the primary process, + * in which case we do the full shutdown.  Otherwise, we only free resources + * but don't stop the process. + */ +static void +process_stop_all(int success UNUSED, int primary) +{ +    while (processes != NULL) { +        if (primary) +            process_stop(processes); +        else +            process_free(processes); +    } +} + + +/* + * Read the PID of a process from a file.  This is necessary when running + * under fakeroot to get the actual PID of the remctld process. + */ +static long +read_pidfile(const char *path) +{ +    FILE *file; +    char buffer[BUFSIZ]; +    long pid; + +    file = fopen(path, "r"); +    if (file == NULL) +        sysbail("cannot open %s", path); +    if (fgets(buffer, sizeof(buffer), file) == NULL) +        sysbail("cannot read from %s", path); +    fclose(file); +    pid = strtol(buffer, NULL, 10); +    if (pid <= 0) +        bail("cannot read PID from %s", path); +    return pid; +} + + +/* + * Start a process and return its status information.  The status information + * is also stored in the global processes linked list so that it can be + * stopped automatically on program exit. + * + * The boolean argument says whether to start the process under fakeroot.  If + * true, PATH_FAKEROOT must be defined, generally by Autoconf.  If it's not + * found, call skip_all. + * + * This is a helper function for process_start and process_start_fakeroot. + */ +static struct process * +process_start_internal(const char *const argv[], const char *pidfile, +                       bool fakeroot) +{ +    size_t i; +    int log_fd; +    const char *name; +    struct timeval tv; +    struct process *process; +    const char **fakeroot_argv = NULL; +    const char *path_fakeroot = PATH_FAKEROOT; + +    /* Check prerequisites. */ +    if (fakeroot && path_fakeroot[0] == '\0') +        skip_all("fakeroot not found"); + +    /* Create the process struct and log file. */ +    process = bcalloc(1, sizeof(struct process)); +    process->pidfile = bstrdup(pidfile); +    process->tmpdir = test_tmpdir(); +    name = strrchr(argv[0], '/'); +    if (name != NULL) +        name++; +    else +        name = argv[0]; +    basprintf(&process->logfile, "%s/%s.log.XXXXXX", process->tmpdir, name); +    log_fd = mkstemp(process->logfile); +    if (log_fd < 0) +        sysbail("cannot create log file for %s", argv[0]); + +    /* If using fakeroot, rewrite argv accordingly. */ +    if (fakeroot) { +        for (i = 0; argv[i] != NULL; i++) +            ; +        fakeroot_argv = bcalloc(2 + i + 1, sizeof(const char *)); +        fakeroot_argv[0] = path_fakeroot; +        fakeroot_argv[1] = "--"; +        for (i = 0; argv[i] != NULL; i++) +            fakeroot_argv[i + 2] = argv[i]; +        fakeroot_argv[i + 2] = NULL; +        argv = fakeroot_argv; +    } + +    /* +     * Fork off the child process, redirect its standard output and standard +     * error to the log file, and then exec the program. +     */ +    process->pid = fork(); +    if (process->pid < 0) +        sysbail("fork failed"); +    else if (process->pid == 0) { +        if (dup2(log_fd, STDOUT_FILENO) < 0) +            sysbail("cannot redirect standard output"); +        if (dup2(log_fd, STDERR_FILENO) < 0) +            sysbail("cannot redirect standard error"); +        close(log_fd); +        if (execv(argv[0], (char *const *) argv) < 0) +            sysbail("exec of %s failed", argv[0]); +    } +    close(log_fd); +    free(fakeroot_argv); + +    /* +     * In the parent.  Wait for the child to start by watching for the PID +     * file to appear in 100ms intervals. +     */ +    for (i = 0; i < PROCESS_WAIT * 10 && access(pidfile, F_OK) != 0; i++) { +        tv.tv_sec = 0; +        tv.tv_usec = 100000; +        select(0, NULL, NULL, NULL, &tv); +    } + +    /* +     * If the PID file still hasn't appeared after ten seconds, attempt to +     * kill the process and then bail. +     */ +    if (access(pidfile, F_OK) != 0) { +        kill(process->pid, SIGTERM); +        alarm(5); +        waitpid(process->pid, NULL, 0); +        alarm(0); +        bail("cannot start %s", argv[0]); +    } + +    /* +     * Read the PID back from the PID file.  This usually isn't necessary for +     * non-forking daemons, but always doing this makes this function general, +     * and it's required when running under fakeroot. +     */ +    if (fakeroot) +        process->pid = read_pidfile(pidfile); +    process->is_child = !fakeroot; + +    /* Register the log file as a source of diag messages. */ +    diag_file_add(process->logfile); + +    /* +     * Add the process to our global list and set our cleanup handler if this +     * is the first process we started. +     */ +    if (processes == NULL) +        test_cleanup_register(process_stop_all); +    process->next = processes; +    processes = process; + +    /* All done. */ +    return process; +} + + +/* + * Start a process and return the opaque process struct.  The process must + * create pidfile with its PID when startup is complete. + */ +struct process * +process_start(const char *const argv[], const char *pidfile) +{ +    return process_start_internal(argv, pidfile, false); +} + + +/* + * Start a process under fakeroot and return the opaque process struct.  If + * fakeroot is not available, calls skip_all.  The process must create pidfile + * with its PID when startup is complete. + */ +struct process * +process_start_fakeroot(const char *const argv[], const char *pidfile) +{ +    return process_start_internal(argv, pidfile, true); +} diff --git a/tests/tap/process.h b/tests/tap/process.h index df74b5f..8137d5d 100644 --- a/tests/tap/process.h +++ b/tests/tap/process.h @@ -4,8 +4,8 @@   * The canonical version of this file is maintained in the rra-c-util package,   * which can be found at <http://www.eyrie.org/~eagle/software/rra-c-util/>.   * - * Written by Russ Allbery <rra@stanford.edu> - * Copyright 2009, 2010 + * Written by Russ Allbery <eagle@eyrie.org> + * Copyright 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 @@ -33,6 +33,9 @@  #include <config.h>  #include <tests/tap/macros.h> +/* Opaque data type for process_start and friends. */ +struct process; +  BEGIN_DECLS  /* @@ -60,6 +63,32 @@ void is_function_output(test_function_type, void *data, int status,  void run_setup(const char *const argv[])      __attribute__((__nonnull__)); +/* + * process_start starts a process in the background, returning an opaque data + * struct that can be used to stop the process later.  The standard output and + * standard error of the process will be sent to a log file registered with + * diag_file_add, so its output will be properly interleaved with the test + * case output. + * + * The process should create a PID file in the path given as the second + * argument when it's finished initialization. + * + * process_start_fakeroot is the same but starts the process under fakeroot. + * PATH_FAKEROOT must be defined (generally by Autoconf).  If fakeroot is not + * found, process_start_fakeroot will call skip_all, so be sure to call this + * function before plan. + * + * process_stop can be called to explicitly stop the process.  If it isn't + * called by the test program, it will be called automatically when the + * program exits. + */ +struct process *process_start(const char *const argv[], const char *pidfile) +    __attribute__((__nonnull__)); +struct process *process_start_fakeroot(const char *const argv[], +                                       const char *pidfile) +    __attribute__((__nonnull__)); +void process_stop(struct process *); +  END_DECLS  #endif /* TAP_PROCESS_H */ diff --git a/tests/tap/remctl.sh b/tests/tap/remctl.sh index 2fd6681..0a511a0 100644 --- a/tests/tap/remctl.sh +++ b/tests/tap/remctl.sh @@ -8,7 +8,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/>.  # -# Written by Russ Allbery <rra@stanford.edu> +# Written by Russ Allbery <eagle@eyrie.org>  # Copyright 2009, 2012  #     The Board of Trustees of the Leland Stanford Junior University  # diff --git a/tests/tap/string.c b/tests/tap/string.c index f5c965c..6ed7e68 100644 --- a/tests/tap/string.c +++ b/tests/tap/string.c @@ -7,7 +7,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 2011, 2012 Russ Allbery <rra@stanford.edu> + * Copyright 2011, 2012 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"), diff --git a/tests/tap/string.h b/tests/tap/string.h index 2f699e4..cc51945 100644 --- a/tests/tap/string.h +++ b/tests/tap/string.h @@ -7,7 +7,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 2011, 2012 Russ Allbery <rra@stanford.edu> + * Copyright 2011, 2012 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"), diff --git a/tests/util/messages-krb5-t.c b/tests/util/messages-krb5-t.c index e3ffe75..c6de5a5 100644 --- a/tests/util/messages-krb5-t.c +++ b/tests/util/messages-krb5-t.c @@ -4,8 +4,8 @@   * The canonical version of this file is maintained in the rra-c-util package,   * which can be found at <http://www.eyrie.org/~eagle/software/rra-c-util/>.   * - * Written by Russ Allbery <rra@stanford.edu> - * Copyright 2010, 2011 + * Written by Russ Allbery <eagle@eyrie.org> + * Copyright 2010, 2011, 2013, 2014   *     The Board of Trustees of the Leland Stanford Junior University   *   * Permission is hereby granted, free of charge, to any person obtaining a @@ -28,17 +28,31 @@   */  #include <config.h> -#include <portable/krb5.h> +#ifdef HAVE_KRB5 +# include <portable/krb5.h> +#endif  #include <portable/system.h>  #include <tests/tap/basic.h>  #include <tests/tap/process.h>  #include <util/macros.h> -#include <util/messages-krb5.h> +#ifdef HAVE_KRB5 +# include <util/messages-krb5.h> +#endif  #include <util/messages.h>  #include <util/xmalloc.h> +/* Skip the whole test if not built with Kerberos support. */ +#ifndef HAVE_KRB5 +int +main(void) +{ +    skip_all("not built with Kerberos support"); +    return 0; +} +#else +  /*   * Test functions.   */ @@ -116,5 +130,9 @@ 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;  } + +#endif /* HAVE_KRB5 */ diff --git a/tests/util/messages-t.c b/tests/util/messages-t.c index 54f1cf1..f60fa6a 100644 --- a/tests/util/messages-t.c +++ b/tests/util/messages-t.c @@ -4,8 +4,8 @@   * The canonical version of this file is maintained in the rra-c-util package,   * which can be found at <http://www.eyrie.org/~eagle/software/rra-c-util/>.   * - * Written by Russ Allbery <rra@stanford.edu> - * Copyright 2002, 2004, 2005 Russ Allbery <rra@stanford.edu> + * Written by Russ Allbery <eagle@eyrie.org> + * Copyright 2002, 2004, 2005 Russ Allbery <eagle@eyrie.org>   * Copyright 2009, 2010, 2011, 2012   *     The Board of Trustees of the Leland Stanford Junior University   * diff --git a/tests/util/xmalloc-t b/tests/util/xmalloc-t index b6c6dfd..d52c448 100755 --- a/tests/util/xmalloc-t +++ b/tests/util/xmalloc-t @@ -5,8 +5,8 @@  # The canonical version of this file is maintained in the rra-c-util package,  # which can be found at <http://www.eyrie.org/~eagle/software/rra-c-util/>.  # -# Written by Russ Allbery <rra@stanford.edu> -# Copyright 2000, 2001, 2006 Russ Allbery <rra@stanford.edu> +# Written by Russ Allbery <eagle@eyrie.org> +# Copyright 2000, 2001, 2006, 2014 Russ Allbery <eagle@eyrie.org>  # Copyright 2008, 2009, 2010, 2012  #     The Board of Trustees of the Leland Stanford Junior University  # @@ -59,31 +59,33 @@ ok_xmalloc () {  # failures in automated testing have been problems with the assumptions around  # memory allocation or problems with the test suite, not problems with the  # underlying xmalloc code. -if [ -z "$RRA_MAINTAINER_TESTS" ] ; then -    skip_all 'xmalloc tests only run for maintainer' +if [ -z "$AUTHOR_TESTING" ] ; then +    skip_all 'xmalloc tests only run for author'  fi  # Total tests. -plan 36 +plan 41  # First run the tests expected to succeed. -ok_xmalloc "malloc small"    0 "" "m" "21"      "0" -ok_xmalloc "malloc large"    0 "" "m" "5500000" "0" -ok_xmalloc "malloc zero"     0 "" "m" "0"       "0" -ok_xmalloc "realloc small"   0 "" "r" "21"      "0" -ok_xmalloc "realloc large"   0 "" "r" "5500000" "0" -ok_xmalloc "strdup small"    0 "" "s" "21"      "0" -ok_xmalloc "strdup large"    0 "" "s" "5500000" "0" -ok_xmalloc "strndup small"   0 "" "n" "21"      "0" -ok_xmalloc "strndup large"   0 "" "n" "5500000" "0" -ok_xmalloc "calloc small"    0 "" "c" "24"      "0" -ok_xmalloc "calloc large"    0 "" "c" "5500000" "0" -ok_xmalloc "asprintf small"  0 "" "a" "24"      "0" -ok_xmalloc "asprintf large"  0 "" "a" "5500000" "0" -ok_xmalloc "vasprintf small" 0 "" "v" "24"      "0" -ok_xmalloc "vasprintf large" 0 "" "v" "5500000" "0" +ok_xmalloc "malloc small"       0 "" "m" "21"       "0" +ok_xmalloc "malloc large"       0 "" "m" "30000000" "0" +ok_xmalloc "malloc zero"        0 "" "m" "0"        "0" +ok_xmalloc "realloc small"      0 "" "r" "21"       "0" +ok_xmalloc "realloc large"      0 "" "r" "30000000" "0" +ok_xmalloc "reallocarray small" 0 "" "y" "20"       "0" +ok_xmalloc "reallocarray large" 0 "" "y" "30000000" "0" +ok_xmalloc "strdup small"       0 "" "s" "21"       "0" +ok_xmalloc "strdup large"       0 "" "s" "30000000" "0" +ok_xmalloc "strndup small"      0 "" "n" "21"       "0" +ok_xmalloc "strndup large"      0 "" "n" "30000000" "0" +ok_xmalloc "calloc small"       0 "" "c" "24"       "0" +ok_xmalloc "calloc large"       0 "" "c" "30000000" "0" +ok_xmalloc "asprintf small"     0 "" "a" "24"       "0" +ok_xmalloc "asprintf large"     0 "" "a" "30000000" "0" +ok_xmalloc "vasprintf small"    0 "" "v" "24"       "0" +ok_xmalloc "vasprintf large"    0 "" "v" "30000000" "0" -# Now limit our memory to 5.5MB and then try the large ones again, all of +# Now limit our memory to 30MB and then try the large ones again, all of  # which should fail.  #  # The exact memory limits used here are essentially black magic.  They need to @@ -91,53 +93,60 @@ ok_xmalloc "vasprintf large" 0 "" "v" "5500000" "0"  # but not so large that we can't reasonably expect to allocate that much  # memory normally.  The amount of memory required varies a lot based on what  # shared libraries are loaded, and if it's too small, all memory allocations -# fail.  5.5MB seems to work reasonably well on both Solaris and Linux. +# fail.  30MB seems to work reasonably well on both Solaris and Linux, even +# when the program is linked with additional libraries.  #  # We assume that there are enough miscellaneous allocations that an allocation  # exactly as large as the limit will always fail.  ok_xmalloc "malloc fail" 1 \ -    "failed to malloc 5500000 bytes at xmalloc.c line 38" \ -    "m" "5500000" "5500000" +    "failed to malloc 30000000 bytes at xmalloc.c line 38" \ +    "m" "30000000" "30000000"  ok_xmalloc "realloc fail" 1 \ -    "failed to realloc 5500000 bytes at xmalloc.c line 66" \ -    "r" "5500000" "5500000" +    "failed to realloc 30000000 bytes at xmalloc.c line 66" \ +    "r" "30000000" "30000000" +ok_xmalloc "reallocarray fail" 1 \ +    "failed to reallocarray 30000000 bytes at xmalloc.c line 96" \ +    "y" "30000000" "30000000"  ok_xmalloc "strdup fail" 1 \ -    "failed to strdup 5500000 bytes at xmalloc.c line 97" \ -    "s" "5500000" "5500000" +    "failed to strdup 30000000 bytes at xmalloc.c line 127" \ +    "s" "30000000" "30000000"  ok_xmalloc "strndup fail" 1 \ -    "failed to strndup 5500000 bytes at xmalloc.c line 143" \ -    "n" "5500000" "5500000" +    "failed to strndup 30000000 bytes at xmalloc.c line 173" \ +    "n" "30000000" "30000000"  ok_xmalloc "calloc fail" 1 \ -    "failed to calloc 5500000 bytes at xmalloc.c line 167" \ -    "c" "5500000" "5500000" +    "failed to calloc 30000000 bytes at xmalloc.c line 197" \ +    "c" "30000000" "30000000"  ok_xmalloc "asprintf fail" 1 \ -    "failed to asprintf 5500000 bytes at xmalloc.c line 191" \ -    "a" "5500000" "5500000" +    "failed to asprintf 30000000 bytes at xmalloc.c line 221" \ +    "a" "30000000" "30000000"  ok_xmalloc "vasprintf fail" 1 \ -    "failed to vasprintf 5500000 bytes at xmalloc.c line 210" \ -    "v" "5500000" "5500000" +    "failed to vasprintf 30000000 bytes at xmalloc.c line 240" \ +    "v" "30000000" "30000000"  # Check our custom error handler. -ok_xmalloc "malloc custom"    1 "malloc 5500000 xmalloc.c 38" \ -    "M" "5500000" "5500000" -ok_xmalloc "realloc custom"   1 "realloc 5500000 xmalloc.c 66" \ -    "R" "5500000" "5500000" -ok_xmalloc "strdup custom"    1 "strdup 5500000 xmalloc.c 97" \ -    "S" "5500000" "5500000" -ok_xmalloc "strndup custom"   1 "strndup 5500000 xmalloc.c 143" \ -    "N" "5500000" "5500000" -ok_xmalloc "calloc custom"    1 "calloc 5500000 xmalloc.c 167" \ -    "C" "5500000" "5500000" -ok_xmalloc "asprintf custom"  1 "asprintf 5500000 xmalloc.c 191" \ -    "A" "5500000" "5500000" -ok_xmalloc "vasprintf custom" 1 "vasprintf 5500000 xmalloc.c 210" \ -    "V" "5500000" "5500000" +ok_xmalloc "malloc custom"       1 "malloc 30000000 xmalloc.c 38" \ +    "M" "30000000" "30000000" +ok_xmalloc "realloc custom"      1 "realloc 30000000 xmalloc.c 66" \ +    "R" "30000000" "30000000" +ok_xmalloc "reallocarray custom" 1 "reallocarray 30000000 xmalloc.c 96" \ +    "Y" "30000000" "30000000" +ok_xmalloc "strdup custom"       1 "strdup 30000000 xmalloc.c 127" \ +    "S" "30000000" "30000000" +ok_xmalloc "strndup custom"      1 "strndup 30000000 xmalloc.c 173" \ +    "N" "30000000" "30000000" +ok_xmalloc "calloc custom"       1 "calloc 30000000 xmalloc.c 197" \ +    "C" "30000000" "30000000" +ok_xmalloc "asprintf custom"     1 "asprintf 30000000 xmalloc.c 221" \ +    "A" "30000000" "30000000" +ok_xmalloc "vasprintf custom"    1 "vasprintf 30000000 xmalloc.c 240" \ +    "V" "30000000" "30000000"  # Check the smaller ones again just for grins. -ok_xmalloc "malloc retry"    0 "" "m" "21" "5500000" -ok_xmalloc "realloc retry"   0 "" "r" "32" "5500000" -ok_xmalloc "strdup retry"    0 "" "s" "64" "5500000" -ok_xmalloc "strndup retry"   0 "" "n" "20" "5500000" -ok_xmalloc "calloc retry"    0 "" "c" "24" "5500000" -ok_xmalloc "asprintf retry"  0 "" "a" "30" "5500000" -ok_xmalloc "vasprintf retry" 0 "" "v" "35" "5500000" +ok_xmalloc "malloc retry"       0 "" "m" "21" "30000000" +ok_xmalloc "realloc retry"      0 "" "r" "32" "30000000" +ok_xmalloc "reallocarray retry" 0 "" "y" "32" "30000000" +ok_xmalloc "strdup retry"       0 "" "s" "64" "30000000" +ok_xmalloc "strndup retry"      0 "" "n" "20" "30000000" +ok_xmalloc "calloc retry"       0 "" "c" "24" "30000000" +ok_xmalloc "asprintf retry"     0 "" "a" "30" "30000000" +ok_xmalloc "vasprintf retry"    0 "" "v" "35" "30000000" diff --git a/tests/util/xmalloc.c b/tests/util/xmalloc.c index 394cab5..e222612 100644 --- a/tests/util/xmalloc.c +++ b/tests/util/xmalloc.c @@ -4,8 +4,8 @@   * 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 2000, 2001, 2006 Russ Allbery <rra@stanford.edu> - * Copyright 2008, 2012 + * Copyright 2000, 2001, 2006 Russ Allbery <eagle@eyrie.org> + * Copyright 2008, 2012, 2013, 2014   *     The Board of Trustees of the Leland Stanford Junior University   *   * Permission is hereby granted, free of charge, to any person obtaining a @@ -110,6 +110,36 @@ test_realloc(size_t size)  /* + * Like test_realloc, but test allocating an array instead.  Returns true on + * success, false on any failure. + */ +static int +test_reallocarray(size_t n, size_t size) +{ +    char *buffer; +    size_t i; + +    buffer = xmalloc(10); +    if (buffer == NULL) +        return 0; +    memset(buffer, 1, 10); +    buffer = xreallocarray(buffer, n, size); +    if (buffer == NULL) +        return 0; +    if (n > 0 && size > 0) +        memset(buffer + 10, 2, (n * size) - 10); +    for (i = 0; i < 10; i++) +        if (buffer[i] != 1) +            return 0; +    for (i = 10; i < n * size; i++) +        if (buffer[i] != 2) +            return 0; +    free(buffer); +    return 1; +} + + +/*   * Generate a string of the size indicated, call xstrdup on it, and then   * ensure the result matches.  Returns true on success, false on any failure.   */ @@ -322,6 +352,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; @@ -329,11 +360,14 @@ main(int argc, char *argv[])              syswarn("Can't set data limit to %lu", (unsigned long) limit);              exit(2);          } -        if (size < limit || code == 'r') { -            tmp = malloc(code == 'r' ? 10 : size); +        if (size < limit || code == 'r' || code == 'y') { +            test_size = (code == 'r' || code == 'y') ? 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); @@ -348,6 +382,7 @@ main(int argc, char *argv[])      case 'c': exit(test_calloc(size) ? willfail : 1);      case 'm': exit(test_malloc(size) ? willfail : 1);      case 'r': exit(test_realloc(size) ? willfail : 1); +    case 'y': exit(test_reallocarray(4, size / 4) ? willfail : 1);      case 's': exit(test_strdup(size) ? willfail : 1);      case 'n': exit(test_strndup(size) ? willfail : 1);      case 'a': exit(test_asprintf(size) ? willfail : 1); | 
