diff options
Diffstat (limited to 'atf-run')
41 files changed, 10409 insertions, 0 deletions
diff --git a/atf-run/Atffile b/atf-run/Atffile new file mode 100644 index 0000000000000..146211e000d47 --- /dev/null +++ b/atf-run/Atffile @@ -0,0 +1,5 @@ +Content-Type: application/X-atf-atffile; version="1" + +prop: test-suite = atf + +tp-glob: *_test diff --git a/atf-run/Kyuafile b/atf-run/Kyuafile new file mode 100644 index 0000000000000..305d699e165ee --- /dev/null +++ b/atf-run/Kyuafile @@ -0,0 +1,13 @@ +syntax("kyuafile", 1) + +test_suite("atf") + +atf_test_program{name="atffile_test"} +atf_test_program{name="config_test"} +atf_test_program{name="fs_test"} +atf_test_program{name="integration_test"} +atf_test_program{name="io_test"} +atf_test_program{name="requirements_test"} +atf_test_program{name="signals_test"} +atf_test_program{name="test_program_test"} +atf_test_program{name="user_test"} diff --git a/atf-run/Makefile.am.inc b/atf-run/Makefile.am.inc new file mode 100644 index 0000000000000..7eb8333b31c0d --- /dev/null +++ b/atf-run/Makefile.am.inc @@ -0,0 +1,150 @@ +# +# Automated Testing Framework (atf) +# +# 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. +# + +bin_PROGRAMS += atf-run/atf-run +atf_run_atf_run_CPPFLAGS = "-DGDB=\"$(GDB)\"" +atf_run_atf_run_SOURCES = atf-run/atf-run.cpp \ + atf-run/atffile.cpp \ + atf-run/atffile.hpp \ + atf-run/config.cpp \ + atf-run/config.hpp \ + atf-run/fs.cpp \ + atf-run/fs.hpp \ + atf-run/io.cpp \ + atf-run/io.hpp \ + atf-run/requirements.cpp \ + atf-run/requirements.hpp \ + atf-run/signals.cpp \ + atf-run/signals.hpp \ + atf-run/test-program.cpp \ + atf-run/test-program.hpp \ + atf-run/timer.cpp \ + atf-run/timer.hpp \ + atf-run/user.cpp \ + atf-run/user.hpp +atf_run_atf_run_LDADD = $(ATF_CXX_LIBS) +dist_man_MANS += atf-run/atf-run.1 + +tests_atf_run_DATA = atf-run/Atffile \ + atf-run/Kyuafile +tests_atf_rundir = $(pkgtestsdir)/atf-run +EXTRA_DIST += $(tests_atf_run_DATA) + +tests_atf_run_PROGRAMS = atf-run/atffile_test +atf_run_atffile_test_SOURCES = atf-run/atffile_test.cpp \ + atf-run/atffile.cpp +atf_run_atffile_test_CPPFLAGS = -I$(srcdir)/atf-c++/detail +atf_run_atffile_test_LDADD = atf-c++/detail/libtest_helpers.la $(ATF_CXX_LIBS) + +tests_atf_run_PROGRAMS += atf-run/bad_metadata_helper +atf_run_bad_metadata_helper_SOURCES = atf-run/bad_metadata_helper.c +atf_run_bad_metadata_helper_LDADD = libatf-c.la + +tests_atf_run_PROGRAMS += atf-run/config_test +atf_run_config_test_SOURCES = atf-run/config_test.cpp \ + atf-run/config.cpp +atf_run_config_test_CPPFLAGS = -I$(srcdir)/atf-c++/detail +atf_run_config_test_LDADD = atf-c++/detail/libtest_helpers.la $(ATF_CXX_LIBS) + +tests_atf_run_PROGRAMS += atf-run/expect_helpers +atf_run_expect_helpers_SOURCES = atf-run/expect_helpers.c +atf_run_expect_helpers_LDADD = libatf-c.la + +tests_atf_run_PROGRAMS += atf-run/fs_test +atf_run_fs_test_SOURCES = atf-run/fs_test.cpp \ + atf-run/fs.cpp \ + atf-run/user.cpp +atf_run_fs_test_LDADD = $(ATF_CXX_LIBS) + +tests_atf_run_PROGRAMS += atf-run/io_test +atf_run_io_test_SOURCES = atf-run/io_test.cpp \ + atf-run/io.cpp \ + atf-run/signals.cpp +atf_run_io_test_LDADD = atf-c++/detail/libtest_helpers.la $(ATF_CXX_LIBS) + +tests_atf_run_PROGRAMS += atf-run/misc_helpers +atf_run_misc_helpers_SOURCES = atf-run/misc_helpers.cpp +atf_run_misc_helpers_LDADD = $(ATF_CXX_LIBS) + +tests_atf_run_PROGRAMS += atf-run/pass_helper +atf_run_pass_helper_SOURCES = atf-run/pass_helper.cpp +atf_run_pass_helper_LDADD = $(ATF_CXX_LIBS) + +tests_atf_run_PROGRAMS += atf-run/several_tcs_helper +atf_run_several_tcs_helper_SOURCES = atf-run/several_tcs_helper.c +atf_run_several_tcs_helper_LDADD = libatf-c.la + +tests_atf_run_PROGRAMS += atf-run/requirements_test +atf_run_requirements_test_SOURCES = atf-run/requirements_test.cpp \ + atf-run/requirements.cpp \ + atf-run/user.cpp +atf_run_requirements_test_LDADD = $(ATF_CXX_LIBS) + +tests_atf_run_PROGRAMS += atf-run/signals_test +atf_run_signals_test_SOURCES = atf-run/signals_test.cpp atf-run/signals.cpp +atf_run_signals_test_LDADD = $(ATF_CXX_LIBS) + +tests_atf_run_PROGRAMS += atf-run/test_program_test +atf_run_test_program_test_SOURCES = atf-run/test_program_test.cpp \ + atf-run/fs.cpp \ + atf-run/io.cpp \ + atf-run/requirements.cpp \ + atf-run/signals.cpp \ + atf-run/test-program.cpp \ + atf-run/timer.cpp \ + atf-run/user.cpp +atf_run_test_program_test_CPPFLAGS = -I$(srcdir)/atf-c++/detail +atf_run_test_program_test_LDADD = atf-c++/detail/libtest_helpers.la $(ATF_CXX_LIBS) + +tests_atf_run_PROGRAMS += atf-run/user_test +atf_run_user_test_SOURCES = atf-run/user_test.cpp atf-run/user.cpp +atf_run_user_test_LDADD = $(ATF_CXX_LIBS) + +tests_atf_run_PROGRAMS += atf-run/zero_tcs_helper +atf_run_zero_tcs_helper_SOURCES = atf-run/zero_tcs_helper.c +atf_run_zero_tcs_helper_LDADD = libatf-c.la + +tests_atf_run_SCRIPTS = atf-run/integration_test +CLEANFILES += atf-run/integration_test +EXTRA_DIST += atf-run/integration_test.sh +atf-run/integration_test: $(srcdir)/atf-run/integration_test.sh + test -d atf-run || mkdir -p atf-run + @src="$(srcdir)/atf-run/integration_test.sh"; \ + dst="atf-run/integration_test"; $(BUILD_SH_TP) + +hooksdir = $(pkgdatadir) +hooks_DATA = atf-run/share/atf-run.hooks +EXTRA_DIST += $(hooks_DATA) + +egdir = $(atf_egdir) +eg_DATA = atf-run/sample/atf-run.hooks +eg_DATA += atf-run/sample/common.conf +EXTRA_DIST += $(eg_DATA) + +# vim: syntax=make:noexpandtab:shiftwidth=8:softtabstop=8 diff --git a/atf-run/atf-run.1 b/atf-run/atf-run.1 new file mode 100644 index 0000000000000..d593f47944513 --- /dev/null +++ b/atf-run/atf-run.1 @@ -0,0 +1,202 @@ +.\" +.\" Automated Testing Framework (atf) +.\" +.\" 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. +.\" +.Dd November 1, 2010 +.Dt ATF-RUN 1 +.Os +.Sh NAME +.Nm atf-run +.Nd executes a collection of test programs +.Sh SYNOPSIS +.Nm +.Op Fl v Ar var1=value1 Op .. Fl v Ar varN=valueN +.Op Ar test_program1 Op Ar .. test_programN +.Nm +.Fl h +.Sh DESCRIPTION +.Nm +executes a collection of test programs or, in other words, a complete +test suite. +The results of each test program are collected by the tool, and are then +multiplexed into a single machine-parseable report; see +.Xr atf-formats 5 +for more details. +This report can later be transformed into many different and saner formats +using the +.Nm atf-report +tool. +.Pp +The list of test programs to execute is read from an +.Pa Atffile +present in the current directory. +This file describes the test suite stored in the directory it lives in, +which aside from the list of test programs also includes meta-data and +configuration variables. +.Pp +.Nm +is also in charge of reading the configuration files that tune the behavior +of each test program and passing down the necessary variables to them. +More details on how this is done are given in the +.Sx Configuration +section. +.Pp +In the first synopsis form, +.Nm +parses the +.Pa Atffile +in the current directory and runs all the test programs specified in it. +If any test program names are given as part of the command line, those are +the ones executed instead of the complete list. +.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 XvXvarXvalueXX +.It Fl h +Shows a short summary of all available options and their purpose. +.It Fl v Ar var=value +Sets the configuration variable +.Ar var +to the given value +.Ar value . +.El +.Ss Configuration +.Nm +reads configuration data from multiple places. +After all of these places have been analyzed, a list of variable-value +pairs are passed to the test programs to be run. +.Pp +The following locations are scanned for configuration data, in order. +Items down the list override values defined above them: +.Bl -enum +.It +Configuration variables defined in the +.Pa Atffile . +.It +Configuration variables defined in the system-wide configuration file +shared among all test suites. +This lives in +.Pa ${ATF_CONFDIR}/common.conf . +.It +Configuration variables defined in the system-wide test-suite-specific +configuration file. +This lives in +.Pa ${ATF_CONFDIR}/<test-suite>.conf . +.It +Configuration variables defined in the user-specific configuration file +shared among all test suites. +This lives in +.Pa ${HOME}/.atf/common.conf . +.It +Configuration variables defined in the user-specific test-suite-specific +configuration file. +This lives in +.Pa ${HOME}/.atf/<test-suite>.conf . +.It +Configuration variables provided as part of the command line through the +.Fl v +option. +.El +.Pp +The value of +.Va ATF_CONFDIR +in the above list determined as detailed in +.Xr atf-config 1 . +.Pp +The following configuration variables are globally recognized: +.Bl -tag -width XunprivilegedXuserXX +.It Va unprivileged-user +The name of the system user that atf-run will drop root privileges into +for test cases defining +.Sq require.user=unprivileged . +Note that this is +.Em not provided for security purposes ; +this feature is only for the convenience of the user. +.El +.Ss Hooks +.Nm Ns 's +internal behavior can be customized by the system administrator and the +user by means of hooks. +These hooks are written in the shell script language for simplicity and +are stored in the following files, which are read in the order provided +below: +.Bl -enum +.It +${ATF_CONFDIR}/atf-run.hooks +.It +${HOME}/.atf/atf-run.hooks +.El +.Pp +The following hooks are supported: +.Bl -tag -width infoXstartXhookXX +.It info_start_hook +Called before +.Nm +executes any test program. +The purpose of this hook is to write additional +.Sq info +stanzas to the top of the output report; these are defined by the +.Sq application/X-atf-tps format +described in +.Xr atf-formats 5 . +Always use the +.Sq atf_tps_writer_info +function to print these. +.Pp +This takes no parameters. +.It info_end_hook +Similar to +.Sq info_start_hook +but executed after all test programs have been run so that additional +.Sq info +stanzas can be added to the bottom of the output report. +.Pp +This takes no parameters. +.El +.Pp +All hooks are accompanied by a function named +.Sq default_<hook_name> +that can be executed by them to invoke the default behavior built into +.Nm . +For example, in order to extend the default +.Sq info_start_hook +hook, we could write the following function: +.Bd -literal -offset indent +info_start_hook() +{ + default_info_start_hook "${@}" + + atf_tps_writer_info "uptime" "$(uptime)" +} +.Ed +.Sh SEE ALSO +.Xr atf-report 1 , +.Xr atf-test-program 1 , +.Xr atf 7 diff --git a/atf-run/atf-run.cpp b/atf-run/atf-run.cpp new file mode 100644 index 0000000000000..e28e8fdf0778d --- /dev/null +++ b/atf-run/atf-run.cpp @@ -0,0 +1,563 @@ +// +// Automated Testing Framework (atf) +// +// 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. +// + +#if defined(HAVE_CONFIG_H) +#include "bconfig.h" +#endif + +extern "C" { +#include <sys/types.h> +#include <sys/param.h> +#include <sys/stat.h> +#include <sys/wait.h> +#include <unistd.h> +} + +#include <algorithm> +#include <cerrno> +#include <cstdlib> +#include <cstring> +#include <fstream> +#include <iostream> +#include <map> +#include <string> + +#include "atf-c++/detail/application.hpp" +#include "atf-c++/config.hpp" +#include "atf-c++/tests.hpp" + +#include "atf-c++/detail/env.hpp" +#include "atf-c++/detail/exceptions.hpp" +#include "atf-c++/detail/fs.hpp" +#include "atf-c++/detail/parser.hpp" +#include "atf-c++/detail/process.hpp" +#include "atf-c++/detail/sanity.hpp" +#include "atf-c++/detail/text.hpp" + +#include "atffile.hpp" +#include "config.hpp" +#include "fs.hpp" +#include "requirements.hpp" +#include "test-program.hpp" + +namespace impl = atf::atf_run; + +#if defined(MAXCOMLEN) +static const std::string::size_type max_core_name_length = MAXCOMLEN; +#else +static const std::string::size_type max_core_name_length = std::string::npos; +#endif + +class atf_run : public atf::application::app { + static const char* m_description; + + atf::tests::vars_map m_cmdline_vars; + + static atf::tests::vars_map::value_type parse_var(const std::string&); + + void process_option(int, const char*); + std::string specific_args(void) const; + options_set specific_options(void) const; + + void parse_vflag(const std::string&); + + std::vector< std::string > conf_args(void) const; + + size_t count_tps(std::vector< std::string >) const; + + int run_test(const atf::fs::path&, impl::atf_tps_writer&, + const atf::tests::vars_map&); + int run_test_directory(const atf::fs::path&, impl::atf_tps_writer&); + int run_test_program(const atf::fs::path&, impl::atf_tps_writer&, + const atf::tests::vars_map&); + + impl::test_case_result get_test_case_result(const std::string&, + const atf::process::status&, const atf::fs::path&) const; + +public: + atf_run(void); + + int main(void); +}; + +static void +sanitize_gdb_env(void) +{ + try { + atf::env::unset("TERM"); + } catch (...) { + // Just swallow exceptions here; they cannot propagate into C, which + // is where this function is called from, and even if these exceptions + // appear they are benign. + } +} + +static void +dump_stacktrace(const atf::fs::path& tp, const atf::process::status& s, + const atf::fs::path& workdir, impl::atf_tps_writer& w) +{ + PRE(s.signaled() && s.coredump()); + + w.stderr_tc("Test program crashed; attempting to get stack trace"); + + const atf::fs::path corename = workdir / + (tp.leaf_name().substr(0, max_core_name_length) + ".core"); + if (!atf::fs::exists(corename)) { + w.stderr_tc("Expected file " + corename.str() + " not found"); + return; + } + + const atf::fs::path gdb(GDB); + const atf::fs::path gdbout = workdir / "gdb.out"; + const atf::process::argv_array args(gdb.leaf_name().c_str(), "-batch", + "-q", "-ex", "bt", tp.c_str(), + corename.c_str(), NULL); + atf::process::status status = atf::process::exec( + gdb, args, + atf::process::stream_redirect_path(gdbout), + atf::process::stream_redirect_path(atf::fs::path("/dev/null")), + sanitize_gdb_env); + if (!status.exited() || status.exitstatus() != EXIT_SUCCESS) { + w.stderr_tc("Execution of " GDB " failed"); + return; + } + + std::ifstream input(gdbout.c_str()); + if (input) { + std::string line; + while (std::getline(input, line).good()) + w.stderr_tc(line); + input.close(); + } + + w.stderr_tc("Stack trace complete"); +} + +const char* atf_run::m_description = + "atf-run is a tool that runs tests programs and collects their " + "results."; + +atf_run::atf_run(void) : + app(m_description, "atf-run(1)", "atf(7)") +{ +} + +void +atf_run::process_option(int ch, const char* arg) +{ + switch (ch) { + case 'v': + parse_vflag(arg); + break; + + default: + UNREACHABLE; + } +} + +std::string +atf_run::specific_args(void) + const +{ + return "[test-program1 .. test-programN]"; +} + +atf_run::options_set +atf_run::specific_options(void) + const +{ + using atf::application::option; + options_set opts; + opts.insert(option('v', "var=value", "Sets the configuration variable " + "`var' to `value'; overrides " + "values in configuration files")); + return opts; +} + +void +atf_run::parse_vflag(const std::string& str) +{ + if (str.empty()) + throw std::runtime_error("-v requires a non-empty argument"); + + std::vector< std::string > ws = atf::text::split(str, "="); + if (ws.size() == 1 && str[str.length() - 1] == '=') { + m_cmdline_vars[ws[0]] = ""; + } else { + if (ws.size() != 2) + throw std::runtime_error("-v requires an argument of the form " + "var=value"); + + m_cmdline_vars[ws[0]] = ws[1]; + } +} + +int +atf_run::run_test(const atf::fs::path& tp, + impl::atf_tps_writer& w, + const atf::tests::vars_map& config) +{ + atf::fs::file_info fi(tp); + + int errcode; + if (fi.get_type() == atf::fs::file_info::dir_type) + errcode = run_test_directory(tp, w); + else { + const atf::tests::vars_map effective_config = + impl::merge_configs(config, m_cmdline_vars); + + errcode = run_test_program(tp, w, effective_config); + } + return errcode; +} + +int +atf_run::run_test_directory(const atf::fs::path& tp, + impl::atf_tps_writer& w) +{ + impl::atffile af = impl::read_atffile(tp / "Atffile"); + + atf::tests::vars_map test_suite_vars; + { + atf::tests::vars_map::const_iterator iter = + af.props().find("test-suite"); + INV(iter != af.props().end()); + test_suite_vars = impl::read_config_files((*iter).second); + } + + bool ok = true; + for (std::vector< std::string >::const_iterator iter = af.tps().begin(); + iter != af.tps().end(); iter++) { + const bool result = run_test(tp / *iter, w, + impl::merge_configs(af.conf(), test_suite_vars)); + ok &= (result == EXIT_SUCCESS); + } + + return ok ? EXIT_SUCCESS : EXIT_FAILURE; +} + +impl::test_case_result +atf_run::get_test_case_result(const std::string& broken_reason, + const atf::process::status& s, + const atf::fs::path& resfile) + const +{ + using atf::text::to_string; + using impl::read_test_case_result; + using impl::test_case_result; + + if (!broken_reason.empty()) { + test_case_result tcr; + + try { + tcr = read_test_case_result(resfile); + + if (tcr.state() == "expected_timeout") { + return tcr; + } else { + return test_case_result("failed", -1, broken_reason); + } + } catch (const std::runtime_error&) { + return test_case_result("failed", -1, broken_reason); + } + } + + if (s.exited()) { + test_case_result tcr; + + try { + tcr = read_test_case_result(resfile); + } catch (const std::runtime_error& e) { + return test_case_result("failed", -1, "Test case exited " + "normally but failed to create the results file: " + + std::string(e.what())); + } + + if (tcr.state() == "expected_death") { + return tcr; + } else if (tcr.state() == "expected_exit") { + if (tcr.value() == -1 || s.exitstatus() == tcr.value()) + return tcr; + else + return test_case_result("failed", -1, "Test case was " + "expected to exit with a " + to_string(tcr.value()) + + " error code but returned " + to_string(s.exitstatus())); + } else if (tcr.state() == "expected_failure") { + if (s.exitstatus() == EXIT_SUCCESS) + return tcr; + else + return test_case_result("failed", -1, "Test case returned an " + "error in expected_failure mode but it should not have"); + } else if (tcr.state() == "expected_signal") { + return test_case_result("failed", -1, "Test case exited cleanly " + "but was expected to receive a signal"); + } else if (tcr.state() == "failed") { + if (s.exitstatus() == EXIT_SUCCESS) + return test_case_result("failed", -1, "Test case " + "exited successfully but reported failure"); + else + return tcr; + } else if (tcr.state() == "passed") { + if (s.exitstatus() == EXIT_SUCCESS) + return tcr; + else + return test_case_result("failed", -1, "Test case exited as " + "passed but reported an error"); + } else if (tcr.state() == "skipped") { + if (s.exitstatus() == EXIT_SUCCESS) + return tcr; + else + return test_case_result("failed", -1, "Test case exited as " + "skipped but reported an error"); + } + } else if (s.signaled()) { + test_case_result tcr; + + try { + tcr = read_test_case_result(resfile); + } catch (const std::runtime_error&) { + return test_case_result("failed", -1, "Test program received " + "signal " + atf::text::to_string(s.termsig()) + + (s.coredump() ? " (core dumped)" : "")); + } + + if (tcr.state() == "expected_death") { + return tcr; + } else if (tcr.state() == "expected_signal") { + if (tcr.value() == -1 || s.termsig() == tcr.value()) + return tcr; + else + return test_case_result("failed", -1, "Test case was " + "expected to exit due to a " + to_string(tcr.value()) + + " signal but got " + to_string(s.termsig())); + } else { + return test_case_result("failed", -1, "Test program received " + "signal " + atf::text::to_string(s.termsig()) + + (s.coredump() ? " (core dumped)" : "") + " and created a " + "bogus results file"); + } + } + UNREACHABLE; + return test_case_result(); +} + +int +atf_run::run_test_program(const atf::fs::path& tp, + impl::atf_tps_writer& w, + const atf::tests::vars_map& config) +{ + int errcode = EXIT_SUCCESS; + + impl::metadata md; + try { + md = impl::get_metadata(tp, config); + } catch (const atf::parser::format_error& e) { + w.start_tp(tp.str(), 0); + w.end_tp("Invalid format for test case list: " + std::string(e.what())); + return EXIT_FAILURE; + } catch (const atf::parser::parse_errors& e) { + const std::string reason = atf::text::join(e, "; "); + w.start_tp(tp.str(), 0); + w.end_tp("Invalid format for test case list: " + reason); + return EXIT_FAILURE; + } + + impl::temp_dir resdir(atf::fs::path(atf::config::get("atf_workdir")) / + "atf-run.XXXXXX"); + + w.start_tp(tp.str(), md.test_cases.size()); + if (md.test_cases.empty()) { + w.end_tp("Bogus test program: reported 0 test cases"); + errcode = EXIT_FAILURE; + } else { + for (std::map< std::string, atf::tests::vars_map >::const_iterator iter + = md.test_cases.begin(); iter != md.test_cases.end(); iter++) { + const std::string& tcname = (*iter).first; + const atf::tests::vars_map& tcmd = (*iter).second; + + w.start_tc(tcname); + + try { + const std::string& reqfail = impl::check_requirements( + tcmd, config); + if (!reqfail.empty()) { + w.end_tc("skipped", reqfail); + continue; + } + } catch (const std::runtime_error& e) { + w.end_tc("failed", e.what()); + errcode = EXIT_FAILURE; + continue; + } + + const std::pair< int, int > user = impl::get_required_user( + tcmd, config); + + atf::fs::path resfile = resdir.get_path() / "tcr"; + INV(!atf::fs::exists(resfile)); + try { + const bool has_cleanup = atf::text::to_bool( + (*tcmd.find("has.cleanup")).second); + + impl::temp_dir workdir(atf::fs::path(atf::config::get( + "atf_workdir")) / "atf-run.XXXXXX"); + if (user.first != -1 && user.second != -1) { + if (::chown(workdir.get_path().c_str(), user.first, + user.second) == -1) { + throw atf::system_error("chown(" + + workdir.get_path().str() + ")", "chown(2) failed", + errno); + } + resfile = workdir.get_path() / "tcr"; + } + + std::pair< std::string, const atf::process::status > s = + impl::run_test_case(tp, tcname, "body", tcmd, config, + resfile, workdir.get_path(), w); + if (s.second.signaled() && s.second.coredump()) + dump_stacktrace(tp, s.second, workdir.get_path(), w); + if (has_cleanup) + (void)impl::run_test_case(tp, tcname, "cleanup", tcmd, + config, resfile, workdir.get_path(), w); + + // TODO: Force deletion of workdir. + + impl::test_case_result tcr = get_test_case_result(s.first, + s.second, resfile); + + w.end_tc(tcr.state(), tcr.reason()); + if (tcr.state() == "failed") + errcode = EXIT_FAILURE; + } catch (...) { + if (atf::fs::exists(resfile)) + atf::fs::remove(resfile); + throw; + } + if (atf::fs::exists(resfile)) + atf::fs::remove(resfile); + + } + w.end_tp(""); + } + + return errcode; +} + +size_t +atf_run::count_tps(std::vector< std::string > tps) + const +{ + size_t ntps = 0; + + for (std::vector< std::string >::const_iterator iter = tps.begin(); + iter != tps.end(); iter++) { + atf::fs::path tp(*iter); + atf::fs::file_info fi(tp); + + if (fi.get_type() == atf::fs::file_info::dir_type) { + impl::atffile af = impl::read_atffile(tp / "Atffile"); + std::vector< std::string > aux = af.tps(); + for (std::vector< std::string >::iterator i2 = aux.begin(); + i2 != aux.end(); i2++) + *i2 = (tp / *i2).str(); + ntps += count_tps(aux); + } else + ntps++; + } + + return ntps; +} + +static +void +call_hook(const std::string& tool, const std::string& hook) +{ + const atf::fs::path sh(atf::config::get("atf_shell")); + const atf::fs::path hooks = + atf::fs::path(atf::config::get("atf_pkgdatadir")) / (tool + ".hooks"); + + const atf::process::status s = + atf::process::exec(sh, + atf::process::argv_array(sh.c_str(), hooks.c_str(), + hook.c_str(), NULL), + atf::process::stream_inherit(), + atf::process::stream_inherit()); + + + if (!s.exited() || s.exitstatus() != EXIT_SUCCESS) + throw std::runtime_error("Failed to run the '" + hook + "' hook " + "for '" + tool + "'"); +} + +int +atf_run::main(void) +{ + impl::atffile af = impl::read_atffile(atf::fs::path("Atffile")); + + std::vector< std::string > tps; + tps = af.tps(); + if (m_argc >= 1) { + // TODO: Ensure that the given test names are listed in the + // Atffile. Take into account that the file can be using globs. + tps.clear(); + for (int i = 0; i < m_argc; i++) + tps.push_back(m_argv[i]); + } + + // Read configuration data for this test suite. + atf::tests::vars_map test_suite_vars; + { + atf::tests::vars_map::const_iterator iter = + af.props().find("test-suite"); + INV(iter != af.props().end()); + test_suite_vars = impl::read_config_files((*iter).second); + } + + impl::atf_tps_writer w(std::cout); + call_hook("atf-run", "info_start_hook"); + w.ntps(count_tps(tps)); + + bool ok = true; + for (std::vector< std::string >::const_iterator iter = tps.begin(); + iter != tps.end(); iter++) { + const bool result = run_test(atf::fs::path(*iter), w, + impl::merge_configs(af.conf(), test_suite_vars)); + ok &= (result == EXIT_SUCCESS); + } + + call_hook("atf-run", "info_end_hook"); + + return ok ? EXIT_SUCCESS : EXIT_FAILURE; +} + +int +main(int argc, char* const* argv) +{ + return atf_run().run(argc, argv); +} diff --git a/atf-run/atffile.cpp b/atf-run/atffile.cpp new file mode 100644 index 0000000000000..22ece645d5fbb --- /dev/null +++ b/atf-run/atffile.cpp @@ -0,0 +1,343 @@ +// +// Automated Testing Framework (atf) +// +// 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. +// + +#include <fstream> + +#include "atf-c/defs.h" + +#include "atf-c++/detail/exceptions.hpp" +#include "atf-c++/detail/expand.hpp" +#include "atf-c++/detail/parser.hpp" +#include "atf-c++/detail/sanity.hpp" + +#include "atffile.hpp" + +namespace impl = atf::atf_run; +namespace detail = atf::atf_run::detail; + +// ------------------------------------------------------------------------ +// The "atf_atffile" auxiliary parser. +// ------------------------------------------------------------------------ + +namespace atf_atffile { + +static const atf::parser::token_type eof_type = 0; +static const atf::parser::token_type nl_type = 1; +static const atf::parser::token_type text_type = 2; +static const atf::parser::token_type colon_type = 3; +static const atf::parser::token_type conf_type = 4; +static const atf::parser::token_type dblquote_type = 5; +static const atf::parser::token_type equal_type = 6; +static const atf::parser::token_type hash_type = 7; +static const atf::parser::token_type prop_type = 8; +static const atf::parser::token_type tp_type = 9; +static const atf::parser::token_type tp_glob_type = 10; + +class tokenizer : public atf::parser::tokenizer< std::istream > { +public: + tokenizer(std::istream& is, size_t curline) : + atf::parser::tokenizer< std::istream > + (is, true, eof_type, nl_type, text_type, curline) + { + add_delim(':', colon_type); + add_delim('=', equal_type); + add_delim('#', hash_type); + add_quote('"', dblquote_type); + add_keyword("conf", conf_type); + add_keyword("prop", prop_type); + add_keyword("tp", tp_type); + add_keyword("tp-glob", tp_glob_type); + } +}; + +} // namespace atf_atffile + +// ------------------------------------------------------------------------ +// The "atf_atffile_reader" class. +// ------------------------------------------------------------------------ + +detail::atf_atffile_reader::atf_atffile_reader(std::istream& is) : + m_is(is) +{ +} + +detail::atf_atffile_reader::~atf_atffile_reader(void) +{ +} + +void +detail::atf_atffile_reader::got_conf( + const std::string& name ATF_DEFS_ATTRIBUTE_UNUSED, + const std::string& val ATF_DEFS_ATTRIBUTE_UNUSED) +{ +} + +void +detail::atf_atffile_reader::got_prop( + const std::string& name ATF_DEFS_ATTRIBUTE_UNUSED, + const std::string& val ATF_DEFS_ATTRIBUTE_UNUSED) +{ +} + +void +detail::atf_atffile_reader::got_tp( + const std::string& name ATF_DEFS_ATTRIBUTE_UNUSED, + bool isglob ATF_DEFS_ATTRIBUTE_UNUSED) +{ +} + +void +detail::atf_atffile_reader::got_eof(void) +{ +} + +void +detail::atf_atffile_reader::read(void) +{ + using atf::parser::parse_error; + using namespace atf_atffile; + + std::pair< size_t, atf::parser::headers_map > hml = + atf::parser::read_headers(m_is, 1); + atf::parser::validate_content_type(hml.second, + "application/X-atf-atffile", 1); + + tokenizer tkz(m_is, hml.first); + atf::parser::parser< tokenizer > p(tkz); + + for (;;) { + try { + atf::parser::token t = + p.expect(conf_type, hash_type, prop_type, tp_type, + tp_glob_type, nl_type, eof_type, + "conf, #, prop, tp, tp-glob, a new line or eof"); + if (t.type() == eof_type) + break; + + if (t.type() == conf_type) { + t = p.expect(colon_type, "`:'"); + + t = p.expect(text_type, "variable name"); + std::string var = t.text(); + + t = p.expect(equal_type, "equal sign"); + + t = p.expect(text_type, "word or quoted string"); + ATF_PARSER_CALLBACK(p, got_conf(var, t.text())); + } else if (t.type() == hash_type) { + (void)p.rest_of_line(); + } else if (t.type() == prop_type) { + t = p.expect(colon_type, "`:'"); + + t = p.expect(text_type, "property name"); + std::string name = t.text(); + + t = p.expect(equal_type, "equale sign"); + + t = p.expect(text_type, "word or quoted string"); + ATF_PARSER_CALLBACK(p, got_prop(name, t.text())); + } else if (t.type() == tp_type) { + t = p.expect(colon_type, "`:'"); + + t = p.expect(text_type, "word or quoted string"); + ATF_PARSER_CALLBACK(p, got_tp(t.text(), false)); + } else if (t.type() == tp_glob_type) { + t = p.expect(colon_type, "`:'"); + + t = p.expect(text_type, "word or quoted string"); + ATF_PARSER_CALLBACK(p, got_tp(t.text(), true)); + } else if (t.type() == nl_type) { + continue; + } else + UNREACHABLE; + + t = p.expect(nl_type, hash_type, eof_type, + "new line or comment"); + if (t.type() == hash_type) { + (void)p.rest_of_line(); + t = p.next(); + } else if (t.type() == eof_type) + break; + } catch (const parse_error& pe) { + p.add_error(pe); + p.reset(nl_type); + } + } + + ATF_PARSER_CALLBACK(p, got_eof()); +} + +// ------------------------------------------------------------------------ +// The "reader" helper class. +// ------------------------------------------------------------------------ + +class reader : public detail::atf_atffile_reader { + const atf::fs::directory& m_dir; + atf::tests::vars_map m_conf, m_props; + std::vector< std::string > m_tps; + + void + got_tp(const std::string& name, bool isglob) + { + if (isglob) { + std::vector< std::string > ms = + atf::expand::expand_glob(name, m_dir.names()); + // Cannot use m_tps.insert(iterator, begin, end) here because it + // does not work under Solaris. + for (std::vector< std::string >::const_iterator iter = ms.begin(); + iter != ms.end(); iter++) + m_tps.push_back(*iter); + } else { + if (m_dir.find(name) == m_dir.end()) + throw atf::not_found_error< atf::fs::path > + ("Cannot locate the " + name + " file", + atf::fs::path(name)); + m_tps.push_back(name); + } + } + + void + got_prop(const std::string& name, const std::string& val) + { + m_props[name] = val; + } + + void + got_conf(const std::string& var, const std::string& val) + { + m_conf[var] = val; + } + +public: + reader(std::istream& is, const atf::fs::directory& dir) : + detail::atf_atffile_reader(is), + m_dir(dir) + { + } + + const atf::tests::vars_map& + conf(void) + const + { + return m_conf; + } + + const atf::tests::vars_map& + props(void) + const + { + return m_props; + } + + const std::vector< std::string >& + tps(void) + const + { + return m_tps; + } +}; + +// ------------------------------------------------------------------------ +// The "atffile" class. +// ------------------------------------------------------------------------ + +impl::atffile::atffile(const atf::tests::vars_map& config_vars, + const std::vector< std::string >& test_program_names, + const atf::tests::vars_map& properties) : + m_conf(config_vars), + m_tps(test_program_names), + m_props(properties) +{ + PRE(properties.find("test-suite") != properties.end()); +} + +const std::vector< std::string >& +impl::atffile::tps(void) + const +{ + return m_tps; +} + +const atf::tests::vars_map& +impl::atffile::conf(void) + const +{ + return m_conf; +} + +const atf::tests::vars_map& +impl::atffile::props(void) + const +{ + return m_props; +} + +// ------------------------------------------------------------------------ +// Free functions. +// ------------------------------------------------------------------------ + +// XXX Glob expansion and file existance checks certainly do not belong in +// a *parser*. This needs to be taken out... +impl::atffile +impl::read_atffile(const atf::fs::path& filename) +{ + // Scan the directory where the atffile lives in to gather a list of + // all possible test programs in it. + fs::directory dir(filename.branch_path()); + dir.erase(filename.leaf_name()); + fs::directory::iterator iter = dir.begin(); + while (iter != dir.end()) { + const std::string& name = (*iter).first; + const fs::file_info& fi = (*iter).second; + + // Discard hidden files and non-executable ones so that they are + // not candidates for glob matching. + if (name[0] == '.' || (!fi.is_owner_executable() && + !fi.is_group_executable())) + dir.erase(iter++); + else + iter++; + } + + // Parse the atffile. + std::ifstream is(filename.c_str()); + if (!is) + throw atf::not_found_error< fs::path > + ("Cannot open Atffile", filename); + reader r(is, dir); + r.read(); + is.close(); + + // Sanity checks. + if (r.props().find("test-suite") == r.props().end()) + throw atf::not_found_error< std::string > + ("Undefined property `test-suite'", "test-suite"); + + return atffile(r.conf(), r.tps(), r.props()); +} diff --git a/atf-run/atffile.hpp b/atf-run/atffile.hpp new file mode 100644 index 0000000000000..8c915c24cf402 --- /dev/null +++ b/atf-run/atffile.hpp @@ -0,0 +1,95 @@ +// +// Automated Testing Framework (atf) +// +// 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. +// + +#if !defined(_ATF_RUN_ATFFILE_HPP_) +#define _ATF_RUN_ATFFILE_HPP_ + +#include <string> +#include <vector> + +#include "atf-c++/tests.hpp" + +#include "atf-c++/detail/fs.hpp" + +namespace atf { +namespace atf_run { + +// ------------------------------------------------------------------------ +// The "atf_atffile_reader" class. +// ------------------------------------------------------------------------ + +namespace detail { + +class atf_atffile_reader { + std::istream& m_is; + +protected: + virtual void got_conf(const std::string&, const std::string &); + virtual void got_prop(const std::string&, const std::string &); + virtual void got_tp(const std::string&, bool); + virtual void got_eof(void); + +public: + atf_atffile_reader(std::istream&); + virtual ~atf_atffile_reader(void); + + void read(void); +}; + +} // namespace detail + +// ------------------------------------------------------------------------ +// The "atffile" class. +// ------------------------------------------------------------------------ + +class atffile { + atf::tests::vars_map m_conf; + std::vector< std::string > m_tps; + atf::tests::vars_map m_props; + +public: + atffile(const atf::tests::vars_map&, + const std::vector< std::string >&, + const atf::tests::vars_map&); + + const atf::tests::vars_map& conf(void) const; + const std::vector< std::string >& tps(void) const; + const atf::tests::vars_map& props(void) const; +}; + +// ------------------------------------------------------------------------ +// Free functions. +// ------------------------------------------------------------------------ + +atffile read_atffile(const fs::path&); + +} // namespace atf_run +} // namespace atf + +#endif // !defined(_ATF_RUN_ATFFILE_HPP_) diff --git a/atf-run/atffile_test.cpp b/atf-run/atffile_test.cpp new file mode 100644 index 0000000000000..e5781a3370b99 --- /dev/null +++ b/atf-run/atffile_test.cpp @@ -0,0 +1,636 @@ +// +// Automated Testing Framework (atf) +// +// Copyright (c) 2009 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/stat.h> +} + +#include <algorithm> +#include <fstream> +#include <memory> + +#include "atf-c++/macros.hpp" + +#include "atf-c++/detail/exceptions.hpp" +#include "atf-c++/detail/test_helpers.hpp" + +#include "atffile.hpp" + +namespace detail = atf::atf_run::detail; + +// ------------------------------------------------------------------------ +// Auxiliary functions. +// ------------------------------------------------------------------------ + +namespace { + +static +std::auto_ptr< std::ofstream > +new_atffile(void) +{ + std::auto_ptr< std::ofstream > os(new std::ofstream("Atffile")); + ATF_REQUIRE(*os); + + (*os) << "Content-Type: application/X-atf-atffile; version=\"1\"\n\n"; + return os; +} + +static +void +touch_exec(const char* name) +{ + std::ofstream os(name); + ATF_REQUIRE(os); + os.close(); + ATF_REQUIRE(::chmod(name, S_IRWXU) != -1); +} + +static inline +bool +is_in(const std::string& value, const std::vector< std::string >& v) +{ + return std::find(v.begin(), v.end(), value) != v.end(); +} + +} // anonymous namespace + +// ------------------------------------------------------------------------ +// Tests cases for the "atffile" parser. +// ------------------------------------------------------------------------ + +class atffile_reader : protected detail::atf_atffile_reader { + void + got_conf(const std::string& name, const std::string& val) + { + m_calls.push_back("got_conf(" + name + ", " + val + ")"); + } + + void + got_prop(const std::string& name, const std::string& val) + { + m_calls.push_back("got_prop(" + name + ", " + val + ")"); + } + + void + got_tp(const std::string& name, bool isglob) + { + m_calls.push_back("got_tp(" + name + ", " + (isglob ? "true" : "false") + + ")"); + } + + void + got_eof(void) + { + m_calls.push_back("got_eof()"); + } + +public: + atffile_reader(std::istream& is) : + detail::atf_atffile_reader(is) + { + } + + void + read(void) + { + atf_atffile_reader::read(); + } + + std::vector< std::string > m_calls; +}; + +ATF_TEST_CASE_WITHOUT_HEAD(atffile_1); +ATF_TEST_CASE_BODY(atffile_1) +{ + const char* input = + "Content-Type: application/X-atf-atffile; version=\"1\"\n" + "\n" + ; + + const char* exp_calls[] = { + "got_eof()", + NULL + }; + + const char* exp_errors[] = { + NULL + }; + + do_parser_test< atffile_reader >(input, exp_calls, exp_errors); +} + +ATF_TEST_CASE_WITHOUT_HEAD(atffile_2); +ATF_TEST_CASE_BODY(atffile_2) +{ + const char* input = + "Content-Type: application/X-atf-atffile; version=\"1\"\n" + "\n" + "# This is a comment on a line of its own.\n" + "# And this is another one.\n" + "\n" + " # Another after some whitespace.\n" + "\n" + "# The last one after an empty line.\n" + ; + + const char* exp_calls[] = { + "got_eof()", + NULL + }; + + const char* exp_errors[] = { + NULL + }; + + do_parser_test< atffile_reader >(input, exp_calls, exp_errors); +} + +ATF_TEST_CASE_WITHOUT_HEAD(atffile_3); +ATF_TEST_CASE_BODY(atffile_3) +{ + const char* input = + "Content-Type: application/X-atf-atffile; version=\"1\"\n" + "\n" + "conf: var1=value1\n" + "conf: var2 = value2\n" + "conf: var3 = value3\n" + "conf: var4 = value4\n" + "\n" + "conf:var5=value5\n" + " conf:var6=value6\n" + "\n" + "conf: var7 = \"This is a long value.\"\n" + "conf: var8 = \"Single-word\"\n" + "conf: var9 = \" Single-word \"\n" + "conf: var10 = Single-word\n" + ; + + const char* exp_calls[] = { + "got_conf(var1, value1)", + "got_conf(var2, value2)", + "got_conf(var3, value3)", + "got_conf(var4, value4)", + "got_conf(var5, value5)", + "got_conf(var6, value6)", + "got_conf(var7, This is a long value.)", + "got_conf(var8, Single-word)", + "got_conf(var9, Single-word )", + "got_conf(var10, Single-word)", + "got_eof()", + NULL + }; + + const char* exp_errors[] = { + NULL + }; + + do_parser_test< atffile_reader >(input, exp_calls, exp_errors); +} + +ATF_TEST_CASE_WITHOUT_HEAD(atffile_4); +ATF_TEST_CASE_BODY(atffile_4) +{ + const char* input = + "Content-Type: application/X-atf-atffile; version=\"1\"\n" + "\n" + "prop: var1=value1\n" + "prop: var2 = value2\n" + "prop: var3 = value3\n" + "prop: var4 = value4\n" + "\n" + "prop:var5=value5\n" + " prop:var6=value6\n" + "\n" + "prop: var7 = \"This is a long value.\"\n" + "prop: var8 = \"Single-word\"\n" + "prop: var9 = \" Single-word \"\n" + "prop: var10 = Single-word\n" + ; + + const char* exp_calls[] = { + "got_prop(var1, value1)", + "got_prop(var2, value2)", + "got_prop(var3, value3)", + "got_prop(var4, value4)", + "got_prop(var5, value5)", + "got_prop(var6, value6)", + "got_prop(var7, This is a long value.)", + "got_prop(var8, Single-word)", + "got_prop(var9, Single-word )", + "got_prop(var10, Single-word)", + "got_eof()", + NULL + }; + + const char* exp_errors[] = { + NULL + }; + + do_parser_test< atffile_reader >(input, exp_calls, exp_errors); +} + +ATF_TEST_CASE_WITHOUT_HEAD(atffile_5); +ATF_TEST_CASE_BODY(atffile_5) +{ + const char* input = + "Content-Type: application/X-atf-atffile; version=\"1\"\n" + "\n" + "tp:foo\n" + "tp: foo\n" + "tp: foo\n" + "tp: foo\n" + "tp: foo\n" + "tp: \"name with spaces\"\n" + "tp: \"single-word\"\n" + "tp: single-word\n" + "\n" + "tp-glob:foo*?bar\n" + "tp-glob: foo*?bar\n" + "tp-glob: foo*?bar\n" + "tp-glob: foo*?bar\n" + "tp-glob: foo*?bar\n" + "tp-glob: \"glob * with ? spaces\"\n" + "tp-glob: \"single-*-word\"\n" + "tp-glob: single-*-word\n" + ; + + const char* exp_calls[] = { + "got_tp(foo, false)", + "got_tp(foo, false)", + "got_tp(foo, false)", + "got_tp(foo, false)", + "got_tp(foo, false)", + "got_tp(name with spaces, false)", + "got_tp(single-word, false)", + "got_tp(single-word, false)", + "got_tp(foo*?bar, true)", + "got_tp(foo*?bar, true)", + "got_tp(foo*?bar, true)", + "got_tp(foo*?bar, true)", + "got_tp(foo*?bar, true)", + "got_tp(glob * with ? spaces, true)", + "got_tp(single-*-word, true)", + "got_tp(single-*-word, true)", + "got_eof()", + NULL + }; + + const char* exp_errors[] = { + NULL + }; + + do_parser_test< atffile_reader >(input, exp_calls, exp_errors); +} + +ATF_TEST_CASE_WITHOUT_HEAD(atffile_6); +ATF_TEST_CASE_BODY(atffile_6) +{ + const char* input = + "Content-Type: application/X-atf-atffile; version=\"1\"\n" + "\n" + "prop: foo = bar # A comment.\n" + "conf: foo = bar # A comment.\n" + "tp: foo # A comment.\n" + "tp-glob: foo # A comment.\n" + ; + + const char* exp_calls[] = { + "got_prop(foo, bar)", + "got_conf(foo, bar)", + "got_tp(foo, false)", + "got_tp(foo, true)", + "got_eof()", + NULL + }; + + const char* exp_errors[] = { + NULL + }; + + do_parser_test< atffile_reader >(input, exp_calls, exp_errors); +} + +ATF_TEST_CASE_WITHOUT_HEAD(atffile_50); +ATF_TEST_CASE_BODY(atffile_50) +{ + const char* input = + "Content-Type: application/X-atf-atffile; version=\"1\"\n" + "\n" + "foo\n" + ; + + const char* exp_calls[] = { + NULL + }; + + // NO_CHECK_STYLE_BEGIN + const char* exp_errors[] = { + "3: Unexpected token `foo'; expected conf, #, prop, tp, tp-glob, a new line or eof", + NULL + }; + // NO_CHECK_STYLE_END + + do_parser_test< atffile_reader >(input, exp_calls, exp_errors); +} + +ATF_TEST_CASE_WITHOUT_HEAD(atffile_51); +ATF_TEST_CASE_BODY(atffile_51) +{ + const char* input = + "Content-Type: application/X-atf-atffile; version=\"1\"\n" + "\n" + "foo bar\n" + "baz\n" + ; + + const char* exp_calls[] = { + NULL + }; + + // NO_CHECK_STYLE_BEGIN + const char* exp_errors[] = { + "3: Unexpected token `foo'; expected conf, #, prop, tp, tp-glob, a new line or eof", + "4: Unexpected token `baz'; expected conf, #, prop, tp, tp-glob, a new line or eof", + NULL + }; + // NO_CHECK_STYLE_END + + do_parser_test< atffile_reader >(input, exp_calls, exp_errors); +} + +ATF_TEST_CASE_WITHOUT_HEAD(atffile_52); +ATF_TEST_CASE_BODY(atffile_52) +{ + const char* input = + "Content-Type: application/X-atf-atffile; version=\"1\"\n" + "\n" + "conf\n" + "conf:\n" + "conf: foo =\n" + "conf: bar = # A comment.\n" + "\n" + "prop\n" + "prop:\n" + "prop: foo =\n" + "prop: bar = # A comment.\n" + "\n" + "tp\n" + "tp:\n" + "tp: # A comment.\n" + "\n" + "tp-glob\n" + "tp-glob:\n" + "tp-glob: # A comment.\n" + ; + + const char* exp_calls[] = { + NULL + }; + + const char* exp_errors[] = { + "3: Unexpected token `<<NEWLINE>>'; expected `:'", + "4: Unexpected token `<<NEWLINE>>'; expected variable name", + "5: Unexpected token `<<NEWLINE>>'; expected word or quoted string", + "6: Unexpected token `#'; expected word or quoted string", + "8: Unexpected token `<<NEWLINE>>'; expected `:'", + "9: Unexpected token `<<NEWLINE>>'; expected property name", + "10: Unexpected token `<<NEWLINE>>'; expected word or quoted string", + "11: Unexpected token `#'; expected word or quoted string", + "13: Unexpected token `<<NEWLINE>>'; expected `:'", + "14: Unexpected token `<<NEWLINE>>'; expected word or quoted string", + "15: Unexpected token `#'; expected word or quoted string", + "17: Unexpected token `<<NEWLINE>>'; expected `:'", + "18: Unexpected token `<<NEWLINE>>'; expected word or quoted string", + "19: Unexpected token `#'; expected word or quoted string", + NULL + }; + + do_parser_test< atffile_reader >(input, exp_calls, exp_errors); +} + +ATF_TEST_CASE_WITHOUT_HEAD(atffile_53); +ATF_TEST_CASE_BODY(atffile_53) +{ + const char* input = + "Content-Type: application/X-atf-atffile; version=\"1\"\n" + "\n" + "prop: foo = \"Correct value\" # With comment.\n" + "\n" + "prop: bar = # A comment.\n" + "\n" + "prop: baz = \"Last variable\"\n" + "\n" + "# End of file.\n" + ; + + const char* exp_calls[] = { + "got_prop(foo, Correct value)", + NULL + }; + + const char* exp_errors[] = { + "5: Unexpected token `#'; expected word or quoted string", + NULL + }; + + do_parser_test< atffile_reader >(input, exp_calls, exp_errors); +} + +ATF_TEST_CASE_WITHOUT_HEAD(atffile_54); +ATF_TEST_CASE_BODY(atffile_54) +{ + const char* input = + "Content-Type: application/X-atf-atffile; version=\"1\"\n" + "\n" + "prop: foo = \"\n" + "prop: bar = \"text\n" + "prop: baz = \"te\\\"xt\n" + "prop: last = \"\\\"\n" + ; + + const char* exp_calls[] = { + NULL + }; + + const char* exp_errors[] = { + "3: Missing double quotes before end of line", + "4: Missing double quotes before end of line", + "5: Missing double quotes before end of line", + "6: Missing double quotes before end of line", + NULL + }; + + do_parser_test< atffile_reader >(input, exp_calls, exp_errors); +} + +// ------------------------------------------------------------------------ +// Tests cases for the "atffile" class. +// ------------------------------------------------------------------------ + +ATF_TEST_CASE(atffile_getters); +ATF_TEST_CASE_HEAD(atffile_getters) {} +ATF_TEST_CASE_BODY(atffile_getters) { + atf::tests::vars_map config_vars; + config_vars["config-var-1"] = "value 1"; + + std::vector< std::string > test_program_names; + test_program_names.push_back("test-program-1"); + + atf::tests::vars_map properties; + properties["test-suite"] = "a test name"; + + const atf::atf_run::atffile atffile(config_vars, test_program_names, + properties); + ATF_REQUIRE(config_vars == atffile.conf()); + ATF_REQUIRE(test_program_names == atffile.tps()); + ATF_REQUIRE(properties == atffile.props()); +} + +// ------------------------------------------------------------------------ +// Tests cases for the free functions. +// ------------------------------------------------------------------------ + +ATF_TEST_CASE_WITHOUT_HEAD(read_ok_simple); +ATF_TEST_CASE_BODY(read_ok_simple) { + std::auto_ptr< std::ofstream > os = new_atffile(); + (*os) << "prop: test-suite = foo\n"; + (*os) << "tp: tp-1\n"; + (*os) << "conf: var1 = value1\n"; + (*os) << "tp: tp-2\n"; + (*os) << "tp: tp-3\n"; + (*os) << "prop: prop1 = propvalue1\n"; + (*os) << "conf: var2 = value2\n"; + (*os).close(); + + touch_exec("tp-1"); + touch_exec("tp-2"); + touch_exec("tp-3"); + + const atf::atf_run::atffile atffile = atf::atf_run::read_atffile( + atf::fs::path("Atffile")); + ATF_REQUIRE_EQ(2, atffile.conf().size()); + ATF_REQUIRE_EQ("value1", atffile.conf().find("var1")->second); + ATF_REQUIRE_EQ("value2", atffile.conf().find("var2")->second); + ATF_REQUIRE_EQ(3, atffile.tps().size()); + ATF_REQUIRE(is_in("tp-1", atffile.tps())); + ATF_REQUIRE(is_in("tp-2", atffile.tps())); + ATF_REQUIRE(is_in("tp-3", atffile.tps())); + ATF_REQUIRE_EQ(2, atffile.props().size()); + ATF_REQUIRE_EQ("foo", atffile.props().find("test-suite")->second); + ATF_REQUIRE_EQ("propvalue1", atffile.props().find("prop1")->second); +} + +ATF_TEST_CASE_WITHOUT_HEAD(read_ok_some_globs); +ATF_TEST_CASE_BODY(read_ok_some_globs) { + std::auto_ptr< std::ofstream > os = new_atffile(); + (*os) << "prop: test-suite = foo\n"; + (*os) << "tp: foo\n"; + (*os) << "tp-glob: *K*\n"; + (*os) << "tp: bar\n"; + (*os) << "tp-glob: t_*\n"; + (*os).close(); + + touch_exec("foo"); + touch_exec("bar"); + touch_exec("aK"); + touch_exec("KKKKK"); + touch_exec("t_hello"); + touch_exec("zzzt_hello"); + + const atf::atf_run::atffile atffile = atf::atf_run::read_atffile( + atf::fs::path("Atffile")); + ATF_REQUIRE_EQ(5, atffile.tps().size()); + ATF_REQUIRE(is_in("foo", atffile.tps())); + ATF_REQUIRE(is_in("bar", atffile.tps())); + ATF_REQUIRE(is_in("aK", atffile.tps())); + ATF_REQUIRE(is_in("KKKKK", atffile.tps())); + ATF_REQUIRE(is_in("t_hello", atffile.tps())); +} + +ATF_TEST_CASE_WITHOUT_HEAD(read_missing_test_suite); +ATF_TEST_CASE_BODY(read_missing_test_suite) { + std::auto_ptr< std::ofstream > os = new_atffile(); + (*os).close(); + + try { + (void)atf::atf_run::read_atffile(atf::fs::path("Atffile")); + ATF_FAIL("Missing property 'test-suite' did not raise an error"); + } catch (const atf::not_found_error< std::string >& e) { + ATF_REQUIRE_EQ("test-suite", e.get_value()); + } +} + +ATF_TEST_CASE_WITHOUT_HEAD(read_missing_test_program); +ATF_TEST_CASE_BODY(read_missing_test_program) { + std::auto_ptr< std::ofstream > os = new_atffile(); + (*os) << "tp: foo\n"; + (*os) << "tp: bar\n"; + (*os) << "tp: baz\n"; + (*os).close(); + + touch_exec("foo"); + touch_exec("baz"); + + try { + (void)atf::atf_run::read_atffile(atf::fs::path("Atffile")); + ATF_FAIL("Missing file 'bar' did not raise an error"); + } catch (const atf::not_found_error< atf::fs::path >& e) { + ATF_REQUIRE_EQ("bar", e.get_value().str()); + } +} + +// ------------------------------------------------------------------------ +// Main. +// ------------------------------------------------------------------------ + +ATF_INIT_TEST_CASES(tcs) +{ + // Add the test cases for the parser class. + ATF_ADD_TEST_CASE(tcs, atffile_1); + ATF_ADD_TEST_CASE(tcs, atffile_2); + ATF_ADD_TEST_CASE(tcs, atffile_3); + ATF_ADD_TEST_CASE(tcs, atffile_4); + ATF_ADD_TEST_CASE(tcs, atffile_5); + ATF_ADD_TEST_CASE(tcs, atffile_6); + ATF_ADD_TEST_CASE(tcs, atffile_50); + ATF_ADD_TEST_CASE(tcs, atffile_51); + ATF_ADD_TEST_CASE(tcs, atffile_52); + ATF_ADD_TEST_CASE(tcs, atffile_53); + ATF_ADD_TEST_CASE(tcs, atffile_54); + + // Add the test cases for the atffile class. + ATF_ADD_TEST_CASE(tcs, atffile_getters); + + // Add the test cases for the free functions. + ATF_ADD_TEST_CASE(tcs, read_ok_simple); + ATF_ADD_TEST_CASE(tcs, read_ok_some_globs); + ATF_ADD_TEST_CASE(tcs, read_missing_test_suite); + ATF_ADD_TEST_CASE(tcs, read_missing_test_program); +} diff --git a/atf-run/bad_metadata_helper.c b/atf-run/bad_metadata_helper.c new file mode 100644 index 0000000000000..0f7fcb926acd7 --- /dev/null +++ b/atf-run/bad_metadata_helper.c @@ -0,0 +1,38 @@ +/* + * Automated Testing Framework (atf) + * + * 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. + */ + +#include <stdio.h> +#include <stdlib.h> + +int +main(void) +{ + printf("incorrectly formatted metadata\n"); + return EXIT_SUCCESS; +} diff --git a/atf-run/config.cpp b/atf-run/config.cpp new file mode 100644 index 0000000000000..0ea240fe20cd3 --- /dev/null +++ b/atf-run/config.cpp @@ -0,0 +1,224 @@ +// +// Automated Testing Framework (atf) +// +// 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. +// + +#include <fstream> +#include <vector> + +#include "atf-c/defs.h" + +#include "atf-c++/config.hpp" + +#include "atf-c++/detail/env.hpp" +#include "atf-c++/detail/fs.hpp" +#include "atf-c++/detail/sanity.hpp" +#include "atf-c++/detail/parser.hpp" + +#include "config.hpp" + +namespace impl = atf::atf_run; +namespace detail = atf::atf_run::detail; + +namespace { + +namespace atf_config { + +static const atf::parser::token_type eof_type = 0; +static const atf::parser::token_type nl_type = 1; +static const atf::parser::token_type text_type = 2; +static const atf::parser::token_type dblquote_type = 3; +static const atf::parser::token_type equal_type = 4; +static const atf::parser::token_type hash_type = 5; + +class tokenizer : public atf::parser::tokenizer< std::istream > { +public: + tokenizer(std::istream& is, size_t curline) : + atf::parser::tokenizer< std::istream > + (is, true, eof_type, nl_type, text_type, curline) + { + add_delim('=', equal_type); + add_delim('#', hash_type); + add_quote('"', dblquote_type); + } +}; + +} // namespace atf_config + +class config_reader : public detail::atf_config_reader { + atf::tests::vars_map m_vars; + + void + got_var(const std::string& var, const std::string& name) + { + m_vars[var] = name; + } + +public: + config_reader(std::istream& is) : + atf_config_reader(is) + { + } + + const atf::tests::vars_map& + get_vars(void) + const + { + return m_vars; + } +}; + +template< class K, class V > +static +void +merge_maps(std::map< K, V >& dest, const std::map< K, V >& src) +{ + for (typename std::map< K, V >::const_iterator iter = src.begin(); + iter != src.end(); iter++) + dest[(*iter).first] = (*iter).second; +} + +static +void +merge_config_file(const atf::fs::path& config_path, + atf::tests::vars_map& config) +{ + std::ifstream is(config_path.c_str()); + if (is) { + config_reader reader(is); + reader.read(); + merge_maps(config, reader.get_vars()); + } +} + +static +std::vector< atf::fs::path > +get_config_dirs(void) +{ + std::vector< atf::fs::path > dirs; + dirs.push_back(atf::fs::path(atf::config::get("atf_confdir"))); + if (atf::env::has("HOME")) + dirs.push_back(atf::fs::path(atf::env::get("HOME")) / ".atf"); + return dirs; +} + +} // anonymous namespace + +detail::atf_config_reader::atf_config_reader(std::istream& is) : + m_is(is) +{ +} + +detail::atf_config_reader::~atf_config_reader(void) +{ +} + +void +detail::atf_config_reader::got_var( + const std::string& var ATF_DEFS_ATTRIBUTE_UNUSED, + const std::string& val ATF_DEFS_ATTRIBUTE_UNUSED) +{ +} + +void +detail::atf_config_reader::got_eof(void) +{ +} + +void +detail::atf_config_reader::read(void) +{ + using atf::parser::parse_error; + using namespace atf_config; + + std::pair< size_t, atf::parser::headers_map > hml = + atf::parser::read_headers(m_is, 1); + atf::parser::validate_content_type(hml.second, + "application/X-atf-config", 1); + + tokenizer tkz(m_is, hml.first); + atf::parser::parser< tokenizer > p(tkz); + + for (;;) { + try { + atf::parser::token t = p.expect(eof_type, hash_type, text_type, + nl_type, + "eof, #, new line or text"); + if (t.type() == eof_type) + break; + + if (t.type() == hash_type) { + (void)p.rest_of_line(); + t = p.expect(nl_type, "new line"); + } else if (t.type() == text_type) { + std::string name = t.text(); + + t = p.expect(equal_type, "equal sign"); + + t = p.expect(text_type, "word or quoted string"); + ATF_PARSER_CALLBACK(p, got_var(name, t.text())); + + t = p.expect(nl_type, hash_type, "new line or comment"); + if (t.type() == hash_type) { + (void)p.rest_of_line(); + t = p.expect(nl_type, "new line"); + } + } else if (t.type() == nl_type) { + } else + UNREACHABLE; + } catch (const parse_error& pe) { + p.add_error(pe); + p.reset(nl_type); + } + } + + ATF_PARSER_CALLBACK(p, got_eof()); +} + +atf::tests::vars_map +impl::merge_configs(const atf::tests::vars_map& lower, + const atf::tests::vars_map& upper) +{ + atf::tests::vars_map merged = lower; + merge_maps(merged, upper); + return merged; +} + +atf::tests::vars_map +impl::read_config_files(const std::string& test_suite_name) +{ + atf::tests::vars_map config; + + const std::vector< atf::fs::path > dirs = get_config_dirs(); + for (std::vector< atf::fs::path >::const_iterator iter = dirs.begin(); + iter != dirs.end(); iter++) { + merge_config_file((*iter) / "common.conf", config); + merge_config_file((*iter) / (test_suite_name + ".conf"), config); + } + + return config; +} diff --git a/atf-run/config.hpp b/atf-run/config.hpp new file mode 100644 index 0000000000000..2cefec9a5fe9e --- /dev/null +++ b/atf-run/config.hpp @@ -0,0 +1,61 @@ +// +// Automated Testing Framework (atf) +// +// 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. +// + +#include <string> +#include <vector> + +#include "atf-c++/tests.hpp" + +namespace atf { +namespace atf_run { + +namespace detail { + +class atf_config_reader { + std::istream& m_is; + +protected: + virtual void got_var(const std::string&, const std::string &); + virtual void got_eof(void); + +public: + atf_config_reader(std::istream&); + virtual ~atf_config_reader(void); + + void read(void); +}; + +} // namespace detail + +atf::tests::vars_map merge_configs(const atf::tests::vars_map&, + const atf::tests::vars_map&); +atf::tests::vars_map read_config_files(const std::string&); + +} // namespace atf_run +} // namespace atf diff --git a/atf-run/config_test.cpp b/atf-run/config_test.cpp new file mode 100644 index 0000000000000..5b103a78acbbc --- /dev/null +++ b/atf-run/config_test.cpp @@ -0,0 +1,391 @@ +// +// Automated Testing Framework (atf) +// +// 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. +// + +#include "atf-c++/detail/env.hpp" +#include "atf-c++/detail/test_helpers.hpp" +#include "atf-c++/config.hpp" +#include "atf-c++/macros.hpp" + +#include "config.hpp" + +namespace impl = atf::atf_run; +namespace detail = atf::atf_run::detail; + +using atf::tests::vars_map; + +namespace atf { +namespace config { + +void __reinit(void); + +} // namespace config +} // namespace atf + +// ------------------------------------------------------------------------- +// Tests for the "config" parser. +// ------------------------------------------------------------------------- + +class config_reader : protected detail::atf_config_reader { + void + got_var(const std::string& name, const std::string& val) + { + m_calls.push_back("got_var(" + name + ", " + val + ")"); + } + + void + got_eof(void) + { + m_calls.push_back("got_eof()"); + } + +public: + config_reader(std::istream& is) : + detail::atf_config_reader(is) + { + } + + void + read(void) + { + atf_config_reader::read(); + } + + std::vector< std::string > m_calls; +}; + +ATF_TEST_CASE_WITHOUT_HEAD(config_1); +ATF_TEST_CASE_BODY(config_1) +{ + const char* input = + "Content-Type: application/X-atf-config; version=\"1\"\n" + "\n" + ; + + const char* exp_calls[] = { + "got_eof()", + NULL + }; + + const char* exp_errors[] = { + NULL + }; + + do_parser_test< config_reader >(input, exp_calls, exp_errors); +} + +ATF_TEST_CASE_WITHOUT_HEAD(config_2); +ATF_TEST_CASE_BODY(config_2) +{ + const char* input = + "Content-Type: application/X-atf-config; version=\"1\"\n" + "\n" + "# This is a comment on a line of its own.\n" + "# And this is another one.\n" + "\n" + " # Another after some whitespace.\n" + "\n" + "# The last one after an empty line.\n" + ; + + const char* exp_calls[] = { + "got_eof()", + NULL + }; + + const char* exp_errors[] = { + NULL + }; + + do_parser_test< config_reader >(input, exp_calls, exp_errors); +} + +ATF_TEST_CASE_WITHOUT_HEAD(config_3); +ATF_TEST_CASE_BODY(config_3) +{ + const char* input = + "Content-Type: application/X-atf-config; version=\"1\"\n" + "\n" + "var1=value1\n" + "var2 = value2\n" + "var3 = value3\n" + "var4 = value4\n" + "\n" + "var5=value5\n" + " var6=value6\n" + "\n" + "var7 = \"This is a long value.\"\n" + "var8 = \"Single-word\"\n" + "var9 = \" Single-word \"\n" + "var10 = Single-word\n" + ; + + const char* exp_calls[] = { + "got_var(var1, value1)", + "got_var(var2, value2)", + "got_var(var3, value3)", + "got_var(var4, value4)", + "got_var(var5, value5)", + "got_var(var6, value6)", + "got_var(var7, This is a long value.)", + "got_var(var8, Single-word)", + "got_var(var9, Single-word )", + "got_var(var10, Single-word)", + "got_eof()", + NULL + }; + + const char* exp_errors[] = { + NULL + }; + + do_parser_test< config_reader >(input, exp_calls, exp_errors); +} + +ATF_TEST_CASE_WITHOUT_HEAD(config_4); +ATF_TEST_CASE_BODY(config_4) +{ + const char* input = + "Content-Type: application/X-atf-config; version=\"1\"\n" + "\n" + "foo = bar # A comment.\n" + ; + + const char* exp_calls[] = { + "got_var(foo, bar)", + "got_eof()", + NULL + }; + + const char* exp_errors[] = { + NULL + }; + + do_parser_test< config_reader >(input, exp_calls, exp_errors); +} + +ATF_TEST_CASE_WITHOUT_HEAD(config_50); +ATF_TEST_CASE_BODY(config_50) +{ + const char* input = + "Content-Type: application/X-atf-config; version=\"1\"\n" + "\n" + "foo\n" + ; + + const char* exp_calls[] = { + NULL + }; + + const char* exp_errors[] = { + "3: Unexpected token `<<NEWLINE>>'; expected equal sign", + NULL + }; + + do_parser_test< config_reader >(input, exp_calls, exp_errors); +} + +ATF_TEST_CASE_WITHOUT_HEAD(config_51); +ATF_TEST_CASE_BODY(config_51) +{ + const char* input = + "Content-Type: application/X-atf-config; version=\"1\"\n" + "\n" + "foo bar\n" + "baz\n" + ; + + const char* exp_calls[] = { + NULL + }; + + const char* exp_errors[] = { + "3: Unexpected token `bar'; expected equal sign", + "4: Unexpected token `<<NEWLINE>>'; expected equal sign", + NULL + }; + + do_parser_test< config_reader >(input, exp_calls, exp_errors); +} + +ATF_TEST_CASE_WITHOUT_HEAD(config_52); +ATF_TEST_CASE_BODY(config_52) +{ + const char* input = + "Content-Type: application/X-atf-config; version=\"1\"\n" + "\n" + "foo =\n" + "bar = # A comment.\n" + ; + + const char* exp_calls[] = { + NULL + }; + + const char* exp_errors[] = { + "3: Unexpected token `<<NEWLINE>>'; expected word or quoted string", + "4: Unexpected token `#'; expected word or quoted string", + NULL + }; + + do_parser_test< config_reader >(input, exp_calls, exp_errors); +} + +ATF_TEST_CASE_WITHOUT_HEAD(config_53); +ATF_TEST_CASE_BODY(config_53) +{ + const char* input = + "Content-Type: application/X-atf-config; version=\"1\"\n" + "\n" + "foo = \"Correct value\" # With comment.\n" + "\n" + "bar = # A comment.\n" + "\n" + "baz = \"Last variable\"\n" + "\n" + "# End of file.\n" + ; + + const char* exp_calls[] = { + "got_var(foo, Correct value)", + NULL + }; + + const char* exp_errors[] = { + "5: Unexpected token `#'; expected word or quoted string", + NULL + }; + + do_parser_test< config_reader >(input, exp_calls, exp_errors); +} + +ATF_TEST_CASE_WITHOUT_HEAD(config_54); +ATF_TEST_CASE_BODY(config_54) +{ + const char* input = + "Content-Type: application/X-atf-config; version=\"1\"\n" + "\n" + "foo = \"\n" + "bar = \"text\n" + "baz = \"te\\\"xt\n" + "last = \"\\\"\n" + ; + + const char* exp_calls[] = { + NULL + }; + + const char* exp_errors[] = { + "3: Missing double quotes before end of line", + "4: Missing double quotes before end of line", + "5: Missing double quotes before end of line", + "6: Missing double quotes before end of line", + NULL + }; + + do_parser_test< config_reader >(input, exp_calls, exp_errors); +} + +// ------------------------------------------------------------------------- +// Tests for the free functions. +// ------------------------------------------------------------------------- + +ATF_TEST_CASE(merge_configs_both_empty); +ATF_TEST_CASE_HEAD(merge_configs_both_empty) {} +ATF_TEST_CASE_BODY(merge_configs_both_empty) { + vars_map lower, upper; + + ATF_REQUIRE(impl::merge_configs(lower, upper).empty()); +} + +ATF_TEST_CASE(merge_configs_lower_empty); +ATF_TEST_CASE_HEAD(merge_configs_lower_empty) {} +ATF_TEST_CASE_BODY(merge_configs_lower_empty) { + vars_map lower, upper; + upper["var"] = "value"; + + vars_map merged = impl::merge_configs(lower, upper); + ATF_REQUIRE_EQ("value", merged["var"]); +} + +ATF_TEST_CASE(merge_configs_upper_empty); +ATF_TEST_CASE_HEAD(merge_configs_upper_empty) {} +ATF_TEST_CASE_BODY(merge_configs_upper_empty) { + vars_map lower, upper; + lower["var"] = "value"; + + vars_map merged = impl::merge_configs(lower, upper); + ATF_REQUIRE_EQ("value", merged["var"]); +} + +ATF_TEST_CASE(merge_configs_mixed); +ATF_TEST_CASE_HEAD(merge_configs_mixed) {} +ATF_TEST_CASE_BODY(merge_configs_mixed) { + vars_map lower, upper; + lower["var1"] = "value1"; + lower["var2"] = "value2-l"; + upper["var2"] = "value2-u"; + upper["var3"] = "value3"; + + vars_map merged = impl::merge_configs(lower, upper); + ATF_REQUIRE_EQ("value1", merged["var1"]); + ATF_REQUIRE_EQ("value2-u", merged["var2"]); + ATF_REQUIRE_EQ("value3", merged["var3"]); +} + +ATF_TEST_CASE(read_config_files_none); +ATF_TEST_CASE_HEAD(read_config_files_none) {} +ATF_TEST_CASE_BODY(read_config_files_none) { + atf::env::set("ATF_CONFDIR", "."); + atf::config::__reinit(); + ATF_REQUIRE(vars_map() == impl::read_config_files("test-suite")); +} + +// ------------------------------------------------------------------------- +// Main. +// ------------------------------------------------------------------------- + +ATF_INIT_TEST_CASES(tcs) +{ + ATF_ADD_TEST_CASE(tcs, config_1); + ATF_ADD_TEST_CASE(tcs, config_2); + ATF_ADD_TEST_CASE(tcs, config_3); + ATF_ADD_TEST_CASE(tcs, config_4); + ATF_ADD_TEST_CASE(tcs, config_50); + ATF_ADD_TEST_CASE(tcs, config_51); + ATF_ADD_TEST_CASE(tcs, config_52); + ATF_ADD_TEST_CASE(tcs, config_53); + ATF_ADD_TEST_CASE(tcs, config_54); + + ATF_ADD_TEST_CASE(tcs, merge_configs_both_empty); + ATF_ADD_TEST_CASE(tcs, merge_configs_lower_empty); + ATF_ADD_TEST_CASE(tcs, merge_configs_upper_empty); + ATF_ADD_TEST_CASE(tcs, merge_configs_mixed); + + ATF_ADD_TEST_CASE(tcs, read_config_files_none); +} diff --git a/atf-run/expect_helpers.c b/atf-run/expect_helpers.c new file mode 100644 index 0000000000000..b38ccf574bc8b --- /dev/null +++ b/atf-run/expect_helpers.c @@ -0,0 +1,193 @@ +/* + * Automated Testing Framework (atf) + * + * 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. + */ + +#include <signal.h> +#include <stdlib.h> +#include <unistd.h> + +#include <atf-c.h> + +ATF_TC_WITHOUT_HEAD(pass_and_pass); +ATF_TC_BODY(pass_and_pass, tc) +{ + atf_tc_expect_pass(); +} + +ATF_TC_WITHOUT_HEAD(pass_but_fail_requirement); +ATF_TC_BODY(pass_but_fail_requirement, tc) +{ + atf_tc_expect_pass(); + atf_tc_fail("Some reason"); +} + +ATF_TC_WITHOUT_HEAD(pass_but_fail_check); +ATF_TC_BODY(pass_but_fail_check, tc) +{ + atf_tc_expect_pass(); + atf_tc_fail_nonfatal("Some reason"); +} + +ATF_TC_WITHOUT_HEAD(fail_and_fail_requirement); +ATF_TC_BODY(fail_and_fail_requirement, tc) +{ + atf_tc_expect_fail("Fail %s", "reason"); + atf_tc_fail("The failure"); + atf_tc_expect_pass(); +} + +ATF_TC_WITHOUT_HEAD(fail_and_fail_check); +ATF_TC_BODY(fail_and_fail_check, tc) +{ + atf_tc_expect_fail("Fail first"); + atf_tc_fail_nonfatal("abc"); + atf_tc_expect_pass(); + + atf_tc_expect_fail("And fail again"); + atf_tc_fail_nonfatal("def"); + atf_tc_expect_pass(); +} + +ATF_TC_WITHOUT_HEAD(fail_but_pass); +ATF_TC_BODY(fail_but_pass, tc) +{ + atf_tc_expect_fail("Fail first"); + atf_tc_fail_nonfatal("abc"); + atf_tc_expect_pass(); + + atf_tc_expect_fail("Will not fail"); + atf_tc_expect_pass(); + + atf_tc_expect_fail("And fail again"); + atf_tc_fail_nonfatal("def"); + atf_tc_expect_pass(); +} + +ATF_TC_WITHOUT_HEAD(exit_any_and_exit); +ATF_TC_BODY(exit_any_and_exit, tc) +{ + atf_tc_expect_exit(-1, "Call will exit"); + exit(EXIT_SUCCESS); +} + +ATF_TC_WITHOUT_HEAD(exit_code_and_exit); +ATF_TC_BODY(exit_code_and_exit, tc) +{ + atf_tc_expect_exit(123, "Call will exit"); + exit(123); +} + +ATF_TC_WITHOUT_HEAD(exit_but_pass); +ATF_TC_BODY(exit_but_pass, tc) +{ + atf_tc_expect_exit(-1, "Call won't exit"); +} + +ATF_TC_WITHOUT_HEAD(signal_any_and_signal); +ATF_TC_BODY(signal_any_and_signal, tc) +{ + atf_tc_expect_signal(-1, "Call will signal"); + kill(getpid(), SIGKILL); +} + +ATF_TC_WITHOUT_HEAD(signal_no_and_signal); +ATF_TC_BODY(signal_no_and_signal, tc) +{ + atf_tc_expect_signal(SIGHUP, "Call will signal"); + kill(getpid(), SIGHUP); +} + +ATF_TC_WITHOUT_HEAD(signal_but_pass); +ATF_TC_BODY(signal_but_pass, tc) +{ + atf_tc_expect_signal(-1, "Call won't signal"); +} + +ATF_TC_WITHOUT_HEAD(death_and_exit); +ATF_TC_BODY(death_and_exit, tc) +{ + atf_tc_expect_death("Exit case"); + exit(123); +} + +ATF_TC_WITHOUT_HEAD(death_and_signal); +ATF_TC_BODY(death_and_signal, tc) +{ + atf_tc_expect_death("Signal case"); + kill(getpid(), SIGKILL); +} + +ATF_TC_WITHOUT_HEAD(death_but_pass); +ATF_TC_BODY(death_but_pass, tc) +{ + atf_tc_expect_death("Call won't die"); +} + +ATF_TC(timeout_and_hang); +ATF_TC_HEAD(timeout_and_hang, tc) +{ + atf_tc_set_md_var(tc, "timeout", "1"); +} +ATF_TC_BODY(timeout_and_hang, tc) +{ + atf_tc_expect_timeout("Will overrun"); + sleep(5); +} + +ATF_TC(timeout_but_pass); +ATF_TC_HEAD(timeout_but_pass, tc) +{ + atf_tc_set_md_var(tc, "timeout", "1"); +} +ATF_TC_BODY(timeout_but_pass, tc) +{ + atf_tc_expect_timeout("Will just exit"); +} + +ATF_TP_ADD_TCS(tp) +{ + ATF_TP_ADD_TC(tp, pass_and_pass); + ATF_TP_ADD_TC(tp, pass_but_fail_requirement); + ATF_TP_ADD_TC(tp, pass_but_fail_check); + ATF_TP_ADD_TC(tp, fail_and_fail_requirement); + ATF_TP_ADD_TC(tp, fail_and_fail_check); + ATF_TP_ADD_TC(tp, fail_but_pass); + ATF_TP_ADD_TC(tp, exit_any_and_exit); + ATF_TP_ADD_TC(tp, exit_code_and_exit); + ATF_TP_ADD_TC(tp, exit_but_pass); + ATF_TP_ADD_TC(tp, signal_any_and_signal); + ATF_TP_ADD_TC(tp, signal_no_and_signal); + ATF_TP_ADD_TC(tp, signal_but_pass); + ATF_TP_ADD_TC(tp, death_and_exit); + ATF_TP_ADD_TC(tp, death_and_signal); + ATF_TP_ADD_TC(tp, death_but_pass); + ATF_TP_ADD_TC(tp, timeout_and_hang); + ATF_TP_ADD_TC(tp, timeout_but_pass); + + return atf_no_error(); +} diff --git a/atf-run/fs.cpp b/atf-run/fs.cpp new file mode 100644 index 0000000000000..b8931e5887301 --- /dev/null +++ b/atf-run/fs.cpp @@ -0,0 +1,264 @@ +// +// Automated Testing Framework (atf) +// +// 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. +// + +#if defined(HAVE_CONFIG_H) +#include "bconfig.h" +#endif + +extern "C" { +#include <sys/types.h> +#include <sys/param.h> +#include <sys/mount.h> +#include <sys/stat.h> + +#include <unistd.h> +} + +#include <cerrno> +#include <cstdlib> +#include <cstring> + +#include "atf-c++/detail/process.hpp" +#include "atf-c++/detail/sanity.hpp" + +#include "fs.hpp" +#include "user.hpp" + +namespace impl = atf::atf_run; +#define IMPL_NAME "atf::atf_run" + +// ------------------------------------------------------------------------ +// Auxiliary functions. +// ------------------------------------------------------------------------ + +static void cleanup_aux(const atf::fs::path&, dev_t, bool); +static void cleanup_aux_dir(const atf::fs::path&, const atf::fs::file_info&, + bool); +static void do_unmount(const atf::fs::path&); + +// The cleanup routines below are tricky: they are executed immediately after +// a test case's death, and after we have forcibly killed any stale processes. +// However, even if the processes are dead, this does not mean that the file +// system we are scanning is stable. In particular, if the test case has +// mounted file systems through fuse/puffs, the fact that the processes died +// does not mean that the file system is truly unmounted. +// +// The code below attempts to cope with this by catching errors and either +// ignoring them or retrying the actions on the same file/directory a few times +// before giving up. +static const int max_retries = 5; +static const int retry_delay_in_seconds = 1; + +// The erase parameter in this routine is to control nested mount points. +// We want to descend into a mount point to unmount anything that is +// mounted under it, but we do not want to delete any files while doing +// this traversal. In other words, we erase files until we cross the +// first mount point, and after that point we only scan and unmount. +static +void +cleanup_aux(const atf::fs::path& p, dev_t parent_device, bool erase) +{ + try { + atf::fs::file_info fi(p); + + if (fi.get_type() == atf::fs::file_info::dir_type) + cleanup_aux_dir(p, fi, fi.get_device() == parent_device); + + if (fi.get_device() != parent_device) + do_unmount(p); + + if (erase) { + if (fi.get_type() == atf::fs::file_info::dir_type) + atf::fs::rmdir(p); + else + atf::fs::remove(p); + } + } catch (const atf::system_error& e) { + if (e.code() != ENOENT && e.code() != ENOTDIR) + throw e; + } +} + +static +void +cleanup_aux_dir(const atf::fs::path& p, const atf::fs::file_info& fi, + bool erase) +{ + if (erase && ((fi.get_mode() & S_IRWXU) != S_IRWXU)) { + int retries = max_retries; +retry_chmod: + if (chmod(p.c_str(), fi.get_mode() | S_IRWXU) == -1) { + if (retries > 0) { + retries--; + ::sleep(retry_delay_in_seconds); + goto retry_chmod; + } else { + throw atf::system_error(IMPL_NAME "::cleanup(" + + p.str() + ")", "chmod(2) failed", + errno); + } + } + } + + std::set< std::string > subdirs; + { + bool ok = false; + int retries = max_retries; + while (!ok) { + INV(retries > 0); + try { + const atf::fs::directory d(p); + subdirs = d.names(); + ok = true; + } catch (const atf::system_error& e) { + retries--; + if (retries == 0) + throw e; + ::sleep(retry_delay_in_seconds); + } + } + INV(ok); + } + + for (std::set< std::string >::const_iterator iter = subdirs.begin(); + iter != subdirs.end(); iter++) { + const std::string& name = *iter; + if (name != "." && name != "..") + cleanup_aux(p / name, fi.get_device(), erase); + } +} + +static +void +do_unmount(const atf::fs::path& in_path) +{ + // At least, FreeBSD's unmount(2) requires the path to be absolute. + // Let's make it absolute in all cases just to be safe that this does + // not affect other systems. + const atf::fs::path& abs_path = in_path.is_absolute() ? + in_path : in_path.to_absolute(); + +#if defined(HAVE_UNMOUNT) + int retries = max_retries; +retry_unmount: + if (unmount(abs_path.c_str(), 0) == -1) { + if (errno == EBUSY && retries > 0) { + retries--; + ::sleep(retry_delay_in_seconds); + goto retry_unmount; + } else { + throw atf::system_error(IMPL_NAME "::cleanup(" + in_path.str() + + ")", "unmount(2) failed", errno); + } + } +#else + // We could use umount(2) instead if it was available... but + // trying to do so under, e.g. Linux, is a nightmare because we + // also have to update /etc/mtab to match what we did. It is + // satf::fser to just leave the system-specific umount(8) tool deal + // with it, at least for now. + + const atf::fs::path prog("umount"); + atf::process::argv_array argv("umount", abs_path.c_str(), NULL); + + atf::process::status s = atf::process::exec(prog, argv, + atf::process::stream_inherit(), atf::process::stream_inherit()); + if (!s.exited() || s.exitstatus() != EXIT_SUCCESS) + throw std::runtime_error("Call to unmount failed"); +#endif +} + +// ------------------------------------------------------------------------ +// The "temp_dir" class. +// ------------------------------------------------------------------------ + +impl::temp_dir::temp_dir(const atf::fs::path& p) +{ + atf::utils::auto_array< char > buf(new char[p.str().length() + 1]); + std::strcpy(buf.get(), p.c_str()); + if (::mkdtemp(buf.get()) == NULL) + throw system_error(IMPL_NAME "::temp_dir::temp_dir(" + + p.str() + ")", "mkdtemp(3) failed", + errno); + + m_path.reset(new atf::fs::path(buf.get())); +} + +impl::temp_dir::~temp_dir(void) +{ + cleanup(*m_path); +} + +const atf::fs::path& +impl::temp_dir::get_path(void) + const +{ + return *m_path; +} + +// ------------------------------------------------------------------------ +// Free functions. +// ------------------------------------------------------------------------ + +atf::fs::path +impl::change_directory(const atf::fs::path& dir) +{ + atf::fs::path olddir = get_current_dir(); + + if (olddir != dir) { + if (::chdir(dir.c_str()) == -1) + throw system_error(IMPL_NAME "::chdir(" + dir.str() + ")", + "chdir(2) failed", errno); + } + + return olddir; +} + +void +impl::cleanup(const atf::fs::path& p) +{ + atf::fs::file_info fi(p); + cleanup_aux(p, fi.get_device(), true); +} + +atf::fs::path +impl::get_current_dir(void) +{ + std::auto_ptr< char > cwd; +#if defined(HAVE_GETCWD_DYN) + cwd.reset(getcwd(NULL, 0)); +#else + cwd.reset(getcwd(NULL, MAXPATHLEN)); +#endif + if (cwd.get() == NULL) + throw atf::system_error(IMPL_NAME "::get_current_dir()", + "getcwd() failed", errno); + + return atf::fs::path(cwd.get()); +} diff --git a/atf-run/fs.hpp b/atf-run/fs.hpp new file mode 100644 index 0000000000000..37382684b57b5 --- /dev/null +++ b/atf-run/fs.hpp @@ -0,0 +1,57 @@ +// +// Automated Testing Framework (atf) +// +// 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. +// + +#if !defined(_ATF_RUN_FS_HPP_) +#define _ATF_RUN_FS_HPP_ + +#include <memory> + +#include "atf-c++/detail/fs.hpp" + +namespace atf { +namespace atf_run { + +class temp_dir { + std::auto_ptr< atf::fs::path > m_path; + +public: + temp_dir(const atf::fs::path&); + ~temp_dir(void); + + const atf::fs::path& get_path(void) const; +}; + +atf::fs::path change_directory(const atf::fs::path&); +void cleanup(const atf::fs::path&); +atf::fs::path get_current_dir(void); + +} // namespace atf_run +} // namespace atf + +#endif // !defined(_ATF_RUN_FS_HPP_) diff --git a/atf-run/fs_test.cpp b/atf-run/fs_test.cpp new file mode 100644 index 0000000000000..f03045eb6131e --- /dev/null +++ b/atf-run/fs_test.cpp @@ -0,0 +1,260 @@ +// +// Automated Testing Framework (atf) +// +// 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. +// + +extern "C" { +#include <sys/types.h> +#include <sys/stat.h> +} + +#include <cerrno> +#include <fstream> + +#include "atf-c++/macros.hpp" + +#include "atf-c++/detail/exceptions.hpp" +#include "atf-c++/detail/fs.hpp" + +#include "fs.hpp" +#include "user.hpp" + +// ------------------------------------------------------------------------ +// Auxiliary functions. +// ------------------------------------------------------------------------ + +static +void +create_file(const char *name) +{ + std::ofstream os(name); + os.close(); +} + +// ------------------------------------------------------------------------ +// Test cases for the "temp_dir" class. +// ------------------------------------------------------------------------ + +ATF_TEST_CASE(temp_dir_raii); +ATF_TEST_CASE_HEAD(temp_dir_raii) +{ + set_md_var("descr", "Tests the RAII behavior of the temp_dir class"); +} +ATF_TEST_CASE_BODY(temp_dir_raii) +{ + using atf::atf_run::temp_dir; + + atf::fs::path t1("non-existent"); + atf::fs::path t2("non-existent"); + + { + atf::fs::path tmpl("testdir.XXXXXX"); + temp_dir td1(tmpl); + temp_dir td2(tmpl); + t1 = td1.get_path(); + t2 = td2.get_path(); + ATF_REQUIRE(t1.str().find("XXXXXX") == std::string::npos); + ATF_REQUIRE(t2.str().find("XXXXXX") == std::string::npos); + ATF_REQUIRE(t1 != t2); + ATF_REQUIRE(!atf::fs::exists(tmpl)); + ATF_REQUIRE( atf::fs::exists(t1)); + ATF_REQUIRE( atf::fs::exists(t2)); + + atf::fs::file_info fi1(t1); + ATF_REQUIRE( fi1.is_owner_readable()); + ATF_REQUIRE( fi1.is_owner_writable()); + ATF_REQUIRE( fi1.is_owner_executable()); + ATF_REQUIRE(!fi1.is_group_readable()); + ATF_REQUIRE(!fi1.is_group_writable()); + ATF_REQUIRE(!fi1.is_group_executable()); + ATF_REQUIRE(!fi1.is_other_readable()); + ATF_REQUIRE(!fi1.is_other_writable()); + ATF_REQUIRE(!fi1.is_other_executable()); + + atf::fs::file_info fi2(t2); + ATF_REQUIRE( fi2.is_owner_readable()); + ATF_REQUIRE( fi2.is_owner_writable()); + ATF_REQUIRE( fi2.is_owner_executable()); + ATF_REQUIRE(!fi2.is_group_readable()); + ATF_REQUIRE(!fi2.is_group_writable()); + ATF_REQUIRE(!fi2.is_group_executable()); + ATF_REQUIRE(!fi2.is_other_readable()); + ATF_REQUIRE(!fi2.is_other_writable()); + ATF_REQUIRE(!fi2.is_other_executable()); + } + + ATF_REQUIRE(t1.str() != "non-existent"); + ATF_REQUIRE(!atf::fs::exists(t1)); + ATF_REQUIRE(t2.str() != "non-existent"); + ATF_REQUIRE(!atf::fs::exists(t2)); +} + + +// ------------------------------------------------------------------------ +// Test cases for the free functions. +// ------------------------------------------------------------------------ + +ATF_TEST_CASE(cleanup); +ATF_TEST_CASE_HEAD(cleanup) +{ + set_md_var("descr", "Tests the cleanup function"); +} +ATF_TEST_CASE_BODY(cleanup) +{ + using atf::atf_run::cleanup; + + ::mkdir("root", 0755); + ::mkdir("root/dir", 0755); + ::mkdir("root/dir/1", 0100); + ::mkdir("root/dir/2", 0644); + create_file("root/reg"); + + atf::fs::path p("root"); + ATF_REQUIRE(atf::fs::exists(p)); + ATF_REQUIRE(atf::fs::exists(p / "dir")); + ATF_REQUIRE(atf::fs::exists(p / "dir/1")); + ATF_REQUIRE(atf::fs::exists(p / "dir/2")); + ATF_REQUIRE(atf::fs::exists(p / "reg")); + cleanup(p); + ATF_REQUIRE(!atf::fs::exists(p)); +} + +ATF_TEST_CASE(cleanup_eacces_on_root); +ATF_TEST_CASE_HEAD(cleanup_eacces_on_root) +{ + set_md_var("descr", "Tests the cleanup function"); +} +ATF_TEST_CASE_BODY(cleanup_eacces_on_root) +{ + using atf::atf_run::cleanup; + + ::mkdir("aux", 0755); + ::mkdir("aux/root", 0755); + ATF_REQUIRE(::chmod("aux", 0555) != -1); + + try { + cleanup(atf::fs::path("aux/root")); + ATF_REQUIRE(atf::atf_run::is_root()); + } catch (const atf::system_error& e) { + ATF_REQUIRE(!atf::atf_run::is_root()); + ATF_REQUIRE_EQ(EACCES, e.code()); + } +} + +ATF_TEST_CASE(cleanup_eacces_on_subdir); +ATF_TEST_CASE_HEAD(cleanup_eacces_on_subdir) +{ + set_md_var("descr", "Tests the cleanup function"); +} +ATF_TEST_CASE_BODY(cleanup_eacces_on_subdir) +{ + using atf::atf_run::cleanup; + + ::mkdir("root", 0755); + ::mkdir("root/1", 0755); + ::mkdir("root/1/2", 0755); + ::mkdir("root/1/2/3", 0755); + ATF_REQUIRE(::chmod("root/1/2", 0555) != -1); + ATF_REQUIRE(::chmod("root/1", 0555) != -1); + + const atf::fs::path p("root"); + cleanup(p); + ATF_REQUIRE(!atf::fs::exists(p)); +} + +ATF_TEST_CASE(change_directory); +ATF_TEST_CASE_HEAD(change_directory) +{ + set_md_var("descr", "Tests the change_directory function"); +} +ATF_TEST_CASE_BODY(change_directory) +{ + using atf::atf_run::change_directory; + using atf::atf_run::get_current_dir; + + ::mkdir("files", 0755); + ::mkdir("files/dir", 0755); + create_file("files/reg"); + + const atf::fs::path old = get_current_dir(); + + ATF_REQUIRE_THROW(atf::system_error, + change_directory(atf::fs::path("files/reg"))); + ATF_REQUIRE(get_current_dir() == old); + + atf::fs::path old2 = change_directory(atf::fs::path("files")); + ATF_REQUIRE(old2 == old); + atf::fs::path old3 = change_directory(atf::fs::path("dir")); + ATF_REQUIRE(old3 == old2 / "files"); + atf::fs::path old4 = change_directory(atf::fs::path("../..")); + ATF_REQUIRE(old4 == old3 / "dir"); + ATF_REQUIRE(get_current_dir() == old); +} + +ATF_TEST_CASE(get_current_dir); +ATF_TEST_CASE_HEAD(get_current_dir) +{ + set_md_var("descr", "Tests the get_current_dir function"); +} +ATF_TEST_CASE_BODY(get_current_dir) +{ + using atf::atf_run::change_directory; + using atf::atf_run::get_current_dir; + + ::mkdir("files", 0755); + ::mkdir("files/dir", 0755); + create_file("files/reg"); + + atf::fs::path curdir = get_current_dir(); + change_directory(atf::fs::path(".")); + ATF_REQUIRE(get_current_dir() == curdir); + change_directory(atf::fs::path("files")); + ATF_REQUIRE(get_current_dir() == curdir / "files"); + change_directory(atf::fs::path("dir")); + ATF_REQUIRE(get_current_dir() == curdir / "files/dir"); + change_directory(atf::fs::path("..")); + ATF_REQUIRE(get_current_dir() == curdir / "files"); + change_directory(atf::fs::path("..")); + ATF_REQUIRE(get_current_dir() == curdir); +} + +// ------------------------------------------------------------------------ +// Main. +// ------------------------------------------------------------------------ + +ATF_INIT_TEST_CASES(tcs) +{ + // Add the tests for the "temp_dir" class. + ATF_ADD_TEST_CASE(tcs, temp_dir_raii); + + // Add the tests for the free functions. + ATF_ADD_TEST_CASE(tcs, cleanup); + ATF_ADD_TEST_CASE(tcs, cleanup_eacces_on_root); + ATF_ADD_TEST_CASE(tcs, cleanup_eacces_on_subdir); + ATF_ADD_TEST_CASE(tcs, change_directory); + ATF_ADD_TEST_CASE(tcs, get_current_dir); +} diff --git a/atf-run/integration_test.sh b/atf-run/integration_test.sh new file mode 100644 index 0000000000000..afd013efbf062 --- /dev/null +++ b/atf-run/integration_test.sh @@ -0,0 +1,1134 @@ +# +# Automated Testing Framework (atf) +# +# 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. +# + +create_atffile() +{ + ATF_CONFDIR="$(pwd)"; export ATF_CONFDIR + + cat >Atffile <<EOF +Content-Type: application/X-atf-atffile; version="1" + +prop: test-suite = atf + +EOF + for f in "${@}"; do + echo "tp: ${f}" >>Atffile + done +} + +create_helper() +{ + cp $(atf_get_srcdir)/misc_helpers helper + create_atffile helper + TESTCASE=${1}; export TESTCASE +} + +create_helper_stdin() +{ + # TODO: This really, really, really must use real test programs. + cat >${1} <<EOF +#! $(atf-config -t atf_shell) +while [ \${#} -gt 0 ]; do + case \${1} in + -l) + echo 'Content-Type: application/X-atf-tp; version="1"' + echo +EOF + cnt=1 + while [ ${cnt} -le ${2} ]; do + echo "echo 'ident: tc${cnt}'" >>${1} + [ ${cnt} -lt ${2} ] && echo "echo" >>${1} + cnt=$((${cnt} + 1)) + done +cat >>${1} <<EOF + exit 0 + ;; + -r*) + resfile=\$(echo \${1} | cut -d r -f 2-) + ;; + esac + testcase=\$(echo \${1} | cut -d : -f 1) + shift +done +EOF + cat >>${1} +} + +create_mount_helper() +{ + cat >${1} <<EOF +#! /usr/bin/env atf-sh + +do_mount() { + platform=\$(uname) + case \${platform} in + Linux|NetBSD) + mount -t tmpfs tmpfs \${1} || atf_fail "Mount failed" + ;; + FreeBSD) + mdmfs -s 16m md \${1} || atf_fail "Mount failed" + ;; + SunOS) + mount -F tmpfs tmpfs \$(pwd)/\${1} || atf_fail "Mount failed" + ;; + *) + atf_fail "create_mount_helper called for an unsupported platform." + ;; + esac +} + +atf_test_case main +main_head() { + atf_set "require.user" "root" +} +main_body() { +EOF + cat >>${1} + cat >>${1} <<EOF +} + +atf_init_test_cases() +{ + atf_add_test_case main +} +EOF +} + +atf_test_case no_warnings +no_warnings_head() +{ + atf_set "descr" "Tests that atf-run suppresses warnings about not running" \ + "within atf-run" +} +no_warnings_body() +{ + create_helper pass + atf_check -s eq:0 -o ignore -e not-match:'WARNING.*atf-run' atf-run helper +} + +atf_test_case config +config_head() +{ + atf_set "descr" "Tests that the config files are read in the correct" \ + "order" +} +config_body() +{ + create_helper config + + mkdir etc + mkdir .atf + + echo "First: read system-wide common.conf." + cat >etc/common.conf <<EOF +Content-Type: application/X-atf-config; version="1" + +1st = "sw common" +2nd = "sw common" +3rd = "sw common" +4th = "sw common" +EOF + atf_check -s eq:0 \ + -o match:'1st: sw common' \ + -o match:'2nd: sw common' \ + -o match:'3rd: sw common' \ + -o match:'4th: sw common' \ + -e ignore -x \ + "ATF_CONFDIR=$(pwd)/etc HOME=$(pwd) atf-run helper" + + echo "Second: read system-wide <test-suite>.conf." + cat >etc/atf.conf <<EOF +Content-Type: application/X-atf-config; version="1" + +1st = "sw atf" +EOF + atf_check -s eq:0 \ + -o match:'1st: sw atf' \ + -o match:'2nd: sw common' \ + -o match:'3rd: sw common' \ + -o match:'4th: sw common' \ + -e ignore -x \ + "ATF_CONFDIR=$(pwd)/etc HOME=$(pwd) atf-run helper" + + echo "Third: read user-specific common.conf." + cat >.atf/common.conf <<EOF +Content-Type: application/X-atf-config; version="1" + +2nd = "us common" +EOF + atf_check -s eq:0 \ + -o match:'1st: sw atf' \ + -o match:'2nd: us common' \ + -o match:'3rd: sw common' \ + -o match:'4th: sw common' \ + -e ignore -x \ + "ATF_CONFDIR=$(pwd)/etc HOME=$(pwd) atf-run helper" + + echo "Fourth: read user-specific <test-suite>.conf." + cat >.atf/atf.conf <<EOF +Content-Type: application/X-atf-config; version="1" + +3rd = "us atf" +EOF + atf_check -s eq:0 \ + -o match:'1st: sw atf' \ + -o match:'2nd: us common' \ + -o match:'3rd: us atf' \ + -o match:'4th: sw common' \ + -e ignore -x \ + "ATF_CONFDIR=$(pwd)/etc HOME=$(pwd) atf-run helper" +} + +atf_test_case vflag +vflag_head() +{ + atf_set "descr" "Tests that the -v flag works and that it properly" \ + "overrides the values in configuration files" +} +vflag_body() +{ + create_helper testvar + + echo "Checking that 'testvar' is not defined." + atf_check -s eq:1 -o ignore -e ignore -x \ + "ATF_CONFDIR=$(pwd)/etc atf-run helper" + + echo "Checking that defining 'testvar' trough '-v' works." + atf_check -s eq:0 -o match:'testvar: a value' -e ignore -x \ + "ATF_CONFDIR=$(pwd)/etc atf-run -v testvar='a value' helper" + + echo "Checking that defining 'testvar' trough the configuration" \ + "file works." + mkdir etc + cat >etc/common.conf <<EOF +Content-Type: application/X-atf-config; version="1" + +testvar = "value in conf file" +EOF + atf_check -s eq:0 -o match:'testvar: value in conf file' -e ignore -x \ + "ATF_CONFDIR=$(pwd)/etc atf-run helper" + + echo "Checking that defining 'testvar' trough -v overrides the" \ + "configuration file." + atf_check -s eq:0 -o match:'testvar: a value' -e ignore -x \ + "ATF_CONFDIR=$(pwd)/etc atf-run -v testvar='a value' helper" +} + +atf_test_case atffile +atffile_head() +{ + atf_set "descr" "Tests that the variables defined by the Atffile" \ + "are recognized and that they take the lowest priority" +} +atffile_body() +{ + create_helper testvar + + echo "Checking that 'testvar' is not defined." + atf_check -s eq:1 -o ignore -e ignore -x \ + "ATF_CONFDIR=$(pwd)/etc atf-run helper" + + echo "Checking that defining 'testvar' trough the Atffile works." + echo 'conf: testvar = "a value"' >>Atffile + atf_check -s eq:0 -o match:'testvar: a value' -e ignore -x \ + "ATF_CONFDIR=$(pwd)/etc atf-run helper" + + echo "Checking that defining 'testvar' trough the configuration" \ + "file overrides the one in the Atffile." + mkdir etc + cat >etc/common.conf <<EOF +Content-Type: application/X-atf-config; version="1" + +testvar = "value in conf file" +EOF + atf_check -s eq:0 -o match:'testvar: value in conf file' -e ignore -x \ + "ATF_CONFDIR=$(pwd)/etc atf-run helper" + rm -rf etc + + echo "Checking that defining 'testvar' trough -v overrides the" \ + "one in the Atffile." + atf_check -s eq:0 -o match:'testvar: new value' -e ignore -x \ + "ATF_CONFDIR=$(pwd)/etc atf-run -v testvar='new value' helper" +} + +atf_test_case atffile_recursive +atffile_recursive_head() +{ + atf_set "descr" "Tests that variables defined by an Atffile are not" \ + "inherited by other Atffiles." +} +atffile_recursive_body() +{ + create_helper testvar + + mkdir dir + mv Atffile helper dir + + echo "Checking that 'testvar' is not inherited." + create_atffile dir + echo 'conf: testvar = "a value"' >> Atffile + atf_check -s eq:1 -o ignore -e ignore -x "ATF_CONFDIR=$(pwd)/etc atf-run" + + echo "Checking that defining 'testvar' in the correct Atffile works." + echo 'conf: testvar = "a value"' >>dir/Atffile + atf_check -s eq:0 -o match:'testvar: a value' -e ignore -x \ + "ATF_CONFDIR=$(pwd)/etc atf-run" +} + +atf_test_case fds +fds_head() +{ + atf_set "descr" "Tests that all streams are properly captured" +} +fds_body() +{ + create_helper fds + + atf_check -s eq:0 \ + -o match:'^tc-so:msg1 to stdout$' \ + -o match:'^tc-so:msg2 to stdout$' \ + -o match:'^tc-se:msg1 to stderr$' \ + -o match:'^tc-se:msg2 to stderr$' \ + -e empty atf-run +} + +atf_test_case mux_streams +mux_streams_head() +{ + atf_set "descr" "Tests for a race condition in stream multiplexing" +} +mux_streams_body() +{ + create_helper mux_streams + + for i in 1 2 3 4 5; do + echo "Attempt ${i}" + atf_check -s eq:0 -o match:'stdout 9999' -o match:'stderr 9999' atf-run + done +} + +atf_test_case expect +expect_head() +{ + atf_set "descr" "Tests the processing of test case results and the" \ + "expect features" +} +expect_body() +{ + ln -s "$(atf_get_srcdir)/expect_helpers" . + create_atffile expect_helpers + + atf_check -s eq:1 \ + -o match:'death_and_exit, expected_death' \ + -o match:'death_and_signal, expected_death' \ + -o match:'death_but_pass, failed' \ + -o match:'exit_any_and_exit, expected_exit' \ + -o match:'exit_but_pass, failed' \ + -o match:'exit_code_and_exit, expected_exit' \ + -o match:'fail_and_fail_check, expected_failure' \ + -o match:'fail_and_fail_requirement, expected_failure' \ + -o match:'fail_but_pass, failed' \ + -o match:'pass_and_pass, passed' \ + -o match:'pass_but_fail_check, failed' \ + -o match:'pass_but_fail_requirement, failed' \ + -o match:'signal_any_and_signal, expected_signal' \ + -o match:'signal_but_pass, failed' \ + -o match:'signal_no_and_signal, expected_signal' \ + -o match:'timeout_and_hang, expected_timeout' \ + -o match:'timeout_but_pass, failed' \ + -e empty atf-run +} + +atf_test_case missing_results +missing_results_head() +{ + atf_set "descr" "Ensures that atf-run correctly handles test cases that " \ + "do not create the results file" +} +missing_results_body() +{ + create_helper_stdin helper 1 <<EOF +test -f \${resfile} && echo "resfile found" +exit 0 +EOF + chmod +x helper + + create_atffile helper + + re='^tc-end: [0-9][0-9]*\.[0-9]*, tc1,' + atf_check -s eq:1 \ + -o match:"${re} failed,.*failed to create" \ + -o not-match:'resfile found' \ + -e empty atf-run +} + +atf_test_case broken_results +broken_results_head() +{ + atf_set "descr" "Ensures that atf-run reports test programs that" \ + "provide a bogus results output as broken programs" +} +broken_results_body() +{ + # We produce two errors from the header to ensure that the parse + # errors are printed on a single line on the output file. Printing + # them on separate lines would be incorrect. + create_helper_stdin helper 1 <<EOF +echo 'line 1' >\${resfile} +echo 'line 2' >>\${resfile} +exit 0 +EOF + chmod +x helper + + create_atffile helper + + re='^tc-end: [0-9][0-9]*\.[0-9]*, tc1,' + atf_check -s eq:1 -o match:"${re} .*line 1.*line 2" -e empty atf-run +} + +atf_test_case broken_tp_list +broken_tp_list_head() +{ + atf_set "descr" "Ensures that atf-run reports test programs that" \ + "provide a bogus test case list" +} +broken_tp_list_body() +{ + cat >helper <<EOF +#! $(atf-config -t atf_shell) +while [ \${#} -gt 0 ]; do + if [ \${1} = -l ]; then + echo 'Content-Type: application/X-atf-tp; version="1"' + echo + echo 'foo: bar' + exit 0 + else + shift + fi +done +exit 0 +EOF + chmod +x helper + + create_atffile helper + + re='^tp-end: [0-9][0-9]*\.[0-9]*, helper,' + re="${re} Invalid format for test case list:.*First property.*ident" + atf_check -s eq:1 -o match:"${re}" -e empty atf-run +} + +atf_test_case zero_tcs +zero_tcs_head() +{ + atf_set "descr" "Ensures that atf-run reports test programs without" \ + "test cases as errors" +} +zero_tcs_body() +{ + create_helper_stdin helper 0 <<EOF +echo 'Content-Type: application/X-atf-tp; version="1"' +echo +exit 1 +EOF + chmod +x helper + + create_atffile helper + + re='^tp-end: [0-9][0-9]*\.[0-9]*, helper,' + atf_check -s eq:1 \ + -o match:"${re} .*Invalid format for test case list" \ + -e empty atf-run +} + +atf_test_case exit_codes +exit_codes_head() +{ + atf_set "descr" "Ensures that atf-run reports bogus exit codes for" \ + "programs correctly" +} +exit_codes_body() +{ + create_helper_stdin helper 1 <<EOF +echo "failed: Yes, it failed" >\${resfile} +exit 0 +EOF + chmod +x helper + + create_atffile helper + + re='^tc-end: [0-9][0-9]*\.[0-9]*, tc1,' + atf_check -s eq:1 \ + -o match:"${re} .*exited successfully.*reported failure" \ + -e empty atf-run +} + +atf_test_case signaled +signaled_head() +{ + atf_set "descr" "Ensures that atf-run reports test program's crashes" \ + "correctly regardless of their actual results" +} +signaled_body() +{ + create_helper_stdin helper 2 <<EOF +echo "passed" >\${resfile} +case \${testcase} in + tc1) ;; + tc2) echo "Killing myself!" ; kill -9 \$\$ ;; +esac +EOF + chmod +x helper + + create_atffile helper + + re='^tc-end: [0-9][0-9]*\.[0-9]*, tc2,' + atf_check -s eq:1 -o match:"${re} .*received signal 9" \ + -e empty atf-run +} + +atf_test_case hooks +hooks_head() +{ + atf_set "descr" "Checks that the default hooks work and that they" \ + "can be overriden by the user" +} +hooks_body() +{ + cp $(atf_get_srcdir)/pass_helper helper + create_atffile helper + + mkdir atf + mkdir .atf + + echo "Checking default hooks" + atf_check -s eq:0 -o match:'^info: time.start, ' \ + -o match:'^info: time.end, ' -e empty -x \ + "ATF_CONFDIR=$(pwd)/atf atf-run" + + echo "Checking the system-wide info_start hook" + cat >atf/atf-run.hooks <<EOF +info_start_hook() +{ + atf_tps_writer_info "test" "sw value" +} +EOF + atf_check -s eq:0 \ + -o match:'^info: test, sw value' \ + -o not-match:'^info: time.start, ' \ + -o match:'^info: time.end, ' \ + -e empty -x \ + "ATF_CONFDIR=$(pwd)/atf atf-run" + + echo "Checking the user-specific info_start hook" + cat >.atf/atf-run.hooks <<EOF +info_start_hook() +{ + atf_tps_writer_info "test" "user value" +} +EOF + atf_check -s eq:0 \ + -o match:'^info: test, user value' \ + -o not-match:'^info: time.start, ' \ + -o match:'^info: time.end, ' \ + -e empty -x \ + "ATF_CONFDIR=$(pwd)/atf atf-run" + + rm atf/atf-run.hooks + rm .atf/atf-run.hooks + + echo "Checking the system-wide info_end hook" + cat >atf/atf-run.hooks <<EOF +info_end_hook() +{ + atf_tps_writer_info "test" "sw value" +} +EOF + atf_check -s eq:0 \ + -o match:'^info: time.start, ' \ + -o not-match:'^info: time.end, ' \ + -o match:'^info: test, sw value' \ + -e empty -x \ + "ATF_CONFDIR=$(pwd)/atf atf-run" + + echo "Checking the user-specific info_end hook" + cat >.atf/atf-run.hooks <<EOF +info_end_hook() +{ + atf_tps_writer_info "test" "user value" +} +EOF + atf_check -s eq:0 \ + -o match:'^info: time.start, ' \ + -o not-match:'^info: time.end, ' \ + -o match:'^info: test, user value' \ + -e empty -x \ + "ATF_CONFDIR=$(pwd)/atf atf-run" +} + +atf_test_case isolation_env +isolation_env_head() +{ + atf_set "descr" "Tests that atf-run sets a set of environment variables" \ + "to known sane values" +} +isolation_env_body() +{ + undef_vars="LANG LC_ALL LC_COLLATE LC_CTYPE LC_MESSAGES LC_MONETARY \ + LC_NUMERIC LC_TIME" + def_vars="HOME TZ" + + mangleenv="env" + for v in ${undef_vars} ${def_vars}; do + mangleenv="${mangleenv} ${v}=bogus-value" + done + + create_helper env_list + create_atffile helper + + # We must ignore stderr in this call (instead of specifying -e empty) + # because, when atf-run invokes the shell to run the hooks, we may get + # error messages about an invalid locale. This happens, at least, when + # the shell is bash 4.x. + atf_check -s eq:0 -o save:stdout -e ignore ${mangleenv} atf-run helper + + for v in ${undef_vars}; do + atf_check -s eq:1 -o empty -e empty grep "^tc-so:${v}=" stdout + done + + for v in ${def_vars}; do + atf_check -s eq:0 -o ignore -e empty grep "^tc-so:${v}=" stdout + done + + atf_check -s eq:0 -o ignore -e empty grep "^tc-so:TZ=UTC" stdout +} + +atf_test_case isolation_home +isolation_home_head() +{ + atf_set "descr" "Tests that atf-run sets HOME to a sane and valid value" +} +isolation_home_body() +{ + create_helper env_home + create_atffile helper + atf_check -s eq:0 -o ignore -e ignore env HOME=foo atf-run helper +} + +atf_test_case isolation_stdin +isolation_stdin_head() +{ + atf_set "descr" "Tests that atf-run nullifies the stdin of test cases" +} +isolation_stdin_body() +{ + create_helper read_stdin + create_atffile helper + atf_check -s eq:0 -o ignore -e ignore -x 'echo hello world | atf-run helper' +} + +atf_test_case isolation_umask +isolation_umask_head() +{ + atf_set "descr" "Tests that atf-run sets the umask to a known value" +} +isolation_umask_body() +{ + create_helper umask + create_atffile helper + + atf_check -s eq:0 -o match:'umask: 0022' -e ignore -x \ + "umask 0000 && atf-run helper" +} + +atf_test_case cleanup_pass +cleanup_pass_head() +{ + atf_set "descr" "Tests that atf-run calls the cleanup routine of the test" \ + "case when the test case result is passed" +} +cleanup_pass_body() +{ + create_helper cleanup_states + create_atffile helper + + atf_check -s eq:0 -o match:'cleanup_states, passed' -e ignore atf-run \ + -v state=pass -v statedir=$(pwd) helper + test -f to-stay || atf_fail "Test case body did not run correctly" + if [ -f to-delete ]; then + atf_fail "Test case cleanup did not run correctly" + fi +} + +atf_test_case cleanup_fail +cleanup_fail_head() +{ + atf_set "descr" "Tests that atf-run calls the cleanup routine of the test" \ + "case when the test case result is failed" +} +cleanup_fail_body() +{ + create_helper cleanup_states + create_atffile helper + + atf_check -s eq:1 -o match:'cleanup_states, failed' -e ignore atf-run \ + -v state=fail -v statedir=$(pwd) helper + test -f to-stay || atf_fail "Test case body did not run correctly" + if [ -f to-delete ]; then + atf_fail "Test case cleanup did not run correctly" + fi +} + +atf_test_case cleanup_skip +cleanup_skip_head() +{ + atf_set "descr" "Tests that atf-run calls the cleanup routine of the test" \ + "case when the test case result is skipped" +} +cleanup_skip_body() +{ + create_helper cleanup_states + create_atffile helper + + atf_check -s eq:0 -o match:'cleanup_states, skipped' -e ignore atf-run \ + -v state=skip -v statedir=$(pwd) helper + test -f to-stay || atf_fail "Test case body did not run correctly" + if [ -f to-delete ]; then + atf_fail "Test case cleanup did not run correctly" + fi +} + +atf_test_case cleanup_curdir +cleanup_curdir_head() +{ + atf_set "descr" "Tests that atf-run calls the cleanup routine in the same" \ + "work directory as the body so that they can share data" +} +cleanup_curdir_body() +{ + create_helper cleanup_curdir + create_atffile helper + + atf_check -s eq:0 -o match:'cleanup_curdir, passed' \ + -o match:'Old value: 1234' -e ignore atf-run helper +} + +atf_test_case cleanup_signal +cleanup_signal_head() +{ + atf_set "descr" "Tests that atf-run calls the cleanup routine if it gets" \ + "a termination signal while running the body" +} +cleanup_signal_body() +{ + : # TODO: Write this. +} + +atf_test_case cleanup_mount +cleanup_mount_head() +{ + atf_set "descr" "Tests that the removal algorithm does not cross" \ + "mount points" + atf_set "require.user" "root" +} +cleanup_mount_body() +{ + ROOT="$(pwd)/root"; export ROOT + + create_mount_helper helper <<EOF +echo \$(pwd) >\${ROOT} +mkdir foo +mkdir foo/bar +mkdir foo/bar/mnt +do_mount foo/bar/mnt +mkdir foo/baz +do_mount foo/baz +mkdir foo/baz/foo +mkdir foo/baz/foo/bar +do_mount foo/baz/foo/bar +EOF + create_atffile helper + chmod +x helper + + platform=$(uname) + case ${platform} in + Linux|FreeBSD|NetBSD|SunOS) + ;; + *) + # XXX Possibly specify in meta-data too. + atf_skip "Test unimplemented in this platform (${platform})" + ;; + esac + + atf_check -s eq:0 -o match:"main, passed" -e ignore atf-run helper + mount | grep $(cat root) && atf_fail "Some file systems remain mounted" + atf_check -s eq:1 -o empty -e empty test -d $(cat root)/foo +} + +atf_test_case cleanup_symlink +cleanup_symlink_head() +{ + atf_set "descr" "Tests that the removal algorithm does not follow" \ + "symlinks, which may live in another device and thus" \ + "be treated as mount points" + atf_set "require.user" "root" +} +cleanup_symlink_body() +{ + ROOT="$(pwd)/root"; export ROOT + + create_mount_helper helper <<EOF +echo \$(pwd) >\${ROOT} +atf_check -s eq:0 -o empty -e empty mkdir foo +atf_check -s eq:0 -o empty -e empty mkdir foo/bar +do_mount foo/bar +atf_check -s eq:0 -o empty -e empty touch a +atf_check -s eq:0 -o empty -e empty ln -s "\$(pwd)/a" foo/bar +EOF + create_atffile helper + chmod +x helper + + platform=$(uname) + case ${platform} in + Linux|FreeBSD|NetBSD|SunOS) + ;; + *) + # XXX Possibly specify in meta-data too. + atf_skip "Test unimplemented in this platform (${platform})" + ;; + esac + + atf_check -s eq:0 -o match:"main, passed" -e ignore atf-run helper + mount | grep $(cat root) && atf_fail "Some file systems remain mounted" + atf_check -s eq:1 -o empty -e empty test -d $(cat root)/foo +} + +atf_test_case require_arch +require_arch_head() +{ + atf_set "descr" "Tests that atf-run validates the require.arch property" +} +require_arch_body() +{ + create_helper require_arch + create_atffile helper + + echo "Checking for the real architecture" + arch=$(atf-config -t atf_arch) + atf_check -s eq:0 -o match:"${TESTCASE}, passed" -e ignore atf-run \ + -v arch="${arch}" helper + atf_check -s eq:0 -o match:"${TESTCASE}, passed" -e ignore atf-run \ + -v arch="foo ${arch}" helper + atf_check -s eq:0 -o match:"${TESTCASE}, passed" -e ignore atf-run \ + -v arch="${arch} foo" helper + + echo "Checking for a fictitious architecture" + arch=fictitious + export ATF_ARCH=fictitious + atf_check -s eq:0 -o match:"${TESTCASE}, passed" -e ignore atf-run \ + -v arch="${arch}" helper + atf_check -s eq:0 -o match:"${TESTCASE}, passed" -e ignore atf-run \ + -v arch="foo ${arch}" helper + atf_check -s eq:0 -o match:"${TESTCASE}, passed" -e ignore atf-run \ + -v arch="${arch} foo" helper + + echo "Triggering some failures" + atf_check -s eq:0 -o match:"${TESTCASE}, skipped, .*foo.*architecture" \ + -e ignore atf-run -v arch="foo" helper + atf_check -s eq:0 \ + -o match:"${TESTCASE}, skipped, .*foo bar.*architectures" -e ignore \ + atf-run -v arch="foo bar" helper + atf_check -s eq:0 \ + -o match:"${TESTCASE}, skipped, .*fictitiousxxx.*architecture" \ + -e ignore atf-run -v arch="${arch}xxx" helper +} + +atf_test_case require_config +require_config_head() +{ + atf_set "descr" "Tests that atf-run validates the require.config property" +} +require_config_body() +{ + create_helper require_config + create_atffile helper + + atf_check -s eq:0 -o match:"${TESTCASE}, skipped, .*var1.*not defined" \ + -e ignore atf-run helper + atf_check -s eq:0 -o match:"${TESTCASE}, skipped, .*var2.*not defined" \ + -e ignore atf-run -v var1=foo helper + atf_check -s eq:0 -o match:"${TESTCASE}, passed" -e ignore atf-run \ + -v var1=a -v var2=' ' helper +} + +atf_test_case require_files +require_files_head() +{ + atf_set "descr" "Tests that atf-run validates the require.files property" +} +require_files_body() +{ + create_helper require_files + create_atffile helper + + touch i-exist + + echo "Checking absolute paths" + atf_check -s eq:0 -o match:"${TESTCASE}, passed" -e ignore atf-run \ + -v files='/bin/cp' helper + atf_check -s eq:0 -o match:"${TESTCASE}, passed" -e ignore atf-run \ + -v files="$(pwd)/i-exist" helper + atf_check -s eq:0 \ + -o match:"${TESTCASE}, skipped, .*/dont-exist" \ + -e ignore atf-run -v files="$(pwd)/i-exist $(pwd)/dont-exist" helper + + echo "Checking that relative paths are not allowed" + atf_check -s eq:1 \ + -o match:"${TESTCASE}, failed, Relative paths.*not allowed.*hello" \ + -e ignore atf-run -v files='hello' helper + atf_check -s eq:1 \ + -o match:"${TESTCASE}, failed, Relative paths.*not allowed.*a/b" \ + -e ignore atf-run -v files='a/b' helper +} + +atf_test_case require_machine +require_machine_head() +{ + atf_set "descr" "Tests that atf-run validates the require.machine property" +} +require_machine_body() +{ + create_helper require_machine + create_atffile helper + + echo "Checking for the real machine type" + machine=$(atf-config -t atf_machine) + atf_check -s eq:0 -o match:"${TESTCASE}, passed" -e ignore atf-run \ + -v machine="${machine}" helper + atf_check -s eq:0 -o match:"${TESTCASE}, passed" -e ignore atf-run \ + -v machine="foo ${machine}" helper + atf_check -s eq:0 -o match:"${TESTCASE}, passed" -e ignore atf-run \ + -v machine="${machine} foo" helper + + echo "Checking for a fictitious machine type" + machine=fictitious + export ATF_MACHINE=fictitious + atf_check -s eq:0 -o match:"${TESTCASE}, passed" -e ignore atf-run \ + -v machine="${machine}" helper + atf_check -s eq:0 -o match:"${TESTCASE}, passed" -e ignore atf-run \ + -v machine="foo ${machine}" helper + atf_check -s eq:0 -o match:"${TESTCASE}, passed" -e ignore atf-run \ + -v machine="${machine} foo" helper + + echo "Triggering some failures" + atf_check -s eq:0 -o match:"${TESTCASE}, skipped, .*foo.*machine type" \ + -e ignore atf-run -v machine="foo" helper + atf_check -s eq:0 \ + -o match:"${TESTCASE}, skipped, .*foo bar.*machine types" -e ignore \ + atf-run -v machine="foo bar" helper + atf_check -s eq:0 \ + -o match:"${TESTCASE}, skipped, .*fictitiousxxx.*machine type" \ + -e ignore atf-run -v machine="${machine}xxx" helper +} + +atf_test_case require_progs +require_progs_head() +{ + atf_set "descr" "Tests that atf-run validates the require.progs property" +} +require_progs_body() +{ + create_helper require_progs + create_atffile helper + + echo "Checking absolute paths" + atf_check -s eq:0 -o match:"${TESTCASE}, passed" -e ignore atf-run \ + -v progs='/bin/cp' helper + atf_check -s eq:0 \ + -o match:"${TESTCASE}, skipped, .*/bin/__non-existent__.*PATH" \ + -e ignore atf-run -v progs='/bin/__non-existent__' helper + + echo "Checking that relative paths are not allowed" + atf_check -s eq:1 \ + -o match:"${TESTCASE}, failed, Relative paths.*not allowed.*bin/cp" \ + -e ignore atf-run -v progs='bin/cp' helper + + echo "Check plain file names, searching them in the PATH." + atf_check -s eq:0 -o match:"${TESTCASE}, passed" -e ignore atf-run \ + -v progs='cp' helper + atf_check -s eq:0 \ + -o match:"${TESTCASE}, skipped, .*__non-existent__.*PATH" -e ignore \ + atf-run -v progs='__non-existent__' helper +} + +atf_test_case require_user_root +require_user_root_head() +{ + atf_set "descr" "Tests that atf-run validates the require.user property" \ + "when it is set to 'root'" +} +require_user_root_body() +{ + create_helper require_user + create_atffile helper + + if [ $(id -u) -eq 0 ]; then + exp=passed + else + exp=skipped + fi + atf_check -s eq:0 -o match:"${TESTCASE}, ${exp}" -e ignore atf-run \ + -v user=root helper +} + +atf_test_case require_user_unprivileged +require_user_unprivileged_head() +{ + atf_set "descr" "Tests that atf-run validates the require.user property" \ + "when it is set to 'root'" +} +require_user_unprivileged_body() +{ + create_helper require_user + create_atffile helper + + if [ $(id -u) -eq 0 ]; then + exp=skipped + else + exp=passed + fi + atf_check -s eq:0 -o match:"${TESTCASE}, ${exp}" -e ignore atf-run \ + -v user=unprivileged helper +} + +atf_test_case require_user_bad +require_user_bad_head() +{ + atf_set "descr" "Tests that atf-run validates the require.user property" \ + "when it is set to 'root'" +} +require_user_bad_body() +{ + create_helper require_user + create_atffile helper + + atf_check -s eq:1 -o match:"${TESTCASE}, failed, Invalid value.*foobar" \ + -e ignore atf-run -v user=foobar helper +} + +atf_test_case timeout +timeout_head() +{ + atf_set "descr" "Tests that atf-run kills a test case that times out" +} +timeout_body() +{ + create_helper timeout + create_atffile helper + + atf_check -s eq:1 \ + -o match:"${TESTCASE}, failed, .*timed out after 1 second" -e ignore \ + atf-run -v statedir=$(pwd) helper + if [ -f finished ]; then + atf_fail "Test case was not killed after time out" + fi +} + +atf_test_case timeout_forkexit +timeout_forkexit_head() +{ + atf_set "descr" "Tests that atf-run deals gracefully with a test program" \ + "that forks, exits, but the child process hangs" +} +timeout_forkexit_body() +{ + create_helper timeout_forkexit + create_atffile helper + + atf_check -s eq:0 -o match:"${TESTCASE}, passed" -e ignore atf-run \ + -v statedir=$(pwd) helper + test -f parent-finished || atf_fail "Parent did not exit as expected" + test -f child-finished && atf_fail "Subprocess exited but it should have" \ + "been forcibly terminated" || true +} + +atf_test_case ignore_deprecated_use_fs +ignore_deprecated_use_fs_head() +{ + atf_set "descr" "Tests that atf-run ignores the deprecated use.fs property" +} +ignore_deprecated_use_fs_body() +{ + create_helper use_fs + create_atffile helper + + atf_check -s eq:0 -o ignore -e ignore atf-run helper +} + +atf_init_test_cases() +{ + atf_add_test_case no_warnings + atf_add_test_case config + atf_add_test_case vflag + atf_add_test_case atffile + atf_add_test_case atffile_recursive + atf_add_test_case expect + atf_add_test_case fds + atf_add_test_case mux_streams + atf_add_test_case missing_results + atf_add_test_case broken_results + atf_add_test_case broken_tp_list + atf_add_test_case zero_tcs + atf_add_test_case exit_codes + atf_add_test_case signaled + atf_add_test_case hooks + atf_add_test_case isolation_env + atf_add_test_case isolation_home + atf_add_test_case isolation_stdin + atf_add_test_case isolation_umask + atf_add_test_case cleanup_pass + atf_add_test_case cleanup_fail + atf_add_test_case cleanup_skip + atf_add_test_case cleanup_curdir + atf_add_test_case cleanup_signal + atf_add_test_case cleanup_mount + atf_add_test_case cleanup_symlink + atf_add_test_case require_arch + atf_add_test_case require_config + atf_add_test_case require_files + atf_add_test_case require_machine + atf_add_test_case require_progs + atf_add_test_case require_user_root + atf_add_test_case require_user_unprivileged + atf_add_test_case require_user_bad + atf_add_test_case timeout + atf_add_test_case timeout_forkexit + atf_add_test_case ignore_deprecated_use_fs +} + +# vim: syntax=sh:expandtab:shiftwidth=4:softtabstop=4 diff --git a/atf-run/io.cpp b/atf-run/io.cpp new file mode 100644 index 0000000000000..9be78d01261f7 --- /dev/null +++ b/atf-run/io.cpp @@ -0,0 +1,361 @@ +// +// Automated Testing Framework (atf) +// +// 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. +// + +extern "C" { +#include <fcntl.h> +#include <poll.h> +#include <signal.h> +#include <unistd.h> +} + +#include <cerrno> +#include <cstring> + +extern "C" { +#include "../atf-c/error.h" +} + +#include "../atf-c++/detail/exceptions.hpp" +#include "../atf-c++/detail/sanity.hpp" +#include "../atf-c++/utils.hpp" + +#include "io.hpp" + +namespace impl = atf::atf_run; +#define IMPL_NAME "atf::atf_run" + +// ------------------------------------------------------------------------ +// The "file_handle" class. +// ------------------------------------------------------------------------ + +impl::file_handle::file_handle(void) : + m_handle(invalid_value()) +{ +} + +impl::file_handle::file_handle(handle_type h) : + m_handle(h) +{ + PRE(m_handle != invalid_value()); +} + +impl::file_handle::file_handle(const file_handle& fh) : + m_handle(fh.m_handle) +{ + fh.m_handle = invalid_value(); +} + +impl::file_handle::~file_handle(void) +{ + if (is_valid()) + close(); +} + +impl::file_handle& +impl::file_handle::operator=(const file_handle& fh) +{ + m_handle = fh.m_handle; + fh.m_handle = invalid_value(); + + return *this; +} + +bool +impl::file_handle::is_valid(void) + const +{ + return m_handle != invalid_value(); +} + +void +impl::file_handle::close(void) +{ + PRE(is_valid()); + + ::close(m_handle); + + m_handle = invalid_value(); +} + +impl::file_handle::handle_type +impl::file_handle::disown(void) +{ + PRE(is_valid()); + + handle_type h = m_handle; + m_handle = invalid_value(); + return h; +} + +impl::file_handle::handle_type +impl::file_handle::get(void) + const +{ + PRE(is_valid()); + + return m_handle; +} + +void +impl::file_handle::posix_remap(handle_type h) +{ + PRE(is_valid()); + + if (m_handle == h) + return; + + if (::dup2(m_handle, h) == -1) + throw system_error(IMPL_NAME "::file_handle::posix_remap", + "dup2(2) failed", errno); + + if (::close(m_handle) == -1) { + ::close(h); + throw system_error(IMPL_NAME "::file_handle::posix_remap", + "close(2) failed", errno); + } + + m_handle = h; +} + +impl::file_handle::handle_type +impl::file_handle::invalid_value(void) +{ + return -1; +} + +// ------------------------------------------------------------------------ +// The "systembuf" class. +// ------------------------------------------------------------------------ + +impl::systembuf::systembuf(handle_type h, std::size_t bufsize) : + m_handle(h), + m_bufsize(bufsize), + m_read_buf(NULL), + m_write_buf(NULL) +{ + PRE(m_handle >= 0); + PRE(m_bufsize > 0); + + try { + m_read_buf = new char[bufsize]; + m_write_buf = new char[bufsize]; + } catch (...) { + if (m_read_buf != NULL) + delete [] m_read_buf; + if (m_write_buf != NULL) + delete [] m_write_buf; + throw; + } + + setp(m_write_buf, m_write_buf + m_bufsize); +} + +impl::systembuf::~systembuf(void) +{ + delete [] m_read_buf; + delete [] m_write_buf; +} + +impl::systembuf::int_type +impl::systembuf::underflow(void) +{ + PRE(gptr() >= egptr()); + + bool ok; + ssize_t cnt = ::read(m_handle, m_read_buf, m_bufsize); + ok = (cnt != -1 && cnt != 0); + + if (!ok) + return traits_type::eof(); + else { + setg(m_read_buf, m_read_buf, m_read_buf + cnt); + return traits_type::to_int_type(*gptr()); + } +} + +impl::systembuf::int_type +impl::systembuf::overflow(int c) +{ + PRE(pptr() >= epptr()); + if (sync() == -1) + return traits_type::eof(); + if (!traits_type::eq_int_type(c, traits_type::eof())) { + traits_type::assign(*pptr(), c); + pbump(1); + } + return traits_type::not_eof(c); +} + +int +impl::systembuf::sync(void) +{ + ssize_t cnt = pptr() - pbase(); + + bool ok; + ok = ::write(m_handle, pbase(), cnt) == cnt; + + if (ok) + pbump(-cnt); + return ok ? 0 : -1; +} + +// ------------------------------------------------------------------------ +// The "pistream" class. +// ------------------------------------------------------------------------ + +impl::pistream::pistream(const int fd) : + std::istream(NULL), + m_systembuf(fd) +{ + rdbuf(&m_systembuf); +} + +// ------------------------------------------------------------------------ +// The "muxer" class. +// ------------------------------------------------------------------------ + +static int +safe_poll(struct pollfd fds[], nfds_t nfds, int timeout) +{ + int ret = ::poll(fds, nfds, timeout); + if (ret == -1) { + if (errno == EINTR) + ret = 0; + else + throw atf::system_error(IMPL_NAME "::safe_poll", "poll(2) failed", + errno); + } + INV(ret >= 0); + return ret; +} + +static size_t +safe_read(const int fd, void* buffer, const size_t nbytes, + const bool report_errors) +{ + int ret; + while ((ret = ::read(fd, buffer, nbytes)) == -1 && errno == EINTR) {} + if (ret == -1) { + INV(errno != EINTR); + + if (report_errors) + throw atf::system_error(IMPL_NAME "::safe_read", "read(2) failed", + errno); + else + ret = 0; + } + INV(ret >= 0); + return static_cast< size_t >(ret); +} + +impl::muxer::muxer(const int* fds, const size_t nfds, const size_t bufsize) : + m_fds(fds), + m_nfds(nfds), + m_bufsize(bufsize), + m_buffers(new std::string[nfds]) +{ +} + +impl::muxer::~muxer(void) +{ +} + +size_t +impl::muxer::read_one(const size_t index, const int fd, std::string& accum, + const bool report_errors) +{ + atf::utils::auto_array< char > buffer(new char[m_bufsize]); + const size_t nbytes = safe_read(fd, buffer.get(), m_bufsize - 1, + report_errors); + INV(nbytes < m_bufsize); + buffer[nbytes] = '\0'; + + std::string line(accum); + + size_t line_start = 0; + for (size_t i = 0; i < nbytes; i++) { + if (buffer[i] == '\n') { + line_callback(index, line); + line.clear(); + accum.clear(); + line_start = i + 1; + } else if (buffer[i] == '\r') { + // Do nothing. + } else { + line.append(1, buffer[i]); + } + } + accum.append(&buffer[line_start]); + + return nbytes; +} + +void +impl::muxer::mux(volatile const bool& terminate) +{ + atf::utils::auto_array< struct pollfd > poll_fds(new struct pollfd[m_nfds]); + for (size_t i = 0; i < m_nfds; i++) { + poll_fds[i].fd = m_fds[i]; + poll_fds[i].events = POLLIN; + } + + size_t nactive = m_nfds; + while (nactive > 0 && !terminate) { + int ret; + while (!terminate && (ret = safe_poll(poll_fds.get(), 2, 250)) == 0) {} + + for (size_t i = 0; !terminate && i < m_nfds; i++) { + if (poll_fds[i].events == 0) + continue; + + if (poll_fds[i].revents & POLLHUP) { + // Any data still available at this point will be processed by + // a call to the flush method. + poll_fds[i].events = 0; + + INV(nactive >= 1); + nactive--; + } else if (poll_fds[i].revents & (POLLIN | POLLRDNORM | POLLRDBAND | + POLLPRI)) { + (void)read_one(i, poll_fds[i].fd, m_buffers[i], true); + } + } + } +} + +void +impl::muxer::flush(void) +{ + for (size_t i = 0; i < m_nfds; i++) { + while (read_one(i, m_fds[i], m_buffers[i], false) > 0) {} + + if (!m_buffers[i].empty()) + line_callback(i, m_buffers[i]); + } +} diff --git a/atf-run/io.hpp b/atf-run/io.hpp new file mode 100644 index 0000000000000..e021cf3096572 --- /dev/null +++ b/atf-run/io.hpp @@ -0,0 +1,432 @@ +// +// Automated Testing Framework (atf) +// +// 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. +// + +#if !defined(_ATF_RUN_IO_HPP_) +#define _ATF_RUN_IO_HPP_ + +#include <istream> +#include <ostream> +#include <streambuf> + +#include "fs.hpp" + +#include "../atf-c++/utils.hpp" + +namespace atf { +namespace atf_run { + +// ------------------------------------------------------------------------ +// The "file_handle" class. +// ------------------------------------------------------------------------ + +//! +//! \brief Simple RAII model for system file handles. +//! +//! The \a file_handle class is a simple RAII model for native system file +//! handles. This class wraps one of such handles grabbing its ownership, +//! and automaticaly closes it upon destruction. It is basically used +//! inside the library to avoid leaking open file handles, shall an +//! unexpected execution trace occur. +//! +//! A \a file_handle object can be copied but doing so invalidates the +//! source object. There can only be a single valid \a file_handle object +//! for a given system file handle. This is similar to std::auto_ptr\<\>'s +//! semantics. +//! +//! This class also provides some convenience methods to issue special file +//! operations under their respective platforms. +//! +class file_handle +{ +public: + //! + //! \brief Opaque name for the native handle type. + //! + //! Each operating system identifies file handles using a specific type. + //! The \a handle_type type is used to transparently refer to file + //! handles regarless of the operating system in which this class is + //! used. + //! + //! If this class is used in a POSIX system, \a NativeSystemHandle is + //! an integer type while it is a \a HANDLE in a Win32 system. + //! + typedef int handle_type; + + //! + //! \brief Constructs an invalid file handle. + //! + //! This constructor creates a new \a file_handle object that represents + //! an invalid file handle. An invalid file handle can be copied but + //! cannot be manipulated in any way (except checking for its validity). + //! + //! \see is_valid() + //! + file_handle(void); + + //! + //! \brief Constructs a new file handle from a native file handle. + //! + //! This constructor creates a new \a file_handle object that takes + //! ownership of the given \a h native file handle. The user must not + //! close \a h on his own during the lifetime of the new object. + //! Ownership can be reclaimed using disown(). + //! + //! \pre The native file handle must be valid; a close operation must + //! succeed on it. + //! + //! \see disown() + //! + file_handle(handle_type h); + + //! + //! \brief Copy constructor; invalidates the source handle. + //! + //! This copy constructor creates a new file handle from a given one. + //! Ownership of the native file handle is transferred to the new + //! object, effectively invalidating the source file handle. This + //! avoids having two live \a file_handle objects referring to the + //! same native file handle. The source file handle need not be + //! valid in the name of simplicity. + //! + //! \post The source file handle is invalid. + //! \post The new file handle owns the source's native file handle. + //! + file_handle(const file_handle& fh); + + //! + //! \brief Releases resources if the handle is valid. + //! + //! If the file handle is valid, the destructor closes it. + //! + //! \see is_valid() + //! + ~file_handle(void); + + //! + //! \brief Assignment operator; invalidates the source handle. + //! + //! This assignment operator transfers ownership of the RHS file + //! handle to the LHS one, effectively invalidating the source file + //! handle. This avoids having two live \a file_handle objects + //! referring to the same native file handle. The source file + //! handle need not be valid in the name of simplicity. + //! + //! \post The RHS file handle is invalid. + //! \post The LHS file handle owns RHS' native file handle. + //! \return A reference to the LHS file handle. + //! + file_handle& operator=(const file_handle& fh); + + //! + //! \brief Checks whether the file handle is valid or not. + //! + //! Returns a boolean indicating whether the file handle is valid or + //! not. If the file handle is invalid, no other applications can be + //! executed other than the destructor. + //! + //! \return True if the file handle is valid; false otherwise. + //! + bool is_valid(void) const; + + //! + //! \brief Closes the file handle. + //! + //! Explicitly closes the file handle, which must be valid. Upon + //! exit, the handle is not valid any more. + //! + //! \pre The file handle is valid. + //! \post The file handle is invalid. + //! \post The native file handle is closed. + //! + void close(void); + + //! + //! \brief Reclaims ownership of the native file handle. + //! + //! Explicitly reclaims ownership of the native file handle contained + //! in the \a file_handle object, returning the native file handle. + //! The caller is responsible of closing it later on. + //! + //! \pre The file handle is valid. + //! \post The file handle is invalid. + //! \return The native file handle. + //! + handle_type disown(void); + + //! + //! \brief Gets the native file handle. + //! + //! Returns the native file handle for the \a file_handle object. + //! The caller can issue any operation on it except closing it. + //! If closing is required, disown() shall be used. + //! + //! \pre The file handle is valid. + //! \return The native file handle. + //! + handle_type get(void) const; + + //! + //! \brief Changes the native file handle to the given one. + //! + //! Given a new native file handle \a h, this operation assigns this + //! handle to the current object, closing its old native file handle. + //! In other words, it first calls dup2() to remap the old handle to + //! the new one and then closes the old handle. + //! + //! If \a h matches the current value of the handle, this is a no-op. + //! This is done for simplicity, to avoid the caller having to check + //! this condition on its own. + //! + //! If \a h is open, it is automatically closed by dup2(). + //! + //! This operation is only available in POSIX systems. + //! + //! \pre The file handle is valid. + //! \pre The native file handle \a h is valid; i.e., it must be + //! closeable. + //! \post The file handle's native file handle is \a h. + //! \throw system_error If the internal remapping operation fails. + //! + void posix_remap(handle_type h); + +private: + //! + //! \brief Internal handle value. + //! + //! This variable holds the native handle value for the file handle + //! hold by this object. It is interesting to note that this needs + //! to be mutable because the copy constructor and the assignment + //! operator invalidate the source object. + //! + mutable handle_type m_handle; + + //! + //! \brief Constant function representing an invalid handle value. + //! + //! Returns the platform-specific handle value that represents an + //! invalid handle. This is a constant function rather than a regular + //! constant because, in the latter case, we cannot define it under + //! Win32 due to the value being of a complex type. + //! + static handle_type invalid_value(void); +}; + +// ------------------------------------------------------------------------ +// The "systembuf" class. +// ------------------------------------------------------------------------ + +//! +//! \brief std::streambuf implementation for system file handles. +//! +//! systembuf provides a std::streambuf implementation for system file +//! handles. Contrarywise to file_handle, this class does \b not take +//! ownership of the native file handle; this should be taken care of +//! somewhere else. +//! +//! This class follows the expected semantics of a std::streambuf object. +//! However, it is not copyable to avoid introducing inconsistences with +//! the on-disk file and the in-memory buffers. +//! +class systembuf : + public std::streambuf, atf::utils::noncopyable +{ +public: + typedef int handle_type; + + //! + //! \brief Constructs a new systembuf for the given file handle. + //! + //! This constructor creates a new systembuf object that reads or + //! writes data from/to the \a h native file handle. This handle + //! is \b not owned by the created systembuf object; the code + //! should take care of it externally. + //! + //! This class buffers input and output; the buffer size may be + //! tuned through the \a bufsize parameter, which defaults to 8192 + //! bytes. + //! + //! \see pistream. + //! + explicit systembuf(handle_type h, std::size_t bufsize = 8192); + ~systembuf(void); + +private: + //! + //! \brief Native file handle used by the systembuf object. + //! + handle_type m_handle; + + //! + //! \brief Internal buffer size used during read and write operations. + //! + std::size_t m_bufsize; + + //! + //! \brief Internal buffer used during read operations. + //! + char* m_read_buf; + + //! + //! \brief Internal buffer used during write operations. + //! + char* m_write_buf; + +protected: + //! + //! \brief Reads new data from the native file handle. + //! + //! This operation is called by input methods when there are no more + //! data in the input buffer. The function fills the buffer with new + //! data, if available. + //! + //! \pre All input positions are exhausted (gptr() >= egptr()). + //! \post The input buffer has new data, if available. + //! \returns traits_type::eof() if a read error occurrs or there are + //! no more data to be read. Otherwise returns + //! traits_type::to_int_type(*gptr()). + //! + virtual int_type underflow(void); + + //! + //! \brief Makes room in the write buffer for additional data. + //! + //! This operation is called by output methods when there is no more + //! space in the output buffer to hold a new element. The function + //! first flushes the buffer's contents to disk and then clears it to + //! leave room for more characters. The given \a c character is + //! stored at the beginning of the new space. + //! + //! \pre All output positions are exhausted (pptr() >= epptr()). + //! \post The output buffer has more space if no errors occurred + //! during the write to disk. + //! \post *(pptr() - 1) is \a c. + //! \returns traits_type::eof() if a write error occurrs. Otherwise + //! returns traits_type::not_eof(c). + //! + virtual int_type overflow(int c); + + //! + //! \brief Flushes the output buffer to disk. + //! + //! Synchronizes the systembuf buffers with the contents of the file + //! associated to this object through the native file handle. The + //! output buffer is flushed to disk and cleared to leave new room + //! for more data. + //! + //! \returns 0 on success, -1 if an error occurred. + //! + virtual int sync(void); +}; + +// ------------------------------------------------------------------------ +// The "pistream" class. +// ------------------------------------------------------------------------ + +//! +//! \brief Child process' output stream. +//! +//! The pistream class represents an output communication channel with the +//! child process. The child process writes data to this stream and the +//! parent process can read it through the pistream object. In other +//! words, from the child's point of view, the communication channel is an +//! output one, but from the parent's point of view it is an input one; +//! hence the confusing pistream name. +//! +//! pistream objects cannot be copied because they own the file handle +//! they use to communicate with the child and because they buffer data +//! that flows through the communication channel. +//! +//! A pistream object behaves as a std::istream stream in all senses. +//! The class is only provided because it must provide a method to let +//! the caller explicitly close the communication channel. +//! +//! \remark <b>Blocking remarks</b>: Functions that read data from this +//! stream can block if the associated file handle blocks during the read. +//! As this class is used to communicate with child processes through +//! anonymous pipes, the most typical blocking condition happens when the +//! child has no more data to send to the pipe's system buffer. When +//! this happens, the buffer eventually empties and the system blocks +//! until the writer generates some data. +//! +class pistream : + public std::istream, utils::noncopyable +{ + //! + //! \brief The file handle managed by this stream. + //! + int m_fd; + + //! + //! \brief The systembuf object used to manage this stream's data. + //! + systembuf m_systembuf; + +public: + //! + //! \brief Creates a new process' output stream. + //! + //! Given a file handle, this constructor creates a new pistream + //! object that owns the given file handle \a fh. Ownership of + //! \a fh is transferred to the created pistream object. + //! + //! \pre \a fh is valid. + //! \post \a fh is invalid. + //! \post The new pistream object owns \a fh. + //! + explicit pistream(const int); +}; + +// ------------------------------------------------------------------------ +// The "muxer" class. +// ------------------------------------------------------------------------ + +class muxer : utils::noncopyable { + const int* m_fds; + const size_t m_nfds; + + const size_t m_bufsize; + atf::utils::auto_array< std::string > m_buffers; + +protected: + virtual void line_callback(const size_t, const std::string&) = 0; + + size_t read_one(const size_t, const int, std::string&, const bool); + +public: + muxer(const int*, const size_t, const size_t bufsize = 1024); + virtual ~muxer(void); + + void mux(volatile const bool&); + void flush(void); +}; + +} // namespace atf_run +} // namespace atf + +#endif // !defined(_ATF_RUN_IO_HPP_) diff --git a/atf-run/io_test.cpp b/atf-run/io_test.cpp new file mode 100644 index 0000000000000..03fc97e82e8fb --- /dev/null +++ b/atf-run/io_test.cpp @@ -0,0 +1,471 @@ +// +// Automated Testing Framework (atf) +// +// 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. +// + +extern "C" { +#include <sys/stat.h> +#include <sys/wait.h> + +#include <fcntl.h> +#include <unistd.h> +} + +#include <cerrno> +#include <cstddef> +#include <cstdlib> +#include <cstring> +#include <fstream> +#include <iostream> +#include <istream> +#include <ostream> + +#include "../atf-c++/detail/sanity.hpp" +#include "../atf-c++/macros.hpp" + +#include "io.hpp" +#include "signals.hpp" + +// ------------------------------------------------------------------------ +// Auxiliary functions. +// ------------------------------------------------------------------------ + +static +void +systembuf_check_data(std::istream& is, std::size_t length) +{ + char ch = 'A', chr; + std::size_t cnt = 0; + while (is >> chr) { + ATF_REQUIRE_EQ(ch, chr); + if (ch == 'Z') + ch = 'A'; + else + ch++; + cnt++; + } + ATF_REQUIRE_EQ(cnt, length); +} + +static +void +systembuf_write_data(std::ostream& os, std::size_t length) +{ + char ch = 'A'; + for (std::size_t i = 0; i < length; i++) { + os << ch; + if (ch == 'Z') + ch = 'A'; + else + ch++; + } + os.flush(); +} + +static +void +systembuf_test_read(std::size_t length, std::size_t bufsize) +{ + using atf::atf_run::systembuf; + + std::ofstream f("test_read.txt"); + systembuf_write_data(f, length); + f.close(); + + int fd = ::open("test_read.txt", O_RDONLY); + ATF_REQUIRE(fd != -1); + systembuf sb(fd, bufsize); + std::istream is(&sb); + systembuf_check_data(is, length); + ::close(fd); + ::unlink("test_read.txt"); +} + +static +void +systembuf_test_write(std::size_t length, std::size_t bufsize) +{ + using atf::atf_run::systembuf; + + int fd = ::open("test_write.txt", O_WRONLY | O_CREAT | O_TRUNC, + S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + ATF_REQUIRE(fd != -1); + systembuf sb(fd, bufsize); + std::ostream os(&sb); + systembuf_write_data(os, length); + ::close(fd); + + std::ifstream is("test_write.txt"); + systembuf_check_data(is, length); + is.close(); + ::unlink("test_write.txt"); +} + +// ------------------------------------------------------------------------ +// Test cases for the "file_handle" class. +// ------------------------------------------------------------------------ + +ATF_TEST_CASE(file_handle_ctor); +ATF_TEST_CASE_HEAD(file_handle_ctor) +{ + set_md_var("descr", "Tests file_handle's constructors"); +} +ATF_TEST_CASE_BODY(file_handle_ctor) +{ + using atf::atf_run::file_handle; + + file_handle fh1; + ATF_REQUIRE(!fh1.is_valid()); + + file_handle fh2(STDOUT_FILENO); + ATF_REQUIRE(fh2.is_valid()); + fh2.disown(); +} + +ATF_TEST_CASE(file_handle_copy); +ATF_TEST_CASE_HEAD(file_handle_copy) +{ + set_md_var("descr", "Tests file_handle's copy constructor"); +} +ATF_TEST_CASE_BODY(file_handle_copy) +{ + using atf::atf_run::file_handle; + + file_handle fh1; + file_handle fh2(STDOUT_FILENO); + + file_handle fh3(fh2); + ATF_REQUIRE(!fh2.is_valid()); + ATF_REQUIRE(fh3.is_valid()); + + fh1 = fh3; + ATF_REQUIRE(!fh3.is_valid()); + ATF_REQUIRE(fh1.is_valid()); + + fh1.disown(); +} + +ATF_TEST_CASE(file_handle_get); +ATF_TEST_CASE_HEAD(file_handle_get) +{ + set_md_var("descr", "Tests the file_handle::get method"); +} +ATF_TEST_CASE_BODY(file_handle_get) +{ + using atf::atf_run::file_handle; + + file_handle fh1(STDOUT_FILENO); + ATF_REQUIRE_EQ(fh1.get(), STDOUT_FILENO); +} + +ATF_TEST_CASE(file_handle_posix_remap); +ATF_TEST_CASE_HEAD(file_handle_posix_remap) +{ + set_md_var("descr", "Tests the file_handle::posix_remap method"); +} +ATF_TEST_CASE_BODY(file_handle_posix_remap) +{ + using atf::atf_run::file_handle; + + int pfd[2]; + + ATF_REQUIRE(::pipe(pfd) != -1); + file_handle rend(pfd[0]); + file_handle wend(pfd[1]); + + ATF_REQUIRE(rend.get() != 10); + ATF_REQUIRE(wend.get() != 10); + wend.posix_remap(10); + ATF_REQUIRE_EQ(wend.get(), 10); + ATF_REQUIRE(::write(wend.get(), "test-posix-remap", 16) != -1); + { + char buf[17]; + ATF_REQUIRE_EQ(::read(rend.get(), buf, sizeof(buf)), 16); + buf[16] = '\0'; + ATF_REQUIRE(std::strcmp(buf, "test-posix-remap") == 0); + } + + // Redo previous to ensure that remapping over the same descriptor + // has no side-effects. + ATF_REQUIRE_EQ(wend.get(), 10); + wend.posix_remap(10); + ATF_REQUIRE_EQ(wend.get(), 10); + ATF_REQUIRE(::write(wend.get(), "test-posix-remap", 16) != -1); + { + char buf[17]; + ATF_REQUIRE_EQ(::read(rend.get(), buf, sizeof(buf)), 16); + buf[16] = '\0'; + ATF_REQUIRE(std::strcmp(buf, "test-posix-remap") == 0); + } +} + +// ------------------------------------------------------------------------ +// Test cases for the "systembuf" class. +// ------------------------------------------------------------------------ + +ATF_TEST_CASE(systembuf_short_read); +ATF_TEST_CASE_HEAD(systembuf_short_read) +{ + set_md_var("descr", "Tests that a short read (one that fits in the " + "internal buffer) works when using systembuf"); +} +ATF_TEST_CASE_BODY(systembuf_short_read) +{ + systembuf_test_read(64, 1024); +} + +ATF_TEST_CASE(systembuf_long_read); +ATF_TEST_CASE_HEAD(systembuf_long_read) +{ + set_md_var("descr", "Tests that a long read (one that does not fit in " + "the internal buffer) works when using systembuf"); +} +ATF_TEST_CASE_BODY(systembuf_long_read) +{ + systembuf_test_read(64 * 1024, 1024); +} + +ATF_TEST_CASE(systembuf_short_write); +ATF_TEST_CASE_HEAD(systembuf_short_write) +{ + set_md_var("descr", "Tests that a short write (one that fits in the " + "internal buffer) works when using systembuf"); +} +ATF_TEST_CASE_BODY(systembuf_short_write) +{ + systembuf_test_write(64, 1024); +} + +ATF_TEST_CASE(systembuf_long_write); +ATF_TEST_CASE_HEAD(systembuf_long_write) +{ + set_md_var("descr", "Tests that a long write (one that does not fit " + "in the internal buffer) works when using systembuf"); +} +ATF_TEST_CASE_BODY(systembuf_long_write) +{ + systembuf_test_write(64 * 1024, 1024); +} + +// ------------------------------------------------------------------------ +// Test cases for the "pistream" class. +// ------------------------------------------------------------------------ + +ATF_TEST_CASE(pistream); +ATF_TEST_CASE_HEAD(pistream) +{ + set_md_var("descr", "Tests the pistream class"); +} +ATF_TEST_CASE_BODY(pistream) +{ + using atf::atf_run::file_handle; + using atf::atf_run::pistream; + using atf::atf_run::systembuf; + + int fds[2]; + ATF_REQUIRE(::pipe(fds) != -1); + + pistream rend(fds[0]); + + systembuf wbuf(fds[1]); + std::ostream wend(&wbuf); + + // XXX This assumes that the pipe's buffer is big enough to accept + // the data written without blocking! + wend << "1Test 1message\n"; + wend.flush(); + std::string tmp; + rend >> tmp; + ATF_REQUIRE_EQ(tmp, "1Test"); + rend >> tmp; + ATF_REQUIRE_EQ(tmp, "1message"); +} + +// ------------------------------------------------------------------------ +// Tests for the "muxer" class. +// ------------------------------------------------------------------------ + +namespace { + +static void +check_stream(std::ostream& os) +{ + // If we receive a signal while writing to the stream, the bad bit gets set. + // Things seem to behave fine afterwards if we clear such error condition. + // However, I'm not sure if it's safe to query errno at this point. + ATF_REQUIRE(os.good() || (os.bad() && errno == EINTR)); + os.clear(); +} + +class mock_muxer : public atf::atf_run::muxer { + void line_callback(const size_t index, const std::string& line) + { + // The following should be enabled but causes the output to be so big + // that it is annoying. Reenable at some point if we make atf store + // the output of the test cases in some other way (e.g. only if a test + // failes), because this message is the only help in seeing how the + // test fails. + //std::cout << "line_callback(" << index << ", " << line << ")\n"; + check_stream(std::cout); + switch (index) { + case 0: lines0.push_back(line); break; + case 1: lines1.push_back(line); break; + default: ATF_REQUIRE(false); + } + } + +public: + mock_muxer(const int* fds, const size_t nfds, const size_t bufsize) : + muxer(fds, nfds, bufsize) {} + + std::vector< std::string > lines0; + std::vector< std::string > lines1; +}; + +static bool child_finished = false; +static void sigchld_handler(int signo) +{ + INV(signo == SIGCHLD); + child_finished = true; +} + +static void +child_printer(const int pipeout[2], const int pipeerr[2], + const size_t iterations) +{ + ::close(pipeout[0]); + ::close(pipeerr[0]); + ATF_REQUIRE(::dup2(pipeout[1], STDOUT_FILENO) != -1); + ATF_REQUIRE(::dup2(pipeerr[1], STDERR_FILENO) != -1); + ::close(pipeout[1]); + ::close(pipeerr[1]); + + for (size_t i = 0; i < iterations; i++) { + std::cout << "stdout " << i << "\n"; + std::cerr << "stderr " << i << "\n"; + } + + std::cout << "stdout eof\n"; + std::cerr << "stderr eof\n"; + std::exit(EXIT_SUCCESS); +} + +static void +muxer_test(const size_t bufsize, const size_t iterations) +{ + int pipeout[2], pipeerr[2]; + ATF_REQUIRE(pipe(pipeout) != -1); + ATF_REQUIRE(pipe(pipeerr) != -1); + + atf::atf_run::signal_programmer sigchld(SIGCHLD, sigchld_handler); + + std::cout.flush(); + std::cerr.flush(); + + pid_t pid = ::fork(); + ATF_REQUIRE(pid != -1); + if (pid == 0) { + sigchld.unprogram(); + child_printer(pipeout, pipeerr, iterations); + UNREACHABLE; + } + ::close(pipeout[1]); + ::close(pipeerr[1]); + + int fds[2] = {pipeout[0], pipeerr[0]}; + mock_muxer mux(fds, 2, bufsize); + + mux.mux(child_finished); + check_stream(std::cout); + std::cout << "mux done\n"; + + mux.flush(); + std::cout << "flush done\n"; + check_stream(std::cout); + + sigchld.unprogram(); + int status; + ATF_REQUIRE(::waitpid(pid, &status, 0) != -1); + ATF_REQUIRE(WIFEXITED(status)); + ATF_REQUIRE(WEXITSTATUS(status) == EXIT_SUCCESS); + + ATF_REQUIRE(std::cout.good()); + ATF_REQUIRE(std::cerr.good()); + for (size_t i = 0; i < iterations; i++) { + std::ostringstream exp0, exp1; + exp0 << "stdout " << i; + exp1 << "stderr " << i; + + ATF_REQUIRE(mux.lines0.size() > i); + ATF_REQUIRE_EQ(exp0.str(), mux.lines0[i]); + ATF_REQUIRE(mux.lines1.size() > i); + ATF_REQUIRE_EQ(exp1.str(), mux.lines1[i]); + } + ATF_REQUIRE_EQ("stdout eof", mux.lines0[iterations]); + ATF_REQUIRE_EQ("stderr eof", mux.lines1[iterations]); + std::cout << "all done\n"; +} + +} // anonymous namespace + +ATF_TEST_CASE_WITHOUT_HEAD(muxer_small_buffer); +ATF_TEST_CASE_BODY(muxer_small_buffer) +{ + muxer_test(4, 20000); +} + +ATF_TEST_CASE_WITHOUT_HEAD(muxer_large_buffer); +ATF_TEST_CASE_BODY(muxer_large_buffer) +{ + muxer_test(1024, 50000); +} + +// ------------------------------------------------------------------------ +// Main. +// ------------------------------------------------------------------------ + +ATF_INIT_TEST_CASES(tcs) +{ + // Add the tests for the "file_handle" class. + ATF_ADD_TEST_CASE(tcs, file_handle_ctor); + ATF_ADD_TEST_CASE(tcs, file_handle_copy); + ATF_ADD_TEST_CASE(tcs, file_handle_get); + ATF_ADD_TEST_CASE(tcs, file_handle_posix_remap); + + // Add the tests for the "systembuf" class. + ATF_ADD_TEST_CASE(tcs, systembuf_short_read); + ATF_ADD_TEST_CASE(tcs, systembuf_long_read); + ATF_ADD_TEST_CASE(tcs, systembuf_short_write); + ATF_ADD_TEST_CASE(tcs, systembuf_long_write); + + // Add the tests for the "pistream" class. + ATF_ADD_TEST_CASE(tcs, pistream); + + // Add the tests for the "muxer" class. + ATF_ADD_TEST_CASE(tcs, muxer_small_buffer); + ATF_ADD_TEST_CASE(tcs, muxer_large_buffer); +} diff --git a/atf-run/misc_helpers.cpp b/atf-run/misc_helpers.cpp new file mode 100644 index 0000000000000..abc9a4a945fe7 --- /dev/null +++ b/atf-run/misc_helpers.cpp @@ -0,0 +1,419 @@ +// +// Automated Testing Framework (atf) +// +// 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. +// + +extern "C" { +#include <sys/stat.h> + +#include <signal.h> +#include <unistd.h> +} + +#include <cstdlib> +#include <fstream> +#include <iomanip> +#include <ios> +#include <iostream> +#include <string> + +#include "atf-c++/macros.hpp" + +#include "atf-c++/detail/env.hpp" +#include "atf-c++/detail/fs.hpp" +#include "atf-c++/detail/process.hpp" +#include "atf-c++/detail/sanity.hpp" + +// ------------------------------------------------------------------------ +// Auxiliary functions. +// ------------------------------------------------------------------------ + +static +void +touch(const std::string& path) +{ + std::ofstream os(path.c_str()); + if (!os) + ATF_FAIL("Could not create file " + path); + os.close(); +} + +// ------------------------------------------------------------------------ +// Helper tests for "t_integration". +// ------------------------------------------------------------------------ + +ATF_TEST_CASE(pass); +ATF_TEST_CASE_HEAD(pass) +{ + set_md_var("descr", "Helper test case for the t_integration test program"); +} +ATF_TEST_CASE_BODY(pass) +{ +} + +ATF_TEST_CASE(config); +ATF_TEST_CASE_HEAD(config) +{ + set_md_var("descr", "Helper test case for the t_integration test program"); +} +ATF_TEST_CASE_BODY(config) +{ + std::cout << "1st: " << get_config_var("1st") << "\n"; + std::cout << "2nd: " << get_config_var("2nd") << "\n"; + std::cout << "3rd: " << get_config_var("3rd") << "\n"; + std::cout << "4th: " << get_config_var("4th") << "\n"; +} + +ATF_TEST_CASE(fds); +ATF_TEST_CASE_HEAD(fds) +{ + set_md_var("descr", "Helper test case for the t_integration test program"); +} +ATF_TEST_CASE_BODY(fds) +{ + std::cout << "msg1 to stdout" << "\n"; + std::cout << "msg2 to stdout" << "\n"; + std::cerr << "msg1 to stderr" << "\n"; + std::cerr << "msg2 to stderr" << "\n"; +} + +ATF_TEST_CASE_WITHOUT_HEAD(mux_streams); +ATF_TEST_CASE_BODY(mux_streams) +{ + for (size_t i = 0; i < 10000; i++) { + switch (i % 5) { + case 0: + std::cout << "stdout " << i << "\n"; + break; + case 1: + std::cerr << "stderr " << i << "\n"; + break; + case 2: + std::cout << "stdout " << i << "\n"; + std::cerr << "stderr " << i << "\n"; + break; + case 3: + std::cout << "stdout " << i << "\n"; + std::cout << "stdout " << i << "\n"; + std::cerr << "stderr " << i << "\n"; + break; + case 4: + std::cout << "stdout " << i << "\n"; + std::cerr << "stderr " << i << "\n"; + std::cerr << "stderr " << i << "\n"; + break; + default: + UNREACHABLE; + } + } +} + +ATF_TEST_CASE(testvar); +ATF_TEST_CASE_HEAD(testvar) +{ + set_md_var("descr", "Helper test case for the t_integration test program"); +} +ATF_TEST_CASE_BODY(testvar) +{ + if (!has_config_var("testvar")) + fail("testvar variable not defined"); + std::cout << "testvar: " << get_config_var("testvar") << "\n"; +} + +ATF_TEST_CASE(env_list); +ATF_TEST_CASE_HEAD(env_list) +{ + set_md_var("descr", "Helper test case for the t_integration test program"); +} +ATF_TEST_CASE_BODY(env_list) +{ + const atf::process::status s = + atf::process::exec(atf::fs::path("env"), + atf::process::argv_array("env", NULL), + atf::process::stream_inherit(), + atf::process::stream_inherit()); + ATF_REQUIRE(s.exited()); + ATF_REQUIRE(s.exitstatus() == EXIT_SUCCESS); +} + +ATF_TEST_CASE(env_home); +ATF_TEST_CASE_HEAD(env_home) +{ + set_md_var("descr", "Helper test case for the t_integration test program"); +} +ATF_TEST_CASE_BODY(env_home) +{ + ATF_REQUIRE(atf::env::has("HOME")); + atf::fs::path p(atf::env::get("HOME")); + atf::fs::file_info fi1(p); + atf::fs::file_info fi2(atf::fs::path(".")); + ATF_REQUIRE_EQ(fi1.get_device(), fi2.get_device()); + ATF_REQUIRE_EQ(fi1.get_inode(), fi2.get_inode()); +} + +ATF_TEST_CASE(read_stdin); +ATF_TEST_CASE_HEAD(read_stdin) +{ + set_md_var("descr", "Helper test case for the t_integration test program"); +} +ATF_TEST_CASE_BODY(read_stdin) +{ + char buf[100]; + ssize_t len = ::read(STDIN_FILENO, buf, sizeof(buf) - 1); + ATF_REQUIRE(len != -1); + + buf[len + 1] = '\0'; + for (ssize_t i = 0; i < len; i++) { + if (buf[i] != '\0') { + fail("The stdin of the test case does not seem to be /dev/zero; " + "got '" + std::string(buf) + "'"); + } + } +} + +ATF_TEST_CASE(umask); +ATF_TEST_CASE_HEAD(umask) +{ + set_md_var("descr", "Helper test case for the t_integration test program"); +} +ATF_TEST_CASE_BODY(umask) +{ + mode_t m = ::umask(0); + std::cout << "umask: " << std::setw(4) << std::setfill('0') + << std::oct << m << "\n"; + (void)::umask(m); +} + +ATF_TEST_CASE_WITH_CLEANUP(cleanup_states); +ATF_TEST_CASE_HEAD(cleanup_states) +{ + set_md_var("descr", "Helper test case for the t_integration test program"); +} +ATF_TEST_CASE_BODY(cleanup_states) +{ + touch(get_config_var("statedir") + "/to-delete"); + touch(get_config_var("statedir") + "/to-stay"); + + if (get_config_var("state") == "fail") + ATF_FAIL("On purpose"); + else if (get_config_var("state") == "skip") + ATF_SKIP("On purpose"); +} +ATF_TEST_CASE_CLEANUP(cleanup_states) +{ + atf::fs::remove(atf::fs::path(get_config_var("statedir") + "/to-delete")); +} + +ATF_TEST_CASE_WITH_CLEANUP(cleanup_curdir); +ATF_TEST_CASE_HEAD(cleanup_curdir) +{ + set_md_var("descr", "Helper test case for the t_integration test program"); +} +ATF_TEST_CASE_BODY(cleanup_curdir) +{ + std::ofstream os("oldvalue"); + if (!os) + ATF_FAIL("Failed to create oldvalue file"); + os << 1234; + os.close(); +} +ATF_TEST_CASE_CLEANUP(cleanup_curdir) +{ + std::ifstream is("oldvalue"); + if (is) { + int i; + is >> i; + std::cout << "Old value: " << i << "\n"; + is.close(); + } +} + +ATF_TEST_CASE(require_arch); +ATF_TEST_CASE_HEAD(require_arch) +{ + set_md_var("descr", "Helper test case for the t_integration test program"); + set_md_var("require.arch", get_config_var("arch", "not-set")); +} +ATF_TEST_CASE_BODY(require_arch) +{ +} + +ATF_TEST_CASE(require_config); +ATF_TEST_CASE_HEAD(require_config) +{ + set_md_var("descr", "Helper test case for the t_integration test program"); + set_md_var("require.config", "var1 var2"); +} +ATF_TEST_CASE_BODY(require_config) +{ + std::cout << "var1: " << get_config_var("var1") << "\n"; + std::cout << "var2: " << get_config_var("var2") << "\n"; +} + +ATF_TEST_CASE(require_files); +ATF_TEST_CASE_HEAD(require_files) +{ + set_md_var("descr", "Helper test case for the t_integration test program"); + set_md_var("require.files", get_config_var("files", "not-set")); +} +ATF_TEST_CASE_BODY(require_files) +{ +} + +ATF_TEST_CASE(require_machine); +ATF_TEST_CASE_HEAD(require_machine) +{ + set_md_var("descr", "Helper test case for the t_integration test program"); + set_md_var("require.machine", get_config_var("machine", "not-set")); +} +ATF_TEST_CASE_BODY(require_machine) +{ +} + +ATF_TEST_CASE(require_progs); +ATF_TEST_CASE_HEAD(require_progs) +{ + set_md_var("descr", "Helper test case for the t_integration test program"); + set_md_var("require.progs", get_config_var("progs", "not-set")); +} +ATF_TEST_CASE_BODY(require_progs) +{ +} + +ATF_TEST_CASE(require_user); +ATF_TEST_CASE_HEAD(require_user) +{ + set_md_var("descr", "Helper test case for the t_integration test program"); + set_md_var("require.user", get_config_var("user", "not-set")); +} +ATF_TEST_CASE_BODY(require_user) +{ +} + +ATF_TEST_CASE(timeout); +ATF_TEST_CASE_HEAD(timeout) +{ + set_md_var("descr", "Helper test case for the t_integration test program"); + set_md_var("timeout", "1"); +} +ATF_TEST_CASE_BODY(timeout) +{ + sleep(10); + touch(get_config_var("statedir") + "/finished"); +} + +ATF_TEST_CASE(timeout_forkexit); +ATF_TEST_CASE_HEAD(timeout_forkexit) +{ + set_md_var("descr", "Helper test case for the t_integration test program"); +} +ATF_TEST_CASE_BODY(timeout_forkexit) +{ + pid_t pid = fork(); + ATF_REQUIRE(pid != -1); + + if (pid == 0) { + sigset_t mask; + sigemptyset(&mask); + + std::cout << "Waiting in subprocess\n"; + std::cout.flush(); + ::sigsuspend(&mask); + + touch(get_config_var("statedir") + "/child-finished"); + std::cout << "Subprocess exiting\n"; + std::cout.flush(); + exit(EXIT_SUCCESS); + } else { + // Don't wait for the child process and let atf-run deal with it. + touch(get_config_var("statedir") + "/parent-finished"); + std::cout << "Parent process exiting\n"; + ATF_PASS(); + } +} + +ATF_TEST_CASE(use_fs); +ATF_TEST_CASE_HEAD(use_fs) +{ + set_md_var("descr", "Helper test case for the t_integration test program"); + set_md_var("use.fs", "this-is-deprecated"); +} +ATF_TEST_CASE_BODY(use_fs) +{ + touch("test-file"); +} + +// ------------------------------------------------------------------------ +// Main. +// ------------------------------------------------------------------------ + +ATF_INIT_TEST_CASES(tcs) +{ + std::string which = atf::env::get("TESTCASE"); + + // Add helper tests for t_integration. + if (which == "pass") + ATF_ADD_TEST_CASE(tcs, pass); + if (which == "config") + ATF_ADD_TEST_CASE(tcs, config); + if (which == "fds") + ATF_ADD_TEST_CASE(tcs, fds); + if (which == "mux_streams") + ATF_ADD_TEST_CASE(tcs, mux_streams); + if (which == "testvar") + ATF_ADD_TEST_CASE(tcs, testvar); + if (which == "env_list") + ATF_ADD_TEST_CASE(tcs, env_list); + if (which == "env_home") + ATF_ADD_TEST_CASE(tcs, env_home); + if (which == "read_stdin") + ATF_ADD_TEST_CASE(tcs, read_stdin); + if (which == "umask") + ATF_ADD_TEST_CASE(tcs, umask); + if (which == "cleanup_states") + ATF_ADD_TEST_CASE(tcs, cleanup_states); + if (which == "cleanup_curdir") + ATF_ADD_TEST_CASE(tcs, cleanup_curdir); + if (which == "require_arch") + ATF_ADD_TEST_CASE(tcs, require_arch); + if (which == "require_config") + ATF_ADD_TEST_CASE(tcs, require_config); + if (which == "require_files") + ATF_ADD_TEST_CASE(tcs, require_files); + if (which == "require_machine") + ATF_ADD_TEST_CASE(tcs, require_machine); + if (which == "require_progs") + ATF_ADD_TEST_CASE(tcs, require_progs); + if (which == "require_user") + ATF_ADD_TEST_CASE(tcs, require_user); + if (which == "timeout") + ATF_ADD_TEST_CASE(tcs, timeout); + if (which == "timeout_forkexit") + ATF_ADD_TEST_CASE(tcs, timeout_forkexit); + if (which == "use_fs") + ATF_ADD_TEST_CASE(tcs, use_fs); +} diff --git a/atf-run/pass_helper.cpp b/atf-run/pass_helper.cpp new file mode 100644 index 0000000000000..b752b131b3872 --- /dev/null +++ b/atf-run/pass_helper.cpp @@ -0,0 +1,44 @@ +// +// Automated Testing Framework (atf) +// +// 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. +// + +#include "atf-c++/macros.hpp" + +ATF_TEST_CASE(main); +ATF_TEST_CASE_HEAD(main) +{ + set_md_var("descr", "Helper test case that always passes"); +} +ATF_TEST_CASE_BODY(main) +{ +} + +ATF_INIT_TEST_CASES(tcs) +{ + ATF_ADD_TEST_CASE(tcs, main); +} diff --git a/atf-run/requirements.cpp b/atf-run/requirements.cpp new file mode 100644 index 0000000000000..75537d5ea52bc --- /dev/null +++ b/atf-run/requirements.cpp @@ -0,0 +1,319 @@ +// +// Automated Testing Framework (atf) +// +// 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. +// + +extern "C" { +#include <sys/param.h> +#include <sys/sysctl.h> +} + +#include <cerrno> +#include <cstring> +#include <stdexcept> + +extern "C" { +#include "atf-c/defs.h" +} + +#include "atf-c++/config.hpp" + +#include "atf-c++/detail/fs.hpp" +#include "atf-c++/detail/env.hpp" +#include "atf-c++/detail/sanity.hpp" +#include "atf-c++/detail/text.hpp" + +#include "requirements.hpp" +#include "user.hpp" + +namespace impl = atf::atf_run; + +namespace { + +static +bool +has_program(const atf::fs::path& program) +{ + bool found = false; + + if (program.is_absolute()) { + found = atf::fs::is_executable(program); + } else { + if (program.str().find('/') != std::string::npos) + throw std::runtime_error("Relative paths are not allowed " + "when searching for a program (" + + program.str() + ")"); + + const std::vector< std::string > dirs = atf::text::split( + atf::env::get("PATH"), ":"); + for (std::vector< std::string >::const_iterator iter = dirs.begin(); + !found && iter != dirs.end(); iter++) { + const atf::fs::path& p = atf::fs::path(*iter) / program; + if (atf::fs::is_executable(p)) + found = true; + } + } + + return found; +} + +static +std::string +check_arch(const std::string& arches) +{ + const std::vector< std::string > v = atf::text::split(arches, " "); + + for (std::vector< std::string >::const_iterator iter = v.begin(); + iter != v.end(); iter++) { + if ((*iter) == atf::config::get("atf_arch")) + return ""; + } + + if (v.size() == 1) + return "Requires the '" + arches + "' architecture"; + else + return "Requires one of the '" + arches + "' architectures"; +} + +static +std::string +check_config(const std::string& variables, const atf::tests::vars_map& config) +{ + const std::vector< std::string > v = atf::text::split(variables, " "); + for (std::vector< std::string >::const_iterator iter = v.begin(); + iter != v.end(); iter++) { + if (config.find((*iter)) == config.end()) + return "Required configuration variable '" + (*iter) + "' not " + "defined"; + } + return ""; +} + +static +std::string +check_files(const std::string& progs) +{ + const std::vector< std::string > v = atf::text::split(progs, " "); + for (std::vector< std::string >::const_iterator iter = v.begin(); + iter != v.end(); iter++) { + const atf::fs::path file(*iter); + if (!file.is_absolute()) + throw std::runtime_error("Relative paths are not allowed when " + "checking for a required file (" + file.str() + ")"); + if (!atf::fs::exists(file)) + return "Required file '" + file.str() + "' not found"; + } + return ""; +} + +static +std::string +check_machine(const std::string& machines) +{ + const std::vector< std::string > v = atf::text::split(machines, " "); + + for (std::vector< std::string >::const_iterator iter = v.begin(); + iter != v.end(); iter++) { + if ((*iter) == atf::config::get("atf_machine")) + return ""; + } + + if (v.size() == 1) + return "Requires the '" + machines + "' machine type"; + else + return "Requires one of the '" + machines + "' machine types"; +} + +#if defined(__APPLE__) || defined(__NetBSD__) +static +std::string +check_memory_sysctl(const int64_t needed, const char* sysctl_variable) +{ + int64_t available; + std::size_t available_length = sizeof(available); + if (::sysctlbyname(sysctl_variable, &available, &available_length, + NULL, 0) == -1) { + const char* e = std::strerror(errno); + return "Failed to get sysctl(hw.usermem64) value: " + std::string(e); + } + + if (available < needed) { + return "Not enough memory; needed " + atf::text::to_string(needed) + + ", available " + atf::text::to_string(available); + } else + return ""; +} +# if defined(__APPLE__) +static +std::string +check_memory_darwin(const int64_t needed) +{ + return check_memory_sysctl(needed, "hw.usermem"); +} +# elif defined(__NetBSD__) +static +std::string +check_memory_netbsd(const int64_t needed) +{ + return check_memory_sysctl(needed, "hw.usermem64"); +} +# else +# error "Conditional error" +# endif +#else +static +std::string +check_memory_unknown(const int64_t needed ATF_DEFS_ATTRIBUTE_UNUSED) +{ + return ""; +} +#endif + +static +std::string +check_memory(const std::string& raw_memory) +{ + const int64_t needed = atf::text::to_bytes(raw_memory); + +#if defined(__APPLE__) + return check_memory_darwin(needed); +#elif defined(__NetBSD__) + return check_memory_netbsd(needed); +#else + return check_memory_unknown(needed); +#endif +} + +static +std::string +check_progs(const std::string& progs) +{ + const std::vector< std::string > v = atf::text::split(progs, " "); + for (std::vector< std::string >::const_iterator iter = v.begin(); + iter != v.end(); iter++) { + if (!has_program(atf::fs::path(*iter))) + return "Required program '" + (*iter) + "' not found in the PATH"; + } + return ""; +} + +static +std::string +check_user(const std::string& user, const atf::tests::vars_map& config) +{ + if (user == "root") { + if (!impl::is_root()) + return "Requires root privileges"; + else + return ""; + } else if (user == "unprivileged") { + if (impl::is_root()) { + const atf::tests::vars_map::const_iterator iter = config.find( + "unprivileged-user"); + if (iter == config.end()) + return "Requires an unprivileged user and the " + "'unprivileged-user' configuration variable is not set"; + else { + const std::string& unprivileged_user = (*iter).second; + try { + (void)impl::get_user_ids(unprivileged_user); + return ""; + } catch (const std::runtime_error& e) { + return "Failed to get information for user " + + unprivileged_user; + } + } + } else + return ""; + } else + throw std::runtime_error("Invalid value '" + user + "' for property " + "require.user"); +} + +} // anonymous namespace + +std::string +impl::check_requirements(const atf::tests::vars_map& metadata, + const atf::tests::vars_map& config) +{ + std::string failure_reason = ""; + + for (atf::tests::vars_map::const_iterator iter = metadata.begin(); + failure_reason.empty() && iter != metadata.end(); iter++) { + const std::string& name = (*iter).first; + const std::string& value = (*iter).second; + INV(!value.empty()); // Enforced by application/X-atf-tp parser. + + if (name == "require.arch") + failure_reason = check_arch(value); + else if (name == "require.config") + failure_reason = check_config(value, config); + else if (name == "require.files") + failure_reason = check_files(value); + else if (name == "require.machine") + failure_reason = check_machine(value); + else if (name == "require.memory") + failure_reason = check_memory(value); + else if (name == "require.progs") + failure_reason = check_progs(value); + else if (name == "require.user") + failure_reason = check_user(value, config); + else { + // Unknown require.* properties are forbidden by the + // application/X-atf-tp parser. + INV(failure_reason.find("require.") != 0); + } + } + + return failure_reason; +} + +std::pair< int, int > +impl::get_required_user(const atf::tests::vars_map& metadata, + const atf::tests::vars_map& config) +{ + const atf::tests::vars_map::const_iterator user = metadata.find( + "require.user"); + if (user == metadata.end()) + return std::make_pair(-1, -1); + + if ((*user).second == "unprivileged") { + if (impl::is_root()) { + const atf::tests::vars_map::const_iterator iter = config.find( + "unprivileged-user"); + try { + return impl::get_user_ids((*iter).second); + } catch (const std::exception& e) { + UNREACHABLE; // This has been validated by check_user. + throw e; + } + } else { + return std::make_pair(-1, -1); + } + } else + return std::make_pair(-1, -1); +} diff --git a/atf-run/requirements.hpp b/atf-run/requirements.hpp new file mode 100644 index 0000000000000..62c072d5fac9a --- /dev/null +++ b/atf-run/requirements.hpp @@ -0,0 +1,44 @@ +// +// Automated Testing Framework (atf) +// +// 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. +// + +#include <string> +#include <utility> + +#include "atf-c++/tests.hpp" + +namespace atf { +namespace atf_run { + +std::string check_requirements(const atf::tests::vars_map&, + const atf::tests::vars_map&); +std::pair< int, int > get_required_user(const atf::tests::vars_map&, + const atf::tests::vars_map&); + +} // namespace atf_run +} // namespace atf diff --git a/atf-run/requirements_test.cpp b/atf-run/requirements_test.cpp new file mode 100644 index 0000000000000..ef72b411d9058 --- /dev/null +++ b/atf-run/requirements_test.cpp @@ -0,0 +1,398 @@ +// +// Automated Testing Framework (atf) +// +// 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. +// + +#include "atf-c++/config.hpp" +#include "atf-c++/detail/text.hpp" +#include "atf-c++/macros.hpp" + +#include "requirements.hpp" +#include "user.hpp" + +namespace impl = atf::atf_run; + +// ------------------------------------------------------------------------- +// Auxiliary functions. +// ------------------------------------------------------------------------- + +namespace { + +const atf::tests::vars_map no_config; + +void +do_check(const std::string& expected, const atf::tests::vars_map& metadata, + const atf::tests::vars_map& config = no_config) +{ + const std::string actual = impl::check_requirements(metadata, config); + if (!atf::text::match(actual, expected)) + ATF_FAIL("Requirements failure reason \"" + actual + "\" does not " + "match \"" + expected + "\""); +} + +} // anonymous namespace + +// ------------------------------------------------------------------------- +// Tests for the require.arch metadata property. +// ------------------------------------------------------------------------- + +ATF_TEST_CASE(require_arch_one_ok); +ATF_TEST_CASE_HEAD(require_arch_one_ok) {} +ATF_TEST_CASE_BODY(require_arch_one_ok) { + atf::tests::vars_map metadata; + metadata["require.arch"] = atf::config::get("atf_arch"); + do_check("", metadata); +} + +ATF_TEST_CASE(require_arch_one_fail); +ATF_TEST_CASE_HEAD(require_arch_one_fail) {} +ATF_TEST_CASE_BODY(require_arch_one_fail) { + atf::tests::vars_map metadata; + metadata["require.arch"] = "__fake_arch__"; + do_check("Requires the '__fake_arch__' architecture", metadata); +} + +ATF_TEST_CASE(require_arch_many_ok); +ATF_TEST_CASE_HEAD(require_arch_many_ok) {} +ATF_TEST_CASE_BODY(require_arch_many_ok) { + atf::tests::vars_map metadata; + metadata["require.arch"] = "__foo__ " + atf::config::get("atf_arch") + + " __bar__"; + do_check("", metadata); +} + +ATF_TEST_CASE(require_arch_many_fail); +ATF_TEST_CASE_HEAD(require_arch_many_fail) {} +ATF_TEST_CASE_BODY(require_arch_many_fail) { + atf::tests::vars_map metadata; + metadata["require.arch"] = "__foo__ __bar__ __baz__"; + do_check("Requires one of the '__foo__ __bar__ __baz__' architectures", + metadata); +} + +// ------------------------------------------------------------------------- +// Tests for the require.config metadata property. +// ------------------------------------------------------------------------- + +ATF_TEST_CASE(require_config_one_ok); +ATF_TEST_CASE_HEAD(require_config_one_ok) {} +ATF_TEST_CASE_BODY(require_config_one_ok) { + atf::tests::vars_map metadata, config; + metadata["require.config"] = "var1"; + config["var1"] = "some-value"; + do_check("", metadata, config); +} + +ATF_TEST_CASE(require_config_one_fail); +ATF_TEST_CASE_HEAD(require_config_one_fail) {} +ATF_TEST_CASE_BODY(require_config_one_fail) { + atf::tests::vars_map metadata, config; + metadata["require.config"] = "var1"; + do_check("Required configuration variable 'var1' not defined", metadata, + config); +} + +ATF_TEST_CASE(require_config_many_ok); +ATF_TEST_CASE_HEAD(require_config_many_ok) {} +ATF_TEST_CASE_BODY(require_config_many_ok) { + atf::tests::vars_map metadata, config; + metadata["require.config"] = "var1 var2 var3"; + config["var1"] = "first-value"; + config["var2"] = "second-value"; + config["var3"] = "third-value"; + do_check("", metadata, config); +} + +ATF_TEST_CASE(require_config_many_fail); +ATF_TEST_CASE_HEAD(require_config_many_fail) {} +ATF_TEST_CASE_BODY(require_config_many_fail) { + atf::tests::vars_map metadata, config; + metadata["require.config"] = "var1 var2 var3"; + config["var1"] = "first-value"; + config["var3"] = "third-value"; + do_check("Required configuration variable 'var2' not defined", metadata, + config); +} + +// ------------------------------------------------------------------------- +// Tests for the require.files metadata property. +// ------------------------------------------------------------------------- + +ATF_TEST_CASE_WITHOUT_HEAD(require_files_one_ok); +ATF_TEST_CASE_BODY(require_files_one_ok) { + atf::tests::vars_map metadata; + metadata["require.files"] = "/bin/ls"; + do_check("", metadata); +} + +ATF_TEST_CASE_WITHOUT_HEAD(require_files_one_missing); +ATF_TEST_CASE_BODY(require_files_one_missing) { + atf::tests::vars_map metadata; + metadata["require.files"] = "/non-existent/foo"; + do_check("Required file '/non-existent/foo' not found", metadata); +} + +ATF_TEST_CASE_WITHOUT_HEAD(require_files_one_fail); +ATF_TEST_CASE_BODY(require_files_one_fail) { + atf::tests::vars_map metadata; + metadata["require.files"] = "/bin/cp this-is-relative"; + ATF_REQUIRE_THROW_RE(std::runtime_error, "Relative.*(this-is-relative)", + impl::check_requirements(metadata, no_config)); +} + +ATF_TEST_CASE_WITHOUT_HEAD(require_files_many_ok); +ATF_TEST_CASE_BODY(require_files_many_ok) { + atf::tests::vars_map metadata; + metadata["require.files"] = "/bin/ls /bin/cp"; + do_check("", metadata); +} + +ATF_TEST_CASE_WITHOUT_HEAD(require_files_many_missing); +ATF_TEST_CASE_BODY(require_files_many_missing) { + atf::tests::vars_map metadata; + metadata["require.files"] = "/bin/ls /non-existent/bar /bin/cp"; + do_check("Required file '/non-existent/bar' not found", metadata); +} + +ATF_TEST_CASE_WITHOUT_HEAD(require_files_many_fail); +ATF_TEST_CASE_BODY(require_files_many_fail) { + atf::tests::vars_map metadata; + metadata["require.files"] = "/bin/cp also-relative"; + ATF_REQUIRE_THROW_RE(std::runtime_error, "Relative.*(also-relative)", + impl::check_requirements(metadata, no_config)); +} + +// ------------------------------------------------------------------------- +// Tests for the require.machine metadata property. +// ------------------------------------------------------------------------- + +ATF_TEST_CASE(require_machine_one_ok); +ATF_TEST_CASE_HEAD(require_machine_one_ok) {} +ATF_TEST_CASE_BODY(require_machine_one_ok) { + atf::tests::vars_map metadata; + metadata["require.machine"] = atf::config::get("atf_machine"); + do_check("", metadata); +} + +ATF_TEST_CASE(require_machine_one_fail); +ATF_TEST_CASE_HEAD(require_machine_one_fail) {} +ATF_TEST_CASE_BODY(require_machine_one_fail) { + atf::tests::vars_map metadata; + metadata["require.machine"] = "__fake_machine__"; + do_check("Requires the '__fake_machine__' machine type", metadata); +} + +ATF_TEST_CASE(require_machine_many_ok); +ATF_TEST_CASE_HEAD(require_machine_many_ok) {} +ATF_TEST_CASE_BODY(require_machine_many_ok) { + atf::tests::vars_map metadata; + metadata["require.machine"] = "__foo__ " + atf::config::get("atf_machine") + + " __bar__"; + do_check("", metadata); +} + +ATF_TEST_CASE(require_machine_many_fail); +ATF_TEST_CASE_HEAD(require_machine_many_fail) {} +ATF_TEST_CASE_BODY(require_machine_many_fail) { + atf::tests::vars_map metadata; + metadata["require.machine"] = "__foo__ __bar__ __baz__"; + do_check("Requires one of the '__foo__ __bar__ __baz__' machine types", + metadata); +} + +// ------------------------------------------------------------------------- +// Tests for the require.memory metadata property. +// ------------------------------------------------------------------------- + +ATF_TEST_CASE_WITHOUT_HEAD(require_memory_ok); +ATF_TEST_CASE_BODY(require_memory_ok) { + atf::tests::vars_map metadata; + metadata["require.memory"] = "1m"; + do_check("", metadata); +} + +ATF_TEST_CASE_WITHOUT_HEAD(require_memory_not_enough); +ATF_TEST_CASE_BODY(require_memory_not_enough) { + atf::tests::vars_map metadata; + metadata["require.memory"] = "128t"; +#if defined(__APPLE__) || defined(__NetBSD__) + do_check("Not enough memory; needed 140737488355328, available [0-9]*", + metadata); +#else + skip("Don't know how to check for the amount of physical memory"); +#endif +} + +ATF_TEST_CASE_WITHOUT_HEAD(require_memory_fail); +ATF_TEST_CASE_BODY(require_memory_fail) { + atf::tests::vars_map metadata; + metadata["require.memory"] = "foo"; + ATF_REQUIRE_THROW(std::runtime_error, + impl::check_requirements(metadata, no_config)); +} + +// ------------------------------------------------------------------------- +// Tests for the require.progs metadata property. +// ------------------------------------------------------------------------- + +ATF_TEST_CASE(require_progs_one_ok); +ATF_TEST_CASE_HEAD(require_progs_one_ok) {} +ATF_TEST_CASE_BODY(require_progs_one_ok) { + atf::tests::vars_map metadata; + metadata["require.progs"] = "cp"; + do_check("", metadata); +} + +ATF_TEST_CASE(require_progs_one_missing); +ATF_TEST_CASE_HEAD(require_progs_one_missing) {} +ATF_TEST_CASE_BODY(require_progs_one_missing) { + atf::tests::vars_map metadata; + metadata["require.progs"] = "cp __non-existent__"; + do_check("Required program '__non-existent__' not found in the PATH", + metadata); +} + +ATF_TEST_CASE(require_progs_one_fail); +ATF_TEST_CASE_HEAD(require_progs_one_fail) {} +ATF_TEST_CASE_BODY(require_progs_one_fail) { + atf::tests::vars_map metadata; + metadata["require.progs"] = "bin/cp"; + ATF_REQUIRE_THROW(std::runtime_error, + impl::check_requirements(metadata, no_config)); +} + +ATF_TEST_CASE(require_progs_many_ok); +ATF_TEST_CASE_HEAD(require_progs_many_ok) {} +ATF_TEST_CASE_BODY(require_progs_many_ok) { + atf::tests::vars_map metadata; + metadata["require.progs"] = "cp ls mv"; + do_check("", metadata); +} + +ATF_TEST_CASE(require_progs_many_missing); +ATF_TEST_CASE_HEAD(require_progs_many_missing) {} +ATF_TEST_CASE_BODY(require_progs_many_missing) { + atf::tests::vars_map metadata; + metadata["require.progs"] = "mv ls __foo__ cp"; + do_check("Required program '__foo__' not found in the PATH", metadata); +} + +ATF_TEST_CASE(require_progs_many_fail); +ATF_TEST_CASE_HEAD(require_progs_many_fail) {} +ATF_TEST_CASE_BODY(require_progs_many_fail) { + atf::tests::vars_map metadata; + metadata["require.progs"] = "ls cp ../bin/cp"; + ATF_REQUIRE_THROW(std::runtime_error, + impl::check_requirements(metadata, no_config)); +} + +// ------------------------------------------------------------------------- +// Tests for the require.user metadata property. +// ------------------------------------------------------------------------- + +ATF_TEST_CASE(require_user_root); +ATF_TEST_CASE_HEAD(require_user_root) {} +ATF_TEST_CASE_BODY(require_user_root) { + atf::tests::vars_map metadata; + metadata["require.user"] = "root"; + if (atf::atf_run::is_root()) + do_check("", metadata); + else + do_check("Requires root privileges", metadata); +} + +ATF_TEST_CASE(require_user_unprivileged); +ATF_TEST_CASE_HEAD(require_user_unprivileged) {} +ATF_TEST_CASE_BODY(require_user_unprivileged) { + atf::tests::vars_map metadata; + metadata["require.user"] = "unprivileged"; + if (atf::atf_run::is_root()) + do_check("Requires an unprivileged user and the 'unprivileged-user' " + "configuration variable is not set", metadata); + else + do_check("", metadata); +} + +ATF_TEST_CASE(require_user_fail); +ATF_TEST_CASE_HEAD(require_user_fail) {} +ATF_TEST_CASE_BODY(require_user_fail) { + atf::tests::vars_map metadata; + metadata["require.user"] = "nobody"; + ATF_REQUIRE_THROW(std::runtime_error, + impl::check_requirements(metadata, no_config)); +} + +// ------------------------------------------------------------------------- +// Main. +// ------------------------------------------------------------------------- + +ATF_INIT_TEST_CASES(tcs) +{ + // Add test cases for require.arch. + ATF_ADD_TEST_CASE(tcs, require_arch_one_ok); + ATF_ADD_TEST_CASE(tcs, require_arch_one_fail); + ATF_ADD_TEST_CASE(tcs, require_arch_many_ok); + ATF_ADD_TEST_CASE(tcs, require_arch_many_fail); + + // Add test cases for require.config. + ATF_ADD_TEST_CASE(tcs, require_config_one_ok); + ATF_ADD_TEST_CASE(tcs, require_config_one_fail); + ATF_ADD_TEST_CASE(tcs, require_config_many_ok); + ATF_ADD_TEST_CASE(tcs, require_config_many_fail); + + // Add test cases for require.files. + ATF_ADD_TEST_CASE(tcs, require_files_one_ok); + ATF_ADD_TEST_CASE(tcs, require_files_one_missing); + ATF_ADD_TEST_CASE(tcs, require_files_one_fail); + ATF_ADD_TEST_CASE(tcs, require_files_many_ok); + ATF_ADD_TEST_CASE(tcs, require_files_many_missing); + ATF_ADD_TEST_CASE(tcs, require_files_many_fail); + + // Add test cases for require.machine. + ATF_ADD_TEST_CASE(tcs, require_machine_one_ok); + ATF_ADD_TEST_CASE(tcs, require_machine_one_fail); + ATF_ADD_TEST_CASE(tcs, require_machine_many_ok); + ATF_ADD_TEST_CASE(tcs, require_machine_many_fail); + + // Add test cases for require.memory. + ATF_ADD_TEST_CASE(tcs, require_memory_ok); + ATF_ADD_TEST_CASE(tcs, require_memory_not_enough); + ATF_ADD_TEST_CASE(tcs, require_memory_fail); + + // Add test cases for require.progs. + ATF_ADD_TEST_CASE(tcs, require_progs_one_ok); + ATF_ADD_TEST_CASE(tcs, require_progs_one_missing); + ATF_ADD_TEST_CASE(tcs, require_progs_one_fail); + ATF_ADD_TEST_CASE(tcs, require_progs_many_ok); + ATF_ADD_TEST_CASE(tcs, require_progs_many_missing); + ATF_ADD_TEST_CASE(tcs, require_progs_many_fail); + + // Add test cases for require.user. + ATF_ADD_TEST_CASE(tcs, require_user_root); + ATF_ADD_TEST_CASE(tcs, require_user_unprivileged); + ATF_ADD_TEST_CASE(tcs, require_user_fail); +} diff --git a/atf-run/sample/atf-run.hooks b/atf-run/sample/atf-run.hooks new file mode 100644 index 0000000000000..86d187d3de3bb --- /dev/null +++ b/atf-run/sample/atf-run.hooks @@ -0,0 +1,23 @@ +# +# Definition of custom hooks for atf-run. +# +# Uncomment any hooks that you want to override and add your own code +# to them. Some sample calls are included in them. Be very careful +# with what you print from here. +# +# See atf-run(1) for more details. +# + +#info_start_hook() +#{ +# default_info_start_hook "${@}" +# +# atf_tps_writer_info "extra.info" "An example" +#} + +#info_end_hook() +#{ +# default_info_end_hook "${@}" +# +# atf_tps_writer_info "extra.info" "An example" +#} diff --git a/atf-run/sample/common.conf b/atf-run/sample/common.conf new file mode 100644 index 0000000000000..464ee96d09cd0 --- /dev/null +++ b/atf-run/sample/common.conf @@ -0,0 +1,11 @@ +Content-Type: application/X-atf-config; version="1" + +# +# Sample configuration file for properties affecting all test suites. +# + +# When running the test suite as root, some tests require to switch to +# an unprivileged user to perform extra checks. Set this variable to +# the user you want to use in those cases. If not set, those tests will +# be skipped. +#unprivileged-user = "_atf" diff --git a/atf-run/several_tcs_helper.c b/atf-run/several_tcs_helper.c new file mode 100644 index 0000000000000..d287c8193a67b --- /dev/null +++ b/atf-run/several_tcs_helper.c @@ -0,0 +1,67 @@ +/* + * Automated Testing Framework (atf) + * + * 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. + */ + +#include <atf-c.h> + +ATF_TC(first); +ATF_TC_HEAD(first, tc) +{ + atf_tc_set_md_var(tc, "descr", "Description 1"); +} +ATF_TC_BODY(first, tc) +{ +} + +ATF_TC_WITH_CLEANUP(second); +ATF_TC_HEAD(second, tc) +{ + atf_tc_set_md_var(tc, "descr", "Description 2"); + atf_tc_set_md_var(tc, "timeout", "500"); + atf_tc_set_md_var(tc, "X-property", "Custom property"); +} +ATF_TC_BODY(second, tc) +{ +} +ATF_TC_CLEANUP(second, tc) +{ +} + +ATF_TC_WITHOUT_HEAD(third); +ATF_TC_BODY(third, tc) +{ +} + +ATF_TP_ADD_TCS(tp) +{ + ATF_TP_ADD_TC(tp, first); + ATF_TP_ADD_TC(tp, second); + ATF_TP_ADD_TC(tp, third); + + return atf_no_error(); +} diff --git a/atf-run/share/atf-run.hooks b/atf-run/share/atf-run.hooks new file mode 100644 index 0000000000000..c94f3bcfc9ec6 --- /dev/null +++ b/atf-run/share/atf-run.hooks @@ -0,0 +1,94 @@ +# +# Automated Testing Framework (atf) +# +# 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_tps_writer_info() +{ + class=${1}; shift + echo "info: ${class}, $*" +} + +info_start_hook() +{ + default_info_start_hook "${@}" +} + +default_info_start_hook() +{ + atf_tps_writer_info "atf.version" $(atf-version | head -n 1) + + atf_tps_writer_info "tests.root" $(pwd) + + atf_tps_writer_info "time.start" $(date) + + atf_tps_writer_info "uname.sysname" $(uname -s) + atf_tps_writer_info "uname.nodename" $(uname -n) + atf_tps_writer_info "uname.release" $(uname -r) + atf_tps_writer_info "uname.version" $(uname -v) + atf_tps_writer_info "uname.machine" $(uname -m) + + # Add all the environment variables to the report. We have to be + # careful with those that span over multiple lines; otherwise their + # values could be printed as multiple different variables (one per + # line), which is incorrect. + oldifs="${IFS}" + IFS=' +' + set -- $(env) + val=${1}; shift + while [ ${#} -gt 0 ]; do + if echo "${1}" | grep '^[a-zA-Z0-0_][a-zA-Z0-9_]*=' >/dev/null; then + atf_tps_writer_info "env" "${val}" + val="${1}" + else + val="${val} ${1}" + fi + shift + done + atf_tps_writer_info "env" "${val}" + IFS="${oldifs}" +} + +info_end_hook() +{ + default_info_end_hook "${@}" +} + +default_info_end_hook() +{ + atf_tps_writer_info "time.end" $(date) +} + +sitehooks=$(atf-config -t atf_confdir)/atf-run.hooks +userhooks=${HOME}/.atf/atf-run.hooks +[ -f ${sitehooks} ] && . ${sitehooks} +[ -f ${userhooks} ] && . ${userhooks} + +eval ${1} + +# vim: syntax=sh:expandtab:shiftwidth=4:softtabstop=4 diff --git a/atf-run/signals.cpp b/atf-run/signals.cpp new file mode 100644 index 0000000000000..851c8f02c2c4c --- /dev/null +++ b/atf-run/signals.cpp @@ -0,0 +1,147 @@ +// +// Automated Testing Framework (atf) +// +// 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. +// + +#if defined(HAVE_CONFIG_H) +#include "bconfig.h" +#endif + +extern "C" { +#include <signal.h> +#include <unistd.h> +} + +#include <cerrno> + +#include "atf-c++/detail/exceptions.hpp" +#include "atf-c++/detail/sanity.hpp" + +#include "signals.hpp" + +namespace impl = atf::atf_run; +#define IMPL_NAME "atf::atf_run" + +const int impl::last_signo = LAST_SIGNO; + +// ------------------------------------------------------------------------ +// The "signal_holder" class. +// ------------------------------------------------------------------------ + +namespace { + +static bool happened[LAST_SIGNO + 1]; + +static +void +holder_handler(const int signo) +{ + happened[signo] = true; +} + +} // anonymous namespace + +impl::signal_holder::signal_holder(const int signo) : + m_signo(signo), + m_sp(NULL) +{ + happened[signo] = false; + m_sp = new signal_programmer(m_signo, holder_handler); +} + +impl::signal_holder::~signal_holder(void) +{ + if (m_sp != NULL) + delete m_sp; + + if (happened[m_signo]) + ::kill(::getpid(), m_signo); +} + +void +impl::signal_holder::process(void) +{ + if (happened[m_signo]) { + delete m_sp; + m_sp = NULL; + happened[m_signo] = false; + ::kill(::getpid(), m_signo); + m_sp = new signal_programmer(m_signo, holder_handler); + } +} + +// ------------------------------------------------------------------------ +// The "signal_programmer" class. +// ------------------------------------------------------------------------ + +impl::signal_programmer::signal_programmer(const int signo, const handler h) : + m_signo(signo), + m_handler(h), + m_programmed(false) +{ + struct ::sigaction sa; + + sa.sa_handler = m_handler; + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + + if (::sigaction(m_signo, &sa, &m_oldsa) == -1) + throw atf::system_error(IMPL_NAME, "Could not install handler for " + "signal", errno); + m_programmed = true; +} + +impl::signal_programmer::~signal_programmer(void) +{ + unprogram(); +} + +void +impl::signal_programmer::unprogram(void) +{ + if (m_programmed) { + if (::sigaction(m_signo, &m_oldsa, NULL) == -1) + UNREACHABLE; + m_programmed = false; + } +} + +// ------------------------------------------------------------------------ +// Free functions. +// ------------------------------------------------------------------------ + +void +impl::reset(const int signo) +{ + struct ::sigaction sa; + + sa.sa_handler = SIG_DFL; + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + + (void)::sigaction(signo, &sa, NULL); +} diff --git a/atf-run/signals.hpp b/atf-run/signals.hpp new file mode 100644 index 0000000000000..8765ac9ec56ec --- /dev/null +++ b/atf-run/signals.hpp @@ -0,0 +1,92 @@ +// +// Automated Testing Framework (atf) +// +// 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. +// + +#if !defined(_ATF_RUN_SIGNALS_HPP_) +#define _ATF_RUN_SIGNALS_HPP_ + +extern "C" { +#include <signal.h> +} + +namespace atf { +namespace atf_run { + +extern const int last_signo; +typedef void (*handler)(const int); + +class signal_programmer; + +// ------------------------------------------------------------------------ +// The "signal_holder" class. +// ------------------------------------------------------------------------ + +// +// A RAII model to hold a signal while the object is alive. +// +class signal_holder { + const int m_signo; + signal_programmer* m_sp; + +public: + signal_holder(const int); + ~signal_holder(void); + + void process(void); +}; + +// ------------------------------------------------------------------------ +// The "signal_programmer" class. +// ------------------------------------------------------------------------ + +// +// A RAII model to program a signal while the object is alive. +// +class signal_programmer { + const int m_signo; + const handler m_handler; + bool m_programmed; + struct sigaction m_oldsa; + +public: + signal_programmer(const int, const handler); + ~signal_programmer(void); + + void unprogram(void); +}; + +// ------------------------------------------------------------------------ +// Free functions. +// ------------------------------------------------------------------------ + +void reset(const int); + +} // namespace atf_run +} // namespace atf + +#endif // !defined(_ATF_RUN_SIGNALS_HPP_) diff --git a/atf-run/signals_test.cpp b/atf-run/signals_test.cpp new file mode 100644 index 0000000000000..358c8a8ab5906 --- /dev/null +++ b/atf-run/signals_test.cpp @@ -0,0 +1,277 @@ +// +// Automated Testing Framework (atf) +// +// 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 <signal.h> +#include <unistd.h> +} + +#include <cerrno> +#include <cstdlib> +#include <iostream> + +#include "atf-c/defs.h" + +#include "atf-c++/macros.hpp" + +#include "atf-c++/detail/exceptions.hpp" +#include "atf-c++/detail/process.hpp" + +#include "signals.hpp" + +// ------------------------------------------------------------------------ +// Auxiliary functions. +// ------------------------------------------------------------------------ + +namespace sigusr1 { + static bool happened = false; + + static + void + handler(int signo ATF_DEFS_ATTRIBUTE_UNUSED) + { + happened = true; + } + + static + void + program(void) + { + struct sigaction sa; + sa.sa_handler = handler; + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + if (::sigaction(SIGUSR1, &sa, NULL) == -1) + throw atf::system_error("sigusr1::program", + "sigaction(2) failed", errno); + } +} // namespace sigusr1 + +namespace sigusr1_2 { + static bool happened = false; + + static + void + handler(int signo ATF_DEFS_ATTRIBUTE_UNUSED) + { + happened = true; + } +} // namespace sigusr1_2 + +// ------------------------------------------------------------------------ +// Tests for the "signal_holder" class. +// ------------------------------------------------------------------------ + +ATF_TEST_CASE(signal_holder_preserve); +ATF_TEST_CASE_HEAD(signal_holder_preserve) +{ + set_md_var("descr", "Tests that signal_holder preserves the original " + "signal handler and restores it upon destruction"); +} +ATF_TEST_CASE_BODY(signal_holder_preserve) +{ + using atf::atf_run::signal_holder; + + sigusr1::program(); + + sigusr1::happened = false; + ::kill(::getpid(), SIGUSR1); + ATF_REQUIRE(sigusr1::happened); + + { + signal_holder hld(SIGUSR1); + ::kill(::getpid(), SIGUSR1); + } + + sigusr1::happened = false; + ::kill(::getpid(), SIGUSR1); + ATF_REQUIRE(sigusr1::happened); +} + +ATF_TEST_CASE(signal_holder_destructor); +ATF_TEST_CASE_HEAD(signal_holder_destructor) +{ + set_md_var("descr", "Tests that signal_holder processes a pending " + "signal upon destruction"); +} +ATF_TEST_CASE_BODY(signal_holder_destructor) +{ + using atf::atf_run::signal_holder; + + sigusr1::program(); + + sigusr1::happened = false; + ::kill(::getpid(), SIGUSR1); + ATF_REQUIRE(sigusr1::happened); + + { + signal_holder hld(SIGUSR1); + + sigusr1::happened = false; + ::kill(::getpid(), SIGUSR1); + ATF_REQUIRE(!sigusr1::happened); + } + ATF_REQUIRE(sigusr1::happened); +} + +ATF_TEST_CASE(signal_holder_process); +ATF_TEST_CASE_HEAD(signal_holder_process) +{ + set_md_var("descr", "Tests that signal_holder's process method works " + "to process a delayed signal explicitly"); +} +ATF_TEST_CASE_BODY(signal_holder_process) +{ + using atf::atf_run::signal_holder; + + sigusr1::program(); + + sigusr1::happened = false; + ::kill(::getpid(), SIGUSR1); + ATF_REQUIRE(sigusr1::happened); + + { + signal_holder hld(SIGUSR1); + + sigusr1::happened = false; + ::kill(::getpid(), SIGUSR1); + ATF_REQUIRE(!sigusr1::happened); + + hld.process(); + ATF_REQUIRE(sigusr1::happened); + + sigusr1::happened = false; + } + ATF_REQUIRE(!sigusr1::happened); +} + +// ------------------------------------------------------------------------ +// Tests for the "signal_programmer" class. +// ------------------------------------------------------------------------ + +ATF_TEST_CASE(signal_programmer_program); +ATF_TEST_CASE_HEAD(signal_programmer_program) +{ + set_md_var("descr", "Tests that signal_programmer correctly installs a " + "handler"); +} +ATF_TEST_CASE_BODY(signal_programmer_program) +{ + using atf::atf_run::signal_programmer; + + signal_programmer sp(SIGUSR1, sigusr1_2::handler); + + sigusr1_2::happened = false; + ::kill(::getpid(), SIGUSR1); + ATF_REQUIRE(sigusr1_2::happened); +} + +ATF_TEST_CASE(signal_programmer_preserve); +ATF_TEST_CASE_HEAD(signal_programmer_preserve) +{ + set_md_var("descr", "Tests that signal_programmer uninstalls the " + "handler during destruction"); +} +ATF_TEST_CASE_BODY(signal_programmer_preserve) +{ + using atf::atf_run::signal_programmer; + + sigusr1::program(); + sigusr1::happened = false; + + { + signal_programmer sp(SIGUSR1, sigusr1_2::handler); + + sigusr1_2::happened = false; + ::kill(::getpid(), SIGUSR1); + ATF_REQUIRE(sigusr1_2::happened); + } + + ATF_REQUIRE(!sigusr1::happened); + ::kill(::getpid(), SIGUSR1); + ATF_REQUIRE(sigusr1::happened); +} + +// ------------------------------------------------------------------------ +// Tests cases for the free functions. +// ------------------------------------------------------------------------ + +static +void +reset_child(void *v ATF_DEFS_ATTRIBUTE_UNUSED) +{ + sigusr1::program(); + + sigusr1::happened = false; + atf::atf_run::reset(SIGUSR1); + kill(::getpid(), SIGUSR1); + + if (sigusr1::happened) { + std::cerr << "Signal was not resetted correctly\n"; + std::abort(); + } else { + std::exit(EXIT_SUCCESS); + } +} + +ATF_TEST_CASE(reset); +ATF_TEST_CASE_HEAD(reset) +{ + set_md_var("descr", "Tests the reset function"); +} +ATF_TEST_CASE_BODY(reset) +{ + atf::process::child c = + atf::process::fork(reset_child, atf::process::stream_inherit(), + atf::process::stream_inherit(), NULL); + + const atf::process::status s = c.wait(); + ATF_REQUIRE(s.exited() || s.signaled()); + ATF_REQUIRE(!s.signaled() || s.termsig() == SIGUSR1); +} + +// ------------------------------------------------------------------------ +// Main. +// ------------------------------------------------------------------------ + +ATF_INIT_TEST_CASES(tcs) +{ + // Add the tests for the "signal_holder" class. + ATF_ADD_TEST_CASE(tcs, signal_holder_preserve); + ATF_ADD_TEST_CASE(tcs, signal_holder_destructor); + ATF_ADD_TEST_CASE(tcs, signal_holder_process); + + // Add the tests for the "signal_programmer" class. + ATF_ADD_TEST_CASE(tcs, signal_programmer_program); + ATF_ADD_TEST_CASE(tcs, signal_programmer_preserve); + + // Add the test cases for the free functions. + ATF_ADD_TEST_CASE(tcs, reset); +} diff --git a/atf-run/test-program.cpp b/atf-run/test-program.cpp new file mode 100644 index 0000000000000..14647c2f78fdc --- /dev/null +++ b/atf-run/test-program.cpp @@ -0,0 +1,790 @@ +// +// Automated Testing Framework (atf) +// +// 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. +// + +extern "C" { +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/time.h> + +#include <fcntl.h> +#include <signal.h> +#include <unistd.h> +} + +#include <cerrno> +#include <cstdlib> +#include <cstring> +#include <fstream> +#include <iostream> + +#include "atf-c/defs.h" + +#include "atf-c++/detail/env.hpp" +#include "atf-c++/detail/parser.hpp" +#include "atf-c++/detail/process.hpp" +#include "atf-c++/detail/sanity.hpp" +#include "atf-c++/detail/text.hpp" + +#include "config.hpp" +#include "fs.hpp" +#include "io.hpp" +#include "requirements.hpp" +#include "signals.hpp" +#include "test-program.hpp" +#include "timer.hpp" +#include "user.hpp" + +namespace impl = atf::atf_run; +namespace detail = atf::atf_run::detail; + +namespace { + +static void +check_stream(std::ostream& os) +{ + // If we receive a signal while writing to the stream, the bad bit gets set. + // Things seem to behave fine afterwards if we clear such error condition. + // However, I'm not sure if it's safe to query errno at this point. + if (os.bad()) { + if (errno == EINTR) + os.clear(); + else + throw std::runtime_error("Failed"); + } +} + +namespace atf_tp { + +static const atf::parser::token_type eof_type = 0; +static const atf::parser::token_type nl_type = 1; +static const atf::parser::token_type text_type = 2; +static const atf::parser::token_type colon_type = 3; +static const atf::parser::token_type dblquote_type = 4; + +class tokenizer : public atf::parser::tokenizer< std::istream > { +public: + tokenizer(std::istream& is, size_t curline) : + atf::parser::tokenizer< std::istream > + (is, true, eof_type, nl_type, text_type, curline) + { + add_delim(':', colon_type); + add_quote('"', dblquote_type); + } +}; + +} // namespace atf_tp + +class metadata_reader : public detail::atf_tp_reader { + impl::test_cases_map m_tcs; + + void got_tc(const std::string& ident, const atf::tests::vars_map& props) + { + if (m_tcs.find(ident) != m_tcs.end()) + throw(std::runtime_error("Duplicate test case " + ident + + " in test program")); + m_tcs[ident] = props; + + if (m_tcs[ident].find("has.cleanup") == m_tcs[ident].end()) + m_tcs[ident].insert(std::make_pair("has.cleanup", "false")); + + if (m_tcs[ident].find("timeout") == m_tcs[ident].end()) + m_tcs[ident].insert(std::make_pair("timeout", "300")); + } + +public: + metadata_reader(std::istream& is) : + detail::atf_tp_reader(is) + { + } + + const impl::test_cases_map& + get_tcs(void) + const + { + return m_tcs; + } +}; + +struct get_metadata_params { + const atf::fs::path& executable; + const atf::tests::vars_map& config; + + get_metadata_params(const atf::fs::path& p_executable, + const atf::tests::vars_map& p_config) : + executable(p_executable), + config(p_config) + { + } +}; + +struct test_case_params { + const atf::fs::path& executable; + const std::string& test_case_name; + const std::string& test_case_part; + const atf::tests::vars_map& metadata; + const atf::tests::vars_map& config; + const atf::fs::path& resfile; + const atf::fs::path& workdir; + + test_case_params(const atf::fs::path& p_executable, + const std::string& p_test_case_name, + const std::string& p_test_case_part, + const atf::tests::vars_map& p_metadata, + const atf::tests::vars_map& p_config, + const atf::fs::path& p_resfile, + const atf::fs::path& p_workdir) : + executable(p_executable), + test_case_name(p_test_case_name), + test_case_part(p_test_case_part), + metadata(p_metadata), + config(p_config), + resfile(p_resfile), + workdir(p_workdir) + { + } +}; + +static +std::string +generate_timestamp(void) +{ + struct timeval tv; + if (gettimeofday(&tv, NULL) == -1) + return "0.0"; + + char buf[32]; + const int len = snprintf(buf, sizeof(buf), "%ld.%ld", + static_cast< long >(tv.tv_sec), + static_cast< long >(tv.tv_usec)); + if (len >= static_cast< int >(sizeof(buf)) || len < 0) + return "0.0"; + else + return buf; +} + +static +void +append_to_vector(std::vector< std::string >& v1, + const std::vector< std::string >& v2) +{ + std::copy(v2.begin(), v2.end(), + std::back_insert_iterator< std::vector< std::string > >(v1)); +} + +static +char** +vector_to_argv(const std::vector< std::string >& v) +{ + char** argv = new char*[v.size() + 1]; + for (std::vector< std::string >::size_type i = 0; i < v.size(); i++) { + argv[i] = strdup(v[i].c_str()); + } + argv[v.size()] = NULL; + return argv; +} + +static +void +exec_or_exit(const atf::fs::path& executable, + const std::vector< std::string >& argv) +{ + // This leaks memory in case of a failure, but it is OK. Exiting will + // do the necessary cleanup. + char* const* native_argv = vector_to_argv(argv); + + ::execv(executable.c_str(), native_argv); + + const std::string message = "Failed to execute '" + executable.str() + + "': " + std::strerror(errno) + "\n"; + if (::write(STDERR_FILENO, message.c_str(), message.length()) == -1) + std::abort(); + std::exit(EXIT_FAILURE); +} + +static +std::vector< std::string > +config_to_args(const atf::tests::vars_map& config) +{ + std::vector< std::string > args; + + for (atf::tests::vars_map::const_iterator iter = config.begin(); + iter != config.end(); iter++) + args.push_back("-v" + (*iter).first + "=" + (*iter).second); + + return args; +} + +static +void +silence_stdin(void) +{ + ::close(STDIN_FILENO); + int fd = ::open("/dev/null", O_RDONLY); + if (fd == -1) + throw std::runtime_error("Could not open /dev/null"); + INV(fd == STDIN_FILENO); +} + +static +void +prepare_child(const atf::fs::path& workdir) +{ + const int ret = ::setpgid(::getpid(), 0); + INV(ret != -1); + + ::umask(S_IWGRP | S_IWOTH); + + for (int i = 1; i <= impl::last_signo; i++) + impl::reset(i); + + atf::env::set("HOME", workdir.str()); + atf::env::unset("LANG"); + atf::env::unset("LC_ALL"); + atf::env::unset("LC_COLLATE"); + atf::env::unset("LC_CTYPE"); + atf::env::unset("LC_MESSAGES"); + atf::env::unset("LC_MONETARY"); + atf::env::unset("LC_NUMERIC"); + atf::env::unset("LC_TIME"); + atf::env::set("TZ", "UTC"); + + atf::env::set("__RUNNING_INSIDE_ATF_RUN", "internal-yes-value"); + + impl::change_directory(workdir); + + silence_stdin(); +} + +static +void +get_metadata_child(void* raw_params) +{ + const get_metadata_params* params = + static_cast< const get_metadata_params* >(raw_params); + + std::vector< std::string > argv; + argv.push_back(params->executable.leaf_name()); + argv.push_back("-l"); + argv.push_back("-s" + params->executable.branch_path().str()); + append_to_vector(argv, config_to_args(params->config)); + + exec_or_exit(params->executable, argv); +} + +void +run_test_case_child(void* raw_params) +{ + const test_case_params* params = + static_cast< const test_case_params* >(raw_params); + + const std::pair< int, int > user = impl::get_required_user( + params->metadata, params->config); + if (user.first != -1 && user.second != -1) + impl::drop_privileges(user); + + // The input 'tp' parameter may be relative and become invalid once + // we change the current working directory. + const atf::fs::path absolute_executable = params->executable.to_absolute(); + + // Prepare the test program's arguments. We use dynamic memory and + // do not care to release it. We are going to die anyway very soon, + // either due to exec(2) or to exit(3). + std::vector< std::string > argv; + argv.push_back(absolute_executable.leaf_name()); + argv.push_back("-r" + params->resfile.str()); + argv.push_back("-s" + absolute_executable.branch_path().str()); + append_to_vector(argv, config_to_args(params->config)); + argv.push_back(params->test_case_name + ":" + params->test_case_part); + + prepare_child(params->workdir); + exec_or_exit(absolute_executable, argv); +} + +static void +tokenize_result(const std::string& line, std::string& out_state, + std::string& out_arg, std::string& out_reason) +{ + const std::string::size_type pos = line.find_first_of(":("); + if (pos == std::string::npos) { + out_state = line; + out_arg = ""; + out_reason = ""; + } else if (line[pos] == ':') { + out_state = line.substr(0, pos); + out_arg = ""; + out_reason = atf::text::trim(line.substr(pos + 1)); + } else if (line[pos] == '(') { + const std::string::size_type pos2 = line.find("):", pos); + if (pos2 == std::string::npos) + throw std::runtime_error("Invalid test case result '" + line + + "': unclosed optional argument"); + out_state = line.substr(0, pos); + out_arg = line.substr(pos + 1, pos2 - pos - 1); + out_reason = atf::text::trim(line.substr(pos2 + 2)); + } else + UNREACHABLE; +} + +static impl::test_case_result +handle_result(const std::string& state, const std::string& arg, + const std::string& reason) +{ + PRE(state == "passed"); + + if (!arg.empty() || !reason.empty()) + throw std::runtime_error("The test case result '" + state + "' cannot " + "be accompanied by a reason nor an expected value"); + + return impl::test_case_result(state, -1, reason); +} + +static impl::test_case_result +handle_result_with_reason(const std::string& state, const std::string& arg, + const std::string& reason) +{ + PRE(state == "expected_death" || state == "expected_failure" || + state == "expected_timeout" || state == "failed" || state == "skipped"); + + if (!arg.empty() || reason.empty()) + throw std::runtime_error("The test case result '" + state + "' must " + "be accompanied by a reason but not by an expected value"); + + return impl::test_case_result(state, -1, reason); +} + +static impl::test_case_result +handle_result_with_reason_and_arg(const std::string& state, + const std::string& arg, + const std::string& reason) +{ + PRE(state == "expected_exit" || state == "expected_signal"); + + if (reason.empty()) + throw std::runtime_error("The test case result '" + state + "' must " + "be accompanied by a reason"); + + int value; + if (arg.empty()) { + value = -1; + } else { + try { + value = atf::text::to_type< int >(arg); + } catch (const std::runtime_error&) { + throw std::runtime_error("The value '" + arg + "' passed to the '" + + state + "' state must be an integer"); + } + } + + return impl::test_case_result(state, value, reason); +} + +} // anonymous namespace + +detail::atf_tp_reader::atf_tp_reader(std::istream& is) : + m_is(is) +{ +} + +detail::atf_tp_reader::~atf_tp_reader(void) +{ +} + +void +detail::atf_tp_reader::got_tc( + const std::string& ident ATF_DEFS_ATTRIBUTE_UNUSED, + const std::map< std::string, std::string >& md ATF_DEFS_ATTRIBUTE_UNUSED) +{ +} + +void +detail::atf_tp_reader::got_eof(void) +{ +} + +void +detail::atf_tp_reader::validate_and_insert(const std::string& name, + const std::string& value, const size_t lineno, + std::map< std::string, std::string >& md) +{ + using atf::parser::parse_error; + + if (value.empty()) + throw parse_error(lineno, "The value for '" + name +"' cannot be " + "empty"); + + const std::string ident_regex = "^[_A-Za-z0-9]+$"; + const std::string integer_regex = "^[0-9]+$"; + + if (name == "descr") { + // Any non-empty value is valid. + } else if (name == "has.cleanup") { + try { + (void)atf::text::to_bool(value); + } catch (const std::runtime_error&) { + throw parse_error(lineno, "The has.cleanup property requires a" + " boolean value"); + } + } else if (name == "ident") { + if (!atf::text::match(value, ident_regex)) + throw parse_error(lineno, "The identifier must match " + + ident_regex + "; was '" + value + "'"); + } else if (name == "require.arch") { + } else if (name == "require.config") { + } else if (name == "require.files") { + } else if (name == "require.machine") { + } else if (name == "require.memory") { + try { + (void)atf::text::to_bytes(value); + } catch (const std::runtime_error&) { + throw parse_error(lineno, "The require.memory property requires an " + "integer value representing an amount of bytes"); + } + } else if (name == "require.progs") { + } else if (name == "require.user") { + } else if (name == "timeout") { + if (!atf::text::match(value, integer_regex)) + throw parse_error(lineno, "The timeout property requires an integer" + " value"); + } else if (name == "use.fs") { + // Deprecated; ignore it. + } else if (name.size() > 2 && name[0] == 'X' && name[1] == '-') { + // Any non-empty value is valid. + } else { + throw parse_error(lineno, "Unknown property '" + name + "'"); + } + + md.insert(std::make_pair(name, value)); +} + +void +detail::atf_tp_reader::read(void) +{ + using atf::parser::parse_error; + using namespace atf_tp; + + std::pair< size_t, atf::parser::headers_map > hml = + atf::parser::read_headers(m_is, 1); + atf::parser::validate_content_type(hml.second, + "application/X-atf-tp", 1); + + tokenizer tkz(m_is, hml.first); + atf::parser::parser< tokenizer > p(tkz); + + try { + atf::parser::token t = p.expect(text_type, "property name"); + if (t.text() != "ident") + throw parse_error(t.lineno(), "First property of a test case " + "must be 'ident'"); + + std::map< std::string, std::string > props; + do { + const std::string name = t.text(); + t = p.expect(colon_type, "`:'"); + const std::string value = atf::text::trim(p.rest_of_line()); + t = p.expect(nl_type, "new line"); + validate_and_insert(name, value, t.lineno(), props); + + t = p.expect(eof_type, nl_type, text_type, "property name, new " + "line or eof"); + if (t.type() == nl_type || t.type() == eof_type) { + const std::map< std::string, std::string >::const_iterator + iter = props.find("ident"); + if (iter == props.end()) + throw parse_error(t.lineno(), "Test case definition did " + "not define an 'ident' property"); + ATF_PARSER_CALLBACK(p, got_tc((*iter).second, props)); + props.clear(); + + if (t.type() == nl_type) { + t = p.expect(text_type, "property name"); + if (t.text() != "ident") + throw parse_error(t.lineno(), "First property of a " + "test case must be 'ident'"); + } + } + } while (t.type() != eof_type); + ATF_PARSER_CALLBACK(p, got_eof()); + } catch (const parse_error& pe) { + p.add_error(pe); + p.reset(nl_type); + } +} + +impl::test_case_result +detail::parse_test_case_result(const std::string& line) +{ + std::string state, arg, reason; + tokenize_result(line, state, arg, reason); + + if (state == "expected_death") + return handle_result_with_reason(state, arg, reason); + else if (state.compare(0, 13, "expected_exit") == 0) + return handle_result_with_reason_and_arg(state, arg, reason); + else if (state.compare(0, 16, "expected_failure") == 0) + return handle_result_with_reason(state, arg, reason); + else if (state.compare(0, 15, "expected_signal") == 0) + return handle_result_with_reason_and_arg(state, arg, reason); + else if (state.compare(0, 16, "expected_timeout") == 0) + return handle_result_with_reason(state, arg, reason); + else if (state == "failed") + return handle_result_with_reason(state, arg, reason); + else if (state == "passed") + return handle_result(state, arg, reason); + else if (state == "skipped") + return handle_result_with_reason(state, arg, reason); + else + throw std::runtime_error("Unknown test case result type in: " + line); +} + +impl::atf_tps_writer::atf_tps_writer(std::ostream& os) : + m_os(os) +{ + atf::parser::headers_map hm; + atf::parser::attrs_map ct_attrs; + ct_attrs["version"] = "3"; + hm["Content-Type"] = + atf::parser::header_entry("Content-Type", "application/X-atf-tps", + ct_attrs); + atf::parser::write_headers(hm, m_os); +} + +void +impl::atf_tps_writer::info(const std::string& what, const std::string& val) +{ + m_os << "info: " << what << ", " << val << "\n"; + m_os.flush(); +} + +void +impl::atf_tps_writer::ntps(size_t p_ntps) +{ + m_os << "tps-count: " << p_ntps << "\n"; + m_os.flush(); +} + +void +impl::atf_tps_writer::start_tp(const std::string& tp, size_t ntcs) +{ + m_tpname = tp; + m_os << "tp-start: " << generate_timestamp() << ", " << tp << ", " + << ntcs << "\n"; + m_os.flush(); +} + +void +impl::atf_tps_writer::end_tp(const std::string& reason) +{ + PRE(reason.find('\n') == std::string::npos); + if (reason.empty()) + m_os << "tp-end: " << generate_timestamp() << ", " << m_tpname << "\n"; + else + m_os << "tp-end: " << generate_timestamp() << ", " << m_tpname + << ", " << reason << "\n"; + m_os.flush(); +} + +void +impl::atf_tps_writer::start_tc(const std::string& tcname) +{ + m_tcname = tcname; + m_os << "tc-start: " << generate_timestamp() << ", " << tcname << "\n"; + m_os.flush(); +} + +void +impl::atf_tps_writer::stdout_tc(const std::string& line) +{ + m_os << "tc-so:" << line << "\n"; + check_stream(m_os); + m_os.flush(); + check_stream(m_os); +} + +void +impl::atf_tps_writer::stderr_tc(const std::string& line) +{ + m_os << "tc-se:" << line << "\n"; + check_stream(m_os); + m_os.flush(); + check_stream(m_os); +} + +void +impl::atf_tps_writer::end_tc(const std::string& state, + const std::string& reason) +{ + std::string str = ", " + m_tcname + ", " + state; + if (!reason.empty()) + str += ", " + reason; + m_os << "tc-end: " << generate_timestamp() << str << "\n"; + m_os.flush(); +} + +impl::metadata +impl::get_metadata(const atf::fs::path& executable, + const atf::tests::vars_map& config) +{ + get_metadata_params params(executable, config); + atf::process::child child = + atf::process::fork(get_metadata_child, + atf::process::stream_capture(), + atf::process::stream_inherit(), + static_cast< void * >(¶ms)); + + impl::pistream outin(child.stdout_fd()); + + metadata_reader parser(outin); + parser.read(); + + const atf::process::status status = child.wait(); + if (!status.exited() || status.exitstatus() != EXIT_SUCCESS) + throw atf::parser::format_error("Test program returned failure " + "exit status for test case list"); + + return metadata(parser.get_tcs()); +} + +impl::test_case_result +impl::read_test_case_result(const atf::fs::path& results_path) +{ + std::ifstream results_file(results_path.c_str()); + if (!results_file) + throw std::runtime_error("Failed to open " + results_path.str()); + + std::string line, extra_line; + std::getline(results_file, line); + if (!results_file.good()) + throw std::runtime_error("Results file is empty"); + + while (std::getline(results_file, extra_line).good()) + line += "<<NEWLINE UNEXPECTED>>" + extra_line; + + results_file.close(); + + return detail::parse_test_case_result(line); +} + +namespace { + +static volatile bool terminate_poll; + +static void +sigchld_handler(const int signo ATF_DEFS_ATTRIBUTE_UNUSED) +{ + terminate_poll = true; +} + +class child_muxer : public impl::muxer { + impl::atf_tps_writer& m_writer; + + void + line_callback(const size_t index, const std::string& line) + { + switch (index) { + case 0: m_writer.stdout_tc(line); break; + case 1: m_writer.stderr_tc(line); break; + default: UNREACHABLE; + } + } + +public: + child_muxer(const int* fds, const size_t nfds, + impl::atf_tps_writer& writer) : + muxer(fds, nfds), + m_writer(writer) + { + } +}; + +} // anonymous namespace + +std::pair< std::string, atf::process::status > +impl::run_test_case(const atf::fs::path& executable, + const std::string& test_case_name, + const std::string& test_case_part, + const atf::tests::vars_map& metadata, + const atf::tests::vars_map& config, + const atf::fs::path& resfile, + const atf::fs::path& workdir, + atf_tps_writer& writer) +{ + // TODO: Capture termination signals and deliver them to the subprocess + // instead. Or maybe do something else; think about it. + + test_case_params params(executable, test_case_name, test_case_part, + metadata, config, resfile, workdir); + atf::process::child child = + atf::process::fork(run_test_case_child, + atf::process::stream_capture(), + atf::process::stream_capture(), + static_cast< void * >(¶ms)); + + terminate_poll = false; + + const atf::tests::vars_map::const_iterator iter = metadata.find("timeout"); + INV(iter != metadata.end()); + const unsigned int timeout = + atf::text::to_type< unsigned int >((*iter).second); + const pid_t child_pid = child.pid(); + + // Get the input stream of stdout and stderr. + impl::file_handle outfh = child.stdout_fd(); + impl::file_handle errfh = child.stderr_fd(); + + bool timed_out = false; + + // Process the test case's output and multiplex it into our output + // stream as we read it. + int fds[2] = {outfh.get(), errfh.get()}; + child_muxer mux(fds, 2, writer); + try { + child_timer timeout_timer(timeout, child_pid, terminate_poll); + signal_programmer sigchld(SIGCHLD, sigchld_handler); + mux.mux(terminate_poll); + timed_out = timeout_timer.fired(); + } catch (...) { + UNREACHABLE; + } + + ::killpg(child_pid, SIGKILL); + mux.flush(); + atf::process::status status = child.wait(); + + std::string reason; + + if (timed_out) { + // Don't assume the child process has been signaled due to the timeout + // expiration as older versions did. The child process may have exited + // but we may have timed out due to a subchild process getting stuck. + reason = "Test case timed out after " + atf::text::to_string(timeout) + + " " + (timeout == 1 ? "second" : "seconds"); + } + + return std::make_pair(reason, status); +} diff --git a/atf-run/test-program.hpp b/atf-run/test-program.hpp new file mode 100644 index 0000000000000..daba3ce01f0de --- /dev/null +++ b/atf-run/test-program.hpp @@ -0,0 +1,150 @@ +// +// Automated Testing Framework (atf) +// +// 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. +// + +#include <map> + +#include "atf-c++/tests.hpp" + +#include "atf-c++/detail/fs.hpp" +#include "atf-c++/detail/process.hpp" + +namespace atf { +namespace atf_run { + +struct test_case_result { + std::string m_state; + int m_value; + std::string m_reason; + +public: + test_case_result(void) : + m_state("UNINITIALIZED"), + m_value(-1), + m_reason("") + { + } + + test_case_result(const std::string& p_state, const int p_value, + const std::string& p_reason) : + m_state(p_state), + m_value(p_value), + m_reason(p_reason) + { + } + + const std::string& + state(void) const + { + return m_state; + } + + int + value(void) const + { + return m_value; + } + + const std::string& + reason(void) const + { + return m_reason; + } +}; + +namespace detail { + +class atf_tp_reader { + std::istream& m_is; + + void validate_and_insert(const std::string&, const std::string&, + const size_t, + std::map< std::string, std::string >&); + +protected: + virtual void got_tc(const std::string&, + const std::map< std::string, std::string >&); + virtual void got_eof(void); + +public: + atf_tp_reader(std::istream&); + virtual ~atf_tp_reader(void); + + void read(void); +}; + +test_case_result parse_test_case_result(const std::string&); + +} // namespace detail + +class atf_tps_writer { + std::ostream& m_os; + + std::string m_tpname, m_tcname; + +public: + atf_tps_writer(std::ostream&); + + void info(const std::string&, const std::string&); + void ntps(size_t); + + void start_tp(const std::string&, size_t); + void end_tp(const std::string&); + + void start_tc(const std::string&); + void stdout_tc(const std::string&); + void stderr_tc(const std::string&); + void end_tc(const std::string&, const std::string&); +}; + +typedef std::map< std::string, atf::tests::vars_map > test_cases_map; + +struct metadata { + test_cases_map test_cases; + + metadata(void) + { + } + + metadata(const test_cases_map& p_test_cases) : + test_cases(p_test_cases) + { + } +}; + +class atf_tps_writer; + +metadata get_metadata(const atf::fs::path&, const atf::tests::vars_map&); +test_case_result read_test_case_result(const atf::fs::path&); +std::pair< std::string, atf::process::status > run_test_case( + const atf::fs::path&, const std::string&, const std::string&, + const atf::tests::vars_map&, const atf::tests::vars_map&, + const atf::fs::path&, const atf::fs::path&, atf_tps_writer&); + +} // namespace atf_run +} // namespace atf diff --git a/atf-run/test_program_test.cpp b/atf-run/test_program_test.cpp new file mode 100644 index 0000000000000..444dc3e4d8f45 --- /dev/null +++ b/atf-run/test_program_test.cpp @@ -0,0 +1,1020 @@ +// +// Automated Testing Framework (atf) +// +// 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. +// + +#include <fstream> +#include <iostream> + +#include "atf-c++/macros.hpp" + +#include "atf-c++/detail/parser.hpp" +#include "atf-c++/detail/test_helpers.hpp" +#include "atf-c++/detail/text.hpp" + +#include "test-program.hpp" + +namespace impl = atf::atf_run; +namespace detail = atf::atf_run::detail; + +using atf::tests::vars_map; + +// ------------------------------------------------------------------------- +// Auxiliary functions. +// ------------------------------------------------------------------------- + +static +atf::fs::path +get_helper(const atf::tests::tc& tc, const char* name) +{ + return atf::fs::path(tc.get_config_var("srcdir")) / name; +} + +static +void +check_property(const vars_map& props, const char* name, const char* value) +{ + const vars_map::const_iterator iter = props.find(name); + ATF_REQUIRE(iter != props.end()); + ATF_REQUIRE_EQ(value, (*iter).second); +} + +static void +check_result(const char* exp_state, const int exp_value, const char* exp_reason, + const impl::test_case_result& tcr) +{ + ATF_REQUIRE_EQ(exp_state, tcr.state()); + ATF_REQUIRE_EQ(exp_value, tcr.value()); + ATF_REQUIRE_EQ(exp_reason, tcr.reason()); +} + +static +void +write_test_case_result(const char *results_path, const std::string& contents) +{ + std::ofstream results_file(results_path); + ATF_REQUIRE(results_file); + + results_file << contents; +} + +static +void +print_indented(const std::string& str) +{ + std::vector< std::string > ws = atf::text::split(str, "\n"); + for (std::vector< std::string >::const_iterator iter = ws.begin(); + iter != ws.end(); iter++) + std::cout << ">>" << *iter << "<<\n"; +} + +// XXX Should this string handling and verbosity level be part of the +// ATF_REQUIRE_EQ macro? It may be hard to predict sometimes that a +// string can have newlines in it, and so the error message generated +// at the moment will be bogus if there are some. +static +void +check_match(const atf::tests::tc& tc, const std::string& str, + const std::string& exp) +{ + if (!atf::text::match(str, exp)) { + std::cout << "String match check failed.\n" + << "Adding >> and << to delimit the string boundaries " + "below.\n"; + std::cout << "GOT:\n"; + print_indented(str); + std::cout << "EXPECTED:\n"; + print_indented(exp); + tc.fail("Constructed string differs from the expected one"); + } +} + +// ------------------------------------------------------------------------- +// Tests for the "tp" reader. +// ------------------------------------------------------------------------- + +class tp_reader : protected detail::atf_tp_reader { + void + got_tc(const std::string& ident, + const std::map< std::string, std::string >& md) + { + std::string call = "got_tc(" + ident + ", {"; + for (std::map< std::string, std::string >::const_iterator iter = + md.begin(); iter != md.end(); iter++) { + if (iter != md.begin()) + call += ", "; + call += (*iter).first + '=' + (*iter).second; + } + call += "})"; + m_calls.push_back(call); + } + + void + got_eof(void) + { + m_calls.push_back("got_eof()"); + } + +public: + tp_reader(std::istream& is) : + detail::atf_tp_reader(is) + { + } + + void + read(void) + { + atf_tp_reader::read(); + } + + std::vector< std::string > m_calls; +}; + +ATF_TEST_CASE_WITHOUT_HEAD(tp_1); +ATF_TEST_CASE_BODY(tp_1) +{ + const char* input = + "Content-Type: application/X-atf-tp; version=\"1\"\n" + "\n" + "ident: test_case_1\n" + "\n" + "ident: test_case_2\n" + "\n" + "ident: test_case_3\n" + ; + + const char* exp_calls[] = { + "got_tc(test_case_1, {ident=test_case_1})", + "got_tc(test_case_2, {ident=test_case_2})", + "got_tc(test_case_3, {ident=test_case_3})", + "got_eof()", + NULL + }; + + const char* exp_errors[] = { + NULL + }; + + do_parser_test< tp_reader >(input, exp_calls, exp_errors); +} + +ATF_TEST_CASE_WITHOUT_HEAD(tp_2); +ATF_TEST_CASE_BODY(tp_2) +{ + const char* input = + "Content-Type: application/X-atf-tp; version=\"1\"\n" + "\n" + "ident: test_case_1\n" + "descr: This is the description\n" + "timeout: 300\n" + "\n" + "ident: test_case_2\n" + "\n" + "ident: test_case_3\n" + "X-prop1: A custom property\n" + "descr: Third test case\n" + ; + + // NO_CHECK_STYLE_BEGIN + const char* exp_calls[] = { + "got_tc(test_case_1, {descr=This is the description, ident=test_case_1, timeout=300})", + "got_tc(test_case_2, {ident=test_case_2})", + "got_tc(test_case_3, {X-prop1=A custom property, descr=Third test case, ident=test_case_3})", + "got_eof()", + NULL + }; + // NO_CHECK_STYLE_END + + const char* exp_errors[] = { + NULL + }; + + do_parser_test< tp_reader >(input, exp_calls, exp_errors); +} + +ATF_TEST_CASE_WITHOUT_HEAD(tp_3); +ATF_TEST_CASE_BODY(tp_3) +{ + const char* input = + "Content-Type: application/X-atf-tp; version=\"1\"\n" + "\n" + "ident: single_test\n" + "descr: Some description\n" + "timeout: 300\n" + "require.arch: thearch\n" + "require.config: foo-bar\n" + "require.files: /a/1 /b/2\n" + "require.machine: themachine\n" + "require.progs: /bin/cp mv\n" + "require.user: root\n" + ; + + // NO_CHECK_STYLE_BEGIN + const char* exp_calls[] = { + "got_tc(single_test, {descr=Some description, ident=single_test, require.arch=thearch, require.config=foo-bar, require.files=/a/1 /b/2, require.machine=themachine, require.progs=/bin/cp mv, require.user=root, timeout=300})", + "got_eof()", + NULL + }; + // NO_CHECK_STYLE_END + + const char* exp_errors[] = { + NULL + }; + + do_parser_test< tp_reader >(input, exp_calls, exp_errors); +} + +ATF_TEST_CASE_WITHOUT_HEAD(tp_4); +ATF_TEST_CASE_BODY(tp_4) +{ + const char* input = + "Content-Type: application/X-atf-tp; version=\"1\"\n" + "\n" + "ident: single_test \n" + "descr: Some description \n" + ; + + const char* exp_calls[] = { + "got_tc(single_test, {descr=Some description, ident=single_test})", + "got_eof()", + NULL + }; + + const char* exp_errors[] = { + NULL + }; + + do_parser_test< tp_reader >(input, exp_calls, exp_errors); +} + +ATF_TEST_CASE_WITHOUT_HEAD(tp_50); +ATF_TEST_CASE_BODY(tp_50) +{ + const char* input = + "Content-Type: application/X-atf-tp; version=\"1\"\n" + "\n" + ; + + const char* exp_calls[] = { + NULL + }; + + const char* exp_errors[] = { + "3: Unexpected token `<<EOF>>'; expected property name", + NULL + }; + + do_parser_test< tp_reader >(input, exp_calls, exp_errors); +} + +ATF_TEST_CASE_WITHOUT_HEAD(tp_51); +ATF_TEST_CASE_BODY(tp_51) +{ + const char* input = + "Content-Type: application/X-atf-tp; version=\"1\"\n" + "\n" + "\n" + "\n" + "\n" + ; + + const char* exp_calls[] = { + NULL + }; + + const char* exp_errors[] = { + "3: Unexpected token `<<NEWLINE>>'; expected property name", + NULL + }; + + do_parser_test< tp_reader >(input, exp_calls, exp_errors); +} + +ATF_TEST_CASE_WITHOUT_HEAD(tp_52); +ATF_TEST_CASE_BODY(tp_52) +{ + const char* input = + "Content-Type: application/X-atf-tp; version=\"1\"\n" + "\n" + "ident: test1\n" + "ident: test2\n" + ; + + const char* exp_calls[] = { + "got_tc(test1, {ident=test1})", + "got_eof()", + NULL + }; + + const char* exp_errors[] = { + NULL + }; + + do_parser_test< tp_reader >(input, exp_calls, exp_errors); +} + +ATF_TEST_CASE_WITHOUT_HEAD(tp_53); +ATF_TEST_CASE_BODY(tp_53) +{ + const char* input = + "Content-Type: application/X-atf-tp; version=\"1\"\n" + "\n" + "descr: Out of order\n" + "ident: test1\n" + ; + + const char* exp_calls[] = { + NULL + }; + + const char* exp_errors[] = { + "3: First property of a test case must be 'ident'", + NULL + }; + + do_parser_test< tp_reader >(input, exp_calls, exp_errors); +} + +ATF_TEST_CASE_WITHOUT_HEAD(tp_54); +ATF_TEST_CASE_BODY(tp_54) +{ + const char* input = + "Content-Type: application/X-atf-tp; version=\"1\"\n" + "\n" + "ident:\n" + ; + + const char* exp_calls[] = { + NULL + }; + + const char* exp_errors[] = { + "3: The value for 'ident' cannot be empty", + NULL + }; + + do_parser_test< tp_reader >(input, exp_calls, exp_errors); +} + +ATF_TEST_CASE_WITHOUT_HEAD(tp_55); +ATF_TEST_CASE_BODY(tp_55) +{ + const char* input = + "Content-Type: application/X-atf-tp; version=\"1\"\n" + "\n" + "ident: +*,\n" + ; + + const char* exp_calls[] = { + NULL + }; + + const char* exp_errors[] = { + "3: The identifier must match ^[_A-Za-z0-9]+$; was '+*,'", + NULL + }; + + do_parser_test< tp_reader >(input, exp_calls, exp_errors); +} + +ATF_TEST_CASE_WITHOUT_HEAD(tp_56); +ATF_TEST_CASE_BODY(tp_56) +{ + const char* input = + "Content-Type: application/X-atf-tp; version=\"1\"\n" + "\n" + "ident: test\n" + "timeout: hello\n" + ; + + const char* exp_calls[] = { + NULL + }; + + const char* exp_errors[] = { + "4: The timeout property requires an integer value", + NULL + }; + + do_parser_test< tp_reader >(input, exp_calls, exp_errors); +} + +ATF_TEST_CASE_WITHOUT_HEAD(tp_57); +ATF_TEST_CASE_BODY(tp_57) +{ + const char* input = + "Content-Type: application/X-atf-tp; version=\"1\"\n" + "\n" + "ident: test\n" + "unknown: property\n" + ; + + const char* exp_calls[] = { + NULL + }; + + const char* exp_errors[] = { + "4: Unknown property 'unknown'", + NULL + }; + + do_parser_test< tp_reader >(input, exp_calls, exp_errors); +} + +ATF_TEST_CASE_WITHOUT_HEAD(tp_58); +ATF_TEST_CASE_BODY(tp_58) +{ + const char* input = + "Content-Type: application/X-atf-tp; version=\"1\"\n" + "\n" + "ident: test\n" + "X-foo:\n" + ; + + const char* exp_calls[] = { + NULL + }; + + const char* exp_errors[] = { + "4: The value for 'X-foo' cannot be empty", + NULL + }; + + do_parser_test< tp_reader >(input, exp_calls, exp_errors); +} + +ATF_TEST_CASE_WITHOUT_HEAD(tp_59); +ATF_TEST_CASE_BODY(tp_59) +{ + const char* input = + "Content-Type: application/X-atf-tp; version=\"1\"\n" + "\n" + "\n" + "ident: test\n" + "timeout: 300\n" + ; + + const char* exp_calls[] = { + NULL + }; + + const char* exp_errors[] = { + "3: Unexpected token `<<NEWLINE>>'; expected property name", + NULL + }; + + do_parser_test< tp_reader >(input, exp_calls, exp_errors); +} + +ATF_TEST_CASE_WITHOUT_HEAD(tp_60); +ATF_TEST_CASE_BODY(tp_60) +{ + const char* input = + "Content-Type: application/X-atf-tp; version=\"1\"\n" + "\n" + "ident: test\n" + "require.memory: 12345D\n" + ; + + const char* exp_calls[] = { + NULL + }; + + const char* exp_errors[] = { + "4: The require.memory property requires an integer value representing" + " an amount of bytes", + NULL + }; + + do_parser_test< tp_reader >(input, exp_calls, exp_errors); +} + +// ------------------------------------------------------------------------- +// Tests for the "tps" writer. +// ------------------------------------------------------------------------- + +ATF_TEST_CASE(atf_tps_writer); +ATF_TEST_CASE_HEAD(atf_tps_writer) +{ + set_md_var("descr", "Verifies the application/X-atf-tps writer"); +} +ATF_TEST_CASE_BODY(atf_tps_writer) +{ + std::ostringstream expss; + std::ostringstream ss; + const char *ts_regex = "[0-9]+\\.[0-9]{1,6}, "; + +#define RESET \ + expss.str(""); \ + ss.str("") + +#define CHECK \ + check_match(*this, ss.str(), expss.str()) + + { + RESET; + + impl::atf_tps_writer w(ss); + expss << "Content-Type: application/X-atf-tps; version=\"3\"\n\n"; + CHECK; + } + + { + RESET; + + impl::atf_tps_writer w(ss); + expss << "Content-Type: application/X-atf-tps; version=\"3\"\n\n"; + CHECK; + + w.info("foo", "bar"); + expss << "info: foo, bar\n"; + CHECK; + + w.info("baz", "second info"); + expss << "info: baz, second info\n"; + CHECK; + } + + { + RESET; + + impl::atf_tps_writer w(ss); + expss << "Content-Type: application/X-atf-tps; version=\"3\"\n\n"; + CHECK; + + w.ntps(0); + expss << "tps-count: 0\n"; + CHECK; + } + + { + RESET; + + impl::atf_tps_writer w(ss); + expss << "Content-Type: application/X-atf-tps; version=\"3\"\n\n"; + CHECK; + + w.ntps(123); + expss << "tps-count: 123\n"; + CHECK; + } + + { + RESET; + + impl::atf_tps_writer w(ss); + expss << "Content-Type: application/X-atf-tps; version=\"3\"\n\n"; + CHECK; + + w.ntps(2); + expss << "tps-count: 2\n"; + CHECK; + + w.start_tp("foo", 0); + expss << "tp-start: " << ts_regex << "foo, 0\n"; + CHECK; + + w.end_tp(""); + expss << "tp-end: " << ts_regex << "foo\n"; + CHECK; + + w.start_tp("bar", 0); + expss << "tp-start: " << ts_regex << "bar, 0\n"; + CHECK; + + w.end_tp("failed program"); + expss << "tp-end: " << ts_regex << "bar, failed program\n"; + CHECK; + } + + { + RESET; + + impl::atf_tps_writer w(ss); + expss << "Content-Type: application/X-atf-tps; version=\"3\"\n\n"; + CHECK; + + w.ntps(1); + expss << "tps-count: 1\n"; + CHECK; + + w.start_tp("foo", 1); + expss << "tp-start: " << ts_regex << "foo, 1\n"; + CHECK; + + w.start_tc("brokentc"); + expss << "tc-start: " << ts_regex << "brokentc\n"; + CHECK; + + w.end_tp("aborted"); + expss << "tp-end: " << ts_regex << "foo, aborted\n"; + CHECK; + } + + { + RESET; + + impl::atf_tps_writer w(ss); + expss << "Content-Type: application/X-atf-tps; version=\"3\"\n\n"; + CHECK; + + w.ntps(1); + expss << "tps-count: 1\n"; + CHECK; + + w.start_tp("thetp", 3); + expss << "tp-start: " << ts_regex << "thetp, 3\n"; + CHECK; + + w.start_tc("passtc"); + expss << "tc-start: " << ts_regex << "passtc\n"; + CHECK; + + w.end_tc("passed", ""); + expss << "tc-end: " << ts_regex << "passtc, passed\n"; + CHECK; + + w.start_tc("failtc"); + expss << "tc-start: " << ts_regex << "failtc\n"; + CHECK; + + w.end_tc("failed", "The reason"); + expss << "tc-end: " << ts_regex << "failtc, failed, The reason\n"; + CHECK; + + w.start_tc("skiptc"); + expss << "tc-start: " << ts_regex << "skiptc\n"; + CHECK; + + w.end_tc("skipped", "The reason"); + expss << "tc-end: " << ts_regex << "skiptc, skipped, The reason\n"; + CHECK; + + w.end_tp(""); + expss << "tp-end: " << ts_regex << "thetp\n"; + CHECK; + } + + { + RESET; + + impl::atf_tps_writer w(ss); + expss << "Content-Type: application/X-atf-tps; version=\"3\"\n\n"; + CHECK; + + w.ntps(1); + expss << "tps-count: 1\n"; + CHECK; + + w.start_tp("thetp", 1); + expss << "tp-start: " << ts_regex << "thetp, 1\n"; + CHECK; + + w.start_tc("thetc"); + expss << "tc-start: " << ts_regex << "thetc\n"; + CHECK; + + w.stdout_tc("a line"); + expss << "tc-so:a line\n"; + CHECK; + + w.stdout_tc("another line"); + expss << "tc-so:another line\n"; + CHECK; + + w.stderr_tc("an error message"); + expss << "tc-se:an error message\n"; + CHECK; + + w.end_tc("passed", ""); + expss << "tc-end: " << ts_regex << "thetc, passed\n"; + CHECK; + + w.end_tp(""); + expss << "tp-end: " << ts_regex << "thetp\n"; + CHECK; + } + + { + RESET; + + impl::atf_tps_writer w(ss); + expss << "Content-Type: application/X-atf-tps; version=\"3\"\n\n"; + CHECK; + + w.ntps(1); + expss << "tps-count: 1\n"; + CHECK; + + w.start_tp("thetp", 0); + expss << "tp-start: " << ts_regex << "thetp, 0\n"; + CHECK; + + w.end_tp(""); + expss << "tp-end: " << ts_regex << "thetp\n"; + CHECK; + + w.info("foo", "bar"); + expss << "info: foo, bar\n"; + CHECK; + + w.info("baz", "second value"); + expss << "info: baz, second value\n"; + CHECK; + } + +#undef CHECK +#undef RESET +} + +// ------------------------------------------------------------------------- +// Tests for the free functions. +// ------------------------------------------------------------------------- + +ATF_TEST_CASE(get_metadata_bad); +ATF_TEST_CASE_HEAD(get_metadata_bad) {} +ATF_TEST_CASE_BODY(get_metadata_bad) { + const atf::fs::path executable = get_helper(*this, "bad_metadata_helper"); + ATF_REQUIRE_THROW(atf::parser::parse_errors, + impl::get_metadata(executable, vars_map())); +} + +ATF_TEST_CASE(get_metadata_zero_tcs); +ATF_TEST_CASE_HEAD(get_metadata_zero_tcs) {} +ATF_TEST_CASE_BODY(get_metadata_zero_tcs) { + const atf::fs::path executable = get_helper(*this, "zero_tcs_helper"); + ATF_REQUIRE_THROW(atf::parser::parse_errors, + impl::get_metadata(executable, vars_map())); +} + +ATF_TEST_CASE(get_metadata_several_tcs); +ATF_TEST_CASE_HEAD(get_metadata_several_tcs) {} +ATF_TEST_CASE_BODY(get_metadata_several_tcs) { + const atf::fs::path executable = get_helper(*this, "several_tcs_helper"); + const impl::metadata md = impl::get_metadata(executable, vars_map()); + ATF_REQUIRE_EQ(3, md.test_cases.size()); + + { + const impl::test_cases_map::const_iterator iter = + md.test_cases.find("first"); + ATF_REQUIRE(iter != md.test_cases.end()); + + ATF_REQUIRE_EQ(4, (*iter).second.size()); + check_property((*iter).second, "descr", "Description 1"); + check_property((*iter).second, "has.cleanup", "false"); + check_property((*iter).second, "ident", "first"); + check_property((*iter).second, "timeout", "300"); + } + + { + const impl::test_cases_map::const_iterator iter = + md.test_cases.find("second"); + ATF_REQUIRE(iter != md.test_cases.end()); + + ATF_REQUIRE_EQ(5, (*iter).second.size()); + check_property((*iter).second, "descr", "Description 2"); + check_property((*iter).second, "has.cleanup", "true"); + check_property((*iter).second, "ident", "second"); + check_property((*iter).second, "timeout", "500"); + check_property((*iter).second, "X-property", "Custom property"); + } + + { + const impl::test_cases_map::const_iterator iter = + md.test_cases.find("third"); + ATF_REQUIRE(iter != md.test_cases.end()); + + ATF_REQUIRE_EQ(3, (*iter).second.size()); + check_property((*iter).second, "has.cleanup", "false"); + check_property((*iter).second, "ident", "third"); + check_property((*iter).second, "timeout", "300"); + } +} + +ATF_TEST_CASE_WITHOUT_HEAD(parse_test_case_result_expected_death); +ATF_TEST_CASE_BODY(parse_test_case_result_expected_death) { + check_result("expected_death", -1, "foo bar", + detail::parse_test_case_result("expected_death: foo bar")); + + ATF_REQUIRE_THROW(std::runtime_error, + detail::parse_test_case_result("expected_death")); + ATF_REQUIRE_THROW(std::runtime_error, + detail::parse_test_case_result("expected_death(3): foo")); +} + +ATF_TEST_CASE_WITHOUT_HEAD(parse_test_case_result_expected_exit); +ATF_TEST_CASE_BODY(parse_test_case_result_expected_exit) { + check_result("expected_exit", -1, "foo bar", + detail::parse_test_case_result("expected_exit: foo bar")); + check_result("expected_exit", -1, "foo bar", + detail::parse_test_case_result("expected_exit(): foo bar")); + check_result("expected_exit", 5, "foo bar", + detail::parse_test_case_result("expected_exit(5): foo bar")); + + ATF_REQUIRE_THROW(std::runtime_error, + detail::parse_test_case_result("expected_exit")); + ATF_REQUIRE_THROW(std::runtime_error, + detail::parse_test_case_result("expected_exit(")); +} + +ATF_TEST_CASE_WITHOUT_HEAD(parse_test_case_result_expected_failure); +ATF_TEST_CASE_BODY(parse_test_case_result_expected_failure) { + check_result("expected_failure", -1, "foo bar", + detail::parse_test_case_result("expected_failure: foo bar")); + + ATF_REQUIRE_THROW(std::runtime_error, + detail::parse_test_case_result("expected_failure")); + ATF_REQUIRE_THROW(std::runtime_error, + detail::parse_test_case_result("expected_failure(3): foo")); +} + +ATF_TEST_CASE_WITHOUT_HEAD(parse_test_case_result_expected_signal); +ATF_TEST_CASE_BODY(parse_test_case_result_expected_signal) { + check_result("expected_signal", -1, "foo bar", + detail::parse_test_case_result("expected_signal: foo bar")); + check_result("expected_signal", -1, "foo bar", + detail::parse_test_case_result("expected_signal(): foo bar")); + check_result("expected_signal", 5, "foo bar", + detail::parse_test_case_result("expected_signal(5): foo bar")); + + ATF_REQUIRE_THROW(std::runtime_error, + detail::parse_test_case_result("expected_signal")); + ATF_REQUIRE_THROW(std::runtime_error, + detail::parse_test_case_result("expected_signal(")); +} + +ATF_TEST_CASE_WITHOUT_HEAD(parse_test_case_result_expected_timeout); +ATF_TEST_CASE_BODY(parse_test_case_result_expected_timeout) { + check_result("expected_timeout", -1, "foo bar", + detail::parse_test_case_result("expected_timeout: foo bar")); + + ATF_REQUIRE_THROW(std::runtime_error, + detail::parse_test_case_result("expected_timeout")); + ATF_REQUIRE_THROW(std::runtime_error, + detail::parse_test_case_result("expected_timeout(3): foo")); +} + +ATF_TEST_CASE_WITHOUT_HEAD(parse_test_case_result_failed); +ATF_TEST_CASE_BODY(parse_test_case_result_failed) { + check_result("failed", -1, "foo bar", + detail::parse_test_case_result("failed: foo bar")); + + ATF_REQUIRE_THROW(std::runtime_error, + detail::parse_test_case_result("failed")); + ATF_REQUIRE_THROW(std::runtime_error, + detail::parse_test_case_result("failed(3): foo")); +} + +ATF_TEST_CASE_WITHOUT_HEAD(parse_test_case_result_passed); +ATF_TEST_CASE_BODY(parse_test_case_result_passed) { + check_result("passed", -1, "", + detail::parse_test_case_result("passed")); + + ATF_REQUIRE_THROW(std::runtime_error, + detail::parse_test_case_result("passed: foo")); + ATF_REQUIRE_THROW(std::runtime_error, + detail::parse_test_case_result("passed(3): foo")); +} + +ATF_TEST_CASE_WITHOUT_HEAD(parse_test_case_result_skipped); +ATF_TEST_CASE_BODY(parse_test_case_result_skipped) { + check_result("skipped", -1, "foo bar", + detail::parse_test_case_result("skipped: foo bar")); + + ATF_REQUIRE_THROW(std::runtime_error, + detail::parse_test_case_result("skipped")); + ATF_REQUIRE_THROW(std::runtime_error, + detail::parse_test_case_result("skipped(3): foo")); +} + +ATF_TEST_CASE_WITHOUT_HEAD(parse_test_case_result_unknown); +ATF_TEST_CASE_BODY(parse_test_case_result_unknown) { + ATF_REQUIRE_THROW(std::runtime_error, + detail::parse_test_case_result("foo")); + ATF_REQUIRE_THROW(std::runtime_error, + detail::parse_test_case_result("bar: foo")); + ATF_REQUIRE_THROW(std::runtime_error, + detail::parse_test_case_result("baz: foo")); +} + +ATF_TEST_CASE_WITHOUT_HEAD(read_test_case_result_failed); +ATF_TEST_CASE_BODY(read_test_case_result_failed) { + write_test_case_result("resfile", "failed: foo bar\n"); + const impl::test_case_result tcr = impl::read_test_case_result( + atf::fs::path("resfile")); + ATF_REQUIRE_EQ("failed", tcr.state()); + ATF_REQUIRE_EQ("foo bar", tcr.reason()); +} + +ATF_TEST_CASE_WITHOUT_HEAD(read_test_case_result_skipped); +ATF_TEST_CASE_BODY(read_test_case_result_skipped) { + write_test_case_result("resfile", "skipped: baz bar\n"); + const impl::test_case_result tcr = impl::read_test_case_result( + atf::fs::path("resfile")); + ATF_REQUIRE_EQ("skipped", tcr.state()); + ATF_REQUIRE_EQ("baz bar", tcr.reason()); +} + + +ATF_TEST_CASE(read_test_case_result_no_file); +ATF_TEST_CASE_HEAD(read_test_case_result_no_file) {} +ATF_TEST_CASE_BODY(read_test_case_result_no_file) { + ATF_REQUIRE_THROW(std::runtime_error, + impl::read_test_case_result(atf::fs::path("resfile"))); +} + +ATF_TEST_CASE_WITHOUT_HEAD(read_test_case_result_empty_file); +ATF_TEST_CASE_BODY(read_test_case_result_empty_file) { + write_test_case_result("resfile", ""); + ATF_REQUIRE_THROW(std::runtime_error, + impl::read_test_case_result(atf::fs::path("resfile"))); +} + +ATF_TEST_CASE_WITHOUT_HEAD(read_test_case_result_invalid); +ATF_TEST_CASE_BODY(read_test_case_result_invalid) { + write_test_case_result("resfile", "passed: hello\n"); + ATF_REQUIRE_THROW(std::runtime_error, + impl::read_test_case_result(atf::fs::path("resfile"))); +} + +ATF_TEST_CASE_WITHOUT_HEAD(read_test_case_result_multiline); +ATF_TEST_CASE_BODY(read_test_case_result_multiline) { + write_test_case_result("resfile", "skipped: foo\nbar\n"); + const impl::test_case_result tcr = impl::read_test_case_result( + atf::fs::path("resfile")); + ATF_REQUIRE_EQ("skipped", tcr.state()); + ATF_REQUIRE_EQ("foo<<NEWLINE UNEXPECTED>>bar", tcr.reason()); +} + +// ------------------------------------------------------------------------- +// Main. +// ------------------------------------------------------------------------- + +ATF_INIT_TEST_CASES(tcs) +{ + ATF_ADD_TEST_CASE(tcs, tp_1); + ATF_ADD_TEST_CASE(tcs, tp_2); + ATF_ADD_TEST_CASE(tcs, tp_3); + ATF_ADD_TEST_CASE(tcs, tp_4); + ATF_ADD_TEST_CASE(tcs, tp_50); + ATF_ADD_TEST_CASE(tcs, tp_51); + ATF_ADD_TEST_CASE(tcs, tp_52); + ATF_ADD_TEST_CASE(tcs, tp_53); + ATF_ADD_TEST_CASE(tcs, tp_54); + ATF_ADD_TEST_CASE(tcs, tp_55); + ATF_ADD_TEST_CASE(tcs, tp_56); + ATF_ADD_TEST_CASE(tcs, tp_57); + ATF_ADD_TEST_CASE(tcs, tp_58); + ATF_ADD_TEST_CASE(tcs, tp_59); + ATF_ADD_TEST_CASE(tcs, tp_60); + + ATF_ADD_TEST_CASE(tcs, atf_tps_writer); + + ATF_ADD_TEST_CASE(tcs, get_metadata_bad); + ATF_ADD_TEST_CASE(tcs, get_metadata_zero_tcs); + ATF_ADD_TEST_CASE(tcs, get_metadata_several_tcs); + + ATF_ADD_TEST_CASE(tcs, parse_test_case_result_expected_death); + ATF_ADD_TEST_CASE(tcs, parse_test_case_result_expected_exit); + ATF_ADD_TEST_CASE(tcs, parse_test_case_result_expected_failure); + ATF_ADD_TEST_CASE(tcs, parse_test_case_result_expected_signal); + ATF_ADD_TEST_CASE(tcs, parse_test_case_result_expected_timeout); + ATF_ADD_TEST_CASE(tcs, parse_test_case_result_failed); + ATF_ADD_TEST_CASE(tcs, parse_test_case_result_passed); + ATF_ADD_TEST_CASE(tcs, parse_test_case_result_skipped); + ATF_ADD_TEST_CASE(tcs, parse_test_case_result_unknown); + + ATF_ADD_TEST_CASE(tcs, read_test_case_result_failed); + ATF_ADD_TEST_CASE(tcs, read_test_case_result_skipped); + ATF_ADD_TEST_CASE(tcs, read_test_case_result_no_file); + ATF_ADD_TEST_CASE(tcs, read_test_case_result_empty_file); + ATF_ADD_TEST_CASE(tcs, read_test_case_result_multiline); + ATF_ADD_TEST_CASE(tcs, read_test_case_result_invalid); + + // TODO: Add tests for run_test_case once all the missing functionality + // is implemented. +} diff --git a/atf-run/timer.cpp b/atf-run/timer.cpp new file mode 100644 index 0000000000000..6ed70d97f62c0 --- /dev/null +++ b/atf-run/timer.cpp @@ -0,0 +1,215 @@ +// +// Automated Testing Framework (atf) +// +// 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. +// + +#if defined(HAVE_CONFIG_H) +# include <bconfig.h> +#endif + +extern "C" { +#include <sys/time.h> +} + +#include <cerrno> +#include <csignal> +#include <ctime> + +extern "C" { +#include "atf-c/defs.h" +} + +#include "atf-c++/detail/exceptions.hpp" +#include "atf-c++/detail/sanity.hpp" + +#include "signals.hpp" +#include "timer.hpp" + +namespace impl = atf::atf_run; +#define IMPL_NAME "atf::atf_run" + +#if !defined(HAVE_TIMER_T) +static impl::timer* compat_handle; +#endif + +// ------------------------------------------------------------------------ +// Auxiliary functions. +// ------------------------------------------------------------------------ + +#if defined(HAVE_TIMER_T) +static +void +handler(const int signo ATF_DEFS_ATTRIBUTE_UNUSED, siginfo_t* si, + void* uc ATF_DEFS_ATTRIBUTE_UNUSED) +{ + impl::timer* timer = static_cast< impl::timer* >(si->si_value.sival_ptr); + timer->set_fired(); + timer->timeout_callback(); +} +#else +static +void +handler(const int signo ATF_DEFS_ATTRIBUTE_UNUSED, + siginfo_t* si ATF_DEFS_ATTRIBUTE_UNUSED, + void* uc ATF_DEFS_ATTRIBUTE_UNUSED) +{ + compat_handle->set_fired(); + compat_handle->timeout_callback(); +} +#endif + +// ------------------------------------------------------------------------ +// The "timer" class. +// ------------------------------------------------------------------------ + +struct impl::timer::impl { +#if defined(HAVE_TIMER_T) + ::timer_t m_timer; + ::itimerspec m_old_it; +#else + ::itimerval m_old_it; +#endif + + struct ::sigaction m_old_sa; + volatile bool m_fired; + + impl(void) : m_fired(false) + { + } +}; + +impl::timer::timer(const unsigned int seconds) : + m_pimpl(new impl()) +{ + struct ::sigaction sa; + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_SIGINFO; + sa.sa_sigaction = ::handler; + if (::sigaction(SIGALRM, &sa, &m_pimpl->m_old_sa) == -1) + throw system_error(IMPL_NAME "::timer::timer", + "Failed to set signal handler", errno); + +#if defined(HAVE_TIMER_T) + struct ::sigevent se; + se.sigev_notify = SIGEV_SIGNAL; + se.sigev_signo = SIGALRM; + se.sigev_value.sival_ptr = static_cast< void* >(this); + se.sigev_notify_function = NULL; + se.sigev_notify_attributes = NULL; + if (::timer_create(CLOCK_MONOTONIC, &se, &m_pimpl->m_timer) == -1) { + ::sigaction(SIGALRM, &m_pimpl->m_old_sa, NULL); + throw system_error(IMPL_NAME "::timer::timer", + "Failed to create timer", errno); + } + + struct ::itimerspec it; + it.it_interval.tv_sec = 0; + it.it_interval.tv_nsec = 0; + it.it_value.tv_sec = seconds; + it.it_value.tv_nsec = 0; + if (::timer_settime(m_pimpl->m_timer, 0, &it, &m_pimpl->m_old_it) == -1) { + ::sigaction(SIGALRM, &m_pimpl->m_old_sa, NULL); + ::timer_delete(m_pimpl->m_timer); + throw system_error(IMPL_NAME "::timer::timer", + "Failed to program timer", errno); + } +#else + ::itimerval it; + it.it_interval.tv_sec = 0; + it.it_interval.tv_usec = 0; + it.it_value.tv_sec = seconds; + it.it_value.tv_usec = 0; + if (::setitimer(ITIMER_REAL, &it, &m_pimpl->m_old_it) == -1) { + ::sigaction(SIGALRM, &m_pimpl->m_old_sa, NULL); + throw system_error(IMPL_NAME "::timer::timer", + "Failed to program timer", errno); + } + INV(compat_handle == NULL); + compat_handle = this; +#endif +} + +impl::timer::~timer(void) +{ +#if defined(HAVE_TIMER_T) + { + const int ret = ::timer_delete(m_pimpl->m_timer); + INV(ret != -1); + } +#else + { + const int ret = ::setitimer(ITIMER_REAL, &m_pimpl->m_old_it, NULL); + INV(ret != -1); + } +#endif + const int ret = ::sigaction(SIGALRM, &m_pimpl->m_old_sa, NULL); + INV(ret != -1); + +#if !defined(HAVE_TIMER_T) + compat_handle = NULL; +#endif +} + +bool +impl::timer::fired(void) + const +{ + return m_pimpl->m_fired; +} + +void +impl::timer::set_fired(void) +{ + m_pimpl->m_fired = true; +} + +// ------------------------------------------------------------------------ +// The "child_timer" class. +// ------------------------------------------------------------------------ + +impl::child_timer::child_timer(const unsigned int seconds, const pid_t pid, + volatile bool& terminate) : + timer(seconds), + m_pid(pid), + m_terminate(terminate) +{ +} + +impl::child_timer::~child_timer(void) +{ +} + +void +impl::child_timer::timeout_callback(void) +{ + static const timespec ts = { 1, 0 }; + m_terminate = true; + ::kill(-m_pid, SIGTERM); + ::nanosleep(&ts, NULL); + if (::kill(-m_pid, 0) != -1) + ::kill(-m_pid, SIGKILL); +} diff --git a/atf-run/timer.hpp b/atf-run/timer.hpp new file mode 100644 index 0000000000000..d903b91a882fa --- /dev/null +++ b/atf-run/timer.hpp @@ -0,0 +1,81 @@ +// +// Automated Testing Framework (atf) +// +// 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. +// + +#if !defined(_ATF_RUN_ALARM_HPP_) +#define _ATF_RUN_ALARM_HPP_ + +extern "C" { +#include <sys/types.h> +} + +#include <memory> + +#include "atf-c++/utils.hpp" + +namespace atf { +namespace atf_run { + +class signal_programmer; + +// ------------------------------------------------------------------------ +// The "timer" class. +// ------------------------------------------------------------------------ + +class timer : utils::noncopyable { + struct impl; + std::auto_ptr< impl > m_pimpl; + +public: + timer(const unsigned int); + virtual ~timer(void); + + bool fired(void) const; + void set_fired(void); + virtual void timeout_callback(void) = 0; +}; + +// ------------------------------------------------------------------------ +// The "child_timer" class. +// ------------------------------------------------------------------------ + +class child_timer : public timer { + const pid_t m_pid; + volatile bool& m_terminate; + +public: + child_timer(const unsigned int, const pid_t, volatile bool&); + virtual ~child_timer(void); + + void timeout_callback(void); +}; + +} // namespace atf_run +} // namespace atf + +#endif // !defined(_ATF_RUN_ALARM_HPP_) diff --git a/atf-run/user.cpp b/atf-run/user.cpp new file mode 100644 index 0000000000000..37329f1666769 --- /dev/null +++ b/atf-run/user.cpp @@ -0,0 +1,89 @@ +// +// Automated Testing Framework (atf) +// +// 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. +// + +extern "C" { +#include <sys/types.h> + +#include <pwd.h> +#include <unistd.h> + +#include "../atf-c/detail/user.h" +} + +#include <stdexcept> +#include <string> + +#include "../atf-c++/detail/sanity.hpp" + +#include "user.hpp" + +namespace impl = atf::atf_run; +#define IMPL_NAME "atf::atf_run" + +uid_t +impl::euid(void) +{ + return atf_user_euid(); +} + +void +impl::drop_privileges(const std::pair< int, int > ids) +{ + if (::setgid(ids.second) == -1) + throw std::runtime_error("Failed to drop group privileges"); + if (::setuid(ids.first) == -1) + throw std::runtime_error("Failed to drop user privileges"); +} + +std::pair< int, int > +impl::get_user_ids(const std::string& user) +{ + const struct passwd* pw = ::getpwnam(user.c_str()); + if (pw == NULL) + throw std::runtime_error("Failed to get information for user " + user); + return std::make_pair(pw->pw_uid, pw->pw_gid); +} + +bool +impl::is_member_of_group(gid_t gid) +{ + return atf_user_is_member_of_group(gid); +} + +bool +impl::is_root(void) +{ + return atf_user_is_root(); +} + +bool +impl::is_unprivileged(void) +{ + return atf_user_is_unprivileged(); +} diff --git a/atf-run/user.hpp b/atf-run/user.hpp new file mode 100644 index 0000000000000..11b3e5546d36d --- /dev/null +++ b/atf-run/user.hpp @@ -0,0 +1,52 @@ +// +// Automated Testing Framework (atf) +// +// 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. +// + +#if !defined(_ATF_RUN_USER_HPP_) +#define _ATF_RUN_USER_HPP_ + +extern "C" { +#include <sys/types.h> +} + +#include <utility> + +namespace atf { +namespace atf_run { + +uid_t euid(void); +void drop_privileges(const std::pair< int, int >); +std::pair< int, int > get_user_ids(const std::string&); +bool is_member_of_group(gid_t); +bool is_root(void); +bool is_unprivileged(void); + +} // namespace atf_run +} // namespace atf + +#endif // !defined(_ATF_RUN_USER_HPP_) diff --git a/atf-run/user_test.cpp b/atf-run/user_test.cpp new file mode 100644 index 0000000000000..7218ba6553b56 --- /dev/null +++ b/atf-run/user_test.cpp @@ -0,0 +1,148 @@ +// +// Automated Testing Framework (atf) +// +// 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. +// + +extern "C" { +#include <sys/param.h> +#include <sys/types.h> +#include <limits.h> +#include <unistd.h> +} + +#include <iostream> +#include <set> + +#include "../atf-c++/macros.hpp" + +#include "user.hpp" + +// ------------------------------------------------------------------------ +// Test cases for the free functions. +// ------------------------------------------------------------------------ + +ATF_TEST_CASE(euid); +ATF_TEST_CASE_HEAD(euid) +{ + set_md_var("descr", "Tests the euid function"); +} +ATF_TEST_CASE_BODY(euid) +{ + using atf::atf_run::euid; + + ATF_REQUIRE_EQ(euid(), ::geteuid()); +} + +ATF_TEST_CASE(is_member_of_group); +ATF_TEST_CASE_HEAD(is_member_of_group) +{ + set_md_var("descr", "Tests the is_member_of_group function"); +} +ATF_TEST_CASE_BODY(is_member_of_group) +{ + using atf::atf_run::is_member_of_group; + + std::set< gid_t > groups; + gid_t maxgid = 0; + { + gid_t gids[NGROUPS_MAX]; + int ngids = ::getgroups(NGROUPS_MAX, gids); + if (ngids == -1) + ATF_FAIL("Call to ::getgroups failed"); + for (int i = 0; i < ngids; i++) { + groups.insert(gids[i]); + if (gids[i] > maxgid) + maxgid = gids[i]; + } + std::cout << "User belongs to " << ngids << " groups\n"; + std::cout << "Last GID is " << maxgid << "\n"; + } + + const gid_t maxgid_limit = 1 << 16; + if (maxgid > maxgid_limit) { + std::cout << "Test truncated from " << maxgid << " groups to " + << maxgid_limit << " to keep the run time reasonable " + "enough\n"; + maxgid = maxgid_limit; + } + + for (gid_t g = 0; g <= maxgid; g++) { + if (groups.find(g) == groups.end()) { + std::cout << "Checking if user does not belong to group " + << g << "\n"; + ATF_REQUIRE(!is_member_of_group(g)); + } else { + std::cout << "Checking if user belongs to group " << g << "\n"; + ATF_REQUIRE(is_member_of_group(g)); + } + } +} + +ATF_TEST_CASE(is_root); +ATF_TEST_CASE_HEAD(is_root) +{ + set_md_var("descr", "Tests the is_root function"); +} +ATF_TEST_CASE_BODY(is_root) +{ + using atf::atf_run::is_root; + + if (::geteuid() == 0) { + ATF_REQUIRE(is_root()); + } else { + ATF_REQUIRE(!is_root()); + } +} + +ATF_TEST_CASE(is_unprivileged); +ATF_TEST_CASE_HEAD(is_unprivileged) +{ + set_md_var("descr", "Tests the is_unprivileged function"); +} +ATF_TEST_CASE_BODY(is_unprivileged) +{ + using atf::atf_run::is_unprivileged; + + if (::geteuid() != 0) { + ATF_REQUIRE(is_unprivileged()); + } else { + ATF_REQUIRE(!is_unprivileged()); + } +} + +// ------------------------------------------------------------------------ +// Main. +// ------------------------------------------------------------------------ + +ATF_INIT_TEST_CASES(tcs) +{ + // Add the tests for the free functions. + ATF_ADD_TEST_CASE(tcs, euid); + ATF_ADD_TEST_CASE(tcs, is_member_of_group); + ATF_ADD_TEST_CASE(tcs, is_root); + ATF_ADD_TEST_CASE(tcs, is_unprivileged); +} diff --git a/atf-run/zero_tcs_helper.c b/atf-run/zero_tcs_helper.c new file mode 100644 index 0000000000000..d4068b17a838c --- /dev/null +++ b/atf-run/zero_tcs_helper.c @@ -0,0 +1,36 @@ +/* + * Automated Testing Framework (atf) + * + * 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. + */ + +#include <atf-c.h> + +ATF_TP_ADD_TCS(tp) +{ + if (tp != NULL) {} /* Use tp. */ + return atf_no_error(); +} |