summaryrefslogtreecommitdiffstats
path: root/unit/atf-src/atf-sh
diff options
context:
space:
mode:
Diffstat (limited to 'unit/atf-src/atf-sh')
-rw-r--r--unit/atf-src/atf-sh/Atffile11
-rw-r--r--unit/atf-src/atf-sh/Kyuafile11
-rw-r--r--unit/atf-src/atf-sh/Makefile.am.inc122
-rw-r--r--unit/atf-src/atf-sh/atf-check.1160
-rw-r--r--unit/atf-src/atf-sh/atf-check.cpp834
-rw-r--r--unit/atf-src/atf-sh/atf-check_test.sh441
-rw-r--r--unit/atf-src/atf-sh/atf-sh.1102
-rw-r--r--unit/atf-src/atf-sh/atf-sh.3372
-rw-r--r--unit/atf-src/atf-sh/atf-sh.cpp185
-rw-r--r--unit/atf-src/atf-sh/atf-sh.m449
-rw-r--r--unit/atf-src/atf-sh/atf-sh.pc.in8
-rw-r--r--unit/atf-src/atf-sh/atf_check_test.sh193
-rw-r--r--unit/atf-src/atf-sh/config_test.sh79
-rw-r--r--unit/atf-src/atf-sh/integration_test.sh160
-rw-r--r--unit/atf-src/atf-sh/libatf-sh.subr770
-rw-r--r--unit/atf-src/atf-sh/misc_helpers.sh305
-rw-r--r--unit/atf-src/atf-sh/normalize_test.sh44
-rw-r--r--unit/atf-src/atf-sh/tc_test.sh60
-rw-r--r--unit/atf-src/atf-sh/tp_test.sh52
19 files changed, 3958 insertions, 0 deletions
diff --git a/unit/atf-src/atf-sh/Atffile b/unit/atf-src/atf-sh/Atffile
new file mode 100644
index 0000000..f1735c3
--- /dev/null
+++ b/unit/atf-src/atf-sh/Atffile
@@ -0,0 +1,11 @@
+Content-Type: application/X-atf-atffile; version="1"
+
+prop: test-suite = atf
+
+tp: tc_test
+tp: tp_test
+tp: normalize_test
+tp: config_test
+tp: atf-check_test
+tp: atf_check_test
+tp: integration_test
diff --git a/unit/atf-src/atf-sh/Kyuafile b/unit/atf-src/atf-sh/Kyuafile
new file mode 100644
index 0000000..01a9253
--- /dev/null
+++ b/unit/atf-src/atf-sh/Kyuafile
@@ -0,0 +1,11 @@
+syntax("kyuafile", 1)
+
+test_suite("atf")
+
+atf_test_program{name="tc_test"}
+atf_test_program{name="tp_test"}
+atf_test_program{name="normalize_test"}
+atf_test_program{name="config_test"}
+atf_test_program{name="atf-check_test"}
+atf_test_program{name="atf_check_test"}
+atf_test_program{name="integration_test"}
diff --git a/unit/atf-src/atf-sh/Makefile.am.inc b/unit/atf-src/atf-sh/Makefile.am.inc
new file mode 100644
index 0000000..59d0911
--- /dev/null
+++ b/unit/atf-src/atf-sh/Makefile.am.inc
@@ -0,0 +1,122 @@
+# Copyright (c) 2007 The NetBSD Foundation, Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
+# CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+# IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
+# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+# GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+# IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+# IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+libexec_PROGRAMS += atf-sh/atf-check
+atf_sh_atf_check_SOURCES = atf-sh/atf-check.cpp
+atf_sh_atf_check_LDADD = $(ATF_CXX_LIBS)
+atf_sh_atf_check_CPPFLAGS = -DATF_SHELL=\"$(ATF_SHELL)\"
+dist_man_MANS += atf-sh/atf-check.1
+
+bin_PROGRAMS += atf-sh/atf-sh
+atf_sh_atf_sh_SOURCES = atf-sh/atf-sh.cpp
+atf_sh_atf_sh_CPPFLAGS = -DATF_LIBEXECDIR=\"$(libexecdir)\" \
+ -DATF_PKGDATADIR=\"$(pkgdatadir)\" \
+ -DATF_SHELL=\"$(ATF_SHELL)\"
+atf_sh_atf_sh_LDADD = $(ATF_CXX_LIBS)
+dist_man_MANS += atf-sh/atf-sh.1
+
+atf_sh_DATA = atf-sh/libatf-sh.subr
+atf_shdir = $(pkgdatadir)
+EXTRA_DIST += $(atf_sh_DATA)
+
+dist_man_MANS += atf-sh/atf-sh.3
+
+atf_aclocal_DATA += atf-sh/atf-sh.m4
+EXTRA_DIST += atf-sh/atf-sh.m4
+
+atf_shpkgconfigdir = $(atf_pkgconfigdir)
+atf_shpkgconfig_DATA = atf-sh/atf-sh.pc
+CLEANFILES += atf-sh/atf-sh.pc
+EXTRA_DIST += atf-sh/atf-sh.pc.in
+atf-sh/atf-sh.pc: $(srcdir)/atf-sh/atf-sh.pc.in Makefile
+ $(AM_V_GEN)test -d atf-sh || mkdir -p atf-sh; \
+ sed -e 's#__ATF_VERSION__#$(PACKAGE_VERSION)#g' \
+ -e 's#__EXEC_PREFIX__#$(exec_prefix)#g' \
+ <$(srcdir)/atf-sh/atf-sh.pc.in >atf-sh/atf-sh.pc.tmp; \
+ mv atf-sh/atf-sh.pc.tmp atf-sh/atf-sh.pc
+
+tests_atf_sh_DATA = atf-sh/Atffile \
+ atf-sh/Kyuafile
+tests_atf_shdir = $(pkgtestsdir)/atf-sh
+EXTRA_DIST += $(tests_atf_sh_DATA)
+
+tests_atf_sh_SCRIPTS = atf-sh/misc_helpers
+CLEANFILES += atf-sh/misc_helpers
+EXTRA_DIST += atf-sh/misc_helpers.sh
+atf-sh/misc_helpers: $(srcdir)/atf-sh/misc_helpers.sh
+ $(AM_V_GEN)src="$(srcdir)/atf-sh/misc_helpers.sh"; \
+ dst="atf-sh/misc_helpers"; $(BUILD_SH_TP)
+
+tests_atf_sh_SCRIPTS += atf-sh/atf_check_test
+CLEANFILES += atf-sh/atf_check_test
+EXTRA_DIST += atf-sh/atf_check_test.sh
+atf-sh/atf_check_test: $(srcdir)/atf-sh/atf_check_test.sh
+ $(AM_V_GEN)src="$(srcdir)/atf-sh/atf_check_test.sh"; \
+ dst="atf-sh/atf_check_test"; $(BUILD_SH_TP)
+
+tests_atf_sh_SCRIPTS += atf-sh/atf-check_test
+CLEANFILES += atf-sh/atf-check_test
+EXTRA_DIST += atf-sh/atf-check_test.sh
+atf-sh/atf-check_test: $(srcdir)/atf-sh/atf-check_test.sh
+ $(AM_V_GEN)src="$(srcdir)/atf-sh/atf-check_test.sh"; \
+ dst="atf-sh/atf-check_test"; $(BUILD_SH_TP)
+
+tests_atf_sh_SCRIPTS += atf-sh/config_test
+CLEANFILES += atf-sh/config_test
+EXTRA_DIST += atf-sh/config_test.sh
+atf-sh/config_test: $(srcdir)/atf-sh/config_test.sh
+ $(AM_V_GEN)src="$(srcdir)/atf-sh/config_test.sh"; \
+ dst="atf-sh/config_test"; $(BUILD_SH_TP)
+
+tests_atf_sh_SCRIPTS += atf-sh/integration_test
+CLEANFILES += atf-sh/integration_test
+EXTRA_DIST += atf-sh/integration_test.sh
+atf-sh/integration_test: $(srcdir)/atf-sh/integration_test.sh
+ $(AM_V_GEN)src="$(srcdir)/atf-sh/integration_test.sh"; \
+ dst="atf-sh/integration_test"; \
+ substs="s,__ATF_SH__,$(exec_prefix)/bin/atf-sh,g"; $(BUILD_SH_TP)
+
+tests_atf_sh_SCRIPTS += atf-sh/normalize_test
+CLEANFILES += atf-sh/normalize_test
+EXTRA_DIST += atf-sh/normalize_test.sh
+atf-sh/normalize_test: $(srcdir)/atf-sh/normalize_test.sh
+ $(AM_V_GEN)src="$(srcdir)/atf-sh/normalize_test.sh"; \
+ dst="atf-sh/normalize_test"; $(BUILD_SH_TP)
+
+tests_atf_sh_SCRIPTS += atf-sh/tc_test
+CLEANFILES += atf-sh/tc_test
+EXTRA_DIST += atf-sh/tc_test.sh
+atf-sh/tc_test: $(srcdir)/atf-sh/tc_test.sh
+ $(AM_V_GEN)src="$(srcdir)/atf-sh/tc_test.sh"; \
+ dst="atf-sh/tc_test"; $(BUILD_SH_TP)
+
+tests_atf_sh_SCRIPTS += atf-sh/tp_test
+CLEANFILES += atf-sh/tp_test
+EXTRA_DIST += atf-sh/tp_test.sh
+atf-sh/tp_test: $(srcdir)/atf-sh/tp_test.sh
+ $(AM_V_GEN)src="$(srcdir)/atf-sh/tp_test.sh"; \
+ dst="atf-sh/tp_test"; $(BUILD_SH_TP)
+
+# vim: syntax=make:noexpandtab:shiftwidth=8:softtabstop=8
diff --git a/unit/atf-src/atf-sh/atf-check.1 b/unit/atf-src/atf-sh/atf-check.1
new file mode 100644
index 0000000..a3bd379
--- /dev/null
+++ b/unit/atf-src/atf-sh/atf-check.1
@@ -0,0 +1,160 @@
+.\" Copyright (c) 2008 The NetBSD Foundation, Inc.
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
+.\" CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+.\" INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+.\" MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+.\" IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
+.\" DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+.\" GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+.\" INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+.\" IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+.\" OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+.\" IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+.Dd October 5, 2014
+.Dt ATF-CHECK 1
+.Os
+.Sh NAME
+.Nm atf-check
+.Nd executes a command and analyzes its results
+.Sh SYNOPSIS
+.Nm
+.Op Fl s Ar qual:value
+.Op Fl o Ar action:arg ...
+.Op Fl e Ar action:arg ...
+.Op Fl x
+.Ar command
+.Sh DESCRIPTION
+.Nm
+executes a given command and analyzes its results, including
+exit code, stdout and stderr.
+.Pp
+.Em Test cases must use
+.Em Xr atf-sh 3 Ns ' Ns s
+.Em Nm atf_check
+.Em builtin function instead of calling this utility directly.
+.Pp
+In the first synopsis form,
+.Nm
+will execute the provided command and apply checks specified
+by arguments.
+By default it will act as if it was run with
+.Fl s
+.Ar exit:0
+.Fl o
+.Ar empty
+.Fl e
+.Ar empty .
+Multiple checks for the same output channel are allowed and, if specified,
+their results will be combined as a logical and (meaning that the output must
+match all the provided checks).
+.Pp
+In the second synopsis form,
+.Nm
+will print information about all supported options and their purpose.
+.Pp
+The following options are available:
+.Bl -tag -width XqualXvalueXX
+.It Fl s Ar qual:value
+Analyzes termination status.
+Must be one of:
+.Bl -tag -width signal:<value> -compact
+.It Ar exit:<value>
+checks that the program exited cleanly and that its exit status is equal to
+.Va value .
+The exit code can be omitted altogether, in which case any clean exit is
+accepted.
+.It Ar ignore
+ignores the exit check.
+.It Ar signal:<value>
+checks that the program exited due to a signal and that the signal that
+terminated it is
+.Va value .
+The signal can be specified both as a number or as a name, or it can also
+be omitted altogether, in which case any signal is accepted.
+.El
+.Pp
+Most of these checkers can be prefixed by the
+.Sq not-
+string, which effectively reverses the check.
+.It Fl o Ar action:arg
+Analyzes standard output.
+Must be one of:
+.Bl -tag -width inline:<value> -compact
+.It Ar empty
+checks that stdout is empty
+.It Ar ignore
+ignores stdout
+.It Ar file:<path>
+compares stdout with given file
+.It Ar inline:<value>
+compares stdout with inline value
+.It Ar match:<regexp>
+looks for a regular expression in stdout
+.It Ar save:<path>
+saves stdout to given file
+.El
+.Pp
+Most of these checkers can be prefixed by the
+.Sq not-
+string, which effectively reverses the check.
+.It Fl e Ar action:arg
+Analyzes standard error (syntax identical to above)
+.It Fl x
+Executes
+.Ar command
+as a shell command line, executing it with the system shell defined by
+.Va ATF_SHELL .
+You should avoid using this flag if at all possible to prevent shell quoting
+issues.
+.El
+.Sh EXIT STATUS
+.Nm
+exits 0 on success, and other (unspecified) value on failure.
+.Sh ENVIRONMENT
+.Bl -tag -width ATFXSHELLXX -compact
+.It Va ATF_SHELL
+Path to the system shell to be used when the
+.Fl x
+is given to run commands.
+.El
+.Sh EXAMPLES
+The following are sample invocations from within a test case.
+Note that we use the
+.Nm atf_check
+function provided by
+.Xr atf-sh 3
+instead of executing
+.Nm
+directly:
+.Bd -literal -offset indent
+# Exit code 0, nothing on stdout/stderr
+atf_check 'true'
+
+# Typical usage if failure is expected
+atf_check -s not-exit:0 'false'
+
+# Checking stdout/stderr
+echo foobar >expout
+atf_check -o file:expout -e inline:"xx\etyy\en" \e
+ 'echo foobar ; printf "xx\etyy\en" >&2'
+
+# Checking for a crash
+atf_check -s signal:sigsegv my_program
+
+# Combined checks
+atf_check -o match:foo -o not-match:bar echo foo baz
+.Ed
+.Sh SEE ALSO
+.Xr atf-sh 1
diff --git a/unit/atf-src/atf-sh/atf-check.cpp b/unit/atf-src/atf-sh/atf-check.cpp
new file mode 100644
index 0000000..866b7bb
--- /dev/null
+++ b/unit/atf-src/atf-sh/atf-check.cpp
@@ -0,0 +1,834 @@
+// Copyright (c) 2008 The NetBSD Foundation, Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
+// CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+// IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
+// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+// IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+extern "C" {
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include <limits.h>
+#include <signal.h>
+#include <unistd.h>
+}
+
+#include <cerrno>
+#include <cstdlib>
+#include <cstring>
+#include <fstream>
+#include <ios>
+#include <iostream>
+#include <iterator>
+#include <list>
+#include <memory>
+#include <utility>
+
+#include "atf-c++/check.hpp"
+#include "atf-c++/detail/application.hpp"
+#include "atf-c++/detail/auto_array.hpp"
+#include "atf-c++/detail/env.hpp"
+#include "atf-c++/detail/exceptions.hpp"
+#include "atf-c++/detail/fs.hpp"
+#include "atf-c++/detail/process.hpp"
+#include "atf-c++/detail/sanity.hpp"
+#include "atf-c++/detail/text.hpp"
+
+// ------------------------------------------------------------------------
+// Auxiliary functions.
+// ------------------------------------------------------------------------
+
+namespace {
+
+enum status_check_t {
+ sc_exit,
+ sc_ignore,
+ sc_signal,
+};
+
+struct status_check {
+ status_check_t type;
+ bool negated;
+ int value;
+
+ status_check(const status_check_t& p_type, const bool p_negated,
+ const int p_value) :
+ type(p_type),
+ negated(p_negated),
+ value(p_value)
+ {
+ }
+};
+
+enum output_check_t {
+ oc_ignore,
+ oc_inline,
+ oc_file,
+ oc_empty,
+ oc_match,
+ oc_save
+};
+
+struct output_check {
+ output_check_t type;
+ bool negated;
+ std::string value;
+
+ output_check(const output_check_t& p_type, const bool p_negated,
+ const std::string& p_value) :
+ type(p_type),
+ negated(p_negated),
+ value(p_value)
+ {
+ }
+};
+
+class temp_file : public std::ostream {
+ std::auto_ptr< atf::fs::path > m_path;
+ int m_fd;
+
+public:
+ temp_file(const char* pattern) :
+ std::ostream(NULL),
+ m_fd(-1)
+ {
+ const atf::fs::path file = atf::fs::path(
+ atf::env::get("TMPDIR", "/tmp")) / pattern;
+
+ atf::auto_array< char > buf(new char[file.str().length() + 1]);
+ std::strcpy(buf.get(), file.c_str());
+
+ m_fd = ::mkstemp(buf.get());
+ if (m_fd == -1)
+ throw atf::system_error("atf_check::temp_file::temp_file(" +
+ file.str() + ")", "mkstemp(3) failed",
+ errno);
+
+ m_path.reset(new atf::fs::path(buf.get()));
+ }
+
+ ~temp_file(void)
+ {
+ close();
+ try {
+ remove(*m_path);
+ } catch (const atf::system_error&) {
+ // Ignore deletion errors.
+ }
+ }
+
+ const atf::fs::path&
+ get_path(void) const
+ {
+ return *m_path;
+ }
+
+ void
+ write(const std::string& text)
+ {
+ if (::write(m_fd, text.c_str(), text.size()) == -1)
+ throw atf::system_error("atf_check", "write(2) failed", errno);
+ }
+
+ void
+ close(void)
+ {
+ if (m_fd != -1) {
+ flush();
+ ::close(m_fd);
+ m_fd = -1;
+ }
+ }
+};
+
+} // anonymous namespace
+
+static int
+parse_exit_code(const std::string& str)
+{
+ try {
+ const int value = atf::text::to_type< int >(str);
+ if (value < 0 || value > 255)
+ throw std::runtime_error("Unused reason");
+ return value;
+ } catch (const std::runtime_error&) {
+ throw atf::application::usage_error("Invalid exit code for -s option; "
+ "must be an integer in range 0-255");
+ }
+}
+
+static struct name_number {
+ const char *name;
+ int signo;
+} signal_names_to_numbers[] = {
+ { "hup", SIGHUP },
+ { "int", SIGINT },
+ { "quit", SIGQUIT },
+ { "trap", SIGTRAP },
+ { "abrt", SIGABRT },
+ { "kill", SIGKILL },
+ { "segv", SIGSEGV },
+ { "pipe", SIGPIPE },
+ { "alrm", SIGALRM },
+ { "term", SIGTERM },
+ { "usr1", SIGUSR1 },
+ { "usr2", SIGUSR2 },
+ { NULL, INT_MIN },
+};
+
+static int
+signal_name_to_number(const std::string& str)
+{
+ struct name_number* iter = signal_names_to_numbers;
+ int signo = INT_MIN;
+ while (signo == INT_MIN && iter->name != NULL) {
+ if (str == iter->name || str == std::string("sig") + iter->name)
+ signo = iter->signo;
+ else
+ iter++;
+ }
+ return signo;
+}
+
+static int
+parse_signal(const std::string& str)
+{
+ const int signo = signal_name_to_number(str);
+ if (signo == INT_MIN) {
+ try {
+ return atf::text::to_type< int >(str);
+ } catch (std::runtime_error) {
+ throw atf::application::usage_error("Invalid signal name or number "
+ "in -s option");
+ }
+ }
+ INV(signo != INT_MIN);
+ return signo;
+}
+
+static status_check
+parse_status_check_arg(const std::string& arg)
+{
+ const std::string::size_type delimiter = arg.find(':');
+ bool negated = (arg.compare(0, 4, "not-") == 0);
+ const std::string action_str = arg.substr(0, delimiter);
+ const std::string action = negated ? action_str.substr(4) : action_str;
+ const std::string value_str = (
+ delimiter == std::string::npos ? "" : arg.substr(delimiter + 1));
+ int value;
+
+ status_check_t type;
+ if (action == "eq") {
+ // Deprecated; use exit instead. TODO: Remove after 0.10.
+ type = sc_exit;
+ if (negated)
+ throw atf::application::usage_error("Cannot negate eq checker");
+ negated = false;
+ value = parse_exit_code(value_str);
+ } else if (action == "exit") {
+ type = sc_exit;
+ if (value_str.empty())
+ value = INT_MIN;
+ else
+ value = parse_exit_code(value_str);
+ } else if (action == "ignore") {
+ if (negated)
+ throw atf::application::usage_error("Cannot negate ignore checker");
+ type = sc_ignore;
+ value = INT_MIN;
+ } else if (action == "ne") {
+ // Deprecated; use not-exit instead. TODO: Remove after 0.10.
+ type = sc_exit;
+ if (negated)
+ throw atf::application::usage_error("Cannot negate ne checker");
+ negated = true;
+ value = parse_exit_code(value_str);
+ } else if (action == "signal") {
+ type = sc_signal;
+ if (value_str.empty())
+ value = INT_MIN;
+ else
+ value = parse_signal(value_str);
+ } else
+ throw atf::application::usage_error("Invalid status checker");
+
+ return status_check(type, negated, value);
+}
+
+static
+output_check
+parse_output_check_arg(const std::string& arg)
+{
+ const std::string::size_type delimiter = arg.find(':');
+ const bool negated = (arg.compare(0, 4, "not-") == 0);
+ const std::string action_str = arg.substr(0, delimiter);
+ const std::string action = negated ? action_str.substr(4) : action_str;
+
+ output_check_t type;
+ if (action == "empty")
+ type = oc_empty;
+ else if (action == "file")
+ type = oc_file;
+ else if (action == "ignore") {
+ if (negated)
+ throw atf::application::usage_error("Cannot negate ignore checker");
+ type = oc_ignore;
+ } else if (action == "inline")
+ type = oc_inline;
+ else if (action == "match")
+ type = oc_match;
+ else if (action == "save") {
+ if (negated)
+ throw atf::application::usage_error("Cannot negate save checker");
+ type = oc_save;
+ } else
+ throw atf::application::usage_error("Invalid output checker");
+
+ return output_check(type, negated, arg.substr(delimiter + 1));
+}
+
+static
+std::string
+flatten_argv(char* const* argv)
+{
+ std::string cmdline;
+
+ char* const* arg = &argv[0];
+ while (*arg != NULL) {
+ if (arg != &argv[0])
+ cmdline += ' ';
+
+ cmdline += *arg;
+
+ arg++;
+ }
+
+ return cmdline;
+}
+
+static
+std::auto_ptr< atf::check::check_result >
+execute(const char* const* argv)
+{
+ // TODO: This should go to stderr... but fixing it now may be hard as test
+ // cases out there might be relying on stderr being silent.
+ std::cout << "Executing command [ ";
+ for (int i = 0; argv[i] != NULL; ++i)
+ std::cout << argv[i] << " ";
+ std::cout << "]\n";
+ std::cout.flush();
+
+ atf::process::argv_array argva(argv);
+ return atf::check::exec(argva);
+}
+
+static
+std::auto_ptr< atf::check::check_result >
+execute_with_shell(char* const* argv)
+{
+ const std::string cmd = flatten_argv(argv);
+
+ const char* sh_argv[4];
+ sh_argv[0] = atf::env::get("ATF_SHELL", ATF_SHELL).c_str();
+ sh_argv[1] = "-c";
+ sh_argv[2] = cmd.c_str();
+ sh_argv[3] = NULL;
+ return execute(sh_argv);
+}
+
+static
+void
+cat_file(const atf::fs::path& path)
+{
+ std::ifstream stream(path.c_str());
+ if (!stream)
+ throw std::runtime_error("Failed to open " + path.str());
+
+ stream >> std::noskipws;
+ std::istream_iterator< char > begin(stream), end;
+ std::ostream_iterator< char > out(std::cerr);
+ std::copy(begin, end, out);
+
+ stream.close();
+}
+
+static
+bool
+grep_file(const atf::fs::path& path, const std::string& regexp)
+{
+ std::ifstream stream(path.c_str());
+ if (!stream)
+ throw std::runtime_error("Failed to open " + path.str());
+
+ bool found = false;
+
+ std::string line;
+ while (!found && !std::getline(stream, line).fail()) {
+ if (atf::text::match(line, regexp))
+ found = true;
+ }
+
+ stream.close();
+
+ return found;
+}
+
+static
+bool
+file_empty(const atf::fs::path& p)
+{
+ atf::fs::file_info f(p);
+
+ return (f.get_size() == 0);
+}
+
+static bool
+compare_files(const atf::fs::path& p1, const atf::fs::path& p2)
+{
+ bool equal = false;
+
+ std::ifstream f1(p1.c_str());
+ if (!f1)
+ throw std::runtime_error("Failed to open " + p1.str());
+
+ std::ifstream f2(p2.c_str());
+ if (!f2)
+ throw std::runtime_error("Failed to open " + p1.str());
+
+ for (;;) {
+ char buf1[512], buf2[512];
+
+ f1.read(buf1, sizeof(buf1));
+ if (f1.bad())
+ throw std::runtime_error("Failed to read from " + p1.str());
+
+ f2.read(buf2, sizeof(buf2));
+ if (f2.bad())
+ throw std::runtime_error("Failed to read from " + p1.str());
+
+ if ((f1.gcount() == 0) && (f2.gcount() == 0)) {
+ equal = true;
+ break;
+ }
+
+ if ((f1.gcount() != f2.gcount()) ||
+ (std::memcmp(buf1, buf2, f1.gcount()) != 0)) {
+ break;
+ }
+ }
+
+ return equal;
+}
+
+static
+void
+print_diff(const atf::fs::path& p1, const atf::fs::path& p2)
+{
+ const atf::process::status s =
+ atf::process::exec(atf::fs::path("diff"),
+ atf::process::argv_array("diff", "-u", p1.c_str(),
+ p2.c_str(), NULL),
+ atf::process::stream_connect(STDOUT_FILENO,
+ STDERR_FILENO),
+ atf::process::stream_inherit());
+
+ if (!s.exited())
+ std::cerr << "Failed to run diff(3)\n";
+
+ if (s.exitstatus() != 1)
+ std::cerr << "Error while running diff(3)\n";
+}
+
+static
+std::string
+decode(const std::string& s)
+{
+ size_t i;
+ std::string res;
+
+ res.reserve(s.length());
+
+ i = 0;
+ while (i < s.length()) {
+ char c = s[i++];
+
+ if (c == '\\') {
+ switch (s[i++]) {
+ case 'a': c = '\a'; break;
+ case 'b': c = '\b'; break;
+ case 'c': break;
+ case 'e': c = 033; break;
+ case 'f': c = '\f'; break;
+ case 'n': c = '\n'; break;
+ case 'r': c = '\r'; break;
+ case 't': c = '\t'; break;
+ case 'v': c = '\v'; break;
+ case '\\': break;
+ case '0':
+ {
+ int count = 3;
+ c = 0;
+ while (--count >= 0 && (unsigned)(s[i] - '0') < 8)
+ c = (c << 3) + (s[i++] - '0');
+ break;
+ }
+ default:
+ --i;
+ break;
+ }
+ }
+
+ res.push_back(c);
+ }
+
+ return res;
+}
+
+static
+bool
+run_status_check(const status_check& sc, const atf::check::check_result& cr)
+{
+ bool result;
+
+ if (sc.type == sc_exit) {
+ if (cr.exited() && sc.value != INT_MIN) {
+ const int status = cr.exitcode();
+
+ if (!sc.negated && sc.value != status) {
+ std::cerr << "Fail: incorrect exit status: "
+ << status << ", expected: "
+ << sc.value << "\n";
+ result = false;
+ } else if (sc.negated && sc.value == status) {
+ std::cerr << "Fail: incorrect exit status: "
+ << status << ", expected: "
+ << "anything else\n";
+ result = false;
+ } else
+ result = true;
+ } else if (cr.exited() && sc.value == INT_MIN) {
+ result = true;
+ } else {
+ std::cerr << "Fail: program did not exit cleanly\n";
+ result = false;
+ }
+ } else if (sc.type == sc_ignore) {
+ result = true;
+ } else if (sc.type == sc_signal) {
+ if (cr.signaled() && sc.value != INT_MIN) {
+ const int status = cr.termsig();
+
+ if (!sc.negated && sc.value != status) {
+ std::cerr << "Fail: incorrect signal received: "
+ << status << ", expected: " << sc.value << "\n";
+ result = false;
+ } else if (sc.negated && sc.value == status) {
+ std::cerr << "Fail: incorrect signal received: "
+ << status << ", expected: "
+ << "anything else\n";
+ result = false;
+ } else
+ result = true;
+ } else if (cr.signaled() && sc.value == INT_MIN) {
+ result = true;
+ } else {
+ std::cerr << "Fail: program did not receive a signal\n";
+ result = false;
+ }
+ } else {
+ UNREACHABLE;
+ result = false;
+ }
+
+ if (result == false) {
+ std::cerr << "stdout:\n";
+ cat_file(atf::fs::path(cr.stdout_path()));
+ std::cerr << "\n";
+
+ std::cerr << "stderr:\n";
+ cat_file(atf::fs::path(cr.stderr_path()));
+ std::cerr << "\n";
+ }
+
+ return result;
+}
+
+static
+bool
+run_status_checks(const std::vector< status_check >& checks,
+ const atf::check::check_result& result)
+{
+ bool ok = false;
+
+ for (std::vector< status_check >::const_iterator iter = checks.begin();
+ !ok && iter != checks.end(); iter++) {
+ ok |= run_status_check(*iter, result);
+ }
+
+ return ok;
+}
+
+static
+bool
+run_output_check(const output_check oc, const atf::fs::path& path,
+ const std::string& stdxxx)
+{
+ bool result;
+
+ if (oc.type == oc_empty) {
+ const bool is_empty = file_empty(path);
+ if (!oc.negated && !is_empty) {
+ std::cerr << "Fail: " << stdxxx << " not empty\n";
+ print_diff(atf::fs::path("/dev/null"), path);
+ result = false;
+ } else if (oc.negated && is_empty) {
+ std::cerr << "Fail: " << stdxxx << " is empty\n";
+ result = false;
+ } else
+ result = true;
+ } else if (oc.type == oc_file) {
+ const bool equals = compare_files(path, atf::fs::path(oc.value));
+ if (!oc.negated && !equals) {
+ std::cerr << "Fail: " << stdxxx << " does not match golden "
+ "output\n";
+ print_diff(atf::fs::path(oc.value), path);
+ result = false;
+ } else if (oc.negated && equals) {
+ std::cerr << "Fail: " << stdxxx << " matches golden output\n";
+ cat_file(atf::fs::path(oc.value));
+ result = false;
+ } else
+ result = true;
+ } else if (oc.type == oc_ignore) {
+ result = true;
+ } else if (oc.type == oc_inline) {
+ temp_file temp("atf-check.XXXXXX");
+ temp.write(decode(oc.value));
+ temp.close();
+
+ const bool equals = compare_files(path, temp.get_path());
+ if (!oc.negated && !equals) {
+ std::cerr << "Fail: " << stdxxx << " does not match expected "
+ "value\n";
+ print_diff(temp.get_path(), path);
+ result = false;
+ } else if (oc.negated && equals) {
+ std::cerr << "Fail: " << stdxxx << " matches expected value\n";
+ cat_file(temp.get_path());
+ result = false;
+ } else
+ result = true;
+ } else if (oc.type == oc_match) {
+ const bool matches = grep_file(path, oc.value);
+ if (!oc.negated && !matches) {
+ std::cerr << "Fail: regexp " + oc.value + " not in " << stdxxx
+ << "\n";
+ cat_file(path);
+ result = false;
+ } else if (oc.negated && matches) {
+ std::cerr << "Fail: regexp " + oc.value + " is in " << stdxxx
+ << "\n";
+ cat_file(path);
+ result = false;
+ } else
+ result = true;
+ } else if (oc.type == oc_save) {
+ INV(!oc.negated);
+ std::ifstream ifs(path.c_str(), std::fstream::binary);
+ ifs >> std::noskipws;
+ std::istream_iterator< char > begin(ifs), end;
+
+ std::ofstream ofs(oc.value.c_str(), std::fstream::binary
+ | std::fstream::trunc);
+ std::ostream_iterator <char> obegin(ofs);
+
+ std::copy(begin, end, obegin);
+ result = true;
+ } else {
+ UNREACHABLE;
+ result = false;
+ }
+
+ return result;
+}
+
+static
+bool
+run_output_checks(const std::vector< output_check >& checks,
+ const atf::fs::path& path, const std::string& stdxxx)
+{
+ bool ok = true;
+
+ for (std::vector< output_check >::const_iterator iter = checks.begin();
+ iter != checks.end(); iter++) {
+ ok &= run_output_check(*iter, path, stdxxx);
+ }
+
+ return ok;
+}
+
+// ------------------------------------------------------------------------
+// The "atf_check" application.
+// ------------------------------------------------------------------------
+
+namespace {
+
+class atf_check : public atf::application::app {
+ bool m_xflag;
+
+ std::vector< status_check > m_status_checks;
+ std::vector< output_check > m_stdout_checks;
+ std::vector< output_check > m_stderr_checks;
+
+ static const char* m_description;
+
+ bool run_output_checks(const atf::check::check_result&,
+ const std::string&) const;
+
+ std::string specific_args(void) const;
+ options_set specific_options(void) const;
+ void process_option(int, const char*);
+ void process_option_s(const std::string&);
+
+public:
+ atf_check(void);
+ int main(void);
+};
+
+} // anonymous namespace
+
+const char* atf_check::m_description =
+ "atf-check executes given command and analyzes its results.";
+
+atf_check::atf_check(void) :
+ app(m_description, "atf-check(1)"),
+ m_xflag(false)
+{
+}
+
+bool
+atf_check::run_output_checks(const atf::check::check_result& r,
+ const std::string& stdxxx)
+ const
+{
+ if (stdxxx == "stdout") {
+ return ::run_output_checks(m_stdout_checks,
+ atf::fs::path(r.stdout_path()), "stdout");
+ } else if (stdxxx == "stderr") {
+ return ::run_output_checks(m_stderr_checks,
+ atf::fs::path(r.stderr_path()), "stderr");
+ } else {
+ UNREACHABLE;
+ return false;
+ }
+}
+
+std::string
+atf_check::specific_args(void)
+ const
+{
+ return "<command>";
+}
+
+atf_check::options_set
+atf_check::specific_options(void)
+ const
+{
+ using atf::application::option;
+ options_set opts;
+
+ opts.insert(option('s', "qual:value", "Handle status. Qualifier "
+ "must be one of: ignore exit:<num> signal:<name|num>"));
+ opts.insert(option('o', "action:arg", "Handle stdout. Action must be "
+ "one of: empty ignore file:<path> inline:<val> match:regexp "
+ "save:<path>"));
+ opts.insert(option('e', "action:arg", "Handle stderr. Action must be "
+ "one of: empty ignore file:<path> inline:<val> match:regexp "
+ "save:<path>"));
+ opts.insert(option('x', "", "Execute command as a shell command"));
+
+ return opts;
+}
+
+void
+atf_check::process_option(int ch, const char* arg)
+{
+ switch (ch) {
+ case 's':
+ m_status_checks.push_back(parse_status_check_arg(arg));
+ break;
+
+ case 'o':
+ m_stdout_checks.push_back(parse_output_check_arg(arg));
+ break;
+
+ case 'e':
+ m_stderr_checks.push_back(parse_output_check_arg(arg));
+ break;
+
+ case 'x':
+ m_xflag = true;
+ break;
+
+ default:
+ UNREACHABLE;
+ }
+}
+
+int
+atf_check::main(void)
+{
+ if (m_argc < 1)
+ throw atf::application::usage_error("No command specified");
+
+ int status = EXIT_FAILURE;
+
+ std::auto_ptr< atf::check::check_result > r =
+ m_xflag ? execute_with_shell(m_argv) : execute(m_argv);
+
+ if (m_status_checks.empty())
+ m_status_checks.push_back(status_check(sc_exit, false, EXIT_SUCCESS));
+ else if (m_status_checks.size() > 1) {
+ // TODO: Remove this restriction.
+ throw atf::application::usage_error("Cannot specify -s more than once");
+ }
+
+ if (m_stdout_checks.empty())
+ m_stdout_checks.push_back(output_check(oc_empty, false, ""));
+ if (m_stderr_checks.empty())
+ m_stderr_checks.push_back(output_check(oc_empty, false, ""));
+
+ if ((run_status_checks(m_status_checks, *r) == false) ||
+ (run_output_checks(*r, "stderr") == false) ||
+ (run_output_checks(*r, "stdout") == false))
+ status = EXIT_FAILURE;
+ else
+ status = EXIT_SUCCESS;
+
+ return status;
+}
+
+int
+main(int argc, char* const* argv)
+{
+ return atf_check().run(argc, argv);
+}
diff --git a/unit/atf-src/atf-sh/atf-check_test.sh b/unit/atf-src/atf-sh/atf-check_test.sh
new file mode 100644
index 0000000..9542dfb
--- /dev/null
+++ b/unit/atf-src/atf-sh/atf-check_test.sh
@@ -0,0 +1,441 @@
+# Copyright (c) 2008 The NetBSD Foundation, Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
+# CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+# IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
+# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+# GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+# IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+# IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+# The Atf_Check and Atf-Shell variables are set by atf-sh.
+
+h_pass()
+{
+ cmd="$1"; shift
+
+ echo "Running [atf-check $*] against [${cmd}]"
+
+ cat >script.sh <<EOF
+#! ${Atf_Shell}
+${cmd}
+EOF
+ chmod +x script.sh
+
+ if ! ${Atf_Check} "${@}" ./script.sh >tmp; then
+ cat tmp
+ atf_fail "atf-check failed"
+ fi
+}
+
+h_fail()
+{
+ cmd="$1"; shift
+
+ echo "Running [atf-check $*] against [${cmd}]"
+
+ cat >script.sh <<EOF
+#! ${Atf_Shell}
+${cmd}
+EOF
+ chmod +x script.sh
+
+ if ${Atf_Check} "${@}" ./script.sh 2>tmp; then
+ cat tmp
+ atf_fail "atf-check succeeded but should fail"
+ fi
+}
+
+atf_test_case sflag_eq_ne
+sflag_eq_ne_head()
+{
+ atf_set "descr" "Tests for the -s option using the 'eq' and 'ne' qualifiers"
+}
+sflag_eq_ne_body()
+{
+ h_pass "true" -s eq:0
+ h_pass "false" -s ne:0
+ h_pass "exit 255" -s eq:255
+ h_pass "exit 0" -s ne:255
+
+ h_fail "exit 256" -s eq:256
+ h_fail "exit -1" -s eq:-1
+ h_fail "true" -s ne:256
+ h_fail "true" -s ne:-1
+}
+
+atf_test_case sflag_exit
+sflag_exit_head()
+{
+ atf_set "descr" "Tests for the -s option using the 'exit' qualifier"
+}
+sflag_exit_body()
+{
+ h_pass 'true' -s exit:0
+ h_pass 'false' -s not-exit:0
+ h_pass 'exit 255' -s exit:255
+ h_pass 'exit 0' -s not-exit:255
+
+ h_fail 'exit 256' -s exit:256
+ h_fail 'exit -1' -s exit:-1
+ h_fail 'true' -s not-exit:256
+ h_fail 'true' -s not-exit:-1
+
+ h_pass 'true' -s exit
+ h_pass 'false' -s exit
+ if ${Atf_Check} -s exit -x 'kill $$'; then
+ atf_fail "Signal detected as clean exit"
+ fi
+}
+
+atf_test_case sflag_ignore
+sflag_ignore_head()
+{
+ atf_set "descr" "Tests for the -s option using the 'ignore' qualifier"
+}
+sflag_ignore_body()
+{
+ h_pass 'true' -s ignore
+ h_pass 'false' -s ignore
+ if ${Atf_Check} -s ignored -x 'kill $$'; then
+ atf_fail "Signal not ignored"
+ fi
+}
+
+atf_test_case sflag_signal
+sflag_signal_head()
+{
+ atf_set "descr" "Tests for the -s option using the 'signal' qualifier"
+}
+sflag_signal_body()
+{
+ ${Atf_Check} -s signal:hup -x 'kill -1 $$' || atf_fail "Signal not detected"
+ ${Atf_Check} -s signal:sighup -x 'kill -1 $$' || atf_fail "Signal not" \
+ "detected"
+ ${Atf_Check} -s signal:1 -x 'kill -1 $$' || atf_fail "Signal not detected"
+ ${Atf_Check} -s signal -x 'kill -1 $$' || atf_fail "Signal not detected"
+
+ ${Atf_Check} -s not-signal:kill -x 'kill -9 $$' && \
+ atf_fail "not-signal:kill matched kill -9"
+ ${Atf_Check} -s not-signal:kill -x 'kill -1 $$' || \
+ atf_fail "not-signal:kill did not match kill -1"
+
+ h_fail 'true' -s signal
+ h_fail 'false' -s signal
+}
+
+atf_test_case xflag
+xflag_head()
+{
+ atf_set "descr" "Tests for the -x option"
+}
+xflag_body()
+{
+ ${Atf_Check} -s ne:0 -o ignore -e ignore "echo foo 2>&1" || \
+ atf_fail "Shell command succeeded without -x"
+
+ ${Atf_Check} -e inline:"foo\n" -x "echo foo 1>&2" || \
+ atf_fail "Cannot run command with -x"
+
+ ${Atf_Check} -o inline:"foo\n" -x echo foo || \
+ atf_fail "Using -x does not respect all provided arguments"
+}
+
+atf_test_case oflag_empty
+oflag_empty_head()
+{
+ atf_set "descr" "Tests for the -o option using the 'empty' argument"
+}
+oflag_empty_body()
+{
+ h_pass "true" -o empty
+ h_fail "echo foo" -o empty
+}
+
+atf_test_case oflag_ignore
+oflag_ignore_head()
+{
+ atf_set "descr" "Tests for the -o option using the 'ignore' argument"
+}
+oflag_ignore_body()
+{
+ h_pass "true" -o ignore
+ h_pass "echo foo" -o ignore
+}
+
+atf_test_case oflag_file
+oflag_file_head()
+{
+ atf_set "descr" "Tests for the -o option using the 'file:' argument"
+}
+oflag_file_body()
+{
+ touch empty
+ h_pass "true" -o file:empty
+
+ echo foo >text
+ h_pass "echo foo" -o file:text
+ h_fail "echo bar" -o file:text
+
+ dd if=/dev/urandom of=bin bs=1k count=10
+ h_pass "cat bin" -o file:bin
+}
+
+atf_test_case oflag_inline
+oflag_inline_head()
+{
+ atf_set "descr" "Tests for the -o option using the 'inline:' argument"
+}
+oflag_inline_body()
+{
+ h_pass "true" -o inline:
+ h_pass "echo foo bar" -o inline:"foo bar\n"
+ h_pass "printf 'foo bar'" -o inline:"foo bar"
+ h_pass "printf '\t\n\t\n'" -o inline:"\t\n\t\n"
+ h_pass "printf '\a\b\033\f\n\r\t\v'" -o inline:"\a\b\e\f\n\r\t\v"
+ h_pass "printf '\011\022\033\012'" -o inline:"\011\022\033\012"
+
+ h_fail "echo foo bar" -o inline:"foo bar"
+ h_fail "echo -n foo bar" -o inline:"foo bar\n"
+}
+
+atf_test_case oflag_match
+oflag_match_head()
+{
+ atf_set "descr" "Tests for the -o option using the 'match:' argument"
+}
+oflag_match_body()
+{
+ h_pass "printf no-newline" -o "match:^no-newline"
+ h_pass "echo line1; echo foo bar" -o "match:^foo"
+ h_pass "echo foo bar" -o "match:o b"
+ h_fail "echo foo bar" -o "match:baz"
+ h_fail "echo foo bar" -o "match:^bar"
+}
+
+atf_test_case oflag_save
+oflag_save_head()
+{
+ atf_set "descr" "Tests for the -o option using the 'save:' argument"
+}
+oflag_save_body()
+{
+ h_pass "echo foo" -o save:out
+ echo foo >exp
+ cmp -s out exp || atf_fail "Saved output does not match expected results"
+}
+
+atf_test_case oflag_multiple
+oflag_multiple_head()
+{
+ atf_set "descr" "Tests for multiple occurrences of the -o option"
+}
+oflag_multiple_body()
+{
+ h_pass "echo foo bar" -o match:foo -o match:bar
+ h_pass "echo foo; echo bar" -o match:foo -o match:bar
+ h_fail "echo foo baz" -o match:bar -o match:foo
+ h_fail "echo foo; echo baz" -o match:bar -o match:foo
+}
+
+atf_test_case oflag_negated
+oflag_negated_head()
+{
+ atf_set "descr" "Tests for negated occurrences of the -o option"
+}
+oflag_negated_body()
+{
+ h_fail "echo foo" -o empty
+ h_pass "echo foo" -o not-empty
+
+ h_pass "echo foo bar" -o match:foo
+ h_fail "echo foo bar" -o not-match:foo
+}
+
+atf_test_case eflag_empty
+eflag_empty_head()
+{
+ atf_set "descr" "Tests for the -e option using the 'empty' argument"
+}
+eflag_empty_body()
+{
+ h_pass "true 1>&2" -e empty
+ h_fail "echo foo 1>&2" -e empty
+}
+
+atf_test_case eflag_ignore
+eflag_ignore_head()
+{
+ atf_set "descr" "Tests for the -e option using the 'ignore' argument"
+}
+eflag_ignore_body()
+{
+ h_pass "true 1>&2" -e ignore
+ h_pass "echo foo 1>&2" -e ignore
+}
+
+atf_test_case eflag_file
+eflag_file_head()
+{
+ atf_set "descr" "Tests for the -e option using the 'file:' argument"
+}
+eflag_file_body()
+{
+ touch empty
+ h_pass "true 1>&2" -e file:empty
+
+ echo foo >text
+ h_pass "echo foo 1>&2" -e file:text
+ h_fail "echo bar 1>&2" -e file:text
+
+ dd if=/dev/urandom of=bin bs=1k count=10
+ h_pass "cat bin 1>&2" -e file:bin
+}
+
+atf_test_case eflag_inline
+eflag_inline_head()
+{
+ atf_set "descr" "Tests for the -e option using the 'inline:' argument"
+}
+eflag_inline_body()
+{
+ h_pass "true 1>&2" -e inline:
+ h_pass "echo foo bar 1>&2" -e inline:"foo bar\n"
+ h_pass "printf 'foo bar' 1>&2" -e inline:"foo bar"
+ h_pass "printf '\t\n\t\n' 1>&2" -e inline:"\t\n\t\n"
+ h_pass "printf '\a\b\033\f\n\r\t\v' 1>&2" -e inline:"\a\b\e\f\n\r\t\v"
+ h_pass "printf '\011\022\033\012' 1>&2" -e inline:"\011\022\033\012"
+
+ h_fail "echo foo bar 1>&2" -e inline:"foo bar"
+ h_fail "echo -n foo bar 1>&2" -e inline:"foo bar\n"
+}
+
+atf_test_case eflag_save
+eflag_save_head()
+{
+ atf_set "descr" "Tests for the -e option using the 'save:' argument"
+}
+eflag_save_body()
+{
+ h_pass "echo foo 1>&2" -e save:out
+ echo foo >exp
+ cmp -s out exp || atf_fail "Saved output does not match expected results"
+}
+
+atf_test_case eflag_match
+eflag_match_head()
+{
+ atf_set "descr" "Tests for the -e option using the 'match:' argument"
+}
+eflag_match_body()
+{
+ h_pass "printf no-newline 1>&2" -e "match:^no-newline"
+ h_pass "echo line1 1>&2; echo foo bar 1>&2" -e "match:^foo"
+ h_pass "echo foo bar 1>&2" -e "match:o b"
+ h_fail "echo foo bar 1>&2" -e "match:baz"
+ h_fail "echo foo bar 1>&2" -e "match:^bar"
+}
+
+atf_test_case eflag_multiple
+eflag_multiple_head()
+{
+ atf_set "descr" "Tests for multiple occurrences of the -e option"
+}
+eflag_multiple_body()
+{
+ h_pass "echo foo bar 1>&2" -e match:foo -e match:bar
+ h_pass "echo foo 1>&2; echo bar 1>&2" -e match:foo -e match:bar
+ h_fail "echo foo baz 1>&2" -e match:bar -e match:foo
+ h_fail "echo foo 1>&2; echo baz 1>&2" -e match:bar -e match:foo
+}
+
+atf_test_case eflag_negated
+eflag_negated_head()
+{
+ atf_set "descr" "Tests for negated occurrences of the -e option"
+}
+eflag_negated_body()
+{
+ h_fail "echo foo 1>&2" -e empty
+ h_pass "echo foo 1>&2" -e not-empty
+
+ h_pass "echo foo bar 1>&2" -e match:foo
+ h_fail "echo foo bar 1>&2" -e not-match:foo
+}
+
+atf_test_case stdin
+stdin_head()
+{
+ atf_set "descr" "Tests that stdin is preserved"
+}
+stdin_body()
+{
+ echo "hello" | ${Atf_Check} -o match:"hello" cat || \
+ atf_fail "atf-check does not seem to respect stdin"
+}
+
+atf_test_case invalid_umask
+invalid_umask_head()
+{
+ atf_set "descr" "Tests for a correct error condition if the umask is" \
+ "too restrictive"
+}
+invalid_umask_body()
+{
+ umask 0222
+ ${Atf_Check} false 2>stderr && \
+ atf_fail "atf-check returned 0 but it should have failed"
+ cat stderr
+ grep 'temporary.*current umask.*0222' stderr >/dev/null || \
+ atf_fail "atf-check did not report an error related to the" \
+ "current umask"
+}
+
+atf_init_test_cases()
+{
+ atf_add_test_case sflag_eq_ne
+ atf_add_test_case sflag_exit
+ atf_add_test_case sflag_ignore
+ atf_add_test_case sflag_signal
+
+ atf_add_test_case xflag
+
+ atf_add_test_case oflag_empty
+ atf_add_test_case oflag_ignore
+ atf_add_test_case oflag_file
+ atf_add_test_case oflag_inline
+ atf_add_test_case oflag_match
+ atf_add_test_case oflag_save
+ atf_add_test_case oflag_multiple
+ atf_add_test_case oflag_negated
+
+ atf_add_test_case eflag_empty
+ atf_add_test_case eflag_ignore
+ atf_add_test_case eflag_file
+ atf_add_test_case eflag_inline
+ atf_add_test_case eflag_match
+ atf_add_test_case eflag_save
+ atf_add_test_case eflag_multiple
+ atf_add_test_case eflag_negated
+
+ atf_add_test_case stdin
+
+ atf_add_test_case invalid_umask
+}
+
+# vim: syntax=sh:expandtab:shiftwidth=4:softtabstop=4
diff --git a/unit/atf-src/atf-sh/atf-sh.1 b/unit/atf-src/atf-sh/atf-sh.1
new file mode 100644
index 0000000..e7eaff8
--- /dev/null
+++ b/unit/atf-src/atf-sh/atf-sh.1
@@ -0,0 +1,102 @@
+.\" Copyright (c) 2010 The NetBSD Foundation, Inc.
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
+.\" CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+.\" INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+.\" MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+.\" IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
+.\" DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+.\" GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+.\" INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+.\" IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+.\" OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+.\" IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+.Dd September 27, 2014
+.Dt ATF-SH 1
+.Os
+.Sh NAME
+.Nm atf-sh
+.Op Fl s Ar shell
+.Nd interpreter for shell-based test programs
+.Sh SYNOPSIS
+.Nm
+.Ar script
+.Sh DESCRIPTION
+.Nm
+is an interpreter that runs the test program given in
+.Ar script
+after loading the
+.Xr atf-sh 3
+library.
+.Pp
+.Nm
+is not a real interpreter though: it is just a wrapper around
+the system-wide shell defined by
+.Va ATF_SHELL .
+.Nm
+executes the interpreter, loads the
+.Xr atf-sh 3
+library and then runs the script.
+You must consider
+.Nm atf-sh
+to be a POSIX shell by default and thus should not use any non-standard
+extensions.
+.Pp
+The following options are available:
+.Bl -tag -width XsXshellXXX
+.It Fl s Ar shell
+Specifies the shell to use instead of the value provided by
+.Va ATF_SHELL .
+.El
+.Sh ENVIRONMENT
+.Bl -tag -width ATFXLIBEXECDIRXX -compact
+.It Va ATF_LIBEXECDIR
+Overrides the builtin directory where
+.Nm
+is located.
+Should not be overridden other than for testing purposes.
+.It Va ATF_PKGDATADIR
+Overrides the builtin directory where
+.Pa libatf-sh.subr
+is located.
+Should not be overridden other than for testing purposes.
+.It Va ATF_SHELL
+Path to the system shell to be used in the generated scripts.
+Scripts must not rely on this variable being set to select a specific
+interpreter.
+.El
+.Sh EXAMPLES
+Scripts using
+.Xr atf-sh 3
+should start with:
+.Bd -literal -offset indent
+#! /usr/bin/env atf-sh
+.Ed
+.Pp
+Alternatively, if you want to explicitly choose a shell interpreter, you cannot
+rely on
+.Xr env 1
+to find
+.Nm .
+Instead, you have to hardcode the path to
+.Nm
+in the script and then use the
+.Fl s
+option afterwards as a
+.Em single parameter :
+.Bd -literal -offset indent
+#! /path/to/bin/atf-sh -s/bin/bash
+.Ed
+.Sh SEE ALSO
+.Xr atf-sh 3
diff --git a/unit/atf-src/atf-sh/atf-sh.3 b/unit/atf-src/atf-sh/atf-sh.3
new file mode 100644
index 0000000..be56539
--- /dev/null
+++ b/unit/atf-src/atf-sh/atf-sh.3
@@ -0,0 +1,372 @@
+.\" Copyright (c) 2008 The NetBSD Foundation, Inc.
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
+.\" CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+.\" INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+.\" MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+.\" IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
+.\" DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+.\" GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+.\" INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+.\" IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+.\" OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+.\" IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+.Dd October 13, 2014
+.Dt ATF-SH 3
+.Os
+.Sh NAME
+.Nm atf_add_test_case ,
+.Nm atf_check ,
+.Nm atf_check_equal ,
+.Nm atf_config_get ,
+.Nm atf_config_has ,
+.Nm atf_expect_death ,
+.Nm atf_expect_exit ,
+.Nm atf_expect_fail ,
+.Nm atf_expect_pass ,
+.Nm atf_expect_signal ,
+.Nm atf_expect_timeout ,
+.Nm atf_fail ,
+.Nm atf_get ,
+.Nm atf_get_srcdir ,
+.Nm atf_pass ,
+.Nm atf_require_prog ,
+.Nm atf_set ,
+.Nm atf_skip ,
+.Nm atf_test_case
+.Nd POSIX shell API to write ATF-based test programs
+.Sh SYNOPSIS
+.Nm atf_add_test_case
+.Qq name
+.Nm atf_check
+.Qq command
+.Nm atf_check_equal
+.Qq expected_expression
+.Qq actual_expression
+.Nm atf_config_get
+.Qq var_name
+.Nm atf_config_has
+.Qq var_name
+.Nm atf_expect_death
+.Qq reason
+.Qq ...
+.Nm atf_expect_exit
+.Qq exitcode
+.Qq reason
+.Qq ...
+.Nm atf_expect_fail
+.Qq reason
+.Qq ...
+.Nm atf_expect_pass
+.Qq
+.Nm atf_expect_signal
+.Qq signo
+.Qq reason
+.Qq ...
+.Nm atf_expect_timeout
+.Qq reason
+.Qq ...
+.Nm atf_fail
+.Qq reason
+.Nm atf_get
+.Qq var_name
+.Nm atf_get_srcdir
+.Nm atf_pass
+.Nm atf_require_prog
+.Qq prog_name
+.Nm atf_set
+.Qq var_name
+.Qq value
+.Nm atf_skip
+.Qq reason
+.Nm atf_test_case
+.Qq name
+.Qq cleanup
+.Sh DESCRIPTION
+ATF
+provides a simple but powerful interface to easily write test programs in
+the POSIX shell language.
+These are extremely helpful given that they are trivial to write due to the
+language simplicity and the great deal of available external tools, so they
+are often ideal to test other applications at the user level.
+.Pp
+Test programs written using this library must be run using the
+.Xr atf-sh 1
+interpreter by putting the following on their very first line:
+.Bd -literal -offset indent
+#! /usr/bin/env atf-sh
+.Ed
+.Pp
+Shell-based test programs always follow this template:
+.Bd -literal -offset indent
+atf_test_case tc1
+tc1_head() {
+ ... first test case's header ...
+}
+tc1_body() {
+ ... first test case's body ...
+}
+
+atf_test_case tc2 cleanup
+tc2_head() {
+ ... second test case's header ...
+}
+tc2_body() {
+ ... second test case's body ...
+}
+tc2_cleanup() {
+ ... second test case's cleanup ...
+}
+
+.Ns ... additional test cases ...
+
+atf_init_test_cases() {
+ atf_add_test_case tc1
+ atf_add_test_case tc2
+ ... add additional test cases ...
+}
+.Ed
+.Ss Definition of test cases
+Test cases have an identifier and are composed of three different parts:
+the header, the body and an optional cleanup routine, all of which are
+described in
+.Xr atf-test-case 4 .
+To define test cases, one can use the
+.Nm atf_test_case
+function, which takes a first parameter specifiying the test case's
+name and instructs the library to set things up to accept it as a valid
+test case.
+The second parameter is optional and, if provided, must be
+.Sq cleanup ;
+providing this parameter allows defining a cleanup routine for the test
+case.
+It is important to note that this function
+.Em does not
+set the test case up for execution when the program is run.
+In order to do so, a later registration is needed through the
+.Nm atf_add_test_case
+function detailed in
+.Sx Program initialization .
+.Pp
+Later on, one must define the three parts of the body by providing two
+or three functions (remember that the cleanup routine is optional).
+These functions are named after the test case's identifier, and are
+.Nm \*(Ltid\*(Gt_head ,
+.Nm \*(Ltid\*(Gt_body
+and
+.Nm \*(Ltid\*(Gt_cleanup .
+None of these take parameters when executed.
+.Ss Program initialization
+The test program must define an
+.Nm atf_init_test_cases
+function, which is in charge of registering the test cases that will be
+executed at run time by using the
+.Nm atf_add_test_case
+function, which takes the name of a test case as its single parameter.
+This main function should not do anything else, except maybe sourcing
+auxiliary source files that define extra variables and functions.
+.Ss Configuration variables
+The test case has read-only access to the current configuration variables
+through the
+.Nm atf_config_has
+and
+.Nm atf_config_get
+methods.
+The former takes a single parameter specifying a variable name and returns
+a boolean indicating whether the variable is defined or not.
+The latter can take one or two parameters.
+If it takes only one, it specifies the variable from which to get the
+value, and this variable must be defined.
+If it takes two, the second one specifies a default value to be returned
+if the variable is not available.
+.Ss Access to the source directory
+It is possible to get the path to the test case's source directory from
+anywhere in the test program by using the
+.Nm atf_get_srcdir
+function.
+It is interesting to note that this can be used inside
+.Nm atf_init_test_cases
+to silently include additional helper files from the source directory.
+.Ss Requiring programs
+Aside from the
+.Va require.progs
+meta-data variable available in the header only, one can also check for
+additional programs in the test case's body by using the
+.Nm atf_require_prog
+function, which takes the base name or full path of a single binary.
+Relative paths are forbidden.
+If it is not found, the test case will be automatically skipped.
+.Ss Test case finalization
+The test case finalizes either when the body reaches its end, at which
+point the test is assumed to have
+.Em passed ,
+or at any explicit call to
+.Nm atf_pass ,
+.Nm atf_fail
+or
+.Nm atf_skip .
+These three functions terminate the execution of the test case immediately.
+The cleanup routine will be processed afterwards in a completely automated
+way, regardless of the test case's termination reason.
+.Pp
+.Nm atf_pass
+does not take any parameters.
+.Nm atf_fail
+and
+.Nm atf_skip
+take a single string parameter that describes why the test case failed or
+was skipped, respectively.
+It is very important to provide a clear error message in both cases so that
+the user can quickly know why the test did not pass.
+.Ss Expectations
+Everything explained in the previous section changes when the test case
+expectations are redefined by the programmer.
+.Pp
+Each test case has an internal state called
+.Sq expect
+that describes what the test case expectations are at any point in time.
+The value of this property can change during execution by any of:
+.Bl -tag -width indent
+.It Nm atf_expect_death Qo reason Qc Qo ... Qc
+Expects the test case to exit prematurely regardless of the nature of the
+exit.
+.It Nm atf_expect_exit Qo exitcode Qc Qo reason Qc Qo ... Qc
+Expects the test case to exit cleanly.
+If
+.Va exitcode
+is not
+.Sq -1 ,
+the runtime engine will validate that the exit code of the test case
+matches the one provided in this call.
+Otherwise, the exact value will be ignored.
+.It Nm atf_expect_fail Qo reason Qc
+Any failure raised in this mode is recorded, but such failures do not report
+the test case as failed; instead, the test case finalizes cleanly and is
+reported as
+.Sq expected failure ;
+this report includes the provided
+.Fa reason
+as part of it.
+If no error is raised while running in this mode, then the test case is
+reported as
+.Sq failed .
+.Pp
+This mode is useful to reproduce actual known bugs in tests.
+Whenever the developer fixes the bug later on, the test case will start
+reporting a failure, signaling the developer that the test case must be
+adjusted to the new conditions.
+In this situation, it is useful, for example, to set
+.Fa reason
+as the bug number for tracking purposes.
+.It Nm atf_expect_pass
+This is the normal mode of execution.
+In this mode, any failure is reported as such to the user and the test case
+is marked as
+.Sq failed .
+.It Nm atf_expect_signal Qo signo Qc Qo reason Qc Qo ... Qc
+Expects the test case to terminate due to the reception of a signal.
+If
+.Va signo
+is not
+.Sq -1 ,
+the runtime engine will validate that the signal that terminated the test
+case matches the one provided in this call.
+Otherwise, the exact value will be ignored.
+.It Nm atf_expect_timeout Qo reason Qc Qo ... Qc
+Expects the test case to execute for longer than its timeout.
+.El
+.Ss Helper functions for common checks
+.Bl -tag -width indent
+.It Nm atf_check Qo [options] Qc Qo command Qc Qo [args] Qc
+Executes a command, performs checks on its exit code and its output, and
+fails the test case if any of the checks is not successful.
+This function is particularly useful in integration tests that verify the
+correct functioning of a binary.
+.Pp
+Internally, this function is just a wrapper over the
+.Xr atf-check 1
+tool (whose manual page provides all details on the calling syntax).
+You should always use the
+.Nm atf_check
+function instead of the
+.Xr atf-check 1
+tool in your scripts; the latter is not even in the path.
+.It Nm atf_check_equal Qo expected_expression Qc Qo actual_expression Qc
+This function takes two expressions, evaluates them and, if their
+results differ, aborts the test case with an appropriate failure message.
+The common style is to put the expected value in the first parameter and the
+actual value in the second parameter.
+.El
+.Sh EXAMPLES
+The following shows a complete test program with a single test case that
+validates the addition operator:
+.Bd -literal -offset indent
+atf_test_case addition
+addition_head() {
+ atf_set "descr" "Sample tests for the addition operator"
+}
+addition_body() {
+ atf_check_equal 0 $((0 + 0))
+ atf_check_equal 1 $((0 + 1))
+ atf_check_equal 1 $((1 + 0))
+
+ atf_check_equal 2 $((1 + 1))
+
+ atf_check_equal 300 $((100 + 200))
+}
+
+atf_init_test_cases() {
+ atf_add_test_case addition
+}
+.Ed
+.Pp
+This other example shows how to include a file with extra helper functions
+in the test program:
+.Bd -literal -offset indent
+.Ns ... definition of test cases ...
+
+atf_init_test_cases() {
+ . $(atf_get_srcdir)/helper_functions.sh
+
+ atf_add_test_case foo1
+ atf_add_test_case foo2
+}
+.Ed
+.Pp
+This example demonstrates the use of the very useful
+.Nm atf_check
+function:
+.Bd -literal -offset indent
+# Check for silent output
+atf_check -s exit:0 -o empty -e empty 'true'
+
+# Check for silent output and failure
+atf_check -s exit:1 -o empty -e empty 'false'
+
+# Check for known stdout and silent stderr
+echo foo >expout
+atf_check -s exit:0 -o file:expout -e empty 'echo foo'
+
+# Generate a file for later inspection
+atf_check -s exit:0 -o save:stdout -e empty 'ls'
+grep foo ls || atf_fail "foo file not found in listing"
+
+# Or just do the match along the way
+atf_check -s exit:0 -o match:"^foo$" -e empty 'ls'
+.Ed
+.Sh SEE ALSO
+.Xr atf-check 1 ,
+.Xr atf-sh 1 ,
+.Xr atf-test-program 1 ,
+.Xr atf-test-case 4
diff --git a/unit/atf-src/atf-sh/atf-sh.cpp b/unit/atf-src/atf-sh/atf-sh.cpp
new file mode 100644
index 0000000..9975573
--- /dev/null
+++ b/unit/atf-src/atf-sh/atf-sh.cpp
@@ -0,0 +1,185 @@
+// Copyright (c) 2010 The NetBSD Foundation, Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
+// CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+// IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
+// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+// IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+extern "C" {
+#include <unistd.h>
+}
+
+#include <cerrno>
+#include <cstdlib>
+#include <cstring>
+#include <iostream>
+
+#include "atf-c++/detail/application.hpp"
+#include "atf-c++/detail/env.hpp"
+#include "atf-c++/detail/fs.hpp"
+#include "atf-c++/detail/sanity.hpp"
+
+// ------------------------------------------------------------------------
+// Auxiliary functions.
+// ------------------------------------------------------------------------
+
+namespace {
+
+static
+std::string
+fix_plain_name(const char *filename)
+{
+ const atf::fs::path filepath(filename);
+ if (filepath.branch_path().str() == ".")
+ return std::string("./") + filename;
+ else
+ return std::string(filename);
+}
+
+static
+std::string*
+construct_script(const char* filename)
+{
+ const std::string libexecdir = atf::env::get(
+ "ATF_LIBEXECDIR", ATF_LIBEXECDIR);
+ const std::string pkgdatadir = atf::env::get(
+ "ATF_PKGDATADIR", ATF_PKGDATADIR);
+ const std::string shell = atf::env::get("ATF_SHELL", ATF_SHELL);
+
+ std::string* command = new std::string();
+ command->reserve(512);
+ (*command) += ("Atf_Check='" + libexecdir + "/atf-check' ; " +
+ "Atf_Shell='" + shell + "' ; " +
+ ". " + pkgdatadir + "/libatf-sh.subr ; " +
+ ". " + fix_plain_name(filename) + " ; " +
+ "main \"${@}\"");
+ return command;
+}
+
+static
+const char**
+construct_argv(const std::string& shell, const int interpreter_argc,
+ const char* const* interpreter_argv)
+{
+ PRE(interpreter_argc >= 1);
+ PRE(interpreter_argv[0] != NULL);
+
+ const std::string* script = construct_script(interpreter_argv[0]);
+
+ const int count = 4 + (interpreter_argc - 1) + 1;
+ const char** argv = new const char*[count];
+ argv[0] = shell.c_str();
+ argv[1] = "-c";
+ argv[2] = script->c_str();
+ argv[3] = interpreter_argv[0];
+
+ for (int i = 1; i < interpreter_argc; i++)
+ argv[4 + i - 1] = interpreter_argv[i];
+
+ argv[count - 1] = NULL;
+
+ return argv;
+}
+
+} // anonymous namespace
+
+// ------------------------------------------------------------------------
+// The "atf_sh" class.
+// ------------------------------------------------------------------------
+
+class atf_sh : public atf::application::app {
+ static const char* m_description;
+
+ atf::fs::path m_shell;
+
+ options_set specific_options(void) const;
+ void process_option(int, const char*);
+
+public:
+ atf_sh(void);
+
+ int main(void);
+};
+
+const char* atf_sh::m_description =
+ "atf-sh is a shell interpreter that extends the functionality of the "
+ "system sh(1) with the atf-sh library.";
+
+atf_sh::atf_sh(void) :
+ app(m_description, "atf-sh(1)"),
+ m_shell(atf::fs::path(atf::env::get("ATF_SHELL", ATF_SHELL)))
+{
+}
+
+atf_sh::options_set
+atf_sh::specific_options(void)
+ const
+{
+ using atf::application::option;
+ options_set opts;
+
+ INV(m_shell == atf::fs::path(atf::env::get("ATF_SHELL", ATF_SHELL)));
+ opts.insert(option('s', "shell", "Path to the shell interpreter to use; "
+ "default: " + m_shell.str()));
+
+ return opts;
+}
+
+void
+atf_sh::process_option(int ch, const char* arg)
+{
+ switch (ch) {
+ case 's':
+ m_shell = atf::fs::path(arg);
+ break;
+
+ default:
+ UNREACHABLE;
+ }
+}
+
+int
+atf_sh::main(void)
+{
+ if (m_argc < 1)
+ throw atf::application::usage_error("No test program provided");
+
+ const atf::fs::path script(m_argv[0]);
+ if (!atf::fs::exists(script))
+ throw std::runtime_error("The test program '" + script.str() + "' "
+ "does not exist");
+
+ const char** argv = construct_argv(m_shell.str(), m_argc, m_argv);
+ // Don't bother keeping track of the memory allocated by construct_argv:
+ // we are going to exec or die immediately.
+
+ const int ret = execv(m_shell.c_str(), const_cast< char** >(argv));
+ INV(ret == -1);
+ std::cerr << "Failed to execute " << m_shell.str() << ": "
+ << std::strerror(errno) << "\n";
+ return EXIT_FAILURE;
+}
+
+int
+main(int argc, char* const* argv)
+{
+ return atf_sh().run(argc, argv);
+}
diff --git a/unit/atf-src/atf-sh/atf-sh.m4 b/unit/atf-src/atf-sh/atf-sh.m4
new file mode 100644
index 0000000..754f502
--- /dev/null
+++ b/unit/atf-src/atf-sh/atf-sh.m4
@@ -0,0 +1,49 @@
+dnl Copyright 2011 Google Inc.
+dnl All rights reserved.
+dnl
+dnl Redistribution and use in source and binary forms, with or without
+dnl modification, are permitted provided that the following conditions are
+dnl met:
+dnl
+dnl * Redistributions of source code must retain the above copyright
+dnl notice, this list of conditions and the following disclaimer.
+dnl * Redistributions in binary form must reproduce the above copyright
+dnl notice, this list of conditions and the following disclaimer in the
+dnl documentation and/or other materials provided with the distribution.
+dnl * Neither the name of Google Inc. nor the names of its contributors
+dnl may be used to endorse or promote products derived from this software
+dnl without specific prior written permission.
+dnl
+dnl THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+dnl "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+dnl LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+dnl A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+dnl OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+dnl SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+dnl LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+dnl DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+dnl THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+dnl (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+dnl OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+dnl ATF_CHECK_SH([version-spec])
+dnl
+dnl Checks if atf-sh is present. If version-spec is provided, ensures that
+dnl the installed version of atf-sh matches the required version. This
+dnl argument must be something like '>= 0.14' and accepts any version
+dnl specification supported by pkg-config.
+dnl
+dnl Defines and substitutes ATF_SH with the full path to the atf-sh interpreter.
+AC_DEFUN([ATF_CHECK_SH], [
+ spec="atf-sh[]m4_default_nblank([ $1], [])"
+ _ATF_CHECK_ARG_WITH(
+ [AC_MSG_CHECKING([for ${spec}])
+ PKG_CHECK_EXISTS([${spec}], [found=yes], [found=no])
+ if test "${found}" = yes; then
+ ATF_SH="$(${PKG_CONFIG} --variable=interpreter atf-sh)"
+ AC_SUBST([ATF_SH], [${ATF_SH}])
+ found_atf_sh=yes
+ fi
+ AC_MSG_RESULT([${ATF_SH}])],
+ [required ${spec} not found])
+])
diff --git a/unit/atf-src/atf-sh/atf-sh.pc.in b/unit/atf-src/atf-sh/atf-sh.pc.in
new file mode 100644
index 0000000..930dc4c
--- /dev/null
+++ b/unit/atf-src/atf-sh/atf-sh.pc.in
@@ -0,0 +1,8 @@
+# ATF pkg-config file
+
+exec_prefix=__EXEC_PREFIX__
+interpreter=${exec_prefix}/bin/atf-sh
+
+Name: atf-sh
+Description: Automated Testing Framework (POSIX shell binding)
+Version: __ATF_VERSION__
diff --git a/unit/atf-src/atf-sh/atf_check_test.sh b/unit/atf-src/atf-sh/atf_check_test.sh
new file mode 100644
index 0000000..163e905
--- /dev/null
+++ b/unit/atf-src/atf-sh/atf_check_test.sh
@@ -0,0 +1,193 @@
+# Copyright (c) 2007 The NetBSD Foundation, Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
+# CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+# IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
+# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+# GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+# IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+# IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+# TODO: Bring in the checks in the bootstrap testsuite for atf_check.
+
+atf_test_case info_ok
+info_ok_head()
+{
+ atf_set "descr" "Verifies that atf_check prints an informative" \
+ "message even when the command is successful"
+}
+info_ok_body()
+{
+ h="$(atf_get_srcdir)/misc_helpers -s $(atf_get_srcdir)"
+
+ atf_check -s eq:0 -o save:stdout -e save:stderr -x \
+ "${h} atf_check_info_ok"
+ grep 'Executing command.*true' stdout >/dev/null || \
+ atf_fail "atf_check does not print an informative message"
+
+ atf_check -s eq:0 -o save:stdout -e save:stderr -x \
+ "${h} atf_check_info_fail"
+ grep 'Executing command.*false' stdout >/dev/null || \
+ atf_fail "atf_check does not print an informative message"
+}
+
+atf_test_case expout_mismatch
+expout_mismatch_head()
+{
+ atf_set "descr" "Verifies that atf_check prints a diff of the" \
+ "stdout and the expected stdout if the two do not" \
+ "match"
+}
+expout_mismatch_body()
+{
+ h="$(atf_get_srcdir)/misc_helpers -s $(atf_get_srcdir)"
+
+ atf_check -s eq:1 -o save:stdout -e save:stderr -x \
+ "${h} atf_check_expout_mismatch"
+ grep 'Executing command.*echo bar' stdout >/dev/null || \
+ atf_fail "atf_check does not print an informative message"
+ grep 'stdout does not match golden output' stderr >/dev/null || \
+ atf_fail "atf_check does not print the stdout header"
+ grep 'stderr' stderr >/dev/null && \
+ atf_fail "atf_check prints the stderr header"
+ grep '^-foo' stderr >/dev/null || \
+ atf_fail "atf_check does not print the stdout's diff"
+ grep '^+bar' stderr >/dev/null || \
+ atf_fail "atf_check does not print the stdout's diff"
+}
+
+atf_test_case experr_mismatch
+experr_mismatch_head()
+{
+ atf_set "descr" "Verifies that atf_check prints a diff of the" \
+ "stderr and the expected stderr if the two do not" \
+ "match"
+}
+experr_mismatch_body()
+{
+ h="$(atf_get_srcdir)/misc_helpers -s $(atf_get_srcdir)"
+
+ atf_check -s eq:1 -o save:stdout -e save:stderr -x \
+ "${h} atf_check_experr_mismatch"
+ grep 'Executing command.*echo bar' stdout >/dev/null || \
+ atf_fail "atf_check does not print an informative message"
+ grep 'stdout' stderr >/dev/null && \
+ atf_fail "atf_check prints the stdout header"
+ grep 'stderr does not match golden output' stderr >/dev/null || \
+ atf_fail "atf_check does not print the stderr header"
+ grep '^-foo' stderr >/dev/null || \
+ atf_fail "atf_check does not print the stderr's diff"
+ grep '^+bar' stderr >/dev/null || \
+ atf_fail "atf_check does not print the stderr's diff"
+}
+
+atf_test_case null_stdout
+null_stdout_head()
+{
+ atf_set "descr" "Verifies that atf_check prints a the stdout it got" \
+ "when it was supposed to be null"
+}
+null_stdout_body()
+{
+ h="$(atf_get_srcdir)/misc_helpers -s $(atf_get_srcdir)"
+
+ atf_check -s eq:1 -o save:stdout -e save:stderr -x \
+ "${h} atf_check_null_stdout"
+ grep 'Executing command.*echo.*These.*contents' stdout >/dev/null || \
+ atf_fail "atf_check does not print an informative message"
+ grep 'stdout not empty' stderr >/dev/null || \
+ atf_fail "atf_check does not print the stdout header"
+ grep 'stderr' stderr >/dev/null && \
+ atf_fail "atf_check prints the stderr header"
+ grep 'These are the contents' stderr >/dev/null || \
+ atf_fail "atf_check does not print stdout's contents"
+}
+
+atf_test_case null_stderr
+null_stderr_head()
+{
+ atf_set "descr" "Verifies that atf_check prints a the stderr it got" \
+ "when it was supposed to be null"
+}
+null_stderr_body()
+{
+ h="$(atf_get_srcdir)/misc_helpers -s $(atf_get_srcdir)"
+
+ atf_check -s eq:1 -o save:stdout -e save:stderr -x \
+ "${h} atf_check_null_stderr"
+ grep 'Executing command.*echo.*These.*contents' stdout >/dev/null || \
+ atf_fail "atf_check does not print an informative message"
+ grep 'stdout' stderr >/dev/null && \
+ atf_fail "atf_check prints the stdout header"
+ grep 'stderr not empty' stderr >/dev/null || \
+ atf_fail "atf_check does not print the stderr header"
+ grep 'These are the contents' stderr >/dev/null || \
+ atf_fail "atf_check does not print stderr's contents"
+}
+
+atf_test_case equal
+equal_head()
+{
+ atf_set "descr" "Verifies that atf_check_equal works"
+}
+equal_body()
+{
+ h="$(atf_get_srcdir)/misc_helpers -s $(atf_get_srcdir)"
+
+ atf_check -s eq:0 -o ignore -e ignore -x "${h} atf_check_equal_ok"
+
+ atf_check -s eq:1 -o ignore -e ignore -x \
+ "${h} -r resfile atf_check_equal_fail"
+ atf_check -s eq:0 -o ignore -e empty grep '^failed: a != b (a != b)$' \
+ resfile
+
+ atf_check -s eq:0 -o ignore -e ignore -x "${h} atf_check_equal_eval_ok"
+
+ atf_check -s eq:1 -o ignore -e ignore -x \
+ "${h} -r resfile atf_check_equal_eval_fail"
+ atf_check -s eq:0 -o ignore -e empty \
+ grep '^failed: \${x} != \${y} (a != b)$' resfile
+}
+
+atf_test_case flush_stdout_on_timeout
+flush_stdout_on_timeout_body()
+{
+ "$(atf_get_srcdir)/misc_helpers" -s "$(atf_get_srcdir)" atf_check_timeout \
+ >out 2>err &
+ pid="${!}"
+ sleep 1
+ kill "${pid}"
+
+ grep 'Executing command.*true' out \
+ || atf_fail 'First command not in output'
+ grep 'Executing command.*sleep 42' out \
+ || atf_fail 'Second command not in output'
+}
+
+atf_init_test_cases()
+{
+ atf_add_test_case info_ok
+ atf_add_test_case expout_mismatch
+ atf_add_test_case experr_mismatch
+ atf_add_test_case null_stdout
+ atf_add_test_case null_stderr
+ atf_add_test_case equal
+ atf_add_test_case flush_stdout_on_timeout
+}
+
+# vim: syntax=sh:expandtab:shiftwidth=4:softtabstop=4
diff --git a/unit/atf-src/atf-sh/config_test.sh b/unit/atf-src/atf-sh/config_test.sh
new file mode 100644
index 0000000..048834c
--- /dev/null
+++ b/unit/atf-src/atf-sh/config_test.sh
@@ -0,0 +1,79 @@
+# Copyright (c) 2007 The NetBSD Foundation, Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
+# CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+# IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
+# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+# GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+# IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+# IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+atf_test_case has
+has_head()
+{
+ atf_set "descr" "Verifies that atf_config_has works"
+}
+has_body()
+{
+ h="$(atf_get_srcdir)/misc_helpers -s $(atf_get_srcdir)"
+
+ atf_check -s eq:0 -o match:'foo not found' -e ignore -x \
+ "TEST_VARIABLE=foo ${h} config_has"
+
+ atf_check -s eq:0 -o match:'foo found' -e ignore -x \
+ "TEST_VARIABLE=foo ${h} -v foo=bar config_has"
+
+ echo "Checking for deprecated variables"
+ atf_check -s eq:0 -o match:'workdir not found' -e ignore -x \
+ "TEST_VARIABLE=workdir ${h} config_has"
+}
+
+atf_test_case get
+get_head()
+{
+ atf_set "descr" "Verifies that atf_config_get works"
+}
+get_body()
+{
+ h="$(atf_get_srcdir)/misc_helpers -s $(atf_get_srcdir)"
+
+ echo "Querying an undefined variable"
+ ( atf_config_get "undefined" ) >out 2>err && \
+ atf_fail "Getting an undefined variable succeeded"
+ grep 'not find' err || \
+ atf_fail "Getting an undefined variable did not report an error"
+
+ echo "Querying an undefined variable using a default value"
+ v=$(atf_config_get "undefined" "the default value")
+ [ "${v}" = "the default value" ] || \
+ atf_fail "Default value does not work"
+
+ atf_check -s eq:0 -o match:'foo = bar' -e ignore -x \
+ "TEST_VARIABLE=foo ${h} -v foo=bar config_get"
+
+ atf_check -s eq:0 -o match:'foo = baz' -e ignore -x \
+ "TEST_VARIABLE=foo ${h} -v foo=baz config_get"
+}
+
+atf_init_test_cases()
+{
+ atf_add_test_case has
+ atf_add_test_case get
+}
+
+# vim: syntax=sh:expandtab:shiftwidth=4:softtabstop=4
diff --git a/unit/atf-src/atf-sh/integration_test.sh b/unit/atf-src/atf-sh/integration_test.sh
new file mode 100644
index 0000000..1150966
--- /dev/null
+++ b/unit/atf-src/atf-sh/integration_test.sh
@@ -0,0 +1,160 @@
+# Copyright (c) 2010 The NetBSD Foundation, Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
+# CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+# IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
+# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+# GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+# IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+# IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+: ${ATF_SH:="__ATF_SH__"}
+
+create_test_program() {
+ local output="${1}"; shift
+ echo "#! ${ATF_SH} ${*}" >"${output}"
+ cat >>"${output}"
+ chmod +x "${output}"
+}
+
+atf_test_case no_args
+no_args_body()
+{
+ cat >experr <<EOF
+atf-sh: ERROR: No test program provided
+atf-sh: See atf-sh(1) for usage details.
+EOF
+ atf_check -s eq:1 -o ignore -e file:experr "${ATF_SH}"
+}
+
+atf_test_case missing_script
+missing_script_body()
+{
+ cat >experr <<EOF
+atf-sh: ERROR: The test program 'non-existent' does not exist
+EOF
+ atf_check -s eq:1 -o ignore -e file:experr "${ATF_SH}" non-existent
+}
+
+atf_test_case arguments
+arguments_body()
+{
+ create_test_program tp <<EOF
+main() {
+ echo ">>>\${0}<<<"
+ while test \${#} -gt 0; do
+ echo ">>>\${1}<<<"
+ shift
+ done
+ true
+}
+EOF
+
+ cat >expout <<EOF
+>>>./tp<<<
+>>> a b <<<
+>>>foo<<<
+EOF
+ atf_check -s eq:0 -o file:expout -e empty ./tp ' a b ' foo
+
+ cat >expout <<EOF
+>>>tp<<<
+>>> hello bye <<<
+>>>foo bar<<<
+EOF
+ atf_check -s eq:0 -o file:expout -e empty "${ATF_SH}" tp \
+ ' hello bye ' 'foo bar'
+}
+
+atf_test_case custom_shell__command_line
+custom_shell__command_line_body()
+{
+ cat >expout <<EOF
+This is the custom shell
+This is the test program
+EOF
+
+ cat >custom-shell <<EOF
+#! /bin/sh
+echo "This is the custom shell"
+exec /bin/sh "\${@}"
+EOF
+ chmod +x custom-shell
+
+ echo 'main() { echo "This is the test program"; }' | create_test_program tp
+ atf_check -s eq:0 -o file:expout -e empty "${ATF_SH}" -s ./custom-shell tp
+}
+
+atf_test_case custom_shell__shebang
+custom_shell__shebang_body()
+{
+ cat >expout <<EOF
+This is the custom shell
+This is the test program
+EOF
+
+ cat >custom-shell <<EOF
+#! /bin/sh
+echo "This is the custom shell"
+exec /bin/sh "\${@}"
+EOF
+ chmod +x custom-shell
+
+ echo 'main() { echo "This is the test program"; }' | create_test_program \
+ tp "-s$(pwd)/custom-shell"
+ atf_check -s eq:0 -o file:expout -e empty ./tp
+}
+
+atf_test_case set_e
+set_e_head()
+{
+ atf_set "descr" "Simple test to validate that atf-sh works even when" \
+ "set -e is enabled"
+}
+set_e_body()
+{
+ cat >custom-shell <<EOF
+#! /bin/sh
+exec /bin/sh -e "\${@}"
+EOF
+ chmod +x custom-shell
+
+ cat >tp <<EOF
+atf_test_case helper
+helper_body() {
+ atf_skip "reached"
+}
+atf_init_test_cases() {
+ atf_add_test_case helper
+}
+EOF
+ atf_check -s eq:0 -o match:skipped.*reached \
+ "${ATF_SH}" -s ./custom-shell tp helper
+}
+
+atf_init_test_cases()
+{
+ atf_add_test_case no_args
+ atf_add_test_case missing_script
+ atf_add_test_case arguments
+ atf_add_test_case custom_shell__command_line
+ atf_add_test_case custom_shell__shebang
+ atf_add_test_case set_e
+}
+
+# vim: syntax=sh:expandtab:shiftwidth=4:softtabstop=4
diff --git a/unit/atf-src/atf-sh/libatf-sh.subr b/unit/atf-src/atf-sh/libatf-sh.subr
new file mode 100644
index 0000000..a078975
--- /dev/null
+++ b/unit/atf-src/atf-sh/libatf-sh.subr
@@ -0,0 +1,770 @@
+# Copyright (c) 2007 The NetBSD Foundation, Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
+# CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+# IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
+# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+# GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+# IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+# IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+# ------------------------------------------------------------------------
+# GLOBAL VARIABLES
+# ------------------------------------------------------------------------
+
+# Values for the expect property.
+Expect=pass
+Expect_Reason=
+
+# A boolean variable that indicates whether we are parsing a test case's
+# head or not.
+Parsing_Head=false
+
+# The program name.
+Prog_Name=${0##*/}
+
+# The file to which the test case will print its result.
+Results_File=
+
+# The test program's source directory: i.e. where its auxiliary data files
+# and helper utilities can be found. Can be overriden through the '-s' flag.
+Source_Dir="$(dirname ${0})"
+
+# Indicates the test case we are currently processing.
+Test_Case=
+
+# List of meta-data variables for the current test case.
+Test_Case_Vars=
+
+# The list of all test cases provided by the test program.
+Test_Cases=
+
+# ------------------------------------------------------------------------
+# PUBLIC INTERFACE
+# ------------------------------------------------------------------------
+
+#
+# atf_add_test_case tc-name
+#
+# Adds the given test case to the list of test cases that form the test
+# program. The name provided here must be accompanied by two functions
+# named after it: <tc-name>_head and <tc-name>_body, and optionally by
+# a <tc-name>_cleanup function.
+#
+atf_add_test_case()
+{
+ Test_Cases="${Test_Cases} ${1}"
+}
+
+#
+# atf_check cmd expcode expout experr
+#
+# Executes atf-check with given arguments and automatically calls
+# atf_fail in case of failure.
+#
+atf_check()
+{
+ ${Atf_Check} "${@}" || \
+ atf_fail "atf-check failed; see the output of the test for details"
+}
+
+#
+# atf_check_equal expected_expression actual_expression
+#
+# Checks that expected_expression's value matches actual_expression's
+# and, if not, raises an error. Ideally expected_expression and
+# actual_expression should be provided quoted (not expanded) so that
+# the error message is helpful; otherwise it will only show the values,
+# not the expressions themselves.
+#
+atf_check_equal()
+{
+ eval _val1=\"${1}\"
+ eval _val2=\"${2}\"
+ test "${_val1}" = "${_val2}" || \
+ atf_fail "${1} != ${2} (${_val1} != ${_val2})"
+}
+
+#
+# atf_config_get varname [defvalue]
+#
+# Prints the value of a configuration variable. If it is not
+# defined, prints the given default value.
+#
+atf_config_get()
+{
+ _varname="__tc_config_var_$(_atf_normalize ${1})"
+ if [ ${#} -eq 1 ]; then
+ eval _value=\"\${${_varname}-__unset__}\"
+ [ "${_value}" = __unset__ ] && \
+ _atf_error 1 "Could not find configuration variable \`${1}'"
+ echo ${_value}
+ elif [ ${#} -eq 2 ]; then
+ eval echo \${${_varname}-${2}}
+ else
+ _atf_error 1 "Incorrect number of parameters for atf_config_get"
+ fi
+}
+
+#
+# atf_config_has varname
+#
+# Returns a boolean indicating if the given configuration variable is
+# defined or not.
+#
+atf_config_has()
+{
+ _varname="__tc_config_var_$(_atf_normalize ${1})"
+ eval _value=\"\${${_varname}-__unset__}\"
+ [ "${_value}" != __unset__ ]
+}
+
+#
+# atf_expect_death reason
+#
+# Sets the expectations to 'death'.
+#
+atf_expect_death()
+{
+ _atf_validate_expect
+
+ Expect=death
+ _atf_create_resfile "expected_death: ${*}"
+}
+
+#
+# atf_expect_timeout reason
+#
+# Sets the expectations to 'timeout'.
+#
+atf_expect_timeout()
+{
+ _atf_validate_expect
+
+ Expect=timeout
+ _atf_create_resfile "expected_timeout: ${*}"
+}
+
+#
+# atf_expect_exit exitcode reason
+#
+# Sets the expectations to 'exit'.
+#
+atf_expect_exit()
+{
+ _exitcode="${1}"; shift
+
+ _atf_validate_expect
+
+ Expect=exit
+ if [ "${_exitcode}" = "-1" ]; then
+ _atf_create_resfile "expected_exit: ${*}"
+ else
+ _atf_create_resfile "expected_exit(${_exitcode}): ${*}"
+ fi
+}
+
+#
+# atf_expect_fail reason
+#
+# Sets the expectations to 'fail'.
+#
+atf_expect_fail()
+{
+ _atf_validate_expect
+
+ Expect=fail
+ Expect_Reason="${*}"
+}
+
+#
+# atf_expect_pass
+#
+# Sets the expectations to 'pass'.
+#
+atf_expect_pass()
+{
+ _atf_validate_expect
+
+ Expect=pass
+ Expect_Reason=
+}
+
+#
+# atf_expect_signal signo reason
+#
+# Sets the expectations to 'signal'.
+#
+atf_expect_signal()
+{
+ _signo="${1}"; shift
+
+ _atf_validate_expect
+
+ Expect=signal
+ if [ "${_signo}" = "-1" ]; then
+ _atf_create_resfile "expected_signal: ${*}"
+ else
+ _atf_create_resfile "expected_signal(${_signo}): ${*}"
+ fi
+}
+
+#
+# atf_expected_failure msg1 [.. msgN]
+#
+# Makes the test case report an expected failure with the given error
+# message. Multiple words can be provided, which are concatenated with
+# a single blank space.
+#
+atf_expected_failure()
+{
+ _atf_create_resfile "expected_failure: ${Expect_Reason}: ${*}"
+ exit 0
+}
+
+#
+# atf_fail msg1 [.. msgN]
+#
+# Makes the test case fail with the given error message. Multiple
+# words can be provided, in which case they are joined by a single
+# blank space.
+#
+atf_fail()
+{
+ case "${Expect}" in
+ fail)
+ atf_expected_failure "${@}"
+ ;;
+ pass)
+ _atf_create_resfile "failed: ${*}"
+ exit 1
+ ;;
+ *)
+ _atf_error 128 "Unreachable"
+ ;;
+ esac
+}
+
+#
+# atf_get varname
+#
+# Prints the value of a test case-specific variable. Given that one
+# should not get the value of non-existent variables, it is fine to
+# always use this function as 'val=$(atf_get var)'.
+#
+atf_get()
+{
+ eval echo \${__tc_var_${Test_Case}_$(_atf_normalize ${1})}
+}
+
+#
+# atf_get_srcdir
+#
+# Prints the value of the test case's source directory.
+#
+atf_get_srcdir()
+{
+ echo ${Source_Dir}
+}
+
+#
+# atf_pass
+#
+# Makes the test case pass. Shouldn't be used in general, as a test
+# case that does not explicitly fail is assumed to pass.
+#
+atf_pass()
+{
+ case "${Expect}" in
+ fail)
+ Expect=pass
+ atf_fail "Test case was expecting a failure but got a pass instead"
+ ;;
+ pass)
+ _atf_create_resfile passed
+ exit 0
+ ;;
+ *)
+ _atf_error 128 "Unreachable"
+ ;;
+ esac
+}
+
+#
+# atf_require_prog prog
+#
+# Checks that the given program name (either provided as an absolute
+# path or as a plain file name) can be found. If it is not available,
+# automatically skips the test case with an appropriate message.
+#
+# Relative paths are not allowed because the test case cannot predict
+# where it will be executed from.
+#
+atf_require_prog()
+{
+ _prog=
+ case ${1} in
+ /*)
+ _prog="${1}"
+ [ -x ${_prog} ] || \
+ atf_skip "The required program ${1} could not be found"
+ ;;
+ */*)
+ atf_fail "atf_require_prog does not accept relative path names \`${1}'"
+ ;;
+ *)
+ _prog=$(_atf_find_in_path "${1}")
+ [ -n "${_prog}" ] || \
+ atf_skip "The required program ${1} could not be found" \
+ "in the PATH"
+ ;;
+ esac
+}
+
+#
+# atf_set varname val1 [.. valN]
+#
+# Sets the test case's variable 'varname' to the specified values
+# which are concatenated using a single blank space. This function
+# is supposed to be called form the test case's head only.
+#
+atf_set()
+{
+ ${Parsing_Head} || \
+ _atf_error 128 "atf_set called from the test case's body"
+
+ Test_Case_Vars="${Test_Case_Vars} ${1}"
+ _var=$(_atf_normalize ${1}); shift
+ eval __tc_var_${Test_Case}_${_var}=\"\${*}\"
+}
+
+#
+# atf_skip msg1 [.. msgN]
+#
+# Skips the test case because of the reason provided. Multiple words
+# can be given, in which case they are joined by a single blank space.
+#
+atf_skip()
+{
+ _atf_create_resfile "skipped: ${*}"
+ exit 0
+}
+
+#
+# atf_test_case tc-name cleanup
+#
+# Defines a new test case named tc-name. The name provided here must be
+# accompanied by two functions named after it: <tc-name>_head and
+# <tc-name>_body. If cleanup is set to 'cleanup', then this also expects
+# a <tc-name>_cleanup function to be defined.
+#
+atf_test_case()
+{
+ eval "${1}_head() { :; }"
+ eval "${1}_body() { atf_fail 'Test case not implemented'; }"
+ if [ "${2}" = cleanup ]; then
+ eval __has_cleanup_${1}=true
+ eval "${1}_cleanup() { :; }"
+ else
+ eval "${1}_cleanup() {
+ _atf_error 1 'Test case ${1} declared without a cleanup routine'; }"
+ fi
+}
+
+# ------------------------------------------------------------------------
+# PRIVATE INTERFACE
+# ------------------------------------------------------------------------
+
+#
+# _atf_config_set varname val1 [.. valN]
+#
+# Sets the test case's private variable 'varname' to the specified
+# values which are concatenated using a single blank space.
+#
+_atf_config_set()
+{
+ _var=$(_atf_normalize ${1}); shift
+ eval __tc_config_var_${_var}=\"\${*}\"
+ Config_Vars="${Config_Vars} __tc_config_var_${_var}"
+}
+
+#
+# _atf_config_set_str varname=val
+#
+# Sets the test case's private variable 'varname' to the specified
+# value. The parameter is of the form 'varname=val'.
+#
+_atf_config_set_from_str()
+{
+ _oldifs=${IFS}
+ IFS='='
+ set -- ${*}
+ _var=${1}
+ shift
+ _val="${@}"
+ IFS=${_oldifs}
+ _atf_config_set "${_var}" "${_val}"
+}
+
+#
+# _atf_create_resfile contents
+#
+# Creates the results file.
+#
+_atf_create_resfile()
+{
+ if [ -n "${Results_File}" ]; then
+ echo "${*}" >"${Results_File}" || \
+ _atf_error 128 "Cannot create results file '${Results_File}'"
+ else
+ echo "${*}"
+ fi
+}
+
+#
+# _atf_error error_code [msg1 [.. msgN]]
+#
+# Prints the given error message (which can be composed of multiple
+# arguments, in which case are joined by a single space) and exits
+# with the specified error code.
+#
+# This must not be used by test programs themselves (hence making
+# the function private) to indicate a test case's failure. They
+# have to use the atf_fail function.
+#
+_atf_error()
+{
+ _error_code="${1}"; shift
+
+ echo "${Prog_Name}: ERROR:" "$@" 1>&2
+ exit ${_error_code}
+}
+
+#
+# _atf_warning msg1 [.. msgN]
+#
+# Prints the given warning message (which can be composed of multiple
+# arguments, in which case are joined by a single space).
+#
+_atf_warning()
+{
+ echo "${Prog_Name}: WARNING:" "$@" 1>&2
+}
+
+#
+# _atf_find_in_path program
+#
+# Looks for a program in the path and prints the full path to it or
+# nothing if it could not be found. It also returns true in case of
+# success.
+#
+_atf_find_in_path()
+{
+ _prog="${1}"
+
+ _oldifs=${IFS}
+ IFS=:
+ for _dir in ${PATH}
+ do
+ if [ -x ${_dir}/${_prog} ]; then
+ IFS=${_oldifs}
+ echo ${_dir}/${_prog}
+ return 0
+ fi
+ done
+ IFS=${_oldifs}
+
+ return 1
+}
+
+#
+# _atf_has_tc name
+#
+# Returns true if the given test case exists.
+#
+_atf_has_tc()
+{
+ for _tc in ${Test_Cases}; do
+ [ "${_tc}" != "${1}" ] || return 0
+ done
+ return 1
+}
+
+#
+# _atf_list_tcs
+#
+# Describes all test cases and prints the list to the standard output.
+#
+_atf_list_tcs()
+{
+ echo 'Content-Type: application/X-atf-tp; version="1"'
+ echo
+
+ set -- ${Test_Cases}
+ while [ ${#} -gt 0 ]; do
+ _atf_parse_head ${1}
+
+ echo "ident: $(atf_get ident)"
+ for _var in ${Test_Case_Vars}; do
+ [ "${_var}" != "ident" ] && echo "${_var}: $(atf_get ${_var})"
+ done
+
+ [ ${#} -gt 1 ] && echo
+ shift
+ done
+}
+
+#
+# _atf_normalize str
+#
+# Normalizes a string so that it is a valid shell variable name.
+#
+_atf_normalize()
+{
+ echo ${1} | tr .- __
+}
+
+#
+# _atf_parse_head tcname
+#
+# Evaluates a test case's head to gather its variables and prepares the
+# test program to run it.
+#
+_atf_parse_head()
+{
+ Parsing_Head=true
+
+ Test_Case="${1}"
+ Test_Case_Vars=
+
+ if _atf_has_cleanup "${1}"; then
+ atf_set has.cleanup "true"
+ fi
+
+ ${1}_head
+ atf_set ident "${1}"
+
+ Parsing_Head=false
+}
+
+#
+# _atf_run_tc tc
+#
+# Runs the specified test case. Prints its exit status to the
+# standard output and returns a boolean indicating if the test was
+# successful or not.
+#
+_atf_run_tc()
+{
+ case ${1} in
+ *:*)
+ _tcname=${1%%:*}
+ _tcpart=${1#*:}
+
+ if [ "${_tcpart}" != body -a "${_tcpart}" != cleanup ]; then
+ _atf_syntax_error "Unknown test case part \`${_tcpart}'"
+ fi
+ ;;
+
+ *)
+ _tcname=${1}
+ _tcpart=body
+ ;;
+ esac
+
+ _atf_has_tc "${_tcname}" || _atf_syntax_error "Unknown test case \`${1}'"
+
+ if [ "${__RUNNING_INSIDE_ATF_RUN}" != "internal-yes-value" ]; then
+ _atf_warning "Running test cases outside of kyua(1) is unsupported"
+ _atf_warning "No isolation nor timeout control is being applied;" \
+ "you may get unexpected failures; see atf-test-case(4)"
+ fi
+
+ _atf_parse_head ${_tcname}
+
+ case ${_tcpart} in
+ body)
+ if ${_tcname}_body; then
+ _atf_validate_expect
+ _atf_create_resfile passed
+ else
+ Expect=pass
+ atf_fail "Test case body returned a non-ok exit code, but" \
+ "this is not allowed"
+ fi
+ ;;
+ cleanup)
+ if _atf_has_cleanup "${_tcname}"; then
+ ${_tcname}_cleanup || _atf_error 128 "The test case cleanup" \
+ "returned a non-ok exit code, but this is not allowed"
+ fi
+ ;;
+ *)
+ _atf_error 128 "Unknown test case part"
+ ;;
+ esac
+}
+
+#
+# _atf_syntax_error msg1 [.. msgN]
+#
+# Formats and prints a syntax error message and terminates the
+# program prematurely.
+#
+_atf_syntax_error()
+{
+ echo "${Prog_Name}: ERROR: ${@}" 1>&2
+ echo "${Prog_Name}: See atf-test-program(1) for usage details." 1>&2
+ exit 1
+}
+
+#
+# _atf_has_cleanup tc-name
+#
+# Returns a boolean indicating if the given test case has a cleanup
+# routine or not.
+#
+_atf_has_cleanup()
+{
+ _found=true
+ eval "[ x\"\${__has_cleanup_${1}}\" = xtrue ] || _found=false"
+ [ "${_found}" = true ]
+}
+
+#
+# _atf_validate_expect
+#
+# Ensures that the current test case state is correct regarding the expect
+# status.
+#
+_atf_validate_expect()
+{
+ case "${Expect}" in
+ death)
+ Expect=pass
+ atf_fail "Test case was expected to terminate abruptly but it" \
+ "continued execution"
+ ;;
+ exit)
+ Expect=pass
+ atf_fail "Test case was expected to exit cleanly but it continued" \
+ "execution"
+ ;;
+ fail)
+ Expect=pass
+ atf_fail "Test case was expecting a failure but none were raised"
+ ;;
+ pass)
+ ;;
+ signal)
+ Expect=pass
+ atf_fail "Test case was expected to receive a termination signal" \
+ "but it continued execution"
+ ;;
+ timeout)
+ Expect=pass
+ atf_fail "Test case was expected to hang but it continued execution"
+ ;;
+ *)
+ _atf_error 128 "Unreachable"
+ ;;
+ esac
+}
+
+#
+# _atf_warning [msg1 [.. msgN]]
+#
+# Prints the given warning message (which can be composed of multiple
+# arguments, in which case are joined by a single space).
+#
+# This must not be used by test programs themselves (hence making
+# the function private).
+#
+_atf_warning()
+{
+ echo "${Prog_Name}: WARNING:" "$@" 1>&2
+}
+
+#
+# main [options] test_case
+#
+# Test program's entry point.
+#
+main()
+{
+ # Process command-line options first.
+ _numargs=${#}
+ _lflag=false
+ while getopts :lr:s:v: arg; do
+ case ${arg} in
+ l)
+ _lflag=true
+ ;;
+
+ r)
+ Results_File=${OPTARG}
+ ;;
+
+ s)
+ Source_Dir=${OPTARG}
+ ;;
+
+ v)
+ _atf_config_set_from_str "${OPTARG}"
+ ;;
+
+ \?)
+ _atf_syntax_error "Unknown option -${OPTARG}."
+ # NOTREACHED
+ ;;
+ esac
+ done
+ shift `expr ${OPTIND} - 1`
+
+ case ${Source_Dir} in
+ /*)
+ ;;
+ *)
+ Source_Dir=$(pwd)/${Source_Dir}
+ ;;
+ esac
+ [ -f ${Source_Dir}/${Prog_Name} ] || \
+ _atf_error 1 "Cannot find the test program in the source" \
+ "directory \`${Source_Dir}'"
+
+ # Call the test program's hook to register all available test cases.
+ atf_init_test_cases
+
+ # Run or list test cases.
+ if `${_lflag}`; then
+ if [ ${#} -gt 0 ]; then
+ _atf_syntax_error "Cannot provide test case names with -l"
+ fi
+ _atf_list_tcs
+ else
+ if [ ${#} -eq 0 ]; then
+ _atf_syntax_error "Must provide a test case name"
+ elif [ ${#} -gt 1 ]; then
+ _atf_syntax_error "Cannot provide more than one test case name"
+ else
+ _atf_run_tc "${1}"
+ fi
+ fi
+}
+
+# vim: syntax=sh:expandtab:shiftwidth=4:softtabstop=4
diff --git a/unit/atf-src/atf-sh/misc_helpers.sh b/unit/atf-src/atf-sh/misc_helpers.sh
new file mode 100644
index 0000000..62d6580
--- /dev/null
+++ b/unit/atf-src/atf-sh/misc_helpers.sh
@@ -0,0 +1,305 @@
+# Copyright (c) 2007 The NetBSD Foundation, Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
+# CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+# IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
+# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+# GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+# IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+# IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+# -------------------------------------------------------------------------
+# Helper tests for "t_atf_check".
+# -------------------------------------------------------------------------
+
+atf_test_case atf_check_info_ok
+atf_check_info_ok_head()
+{
+ atf_set "descr" "Helper test case for the t_atf_check test program"
+}
+atf_check_info_ok_body()
+{
+ atf_check -s eq:0 -o empty -e empty true
+}
+
+atf_test_case atf_check_info_fail
+atf_check_info_fail_head()
+{
+ atf_set "descr" "Helper test case for the t_atf_check test program"
+}
+atf_check_info_fail_body()
+{
+ # In Solaris, /usr/bin/false returns 255 rather than 1. Use the
+ # built-in version for the check.
+ atf_check -s eq:1 -o empty -e empty sh -c "false"
+}
+
+atf_test_case atf_check_expout_mismatch
+atf_check_expout_mismatch_head()
+{
+ atf_set "descr" "Helper test case for the t_atf_check test program"
+}
+atf_check_expout_mismatch_body()
+{
+ cat >expout <<SECONDEOF
+foo
+SECONDEOF
+ atf_check -s eq:0 -o file:expout -e empty echo bar
+}
+
+atf_test_case atf_check_experr_mismatch
+atf_check_experr_mismatch_head()
+{
+ atf_set "descr" "Helper test case for the t_atf_check test program"
+}
+atf_check_experr_mismatch_body()
+{
+ cat >experr <<SECONDEOF
+foo
+SECONDEOF
+ atf_check -s eq:0 -o empty -e file:experr -x 'echo bar 1>&2'
+}
+
+atf_test_case atf_check_null_stdout
+atf_check_null_stdout_head()
+{
+ atf_set "descr" "Helper test case for the t_atf_check test program"
+}
+atf_check_null_stdout_body()
+{
+ atf_check -s eq:0 -o empty -e empty echo "These are the contents"
+}
+
+atf_test_case atf_check_null_stderr
+atf_check_null_stderr_head()
+{
+ atf_set "descr" "Helper test case for the t_atf_check test program"
+}
+atf_check_null_stderr_body()
+{
+ atf_check -s eq:0 -o empty -e empty -x 'echo "These are the contents" 1>&2'
+}
+
+atf_test_case atf_check_equal_ok
+atf_check_equal_ok_head()
+{
+ atf_set "descr" "Helper test case for the t_atf_check test program"
+}
+atf_check_equal_ok_body()
+{
+ atf_check_equal a a
+}
+
+atf_test_case atf_check_equal_fail
+atf_check_equal_fail_head()
+{
+ atf_set "descr" "Helper test case for the t_atf_check test program"
+}
+atf_check_equal_fail_body()
+{
+ atf_check_equal a b
+}
+
+atf_test_case atf_check_equal_eval_ok
+atf_check_equal_eval_ok_head()
+{
+ atf_set "descr" "Helper test case for the t_atf_check test program"
+}
+atf_check_equal_eval_ok_body()
+{
+ x=a
+ y=a
+ atf_check_equal '${x}' '${y}'
+}
+
+atf_test_case atf_check_equal_eval_fail
+atf_check_equal_eval_fail_head()
+{
+ atf_set "descr" "Helper test case for the t_atf_check test program"
+}
+atf_check_equal_eval_fail_body()
+{
+ x=a
+ y=b
+ atf_check_equal '${x}' '${y}'
+}
+
+atf_test_case atf_check_timeout
+atf_check_timeout_head()
+{
+ atf_set "descr" "Helper test case for the t_atf_check test program"
+ atf_set "timeout" 1
+}
+atf_check_timeout_body()
+{
+ atf_check true
+ atf_check sleep 42
+}
+
+# -------------------------------------------------------------------------
+# Helper tests for "t_config".
+# -------------------------------------------------------------------------
+
+atf_test_case config_get
+config_get_head()
+{
+ atf_set "descr" "Helper test case for the t_config test program"
+}
+config_get_body()
+{
+ if atf_config_has ${TEST_VARIABLE}; then
+ echo "${TEST_VARIABLE} = $(atf_config_get ${TEST_VARIABLE})"
+ fi
+}
+
+atf_test_case config_has
+config_has_head()
+{
+ atf_set "descr" "Helper test case for the t_config test program"
+}
+config_has_body()
+{
+ if atf_config_has ${TEST_VARIABLE}; then
+ echo "${TEST_VARIABLE} found"
+ else
+ echo "${TEST_VARIABLE} not found"
+ fi
+}
+
+# -------------------------------------------------------------------------
+# Helper tests for "t_normalize".
+# -------------------------------------------------------------------------
+
+atf_test_case normalize
+normalize_head()
+{
+ atf_set "descr" "Helper test case for the t_normalize test program"
+ atf_set "a.b" "test value 1"
+ atf_set "c-d" "test value 2"
+}
+normalize_body()
+{
+ echo "a.b: $(atf_get a.b)"
+ echo "c-d: $(atf_get c-d)"
+}
+
+# -------------------------------------------------------------------------
+# Helper tests for "t_tc".
+# -------------------------------------------------------------------------
+
+atf_test_case tc_pass_true
+tc_pass_true_head()
+{
+ atf_set "descr" "Helper test case for the t_tc test program"
+}
+tc_pass_true_body()
+{
+ true
+}
+
+atf_test_case tc_pass_false
+tc_pass_false_head()
+{
+ atf_set "descr" "Helper test case for the t_tc test program"
+}
+tc_pass_false_body()
+{
+ false
+}
+
+atf_test_case tc_pass_return_error
+tc_pass_return_error_head()
+{
+ atf_set "descr" "Helper test case for the t_tc test program"
+}
+tc_pass_return_error_body()
+{
+ return 1
+}
+
+atf_test_case tc_fail
+tc_fail_head()
+{
+ atf_set "descr" "Helper test case for the t_tc test program"
+}
+tc_fail_body()
+{
+ echo "An error" 1>&2
+ exit 1
+}
+
+atf_test_case tc_missing_body
+tc_missing_body_head()
+{
+ atf_set "descr" "Helper test case for the t_tc test program"
+}
+
+# -------------------------------------------------------------------------
+# Helper tests for "t_tp".
+# -------------------------------------------------------------------------
+
+atf_test_case tp_srcdir
+tp_srcdir_head()
+{
+ atf_set "descr" "Helper test case for the t_tp test program"
+}
+tp_srcdir_body()
+{
+ echo "Calling helper"
+ helper_subr || atf_fail "Could not call helper subroutine"
+}
+
+# -------------------------------------------------------------------------
+# Main.
+# -------------------------------------------------------------------------
+
+atf_init_test_cases()
+{
+ # Add helper tests for t_atf_check.
+ atf_add_test_case atf_check_info_ok
+ atf_add_test_case atf_check_info_fail
+ atf_add_test_case atf_check_expout_mismatch
+ atf_add_test_case atf_check_experr_mismatch
+ atf_add_test_case atf_check_null_stdout
+ atf_add_test_case atf_check_null_stderr
+ atf_add_test_case atf_check_equal_ok
+ atf_add_test_case atf_check_equal_fail
+ atf_add_test_case atf_check_equal_eval_ok
+ atf_add_test_case atf_check_equal_eval_fail
+ atf_add_test_case atf_check_timeout
+
+ # Add helper tests for t_config.
+ atf_add_test_case config_get
+ atf_add_test_case config_has
+
+ # Add helper tests for t_normalize.
+ atf_add_test_case normalize
+
+ # Add helper tests for t_tc.
+ atf_add_test_case tc_pass_true
+ atf_add_test_case tc_pass_false
+ atf_add_test_case tc_pass_return_error
+ atf_add_test_case tc_fail
+ atf_add_test_case tc_missing_body
+
+ # Add helper tests for t_tp.
+ [ -f $(atf_get_srcdir)/subrs ] && . $(atf_get_srcdir)/subrs
+ atf_add_test_case tp_srcdir
+}
+
+# vim: syntax=sh:expandtab:shiftwidth=4:softtabstop=4
diff --git a/unit/atf-src/atf-sh/normalize_test.sh b/unit/atf-src/atf-sh/normalize_test.sh
new file mode 100644
index 0000000..0419db3
--- /dev/null
+++ b/unit/atf-src/atf-sh/normalize_test.sh
@@ -0,0 +1,44 @@
+# Copyright (c) 2007 The NetBSD Foundation, Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
+# CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+# IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
+# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+# GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+# IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+# IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+atf_test_case main
+main_head()
+{
+ atf_set "descr" "Verifies that variable names with symbols not" \
+ "allowed as part of shell variable names work"
+}
+main_body()
+{
+ h="$(atf_get_srcdir)/misc_helpers -s $(atf_get_srcdir)"
+ atf_check -s eq:0 -o match:'a.b: test value 1' \
+ -o match:'c-d: test value 2' -e ignore ${h} normalize
+}
+
+atf_init_test_cases()
+{
+ atf_add_test_case main
+}
+
+# vim: syntax=sh:expandtab:shiftwidth=4:softtabstop=4
diff --git a/unit/atf-src/atf-sh/tc_test.sh b/unit/atf-src/atf-sh/tc_test.sh
new file mode 100644
index 0000000..1117047
--- /dev/null
+++ b/unit/atf-src/atf-sh/tc_test.sh
@@ -0,0 +1,60 @@
+# Copyright (c) 2007 The NetBSD Foundation, Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
+# CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+# IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
+# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+# GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+# IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+# IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+atf_test_case default_status
+default_status_head()
+{
+ atf_set "descr" "Verifies that test cases get the correct default" \
+ "status if they did not provide any"
+}
+default_status_body()
+{
+ h="$(atf_get_srcdir)/misc_helpers -s $(atf_get_srcdir)"
+ atf_check -s eq:0 -o ignore -e ignore ${h} tc_pass_true
+ atf_check -s eq:1 -o ignore -e ignore ${h} tc_pass_false
+ atf_check -s eq:1 -o match:'failed:.*body.*non-ok exit code' -e ignore \
+ ${h} tc_pass_return_error
+ atf_check -s eq:1 -o ignore -e match:'An error' ${h} tc_fail
+}
+
+atf_test_case missing_body
+missing_body_head()
+{
+ atf_set "descr" "Verifies that test cases without a body are reported" \
+ "as failed"
+}
+missing_body_body()
+{
+ h="$(atf_get_srcdir)/misc_helpers -s $(atf_get_srcdir)"
+ atf_check -s eq:1 -o ignore -e ignore ${h} tc_missing_body
+}
+
+atf_init_test_cases()
+{
+ atf_add_test_case default_status
+ atf_add_test_case missing_body
+}
+
+# vim: syntax=sh:expandtab:shiftwidth=4:softtabstop=4
diff --git a/unit/atf-src/atf-sh/tp_test.sh b/unit/atf-src/atf-sh/tp_test.sh
new file mode 100644
index 0000000..a9f1b96
--- /dev/null
+++ b/unit/atf-src/atf-sh/tp_test.sh
@@ -0,0 +1,52 @@
+# Copyright (c) 2007 The NetBSD Foundation, Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
+# CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+# IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
+# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+# GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+# IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+# IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+atf_test_case srcdir
+srcdir_head()
+{
+ atf_set "descr" "Verifies that the source directory can be queried" \
+ "from the initialization function"
+}
+srcdir_body()
+{
+ mkdir work
+ atf_check -s eq:0 -o empty -e empty cp "$(atf_get_srcdir)/misc_helpers" work
+ cat >work/subrs <<EOF
+helper_subr() {
+ echo "This is a helper subroutine"
+}
+EOF
+
+ atf_check -s eq:0 -o match:'Calling helper' \
+ -o match:'This is a helper subroutine' -e ignore ./work/misc_helpers \
+ -s "$(pwd)"/work tp_srcdir
+}
+
+atf_init_test_cases()
+{
+ atf_add_test_case srcdir
+}
+
+# vim: syntax=sh:expandtab:shiftwidth=4:softtabstop=4