/Makefile
/Makefile.in
/aclocal.m4
+/autom4te.cache/
/build-aux/
/config.h
/config.h.in
/tests/portable/asprintf-t
/tests/portable/daemon-t
/tests/portable/mkstemp-t
+/tests/portable/reallocarray-t
/tests/portable/setenv-t
/tests/portable/snprintf-t
/tests/portable/strlcat-t
/tests/portable/strlcpy-t
/tests/portable/strndup-t
/tests/runtests
-/tests/util/concat-t
/tests/util/messages-krb5-t
/tests/util/messages-t
/tests/util/xmalloc
# Automake makefile for kstart.
#
# Written by Russ Allbery <eagle@eyrie.org>
+# Copyright 2015 Russ Allbery <eagle@eyrie.org>
# Copyright 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
# 2011 The Board of Trustees of the Leland Stanford Junior University
#
tests/krenew/afs-t tests/krenew/basic-t tests/krenew/daemon-t \
tests/krenew/errors-t tests/krenew/keyring-t \
tests/krenew/non-renewable-t tests/libtest.pl tests/tap/libtap.sh \
- tests/util/xmalloc-t
+ tests/tap/perl/Test/RRA.pm tests/tap/perl/Test/RRA/Automake.pm \
+ tests/tap/perl/Test/RRA/Config.pm tests/util/xmalloc-t
+
+# Enable AFS support when running distcheck.
+DISTCHECK_CONFIGURE_FLAGS = --enable-setpag
# The following library order matters for annoying reasons. Older
# libafsauthent contains its own com_err implementation, which we do not
portable/krb5.h portable/macros.h portable/stdbool.h \
portable/system.h
portable_libportable_a_LIBADD = $(LIBOBJS)
-util_libutil_a_SOURCES = util/command.c util/command.h util/concat.c \
- util/concat.h util/macros.h util/messages-krb5.c \
- util/messages-krb5.h util/messages.c util/messages.h util/xmalloc.c \
- util/xmalloc.h
+util_libutil_a_SOURCES = util/command.c util/command.h util/macros.h \
+ util/messages-krb5.c util/messages-krb5.h util/messages.c \
+ util/messages.h util/xmalloc.c util/xmalloc.h
# Conditionally build the replacement kafs library and add it to the
# libraries used by the other programs.
$(K5START_LIBS)
dist_man_MANS = k5start.1 krenew.1
-DISTCLEANFILES = tests/data/.placeholder
-MAINTAINERCLEANFILES = Makefile.in aclocal.m4 build-aux/compile \
- build-aux/config.guess build-aux/config.sub build-aux/depcomp \
- build-aux/install-sh build-aux/missing config.h.in config.h.in~ \
- configure k5start.1 krenew.1
+DISTCLEANFILES = config.h.in~ tests/data/.placeholder
+MAINTAINERCLEANFILES = Makefile.in aclocal.m4 build-aux/compile \
+ build-aux/config.guess build-aux/config.sub build-aux/depcomp \
+ build-aux/install-sh build-aux/missing config.h.in configure \
+ k5start.1 krenew.1
+
+# Remove the Autoconf cache directory on make distclean.
+distclean-local:
+ rm -rf autom4te.cache
-# A set of flags for warnings. Add -O because gcc won't find some warnings
+# A set of flags for warnings. Add -O because gcc won't find some warnings
# without optimization turned on. Desirable warnings that can't be turned
# on due to other problems:
#
-# -Wconversion http://bugs.debian.org/488884 (htons warnings)
+# -Wconversion http://bugs.debian.org/488884 (htons warnings)
#
-# Last checked against gcc 4.6.1 (2011-05-04).
-WARNINGS = -g -O -Wall -Wextra -Wendif-labels -Wformat=2 -Winit-self \
- -Wswitch-enum -Wdeclaration-after-statement -Wshadow \
- -Wpointer-arith -Wbad-function-cast -Wcast-align -Wwrite-strings \
- -Wjump-misses-init -Wlogical-op -Wstrict-prototypes \
- -Wmissing-prototypes -Wredundant-decls -Wnested-externs -Werror
+# Last checked against gcc 4.8.2 (2014-04-12). -D_FORTIFY_SOURCE=2 enables
+# warn_unused_result attribute markings on glibc functions on Linux, which
+# catches a few more issues.
+if WARNINGS_GCC
+ WARNINGS = -g -O -fstrict-overflow -fstrict-aliasing -D_FORTIFY_SOURCE=2 \
+ -Wall -Wextra -Wendif-labels -Wformat=2 -Winit-self -Wswitch-enum \
+ -Wstrict-overflow=5 -Wmissing-format-attribute -Wfloat-equal \
+ -Wdeclaration-after-statement -Wshadow -Wpointer-arith \
+ -Wbad-function-cast -Wcast-align -Wwrite-strings -Wjump-misses-init \
+ -Wlogical-op -Wstrict-prototypes -Wold-style-definition \
+ -Wmissing-prototypes -Wnormalized=nfc -Wpacked -Wredundant-decls \
+ -Wnested-externs -Winline -Wvla -Werror
+endif
+if WARNINGS_CLANG
+ WARNINGS = -Weverything -Wno-padded
+endif
warnings:
- $(MAKE) V=0 CFLAGS='$(WARNINGS)'
- $(MAKE) V=0 CFLAGS='$(WARNINGS)' $(check_PROGRAMS)
+ $(MAKE) V=0 CFLAGS='$(WARNINGS)' KRB5_CPPFLAGS='$(KRB5_CPPFLAGS_GCC)'
+ $(MAKE) V=0 CFLAGS='$(WARNINGS)' \
+ KRB5_CPPFLAGS='$(KRB5_CPPFLAGS_GCC)' $(check_PROGRAMS)
# The bits below are for the test suite, not for the main package.
check_PROGRAMS = tests/runtests tests/kafs/basic tests/kafs/haspag-t \
tests/portable/asprintf-t tests/portable/daemon-t \
- tests/portable/mkstemp-t tests/portable/setenv-t \
- tests/portable/snprintf-t tests/portable/strlcat-t \
- tests/portable/strlcpy-t tests/portable/strndup-t \
- tests/util/concat-t tests/util/messages-krb5-t \
+ tests/portable/mkstemp-t tests/portable/reallocarray-t \
+ tests/portable/setenv-t tests/portable/snprintf-t \
+ tests/portable/strlcat-t tests/portable/strlcpy-t \
+ tests/portable/strndup-t tests/util/messages-krb5-t \
tests/util/messages-t tests/util/xmalloc
tests_runtests_CPPFLAGS = -DSOURCE='"$(abs_top_srcdir)/tests"' \
-DBUILD='"$(abs_top_builddir)/tests"'
check_LIBRARIES = tests/tap/libtap.a
tests_tap_libtap_a_CPPFLAGS = -I$(abs_top_srcdir)/tests
tests_tap_libtap_a_SOURCES = tests/tap/basic.c tests/tap/basic.h \
- tests/tap/process.c tests/tap/process.h tests/tap/string.c \
- tests/tap/string.h
+ tests/tap/macros.h tests/tap/process.c tests/tap/process.h \
+ tests/tap/string.c tests/tap/string.h
# kafs tests are buit differently depending on whether we use our local
# libkafs replacement.
tests_portable_mkstemp_t_SOURCES = tests/portable/mkstemp-t.c \
tests/portable/mkstemp.c
tests_portable_mkstemp_t_LDADD = tests/tap/libtap.a portable/libportable.a
+tests_portable_reallocarray_t_SOURCES = tests/portable/reallocarray-t.c \
+ tests/portable/reallocarray.c
+tests_portable_reallocarray_t_LDADD = tests/tap/libtap.a \
+ portable/libportable.a
tests_portable_setenv_t_SOURCES = tests/portable/setenv-t.c \
tests/portable/setenv.c
tests_portable_setenv_t_LDADD = tests/tap/libtap.a portable/libportable.a
tests_util_xmalloc_LDADD = util/libutil.a portable/libportable.a
check-local: $(check_PROGRAMS)
- cd tests && ./runtests $(abs_top_srcdir)/tests/TESTS
+ cd tests && ./runtests -l $(abs_top_srcdir)/tests/TESTS
Fix k5start and krenew to not incorrectly reject the -b flag in
conjunction with -K or a command. Patch from Lars Hanke.
+ Update to rra-c-util 5.9:
+
+ * Add missing va_end to xasprintf implementation.
+ * Improve portability to Kerberos included in Solaris 10.
+ * Use appropriate warning flags with Clang (currently not warning clean).
+ * Use Lancaster Consensus environment variables to control tests.
+ * Use calloc or reallocarray for protection against integer overflows.
+ * Suppress warnings from Kerberos headers in non-system paths.
+ * Assume calloc initializes pointers to NULL.
+ * Assume free(NULL) is properly ignored.
+ * Improve error handling in xasprintf and xvasprintf.
+ * Check the return status of snprintf and vsnprintf properly.
+ * Preserve errno if snprintf fails in vasprintf replacement.
+ * Fix probing for Heimdal's libroken to work with older versions.
+ * Improve POD tests.
+ * Fix kafs compilation failure on Solaris 11 or later.
+ * Drop concat from the util library in favor of asprintf.
+ * Fail on any error in [bx]asprintf and [bx]vasprintf.
+ * Pass --deps to krb5-config in the non-reduced-dependencies case.
+ * Silence __attribute__ warnings on more compilers.
+
+ Update to C TAP Harness 3.4:
+
+ * Fix segfault in runtests with an empty test list.
+ * Display verbose test results with -v or C_TAP_VERBOSE.
+ * Support comments and blank lines in test lists.
+ * Check for integer overflow on memory allocations.
+ * Reopen standard input to /dev/null when running a test list.
+ * Don't leak extraneous file descriptors to tests.
+ * Suppress lazy plans and test summaries if the test failed with bail.
+ * runtests now treats the command line as a list of tests by default.
+ * The full test executable path can now be passed to runtests -o.
+ * Improved harness output for tests with lazy plans.
+ * Improved harness output to a terminal for some abort cases.
+ * Flush harness output after each test even when not on a terminal.
+ * Only use feature-test macros when requested or built with gcc -ansi.
+ * Drop is_double from the C TAP library to avoid requiring -lm.
+ * Avoid using local in the shell libtap.sh library.
+ * Silence __attribute__ warnings on more compilers.
+ * runtests now frees all allocated resources on exit.
+
kstart 4.1 (2012-01-07)
Fix a regression introduced in kstart 4.0 that caused k5start -H and
set -e
+# Regenerate all the autotools files.
autoreconf -i --force
-rm -rf autom4te.cache
# Generate manual pages.
version=`grep '^kstart' NEWS | head -1 | cut -d' ' -f2`
dnl Autoconf configuration for kstart.
dnl
dnl Written by Russ Allbery <eagle@eyrie.org>
+dnl Copyright 2015 Russ Allbery <eagle@eyrie.org>
dnl Copyright 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010
dnl 2011, 2014
dnl The Board of Trustees of the Leland Stanford Junior University
-Wall -Werror])
AM_MAINTAINER_MODE
+dnl Detect unexpanded macros.
+m4_pattern_forbid([^_?RRA_])
+
AC_PROG_CC
AC_USE_SYSTEM_EXTENSIONS
+RRA_PROG_CC_CLANG
AC_SYS_LARGEFILE
AM_PROG_CC_C_O
m4_ifdef([AM_PROG_AR], [AM_PROG_AR])
dnl Other portability checks.
AC_HEADER_STDBOOL
-AC_CHECK_HEADERS([strings.h sys/bitypes.h sys/time.h syslog.h])
+AC_CHECK_HEADERS([sys/bitypes.h sys/select.h sys/time.h syslog.h])
AC_CHECK_DECLS([snprintf, vsnprintf])
RRA_C_C99_VAMACROS
RRA_C_GNU_VAMACROS
[#include <sys/types.h>])
RRA_FUNC_SNPRINTF
AC_CHECK_FUNCS([setrlimit setsid])
-AC_REPLACE_FUNCS([asprintf daemon mkstemp setenv strlcat strlcpy strndup])
+AC_REPLACE_FUNCS([asprintf daemon mkstemp reallocarray setenv strlcat strlcpy])
+AC_REPLACE_FUNCS([strndup])
+
+dnl Enable appropriate warnings.
+AM_CONDITIONAL([WARNINGS_GCC], [test x"$GCC" = xyes && test x"$CLANG" != xyes])
+AM_CONDITIONAL([WARNINGS_CLANG], [test x"$CLANG" = xyes])
dnl Create the tests/data directory.
AC_CONFIG_COMMANDS([tests/data/.placeholder], [touch tests/data/.placeholder])
* since otherwise we wouldn't be able to wait for the child process.
*/
if (config->background)
- daemon(0, 0);
+ if (daemon(0, 0) < 0) {
+ syswarn("cannot background");
+ exit_cleanup(ctx, config, 1);
+ }
/* Write out the PID file. */
if (config->pidfile != NULL)
#include <time.h>
#include <internal.h>
-#include <util/concat.h>
#include <util/macros.h>
#include <util/messages.h>
#include <util/messages-krb5.h>
int fd;
char *tmp;
- if (xasprintf(&tmp, "%s_XXXXXX", config->cache) < 0)
- die("cannot format ticket cache name");
+ xasprintf(&tmp, "%s_XXXXXX", config->cache);
fd = mkstemp(tmp);
if (fd < 0) {
syswarn("cannot create temporary ticket cache file");
if (!private->quiet)
printf("Password: ");
- fgets(buffer, sizeof(buffer), stdin);
+ if (fgets(buffer, sizeof(buffer), stdin) == NULL) {
+ syswarn("cannot read password");
+ code = KRB5_LIBOS_CANTREADPWD;
+ goto done;
+ }
p = strchr(buffer, '\n');
if (p != NULL)
*p = '\0';
argc--;
argv++;
}
- if (argc >= 1)
+ if (argv[0] != NULL)
config.command = argv;
/* If -x was given, we still want to exit on initial auth failure. */
int fd;
char *tmp, *cache;
- if (xasprintf(&tmp, "/tmp/krb5cc_%d_XXXXXX", (int) getuid()) < 0)
- die("cannot format ticket cache name");
+ xasprintf(&tmp, "/tmp/krb5cc_%d_XXXXXX", (int) getuid());
fd = mkstemp(tmp);
if (fd < 0)
sysdie("cannot create ticket cache file");
if (fchmod(fd, 0600) < 0)
sysdie("cannot chmod ticket cache file");
- if (xasprintf(&cache, "FILE:%s", tmp) < 0)
- die("cannot format ticket cache name");
+ xasprintf(&cache, "FILE:%s", tmp);
free(tmp);
config.cache = cache;
config.clean_cache = true;
* instance onto the end of the username.
*/
if (inst != NULL)
- if (xasprintf(&principal, "%s/%s", principal, inst) < 0)
- die("cannot format principal name");
+ xasprintf(&principal, "%s/%s", principal, inst);
code = krb5_parse_name(ctx, principal, &config.client);
if (code != 0)
die_krb5(ctx, code, "error parsing %s", principal);
sname = "krbtgt";
if (sinst == NULL)
sinst = srealm;
- if (xasprintf(&private.service, "%s/%s@%s", sname, sinst, srealm) < 0)
- die("cannot format service principal name");
+ xasprintf(&private.service, "%s/%s@%s", sname, sinst, srealm);
code = krb5_build_principal(ctx, &private.ksprinc, strlen(srealm),
srealm, sname, sinst, (const char *) NULL);
if (code != 0)
int fd, code, oerrno, callnum;
#ifdef _ILP32
- struct afssysargs32 syscall_data;
+ struct afssysargs syscall_data;
syscall_data.syscall = call;
syscall_data.param1 = param1;
syscall_data.param4 = param4;
syscall_data.param5 = 0;
syscall_data.param6 = 0;
- callnum = _IOW('C', 2, struct afssysargs32);
+ callnum = _IOW('C', 2, struct afssysargs);
#else
- struct afssysargs syscall_data;
+ struct afssysargs64 syscall_data;
syscall_data.syscall = call;
syscall_data.param1 = param1;
syscall_data.param4 = param4;
syscall_data.param5 = 0;
syscall_data.param6 = 0;
- callnum = _IOW('C', 1, struct afssysargs);
+ callnum = _IOW('C', 1, struct afssysargs64);
#endif
fd = open("/dev/afs", O_RDWR);
#include <time.h>
#include <internal.h>
-#include <util/concat.h>
#include <util/macros.h>
#include <util/messages.h>
#include <util/messages-krb5.h>
char *name;
int fd;
- if (xasprintf(&name, "/tmp/krb5cc_%d_XXXXXX", (int) getuid()) < 0)
- die("cannot format ticket cache name");
+ xasprintf(&name, "/tmp/krb5cc_%d_XXXXXX", (int) getuid());
fd = mkstemp(name);
if (fd < 0)
sysdie("cannot create ticket cache file");
--- /dev/null
+dnl Determine whether the current compiler is Clang.
+dnl
+dnl If the current compiler is Clang, set the shell variable CLANG to yes.
+dnl
+dnl The canonical version of this file is maintained in the rra-c-util
+dnl package, available at <http://www.eyrie.org/~eagle/software/rra-c-util/>.
+dnl
+dnl Copyright 2015 Russ Allbery <eagle@eyrie.org>
+dnl
+dnl This file is free software; the authors give unlimited permission to copy
+dnl and/or distribute it, with or without modifications, as long as this
+dnl notice is preserved.
+
+dnl Source used by RRA_PROG_CC_CLANG.
+AC_DEFUN([_RRA_PROG_CC_CLANG_SOURCE], [[
+#if ! __clang__
+#error
+#endif
+]])
+
+AC_DEFUN([RRA_PROG_CC_CLANG],
+[AC_CACHE_CHECK([if the compiler is Clang], [rra_cv_prog_cc_clang],
+ [AC_COMPILE_IFELSE([AC_LANG_SOURCE([_RRA_PROG_CC_CLANG_SOURCE])],
+ [rra_cv_prog_cc_clang=yes],
+ [rra_cv_prog_cc_clang=no])])
+ AS_IF([test x"$rra_cv_prog_cc_clang" = xyes], [CLANG=yes])])
AC_CHECK_HEADERS([sys/ioccom.h])
AS_IF([test x"$rra_libkafs" != xfalse],
[_RRA_LIB_KAFS_PATHS
- AS_IF([test x"$rra_use_kerberos" = xtrue],
+ AS_IF([test x"$rra_use_KRB5" = xtrue],
[RRA_LIB_KRB5_SWITCH])
RRA_LIB_KAFS_SWITCH
AC_CHECK_LIB([kafs], [k_hasafs],
AC_CHECK_FUNCS([k_pioctl])
AC_REPLACE_FUNCS([k_haspag])
RRA_LIB_KAFS_RESTORE
- AS_IF([test x"$rra_use_kerberos" = xtrue],
+ AS_IF([test x"$rra_use_KRB5" = xtrue],
[RRA_LIB_KRB5_RESTORE])])
dnl If we found a libkafs, we have k_hasafs. Set the appropriate
--- /dev/null
+dnl Use krb5-config to get link paths for Kerberos libraries.
+dnl
+dnl Provides one macro, RRA_KRB5_CONFIG, which attempts to get compiler and
+dnl linker flags for a library via krb5-config and sets the appropriate shell
+dnl variables. Defines the Autoconf variable PATH_KRB5_CONFIG, which can be
+dnl used to find the default path to krb5-config.
+dnl
+dnl Depends on RRA_ENABLE_REDUCED_DEPENDS.
+dnl
+dnl The canonical version of this file is maintained in the rra-c-util
+dnl package, available at <http://www.eyrie.org/~eagle/software/rra-c-util/>.
+dnl
+dnl Written by Russ Allbery <eagle@eyrie.org>
+dnl Copyright 2011, 2012
+dnl The Board of Trustees of the Leland Stanford Junior University
+dnl
+dnl This file is free software; the authors give unlimited permission to copy
+dnl and/or distribute it, with or without modifications, as long as this
+dnl notice is preserved.
+
+dnl Check for krb5-config in the user's path and set PATH_KRB5_CONFIG. This
+dnl is moved into a separate macro so that it can be loaded via AC_REQUIRE,
+dnl meaning it will only be run once even if we link with multiple krb5-config
+dnl libraries.
+AC_DEFUN([_RRA_KRB5_CONFIG_PATH],
+[AC_ARG_VAR([PATH_KRB5_CONFIG], [Path to krb5-config])
+ AC_PATH_PROG([PATH_KRB5_CONFIG], [krb5-config], [],
+ [${PATH}:/usr/kerberos/bin])])
+
+dnl Check whether the --deps flag is supported by krb5-config. Takes the path
+dnl to krb5-config to use. Note that this path is not embedded in the cache
+dnl variable, so this macro implicitly assumes that we will always use the
+dnl same krb5-config program.
+AC_DEFUN([_RRA_KRB5_CONFIG_DEPS],
+[AC_REQUIRE([_RRA_KRB5_CONFIG_PATH])
+ AC_CACHE_CHECK([for --deps support in krb5-config],
+ [rra_cv_krb5_config_deps],
+ [AS_IF(["$1" 2>&1 | grep deps >/dev/null 2>&1],
+ [rra_cv_krb5_config_deps=yes],
+ [rra_cv_krb5_config_deps=no])])])
+
+dnl Obtain the library flags for a particular library using krb5-config.
+dnl Takes the path to the krb5-config program to use, the argument to
+dnl krb5-config to use, and the variable prefix under which to store the
+dnl library flags.
+AC_DEFUN([_RRA_KRB5_CONFIG_LIBS],
+[AC_REQUIRE([_RRA_KRB5_CONFIG_PATH])
+ AC_REQUIRE([RRA_ENABLE_REDUCED_DEPENDS])
+ _RRA_KRB5_CONFIG_DEPS([$1])
+ AS_IF([test x"$rra_reduced_depends" = xfalse \
+ && test x"$rra_cv_krb5_config_deps" = xyes],
+ [$3[]_LIBS=`"$1" --deps --libs $2 2>/dev/null`],
+ [$3[]_LIBS=`"$1" --libs $2 2>/dev/null`])])
+
+dnl Attempt to find the flags for a library using krb5-config. Takes the
+dnl following arguments (in order):
+dnl
+dnl 1. The root directory for the library in question, generally from an
+dnl Autoconf --with flag. Used by preference as the path to krb5-config.
+dnl
+dnl 2. The argument to krb5-config to retrieve flags for this particular
+dnl library.
+dnl
+dnl 3. The variable prefix to use when setting CPPFLAGS and LIBS variables
+dnl based on the result of krb5-config.
+dnl
+dnl 4. Further actions to take if krb5-config was found and supported that
+dnl library type.
+dnl
+dnl 5. Further actions to take if krb5-config could not be used to get flags
+dnl for that library type.
+dnl
+dnl Special-case a krb5-config argument of krb5 and run krb5-config without an
+dnl argument if that option was requested and not supported. Old versions of
+dnl krb5-config didn't take an argument to specify the library type, but
+dnl always returned the flags for libkrb5.
+AC_DEFUN([RRA_KRB5_CONFIG],
+[AC_REQUIRE([_RRA_KRB5_CONFIG_PATH])
+ rra_krb5_config_$3=
+ rra_krb5_config_$3[]_ok=
+ AS_IF([test x"$1" != x && test -x "$1/bin/krb5-config"],
+ [rra_krb5_config_$3="$1/bin/krb5-config"],
+ [rra_krb5_config_$3="$PATH_KRB5_CONFIG"])
+ AS_IF([test x"$rra_krb5_config_$3" != x && test -x "$rra_krb5_config_$3"],
+ [AC_CACHE_CHECK([for $2 support in krb5-config], [rra_cv_lib_$3[]_config],
+ [AS_IF(["$rra_krb5_config_$3" 2>&1 | grep $2 >/dev/null 2>&1],
+ [rra_cv_lib_$3[]_config=yes],
+ [rra_cv_lib_$3[]_config=no])])
+ AS_IF([test "$rra_cv_lib_$3[]_config" = yes],
+ [$3[]_CPPFLAGS=`"$rra_krb5_config_$3" --cflags $2 2>/dev/null`
+ _RRA_KRB5_CONFIG_LIBS([$rra_krb5_config_$3], [$2], [$3])
+ rra_krb5_config_$3[]_ok=yes],
+ [AS_IF([test x"$2" = xkrb5],
+ [$3[]_CPPFLAGS=`"$rra_krb5_config_$3" --cflags 2>/dev/null`
+ $3[]_LIBS=`"$rra_krb5_config_$3" --libs $2 2>/dev/null`
+ rra_krb5_config_$3[]_ok=yes])])])
+ AS_IF([test x"$rra_krb5_config_$3[]_ok" = xyes],
+ [$3[]_CPPFLAGS=`echo "$$3[]_CPPFLAGS" | sed 's%-I/usr/include %%'`
+ $3[]_CPPFLAGS=`echo "$$3[]_CPPFLAGS" | sed 's%-I/usr/include$%%'`
+ $4],
+ [$5])])
dnl RRA_LIB_KRB5_SWITCH to set CPPFLAGS, LDFLAGS, and LIBS to include the
dnl Kerberos libraries, saving the current values first, and
dnl RRA_LIB_KRB5_RESTORE to restore those settings to before the last
-dnl RRA_LIB_KRB5_SWITCH. HAVE_KERBEROS will always be defined if RRA_LIB_KRB5
-dnl is used.
+dnl RRA_LIB_KRB5_SWITCH. HAVE_KRB5 will always be defined if RRA_LIB_KRB5 is
+dnl used.
dnl
dnl If KRB5_CPPFLAGS, KRB5_LDFLAGS, or KRB5_LIBS are set before calling these
dnl macros, their values will be added to whatever the macros discover.
dnl
+dnl KRB5_CPPFLAGS_GCC will be set to the same value as KRB5_CPPFLAGS but with
+dnl any occurrences of -I changed to -isystem. This may be useful to suppress
+dnl warnings from the Kerberos header files when building with GCC and
+dnl aggressive warning flags. Be aware that this change will change the
+dnl compiler header file search order as well.
+dnl
dnl Provides the RRA_LIB_KRB5_OPTIONAL macro, which should be used if Kerberos
-dnl support is optional. This macro will still always set the substitution
-dnl variables, but they'll be empty unless --with-krb5 is given. Also,
-dnl HAVE_KERBEROS will be defined if --with-krb5 is given and
-dnl $rra_use_kerberos will be set to "true".
+dnl support is optional. In this case, Kerberos libraries are mandatory if
+dnl --with-krb5 is given, and will not be probed for if --without-krb5 is
+dnl given. Otherwise, they'll be probed for but will not be required.
+dnl Defines HAVE_KRB5 and sets rra_use_KRB5 to true if the libraries are
+dnl found. The substitution variables will always be set, but they will be
+dnl empty unless Kerberos libraries are found and the user did not disable
+dnl Kerberos support.
dnl
dnl Sets the Automake conditional KRB5_USES_COM_ERR saying whether we use
dnl com_err, since if we're also linking with AFS libraries, we may have to
dnl change library ordering in that case.
dnl
-dnl Depends on RRA_ENABLE_REDUCED_DEPENDS and RRA_SET_LDFLAGS.
+dnl Depends on RRA_KRB5_CONFIG, RRA_ENABLE_REDUCED_DEPENDS, and
+dnl RRA_SET_LDFLAGS.
dnl
dnl Also provides RRA_FUNC_KRB5_GET_INIT_CREDS_OPT_FREE_ARGS, which checks
dnl whether krb5_get_init_creds_opt_free takes one argument or two. Defines
dnl package, available at <http://www.eyrie.org/~eagle/software/rra-c-util/>.
dnl
dnl Written by Russ Allbery <eagle@eyrie.org>
-dnl Copyright 2005, 2006, 2007, 2008, 2009, 2010, 2011
+dnl Copyright 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2013, 2014
dnl The Board of Trustees of the Leland Stanford Junior University
dnl
dnl This file is free software; the authors give unlimited permission to copy
dnl and/or distribute it, with or without modifications, as long as this
dnl notice is preserved.
+dnl Ignore Automake conditionals if not using Automake.
+m4_define_default([AM_CONDITIONAL], [:])
+
dnl Headers to include when probing for Kerberos library properties.
AC_DEFUN([RRA_INCLUDES_KRB5], [[
#if HAVE_KRB5_H
# include <krb5.h>
+#elif HAVE_KERBEROSV5_KRB5_H
+# include <kerberosv5/krb5.h>
#else
# include <krb5/krb5.h>
#endif
[AS_IF([test x"$rra_krb5_root" != x/usr],
[KRB5_CPPFLAGS="-I${rra_krb5_root}/include"])])])])
+dnl Check for a header using a file existence check rather than using
+dnl AC_CHECK_HEADERS. This is used if there were arguments to configure
+dnl specifying the Kerberos header path, since we may have one header in the
+dnl default include path and another under our explicitly-configured Kerberos
+dnl location.
+AC_DEFUN([_RRA_LIB_KRB5_CHECK_HEADER],
+[AC_MSG_CHECKING([for $1])
+ AS_IF([test -f "${rra_krb5_incroot}/$1"],
+ [AC_DEFINE_UNQUOTED(AS_TR_CPP([HAVE_$1]), [1],
+ [Define to 1 if you have the <$1> header file.])
+ AC_MSG_RESULT([yes])],
+ [AC_MSG_RESULT([no])])])
+
+dnl Check for the com_err header. Internal helper macro since we need
+dnl to do the same checks in multiple places.
+AC_DEFUN([_RRA_LIB_KRB5_CHECK_HEADER_COM_ERR],
+[AS_IF([test x"$rra_krb5_incroot" = x],
+ [AC_CHECK_HEADERS([et/com_err.h kerberosv5/com_err.h])],
+ [_RRA_LIB_KRB5_CHECK_HEADER([et/com_err.h])
+ _RRA_LIB_KRB5_CHECK_HEADER([kerberosv5/com_err.h])])])
+
+dnl Check for the main Kerberos header. Internal helper macro since we need
+dnl to do the same checks in multiple places.
+AC_DEFUN([_RRA_LIB_KRB5_CHECK_HEADER_KRB5],
+[AS_IF([test x"$rra_krb5_incroot" = x],
+ [AC_CHECK_HEADERS([krb5.h kerberosv5/krb5.h krb5/krb5.h])],
+ [_RRA_LIB_KRB5_CHECK_HEADER([krb5.h])
+ _RRA_LIB_KRB5_CHECK_HEADER([kerberosv5/krb5.h])
+ _RRA_LIB_KRB5_CHECK_HEADER([krb5/krb5.h])])])
+
dnl Does the appropriate library checks for reduced-dependency Kerberos
dnl linkage. The single argument, if true, says to fail if Kerberos could not
dnl be found.
[AS_IF([test x"$1" = xtrue],
[AC_MSG_ERROR([cannot find usable Kerberos library])])])
LIBS="$KRB5_LIBS $LIBS"
- AC_CHECK_HEADERS([krb5.h krb5/krb5.h])
+ _RRA_LIB_KRB5_CHECK_HEADER_KRB5
AC_CHECK_FUNCS([krb5_get_error_message],
[AC_CHECK_FUNCS([krb5_free_error_message])],
[AC_CHECK_FUNCS([krb5_get_error_string], [],
[RRA_INCLUDES_KRB5])],
[AC_CHECK_LIB([com_err], [com_err],
[KRB5_LIBS="$KRB5_LIBS -lcom_err"],
- [AC_MSG_ERROR([cannot find usable com_err library])])
- AC_CHECK_HEADERS([et/com_err.h])])])])])
+ [AS_IF([test x"$1" = xtrue],
+ [AC_MSG_ERROR([cannot find usable com_err library])],
+ [KRB5_LIBS=""])])
+ _RRA_LIB_KRB5_CHECK_HEADER_COM_ERR])])])])
RRA_LIB_KRB5_RESTORE])
dnl Does the appropriate library checks for Kerberos linkage when we don't
[AC_CHECK_LIB([nsl], [socket], [LIBS="-lnsl -lsocket $LIBS"], [],
[-lsocket])])
AC_SEARCH_LIBS([crypt], [crypt])
- AC_SEARCH_LIBS([rk_simple_execve], [roken])
+ AC_SEARCH_LIBS([roken_concat], [roken])
rra_krb5_extra="$LIBS"
LIBS="$rra_krb5_save_LIBS"
AC_CHECK_LIB([krb5], [krb5_init_context],
[$rra_krb5_extra])],
[-lasn1 -lcom_err -lcrypto $rra_krb5_extra])
LIBS="$KRB5_LIBS $LIBS"
- AC_CHECK_HEADERS([krb5.h krb5/krb5.h])
+ _RRA_LIB_KRB5_CHECK_HEADER_KRB5
AC_CHECK_FUNCS([krb5_get_error_message],
[AC_CHECK_FUNCS([krb5_free_error_message])],
[AC_CHECK_FUNCS([krb5_get_error_string], [],
[AC_CHECK_FUNCS([krb5_svc_get_msg],
[AC_CHECK_HEADERS([ibm_svc/krb5_svc.h], [], [],
[RRA_INCLUDES_KRB5])],
- [AC_CHECK_HEADERS([et/com_err.h])])])])])
+ [_RRA_LIB_KRB5_CHECK_HEADER_COM_ERR])])])])
RRA_LIB_KRB5_RESTORE])
dnl Sanity-check the results of krb5-config and be sure we can really link a
dnl additional probing we need to do to uncover error handling features, and
dnl falls back on the manual checks.
AC_DEFUN([_RRA_LIB_KRB5_CONFIG],
-[AC_ARG_VAR([PATH_KRB5_CONFIG], [Path to krb5-config])
- AS_IF([test x"$rra_krb5_root" != x && test -z "$PATH_KRB5_CONFIG"],
- [AS_IF([test -x "${rra_krb5_root}/bin/krb5-config"],
- [PATH_KRB5_CONFIG="${rra_krb5_root}/bin/krb5-config"])],
- [AC_PATH_PROG([PATH_KRB5_CONFIG], [krb5-config], [],
- [${PATH}:/usr/kerberos/bin])])
- AS_IF([test x"$PATH_KRB5_CONFIG" != x && test -x "$PATH_KRB5_CONFIG"],
- [AC_CACHE_CHECK([for krb5 support in krb5-config],
- [rra_cv_lib_krb5_config],
- [AS_IF(["$PATH_KRB5_CONFIG" 2>&1 | grep krb5 >/dev/null 2>&1],
- [rra_cv_lib_krb5_config=yes],
- [rra_cv_lib_krb5_config=no])])
- AS_IF([test x"$rra_cv_lib_krb5_config" = xyes],
- [KRB5_CPPFLAGS=`"$PATH_KRB5_CONFIG" --cflags krb5 2>/dev/null`
- KRB5_LIBS=`"$PATH_KRB5_CONFIG" --libs krb5 2>/dev/null`],
- [KRB5_CPPFLAGS=`"$PATH_KRB5_CONFIG" --cflags 2>/dev/null`
- KRB5_LIBS=`"$PATH_KRB5_CONFIG" --libs 2>/dev/null`])
- KRB5_CPPFLAGS=`echo "$KRB5_CPPFLAGS" | sed 's%-I/usr/include %%'`
- KRB5_CPPFLAGS=`echo "$KRB5_CPPFLAGS" | sed 's%-I/usr/include$%%'`
- _RRA_LIB_KRB5_CHECK([$1])
- RRA_LIB_KRB5_SWITCH
- AC_CHECK_HEADERS([krb5.h krb5/krb5.h])
- AC_CHECK_FUNCS([krb5_get_error_message],
- [AC_CHECK_FUNCS([krb5_free_error_message])],
- [AC_CHECK_FUNCS([krb5_get_error_string], [],
- [AC_CHECK_FUNCS([krb5_get_err_txt], [],
- [AC_CHECK_FUNCS([krb5_svc_get_msg],
- [AC_CHECK_HEADERS([ibm_svc/krb5_svc.h], [], [],
- [RRA_INCLUDES_KRB5])],
- [AC_CHECK_HEADERS([et/com_err.h])])])])])
- RRA_LIB_KRB5_RESTORE],
- [_RRA_LIB_KRB5_PATHS
- _RRA_LIB_KRB5_MANUAL([$1])])])
+[RRA_KRB5_CONFIG([${rra_krb5_root}], [krb5], [KRB5],
+ [_RRA_LIB_KRB5_CHECK([$1])
+ RRA_LIB_KRB5_SWITCH
+ _RRA_LIB_KRB5_CHECK_HEADER_KRB5
+ AC_CHECK_FUNCS([krb5_get_error_message],
+ [AC_CHECK_FUNCS([krb5_free_error_message])],
+ [AC_CHECK_FUNCS([krb5_get_error_string], [],
+ [AC_CHECK_FUNCS([krb5_get_err_txt], [],
+ [AC_CHECK_FUNCS([krb5_svc_get_msg],
+ [AC_CHECK_HEADERS([ibm_svc/krb5_svc.h], [], [],
+ [RRA_INCLUDES_KRB5])],
+ [_RRA_LIB_KRB5_CHECK_HEADER_COM_ERR])])])])
+ RRA_LIB_KRB5_RESTORE],
+ [_RRA_LIB_KRB5_PATHS
+ _RRA_LIB_KRB5_MANUAL([$1])])])
dnl The core of the library checking, shared between RRA_LIB_KRB5 and
dnl RRA_LIB_KRB5_OPTIONAL. The single argument, if "true", says to fail if
-dnl Kerberos could not be found.
+dnl Kerberos could not be found. Set up rra_krb5_incroot for later header
+dnl checking.
AC_DEFUN([_RRA_LIB_KRB5_INTERNAL],
[AC_REQUIRE([RRA_ENABLE_REDUCED_DEPENDS])
+ rra_krb5_incroot=
+ AC_SUBST([KRB5_CPPFLAGS])
+ AC_SUBST([KRB5_CPPFLAGS_GCC])
+ AC_SUBST([KRB5_LDFLAGS])
+ AC_SUBST([KRB5_LIBS])
+ AS_IF([test x"$rra_krb5_includedir" != x],
+ [rra_krb5_incroot="$rra_krb5_includedir"],
+ [AS_IF([test x"$rra_krb5_root" != x],
+ [rra_krb5_incroot="${rra_krb5_root}/include"])])
AS_IF([test x"$rra_reduced_depends" = xtrue],
[_RRA_LIB_KRB5_PATHS
_RRA_LIB_KRB5_REDUCED([$1])],
[_RRA_LIB_KRB5_PATHS
_RRA_LIB_KRB5_MANUAL([$1])])])
rra_krb5_uses_com_err=false
- AS_CASE([$LIBS], [*-lcom_err*], [rra_krb5_uses_com_err=true])
- AM_CONDITIONAL([KRB5_USES_COM_ERR], [test x"$rra_krb5_uses_com_err" = xtrue])])
+ AS_CASE([$KRB5_LIBS], [*-lcom_err*], [rra_krb5_uses_com_err=true])
+ AM_CONDITIONAL([KRB5_USES_COM_ERR],
+ [test x"$rra_krb5_uses_com_err" = xtrue])
+ KRB5_CPPFLAGS_GCC=`echo "$KRB5_CPPFLAGS" | sed -e 's/-I/-isystem /g'`])
dnl The main macro for packages with mandatory Kerberos support.
AC_DEFUN([RRA_LIB_KRB5],
[rra_krb5_root=
rra_krb5_libdir=
rra_krb5_includedir=
- rra_use_kerberos=true
- AC_SUBST([KRB5_CPPFLAGS])
- AC_SUBST([KRB5_LDFLAGS])
- AC_SUBST([KRB5_LIBS])
+ rra_use_KRB5=true
AC_ARG_WITH([krb5],
[AS_HELP_STRING([--with-krb5=DIR],
[AS_IF([test x"$withval" != xyes && test x"$withval" != xno],
[rra_krb5_libdir="$withval"])])
_RRA_LIB_KRB5_INTERNAL([true])
- AC_DEFINE([HAVE_KERBEROS], 1, [Define to enable Kerberos features.])])
+ AC_DEFINE([HAVE_KRB5], 1, [Define to enable Kerberos features.])])
dnl The main macro for packages with optional Kerberos support.
AC_DEFUN([RRA_LIB_KRB5_OPTIONAL],
[rra_krb5_root=
rra_krb5_libdir=
rra_krb5_includedir=
- rra_use_kerberos=
- AC_SUBST([KRB5_CPPFLAGS])
- AC_SUBST([KRB5_LDFLAGS])
- AC_SUBST([KRB5_LIBS])
+ rra_use_KRB5=
AC_ARG_WITH([krb5],
[AS_HELP_STRING([--with-krb5@<:@=DIR@:>@],
[Location of Kerberos headers and libraries])],
[AS_IF([test x"$withval" = xno],
- [rra_use_kerberos=false],
+ [rra_use_KRB5=false],
[AS_IF([test x"$withval" != xyes], [rra_krb5_root="$withval"])
- rra_use_kerberos=true])])
+ rra_use_KRB5=true])])
AC_ARG_WITH([krb5-include],
[AS_HELP_STRING([--with-krb5-include=DIR],
[Location of Kerberos headers])],
[AS_IF([test x"$withval" != xyes && test x"$withval" != xno],
[rra_krb5_libdir="$withval"])])
- AS_IF([test x"$rra_use_kerberos" != xfalse],
- [AS_IF([test x"$rra_use_kerberos" = xtrue],
+ AS_IF([test x"$rra_use_KRB5" != xfalse],
+ [AS_IF([test x"$rra_use_KRB5" = xtrue],
[_RRA_LIB_KRB5_INTERNAL([true])],
[_RRA_LIB_KRB5_INTERNAL([false])])],
[AM_CONDITIONAL([KRB5_USES_COM_ERR], [false])])
AS_IF([test x"$KRB5_LIBS" != x],
- [AC_DEFINE([HAVE_KERBEROS], 1, [Define to enable Kerberos features.])])])
+ [rra_use_KRB5=true
+ AC_DEFINE([HAVE_KRB5], 1, [Define to enable Kerberos features.])])])
dnl Source used by RRA_FUNC_KRB5_GET_INIT_CREDS_OPT_FREE_ARGS.
AC_DEFUN([_RRA_FUNC_KRB5_OPT_FREE_ARGS_SOURCE], [RRA_INCLUDES_KRB5] [[
*/
#include <config.h>
+#include <portable/macros.h>
#include <portable/system.h>
+#include <errno.h>
+
/*
* If we're running the test suite, rename the functions to avoid conflicts
* with the system versions.
*/
#if TESTING
+# undef asprintf
+# undef vasprintf
# define asprintf test_asprintf
# define vasprintf test_vasprintf
int test_asprintf(char **, const char *, ...)
__attribute__((__format__(printf, 2, 3)));
-int test_vasprintf(char **, const char *, va_list);
+int test_vasprintf(char **, const char *, va_list)
+ __attribute__((__format__(printf, 2, 0)));
#endif
+
int
asprintf(char **strp, const char *fmt, ...)
{
return status;
}
+
int
vasprintf(char **strp, const char *fmt, va_list args)
{
va_list args_copy;
- int status, needed;
+ int status, needed, oerrno;
va_copy(args_copy, args);
needed = vsnprintf(NULL, 0, fmt, args_copy);
if (status >= 0)
return status;
else {
+ oerrno = errno;
free(*strp);
*strp = NULL;
+ errno = oerrno;
return status;
}
}
* work.
*/
-/* Prototype to avoid gcc warnings. */
-int portable_dummy(void);
+#include <portable/macros.h>
+
+/* Prototype to avoid gcc warnings and set visibility. */
+int portable_dummy(void) __attribute__((__visibility__("hidden")));
int
portable_dummy(void)
* system call. Fall back on analyzing the groups.
*/
ngroups = getgroups(0, NULL);
- groups = malloc(sizeof(*groups) * ngroups);
+ groups = calloc(ngroups, sizeof(*groups));
if (groups == NULL)
return 0;
ngroups = getgroups(ngroups, groups);
* which can be found at <http://www.eyrie.org/~eagle/software/rra-c-util/>.
*
* Written by Russ Allbery <eagle@eyrie.org>
- * Copyright 2006, 2007, 2008, 2010
+ * Copyright 2006, 2007, 2008, 2010, 2013
* The Board of Trustees of the Leland Stanford Junior University
*
* Permission is hereby granted, free of charge, to any person obtaining a
#define PORTABLE_KAFS_H 1
#include <config.h>
-#ifdef HAVE_KERBEROS
+#ifdef HAVE_KRB5
# include <portable/krb5.h>
#endif
#include <portable/macros.h>
# include <kafs.h>
# elif HAVE_KOPENAFS_H
# include <kopenafs.h>
+# else
+struct ViceIoctl {
+ void *in, *out;
+ short in_size;
+ short out_size;
+};
+int k_hasafs(void);
+int k_pioctl(char *, struct ViceIoctl *, void *, int);
+int k_setpag(void);
+int k_unlog(void);
# endif
-# ifndef HAVE_K_HASPAG
+# ifdef HAVE_K_HASPAG
+# if !defined(HAVE_KAFS_H) && !defined(HAVE_KOPENAFS_H)
+int k_haspag(void);
+# endif
+# else
int k_haspag(void) __attribute__((__visibility__("hidden")));
# endif
# include <afs/afssyscalls.h>
# else
int lsetpag(void);
+int lpioctl(char *, int, void *, int);
# endif
-# define k_hasafs() (1)
-# define k_setpag() lsetpag()
-# define k_unlog() (errno = ENOSYS, -1)
+# define k_hasafs() (1)
+# define k_pioctl(p, c, a, f) lpioctl((p), (c), (a), (f))
+# define k_setpag() lsetpag()
+# define k_unlog() (errno = ENOSYS, -1)
int k_haspag(void) __attribute__((__visibility__("hidden")));
/* We have no kafs implementation available. */
#else
# undef HAVE_KAFS
-# define k_hasafs() (0)
-# define k_haspag() (0)
-# define k_setpag() (errno = ENOSYS, -1)
-# define k_unlog() (errno = ENOSYS, -1)
+# define k_hasafs() (0)
+# define k_haspag() (0)
+# define k_pioctl(p, c, a, f) (errno = ENOSYS, -1)
+# define k_setpag() (errno = ENOSYS, -1)
+# define k_unlog() (errno = ENOSYS, -1)
#endif
END_DECLS
#include <config.h>
#include <portable/krb5.h>
+#include <portable/macros.h>
#include <portable/system.h>
#include <errno.h>
# include <ibm_svc/krb5_svc.h>
# elif defined(HAVE_ET_COM_ERR_H)
# include <et/com_err.h>
+# elif defined(HAVE_KERBEROSV5_COM_ERR_H)
+# include <kerberosv5/com_err.h>
# else
# include <com_err.h>
# endif
#ifndef HAVE_KRB5_CC_GET_FULL_NAME
/*
* Given a Kerberos ticket cache, return the full name (TYPE:name) in
- * newly-allocated memory. Returns an error code.
+ * newly-allocated memory. Returns an error code. Avoid asprintf and
+ * snprintf here in case someone wants to use this code without the rest of
+ * the portability layer.
*/
krb5_error_code
krb5_cc_get_full_name(krb5_context ctx, krb5_ccache ccache, char **out)
#endif
#include <portable/macros.h>
-#ifdef HAVE_KRB5_H
+#if defined(HAVE_KRB5_H)
# include <krb5.h>
+#elif defined(HAVE_KERBEROSV5_KRB5_H)
+# include <kerberosv5/krb5.h>
#else
# include <krb5/krb5.h>
#endif
/* Undo default visibility change. */
#pragma GCC visibility pop
+END_DECLS
+
#endif /* !PORTABLE_KRB5_H */
# 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)) \
+ && !defined(__clang__)
+# 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
+
/*
* BEGIN_DECLS is used at the beginning of declarations so that C++
* compilers don't mangle their names. END_DECLS is used at the end.
#include <errno.h>
#include <fcntl.h>
-#include <sys/time.h>
+#ifdef HAVE_SYS_TIME_H
+# include <sys/time.h>
+#endif
+#include <time.h>
/*
* If we're running the test suite, rename mkstemp to avoid conflicts with the
--- /dev/null
+/*
+ * Replacement for a missing reallocarray.
+ *
+ * Provides the same functionality as the OpenBSD library function
+ * reallocarray for those systems that don't have it. This function is the
+ * same as realloc, but takes the size arguments in the same form as calloc
+ * and checks for overflow so that the caller doesn't need to.
+ *
+ * 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>
+ *
+ * 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
+ * international treaty, and hereby commit to the public, at large, that they
+ * shall not, at any time in the future, seek to enforce any copyright in this
+ * work against any person or entity, or prevent any person or entity from
+ * copying, publishing, distributing or creating derivative works of this
+ * work.
+ */
+
+#include <config.h>
+#include <portable/system.h>
+
+#include <errno.h>
+
+/*
+ * If we're running the test suite, rename reallocarray to avoid conflicts
+ * with the system version. #undef it first because some systems may define
+ * it to another name.
+ */
+#if TESTING
+# undef reallocarray
+# define reallocarray test_reallocarray
+void *test_reallocarray(void *, size_t, size_t);
+#endif
+
+/*
+ * nmemb * size cannot overflow if both are smaller than sqrt(SIZE_MAX). We
+ * can calculate that value statically by using 2^(sizeof(size_t) * 8) as the
+ * value of SIZE_MAX and then taking the square root, which gives
+ * 2^(sizeof(size_t) * 4). Compute the exponentiation with shift.
+ */
+#define CHECK_THRESHOLD (1UL << (sizeof(size_t) * 4))
+
+void *
+reallocarray(void *ptr, size_t nmemb, size_t size)
+{
+ if (nmemb >= CHECK_THRESHOLD || size >= CHECK_THRESHOLD)
+ if (nmemb > 0 && SIZE_MAX / nmemb <= size) {
+ errno = ENOMEM;
+ return NULL;
+ }
+ return realloc(ptr, nmemb * size);
+}
* the system version.
*/
#if TESTING
+# undef setenv
# define setenv test_setenv
int test_setenv(const char *, const char *, int);
#endif
setenv(const char *name, const char *value, int overwrite)
{
char *envstring;
- size_t size;
+ /* Do nothing if not overwriting and the variable is already set. */
if (!overwrite && getenv(name) != NULL)
return 0;
- /*
- * Allocate memory for the environment string. We intentionally don't use
- * concat here, or the xmalloc family of allocation routines, since the
- * intention is to provide a replacement for the standard library function
- * which sets errno and returns in the event of a memory allocation
- * failure.
- */
- size = strlen(name) + 1 + strlen(value) + 1;
- envstring = malloc(size);
- if (envstring == NULL)
- return -1;
-
/*
* Build the environment string and add it to the environment using
* putenv. Systems without putenv lose, but XPG4 requires it.
+ *
+ * We intentionally don't use the xmalloc family of allocation routines
+ * here, since the intention is to provide a replacement for the standard
+ * library function that sets errno and returns in the event of a memory
+ * allocation failure.
*/
- strlcpy(envstring, name, size);
- strlcat(envstring, "=", size);
- strlcat(envstring, value, size);
+ if (asprintf(&envstring, "%s=%s", name, value) < 0)
+ return -1;
return putenv(envstring);
/*
* Replacement for a missing snprintf or vsnprintf.
*
* The following implementation of snprintf was taken mostly verbatim from
- * <http://www.fiction.net/~blong/programs/>; it is the version of snprintf
- * used in Mutt.
+ * <http://www.fiction.net/blong/programs/>; it is the version of snprintf
+ * used in Mutt. A possibly newer version is used in wget, found at
+ * <https://github.com/wertarbyte/wget/blob/master/src/snprintf.c>.
*
* Please do not reformat or otherwise change this file more than necessary so
* that later merges with the original source are easy. Bug fixes and
* conflicts with the system version.
*/
#if TESTING
+# undef snprintf
+# undef vsnprintf
# define snprintf test_snprintf
# define vsnprintf test_vsnprintf
#endif
break;
case 'w':
/* not supported yet, treat as next char */
- ch = *format++;
+ format++;
break;
default:
/* Unknown, skip */
/* For each leading 0 in fractional part, print one more
fractional digit. */
LDOUBLE temp;
- if (ufvalue != 0)
+ if (ufvalue > 0)
for (temp = ufvalue; temp < 0.1; temp *= 10)
++max;
}
* the system version.
*/
#if TESTING
+# undef strlcat
# define strlcat test_strlcat
size_t test_strlcat(char *, const char *, size_t);
#endif
* the system version.
*/
#if TESTING
+# undef strlcpy
# define strlcpy test_strlcpy
size_t test_strlcpy(char *, const char *, size_t);
#endif
char *
strndup(const char *s, size_t n)
{
+ const char *p;
size_t length;
char *copy;
errno = EINVAL;
return NULL;
}
- length = strlen(s);
- if (length > n)
- length = n;
+
+ /* Don't assume that the source string is nul-terminated. */
+ for (p = s; (size_t) (p - s) < n && *p != '\0'; p++)
+ ;
+ length = p - s;
copy = malloc(length + 1);
if (copy == NULL)
return NULL;
* file is the equivalent of including all of the following headers,
* portably:
*
- * #include <sys/types.h>
+ * #include <inttypes.h>
+ * #include <limits.h>
* #include <stdarg.h>
* #include <stdbool.h>
+ * #include <stddef.h>
* #include <stdio.h>
* #include <stdlib.h>
- * #include <stddef.h>
* #include <stdint.h>
* #include <string.h>
* #include <strings.h>
+ * #include <sys/types.h>
* #include <unistd.h>
*
* Missing functions are provided via #define or prototyped if available from
#include <portable/macros.h>
/* A set of standard ANSI C headers. We don't care about pre-ANSI systems. */
+#if HAVE_INTTYPES_H
+# include <inttypes.h>
+#endif
+#include <limits.h>
#include <stdarg.h>
#include <stddef.h>
+#if HAVE_STDINT_H
+# include <stdint.h>
+#endif
#include <stdio.h>
#include <stdlib.h>
-#include <sys/types.h>
#include <string.h>
#if HAVE_STRINGS_H
# include <strings.h>
#endif
-#if HAVE_INTTYPES_H
-# include <inttypes.h>
-#endif
-#if HAVE_STDINT_H
-# include <stdint.h>
-#endif
+#include <sys/types.h>
#if HAVE_UNISTD_H
# include <unistd.h>
#endif
#if !HAVE_MKSTEMP
extern int mkstemp(char *);
#endif
+#if !HAVE_REALLOCARRAY
+extern void *reallocarray(void *, size_t, size_t);
+#endif
#if !HAVE_SETENV
extern int setenv(const char *, const char *, int);
#endif
-#if !HAVE_STRLCAT
+#if !HAVE_DECL_STRLCAT
extern size_t strlcat(char *, const char *, size_t);
#endif
-#if !HAVE_STRLCPY
+#if !HAVE_DECL_STRLCPY
extern size_t strlcpy(char *, const char *, size_t);
#endif
#if !HAVE_STRNDUP
portable/asprintf
portable/daemon
portable/mkstemp
+portable/reallocarray
portable/setenv
portable/snprintf
portable/strlcat
portable/strlcpy
portable/strndup
-util/concat
util/messages
util/messages-krb5
util/xmalloc
--- /dev/null
+# Configuration for Perl tests. -*- perl -*-
+
+# File must end with this line.
+1;
-#!/usr/bin/perl -w
+#!/usr/bin/perl
#
-# Check for spelling errors in POD documentation
+# Checks all POD files in the tree for spelling errors using Test::Spelling.
#
-# Checks all POD files in the tree for spelling problems using Pod::Spell and
-# either aspell or ispell. aspell is preferred. This test is disabled unless
-# RRA_MAINTAINER_TESTS is set, since spelling dictionaries vary too much
-# between environments.
+# The canonical version of this file is maintained in the rra-c-util package,
+# which can be found at <http://www.eyrie.org/~eagle/software/rra-c-util/>.
#
-# Copyright 2008, 2009, 2011 Russ Allbery <eagle@eyrie.org>
+# Written by Russ Allbery <eagle@eyrie.org>
+# Copyright 2012, 2013, 2014
+# The Board of Trustees of the Leland Stanford Junior University
#
-# See LICENSE for licensing terms.
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the "Software"),
+# to deal in the Software without restriction, including without limitation
+# the rights to use, copy, modify, merge, publish, distribute, sublicense,
+# and/or sell copies of the Software, and to permit persons to whom the
+# Software is furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+# DEALINGS IN THE SOFTWARE.
+use 5.006;
use strict;
-use Test::More;
+use warnings;
-# Skip all spelling tests unless the maintainer environment variable is set.
-plan skip_all => 'spelling tests only run for maintainer'
- unless $ENV{RRA_MAINTAINER_TESTS};
+use lib "$ENV{SOURCE}/tap/perl";
+
+use Test::More;
+use Test::RRA qw(skip_unless_author use_prereq);
+use Test::RRA::Automake qw(automake_setup perl_dirs);
-# Load required Perl modules.
-eval 'use Test::Pod 1.00';
-plan skip_all => 'Test::Pod 1.00 required for testing POD' if $@;
-eval 'use Pod::Spell';
-plan skip_all => 'Pod::Spell required to test POD spelling' if $@;
+# Only run this test for the module author since the required stopwords are
+# too sensitive to the exact spell-checking program and dictionary.
+skip_unless_author('Spelling tests');
-# Locate a spell-checker. hunspell is not currently supported due to its lack
-# of support for contractions (at least in the version in Debian).
-my @spell;
-my %options = (aspell => [ qw(-d en_US --home-dir=./ list) ],
- ispell => [ qw(-d american -l -p /dev/null) ]);
-SEARCH: for my $program (qw/aspell ispell/) {
- for my $dir (split ':', $ENV{PATH}) {
- if (-x "$dir/$program") {
- @spell = ("$dir/$program", @{ $options{$program} });
- }
- last SEARCH if @spell;
- }
-}
-plan skip_all => 'aspell or ispell required to test POD spelling'
- unless @spell;
+# Load prerequisite modules.
+use_prereq('Test::Spelling');
-# Prerequisites are satisfied, so we're going to do some testing. Figure out
-# what POD files we have and from that develop our plan.
-$| = 1;
-my @pod = map {
- my $pod = "$ENV{SOURCE}/../" . $_;
- $pod =~ s,[^/.][^/]*/../,,g;
- $pod;
-} qw(k5start.pod krenew.pod);
-plan tests => scalar @pod;
+# Set up Automake testing.
+automake_setup();
-# Finally, do the checks.
-for my $pod (@pod) {
- my $child = open (CHILD, '-|');
- if (not defined $child) {
- BAIL_OUT ("cannot fork: $!");
- } elsif ($child == 0) {
- my $pid = open (SPELL, '|-', @spell)
- or BAIL_OUT ("cannot run @spell: $!");
- open (POD, '<', $pod) or BAIL_OUT ("cannot open $pod: $!");
- my $parser = Pod::Spell->new;
- $parser->parse_from_filehandle (\*POD, \*SPELL);
- close POD;
- close SPELL;
- exit ($? >> 8);
- } else {
- my @words = <CHILD>;
- close CHILD;
- SKIP: {
- skip "@spell failed for $pod", 1 unless $? == 0;
- for (@words) {
- s/^\s+//;
- s/\s+$//;
- }
- is ("@words", '', $pod);
- }
- }
-}
+# Run the tests.
+all_pod_files_spelling_ok(perl_dirs());
-#!/usr/bin/perl -w
+#!/usr/bin/perl
#
-# Test formatting of POD documentation.
+# Check all POD documents in the tree, except for any embedded Perl module
+# distribution, for POD formatting errors.
+#
+# The canonical version of this file is maintained in the rra-c-util package,
+# which can be found at <http://www.eyrie.org/~eagle/software/rra-c-util/>.
#
# Written by Russ Allbery <eagle@eyrie.org>
-# Copyright 2008, 2009, 2011
+# Copyright 2012, 2013, 2014
# The Board of Trustees of the Leland Stanford Junior University
#
-# See LICENSE for licensing terms.
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the "Software"),
+# to deal in the Software without restriction, including without limitation
+# the rights to use, copy, modify, merge, publish, distribute, sublicense,
+# and/or sell copies of the Software, and to permit persons to whom the
+# Software is furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+# DEALINGS IN THE SOFTWARE.
+use 5.006;
use strict;
+use warnings;
+
+use lib "$ENV{SOURCE}/tap/perl";
+
use Test::More;
+use Test::RRA qw(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');
-my @podfiles = qw(k5start.pod krenew.pod);
+# Set up Automake testing.
+automake_setup();
-eval 'use Test::Pod 1.00';
-plan skip_all => "Test::Pod 1.00 required for testing POD" if $@;
-my $srcdir = "$ENV{SOURCE}";
-$srcdir =~ s,[^/]+/*$,,;
-chdir "$srcdir" or die "$0: cannot chdir to $srcdir: $!\n";
-all_pod_files_ok (@podfiles);
+# Run the tests.
+all_pod_files_ok(perl_dirs());
fi
fi
-# If aklog failed, we can't run the rest of the tests.
+# If aklog or tokens failed, we can't run the rest of the tests.
if [ "$status" -eq 3 ] ; then
- skip_block 3 'aklog failed'
+ skip_block 3 'aklog or tokens failed'
else
if sed -n '/^=== tokens .aklog./,/^===/p' basic-output \
| grep -i 'tokens for ' > /dev/null ; then
* k_setpag, then tokens, then aklog, then tokens, and then k_unlog, sending
* the output to standard output and errors to standard error. If either
* k_setpag or k_unlog return failure, it reports an error to standard error
- * and exits with status 1. If aklog fails, it exits with status 3. If the
- * commands all finish, it exits 0.
+ * and exits with status 1. If aklog or tokens fails, it exits with status 3.
+ * If the commands all finish, it exits 0.
*
* 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 2009, 2010
+ * Copyright 2009, 2010, 2012
* The Board of Trustees of the Leland Stanford Junior University
*
* Permission is hereby granted, free of charge, to any person obtaining a
}
printf("=== tokens (setpag) ===\n");
fflush(stdout);
- system("tokens");
+ if (system("tokens") != 0)
+ exit(3);
if (system(PATH_AKLOG) != 0)
exit(3);
printf("=== tokens (aklog) ===\n");
fflush(stdout);
- system("tokens");
+ if (system("tokens") != 0)
+ exit(3);
if (k_unlog() != 0) {
fprintf(stderr, "k_unlog failed: %s", strerror(errno));
exit(1);
}
printf("=== tokens (unlog) ===\n");
fflush(stdout);
- system("tokens");
+ if (system("tokens") != 0)
+ exit(3);
return 0;
}
*/
#include <config.h>
+#include <portable/macros.h>
#include <portable/system.h>
#include <tests/tap/basic.h>
# include <sys/select.h>
#endif
#include <sys/stat.h>
-#include <sys/time.h>
+#ifdef HAVE_SYS_TIME_H
+# include <sys/time.h>
+#endif
#include <sys/wait.h>
+#include <time.h>
#include <tests/tap/basic.h>
if (child < 0)
sysbail("cannot fork");
else if (child == 0) {
- is_int(0, daemon(1, 1), "daemon(1, 1)");
+ is_int(0, test_daemon(1, 1), "daemon(1, 1)");
fd = open("/dev/tty", O_RDONLY);
ok(fd < 0, "...no tty");
is_string(start, getcwd(dir, sizeof(dir)), "...in same directory");
if (child < 0)
sysbail("cannot fork");
else if (child == 0) {
- is_int(0, daemon(0, 1), "daemon(0, 1)");
+ is_int(0, test_daemon(0, 1), "daemon(0, 1)");
is_string("/", getcwd(dir, sizeof(dir)), "...now in /");
if (chdir(start) != 0)
sysbail("cannot chdir to %s", start);
if (child < 0)
sysbail("cannot fork");
else if (child == 0) {
- daemon(0, 0);
+ if (test_daemon(0, 0) != 0)
+ sysbail("daemon failed");
if (chdir(start) != 0)
sysbail("cannot chdir to %s", start);
ok(0, "output from child that should be hidden");
--- /dev/null
+/*
+ * reallocarray test suite.
+ *
+ * This does some simple sanity checks and checks some of the overflow
+ * detection, but isn't particularly thorough.
+ *
+ * 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>
+ *
+ * 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
+ * international treaty, and hereby commit to the public, at large, that they
+ * shall not, at any time in the future, seek to enforce any copyright in this
+ * work against any person or entity, or prevent any person or entity from
+ * copying, publishing, distributing or creating derivative works of this
+ * work.
+ */
+
+#include <config.h>
+#include <portable/system.h>
+
+#include <errno.h>
+
+#include <tests/tap/basic.h>
+
+void *test_reallocarray(void *, size_t, size_t);
+
+
+int
+main(void)
+{
+ char *p, *base;
+ size_t sqrt_max;
+ int oerrno;
+
+ plan(15);
+
+ /* Test success cases and write to the memory for valgrind checks. */
+ p = test_reallocarray(NULL, 2, 5);
+ memcpy(p, "123456789", 10);
+ is_string("123456789", p, "reallocarray of NULL");
+ p = test_reallocarray(p, 4, 5);
+ is_string("123456789", p, "reallocarray after resize");
+ memcpy(p + 9, "0123456789", 11);
+ is_string("1234567890123456789", p, "write to larger memory segment");
+ free(p);
+
+ /*
+ * If nmemb or size are 0, we should either get NULL or a pointer we can
+ * free. Make sure we don't get something weird, like division by zero.
+ */
+ p = test_reallocarray(NULL, 0, 100);
+ if (p != NULL)
+ free(p);
+ p = test_reallocarray(NULL, 100, 0);
+ if (p != NULL)
+ free(p);
+
+ /* Test the range-checking error cases. */
+ p = test_reallocarray(NULL, 2, SIZE_MAX / 2);
+ oerrno = errno;
+ ok(p == NULL, "reallocarray fails for 2, SIZE_MAX / 2");
+ is_int(ENOMEM, oerrno, "...with correct errno");
+ base = malloc(10);
+ p = test_reallocarray(base, 3, SIZE_MAX / 3);
+ oerrno = errno;
+ ok(p == NULL, "reallocarray fails for 3, SIZE_MAX / 3");
+ is_int(ENOMEM, oerrno, "...with correct errno");
+ sqrt_max = (1UL << (sizeof(size_t) * 4));
+ p = test_reallocarray(base, sqrt_max, sqrt_max);
+ oerrno = errno;
+ ok(p == NULL, "reallocarray fails for sqrt(SIZE_MAX), sqrt(SIZE_MAX)");
+ is_int(ENOMEM, oerrno, "...with correct errno");
+ p = test_reallocarray(base, 1, SIZE_MAX);
+ oerrno = errno;
+ ok(p == NULL, "reallocarray fails for 1, SIZE_MAX");
+ is_int(ENOMEM, oerrno, "...with correct errno");
+ p = test_reallocarray(base, SIZE_MAX, 1);
+ oerrno = errno;
+ ok(p == NULL, "reallocarray fails for SIZE_MAX, 1");
+ is_int(ENOMEM, oerrno, "...with correct errno");
+ p = test_reallocarray(base, 2, SIZE_MAX);
+ oerrno = errno;
+ ok(p == NULL, "reallocarray fails for 2, SIZE_MAX");
+ is_int(ENOMEM, oerrno, "...with correct errno");
+
+ /* Clean up and exit. */
+ free(base);
+ return 0;
+}
--- /dev/null
+#define TESTING 1
+#include <portable/reallocarray.c>
* Disable the requirement that format strings be literals. We need variable
* formats for easy testing.
*/
-#pragma GCC diagnostic ignored "-Wformat-nonliteral"
+#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ > 2) || defined(__clang__)
+# pragma GCC diagnostic ignored "-Wformat-nonliteral"
+#endif
/*
* Intentionally don't add the printf attribute here since we pass a
int
main(void)
{
+ char buffer[3];
char *result = NULL;
- plan(6);
+ plan(7);
result = test_strndup("foo", 8);
is_string("foo", result, "strndup longer than string");
result = test_strndup("foo", 0);
is_string("", result, "strndup of size 0");
free(result);
+ memcpy(buffer, "foo", 3);
+ result = test_strndup(buffer, 3);
+ is_string("foo", result, "strndup of non-nul-terminated string");
+ free(result);
errno = 0;
result = test_strndup(NULL, 0);
is_string(NULL, result, "strndup of NULL");
*
* Usage:
*
- * runtests [-b <build-dir>] [-s <source-dir>] <test-list>
- * runtests -o [-b <build-dir>] [-s <source-dir>] <test>
+ * runtests [-hv] [-b <build-dir>] [-s <source-dir>] -l <test-list>
+ * runtests [-hv] [-b <build-dir>] [-s <source-dir>] <test> [<test> ...]
+ * runtests -o [-h] [-b <build-dir>] [-s <source-dir>] <test>
*
* In the first case, expects a list of executables located in the given file,
* one line per executable. For each one, runs it as part of a test suite,
- * reporting results. Test output should start with a line containing the
- * number of tests (numbered from 1 to this number), optionally preceded by
- * "1..", although that line may be given anywhere in the output. Each
- * additional line should be in the following format:
+ * reporting results. In the second case, use the same infrastructure, but
+ * run only the tests listed on the command line.
+ *
+ * Test output should start with a line containing the number of tests
+ * (numbered from 1 to this number), optionally preceded by "1..", although
+ * that line may be given anywhere in the output. Each additional line should
+ * be in the following format:
*
* ok <number>
* not ok <number>
* directories. These paths can also be set with the -b and -s command-line
* options, which will override anything set at build time.
*
+ * If the -v option is given, or the C_TAP_VERBOSE environment variable is set,
+ * display the full output of each test as it runs rather than showing a
+ * summary of the results of each test.
+ *
* Any bug reports, bug fixes, and improvements are very much welcome and
* 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 <eagle@eyrie.org>
+ * Copyright 2000, 2001, 2004, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013,
+ * 2014, 2015 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"),
*/
/* Required for fdopen(), getopt(), and putenv(). */
-#ifndef _XOPEN_SOURCE
-# define _XOPEN_SOURCE 500
+#if defined(__STRICT_ANSI__) || defined(PEDANTIC)
+# ifndef _XOPEN_SOURCE
+# define _XOPEN_SOURCE 500
+# endif
#endif
#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>
/* 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.
TEST_INVALID
};
+/* Really, just a boolean, but this is more self-documenting. */
+enum test_verbose {
+ CONCISE = 0,
+ VERBOSE = 1
+};
+
/* Indicates the state of our plan. */
enum plan_status {
PLAN_INIT, /* Nothing seen yet. */
/* 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 {
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. */
};
/*
- * 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\
- %s -o [-b <build-dir>] [-s <source-dir>] <test>\n\
+Usage: %s [-hv] [-b <build-dir>] [-s <source-dir>] <test> ...\n\
+ %s [-hv] [-b <build-dir>] [-s <source-dir>] -l <test-list>\n\
+ %s -o [-h] [-b <build-dir>] [-s <source-dir>] <test>\n\
\n\
Options:\n\
-b <build-dir> Set the build directory to <build-dir>\n\
+%s";
+static const char usage_extra[] = "\
+ -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\
+ -v Show the full output of each test\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
-------------------------- -------------- ---- ---- ------------------------";
/* 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 defined(__GNUC__) && !defined(__clang__)
+# if __GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 3)
+# define __alloc_size__(spec, args...) /* empty */
+# endif
+# 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__));
/*
}
+/*
+ * 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.
*/
/*
* 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;
}
}
+/*
+ * 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.
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;
}
+/*
+ * 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;
+}
+
+
+/*
+ * Report an invalid test number and set the appropriate flags. Pulled into a
+ * separate function since we do this in several places.
+ */
+static void
+invalid_test_number(struct testset *ts, long n, enum test_verbose verbose)
+{
+ if (!verbose)
+ test_backspace(ts);
+ printf("ABORTED (invalid test number %ld)\n", n);
+ ts->aborted = 1;
+ ts->reported = 1;
+}
+
+
/*
* 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
* continue, false otherwise.
*/
static int
-test_plan(const char *line, struct testset *ts)
+test_plan(const char *line, struct testset *ts, enum test_verbose verbose)
{
- unsigned long i;
long n;
/*
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) {
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) {
+ invalid_test_number(ts, (long) ts->count, verbose);
+ 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, (unsigned long) n);
+ ts->count = (unsigned long) 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;
}
* reported status.
*/
static void
-test_checkline(const char *line, struct testset *ts)
+test_checkline(const char *line, struct testset *ts,
+ enum test_verbose verbose)
{
enum test_status status = TEST_PASS;
const char *bail;
char *end;
long number;
- unsigned long i, current;
+ unsigned long current;
int outlen;
/* Before anything, check for a test abort. */
length = strlen(bail);
if (bail[length - 1] == '\n')
length--;
- test_backspace(ts);
+ if (!verbose)
+ test_backspace(ts);
printf("ABORTED (%.*s)\n", (int) length, bail);
ts->reported = 1;
}
/* If we haven't yet seen a plan, look for one. */
if (ts->plan == PLAN_INIT && isdigit((unsigned char)(*line))) {
- if (!test_plan(line, ts))
+ if (!test_plan(line, ts, verbose))
return;
} else if (strncmp(line, "1..", 3) == 0) {
if (ts->plan == PLAN_PENDING) {
- if (!test_plan(line, ts))
+ if (!test_plan(line, ts, verbose))
return;
} else {
+ if (!verbose)
+ test_backspace(ts);
puts("ABORTED (multiple plans)");
ts->aborted = 1;
ts->reported = 1;
errno = 0;
number = strtol(line, &end, 10);
if (errno != 0 || end == line)
- number = ts->current + 1;
- current = number;
- if (number <= 0 || (current > ts->count && ts->plan == PLAN_FIRST)) {
- test_backspace(ts);
- printf("ABORTED (invalid test number %lu)\n", current);
- ts->aborted = 1;
- ts->reported = 1;
+ current = ts->current + 1;
+ else if (number <= 0) {
+ invalid_test_number(ts, number, verbose);
+ return;
+ } else
+ current = (unsigned long) number;
+ if (current > ts->count && ts->plan == PLAN_FIRST) {
+ invalid_test_number(ts, (long) current, verbose);
return;
}
/* 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;
- }
}
/*
/* Make sure that the test number is in range and not a duplicate. */
if (ts->results[current - 1] != TEST_INVALID) {
- test_backspace(ts);
+ if (!verbose)
+ test_backspace(ts);
printf("ABORTED (duplicate test number %lu)\n", current);
ts->aborted = 1;
ts->reported = 1;
}
ts->current = current;
ts->results[current - 1] = status;
- test_backspace(ts);
- if (isatty(STDOUT_FILENO)) {
- outlen = printf("%lu/%lu", current, ts->count);
- ts->length = (outlen >= 0) ? outlen : 0;
+ if (!verbose && isatty(STDOUT_FILENO)) {
+ test_backspace(ts);
+ if (ts->plan == PLAN_PENDING)
+ outlen = printf("%lu/?", current);
+ else
+ outlen = printf("%lu/%lu", current, ts->count);
+ ts->length = (outlen >= 0) ? (unsigned int) outlen : 0;
fflush(stdout);
}
}
* disable this).
*/
static unsigned int
-test_print_range(unsigned long first, unsigned long last, unsigned int chars,
+test_print_range(unsigned long first, unsigned long last, unsigned long chars,
unsigned int limit)
{
unsigned int needed = 0;
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)");
* false otherwise.
*/
static int
-test_run(struct testset *ts)
+test_run(struct testset *ts, enum test_verbose verbose)
{
pid_t testpid, child;
int outfd, status;
sysdie("fdopen failed");
}
- /* Pass each line of output to test_checkline(). */
- while (!ts->aborted && fgets(buffer, sizeof(buffer), output))
- test_checkline(buffer, ts);
+ /*
+ * Pass each line of output to test_checkline(), and print the line if
+ * verbosity is requested.
+ */
+ while (!ts->aborted && fgets(buffer, sizeof(buffer), output)) {
+ if (verbose)
+ printf("%s", buffer);
+ test_checkline(buffer, ts, verbose);
+ }
if (ferror(output) || ts->plan == PLAN_INIT)
ts->aborted = 1;
- test_backspace(ts);
+ if (!verbose)
+ test_backspace(ts);
/*
* Consume the rest of the test output, close the output descriptor,
* for eventual output.
*/
while (fgets(buffer, sizeof(buffer), output))
- ;
+ if (verbose)
+ printf("%s", buffer);
fclose(output);
child = waitpid(testpid, &ts->status, 0);
if (child == (pid_t) -1) {
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;
+ char *path = NULL;
+ 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)
+ /* 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 = xstrdup(name);
+ return path;
+}
+
+
+/*
+ * Read a list of tests from a file, returning the list of tests as a struct
+ * testlist, or NULL if there were no tests (such as a file containing only
+ * comments). 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];
+ const char *testname;
+ 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';
+
+ /* Skip comments, leading spaces, and blank lines. */
+ testname = skip_whitespace(buffer);
+ if (strlen(testname) == 0)
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;
+ if (testname[0] == '#')
+ continue;
+
+ /* Allocate the new testset structure. */
+ 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(testname);
}
- if (path == NULL) {
- path = xmalloc(strlen(name) + 3);
- sprintf(path, "%s-t", name);
+ fclose(file);
+
+ /* If there were no tests, current is still NULL. */
+ if (current == NULL) {
+ free(listhead);
+ return NULL;
}
- ts->path = path;
+
+ /* 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, or NULL if there were no tests. 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]);
+ }
+
+ /* If there were no tests, current is still NULL. */
+ if (current == NULL) {
+ free(listhead);
+ return NULL;
+ }
+
+ /* 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,
+ enum test_verbose verbose)
{
- FILE *tests;
- unsigned int length, i;
- unsigned int longest = 0;
- char buffer[BUFSIZ];
- unsigned int line;
- struct testset ts, *tmp;
+ size_t length, i;
+ size_t longest = 0;
+ 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
/* 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++)
- putchar('.');
+ /* 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);
+ if (verbose)
+ fputs("\n\n", stdout);
+ else
+ 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, verbose);
+ fflush(stdout);
+ if (verbose)
+ putchar('\n');
+
+ /* 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) {
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)
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),
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);
}
int option;
int status = 0;
int single = 0;
+ enum test_verbose verbose = CONCISE;
char *source_env = NULL;
char *build_env = NULL;
- const char *list;
+ const char *program;
+ 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) {
+ program = argv[0];
+ while ((option = getopt(argc, argv, "b:hl:os:v")) != EOF) {
switch (option) {
case 'b':
build = optarg;
break;
case 'h':
- printf(usage_message, argv[0], argv[0]);
+ printf(usage_message, program, program, program, usage_extra);
exit(0);
+ case 'l':
+ list = optarg;
break;
case 'o':
single = 1;
case 's':
source = optarg;
break;
+ case 'v':
+ verbose = VERBOSE;
+ break;
default:
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, program, program, program, usage_extra);
exit(1);
}
- argc -= optind;
- argv += optind;
+ /*
+ * If C_TAP_VERBOSE is set in the environment, that also turns on verbose
+ * mode.
+ */
+ if (getenv("C_TAP_VERBOSE") != NULL)
+ verbose = VERBOSE;
+
+ /* 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, verbose) ? 0 : 1;
+ } else {
+ tests = build_test_list(argv, argc);
+ status = test_batch(tests, source, build, verbose) ? 0 : 1;
}
- /* For valgrind cleanliness. */
+ /* For valgrind cleanliness, free all our memory. */
if (source_env != NULL) {
putenv((char *) "SOURCE=");
free(source_env);
* number and some number of appropriate arguments, check to be sure the
* results match the expected output using the arguments, and print out
* something appropriate for that test number. Other utility routines help in
- * constructing more complex tests, skipping tests, or setting up the TAP
- * output format.
+ * constructing more complex tests, skipping tests, reporting errors, setting
+ * up the TAP output format, or finding things in the test environment.
*
* 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 Russ Allbery <eagle@eyrie.org>
- * Copyright 2001, 2002, 2004, 2005, 2006, 2007, 2008, 2011
+ * Copyright 2009, 2010, 2011, 2012, 2013, 2014, 2015
+ * 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
* DEALINGS IN THE SOFTWARE.
*/
-/* Required for isnan() and isinf(). */
-#ifndef _XOPEN_SOURCE
-# define _XOPEN_SOURCE 600
-#endif
-
#include <errno.h>
-#include <math.h>
+#include <limits.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
-#include <tap/basic.h>
+#include <tests/tap/basic.h>
/* Windows provides mkdir and rmdir under different names. */
#ifdef _WIN32
/*
* 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;
* 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 interleaved 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 size, 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) {
+ size = file->bufsize > INT_MAX ? INT_MAX : (int) file->bufsize;
+ if (fgets(file->buffer, size, 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.
+ * 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), 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;
+
+ /* Don't do anything further if we never planned a test. */
+ if (_planned == 0)
+ return;
- if (_planned == 0 && !_lazy)
+ /* 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) {
- 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");
+ }
}
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;
}
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');
}
/*
* 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;
}
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');
}
}
* 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;
}
* 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);
- }
- putchar('\n');
-}
-
-
-/*
- * Takes an expected double and a seen double and assumes the test passes if
- * those two numbers are within delta of each other.
- */
-void
-is_double(double wanted, double seen, double epsilon, const char *format, ...)
-{
- fflush(stderr);
- if ((isnan(wanted) && isnan(seen))
- || (isinf(wanted) && isinf(seen) && wanted == seen)
- || fabs(wanted - seen) <= epsilon)
- printf("ok %lu", testnum++);
- else {
- printf("# wanted: %g\n# seen: %g\n", wanted, seen);
- printf("not ok %lu", testnum++);
- _failed++;
- }
- if (format != NULL) {
- va_list args;
-
- va_start(args, format);
- print_desc(format, args);
- va_end(args);
- }
+ PRINT_DESC(" - ", format);
putchar('\n');
+ return success;
}
* 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;
}
{
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);
}
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;
+ }
}
}
+/*
+ * 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.
*/
}
+/*
+ * Copy up to n characters of a string, reporting a fatal error with bail on
+ * failure. Don't use the system strndup function, since it may not exist and
+ * the TAP library doesn't assume any portability support.
+ */
+char *
+bstrndup(const char *s, size_t n)
+{
+ const char *p;
+ char *copy;
+ size_t length;
+
+ /* Don't assume that the source string is nul-terminated. */
+ for (p = s; (size_t) (p - s) < n && *p != '\0'; p++)
+ ;
+ length = (size_t) (p - s);
+ copy = malloc(length + 1);
+ if (p == NULL)
+ sysbail("failed to strndup %lu bytes", (unsigned long) length);
+ memcpy(copy, s, length);
+ copy[length] = '\0';
+ return copy;
+}
+
+
/*
* Locate a test file. Given the partial path to a file, look under BUILD and
* then SOURCE for the file and return the full path to the file. Returns
* NULL if the file doesn't exist. A non-NULL return should be freed with
* 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;
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);
void
test_file_path_free(char *path)
{
- if (path != NULL)
- free(path);
+ free(path);
}
{
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);
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;
}
* 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 Russ Allbery <eagle@eyrie.org>
- * Copyright 2001, 2002, 2004, 2005, 2006, 2007, 2008, 2011
+ * Copyright 2009, 2010, 2011, 2012, 2013, 2014, 2015
+ * 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
#ifndef TAP_BASIC_H
#define TAP_BASIC_H 1
+#include <tests/tap/macros.h>
#include <stdarg.h> /* va_list */
-#include <sys/types.h> /* size_t */
-
-/*
- * __attribute__ is available in gcc 2.5 and later, but only with gcc 2.7
- * could you use the __format__ form of the attributes, which is what we use
- * (to avoid confusion with other macros).
- */
-#ifndef __attribute__
-# if __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 7)
-# define __attribute__(spec) /* empty */
-# endif
-#endif
-
-/*
- * BEGIN_DECLS is used at the beginning of declarations so that C++
- * compilers don't mangle their names. END_DECLS is used at the end.
- */
-#undef BEGIN_DECLS
-#undef END_DECLS
-#ifdef __cplusplus
-# define BEGIN_DECLS extern "C" {
-# define END_DECLS }
-#else
-# define BEGIN_DECLS /* empty */
-# define END_DECLS /* empty */
-#endif
+#include <stddef.h> /* size_t */
/*
* Used for iterating through arrays. ARRAY_SIZE returns the number of
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);
/*
* 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)
+ __attribute__((__format__(printf, 2, 0)));
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_double(double wanted, double seen, double epsilon,
- const char *format, ...)
- __attribute__((__format__(printf, 4, 5)));
-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). */
__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__, __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);
/*
* 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 arguments: 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 */
# Shell function library for test cases.
#
+# Note that while many of the functions in this library could benefit from
+# using "local" to avoid possibly hammering global variables, Solaris /bin/sh
+# doesn't support local and this library aspires to be portable to Solaris
+# Bourne shell. Instead, all private variables are prefixed with "tap_".
+#
# This file provides a TAP-compatible shell function library useful for
# writing test cases. It is part of C TAP Harness, which can be found at
# <http://www.eyrie.org/~eagle/software/c-tap-harness/>.
#
# Written by Russ Allbery <eagle@eyrie.org>
-# Copyright 2009, 2010, 2011 Russ Allbery <eagle@eyrie.org>
-# Copyright 2006, 2007, 2008
+# 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
# Report the test status on exit.
finish () {
- local highest looks
- highest=`expr "$count" - 1`
+ tap_highest=`expr "$count" - 1`
if [ "$planned" = 0 ] ; then
- echo "1..$highest"
- planned="$highest"
+ echo "1..$tap_highest"
+ planned="$tap_highest"
fi
- looks='# Looks like you'
+ tap_looks='# Looks like you'
if [ "$planned" -gt 0 ] ; then
- if [ "$planned" -gt "$highest" ] ; then
+ if [ "$planned" -gt "$tap_highest" ] ; then
if [ "$planned" -gt 1 ] ; then
- echo "$looks planned $planned tests but only ran $highest"
+ echo "$tap_looks planned $planned tests but only ran" \
+ "$tap_highest"
else
- echo "$looks planned $planned test but only ran $highest"
+ echo "$tap_looks planned $planned test but only ran" \
+ "$tap_highest"
fi
- elif [ "$planned" -lt "$highest" ] ; then
- local extra
- extra=`expr "$highest" - "$planned"`
+ elif [ "$planned" -lt "$tap_highest" ] ; then
+ tap_extra=`expr "$tap_highest" - "$planned"`
if [ "$planned" -gt 1 ] ; then
- echo "$looks planned $planned tests but ran $extra extra"
+ echo "$tap_looks planned $planned tests but ran" \
+ "$tap_extra extra"
else
- echo "$looks planned $planned test but ran $extra extra"
+ echo "$tap_looks planned $planned test but ran" \
+ "$tap_extra extra"
fi
elif [ "$failed" -gt 0 ] ; then
if [ "$failed" -gt 1 ] ; then
- echo "$looks failed $failed tests of $planned"
+ echo "$tap_looks failed $failed tests of $planned"
else
- echo "$looks failed $failed test of $planned"
+ echo "$tap_looks failed $failed test of $planned"
fi
elif [ "$planned" -gt 1 ] ; then
echo "# All $planned tests successful or skipped"
# Skip the entire test suite. Should be run instead of plan.
skip_all () {
- local desc
- desc="$1"
- if [ -n "$desc" ] ; then
- echo "1..0 # skip $desc"
+ tap_desc="$1"
+ if [ -n "$tap_desc" ] ; then
+ echo "1..0 # skip $tap_desc"
else
echo "1..0 # skip"
fi
# command is successful, false otherwise. The count starts at 1 and is
# updated each time ok is printed.
ok () {
- local desc
- desc="$1"
- if [ -n "$desc" ] ; then
- desc=" - $desc"
+ tap_desc="$1"
+ if [ -n "$tap_desc" ] ; then
+ tap_desc=" - $tap_desc"
fi
shift
if "$@" ; then
- echo ok $count$desc
+ echo ok "$count$tap_desc"
else
- echo not ok $count$desc
+ echo not ok "$count$tap_desc"
failed=`expr $failed + 1`
fi
count=`expr $count + 1`
# Report the same status on a whole set of tests. Takes the count of tests,
# the description, and then the command to run to determine the status.
ok_block () {
- local end i desc
- i=$count
- end=`expr $count + $1`
- shift
- desc="$1"
+ tap_i=$count
+ tap_end=`expr $count + $1`
shift
- while [ "$i" -lt "$end" ] ; do
- ok "$desc" "$@"
- i=`expr $i + 1`
+ while [ "$tap_i" -lt "$tap_end" ] ; do
+ ok "$@"
+ tap_i=`expr $tap_i + 1`
done
}
# Skip a whole set of tests. Takes the count and then the reason for skipping
# the test.
skip_block () {
- local i end
- i=$count
- end=`expr $count + $1`
+ tap_i=$count
+ tap_end=`expr $count + $1`
shift
- while [ "$i" -lt "$end" ] ; do
+ while [ "$tap_i" -lt "$tap_end" ] ; do
skip "$@"
- i=`expr $i + 1`
+ tap_i=`expr $tap_i + 1`
done
}
# Portable variant of printf '%s\n' "$*". In the majority of cases, this
# function is slower than printf, because the latter is often implemented
# as a builtin command. The value of the variable IFS is ignored.
+#
+# This macro must not be called via backticks inside double quotes, since this
+# will result in bizarre escaping behavior and lots of extra backslashes on
+# Solaris.
puts () {
cat << EOH
$@
# If the command may contain system-specific error messages in its output,
# add strip_colon_error before the command to post-process its output.
ok_program () {
- local desc w_status w_output output status
- desc="$1"
+ tap_desc="$1"
shift
- w_status="$1"
+ tap_w_status="$1"
shift
- w_output="$1"
+ tap_w_output="$1"
shift
- output=`"$@" 2>&1`
- status=$?
- if [ $status = $w_status ] && [ x"$output" = x"$w_output" ] ; then
- ok "$desc" true
+ tap_output=`"$@" 2>&1`
+ tap_status=$?
+ if [ $tap_status = $tap_w_status ] \
+ && [ x"$tap_output" = x"$tap_w_output" ] ; then
+ ok "$tap_desc" true
else
- echo "# saw: ($status) $output"
- echo "# not: ($w_status) $w_output"
- ok "$desc" false
+ echo "# saw: ($tap_status) $tap_output"
+ echo "# not: ($tap_w_status) $tap_w_output"
+ ok "$tap_desc" false
fi
}
# message.) This is used to remove system-specific error messages (coming
# from strerror, for example).
strip_colon_error() {
- local output status
- output=`"$@" 2>&1`
- status=$?
- output=`puts "$output" | sed 's/^\([^ ]* [^:]*\):.*/\1/'`
- puts "$output"
- return $status
+ tap_output=`"$@" 2>&1`
+ tap_status=$?
+ tap_output=`puts "$tap_output" | sed 's/^\([^ ]* [^:]*\):.*/\1/'`
+ puts "$tap_output"
+ return $tap_status
}
# Bail out with an error message.
bail () {
echo 'Bail out!' "$@"
- exit 1
+ exit 255
}
# Output a diagnostic on standard error, preceded by the required # mark.
# Search for the given file first in $BUILD and then in $SOURCE and echo the
# path where the file was found, or the empty string if the file wasn't
# found.
+#
+# This macro uses puts, so don't run it using backticks inside double quotes
+# or bizarre quoting behavior will happen with Solaris sh.
test_file_path () {
if [ -n "$BUILD" ] && [ -f "$BUILD/$1" ] ; then
puts "$BUILD/$1"
# Create $BUILD/tmp for use by tests for storing temporary files and return
# the path (via standard output).
+#
+# This macro uses puts, so don't run it using backticks inside double quotes
+# or bizarre quoting behavior will happen with Solaris sh.
test_tmpdir () {
- local tmpdir
if [ -z "$BUILD" ] ; then
- tmpdir="./tmp"
+ tap_tmpdir="./tmp"
else
- tmpdir="$BUILD"/tmp
+ tap_tmpdir="$BUILD"/tmp
fi
- if [ ! -d "$tmpdir" ] ; then
- mkdir "$tmpdir" || bail "Error creating $tmpdir"
+ if [ ! -d "$tap_tmpdir" ] ; then
+ mkdir "$tap_tmpdir" || bail "Error creating $tap_tmpdir"
fi
- puts "$tmpdir"
+ puts "$tap_tmpdir"
}
--- /dev/null
+/*
+ * Helpful macros for TAP header files.
+ *
+ * This is not, strictly speaking, related to TAP, but any TAP add-on is
+ * probably going to need these macros, so define them in one place so that
+ * everyone can pull them in.
+ *
+ * This file is part of C TAP Harness. The current version plus supporting
+ * documentation is at <http://www.eyrie.org/~eagle/software/c-tap-harness/>.
+ *
+ * Copyright 2008, 2012, 2013, 2015 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"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef TAP_MACROS_H
+#define TAP_MACROS_H 1
+
+/*
+ * __attribute__ is available in gcc 2.5 and later, but only with gcc 2.7
+ * could you use the __format__ form of the attributes, which is what we use
+ * (to avoid confusion with other macros), and only with gcc 2.96 can you use
+ * the attribute __malloc__. 2.96 is very old, so don't bother trying to get
+ * the other attributes to work with GCC versions between 2.7 and 2.96.
+ */
+#ifndef __attribute__
+# if __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 96)
+# define __attribute__(spec) /* empty */
+# endif
+#endif
+
+/*
+ * We use __alloc_size__, but it was only available in fairly recent versions
+ * of GCC. Suppress warnings about the unknown attribute if GCC is too old.
+ * We know that we're GCC at this point, so we can use the GCC variadic macro
+ * extension, which will still work with versions of GCC too old to have C99
+ * variadic macro support.
+ */
+#if !defined(__attribute__) && !defined(__alloc_size__)
+# if defined(__GNUC__) && !defined(__clang__)
+# if __GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 3)
+# define __alloc_size__(spec, args...) /* empty */
+# endif
+# 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
+ * attributes on declarations. This unfortunately will affect the entire
+ * compilation context, but there's no push and pop available.
+ */
+#if !defined(__attribute__) && (defined(__llvm__) || defined(__clang__))
+# pragma GCC diagnostic ignored "-Wattributes"
+#endif
+
+/* Used for unused parameters to silence gcc warnings. */
+#define UNUSED __attribute__((__unused__))
+
+/*
+ * BEGIN_DECLS is used at the beginning of declarations so that C++
+ * compilers don't mangle their names. END_DECLS is used at the end.
+ */
+#undef BEGIN_DECLS
+#undef END_DECLS
+#ifdef __cplusplus
+# define BEGIN_DECLS extern "C" {
+# define END_DECLS }
+#else
+# define BEGIN_DECLS /* empty */
+# define END_DECLS /* empty */
+#endif
+
+#endif /* TAP_MACROS_H */
--- /dev/null
+# Helper functions for test programs written in Perl.
+#
+# This module provides a collection of helper functions used by test programs
+# written in Perl. This is a general collection of functions that can be used
+# by both C packages with Automake and by stand-alone Perl modules. See
+# Test::RRA::Automake for additional functions specifically for C Automake
+# distributions.
+
+package Test::RRA;
+
+use 5.006;
+use strict;
+use warnings;
+
+use Exporter;
+use Test::More;
+
+# For Perl 5.006 compatibility.
+## no critic (ClassHierarchies::ProhibitExplicitISA)
+
+# Declare variables that should be set in BEGIN for robustness.
+our (@EXPORT_OK, @ISA, $VERSION);
+
+# Set $VERSION and everything export-related in a BEGIN block for robustness
+# against circular module loading (not that we load any modules, but
+# consistency is good).
+BEGIN {
+ @ISA = qw(Exporter);
+ @EXPORT_OK = qw(skip_unless_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 = '5.09';
+}
+
+# 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_author {
+ my ($description) = @_;
+ 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
+# the program.
+#
+# The special logic here is based on Test::More and is required to get the
+# imports to happen in the caller's namespace.
+#
+# $module - Name of the module to load
+# @imports - Any arguments to import, possibly including a version
+#
+# Returns: undef
+sub use_prereq {
+ my ($module, @imports) = @_;
+
+ # If the first import looks like a version, pass it as a bare string.
+ my $version = q{};
+ if (@imports >= 1 && $imports[0] =~ m{ \A \d+ (?: [.][\d_]+ )* \z }xms) {
+ $version = shift(@imports);
+ }
+
+ # Get caller information to put imports in the correct package.
+ my ($package) = caller;
+
+ # Do the import with eval, and try to isolate it from the surrounding
+ # context as much as possible. Based heavily on Test::More::_eval.
+ ## no critic (BuiltinFunctions::ProhibitStringyEval)
+ ## no critic (ValuesAndExpressions::ProhibitImplicitNewlines)
+ my ($result, $error, $sigdie);
+ {
+ local $@ = undef;
+ local $! = undef;
+ local $SIG{__DIE__} = undef;
+ $result = eval qq{
+ package $package;
+ use $module $version \@imports;
+ 1;
+ };
+ $error = $@;
+ $sigdie = $SIG{__DIE__} || undef;
+ }
+
+ # If the use failed for any reason, skip the test.
+ if (!$result || $error) {
+ my $name = length($version) > 0 ? "$module $version" : $module;
+ plan skip_all => "$name required for test";
+ }
+
+ # If the module set $SIG{__DIE__}, we cleared that via local. Restore it.
+ ## no critic (Variables::RequireLocalizedPunctuationVars)
+ if (defined($sigdie)) {
+ $SIG{__DIE__} = $sigdie;
+ }
+ return;
+}
+
+1;
+__END__
+
+=for stopwords
+Allbery Allbery's DESC bareword sublicense MERCHANTABILITY NONINFRINGEMENT
+rra-c-util
+
+=head1 NAME
+
+Test::RRA - Support functions for Perl tests
+
+=head1 SYNOPSIS
+
+ use Test::RRA
+ qw(skip_unless_author skip_unless_automated use_prereq);
+
+ # 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('Perl6::Slurp', 'slurp');
+ use_prereq('Test::Script::Run', '0.04');
+
+=head1 DESCRIPTION
+
+This module collects utility functions that are useful for Perl test
+scripts. It assumes Russ Allbery's Perl module layout and test
+conventions and will only be useful for other people if they use the
+same conventions.
+
+=head1 FUNCTIONS
+
+None of these functions are imported by default. The ones used by a
+script should be explicitly imported.
+
+=over 4
+
+=item skip_unless_author(DESC)
+
+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 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 ...])
+
+Attempts to load MODULE with the given VERSION and import arguments. If
+this fails for any reason, the test will be skipped (by calling C<plan
+skip_all> from Test::More) with a skip reason saying that MODULE is
+required for the test.
+
+VERSION will be passed to C<use> as a version bareword if it looks like a
+version number. The remaining IMPORT arguments will be passed as the
+value of an array.
+
+=back
+
+=head1 AUTHOR
+
+Russ Allbery <eagle@eyrie.org>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright 2013, 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.
+
+=head1 SEE ALSO
+
+Test::More(3), Test::RRA::Automake(3), Test::RRA::Config(3)
+
+This module is maintained in the rra-c-util package. The current version
+is available from L<http://www.eyrie.org/~eagle/software/rra-c-util/>.
+
+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
--- /dev/null
+# Helper functions for Perl test programs in Automake distributions.
+#
+# This module provides a collection of helper functions used by test programs
+# written in Perl and included in C source distributions that use Automake.
+# They embed knowledge of how I lay out my source trees and test suites with
+# Autoconf and Automake. They may be usable by others, but doing so will
+# require closely following the conventions implemented by the rra-c-util
+# utility collection.
+#
+# All the functions here assume that BUILD and SOURCE are set in the
+# environment. This is normally done via the C TAP Harness runtests wrapper.
+
+package Test::RRA::Automake;
+
+use 5.006;
+use strict;
+use warnings;
+
+# For Perl 5.006 compatibility.
+## no critic (ClassHierarchies::ProhibitExplicitISA)
+
+use Exporter;
+use File::Spec;
+use Test::More;
+use Test::RRA::Config qw($LIBRARY_PATH);
+
+# Used below for use lib calls.
+my ($PERL_BLIB_ARCH, $PERL_BLIB_LIB);
+
+# Determine the path to the build tree of any embedded Perl module package in
+# this source package. We do this in a BEGIN block because we're going to use
+# the results in a use lib command below.
+BEGIN {
+ $PERL_BLIB_ARCH = File::Spec->catdir(qw(perl blib arch));
+ $PERL_BLIB_LIB = File::Spec->catdir(qw(perl blib lib));
+
+ # If BUILD is set, we can come up with better values.
+ if (defined($ENV{BUILD})) {
+ my ($vol, $dirs) = File::Spec->splitpath($ENV{BUILD}, 1);
+ my @dirs = File::Spec->splitdir($dirs);
+ pop(@dirs);
+ $PERL_BLIB_ARCH = File::Spec->catdir(@dirs, qw(perl blib arch));
+ $PERL_BLIB_LIB = File::Spec->catdir(@dirs, qw(perl blib lib));
+ }
+}
+
+# Prefer the modules built as part of our source package. Otherwise, we may
+# not find Perl modules while testing, or find the wrong versions.
+use lib $PERL_BLIB_ARCH;
+use lib $PERL_BLIB_LIB;
+
+# Declare variables that should be set in BEGIN for robustness.
+our (@EXPORT_OK, @ISA, $VERSION);
+
+# Set $VERSION and everything export-related in a BEGIN block for robustness
+# against circular module loading (not that we load any modules, but
+# consistency is good).
+BEGIN {
+ @ISA = qw(Exporter);
+ @EXPORT_OK = qw(automake_setup perl_dirs test_file_path 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 = '5.09';
+}
+
+# Perl directories to skip globally for perl_dirs. We ignore the perl
+# directory if it exists since, in my packages, it is treated as a Perl module
+# distribution and has its own standalone test suite.
+my @GLOBAL_SKIP = qw(.git perl);
+
+# 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
+# configuration option is set. Calls BAIL_OUT if BUILD or SOURCE are missing
+# or if anything else fails.
+#
+# $args_ref - Reference to a hash of arguments to configure behavior:
+# chdir_build - If set to a true value, changes to BUILD instead of SOURCE
+#
+# Returns: undef
+sub automake_setup {
+ my ($args_ref) = @_;
+
+ # Bail if BUILD or SOURCE are not set.
+ if (!$ENV{BUILD}) {
+ BAIL_OUT('BUILD not defined (run under runtests)');
+ }
+ if (!$ENV{SOURCE}) {
+ BAIL_OUT('SOURCE not defined (run under runtests)');
+ }
+
+ # BUILD or SOURCE will be the test directory. Change to the parent.
+ my $start = $args_ref->{chdir_build} ? $ENV{BUILD} : $ENV{SOURCE};
+ my ($vol, $dirs) = File::Spec->splitpath($start, 1);
+ my @dirs = File::Spec->splitdir($dirs);
+ pop(@dirs);
+
+ # Simplify relative paths at the end of the directory.
+ my $ups = 0;
+ my $i = $#dirs;
+ while ($i > 2 && $dirs[$i] eq File::Spec->updir) {
+ $ups++;
+ $i--;
+ }
+ for (1 .. $ups) {
+ pop(@dirs);
+ pop(@dirs);
+ }
+ my $root = File::Spec->catpath($vol, File::Spec->catdir(@dirs), q{});
+ chdir($root) or BAIL_OUT("cannot chdir to $root: $!");
+
+ # If BUILD is a subdirectory of SOURCE, add it to the global ignore list.
+ my ($buildvol, $builddirs) = File::Spec->splitpath($ENV{BUILD}, 1);
+ my @builddirs = File::Spec->splitdir($builddirs);
+ pop(@builddirs);
+ if ($buildvol eq $vol && @builddirs == @dirs + 1) {
+ while (@dirs && $builddirs[0] eq $dirs[0]) {
+ shift(@builddirs);
+ shift(@dirs);
+ }
+ if (@builddirs == 1) {
+ push(@GLOBAL_SKIP, $builddirs[0]);
+ }
+ }
+
+ # Set LD_LIBRARY_PATH if the $LIBRARY_PATH configuration option is set.
+ ## no critic (Variables::RequireLocalizedPunctuationVars)
+ if (defined($LIBRARY_PATH)) {
+ @builddirs = File::Spec->splitdir($builddirs);
+ pop(@builddirs);
+ my $libdir = File::Spec->catdir(@builddirs, $LIBRARY_PATH);
+ my $path = File::Spec->catpath($buildvol, $libdir, q{});
+ if (-d "$path/.libs") {
+ $path .= '/.libs';
+ }
+ if ($ENV{LD_LIBRARY_PATH}) {
+ $ENV{LD_LIBRARY_PATH} .= ":$path";
+ } else {
+ $ENV{LD_LIBRARY_PATH} = $path;
+ }
+ }
+ return;
+}
+
+# Returns a list of directories that may contain Perl scripts and that should
+# be passed to Perl test infrastructure that expects a list of directories to
+# recursively check. The list will be all eligible top-level directories in
+# the package except for the tests directory, which is broken out to one
+# additional level. Calls BAIL_OUT on any problems
+#
+# $args_ref - Reference to a hash of arguments to configure behavior:
+# skip - A reference to an array of directories to skip
+#
+# Returns: List of directories possibly containing Perl scripts to test
+sub perl_dirs {
+ my ($args_ref) = @_;
+
+ # Add the global skip list.
+ my @skip = $args_ref->{skip} ? @{ $args_ref->{skip} } : ();
+ push(@skip, @GLOBAL_SKIP);
+
+ # Separate directories to skip under tests from top-level directories.
+ my @skip_tests = grep { m{ \A tests/ }xms } @skip;
+ @skip = grep { !m{ \A tests }xms } @skip;
+ for my $skip_dir (@skip_tests) {
+ $skip_dir =~ s{ \A tests/ }{}xms;
+ }
+
+ # Convert the skip lists into hashes for convenience.
+ my %skip = map { $_ => 1 } @skip, 'tests';
+ my %skip_tests = map { $_ => 1 } @skip_tests;
+
+ # Build the list of top-level directories to test.
+ opendir(my $rootdir, q{.}) or BAIL_OUT("cannot open .: $!");
+ my @dirs = grep { -d && !$skip{$_} } readdir($rootdir);
+ closedir($rootdir);
+ @dirs = File::Spec->no_upwards(@dirs);
+
+ # Add the list of subdirectories of the tests directory.
+ if (-d 'tests') {
+ opendir(my $testsdir, q{tests}) or BAIL_OUT("cannot open tests: $!");
+
+ # Skip if found in %skip_tests or if not a directory.
+ my $is_skipped = sub {
+ my ($dir) = @_;
+ return 1 if $skip_tests{$dir};
+ $dir = File::Spec->catdir('tests', $dir);
+ return -d $dir ? 0 : 1;
+ };
+
+ # Build the filtered list of subdirectories of tests.
+ my @test_dirs = grep { !$is_skipped->($_) } readdir($testsdir);
+ closedir($testsdir);
+ @test_dirs = File::Spec->no_upwards(@test_dirs);
+
+ # Add the tests directory to the start of the directory name.
+ push(@dirs, map { File::Spec->catdir('tests', $_) } @test_dirs);
+ }
+ return @dirs;
+}
+
+# Find a configuration file for the test suite. Searches relative to BUILD
+# first and then SOURCE and returns whichever is found first. Calls BAIL_OUT
+# if the file could not be found.
+#
+# $file - Partial path to the file
+#
+# Returns: Full path to the file
+sub test_file_path {
+ my ($file) = @_;
+ BASE:
+ for my $base ($ENV{BUILD}, $ENV{SOURCE}) {
+ next if !defined($base);
+ if (-f "$base/$file") {
+ return "$base/$file";
+ }
+ }
+ BAIL_OUT("cannot find $file");
+ return;
+}
+
+# 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 umask
+
+=head1 NAME
+
+Test::RRA::Automake - Automake-aware support functions for Perl tests
+
+=head1 SYNOPSIS
+
+ use Test::RRA::Automake qw(automake_setup perl_dirs test_file_path);
+ automake_setup({ chdir_build => 1 });
+
+ # Paths to directories that may contain Perl scripts.
+ my @dirs = perl_dirs({ skip => [qw(lib)] });
+
+ # Configuration for Kerberos tests.
+ my $keytab = test_file_path('config/keytab');
+
+=head1 DESCRIPTION
+
+This module collects utility functions that are useful for test scripts
+written in Perl and included in a C Automake-based package. They assume
+the layout of a package that uses rra-c-util and C TAP Harness for the
+test structure.
+
+Loading this module will also add the directories C<perl/blib/arch> and
+C<perl/blib/lib> to the Perl library search path, relative to BUILD if
+that environment variable is set. This is harmless for C Automake
+projects that don't contain an embedded Perl module, and for those
+projects that do, this will allow subsequent C<use> calls to find modules
+that are built as part of the package build process.
+
+The automake_setup() function should be called before calling any other
+functions provided by this module.
+
+=head1 FUNCTIONS
+
+None of these functions are imported by default. The ones used by a
+script should be explicitly imported. On failure, all of these functions
+call BAIL_OUT (from Test::More).
+
+=over 4
+
+=item automake_setup([ARGS])
+
+Verifies that the BUILD and SOURCE environment variables are set and
+then changes directory to the top of the source tree (which is one
+directory up from the SOURCE path, since SOURCE points to the top of
+the tests directory).
+
+If ARGS is given, it should be a reference to a hash of configuration
+options. Only one option is supported: C<chdir_build>. If it is set
+to a true value, automake_setup() changes directories to the top of
+the build tree instead.
+
+=item perl_dirs([ARGS])
+
+Returns a list of directories that may contain Perl scripts that should be
+tested by test scripts that test all Perl in the source tree (such as
+syntax or coding style checks). The paths will be simple directory names
+relative to the current directory or two-part directory names under the
+F<tests> directory. (Directories under F<tests> are broken out separately
+since it's common to want to apply different policies to different
+subdirectories of F<tests>.)
+
+If ARGS is given, it should be a reference to a hash of configuration
+options. Only one option is supported: C<skip>, whose value should be a
+reference to an array of additional top-level directories or directories
+starting with C<tests/> that should be skipped.
+
+=item test_file_path(FILE)
+
+Given FILE, which should be a relative path, locates that file relative to
+the test directory in either the source or build tree. FILE will be
+checked for relative to the environment variable BUILD first, and then
+relative to SOURCE. test_file_path() returns the full path to FILE or
+calls BAIL_OUT if FILE could not be found.
+
+=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 <eagle@eyrie.org>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright 2014 Russ Allbery <eagle@eyrie.org>
+
+Copyright 2013 The Board of Trustees of the Leland Stanford Junior
+University
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the "Software"),
+to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
+
+=head1 SEE ALSO
+
+Test::More(3), Test::RRA(3), Test::RRA::Config(3)
+
+The C TAP Harness test driver and libraries for TAP-based C testing are
+available from L<http://www.eyrie.org/~eagle/software/c-tap-harness/>.
+
+This module is maintained in the rra-c-util package. The current version
+is available from L<http://www.eyrie.org/~eagle/software/rra-c-util/>.
+
+=cut
--- /dev/null
+# Configuration for Perl test cases.
+#
+# In order to reuse the same Perl test cases in multiple packages, I use a
+# configuration file to store some package-specific data. This module loads
+# that configuration and provides the namespace for the configuration
+# settings.
+
+package Test::RRA::Config;
+
+use 5.006;
+use strict;
+use warnings;
+
+# For Perl 5.006 compatibility.
+## no critic (ClassHierarchies::ProhibitExplicitISA)
+
+use Exporter;
+use Test::More;
+
+# Declare variables that should be set in BEGIN for robustness.
+our (@EXPORT_OK, @ISA, $VERSION);
+
+# Set $VERSION and everything export-related in a BEGIN block for robustness
+# against circular module loading (not that we load any modules, but
+# consistency is good).
+BEGIN {
+ @ISA = qw(Exporter);
+ @EXPORT_OK = qw(
+ $COVERAGE_LEVEL @COVERAGE_SKIP_TESTS @CRITIC_IGNORE $LIBRARY_PATH
+ $MINIMUM_VERSION %MINIMUM_VERSION @POD_COVERAGE_EXCLUDE @STRICT_IGNORE
+ @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 = '5.09';
+}
+
+# If BUILD or SOURCE are set in the environment, look for data/perl.conf under
+# those paths for a C Automake package. Otherwise, look in t/data/perl.conf
+# for a standalone Perl module. Don't use Test::RRA::Automake since it may
+# not exist.
+our $PATH;
+for my $base ($ENV{BUILD}, $ENV{SOURCE}, 't') {
+ next if !defined($base);
+ my $path = "$base/data/perl.conf";
+ if (-r $path) {
+ $PATH = $path;
+ last;
+ }
+}
+if (!defined($PATH)) {
+ BAIL_OUT('cannot find data/perl.conf');
+}
+
+# Pre-declare all of our variables and set any defaults.
+our $COVERAGE_LEVEL = 100;
+our @COVERAGE_SKIP_TESTS;
+our @CRITIC_IGNORE;
+our $LIBRARY_PATH;
+our $MINIMUM_VERSION = '5.008';
+our %MINIMUM_VERSION;
+our @POD_COVERAGE_EXCLUDE;
+our @STRICT_IGNORE;
+our @STRICT_PREREQ;
+
+# Load the configuration.
+if (!do($PATH)) {
+ my $error = $@ || $! || 'loading file did not return true';
+ BAIL_OUT("cannot load data/perl.conf: $error");
+}
+
+1;
+__END__
+
+=for stopwords
+Allbery rra-c-util Automake perlcritic .libs namespace subdirectory
+sublicense MERCHANTABILITY NONINFRINGEMENT
+
+=head1 NAME
+
+Test::RRA::Config - Perl test configuration
+
+=head1 SYNOPSIS
+
+ use Test::RRA::Config qw($MINIMUM_VERSION);
+ print "Required Perl version is $MINIMUM_VERSION\n";
+
+=head1 DESCRIPTION
+
+Test::RRA::Config encapsulates per-package configuration for generic Perl
+test programs that are shared between multiple packages using the
+rra-c-util infrastructure. It handles locating and loading the test
+configuration file for both C Automake packages and stand-alone Perl
+modules.
+
+Test::RRA::Config looks for a file named F<data/perl.conf> relative to the
+root of the test directory. That root is taken from the environment
+variables BUILD or SOURCE (in that order) if set, which will be the case
+for C Automake packages using C TAP Harness. If neither is set, it
+expects the root of the test directory to be a directory named F<t>
+relative to the current directory, which will be the case for stand-alone
+Perl modules.
+
+The following variables are supported:
+
+=over 4
+
+=item $COVERAGE_LEVEL
+
+The coverage level achieved by the test suite for Perl test coverage
+testing using Test::Strict, as a percentage. The test will fail if test
+coverage less than this percentage is achieved. If not given, defaults
+to 100.
+
+=item @COVERAGE_SKIP_TESTS
+
+Directories under F<t> whose tests should be skipped when doing coverage
+testing. This can be tests that won't contribute to coverage or tests
+that don't run properly under Devel::Cover for some reason (such as ones
+that use taint checking). F<docs> and F<style> will always be skipped
+regardless of this setting.
+
+=item @CRITIC_IGNORE
+
+Additional directories to ignore when doing recursive perlcritic testing.
+The contents of this directory must be either top-level directory names or
+directory names starting with F<tests/>.
+
+=item $LIBRARY_PATH
+
+Add this directory (or a F<.libs> subdirectory) relative to the top of the
+source tree to LD_LIBRARY_PATH when checking the syntax of Perl modules.
+This may be required to pick up libraries that are used by in-tree Perl
+modules so that Perl scripts can pass a syntax check.
+
+=item $MINIMUM_VERSION
+
+Default minimum version requirement for included Perl scripts. If not
+given, defaults to 5.008.
+
+=item %MINIMUM_VERSION
+
+Minimum version exceptions for specific directories. The keys should be
+minimum versions of Perl to enforce. The value for each key should be a
+reference to an array of either top-level directory names or directory
+names starting with F<tests/>. All files in those directories will have
+that minimum Perl version constraint imposed instead of $MINIMUM_VERSION.
+
+=item @POD_COVERAGE_EXCLUDE
+
+Regexes that match method names that should be excluded from POD coverage
+testing. Normally, all methods have to be documented in the POD for a
+Perl module, but methods matching any of these regexes will be considered
+private and won't require documentation.
+
+=item @STRICT_IGNORE
+
+Additional directories to ignore when doing recursive Test::Strict testing
+for C<use strict> and C<use warnings>. The contents of this directory
+must be either top-level directory names or directory names starting with
+F<tests/>.
+
+=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
+into the local namespace to avoid long variable names.
+
+=head1 AUTHOR
+
+Russ Allbery <eagle@eyrie.org>
+
+=head1 COPYRIGHT AND LICENSE
+
+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
+copy of this software and associated documentation files (the "Software"),
+to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
+
+=head1 SEE ALSO
+
+perlcritic(1), Test::MinimumVersion(3), Test::RRA(3),
+Test::RRA::Automake(3), Test::Strict(3)
+
+This module is maintained in the rra-c-util package. The current version
+is available from L<http://www.eyrie.org/~eagle/software/rra-c-util/>.
+
+The C TAP Harness test driver and libraries for TAP-based C testing are
+available from L<http://www.eyrie.org/~eagle/software/c-tap-harness/>.
+
+=cut
* 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 <eagle@eyrie.org>
- * Copyright 2002, 2004, 2005 Russ Allbery <eagle@eyrie.org>
- * Copyright 2009, 2010, 2011
+ * 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
#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>
+#ifdef HAVE_SYS_TIME_H
+# include <sys/time.h>
+#endif
#include <sys/wait.h>
+#include <time.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
buf[count < 0 ? 0 : count] = '\0';
if (waitpid(child, &rval, 0) == (pid_t) -1)
sysbail("waitpid failed");
+ close(fds[0]);
}
/* Store the output and return. */
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;
+
+ /* Do nothing if called with a NULL argument. */
+ if (process == NULL)
+ return;
+
+ /* 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);
+}
* which can be found at <http://www.eyrie.org/~eagle/software/rra-c-util/>.
*
* Written by Russ Allbery <eagle@eyrie.org>
- * Copyright 2009, 2010
+ * 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
#define TAP_PROCESS_H 1
#include <config.h>
-#include <portable/macros.h>
+#include <tests/tap/macros.h>
+
+/* Opaque data type for process_start and friends. */
+struct process;
BEGIN_DECLS
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 */
* 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 Russ Allbery <eagle@eyrie.org>
+ * 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"),
* vsprintf into a newly allocated string, reporting a fatal error with bail
* on failure.
*/
-int
+void
bvasprintf(char **strp, const char *fmt, va_list args)
{
int status;
status = vasprintf(strp, fmt, args);
if (status < 0)
sysbail("failed to allocate memory for vasprintf");
- return status;
}
* sprintf into a newly allocated string, reporting a fatal error with bail on
* failure.
*/
-int
+void
basprintf(char **strp, const char *fmt, ...)
{
va_list args;
- int status;
va_start(args, fmt);
- status = bvasprintf(strp, fmt, args);
+ bvasprintf(strp, fmt, args);
va_end(args);
- return status;
}
* 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 Russ Allbery <eagle@eyrie.org>
+ * 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"),
#define TAP_STRING_H 1
#include <config.h>
-#include <portable/macros.h>
+#include <tests/tap/macros.h>
#include <stdarg.h> /* va_list */
BEGIN_DECLS
-/* sprintf into an allocated string, calling bail on allocation failure. */
-int basprintf(char **, const char *, ...)
+/* sprintf into an allocated string, calling bail on any failure. */
+void basprintf(char **, const char *, ...)
__attribute__((__nonnull__, __format__(printf, 2, 3)));
-int bvasprintf(char **, const char *, va_list)
- __attribute__((__nonnull__));
+void bvasprintf(char **, const char *, va_list)
+ __attribute__((__nonnull__, __format__(printf, 2, 0)));
END_DECLS
+++ /dev/null
-/*
- * concat test suite.
- *
- * 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>
- *
- * 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
- * international treaty, and hereby commit to the public, at large, that they
- * shall not, at any time in the future, seek to enforce any copyright in this
- * work against any person or entity, or prevent any person or entity from
- * copying, publishing, distributing or creating derivative works of this
- * work.
- */
-
-#include <config.h>
-#include <portable/system.h>
-
-#include <tests/tap/basic.h>
-#include <util/concat.h>
-
-#define END (char *) 0
-
-/*
- * Memory leaks everywhere! Whoo-hoo!
- */
-int
-main(void)
-{
- plan(13);
-
- is_string("a", concat("a", END), "concat 1");
- is_string("ab", concat("a", "b", END), "concat 2");
- is_string("ab", concat("ab", "", END), "concat 3");
- is_string("ab", concat("", "ab", END), "concat 4");
- is_string("", concat("", END), "concat 5");
- is_string("abcde", concat("ab", "c", "", "de", END), "concat 6");
- is_string("abcde", concat("abc", "de", END, "f", END), "concat 7");
-
- is_string("/foo", concatpath("/bar", "/foo"), "path 1");
- is_string("/foo/bar", concatpath("/foo", "bar"), "path 2");
- is_string("./bar", concatpath("/foo", "./bar"), "path 3");
- is_string("/bar/baz/foo/bar", concatpath("/bar/baz", "foo/bar"), "path 4");
- is_string("./foo", concatpath(NULL, "foo"), "path 5");
- is_string("/foo/bar", concatpath(NULL, "/foo/bar"), "path 6");
-
- return 0;
-}
* which can be found at <http://www.eyrie.org/~eagle/software/rra-c-util/>.
*
* Written by Russ Allbery <eagle@eyrie.org>
- * Copyright 2010, 2011
+ * Copyright 2010, 2011, 2013, 2014
* The Board of Trustees of the Leland Stanford Junior University
*
* Permission is hereby granted, free of charge, to any person obtaining a
*/
#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.
*/
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 */
* which can be found at <http://www.eyrie.org/~eagle/software/rra-c-util/>.
*
* Written by Russ Allbery <eagle@eyrie.org>
- * Copyright 2002, 2004, 2005 Russ Allbery <eagle@eyrie.org>
- * Copyright 2009, 2010, 2011
+ * Copyright 2002, 2004, 2005, 2015 Russ Allbery <eagle@eyrie.org>
+ * Copyright 2009, 2010, 2011, 2012
* The Board of Trustees of the Leland Stanford Junior University
*
* Permission is hereby granted, free of charge, to any person obtaining a
#include <tests/tap/basic.h>
#include <tests/tap/process.h>
-#include <util/concat.h>
#include <util/macros.h>
#include <util/messages.h>
#include <util/xmalloc.h>
sysdie("fatal");
}
-static void log_msg(size_t len, const char *format, va_list args, int error) {
+static void __attribute__((__format__(printf, 2, 0)))
+log_msg(size_t len, const char *format, va_list args, int error) {
fprintf(stderr, "%lu %d ", (unsigned long) len, error);
vfprintf(stderr, format, args);
fprintf(stderr, "\n");
{
char *full_output, *name;
- full_output = concat(output, ": ", strerror(error), "\n", (char *) NULL);
+ xasprintf(&full_output, "%s: %s\n", output, strerror(error));
xasprintf(&name, "strerror %lu", testnum / 3 + 1);
is_function_output(function, NULL, status, full_output, "%s", name);
free(full_output);
# which can be found at <http://www.eyrie.org/~eagle/software/rra-c-util/>.
#
# Written by Russ Allbery <eagle@eyrie.org>
-# Copyright 2000, 2001, 2006 Russ Allbery <eagle@eyrie.org>
-# Copyright 2008, 2009, 2010
+# 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
#
# Permission is hereby granted, free of charge, to any person obtaining a
shift
w_output="$1"
shift
- output=`./xmalloc "$@" 2>&1`
+ output=`strip_colon_error ./xmalloc "$@" 2>&1`
status=$?
- if [ "$w_status" -ne 0 ] ; then
- output=`echo "$output" | sed 's/:.*//'`
- fi
if [ $status = $w_status ] && [ x"$output" = x"$w_output" ] ; then
ok "$desc" true
elif [ $status = 2 ] ; then
+ diag "$output"
skip "no data limit support"
else
- echo "# saw: ($status) $output"
- echo "# not: ($w_status) $w_output"
+ diag "saw: ($status) $output"
+ diag "not: ($w_status) $w_output"
ok "$desc" false
fi
}
# 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" "3500000" "0"
-ok_xmalloc "malloc zero" 0 "" "m" "0" "0"
-ok_xmalloc "realloc small" 0 "" "r" "21" "0"
-ok_xmalloc "realloc large" 0 "" "r" "3500000" "0"
-ok_xmalloc "strdup small" 0 "" "s" "21" "0"
-ok_xmalloc "strdup large" 0 "" "s" "3500000" "0"
-ok_xmalloc "strndup small" 0 "" "n" "21" "0"
-ok_xmalloc "strndup large" 0 "" "n" "3500000" "0"
-ok_xmalloc "calloc small" 0 "" "c" "24" "0"
-ok_xmalloc "calloc large" 0 "" "c" "3500000" "0"
-ok_xmalloc "asprintf small" 0 "" "a" "24" "0"
-ok_xmalloc "asprintf large" 0 "" "a" "3500000" "0"
-ok_xmalloc "vasprintf small" 0 "" "v" "24" "0"
-ok_xmalloc "vasprintf large" 0 "" "v" "3500000" "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 3.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
# be large enough to allow the program to be loaded and do small allocations,
# but not so large that we can't reasonably expect to allocate that much
-# memory normally. 3.5MB seems to work reasonably well on both Solaris and
-# Linux.
+# 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. 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 3500000 bytes at xmalloc.c line 38" \
- "m" "3500000" "3500000"
+ "failed to malloc 30000000 bytes at xmalloc.c line 41" \
+ "m" "30000000" "30000000"
ok_xmalloc "realloc fail" 1 \
- "failed to realloc 3500000 bytes at xmalloc.c line 66" \
- "r" "3500000" "3500000"
+ "failed to realloc 30000000 bytes at xmalloc.c line 69" \
+ "r" "30000000" "30000000"
+ok_xmalloc "reallocarray fail" 1 \
+ "failed to reallocarray 30000000 bytes at xmalloc.c line 99" \
+ "y" "30000000" "30000000"
ok_xmalloc "strdup fail" 1 \
- "failed to strdup 3500000 bytes at xmalloc.c line 97" \
- "s" "3500000" "3500000"
+ "failed to strdup 30000000 bytes at xmalloc.c line 130" \
+ "s" "30000000" "30000000"
ok_xmalloc "strndup fail" 1 \
- "failed to strndup 3500000 bytes at xmalloc.c line 124" \
- "n" "3500000" "3500000"
+ "failed to strndup 30000000 bytes at xmalloc.c line 176" \
+ "n" "30000000" "30000000"
ok_xmalloc "calloc fail" 1 \
- "failed to calloc 3500000 bytes at xmalloc.c line 148" \
- "c" "3500000" "3500000"
+ "failed to calloc 30000000 bytes at xmalloc.c line 200" \
+ "c" "30000000" "30000000"
ok_xmalloc "asprintf fail" 1 \
- "failed to asprintf 3500000 bytes at xmalloc.c line 173" \
- "a" "3500000" "3500000"
+ "failed to asprintf 30000000 bytes at xmalloc.c line 224" \
+ "a" "30000000" "30000000"
ok_xmalloc "vasprintf fail" 1 \
- "failed to vasprintf 3500000 bytes at xmalloc.c line 195" \
- "v" "3500000" "3500000"
+ "failed to vasprintf 30000000 bytes at xmalloc.c line 243" \
+ "v" "30000000" "30000000"
# Check our custom error handler.
-ok_xmalloc "malloc custom" 1 "malloc 3500000 xmalloc.c 38" \
- "M" "3500000" "3500000"
-ok_xmalloc "realloc custom" 1 "realloc 3500000 xmalloc.c 66" \
- "R" "3500000" "3500000"
-ok_xmalloc "strdup custom" 1 "strdup 3500000 xmalloc.c 97" \
- "S" "3500000" "3500000"
-ok_xmalloc "strndup custom" 1 "strndup 3500000 xmalloc.c 124" \
- "N" "3500000" "3500000"
-ok_xmalloc "calloc custom" 1 "calloc 3500000 xmalloc.c 148" \
- "C" "3500000" "3500000"
-ok_xmalloc "asprintf custom" 1 "asprintf 3500000 xmalloc.c 173" \
- "A" "3500000" "3500000"
-ok_xmalloc "vasprintf custom" 1 "vasprintf 3500000 xmalloc.c 195" \
- "V" "3500000" "3500000"
+ok_xmalloc "malloc custom" 1 "malloc 30000000 xmalloc.c 41" \
+ "M" "30000000" "30000000"
+ok_xmalloc "realloc custom" 1 "realloc 30000000 xmalloc.c 69" \
+ "R" "30000000" "30000000"
+ok_xmalloc "reallocarray custom" 1 "reallocarray 30000000 xmalloc.c 99" \
+ "Y" "30000000" "30000000"
+ok_xmalloc "strdup custom" 1 "strdup 30000000 xmalloc.c 130" \
+ "S" "30000000" "30000000"
+ok_xmalloc "strndup custom" 1 "strndup 30000000 xmalloc.c 176" \
+ "N" "30000000" "30000000"
+ok_xmalloc "calloc custom" 1 "calloc 30000000 xmalloc.c 200" \
+ "C" "30000000" "30000000"
+ok_xmalloc "asprintf custom" 1 "asprintf 30000000 xmalloc.c 224" \
+ "A" "30000000" "30000000"
+ok_xmalloc "vasprintf custom" 1 "vasprintf 30000000 xmalloc.c 243" \
+ "V" "30000000" "30000000"
# Check the smaller ones again just for grins.
-ok_xmalloc "malloc retry" 0 "" "m" "21" "3500000"
-ok_xmalloc "realloc retry" 0 "" "r" "32" "3500000"
-ok_xmalloc "strdup retry" 0 "" "s" "64" "3500000"
-ok_xmalloc "strndup retry" 0 "" "n" "20" "3500000"
-ok_xmalloc "calloc retry" 0 "" "c" "24" "3500000"
-ok_xmalloc "asprintf retry" 0 "" "a" "30" "3500000"
-ok_xmalloc "vasprintf retry" 0 "" "v" "35" "3500000"
+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"
* which can be found at <http://www.eyrie.org/~eagle/software/rra-c-util/>.
*
* Copyright 2000, 2001, 2006 Russ Allbery <eagle@eyrie.org>
- * Copyright 2008
+ * 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
#include <ctype.h>
#include <errno.h>
-#include <sys/time.h>
+#ifdef HAVE_SYS_TIME_H
+# include <sys/time.h>
+#endif
+#include <time.h>
/* Linux requires sys/time.h be included before sys/resource.h. */
#include <sys/resource.h>
}
+/*
+ * 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.
/*
* Generate a string of the size indicated plus some, call xstrndup on it, and
- * then ensure the result matches. Returns true on success, false on any
- * failure.
+ * then ensure the result matches. Also test xstrdup on a string that's
+ * shorter than the specified size and ensure that we don't copy too much, and
+ * on a string that's not nul-terminated. Returns true on success, false on
+ * any failure.
*/
static int
test_strndup(size_t size)
{
char *string, *copy;
- int match, toomuch;
+ int shortmatch, nonulmatch, match, toomuch;
+
+ /* Copy a short string. */
+ string = xmalloc(5);
+ memcpy(string, "test", 5);
+ copy = xstrndup(string, size);
+ shortmatch = strcmp(string, copy);
+ free(string);
+ free(copy);
+
+ /* Copy a string that's not nul-terminated. */
+ string = xmalloc(4);
+ memcpy(string, "test", 4);
+ copy = xstrndup(string, 4);
+ nonulmatch = strcmp(copy, "test");
+ free(string);
+ free(copy);
+ /* Now the test of running out of memory. */
string = xmalloc(size + 1);
if (string == NULL)
return 0;
toomuch = strcmp(string, copy);
free(string);
free(copy);
- return (match == 0 && toomuch != 0);
+ return (shortmatch == 0 && nonulmatch == 0 && match == 0 && toomuch != 0);
}
test_asprintf(size_t size)
{
char *copy, *string;
- int status;
size_t i;
string = xmalloc(size);
memset(string, 42, size - 1);
string[size - 1] = '\0';
- status = xasprintf(©, "%s", string);
+ xasprintf(©, "%s", string);
free(string);
- if (status < 0)
- return 0;
for (i = 0; i < size - 1; i++)
if (copy[i] != 42)
return 0;
/* Wrapper around vasprintf to do the va_list stuff. */
-static int
+static void __attribute__((__format__(printf, 2, 3)))
xvasprintf_wrapper(char **strp, const char *format, ...)
{
va_list args;
- int status;
va_start(args, format);
- status = xvasprintf(strp, format, args);
+ xvasprintf(strp, format, args);
va_end(args);
- return status;
}
test_vasprintf(size_t size)
{
char *copy, *string;
- int status;
size_t i;
string = xmalloc(size);
memset(string, 42, size - 1);
string[size - 1] = '\0';
- status = xvasprintf_wrapper(©, "%s", string);
+ xvasprintf_wrapper(©, "%s", string);
free(string);
- if (status < 0)
- return 0;
for (i = 0; i < size - 1; i++)
if (copy[i] != 42)
return 0;
#if HAVE_SETRLIMIT && defined(RLIMIT_AS)
struct rlimit rl;
void *tmp;
+ size_t test_size;
rl.rlim_cur = limit;
rl.rlim_max = limit;
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",
- (unsigned long) size);
+ syswarn("Can't allocate initial memory of %lu (limit %lu)",
+ (unsigned long) test_size, (unsigned long) limit);
exit(2);
}
free(tmp);
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);
+++ /dev/null
-/*
- * Concatenate strings with dynamic memory allocation.
- *
- * Usage:
- *
- * string = concat(string1, string2, ..., (char *) 0);
- * path = concatpath(base, name);
- *
- * Dynamically allocates (using xmalloc) sufficient memory to hold all of the
- * strings given and then concatenates them together into that allocated
- * memory, returning a pointer to it. Caller is responsible for freeing.
- * Assumes xmalloc is available. The last argument must be a null pointer (to
- * a char *, if you actually find a platform where it matters).
- *
- * concatpath is similar, except that it only takes two arguments. If the
- * second argument begins with / or ./, a copy of it is returned; otherwise,
- * the first argument, a slash, and the second argument are concatenated
- * together and returned. This is useful for building file names where names
- * that aren't fully qualified are qualified with some particular directory.
- *
- * 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>
- *
- * 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
- * international treaty, and hereby commit to the public, at large, that they
- * shall not, at any time in the future, seek to enforce any copyright in this
- * work against any person or entity, or prevent any person or entity from
- * copying, publishing, distributing or creating derivative works of this
- * work.
- */
-
-#include <config.h>
-#include <portable/system.h>
-
-#include <util/concat.h>
-#include <util/xmalloc.h>
-
-/* Abbreviation for cleaner code. */
-#define VA_NEXT(var, type) ((var) = (type) va_arg(args, type))
-
-
-/*
- * Concatenate all of the arguments into a newly allocated string. ANSI C
- * requires at least one named parameter, but it's not treated any different
- * than the rest.
- */
-char *
-concat(const char *first, ...)
-{
- va_list args;
- char *result, *p;
- const char *string;
- size_t length = 0;
-
- /* Find the total memory required. */
- va_start(args, first);
- for (string = first; string != NULL; VA_NEXT(string, const char *))
- length += strlen(string);
- va_end(args);
- length++;
-
- /*
- * Create the string. Doing the copy ourselves avoids useless string
- * traversals of result, if using strcat, or string, if using strlen to
- * increment a pointer into result, at the cost of losing the native
- * optimization of strcat if any.
- */
- result = xmalloc(length);
- p = result;
- va_start(args, first);
- for (string = first; string != NULL; VA_NEXT(string, const char *))
- while (*string != '\0')
- *p++ = *string++;
- va_end(args);
- *p = '\0';
-
- return result;
-}
-
-
-/*
- * Concatenate name with base, unless name begins with / or ./. Return the
- * new string in newly allocated memory.
- */
-char *
-concatpath(const char *base, const char *name)
-{
- if (name[0] == '/' || (name[0] == '.' && name[1] == '/'))
- return xstrdup(name);
- else
- return concat(base != NULL ? base : ".", "/", name, (char *) 0);
-}
+++ /dev/null
-/*
- * Prototypes for string concatenation with dynamic memory allocation.
- *
- * 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>
- *
- * 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
- * international treaty, and hereby commit to the public, at large, that they
- * shall not, at any time in the future, seek to enforce any copyright in this
- * work against any person or entity, or prevent any person or entity from
- * copying, publishing, distributing or creating derivative works of this
- * work.
- */
-
-#ifndef UTIL_CONCAT_H
-#define UTIL_CONCAT_H 1
-
-#include <config.h>
-#include <portable/macros.h>
-
-BEGIN_DECLS
-
-/* Default to a hidden visibility for all util functions. */
-#pragma GCC visibility push(hidden)
-
-/* Concatenate NULL-terminated strings into a newly allocated string. */
-char *concat(const char *first, ...)
- __attribute__((__malloc__, __nonnull__(1)));
-
-/*
- * Given a base path and a file name, create a newly allocated path string.
- * The name will be appended to base with a / between them. Exceptionally, if
- * name begins with a slash, it will be strdup'd and returned as-is.
- */
-char *concatpath(const char *base, const char *name)
- __attribute__((__malloc__, __nonnull__(2)));
-
-/* Undo default visibility change. */
-#pragma GCC visibility pop
-
-END_DECLS
-
-#endif /* UTIL_CONCAT_H */
#define ARRAY_SIZE(array) (sizeof(array) / sizeof((array)[0]))
#define ARRAY_END(array) (&(array)[ARRAY_SIZE(array)])
+/* Used to name the elements of the array passed to pipe. */
+#define PIPE_READ 0
+#define PIPE_WRITE 1
+
/* Used for unused parameters to silence gcc warnings. */
#define UNUSED __attribute__((__unused__))
* which can be found at <http://www.eyrie.org/~eagle/software/rra-c-util/>.
*
* Written by Russ Allbery <eagle@eyrie.org>
- * Copyright 2006, 2007, 2008, 2009, 2010
+ * Copyright 2006, 2007, 2008, 2009, 2010, 2013
* The Board of Trustees of the Leland Stanford Junior University
*
* Permission is hereby granted, free of charge, to any person obtaining a
#include <portable/krb5.h>
#include <portable/system.h>
-#include <util/macros.h>
#include <util/messages.h>
#include <util/messages-krb5.h>
#include <util/xmalloc.h>
if (ctx != NULL)
k5_msg = krb5_get_error_message(ctx, code);
va_start(args, format);
- if (xvasprintf(&message, format, args) < 0)
- die("internal error: unable to format error message");
+ xvasprintf(&message, format, args);
va_end(args);
if (k5_msg == NULL)
die("%s", message);
if (ctx != NULL)
k5_msg = krb5_get_error_message(ctx, code);
va_start(args, format);
- if (xvasprintf(&message, format, args) < 0)
- die("internal error: unable to format error message");
+ xvasprintf(&message, format, args);
va_end(args);
if (k5_msg == NULL)
warn("%s", message);
* which can be found at <http://www.eyrie.org/~eagle/software/rra-c-util/>.
*
* Written by Russ Allbery <eagle@eyrie.org>
- * Copyright 2006, 2007, 2008, 2009, 2010
+ * Copyright 2006, 2007, 2008, 2009, 2010, 2013
* The Board of Trustees of the Leland Stanford Junior University
*
* Permission is hereby granted, free of charge, to any person obtaining a
#define UTIL_MESSAGES_KRB5_H 1
#include <config.h>
+#include <portable/krb5.h>
#include <portable/macros.h>
-#include <krb5.h>
-#include <sys/types.h>
-
BEGIN_DECLS
/* Default to a hidden visibility for all util functions. */
* which can be found at <http://www.eyrie.org/~eagle/software/rra-c-util/>.
*
* Written by Russ Allbery <eagle@eyrie.org>
- * Copyright 2008, 2009, 2010
+ * Copyright 2008, 2009, 2010, 2013
* The Board of Trustees of the Leland Stanford Junior University
* Copyright (c) 2004, 2005, 2006
* by Internet Systems Consortium, Inc. ("ISC")
if (*list != stdout_handlers && *list != stderr_handlers)
free(*list);
- *list = xmalloc(sizeof(message_handler_func) * (count + 1));
+ *list = xcalloc(count + 1, sizeof(message_handler_func));
for (i = 0; i < count; i++)
(*list)[i] = (message_handler_func) va_arg(args, message_handler_func);
(*list)[count] = NULL;
HANDLER_FUNCTION(die)
+/*
+ * Reset all handlers back to the defaults and free all allocated memory.
+ * This is primarily useful for programs that undergo comprehensive memory
+ * allocation analysis.
+ */
+void
+message_handlers_reset(void)
+{
+ free(debug_handlers);
+ debug_handlers = NULL;
+ if (notice_handlers != stdout_handlers) {
+ free(notice_handlers);
+ notice_handlers = stdout_handlers;
+ }
+ if (warn_handlers != stderr_handlers) {
+ free(warn_handlers);
+ warn_handlers = stderr_handlers;
+ }
+ if (die_handlers != stderr_handlers) {
+ free(die_handlers);
+ die_handlers = stderr_handlers;
+ }
+}
+
+
/*
* Print a message to stdout, supporting message_program_name.
*/
* This needs further attention on Windows. For example, it currently doesn't
* log the errno information.
*/
-static void
+static void __attribute__((__format__(printf, 3, 0)))
message_log_syslog(int pri, size_t len, const char *fmt, va_list args, int err)
{
char *buffer;
+ int status;
buffer = malloc(len + 1);
if (buffer == NULL) {
(unsigned long) len + 1, __FILE__, __LINE__, strerror(errno));
exit(message_fatal_cleanup ? (*message_fatal_cleanup)() : 1);
}
- vsnprintf(buffer, len + 1, fmt, args);
+ status = vsnprintf(buffer, len + 1, fmt, args);
+ if (status < 0) {
+ warn("failed to format output with vsnprintf in syslog handler");
+ free(buffer);
+ return;
+ }
#ifdef _WIN32
{
HANDLE eventlog;
* The canonical version of this file is maintained in the rra-c-util package,
* which can be found at <http://www.eyrie.org/~eagle/software/rra-c-util/>.
*
- * Copyright 2008, 2010
+ * Copyright 2008, 2010, 2013, 2014
* The Board of Trustees of the Leland Stanford Junior University
* Copyright (c) 2004, 2005, 2006
* by Internet Systems Consortium, Inc. ("ISC")
#include <portable/macros.h>
#include <stdarg.h>
+#include <stddef.h>
BEGIN_DECLS
void message_handlers_warn(unsigned int count, ...);
void message_handlers_die(unsigned int count, ...);
+/*
+ * Reset all message handlers back to the defaults and free any memory that
+ * was allocated by the other message_handlers_* functions.
+ */
+void message_handlers_reset(void);
+
/*
* Some useful handlers, intended to be passed to message_handlers_*. All
* handlers take the length of the formatted message, the format, a variadic
* argument list, and the errno setting if any.
*/
void message_log_stdout(size_t, const char *, va_list, int)
- __attribute__((__nonnull__));
+ __attribute__((__format__(printf, 2, 0), __nonnull__));
void message_log_stderr(size_t, const char *, va_list, int)
- __attribute__((__nonnull__));
+ __attribute__((__format__(printf, 2, 0), __nonnull__));
void message_log_syslog_debug(size_t, const char *, va_list, int)
- __attribute__((__nonnull__));
+ __attribute__((__format__(printf, 2, 0), __nonnull__));
void message_log_syslog_info(size_t, const char *, va_list, int)
- __attribute__((__nonnull__));
+ __attribute__((__format__(printf, 2, 0), __nonnull__));
void message_log_syslog_notice(size_t, const char *, va_list, int)
- __attribute__((__nonnull__));
+ __attribute__((__format__(printf, 2, 0), __nonnull__));
void message_log_syslog_warning(size_t, const char *, va_list, int)
- __attribute__((__nonnull__));
+ __attribute__((__format__(printf, 2, 0), __nonnull__));
void message_log_syslog_err(size_t, const char *, va_list, int)
- __attribute__((__nonnull__));
+ __attribute__((__format__(printf, 2, 0), __nonnull__));
void message_log_syslog_crit(size_t, const char *, va_list, int)
- __attribute__((__nonnull__));
+ __attribute__((__format__(printf, 2, 0), __nonnull__));
/* The type of a message handler. */
-typedef void (*message_handler_func)(size_t, const char *, va_list, int);
+typedef void (*message_handler_func)(size_t, const char *, va_list, int)
+ __attribute__((__format__(printf, 2, 0)));
/* If non-NULL, called before exit and its return value passed to exit. */
extern int (*message_fatal_cleanup)(void);
* buffer = xmalloc(1024);
* xrealloc(buffer, 2048);
* free(buffer);
- * buffer = xcalloc(1024);
+ * buffer = xcalloc(1, 1024);
* free(buffer);
* buffer = xstrdup(string);
* free(buffer);
* allocation function will try its allocation again (calling the handler
* again if it still fails).
*
+ * xreallocarray behaves the same as the OpenBSD reallocarray function but for
+ * the same error checking, which in turn is the same as realloc but with
+ * calloc-style arguments and size overflow checking.
+ *
* xstrndup behaves like xstrdup but only copies the given number of
* characters. It allocates an additional byte over its second argument and
* always nul-terminates the string.
* 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 2015 Russ Allbery <eagle@eyrie.org>
+ * Copyright 2012, 2013, 2014
+ * The Board of Trustees of the Leland Stanford Junior University
* Copyright (c) 2004, 2005, 2006
* by Internet Systems Consortium, Inc. ("ISC")
* Copyright (c) 1991, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001,
#include <config.h>
#include <portable/system.h>
-#include <errno.h>
-
#include <util/messages.h>
#include <util/xmalloc.h>
void
xmalloc_fail(const char *function, size_t size, const char *file, int line)
{
- sysdie("failed to %s %lu bytes at %s line %d", function,
- (unsigned long) size, file, line);
+ if (size == 0)
+ sysdie("failed to format output with %s at %s line %d", function,
+ file, line);
+ else
+ sysdie("failed to %s %lu bytes at %s line %d", function,
+ (unsigned long) size, file, line);
}
/* Assign to this variable to choose a handler other than the default. */
}
+void *
+x_reallocarray(void *p, size_t n, size_t size, const char *file, int line)
+{
+ void *newp;
+
+ newp = reallocarray(p, n, size);
+ while (newp == NULL && size > 0 && n > 0) {
+ (*xmalloc_error_handler)("reallocarray", n * size, file, line);
+ newp = reallocarray(p, n, size);
+ }
+ return newp;
+}
+
+
char *
x_strdup(const char *s, const char *file, int line)
{
}
+/*
+ * Avoid using the system strndup function since it may not exist (on Mac OS
+ * X, for example), and there's no need to introduce another portability
+ * requirement.
+ */
char *
x_strndup(const char *s, size_t size, const char *file, int line)
{
- char *p;
+ const char *p;
+ size_t length;
+ char *copy;
- p = malloc(size + 1);
- while (p == NULL) {
- (*xmalloc_error_handler)("strndup", size + 1, file, line);
- p = malloc(size + 1);
+ /* Don't assume that the source string is nul-terminated. */
+ for (p = s; (size_t) (p - s) < size && *p != '\0'; p++)
+ ;
+ length = p - s;
+ copy = malloc(length + 1);
+ while (copy == NULL) {
+ (*xmalloc_error_handler)("strndup", length + 1, file, line);
+ copy = malloc(length + 1);
}
- memcpy(p, s, size);
- p[size] = '\0';
- return p;
+ memcpy(copy, s, length);
+ copy[length] = '\0';
+ return copy;
}
-int
+void
x_vasprintf(char **strp, const char *fmt, va_list args, const char *file,
int line)
{
va_copy(args_copy, args);
status = vasprintf(strp, fmt, args_copy);
va_end(args_copy);
- while (status < 0 && errno == ENOMEM) {
+ while (status < 0) {
va_copy(args_copy, args);
status = vsnprintf(NULL, 0, fmt, args_copy);
va_end(args_copy);
- (*xmalloc_error_handler)("vasprintf", status + 1, file, line);
+ status = (status < 0) ? 0 : status + 1;
+ (*xmalloc_error_handler)("vasprintf", status, file, line);
va_copy(args_copy, args);
status = vasprintf(strp, fmt, args_copy);
va_end(args_copy);
}
- return status;
}
#if HAVE_C99_VAMACROS || HAVE_GNU_VAMACROS
-int
+void
x_asprintf(char **strp, const char *file, int line, const char *fmt, ...)
{
va_list args, args_copy;
va_copy(args_copy, args);
status = vasprintf(strp, fmt, args_copy);
va_end(args_copy);
- while (status < 0 && errno == ENOMEM) {
+ while (status < 0) {
va_copy(args_copy, args);
status = vsnprintf(NULL, 0, fmt, args_copy);
va_end(args_copy);
- (*xmalloc_error_handler)("asprintf", status + 1, file, line);
+ status = (status < 0) ? 0 : status + 1;
+ (*xmalloc_error_handler)("asprintf", status, file, line);
va_copy(args_copy, args);
status = vasprintf(strp, fmt, args_copy);
va_end(args_copy);
}
- return status;
+ va_end(args);
}
#else /* !(HAVE_C99_VAMACROS || HAVE_GNU_VAMACROS) */
-int
+void
x_asprintf(char **strp, const char *fmt, ...)
{
va_list args, args_copy;
va_copy(args_copy, args);
status = vasprintf(strp, fmt, args_copy);
va_end(args_copy);
- while (status < 0 && errno == ENOMEM) {
+ while (status < 0) {
va_copy(args_copy, args);
status = vsnprintf(NULL, 0, fmt, args_copy);
va_end(args_copy);
- (*xmalloc_error_handler)("asprintf", status + 1, __FILE__, __LINE__);
+ status = (status < 0) ? 0 : status + 1;
+ (*xmalloc_error_handler)("asprintf", status, __FILE__, __LINE__);
va_copy(args_copy, args);
status = vasprintf(strp, fmt, args_copy);
va_end(args_copy);
}
- return status;
+ va_end(args);
}
#endif /* !(HAVE_C99_VAMACROS || HAVE_GNU_VAMACROS) */
* The canonical version of this file is maintained in the rra-c-util package,
* which can be found at <http://www.eyrie.org/~eagle/software/rra-c-util/>.
*
- * Copyright 2010
+ * Copyright 2010, 2012, 2013, 2014
* The Board of Trustees of the Leland Stanford Junior University
* Copyright (c) 2004, 2005, 2006
* by Internet Systems Consortium, Inc. ("ISC")
#include <config.h>
#include <portable/macros.h>
-#include <sys/types.h>
+#include <stdarg.h>
+#include <stddef.h>
/*
* The functions are actually macros so that we can pick up the file and line
#define xstrdup(p) x_strdup((p), __FILE__, __LINE__)
#define xstrndup(p, size) x_strndup((p), (size), __FILE__, __LINE__)
#define xvasprintf(p, f, a) x_vasprintf((p), (f), (a), __FILE__, __LINE__)
+#define xreallocarray(p, n, size) \
+ x_reallocarray((p), (n), (size), __FILE__, __LINE__)
/*
* asprintf is a special case since it takes variable arguments. If we have
__attribute__((__alloc_size__(1), __malloc__, __nonnull__));
void *x_realloc(void *, size_t, const char *, int)
__attribute__((__alloc_size__(2), __malloc__, __nonnull__(3)));
+void *x_reallocarray(void *, size_t, size_t, const char *, int)
+ __attribute__((__alloc_size__(2, 3), __malloc__, __nonnull__(4)));
char *x_strdup(const char *, const char *, int)
__attribute__((__malloc__, __nonnull__));
char *x_strndup(const char *, size_t, const char *, int)
__attribute__((__malloc__, __nonnull__));
-int x_vasprintf(char **, const char *, va_list, const char *, int)
- __attribute__((__nonnull__));
+void x_vasprintf(char **, const char *, va_list, const char *, int)
+ __attribute__((__nonnull__, __format__(printf, 2, 0)));
/* asprintf special case. */
#if HAVE_C99_VAMACROS || HAVE_GNU_VAMACROS
-int x_asprintf(char **, const char *, int, const char *, ...)
+void x_asprintf(char **, const char *, int, const char *, ...)
__attribute__((__nonnull__, __format__(printf, 4, 5)));
#else
-int x_asprintf(char **, const char *, ...)
+void x_asprintf(char **, const char *, ...)
__attribute__((__nonnull__, __format__(printf, 2, 3)));
#endif
-/* Failure handler takes the function, the size, the file, and the line. */
+/*
+ * Failure handler takes the function, the size, the file, and the line. The
+ * size will be zero if the failure was due to some failure in snprintf
+ * instead of a memory allocation failure.
+ */
typedef void (*xmalloc_handler_type)(const char *, size_t, const char *, int);
/* The default error handler. */