diff options
Diffstat (limited to 'unit/atf-src/atf-sh')
-rw-r--r-- | unit/atf-src/atf-sh/Atffile | 11 | ||||
-rw-r--r-- | unit/atf-src/atf-sh/Kyuafile | 11 | ||||
-rw-r--r-- | unit/atf-src/atf-sh/Makefile.am.inc | 122 | ||||
-rw-r--r-- | unit/atf-src/atf-sh/atf-check.1 | 160 | ||||
-rw-r--r-- | unit/atf-src/atf-sh/atf-check.cpp | 834 | ||||
-rw-r--r-- | unit/atf-src/atf-sh/atf-check_test.sh | 441 | ||||
-rw-r--r-- | unit/atf-src/atf-sh/atf-sh.1 | 102 | ||||
-rw-r--r-- | unit/atf-src/atf-sh/atf-sh.3 | 372 | ||||
-rw-r--r-- | unit/atf-src/atf-sh/atf-sh.cpp | 185 | ||||
-rw-r--r-- | unit/atf-src/atf-sh/atf-sh.m4 | 49 | ||||
-rw-r--r-- | unit/atf-src/atf-sh/atf-sh.pc.in | 8 | ||||
-rw-r--r-- | unit/atf-src/atf-sh/atf_check_test.sh | 193 | ||||
-rw-r--r-- | unit/atf-src/atf-sh/config_test.sh | 79 | ||||
-rw-r--r-- | unit/atf-src/atf-sh/integration_test.sh | 160 | ||||
-rw-r--r-- | unit/atf-src/atf-sh/libatf-sh.subr | 770 | ||||
-rw-r--r-- | unit/atf-src/atf-sh/misc_helpers.sh | 305 | ||||
-rw-r--r-- | unit/atf-src/atf-sh/normalize_test.sh | 44 | ||||
-rw-r--r-- | unit/atf-src/atf-sh/tc_test.sh | 60 | ||||
-rw-r--r-- | unit/atf-src/atf-sh/tp_test.sh | 52 |
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 |