diff options
Diffstat (limited to '')
90 files changed, 24137 insertions, 0 deletions
diff --git a/unit/atf-src/tools/Atffile b/unit/atf-src/tools/Atffile new file mode 100644 index 0000000..8896078 --- /dev/null +++ b/unit/atf-src/tools/Atffile @@ -0,0 +1,25 @@ +Content-Type: application/X-atf-atffile; version="1" + +prop: test-suite = atf + +tp: atf-config_test +tp: atf-report_test +tp: atf-run_test +tp: application_test +tp: atffile_test +tp: auto_array_test +tp: config_test +tp: config_file_test +tp: env_test +tp: expand_test +tp: fs_test +tp: io_test +tp: parser_test +tp: process_test +tp: reader_test +tp: requirements_test +tp: signals_test +tp: test_program_test +tp: text_test +tp: ui_test +tp: user_test diff --git a/unit/atf-src/tools/Kyuafile b/unit/atf-src/tools/Kyuafile new file mode 100644 index 0000000..09c5145 --- /dev/null +++ b/unit/atf-src/tools/Kyuafile @@ -0,0 +1,24 @@ +syntax("kyuafile", 1) + +test_suite("atf") + +atf_test_program{name="atf-config_test"} +atf_test_program{name="atf-report_test"} +atf_test_program{name="atf-run_test"} +atf_test_program{name="application_test"} +atf_test_program{name="atffile_test"} +atf_test_program{name="auto_array_test"} +atf_test_program{name="config_test"} +atf_test_program{name="config_file_test"} +atf_test_program{name="env_test"} +atf_test_program{name="expand_test"} +atf_test_program{name="fs_test"} +atf_test_program{name="io_test"} +atf_test_program{name="parser_test"} +atf_test_program{name="process_test"} +atf_test_program{name="reader_test"} +atf_test_program{name="requirements_test"} +atf_test_program{name="signals_test"} +atf_test_program{name="test_program_test"} +atf_test_program{name="text_test"} +atf_test_program{name="ui_test"} diff --git a/unit/atf-src/tools/Makefile.am.inc b/unit/atf-src/tools/Makefile.am.inc new file mode 100644 index 0000000..00c3b13 --- /dev/null +++ b/unit/atf-src/tools/Makefile.am.inc @@ -0,0 +1,330 @@ +# +# 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. +# + +TOOLS_CPPFLAGS = -I$(top_srcdir)/tools -I$(top_builddir)/tools +TOOLS_LDADD = tools/libtools.a + +noinst_LIBRARIES = tools/libtools.a +tools_libtools_a_SOURCES = tools/application.cpp \ + tools/application.hpp \ + tools/atffile.cpp \ + tools/atffile.hpp \ + tools/auto_array.hpp \ + tools/config.cpp \ + tools/config.hpp \ + tools/config_file.cpp \ + tools/config_file.hpp \ + tools/env.cpp \ + tools/env.hpp \ + tools/exceptions.cpp \ + tools/exceptions.hpp \ + tools/expand.cpp \ + tools/expand.hpp \ + tools/fs.cpp \ + tools/fs.hpp \ + tools/io.cpp \ + tools/io.hpp \ + tools/parser.cpp \ + tools/parser.hpp \ + tools/process.cpp \ + tools/process.hpp \ + tools/reader.cpp \ + tools/reader.hpp \ + tools/requirements.cpp \ + tools/requirements.hpp \ + tools/signals.cpp \ + tools/signals.hpp \ + tools/test-program.cpp \ + tools/test-program.hpp \ + tools/test_helpers.hpp \ + tools/text.cpp \ + tools/text.hpp \ + tools/timers.cpp \ + tools/timers.hpp \ + tools/ui.cpp \ + tools/ui.hpp \ + tools/user.cpp \ + tools/user.hpp +nodist_tools_libtools_a_SOURCES = tools/defs.hpp +tools_libtools_a_CPPFLAGS = "-DATF_ARCH=\"$(atf_arch)\"" \ + "-DATF_BUILD_CC=\"$(ATF_BUILD_CC)\"" \ + "-DATF_BUILD_CFLAGS=\"$(ATF_BUILD_CFLAGS)\"" \ + "-DATF_BUILD_CPP=\"$(ATF_BUILD_CPP)\"" \ + "-DATF_BUILD_CPPFLAGS=\"$(ATF_BUILD_CPPFLAGS)\"" \ + "-DATF_BUILD_CXX=\"$(ATF_BUILD_CXX)\"" \ + "-DATF_BUILD_CXXFLAGS=\"$(ATF_BUILD_CXXFLAGS)\"" \ + "-DATF_CONFDIR=\"$(atf_confdir)\"" \ + "-DATF_INCLUDEDIR=\"$(includedir)\"" \ + "-DATF_LIBDIR=\"$(libdir)\"" \ + "-DATF_LIBEXECDIR=\"$(libexecdir)\"" \ + "-DATF_MACHINE=\"$(atf_machine)\"" \ + "-DATF_PKGDATADIR=\"$(pkgdatadir)\"" \ + "-DATF_SHELL=\"$(ATF_SHELL)\"" \ + "-DATF_WORKDIR=\"$(ATF_WORKDIR)\"" \ + $(TOOLS_CPPFLAGS) + +# XXX For some reason, the nodist line above does not work as expected. +# Work this problem around. +DIST_HOOKS += kill-defs-hpp +kill-defs-hpp: + rm -f $(distdir)/tools/defs.hpp + + + +bin_PROGRAMS += tools/atf-config +tools_atf_config_SOURCES = tools/atf-config.cpp +tools_atf_config_CPPFLAGS = $(TOOLS_CPPFLAGS) +tools_atf_config_LDADD = $(TOOLS_LDADD) +dist_man_MANS += tools/atf-config.1 + +bin_PROGRAMS += tools/atf-report +tools_atf_report_SOURCES = tools/atf-report.cpp +tools_atf_report_CPPFLAGS = $(TOOLS_CPPFLAGS) +tools_atf_report_LDADD = $(TOOLS_LDADD) +dist_man_MANS += tools/atf-report.1 + +bin_PROGRAMS += tools/atf-run +tools_atf_run_CPPFLAGS = $(TOOLS_CPPFLAGS) "-DGDB=\"$(GDB)\"" +tools_atf_run_SOURCES = tools/atf-run.cpp +tools_atf_run_LDADD = $(TOOLS_LDADD) +dist_man_MANS += tools/atf-run.1 + +bin_PROGRAMS += tools/atf-version +tools_atf_version_SOURCES = tools/atf-version.cpp +nodist_tools_atf_version_SOURCES = tools/revision.h +tools_atf_version_CPPFLAGS = $(TOOLS_CPPFLAGS) +tools_atf_version_LDADD = $(TOOLS_LDADD) +dist_man_MANS += tools/atf-version.1 + +EXTRA_DIST += tools/generate-revision.sh + +BUILT_SOURCES += tools/revision.h +CLEANFILES += tools/revision.h +tools/revision.h: tools/revision.h.stamp + @test -d tools || mkdir -p tools + @cmp -s tools/revision.h tools/revision.h.stamp || \ + cp -p tools/revision.h.stamp tools/revision.h + +CLEANFILES += tools/revision.h.stamp +PHONY_TARGETS += tools/revision.h.stamp +tools/revision.h.stamp: + @test -d tools || mkdir -p tools + @$(top_srcdir)/tools/generate-revision.sh \ + -g "$(GIT)" -r $(top_srcdir) -o tools/revision.h.stamp \ + -v $(PACKAGE_VERSION) + +#man_MANS += tools/atf.7 +#CLEANFILES += tools/atf.7 +#EXTRA_DIST += tools/atf.7.in + +dist_man_MANS += tools/atf-formats.5 + +tools/atf.7: $(srcdir)/tools/atf.7.in + test -d tools || mkdir -p tools + sed -e 's#__DOCDIR__#$(docdir)#g' \ + -e 's#__TESTSDIR__#$(testsdir)#g' \ + <$(srcdir)/tools/atf.7.in >tools/atf.7.tmp + mv tools/atf.7.tmp tools/atf.7 + + + +cssdir = $(atf_cssdir) +css_DATA = tools/tests-results.css +EXTRA_DIST += $(css_DATA) + +dtddir = $(atf_dtddir) +dtd_DATA = tools/tests-results.dtd +EXTRA_DIST += $(dtd_DATA) + +egdir = $(atf_egdir) +eg_DATA = tools/sample/atf-run.hooks +eg_DATA += tools/sample/common.conf +EXTRA_DIST += $(eg_DATA) + +hooksdir = $(pkgdatadir) +hooks_DATA = tools/share/atf-run.hooks +EXTRA_DIST += $(hooks_DATA) + +xsldir = $(atf_xsldir) +xsl_DATA = tools/tests-results.xsl +EXTRA_DIST += $(xsl_DATA) + + + +tests_tools_DATA = tools/Atffile tools/Kyuafile +tests_toolsdir = $(pkgtestsdir)/tools +EXTRA_DIST += $(tests_tools_DATA) + +tests_tools_SCRIPTS = tools/atf-config_test +CLEANFILES += tools/atf-config_test +EXTRA_DIST += tools/atf-config_test.sh +tools/atf-config_test: $(srcdir)/tools/atf-config_test.sh + @test -d tools || mkdir -p tools + @src="$(srcdir)/tools/atf-config_test.sh"; \ + dst="tools/atf-config_test"; $(BUILD_SH_TP) + +tests_tools_SCRIPTS += tools/atf-report_test +CLEANFILES += tools/atf-report_test +EXTRA_DIST += tools/atf-report_test.sh +tools/atf-report_test: $(srcdir)/tools/atf-report_test.sh + @test -d tools || mkdir -p tools + @src="$(srcdir)/tools/atf-report_test.sh"; \ + dst="tools/atf-report_test"; $(BUILD_SH_TP) + +tests_tools_SCRIPTS += tools/atf-run_test +CLEANFILES += tools/atf-run_test +EXTRA_DIST += tools/atf-run_test.sh +tools/atf-run_test: $(srcdir)/tools/atf-run_test.sh + @test -d tools || mkdir -p tools + @src="$(srcdir)/tools/atf-run_test.sh"; \ + dst="tools/atf-run_test"; $(BUILD_SH_TP) + +tests_tools_PROGRAMS = tools/application_test +tools_application_test_SOURCES = tools/application_test.cpp +tools_application_test_CPPFLAGS = $(TOOLS_CPPFLAGS) +tools_application_test_LDADD = $(TOOLS_LDADD) $(ATF_CXX_LIBS) + +tests_tools_PROGRAMS += tools/atffile_test +tools_atffile_test_SOURCES = tools/atffile_test.cpp +tools_atffile_test_CPPFLAGS = $(TOOLS_CPPFLAGS) +tools_atffile_test_LDADD = $(TOOLS_LDADD) $(ATF_CXX_LIBS) + +tests_tools_PROGRAMS += tools/auto_array_test +tools_auto_array_test_SOURCES = tools/auto_array_test.cpp +tools_auto_array_test_CPPFLAGS = $(TOOLS_CPPFLAGS) +tools_auto_array_test_LDADD = $(TOOLS_LDADD) $(ATF_CXX_LIBS) + +tests_tools_PROGRAMS += tools/bad_metadata_helper +tools_bad_metadata_helper_SOURCES = tools/bad_metadata_helper.c +tools_bad_metadata_helper_LDADD = libatf-c.la + +tests_tools_PROGRAMS += tools/config_test +tools_config_test_SOURCES = tools/config_test.cpp +tools_config_test_CPPFLAGS = $(TOOLS_CPPFLAGS) +tools_config_test_LDADD = $(TOOLS_LDADD) $(ATF_CXX_LIBS) + +tests_tools_PROGRAMS += tools/config_file_test +tools_config_file_test_SOURCES = tools/config_file_test.cpp +tools_config_file_test_CPPFLAGS = $(TOOLS_CPPFLAGS) +tools_config_file_test_LDADD = $(TOOLS_LDADD) $(ATF_CXX_LIBS) + +tests_tools_PROGRAMS += tools/env_test +tools_env_test_SOURCES = tools/env_test.cpp +tools_env_test_CPPFLAGS = $(TOOLS_CPPFLAGS) +tools_env_test_LDADD = $(TOOLS_LDADD) $(ATF_CXX_LIBS) + +tests_tools_PROGRAMS += tools/expand_test +tools_expand_test_SOURCES = tools/expand_test.cpp +tools_expand_test_CPPFLAGS = $(TOOLS_CPPFLAGS) +tools_expand_test_LDADD = $(TOOLS_LDADD) $(ATF_CXX_LIBS) + +tests_tools_PROGRAMS += tools/expect_helpers +tools_expect_helpers_SOURCES = tools/expect_helpers.c +tools_expect_helpers_LDADD = libatf-c.la + +tests_tools_PROGRAMS += tools/fail_helper +tools_fail_helper_SOURCES = tools/fail_helper.cpp +tools_fail_helper_LDADD = $(ATF_CXX_LIBS) + +tests_tools_PROGRAMS += tools/fs_test +tools_fs_test_SOURCES = tools/fs_test.cpp +tools_fs_test_CPPFLAGS = $(TOOLS_CPPFLAGS) +tools_fs_test_LDADD = $(TOOLS_LDADD) $(ATF_CXX_LIBS) + +tests_tools_PROGRAMS += tools/io_test +tools_io_test_SOURCES = tools/io_test.cpp +tools_io_test_CPPFLAGS = $(TOOLS_CPPFLAGS) +tools_io_test_LDADD = $(TOOLS_LDADD) $(ATF_CXX_LIBS) + +tests_tools_PROGRAMS += tools/misc_helpers +tools_misc_helpers_SOURCES = tools/misc_helpers.cpp +tools_misc_helpers_CPPFLAGS = $(TOOLS_CPPFLAGS) +tools_misc_helpers_LDADD = $(TOOLS_LDADD) $(ATF_CXX_LIBS) + +tests_tools_PROGRAMS += tools/parser_test +tools_parser_test_SOURCES = tools/parser_test.cpp +tools_parser_test_CPPFLAGS = $(TOOLS_CPPFLAGS) +tools_parser_test_LDADD = $(TOOLS_LDADD) $(ATF_CXX_LIBS) + +tests_tools_PROGRAMS += tools/process_test +tools_process_test_SOURCES = tools/process_test.cpp +tools_process_test_CPPFLAGS = $(TOOLS_CPPFLAGS) +tools_process_test_LDADD = $(TOOLS_LDADD) $(ATF_CXX_LIBS) + +tests_tools_PROGRAMS += tools/pass_helper +tools_pass_helper_SOURCES = tools/pass_helper.cpp +tools_pass_helper_LDADD = $(ATF_CXX_LIBS) + +tests_tools_PROGRAMS += tools/process_helpers +tools_process_helpers_SOURCES = tools/process_helpers.c + +tests_tools_PROGRAMS += tools/reader_test +tools_reader_test_SOURCES = tools/reader_test.cpp +tools_reader_test_CPPFLAGS = $(TOOLS_CPPFLAGS) +tools_reader_test_LDADD = $(TOOLS_LDADD) $(ATF_CXX_LIBS) + +tests_tools_PROGRAMS += tools/requirements_test +tools_requirements_test_SOURCES = tools/requirements_test.cpp +tools_requirements_test_CPPFLAGS = $(TOOLS_CPPFLAGS) +tools_requirements_test_LDADD = $(TOOLS_LDADD) $(ATF_CXX_LIBS) + +tests_tools_PROGRAMS += tools/several_tcs_helper +tools_several_tcs_helper_SOURCES = tools/several_tcs_helper.c +tools_several_tcs_helper_LDADD = libatf-c.la + +tests_tools_PROGRAMS += tools/signals_test +tools_signals_test_SOURCES = tools/signals_test.cpp tools/signals.cpp +tools_signals_test_CPPFLAGS = $(TOOLS_CPPFLAGS) +tools_signals_test_LDADD = $(TOOLS_LDADD) $(ATF_CXX_LIBS) + +tests_tools_PROGRAMS += tools/test_program_test +tools_test_program_test_SOURCES = tools/test_program_test.cpp +tools_test_program_test_CPPFLAGS = $(TOOLS_CPPFLAGS) +tools_test_program_test_LDADD = $(TOOLS_LDADD) $(ATF_CXX_LIBS) + +tests_tools_PROGRAMS += tools/text_test +tools_text_test_SOURCES = tools/text_test.cpp tools/text.cpp +tools_text_test_CPPFLAGS = $(TOOLS_CPPFLAGS) +tools_text_test_LDADD = $(TOOLS_LDADD) $(ATF_CXX_LIBS) + +tests_tools_PROGRAMS += tools/ui_test +tools_ui_test_SOURCES = tools/ui_test.cpp tools/ui.cpp +tools_ui_test_CPPFLAGS = $(TOOLS_CPPFLAGS) +tools_ui_test_LDADD = $(TOOLS_LDADD) $(ATF_CXX_LIBS) + +tests_tools_PROGRAMS += tools/user_test +tools_user_test_SOURCES = tools/user_test.cpp tools/user.cpp +tools_user_test_CPPFLAGS = $(TOOLS_CPPFLAGS) +tools_user_test_LDADD = $(TOOLS_LDADD) $(ATF_CXX_LIBS) + +tests_tools_PROGRAMS += tools/zero_tcs_helper +tools_zero_tcs_helper_SOURCES = tools/zero_tcs_helper.c +tools_zero_tcs_helper_LDADD = libatf-c.la + +# vim: syntax=make:noexpandtab:shiftwidth=8:softtabstop=8 diff --git a/unit/atf-src/tools/application.cpp b/unit/atf-src/tools/application.cpp new file mode 100644 index 0000000..d32fd7e --- /dev/null +++ b/unit/atf-src/tools/application.cpp @@ -0,0 +1,317 @@ +// +// 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 "config.h" +#endif + +extern "C" { +#include <unistd.h> +} + +#include <cassert> +#include <cstdarg> +#include <cstdio> +#include <cstdlib> +#include <cstring> +#include <iostream> + +#include "application.hpp" +#include "defs.hpp" +#include "ui.hpp" + +#if !defined(HAVE_VSNPRINTF_IN_STD) +namespace std { +using ::vsnprintf; +} +#endif // !defined(HAVE_VSNPRINTF_IN_STD) + +namespace impl = tools::application; +#define IMPL_NAME "tools::application" + +// ------------------------------------------------------------------------ +// The "usage_error" class. +// ------------------------------------------------------------------------ + +impl::usage_error::usage_error(const char *fmt, ...) + throw() : + std::runtime_error("usage_error; message unformatted") +{ + va_list ap; + + va_start(ap, fmt); + std::vsnprintf(m_text, sizeof(m_text), fmt, ap); + va_end(ap); +} + +impl::usage_error::~usage_error(void) + throw() +{ +} + +const char* +impl::usage_error::what(void) + const throw() +{ + return m_text; +} + +// ------------------------------------------------------------------------ +// The "application" class. +// ------------------------------------------------------------------------ + +impl::option::option(char ch, + const std::string& a, + const std::string& desc) : + m_character(ch), + m_argument(a), + m_description(desc) +{ +} + +bool +impl::option::operator<(const impl::option& o) + const +{ + return m_character < o.m_character; +} + +impl::app::app(const std::string& description, + const std::string& manpage, + const std::string& global_manpage) : + m_hflag(false), + m_argc(-1), + m_argv(NULL), + m_prog_name(NULL), + m_description(description), + m_manpage(manpage), + m_global_manpage(global_manpage) +{ +} + +impl::app::~app(void) +{ +} + +bool +impl::app::inited(void) +{ + return m_argc != -1; +} + +impl::app::options_set +impl::app::options(void) +{ + options_set opts = specific_options(); + opts.insert(option('h', "", "Shows this help message")); + return opts; +} + +std::string +impl::app::specific_args(void) + const +{ + return ""; +} + +impl::app::options_set +impl::app::specific_options(void) + const +{ + return options_set(); +} + +void +impl::app::process_option(int ch ATF_DEFS_ATTRIBUTE_UNUSED, + const char* arg ATF_DEFS_ATTRIBUTE_UNUSED) +{ +} + +void +impl::app::process_options(void) +{ + assert(inited()); + + std::string optstr; +#if defined(HAVE_GNU_GETOPT) + optstr += '+'; // Turn on POSIX behavior. +#endif + optstr += ':'; + { + options_set opts = options(); + for (options_set::const_iterator iter = opts.begin(); + iter != opts.end(); iter++) { + const option& opt = (*iter); + + optstr += opt.m_character; + if (!opt.m_argument.empty()) + optstr += ':'; + } + } + + int ch; + const int old_opterr = ::opterr; + ::opterr = 0; + while ((ch = ::getopt(m_argc, m_argv, optstr.c_str())) != -1) { + switch (ch) { + case 'h': + m_hflag = true; + break; + + case ':': + throw usage_error("Option -%c requires an argument.", + ::optopt); + + case '?': + throw usage_error("Unknown option -%c.", ::optopt); + + default: + process_option(ch, ::optarg); + } + } + m_argc -= ::optind; + m_argv += ::optind; + + // Clear getopt state just in case the test wants to use it. + opterr = old_opterr; + optind = 1; +#if defined(HAVE_OPTRESET) + optreset = 1; +#endif +} + +void +impl::app::usage(std::ostream& os) +{ + assert(inited()); + + std::string args = specific_args(); + if (!args.empty()) + args = " " + args; + os << ui::format_text_with_tag(std::string(m_prog_name) + " [options]" + + args, "Usage: ", false) << "\n\n" + << ui::format_text(m_description) << "\n\n"; + + options_set opts = options(); + assert(!opts.empty()); + os << "Available options:\n"; + size_t coldesc = 0; + for (options_set::const_iterator iter = opts.begin(); + iter != opts.end(); iter++) { + const option& opt = (*iter); + + if (opt.m_argument.length() + 1 > coldesc) + coldesc = opt.m_argument.length() + 1; + } + for (options_set::const_iterator iter = opts.begin(); + iter != opts.end(); iter++) { + const option& opt = (*iter); + + std::string tag = std::string(" -") + opt.m_character; + if (opt.m_argument.empty()) + tag += " "; + else + tag += " " + opt.m_argument + " "; + os << ui::format_text_with_tag(opt.m_description, tag, false, + coldesc + 10) << "\n"; + } + os << "\n"; + + std::string gmp; + if (!m_global_manpage.empty()) + gmp = " and " + m_global_manpage; + os << ui::format_text("For more details please see " + m_manpage + + gmp + ".") + << "\n"; +} + +int +impl::app::run(int argc, char* const* argv) +{ + assert(argc > 0); + assert(argv != NULL); + + m_argc = argc; + m_argv = argv; + + m_argv0 = m_argv[0]; + + m_prog_name = std::strrchr(m_argv[0], '/'); + if (m_prog_name == NULL) + m_prog_name = m_argv[0]; + else + m_prog_name++; + + // Libtool workaround: if running from within the source tree (binaries + // that are not installed yet), skip the "lt-" prefix added to files in + // the ".libs" directory to show the real (not temporary) name. + if (std::strncmp(m_prog_name, "lt-", 3) == 0) + m_prog_name += 3; + + const std::string bug = + std::string("This is probably a bug in ") + m_prog_name + + " or one of the libraries it uses. Please report this problem to " + PACKAGE_BUGREPORT " and provide as many details as possible " + "describing how you got to this condition."; + + int errcode; + try { + int oldargc = m_argc; + + process_options(); + + if (m_hflag) { + if (oldargc != 2) + throw usage_error("-h must be given alone."); + + usage(std::cout); + errcode = EXIT_SUCCESS; + } else + errcode = main(); + } catch (const usage_error& e) { + std::cerr << ui::format_error(m_prog_name, e.what()) << "\n" + << ui::format_info(m_prog_name, std::string("Type `") + + m_prog_name + " -h' for more details.") + << "\n"; + errcode = EXIT_FAILURE; + } catch (const std::runtime_error& e) { + std::cerr << ui::format_error(m_prog_name, std::string(e.what())) + << "\n"; + errcode = EXIT_FAILURE; + } catch (const std::exception& e) { + std::cerr << ui::format_error(m_prog_name, std::string("Caught " + "unexpected error: ") + e.what() + "\n" + bug) << "\n"; + errcode = EXIT_FAILURE; + } catch (...) { + std::cerr << ui::format_error(m_prog_name, std::string("Caught " + "unknown error\n") + bug) << "\n"; + errcode = EXIT_FAILURE; + } + return errcode; +} diff --git a/unit/atf-src/tools/application.hpp b/unit/atf-src/tools/application.hpp new file mode 100644 index 0000000..5a8d57c --- /dev/null +++ b/unit/atf-src/tools/application.hpp @@ -0,0 +1,113 @@ +// +// 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(TOOLS_APPLICATION_HPP) +#define TOOLS_APPLICATION_HPP + +#include <ostream> +#include <set> +#include <stdexcept> +#include <string> + +namespace tools { +namespace application { + +// ------------------------------------------------------------------------ +// The "usage_error" class. +// ------------------------------------------------------------------------ + +class usage_error : public std::runtime_error { + char m_text[4096]; + +public: + usage_error(const char*, ...) throw(); + ~usage_error(void) throw(); + + const char* what(void) const throw(); +}; + +// ------------------------------------------------------------------------ +// The "option" class. +// ------------------------------------------------------------------------ + +class option { + char m_character; + std::string m_argument; + std::string m_description; + + friend class app; + +public: + option(char, const std::string&, const std::string&); + + bool operator<(const option&) const; +}; + +// ------------------------------------------------------------------------ +// The "app" class. +// ------------------------------------------------------------------------ + +class app { + bool m_hflag; + + void process_options(void); + void usage(std::ostream&); + + bool inited(void); + +protected: + typedef std::set< option > options_set; + + int m_argc; + char* const* m_argv; + + const char* m_argv0; + const char* m_prog_name; + std::string m_description; + std::string m_manpage, m_global_manpage; + + options_set options(void); + + // To be redefined. + virtual std::string specific_args(void) const; + virtual options_set specific_options(void) const; + virtual void process_option(int, const char*); + virtual int main(void) = 0; + +public: + app(const std::string&, const std::string&, const std::string&); + virtual ~app(void); + + int run(int, char* const*); +}; + +} // namespace application +} // namespace tools + +#endif // !defined(TOOLS_APPLICATION_HPP) diff --git a/unit/atf-src/tools/application_test.cpp b/unit/atf-src/tools/application_test.cpp new file mode 100644 index 0000000..a9013cd --- /dev/null +++ b/unit/atf-src/tools/application_test.cpp @@ -0,0 +1,94 @@ +// +// 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 <unistd.h> +} + +#include <atf-c++.hpp> + +#include "application.hpp" + +class getopt_app : public tools::application::app { +public: + getopt_app(void) : app("description", "manpage", "other") {} + + int main(void) + { + // Provide an option that is unknown to the application driver and + // one that is, together with an argument that would be swallowed by + // the test program option if it were recognized. + int argc = 4; + char arg1[] = "progname"; + char arg2[] = "-Z"; + char arg3[] = "-s"; + char arg4[] = "foo"; + char *const argv[] = { arg1, arg2, arg3, arg4, NULL }; + + int ch; + bool zflag; + + // Given that this obviously is an application, and that we used the + // same driver to start, we can test getopt(3) right here without doing + // any fancy stuff. + zflag = false; + while ((ch = ::getopt(argc, argv, ":Z")) != -1) { + switch (ch) { + case 'Z': + zflag = true; + break; + + case '?': + default: + if (optopt != 's') + ATF_FAIL("Unexpected unknown option found"); + } + } + + ATF_REQUIRE(zflag); + ATF_REQUIRE_EQ(1, argc - optind); + ATF_REQUIRE_EQ(std::string("foo"), argv[optind]); + + return 0; + } +}; + +ATF_TEST_CASE_WITHOUT_HEAD(getopt); +ATF_TEST_CASE_BODY(getopt) +{ + int argc = 1; + char arg1[] = "progname"; + char *const argv[] = { arg1, NULL }; + ATF_REQUIRE_EQ(0, getopt_app().run(argc, argv)); +} + +ATF_INIT_TEST_CASES(tcs) +{ + ATF_ADD_TEST_CASE(tcs, getopt); +} diff --git a/unit/atf-src/tools/atf-config.1 b/unit/atf-src/tools/atf-config.1 new file mode 100644 index 0000000..deae6f6 --- /dev/null +++ b/unit/atf-src/tools/atf-config.1 @@ -0,0 +1,184 @@ +.\" +.\" 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 March 14, 2009 +.Dt ATF-CONFIG 1 +.Os +.Sh NAME +.Nm atf-config +.Nd queries static configuration information of ATF +.Sh SYNOPSIS +.Nm +.Op Fl t +.Op Ar var1 Op Ar .. varN +.Nm +.Fl h +.Sh DESCRIPTION +.Nm +is a utility that queries static configuration information of ATF. +Static configuration refers to all those values for settings that +were built into the ATF binaries at build time. +.Pp +In the first synopsis form, +.Nm +will print variable-value pairs for all built-in static variables if +no variable names are provided as arguments. +If any is provided, it will only print the variable-value pairs for +those variables. +The output of the utility does not use the +.Sq = +symbol to separate the variable name from its corresponding value in +an attempt to avoid sourcing the output in shell scripts or Makefiles. +If you need to do that, the +.Fl t +flag allows you to query the value of individual variables without any +surrounding text. +.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 flag +.It Fl h +Shows a short summary of all available options and their purpose. +.It Fl t +Changes the output of the utility to show the variable values, one +per line, without the variable names. +.El +.Ss Static configuration variables +The following list describes all the variables that are part of ATF's +static configuration: +.Bl -tag -width atfXbuildXcppflagsXX +.It Va atf_arch +The architecture name detected by ATF. +This is derived from +.Va atf_machine +because it is a subset of it. +Given that this name might be misdetected, it is provided to the user +as a configuration variable so that he can fix its value temporarily +until a real fix is incorporated into mainstream sources. +.It Va atf_build_cc +The C compiler used by the ATF checks that provide build-time tests. +.It Va atf_build_cflags +The C compiler flags used by the ATF checks that provide build-time tests. +.It Va atf_build_cpp +The C/C++ preprocessor used by the ATF checks that provide build-time tests. +.It Va atf_build_cppflags +The C/C++ preprocessor flags used by the ATF checks that provide build-time +tests. +.It Va atf_build_cxx +The C++ compiler used by the ATF checks that provide build-time tests. +.It Va atf_build_cxxflags +The C++ compiler flags used by the ATF checks that provide build-time tests. +.It Va atf_confdir +The path to the directory that contains the system-wide configuration +files for ATF. +.It Va atf_includedir +The path to the directory that contains the ATF header files. +.It Va atf_libdir +The path to the directory that contains the ATF libraries. +.It Va atf_libexecdir +The path to the directory that contains the auxiliary utilities of ATF, +used internally by the public tools. +.It Va atf_machine +The machine type name detected by ATF. +This should not be tunable but is provided for symmetry with +.Va atf_arch . +.It Va atf_pkgdatadir +The path to the directory that contains the files that form the ATF's +shell-scripting library. +.It Va atf_shell +The path to the shell interpreter that will be used by ATF. +.It Va atf_workdir +The path to the temporary directory that the utilities and the test +programs will use to store temporary files in. +.El +.Sh ENVIRONMENT +Every variable that is part of the static configuration can be +overridden at run-time by defining an environment variable. +This environment variable has the exact same name as the one shown by +.Nm +except that the name is all composed of uppercase letters. +.Pp +In general, empty values in the environment will be ignored unless +otherwise noted below. +.Pp +The recognized environment variables are: +.Bl -tag -width ATFXBUILDXCPPFLAGSXX +.It Ev ATF_ARCH +Overrides the built-in value of +.Va atf_arch . +.It Ev ATF_BUILD_CC +Overrides the built-in value of +.Va atf_build_cc . +.It Ev ATF_BUILD_CFLAGS +Overrides the built-in value of +.Va atf_build_cflags . +Empty values are allowed. +.It Ev ATF_BUILD_CPP +Overrides the built-in value of +.Va atf_build_cpp . +.It Ev ATF_BUILD_CPPFLAGS +Overrides the built-in value of +.Va atf_build_cppflags . +Empty values are allowed. +.It Ev ATF_BUILD_CXX +Overrides the built-in value of +.Va atf_build_cxx . +.It Ev ATF_BUILD_CXXFLAGS +Overrides the built-in value of +.Va atf_build_cxxflags . +Empty values are allowed. +.It Ev ATF_CONFDIR +Overrides the built-in value of +.Va atf_confdir . +.It Ev ATF_INCLUDEDIR +Overrides the built-in value of +.Va atf_includedir . +.It Ev ATF_LIBDIR +Overrides the built-in value of +.Va atf_libdir . +.It Ev ATF_LIBEXECDIR +Overrides the built-in value of +.Va atf_libexecdir . +.It Ev ATF_MACHINE +Overrides the built-in value of +.Va atf_machine . +.It Ev ATF_PKGDATADIR +Overrides the built-in value of +.Va atf_pkgdatadir . +.It Ev ATF_SHELL +Overrides the built-in value of +.Va atf_shell . +.It Ev ATF_WORKDIR +Overrides the built-in value of +.Va atf_workdir . +.El +.Sh SEE ALSO +.Xr atf 7 diff --git a/unit/atf-src/tools/atf-config.cpp b/unit/atf-src/tools/atf-config.cpp new file mode 100644 index 0000000..4d82d55 --- /dev/null +++ b/unit/atf-src/tools/atf-config.cpp @@ -0,0 +1,140 @@ +// +// 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 <cstdlib> +#include <iostream> +#include <map> +#include <string> + +#include "application.hpp" +#include "config.hpp" +#include "defs.hpp" + +class atf_config : public tools::application::app { + static const char* m_description; + + bool m_tflag; + + void process_option(int, const char*); + std::string specific_args(void) const; + options_set specific_options(void) const; + + std::string format_var(const std::string&, const std::string&); + +public: + atf_config(void); + + int main(void); +}; + +const char* atf_config::m_description = + "atf-config is a tool that queries the value of several " + "installation-specific configuration values of the atf. " + "It can be used by external tools to discover where specific " + "internal atf files are installed."; + +atf_config::atf_config(void) : + app(m_description, "atf-config(1)", "atf(7)"), + m_tflag(false) +{ +} + +void +atf_config::process_option(int ch, const char* arg ATF_DEFS_ATTRIBUTE_UNUSED) +{ + switch (ch) { + case 't': + m_tflag = true; + break; + + default: + std::abort(); + } +} + +std::string +atf_config::specific_args(void) + const +{ + return "[var1 [.. varN]]"; +} + +atf_config::options_set +atf_config::specific_options(void) + const +{ + using tools::application::option; + options_set opts; + opts.insert(option('t', "", "Terse output: show values only")); + return opts; +} + +std::string +atf_config::format_var(const std::string& name, const std::string& val) +{ + std::string str; + + if (m_tflag) + str = val; + else + str = name + " : " + val; + + return str; +} + +int +atf_config::main(void) +{ + if (m_argc < 1) { + std::map< std::string, std::string > cv = tools::config::get_all(); + + for (std::map< std::string, std::string >::const_iterator iter = + cv.begin(); iter != cv.end(); iter++) + std::cout << format_var((*iter).first, (*iter).second) << "\n"; + } else { + for (int i = 0; i < m_argc; i++) { + if (!tools::config::has(m_argv[i])) + throw std::runtime_error(std::string("Unknown variable `") + + m_argv[i] + "'"); + } + + for (int i = 0; i < m_argc; i++) { + std::cout << format_var(m_argv[i], tools::config::get(m_argv[i])) + << "\n"; + } + } + + return EXIT_SUCCESS; +} + +int +main(int argc, char* const* argv) +{ + return atf_config().run(argc, argv); +} diff --git a/unit/atf-src/tools/atf-config_test.sh b/unit/atf-src/tools/atf-config_test.sh new file mode 100644 index 0000000..5d6505a --- /dev/null +++ b/unit/atf-src/tools/atf-config_test.sh @@ -0,0 +1,180 @@ +# +# 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. +# + +all_vars="atf_arch \ + atf_build_cc \ + atf_build_cflags \ + atf_build_cpp \ + atf_build_cppflags \ + atf_build_cxx \ + atf_build_cxxflags \ + atf_confdir \ + atf_includedir \ + atf_libdir \ + atf_libexecdir \ + atf_machine \ + atf_pkgdatadir \ + atf_shell \ + atf_workdir" +all_vars_no=15 + +atf_test_case list_all +list_all_head() +{ + atf_set "descr" "Tests that at atf-config prints all expected" \ + "variables, and not more" +} +list_all_body() +{ + atf_check -s eq:0 -o save:stdout -e empty atf-config + atf_check -s eq:0 -o empty -e empty \ + test "$(wc -l stdout | awk '{ print $1 }')" = "${all_vars_no}" + for v in ${all_vars}; do + atf_check -s eq:0 -o ignore -e empty grep "${v}" stdout + done +} + +atf_test_case query_one +query_one_head() +{ + atf_set "descr" "Tests that querying a single variable works" +} +query_one_body() +{ + for v in ${all_vars}; do + atf_check -s eq:0 -o save:stdout -o match:"${v}" -e empty \ + atf-config "${v}" + atf_check -s eq:0 -o empty -e empty \ + test "$(wc -l stdout | awk '{ print $1 }')" = 1 + done +} + +atf_test_case query_one_terse +query_one_terse_head() +{ + atf_set "descr" "Tests that querying a single variable in terse mode" \ + "works" +} +query_one_terse_body() +{ + for v in ${all_vars}; do + atf_check -s eq:0 -o save:stdout -o match:"${v}" -e empty \ + atf-config "${v}" + atf_check -s eq:0 -o empty -e empty \ + test "$(wc -l stdout | awk '{ print $1 }')" = 1 + atf_check -s eq:0 -o save:stdout -e empty cut -d ' ' -f 3- stdout + atf_check -s eq:0 -o empty -e empty mv stdout expout + atf_check -s eq:0 -o file:expout -e empty atf-config -t "${v}" + done +} + +atf_test_case query_multiple +query_multiple_head() +{ + atf_set "descr" "Tests that querying multiple variables works" +} +query_multiple_body() +{ + atf_check -s eq:0 -o save:stdout -o match:'atf_libexecdir' \ + -o match:'atf_shell' -e empty atf-config atf_libexecdir atf_shell + atf_check -s eq:0 -o empty -e empty \ + test "$(wc -l stdout | awk '{ print $1 }')" = 2 +} + +atf_test_case query_unknown +query_unknown_head() +{ + atf_set "descr" "Tests that querying an unknown variable delivers" \ + "the correct error" +} +query_unknown_body() +{ + atf_check -s eq:1 -o empty -e match:'Unknown variable.*non_existent' \ + atf-config non_existent +} + +atf_test_case query_mixture +query_mixture_head() +{ + atf_set "descr" "Tests that querying a known and an unknown variable" \ + "delivers the correct error" +} +query_mixture_body() +{ + for v in ${all_vars}; do + atf_check -s eq:1 -o empty -e match:'Unknown variable.*non_existent' \ + atf-config "${v}" non_existent + atf_check -s eq:1 -o empty -e match:'Unknown variable.*non_existent' \ + atf-config non_existent "${v}" + done +} + +atf_test_case override_env +override_env_head() +{ + atf_set "descr" "Tests that build-time variables can be overriden" \ + "through their corresponding environment variables" +} +override_env_body() +{ + for v in ${all_vars}; do + V=$(echo ${v} | tr '[a-z]' '[A-Z]') + atf_check -s eq:0 -o save:stdout -e empty -x "${V}=testval atf-config" + atf_check -s eq:0 -o empty -e empty mv stdout all + + atf_check -s eq:0 -o save:stdout -e empty grep "^${v} : " all + atf_check -s eq:0 -o empty -e empty mv stdout affected + atf_check -s eq:0 -o save:stdout -e empty grep -v "^${v} : " all + atf_check -s eq:0 -o empty -e empty mv stdout unaffected + + atf_check -s eq:0 -o empty -e empty \ + test "$(wc -l affected | awk '{ print $1 }')" = 1 + atf_check -s eq:0 -o empty -e empty \ + test "$(wc -l unaffected | awk '{ print $1 }')" = \ + "$((${all_vars_no} -1))" + + atf_check -s eq:0 -o ignore -e empty grep "^${v} : testval$" affected + atf_check -s eq:1 -o empty -e empty grep ' : testval$' unaffected + done +} + +atf_init_test_cases() +{ + atf_add_test_case list_all + + atf_add_test_case query_one + atf_add_test_case query_one_terse + atf_add_test_case query_multiple + atf_add_test_case query_unknown + atf_add_test_case query_mixture + + atf_add_test_case override_env +} + +# vim: syntax=sh:expandtab:shiftwidth=4:softtabstop=4 diff --git a/unit/atf-src/tools/atf-formats.5 b/unit/atf-src/tools/atf-formats.5 new file mode 100644 index 0000000..bb919f4 --- /dev/null +++ b/unit/atf-src/tools/atf-formats.5 @@ -0,0 +1,231 @@ +.\" +.\" 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 December 20, 2011 +.Dt ATF-FORMATS 5 +.Os +.Sh NAME +.Nm atf-formats +.Nd machine-parseable data formats used by ATF +.Sh DESCRIPTION +This manual page describes the multiple data formats used in ATF. +These formats affect configuration files, control files and any data that +is externalized or internalized by the tools. +.Pp +Data files are always organized as follows: +.Bd -literal -offset indent +Header1: Value1 \\ + ... | head +HeaderN: ValueN / + mandatory blank line +Free-form text contents \\ + ... | body + ... / +.Ed +.Pp +A file must always contain a +.Sq Content-Type +header and must always separate that header from the body with a blank +line, even if the body is empty. +.Pp +The +.Sq Content-Type +is always of the form: +.Bd -literal -offset indent +Content-Type: application/X-atf-<subtype>; version="<version>" +.Ed +.Pp +where +.Sq subtype +indicates the specific file format and +.Sq version +its format version. +This header must be the first one of the file. +.Pp +The main purpose of the +.Sq Content-Type +header, aside from determining the format used in the file, is to allow +future changes to a given format. +Whenever an incompatible change is made, the version is bumped by one. +By keeping the header in the first line, future versions may even remove +the need for such a header -- e.g. if some format was replaced by XML +files, which have their own mandatory header. +.Pp +The rest of this document details the different format types. +.Ss Format: application/X-atf-atffile, version: 1 +Atffiles are logically divided into three sections: +.Bl -bullet +.It +Test programs: the list of test programs that define the test suite +described by the Atffile. +.It +Meta-data properties: these define some constant values applicable to +all the test programs defined in the file. +In some sense they define the properties that describe the test suite. +.It +Configuration variables: defaults for configuration variables that +can be overridden through configuration files or the command line. +.El +.Pp +The grammar for Atffiles is the following: +.Bd -literal -offset indent +DATA ::= ( ( CONF | PROP | TP )? COMMENT? NEWLINE )* EOF +CONF ::= 'conf:' WORD EQUAL STRING +PROP ::= 'prop:' WORD EQUAL STRING +TP ::= TPFILE | TPGLOB +TPFILE ::= 'tp: ' STRING +TPGLOB ::= 'tp-glob: ' STRING +STRING ::= WORD | '"' WORD* '"' +.Ed +.Pp +The meaning of the constructions above is: +.Bl -tag -width TPGLOBXX +.It CONF +Definition of a configuration variable. +.It PROP +Definition of a meta-data property. +.It TPFILE +Addition of a test program into the test suite. +The string is taken literally as the program's name, and this program +must exist. +.It TPGLOB +Addition of multiple test programs into the test suite. +The string is taken as a glob pattern, which may have or not have any +matches in the current directory. +.El +.Pp +An example: +.Bd -literal -offset indent +prop: test-suite = utilities + +conf: unprivileged-user = nobody + +tp: t_cp +tp: t_mv +tp: t_df +tp-glob: t_dir_* +.Ed +.Ss Format: application/X-atf-config, version: 1 +Configuration files are very simple: they only contain a list of variable +name/variable value pairs. +Their grammar is: +.Bd -literal -offset indent +DATA ::= ( VAR? COMMENT? NEWLINE )* EOF +VAR ::= WORD EQUAL STRING +COMMENT ::= HASH WORD* +STRING ::= WORD | '"' WORD* '"' +.Ed +.Pp +An example: +.Bd -literal -offset indent +# This is the system-wide configuration file for ATF. +# The above and this line are comments placed on their own line. + +var1 = this is a variable value +var2 = this is another one # Optional comment at the end. +.Ed +.Ss Format: application/X-atf-tps, version: 3 +The +.Sq application/X-atf-tps +format multiplexes the standard output, standard error and results output +streams from multiple test programs into a single data file. +This format is used by +.Xr atf-run 1 +to report the execution of several test programs and is later parsed by +.Xr atf-report 1 +to inform the user of this process. +It has the following grammar: +.Bd -literal -offset indent +DATA ::= INFO* TPS-COUNT TP-STANZA* INFO* EOF +INFO ::= 'info' COLON STRING COMMA STRING NEWLINE +TPS-COUNT ::= 'tps-count' COLON POSITIVE-NUMBER NEWLINE +TP-STANZA ::= TP-START TC-STANZA* TC-END +TP-START ::= 'tp-start' COLON TIMESTAMP COMMA STRING COMMA + POSITIVE-NUMBER NEWLINE +TP-END ::= 'tc-end' COLON TIMESTAMP COMMA STRING (COMMA STRING)? +TC-STANZA ::= TC-START (TC-SO | TC-SE)* TC-END +TC-START ::= 'tc-start' COLON TIMESTAMP COMMA STRING NEWLINE +TC-SO ::= 'tc-so' COLON STRING NEWLINE +TC-SE ::= 'tc-se' COLON STRING NEWLINE +TC-END ::= 'tc-end' COLON TIMESTAMP COMMA STRING COMMA TCR NEWLINE +TCR ::= 'passed' | ('failed' | 'skipped') COMMA STRING +TIMESTAMP ::= [0-9]+.[0-9]+ +.Ed +.Pp +The meaning of the constructions above is: +.Bl -tag -width TPSXCOUNTXX +.It TPS-COUNT +Indicates the number of test programs that will be executed. +There will be this exact amount of +.Sq TP-STANZA +constructions following it. +.It TP-START +Indicates the beginning of a test program. +This includes the program's name and the amount of test cases that +will follow. +.It TP-END +Indicates the completion of a test program. +This is followed by the program's name and, if the program ended +prematurely, an error message indicating the reason of its failure. +A successful execution of a test program (regardless of the status of its +test cases) must not be accompanied by any reason. +.It TC-START +Indicates the beginning of a test case. +This is accompanied by the test case's name. +.It TC-SO +Contains a text line sent to the standard output stream during the +execution of the test case. +Leading and trailing space is preserved. +.It TC-SE +Contains a text line sent to the standard error stream during the +execution of the test case. +Leading and trailing space is preserved. +.It TC-END +Indicates the completion of a test case. +This is accompanied by the test case's name, its result and the reason +associated with this result (if applicable). +.El +.Pp +An example: +.Bd -literal -offset indent +tps-count: 2 +tp-start: calculator, 1324318951.838923, 2 +tc-start: add, 1324318951.839101 +tc-end: add, 1324318951.839500, passed +tc-start: subtract, 1324318951.840001 +tc-so: 3-2 expected to return 1 but got 0 +tc-end: subtract, 1324318952.000123, failed, Calculated an unexpected value +tp-end: calculator, 1324318952.002301 +tp-start: files, 1, 1324318952.502348 +tc-start: copy, 1324318952.508291 +tc-se: could not find the cp(1) utility +tc-end: copy, 1324318953.203145, skipped +tp-end: files, 1324318953.203800 +.Ed +.Sh SEE ALSO +.Xr atf 7 diff --git a/unit/atf-src/tools/atf-report.1 b/unit/atf-src/tools/atf-report.1 new file mode 100644 index 0000000..df03af0 --- /dev/null +++ b/unit/atf-src/tools/atf-report.1 @@ -0,0 +1,168 @@ +.\" +.\" 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 December 16, 2011 +.Dt ATF-REPORT 1 +.Os +.Sh NAME +.Nm atf-report +.Nd transforms the output of atf-run to different formats +.Sh SYNOPSIS +.Nm +.Op Fl o Ar fmt1:path1 Op .. Fl o Ar fmtN:pathN +.Nm +.Fl h +.Sh DESCRIPTION +.Nm +reads the output of +.Nm atf-run +and transforms it to different formats. +Some of these are user-friendly and others are machine-parseable, which +opens a wide range of possibilities to analyze the results of a test +suite's execution. +See +.Sx Output formats +below for more details on which these formats are. +.Pp +In the first synopsis form, +.Nm +reads the output of +.Nm atf-run +through its standard input and, if no +.Fl o +options are given, prints a user-friendly report on its standard +output using the +.Sq ticker +format. +If +.Fl o +options are provided (more than one are allowed), they specify the complete +list of reports to generate. +They are all generated simultaneously, and for obvious reasons, two reports +cannot be written to the same file. +Note that the default output is suppressed when +.Fl o +is provided. +.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 XoXfmtXpathXX +.It Fl h +Shows a short summary of all available options and their purpose. +.It Fl o Ar fmt:path +Adds a new output format. +.Ar fmt +is one of the formats described later on in +.Sx Output formats . +.Ar path +specifies where the report will be written to. +Depending on the chosen format, this may refer to a single file or to +a directory. +For those formats that write to a single file, specifying a +.Sq - +as the path will redirect the report to the standard output. +.El +.Ss Output formats +The following output formats are allowed: +.Bl -tag -width tickerXX +.It csv +A machine-parseable Comma-Separated Values (CSV) file. +This file contains the results for all test cases and test programs. +Test cases are logged using the following syntax: +.Bd -literal -offset indent +tc, duration, test-program, test-case, result[, reason] +.Ed +.Pp +The +.Sq result +field for test cases is always one of +.Sq passed , +.Sq skipped +or +.Sq failed . +The last two are always followed by a reason. +.Pp +Test programs are logged with the following syntax: +.Bd -literal -offset indent +tp, duration, test-program, result[, reason] +.Ed +.Pp +In this case, the +.Sq result +can be one of: +.Sq passed , +which denotes test programs that ran without any failure; +.Sq failed , +which refers to test programs in which one or more test cases failed; +or +.Sq bogus , +which mentions those test programs that failed to execute by some reason. +The reason field is only available in the last case. +.Pp +The time required to execute each test case and test program is +also provided. +You should not rely on the order of the entries in the resulting output. +.It ticker +A user-friendly report that shows the progress of the test suite's +execution as it operates. +This type of report should always be redirected to a virtual terminal, +not a file, as it may use control sequences that will make the output +unreadable in regular files. +.It xml +A report contained in a single XML file. +Ideal for later processing with +.Xr xsltproc 1 +to generate nice HTML reports. +.El +.Sh EXAMPLES +The most simple way of running a test suite is to pipe the output of +.Nm atf-run +through +.Nm +without any additional flags. +This will use the default output format, which is suitable to most users: +.Bd -literal -offset indent +atf-run | atf-report +.Ed +.Pp +In some situations, it may be interesting to get a machine-parseable file +aside from the standard report. +This can be done as follows: +.Bd -literal -offset indent +atf-run | atf-report -o csv:testsuite.csv -o ticker:- +.Ed +.Pp +Or if the standard report is not desired, thus achieving completely silent +operation: +atf-run | atf-report -o csv:testsuite.csv +.Sh SEE ALSO +.Xr atf-run 1 , +.Xr atf 7 diff --git a/unit/atf-src/tools/atf-report.cpp b/unit/atf-src/tools/atf-report.cpp new file mode 100644 index 0000000..cb065f9 --- /dev/null +++ b/unit/atf-src/tools/atf-report.cpp @@ -0,0 +1,710 @@ +// +// 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/time.h> +} + +#include <cctype> +#include <cstdlib> +#include <fstream> +#include <iomanip> +#include <iostream> +#include <memory> +#include <sstream> +#include <utility> +#include <vector> + +#include "application.hpp" +#include "defs.hpp" +#include "fs.hpp" +#include "reader.hpp" +#include "text.hpp" +#include "ui.hpp" + +typedef std::auto_ptr< std::ostream > ostream_ptr; + +static ostream_ptr +open_outfile(const tools::fs::path& path) +{ + ostream_ptr osp; + if (path.str() == "-") + osp = ostream_ptr(new std::ofstream("/dev/stdout")); + else + osp = ostream_ptr(new std::ofstream(path.c_str())); + if (!(*osp)) + throw std::runtime_error("Could not create file " + path.str()); + return osp; +} + +static std::string +format_tv(struct timeval* tv) +{ + std::ostringstream output; + output << static_cast< long >(tv->tv_sec) << '.' + << std::setfill('0') << std::setw(6) + << static_cast< long >(tv->tv_usec); + return output.str(); +} + +// ------------------------------------------------------------------------ +// The "writer" interface. +// ------------------------------------------------------------------------ + +//! +//! \brief A base class that defines an output format. +//! +//! The writer base class defines a generic interface to output formats. +//! This is meant to be subclassed, and each subclass can redefine any +//! method to format the information as it wishes. +//! +//! This class is not tied to a output stream nor a file because, depending +//! on the output format, we will want to write to a single file or to +//! multiple ones. +//! +class writer { +public: + writer(void) {} + virtual ~writer(void) {} + + virtual void write_info(const std::string&, const std::string&) {} + virtual void write_ntps(size_t) {} + virtual void write_tp_start(const std::string&, size_t) {} + virtual void write_tp_end(struct timeval*, const std::string&) {} + virtual void write_tc_start(const std::string&) {} + virtual void write_tc_stdout_line(const std::string&) {} + virtual void write_tc_stderr_line(const std::string&) {} + virtual void write_tc_end(const std::string&, struct timeval*, + const std::string&) {} + virtual void write_eof(void) {} +}; + +// ------------------------------------------------------------------------ +// The "csv_writer" class. +// ------------------------------------------------------------------------ + +//! +//! \brief A very simple plain-text output format. +//! +//! The csv_writer class implements a very simple plain-text output +//! format that summarizes the results of each executed test case. The +//! results are meant to be easily parseable by third-party tools, hence +//! they are formatted as a CSV file. +//! +class csv_writer : public writer { + ostream_ptr m_os; + bool m_failed; + + std::string m_tpname; + std::string m_tcname; + +public: + csv_writer(const tools::fs::path& p) : + m_os(open_outfile(p)) + { + } + + virtual + void + write_tp_start(const std::string& name, + size_t ntcs ATF_DEFS_ATTRIBUTE_UNUSED) + { + m_tpname = name; + m_failed = false; + } + + virtual + void + write_tp_end(struct timeval* tv, const std::string& reason) + { + const std::string timestamp = format_tv(tv); + + if (!reason.empty()) + (*m_os) << "tp, " << timestamp << ", " << m_tpname << ", bogus, " + << reason << "\n"; + else if (m_failed) + (*m_os) << "tp, " << timestamp << ", "<< m_tpname << ", failed\n"; + else + (*m_os) << "tp, " << timestamp << ", "<< m_tpname << ", passed\n"; + } + + virtual + void + write_tc_start(const std::string& name) + { + m_tcname = name; + } + + virtual + void + write_tc_end(const std::string& state, struct timeval* tv, + const std::string& reason) + { + std::string str = m_tpname + ", " + m_tcname + ", " + state; + if (!reason.empty()) + str += ", " + reason; + (*m_os) << "tc, " << format_tv(tv) << ", " << str << "\n"; + + if (state == "failed") + m_failed = true; + } +}; + +// ------------------------------------------------------------------------ +// The "ticker_writer" class. +// ------------------------------------------------------------------------ + +//! +//! \brief A console-friendly output format. +//! +//! The ticker_writer class implements a formatter that is user-friendly +//! in the sense that it shows the execution of test cases in an easy to +//! read format. It is not meant to be parseable and its format can +//! freely change across releases. +//! +class ticker_writer : public writer { + ostream_ptr m_os; + + size_t m_curtp, m_ntps; + size_t m_tcs_passed, m_tcs_failed, m_tcs_skipped, m_tcs_expected_failures; + std::string m_tcname, m_tpname; + std::vector< std::string > m_failed_tcs; + std::map< std::string, std::string > m_expected_failures_tcs; + std::vector< std::string > m_failed_tps; + + void + write_info(const std::string& what, const std::string& val) + { + if (what == "tests.root") { + (*m_os) << "Tests root: " << val << "\n\n"; + } + } + + void + write_ntps(size_t ntps) + { + m_curtp = 1; + m_tcs_passed = 0; + m_tcs_failed = 0; + m_tcs_skipped = 0; + m_tcs_expected_failures = 0; + m_ntps = ntps; + } + + void + write_tp_start(const std::string& tp, size_t ntcs) + { + using tools::text::to_string; + using tools::ui::format_text; + + m_tpname = tp; + + (*m_os) << format_text(tp + " (" + to_string(m_curtp) + + "/" + to_string(m_ntps) + "): " + + to_string(ntcs) + " test cases") + << "\n"; + (*m_os).flush(); + } + + void + write_tp_end(struct timeval* tv, const std::string& reason) + { + using tools::ui::format_text_with_tag; + + m_curtp++; + + if (!reason.empty()) { + (*m_os) << format_text_with_tag("BOGUS TEST PROGRAM: Cannot " + "trust its results because " + "of `" + reason + "'", + m_tpname + ": ", false) + << "\n"; + m_failed_tps.push_back(m_tpname); + } + (*m_os) << "[" << format_tv(tv) << "s]\n\n"; + (*m_os).flush(); + + m_tpname.clear(); + } + + void + write_tc_start(const std::string& tcname) + { + m_tcname = tcname; + + (*m_os) << " " + tcname + ": "; + (*m_os).flush(); + } + + void + write_tc_end(const std::string& state, struct timeval* tv, + const std::string& reason) + { + std::string str; + + (*m_os) << "[" << format_tv(tv) << "s] "; + + if (state == "expected_death" || state == "expected_exit" || + state == "expected_failure" || state == "expected_signal" || + state == "expected_timeout") { + str = "Expected failure: " + reason; + m_tcs_expected_failures++; + m_expected_failures_tcs[m_tpname + ":" + m_tcname] = reason; + } else if (state == "failed") { + str = "Failed: " + reason; + m_tcs_failed++; + m_failed_tcs.push_back(m_tpname + ":" + m_tcname); + } else if (state == "passed") { + str = "Passed."; + m_tcs_passed++; + } else if (state == "skipped") { + str = "Skipped: " + reason; + m_tcs_skipped++; + } else + std::abort(); + + // XXX Wrap text. format_text_with_tag does not currently allow + // to specify the current column, which is needed because we have + // already printed the tc's name. + (*m_os) << str << '\n'; + + m_tcname = ""; + } + + static void + write_expected_failures(const std::map< std::string, std::string >& xfails, + std::ostream& os) + { + using tools::ui::format_text; + using tools::ui::format_text_with_tag; + + os << format_text("Test cases for known bugs:") << "\n"; + + for (std::map< std::string, std::string >::const_iterator iter = + xfails.begin(); iter != xfails.end(); iter++) { + const std::string& name = (*iter).first; + const std::string& reason = (*iter).second; + + os << format_text_with_tag(reason, " " + name + ": ", false) + << "\n"; + } + } + + void + write_eof(void) + { + using tools::text::join; + using tools::text::to_string; + using tools::ui::format_text; + using tools::ui::format_text_with_tag; + + if (!m_failed_tps.empty()) { + (*m_os) << format_text("Failed (bogus) test programs:") + << "\n"; + (*m_os) << format_text_with_tag(join(m_failed_tps, ", "), + " ", false) << "\n\n"; + } + + if (!m_expected_failures_tcs.empty()) { + write_expected_failures(m_expected_failures_tcs, *m_os); + (*m_os) << "\n"; + } + + if (!m_failed_tcs.empty()) { + (*m_os) << format_text("Failed test cases:") << "\n"; + (*m_os) << format_text_with_tag(join(m_failed_tcs, ", "), + " ", false) << "\n\n"; + } + + (*m_os) << format_text("Summary for " + to_string(m_ntps) + + " test programs:") << "\n"; + (*m_os) << format_text_with_tag(to_string(m_tcs_passed) + + " passed test cases.", + " ", false) << "\n"; + (*m_os) << format_text_with_tag(to_string(m_tcs_failed) + + " failed test cases.", + " ", false) << "\n"; + (*m_os) << format_text_with_tag(to_string(m_tcs_expected_failures) + + " expected failed test cases.", + " ", false) << "\n"; + (*m_os) << format_text_with_tag(to_string(m_tcs_skipped) + + " skipped test cases.", + " ", false) << "\n"; + } + +public: + ticker_writer(const tools::fs::path& p) : + m_os(open_outfile(p)) + { + } +}; + +// ------------------------------------------------------------------------ +// The "xml" class. +// ------------------------------------------------------------------------ + +//! +//! \brief A single-file XML output format. +//! +//! The xml_writer class implements a formatter that prints the results +//! of test cases in an XML format easily parseable later on by other +//! utilities. +//! +class xml_writer : public writer { + ostream_ptr m_os; + + std::string m_tcname, m_tpname; + + static + std::string + attrval(const std::string& str) + { + return str; + } + + static + std::string + elemval(const std::string& str) + { + std::ostringstream buf; + for (std::string::const_iterator iter = str.begin(); + iter != str.end(); iter++) { + const int character = static_cast< unsigned char >(*iter); + if (character == '&') { + buf << "&"; + } else if (character == '<') { + buf << "<"; + } else if (character == '>') { + buf << ">"; + } else if (std::isalnum(character) || std::ispunct(character) || + std::isspace(character)) { + buf << static_cast< char >(character); + } else { + buf << "&#" << character << ";"; + } + } + return buf.str(); + } + + void + write_info(const std::string& what, const std::string& val) + { + (*m_os) << "<info class=\"" << what << "\">" << val << "</info>\n"; + } + + void + write_tp_start(const std::string& tp, + size_t ntcs ATF_DEFS_ATTRIBUTE_UNUSED) + { + (*m_os) << "<tp id=\"" << attrval(tp) << "\">\n"; + } + + void + write_tp_end(struct timeval* tv, const std::string& reason) + { + if (!reason.empty()) + (*m_os) << "<failed>" << elemval(reason) << "</failed>\n"; + (*m_os) << "<tp-time>" << format_tv(tv) << "</tp-time>"; + (*m_os) << "</tp>\n"; + } + + void + write_tc_start(const std::string& tcname) + { + (*m_os) << "<tc id=\"" << attrval(tcname) << "\">\n"; + } + + void + write_tc_stdout_line(const std::string& line) + { + (*m_os) << "<so>" << elemval(line) << "</so>\n"; + } + + void + write_tc_stderr_line(const std::string& line) + { + (*m_os) << "<se>" << elemval(line) << "</se>\n"; + } + + void + write_tc_end(const std::string& state, struct timeval* tv, + const std::string& reason) + { + std::string str; + + if (state == "expected_death" || state == "expected_exit" || + state == "expected_failure" || state == "expected_signal" || + state == "expected_timeout") { + (*m_os) << "<" << state << ">" << elemval(reason) + << "</" << state << ">\n"; + } else if (state == "passed") { + (*m_os) << "<passed />\n"; + } else if (state == "failed") { + (*m_os) << "<failed>" << elemval(reason) << "</failed>\n"; + } else if (state == "skipped") { + (*m_os) << "<skipped>" << elemval(reason) << "</skipped>\n"; + } else + std::abort(); + (*m_os) << "<tc-time>" << format_tv(tv) << "</tc-time>"; + (*m_os) << "</tc>\n"; + } + + void + write_eof(void) + { + (*m_os) << "</tests-results>\n"; + } + +public: + xml_writer(const tools::fs::path& p) : + m_os(open_outfile(p)) + { + (*m_os) << "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\n" + << "<!DOCTYPE tests-results PUBLIC " + "\"-//NetBSD//DTD ATF Tests Results 0.1//EN\" " + "\"http://www.NetBSD.org/XML/atf/tests-results.dtd\">\n\n" + "<tests-results>\n"; + } +}; + +// ------------------------------------------------------------------------ +// The "converter" class. +// ------------------------------------------------------------------------ + +//! +//! \brief A reader that redirects events to multiple writers. +//! +//! The converter class implements an atf_tps_reader that, for each event +//! raised by the parser, redirects it to multiple writers so that they +//! can reformat it according to their output rules. +//! +class converter : public tools::atf_report::atf_tps_reader { + typedef std::vector< writer* > outs_vector; + outs_vector m_outs; + + void + got_info(const std::string& what, const std::string& val) + { + for (outs_vector::iterator iter = m_outs.begin(); + iter != m_outs.end(); iter++) + (*iter)->write_info(what, val); + } + + void + got_ntps(size_t ntps) + { + for (outs_vector::iterator iter = m_outs.begin(); + iter != m_outs.end(); iter++) + (*iter)->write_ntps(ntps); + } + + void + got_tp_start(const std::string& tp, size_t ntcs) + { + for (outs_vector::iterator iter = m_outs.begin(); + iter != m_outs.end(); iter++) + (*iter)->write_tp_start(tp, ntcs); + } + + void + got_tp_end(struct timeval* tv, const std::string& reason) + { + for (outs_vector::iterator iter = m_outs.begin(); + iter != m_outs.end(); iter++) + (*iter)->write_tp_end(tv, reason); + } + + void + got_tc_start(const std::string& tcname) + { + for (outs_vector::iterator iter = m_outs.begin(); + iter != m_outs.end(); iter++) + (*iter)->write_tc_start(tcname); + } + + void + got_tc_stdout_line(const std::string& line) + { + for (outs_vector::iterator iter = m_outs.begin(); + iter != m_outs.end(); iter++) + (*iter)->write_tc_stdout_line(line); + } + + void + got_tc_stderr_line(const std::string& line) + { + for (outs_vector::iterator iter = m_outs.begin(); + iter != m_outs.end(); iter++) + (*iter)->write_tc_stderr_line(line); + } + + void + got_tc_end(const std::string& state, struct timeval* tv, + const std::string& reason) + { + for (outs_vector::iterator iter = m_outs.begin(); + iter != m_outs.end(); iter++) + (*iter)->write_tc_end(state, tv, reason); + } + + void + got_eof(void) + { + for (outs_vector::iterator iter = m_outs.begin(); + iter != m_outs.end(); iter++) + (*iter)->write_eof(); + } + +public: + converter(std::istream& is) : + tools::atf_report::atf_tps_reader(is) + { + } + + ~converter(void) + { + for (outs_vector::iterator iter = m_outs.begin(); + iter != m_outs.end(); iter++) + delete *iter; + } + + void + add_output(const std::string& fmt, const tools::fs::path& p) + { + if (fmt == "csv") { + m_outs.push_back(new csv_writer(p)); + } else if (fmt == "ticker") { + m_outs.push_back(new ticker_writer(p)); + } else if (fmt == "xml") { + m_outs.push_back(new xml_writer(p)); + } else + throw std::runtime_error("Unknown format `" + fmt + "'"); + } +}; + +// ------------------------------------------------------------------------ +// The "atf_report" class. +// ------------------------------------------------------------------------ + +class atf_report : public tools::application::app { + static const char* m_description; + + typedef std::pair< std::string, tools::fs::path > fmt_path_pair; + std::vector< fmt_path_pair > m_oflags; + + void process_option(int, const char*); + options_set specific_options(void) const; + +public: + atf_report(void); + + int main(void); +}; + +const char* atf_report::m_description = + "atf-report is a tool that parses the output of atf-run and " + "generates user-friendly reports in multiple different formats."; + +atf_report::atf_report(void) : + app(m_description, "atf-report(1)", "atf(7)") +{ +} + +void +atf_report::process_option(int ch, const char* arg) +{ + switch (ch) { + case 'o': + { + std::string str(arg); + std::string::size_type pos = str.find(':'); + if (pos == std::string::npos) + throw std::runtime_error("Syntax error in -o option"); + else { + std::string fmt = str.substr(0, pos); + tools::fs::path path = tools::fs::path(str.substr(pos + 1)); + m_oflags.push_back(fmt_path_pair(fmt, path)); + } + } + break; + + default: + std::abort(); + } +} + +atf_report::options_set +atf_report::specific_options(void) + const +{ + using tools::application::option; + options_set opts; + opts.insert(option('o', "fmt:path", "Adds a new output file; multiple " + "ones can be specified, and a - " + "path means stdout")); + return opts; +} + +int +atf_report::main(void) +{ + if (m_argc > 0) + throw std::runtime_error("No arguments allowed"); + + if (m_oflags.empty()) + m_oflags.push_back(fmt_path_pair("ticker", tools::fs::path("-"))); + + // Look for path duplicates. + std::set< tools::fs::path > paths; + for (std::vector< fmt_path_pair >::const_iterator iter = m_oflags.begin(); + iter != m_oflags.end(); iter++) { + tools::fs::path p = (*iter).second; + if (p == tools::fs::path("/dev/stdout")) + p = tools::fs::path("-"); + if (paths.find(p) != paths.end()) + throw std::runtime_error("The file `" + p.str() + "' was " + "specified more than once"); + paths.insert((*iter).second); + } + + // Generate the output files. + converter cnv(std::cin); + for (std::vector< fmt_path_pair >::const_iterator iter = m_oflags.begin(); + iter != m_oflags.end(); iter++) + cnv.add_output((*iter).first, (*iter).second); + cnv.read(); + + return EXIT_SUCCESS; +} + +int +main(int argc, char* const* argv) +{ + return atf_report().run(argc, argv); +} diff --git a/unit/atf-src/tools/atf-report_test.sh b/unit/atf-src/tools/atf-report_test.sh new file mode 100644 index 0000000..9f0f47f --- /dev/null +++ b/unit/atf-src/tools/atf-report_test.sh @@ -0,0 +1,449 @@ +# +# 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_helpers() +{ + mkdir dir1 + cp $(atf_get_srcdir)/pass_helper dir1/tp1 + cp $(atf_get_srcdir)/fail_helper dir1/tp2 + cp $(atf_get_srcdir)/pass_helper tp3 + cp $(atf_get_srcdir)/fail_helper tp4 + + cat >tp5 <<EOF +#! $(atf-config -t atf_shell) +echo foo +EOF + chmod +x tp5 + + cat >Atffile <<EOF +Content-Type: application/X-atf-atffile; version="1" + +prop: test-suite = atf + +tp: dir1 +tp: tp3 +tp: tp4 +tp: tp5 +EOF + + cat >dir1/Atffile <<EOF +Content-Type: application/X-atf-atffile; version="1" + +prop: test-suite = atf + +tp: tp1 +tp: tp2 +EOF +} + +run_helpers() +{ + mkdir etc + cat >etc/atf-run.hooks <<EOF +#! $(atf-config -t atf_shell) + +info_start_hook() +{ + atf_tps_writer_info "startinfo" "A value" +} + +info_end_hook() +{ + atf_tps_writer_info "endinfo" "Another value" +} +EOF + echo "Using atf-run to run helpers" + ATF_CONFDIR=$(pwd)/etc atf-run >tps.out 2>/dev/null + rm -rf etc +} + +atf_test_case default +default_head() +{ + atf_set "descr" "Checks that the default output uses the ticker" \ + "format" +} +default_body() +{ + create_helpers + run_helpers + + # Check that the default output uses the ticker format. + atf_check -s eq:0 -o match:'test cases' -o match:'Failed test cases' \ + -o match:'Summary for' -e empty -x 'atf-report <tps.out' +} + +# XXX The test for all expect_ values should be intermixed with the other +# tests. However, to do that, we need to migrate to using C helpers for +# simplicity in raising signals... +atf_test_case expect +expect_body() +{ + ln -s "$(atf_get_srcdir)/expect_helpers" . + cat >Atffile <<EOF +Content-Type: application/X-atf-atffile; version="1" + +prop: test-suite = atf + +tp: expect_helpers +EOF + run_helpers + +# NO_CHECK_STYLE_BEGIN + cat >expout <<EOF +tc, #.#, expect_helpers, death_and_exit, expected_death, Exit case +tc, #.#, expect_helpers, death_and_signal, expected_death, Signal case +tc, #.#, expect_helpers, death_but_pass, failed, Test case was expected to terminate abruptly but it continued execution +tc, #.#, expect_helpers, exit_any_and_exit, expected_exit, Call will exit +tc, #.#, expect_helpers, exit_but_pass, failed, Test case was expected to exit cleanly but it continued execution +tc, #.#, expect_helpers, exit_code_and_exit, expected_exit, Call will exit +tc, #.#, expect_helpers, fail_and_fail_check, expected_failure, And fail again: 2 checks failed as expected; see output for more details +tc, #.#, expect_helpers, fail_and_fail_requirement, expected_failure, Fail reason: The failure +tc, #.#, expect_helpers, fail_but_pass, failed, Test case was expecting a failure but none were raised +tc, #.#, expect_helpers, pass_and_pass, passed +tc, #.#, expect_helpers, pass_but_fail_check, failed, 1 checks failed; see output for more details +tc, #.#, expect_helpers, pass_but_fail_requirement, failed, Some reason +tc, #.#, expect_helpers, signal_any_and_signal, expected_signal, Call will signal +tc, #.#, expect_helpers, signal_but_pass, failed, Test case was expected to receive a termination signal but it continued execution +tc, #.#, expect_helpers, signal_no_and_signal, expected_signal, Call will signal +tc, #.#, expect_helpers, timeout_and_hang, expected_timeout, Will overrun +tc, #.#, expect_helpers, timeout_but_pass, failed, Test case was expected to hang but it continued execution +tp, #.#, expect_helpers, failed +EOF +# NO_CHECK_STYLE_END + atf_check -s eq:0 -o file:expout -e empty -x \ + "atf-report -o csv:- <tps.out | " \ + "sed -E -e 's/[0-9]+.[0-9]{6}, /#.#, /'" + +# NO_CHECK_STYLE_BEGIN + cat >expout <<EOF +expect_helpers (1/1): 17 test cases + death_and_exit: [#.#s] Expected failure: Exit case + death_and_signal: [#.#s] Expected failure: Signal case + death_but_pass: [#.#s] Failed: Test case was expected to terminate abruptly but it continued execution + exit_any_and_exit: [#.#s] Expected failure: Call will exit + exit_but_pass: [#.#s] Failed: Test case was expected to exit cleanly but it continued execution + exit_code_and_exit: [#.#s] Expected failure: Call will exit + fail_and_fail_check: [#.#s] Expected failure: And fail again: 2 checks failed as expected; see output for more details + fail_and_fail_requirement: [#.#s] Expected failure: Fail reason: The failure + fail_but_pass: [#.#s] Failed: Test case was expecting a failure but none were raised + pass_and_pass: [#.#s] Passed. + pass_but_fail_check: [#.#s] Failed: 1 checks failed; see output for more details + pass_but_fail_requirement: [#.#s] Failed: Some reason + signal_any_and_signal: [#.#s] Expected failure: Call will signal + signal_but_pass: [#.#s] Failed: Test case was expected to receive a termination signal but it continued execution + signal_no_and_signal: [#.#s] Expected failure: Call will signal + timeout_and_hang: [#.#s] Expected failure: Will overrun + timeout_but_pass: [#.#s] Failed: Test case was expected to hang but it continued execution +[#.#s] + +Test cases for known bugs: + expect_helpers:death_and_exit: Exit case + expect_helpers:death_and_signal: Signal case + expect_helpers:exit_any_and_exit: Call will exit + expect_helpers:exit_code_and_exit: Call will exit + expect_helpers:fail_and_fail_check: And fail again: 2 checks failed as expected; see output for more details + expect_helpers:fail_and_fail_requirement: Fail reason: The failure + expect_helpers:signal_any_and_signal: Call will signal + expect_helpers:signal_no_and_signal: Call will signal + expect_helpers:timeout_and_hang: Will overrun + +Failed test cases: + expect_helpers:death_but_pass, expect_helpers:exit_but_pass, expect_helpers:fail_but_pass, expect_helpers:pass_but_fail_check, expect_helpers:pass_but_fail_requirement, expect_helpers:signal_but_pass, expect_helpers:timeout_but_pass + +Summary for 1 test programs: + 1 passed test cases. + 7 failed test cases. + 9 expected failed test cases. + 0 skipped test cases. +EOF +# NO_CHECK_STYLE_END + atf_check -s eq:0 -o file:expout -e empty -x \ + "atf-report -o ticker:- <tps.out | " \ + "sed -E -e 's/[0-9]+.[0-9]{6}/#.#/'" + + # Just ensure that this does not crash for now... + atf_check -s eq:0 -o ignore -e empty -x "atf-report -o xml:- <tps.out" +} + +atf_test_case oflag +oflag_head() +{ + atf_set "descr" "Checks that the -o flag works" +} +oflag_body() +{ + create_helpers + run_helpers + + # Get the default output. + atf_check -s eq:0 -o save:stdout -e empty -x 'atf-report <tps.out' + mv stdout defout + + # Check that changing the stdout output works. + atf_check -s eq:0 -o save:stdout -e empty -x 'atf-report -o csv:- <tps.out' + atf_check -s eq:1 -o empty -e empty cmp -s defout stdout + cp stdout expcsv + + # Check that sending the output to a file does not write to stdout. + atf_check -s eq:0 -o empty -e empty -x 'atf-report -o csv:fmt.out <tps.out' + atf_check -s eq:0 -o empty -e empty cmp -s expcsv fmt.out + rm -f fmt.out + + # Check that defining two outputs using the same format works. + atf_check -s eq:0 -o empty -e empty -x \ + 'atf-report -o csv:fmt.out -o csv:fmt2.out <tps.out' + atf_check -s eq:0 -o empty -e empty cmp -s expcsv fmt.out + atf_check -s eq:0 -o empty -e empty cmp -s fmt.out fmt2.out + rm -f fmt.out fmt2.out + + # Check that defining two outputs using different formats works. + atf_check -s eq:0 -o empty -e empty -x \ + 'atf-report -o csv:fmt.out -o ticker:fmt2.out <tps.out' + atf_check -s eq:0 -o empty -e empty cmp -s expcsv fmt.out + atf_check -s eq:1 -o empty -e empty cmp -s fmt.out fmt2.out + atf_check -s eq:0 -o ignore -e empty grep "test cases" fmt2.out + atf_check -s eq:0 -o ignore -e empty grep "Failed test cases" fmt2.out + atf_check -s eq:0 -o ignore -e empty grep "Summary for" fmt2.out + rm -f fmt.out fmt2.out + + # Check that defining two outputs over the same file does not work. + atf_check -s eq:1 -o empty -e match:'more than once' -x \ + 'atf-report -o csv:fmt.out -o ticker:fmt.out <tps.out' + rm -f fmt.out + + # Check that defining two outputs over stdout (but using different + # paths) does not work. + atf_check -s eq:1 -o empty -e match:'more than once' -x \ + 'atf-report -o csv:- -o ticker:/dev/stdout <tps.out' + rm -f fmt.out +} + +atf_test_case output_csv +output_csv_head() +{ + atf_set "descr" "Checks the CSV output format" +} +output_csv_body() +{ + create_helpers + run_helpers + +# NO_CHECK_STYLE_BEGIN + cat >expout <<EOF +tc, #.#, dir1/tp1, main, passed +tp, #.#, dir1/tp1, passed +tc, #.#, dir1/tp2, main, failed, This always fails +tp, #.#, dir1/tp2, failed +tc, #.#, tp3, main, passed +tp, #.#, tp3, passed +tc, #.#, tp4, main, failed, This always fails +tp, #.#, tp4, failed +tp, #.#, tp5, bogus, Invalid format for test case list: 1: Unexpected token \`<<NEWLINE>>'; expected \`:' +EOF +# NO_CHECK_STYLE_END + + atf_check -s eq:0 -o file:expout -e empty -x \ + "atf-report -o csv:- <tps.out | sed -E -e 's/[0-9]+.[0-9]{6}, /#.#, /'" +} + +atf_test_case output_ticker +output_ticker_head() +{ + atf_set "descr" "Checks the ticker output format" +} +output_ticker_body() +{ + create_helpers + run_helpers + +# NO_CHECK_STYLE_BEGIN + cat >expout <<EOF +dir1/tp1 (1/5): 1 test cases + main: [#.#s] Passed. +[#.#s] + +dir1/tp2 (2/5): 1 test cases + main: [#.#s] Failed: This always fails +[#.#s] + +tp3 (3/5): 1 test cases + main: [#.#s] Passed. +[#.#s] + +tp4 (4/5): 1 test cases + main: [#.#s] Failed: This always fails +[#.#s] + +tp5 (5/5): 0 test cases +tp5: BOGUS TEST PROGRAM: Cannot trust its results because of \`Invalid format for test case list: 1: Unexpected token \`<<NEWLINE>>'; expected \`:'' +[#.#s] + +Failed (bogus) test programs: + tp5 + +Failed test cases: + dir1/tp2:main, tp4:main + +Summary for 5 test programs: + 2 passed test cases. + 2 failed test cases. + 0 expected failed test cases. + 0 skipped test cases. +EOF + + atf_check -s eq:0 -o file:expout -e empty -x \ + "atf-report -o ticker:- <tps.out | sed -E -e 's/[0-9]+.[0-9]{6}/#.#/'" +} +# NO_CHECK_STYLE_END + +atf_test_case output_xml +output_xml_head() +{ + atf_set "descr" "Checks the XML output format" +} +output_xml_body() +{ + create_helpers + run_helpers + +# NO_CHECK_STYLE_BEGIN + cat >expout <<EOF +<?xml version="1.0" encoding="ISO-8859-1"?> +<!DOCTYPE tests-results PUBLIC "-//NetBSD//DTD ATF Tests Results 0.1//EN" "http://www.NetBSD.org/XML/atf/tests-results.dtd"> + +<tests-results> +<info class="startinfo">A value</info> +<tp id="dir1/tp1"> +<tc id="main"> +<passed /> +<tc-time>#.#</tc-time></tc> +<tp-time>#.#</tp-time></tp> +<tp id="dir1/tp2"> +<tc id="main"> +<failed>This always fails</failed> +<tc-time>#.#</tc-time></tc> +<tp-time>#.#</tp-time></tp> +<tp id="tp3"> +<tc id="main"> +<passed /> +<tc-time>#.#</tc-time></tc> +<tp-time>#.#</tp-time></tp> +<tp id="tp4"> +<tc id="main"> +<failed>This always fails</failed> +<tc-time>#.#</tc-time></tc> +<tp-time>#.#</tp-time></tp> +<tp id="tp5"> +<failed>Invalid format for test case list: 1: Unexpected token \`<<NEWLINE>>'; expected \`:'</failed> +<tp-time>#.#</tp-time></tp> +<info class="endinfo">Another value</info> +</tests-results> +EOF +# NO_CHECK_STYLE_END + + atf_check -s eq:0 -o file:expout -e empty -x \ + "atf-report -o xml:- < tps.out | sed -E -e 's/>[0-9]+.[0-9]{6}</>#.#</'" +} + +atf_test_case output_xml_space +output_xml_space_head() +{ + atf_set "descr" "Checks that the XML output format properly preserves" \ + "leading and trailing whitespace in stdout and stderr" \ + "lines" +} +output_xml_space_body() +{ + export TESTCASE=diff + cp $(atf_get_srcdir)/misc_helpers . + cat >Atffile <<EOF +Content-Type: application/X-atf-atffile; version="1" + +prop: test-suite = atf + +tp: misc_helpers +EOF + +# NO_CHECK_STYLE_BEGIN + cat >expout <<EOF +<?xml version="1.0" encoding="ISO-8859-1"?> +<!DOCTYPE tests-results PUBLIC "-//NetBSD//DTD ATF Tests Results 0.1//EN" "http://www.NetBSD.org/XML/atf/tests-results.dtd"> + +<tests-results> +<info class="startinfo">A value</info> +<tp id="misc_helpers"> +<tc id="diff"> +<so>--- a 2007-11-04 14:00:41.000000000 +0100</so> +<so>+++ b 2007-11-04 14:00:48.000000000 +0100</so> +<so>@@ -1,7 +1,7 @@</so> +<so> This test is meant to simulate a diff.</so> +<so> Blank space at beginning of context lines must be preserved.</so> +<so> </so> +<so>-First original line.</so> +<so>-Second original line.</so> +<so>+First modified line.</so> +<so>+Second modified line.</so> +<so> </so> +<so> EOF</so> +<passed /> +<tc-time>#.#</tc-time></tc> +<tp-time>#.#</tp-time></tp> +<info class="endinfo">Another value</info> +</tests-results> +EOF +# NO_CHECK_STYLE_END + + run_helpers + atf_check -s eq:0 -o file:expout -e empty -x \ + "atf-report -o xml:- <tps.out | sed -E -e 's/>[0-9]+.[0-9]{6}</>#.#</'" +} + +atf_test_case too_many_args +too_many_args_body() +{ + cat >experr <<EOF +atf-report: ERROR: No arguments allowed +EOF + atf_check -s eq:1 -o empty -e file:experr atf-report foo +} + +atf_init_test_cases() +{ + atf_add_test_case default + atf_add_test_case expect + atf_add_test_case oflag + atf_add_test_case output_csv + atf_add_test_case output_ticker + atf_add_test_case output_xml + atf_add_test_case output_xml_space + atf_add_test_case too_many_args +} + +# vim: syntax=sh:expandtab:shiftwidth=4:softtabstop=4 diff --git a/unit/atf-src/tools/atf-run.1 b/unit/atf-src/tools/atf-run.1 new file mode 100644 index 0000000..d593f47 --- /dev/null +++ b/unit/atf-src/tools/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/unit/atf-src/tools/atf-run.cpp b/unit/atf-src/tools/atf-run.cpp new file mode 100644 index 0000000..a05b681 --- /dev/null +++ b/unit/atf-src/tools/atf-run.cpp @@ -0,0 +1,567 @@ +// +// 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 "config.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 <cassert> +#include <cerrno> +#include <cstdlib> +#include <cstring> +#include <fstream> +#include <iostream> +#include <map> +#include <string> + +#include "application.hpp" +#include "atffile.hpp" +#include "config.hpp" +#include "config_file.hpp" +#include "env.hpp" +#include "exceptions.hpp" +#include "fs.hpp" +#include "parser.hpp" +#include "process.hpp" +#include "requirements.hpp" +#include "test-program.hpp" +#include "text.hpp" + +namespace { + +typedef std::map< std::string, std::string > vars_map; + +} // anonymous namespace + +#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 tools::application::app { + static const char* m_description; + + vars_map m_cmdline_vars; + + static 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 tools::fs::path&, tools::test_program::atf_tps_writer&, + const vars_map&); + int run_test_directory(const tools::fs::path&, + tools::test_program::atf_tps_writer&); + int run_test_program(const tools::fs::path&, + tools::test_program::atf_tps_writer&, + const vars_map&); + + tools::test_program::test_case_result get_test_case_result( + const std::string&, const tools::process::status&, + const tools::fs::path&) const; + +public: + atf_run(void); + + int main(void); +}; + +static void +sanitize_gdb_env(void) +{ + try { + tools::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 tools::fs::path& tp, const tools::process::status& s, + const tools::fs::path& workdir, + tools::test_program::atf_tps_writer& w) +{ + assert(s.signaled() && s.coredump()); + + w.stderr_tc("Test program crashed; attempting to get stack trace"); + + const tools::fs::path corename = workdir / + (tp.leaf_name().substr(0, max_core_name_length) + ".core"); + if (!tools::fs::exists(corename)) { + w.stderr_tc("Expected file " + corename.str() + " not found"); + return; + } + + const tools::fs::path gdb(GDB); + const tools::fs::path gdbout = workdir / "gdb.out"; + const tools::process::argv_array args(gdb.leaf_name().c_str(), "-batch", + "-q", "-ex", "bt", tp.c_str(), + corename.c_str(), NULL); + tools::process::status status = tools::process::exec( + gdb, args, + tools::process::stream_redirect_path(gdbout), + tools::process::stream_redirect_path(tools::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: + std::abort(); + } +} + +std::string +atf_run::specific_args(void) + const +{ + return "[test-program1 .. test-programN]"; +} + +atf_run::options_set +atf_run::specific_options(void) + const +{ + using tools::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 = tools::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 tools::fs::path& tp, + tools::test_program::atf_tps_writer& w, + const vars_map& config) +{ + tools::fs::file_info fi(tp); + + int errcode; + if (fi.get_type() == tools::fs::file_info::dir_type) + errcode = run_test_directory(tp, w); + else { + const vars_map effective_config = + tools::config_file::merge_configs(config, m_cmdline_vars); + + errcode = run_test_program(tp, w, effective_config); + } + return errcode; +} + +int +atf_run::run_test_directory(const tools::fs::path& tp, + tools::test_program::atf_tps_writer& w) +{ + tools::atffile af = tools::read_atffile(tp / "Atffile"); + + vars_map test_suite_vars; + { + vars_map::const_iterator iter = af.props().find("test-suite"); + assert(iter != af.props().end()); + test_suite_vars = tools::config_file::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, + tools::config_file::merge_configs(af.conf(), test_suite_vars)); + ok &= (result == EXIT_SUCCESS); + } + + return ok ? EXIT_SUCCESS : EXIT_FAILURE; +} + +tools::test_program::test_case_result +atf_run::get_test_case_result(const std::string& broken_reason, + const tools::process::status& s, + const tools::fs::path& resfile) + const +{ + using tools::text::to_string; + using tools::test_program::read_test_case_result; + using tools::test_program::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 " + tools::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 " + tools::text::to_string(s.termsig()) + + (s.coredump() ? " (core dumped)" : "") + " and created a " + "bogus results file"); + } + } + std::abort(); + return test_case_result(); +} + +int +atf_run::run_test_program(const tools::fs::path& tp, + tools::test_program::atf_tps_writer& w, + const vars_map& config) +{ + int errcode = EXIT_SUCCESS; + + tools::test_program::metadata md; + try { + md = tools::test_program::get_metadata(tp, config); + } catch (const tools::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 tools::parser::parse_errors& e) { + const std::string reason = tools::text::join(e, "; "); + w.start_tp(tp.str(), 0); + w.end_tp("Invalid format for test case list: " + reason); + return EXIT_FAILURE; + } + + tools::fs::temp_dir resdir( + tools::fs::path(tools::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, vars_map >::const_iterator iter + = md.test_cases.begin(); iter != md.test_cases.end(); iter++) { + const std::string& tcname = (*iter).first; + const vars_map& tcmd = (*iter).second; + + w.start_tc(tcname); + + try { + const std::string& reqfail = tools::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 = tools::get_required_user( + tcmd, config); + + tools::fs::path resfile = resdir.get_path() / "tcr"; + assert(!tools::fs::exists(resfile)); + try { + const bool has_cleanup = tools::text::to_bool( + (*tcmd.find("has.cleanup")).second); + + tools::fs::temp_dir workdir(tools::fs::path(tools::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 tools::system_error("chown(" + + workdir.get_path().str() + ")", "chown(2) failed", + errno); + } + resfile = workdir.get_path() / "tcr"; + } + + std::pair< std::string, const tools::process::status > s = + tools::test_program::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)tools::test_program::run_test_case( + tp, tcname, "cleanup", tcmd, + config, resfile, workdir.get_path(), w); + + // TODO: Force deletion of workdir. + + tools::test_program::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 (tools::fs::exists(resfile)) + tools::fs::remove(resfile); + throw; + } + if (tools::fs::exists(resfile)) + tools::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++) { + tools::fs::path tp(*iter); + tools::fs::file_info fi(tp); + + if (fi.get_type() == tools::fs::file_info::dir_type) { + tools::atffile af = tools::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 tools::fs::path sh(tools::config::get("atf_shell")); + const tools::fs::path hooks = + tools::fs::path(tools::config::get("atf_pkgdatadir")) / (tool + ".hooks"); + + const tools::process::status s = + tools::process::exec(sh, + tools::process::argv_array(sh.c_str(), hooks.c_str(), + hook.c_str(), NULL), + tools::process::stream_inherit(), + tools::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) +{ + tools::atffile af = tools::read_atffile(tools::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. + vars_map test_suite_vars; + { + vars_map::const_iterator iter = af.props().find("test-suite"); + assert(iter != af.props().end()); + test_suite_vars = tools::config_file::read_config_files((*iter).second); + } + + tools::test_program::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(tools::fs::path(*iter), w, + tools::config_file::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/unit/atf-src/tools/atf-run_test.sh b/unit/atf-src/tools/atf-run_test.sh new file mode 100644 index 0000000..afd013e --- /dev/null +++ b/unit/atf-src/tools/atf-run_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/unit/atf-src/tools/atf-version.1 b/unit/atf-src/tools/atf-version.1 new file mode 100644 index 0000000..136f13c --- /dev/null +++ b/unit/atf-src/tools/atf-version.1 @@ -0,0 +1,56 @@ +.\" +.\" 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 September 16, 2007 +.Dt ATF-VERSION 1 +.Os +.Sh NAME +.Nm atf-version +.Nd shows information about the installed ATF version +.Sh SYNOPSIS +.Nm +.Nm +.Fl h +.Sh DESCRIPTION +.Nm +is a utility that prints information about the version of ATF currently +installed in the system. +.Pp +In the first synopsis form, +.Nm +shows the release version of the ATF package installed in the system (the +installation that corresponds to the instance of +.Nm +being executed) and also shows information related to the repository +revision used to build that package. +.Pp +In the second synopsis form, +.Nm +will print information about all supported options and their purpose. +.Sh SEE ALSO +.Xr atf 7 diff --git a/unit/atf-src/tools/atf-version.cpp b/unit/atf-src/tools/atf-version.cpp new file mode 100644 index 0000000..b93f33c --- /dev/null +++ b/unit/atf-src/tools/atf-version.cpp @@ -0,0 +1,91 @@ +// +// 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 "config.h" +#endif + +#include <cstdlib> +#include <iostream> + +#include "application.hpp" +#include "revision.h" +#include "ui.hpp" + +class atf_version : public tools::application::app { + static const char* m_description; + +public: + atf_version(void); + + int main(void); +}; + +const char* atf_version::m_description = + "atf-version is a tool that shows information about the currently " + "installed version of ATF."; + +atf_version::atf_version(void) : + app(m_description, "atf-version(1)", "atf(7)") +{ +} + +int +atf_version::main(void) +{ + using tools::ui::format_text; + using tools::ui::format_text_with_tag; + + std::cout << PACKAGE_STRING " (" PACKAGE_TARNAME "-" PACKAGE_VERSION + ")\n" PACKAGE_COPYRIGHT "\n\n"; + +#if defined(PACKAGE_REVISION_TYPE_DIST) + std::cout << format_text("Built from a distribution file; no revision " + "information available.") << "\n"; +#elif defined(PACKAGE_REVISION_TYPE_GIT) + std::cout << format_text_with_tag(PACKAGE_REVISION_BRANCH, "Branch: ", + false) << "\n"; + std::cout << format_text_with_tag(PACKAGE_REVISION_BASE +# if PACKAGE_REVISION_MODIFIED + " (locally modified)" +# endif + " " PACKAGE_REVISION_DATE, + "Base revision: ", false) << "\n"; +#else +# error "Unknown PACKAGE_REVISION_TYPE value" +#endif + + return EXIT_SUCCESS; +} + +int +main(int argc, char* const* argv) +{ + return atf_version().run(argc, argv); +} diff --git a/unit/atf-src/tools/atf.7.in b/unit/atf-src/tools/atf.7.in new file mode 100644 index 0000000..b11054c --- /dev/null +++ b/unit/atf-src/tools/atf.7.in @@ -0,0 +1,192 @@ +.\" +.\" 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 August 28, 2010 +.Dt ATF 7 +.Os +.Sh NAME +.Nm ATF +.Nd introduction to the Automated Testing Framework +.Sh DESCRIPTION +.Em IMPORTANT: If you are here because you want to know how to run the tests in +.Pa __TESTSDIR__ , +.Em you most likely want to read the +.Xr tests 7 +.Em manual page instead. +.Pp +The Automated Testing Framework +.Pf ( Nm ) +is a collection of libraries and utilities designed to ease unattended +application testing in the hands of developers and end users of a specific +piece of software. +.Pp +As regards developers, +.Nm +provides the necessary means to easily create +test suites composed of multiple test programs, which in turn are a +collection of test cases. +It also attempts to simplify the debugging of problems when these test +cases detect an error by providing as much information as possible +about the failure. +.Pp +As regards users, it simplifies the process of running the test suites and, +in special, encourages end users to run them often: they do not need to +have source trees around nor any other development tools installed to be +able to certify that a given piece of software works on their machine as +advertised. +.Pp +If your operating systems distributes +.Nm , +it is possible that it provides an introductory +.Xr tests 7 +manual page. +You are encouraged to read it now. +.Ss License +.Nm +is distributed under the terms of the TNF License, a 2-clause BSD license. +For more details please see: +.Bd -literal -offset indent +.Pa __DOCDIR__/COPYING +.Ed +.Ss Components +.Nm +is a highly modular piece of software. +It provides a couple of libraries to ease the implementation of test +programs: one for the C and C++ languages and another one for shell +scripts. +It also includes multiple small utilities that follow the principle of +doing a single thing but doing it right. +This section outlines which these components are. +.Pp +Public utilities: +.Bl -tag -width atfXtestXprogramXXXXX +.It Xr atf-check 1 +Executes a command and checks that its exit code, its standard output +and its standard error output match pre-specified expected values. +.It Xr atf-config 1 +Queries static configuration information. +.It Xr atf-report 1 +Converts the output of +.Nm atf-run +to user-friendly and/or machine-parseable reports. +.It Xr atf-run 1 +Automates the execution of a series of test programs and collects their +results in a unified report. +.It Xr atf-sh 1 +Shell interpreter for shell-based test programs. +.El +.Pp +Programming interfaces: +.Bl -tag -width atfXtestXprogramXXXXX +.It Xr atf-c-api 3 +C programming interface for test programs. +.It Xr atf-c++-api 3 +C++ programming interface for test programs. +.It Xr atf-sh-api 3 +POSIX shell programming interface for test programs. +.El +.Pp +Other: +.Bl -tag -width atfXtestXprogramXXXXX +.It Xr atf-formats 5 +Description of the machine-parseable data formats used by the tools. +.It Xr atf-test-case 4 +Generic description of test cases, independent of the language they are +implemented in. +.It Xr atf-test-program 1 +Common interface provided by the test programs written using the +.Nm +libraries. +.El +.Ss Recommended reading order +For end users wishing to run tests: +.Bl -enum -compact +.It +.Xr tests 7 +(only if provided by your operating system). +.It +.Xr atf-test-program 1 +.It +.Xr atf-run 1 +.It +.Xr atf-report 1 +.It +.Xr atf-config 1 +.El +.Pp +For developers wanting to write their own tests: +.Bl -enum -compact +.It +Everything recommended to users. +.It +.Xr atf-test-case 4 +.It +.Xr atf-c-api 3 +.It +.Xr atf-c++-api 3 +.It +.Xr atf-sh-api 3 +.It +.Xr atf-sh 1 +.It +.Xr atf-check 1 +.El +.Pp +For those interested in +.Nm +internals: +.Bl -enum -compact +.It +Everything recommended to users. +.It +Everything recommended to developers. +.It +.Xr atf-formats 5 +.El +.Sh SEE ALSO +.Xr tests 7 +.Sh HISTORY +.Nm +started as a Google Summer of Code 2007 project mentored by The NetBSD +Foundation. +Its original goal was to provide a testing framework for The NetBSD +Operating System, but it grew as an independent project because the +framework itself did not need to be tied to a specific operating system. +.Pp +For more details on this subject, please see: +.Bd -literal -offset indent +.Pa __DOCDIR__/NEWS +.Pa __DOCDIR__/ROADMAP +.Ed +.Sh AUTHORS +For more details on the people that made +.Nm +possible, please see: +.Bd -literal -offset indent +.Pa __DOCDIR__/AUTHORS +.Ed diff --git a/unit/atf-src/tools/atffile.cpp b/unit/atf-src/tools/atffile.cpp new file mode 100644 index 0000000..b04c59e --- /dev/null +++ b/unit/atf-src/tools/atffile.cpp @@ -0,0 +1,348 @@ +// +// 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 <cassert> +#include <cstdlib> +#include <fstream> + +#include "atffile.hpp" +#include "defs.hpp" +#include "exceptions.hpp" +#include "expand.hpp" +#include "parser.hpp" + +namespace impl = tools; +namespace detail = tools::detail; + +namespace { + +typedef std::map< std::string, std::string > vars_map; + +} // anonymous namespace + +// ------------------------------------------------------------------------ +// The "atf_atffile" auxiliary parser. +// ------------------------------------------------------------------------ + +namespace atf_atffile { + +static const tools::parser::token_type eof_type = 0; +static const tools::parser::token_type nl_type = 1; +static const tools::parser::token_type text_type = 2; +static const tools::parser::token_type colon_type = 3; +static const tools::parser::token_type conf_type = 4; +static const tools::parser::token_type dblquote_type = 5; +static const tools::parser::token_type equal_type = 6; +static const tools::parser::token_type hash_type = 7; +static const tools::parser::token_type prop_type = 8; +static const tools::parser::token_type tp_type = 9; +static const tools::parser::token_type tp_glob_type = 10; + +class tokenizer : public tools::parser::tokenizer< std::istream > { +public: + tokenizer(std::istream& is, size_t curline) : + tools::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 tools::parser::parse_error; + using namespace atf_atffile; + + std::pair< size_t, tools::parser::headers_map > hml = + tools::parser::read_headers(m_is, 1); + tools::parser::validate_content_type(hml.second, + "application/X-atf-atffile", 1); + + tokenizer tkz(m_is, hml.first); + tools::parser::parser< tokenizer > p(tkz); + + for (;;) { + try { + tools::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 + std::abort(); + + 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 tools::fs::directory& m_dir; + 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 = + tools::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 tools::not_found_error< tools::fs::path > + ("Cannot locate the " + name + " file", + tools::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 tools::fs::directory& dir) : + detail::atf_atffile_reader(is), + m_dir(dir) + { + } + + const vars_map& + conf(void) + const + { + return m_conf; + } + + const 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 vars_map& config_vars, + const std::vector< std::string >& test_program_names, + const vars_map& properties) : + m_conf(config_vars), + m_tps(test_program_names), + m_props(properties) +{ + assert(properties.find("test-suite") != properties.end()); +} + +const std::vector< std::string >& +impl::atffile::tps(void) + const +{ + return m_tps; +} + +const vars_map& +impl::atffile::conf(void) + const +{ + return m_conf; +} + +const 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 tools::fs::path& filename) +{ + // Scan the directory where the atffile lives in to gather a list of + // all possible test programs in it. + tools::fs::directory dir(filename.branch_path()); + dir.erase(filename.leaf_name()); + tools::fs::directory::iterator iter = dir.begin(); + while (iter != dir.end()) { + const std::string& name = (*iter).first; + const tools::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 tools::not_found_error< tools::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 tools::not_found_error< std::string > + ("Undefined property `test-suite'", "test-suite"); + + return atffile(r.conf(), r.tps(), r.props()); +} diff --git a/unit/atf-src/tools/atffile.hpp b/unit/atf-src/tools/atffile.hpp new file mode 100644 index 0000000..f326b24 --- /dev/null +++ b/unit/atf-src/tools/atffile.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(TOOLS_ATFFILE_HPP) +#define TOOLS_ATFFILE_HPP + +#include <map> +#include <string> +#include <vector> + +#include "fs.hpp" + +namespace tools { + +// ------------------------------------------------------------------------ +// 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 { + std::map< std::string, std::string > m_conf; + std::vector< std::string > m_tps; + std::map< std::string, std::string > m_props; + +public: + atffile(const std::map< std::string, std::string >&, + const std::vector< std::string >&, + const std::map< std::string, std::string >&); + + const std::map< std::string, std::string >& conf(void) const; + const std::vector< std::string >& tps(void) const; + const std::map< std::string, std::string >& props(void) const; +}; + +// ------------------------------------------------------------------------ +// Free functions. +// ------------------------------------------------------------------------ + +atffile read_atffile(const tools::fs::path&); + +} // namespace tools + +#endif // !defined(TOOLS_ATFFILE_HPP) diff --git a/unit/atf-src/tools/atffile_test.cpp b/unit/atf-src/tools/atffile_test.cpp new file mode 100644 index 0000000..cb358df --- /dev/null +++ b/unit/atf-src/tools/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++.hpp> + +#include "atffile.hpp" +#include "exceptions.hpp" +#include "test_helpers.hpp" + +namespace detail = tools::detail; + +// ------------------------------------------------------------------------ +// Auxiliary functions. +// ------------------------------------------------------------------------ + +namespace { + +typedef std::map< std::string, std::string > vars_map; + +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) { + 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"); + + vars_map properties; + properties["test-suite"] = "a test name"; + + const tools::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 tools::atffile atffile = tools::read_atffile( + tools::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 tools::atffile atffile = tools::read_atffile( + tools::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)tools::read_atffile(tools::fs::path("Atffile")); + ATF_FAIL("Missing property 'test-suite' did not raise an error"); + } catch (const tools::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)tools::read_atffile(tools::fs::path("Atffile")); + ATF_FAIL("Missing file 'bar' did not raise an error"); + } catch (const tools::not_found_error< tools::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/unit/atf-src/tools/auto_array.hpp b/unit/atf-src/tools/auto_array.hpp new file mode 100644 index 0000000..773f875 --- /dev/null +++ b/unit/atf-src/tools/auto_array.hpp @@ -0,0 +1,179 @@ +// +// 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(TOOLS_AUTO_ARRAY_HPP) +#define TOOLS_AUTO_ARRAY_HPP + +#include <cstddef> + +namespace tools { + +// ------------------------------------------------------------------------ +// The "auto_array" class. +// ------------------------------------------------------------------------ + +template< class T > +struct auto_array_ref { + T* m_ptr; + + explicit auto_array_ref(T*); +}; + +template< class T > +auto_array_ref< T >::auto_array_ref(T* ptr) : + m_ptr(ptr) +{ +} + +template< class T > +class auto_array { + T* m_ptr; + +public: + auto_array(T* = NULL) throw(); + auto_array(auto_array< T >&) throw(); + auto_array(auto_array_ref< T >) throw(); + ~auto_array(void) throw(); + + T* get(void) throw(); + const T* get(void) const throw(); + T* release(void) throw(); + void reset(T* = NULL) throw(); + + auto_array< T >& operator=(auto_array< T >&) throw(); + auto_array< T >& operator=(auto_array_ref< T >) throw(); + + T& operator[](int) throw(); + operator auto_array_ref< T >(void) throw(); +}; + +template< class T > +auto_array< T >::auto_array(T* ptr) + throw() : + m_ptr(ptr) +{ +} + +template< class T > +auto_array< T >::auto_array(auto_array< T >& ptr) + throw() : + m_ptr(ptr.release()) +{ +} + +template< class T > +auto_array< T >::auto_array(auto_array_ref< T > ref) + throw() : + m_ptr(ref.m_ptr) +{ +} + +template< class T > +auto_array< T >::~auto_array(void) + throw() +{ + if (m_ptr != NULL) + delete [] m_ptr; +} + +template< class T > +T* +auto_array< T >::get(void) + throw() +{ + return m_ptr; +} + +template< class T > +const T* +auto_array< T >::get(void) + const throw() +{ + return m_ptr; +} + +template< class T > +T* +auto_array< T >::release(void) + throw() +{ + T* ptr = m_ptr; + m_ptr = NULL; + return ptr; +} + +template< class T > +void +auto_array< T >::reset(T* ptr) + throw() +{ + if (m_ptr != NULL) + delete [] m_ptr; + m_ptr = ptr; +} + +template< class T > +auto_array< T >& +auto_array< T >::operator=(auto_array< T >& ptr) + throw() +{ + reset(ptr.release()); + return *this; +} + +template< class T > +auto_array< T >& +auto_array< T >::operator=(auto_array_ref< T > ref) + throw() +{ + if (m_ptr != ref.m_ptr) { + delete [] m_ptr; + m_ptr = ref.m_ptr; + } + return *this; +} + +template< class T > +T& +auto_array< T >::operator[](int pos) + throw() +{ + return m_ptr[pos]; +} + +template< class T > +auto_array< T >::operator auto_array_ref< T >(void) + throw() +{ + return auto_array_ref< T >(release()); +} + +} // namespace tools + +#endif // !defined(TOOLS_AUTO_ARRAY_HPP) diff --git a/unit/atf-src/tools/auto_array_test.cpp b/unit/atf-src/tools/auto_array_test.cpp new file mode 100644 index 0000000..56bbae5 --- /dev/null +++ b/unit/atf-src/tools/auto_array_test.cpp @@ -0,0 +1,303 @@ +// +// 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 <iostream> + +#include <atf-c++.hpp> + +#include "auto_array.hpp" +#include "defs.hpp" + +// ------------------------------------------------------------------------ +// Tests for the "auto_array" class. +// ------------------------------------------------------------------------ + +class test_array { +public: + int m_value; + + static ssize_t m_nblocks; + + static + tools::auto_array< test_array > + do_copy(tools::auto_array< test_array >& ta) + { + return tools::auto_array< test_array >(ta); + } + + void* operator new(size_t size ATF_DEFS_ATTRIBUTE_UNUSED) + { + ATF_FAIL("New called but should have been new[]"); + return new int(5); + } + + void* operator new[](size_t size) + { + m_nblocks++; + void* mem = ::operator new(size); + std::cout << "Allocated 'test_array' object " << mem << "\n"; + return mem; + } + + void operator delete(void* mem ATF_DEFS_ATTRIBUTE_UNUSED) + { + ATF_FAIL("Delete called but should have been delete[]"); + } + + void operator delete[](void* mem) + { + std::cout << "Releasing 'test_array' object " << mem << "\n"; + if (m_nblocks == 0) + ATF_FAIL("Unbalanced delete[]"); + m_nblocks--; + ::operator delete(mem); + } +}; + +ssize_t test_array::m_nblocks = 0; + +ATF_TEST_CASE(auto_array_scope); +ATF_TEST_CASE_HEAD(auto_array_scope) +{ + set_md_var("descr", "Tests the automatic scope handling in the " + "auto_array smart pointer class"); +} +ATF_TEST_CASE_BODY(auto_array_scope) +{ + using tools::auto_array; + + ATF_REQUIRE_EQ(test_array::m_nblocks, 0); + { + auto_array< test_array > t(new test_array[10]); + ATF_REQUIRE_EQ(test_array::m_nblocks, 1); + } + ATF_REQUIRE_EQ(test_array::m_nblocks, 0); +} + +ATF_TEST_CASE(auto_array_copy); +ATF_TEST_CASE_HEAD(auto_array_copy) +{ + set_md_var("descr", "Tests the auto_array smart pointer class' copy " + "constructor"); +} +ATF_TEST_CASE_BODY(auto_array_copy) +{ + using tools::auto_array; + + ATF_REQUIRE_EQ(test_array::m_nblocks, 0); + { + auto_array< test_array > t1(new test_array[10]); + ATF_REQUIRE_EQ(test_array::m_nblocks, 1); + + { + auto_array< test_array > t2(t1); + ATF_REQUIRE_EQ(test_array::m_nblocks, 1); + } + ATF_REQUIRE_EQ(test_array::m_nblocks, 0); + } + ATF_REQUIRE_EQ(test_array::m_nblocks, 0); +} + +ATF_TEST_CASE(auto_array_copy_ref); +ATF_TEST_CASE_HEAD(auto_array_copy_ref) +{ + set_md_var("descr", "Tests the auto_array smart pointer class' copy " + "constructor through the auxiliary auto_array_ref object"); +} +ATF_TEST_CASE_BODY(auto_array_copy_ref) +{ + using tools::auto_array; + + ATF_REQUIRE_EQ(test_array::m_nblocks, 0); + { + auto_array< test_array > t1(new test_array[10]); + ATF_REQUIRE_EQ(test_array::m_nblocks, 1); + + { + auto_array< test_array > t2 = test_array::do_copy(t1); + ATF_REQUIRE_EQ(test_array::m_nblocks, 1); + } + ATF_REQUIRE_EQ(test_array::m_nblocks, 0); + } + ATF_REQUIRE_EQ(test_array::m_nblocks, 0); +} + +ATF_TEST_CASE(auto_array_get); +ATF_TEST_CASE_HEAD(auto_array_get) +{ + set_md_var("descr", "Tests the auto_array smart pointer class' get " + "method"); +} +ATF_TEST_CASE_BODY(auto_array_get) +{ + using tools::auto_array; + + test_array* ta = new test_array[10]; + auto_array< test_array > t(ta); + ATF_REQUIRE_EQ(t.get(), ta); +} + +ATF_TEST_CASE(auto_array_release); +ATF_TEST_CASE_HEAD(auto_array_release) +{ + set_md_var("descr", "Tests the auto_array smart pointer class' release " + "method"); +} +ATF_TEST_CASE_BODY(auto_array_release) +{ + using tools::auto_array; + + test_array* ta1 = new test_array[10]; + { + auto_array< test_array > t(ta1); + ATF_REQUIRE_EQ(test_array::m_nblocks, 1); + test_array* ta2 = t.release(); + ATF_REQUIRE_EQ(ta2, ta1); + ATF_REQUIRE_EQ(test_array::m_nblocks, 1); + } + ATF_REQUIRE_EQ(test_array::m_nblocks, 1); + delete [] ta1; +} + +ATF_TEST_CASE(auto_array_reset); +ATF_TEST_CASE_HEAD(auto_array_reset) +{ + set_md_var("descr", "Tests the auto_array smart pointer class' reset " + "method"); +} +ATF_TEST_CASE_BODY(auto_array_reset) +{ + using tools::auto_array; + + test_array* ta1 = new test_array[10]; + test_array* ta2 = new test_array[10]; + ATF_REQUIRE_EQ(test_array::m_nblocks, 2); + + { + auto_array< test_array > t(ta1); + ATF_REQUIRE_EQ(test_array::m_nblocks, 2); + t.reset(ta2); + ATF_REQUIRE_EQ(test_array::m_nblocks, 1); + t.reset(); + ATF_REQUIRE_EQ(test_array::m_nblocks, 0); + } + ATF_REQUIRE_EQ(test_array::m_nblocks, 0); +} + +ATF_TEST_CASE(auto_array_assign); +ATF_TEST_CASE_HEAD(auto_array_assign) +{ + set_md_var("descr", "Tests the auto_array smart pointer class' " + "assignment operator"); +} +ATF_TEST_CASE_BODY(auto_array_assign) +{ + using tools::auto_array; + + ATF_REQUIRE_EQ(test_array::m_nblocks, 0); + { + auto_array< test_array > t1(new test_array[10]); + ATF_REQUIRE_EQ(test_array::m_nblocks, 1); + + { + auto_array< test_array > t2; + t2 = t1; + ATF_REQUIRE_EQ(test_array::m_nblocks, 1); + } + ATF_REQUIRE_EQ(test_array::m_nblocks, 0); + } + ATF_REQUIRE_EQ(test_array::m_nblocks, 0); +} + +ATF_TEST_CASE(auto_array_assign_ref); +ATF_TEST_CASE_HEAD(auto_array_assign_ref) +{ + set_md_var("descr", "Tests the auto_array smart pointer class' " + "assignment operator through the auxiliary auto_array_ref " + "object"); +} +ATF_TEST_CASE_BODY(auto_array_assign_ref) +{ + using tools::auto_array; + + ATF_REQUIRE_EQ(test_array::m_nblocks, 0); + { + auto_array< test_array > t1(new test_array[10]); + ATF_REQUIRE_EQ(test_array::m_nblocks, 1); + + { + auto_array< test_array > t2; + t2 = test_array::do_copy(t1); + ATF_REQUIRE_EQ(test_array::m_nblocks, 1); + } + ATF_REQUIRE_EQ(test_array::m_nblocks, 0); + } + ATF_REQUIRE_EQ(test_array::m_nblocks, 0); +} + +ATF_TEST_CASE(auto_array_access); +ATF_TEST_CASE_HEAD(auto_array_access) +{ + set_md_var("descr", "Tests the auto_array smart pointer class' access " + "operator"); +} +ATF_TEST_CASE_BODY(auto_array_access) +{ + using tools::auto_array; + + auto_array< test_array > t(new test_array[10]); + + for (int i = 0; i < 10; i++) + t[i].m_value = i * 2; + + for (int i = 0; i < 10; i++) + ATF_REQUIRE_EQ(t[i].m_value, i * 2); +} + +// ------------------------------------------------------------------------ +// Main. +// ------------------------------------------------------------------------ + +ATF_INIT_TEST_CASES(tcs) +{ + // Add the test for the "auto_array" class. + ATF_ADD_TEST_CASE(tcs, auto_array_scope); + ATF_ADD_TEST_CASE(tcs, auto_array_copy); + ATF_ADD_TEST_CASE(tcs, auto_array_copy_ref); + ATF_ADD_TEST_CASE(tcs, auto_array_get); + ATF_ADD_TEST_CASE(tcs, auto_array_release); + ATF_ADD_TEST_CASE(tcs, auto_array_reset); + ATF_ADD_TEST_CASE(tcs, auto_array_assign); + ATF_ADD_TEST_CASE(tcs, auto_array_assign_ref); + ATF_ADD_TEST_CASE(tcs, auto_array_access); +} diff --git a/unit/atf-src/tools/bad_metadata_helper.c b/unit/atf-src/tools/bad_metadata_helper.c new file mode 100644 index 0000000..0f7fcb9 --- /dev/null +++ b/unit/atf-src/tools/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/unit/atf-src/tools/config.cpp b/unit/atf-src/tools/config.cpp new file mode 100644 index 0000000..95bcf55 --- /dev/null +++ b/unit/atf-src/tools/config.cpp @@ -0,0 +1,135 @@ +// +// 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 <cassert> +#include <map> + +#include "config.hpp" +#include "env.hpp" +#include "text.hpp" + +static std::map< std::string, std::string > m_variables; + +static struct var { + const char *name; + const char *default_value; + bool can_be_empty; +} vars[] = { + { "ATF_ARCH", ATF_ARCH, false, }, + { "ATF_BUILD_CC", ATF_BUILD_CC, false, }, + { "ATF_BUILD_CFLAGS", ATF_BUILD_CFLAGS, true, }, + { "ATF_BUILD_CPP", ATF_BUILD_CPP, false, }, + { "ATF_BUILD_CPPFLAGS", ATF_BUILD_CPPFLAGS, true, }, + { "ATF_BUILD_CXX", ATF_BUILD_CXX, false, }, + { "ATF_BUILD_CXXFLAGS", ATF_BUILD_CXXFLAGS, true, }, + { "ATF_CONFDIR", ATF_CONFDIR, false, }, + { "ATF_INCLUDEDIR", ATF_INCLUDEDIR, false, }, + { "ATF_LIBDIR", ATF_LIBDIR, false, }, + { "ATF_LIBEXECDIR", ATF_LIBEXECDIR, false, }, + { "ATF_MACHINE", ATF_MACHINE, false, }, + { "ATF_PKGDATADIR", ATF_PKGDATADIR, false, }, + { "ATF_SHELL", ATF_SHELL, false, }, + { "ATF_WORKDIR", ATF_WORKDIR, false, }, + { NULL, NULL, false, }, +}; + +// +// Adds all predefined standard build-time variables to the m_variables +// map, considering the values a user may have provided in the environment. +// +// Can only be called once during the program's lifetime. +// +static +void +init_variables(void) +{ + assert(m_variables.empty()); + + for (struct var* v = vars; v->name != NULL; v++) { + const std::string varname = tools::text::to_lower(v->name); + + if (tools::env::has(v->name)) { + const std::string envval = tools::env::get(v->name); + if (envval.empty() && !v->can_be_empty) + m_variables[varname] = v->default_value; + else + m_variables[varname] = envval; + } else { + m_variables[varname] = v->default_value; + } + } + + assert(!m_variables.empty()); +} + +const std::string& +tools::config::get(const std::string& varname) +{ + if (m_variables.empty()) + init_variables(); + + assert(has(varname)); + return m_variables[varname]; +} + +const std::map< std::string, std::string >& +tools::config::get_all(void) +{ + if (m_variables.empty()) + init_variables(); + + return m_variables; +} + +bool +tools::config::has(const std::string& varname) +{ + if (m_variables.empty()) + init_variables(); + + return m_variables.find(varname) != m_variables.end(); +} + +namespace tools { +namespace config { +// +// Auxiliary function for the t_config test program so that it can +// revert the configuration's global status to an empty state and +// do new tests from there on. +// +// Ideally this shouldn't be part of the production library... but +// this is so small that it does not matter. +// +void +__reinit(void) +{ + m_variables.clear(); +} +} // namespace config +} // namespace tools diff --git a/unit/atf-src/tools/config.hpp b/unit/atf-src/tools/config.hpp new file mode 100644 index 0000000..0a43873 --- /dev/null +++ b/unit/atf-src/tools/config.hpp @@ -0,0 +1,75 @@ +// +// 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(TOOLS_CONFIG_HPP) +#define TOOLS_CONFIG_HPP + +#include <map> +#include <string> + +namespace tools { + +namespace config { + +//! +//! \brief Gets a build-time configuration variable's value. +//! +//! Given the name of a build-time configuration variable, returns its +//! textual value. The user is free to override these by setting their +//! corresponding environment variables. Therefore always use this +//! interface to get the value of these variables. +//! +//! \pre The variable must exist. +//! +const std::string& get(const std::string&); + +//! +//! \brief Returns all the build-time configuration variables. +//! +//! Returns a name to value map containing all build-time configuration +//! variables. +//! +const std::map< std::string, std::string >& get_all(void); + +//! +//! \brief Checks whether a build-time configuration variable exists. +//! +//! Given the name of a build-time configuration variable, checks +//! whether it is defined and returns a boolean indicating this +//! condition. The program only has to use this function to sanity-check +//! a variable name provided by the user. Otherwise it can assume that +//! the variables are defined. +//! +bool has(const std::string&); + +} // namespace config + +} // namespace tools + +#endif // !defined(TOOLS_CONFIG_HPP) diff --git a/unit/atf-src/tools/config_file.cpp b/unit/atf-src/tools/config_file.cpp new file mode 100644 index 0000000..3802c06 --- /dev/null +++ b/unit/atf-src/tools/config_file.cpp @@ -0,0 +1,223 @@ +// +// 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 <cstdlib> +#include <fstream> +#include <vector> + +#include "config.hpp" +#include "config_file.hpp" +#include "defs.hpp" +#include "env.hpp" +#include "fs.hpp" +#include "parser.hpp" + +namespace impl = tools::config_file; +namespace detail = tools::config_file::detail; + +namespace { + +typedef std::map< std::string, std::string > vars_map; + +namespace atf_config { + +static const tools::parser::token_type eof_type = 0; +static const tools::parser::token_type nl_type = 1; +static const tools::parser::token_type text_type = 2; +static const tools::parser::token_type dblquote_type = 3; +static const tools::parser::token_type equal_type = 4; +static const tools::parser::token_type hash_type = 5; + +class tokenizer : public tools::parser::tokenizer< std::istream > { +public: + tokenizer(std::istream& is, size_t curline) : + tools::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 { + 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 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 tools::fs::path& config_path, + 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< tools::fs::path > +get_config_dirs(void) +{ + std::vector< tools::fs::path > dirs; + dirs.push_back(tools::fs::path(tools::config::get("atf_confdir"))); + if (tools::env::has("HOME")) + dirs.push_back(tools::fs::path(tools::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 tools::parser::parse_error; + using namespace atf_config; + + std::pair< size_t, tools::parser::headers_map > hml = + tools::parser::read_headers(m_is, 1); + tools::parser::validate_content_type(hml.second, + "application/X-atf-config", 1); + + tokenizer tkz(m_is, hml.first); + tools::parser::parser< tokenizer > p(tkz); + + for (;;) { + try { + tools::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 + std::abort(); + } catch (const parse_error& pe) { + p.add_error(pe); + p.reset(nl_type); + } + } + + ATF_PARSER_CALLBACK(p, got_eof()); +} + +vars_map +impl::merge_configs(const vars_map& lower, + const vars_map& upper) +{ + vars_map merged = lower; + merge_maps(merged, upper); + return merged; +} + +vars_map +impl::read_config_files(const std::string& test_suite_name) +{ + vars_map config; + + const std::vector< tools::fs::path > dirs = get_config_dirs(); + for (std::vector< tools::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/unit/atf-src/tools/config_file.hpp b/unit/atf-src/tools/config_file.hpp new file mode 100644 index 0000000..0c68bcb --- /dev/null +++ b/unit/atf-src/tools/config_file.hpp @@ -0,0 +1,66 @@ +// +// 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(TOOLS_CONFIG_FILE_HPP) +#define TOOLS_CONFIG_FILE_HPP + +#include <map> +#include <string> +#include <vector> + +namespace tools { +namespace config_file { + +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 + +std::map< std::string, std::string > merge_configs( + const std::map< std::string, std::string >&, + const std::map< std::string, std::string >&); +std::map< std::string, std::string > read_config_files(const std::string&); + +} // namespace config_file +} // namespace tools + +#endif // !defined(TOOLS_CONFIG_FILE_HPP) diff --git a/unit/atf-src/tools/config_file_test.cpp b/unit/atf-src/tools/config_file_test.cpp new file mode 100644 index 0000000..f042634 --- /dev/null +++ b/unit/atf-src/tools/config_file_test.cpp @@ -0,0 +1,395 @@ +// +// 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++.hpp> + +#include "config.hpp" +#include "config_file.hpp" +#include "env.hpp" +#include "test_helpers.hpp" + +namespace impl = tools::config_file; +namespace detail = tools::config_file::detail; + +namespace { + +typedef std::map< std::string, std::string > vars_map; + +} // anonymous namespace + +namespace tools { +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) { + tools::env::set("ATF_CONFDIR", "."); + tools::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/unit/atf-src/tools/config_test.cpp b/unit/atf-src/tools/config_test.cpp new file mode 100644 index 0000000..d7d6719 --- /dev/null +++ b/unit/atf-src/tools/config_test.cpp @@ -0,0 +1,221 @@ +// +// 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 <cstring> +#include <iostream> + +#include <atf-c++.hpp> + +#include "config.hpp" +#include "env.hpp" +#include "exceptions.hpp" + +static const char *test_value = "env-value"; + +static struct varnames { + const char *lc; + const char *uc; + bool can_be_empty; +} all_vars[] = { + { "atf_arch", "ATF_ARCH", false }, + { "atf_build_cc", "ATF_BUILD_CC", false }, + { "atf_build_cflags", "ATF_BUILD_CFLAGS", true }, + { "atf_build_cpp", "ATF_BUILD_CPP", false }, + { "atf_build_cppflags", "ATF_BUILD_CPPFLAGS", true }, + { "atf_build_cxx", "ATF_BUILD_CXX", false }, + { "atf_build_cxxflags", "ATF_BUILD_CXXFLAGS", true }, + { "atf_confdir", "ATF_CONFDIR", false }, + { "atf_includedir", "ATF_INCLUDEDIR", false }, + { "atf_libdir", "ATF_LIBDIR", false }, + { "atf_libexecdir", "ATF_LIBEXECDIR", false }, + { "atf_machine", "ATF_MACHINE", false }, + { "atf_pkgdatadir", "ATF_PKGDATADIR", false }, + { "atf_shell", "ATF_SHELL", false }, + { "atf_workdir", "ATF_WORKDIR", false }, + { NULL, NULL, false } +}; + +// ------------------------------------------------------------------------ +// Auxiliary functions. +// ------------------------------------------------------------------------ + +namespace tools { + namespace config { + void __reinit(void); + } +} + +static +void +set_env_var(const char* name, const char* val) +{ + try { + tools::env::set(name, val); + } catch (const tools::system_error&) { + ATF_FAIL(std::string("set_env_var(") + name + ", " + val + + ") failed"); + } +} + +static +void +unset_env_var(const char* name) +{ + try { + tools::env::unset(name); + } catch (const tools::system_error&) { + ATF_FAIL(std::string("unset_env_var(") + name + ") failed"); + } +} + +static +size_t +all_vars_count(void) +{ + size_t count = 0; + for (const struct varnames* v = all_vars; v->lc != NULL; v++) + count++; + return count; +} + +static +void +unset_all(void) +{ + for (const struct varnames* v = all_vars; v->lc != NULL; v++) + unset_env_var(v->uc); +} + +static +void +compare_one(const char* var, const char* expvalue) +{ + std::cout << "Checking that " << var << " is set to " << expvalue << "\n"; + + for (const struct varnames* v = all_vars; v->lc != NULL; v++) { + if (std::strcmp(v->lc, var) == 0) + ATF_REQUIRE_EQ(tools::config::get(v->lc), test_value); + else + ATF_REQUIRE(tools::config::get(v->lc) != test_value); + } +} + +// ------------------------------------------------------------------------ +// Test cases for the free functions. +// ------------------------------------------------------------------------ + +ATF_TEST_CASE(get); +ATF_TEST_CASE_HEAD(get) +{ + set_md_var("descr", "Tests the config::get function"); +} +ATF_TEST_CASE_BODY(get) +{ + // Unset all known environment variables and make sure the built-in + // values do not match the bogus value we will use for testing. + unset_all(); + tools::config::__reinit(); + for (const struct varnames* v = all_vars; v->lc != NULL; v++) + ATF_REQUIRE(tools::config::get(v->lc) != test_value); + + // Test the behavior of empty values. + for (const struct varnames* v = all_vars; v->lc != NULL; v++) { + unset_all(); + if (!tools::config::get(v->lc).empty()) { + set_env_var(v->uc, ""); + tools::config::__reinit(); + if (v->can_be_empty) + ATF_REQUIRE(tools::config::get(v->lc).empty()); + else + ATF_REQUIRE(!tools::config::get(v->lc).empty()); + } + } + + // Check if the ATF_ARCH variable is recognized. + for (const struct varnames* v = all_vars; v->lc != NULL; v++) { + unset_all(); + set_env_var(v->uc, test_value); + tools::config::__reinit(); + compare_one(v->lc, test_value); + } +} + +ATF_TEST_CASE(get_all); +ATF_TEST_CASE_HEAD(get_all) +{ + set_md_var("descr", "Tests the config::get_all function"); +} +ATF_TEST_CASE_BODY(get_all) +{ + tools::config::__reinit(); + + // Check that the valid variables, and only those, are returned. + std::map< std::string, std::string > vars = tools::config::get_all(); + ATF_REQUIRE_EQ(vars.size(), all_vars_count()); + for (const struct varnames* v = all_vars; v->lc != NULL; v++) + ATF_REQUIRE(vars.find(v->lc) != vars.end()); +} + +ATF_TEST_CASE(has); +ATF_TEST_CASE_HEAD(has) +{ + set_md_var("descr", "Tests the config::has function"); +} +ATF_TEST_CASE_BODY(has) +{ + tools::config::__reinit(); + + // Check for all the variables that must exist. + for (const struct varnames* v = all_vars; v->lc != NULL; v++) + ATF_REQUIRE(tools::config::has(v->lc)); + + // Same as above, but using uppercase (which is incorrect). + for (const struct varnames* v = all_vars; v->lc != NULL; v++) + ATF_REQUIRE(!tools::config::has(v->uc)); + + // Check for some other variables that cannot exist. + ATF_REQUIRE(!tools::config::has("foo")); + ATF_REQUIRE(!tools::config::has("BAR")); + ATF_REQUIRE(!tools::config::has("atf_foo")); + ATF_REQUIRE(!tools::config::has("ATF_BAR")); + ATF_REQUIRE(!tools::config::has("atf_shel")); + ATF_REQUIRE(!tools::config::has("atf_shells")); +} + +// ------------------------------------------------------------------------ +// Main. +// ------------------------------------------------------------------------ + +ATF_INIT_TEST_CASES(tcs) +{ + // Add the test cases for the free functions. + ATF_ADD_TEST_CASE(tcs, has); + ATF_ADD_TEST_CASE(tcs, get); + ATF_ADD_TEST_CASE(tcs, get_all); +} diff --git a/unit/atf-src/tools/defs.hpp.in b/unit/atf-src/tools/defs.hpp.in new file mode 100644 index 0000000..9542795 --- /dev/null +++ b/unit/atf-src/tools/defs.hpp.in @@ -0,0 +1,37 @@ +// +// 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. +// + +#if !defined(TOOLS_DEFS_HPP) +#define TOOLS_DEFS_HPP + +#define ATF_DEFS_ATTRIBUTE_FORMAT_PRINTF(a, b) @ATTRIBUTE_FORMAT_PRINTF@ +#define ATF_DEFS_ATTRIBUTE_NORETURN @ATTRIBUTE_NORETURN@ +#define ATF_DEFS_ATTRIBUTE_UNUSED @ATTRIBUTE_UNUSED@ + +#endif /* !defined(TOOLS_DEFS_HPP) */ diff --git a/unit/atf-src/tools/env.cpp b/unit/atf-src/tools/env.cpp new file mode 100644 index 0000000..856ef98 --- /dev/null +++ b/unit/atf-src/tools/env.cpp @@ -0,0 +1,102 @@ +// +// 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 "config.h" +#endif + +extern "C" { +#include <errno.h> +#include <stdlib.h> +#include <string.h> +} + +#include <cassert> + +#include "env.hpp" +#include "exceptions.hpp" + +namespace impl = tools::env; +#define IMPL_NAME "tools::env" + +// ------------------------------------------------------------------------ +// Free functions. +// ------------------------------------------------------------------------ + +std::string +impl::get(const std::string& name) +{ + const char* val = getenv(name.c_str()); + assert(val != NULL); + return val; +} + +bool +impl::has(const std::string& name) +{ + return getenv(name.c_str()) != NULL; +} + +void +impl::set(const std::string& name, const std::string& val) +{ +#if defined(HAVE_SETENV) + if (setenv(name.c_str(), val.c_str(), 1) == -1) + throw tools::system_error(IMPL_NAME "::set", + "Cannot set environment variable '" + name + + "' to '" + val + "'", + errno); +#elif defined(HAVE_PUTENV) + const std::string buf = name + "=" + val; + if (putenv(strdup(buf.c_str())) == -1) + throw tools::system_error(IMPL_NAME "::set", + "Cannot set environment variable '" + name + + "' to '" + val + "'", + errno); +#else +# error "Don't know how to set an environment variable." +#endif +} + +void +impl::unset(const std::string& name) +{ +#if defined(HAVE_UNSETENV) + unsetenv(name.c_str()); +#elif defined(HAVE_PUTENV) + const std::string buf = name + "="; + + if (putenv(strdup(buf.c_str())) == -1) + throw tools::system_error(IMPL_NAME "::unset", + "Cannot unset environment variable '" + + name + "'", errno); +#else +# error "Don't know how to unset an environment variable." +#endif +} diff --git a/unit/atf-src/tools/env.hpp b/unit/atf-src/tools/env.hpp new file mode 100644 index 0000000..f87a58f --- /dev/null +++ b/unit/atf-src/tools/env.hpp @@ -0,0 +1,84 @@ +// +// 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(TOOLS_ENV_HPP) +#define TOOLS_ENV_HPP + +#include <string> + +namespace tools { +namespace env { + +// ------------------------------------------------------------------------ +// Free functions. +// ------------------------------------------------------------------------ + +//! +//! \brief Returns the value of an environment variable. +//! +//! Returns the value of the specified environment variable. The variable +//! must be defined. +//! +std::string get(const std::string&); + +//! +//! \brief Checks if the environment has a variable. +//! +//! Checks if the environment has a given variable. +//! +bool has(const std::string&); + +//! +//! \brief Sets an environment variable to a given value. +//! +//! Sets the specified environment variable to the given value. Note that +//! variables set to the empty string are different to undefined ones. +//! +//! Be aware that this alters the program's global status, which in general +//! is a bad thing to do due to the side-effects it may have. There are +//! some legitimate usages for this function, though. +//! +void set(const std::string&, const std::string&); + +//! +//! \brief Unsets an environment variable. +//! +//! Unsets the specified environment variable Note that undefined +//! variables are different to those defined but set to an empty value. +//! +//! Be aware that this alters the program's global status, which in general +//! is a bad thing to do due to the side-effects it may have. There are +//! some legitimate usages for this function, though. +//! +void unset(const std::string&); + +} // namespace env +} // namespace tools + +#endif // !defined(TOOLS_ENV_HPP) diff --git a/unit/atf-src/tools/env_test.cpp b/unit/atf-src/tools/env_test.cpp new file mode 100644 index 0000000..441a16f --- /dev/null +++ b/unit/atf-src/tools/env_test.cpp @@ -0,0 +1,91 @@ +// +// 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++.hpp> + +#include "env.hpp" + +// ------------------------------------------------------------------------ +// Test cases for the free functions. +// ------------------------------------------------------------------------ + +ATF_TEST_CASE(has_get); +ATF_TEST_CASE_HEAD(has_get) +{ + set_md_var("descr", "Tests the has and get functions"); +} +ATF_TEST_CASE_BODY(has_get) +{ + ATF_REQUIRE(tools::env::has("PATH")); + ATF_REQUIRE(!tools::env::get("PATH").empty()); + + ATF_REQUIRE(!tools::env::has("_UNDEFINED_VARIABLE_")); +} + +ATF_TEST_CASE(set); +ATF_TEST_CASE_HEAD(set) +{ + set_md_var("descr", "Tests the set function"); +} +ATF_TEST_CASE_BODY(set) +{ + ATF_REQUIRE(tools::env::has("PATH")); + const std::string& oldval = tools::env::get("PATH"); + tools::env::set("PATH", "foo-bar"); + ATF_REQUIRE(tools::env::get("PATH") != oldval); + ATF_REQUIRE_EQ(tools::env::get("PATH"), "foo-bar"); + + ATF_REQUIRE(!tools::env::has("_UNDEFINED_VARIABLE_")); + tools::env::set("_UNDEFINED_VARIABLE_", "foo2-bar2"); + ATF_REQUIRE_EQ(tools::env::get("_UNDEFINED_VARIABLE_"), "foo2-bar2"); +} + +ATF_TEST_CASE(unset); +ATF_TEST_CASE_HEAD(unset) +{ + set_md_var("descr", "Tests the unset function"); +} +ATF_TEST_CASE_BODY(unset) +{ + ATF_REQUIRE(tools::env::has("PATH")); + tools::env::unset("PATH"); + ATF_REQUIRE(!tools::env::has("PATH")); +} + +// ------------------------------------------------------------------------ +// Main. +// ------------------------------------------------------------------------ + +ATF_INIT_TEST_CASES(tcs) +{ + // Add the test cases for the free functions. + ATF_ADD_TEST_CASE(tcs, has_get); + ATF_ADD_TEST_CASE(tcs, set); + ATF_ADD_TEST_CASE(tcs, unset); +} diff --git a/unit/atf-src/tools/exceptions.cpp b/unit/atf-src/tools/exceptions.cpp new file mode 100644 index 0000000..8587121 --- /dev/null +++ b/unit/atf-src/tools/exceptions.cpp @@ -0,0 +1,74 @@ +// +// 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 <cstring> + +#include "exceptions.hpp" + +// ------------------------------------------------------------------------ +// The "system_error" type. +// ------------------------------------------------------------------------ + +tools::system_error::system_error(const std::string& who, + const std::string& message, + int sys_err) : + std::runtime_error(who + ": " + message), + m_sys_err(sys_err) +{ +} + +tools::system_error::~system_error(void) + throw() +{ +} + +int +tools::system_error::code(void) + const + throw() +{ + return m_sys_err; +} + +const char* +tools::system_error::what(void) + const + throw() +{ + try { + if (m_message.length() == 0) { + m_message = std::string(std::runtime_error::what()) + ": "; + m_message += ::strerror(m_sys_err); + } + + return m_message.c_str(); + } catch (...) { + return "Unable to format system_error message"; + } +} diff --git a/unit/atf-src/tools/exceptions.hpp b/unit/atf-src/tools/exceptions.hpp new file mode 100644 index 0000000..4346b7b --- /dev/null +++ b/unit/atf-src/tools/exceptions.hpp @@ -0,0 +1,93 @@ +// +// 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(TOOLS_EXCEPTIONS_HPP) +#define TOOLS_EXCEPTIONS_HPP + +#include <stdexcept> +#include <string> + +namespace tools { + +template< class T > +class not_found_error : + public std::runtime_error +{ + T m_value; + +public: + not_found_error(const std::string& message, const T& value) throw(); + + virtual ~not_found_error(void) throw(); + + const T& get_value(void) const throw(); +}; + +template< class T > +inline +not_found_error< T >::not_found_error(const std::string& message, + const T& value) + throw() : + std::runtime_error(message), + m_value(value) +{ +} + +template< class T > +inline +not_found_error< T >::~not_found_error(void) + throw() +{ +} + +template< class T > +inline +const T& +not_found_error< T >::get_value(void) + const + throw() +{ + return m_value; +} + +class system_error : public std::runtime_error { + int m_sys_err; + mutable std::string m_message; + +public: + system_error(const std::string&, const std::string&, int); + ~system_error(void) throw(); + + int code(void) const throw(); + const char* what(void) const throw(); +}; + +} // namespace tools + +#endif // !defined(TOOLS_EXCEPTIONS_HPP) diff --git a/unit/atf-src/tools/expand.cpp b/unit/atf-src/tools/expand.cpp new file mode 100644 index 0000000..3ae27d0 --- /dev/null +++ b/unit/atf-src/tools/expand.cpp @@ -0,0 +1,81 @@ +// +// 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 <stdexcept> + +#include "expand.hpp" +#include "text.hpp" + +namespace impl = tools::expand; +#define IMPL_NAME "tools::expand" + +// ------------------------------------------------------------------------ +// Auxiliary functions. +// ------------------------------------------------------------------------ + +namespace { + +std::string +glob_to_regex(const std::string& glob) +{ + std::string regex; + regex.reserve(glob.length() * 2); + + regex += '^'; + for (std::string::const_iterator iter = glob.begin(); iter != glob.end(); + iter++) { + switch (*iter) { + case '*': regex += ".*"; break; + case '?': regex += "."; break; + default: regex += *iter; + } + } + regex += '$'; + + return regex; +} + +} // anonymous namespace + +// ------------------------------------------------------------------------ +// Free functions. +// ------------------------------------------------------------------------ + +bool +impl::is_glob(const std::string& glob) +{ + // NOTE: Keep this in sync with glob_to_regex! + return glob.find_first_of("*?") != std::string::npos; +} + +bool +impl::matches_glob(const std::string& glob, const std::string& candidate) +{ + return tools::text::match(candidate, glob_to_regex(glob)); +} diff --git a/unit/atf-src/tools/expand.hpp b/unit/atf-src/tools/expand.hpp new file mode 100644 index 0000000..b813544 --- /dev/null +++ b/unit/atf-src/tools/expand.hpp @@ -0,0 +1,82 @@ +// +// 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(TOOLS_EXPAND_HPP) +#define TOOLS_EXPAND_HPP + +#include <string> +#include <vector> + +namespace tools { +namespace expand { + +// ------------------------------------------------------------------------ +// Free functions. +// ------------------------------------------------------------------------ + +//! +//! \brief Checks if the given string is a glob pattern. +//! +//! Returns true if the given string is a glob pattern; i.e. if it contains +//! any character that will be expanded by expand_glob. +//! +bool is_glob(const std::string&); + +//! +//! \brief Checks if a given string matches a glob pattern. +//! +//! Given a glob pattern and a string, checks whether the former matches +//! the latter. Returns a boolean indicating this condition. +//! +bool matches_glob(const std::string&, const std::string&); + +//! +//! \brief Expands a glob pattern among multiple candidates. +//! +//! Given a glob pattern and a set of candidate strings, checks which of +//! those strings match the glob pattern and returns them. +//! +template< class T > +std::vector< std::string > expand_glob(const std::string& glob, + const T& candidates) +{ + std::vector< std::string > exps; + + for (typename T::const_iterator iter = candidates.begin(); + iter != candidates.end(); iter++) + if (matches_glob(glob, *iter)) + exps.push_back(*iter); + + return exps; +} + +} // namespace expand +} // namespace tools + +#endif // !defined(TOOLS_EXPAND_HPP) diff --git a/unit/atf-src/tools/expand_test.cpp b/unit/atf-src/tools/expand_test.cpp new file mode 100644 index 0000000..fb59de8 --- /dev/null +++ b/unit/atf-src/tools/expand_test.cpp @@ -0,0 +1,272 @@ +// +// 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 <cstring> + +#include <atf-c++.hpp> + +#include "expand.hpp" + +// XXX Many of the tests here are duplicated in atf-c/t_expand. Should +// find a way to easily share them, or maybe remove the ones here. + +// ------------------------------------------------------------------------ +// Test cases for the free functions. +// ------------------------------------------------------------------------ + +ATF_TEST_CASE(is_glob); +ATF_TEST_CASE_HEAD(is_glob) +{ + set_md_var("descr", "Tests the is_glob function."); +} +ATF_TEST_CASE_BODY(is_glob) +{ + using tools::expand::is_glob; + + ATF_REQUIRE(!is_glob("")); + ATF_REQUIRE(!is_glob("a")); + ATF_REQUIRE(!is_glob("foo")); + + ATF_REQUIRE( is_glob("*")); + ATF_REQUIRE( is_glob("a*")); + ATF_REQUIRE( is_glob("*a")); + ATF_REQUIRE( is_glob("a*b")); + + ATF_REQUIRE( is_glob("?")); + ATF_REQUIRE( is_glob("a?")); + ATF_REQUIRE( is_glob("?a")); + ATF_REQUIRE( is_glob("a?b")); +} + +ATF_TEST_CASE(matches_glob_plain); +ATF_TEST_CASE_HEAD(matches_glob_plain) +{ + set_md_var("descr", "Tests the matches_glob function by using plain " + "text strings as patterns only."); +} +ATF_TEST_CASE_BODY(matches_glob_plain) +{ + using tools::expand::matches_glob; + + ATF_REQUIRE( matches_glob("", "")); + ATF_REQUIRE(!matches_glob("a", "")); + ATF_REQUIRE(!matches_glob("", "a")); + + ATF_REQUIRE( matches_glob("ab", "ab")); + ATF_REQUIRE(!matches_glob("abc", "ab")); + ATF_REQUIRE(!matches_glob("ab", "abc")); +} + +ATF_TEST_CASE(matches_glob_star); +ATF_TEST_CASE_HEAD(matches_glob_star) +{ + set_md_var("descr", "Tests the matches_glob function by using the '*' " + "meta-character as part of the pattern."); +} +ATF_TEST_CASE_BODY(matches_glob_star) +{ + using tools::expand::matches_glob; + + ATF_REQUIRE( matches_glob("*", "")); + ATF_REQUIRE( matches_glob("*", "a")); + ATF_REQUIRE( matches_glob("*", "ab")); + + ATF_REQUIRE(!matches_glob("a*", "")); + ATF_REQUIRE( matches_glob("a*", "a")); + ATF_REQUIRE( matches_glob("a*", "aa")); + ATF_REQUIRE( matches_glob("a*", "ab")); + ATF_REQUIRE( matches_glob("a*", "abc")); + ATF_REQUIRE(!matches_glob("a*", "ba")); + + ATF_REQUIRE( matches_glob("*a", "a")); + ATF_REQUIRE( matches_glob("*a", "ba")); + ATF_REQUIRE(!matches_glob("*a", "bc")); + ATF_REQUIRE(!matches_glob("*a", "bac")); + + ATF_REQUIRE( matches_glob("*ab", "ab")); + ATF_REQUIRE( matches_glob("*ab", "aab")); + ATF_REQUIRE( matches_glob("*ab", "aaab")); + ATF_REQUIRE( matches_glob("*ab", "bab")); + ATF_REQUIRE(!matches_glob("*ab", "bcb")); + ATF_REQUIRE(!matches_glob("*ab", "bacb")); + + ATF_REQUIRE( matches_glob("a*b", "ab")); + ATF_REQUIRE( matches_glob("a*b", "acb")); + ATF_REQUIRE( matches_glob("a*b", "acdeb")); + ATF_REQUIRE(!matches_glob("a*b", "acdebz")); + ATF_REQUIRE(!matches_glob("a*b", "zacdeb")); +} + +ATF_TEST_CASE(matches_glob_question); +ATF_TEST_CASE_HEAD(matches_glob_question) +{ + set_md_var("descr", "Tests the matches_glob function by using the '?' " + "meta-character as part of the pattern."); +} +ATF_TEST_CASE_BODY(matches_glob_question) +{ + using tools::expand::matches_glob; + + ATF_REQUIRE(!matches_glob("?", "")); + ATF_REQUIRE( matches_glob("?", "a")); + ATF_REQUIRE(!matches_glob("?", "ab")); + + ATF_REQUIRE( matches_glob("?", "b")); + ATF_REQUIRE( matches_glob("?", "c")); + + ATF_REQUIRE( matches_glob("a?", "ab")); + ATF_REQUIRE( matches_glob("a?", "ac")); + ATF_REQUIRE(!matches_glob("a?", "ca")); + + ATF_REQUIRE( matches_glob("???", "abc")); + ATF_REQUIRE( matches_glob("???", "def")); + ATF_REQUIRE(!matches_glob("???", "a")); + ATF_REQUIRE(!matches_glob("???", "ab")); + ATF_REQUIRE(!matches_glob("???", "abcd")); +} + +ATF_TEST_CASE(expand_glob_base); +ATF_TEST_CASE_HEAD(expand_glob_base) +{ + set_md_var("descr", "Tests the expand_glob function with random " + "patterns."); +} +ATF_TEST_CASE_BODY(expand_glob_base) +{ + using tools::expand::expand_glob; + + std::vector< std::string > candidates; + candidates.push_back("foo"); + candidates.push_back("bar"); + candidates.push_back("baz"); + candidates.push_back("foobar"); + candidates.push_back("foobarbaz"); + candidates.push_back("foobarbazfoo"); + + std::vector< std::string > exps; + + exps = expand_glob("foo", candidates); + ATF_REQUIRE_EQ(exps.size(), 1); + ATF_REQUIRE(exps[0] == "foo"); + + exps = expand_glob("bar", candidates); + ATF_REQUIRE_EQ(exps.size(), 1); + ATF_REQUIRE(exps[0] == "bar"); + + exps = expand_glob("foo*", candidates); + ATF_REQUIRE_EQ(exps.size(), 4); + ATF_REQUIRE(exps[0] == "foo"); + ATF_REQUIRE(exps[1] == "foobar"); + ATF_REQUIRE(exps[2] == "foobarbaz"); + ATF_REQUIRE(exps[3] == "foobarbazfoo"); + + exps = expand_glob("*foo", candidates); + ATF_REQUIRE_EQ(exps.size(), 2); + ATF_REQUIRE(exps[0] == "foo"); + ATF_REQUIRE(exps[1] == "foobarbazfoo"); + + exps = expand_glob("*foo*", candidates); + ATF_REQUIRE_EQ(exps.size(), 4); + ATF_REQUIRE(exps[0] == "foo"); + ATF_REQUIRE(exps[1] == "foobar"); + ATF_REQUIRE(exps[2] == "foobarbaz"); + ATF_REQUIRE(exps[3] == "foobarbazfoo"); + + exps = expand_glob("ba", candidates); + ATF_REQUIRE_EQ(exps.size(), 0); + + exps = expand_glob("ba*", candidates); + ATF_REQUIRE_EQ(exps.size(), 2); + ATF_REQUIRE(exps[0] == "bar"); + ATF_REQUIRE(exps[1] == "baz"); + + exps = expand_glob("*ba", candidates); + ATF_REQUIRE_EQ(exps.size(), 0); + + exps = expand_glob("*ba*", candidates); + ATF_REQUIRE_EQ(exps.size(), 5); + ATF_REQUIRE(exps[0] == "bar"); + ATF_REQUIRE(exps[1] == "baz"); + ATF_REQUIRE(exps[2] == "foobar"); + ATF_REQUIRE(exps[3] == "foobarbaz"); + ATF_REQUIRE(exps[4] == "foobarbazfoo"); +} + +ATF_TEST_CASE(expand_glob_tps); +ATF_TEST_CASE_HEAD(expand_glob_tps) +{ + set_md_var("descr", "Tests the expand_glob function with patterns that " + "match typical test program names. This is just a subcase " + "of expand_base, but it is nice to make sure that it really " + "works."); +} +ATF_TEST_CASE_BODY(expand_glob_tps) +{ + using tools::expand::expand_glob; + + std::vector< std::string > candidates; + candidates.push_back("Atffile"); + candidates.push_back("h_foo"); + candidates.push_back("t_foo"); + candidates.push_back("t_bar"); + candidates.push_back("t_baz"); + candidates.push_back("foo_helper"); + candidates.push_back("foo_test"); + candidates.push_back("bar_test"); + candidates.push_back("baz_test"); + + std::vector< std::string > exps; + + exps = expand_glob("t_*", candidates); + ATF_REQUIRE_EQ(exps.size(), 3); + ATF_REQUIRE(exps[0] == "t_foo"); + ATF_REQUIRE(exps[1] == "t_bar"); + ATF_REQUIRE(exps[2] == "t_baz"); + + exps = expand_glob("*_test", candidates); + ATF_REQUIRE_EQ(exps.size(), 3); + ATF_REQUIRE(exps[0] == "foo_test"); + ATF_REQUIRE(exps[1] == "bar_test"); + ATF_REQUIRE(exps[2] == "baz_test"); +} + +// ------------------------------------------------------------------------ +// Main. +// ------------------------------------------------------------------------ + +ATF_INIT_TEST_CASES(tcs) +{ + // Add the tests for the free functions. + ATF_ADD_TEST_CASE(tcs, is_glob); + ATF_ADD_TEST_CASE(tcs, matches_glob_plain); + ATF_ADD_TEST_CASE(tcs, matches_glob_star); + ATF_ADD_TEST_CASE(tcs, matches_glob_question); + ATF_ADD_TEST_CASE(tcs, expand_glob_base); + ATF_ADD_TEST_CASE(tcs, expand_glob_tps); +} diff --git a/unit/atf-src/tools/expect_helpers.c b/unit/atf-src/tools/expect_helpers.c new file mode 100644 index 0000000..b38ccf5 --- /dev/null +++ b/unit/atf-src/tools/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/unit/atf-src/tools/fail_helper.cpp b/unit/atf-src/tools/fail_helper.cpp new file mode 100644 index 0000000..736b5e8 --- /dev/null +++ b/unit/atf-src/tools/fail_helper.cpp @@ -0,0 +1,45 @@ +// +// 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++.hpp> + +ATF_TEST_CASE(main); +ATF_TEST_CASE_HEAD(main) +{ + set_md_var("descr", "Helper test case that always fails"); +} +ATF_TEST_CASE_BODY(main) +{ + fail("This always fails"); +} + +ATF_INIT_TEST_CASES(tcs) +{ + ATF_ADD_TEST_CASE(tcs, main); +} diff --git a/unit/atf-src/tools/fs.cpp b/unit/atf-src/tools/fs.cpp new file mode 100644 index 0000000..deed28c --- /dev/null +++ b/unit/atf-src/tools/fs.cpp @@ -0,0 +1,744 @@ +// +// 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 "config.h" +#endif + +extern "C" { +#include <sys/param.h> +#include <sys/types.h> +#include <sys/mount.h> +#include <sys/stat.h> +#include <sys/wait.h> + +#include <dirent.h> +#include <libgen.h> +#include <unistd.h> +} + +#include <cassert> +#include <cerrno> +#include <cstdlib> +#include <cstring> + +#include "auto_array.hpp" +#include "env.hpp" +#include "exceptions.hpp" +#include "fs.hpp" +#include "process.hpp" +#include "text.hpp" +#include "user.hpp" + +namespace impl = tools::fs; +#define IMPL_NAME "tools::fs" + +// ------------------------------------------------------------------------ +// Auxiliary functions. +// ------------------------------------------------------------------------ + +static void cleanup_aux(const impl::path&, dev_t, bool); +static void cleanup_aux_dir(const impl::path&, const impl::file_info&, + bool); +static void do_unmount(const impl::path&); +static bool safe_access(const impl::path&, int, int); + +static const int access_f = 1 << 0; +static const int access_r = 1 << 1; +static const int access_w = 1 << 2; +static const int access_x = 1 << 3; + +//! +//! An implementation of access(2) but using the effective user value +//! instead of the real one. Also avoids false positives for root when +//! asking for execute permissions, which appear in SunOS. +//! +static +void +eaccess(const tools::fs::path& p, int mode) +{ + assert(mode & access_f || mode & access_r || + mode & access_w || mode & access_x); + + struct stat st; + if (lstat(p.c_str(), &st) == -1) + throw tools::system_error(IMPL_NAME "::eaccess", + "Cannot get information from file " + + p.str(), errno); + + /* Early return if we are only checking for existence and the file + * exists (stat call returned). */ + if (mode & access_f) + return; + + bool ok = false; + if (tools::user::is_root()) { + if (!ok && !(mode & access_x)) { + /* Allow root to read/write any file. */ + ok = true; + } + + if (!ok && (st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))) { + /* Allow root to execute the file if any of its execution bits + * are set. */ + ok = true; + } + } else { + if (!ok && (tools::user::euid() == st.st_uid)) { + ok = ((mode & access_r) && (st.st_mode & S_IRUSR)) || + ((mode & access_w) && (st.st_mode & S_IWUSR)) || + ((mode & access_x) && (st.st_mode & S_IXUSR)); + } + if (!ok && tools::user::is_member_of_group(st.st_gid)) { + ok = ((mode & access_r) && (st.st_mode & S_IRGRP)) || + ((mode & access_w) && (st.st_mode & S_IWGRP)) || + ((mode & access_x) && (st.st_mode & S_IXGRP)); + } + if (!ok && ((tools::user::euid() != st.st_uid) && + !tools::user::is_member_of_group(st.st_gid))) { + ok = ((mode & access_r) && (st.st_mode & S_IROTH)) || + ((mode & access_w) && (st.st_mode & S_IWOTH)) || + ((mode & access_x) && (st.st_mode & S_IXOTH)); + } + } + + if (!ok) + throw tools::system_error(IMPL_NAME "::eaccess", "Access check failed", + EACCES); +} + +//! +//! \brief A controlled version of access(2). +//! +//! This function reimplements the standard access(2) system call to +//! safely control its exit status and raise an exception in case of +//! failure. +//! +static +bool +safe_access(const impl::path& p, int mode, int experr) +{ + try { + eaccess(p, mode); + return true; + } catch (const tools::system_error& e) { + if (e.code() == experr) + return false; + else + throw e; + } +} + +// 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 impl::path& p, dev_t parent_device, bool erase) +{ + try { + impl::file_info fi(p); + + if (fi.get_type() == impl::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() == impl::file_info::dir_type) + impl::rmdir(p); + else + impl::remove(p); + } + } catch (const tools::system_error& e) { + if (e.code() != ENOENT && e.code() != ENOTDIR) + throw e; + } +} + +static +void +cleanup_aux_dir(const impl::path& p, const impl::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 tools::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) { + assert(retries > 0); + try { + const impl::directory d(p); + subdirs = d.names(); + ok = true; + } catch (const tools::system_error& e) { + retries--; + if (retries == 0) + throw e; + ::sleep(retry_delay_in_seconds); + } + } + assert(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 impl::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 impl::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 tools::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 + // stools::fser to just leave the system-specific umount(8) tool deal + // with it, at least for now. + + const impl::path prog("umount"); + tools::process::argv_array argv("umount", abs_path.c_str(), NULL); + + tools::process::status s = tools::process::exec(prog, argv, + tools::process::stream_inherit(), tools::process::stream_inherit()); + if (!s.exited() || s.exitstatus() != EXIT_SUCCESS) + throw std::runtime_error("Call to unmount failed"); +#endif +} + +static +std::string +normalize(const std::string& in) +{ + assert(!in.empty()); + + std::string out; + + std::string::size_type pos = 0; + do { + const std::string::size_type next_pos = in.find('/', pos); + + const std::string component = in.substr(pos, next_pos - pos); + if (!component.empty()) { + if (pos == 0) + out += component; + else if (component != ".") + out += "/" + component; + } + + if (next_pos == std::string::npos) + pos = next_pos; + else + pos = next_pos + 1; + } while (pos != std::string::npos); + + return out.empty() ? "/" : out; +} + +// ------------------------------------------------------------------------ +// The "path" class. +// ------------------------------------------------------------------------ + +impl::path::path(const std::string& s) : + m_data(normalize(s)) +{ +} + +impl::path::~path(void) +{ +} + +const char* +impl::path::c_str(void) + const +{ + return m_data.c_str(); +} + +std::string +impl::path::str(void) + const +{ + return m_data; +} + +bool +impl::path::is_absolute(void) + const +{ + return !m_data.empty() && m_data[0] == '/'; +} + +bool +impl::path::is_root(void) + const +{ + return m_data == "/"; +} + +impl::path +impl::path::branch_path(void) + const +{ + const std::string::size_type endpos = m_data.rfind('/'); + if (endpos == std::string::npos) + return path("."); + else if (endpos == 0) + return path("/"); + else + return path(m_data.substr(0, endpos)); +} + +std::string +impl::path::leaf_name(void) + const +{ + std::string::size_type begpos = m_data.rfind('/'); + if (begpos == std::string::npos) + begpos = 0; + else + begpos++; + + return m_data.substr(begpos); +} + +impl::path +impl::path::to_absolute(void) + const +{ + assert(!is_absolute()); + return get_current_dir() / m_data; +} + +bool +impl::path::operator==(const path& p) + const +{ + return m_data == p.m_data; +} + +bool +impl::path::operator!=(const path& p) + const +{ + return m_data != p.m_data; +} + +impl::path +impl::path::operator/(const std::string& p) + const +{ + return path(m_data + "/" + normalize(p)); +} + +impl::path +impl::path::operator/(const path& p) + const +{ + return path(m_data) / p.m_data; +} + +bool +impl::path::operator<(const path& p) + const +{ + return std::strcmp(m_data.c_str(), p.m_data.c_str()) < 0; +} + +// ------------------------------------------------------------------------ +// The "file_info" class. +// ------------------------------------------------------------------------ + +const int impl::file_info::blk_type = 1; +const int impl::file_info::chr_type = 2; +const int impl::file_info::dir_type = 3; +const int impl::file_info::fifo_type = 4; +const int impl::file_info::lnk_type = 5; +const int impl::file_info::reg_type = 6; +const int impl::file_info::sock_type = 7; +const int impl::file_info::wht_type = 8; + +impl::file_info::file_info(const path& p) +{ + if (lstat(p.c_str(), &m_sb) == -1) + throw system_error(IMPL_NAME "::file_info", + "Cannot get information of " + p.str() + "; " + + "lstat(2) failed", errno); + + int type = m_sb.st_mode & S_IFMT; + switch (type) { + case S_IFBLK: m_type = blk_type; break; + case S_IFCHR: m_type = chr_type; break; + case S_IFDIR: m_type = dir_type; break; + case S_IFIFO: m_type = fifo_type; break; + case S_IFLNK: m_type = lnk_type; break; + case S_IFREG: m_type = reg_type; break; + case S_IFSOCK: m_type = sock_type; break; +#if defined(S_IFWHT) + case S_IFWHT: m_type = wht_type; break; +#endif + default: + throw system_error(IMPL_NAME "::file_info", "Unknown file type " + "error", EINVAL); + } +} + +impl::file_info::~file_info(void) +{ +} + +dev_t +impl::file_info::get_device(void) + const +{ + return m_sb.st_dev; +} + +ino_t +impl::file_info::get_inode(void) + const +{ + return m_sb.st_ino; +} + +mode_t +impl::file_info::get_mode(void) + const +{ + return m_sb.st_mode & ~S_IFMT; +} + +off_t +impl::file_info::get_size(void) + const +{ + return m_sb.st_size; +} + +int +impl::file_info::get_type(void) + const +{ + return m_type; +} + +bool +impl::file_info::is_owner_readable(void) + const +{ + return m_sb.st_mode & S_IRUSR; +} + +bool +impl::file_info::is_owner_writable(void) + const +{ + return m_sb.st_mode & S_IWUSR; +} + +bool +impl::file_info::is_owner_executable(void) + const +{ + return m_sb.st_mode & S_IXUSR; +} + +bool +impl::file_info::is_group_readable(void) + const +{ + return m_sb.st_mode & S_IRGRP; +} + +bool +impl::file_info::is_group_writable(void) + const +{ + return m_sb.st_mode & S_IWGRP; +} + +bool +impl::file_info::is_group_executable(void) + const +{ + return m_sb.st_mode & S_IXGRP; +} + +bool +impl::file_info::is_other_readable(void) + const +{ + return m_sb.st_mode & S_IROTH; +} + +bool +impl::file_info::is_other_writable(void) + const +{ + return m_sb.st_mode & S_IWOTH; +} + +bool +impl::file_info::is_other_executable(void) + const +{ + return m_sb.st_mode & S_IXOTH; +} + +// ------------------------------------------------------------------------ +// The "directory" class. +// ------------------------------------------------------------------------ + +impl::directory::directory(const path& p) +{ + DIR* dp = ::opendir(p.c_str()); + if (dp == NULL) + throw system_error(IMPL_NAME "::directory::directory(" + + p.str() + ")", "opendir(3) failed", errno); + + struct dirent* dep; + while ((dep = ::readdir(dp)) != NULL) { + path entryp = p / dep->d_name; + insert(value_type(dep->d_name, file_info(entryp))); + } + + if (::closedir(dp) == -1) + throw system_error(IMPL_NAME "::directory::directory(" + + p.str() + ")", "closedir(3) failed", errno); +} + +std::set< std::string > +impl::directory::names(void) + const +{ + std::set< std::string > ns; + + for (const_iterator iter = begin(); iter != end(); iter++) + ns.insert((*iter).first); + + return ns; +} + +// ------------------------------------------------------------------------ +// The "temp_dir" class. +// ------------------------------------------------------------------------ + +impl::temp_dir::temp_dir(const path& p) +{ + tools::auto_array< char > buf(new char[p.str().length() + 1]); + std::strcpy(buf.get(), p.c_str()); + if (::mkdtemp(buf.get()) == NULL) + throw tools::system_error(IMPL_NAME "::temp_dir::temp_dir(" + + p.str() + ")", "mkdtemp(3) failed", + errno); + + m_path.reset(new path(buf.get())); +} + +impl::temp_dir::~temp_dir(void) +{ + cleanup(*m_path); +} + +const impl::path& +impl::temp_dir::get_path(void) + const +{ + return *m_path; +} + +// ------------------------------------------------------------------------ +// Free functions. +// ------------------------------------------------------------------------ + +bool +impl::exists(const path& p) +{ + try { + eaccess(p, access_f); + return true; + } catch (const system_error& e) { + if (e.code() == ENOENT) + return false; + else + throw; + } +} + +bool +impl::have_prog_in_path(const std::string& prog) +{ + assert(prog.find('/') == std::string::npos); + + // Do not bother to provide a default value for PATH. If it is not + // there something is broken in the user's environment. + if (!tools::env::has("PATH")) + throw std::runtime_error("PATH not defined in the environment"); + std::vector< std::string > dirs = + tools::text::split(tools::env::get("PATH"), ":"); + + bool found = false; + for (std::vector< std::string >::const_iterator iter = dirs.begin(); + !found && iter != dirs.end(); iter++) { + const path& dir = path(*iter); + + if (is_executable(dir / prog)) + found = true; + } + return found; +} + +bool +impl::is_executable(const path& p) +{ + if (!exists(p)) + return false; + return safe_access(p, access_x, EACCES); +} + +void +impl::remove(const path& p) +{ + if (file_info(p).get_type() == file_info::dir_type) + throw tools::system_error(IMPL_NAME "::remove(" + p.str() + ")", + "Is a directory", + EPERM); + if (::unlink(p.c_str()) == -1) + throw tools::system_error(IMPL_NAME "::remove(" + p.str() + ")", + "unlink(" + p.str() + ") failed", + errno); +} + +void +impl::rmdir(const path& p) +{ + if (::rmdir(p.c_str())) { + if (errno == EEXIST) { + /* Some operating systems (e.g. OpenSolaris 200906) return + * EEXIST instead of ENOTEMPTY for non-empty directories. + * Homogenize the return value so that callers don't need + * to bother about differences in operating systems. */ + errno = ENOTEMPTY; + } + throw system_error(IMPL_NAME "::rmdir", "Cannot remove directory", + errno); + } +} + +impl::path +impl::change_directory(const path& dir) +{ + path olddir = get_current_dir(); + + if (olddir != dir) { + if (::chdir(dir.c_str()) == -1) + throw tools::system_error(IMPL_NAME "::chdir(" + dir.str() + ")", + "chdir(2) failed", errno); + } + + return olddir; +} + +void +impl::cleanup(const path& p) +{ + impl::file_info fi(p); + cleanup_aux(p, fi.get_device(), true); +} + +impl::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 tools::system_error(IMPL_NAME "::get_current_dir()", + "getcwd() failed", errno); + + return path(cwd.get()); +} diff --git a/unit/atf-src/tools/fs.hpp b/unit/atf-src/tools/fs.hpp new file mode 100644 index 0000000..f45f8f1 --- /dev/null +++ b/unit/atf-src/tools/fs.hpp @@ -0,0 +1,377 @@ +// +// 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(TOOLS_FS_HPP) +#define TOOLS_FS_HPP + +extern "C" { +#include <sys/types.h> +#include <sys/stat.h> +} + +#include <map> +#include <memory> +#include <ostream> +#include <set> +#include <stdexcept> +#include <string> + +namespace tools { +namespace fs { + +// ------------------------------------------------------------------------ +// The "path" class. +// ------------------------------------------------------------------------ + +//! +//! \brief A class to represent a path to a file. +//! +//! The path class represents the route to a file or directory in the +//! file system. All file manipulation operations use this class to +//! represent their arguments as it takes care of normalizing user-provided +//! strings and ensures they are valid. +//! +//! It is important to note that the file pointed to by a path need not +//! exist. +//! +class path { + //! + //! \brief Internal representation of a path. + //! + std::string m_data; + +public: + //! \brief Constructs a new path from a user-provided string. + //! + //! This constructor takes a string, either provided by the program's + //! code or by the user and constructs a new path object. The string + //! is normalized to not contain multiple delimiters together and to + //! remove any trailing one. + //! + //! The input string cannot be empty. + //! + explicit path(const std::string&); + + //! + //! \brief Destructor for the path class. + //! + ~path(void); + + //! + //! \brief Returns a pointer to a C-style string representing this path. + //! + const char* c_str(void) const; + + //! + //! \brief Returns a string representing this path. + //! XXX Really needed? + //! + std::string str(void) const; + + //! + //! \brief Returns the branch path of this path. + //! + //! Calculates and returns the branch path of this path. In other + //! words, it returns what the standard ::dirname function would return. + //! + path branch_path(void) const; + + //! + //! \brief Returns the leaf name of this path. + //! + //! Calculates and returns the leaf name of this path. In other words, + //! it returns what the standard ::basename function would return. + //! + std::string leaf_name(void) const; + + //! + //! \brief Checks whether this path is absolute or not. + //! + //! Returns a boolean indicating if this is an absolute path or not; + //! i.e. if it starts with a slash. + //! + bool is_absolute(void) const; + + //! + //! \brief Checks whether this path points to the root directory or not. + //! + //! Returns a boolean indicating if this is path points to the root + //! directory or not. The checks made by this are extremely simple (so + //! the results cannot always be trusted) but they are enough for our + //! modest sanity-checking needs. I.e. "/../" could return false. + //! + bool is_root(void) const; + + //! + //! \brief Converts the path to be absolute. + //! + //! \pre The path was not absolute. + //! + path to_absolute(void) const; + + //! + //! \brief Checks if two paths are equal. + //! + bool operator==(const path&) const; + + //! + //! \brief Checks if two paths are different. + //! + bool operator!=(const path&) const; + + //! + //! \brief Concatenates a path with a string. + //! + //! Constructs a new path object that is the concatenation of the + //! left-hand path with the right-hand string. The string is normalized + //! before the concatenation, and a path delimiter is introduced between + //! the two components if needed. + //! + path operator/(const std::string&) const; + + //! + //! \brief Concatenates a path with another path. + //! + //! Constructs a new path object that is the concatenation of the + //! left-hand path with the right-hand one. A path delimiter is + //! introduced between the two components if needed. + //! + path operator/(const path&) const; + + //! + //! \brief Checks if a path has to be sorted before another one + //! lexicographically. + //! + bool operator<(const path&) const; +}; + +// ------------------------------------------------------------------------ +// The "file_info" class. +// ------------------------------------------------------------------------ + +class directory; + +//! +//! \brief A class that contains information about a file. +//! +//! The file_info class holds information about an specific file that +//! exists in the file system. +//! +class file_info { + int m_type; + struct stat m_sb; + +public: + //! + //! \brief The file's type. + //! + static const int blk_type; + static const int chr_type; + static const int dir_type; + static const int fifo_type; + static const int lnk_type; + static const int reg_type; + static const int sock_type; + static const int wht_type; + + //! + //! \brief Constructs a new file_info based on a given file. + //! + //! This constructor creates a new file_info object and fills it with + //! the data returned by ::stat when run on the given file, which must + //! exist. + //! + explicit file_info(const path&); + + //! + //! \brief The destructor. + //! + ~file_info(void); + + //! + //! \brief Returns the device containing the file. + //! + dev_t get_device(void) const; + + //! + //! \brief Returns the file's inode. + //! + ino_t get_inode(void) const; + + //! + //! \brief Returns the file's permissions. + //! + mode_t get_mode(void) const; + + //! + //! \brief Returns the file's size. + //! + off_t get_size(void) const; + + //! + //! \brief Returns the file's type. + //! + int get_type(void) const; + + //! + //! \brief Returns whether the file is readable by its owner or not. + //! + bool is_owner_readable(void) const; + + //! + //! \brief Returns whether the file is writable by its owner or not. + //! + bool is_owner_writable(void) const; + + //! + //! \brief Returns whether the file is executable by its owner or not. + //! + bool is_owner_executable(void) const; + + //! + //! \brief Returns whether the file is readable by the users belonging + //! to its group or not. + //! + bool is_group_readable(void) const; + + //! + //! \brief Returns whether the file is writable the users belonging to + //! its group or not. + //! + bool is_group_writable(void) const; + + //! + //! \brief Returns whether the file is executable by the users + //! belonging to its group or not. + //! + bool is_group_executable(void) const; + + //! + //! \brief Returns whether the file is readable by people different + //! than the owner and those belonging to the group or not. + //! + bool is_other_readable(void) const; + + //! + //! \brief Returns whether the file is write by people different + //! than the owner and those belonging to the group or not. + //! + bool is_other_writable(void) const; + + //! + //! \brief Returns whether the file is executable by people different + //! than the owner and those belonging to the group or not. + //! + bool is_other_executable(void) const; +}; + +// ------------------------------------------------------------------------ +// The "directory" class. +// ------------------------------------------------------------------------ + +//! +//! \brief A class representing a file system directory. +//! +//! The directory class represents a group of files in the file system and +//! corresponds to exactly one directory. +//! +class directory : public std::map< std::string, file_info > { +public: + //! + //! \brief Constructs a new directory. + //! + //! Constructs a new directory object representing the given path. + //! The directory must exist at creation time as the contents of the + //! class are gathered from it. + //! + directory(const path&); + + //! + //! \brief Returns the file names of the files in the directory. + //! + //! Returns the leaf names of all files contained in the directory. + //! I.e. the keys of the directory map. + //! + std::set< std::string > names(void) const; +}; + +// ------------------------------------------------------------------------ +// The "temp_dir" class. +// ------------------------------------------------------------------------ + +class temp_dir { + std::auto_ptr< tools::fs::path > m_path; + +public: + temp_dir(const tools::fs::path&); + ~temp_dir(void); + + const tools::fs::path& get_path(void) const; +}; + +// ------------------------------------------------------------------------ +// Free functions. +// ------------------------------------------------------------------------ + +//! +//! \brief Checks if the given path exists. +//! +bool exists(const path&); + +//! +//! \brief Looks for the given program in the PATH. +//! +//! Given a program name (without slashes) looks for it in the path and +//! returns its full path name if found, otherwise an empty path. +//! +bool have_prog_in_path(const std::string&); + +//! +//! \brief Checks if the given path exists, is accessible and is executable. +//! +bool is_executable(const path&); + +//! +//! \brief Removes a given file. +//! +void remove(const path&); + +//! +//! \brief Removes an empty directory. +//! +void rmdir(const path&); + +tools::fs::path change_directory(const tools::fs::path&); +void cleanup(const tools::fs::path&); +tools::fs::path get_current_dir(void); + +} // namespace fs +} // namespace tools + +#endif // !defined(TOOLS_FS_HPP) diff --git a/unit/atf-src/tools/fs_test.cpp b/unit/atf-src/tools/fs_test.cpp new file mode 100644 index 0000000..5f482a4 --- /dev/null +++ b/unit/atf-src/tools/fs_test.cpp @@ -0,0 +1,743 @@ +// +// 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 <cstdio> +#include <fstream> + +#include <atf-c++.hpp> + +#include "exceptions.hpp" +#include "fs.hpp" +#include "user.hpp" + +// ------------------------------------------------------------------------ +// Auxiliary functions. +// ------------------------------------------------------------------------ + +static +void +create_file(const char *name) +{ + std::ofstream os(name); + os.close(); +} + +static +void +create_files(void) +{ + ::mkdir("files", 0755); + ::mkdir("files/dir", 0755); + + std::ofstream os("files/reg"); + os.close(); + + // TODO: Should create all other file types (blk, chr, fifo, lnk, sock) + // and test for them... but the underlying file system may not support + // most of these. Specially as we are working on /tmp, which can be + // mounted with flags such as "nodev". See how to deal with this + // situation. +} + +// ------------------------------------------------------------------------ +// Test cases for the "path" class. +// ------------------------------------------------------------------------ + +ATF_TEST_CASE(path_normalize); +ATF_TEST_CASE_HEAD(path_normalize) +{ + set_md_var("descr", "Tests the path's normalization"); +} +ATF_TEST_CASE_BODY(path_normalize) +{ + using tools::fs::path; + + ATF_REQUIRE_EQ(path(".").str(), "."); + ATF_REQUIRE_EQ(path("..").str(), ".."); + + ATF_REQUIRE_EQ(path("foo").str(), "foo"); + ATF_REQUIRE_EQ(path("foo/bar").str(), "foo/bar"); + ATF_REQUIRE_EQ(path("foo/bar/").str(), "foo/bar"); + + ATF_REQUIRE_EQ(path("/foo").str(), "/foo"); + ATF_REQUIRE_EQ(path("/foo/bar").str(), "/foo/bar"); + ATF_REQUIRE_EQ(path("/foo/bar/").str(), "/foo/bar"); + + ATF_REQUIRE_EQ(path("///foo").str(), "/foo"); + ATF_REQUIRE_EQ(path("///foo///bar").str(), "/foo/bar"); + ATF_REQUIRE_EQ(path("///foo///bar///").str(), "/foo/bar"); +} + +ATF_TEST_CASE(path_is_absolute); +ATF_TEST_CASE_HEAD(path_is_absolute) +{ + set_md_var("descr", "Tests the path::is_absolute function"); +} +ATF_TEST_CASE_BODY(path_is_absolute) +{ + using tools::fs::path; + + ATF_REQUIRE( path("/").is_absolute()); + ATF_REQUIRE( path("////").is_absolute()); + ATF_REQUIRE( path("////a").is_absolute()); + ATF_REQUIRE( path("//a//").is_absolute()); + ATF_REQUIRE(!path("a////").is_absolute()); + ATF_REQUIRE(!path("../foo").is_absolute()); +} + +ATF_TEST_CASE(path_is_root); +ATF_TEST_CASE_HEAD(path_is_root) +{ + set_md_var("descr", "Tests the path::is_root function"); +} +ATF_TEST_CASE_BODY(path_is_root) +{ + using tools::fs::path; + + ATF_REQUIRE( path("/").is_root()); + ATF_REQUIRE( path("////").is_root()); + ATF_REQUIRE(!path("////a").is_root()); + ATF_REQUIRE(!path("//a//").is_root()); + ATF_REQUIRE(!path("a////").is_root()); + ATF_REQUIRE(!path("../foo").is_root()); +} + +ATF_TEST_CASE(path_branch_path); +ATF_TEST_CASE_HEAD(path_branch_path) +{ + set_md_var("descr", "Tests the path::branch_path function"); +} +ATF_TEST_CASE_BODY(path_branch_path) +{ + using tools::fs::path; + + ATF_REQUIRE_EQ(path(".").branch_path().str(), "."); + ATF_REQUIRE_EQ(path("foo").branch_path().str(), "."); + ATF_REQUIRE_EQ(path("foo/bar").branch_path().str(), "foo"); + ATF_REQUIRE_EQ(path("/foo").branch_path().str(), "/"); + ATF_REQUIRE_EQ(path("/foo/bar").branch_path().str(), "/foo"); +} + +ATF_TEST_CASE(path_leaf_name); +ATF_TEST_CASE_HEAD(path_leaf_name) +{ + set_md_var("descr", "Tests the path::leaf_name function"); +} +ATF_TEST_CASE_BODY(path_leaf_name) +{ + using tools::fs::path; + + ATF_REQUIRE_EQ(path(".").leaf_name(), "."); + ATF_REQUIRE_EQ(path("foo").leaf_name(), "foo"); + ATF_REQUIRE_EQ(path("foo/bar").leaf_name(), "bar"); + ATF_REQUIRE_EQ(path("/foo").leaf_name(), "foo"); + ATF_REQUIRE_EQ(path("/foo/bar").leaf_name(), "bar"); +} + +ATF_TEST_CASE(path_compare_equal); +ATF_TEST_CASE_HEAD(path_compare_equal) +{ + set_md_var("descr", "Tests the comparison for equality between paths"); +} +ATF_TEST_CASE_BODY(path_compare_equal) +{ + using tools::fs::path; + + ATF_REQUIRE(path("/") == path("///")); + ATF_REQUIRE(path("/a") == path("///a")); + ATF_REQUIRE(path("/a") == path("///a///")); + + ATF_REQUIRE(path("a/b/c") == path("a//b//c")); + ATF_REQUIRE(path("a/b/c") == path("a//b//c///")); +} + +ATF_TEST_CASE(path_compare_different); +ATF_TEST_CASE_HEAD(path_compare_different) +{ + set_md_var("descr", "Tests the comparison for difference between paths"); +} +ATF_TEST_CASE_BODY(path_compare_different) +{ + using tools::fs::path; + + ATF_REQUIRE(path("/") != path("//a/")); + ATF_REQUIRE(path("/a") != path("a///")); + + ATF_REQUIRE(path("a/b/c") != path("a/b")); + ATF_REQUIRE(path("a/b/c") != path("a//b")); + ATF_REQUIRE(path("a/b/c") != path("/a/b/c")); + ATF_REQUIRE(path("a/b/c") != path("/a//b//c")); +} + +ATF_TEST_CASE(path_concat); +ATF_TEST_CASE_HEAD(path_concat) +{ + set_md_var("descr", "Tests the concatenation of multiple paths"); +} +ATF_TEST_CASE_BODY(path_concat) +{ + using tools::fs::path; + + ATF_REQUIRE_EQ((path("foo") / "bar").str(), "foo/bar"); + ATF_REQUIRE_EQ((path("foo/") / "/bar").str(), "foo/bar"); + ATF_REQUIRE_EQ((path("foo/") / "/bar/baz").str(), "foo/bar/baz"); + ATF_REQUIRE_EQ((path("foo/") / "///bar///baz").str(), "foo/bar/baz"); +} + +ATF_TEST_CASE(path_to_absolute); +ATF_TEST_CASE_HEAD(path_to_absolute) +{ + set_md_var("descr", "Tests the conversion of a relative path to an " + "absolute one"); +} +ATF_TEST_CASE_BODY(path_to_absolute) +{ + using tools::fs::file_info; + using tools::fs::path; + + create_files(); + + { + const path p("."); + path pa = p.to_absolute(); + ATF_REQUIRE(pa.is_absolute()); + + file_info fi(p); + file_info fia(pa); + ATF_REQUIRE_EQ(fi.get_device(), fia.get_device()); + ATF_REQUIRE_EQ(fi.get_inode(), fia.get_inode()); + } + + { + const path p("files/reg"); + path pa = p.to_absolute(); + ATF_REQUIRE(pa.is_absolute()); + + file_info fi(p); + file_info fia(pa); + ATF_REQUIRE_EQ(fi.get_device(), fia.get_device()); + ATF_REQUIRE_EQ(fi.get_inode(), fia.get_inode()); + } +} + +ATF_TEST_CASE(path_op_less); +ATF_TEST_CASE_HEAD(path_op_less) +{ + set_md_var("descr", "Tests that the path's less-than operator works"); +} +ATF_TEST_CASE_BODY(path_op_less) +{ + using tools::fs::path; + + create_files(); + + ATF_REQUIRE(!(path("aaa") < path("aaa"))); + + ATF_REQUIRE( path("aab") < path("abc")); + ATF_REQUIRE(!(path("abc") < path("aab"))); +} + +// ------------------------------------------------------------------------ +// Test cases for the "directory" class. +// ------------------------------------------------------------------------ + +ATF_TEST_CASE(directory_read); +ATF_TEST_CASE_HEAD(directory_read) +{ + set_md_var("descr", "Tests the directory class creation, which reads " + "the contents of a directory"); +} +ATF_TEST_CASE_BODY(directory_read) +{ + using tools::fs::directory; + using tools::fs::path; + + create_files(); + + directory d(path("files")); + ATF_REQUIRE_EQ(d.size(), 4); + ATF_REQUIRE(d.find(".") != d.end()); + ATF_REQUIRE(d.find("..") != d.end()); + ATF_REQUIRE(d.find("dir") != d.end()); + ATF_REQUIRE(d.find("reg") != d.end()); +} + +ATF_TEST_CASE(directory_file_info); +ATF_TEST_CASE_HEAD(directory_file_info) +{ + set_md_var("descr", "Tests that the file_info objects attached to the " + "directory are valid"); +} +ATF_TEST_CASE_BODY(directory_file_info) +{ + using tools::fs::directory; + using tools::fs::file_info; + using tools::fs::path; + + create_files(); + + directory d(path("files")); + + { + directory::const_iterator iter = d.find("dir"); + ATF_REQUIRE(iter != d.end()); + const file_info& fi = (*iter).second; + ATF_REQUIRE(fi.get_type() == file_info::dir_type); + } + + { + directory::const_iterator iter = d.find("reg"); + ATF_REQUIRE(iter != d.end()); + const file_info& fi = (*iter).second; + ATF_REQUIRE(fi.get_type() == file_info::reg_type); + } +} + +ATF_TEST_CASE(directory_names); +ATF_TEST_CASE_HEAD(directory_names) +{ + set_md_var("descr", "Tests the directory's names method"); +} +ATF_TEST_CASE_BODY(directory_names) +{ + using tools::fs::directory; + using tools::fs::path; + + create_files(); + + directory d(path("files")); + std::set< std::string > ns = d.names(); + ATF_REQUIRE_EQ(ns.size(), 4); + ATF_REQUIRE(ns.find(".") != ns.end()); + ATF_REQUIRE(ns.find("..") != ns.end()); + ATF_REQUIRE(ns.find("dir") != ns.end()); + ATF_REQUIRE(ns.find("reg") != ns.end()); +} + +// ------------------------------------------------------------------------ +// Test cases for the "file_info" class. +// ------------------------------------------------------------------------ + +ATF_TEST_CASE(file_info_stat); +ATF_TEST_CASE_HEAD(file_info_stat) +{ + set_md_var("descr", "Tests the file_info creation and its basic contents"); +} +ATF_TEST_CASE_BODY(file_info_stat) +{ + using tools::fs::file_info; + using tools::fs::path; + + create_files(); + + { + path p("files/dir"); + file_info fi(p); + ATF_REQUIRE(fi.get_type() == file_info::dir_type); + } + + { + path p("files/reg"); + file_info fi(p); + ATF_REQUIRE(fi.get_type() == file_info::reg_type); + } +} + +ATF_TEST_CASE(file_info_perms); +ATF_TEST_CASE_HEAD(file_info_perms) +{ + set_md_var("descr", "Tests the file_info methods to get the file's " + "permissions"); +} +ATF_TEST_CASE_BODY(file_info_perms) +{ + using tools::fs::file_info; + using tools::fs::path; + + path p("file"); + + std::ofstream os(p.c_str()); + os.close(); + +#define perms(ur, uw, ux, gr, gw, gx, othr, othw, othx) \ + { \ + file_info fi(p); \ + ATF_REQUIRE(fi.is_owner_readable() == ur); \ + ATF_REQUIRE(fi.is_owner_writable() == uw); \ + ATF_REQUIRE(fi.is_owner_executable() == ux); \ + ATF_REQUIRE(fi.is_group_readable() == gr); \ + ATF_REQUIRE(fi.is_group_writable() == gw); \ + ATF_REQUIRE(fi.is_group_executable() == gx); \ + ATF_REQUIRE(fi.is_other_readable() == othr); \ + ATF_REQUIRE(fi.is_other_writable() == othw); \ + ATF_REQUIRE(fi.is_other_executable() == othx); \ + } + + ::chmod(p.c_str(), 0000); + perms(false, false, false, false, false, false, false, false, false); + + ::chmod(p.c_str(), 0001); + perms(false, false, false, false, false, false, false, false, true); + + ::chmod(p.c_str(), 0010); + perms(false, false, false, false, false, true, false, false, false); + + ::chmod(p.c_str(), 0100); + perms(false, false, true, false, false, false, false, false, false); + + ::chmod(p.c_str(), 0002); + perms(false, false, false, false, false, false, false, true, false); + + ::chmod(p.c_str(), 0020); + perms(false, false, false, false, true, false, false, false, false); + + ::chmod(p.c_str(), 0200); + perms(false, true, false, false, false, false, false, false, false); + + ::chmod(p.c_str(), 0004); + perms(false, false, false, false, false, false, true, false, false); + + ::chmod(p.c_str(), 0040); + perms(false, false, false, true, false, false, false, false, false); + + ::chmod(p.c_str(), 0400); + perms(true, false, false, false, false, false, false, false, false); + + ::chmod(p.c_str(), 0644); + perms(true, true, false, true, false, false, true, false, false); + + ::chmod(p.c_str(), 0755); + perms(true, true, true, true, false, true, true, false, true); + + ::chmod(p.c_str(), 0777); + perms(true, true, true, true, true, true, true, true, true); + +#undef perms +} + +// ------------------------------------------------------------------------ +// 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) +{ + tools::fs::path t1("non-existent"); + tools::fs::path t2("non-existent"); + + { + tools::fs::path tmpl("testdir.XXXXXX"); + tools::fs::temp_dir td1(tmpl); + tools::fs::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(!tools::fs::exists(tmpl)); + ATF_REQUIRE( tools::fs::exists(t1)); + ATF_REQUIRE( tools::fs::exists(t2)); + + tools::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()); + + tools::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(!tools::fs::exists(t1)); + ATF_REQUIRE(t2.str() != "non-existent"); + ATF_REQUIRE(!tools::fs::exists(t2)); +} + + +// ------------------------------------------------------------------------ +// Test cases for the free functions. +// ------------------------------------------------------------------------ + +ATF_TEST_CASE(exists); +ATF_TEST_CASE_HEAD(exists) +{ + set_md_var("descr", "Tests the exists function"); +} +ATF_TEST_CASE_BODY(exists) +{ + using tools::fs::exists; + using tools::fs::path; + + create_files(); + + ATF_REQUIRE( exists(path("files"))); + ATF_REQUIRE(!exists(path("file"))); + ATF_REQUIRE(!exists(path("files2"))); + + ATF_REQUIRE( exists(path("files/."))); + ATF_REQUIRE( exists(path("files/.."))); + ATF_REQUIRE( exists(path("files/dir"))); + ATF_REQUIRE( exists(path("files/reg"))); + ATF_REQUIRE(!exists(path("files/foo"))); +} + +ATF_TEST_CASE(is_executable); +ATF_TEST_CASE_HEAD(is_executable) +{ + set_md_var("descr", "Tests the is_executable function"); +} +ATF_TEST_CASE_BODY(is_executable) +{ + using tools::fs::is_executable; + using tools::fs::path; + + create_files(); + + ATF_REQUIRE( is_executable(path("files"))); + ATF_REQUIRE( is_executable(path("files/."))); + ATF_REQUIRE( is_executable(path("files/.."))); + ATF_REQUIRE( is_executable(path("files/dir"))); + + ATF_REQUIRE(!is_executable(path("non-existent"))); + + ATF_REQUIRE(!is_executable(path("files/reg"))); + ATF_REQUIRE(::chmod("files/reg", 0755) != -1); + ATF_REQUIRE( is_executable(path("files/reg"))); +} + +ATF_TEST_CASE(remove); +ATF_TEST_CASE_HEAD(remove) +{ + set_md_var("descr", "Tests the remove function"); +} +ATF_TEST_CASE_BODY(remove) +{ + using tools::fs::exists; + using tools::fs::path; + using tools::fs::remove; + + create_files(); + + ATF_REQUIRE( exists(path("files/reg"))); + remove(path("files/reg")); + ATF_REQUIRE(!exists(path("files/reg"))); + + ATF_REQUIRE( exists(path("files/dir"))); + ATF_REQUIRE_THROW(tools::system_error, remove(path("files/dir"))); + ATF_REQUIRE( exists(path("files/dir"))); +} + +ATF_TEST_CASE(cleanup); +ATF_TEST_CASE_HEAD(cleanup) +{ + set_md_var("descr", "Tests the cleanup function"); +} +ATF_TEST_CASE_BODY(cleanup) +{ + using tools::fs::cleanup; + + ::mkdir("root", 0755); + ::mkdir("root/dir", 0755); + ::mkdir("root/dir/1", 0100); + ::mkdir("root/dir/2", 0644); + create_file("root/reg"); + + tools::fs::path p("root"); + ATF_REQUIRE(tools::fs::exists(p)); + ATF_REQUIRE(tools::fs::exists(p / "dir")); + ATF_REQUIRE(tools::fs::exists(p / "dir/1")); + ATF_REQUIRE(tools::fs::exists(p / "dir/2")); + ATF_REQUIRE(tools::fs::exists(p / "reg")); + cleanup(p); + ATF_REQUIRE(!tools::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 tools::fs::cleanup; + + ::mkdir("aux", 0755); + ::mkdir("aux/root", 0755); + ATF_REQUIRE(::chmod("aux", 0555) != -1); + + try { + cleanup(tools::fs::path("aux/root")); + ATF_REQUIRE(tools::user::is_root()); + } catch (const tools::system_error& e) { + ATF_REQUIRE(!tools::user::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 tools::fs::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 tools::fs::path p("root"); + cleanup(p); + ATF_REQUIRE(!tools::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 tools::fs::change_directory; + using tools::fs::get_current_dir; + + ::mkdir("files", 0755); + ::mkdir("files/dir", 0755); + create_file("files/reg"); + + const tools::fs::path old = get_current_dir(); + + ATF_REQUIRE_THROW(tools::system_error, + change_directory(tools::fs::path("files/reg"))); + ATF_REQUIRE(get_current_dir() == old); + + tools::fs::path old2 = change_directory(tools::fs::path("files")); + ATF_REQUIRE(old2 == old); + tools::fs::path old3 = change_directory(tools::fs::path("dir")); + ATF_REQUIRE(old3 == old2 / "files"); + tools::fs::path old4 = change_directory(tools::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 tools::fs::change_directory; + using tools::fs::get_current_dir; + + ::mkdir("files", 0755); + ::mkdir("files/dir", 0755); + create_file("files/reg"); + + tools::fs::path curdir = get_current_dir(); + change_directory(tools::fs::path(".")); + ATF_REQUIRE(get_current_dir() == curdir); + change_directory(tools::fs::path("files")); + ATF_REQUIRE(get_current_dir() == curdir / "files"); + change_directory(tools::fs::path("dir")); + ATF_REQUIRE(get_current_dir() == curdir / "files/dir"); + change_directory(tools::fs::path("..")); + ATF_REQUIRE(get_current_dir() == curdir / "files"); + change_directory(tools::fs::path("..")); + ATF_REQUIRE(get_current_dir() == curdir); +} + +// ------------------------------------------------------------------------ +// Main. +// ------------------------------------------------------------------------ + +ATF_INIT_TEST_CASES(tcs) +{ + // Add the tests for the "path" class. + ATF_ADD_TEST_CASE(tcs, path_normalize); + ATF_ADD_TEST_CASE(tcs, path_is_absolute); + ATF_ADD_TEST_CASE(tcs, path_is_root); + ATF_ADD_TEST_CASE(tcs, path_branch_path); + ATF_ADD_TEST_CASE(tcs, path_leaf_name); + ATF_ADD_TEST_CASE(tcs, path_compare_equal); + ATF_ADD_TEST_CASE(tcs, path_compare_different); + ATF_ADD_TEST_CASE(tcs, path_concat); + ATF_ADD_TEST_CASE(tcs, path_to_absolute); + ATF_ADD_TEST_CASE(tcs, path_op_less); + + // Add the tests for the "file_info" class. + ATF_ADD_TEST_CASE(tcs, file_info_stat); + ATF_ADD_TEST_CASE(tcs, file_info_perms); + + // Add the tests for the "directory" class. + ATF_ADD_TEST_CASE(tcs, directory_read); + ATF_ADD_TEST_CASE(tcs, directory_names); + ATF_ADD_TEST_CASE(tcs, directory_file_info); + + // 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, exists); + ATF_ADD_TEST_CASE(tcs, is_executable); + ATF_ADD_TEST_CASE(tcs, remove); + 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/unit/atf-src/tools/generate-revision.sh b/unit/atf-src/tools/generate-revision.sh new file mode 100755 index 0000000..169be85 --- /dev/null +++ b/unit/atf-src/tools/generate-revision.sh @@ -0,0 +1,142 @@ +#! /bin/sh +# +# 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. +# + +# +# Generates a header file with information about the revision used to +# build ATF. +# + +set -e + +Prog_Name=${0##*/} + +GIT= +ROOT= + +# +# err message +# +err() { + echo "${Prog_Name}: ${@}" 1>&2 + exit 1 +} + +# +# call_git args +# +call_git() { + ( cd "${ROOT}" && "${GIT}" "${@}" ) +} + +# +# generate_from_dist revfile version +# +generate_from_dist() { + revfile=${1}; shift + version=${1}; shift + + >${revfile} + + echo "#define PACKAGE_REVISION_TYPE_DIST" >>${revfile} +} + +# +# generate_from_git revfile +# +generate_from_git() { + revfile=${1} + + rev_base_id=$(call_git rev-parse HEAD) + rev_branch=$(call_git branch | grep '^\* ' | cut -d ' ' -f 2-) + rev_date=$(call_git log -1 | grep '^Date:' | sed -e 's,^Date:[ \t]*,,') + if [ -z "$(call_git status -s)" ]; then + rev_modified=false + else + rev_modified=true + fi + + >${revfile} + + echo "#define PACKAGE_REVISION_TYPE_GIT" >>${revfile} + + echo "#define PACKAGE_REVISION_BRANCH \"${rev_branch}\"" >>${revfile} + echo "#define PACKAGE_REVISION_BASE \"${rev_base_id}\"" >>${revfile} + + if [ ${rev_modified} = true ]; then + echo "#define PACKAGE_REVISION_MODIFIED 1" >>${revfile} + fi + + echo "#define PACKAGE_REVISION_DATE \"${rev_date}\"" >>${revfile} +} + +# +# main +# +# Entry point. +# +main() { + outfile= + version= + while getopts :g:r:o:v: arg; do + case ${arg} in + g) + GIT=${OPTARG} + ;; + o) + outfile=${OPTARG} + ;; + r) + ROOT=${OPTARG} + ;; + v) + version=${OPTARG} + ;; + *) + err "Unknown option ${arg}" + ;; + esac + done + [ -n "${ROOT}" ] || \ + err "Must specify the top-level source directory with -r" + [ -n "${outfile}" ] || \ + err "Must specify an output file with -o" + [ -n "${version}" ] || \ + err "Must specify a version number with -v" + + if [ -n "${GIT}" -a -d ${ROOT}/.git ]; then + generate_from_git ${outfile} + else + generate_from_dist ${outfile} ${version} + fi +} + +main "${@}" + +# vim: syntax=sh:expandtab:shiftwidth=4:softtabstop=4 diff --git a/unit/atf-src/tools/io.cpp b/unit/atf-src/tools/io.cpp new file mode 100644 index 0000000..4cf8f6a --- /dev/null +++ b/unit/atf-src/tools/io.cpp @@ -0,0 +1,356 @@ +// +// 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 <cassert> +#include <cerrno> +#include <cstring> + +#include "auto_array.hpp" +#include "exceptions.hpp" +#include "io.hpp" + +namespace impl = tools::io; +#define IMPL_NAME "tools::io" + +// ------------------------------------------------------------------------ +// 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) +{ + assert(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) +{ + assert(is_valid()); + + ::close(m_handle); + + m_handle = invalid_value(); +} + +impl::file_handle::handle_type +impl::file_handle::disown(void) +{ + assert(is_valid()); + + handle_type h = m_handle; + m_handle = invalid_value(); + return h; +} + +impl::file_handle::handle_type +impl::file_handle::get(void) + const +{ + assert(is_valid()); + + return m_handle; +} + +void +impl::file_handle::posix_remap(handle_type h) +{ + assert(is_valid()); + + if (m_handle == h) + return; + + if (::dup2(m_handle, h) == -1) + throw tools::system_error(IMPL_NAME "::file_handle::posix_remap", + "dup2(2) failed", errno); + + if (::close(m_handle) == -1) { + ::close(h); + throw tools::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) +{ + assert(m_handle >= 0); + assert(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) +{ + assert(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) +{ + assert(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 tools::system_error(IMPL_NAME "::safe_poll", "poll(2) failed", + errno); + } + assert(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) { + assert(errno != EINTR); + + if (report_errors) + throw tools::system_error(IMPL_NAME "::safe_read", "read(2) failed", + errno); + else + ret = 0; + } + assert(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) +{ + tools::auto_array< char > buffer(new char[m_bufsize]); + const size_t nbytes = safe_read(fd, buffer.get(), m_bufsize - 1, + report_errors); + assert(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) +{ + tools::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; + + assert(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/unit/atf-src/tools/io.hpp b/unit/atf-src/tools/io.hpp new file mode 100644 index 0000000..3fb417b --- /dev/null +++ b/unit/atf-src/tools/io.hpp @@ -0,0 +1,436 @@ +// +// 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(TOOLS_IO_HPP) +#define TOOLS_IO_HPP + +#include <istream> +#include <ostream> +#include <streambuf> + +#include "auto_array.hpp" +#include "fs.hpp" + +namespace tools { +namespace io { + +// ------------------------------------------------------------------------ +// 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 +{ + // Non-copyable. + systembuf(const systembuf&); + systembuf& operator=(const systembuf&); + +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 +{ + // Non-copyable. + pistream(const pistream&); + pistream& operator=(const pistream&); + + //! + //! \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 { + // Non-copyable. + muxer(const muxer&); + muxer& operator=(const muxer&); + + const int* m_fds; + const size_t m_nfds; + + const size_t m_bufsize; + tools::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 io +} // namespace tools + +#endif // !defined(TOOLS_IO_HPP) diff --git a/unit/atf-src/tools/io_test.cpp b/unit/atf-src/tools/io_test.cpp new file mode 100644 index 0000000..b55428f --- /dev/null +++ b/unit/atf-src/tools/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 <cassert> +#include <cerrno> +#include <cstddef> +#include <cstdlib> +#include <cstring> +#include <fstream> +#include <iostream> +#include <istream> +#include <ostream> + +#include <atf-c++.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 tools::io::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 tools::io::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 tools::io::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 tools::io::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 tools::io::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 tools::io::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 tools::io::file_handle; + using tools::io::pistream; + using tools::io::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 tools::io::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) +{ + assert(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); + + tools::signals::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); + std::abort(); + } + ::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/unit/atf-src/tools/misc_helpers.cpp b/unit/atf-src/tools/misc_helpers.cpp new file mode 100644 index 0000000..8ec4dde --- /dev/null +++ b/unit/atf-src/tools/misc_helpers.cpp @@ -0,0 +1,448 @@ +// +// 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++.hpp> + +#include "env.hpp" +#include "fs.hpp" +#include "process.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 "atf-run_test". +// ------------------------------------------------------------------------ + +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: + std::abort(); + } + } +} + +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 tools::process::status s = + tools::process::exec(tools::fs::path("env"), + tools::process::argv_array("env", NULL), + tools::process::stream_inherit(), + tools::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(tools::env::has("HOME")); + tools::fs::path p(tools::env::get("HOME")); + tools::fs::file_info fi1(p); + tools::fs::file_info fi2(tools::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) +{ + tools::fs::remove(tools::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"); +} + +// ------------------------------------------------------------------------ +// Helper tests for "atf-report_test". +// ------------------------------------------------------------------------ + +ATF_TEST_CASE(diff); +ATF_TEST_CASE_HEAD(diff) +{ + set_md_var("descr", "Helper test case for the t_integration program"); +} +ATF_TEST_CASE_BODY(diff) +{ + std::cout << "--- a 2007-11-04 14:00:41.000000000 +0100\n"; + std::cout << "+++ b 2007-11-04 14:00:48.000000000 +0100\n"; + std::cout << "@@ -1,7 +1,7 @@\n"; + std::cout << " This test is meant to simulate a diff.\n"; + std::cout << " Blank space at beginning of context lines must be " + "preserved.\n"; + std::cout << " \n"; + std::cout << "-First original line.\n"; + std::cout << "-Second original line.\n"; + std::cout << "+First modified line.\n"; + std::cout << "+Second modified line.\n"; + std::cout << " \n"; + std::cout << " EOF\n"; +} + +// ------------------------------------------------------------------------ +// Main. +// ------------------------------------------------------------------------ + +ATF_INIT_TEST_CASES(tcs) +{ + std::string which = tools::env::get("TESTCASE"); + + // Add helper tests for atf-run_test. + 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); + + // Add helper tests for atf-report_test. + if (which == "diff") + ATF_ADD_TEST_CASE(tcs, diff); +} diff --git a/unit/atf-src/tools/parser.cpp b/unit/atf-src/tools/parser.cpp new file mode 100644 index 0000000..e6b3a3b --- /dev/null +++ b/unit/atf-src/tools/parser.cpp @@ -0,0 +1,384 @@ +// +// 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 <cassert> +#include <sstream> + +#include "parser.hpp" +#include "text.hpp" + +namespace impl = tools::parser; +#define IMPL_NAME "tools::parser" + +// ------------------------------------------------------------------------ +// The "parse_error" class. +// ------------------------------------------------------------------------ + +impl::parse_error::parse_error(size_t line, std::string msg) : + std::runtime_error(msg), + std::pair< size_t, std::string >(line, msg) +{ +} + +impl::parse_error::~parse_error(void) + throw() +{ +} + +const char* +impl::parse_error::what(void) + const throw() +{ + try { + std::ostringstream oss; + oss << "LONELY PARSE ERROR: " << first << ": " << second; + m_msg = oss.str(); + return m_msg.c_str(); + } catch (...) { + return "Could not format message for parsing error."; + } +} + +impl::parse_error::operator std::string(void) + const +{ + return tools::text::to_string(first) + ": " + second; +} + +// ------------------------------------------------------------------------ +// The "parse_errors" class. +// ------------------------------------------------------------------------ + +impl::parse_errors::parse_errors(void) : + std::runtime_error("No parsing errors yet") +{ + m_msg.clear(); +} + +impl::parse_errors::~parse_errors(void) + throw() +{ +} + +const char* +impl::parse_errors::what(void) + const throw() +{ + try { + m_msg = tools::text::join(*this, "\n"); + return m_msg.c_str(); + } catch (...) { + return "Could not format messages for parsing errors."; + } +} + +// ------------------------------------------------------------------------ +// The "format_error" class. +// ------------------------------------------------------------------------ + +impl::format_error::format_error(const std::string& w) : + std::runtime_error(w.c_str()) +{ +} + +// ------------------------------------------------------------------------ +// The "token" class. +// ------------------------------------------------------------------------ + +impl::token::token(void) : + m_inited(false) +{ +} + +impl::token::token(size_t p_line, + const token_type& p_type, + const std::string& p_text) : + m_inited(true), + m_line(p_line), + m_type(p_type), + m_text(p_text) +{ +} + +size_t +impl::token::lineno(void) + const +{ + return m_line; +} + +const impl::token_type& +impl::token::type(void) + const +{ + return m_type; +} + +const std::string& +impl::token::text(void) + const +{ + return m_text; +} + +impl::token::operator bool(void) + const +{ + return m_inited; +} + +bool +impl::token::operator!(void) + const +{ + return !m_inited; +} + +// ------------------------------------------------------------------------ +// The "header_entry" class. +// ------------------------------------------------------------------------ + +impl::header_entry::header_entry(void) +{ +} + +impl::header_entry::header_entry(const std::string& n, const std::string& v, + attrs_map as) : + m_name(n), + m_value(v), + m_attrs(as) +{ +} + +const std::string& +impl::header_entry::name(void) const +{ + return m_name; +} + +const std::string& +impl::header_entry::value(void) const +{ + return m_value; +} + +const impl::attrs_map& +impl::header_entry::attrs(void) const +{ + return m_attrs; +} + +bool +impl::header_entry::has_attr(const std::string& n) const +{ + return m_attrs.find(n) != m_attrs.end(); +} + +const std::string& +impl::header_entry::get_attr(const std::string& n) const +{ + attrs_map::const_iterator iter = m_attrs.find(n); + assert(iter != m_attrs.end()); + return (*iter).second; +} + +// ------------------------------------------------------------------------ +// The header tokenizer. +// ------------------------------------------------------------------------ + +namespace header { + +static const impl::token_type eof_type = 0; +static const impl::token_type nl_type = 1; +static const impl::token_type text_type = 2; +static const impl::token_type colon_type = 3; +static const impl::token_type semicolon_type = 4; +static const impl::token_type dblquote_type = 5; +static const impl::token_type equal_type = 6; + +class tokenizer : public impl::tokenizer< std::istream > { +public: + tokenizer(std::istream& is, size_t curline) : + impl::tokenizer< std::istream > + (is, true, eof_type, nl_type, text_type, curline) + { + add_delim(';', semicolon_type); + add_delim(':', colon_type); + add_delim('=', equal_type); + add_quote('"', dblquote_type); + } +}; + +static +impl::parser< header::tokenizer >& +read(impl::parser< header::tokenizer >& p, impl::header_entry& he) +{ + using namespace header; + + impl::token t = p.expect(text_type, nl_type, "a header name"); + if (t.type() == nl_type) { + he = impl::header_entry(); + return p; + } + std::string hdr_name = t.text(); + + t = p.expect(colon_type, "`:'"); + + t = p.expect(text_type, "a textual value"); + std::string hdr_value = t.text(); + + impl::attrs_map attrs; + + for (;;) { + t = p.expect(eof_type, semicolon_type, nl_type, + "eof, `;' or new line"); + if (t.type() == eof_type || t.type() == nl_type) + break; + + t = p.expect(text_type, "an attribute name"); + std::string attr_name = t.text(); + + t = p.expect(equal_type, "`='"); + + t = p.expect(text_type, "word or quoted string"); + std::string attr_value = t.text(); + attrs[attr_name] = attr_value; + } + + he = impl::header_entry(hdr_name, hdr_value, attrs); + + return p; +} + +static +std::ostream& +write(std::ostream& os, const impl::header_entry& he) +{ + std::string line = he.name() + ": " + he.value(); + impl::attrs_map as = he.attrs(); + for (impl::attrs_map::const_iterator iter = as.begin(); iter != as.end(); + iter++) { + assert((*iter).second.find('\"') == std::string::npos); + line += "; " + (*iter).first + "=\"" + (*iter).second + "\""; + } + + os << line << "\n"; + + return os; +} + +} // namespace header + +// ------------------------------------------------------------------------ +// Free functions. +// ------------------------------------------------------------------------ + +std::pair< size_t, impl::headers_map > +impl::read_headers(std::istream& is, size_t curline) +{ + using impl::format_error; + + headers_map hm; + + // + // Grammar + // + // header = entry+ nl + // entry = line nl + // line = text colon text + // (semicolon (text equal (text | dblquote string dblquote)))* + // string = quoted_string + // + + header::tokenizer tkz(is, curline); + impl::parser< header::tokenizer > p(tkz); + + bool first = true; + for (;;) { + try { + header_entry he; + if (!header::read(p, he).good() || he.name().empty()) + break; + + if (first && he.name() != "Content-Type") + throw format_error("Could not determine content type"); + else + first = false; + + hm[he.name()] = he; + } catch (const impl::parse_error& pe) { + p.add_error(pe); + p.reset(header::nl_type); + } + } + + if (!is.good()) + throw format_error("Unexpected end of stream"); + + return std::pair< size_t, headers_map >(tkz.lineno(), hm); +} + +void +impl::write_headers(const impl::headers_map& hm, std::ostream& os) +{ + assert(!hm.empty()); + headers_map::const_iterator ct = hm.find("Content-Type"); + assert(ct != hm.end()); + header::write(os, (*ct).second); + for (headers_map::const_iterator iter = hm.begin(); iter != hm.end(); + iter++) { + if ((*iter).first != "Content-Type") + header::write(os, (*iter).second); + } + os << "\n"; +} + +void +impl::validate_content_type(const impl::headers_map& hm, const std::string& fmt, + int version) +{ + using impl::format_error; + + headers_map::const_iterator iter = hm.find("Content-Type"); + if (iter == hm.end()) + throw format_error("Could not determine content type"); + + const header_entry& he = (*iter).second; + if (he.value() != fmt) + throw format_error("Mismatched content type: expected `" + fmt + + "' but got `" + he.value() + "'"); + + if (!he.has_attr("version")) + throw format_error("Could not determine version"); + const std::string& vstr = tools::text::to_string(version); + if (he.get_attr("version") != vstr) + throw format_error("Mismatched version: expected `" + + vstr + "' but got `" + + he.get_attr("version") + "'"); +} diff --git a/unit/atf-src/tools/parser.hpp b/unit/atf-src/tools/parser.hpp new file mode 100644 index 0000000..eb00cb4 --- /dev/null +++ b/unit/atf-src/tools/parser.hpp @@ -0,0 +1,607 @@ +// +// 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(TOOLS_PARSER_HPP) +#define TOOLS_PARSER_HPP + +#include <istream> +#include <map> +#include <ostream> +#include <stdexcept> +#include <string> +#include <utility> +#include <vector> + +namespace tools { +namespace parser { + +// ------------------------------------------------------------------------ +// The "parse_error" class. +// ------------------------------------------------------------------------ + +class parse_error : public std::runtime_error, + public std::pair< size_t, std::string > { + mutable std::string m_msg; + +public: + parse_error(size_t, std::string); + ~parse_error(void) throw(); + + const char* what(void) const throw(); + + operator std::string(void) const; +}; + +// ------------------------------------------------------------------------ +// The "parse_errors" class. +// ------------------------------------------------------------------------ + +class parse_errors : public std::runtime_error, + public std::vector< parse_error > { + std::vector< parse_error > m_errors; + mutable std::string m_msg; + +public: + parse_errors(void); + ~parse_errors(void) throw(); + + const char* what(void) const throw(); +}; + +// ------------------------------------------------------------------------ +// The "format_error" class. +// ------------------------------------------------------------------------ + +class format_error : public std::runtime_error { +public: + format_error(const std::string&); +}; + +// ------------------------------------------------------------------------ +// The "token" class. +// ------------------------------------------------------------------------ + +typedef int token_type; + +//! +//! \brief Representation of a read token. +//! +//! A pair that contains the information of a token read from a stream. +//! It contains the token's type and its associated data, if any. +//! +struct token { + bool m_inited; + size_t m_line; + token_type m_type; + std::string m_text; + +public: + token(void); + token(size_t, const token_type&, const std::string& = ""); + + size_t lineno(void) const; + const token_type& type(void) const; + const std::string& text(void) const; + + operator bool(void) const; + bool operator!(void) const; +}; + +// ------------------------------------------------------------------------ +// The "tokenizer" class. +// ------------------------------------------------------------------------ + +//! +//! \brief A stream tokenizer. +//! +//! This template implements an extremely simple, line-oriented stream +//! tokenizer. It is only able to recognize one character-long delimiters, +//! random-length keywords, skip whitespace and, anything that does not +//! match these rules is supposed to be a word. +//! +//! Parameter IS: The input stream's type. +//! +template< class IS > +class tokenizer { + IS& m_is; + size_t m_lineno; + token m_la; + + bool m_skipws; + token_type m_eof_type, m_nl_type, m_text_type; + + std::map< char, token_type > m_delims_map; + std::string m_delims_str; + + char m_quotech; + token_type m_quotetype; + + std::map< std::string, token_type > m_keywords_map; + + token_type alloc_type(void); + + template< class TKZ > + friend + class parser; + +public: + tokenizer(IS&, bool, const token_type&, const token_type&, + const token_type&, size_t = 1); + + size_t lineno(void) const; + + void add_delim(char, const token_type&); + void add_keyword(const std::string&, const token_type&); + void add_quote(char, const token_type&); + + token next(void); + std::string rest_of_line(void); +}; + +template< class IS > +tokenizer< IS >::tokenizer(IS& p_is, + bool p_skipws, + const token_type& p_eof_type, + const token_type& p_nl_type, + const token_type& p_text_type, + size_t p_lineno) : + m_is(p_is), + m_lineno(p_lineno), + m_skipws(p_skipws), + m_eof_type(p_eof_type), + m_nl_type(p_nl_type), + m_text_type(p_text_type), + m_quotech(-1) +{ +} + +template< class IS > +size_t +tokenizer< IS >::lineno(void) + const +{ + return m_lineno; +} + +template< class IS > +void +tokenizer< IS >::add_delim(char delim, const token_type& type) +{ + m_delims_map[delim] = type; + m_delims_str += delim; +} + +template< class IS > +void +tokenizer< IS >::add_keyword(const std::string& keyword, + const token_type& type) +{ + m_keywords_map[keyword] = type; +} + +template< class IS > +void +tokenizer< IS >::add_quote(char ch, const token_type& type) +{ + m_quotech = ch; + m_quotetype = type; +} + +template< class IS > +token +tokenizer< IS >::next(void) +{ + if (m_la) { + token t = m_la; + m_la = token(); + if (t.type() == m_nl_type) + m_lineno++; + return t; + } + + char ch; + std::string text; + + bool done = false, quoted = false; + token t(m_lineno, m_eof_type, "<<EOF>>"); + while (!done && m_is.get(ch).good()) { + if (ch == m_quotech) { + if (text.empty()) { + bool escaped = false; + while (!done && m_is.get(ch).good()) { + if (!escaped) { + if (ch == '\\') + escaped = true; + else if (ch == '\n') { + m_la = token(m_lineno, m_nl_type, "<<NEWLINE>>"); + throw parse_error(t.lineno(), + "Missing double quotes before " + "end of line"); + } else if (ch == m_quotech) + done = true; + else + text += ch; + } else { + text += ch; + escaped = false; + } + } + if (!m_is.good()) + throw parse_error(t.lineno(), + "Missing double quotes before " + "end of file"); + t = token(m_lineno, m_text_type, text); + quoted = true; + } else { + m_is.putback(ch); + done = true; + } + } else { + typename std::map< char, token_type >::const_iterator idelim; + idelim = m_delims_map.find(ch); + if (idelim != m_delims_map.end()) { + done = true; + if (text.empty()) + t = token(m_lineno, (*idelim).second, + std::string("") + ch); + else + m_is.putback(ch); + } else if (ch == '\n') { + done = true; + if (text.empty()) + t = token(m_lineno, m_nl_type, "<<NEWLINE>>"); + else + m_is.putback(ch); + } else if (m_skipws && (ch == ' ' || ch == '\t')) { + if (!text.empty()) + done = true; + } else + text += ch; + } + } + + if (!quoted && !text.empty()) { + typename std::map< std::string, token_type >::const_iterator ikw; + ikw = m_keywords_map.find(text); + if (ikw != m_keywords_map.end()) + t = token(m_lineno, (*ikw).second, text); + else + t = token(m_lineno, m_text_type, text); + } + + if (t.type() == m_nl_type) + m_lineno++; + + return t; +} + +template< class IS > +std::string +tokenizer< IS >::rest_of_line(void) +{ + std::string str; + while (m_is.good() && m_is.peek() != '\n') + str += m_is.get(); + return str; +} + +// ------------------------------------------------------------------------ +// The "parser" class. +// ------------------------------------------------------------------------ + +template< class TKZ > +class parser { + TKZ& m_tkz; + token m_last; + parse_errors m_errors; + bool m_thrown; + +public: + parser(TKZ& tkz); + ~parser(void); + + bool good(void) const; + void add_error(const parse_error&); + bool has_errors(void) const; + + token next(void); + std::string rest_of_line(void); + token reset(const token_type&); + + token + expect(const token_type&, + const std::string&); + + token + expect(const token_type&, + const token_type&, + const std::string&); + + token + expect(const token_type&, + const token_type&, + const token_type&, + const std::string&); + + token + expect(const token_type&, + const token_type&, + const token_type&, + const token_type&, + const std::string&); + + token + expect(const token_type&, + const token_type&, + const token_type&, + const token_type&, + const token_type&, + const token_type&, + const token_type&, + const std::string&); + + token + expect(const token_type&, + const token_type&, + const token_type&, + const token_type&, + const token_type&, + const token_type&, + const token_type&, + const token_type&, + const std::string&); +}; + +template< class TKZ > +parser< TKZ >::parser(TKZ& tkz) : + m_tkz(tkz), + m_thrown(false) +{ +} + +template< class TKZ > +parser< TKZ >::~parser(void) +{ + if (!m_errors.empty() && !m_thrown) + throw m_errors; +} + +template< class TKZ > +bool +parser< TKZ >::good(void) + const +{ + return m_tkz.m_is.good(); +} + +template< class TKZ > +void +parser< TKZ >::add_error(const parse_error& pe) +{ + m_errors.push_back(pe); +} + +template< class TKZ > +bool +parser< TKZ >::has_errors(void) + const +{ + return !m_errors.empty(); +} + +template< class TKZ > +token +parser< TKZ >::next(void) +{ + token t = m_tkz.next(); + + m_last = t; + + if (t.type() == m_tkz.m_eof_type) { + if (!m_errors.empty()) { + m_thrown = true; + throw m_errors; + } + } + + return t; +} + +template< class TKZ > +std::string +parser< TKZ >::rest_of_line(void) +{ + return m_tkz.rest_of_line(); +} + +template< class TKZ > +token +parser< TKZ >::reset(const token_type& stop) +{ + token t = m_last; + + while (t.type() != m_tkz.m_eof_type && t.type() != stop) + t = next(); + + return t; +} + +template< class TKZ > +token +parser< TKZ >::expect(const token_type& t1, + const std::string& textual) +{ + token t = next(); + + if (t.type() != t1) + throw parse_error(t.lineno(), + "Unexpected token `" + t.text() + + "'; expected " + textual); + + return t; +} + +template< class TKZ > +token +parser< TKZ >::expect(const token_type& t1, + const token_type& t2, + const std::string& textual) +{ + token t = next(); + + if (t.type() != t1 && t.type() != t2) + throw parse_error(t.lineno(), + "Unexpected token `" + t.text() + + "'; expected " + textual); + + return t; +} + +template< class TKZ > +token +parser< TKZ >::expect(const token_type& t1, + const token_type& t2, + const token_type& t3, + const std::string& textual) +{ + token t = next(); + + if (t.type() != t1 && t.type() != t2 && t.type() != t3) + throw parse_error(t.lineno(), + "Unexpected token `" + t.text() + + "'; expected " + textual); + + return t; +} + +template< class TKZ > +token +parser< TKZ >::expect(const token_type& t1, + const token_type& t2, + const token_type& t3, + const token_type& t4, + const std::string& textual) +{ + token t = next(); + + if (t.type() != t1 && t.type() != t2 && t.type() != t3 && + t.type() != t4) + throw parse_error(t.lineno(), + "Unexpected token `" + t.text() + + "'; expected " + textual); + + return t; +} + +template< class TKZ > +token +parser< TKZ >::expect(const token_type& t1, + const token_type& t2, + const token_type& t3, + const token_type& t4, + const token_type& t5, + const token_type& t6, + const token_type& t7, + const std::string& textual) +{ + token t = next(); + + if (t.type() != t1 && t.type() != t2 && t.type() != t3 && + t.type() != t4 && t.type() != t5 && t.type() != t6 && + t.type() != t7) + throw parse_error(t.lineno(), + "Unexpected token `" + t.text() + + "'; expected " + textual); + + return t; +} + +template< class TKZ > +token +parser< TKZ >::expect(const token_type& t1, + const token_type& t2, + const token_type& t3, + const token_type& t4, + const token_type& t5, + const token_type& t6, + const token_type& t7, + const token_type& t8, + const std::string& textual) +{ + token t = next(); + + if (t.type() != t1 && t.type() != t2 && t.type() != t3 && + t.type() != t4 && t.type() != t5 && t.type() != t6 && + t.type() != t7 && t.type() != t8) + throw parse_error(t.lineno(), + "Unexpected token `" + t.text() + + "'; expected " + textual); + + return t; +} + +#define ATF_PARSER_CALLBACK(parser, func) \ + do { \ + if (!(parser).has_errors()) \ + func; \ + } while (false) + +// ------------------------------------------------------------------------ +// Header parsing. +// ------------------------------------------------------------------------ + +typedef std::map< std::string, std::string > attrs_map; + +class header_entry { + std::string m_name; + std::string m_value; + attrs_map m_attrs; + +public: + header_entry(void); + header_entry(const std::string&, const std::string&, + attrs_map = attrs_map()); + + const std::string& name(void) const; + const std::string& value(void) const; + const attrs_map& attrs(void) const; + bool has_attr(const std::string&) const; + const std::string& get_attr(const std::string&) const; +}; + +typedef std::map< std::string, header_entry > headers_map; + +std::pair< size_t, headers_map > read_headers(std::istream&, size_t); +void write_headers(const headers_map&, std::ostream&); +void validate_content_type(const headers_map&, const std::string&, int); + +} // namespace parser +} // namespace tools + +#endif // !defined(TOOLS_PARSER_HPP) diff --git a/unit/atf-src/tools/parser_test.cpp b/unit/atf-src/tools/parser_test.cpp new file mode 100644 index 0000000..434a1a6 --- /dev/null +++ b/unit/atf-src/tools/parser_test.cpp @@ -0,0 +1,1043 @@ +// +// 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 <sstream> + +#include <atf-c++.hpp> + +#include "parser.hpp" +#include "test_helpers.hpp" + +// ------------------------------------------------------------------------ +// Tests for the "parse_error" class. +// ------------------------------------------------------------------------ + +ATF_TEST_CASE(parse_error_to_string); +ATF_TEST_CASE_HEAD(parse_error_to_string) +{ + set_md_var("descr", "Tests the parse_error conversion to strings"); +} +ATF_TEST_CASE_BODY(parse_error_to_string) +{ + using tools::parser::parse_error; + + const parse_error e(123, "This is the message"); + ATF_REQUIRE_EQ("123: This is the message", std::string(e)); +} + +// ------------------------------------------------------------------------ +// Tests for the "parse_errors" class. +// ------------------------------------------------------------------------ + +ATF_TEST_CASE(parse_errors_what); +ATF_TEST_CASE_HEAD(parse_errors_what) +{ + set_md_var("descr", "Tests the parse_errors description"); +} +ATF_TEST_CASE_BODY(parse_errors_what) +{ + using tools::parser::parse_error; + using tools::parser::parse_errors; + + parse_errors es; + es.push_back(parse_error(2, "Second error")); + es.push_back(parse_error(1, "First error")); + + ATF_REQUIRE_EQ("2: Second error\n1: First error", std::string(es.what())); +} + +// ------------------------------------------------------------------------ +// Tests for the "token" class. +// ------------------------------------------------------------------------ + +ATF_TEST_CASE(token_getters); +ATF_TEST_CASE_HEAD(token_getters) +{ + set_md_var("descr", "Tests the token getters"); +} +ATF_TEST_CASE_BODY(token_getters) +{ + using tools::parser::token; + + { + token t(10, 0); + ATF_REQUIRE_EQ(t.lineno(), 10); + ATF_REQUIRE_EQ(t.type(), 0); + ATF_REQUIRE(t.text().empty()); + } + + { + token t(10, 0, "foo"); + ATF_REQUIRE_EQ(t.lineno(), 10); + ATF_REQUIRE_EQ(t.type(), 0); + ATF_REQUIRE_EQ(t.text(), "foo"); + } + + { + token t(20, 1); + ATF_REQUIRE_EQ(t.lineno(), 20); + ATF_REQUIRE_EQ(t.type(), 1); + ATF_REQUIRE(t.text().empty()); + } + + { + token t(20, 1, "bar"); + ATF_REQUIRE_EQ(t.lineno(), 20); + ATF_REQUIRE_EQ(t.type(), 1); + ATF_REQUIRE_EQ(t.text(), "bar"); + } +} + +// ------------------------------------------------------------------------ +// Tests for the "tokenizer" class. +// ------------------------------------------------------------------------ + +#define EXPECT(tkz, ttype, ttext) \ + do { \ + tools::parser::token t = tkz.next(); \ + ATF_REQUIRE(t.type() == ttype); \ + ATF_REQUIRE_EQ(t.text(), ttext); \ + } while (false); + +namespace minimal { + + static const tools::parser::token_type eof_type = 0; + static const tools::parser::token_type nl_type = 1; + static const tools::parser::token_type word_type = 2; + + class tokenizer : public tools::parser::tokenizer< std::istream > { + public: + tokenizer(std::istream& is, bool skipws) : + tools::parser::tokenizer< std::istream > + (is, skipws, eof_type, nl_type, word_type) + { + } + }; + +} + +namespace delims { + + static const tools::parser::token_type eof_type = 0; + static const tools::parser::token_type nl_type = 1; + static const tools::parser::token_type word_type = 2; + static const tools::parser::token_type plus_type = 3; + static const tools::parser::token_type minus_type = 4; + static const tools::parser::token_type equal_type = 5; + + class tokenizer : public tools::parser::tokenizer< std::istream > { + public: + tokenizer(std::istream& is, bool skipws) : + tools::parser::tokenizer< std::istream > + (is, skipws, eof_type, nl_type, word_type) + { + add_delim('+', plus_type); + add_delim('-', minus_type); + add_delim('=', equal_type); + } + }; + +} + +namespace keywords { + + static const tools::parser::token_type eof_type = 0; + static const tools::parser::token_type nl_type = 1; + static const tools::parser::token_type word_type = 2; + static const tools::parser::token_type var_type = 3; + static const tools::parser::token_type loop_type = 4; + static const tools::parser::token_type endloop_type = 5; + + class tokenizer : public tools::parser::tokenizer< std::istream > { + public: + tokenizer(std::istream& is, bool skipws) : + tools::parser::tokenizer< std::istream > + (is, skipws, eof_type, nl_type, word_type) + { + add_keyword("var", var_type); + add_keyword("loop", loop_type); + add_keyword("endloop", endloop_type); + } + }; + +} + +namespace quotes { + + static const tools::parser::token_type eof_type = 0; + static const tools::parser::token_type nl_type = 1; + static const tools::parser::token_type word_type = 2; + static const tools::parser::token_type dblquote_type = 3; + + class tokenizer : public tools::parser::tokenizer< std::istream > { + public: + tokenizer(std::istream& is, bool skipws) : + tools::parser::tokenizer< std::istream > + (is, skipws, eof_type, nl_type, word_type) + { + add_quote('"', dblquote_type); + } + }; + +} + +ATF_TEST_CASE(tokenizer_minimal_nows); +ATF_TEST_CASE_HEAD(tokenizer_minimal_nows) +{ + set_md_var("descr", "Tests the tokenizer class using a minimal parser " + "and not skipping whitespace"); +} +ATF_TEST_CASE_BODY(tokenizer_minimal_nows) +{ + using namespace minimal; + + { + std::istringstream iss(""); + tokenizer mt(iss, false); + + EXPECT(mt, eof_type, "<<EOF>>"); + EXPECT(mt, eof_type, "<<EOF>>"); + } + + { + std::istringstream iss("\n"); + tokenizer mt(iss, false); + + EXPECT(mt, nl_type, "<<NEWLINE>>"); + EXPECT(mt, eof_type, "<<EOF>>"); + EXPECT(mt, eof_type, "<<EOF>>"); + } + + { + std::istringstream iss("\n\n\n"); + tokenizer mt(iss, false); + + EXPECT(mt, nl_type, "<<NEWLINE>>"); + EXPECT(mt, nl_type, "<<NEWLINE>>"); + EXPECT(mt, nl_type, "<<NEWLINE>>"); + EXPECT(mt, eof_type, "<<EOF>>"); + EXPECT(mt, eof_type, "<<EOF>>"); + } + + { + std::istringstream iss("line 1"); + tokenizer mt(iss, false); + + EXPECT(mt, word_type, "line 1"); + EXPECT(mt, eof_type, "<<EOF>>"); + EXPECT(mt, eof_type, "<<EOF>>"); + } + + { + std::istringstream iss("line 1\n"); + tokenizer mt(iss, false); + + EXPECT(mt, word_type, "line 1"); + EXPECT(mt, nl_type, "<<NEWLINE>>"); + EXPECT(mt, eof_type, "<<EOF>>"); + EXPECT(mt, eof_type, "<<EOF>>"); + } + + { + std::istringstream iss("line 1\nline 2"); + tokenizer mt(iss, false); + + EXPECT(mt, word_type, "line 1"); + EXPECT(mt, nl_type, "<<NEWLINE>>"); + EXPECT(mt, word_type, "line 2"); + EXPECT(mt, eof_type, "<<EOF>>"); + EXPECT(mt, eof_type, "<<EOF>>"); + } + + { + std::istringstream iss("line 1\nline 2\nline 3\n"); + tokenizer mt(iss, false); + + EXPECT(mt, word_type, "line 1"); + EXPECT(mt, nl_type, "<<NEWLINE>>"); + EXPECT(mt, word_type, "line 2"); + EXPECT(mt, nl_type, "<<NEWLINE>>"); + EXPECT(mt, word_type, "line 3"); + EXPECT(mt, nl_type, "<<NEWLINE>>"); + EXPECT(mt, eof_type, "<<EOF>>"); + EXPECT(mt, eof_type, "<<EOF>>"); + } +} + +ATF_TEST_CASE(tokenizer_minimal_ws); +ATF_TEST_CASE_HEAD(tokenizer_minimal_ws) +{ + set_md_var("descr", "Tests the tokenizer class using a minimal parser " + "and skipping whitespace"); +} +ATF_TEST_CASE_BODY(tokenizer_minimal_ws) +{ + using namespace minimal; + + { + std::istringstream iss(""); + minimal::tokenizer mt(iss, true); + + EXPECT(mt, eof_type, "<<EOF>>"); + EXPECT(mt, eof_type, "<<EOF>>"); + } + + { + std::istringstream iss(" \t "); + tokenizer mt(iss, true); + + EXPECT(mt, eof_type, "<<EOF>>"); + EXPECT(mt, eof_type, "<<EOF>>"); + } + + { + std::istringstream iss("\n"); + tokenizer mt(iss, true); + + EXPECT(mt, nl_type, "<<NEWLINE>>"); + EXPECT(mt, eof_type, "<<EOF>>"); + EXPECT(mt, eof_type, "<<EOF>>"); + } + + { + std::istringstream iss(" \t \n \t "); + tokenizer mt(iss, true); + + EXPECT(mt, nl_type, "<<NEWLINE>>"); + EXPECT(mt, eof_type, "<<EOF>>"); + EXPECT(mt, eof_type, "<<EOF>>"); + } + + { + std::istringstream iss("\n\n\n"); + tokenizer mt(iss, true); + + EXPECT(mt, nl_type, "<<NEWLINE>>"); + EXPECT(mt, nl_type, "<<NEWLINE>>"); + EXPECT(mt, nl_type, "<<NEWLINE>>"); + EXPECT(mt, eof_type, "<<EOF>>"); + EXPECT(mt, eof_type, "<<EOF>>"); + } + + { + std::istringstream iss("line 1"); + tokenizer mt(iss, true); + + EXPECT(mt, word_type, "line"); + EXPECT(mt, word_type, "1"); + EXPECT(mt, eof_type, "<<EOF>>"); + EXPECT(mt, eof_type, "<<EOF>>"); + } + + { + std::istringstream iss(" \tline\t 1\t"); + tokenizer mt(iss, true); + + EXPECT(mt, word_type, "line"); + EXPECT(mt, word_type, "1"); + EXPECT(mt, eof_type, "<<EOF>>"); + EXPECT(mt, eof_type, "<<EOF>>"); + } + + { + std::istringstream iss("line 1\n"); + tokenizer mt(iss, true); + + EXPECT(mt, word_type, "line"); + EXPECT(mt, word_type, "1"); + EXPECT(mt, nl_type, "<<NEWLINE>>"); + EXPECT(mt, eof_type, "<<EOF>>"); + EXPECT(mt, eof_type, "<<EOF>>"); + } + + { + std::istringstream iss("line 1\nline 2"); + tokenizer mt(iss, true); + + EXPECT(mt, word_type, "line"); + EXPECT(mt, word_type, "1"); + EXPECT(mt, nl_type, "<<NEWLINE>>"); + EXPECT(mt, word_type, "line"); + EXPECT(mt, word_type, "2"); + EXPECT(mt, eof_type, "<<EOF>>"); + EXPECT(mt, eof_type, "<<EOF>>"); + } + + { + std::istringstream iss("line 1\nline 2\nline 3\n"); + tokenizer mt(iss, true); + + EXPECT(mt, word_type, "line"); + EXPECT(mt, word_type, "1"); + EXPECT(mt, nl_type, "<<NEWLINE>>"); + EXPECT(mt, word_type, "line"); + EXPECT(mt, word_type, "2"); + EXPECT(mt, nl_type, "<<NEWLINE>>"); + EXPECT(mt, word_type, "line"); + EXPECT(mt, word_type, "3"); + EXPECT(mt, nl_type, "<<NEWLINE>>"); + EXPECT(mt, eof_type, "<<EOF>>"); + EXPECT(mt, eof_type, "<<EOF>>"); + } + + { + std::istringstream iss(" \t line \t 1\n\tline\t2\n line 3 \n"); + tokenizer mt(iss, true); + + EXPECT(mt, word_type, "line"); + EXPECT(mt, word_type, "1"); + EXPECT(mt, nl_type, "<<NEWLINE>>"); + EXPECT(mt, word_type, "line"); + EXPECT(mt, word_type, "2"); + EXPECT(mt, nl_type, "<<NEWLINE>>"); + EXPECT(mt, word_type, "line"); + EXPECT(mt, word_type, "3"); + EXPECT(mt, nl_type, "<<NEWLINE>>"); + EXPECT(mt, eof_type, "<<EOF>>"); + EXPECT(mt, eof_type, "<<EOF>>"); + } +} + +ATF_TEST_CASE(tokenizer_delims_nows); +ATF_TEST_CASE_HEAD(tokenizer_delims_nows) +{ + set_md_var("descr", "Tests the tokenizer class using a parser with some " + "additional delimiters and not skipping whitespace"); +} +ATF_TEST_CASE_BODY(tokenizer_delims_nows) +{ + using namespace delims; + + { + std::istringstream iss("+-="); + tokenizer mt(iss, false); + + EXPECT(mt, plus_type, "+"); + EXPECT(mt, minus_type, "-"); + EXPECT(mt, equal_type, "="); + EXPECT(mt, eof_type, "<<EOF>>"); + EXPECT(mt, eof_type, "<<EOF>>"); + } + + { + std::istringstream iss("+++"); + tokenizer mt(iss, false); + + EXPECT(mt, plus_type, "+"); + EXPECT(mt, plus_type, "+"); + EXPECT(mt, plus_type, "+"); + EXPECT(mt, eof_type, "<<EOF>>"); + EXPECT(mt, eof_type, "<<EOF>>"); + } + + { + std::istringstream iss("\n+\n++\n"); + tokenizer mt(iss, false); + + EXPECT(mt, nl_type, "<<NEWLINE>>"); + EXPECT(mt, plus_type, "+"); + EXPECT(mt, nl_type, "<<NEWLINE>>"); + EXPECT(mt, plus_type, "+"); + EXPECT(mt, plus_type, "+"); + EXPECT(mt, nl_type, "<<NEWLINE>>"); + EXPECT(mt, eof_type, "<<EOF>>"); + EXPECT(mt, eof_type, "<<EOF>>"); + } + + { + std::istringstream iss("foo+bar=baz"); + tokenizer mt(iss, false); + + EXPECT(mt, word_type, "foo"); + EXPECT(mt, plus_type, "+"); + EXPECT(mt, word_type, "bar"); + EXPECT(mt, equal_type, "="); + EXPECT(mt, word_type, "baz"); + EXPECT(mt, eof_type, "<<EOF>>"); + EXPECT(mt, eof_type, "<<EOF>>"); + } + + { + std::istringstream iss(" foo\t+\tbar = baz "); + tokenizer mt(iss, false); + + EXPECT(mt, word_type, " foo\t"); + EXPECT(mt, plus_type, "+"); + EXPECT(mt, word_type, "\tbar "); + EXPECT(mt, equal_type, "="); + EXPECT(mt, word_type, " baz "); + EXPECT(mt, eof_type, "<<EOF>>"); + EXPECT(mt, eof_type, "<<EOF>>"); + } +} + +ATF_TEST_CASE(tokenizer_delims_ws); +ATF_TEST_CASE_HEAD(tokenizer_delims_ws) +{ + set_md_var("descr", "Tests the tokenizer class using a parser with some " + "additional delimiters and skipping whitespace"); +} +ATF_TEST_CASE_BODY(tokenizer_delims_ws) +{ + using namespace delims; + + { + std::istringstream iss(" foo\t+\tbar = baz "); + tokenizer mt(iss, true); + + EXPECT(mt, word_type, "foo"); + EXPECT(mt, plus_type, "+"); + EXPECT(mt, word_type, "bar"); + EXPECT(mt, equal_type, "="); + EXPECT(mt, word_type, "baz"); + EXPECT(mt, eof_type, "<<EOF>>"); + EXPECT(mt, eof_type, "<<EOF>>"); + } +} + +ATF_TEST_CASE(tokenizer_keywords_nows); +ATF_TEST_CASE_HEAD(tokenizer_keywords_nows) +{ + set_md_var("descr", "Tests the tokenizer class using a parser with some " + "additional keywords and not skipping whitespace"); +} +ATF_TEST_CASE_BODY(tokenizer_keywords_nows) +{ + using namespace keywords; + + { + std::istringstream iss("var"); + tokenizer mt(iss, false); + + EXPECT(mt, var_type, "var"); + EXPECT(mt, eof_type, "<<EOF>>"); + EXPECT(mt, eof_type, "<<EOF>>"); + } + + { + std::istringstream iss("va"); + tokenizer mt(iss, false); + + EXPECT(mt, word_type, "va"); + EXPECT(mt, eof_type, "<<EOF>>"); + EXPECT(mt, eof_type, "<<EOF>>"); + } + + { + std::istringstream iss("vara"); + tokenizer mt(iss, false); + + EXPECT(mt, word_type, "vara"); + EXPECT(mt, eof_type, "<<EOF>>"); + EXPECT(mt, eof_type, "<<EOF>>"); + } + + { + std::istringstream iss("var "); + tokenizer mt(iss, false); + + EXPECT(mt, word_type, "var "); + EXPECT(mt, eof_type, "<<EOF>>"); + EXPECT(mt, eof_type, "<<EOF>>"); + } + + { + std::istringstream iss("var\nloop\nendloop"); + tokenizer mt(iss, false); + + EXPECT(mt, var_type, "var"); + EXPECT(mt, nl_type, "<<NEWLINE>>"); + EXPECT(mt, loop_type, "loop"); + EXPECT(mt, nl_type, "<<NEWLINE>>"); + EXPECT(mt, endloop_type, "endloop"); + EXPECT(mt, eof_type, "<<EOF>>"); + EXPECT(mt, eof_type, "<<EOF>>"); + } +} + +ATF_TEST_CASE(tokenizer_keywords_ws); +ATF_TEST_CASE_HEAD(tokenizer_keywords_ws) +{ + set_md_var("descr", "Tests the tokenizer class using a parser with some " + "additional keywords and not skipping whitespace"); +} +ATF_TEST_CASE_BODY(tokenizer_keywords_ws) +{ + using namespace keywords; + + { + std::istringstream iss("var "); + tokenizer mt(iss, true); + + EXPECT(mt, var_type, "var"); + EXPECT(mt, eof_type, "<<EOF>>"); + EXPECT(mt, eof_type, "<<EOF>>"); + } + + { + std::istringstream iss(" var \n\tloop\t\n \tendloop \t"); + tokenizer mt(iss, true); + + EXPECT(mt, var_type, "var"); + EXPECT(mt, nl_type, "<<NEWLINE>>"); + EXPECT(mt, loop_type, "loop"); + EXPECT(mt, nl_type, "<<NEWLINE>>"); + EXPECT(mt, endloop_type, "endloop"); + EXPECT(mt, eof_type, "<<EOF>>"); + EXPECT(mt, eof_type, "<<EOF>>"); + } + + { + std::istringstream iss("var loop endloop"); + tokenizer mt(iss, true); + + EXPECT(mt, var_type, "var"); + EXPECT(mt, loop_type, "loop"); + EXPECT(mt, endloop_type, "endloop"); + EXPECT(mt, eof_type, "<<EOF>>"); + EXPECT(mt, eof_type, "<<EOF>>"); + } +} + +ATF_TEST_CASE(tokenizer_quotes_nows); +ATF_TEST_CASE_HEAD(tokenizer_quotes_nows) +{ + set_md_var("descr", "Tests the tokenizer class using a parser with " + "quoted strings and not skipping whitespace"); +} +ATF_TEST_CASE_BODY(tokenizer_quotes_nows) +{ + using namespace quotes; + + { + std::istringstream iss("var"); + tokenizer mt(iss, false); + + EXPECT(mt, word_type, "var"); + EXPECT(mt, eof_type, "<<EOF>>"); + EXPECT(mt, eof_type, "<<EOF>>"); + } + + { + std::istringstream iss("\"var\""); + tokenizer mt(iss, false); + + EXPECT(mt, word_type, "var"); + EXPECT(mt, eof_type, "<<EOF>>"); + EXPECT(mt, eof_type, "<<EOF>>"); + } + + { + std::istringstream iss("var1\"var2\""); + tokenizer mt(iss, false); + + EXPECT(mt, word_type, "var1"); + EXPECT(mt, word_type, "var2"); + EXPECT(mt, eof_type, "<<EOF>>"); + EXPECT(mt, eof_type, "<<EOF>>"); + } + + { + std::istringstream iss("var1\" var2 \""); + tokenizer mt(iss, false); + + EXPECT(mt, word_type, "var1"); + EXPECT(mt, word_type, " var2 "); + EXPECT(mt, eof_type, "<<EOF>>"); + EXPECT(mt, eof_type, "<<EOF>>"); + } +} + +ATF_TEST_CASE(tokenizer_quotes_ws); +ATF_TEST_CASE_HEAD(tokenizer_quotes_ws) +{ + set_md_var("descr", "Tests the tokenizer class using a parser with " + "quoted strings and skipping whitespace"); +} +ATF_TEST_CASE_BODY(tokenizer_quotes_ws) +{ + using namespace quotes; + + { + std::istringstream iss(" var "); + tokenizer mt(iss, true); + + EXPECT(mt, word_type, "var"); + EXPECT(mt, eof_type, "<<EOF>>"); + EXPECT(mt, eof_type, "<<EOF>>"); + } + + { + std::istringstream iss(" \"var\" "); + tokenizer mt(iss, true); + + EXPECT(mt, word_type, "var"); + EXPECT(mt, eof_type, "<<EOF>>"); + EXPECT(mt, eof_type, "<<EOF>>"); + } + + { + std::istringstream iss(" var1 \"var2\" "); + tokenizer mt(iss, true); + + EXPECT(mt, word_type, "var1"); + EXPECT(mt, word_type, "var2"); + EXPECT(mt, eof_type, "<<EOF>>"); + EXPECT(mt, eof_type, "<<EOF>>"); + } + + { + std::istringstream iss(" var1 \" var2 \" "); + tokenizer mt(iss, true); + + EXPECT(mt, word_type, "var1"); + EXPECT(mt, word_type, " var2 "); + EXPECT(mt, eof_type, "<<EOF>>"); + EXPECT(mt, eof_type, "<<EOF>>"); + } +} + +// ------------------------------------------------------------------------ +// Tests for the headers parser. +// ------------------------------------------------------------------------ + +class header_reader { + std::istream& m_is; + +public: + header_reader(std::istream& is) : + m_is(is) + { + } + + void + read(void) + { + std::pair< size_t, tools::parser::headers_map > hml = + tools::parser::read_headers(m_is, 1); + tools::parser::validate_content_type(hml.second, + "application/X-atf-headers-test", 1234); + } + + std::vector< std::string > m_calls; +}; + +ATF_TEST_CASE_WITHOUT_HEAD(headers_1); +ATF_TEST_CASE_BODY(headers_1) +{ + const char* input = + "" + ; + + const char* exp_calls[] = { + NULL + }; + + const char* exp_errors[] = { + "1: Unexpected token `<<EOF>>'; expected a header name", + NULL + }; + + do_parser_test< header_reader >(input, exp_calls, exp_errors); +} + +ATF_TEST_CASE_WITHOUT_HEAD(headers_2); +ATF_TEST_CASE_BODY(headers_2) +{ + const char* input = + "Content-Type\n" + ; + + const char* exp_calls[] = { + NULL + }; + + const char* exp_errors[] = { + "1: Unexpected token `<<NEWLINE>>'; expected `:'", + NULL + }; + + do_parser_test< header_reader >(input, exp_calls, exp_errors); +} + +ATF_TEST_CASE_WITHOUT_HEAD(headers_3); +ATF_TEST_CASE_BODY(headers_3) +{ + const char* input = + "Content-Type:\n" + ; + + const char* exp_calls[] = { + NULL + }; + + const char* exp_errors[] = { + "1: Unexpected token `<<NEWLINE>>'; expected a textual value", + NULL + }; + + do_parser_test< header_reader >(input, exp_calls, exp_errors); +} + +ATF_TEST_CASE_WITHOUT_HEAD(headers_4); +ATF_TEST_CASE_BODY(headers_4) +{ + const char* input = + "Content-Type: application/X-atf-headers-test\n" + ; + + const char* exp_calls[] = { + NULL + }; + + const char* exp_errors[] = { + "2: Unexpected token `<<EOF>>'; expected a header name", + NULL + }; + + do_parser_test< header_reader >(input, exp_calls, exp_errors); +} + +ATF_TEST_CASE_WITHOUT_HEAD(headers_5); +ATF_TEST_CASE_BODY(headers_5) +{ + const char* input = + "Content-Type: application/X-atf-headers-test;\n" + ; + + const char* exp_calls[] = { + NULL + }; + + const char* exp_errors[] = { + "1: Unexpected token `<<NEWLINE>>'; expected an attribute name", + NULL + }; + + do_parser_test< header_reader >(input, exp_calls, exp_errors); +} + +ATF_TEST_CASE_WITHOUT_HEAD(headers_6); +ATF_TEST_CASE_BODY(headers_6) +{ + const char* input = + "Content-Type: application/X-atf-headers-test; version\n" + ; + + const char* exp_calls[] = { + NULL + }; + + const char* exp_errors[] = { + "1: Unexpected token `<<NEWLINE>>'; expected `='", + NULL + }; + + do_parser_test< header_reader >(input, exp_calls, exp_errors); +} + +ATF_TEST_CASE_WITHOUT_HEAD(headers_7); +ATF_TEST_CASE_BODY(headers_7) +{ + const char* input = + "Content-Type: application/X-atf-headers-test; version=\n" + ; + + const char* exp_calls[] = { + NULL + }; + + const char* exp_errors[] = { + "1: Unexpected token `<<NEWLINE>>'; expected word or quoted string", + NULL + }; + + do_parser_test< header_reader >(input, exp_calls, exp_errors); +} + +ATF_TEST_CASE_WITHOUT_HEAD(headers_8); +ATF_TEST_CASE_BODY(headers_8) +{ + const char* input = + "Content-Type: application/X-atf-headers-test; version=\"1234\n" + ; + + const char* exp_calls[] = { + NULL + }; + + const char* exp_errors[] = { + "1: Missing double quotes before end of line", + NULL + }; + + do_parser_test< header_reader >(input, exp_calls, exp_errors); +} + +ATF_TEST_CASE_WITHOUT_HEAD(headers_9); +ATF_TEST_CASE_BODY(headers_9) +{ + const char* input = + "Content-Type: application/X-atf-headers-test; version=1234\"\n" + ; + + const char* exp_calls[] = { + NULL + }; + + const char* exp_errors[] = { + "1: Missing double quotes before end of line", + NULL + }; + + do_parser_test< header_reader >(input, exp_calls, exp_errors); +} + +ATF_TEST_CASE_WITHOUT_HEAD(headers_10); +ATF_TEST_CASE_BODY(headers_10) +{ + const char* input = + "Content-Type: application/X-atf-headers-test; version=1234\n" + ; + + const char* exp_calls[] = { + NULL + }; + + const char* exp_errors[] = { + "2: Unexpected token `<<EOF>>'; expected a header name", + NULL + }; + + do_parser_test< header_reader >(input, exp_calls, exp_errors); +} + +ATF_TEST_CASE_WITHOUT_HEAD(headers_11); +ATF_TEST_CASE_BODY(headers_11) +{ + const char* input = + "Content-Type: application/X-atf-headers-test; version=\"1234\"\n" + ; + + const char* exp_calls[] = { + NULL + }; + + const char* exp_errors[] = { + "2: Unexpected token `<<EOF>>'; expected a header name", + NULL + }; + + do_parser_test< header_reader >(input, exp_calls, exp_errors); +} + +ATF_TEST_CASE_WITHOUT_HEAD(headers_12); +ATF_TEST_CASE_BODY(headers_12) +{ + const char* input = + "Content-Type: application/X-atf-headers-test; version=\"1234\"\n" + "a b\n" + "a-b:\n" + "a-b: foo;\n" + "a-b: foo; var\n" + "a-b: foo; var=\n" + "a-b: foo; var=\"a\n" + "a-b: foo; var=a\"\n" + "a-b: foo; var=\"a\";\n" + "a-b: foo; var=\"a\"; second\n" + "a-b: foo; var=\"a\"; second=\n" + "a-b: foo; var=\"a\"; second=\"b\n" + "a-b: foo; var=\"a\"; second=b\"\n" + "a-b: foo; var=\"a\"; second=\"b\"\n" + ; + + const char* exp_calls[] = { + NULL + }; + + const char* exp_errors[] = { + "2: Unexpected token `b'; expected `:'", + "3: Unexpected token `<<NEWLINE>>'; expected a textual value", + "4: Unexpected token `<<NEWLINE>>'; expected an attribute name", + "5: Unexpected token `<<NEWLINE>>'; expected `='", + "6: Unexpected token `<<NEWLINE>>'; expected word or quoted string", + "7: Missing double quotes before end of line", + "8: Missing double quotes before end of line", + "9: Unexpected token `<<NEWLINE>>'; expected an attribute name", + "10: Unexpected token `<<NEWLINE>>'; expected `='", + "11: Unexpected token `<<NEWLINE>>'; expected word or quoted string", + "12: Missing double quotes before end of line", + "13: Missing double quotes before end of line", + NULL + }; + + do_parser_test< header_reader >(input, exp_calls, exp_errors); +} + +// ------------------------------------------------------------------------ +// Main. +// ------------------------------------------------------------------------ + +ATF_INIT_TEST_CASES(tcs) +{ + // Add test cases for the "parse_error" class. + ATF_ADD_TEST_CASE(tcs, parse_error_to_string); + + // Add test cases for the "parse_errors" class. + ATF_ADD_TEST_CASE(tcs, parse_errors_what); + + // Add test cases for the "token" class. + ATF_ADD_TEST_CASE(tcs, token_getters); + + // Add test cases for the "tokenizer" class. + ATF_ADD_TEST_CASE(tcs, tokenizer_minimal_nows); + ATF_ADD_TEST_CASE(tcs, tokenizer_minimal_ws); + ATF_ADD_TEST_CASE(tcs, tokenizer_delims_nows); + ATF_ADD_TEST_CASE(tcs, tokenizer_delims_ws); + ATF_ADD_TEST_CASE(tcs, tokenizer_keywords_nows); + ATF_ADD_TEST_CASE(tcs, tokenizer_keywords_ws); + ATF_ADD_TEST_CASE(tcs, tokenizer_quotes_nows); + ATF_ADD_TEST_CASE(tcs, tokenizer_quotes_ws); + + // Add the tests for the headers parser. + + // Add the test cases for the header file. + ATF_ADD_TEST_CASE(tcs, headers_1); + ATF_ADD_TEST_CASE(tcs, headers_2); + ATF_ADD_TEST_CASE(tcs, headers_3); + ATF_ADD_TEST_CASE(tcs, headers_4); + ATF_ADD_TEST_CASE(tcs, headers_5); + ATF_ADD_TEST_CASE(tcs, headers_6); + ATF_ADD_TEST_CASE(tcs, headers_7); + ATF_ADD_TEST_CASE(tcs, headers_8); + ATF_ADD_TEST_CASE(tcs, headers_9); + ATF_ADD_TEST_CASE(tcs, headers_10); + ATF_ADD_TEST_CASE(tcs, headers_11); + ATF_ADD_TEST_CASE(tcs, headers_12); +} diff --git a/unit/atf-src/tools/pass_helper.cpp b/unit/atf-src/tools/pass_helper.cpp new file mode 100644 index 0000000..4fa75b4 --- /dev/null +++ b/unit/atf-src/tools/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++.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/unit/atf-src/tools/process.cpp b/unit/atf-src/tools/process.cpp new file mode 100644 index 0000000..987a60e --- /dev/null +++ b/unit/atf-src/tools/process.cpp @@ -0,0 +1,492 @@ +// +// 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 <sys/wait.h> + +#include <fcntl.h> +#include <signal.h> +} + +#include <cassert> +#include <cstdarg> +#include <cerrno> +#include <cstring> +#include <iostream> + +#include "defs.hpp" +#include "exceptions.hpp" +#include "text.hpp" +#include "process.hpp" + +namespace detail = tools::process::detail; +namespace impl = tools::process; +#define IMPL_NAME "tools::process" + +// ------------------------------------------------------------------------ +// Auxiliary functions. +// ------------------------------------------------------------------------ + +template< class C > +tools::auto_array< const char* > +collection_to_argv(const C& c) +{ + tools::auto_array< const char* > argv(new const char*[c.size() + 1]); + + std::size_t pos = 0; + for (typename C::const_iterator iter = c.begin(); iter != c.end(); + iter++) { + argv[pos] = (*iter).c_str(); + pos++; + } + assert(pos == c.size()); + argv[pos] = NULL; + + return argv; +} + +template< class C > +C +argv_to_collection(const char* const* argv) +{ + C c; + + for (const char* const* iter = argv; *iter != NULL; iter++) + c.push_back(std::string(*iter)); + + return c; +} + +static +void +safe_dup(const int oldfd, const int newfd) +{ + if (oldfd != newfd) { + if (dup2(oldfd, newfd) == -1) { + throw tools::system_error(IMPL_NAME "::safe_dup", + "Could not allocate file descriptor", + errno); + } else { + ::close(oldfd); + } + } +} + +static +int +const_execvp(const char *file, const char *const *argv) +{ +#define UNCONST(a) ((void *)(unsigned long)(const void *)(a)) + return ::execvp(file, (char* const*)(UNCONST(argv))); +#undef UNCONST +} + +void +detail::do_exec(void *v) +{ + struct exec_args *ea = reinterpret_cast<struct exec_args *>(v); + + if (ea->m_prehook != NULL) + ea->m_prehook(); + + const int ret = const_execvp(ea->m_prog.c_str(), ea->m_argv.exec_argv()); + const int errnocopy = errno; + assert(ret == -1); + std::cerr << "exec(" << ea->m_prog.str() << ") failed: " + << std::strerror(errnocopy) << "\n"; + std::exit(EXIT_FAILURE); +} + +// ------------------------------------------------------------------------ +// The "argv_array" type. +// ------------------------------------------------------------------------ + +impl::argv_array::argv_array(void) : + m_exec_argv(collection_to_argv(m_args)) +{ +} + +impl::argv_array::argv_array(const char* arg1, ...) +{ + m_args.push_back(arg1); + + { + va_list ap; + const char* nextarg; + + va_start(ap, arg1); + while ((nextarg = va_arg(ap, const char*)) != NULL) + m_args.push_back(nextarg); + va_end(ap); + } + + ctor_init_exec_argv(); +} + +impl::argv_array::argv_array(const char* const* ca) : + m_args(argv_to_collection< args_vector >(ca)), + m_exec_argv(collection_to_argv(m_args)) +{ +} + +impl::argv_array::argv_array(const argv_array& a) : + m_args(a.m_args), + m_exec_argv(collection_to_argv(m_args)) +{ +} + +void +impl::argv_array::ctor_init_exec_argv(void) +{ + m_exec_argv = collection_to_argv(m_args); +} + +const char* const* +impl::argv_array::exec_argv(void) + const +{ + return m_exec_argv.get(); +} + +impl::argv_array::size_type +impl::argv_array::size(void) + const +{ + return m_args.size(); +} + +const char* +impl::argv_array::operator[](int idx) + const +{ + return m_args[idx].c_str(); +} + +impl::argv_array::const_iterator +impl::argv_array::begin(void) + const +{ + return m_args.begin(); +} + +impl::argv_array::const_iterator +impl::argv_array::end(void) + const +{ + return m_args.end(); +} + +impl::argv_array& +impl::argv_array::operator=(const argv_array& a) +{ + if (this != &a) { + m_args = a.m_args; + m_exec_argv = collection_to_argv(m_args); + } + return *this; +} + +// ------------------------------------------------------------------------ +// The "stream" types. +// ------------------------------------------------------------------------ + +impl::stream_capture::stream_capture(void) +{ + for (int i = 0; i < 2; i++) + m_pipefds[i] = -1; +} + +impl::stream_capture::~stream_capture(void) +{ + for (int i = 0; i < 2; i++) + if (m_pipefds[i] != -1) + ::close(m_pipefds[i]); +} + +void +impl::stream_capture::prepare(void) +{ + if (pipe(m_pipefds) == -1) + throw system_error(IMPL_NAME "::stream_capture::prepare", + "Failed to create pipe", errno); +} + +int +impl::stream_capture::connect_parent(void) +{ + ::close(m_pipefds[1]); m_pipefds[1] = -1; + const int fd = m_pipefds[0]; + m_pipefds[0] = -1; + return fd; +} + +void +impl::stream_capture::connect_child(const int fd) +{ + ::close(m_pipefds[0]); m_pipefds[0] = -1; + if (m_pipefds[1] != fd) { + safe_dup(m_pipefds[1], fd); + } + m_pipefds[1] = -1; +} + +impl::stream_connect::stream_connect(const int src_fd, const int tgt_fd) : + m_src_fd(src_fd), m_tgt_fd(tgt_fd) +{ +} + +void +impl::stream_connect::prepare(void) +{ +} + +int +impl::stream_connect::connect_parent(void) +{ + return -1; +} + +void +impl::stream_connect::connect_child(const int fd ATF_DEFS_ATTRIBUTE_UNUSED) +{ + safe_dup(m_tgt_fd, m_src_fd); +} + +impl::stream_inherit::stream_inherit(void) +{ +} + +void +impl::stream_inherit::prepare(void) +{ +} + +int +impl::stream_inherit::connect_parent(void) +{ + return -1; +} + +void +impl::stream_inherit::connect_child(const int fd ATF_DEFS_ATTRIBUTE_UNUSED) +{ +} + +impl::stream_redirect_fd::stream_redirect_fd(const int fd) : + m_fd(fd) +{ +} + +void +impl::stream_redirect_fd::prepare(void) +{ +} + +int +impl::stream_redirect_fd::connect_parent(void) +{ + return -1; +} + +void +impl::stream_redirect_fd::connect_child(const int fd) +{ + safe_dup(m_fd, fd); +} + +impl::stream_redirect_path::stream_redirect_path(const tools::fs::path& p) : + m_path(p) +{ +} + +void +impl::stream_redirect_path::prepare(void) +{ +} + +int +impl::stream_redirect_path::connect_parent(void) +{ + return -1; +} + +void +impl::stream_redirect_path::connect_child(const int fd) +{ + const int aux = ::open(m_path.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0644); + if (aux == -1) + throw system_error(IMPL_NAME "::stream_redirect_path::connect_child", + "Could not create " + m_path.str(), errno); + else + safe_dup(aux, fd); +} + +// ------------------------------------------------------------------------ +// The "status" type. +// ------------------------------------------------------------------------ + +impl::status::status(int s) : + m_status(s) +{ +} + +impl::status::~status(void) +{ +} + +bool +impl::status::exited(void) + const +{ + int mutable_status = m_status; + return WIFEXITED(mutable_status); +} + +int +impl::status::exitstatus(void) + const +{ + assert(exited()); + int mutable_status = m_status; + return WEXITSTATUS(mutable_status); +} + +bool +impl::status::signaled(void) + const +{ + int mutable_status = m_status; + return WIFSIGNALED(mutable_status); +} + +int +impl::status::termsig(void) + const +{ + assert(signaled()); + int mutable_status = m_status; + return WTERMSIG(mutable_status); +} + +bool +impl::status::coredump(void) + const +{ + assert(signaled()); +#if defined(WCOREDUMP) + int mutable_status = m_status; + return WCOREDUMP(mutable_status); +#else + return false; +#endif +} + +// ------------------------------------------------------------------------ +// The "child" type. +// ------------------------------------------------------------------------ + +impl::child::child(const pid_t pid_arg, const int stdout_fd_arg, + const int stderr_fd_arg) : + m_pid(pid_arg), + m_stdout(stdout_fd_arg), + m_stderr(stderr_fd_arg), + m_waited(false) +{ +} + +impl::child::~child(void) +{ + if (!m_waited) { + ::kill(m_pid, SIGTERM); + (void)wait(); + + if (m_stdout != -1) + ::close(m_stdout); + if (m_stderr != -1) + ::close(m_stderr); + } +} + +impl::status +impl::child::wait(void) +{ + int s; + + if (::waitpid(m_pid, &s, 0) == -1) + throw system_error(IMPL_NAME "::child::wait", "Failed waiting for " + "process " + text::to_string(m_pid), errno); + + if (m_stdout != -1) + ::close(m_stdout); m_stdout = -1; + if (m_stderr != -1) + ::close(m_stderr); m_stderr = -1; + + m_waited = true; + return status(s); +} + +pid_t +impl::child::pid(void) + const +{ + return m_pid; +} + +int +impl::child::stdout_fd(void) +{ + return m_stdout; +} + +int +impl::child::stderr_fd(void) +{ + return m_stderr; +} + +// ------------------------------------------------------------------------ +// Free functions. +// ------------------------------------------------------------------------ + +void +detail::flush_streams(void) +{ + // This is a weird hack to ensure that the output of the parent process + // is flushed before executing a child which prevents, for example, the + // output of the atf-run hooks to appear before the output of atf-run + // itself. + // + // TODO: This should only be executed when inheriting the stdout or + // stderr file descriptors. However, the flushing is specific to the + // iostreams, so we cannot do it from the C library where all the process + // logic is performed. Come up with a better design. + std::cout.flush(); + std::cerr.flush(); +} diff --git a/unit/atf-src/tools/process.hpp b/unit/atf-src/tools/process.hpp new file mode 100644 index 0000000..2ebd865 --- /dev/null +++ b/unit/atf-src/tools/process.hpp @@ -0,0 +1,324 @@ +// +// 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. +// + +#if !defined(TOOLS_PROCESS_HPP) +#define TOOLS_PROCESS_HPP + +extern "C" { +#include <sys/types.h> + +#include <unistd.h> +} + +#include <cerrno> +#include <cstdlib> +#include <iostream> +#include <string> +#include <vector> + +#include "auto_array.hpp" +#include "exceptions.hpp" +#include "fs.hpp" + +namespace tools { +namespace process { + +class child; +class status; + +// ------------------------------------------------------------------------ +// The "argv_array" type. +// ------------------------------------------------------------------------ + +class argv_array { + typedef std::vector< std::string > args_vector; + args_vector m_args; + + // TODO: This is immutable, so we should be able to use + // std::tr1::shared_array instead when it becomes widely available. + // The reason would be to remove all copy constructors and assignment + // operators from this class. + auto_array< const char* > m_exec_argv; + void ctor_init_exec_argv(void); + +public: + typedef args_vector::const_iterator const_iterator; + typedef args_vector::size_type size_type; + + argv_array(void); + argv_array(const char*, ...); + explicit argv_array(const char* const*); + template< class C > explicit argv_array(const C&); + argv_array(const argv_array&); + + const char* const* exec_argv(void) const; + size_type size(void) const; + const char* operator[](int) const; + + const_iterator begin(void) const; + const_iterator end(void) const; + + argv_array& operator=(const argv_array&); +}; + +template< class C > +argv_array::argv_array(const C& c) +{ + for (typename C::const_iterator iter = c.begin(); iter != c.end(); + iter++) + m_args.push_back(*iter); + ctor_init_exec_argv(); +} + +// ------------------------------------------------------------------------ +// The "stream" types. +// ------------------------------------------------------------------------ + +class stream_capture { + int m_pipefds[2]; + + // Allow access to the getters. + template< class OutStream, class ErrStream > friend + child fork(void (*)(void*), OutStream, ErrStream, void*); + template< class OutStream, class ErrStream > friend + status exec(const tools::fs::path&, const argv_array&, + const OutStream&, const ErrStream&, void (*)(void)); + + void prepare(void); + int connect_parent(void); + void connect_child(const int); + +public: + stream_capture(void); + ~stream_capture(void); +}; + +class stream_connect { + int m_src_fd; + int m_tgt_fd; + + // Allow access to the getters. + template< class OutStream, class ErrStream > friend + child fork(void (*)(void*), OutStream, ErrStream, void*); + template< class OutStream, class ErrStream > friend + status exec(const tools::fs::path&, const argv_array&, + const OutStream&, const ErrStream&, void (*)(void)); + + void prepare(void); + int connect_parent(void); + void connect_child(const int); + +public: + stream_connect(const int, const int); +}; + +class stream_inherit { + // Allow access to the getters. + template< class OutStream, class ErrStream > friend + child fork(void (*)(void*), OutStream, ErrStream, void*); + template< class OutStream, class ErrStream > friend + status exec(const tools::fs::path&, const argv_array&, + const OutStream&, const ErrStream&, void (*)(void)); + + void prepare(void); + int connect_parent(void); + void connect_child(const int); + +public: + stream_inherit(void); +}; + +class stream_redirect_fd { + int m_fd; + + // Allow access to the getters. + template< class OutStream, class ErrStream > friend + child fork(void (*)(void*), OutStream, ErrStream, void*); + template< class OutStream, class ErrStream > friend + status exec(const tools::fs::path&, const argv_array&, + const OutStream&, const ErrStream&, void (*)(void)); + + void prepare(void); + int connect_parent(void); + void connect_child(const int); + +public: + stream_redirect_fd(const int); +}; + +class stream_redirect_path { + const tools::fs::path m_path; + + // Allow access to the getters. + template< class OutStream, class ErrStream > friend + child fork(void (*)(void*), OutStream, ErrStream, void*); + template< class OutStream, class ErrStream > friend + status exec(const tools::fs::path&, const argv_array&, + const OutStream&, const ErrStream&, void (*)(void)); + + void prepare(void); + int connect_parent(void); + void connect_child(const int); + +public: + stream_redirect_path(const tools::fs::path&); +}; + +// ------------------------------------------------------------------------ +// The "status" type. +// ------------------------------------------------------------------------ + +class status { + int m_status; + + friend class child; + template< class OutStream, class ErrStream > friend + status exec(const tools::fs::path&, const argv_array&, + const OutStream&, const ErrStream&, void (*)(void)); + + status(int); + +public: + ~status(void); + + bool exited(void) const; + int exitstatus(void) const; + + bool signaled(void) const; + int termsig(void) const; + bool coredump(void) const; +}; + +// ------------------------------------------------------------------------ +// The "child" type. +// ------------------------------------------------------------------------ + +class child { + pid_t m_pid; + + int m_stdout; + int m_stderr; + + bool m_waited; + + template< class OutStream, class ErrStream > friend + child fork(void (*)(void*), OutStream, ErrStream, void*); + + child(const pid_t, const int, const int); + +public: + ~child(void); + + status wait(void); + + pid_t pid(void) const; + int stdout_fd(void); + int stderr_fd(void); +}; + +// ------------------------------------------------------------------------ +// Free functions. +// ------------------------------------------------------------------------ + +namespace detail { +void flush_streams(void); + +struct exec_args { + const tools::fs::path m_prog; + const argv_array& m_argv; + void (*m_prehook)(void); +}; + +void do_exec(void *); +} // namespace detail + +// TODO: The void* cookie can probably be templatized, thus also allowing +// const data structures. +template< class OutStream, class ErrStream > +child +fork(void (*start)(void*), OutStream outsb, ErrStream errsb, void* v) +{ + detail::flush_streams(); + + outsb.prepare(); + errsb.prepare(); + + pid_t pid = ::fork(); + if (pid == -1) { + throw system_error("tools::process::child::fork", + "Failed to fork", errno); + } else if (pid == 0) { + try { + outsb.connect_child(STDOUT_FILENO); + errsb.connect_child(STDERR_FILENO); + start(v); + std::abort(); + } catch (...) { + std::cerr << "Unhandled error while running subprocess\n"; + std::exit(EXIT_FAILURE); + } + } else { + const int stdout_fd = outsb.connect_parent(); + const int stderr_fd = errsb.connect_parent(); + return child(pid, stdout_fd, stderr_fd); + } +} + +template< class OutStream, class ErrStream > +status +exec(const tools::fs::path& prog, const argv_array& argv, + const OutStream& outsb, const ErrStream& errsb, + void (*prehook)(void)) +{ + struct detail::exec_args ea = { prog, argv, prehook }; + child c = fork(detail::do_exec, outsb, errsb, &ea); + +again: + try { + return c.wait(); + } catch (const system_error& e) { + if (e.code() == EINTR) + goto again; + else + throw e; + } +} + +template< class OutStream, class ErrStream > +status +exec(const tools::fs::path& prog, const argv_array& argv, + const OutStream& outsb, const ErrStream& errsb) +{ + return exec(prog, argv, outsb, errsb, NULL); +} + +} // namespace process +} // namespace tools + +#endif // !defined(TOOLS_PROCESS_HPP) diff --git a/unit/atf-src/tools/process_helpers.c b/unit/atf-src/tools/process_helpers.c new file mode 100644 index 0000000..61f1b67 --- /dev/null +++ b/unit/atf-src/tools/process_helpers.c @@ -0,0 +1,117 @@ +/* + * 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. + */ + +#include <sys/types.h> + +#include <assert.h> /* NO_CHECK_STYLE */ +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +static +int +h_echo(const char *msg) +{ + printf("%s\n", msg); + return EXIT_SUCCESS; +} + +static +int +h_exit_failure(void) +{ + return EXIT_FAILURE; +} + +static +int +h_exit_signal(void) +{ + kill(getpid(), SIGKILL); + assert(0); /* NO_CHECK_STYLE */ + return EXIT_FAILURE; +} + +static +int +h_exit_success(void) +{ + return EXIT_SUCCESS; +} + +static +int +h_stdout_stderr(const char *id) +{ + fprintf(stdout, "Line 1 to stdout for %s\n", id); + fprintf(stdout, "Line 2 to stdout for %s\n", id); + fprintf(stderr, "Line 1 to stderr for %s\n", id); + fprintf(stderr, "Line 2 to stderr for %s\n", id); + + return EXIT_SUCCESS; +} + +static +void +check_args(const int argc, const char *const argv[], const int required) +{ + if (argc < required) { + fprintf(stderr, "Usage: %s helper-name [args]\n", argv[0]); + exit(EXIT_FAILURE); + } +} + +int +main(int argc, const char *const argv[]) +{ + int exitcode; + + check_args(argc, argv, 2); + + if (strcmp(argv[1], "echo") == 0) { + check_args(argc, argv, 3); + exitcode = h_echo(argv[2]); + } else if (strcmp(argv[1], "exit-failure") == 0) + exitcode = h_exit_failure(); + else if (strcmp(argv[1], "exit-signal") == 0) + exitcode = h_exit_signal(); + else if (strcmp(argv[1], "exit-success") == 0) + exitcode = h_exit_success(); + else if (strcmp(argv[1], "stdout-stderr") == 0) { + check_args(argc, argv, 3); + exitcode = h_stdout_stderr(argv[2]); + } else { + fprintf(stderr, "%s: Unknown helper %s\n", argv[0], argv[1]); + exitcode = EXIT_FAILURE; + } + + return exitcode; +} diff --git a/unit/atf-src/tools/process_test.cpp b/unit/atf-src/tools/process_test.cpp new file mode 100644 index 0000000..70da919 --- /dev/null +++ b/unit/atf-src/tools/process_test.cpp @@ -0,0 +1,360 @@ +// +// 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. +// + +#include <cstdlib> +#include <cstring> + +#include <atf-c++.hpp> + +#include "process.hpp" +#include "test_helpers.hpp" + +// TODO: Testing the fork function is a huge task and I'm afraid of +// copy/pasting tons of stuff from the C version. I'd rather not do that +// until some code can be shared, which cannot happen until the C++ binding +// is cleaned by a fair amount. Instead... just rely (at the moment) on +// the system tests for the tools using this module. + +// ------------------------------------------------------------------------ +// Auxiliary functions. +// ------------------------------------------------------------------------ + +static +std::size_t +array_size(const char* const* array) +{ + std::size_t size = 0; + + for (const char* const* ptr = array; *ptr != NULL; ptr++) + size++; + + return size; +} + +static +tools::process::status +exec_process_helpers(const atf::tests::tc& tc, const char* helper_name) +{ + using tools::process::exec; + + const tools::fs::path helpers = tools::fs::path(tc.get_config_var("srcdir")) / + "process_helpers"; + + std::vector< std::string > argv; + argv.push_back(helpers.leaf_name()); + argv.push_back(helper_name); + + return exec(helpers, + tools::process::argv_array(argv), + tools::process::stream_inherit(), + tools::process::stream_inherit()); +} + +// ------------------------------------------------------------------------ +// Tests for the "argv_array" type. +// ------------------------------------------------------------------------ + +ATF_TEST_CASE(argv_array_init_carray); +ATF_TEST_CASE_HEAD(argv_array_init_carray) +{ + set_md_var("descr", "Tests that argv_array is correctly constructed " + "from a C-style array of strings"); +} +ATF_TEST_CASE_BODY(argv_array_init_carray) +{ + { + const char* const carray[] = { NULL }; + tools::process::argv_array argv(carray); + + ATF_REQUIRE_EQ(argv.size(), 0); + } + + { + const char* const carray[] = { "arg0", NULL }; + tools::process::argv_array argv(carray); + + ATF_REQUIRE_EQ(argv.size(), 1); + ATF_REQUIRE(std::strcmp(argv[0], carray[0]) == 0); + } + + { + const char* const carray[] = { "arg0", "arg1", "arg2", NULL }; + tools::process::argv_array argv(carray); + + ATF_REQUIRE_EQ(argv.size(), 3); + ATF_REQUIRE(std::strcmp(argv[0], carray[0]) == 0); + ATF_REQUIRE(std::strcmp(argv[1], carray[1]) == 0); + ATF_REQUIRE(std::strcmp(argv[2], carray[2]) == 0); + } +} + +ATF_TEST_CASE(argv_array_init_col); +ATF_TEST_CASE_HEAD(argv_array_init_col) +{ + set_md_var("descr", "Tests that argv_array is correctly constructed " + "from a string collection"); +} +ATF_TEST_CASE_BODY(argv_array_init_col) +{ + { + std::vector< std::string > col; + tools::process::argv_array argv(col); + + ATF_REQUIRE_EQ(argv.size(), 0); + } + + { + std::vector< std::string > col; + col.push_back("arg0"); + tools::process::argv_array argv(col); + + ATF_REQUIRE_EQ(argv.size(), 1); + ATF_REQUIRE_EQ(argv[0], col[0]); + } + + { + std::vector< std::string > col; + col.push_back("arg0"); + col.push_back("arg1"); + col.push_back("arg2"); + tools::process::argv_array argv(col); + + ATF_REQUIRE_EQ(argv.size(), 3); + ATF_REQUIRE_EQ(argv[0], col[0]); + ATF_REQUIRE_EQ(argv[1], col[1]); + ATF_REQUIRE_EQ(argv[2], col[2]); + } +} + +ATF_TEST_CASE(argv_array_init_empty); +ATF_TEST_CASE_HEAD(argv_array_init_empty) +{ + set_md_var("descr", "Tests that argv_array is correctly constructed " + "by the default constructor"); +} +ATF_TEST_CASE_BODY(argv_array_init_empty) +{ + tools::process::argv_array argv; + + ATF_REQUIRE_EQ(argv.size(), 0); +} + +ATF_TEST_CASE(argv_array_init_varargs); +ATF_TEST_CASE_HEAD(argv_array_init_varargs) +{ + set_md_var("descr", "Tests that argv_array is correctly constructed " + "from a variable list of arguments"); +} +ATF_TEST_CASE_BODY(argv_array_init_varargs) +{ + { + tools::process::argv_array argv("arg0", NULL); + + ATF_REQUIRE_EQ(argv.size(), 1); + ATF_REQUIRE_EQ(argv[0], std::string("arg0")); + } + + { + tools::process::argv_array argv("arg0", "arg1", "arg2", NULL); + + ATF_REQUIRE_EQ(argv.size(), 3); + ATF_REQUIRE_EQ(argv[0], std::string("arg0")); + ATF_REQUIRE_EQ(argv[1], std::string("arg1")); + ATF_REQUIRE_EQ(argv[2], std::string("arg2")); + } +} + +ATF_TEST_CASE(argv_array_assign); +ATF_TEST_CASE_HEAD(argv_array_assign) +{ + set_md_var("descr", "Tests that assigning an argv_array works"); +} +ATF_TEST_CASE_BODY(argv_array_assign) +{ + using tools::process::argv_array; + + const char* const carray1[] = { "arg1", NULL }; + const char* const carray2[] = { "arg1", "arg2", NULL }; + + std::auto_ptr< argv_array > argv1(new argv_array(carray1)); + std::auto_ptr< argv_array > argv2(new argv_array(carray2)); + + *argv2 = *argv1; + ATF_REQUIRE_EQ(argv2->size(), argv1->size()); + ATF_REQUIRE(std::strcmp((*argv2)[0], (*argv1)[0]) == 0); + + ATF_REQUIRE(argv2->exec_argv() != argv1->exec_argv()); + argv1.release(); + { + const char* const* eargv2 = argv2->exec_argv(); + ATF_REQUIRE(std::strcmp(eargv2[0], carray1[0]) == 0); + ATF_REQUIRE_EQ(eargv2[1], static_cast< const char* >(NULL)); + } + + argv2.release(); +} + +ATF_TEST_CASE(argv_array_copy); +ATF_TEST_CASE_HEAD(argv_array_copy) +{ + set_md_var("descr", "Tests that copying an argv_array constructed from " + "a C-style array of strings works"); +} +ATF_TEST_CASE_BODY(argv_array_copy) +{ + using tools::process::argv_array; + + const char* const carray[] = { "arg0", NULL }; + + std::auto_ptr< argv_array > argv1(new argv_array(carray)); + std::auto_ptr< argv_array > argv2(new argv_array(*argv1)); + + ATF_REQUIRE_EQ(argv2->size(), argv1->size()); + ATF_REQUIRE(std::strcmp((*argv2)[0], (*argv1)[0]) == 0); + + ATF_REQUIRE(argv2->exec_argv() != argv1->exec_argv()); + argv1.release(); + { + const char* const* eargv2 = argv2->exec_argv(); + ATF_REQUIRE(std::strcmp(eargv2[0], carray[0]) == 0); + ATF_REQUIRE_EQ(eargv2[1], static_cast< const char* >(NULL)); + } + + argv2.release(); +} + +ATF_TEST_CASE(argv_array_exec_argv); +ATF_TEST_CASE_HEAD(argv_array_exec_argv) +{ + set_md_var("descr", "Tests that the exec argv provided by an argv_array " + "is correct"); +} +ATF_TEST_CASE_BODY(argv_array_exec_argv) +{ + using tools::process::argv_array; + + { + argv_array argv; + const char* const* eargv = argv.exec_argv(); + ATF_REQUIRE_EQ(array_size(eargv), 0); + ATF_REQUIRE_EQ(eargv[0], static_cast< const char* >(NULL)); + } + + { + const char* const carray[] = { "arg0", NULL }; + argv_array argv(carray); + const char* const* eargv = argv.exec_argv(); + ATF_REQUIRE_EQ(array_size(eargv), 1); + ATF_REQUIRE(std::strcmp(eargv[0], "arg0") == 0); + ATF_REQUIRE_EQ(eargv[1], static_cast< const char* >(NULL)); + } + + { + std::vector< std::string > col; + col.push_back("arg0"); + argv_array argv(col); + const char* const* eargv = argv.exec_argv(); + ATF_REQUIRE_EQ(array_size(eargv), 1); + ATF_REQUIRE(std::strcmp(eargv[0], "arg0") == 0); + ATF_REQUIRE_EQ(eargv[1], static_cast< const char* >(NULL)); + } +} + +ATF_TEST_CASE(argv_array_iter); +ATF_TEST_CASE_HEAD(argv_array_iter) +{ + set_md_var("descr", "Tests that an argv_array can be iterated"); +} +ATF_TEST_CASE_BODY(argv_array_iter) +{ + using tools::process::argv_array; + + std::vector< std::string > vector; + vector.push_back("arg0"); + vector.push_back("arg1"); + vector.push_back("arg2"); + + argv_array argv(vector); + ATF_REQUIRE_EQ(argv.size(), 3); + std::vector< std::string >::size_type pos = 0; + for (argv_array::const_iterator iter = argv.begin(); iter != argv.end(); + iter++) { + ATF_REQUIRE_EQ(*iter, vector[pos]); + pos++; + } +} + +// ------------------------------------------------------------------------ +// Tests cases for the free functions. +// ------------------------------------------------------------------------ + +ATF_TEST_CASE(exec_failure); +ATF_TEST_CASE_HEAD(exec_failure) +{ + set_md_var("descr", "Tests execing a command that reports failure"); +} +ATF_TEST_CASE_BODY(exec_failure) +{ + const tools::process::status s = exec_process_helpers(*this, "exit-failure"); + ATF_REQUIRE(s.exited()); + ATF_REQUIRE_EQ(s.exitstatus(), EXIT_FAILURE); +} + +ATF_TEST_CASE(exec_success); +ATF_TEST_CASE_HEAD(exec_success) +{ + set_md_var("descr", "Tests execing a command that reports success"); +} +ATF_TEST_CASE_BODY(exec_success) +{ + const tools::process::status s = exec_process_helpers(*this, "exit-success"); + ATF_REQUIRE(s.exited()); + ATF_REQUIRE_EQ(s.exitstatus(), EXIT_SUCCESS); +} + +// ------------------------------------------------------------------------ +// Main. +// ------------------------------------------------------------------------ + +ATF_INIT_TEST_CASES(tcs) +{ + // Add the test cases for the "argv_array" type. + ATF_ADD_TEST_CASE(tcs, argv_array_assign); + ATF_ADD_TEST_CASE(tcs, argv_array_copy); + ATF_ADD_TEST_CASE(tcs, argv_array_exec_argv); + ATF_ADD_TEST_CASE(tcs, argv_array_init_carray); + ATF_ADD_TEST_CASE(tcs, argv_array_init_col); + ATF_ADD_TEST_CASE(tcs, argv_array_init_empty); + ATF_ADD_TEST_CASE(tcs, argv_array_init_varargs); + ATF_ADD_TEST_CASE(tcs, argv_array_iter); + + // Add the test cases for the free functions. + ATF_ADD_TEST_CASE(tcs, exec_failure); + ATF_ADD_TEST_CASE(tcs, exec_success); +} diff --git a/unit/atf-src/tools/reader.cpp b/unit/atf-src/tools/reader.cpp new file mode 100644 index 0000000..67fce66 --- /dev/null +++ b/unit/atf-src/tools/reader.cpp @@ -0,0 +1,440 @@ +// +// 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/time.h> +} + +#include <cassert> +#include <cstdlib> +#include <map> +#include <sstream> +#include <utility> + +#include "defs.hpp" +#include "parser.hpp" +#include "reader.hpp" +#include "text.hpp" + +namespace impl = tools::atf_report; +#define IMPL_NAME "tools::atf_report" + +// ------------------------------------------------------------------------ +// Auxiliary functions. +// ------------------------------------------------------------------------ + +template< typename Type > +Type +string_to_int(const std::string& str) +{ + std::istringstream ss(str); + Type s; + ss >> s; + + return s; +} + +// ------------------------------------------------------------------------ +// The "atf_tps" auxiliary parser. +// ------------------------------------------------------------------------ + +namespace atf_tps { + +static const tools::parser::token_type eof_type = 0; +static const tools::parser::token_type nl_type = 1; +static const tools::parser::token_type text_type = 2; +static const tools::parser::token_type colon_type = 3; +static const tools::parser::token_type comma_type = 4; +static const tools::parser::token_type tps_count_type = 5; +static const tools::parser::token_type tp_start_type = 6; +static const tools::parser::token_type tp_end_type = 7; +static const tools::parser::token_type tc_start_type = 8; +static const tools::parser::token_type tc_so_type = 9; +static const tools::parser::token_type tc_se_type = 10; +static const tools::parser::token_type tc_end_type = 11; +static const tools::parser::token_type passed_type = 12; +static const tools::parser::token_type failed_type = 13; +static const tools::parser::token_type skipped_type = 14; +static const tools::parser::token_type info_type = 16; +static const tools::parser::token_type expected_death_type = 17; +static const tools::parser::token_type expected_exit_type = 18; +static const tools::parser::token_type expected_failure_type = 19; +static const tools::parser::token_type expected_signal_type = 20; +static const tools::parser::token_type expected_timeout_type = 21; + +class tokenizer : public tools::parser::tokenizer< std::istream > { +public: + tokenizer(std::istream& is, size_t curline) : + tools::parser::tokenizer< std::istream > + (is, true, eof_type, nl_type, text_type, curline) + { + add_delim(':', colon_type); + add_delim(',', comma_type); + add_keyword("tps-count", tps_count_type); + add_keyword("tp-start", tp_start_type); + add_keyword("tp-end", tp_end_type); + add_keyword("tc-start", tc_start_type); + add_keyword("tc-so", tc_so_type); + add_keyword("tc-se", tc_se_type); + add_keyword("tc-end", tc_end_type); + add_keyword("passed", passed_type); + add_keyword("failed", failed_type); + add_keyword("skipped", skipped_type); + add_keyword("info", info_type); + add_keyword("expected_death", expected_death_type); + add_keyword("expected_exit", expected_exit_type); + add_keyword("expected_failure", expected_failure_type); + add_keyword("expected_signal", expected_signal_type); + add_keyword("expected_timeout", expected_timeout_type); + } +}; + +} // namespace atf_tps + +struct timeval +read_timeval(tools::parser::parser< atf_tps::tokenizer >& parser) +{ + using namespace atf_tps; + + tools::parser::token t = parser.expect(text_type, "timestamp"); + const std::string::size_type divider = t.text().find('.'); + if (divider == std::string::npos || divider == 0 || + divider == t.text().length() - 1) + throw tools::parser::parse_error(t.lineno(), + "Malformed timestamp value " + t.text()); + + struct timeval tv; + tv.tv_sec = string_to_int< long >(t.text().substr(0, divider)); + tv.tv_usec = string_to_int< long >(t.text().substr(divider + 1)); + return tv; +} + +// ------------------------------------------------------------------------ +// The "atf_tps_reader" class. +// ------------------------------------------------------------------------ + +impl::atf_tps_reader::atf_tps_reader(std::istream& is) : + m_is(is) +{ +} + +impl::atf_tps_reader::~atf_tps_reader(void) +{ +} + +void +impl::atf_tps_reader::got_info( + const std::string& what ATF_DEFS_ATTRIBUTE_UNUSED, + const std::string& val ATF_DEFS_ATTRIBUTE_UNUSED) +{ +} + +void +impl::atf_tps_reader::got_ntps(size_t ntps ATF_DEFS_ATTRIBUTE_UNUSED) +{ +} + +void +impl::atf_tps_reader::got_tp_start( + const std::string& tp ATF_DEFS_ATTRIBUTE_UNUSED, + size_t ntcs ATF_DEFS_ATTRIBUTE_UNUSED) +{ +} + +void +impl::atf_tps_reader::got_tp_end( + struct timeval* tv ATF_DEFS_ATTRIBUTE_UNUSED, + const std::string& reason ATF_DEFS_ATTRIBUTE_UNUSED) +{ +} + +void +impl::atf_tps_reader::got_tc_start( + const std::string& tcname ATF_DEFS_ATTRIBUTE_UNUSED) +{ +} + +void +impl::atf_tps_reader::got_tc_stdout_line( + const std::string& line ATF_DEFS_ATTRIBUTE_UNUSED) +{ +} + +void +impl::atf_tps_reader::got_tc_stderr_line( + const std::string& line ATF_DEFS_ATTRIBUTE_UNUSED) +{ +} + +void +impl::atf_tps_reader::got_tc_end( + const std::string& state ATF_DEFS_ATTRIBUTE_UNUSED, + struct timeval* tv ATF_DEFS_ATTRIBUTE_UNUSED, + const std::string& reason ATF_DEFS_ATTRIBUTE_UNUSED) +{ +} + +void +impl::atf_tps_reader::got_eof(void) +{ +} + +void +impl::atf_tps_reader::read_info(void* pptr) +{ + using tools::parser::parse_error; + using namespace atf_tps; + + tools::parser::parser< tokenizer >& p = + *reinterpret_cast< tools::parser::parser< tokenizer >* > + (pptr); + + (void)p.expect(colon_type, "`:'"); + + tools::parser::token t = p.expect(text_type, "info property name"); + (void)p.expect(comma_type, "`,'"); + got_info(t.text(), tools::text::trim(p.rest_of_line())); + + (void)p.expect(nl_type, "new line"); +} + +void +impl::atf_tps_reader::read_tp(void* pptr) +{ + using tools::parser::parse_error; + using namespace atf_tps; + + tools::parser::parser< tokenizer >& p = + *reinterpret_cast< tools::parser::parser< tokenizer >* > + (pptr); + + tools::parser::token t = p.expect(tp_start_type, + "start of test program"); + + t = p.expect(colon_type, "`:'"); + + struct timeval s1 = read_timeval(p); + + t = p.expect(comma_type, "`,'"); + + t = p.expect(text_type, "test program name"); + std::string tpname = t.text(); + + t = p.expect(comma_type, "`,'"); + + t = p.expect(text_type, "number of test programs"); + size_t ntcs = string_to_int< std::size_t >(t.text()); + + t = p.expect(nl_type, "new line"); + + ATF_PARSER_CALLBACK(p, got_tp_start(tpname, ntcs)); + + size_t i = 0; + while (p.good() && i < ntcs) { + try { + read_tc(&p); + i++; + } catch (const parse_error& pe) { + p.add_error(pe); + p.reset(nl_type); + } + } + t = p.expect(tp_end_type, "end of test program"); + + t = p.expect(colon_type, "`:'"); + + struct timeval s2 = read_timeval(p); + + struct timeval s3; + timersub(&s2, &s1, &s3); + + t = p.expect(comma_type, "`,'"); + + t = p.expect(text_type, "test program name"); + if (t.text() != tpname) + throw parse_error(t.lineno(), "Test program name used in " + "terminator does not match " + "opening"); + + t = p.expect(nl_type, comma_type, + "new line or comma_type"); + std::string reason; + if (t.type() == comma_type) { + reason = tools::text::trim(p.rest_of_line()); + if (reason.empty()) + throw parse_error(t.lineno(), + "Empty reason for failed test program"); + t = p.next(); + } + + ATF_PARSER_CALLBACK(p, got_tp_end(&s3, reason)); +} + +void +impl::atf_tps_reader::read_tc(void* pptr) +{ + using tools::parser::parse_error; + using namespace atf_tps; + + tools::parser::parser< tokenizer >& p = + *reinterpret_cast< tools::parser::parser< tokenizer >* > + (pptr); + + tools::parser::token t = p.expect(tc_start_type, "start of test case"); + + t = p.expect(colon_type, "`:'"); + + struct timeval s1 = read_timeval(p); + + t = p.expect(comma_type, "`,'"); + + t = p.expect(text_type, "test case name"); + std::string tcname = t.text(); + + ATF_PARSER_CALLBACK(p, got_tc_start(tcname)); + + t = p.expect(nl_type, "new line"); + + t = p.expect(tc_end_type, tc_so_type, tc_se_type, + "end of test case or test case's stdout/stderr line"); + while (t.type() != tc_end_type && + (t.type() == tc_so_type || t.type() == tc_se_type)) { + tools::parser::token t2 = t; + + t = p.expect(colon_type, "`:'"); + + std::string line = p.rest_of_line(); + + if (t2.type() == tc_so_type) { + ATF_PARSER_CALLBACK(p, got_tc_stdout_line(line)); + } else { + assert(t2.type() == tc_se_type); + ATF_PARSER_CALLBACK(p, got_tc_stderr_line(line)); + } + + t = p.expect(nl_type, "new line"); + + t = p.expect(tc_end_type, tc_so_type, tc_se_type, + "end of test case or test case's stdout/stderr line"); + } + + t = p.expect(colon_type, "`:'"); + + struct timeval s2 = read_timeval(p); + + struct timeval s3; + timersub(&s2, &s1, &s3); + + t = p.expect(comma_type, "`,'"); + + t = p.expect(text_type, "test case name"); + if (t.text() != tcname) + throw parse_error(t.lineno(), + "Test case name used in terminator does not " + "match opening"); + + t = p.expect(comma_type, "`,'"); + + t = p.expect(expected_death_type, expected_exit_type, expected_failure_type, + expected_signal_type, expected_timeout_type, passed_type, failed_type, + skipped_type, "expected_{death,exit,failure,signal,timeout}, failed, " + "passed or skipped"); + if (t.type() == passed_type) { + ATF_PARSER_CALLBACK(p, got_tc_end("passed", &s3, "")); + } else { + std::string state; + if (t.type() == expected_death_type) state = "expected_death"; + else if (t.type() == expected_exit_type) state = "expected_exit"; + else if (t.type() == expected_failure_type) state = "expected_failure"; + else if (t.type() == expected_signal_type) state = "expected_signal"; + else if (t.type() == expected_timeout_type) state = "expected_timeout"; + else if (t.type() == failed_type) state = "failed"; + else if (t.type() == skipped_type) state = "skipped"; + else std::abort(); + + t = p.expect(comma_type, "`,'"); + std::string reason = tools::text::trim(p.rest_of_line()); + if (reason.empty()) + throw parse_error(t.lineno(), "Empty reason for " + state + + " test case result"); + ATF_PARSER_CALLBACK(p, got_tc_end(state, &s3, reason)); + } + + t = p.expect(nl_type, "new line"); +} + +void +impl::atf_tps_reader::read(void) +{ + using tools::parser::parse_error; + using namespace atf_tps; + + std::pair< size_t, tools::parser::headers_map > hml = + tools::parser::read_headers(m_is, 1); + tools::parser::validate_content_type(hml.second, + "application/X-atf-tps", 3); + + tokenizer tkz(m_is, hml.first); + tools::parser::parser< tokenizer > p(tkz); + + try { + tools::parser::token t; + + while ((t = p.expect(tps_count_type, info_type, "tps-count or info " + "field")).type() == info_type) + read_info(&p); + + t = p.expect(colon_type, "`:'"); + + t = p.expect(text_type, "number of test programs"); + size_t ntps = string_to_int< std::size_t >(t.text()); + ATF_PARSER_CALLBACK(p, got_ntps(ntps)); + + t = p.expect(nl_type, "new line"); + + size_t i = 0; + while (p.good() && i < ntps) { + try { + read_tp(&p); + i++; + } catch (const parse_error& pe) { + p.add_error(pe); + p.reset(nl_type); + } + } + + while ((t = p.expect(eof_type, info_type, "end of stream or info " + "field")).type() == info_type) + read_info(&p); + ATF_PARSER_CALLBACK(p, got_eof()); + } catch (const parse_error& pe) { + p.add_error(pe); + p.reset(nl_type); + } +} diff --git a/unit/atf-src/tools/reader.hpp b/unit/atf-src/tools/reader.hpp new file mode 100644 index 0000000..34d5d25 --- /dev/null +++ b/unit/atf-src/tools/reader.hpp @@ -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. +// + +#if !defined(TOOLS_FORMATS_HPP) +#define TOOLS_FORMATS_HPP + +extern "C" { +#include <sys/time.h> +} + +#include <istream> +#include <string> + +namespace tools { +namespace atf_report { + +struct test_case_result { + enum state_enum { + PASSED, + FAILED, + SKIPPED, + }; + const state_enum state; + const std::string& reason; + + test_case_result(const state_enum p_state, const std::string& p_reason) : + state(p_state), + reason(p_reason) + { + } +}; + +class atf_tps_reader { + std::istream& m_is; + + void read_info(void*); + void read_tp(void*); + void read_tc(void*); + +protected: + virtual void got_info(const std::string&, const std::string&); + virtual void got_ntps(size_t); + virtual void got_tp_start(const std::string&, size_t); + virtual void got_tp_end(struct timeval*, const std::string&); + + virtual void got_tc_start(const std::string&); + virtual void got_tc_stdout_line(const std::string&); + virtual void got_tc_stderr_line(const std::string&); + virtual void got_tc_end(const std::string&, struct timeval*, + const std::string&); + virtual void got_eof(void); + +public: + atf_tps_reader(std::istream&); + virtual ~atf_tps_reader(void); + + void read(void); +}; + +} // namespace atf_report +} // namespace tools + +#endif // !defined(TOOLS_FORMATS_HPP) diff --git a/unit/atf-src/tools/reader_test.cpp b/unit/atf-src/tools/reader_test.cpp new file mode 100644 index 0000000..745b0bb --- /dev/null +++ b/unit/atf-src/tools/reader_test.cpp @@ -0,0 +1,986 @@ +// +// 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 <iostream> +#include <sstream> +#include <string> +#include <utility> +#include <vector> + +#include <atf-c++.hpp> + +#include "parser.hpp" +#include "reader.hpp" +#include "test_helpers.hpp" +#include "text.hpp" + +namespace impl = tools::atf_report; + +class tps_reader : protected impl::atf_tps_reader { + void + got_info(const std::string& what, const std::string& val) + { + m_calls.push_back("got_info(" + what + ", " + val + ")"); + } + + void + got_ntps(size_t ntps) + { + m_calls.push_back("got_ntps(" + tools::text::to_string(ntps) + ")"); + } + + void + got_tp_start(const std::string& tpname, size_t ntcs) + { + m_calls.push_back("got_tp_start(" + tpname + ", " + + tools::text::to_string(ntcs) + ")"); + } + + void + got_tp_end(struct timeval* tv ATF_DEFS_ATTRIBUTE_UNUSED, + const std::string& reason) + { + m_calls.push_back("got_tp_end(" + reason + ")"); + } + + void + got_tc_start(const std::string& tcname) + { + m_calls.push_back("got_tc_start(" + tcname + ")"); + } + + void + got_tc_end(const std::string& state, + struct timeval* tv ATF_DEFS_ATTRIBUTE_UNUSED, + const std::string& reason) + { + const std::string r = state + (reason.empty() ? "" : ", " + reason); + m_calls.push_back("got_tc_end(" + r + ")"); + } + + void + got_tc_stdout_line(const std::string& line) + { + m_calls.push_back("got_tc_stdout_line(" + line + ")"); + } + + void + got_tc_stderr_line(const std::string& line) + { + m_calls.push_back("got_tc_stderr_line(" + line + ")"); + } + + void + got_eof(void) + { + m_calls.push_back("got_eof()"); + } + +public: + tps_reader(std::istream& is) : + impl::atf_tps_reader(is) + { + } + + void + read(void) + { + atf_tps_reader::read(); + } + + std::vector< std::string > m_calls; +}; + +ATF_TEST_CASE_WITHOUT_HEAD(tps_1); +ATF_TEST_CASE_BODY(tps_1) +{ + const char* input = + "Content-Type: application/X-atf-tps; version=\"3\"\n" + "\n" + "tps-count: 0\n" + ; + + const char* exp_calls[] = { + "got_ntps(0)", + "got_eof()", + NULL + }; + + const char* exp_errors[] = { + NULL + }; + + do_parser_test< tps_reader >(input, exp_calls, exp_errors); +} + +ATF_TEST_CASE_WITHOUT_HEAD(tps_2); +ATF_TEST_CASE_BODY(tps_2) +{ + const char* input = + "Content-Type: application/X-atf-tps; version=\"3\"\n" + "\n" + "tps-count: 2\n" + "tp-start: 123.456, first-prog, 0\n" + "tp-end: 123.567, first-prog\n" + "tp-start: 123.678, second-prog, 0\n" + "tp-end: 123.789, second-prog, This program failed\n" + ; + + const char* exp_calls[] = { + "got_ntps(2)", + "got_tp_start(first-prog, 0)", + "got_tp_end()", + "got_tp_start(second-prog, 0)", + "got_tp_end(This program failed)", + "got_eof()", + NULL + }; + + const char* exp_errors[] = { + NULL + }; + + do_parser_test< tps_reader >(input, exp_calls, exp_errors); +} + +ATF_TEST_CASE_WITHOUT_HEAD(tps_3); +ATF_TEST_CASE_BODY(tps_3) +{ + const char* input = + "Content-Type: application/X-atf-tps; version=\"3\"\n" + "\n" + "tps-count: 2\n" + "tp-start: 123.123, first-prog, 3\n" + "tc-start: 123.234, first-test\n" + "tc-end: 123.345, first-test, passed\n" + "tc-start: 123.456, second-test\n" + "tc-end: 123.567, second-test, skipped, Testing skipped reason\n" + "tc-start: 123.678, third.test\n" + "tc-end: 123.789, third.test, failed, Testing failed reason\n" + "tp-end: 123.890, first-prog\n" + "tp-start: 124.901, second-prog, 3\n" + "tc-start: 124.1012, first-test\n" + "tc-so:first stdout line for 1st test\n" + "tc-se:first stderr line for 1st test\n" + "tc-so:second stdout line for 1st test\n" + "tc-se:second stderr line for 1st test\n" + "tc-end: 124.1123, first-test, passed\n" + "tc-start: 124.1234, second-test\n" + "tc-so:first stdout line for 2nd test\n" + "tc-se:first stderr line for 2nd test\n" + "tc-so:second stdout line for 2nd test\n" + "tc-se:second stderr line for 2nd test\n" + "tc-end: 124.1345, second-test, skipped, Testing skipped reason\n" + "tc-start: 124.1456, third.test\n" + "tc-so:first stdout line for 3rd test\n" + "tc-se:first stderr line for 3rd test\n" + "tc-so:second stdout line for 3rd test\n" + "tc-se:second stderr line for 3rd test\n" + "tc-end: 124.1567, third.test, failed, Testing failed reason\n" + "tp-end: 124.1678, second-prog, This program failed\n" + ; + + const char* exp_calls[] = { + "got_ntps(2)", + "got_tp_start(first-prog, 3)", + "got_tc_start(first-test)", + "got_tc_end(passed)", + "got_tc_start(second-test)", + "got_tc_end(skipped, Testing skipped reason)", + "got_tc_start(third.test)", + "got_tc_end(failed, Testing failed reason)", + "got_tp_end()", + "got_tp_start(second-prog, 3)", + "got_tc_start(first-test)", + "got_tc_stdout_line(first stdout line for 1st test)", + "got_tc_stderr_line(first stderr line for 1st test)", + "got_tc_stdout_line(second stdout line for 1st test)", + "got_tc_stderr_line(second stderr line for 1st test)", + "got_tc_end(passed)", + "got_tc_start(second-test)", + "got_tc_stdout_line(first stdout line for 2nd test)", + "got_tc_stderr_line(first stderr line for 2nd test)", + "got_tc_stdout_line(second stdout line for 2nd test)", + "got_tc_stderr_line(second stderr line for 2nd test)", + "got_tc_end(skipped, Testing skipped reason)", + "got_tc_start(third.test)", + "got_tc_stdout_line(first stdout line for 3rd test)", + "got_tc_stderr_line(first stderr line for 3rd test)", + "got_tc_stdout_line(second stdout line for 3rd test)", + "got_tc_stderr_line(second stderr line for 3rd test)", + "got_tc_end(failed, Testing failed reason)", + "got_tp_end(This program failed)", + "got_eof()", + NULL + }; + + const char* exp_errors[] = { + NULL + }; + + do_parser_test< tps_reader >(input, exp_calls, exp_errors); +} + +ATF_TEST_CASE_WITHOUT_HEAD(tps_4); +ATF_TEST_CASE_BODY(tps_4) +{ + const char* input = + "Content-Type: application/X-atf-tps; version=\"3\"\n" + "\n" + "info: a, foo\n" + "info: b, bar\n" + "info: c, baz\n" + "tps-count: 2\n" + "tp-start: 234.1, first-prog, 3\n" + "tc-start: 234.12, first-test\n" + "tc-end: 234.23, first-test, passed\n" + "tc-start: 234.34, second-test\n" + "tc-end: 234.45, second-test, skipped, Testing skipped reason\n" + "tc-start: 234.56, third-test\n" + "tc-end: 234.67, third-test, failed, Testing failed reason\n" + "tp-end: 234.78, first-prog\n" + "tp-start: 234.89, second-prog, 3\n" + "tc-start: 234.90, first-test\n" + "tc-so:first stdout line for 1st test\n" + "tc-se:first stderr line for 1st test\n" + "tc-so:second stdout line for 1st test\n" + "tc-se:second stderr line for 1st test\n" + "tc-end: 234.101, first-test, passed\n" + "tc-start: 234.112, second-test\n" + "tc-so:first stdout line for 2nd test\n" + "tc-se:first stderr line for 2nd test\n" + "tc-so:second stdout line for 2nd test\n" + "tc-se:second stderr line for 2nd test\n" + "tc-end: 234.123, second-test, skipped, Testing skipped reason\n" + "tc-start: 234.134, third-test\n" + "tc-so:first stdout line for 3rd test\n" + "tc-se:first stderr line for 3rd test\n" + "tc-so:second stdout line for 3rd test\n" + "tc-se:second stderr line for 3rd test\n" + "tc-end: 234.145, third-test, failed, Testing failed reason\n" + "tp-end: 234.156, second-prog, This program failed\n" + "info: d, foo\n" + "info: e, bar\n" + "info: f, baz\n" + ; + + const char* exp_calls[] = { + "got_info(a, foo)", + "got_info(b, bar)", + "got_info(c, baz)", + "got_ntps(2)", + "got_tp_start(first-prog, 3)", + "got_tc_start(first-test)", + "got_tc_end(passed)", + "got_tc_start(second-test)", + "got_tc_end(skipped, Testing skipped reason)", + "got_tc_start(third-test)", + "got_tc_end(failed, Testing failed reason)", + "got_tp_end()", + "got_tp_start(second-prog, 3)", + "got_tc_start(first-test)", + "got_tc_stdout_line(first stdout line for 1st test)", + "got_tc_stderr_line(first stderr line for 1st test)", + "got_tc_stdout_line(second stdout line for 1st test)", + "got_tc_stderr_line(second stderr line for 1st test)", + "got_tc_end(passed)", + "got_tc_start(second-test)", + "got_tc_stdout_line(first stdout line for 2nd test)", + "got_tc_stderr_line(first stderr line for 2nd test)", + "got_tc_stdout_line(second stdout line for 2nd test)", + "got_tc_stderr_line(second stderr line for 2nd test)", + "got_tc_end(skipped, Testing skipped reason)", + "got_tc_start(third-test)", + "got_tc_stdout_line(first stdout line for 3rd test)", + "got_tc_stderr_line(first stderr line for 3rd test)", + "got_tc_stdout_line(second stdout line for 3rd test)", + "got_tc_stderr_line(second stderr line for 3rd test)", + "got_tc_end(failed, Testing failed reason)", + "got_tp_end(This program failed)", + "got_info(d, foo)", + "got_info(e, bar)", + "got_info(f, baz)", + "got_eof()", + NULL + }; + + const char* exp_errors[] = { + NULL + }; + + do_parser_test< tps_reader >(input, exp_calls, exp_errors); +} + +ATF_TEST_CASE_WITHOUT_HEAD(tps_5); +ATF_TEST_CASE_BODY(tps_5) +{ + const char* input = + "Content-Type: application/X-atf-tps; version=\"3\"\n" + "\n" + "tps-count: 1\n" + "tp-start: 345.123, the-prog, 1\n" + "tc-start: 345.134, the-test\n" + "tc-so:--- a 2007-11-04 14:00:41.000000000 +0100\n" + "tc-so:+++ b 2007-11-04 14:00:48.000000000 +0100\n" + "tc-so:@@ -1,7 +1,7 @@\n" + "tc-so: This test is meant to simulate a diff.\n" + "tc-so: Blank space at beginning of context lines must be preserved.\n" + "tc-so: \n" + "tc-so:-First original line.\n" + "tc-so:-Second original line.\n" + "tc-so:+First modified line.\n" + "tc-so:+Second modified line.\n" + "tc-so: \n" + "tc-so: EOF\n" + "tc-end: 345.145, the-test, passed\n" + "tp-end: 345.156, the-prog\n" + ; + + // NO_CHECK_STYLE_BEGIN + const char* exp_calls[] = { + "got_ntps(1)", + "got_tp_start(the-prog, 1)", + "got_tc_start(the-test)", + "got_tc_stdout_line(--- a 2007-11-04 14:00:41.000000000 +0100)", + "got_tc_stdout_line(+++ b 2007-11-04 14:00:48.000000000 +0100)", + "got_tc_stdout_line(@@ -1,7 +1,7 @@)", + "got_tc_stdout_line( This test is meant to simulate a diff.)", + "got_tc_stdout_line( Blank space at beginning of context lines must be preserved.)", + "got_tc_stdout_line( )", + "got_tc_stdout_line(-First original line.)", + "got_tc_stdout_line(-Second original line.)", + "got_tc_stdout_line(+First modified line.)", + "got_tc_stdout_line(+Second modified line.)", + "got_tc_stdout_line( )", + "got_tc_stdout_line( EOF)", + "got_tc_end(passed)", + "got_tp_end()", + "got_eof()", + NULL + }; + // NO_CHECK_STYLE_END + + const char* exp_errors[] = { + NULL + }; + + do_parser_test< tps_reader >(input, exp_calls, exp_errors); +} + +ATF_TEST_CASE_WITHOUT_HEAD(tps_6); +ATF_TEST_CASE_BODY(tps_6) +{ + const char* input = + "Content-Type: application/X-atf-tps; version=\"3\"\n" + "\n" + "tps-count: 1\n" + "tp-start: 321.1, the-prog, 8\n" + "tc-start: 321.12, one\n" + "tc-end: 321.23, one, expected_death, The reason\n" + "tc-start: 321.34, two\n" + "tc-end: 321.45, two, expected_exit, This would be an exit\n" + "tc-start: 321.56, three\n" + "tc-end: 321.67, three, expected_failure, And this a failure\n" + "tc-start: 321.78, four\n" + "tc-end: 321.89, four, expected_signal, And this a signal\n" + "tc-start: 321.90, five\n" + "tc-end: 321.101, five, failed, Another reason\n" + "tc-start: 321.112, six\n" + "tc-end: 321.123, six, passed\n" + "tc-start: 321.134, seven\n" + "tc-end: 321.145, seven, skipped, Skipping it\n" + "tc-start: 321.156, eight\n" + "tc-end: 321.167, eight, expected_timeout, Some hang reason\n" + "tp-end: 321.178, the-prog\n" + ; + + // NO_CHECK_STYLE_BEGIN + const char* exp_calls[] = { + "got_ntps(1)", + "got_tp_start(the-prog, 8)", + "got_tc_start(one)", + "got_tc_end(expected_death, The reason)", + "got_tc_start(two)", + "got_tc_end(expected_exit, This would be an exit)", + "got_tc_start(three)", + "got_tc_end(expected_failure, And this a failure)", + "got_tc_start(four)", + "got_tc_end(expected_signal, And this a signal)", + "got_tc_start(five)", + "got_tc_end(failed, Another reason)", + "got_tc_start(six)", + "got_tc_end(passed)", + "got_tc_start(seven)", + "got_tc_end(skipped, Skipping it)", + "got_tc_start(eight)", + "got_tc_end(expected_timeout, Some hang reason)", + "got_tp_end()", + "got_eof()", + NULL + }; + // NO_CHECK_STYLE_END + + const char* exp_errors[] = { + NULL + }; + + do_parser_test< tps_reader >(input, exp_calls, exp_errors); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(tps_50); +ATF_TEST_CASE_BODY(tps_50) +{ + const char* input = + "Content-Type: application/X-atf-tps; version=\"3\"\n" + "\n" + "foo\n" + ; + + const char* exp_calls[] = { + NULL + }; + + const char* exp_errors[] = { + "3: Unexpected token `foo'; expected tps-count or info field", + NULL + }; + + do_parser_test< tps_reader >(input, exp_calls, exp_errors); +} + +ATF_TEST_CASE_WITHOUT_HEAD(tps_51); +ATF_TEST_CASE_BODY(tps_51) +{ + const char* input = + "Content-Type: application/X-atf-tps; version=\"3\"\n" + "\n" + "tps-count\n" + ; + + const char* exp_calls[] = { + NULL + }; + + const char* exp_errors[] = { + "3: Unexpected token `<<NEWLINE>>'; expected `:'", + NULL + }; + + do_parser_test< tps_reader >(input, exp_calls, exp_errors); +} + +ATF_TEST_CASE_WITHOUT_HEAD(tps_52); +ATF_TEST_CASE_BODY(tps_52) +{ + const char* input = + "Content-Type: application/X-atf-tps; version=\"3\"\n" + "\n" + "tps-count:\n" + ; + + const char* exp_calls[] = { + NULL + }; + + const char* exp_errors[] = { + "3: Unexpected token `<<NEWLINE>>'; expected number of test programs", + NULL + }; + + do_parser_test< tps_reader >(input, exp_calls, exp_errors); +} + +ATF_TEST_CASE_WITHOUT_HEAD(tps_53); +ATF_TEST_CASE_BODY(tps_53) +{ + const char* input = + "Content-Type: application/X-atf-tps; version=\"3\"\n" + "\n" + "tps-count: 1\n" + "foo\n" + ; + + const char* exp_calls[] = { + "got_ntps(1)", + NULL + }; + + const char* exp_errors[] = { + "4: Unexpected token `foo'; expected start of test program", + NULL + }; + + do_parser_test< tps_reader >(input, exp_calls, exp_errors); +} + +ATF_TEST_CASE_WITHOUT_HEAD(tps_54); +ATF_TEST_CASE_BODY(tps_54) +{ + const char* input = + "Content-Type: application/X-atf-tps; version=\"3\"\n" + "\n" + "tps-count: 1\n" + "foo\n" + "tp-start\n" + "tp-start:\n" + "tp-start: 123\n" + "tp-start: 123.\n" + "tp-start: 123.456\n" + "tp-start: 123.456,\n" + "tp-start: 123.456, foo\n" + "tp-start: 123.456, foo,\n" + "tp-start: 123.456, foo, 0\n" + "bar\n" + "tp-start: 456.789, foo, 0\n" + "tp-end\n" + "tp-start: 777.777, foo, 0\n" + "tp-end:\n" + "tp-start: 777.777, foo, 0\n" + "tp-end: 777\n" + "tp-start: 777.777, foo, 0\n" + "tp-end: 777.\n" + "tp-start: 777.777, foo, 0\n" + "tp-end: 777.888\n" + "tp-start: 777.777, foo, 0\n" + "tp-end: 777.888, \n" + "tp-start: 777.777, foo, 0\n" + "tp-end: 777.888, bar\n" + "tp-start: 777.777, foo, 0\n" + "tp-end: 777.888, foo,\n" + ; + + const char* exp_calls[] = { + "got_ntps(1)", + NULL + }; + + const char* exp_errors[] = { + "4: Unexpected token `foo'; expected start of test program", + "5: Unexpected token `<<NEWLINE>>'; expected `:'", + "6: Unexpected token `<<NEWLINE>>'; expected timestamp", + "7: Malformed timestamp value 123", + "8: Malformed timestamp value 123.", + "9: Unexpected token `<<NEWLINE>>'; expected `,'", + "10: Unexpected token `<<NEWLINE>>'; expected test program name", + "11: Unexpected token `<<NEWLINE>>'; expected `,'", + "12: Unexpected token `<<NEWLINE>>'; expected number of test programs", + "14: Unexpected token `bar'; expected end of test program", + "16: Unexpected token `<<NEWLINE>>'; expected `:'", + "18: Unexpected token `<<NEWLINE>>'; expected timestamp", + "20: Malformed timestamp value 777", + "22: Malformed timestamp value 777.", + "24: Unexpected token `<<NEWLINE>>'; expected `,'", + + "26: Unexpected token `<<NEWLINE>>'; expected test program name", + "28: Test program name used in terminator does not match opening", + "30: Empty reason for failed test program", + NULL + }; + + do_parser_test< tps_reader >(input, exp_calls, exp_errors); +} + +ATF_TEST_CASE_WITHOUT_HEAD(tps_55); +ATF_TEST_CASE_BODY(tps_55) +{ + const char* input = + "Content-Type: application/X-atf-tps; version=\"3\"\n" + "\n" + "tps-count: 1\n" + "tp-start: 100.200, foo, 1\n" + "foo\n" + "tc-start\n" + "tc-start:\n" + "tc-start: 111\n" + "tc-start: 111.\n" + "tc-start: 111.222\n" + "tc-start: 111.222,\n" + "tc-start: 111.222, foo\n" + "bar\n" + "tc-start: 111.333, foo\n" + "tc-end\n" + "tc-start: 111.444, foo\n" + "tc-end:\n" + "tc-start: 111.444, foo\n" + "tc-end: 111\n" + "tc-start: 111.444, foo\n" + "tc-end: 111.\n" + "tc-start: 111.444, foo\n" + "tc-end: 111.555\n" + "tc-start: 111.444, foo\n" + "tc-end: 111.555, \n" + "tc-start: 111.444, foo\n" + "tc-end: 111.555, bar\n" + "tc-start: 111.444, foo\n" + "tc-end: 111.555, foo\n" + "tc-start: 111.444, foo\n" + "tc-end: 111.555, foo,\n" + "tp-end: 111.666, foo\n" + ; + + const char* exp_calls[] = { + "got_ntps(1)", + "got_tp_start(foo, 1)", + NULL + }; + + // NO_CHECK_STYLE_BEGIN + const char* exp_errors[] = { + "5: Unexpected token `foo'; expected start of test case", + "6: Unexpected token `<<NEWLINE>>'; expected `:'", + "7: Unexpected token `<<NEWLINE>>'; expected timestamp", + "8: Malformed timestamp value 111", + "9: Malformed timestamp value 111.", + "10: Unexpected token `<<NEWLINE>>'; expected `,'", + "11: Unexpected token `<<NEWLINE>>'; expected test case name", + "13: Unexpected token `bar'; expected end of test case or test case's stdout/stderr line", + "15: Unexpected token `<<NEWLINE>>'; expected `:'", + "17: Unexpected token `<<NEWLINE>>'; expected timestamp", + "19: Malformed timestamp value 111", + "21: Malformed timestamp value 111.", + "23: Unexpected token `<<NEWLINE>>'; expected `,'", + "25: Unexpected token `<<NEWLINE>>'; expected test case name", + "27: Test case name used in terminator does not match opening", + "29: Unexpected token `<<NEWLINE>>'; expected `,'", + "31: Unexpected token `<<NEWLINE>>'; expected expected_{death,exit,failure,signal,timeout}, failed, passed or skipped", + "32: Unexpected token `tp-end'; expected start of test case", + NULL + }; + // NO_CHECK_STYLE_END + + do_parser_test< tps_reader >(input, exp_calls, exp_errors); +} + +ATF_TEST_CASE_WITHOUT_HEAD(tps_56); +ATF_TEST_CASE_BODY(tps_56) +{ + const char* input = + "Content-Type: application/X-atf-tps; version=\"3\"\n" + "\n" + "tps-count: 1\n" + "tp-start: 111.222, foo, 1\n" + "tc-start: 111.333, foo\n" + "tc-end: 111.444, foo, passe\n" + "tc-start: 111.333, foo\n" + "tc-end: 111.444, foo, passed,\n" + "tc-start: 111.555, bar\n" + "tc-end: 111.666, bar, failed\n" + "tc-start: 111.555, bar\n" + "tc-end: 111.666, bar, failed,\n" + "tc-start: 111.555, baz\n" + "tc-end: 111.666, baz, skipped\n" + "tc-start: 111.555, baz\n" + "tc-end: 111.666, baz, skipped,\n" + "tp-end: 111.777, foo\n" + ; + + const char* exp_calls[] = { + "got_ntps(1)", + "got_tp_start(foo, 1)", + "got_tc_start(foo)", + NULL + }; + + // NO_CHECK_STYLE_BEGIN + const char* exp_errors[] = { + "6: Unexpected token `passe'; expected expected_{death,exit,failure,signal,timeout}, failed, passed or skipped", + "8: Unexpected token `,'; expected new line", + "10: Unexpected token `<<NEWLINE>>'; expected `,'", + "12: Empty reason for failed test case result", + "14: Unexpected token `<<NEWLINE>>'; expected `,'", + "16: Empty reason for skipped test case result", + "17: Unexpected token `tp-end'; expected start of test case", + NULL + }; + // NO_CHECK_STYLE_END + + do_parser_test< tps_reader >(input, exp_calls, exp_errors); +} + +ATF_TEST_CASE_WITHOUT_HEAD(tps_57); +ATF_TEST_CASE_BODY(tps_57) +{ + const char* input = + "Content-Type: application/X-atf-tps; version=\"3\"\n" + "\n" + "tps-count: 2\n" + "tp-start: 111.222, foo, 0\n" + "tp-end: 111.333, foo\n" + ; + + const char* exp_calls[] = { + "got_ntps(2)", + "got_tp_start(foo, 0)", + "got_tp_end()", + NULL + }; + + const char* exp_errors[] = { + "6: Unexpected token `<<EOF>>'; expected start of test program", + NULL + }; + + do_parser_test< tps_reader >(input, exp_calls, exp_errors); +} + +ATF_TEST_CASE_WITHOUT_HEAD(tps_58); +ATF_TEST_CASE_BODY(tps_58) +{ + const char* input = + "Content-Type: application/X-atf-tps; version=\"3\"\n" + "\n" + "tps-count: 1\n" + "tp-start: 111.222, foo, 0\n" + "tp-end: 111.333, foo\n" + "tp-start: 111.444, bar, 0\n" + "tp-end: 111.555, bar\n" + ; + + const char* exp_calls[] = { + "got_ntps(1)", + "got_tp_start(foo, 0)", + "got_tp_end()", + NULL + }; + + const char* exp_errors[] = { + "6: Unexpected token `tp-start'; expected end of stream or info field", + NULL + }; + + do_parser_test< tps_reader >(input, exp_calls, exp_errors); +} + +ATF_TEST_CASE_WITHOUT_HEAD(tps_59); +ATF_TEST_CASE_BODY(tps_59) +{ + const char* input = + "Content-Type: application/X-atf-tps; version=\"3\"\n" + "\n" + "info\n" + ; + + const char* exp_calls[] = { + NULL + }; + + const char* exp_errors[] = { + "3: Unexpected token `<<NEWLINE>>'; expected `:'", + NULL + }; + + do_parser_test< tps_reader >(input, exp_calls, exp_errors); +} + +ATF_TEST_CASE_WITHOUT_HEAD(tps_60); +ATF_TEST_CASE_BODY(tps_60) +{ + const char* input = + "Content-Type: application/X-atf-tps; version=\"3\"\n" + "\n" + "info:\n" + ; + + const char* exp_calls[] = { + NULL + }; + + const char* exp_errors[] = { + "3: Unexpected token `<<NEWLINE>>'; expected info property name", + NULL + }; + + do_parser_test< tps_reader >(input, exp_calls, exp_errors); +} + +ATF_TEST_CASE_WITHOUT_HEAD(tps_61); +ATF_TEST_CASE_BODY(tps_61) +{ + const char* input = + "Content-Type: application/X-atf-tps; version=\"3\"\n" + "\n" + "info: a\n" + ; + + const char* exp_calls[] = { + NULL + }; + + const char* exp_errors[] = { + "3: Unexpected token `<<NEWLINE>>'; expected `,'", + NULL + }; + + do_parser_test< tps_reader >(input, exp_calls, exp_errors); +} + +ATF_TEST_CASE_WITHOUT_HEAD(tps_62); +ATF_TEST_CASE_BODY(tps_62) +{ + const char* input = + "Content-Type: application/X-atf-tps; version=\"3\"\n" + "\n" + "info: a,\n" + ; + + const char* exp_calls[] = { + "got_info(a, )", + NULL + }; + + const char* exp_errors[] = { + "4: Unexpected token `<<EOF>>'; expected tps-count or info field", + NULL + }; + + do_parser_test< tps_reader >(input, exp_calls, exp_errors); +} + +ATF_TEST_CASE_WITHOUT_HEAD(tps_63); +ATF_TEST_CASE_BODY(tps_63) +{ + const char* input = + "Content-Type: application/X-atf-tps; version=\"3\"\n" + "\n" + "info: a, b\n" + ; + + const char* exp_calls[] = { + "got_info(a, b)", + NULL + }; + + const char* exp_errors[] = { + "4: Unexpected token `<<EOF>>'; expected tps-count or info field", + NULL + }; + + do_parser_test< tps_reader >(input, exp_calls, exp_errors); +} + +ATF_TEST_CASE_WITHOUT_HEAD(tps_64); +ATF_TEST_CASE_BODY(tps_64) +{ + const char* input = + "Content-Type: application/X-atf-tps; version=\"3\"\n" + "\n" + "info: a, b\n" + "info: a.b.c.def, g\n" + "tps-count\n" + ; + + const char* exp_calls[] = { + "got_info(a, b)", + "got_info(a.b.c.def, g)", + NULL + }; + + const char* exp_errors[] = { + "5: Unexpected token `<<NEWLINE>>'; expected `:'", + NULL + }; + + do_parser_test< tps_reader >(input, exp_calls, exp_errors); +} + +ATF_TEST_CASE_WITHOUT_HEAD(tps_65); +ATF_TEST_CASE_BODY(tps_65) +{ + const char* input = + "Content-Type: application/X-atf-tps; version=\"3\"\n" + "\n" + "info: a, b\n" + "tps-count:\n" + ; + + const char* exp_calls[] = { + "got_info(a, b)", + NULL + }; + + const char* exp_errors[] = { + "4: Unexpected token `<<NEWLINE>>'; expected number of test programs", + NULL + }; + + do_parser_test< tps_reader >(input, exp_calls, exp_errors); +} + +ATF_TEST_CASE_WITHOUT_HEAD(tps_66); +ATF_TEST_CASE_BODY(tps_66) +{ + const char* input = + "Content-Type: application/X-atf-tps; version=\"3\"\n" + "\n" + "info: a, b\n" + "tps-count: 0\n" + "info\n" + ; + + const char* exp_calls[] = { + "got_info(a, b)", + "got_ntps(0)", + NULL + }; + + const char* exp_errors[] = { + "5: Unexpected token `<<NEWLINE>>'; expected `:'", + NULL + }; + + do_parser_test< tps_reader >(input, exp_calls, exp_errors); +} + +ATF_INIT_TEST_CASES(tcs) +{ + ATF_ADD_TEST_CASE(tcs, tps_1); + ATF_ADD_TEST_CASE(tcs, tps_2); + ATF_ADD_TEST_CASE(tcs, tps_3); + ATF_ADD_TEST_CASE(tcs, tps_4); + ATF_ADD_TEST_CASE(tcs, tps_5); + ATF_ADD_TEST_CASE(tcs, tps_6); + ATF_ADD_TEST_CASE(tcs, tps_50); + ATF_ADD_TEST_CASE(tcs, tps_51); + ATF_ADD_TEST_CASE(tcs, tps_52); + ATF_ADD_TEST_CASE(tcs, tps_53); + ATF_ADD_TEST_CASE(tcs, tps_54); + ATF_ADD_TEST_CASE(tcs, tps_55); + ATF_ADD_TEST_CASE(tcs, tps_56); + ATF_ADD_TEST_CASE(tcs, tps_57); + ATF_ADD_TEST_CASE(tcs, tps_58); + ATF_ADD_TEST_CASE(tcs, tps_59); + ATF_ADD_TEST_CASE(tcs, tps_60); + ATF_ADD_TEST_CASE(tcs, tps_61); + ATF_ADD_TEST_CASE(tcs, tps_62); + ATF_ADD_TEST_CASE(tcs, tps_63); + ATF_ADD_TEST_CASE(tcs, tps_64); + ATF_ADD_TEST_CASE(tcs, tps_65); + ATF_ADD_TEST_CASE(tcs, tps_66); +} diff --git a/unit/atf-src/tools/requirements.cpp b/unit/atf-src/tools/requirements.cpp new file mode 100644 index 0000000..97fcdc8 --- /dev/null +++ b/unit/atf-src/tools/requirements.cpp @@ -0,0 +1,325 @@ +// +// 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 <cassert> +#include <cerrno> +#include <cstdlib> +#include <cstring> +#include <stdexcept> + +#include "config.hpp" +#include "defs.hpp" +#include "env.hpp" +#include "fs.hpp" +#include "requirements.hpp" +#include "text.hpp" +#include "user.hpp" + +namespace impl = tools; + +namespace { + +typedef std::map< std::string, std::string > vars_map; + +static +bool +has_program(const tools::fs::path& program) +{ + bool found = false; + + if (program.is_absolute()) { + found = tools::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 = tools::text::split( + tools::env::get("PATH"), ":"); + for (std::vector< std::string >::const_iterator iter = dirs.begin(); + !found && iter != dirs.end(); iter++) { + const tools::fs::path& p = tools::fs::path(*iter) / program; + if (tools::fs::is_executable(p)) + found = true; + } + } + + return found; +} + +static +std::string +check_arch(const std::string& arches) +{ + const std::vector< std::string > v = tools::text::split(arches, " "); + + for (std::vector< std::string >::const_iterator iter = v.begin(); + iter != v.end(); iter++) { + if ((*iter) == tools::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 vars_map& config) +{ + const std::vector< std::string > v = tools::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 = tools::text::split(progs, " "); + for (std::vector< std::string >::const_iterator iter = v.begin(); + iter != v.end(); iter++) { + const tools::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 (!tools::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 = tools::text::split(machines, " "); + + for (std::vector< std::string >::const_iterator iter = v.begin(); + iter != v.end(); iter++) { + if ((*iter) == tools::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(__FreeBSD__) || 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 " + tools::text::to_string(needed) + + ", available " + tools::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(__FreeBSD__) +static +std::string +check_memory_freebsd(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 = tools::text::to_bytes(raw_memory); + +#if defined(__APPLE__) + return check_memory_darwin(needed); +#elif defined(__FreeBSD__) + return check_memory_freebsd(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 = tools::text::split(progs, " "); + for (std::vector< std::string >::const_iterator iter = v.begin(); + iter != v.end(); iter++) { + if (!has_program(tools::fs::path(*iter))) + return "Required program '" + (*iter) + "' not found in the PATH"; + } + return ""; +} + +static +std::string +check_user(const std::string& user, const vars_map& config) +{ + if (user == "root") { + if (!tools::user::is_root()) + return "Requires root privileges"; + else + return ""; + } else if (user == "unprivileged") { + if (tools::user::is_root()) { + const 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)tools::user::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 vars_map& metadata, + const vars_map& config) +{ + std::string failure_reason = ""; + + for (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; + assert(!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. + assert(failure_reason.find("require.") != 0); + } + } + + return failure_reason; +} + +std::pair< int, int > +impl::get_required_user(const vars_map& metadata, + const vars_map& config) +{ + const vars_map::const_iterator user = metadata.find( + "require.user"); + if (user == metadata.end()) + return std::make_pair(-1, -1); + + if ((*user).second == "unprivileged") { + if (tools::user::is_root()) { + const vars_map::const_iterator iter = config.find( + "unprivileged-user"); + try { + return tools::user::get_user_ids((*iter).second); + } catch (const std::exception& e) { + std::abort(); // This has been validated by check_user. + } + } else { + return std::make_pair(-1, -1); + } + } else + return std::make_pair(-1, -1); +} diff --git a/unit/atf-src/tools/requirements.hpp b/unit/atf-src/tools/requirements.hpp new file mode 100644 index 0000000..1eefdaf --- /dev/null +++ b/unit/atf-src/tools/requirements.hpp @@ -0,0 +1,47 @@ +// +// 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(TOOLS_REQUIREMENTS_HPP) +#define TOOLS_REQUIREMENTS_HPP + +#include <map> +#include <string> +#include <utility> + +namespace tools { + +std::string check_requirements(const std::map< std::string, std::string >&, + const std::map< std::string, std::string >&); +std::pair< int, int > get_required_user( + const std::map< std::string, std::string >&, + const std::map< std::string, std::string >&); + +} // namespace tools + +#endif // !defined(TOOLS_REQUIREMENTS_HPP) diff --git a/unit/atf-src/tools/requirements_test.cpp b/unit/atf-src/tools/requirements_test.cpp new file mode 100644 index 0000000..6d37d75 --- /dev/null +++ b/unit/atf-src/tools/requirements_test.cpp @@ -0,0 +1,400 @@ +// +// 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++.hpp> + +#include "config.hpp" +#include "requirements.hpp" +#include "text.hpp" +#include "user.hpp" + +namespace impl = tools; + +// ------------------------------------------------------------------------- +// Auxiliary functions. +// ------------------------------------------------------------------------- + +namespace { + +typedef std::map< std::string, std::string > vars_map; + +const vars_map no_config; + +void +do_check(const std::string& expected, const vars_map& metadata, + const vars_map& config = no_config) +{ + const std::string actual = impl::check_requirements(metadata, config); + if (!tools::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) { + vars_map metadata; + metadata["require.arch"] = tools::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) { + 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) { + vars_map metadata; + metadata["require.arch"] = "__foo__ " + tools::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) { + 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) { + 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) { + 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) { + 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) { + 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) { + 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) { + 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) { + 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) { + 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) { + 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) { + 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) { + vars_map metadata; + metadata["require.machine"] = tools::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) { + 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) { + vars_map metadata; + metadata["require.machine"] = "__foo__ " + + tools::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) { + 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) { + 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) { + vars_map metadata; + metadata["require.memory"] = "128t"; +#if defined(__APPLE__) || defined(__FreeBSD__) || 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) { + 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) { + 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) { + 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) { + 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) { + 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) { + 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) { + 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) { + vars_map metadata; + metadata["require.user"] = "root"; + if (tools::user::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) { + vars_map metadata; + metadata["require.user"] = "unprivileged"; + if (tools::user::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) { + 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/unit/atf-src/tools/sample/atf-run.hooks b/unit/atf-src/tools/sample/atf-run.hooks new file mode 100644 index 0000000..86d187d --- /dev/null +++ b/unit/atf-src/tools/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/unit/atf-src/tools/sample/common.conf b/unit/atf-src/tools/sample/common.conf new file mode 100644 index 0000000..464ee96 --- /dev/null +++ b/unit/atf-src/tools/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/unit/atf-src/tools/several_tcs_helper.c b/unit/atf-src/tools/several_tcs_helper.c new file mode 100644 index 0000000..d287c81 --- /dev/null +++ b/unit/atf-src/tools/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/unit/atf-src/tools/share/atf-run.hooks b/unit/atf-src/tools/share/atf-run.hooks new file mode 100644 index 0000000..c94f3bc --- /dev/null +++ b/unit/atf-src/tools/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/unit/atf-src/tools/signals.cpp b/unit/atf-src/tools/signals.cpp new file mode 100644 index 0000000..1e6c1ff --- /dev/null +++ b/unit/atf-src/tools/signals.cpp @@ -0,0 +1,146 @@ +// +// 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 "config.h" +#endif + +extern "C" { +#include <signal.h> +#include <unistd.h> +} + +#include <cerrno> +#include <cstdlib> + +#include "exceptions.hpp" +#include "signals.hpp" + +namespace impl = tools::signals; +#define IMPL_NAME "tools::signals" + +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 tools::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) + std::abort(); + 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/unit/atf-src/tools/signals.hpp b/unit/atf-src/tools/signals.hpp new file mode 100644 index 0000000..549a2ca --- /dev/null +++ b/unit/atf-src/tools/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(TOOLS_SIGNALS_HPP) +#define TOOLS_SIGNALS_HPP + +extern "C" { +#include <signal.h> +} + +namespace tools { +namespace signals { + +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 signals +} // namespace tools + +#endif // !defined(TOOLS_SIGNALS_HPP) diff --git a/unit/atf-src/tools/signals_test.cpp b/unit/atf-src/tools/signals_test.cpp new file mode 100644 index 0000000..aa6a8e5 --- /dev/null +++ b/unit/atf-src/tools/signals_test.cpp @@ -0,0 +1,275 @@ +// +// 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++.hpp> + +#include "defs.hpp" +#include "exceptions.hpp" +#include "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 tools::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 tools::signals::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 tools::signals::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 tools::signals::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 tools::signals::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 tools::signals::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; + tools::signals::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) +{ + tools::process::child c = + tools::process::fork(reset_child, tools::process::stream_inherit(), + tools::process::stream_inherit(), NULL); + + const tools::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/unit/atf-src/tools/test-program.cpp b/unit/atf-src/tools/test-program.cpp new file mode 100644 index 0000000..91ad78a --- /dev/null +++ b/unit/atf-src/tools/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 <cassert> +#include <cerrno> +#include <cstdlib> +#include <cstring> +#include <fstream> +#include <iostream> + +#include "config_file.hpp" +#include "defs.hpp" +#include "env.hpp" +#include "fs.hpp" +#include "io.hpp" +#include "parser.hpp" +#include "process.hpp" +#include "requirements.hpp" +#include "signals.hpp" +#include "test-program.hpp" +#include "text.hpp" +#include "timers.hpp" +#include "user.hpp" + +namespace impl = tools::test_program; +namespace detail = tools::test_program::detail; + +namespace { + +typedef std::map< std::string, std::string > vars_map; + +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 tools::parser::token_type eof_type = 0; +static const tools::parser::token_type nl_type = 1; +static const tools::parser::token_type text_type = 2; +static const tools::parser::token_type colon_type = 3; +static const tools::parser::token_type dblquote_type = 4; + +class tokenizer : public tools::parser::tokenizer< std::istream > { +public: + tokenizer(std::istream& is, size_t curline) : + tools::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 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 tools::fs::path& executable; + const vars_map& config; + + get_metadata_params(const tools::fs::path& p_executable, + const vars_map& p_config) : + executable(p_executable), + config(p_config) + { + } +}; + +struct test_case_params { + const tools::fs::path& executable; + const std::string& test_case_name; + const std::string& test_case_part; + const vars_map& metadata; + const vars_map& config; + const tools::fs::path& resfile; + const tools::fs::path& workdir; + + test_case_params(const tools::fs::path& p_executable, + const std::string& p_test_case_name, + const std::string& p_test_case_part, + const vars_map& p_metadata, + const vars_map& p_config, + const tools::fs::path& p_resfile, + const tools::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 tools::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 vars_map& config) +{ + std::vector< std::string > args; + + for (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"); + assert(fd == STDIN_FILENO); +} + +static +void +prepare_child(const tools::fs::path& workdir) +{ + const int ret = ::setpgid(::getpid(), 0); + assert(ret != -1); + + ::umask(S_IWGRP | S_IWOTH); + + for (int i = 1; i <= tools::signals::last_signo; i++) + tools::signals::reset(i); + + tools::env::set("HOME", workdir.str()); + tools::env::unset("LANG"); + tools::env::unset("LC_ALL"); + tools::env::unset("LC_COLLATE"); + tools::env::unset("LC_CTYPE"); + tools::env::unset("LC_MESSAGES"); + tools::env::unset("LC_MONETARY"); + tools::env::unset("LC_NUMERIC"); + tools::env::unset("LC_TIME"); + tools::env::set("TZ", "UTC"); + + tools::env::set("__RUNNING_INSIDE_ATF_RUN", "internal-yes-value"); + + tools::fs::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 = tools::get_required_user( + params->metadata, params->config); + if (user.first != -1 && user.second != -1) + tools::user::drop_privileges(user); + + // The input 'tp' parameter may be relative and become invalid once + // we change the current working directory. + const tools::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 = tools::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 = tools::text::trim(line.substr(pos2 + 2)); + } else + std::abort(); +} + +static impl::test_case_result +handle_result(const std::string& state, const std::string& arg, + const std::string& reason) +{ + assert(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) +{ + assert(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) +{ + assert(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 = tools::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 tools::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)tools::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 (!tools::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)tools::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 (!tools::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 tools::parser::parse_error; + using namespace atf_tp; + + std::pair< size_t, tools::parser::headers_map > hml = + tools::parser::read_headers(m_is, 1); + tools::parser::validate_content_type(hml.second, + "application/X-atf-tp", 1); + + tokenizer tkz(m_is, hml.first); + tools::parser::parser< tokenizer > p(tkz); + + try { + tools::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 = tools::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) +{ + tools::parser::headers_map hm; + tools::parser::attrs_map ct_attrs; + ct_attrs["version"] = "3"; + hm["Content-Type"] = + tools::parser::header_entry("Content-Type", "application/X-atf-tps", + ct_attrs); + tools::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) +{ + assert(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 tools::fs::path& executable, + const vars_map& config) +{ + get_metadata_params params(executable, config); + tools::process::child child = + tools::process::fork(get_metadata_child, + tools::process::stream_capture(), + tools::process::stream_inherit(), + static_cast< void * >(¶ms)); + + tools::io::pistream outin(child.stdout_fd()); + + metadata_reader parser(outin); + parser.read(); + + const tools::process::status status = child.wait(); + if (!status.exited() || status.exitstatus() != EXIT_SUCCESS) + throw tools::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 tools::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 tools::io::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: std::abort(); + } + } + +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, tools::process::status > +impl::run_test_case(const tools::fs::path& executable, + const std::string& test_case_name, + const std::string& test_case_part, + const vars_map& metadata, + const vars_map& config, + const tools::fs::path& resfile, + const tools::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); + tools::process::child child = + tools::process::fork(run_test_case_child, + tools::process::stream_capture(), + tools::process::stream_capture(), + static_cast< void * >(¶ms)); + + terminate_poll = false; + + const vars_map::const_iterator iter = metadata.find("timeout"); + assert(iter != metadata.end()); + const unsigned int timeout = + tools::text::to_type< unsigned int >((*iter).second); + const pid_t child_pid = child.pid(); + + // Get the input stream of stdout and stderr. + tools::io::file_handle outfh = child.stdout_fd(); + tools::io::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 { + timers::child_timer timeout_timer(timeout, child_pid, terminate_poll); + signals::signal_programmer sigchld(SIGCHLD, sigchld_handler); + mux.mux(terminate_poll); + timed_out = timeout_timer.fired(); + } catch (...) { + std::abort(); + } + + ::killpg(child_pid, SIGKILL); + mux.flush(); + tools::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 " + tools::text::to_string(timeout) + + " " + (timeout == 1 ? "second" : "seconds"); + } + + return std::make_pair(reason, status); +} diff --git a/unit/atf-src/tools/test-program.hpp b/unit/atf-src/tools/test-program.hpp new file mode 100644 index 0000000..f2f1972 --- /dev/null +++ b/unit/atf-src/tools/test-program.hpp @@ -0,0 +1,157 @@ +// +// 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(TOOLS_TEST_PROGRAM_HPP) +#define TOOLS_TEST_PROGRAM_HPP + +#include <map> +#include <string> + +#include "fs.hpp" +#include "process.hpp" + +namespace tools { +namespace test_program { + +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, std::map< std::string, std::string > > + 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 tools::fs::path&, + const std::map< std::string, std::string >&); +test_case_result read_test_case_result(const tools::fs::path&); +std::pair< std::string, tools::process::status > run_test_case( + const tools::fs::path&, const std::string&, const std::string&, + const std::map< std::string, std::string >&, + const std::map< std::string, std::string >&, + const tools::fs::path&, const tools::fs::path&, atf_tps_writer&); + +} // namespace test_program +} // namespace tools + +#endif // !defined(TOOLS_TEST_PROGRAM_HPP) diff --git a/unit/atf-src/tools/test_helpers.hpp b/unit/atf-src/tools/test_helpers.hpp new file mode 100644 index 0000000..3ac92d0 --- /dev/null +++ b/unit/atf-src/tools/test_helpers.hpp @@ -0,0 +1,116 @@ +// +// 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. +// + +#if defined(TOOLS_TEST_HELPERS_H) +# error "Cannot include test_helpers.hpp more than once." +#else +# define TOOLS_TEST_HELPERS_H +#endif + +#include <iostream> +#include <sstream> +#include <utility> + +#include <atf-c++.hpp> + +#include "parser.hpp" +#include "text.hpp" + +namespace test_helpers_detail { + +typedef std::vector< std::string > string_vector; + +template< class Reader > +std::pair< string_vector, string_vector > +do_read(const char* input) +{ + string_vector errors; + + std::istringstream is(input); + Reader reader(is); + try { + reader.read(); + } catch (const tools::parser::parse_errors& pes) { + for (std::vector< tools::parser::parse_error >::const_iterator iter = + pes.begin(); iter != pes.end(); iter++) + errors.push_back(*iter); + } catch (const tools::parser::parse_error& pe) { + ATF_FAIL("Raised a lonely parse error: " + + tools::text::to_string(pe.first) + ": " + pe.second); + } + + return std::make_pair(reader.m_calls, errors); +} + +void +check_equal(const char* expected[], const string_vector& actual) +{ + const char** expected_iter = expected; + string_vector::const_iterator actual_iter = actual.begin(); + + bool equals = true; + while (equals && *expected_iter != NULL && actual_iter != actual.end()) { + if (*expected_iter != *actual_iter) { + equals = false; + } else { + expected_iter++; + actual_iter++; + } + } + if (equals && ((*expected_iter == NULL && actual_iter != actual.end()) || + (*expected_iter != NULL && actual_iter == actual.end()))) + equals = false; + + if (!equals) { + std::cerr << "EXPECTED:\n"; + for (expected_iter = expected; *expected_iter != NULL; expected_iter++) + std::cerr << *expected_iter << "\n"; + + std::cerr << "ACTUAL:\n"; + for (actual_iter = actual.begin(); actual_iter != actual.end(); + actual_iter++) + std::cerr << *actual_iter << "\n"; + + ATF_FAIL("Expected results differ to actual values"); + } +} + +} // namespace test_helpers_detail + +template< class Reader > +void +do_parser_test(const char* input, const char* exp_calls[], + const char* exp_errors[]) +{ + const std::pair< test_helpers_detail::string_vector, + test_helpers_detail::string_vector > + actual = test_helpers_detail::do_read< Reader >(input); + test_helpers_detail::check_equal(exp_calls, actual.first); + test_helpers_detail::check_equal(exp_errors, actual.second); +} diff --git a/unit/atf-src/tools/test_program_test.cpp b/unit/atf-src/tools/test_program_test.cpp new file mode 100644 index 0000000..693790d --- /dev/null +++ b/unit/atf-src/tools/test_program_test.cpp @@ -0,0 +1,1023 @@ +// +// 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++.hpp> + +#include "parser.hpp" +#include "test-program.hpp" +#include "test_helpers.hpp" +#include "text.hpp" + +namespace impl = tools::test_program; +namespace detail = tools::test_program::detail; + +// ------------------------------------------------------------------------- +// Auxiliary functions. +// ------------------------------------------------------------------------- + +namespace { + +typedef std::map< std::string, std::string > vars_map; + +static +tools::fs::path +get_helper(const atf::tests::tc& tc, const char* name) +{ + return tools::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 = tools::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 (!tools::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"); + } +} + +} // anonymous namespace + +// ------------------------------------------------------------------------- +// 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 tools::fs::path executable = get_helper(*this, "bad_metadata_helper"); + ATF_REQUIRE_THROW(tools::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 tools::fs::path executable = get_helper(*this, "zero_tcs_helper"); + ATF_REQUIRE_THROW(tools::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 tools::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( + tools::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( + tools::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(tools::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(tools::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(tools::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( + tools::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/unit/atf-src/tools/tests-results.css b/unit/atf-src/tools/tests-results.css new file mode 100644 index 0000000..24fc12c --- /dev/null +++ b/unit/atf-src/tools/tests-results.css @@ -0,0 +1,199 @@ +/* + * 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. + */ + +body { + margin: 0 0 0 0; +} + +.nobr { + white-space: nowrap; +} + +h1 { + background: black; + color: white; + font-family: Arial; + font-size: 24pt; + padding: 5pt; +} + +h2 { + background: #eeeeee; + font-family: Arial; + font-size: 16pt; + margin-left: 10pt; + margin-right: 10pt; + padding: 3pt; +} + +h3 { + font-family: Arial; + font-size: 12pt; + margin-left: 20pt; + margin-right: 20pt; + padding: 3pt; +} + +p.details { + margin-left: 20pt; + margin-right: 20pt; +} + +p.term { + margin-left: 40pt; + margin-right: 40pt; +} + +pre.so { + margin-left: 40pt; + margin-right: 40pt; +} + +pre.se { + margin-left: 40pt; + margin-right: 40pt; +} + +table.summary { + border-collapse: collapse; + border-color: black; + border-style: solid; + border-width: 1pt; + margin-left: auto; + margin-right: auto; +} + +table.summary th { + background: #aaaadd; + border-style: solid; + border-width: 1pt; + padding: 3pt 6pt 3pt 6pt; +} + +table.summary td { + border-style: solid; + border-width: 1pt; + padding: 3pt 6pt 3pt 6pt; +} + +table.summary td.numeric p { + text-align: right; +} + +table.summary td.numeric-error p { + text-align: right; + color: red; +} + +table.summary td.numeric-warning p { + text-align: right; + color: #aaaa00; +} + +table.summary tr.group { + background: #dddddd; +} + +table.summary tr.entry td p { + margin-left: 10pt; +} + +table.tcs-summary { + border-collapse: collapse; + border-color: black; + border-style: solid; + border-width: 1pt; + margin-left: auto; + margin-right: auto; + width: 80%; +} + +table.tcs-summary td { + border-style: solid; + border-width: 1pt; + padding: 3pt 6pt 3pt 6pt; +} + +table.tcs-summary th { + background: #aaaadd; + border-style: solid; + border-width: 1pt; + padding: 3pt 6pt 3pt 6pt; +} + +table.tcs-summary td.numeric { + width: 1pt; +} + +table.tcs-summary td.numeric p { + text-align: right; +} + +table.tcs-summary td.tp-numeric { + background: #dddddd; + width: 1pt; +} + +table.tcs-summary td.tp-numeric p { + text-align: right; +} + +table.tcs-summary td.tp-id { + background: #dddddd; + font-weight: bold; + width: 1pt; +} + +table.tcs-summary td.tc-id p { + margin-left: 10pt; +} + +table.tcs-summary td.tcr-passed { + background: #aaffaa; + width: 1pt; +} + +table.tcs-summary td.tcr-failed { + background: #ffaaaa; + width: 1pt; +} + +table.tcs-summary td.tcr-skipped { + background: #ffffaa; + width: 1pt; +} + +table.tcs-summary td.tcr-xfail { + background: #ffaaff; + width: 1pt; +} + +table.tcs-summary td.tcr-reason { + width: 100%; +} diff --git a/unit/atf-src/tools/tests-results.dtd b/unit/atf-src/tools/tests-results.dtd new file mode 100644 index 0000000..c16f7ed --- /dev/null +++ b/unit/atf-src/tools/tests-results.dtd @@ -0,0 +1,61 @@ +<!-- + ++ 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. + --> + +<!-- TODO(1.0): Set the version of the DTD to 1.0 --> + +<!-- + ++ PUBLIC: -//NetBSD//DTD ATF Tests Results 0.1//EN + ++ URI: http://www.NetBSD.org/XML/atf/tests-results.dtd + --> + +<!ELEMENT tests-results (info*, tp*, info*)> + +<!ELEMENT info (#PCDATA)> +<!ATTLIST info class CDATA #REQUIRED> + +<!ELEMENT tp (tc*, failed?)> +<!ATTLIST tp id ID #REQUIRED> + +<!ELEMENT tc ((so, se)*, (passed, failed, skipped))> +<!ATTLIST tc id NMTOKEN #REQUIRED> + +<!ELEMENT expected_death (#PCDATA)> +<!ELEMENT expected_exit (#PCDATA)> +<!ELEMENT expected_failure (#PCDATA)> +<!ELEMENT expected_signal (#PCDATA)> +<!ELEMENT expected_timeout (#PCDATA)> +<!ELEMENT passed EMPTY> +<!ELEMENT failed (#PCDATA)> +<!ELEMENT skipped (#PCDATA)> +<!ELEMENT so (#PCDATA)> +<!ELEMENT se (#PCDATA)> + +<!ENTITY amp "/"> +<!ENTITY lt "J"> +<!ENTITY gt "L"> diff --git a/unit/atf-src/tools/tests-results.xsl b/unit/atf-src/tools/tests-results.xsl new file mode 100644 index 0000000..91b77c5 --- /dev/null +++ b/unit/atf-src/tools/tests-results.xsl @@ -0,0 +1,564 @@ +<?xml version="1.0" encoding="ISO-8859-1"?> +<!DOCTYPE xsl:stylesheet [<!ENTITY nbsp " ">]> + +<!-- + ++ 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. + --> + +<xsl:stylesheet version="1.0" + xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> + + <!-- Parameters that can be overriden by the user. --> + <xsl:param name="global.css">tests-results.css</xsl:param> + <xsl:param name="global.title">ATF Tests Results</xsl:param> + + <xsl:variable name="ntps" + select="count(tests-results/tp)" /> + <xsl:variable name="ntps-failed" + select="count(tests-results/tp/failed)" /> + <xsl:variable name="ntcs" + select="count(tests-results/tp/tc)" /> + <xsl:variable name="ntcs-passed" + select="count(tests-results/tp/tc/passed)" /> + <xsl:variable name="ntcs-failed" + select="count(tests-results/tp/tc/failed)" /> + <xsl:variable name="ntcs-skipped" + select="count(tests-results/tp/tc/skipped)" /> + <xsl:variable name="ntcs-xfail" + select="count(tests-results/tp/tc/expected_death) + + count(tests-results/tp/tc/expected_exit) + + count(tests-results/tp/tc/expected_failure) + + count(tests-results/tp/tc/expected_signal) + + count(tests-results/tp/tc/expected_timeout)" /> + + <xsl:template match="/"> + <xsl:copy> + <xsl:apply-templates select="@*|node()" /> + </xsl:copy> + </xsl:template> + + <xsl:template match="tests-results"> + <html xmlns="http://www.w3.org/1999/xhtml"> + <head> + <meta http-equiv="Content-Type" + content="text/html; charset=iso-8859-1" /> + <link rel="stylesheet" type="text/css" href="{$global.css}" /> + + <title><xsl:value-of select="$global.title" /></title> + </head> + + <body> + <h1><xsl:value-of select="$global.title" /></h1> + + <xsl:call-template name="info-top" /> + <xsl:call-template name="tcs-summary" /> + <xsl:if test="$ntcs-failed > 0"> + <xsl:call-template name="failed-tcs-summary" /> + </xsl:if> + <xsl:if test="$ntcs-xfail > 0"> + <xsl:call-template name="xfail-tcs-summary" /> + </xsl:if> + <xsl:if test="$ntcs-skipped > 0"> + <xsl:call-template name="skipped-tcs-summary" /> + </xsl:if> + <xsl:if test="$ntps-failed > 0"> + <xsl:call-template name="failed-tps-summary" /> + </xsl:if> + <xsl:call-template name="info-bottom" /> + + <xsl:apply-templates select="tp" mode="details" /> + </body> + </html> + </xsl:template> + + <xsl:template name="info-top"> + <h2>Execution summary</h2> + + <table class="summary"> + <tr> + <th class="nobr"><p>Item</p></th> + <th class="nobr"><p>Value</p></th> + </tr> + + <tr class="group"> + <td colspan="2"><p>ATF</p></td> + </tr> + <tr class="entry"> + <td><p>Version</p></td> + <td><p><xsl:apply-templates + select="info[@class = 'atf.version']" /></p></td> + </tr> + + <tr class="group"> + <td colspan="2"><p>Timings</p></td> + </tr> + <tr class="entry"> + <td><p>Start time of tests</p></td> + <td><p><xsl:apply-templates + select="info[@class = 'time.start']" /></p></td> + </tr> + <tr class="entry"> + <td><p>End time of tests</p></td> + <td><p><xsl:apply-templates + select="info[@class = 'time.end']" /></p></td> + </tr> + + <tr class="group"> + <td colspan="2"><p>System information</p></td> + </tr> + <tr class="entry"> + <td><p>Host name</p></td> + <td><p><xsl:apply-templates + select="info[@class = 'uname.nodename']" /></p></td> + </tr> + <tr class="entry"> + <td><p>Operating system</p></td> + <td><p><xsl:apply-templates + select="info[@class = 'uname.sysname']" /></p></td> + </tr> + <tr class="entry"> + <td><p>Operating system release</p></td> + <td><p><xsl:apply-templates + select="info[@class = 'uname.release']" /></p></td> + </tr> + <tr class="entry"> + <td><p>Operating system version</p></td> + <td><p><xsl:apply-templates + select="info[@class = 'uname.version']" /></p></td> + </tr> + <tr class="entry"> + <td><p>Platform</p></td> + <td><p><xsl:apply-templates + select="info[@class = 'uname.machine']" /></p></td> + </tr> + + <tr class="group"> + <td colspan="2"><p>Tests results</p></td> + </tr> + <tr class="entry"> + <td><p>Root</p></td> + <td><p><xsl:value-of + select="info[@class = 'tests.root']" /></p></td> + </tr> + <tr class="entry"> + <td><p>Test programs</p></td> + <td class="numeric"><p><xsl:value-of select="$ntps" /></p></td> + </tr> + <tr class="entry"> + <xsl:choose> + <xsl:when test="$ntps-failed > 0"> + <td><p><a href="#failed-tps-summary">Bogus test + programs</a></p></td> + <td class="numeric-error"> + <p><xsl:value-of select="$ntps-failed" /></p> + </td> + </xsl:when> + <xsl:otherwise> + <td><p>Bogus test programs</p></td> + <td class="numeric"> + <p><xsl:value-of select="$ntps-failed" /></p> + </td> + </xsl:otherwise> + </xsl:choose> + </tr> + <tr class="entry"> + <td><p>Test cases</p></td> + <td class="numeric"><p><xsl:value-of select="$ntcs" /></p></td> + </tr> + <tr class="entry"> + <td><p>Passed test cases</p></td> + <td class="numeric"><p><xsl:value-of select="$ntcs-passed" /></p></td> + </tr> + <tr class="entry"> + <xsl:choose> + <xsl:when test="$ntcs-failed > 0"> + <td><p><a href="#failed-tcs-summary">Failed test + cases</a></p></td> + <td class="numeric-error"> + <p><xsl:value-of select="$ntcs-failed" /></p> + </td> + </xsl:when> + <xsl:otherwise> + <td><p>Failed test cases</p></td> + <td class="numeric"> + <p><xsl:value-of select="$ntcs-failed" /></p> + </td> + </xsl:otherwise> + </xsl:choose> + </tr> + <tr class="entry"> + <xsl:choose> + <xsl:when test="$ntcs-xfail > 0"> + <td><p><a href="#xfail-tcs-summary">Expected + failures</a></p></td> + <td class="numeric-warning"> + <p><xsl:value-of select="$ntcs-xfail" /></p> + </td> + </xsl:when> + <xsl:otherwise> + <td><p>Expected failures</p></td> + <td class="numeric"> + <p><xsl:value-of select="$ntcs-xfail" /></p> + </td> + </xsl:otherwise> + </xsl:choose> + </tr> + <tr class="entry"> + <xsl:choose> + <xsl:when test="$ntcs-skipped > 0"> + <td><p><a href="#skipped-tcs-summary">Skipped test + cases</a></p></td> + <td class="numeric-warning"> + <p><xsl:value-of select="$ntcs-skipped" /></p> + </td> + </xsl:when> + <xsl:otherwise> + <td><p>Skipped test cases</p></td> + <td class="numeric"> + <p><xsl:value-of select="$ntcs-skipped" /></p> + </td> + </xsl:otherwise> + </xsl:choose> + </tr> + + <tr class="group"> + <td colspan="2"><p><a href="#execution-details">See more execution + details</a></p></td> + </tr> + </table> + </xsl:template> + + <xsl:template name="info-bottom"> + <a name="execution-details" /> + <h2 id="execution-details">Execution details</h2> + + <h3>Environment variables</h3> + + <ul> + <xsl:apply-templates select="info[@class = 'env']"> + <xsl:sort /> + </xsl:apply-templates> + </ul> + </xsl:template> + + <xsl:template match="info[@class = 'env']"> + <li> + <p><xsl:apply-templates /></p> + </li> + </xsl:template> + + <xsl:template name="tcs-summary"> + <h2>Test cases summary</h2> + + <table class="tcs-summary"> + <tr> + <th class="nobr"><p>Test case</p></th> + <th class="nobr"><p>Result</p></th> + <th class="nobr"><p>Reason</p></th> + <th class="nobr"><p>Duration</p></th> + </tr> + <xsl:apply-templates select="tp" mode="summary"> + <xsl:with-param name="which">all</xsl:with-param> + </xsl:apply-templates> + </table> + </xsl:template> + + <xsl:template name="xfail-tcs-summary"> + <a name="xfail-tcs-summary" /> + <h2 id="xfail-tcs-summary">Expected failures summary</h2> + + <table class="tcs-summary"> + <tr> + <th class="nobr"><p>Test case</p></th> + <th class="nobr"><p>Result</p></th> + <th class="nobr"><p>Reason</p></th> + <th class="nobr"><p>Duration</p></th> + </tr> + <xsl:apply-templates select="tp" mode="summary"> + <xsl:with-param name="which">xfail</xsl:with-param> + </xsl:apply-templates> + </table> + </xsl:template> + + <xsl:template name="failed-tcs-summary"> + <a name="failed-tcs-summary" /> + <h2 id="failed-tcs-summary">Failed test cases summary</h2> + + <table class="tcs-summary"> + <tr> + <th class="nobr"><p>Test case</p></th> + <th class="nobr"><p>Result</p></th> + <th class="nobr"><p>Reason</p></th> + <th class="nobr"><p>Duration</p></th> + </tr> + <xsl:apply-templates select="tp" mode="summary"> + <xsl:with-param name="which">failed</xsl:with-param> + </xsl:apply-templates> + </table> + </xsl:template> + + <xsl:template name="failed-tps-summary"> + <a name="failed-tps-summary" /> + <h2 id="failed-tps-summary">Bogus test programs summary</h2> + + <table class="tcs-summary"> + <tr> + <th class="nobr">Test program</th> + </tr> + <xsl:apply-templates select="tp" mode="summary"> + <xsl:with-param name="which">bogus</xsl:with-param> + </xsl:apply-templates> + </table> + </xsl:template> + + <xsl:template name="skipped-tcs-summary"> + <a name="skipped-tcs-summary" /> + <h2 id="skipped-tcs-summary">Skipped test cases summary</h2> + + <table class="tcs-summary"> + <tr> + <th class="nobr"><p>Test case</p></th> + <th class="nobr"><p>Result</p></th> + <th class="nobr"><p>Reason</p></th> + <th class="nobr"><p>Duration</p></th> + </tr> + <xsl:apply-templates select="tp" mode="summary"> + <xsl:with-param name="which">skipped</xsl:with-param> + </xsl:apply-templates> + </table> + </xsl:template> + + <xsl:template match="tp" mode="summary"> + <xsl:param name="which" /> + + <xsl:variable name="chosen"> + <xsl:choose> + <xsl:when test="$which = 'bogus' and failed">yes</xsl:when> + <xsl:when test="$which = 'passed' and tc/passed">yes</xsl:when> + <xsl:when test="$which = 'failed' and tc/failed">yes</xsl:when> + <xsl:when test="$which = 'xfail' and + tc/expected_death">yes</xsl:when> + <xsl:when test="$which = 'xfail' and + tc/expected_exit">yes</xsl:when> + <xsl:when test="$which = 'xfail' and + tc/expected_failure">yes</xsl:when> + <xsl:when test="$which = 'xfail' and + tc/expected_signal">yes</xsl:when> + <xsl:when test="$which = 'xfail' and + tc/expected_timeout">yes</xsl:when> + <xsl:when test="$which = 'skipped' and tc/skipped">yes</xsl:when> + <xsl:when test="$which = 'all'">yes</xsl:when> + <xsl:otherwise>no</xsl:otherwise> + </xsl:choose> + </xsl:variable> + + <xsl:if test="$chosen = 'yes'"> + <tr> + <td class="tp-id" colspan="3"> + <p><xsl:value-of select="@id" /></p> + </td> + <td class="tp-numeric"> + <p><xsl:value-of select="tp-time" />s</p> + </td> + </tr> + <xsl:if test="$which != 'bogus'"> + <xsl:apply-templates select="tc" mode="summary"> + <xsl:with-param name="which" select="$which" /> + </xsl:apply-templates> + </xsl:if> + </xsl:if> + </xsl:template> + + <xsl:template match="tc" mode="summary"> + <xsl:param name="which" /> + + <xsl:variable name="full-id" + select="concat(translate(../@id, '/', '_'), '_', @id)" /> + + <xsl:variable name="chosen"> + <xsl:choose> + <xsl:when test="$which = 'passed' and ./passed">yes</xsl:when> + <xsl:when test="$which = 'failed' and ./failed">yes</xsl:when> + <xsl:when test="$which = 'xfail' and + ./expected_death">yes</xsl:when> + <xsl:when test="$which = 'xfail' and + ./expected_exit">yes</xsl:when> + <xsl:when test="$which = 'xfail' and + ./expected_failure">yes</xsl:when> + <xsl:when test="$which = 'xfail' and + ./expected_signal">yes</xsl:when> + <xsl:when test="$which = 'xfail' and + ./expected_timeout">yes</xsl:when> + <xsl:when test="$which = 'skipped' and ./skipped">yes</xsl:when> + <xsl:when test="$which = 'all'">yes</xsl:when> + <xsl:otherwise>no</xsl:otherwise> + </xsl:choose> + </xsl:variable> + + <xsl:if test="$chosen = 'yes'"> + <tr> + <td class="tc-id"> + <xsl:choose> + <xsl:when test="expected_death|expected_exit|expected_failure| + expected_signal|expected_timeout|failed|skipped"> + <p><a href="#{$full-id}"><xsl:value-of select="@id" /></a></p> + </xsl:when> + <xsl:otherwise> + <p><xsl:value-of select="@id" /></p> + </xsl:otherwise> + </xsl:choose> + </td> + <xsl:apply-templates select="expected_death|expected_exit| + expected_failure|expected_timeout| + expected_signal|failed|passed| + skipped" mode="tc" /> + <td class="numeric"> + <p><xsl:value-of select="tc-time" />s</p> + </td> + </tr> + </xsl:if> + </xsl:template> + + <xsl:template match="passed" mode="tc"> + <td class="tcr-passed"><p class="nobr">Passed</p></td> + <td class="tcr-reason"><p>N/A</p></td> + </xsl:template> + + <xsl:template match="expected_death" mode="tc"> + <td class="tcr-xfail"><p class="nobr">Expected death</p></td> + <td class="tcr-reason"><p><xsl:apply-templates /></p></td> + </xsl:template> + + <xsl:template match="expected_exit" mode="tc"> + <td class="tcr-xfail"><p class="nobr">Expected exit</p></td> + <td class="tcr-reason"><p><xsl:apply-templates /></p></td> + </xsl:template> + + <xsl:template match="expected_failure" mode="tc"> + <td class="tcr-xfail"><p class="nobr">Expected failure</p></td> + <td class="tcr-reason"><p><xsl:apply-templates /></p></td> + </xsl:template> + + <xsl:template match="expected_timeout" mode="tc"> + <td class="tcr-xfail"><p class="nobr">Expected timeout</p></td> + <td class="tcr-reason"><p><xsl:apply-templates /></p></td> + </xsl:template> + + <xsl:template match="expected_signal" mode="tc"> + <td class="tcr-xfail"><p class="nobr">Expected signal</p></td> + <td class="tcr-reason"><p><xsl:apply-templates /></p></td> + </xsl:template> + + <xsl:template match="failed" mode="tc"> + <td class="tcr-failed"><p class="nobr">Failed</p></td> + <td class="tcr-reason"><p><xsl:apply-templates /></p></td> + </xsl:template> + + <xsl:template match="skipped" mode="tc"> + <td class="tcr-skipped"><p class="nobr">Skipped</p></td> + <td class="tcr-reason"><p><xsl:apply-templates /></p></td> + </xsl:template> + + <xsl:template match="tp" mode="details"> + <xsl:apply-templates select="tc[expected_death|expected_exit| + expected_failure|expected_signal| + expected_timeout|failed|skipped]" + mode="details" /> + </xsl:template> + + <xsl:template match="failed" mode="details"> + <p class="term"><strong>FAILED</strong>: <xsl:apply-templates /></p> + </xsl:template> + + <xsl:template match="expected_death|expected_exit|expected_failure| + expected_signal|expected_timeout" mode="details"> + <p class="term"><strong>XFAIL</strong>: <xsl:apply-templates /></p> + </xsl:template> + + <xsl:template match="skipped" mode="details"> + <p class="term"><strong>SKIPPED</strong>: <xsl:apply-templates /></p> + </xsl:template> + + <xsl:template match="tc" mode="details"> + <xsl:variable name="full-id" + select="concat(translate(../@id, '/', '_'), '_', @id)" /> + + <a name="{$full-id}" /> + <h2 id="{$full-id}">Test case: + <xsl:value-of select="../@id" /><xsl:text>/</xsl:text> + <xsl:value-of select="@id" /></h2> + + <xsl:if test="tc-time"> + <p class="details">Duration: + <xsl:apply-templates select="tc-time" mode="details" /></p> + </xsl:if> + + <h3>Termination reason</h3> + <xsl:apply-templates select="expected_death|expected_exit|expected_failure| + expected_signal|expected_timeout| + failed|skipped" + mode="details" /> + + <xsl:if test="so"> + <h3>Standard output stream</h3> + <pre class="so"><xsl:apply-templates select="so" mode="details" /></pre> + </xsl:if> + + <xsl:if test="se"> + <h3>Standard error stream</h3> + <pre class="se"><xsl:apply-templates select="se" mode="details" /></pre> + </xsl:if> + </xsl:template> + + <xsl:template match="tc-time" mode="details"> + <xsl:apply-templates /> seconds + </xsl:template> + + <xsl:template match="so" mode="details"> + <xsl:apply-templates /> + <xsl:if test="position() != last()"> + <xsl:text> +</xsl:text> + </xsl:if> + </xsl:template> + + <xsl:template match="se" mode="details"> + <xsl:apply-templates /> + <xsl:if test="position() != last()"> + <xsl:text> +</xsl:text> + </xsl:if> + </xsl:template> + + <xsl:template match="@*|node()" priority="-1"> + <xsl:copy> + <xsl:apply-templates select="@*|node()" /> + </xsl:copy> + </xsl:template> + +</xsl:stylesheet> diff --git a/unit/atf-src/tools/text.cpp b/unit/atf-src/tools/text.cpp new file mode 100644 index 0000000..9084f7e --- /dev/null +++ b/unit/atf-src/tools/text.cpp @@ -0,0 +1,158 @@ +// +// 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 <regex.h> +} + +#include <cctype> +#include <cerrno> +#include <cstring> + +#include "exceptions.hpp" +#include "text.hpp" + +namespace impl = tools::text; +#define IMPL_NAME "tools::text" + +char* +impl::duplicate(const char* str) +{ + char* copy = new char[std::strlen(str) + 1]; + std::strcpy(copy, str); + return copy; +} + +bool +impl::match(const std::string& str, const std::string& regex) +{ + bool found; + + // Special case: regcomp does not like empty regular expressions. + if (regex.empty()) { + found = str.empty(); + } else { + ::regex_t preg; + + if (::regcomp(&preg, regex.c_str(), REG_EXTENDED) != 0) + throw std::runtime_error("Invalid regular expression '" + regex + + "'"); + + const int res = ::regexec(&preg, str.c_str(), 0, NULL, 0); + regfree(&preg); + if (res != 0 && res != REG_NOMATCH) + throw std::runtime_error("Invalid regular expression " + regex); + + found = res == 0; + } + + return found; +} + +std::string +impl::to_lower(const std::string& str) +{ + std::string lc; + for (std::string::const_iterator iter = str.begin(); iter != str.end(); + iter++) + lc += std::tolower(*iter); + return lc; +} + +std::vector< std::string > +impl::split(const std::string& str, const std::string& delim) +{ + std::vector< std::string > words; + + std::string::size_type pos = 0, newpos = 0; + while (pos < str.length() && newpos != std::string::npos) { + newpos = str.find(delim, pos); + if (newpos != pos) + words.push_back(str.substr(pos, newpos - pos)); + pos = newpos + delim.length(); + } + + return words; +} + +std::string +impl::trim(const std::string& str) +{ + std::string::size_type pos1 = str.find_first_not_of(" \t"); + std::string::size_type pos2 = str.find_last_not_of(" \t"); + + if (pos1 == std::string::npos && pos2 == std::string::npos) + return ""; + else if (pos1 == std::string::npos) + return str.substr(0, str.length() - pos2); + else if (pos2 == std::string::npos) + return str.substr(pos1); + else + return str.substr(pos1, pos2 - pos1 + 1); +} + +bool +impl::to_bool(const std::string& str) +{ + const std::string lower = to_lower(str); + if (lower == "yes" || lower == "true") + return true; + else if (lower == "no" || lower == "false") + return false; + else { + // XXX Not really a libc error. + throw system_error(IMPL_NAME "::to_bool", "Cannot convert string " + "'" + str + "' to boolean", EINVAL); + } +} + +int64_t +impl::to_bytes(std::string str) +{ + if (str.empty()) + throw std::runtime_error("Empty value"); + + const char unit = str[str.length() - 1]; + int64_t multiplier; + switch (unit) { + case 'k': case 'K': multiplier = 1 << 10; break; + case 'm': case 'M': multiplier = 1 << 20; break; + case 'g': case 'G': multiplier = 1 << 30; break; + case 't': case 'T': multiplier = int64_t(1) << 40; break; + default: + if (!std::isdigit(unit)) + throw std::runtime_error(std::string("Unknown size unit '") + unit + + "'"); + multiplier = 1; + } + if (multiplier != 1) + str.erase(str.length() - 1); + + return to_type< int64_t >(str) * multiplier; +} diff --git a/unit/atf-src/tools/text.hpp b/unit/atf-src/tools/text.hpp new file mode 100644 index 0000000..0b3dbaa --- /dev/null +++ b/unit/atf-src/tools/text.hpp @@ -0,0 +1,153 @@ +// +// 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(TOOLS_TEXT_HPP) +#define TOOLS_TEXT_HPP + +extern "C" { +#include <stdint.h> +} + +#include <sstream> +#include <stdexcept> +#include <string> +#include <vector> + +namespace tools { +namespace text { + +//! +//! \brief Duplicates a C string using the new[] allocator. +//! +//! Replaces the functionality of strdup by using the new[] allocator and +//! thus allowing the resulting memory to be managed by utils::auto_array. +//! +char* duplicate(const char*); + +//! +//! \brief Joins multiple words into a string. +//! +//! Joins a list of words into a string, separating them using the provided +//! separator. Empty words are not omitted. +//! +template< class T > +std::string +join(const T& words, const std::string& separator) +{ + std::string str; + + typename T::const_iterator iter = words.begin(); + bool done = iter == words.end(); + while (!done) { + str += *iter; + iter++; + if (iter != words.end()) + str += separator; + else + done = true; + } + + return str; +} + +//! +//! \brief Checks if the string matches a regular expression. +//! +bool match(const std::string&, const std::string&); + +//! +//! \brief Splits a string into words. +//! +//! Splits the given string into multiple words, all separated by the +//! given delimiter. Multiple occurrences of the same delimiter are +//! not condensed so that rejoining the words later on using the same +//! delimiter results in the original string. +//! +std::vector< std::string > split(const std::string&, const std::string&); + +//! +//! \brief Removes whitespace from the beginning and end of a string. +//! +std::string trim(const std::string&); + +//! +//! \brief Converts a string to a boolean value. +//! +bool to_bool(const std::string&); + +//! +//! \brief Converts the given string to a bytes size. +//! +int64_t to_bytes(std::string); + +//! +//! \brief Changes the case of a string to lowercase. +//! +//! Returns a new string that is a lowercased version of the original +//! one. +//! +std::string to_lower(const std::string&); + +//! +//! \brief Converts the given object to a string. +//! +//! Returns a string with the representation of the given object. There +//! must exist an operator<< method for that object. +//! +template< class T > +std::string +to_string(const T& ob) +{ + std::ostringstream ss; + ss << ob; + return ss.str(); +} + +//! +//! \brief Converts the given string to another type. +//! +//! Attempts to convert the given string to the requested type. Throws +//! an exception if the conversion failed. +//! +template< class T > +T +to_type(const std::string& str) +{ + std::istringstream ss(str); + T value; + ss >> value; + if (!ss.eof() || (ss.eof() && (ss.fail() || ss.bad()))) + throw std::runtime_error("Cannot convert string to requested type"); + return value; +} + +} // namespace text +} // namespace tools + +#endif // !defined(TOOLS_TEXT_HPP) diff --git a/unit/atf-src/tools/text_test.cpp b/unit/atf-src/tools/text_test.cpp new file mode 100644 index 0000000..0fa31d8 --- /dev/null +++ b/unit/atf-src/tools/text_test.cpp @@ -0,0 +1,390 @@ +// +// 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 <cstring> +#include <set> +#include <vector> + +#include <atf-c++.hpp> + +#include "text.hpp" + +// ------------------------------------------------------------------------ +// Test cases for the free functions. +// ------------------------------------------------------------------------ + +ATF_TEST_CASE(duplicate); +ATF_TEST_CASE_HEAD(duplicate) +{ + set_md_var("descr", "Tests the duplicate function"); +} +ATF_TEST_CASE_BODY(duplicate) +{ + using tools::text::duplicate; + + const char* orig = "foo"; + + char* copy = duplicate(orig); + ATF_REQUIRE_EQ(std::strlen(copy), 3); + ATF_REQUIRE(std::strcmp(copy, "foo") == 0); + + std::strcpy(copy, "bar"); + ATF_REQUIRE(std::strcmp(copy, "bar") == 0); + ATF_REQUIRE(std::strcmp(orig, "foo") == 0); +} + +ATF_TEST_CASE(join); +ATF_TEST_CASE_HEAD(join) +{ + set_md_var("descr", "Tests the join function"); +} +ATF_TEST_CASE_BODY(join) +{ + using tools::text::join; + + // First set of tests using a non-sorted collection, std::vector. + { + std::vector< std::string > words; + std::string str; + + words.clear(); + str = join(words, ","); + ATF_REQUIRE_EQ(str, ""); + + words.clear(); + words.push_back(""); + str = join(words, ","); + ATF_REQUIRE_EQ(str, ""); + + words.clear(); + words.push_back(""); + words.push_back(""); + str = join(words, ","); + ATF_REQUIRE_EQ(str, ","); + + words.clear(); + words.push_back("foo"); + words.push_back(""); + words.push_back("baz"); + str = join(words, ","); + ATF_REQUIRE_EQ(str, "foo,,baz"); + + words.clear(); + words.push_back("foo"); + words.push_back("bar"); + words.push_back("baz"); + str = join(words, ","); + ATF_REQUIRE_EQ(str, "foo,bar,baz"); + } + + // Second set of tests using a sorted collection, std::set. + { + std::set< std::string > words; + std::string str; + + words.clear(); + str = join(words, ","); + ATF_REQUIRE_EQ(str, ""); + + words.clear(); + words.insert(""); + str = join(words, ","); + ATF_REQUIRE_EQ(str, ""); + + words.clear(); + words.insert("foo"); + words.insert(""); + words.insert("baz"); + str = join(words, ","); + ATF_REQUIRE_EQ(str, ",baz,foo"); + + words.clear(); + words.insert("foo"); + words.insert("bar"); + words.insert("baz"); + str = join(words, ","); + ATF_REQUIRE_EQ(str, "bar,baz,foo"); + } +} + +ATF_TEST_CASE(match); +ATF_TEST_CASE_HEAD(match) +{ + set_md_var("descr", "Tests the match function"); +} +ATF_TEST_CASE_BODY(match) +{ + using tools::text::match; + + ATF_REQUIRE_THROW(std::runtime_error, match("", "[")); + + ATF_REQUIRE(match("", "")); + ATF_REQUIRE(!match("foo", "")); + + ATF_REQUIRE(match("", ".*")); + ATF_REQUIRE(match("", "[a-z]*")); + + ATF_REQUIRE(match("hello", "hello")); + ATF_REQUIRE(match("hello", "[a-z]+")); + ATF_REQUIRE(match("hello", "^[a-z]+$")); + + ATF_REQUIRE(!match("hello", "helooo")); + ATF_REQUIRE(!match("hello", "[a-z]+5")); + ATF_REQUIRE(!match("hello", "^ [a-z]+$")); +} + +ATF_TEST_CASE(split); +ATF_TEST_CASE_HEAD(split) +{ + set_md_var("descr", "Tests the split function"); +} +ATF_TEST_CASE_BODY(split) +{ + using tools::text::split; + + std::vector< std::string > words; + + words = split("", " "); + ATF_REQUIRE_EQ(words.size(), 0); + + words = split(" ", " "); + ATF_REQUIRE_EQ(words.size(), 0); + + words = split(" ", " "); + ATF_REQUIRE_EQ(words.size(), 0); + + words = split("a b", " "); + ATF_REQUIRE_EQ(words.size(), 2); + ATF_REQUIRE_EQ(words[0], "a"); + ATF_REQUIRE_EQ(words[1], "b"); + + words = split("a b c d", " "); + ATF_REQUIRE_EQ(words.size(), 4); + ATF_REQUIRE_EQ(words[0], "a"); + ATF_REQUIRE_EQ(words[1], "b"); + ATF_REQUIRE_EQ(words[2], "c"); + ATF_REQUIRE_EQ(words[3], "d"); + + words = split("foo bar", " "); + ATF_REQUIRE_EQ(words.size(), 2); + ATF_REQUIRE_EQ(words[0], "foo"); + ATF_REQUIRE_EQ(words[1], "bar"); + + words = split("foo bar baz foobar", " "); + ATF_REQUIRE_EQ(words.size(), 4); + ATF_REQUIRE_EQ(words[0], "foo"); + ATF_REQUIRE_EQ(words[1], "bar"); + ATF_REQUIRE_EQ(words[2], "baz"); + ATF_REQUIRE_EQ(words[3], "foobar"); + + words = split(" foo bar", " "); + ATF_REQUIRE_EQ(words.size(), 2); + ATF_REQUIRE_EQ(words[0], "foo"); + ATF_REQUIRE_EQ(words[1], "bar"); + + words = split("foo bar", " "); + ATF_REQUIRE_EQ(words.size(), 2); + ATF_REQUIRE_EQ(words[0], "foo"); + ATF_REQUIRE_EQ(words[1], "bar"); + + words = split("foo bar ", " "); + ATF_REQUIRE_EQ(words.size(), 2); + ATF_REQUIRE_EQ(words[0], "foo"); + ATF_REQUIRE_EQ(words[1], "bar"); + + words = split(" foo bar ", " "); + ATF_REQUIRE_EQ(words.size(), 2); + ATF_REQUIRE_EQ(words[0], "foo"); + ATF_REQUIRE_EQ(words[1], "bar"); +} + +ATF_TEST_CASE(split_delims); +ATF_TEST_CASE_HEAD(split_delims) +{ + set_md_var("descr", "Tests the split function using different delimiters"); +} +ATF_TEST_CASE_BODY(split_delims) +{ + using tools::text::split; + + std::vector< std::string > words; + + words = split("", "/"); + ATF_REQUIRE_EQ(words.size(), 0); + + words = split(" ", "/"); + ATF_REQUIRE_EQ(words.size(), 1); + ATF_REQUIRE_EQ(words[0], " "); + + words = split(" ", "/"); + ATF_REQUIRE_EQ(words.size(), 1); + ATF_REQUIRE_EQ(words[0], " "); + + words = split("a/b", "/"); + ATF_REQUIRE_EQ(words.size(), 2); + ATF_REQUIRE_EQ(words[0], "a"); + ATF_REQUIRE_EQ(words[1], "b"); + + words = split("aLONGDELIMbcdLONGDELIMef", "LONGDELIM"); + ATF_REQUIRE_EQ(words.size(), 3); + ATF_REQUIRE_EQ(words[0], "a"); + ATF_REQUIRE_EQ(words[1], "bcd"); + ATF_REQUIRE_EQ(words[2], "ef"); +} + +ATF_TEST_CASE(trim); +ATF_TEST_CASE_HEAD(trim) +{ + set_md_var("descr", "Tests the trim function"); +} +ATF_TEST_CASE_BODY(trim) +{ + using tools::text::trim; + + ATF_REQUIRE_EQ(trim(""), ""); + ATF_REQUIRE_EQ(trim(" "), ""); + ATF_REQUIRE_EQ(trim("\t"), ""); + + ATF_REQUIRE_EQ(trim(" foo"), "foo"); + ATF_REQUIRE_EQ(trim("\t foo"), "foo"); + ATF_REQUIRE_EQ(trim(" \tfoo"), "foo"); + ATF_REQUIRE_EQ(trim("foo\t "), "foo"); + ATF_REQUIRE_EQ(trim("foo \t"), "foo"); + + ATF_REQUIRE_EQ(trim("foo bar"), "foo bar"); + ATF_REQUIRE_EQ(trim("\t foo bar"), "foo bar"); + ATF_REQUIRE_EQ(trim(" \tfoo bar"), "foo bar"); + ATF_REQUIRE_EQ(trim("foo bar\t "), "foo bar"); + ATF_REQUIRE_EQ(trim("foo bar \t"), "foo bar"); +} + +ATF_TEST_CASE(to_bool); +ATF_TEST_CASE_HEAD(to_bool) +{ + set_md_var("descr", "Tests the to_string function"); +} +ATF_TEST_CASE_BODY(to_bool) +{ + using tools::text::to_bool; + + ATF_REQUIRE(to_bool("true")); + ATF_REQUIRE(to_bool("TRUE")); + ATF_REQUIRE(to_bool("yes")); + ATF_REQUIRE(to_bool("YES")); + + ATF_REQUIRE(!to_bool("false")); + ATF_REQUIRE(!to_bool("FALSE")); + ATF_REQUIRE(!to_bool("no")); + ATF_REQUIRE(!to_bool("NO")); + + ATF_REQUIRE_THROW(std::runtime_error, to_bool("")); + ATF_REQUIRE_THROW(std::runtime_error, to_bool("tru")); + ATF_REQUIRE_THROW(std::runtime_error, to_bool("true2")); + ATF_REQUIRE_THROW(std::runtime_error, to_bool("fals")); + ATF_REQUIRE_THROW(std::runtime_error, to_bool("false2")); +} + +ATF_TEST_CASE(to_bytes); +ATF_TEST_CASE_HEAD(to_bytes) +{ + set_md_var("descr", "Tests the to_bytes function"); +} +ATF_TEST_CASE_BODY(to_bytes) +{ + using tools::text::to_bytes; + + ATF_REQUIRE_EQ(0, to_bytes("0")); + ATF_REQUIRE_EQ(12345, to_bytes("12345")); + ATF_REQUIRE_EQ(2 * 1024, to_bytes("2k")); + ATF_REQUIRE_EQ(4 * 1024 * 1024, to_bytes("4m")); + ATF_REQUIRE_EQ(int64_t(8) * 1024 * 1024 * 1024, to_bytes("8g")); + ATF_REQUIRE_EQ(int64_t(16) * 1024 * 1024 * 1024 * 1024, to_bytes("16t")); + + ATF_REQUIRE_THROW_RE(std::runtime_error, "Empty", to_bytes("")); + ATF_REQUIRE_THROW_RE(std::runtime_error, "Unknown size unit 'd'", + to_bytes("12d")); + ATF_REQUIRE_THROW(std::runtime_error, to_bytes(" ")); + ATF_REQUIRE_THROW(std::runtime_error, to_bytes(" k")); +} + +ATF_TEST_CASE(to_string); +ATF_TEST_CASE_HEAD(to_string) +{ + set_md_var("descr", "Tests the to_string function"); +} +ATF_TEST_CASE_BODY(to_string) +{ + using tools::text::to_string; + + ATF_REQUIRE_EQ(to_string('a'), "a"); + ATF_REQUIRE_EQ(to_string("a"), "a"); + ATF_REQUIRE_EQ(to_string(5), "5"); +} + +ATF_TEST_CASE(to_type); +ATF_TEST_CASE_HEAD(to_type) +{ + set_md_var("descr", "Tests the to_type function"); +} +ATF_TEST_CASE_BODY(to_type) +{ + using tools::text::to_type; + + ATF_REQUIRE_EQ(to_type< int >("0"), 0); + ATF_REQUIRE_EQ(to_type< int >("1234"), 1234); + ATF_REQUIRE_THROW(std::runtime_error, to_type< int >(" ")); + ATF_REQUIRE_THROW(std::runtime_error, to_type< int >("0 a")); + ATF_REQUIRE_THROW(std::runtime_error, to_type< int >("a")); + + ATF_REQUIRE_EQ(to_type< float >("0.5"), 0.5); + ATF_REQUIRE_EQ(to_type< float >("1234.5"), 1234.5); + ATF_REQUIRE_THROW(std::runtime_error, to_type< float >("0.5 a")); + ATF_REQUIRE_THROW(std::runtime_error, to_type< float >("a")); + + ATF_REQUIRE_EQ(to_type< std::string >("a"), "a"); +} + +// ------------------------------------------------------------------------ +// Main. +// ------------------------------------------------------------------------ + +ATF_INIT_TEST_CASES(tcs) +{ + // Add the test cases for the free functions. + ATF_ADD_TEST_CASE(tcs, duplicate); + ATF_ADD_TEST_CASE(tcs, join); + ATF_ADD_TEST_CASE(tcs, match); + ATF_ADD_TEST_CASE(tcs, split); + ATF_ADD_TEST_CASE(tcs, split_delims); + ATF_ADD_TEST_CASE(tcs, trim); + ATF_ADD_TEST_CASE(tcs, to_bool); + ATF_ADD_TEST_CASE(tcs, to_bytes); + ATF_ADD_TEST_CASE(tcs, to_string); + ATF_ADD_TEST_CASE(tcs, to_type); +} diff --git a/unit/atf-src/tools/timers.cpp b/unit/atf-src/tools/timers.cpp new file mode 100644 index 0000000..a5334f4 --- /dev/null +++ b/unit/atf-src/tools/timers.cpp @@ -0,0 +1,211 @@ +// +// 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 <config.h> +#endif + +extern "C" { +#include <sys/time.h> +} + +#include <cassert> +#include <cerrno> +#include <csignal> +#include <ctime> + +#include "defs.hpp" +#include "exceptions.hpp" +#include "signals.hpp" +#include "timers.hpp" + +namespace impl = tools::timers; +#define IMPL_NAME "tools::timers" + +#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 tools::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 tools::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 tools::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 tools::system_error(IMPL_NAME "::timer::timer", + "Failed to program timer", errno); + } + assert(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); + assert(ret != -1); + } +#else + { + const int ret = ::setitimer(ITIMER_REAL, &m_pimpl->m_old_it, NULL); + assert(ret != -1); + } +#endif + const int ret = ::sigaction(SIGALRM, &m_pimpl->m_old_sa, NULL); + assert(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/unit/atf-src/tools/timers.hpp b/unit/atf-src/tools/timers.hpp new file mode 100644 index 0000000..bf43ffb --- /dev/null +++ b/unit/atf-src/tools/timers.hpp @@ -0,0 +1,83 @@ +// +// 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(TOOLS_TIMERS_HPP) +#define TOOLS_TIMERS_HPP + +extern "C" { +#include <sys/types.h> +} + +#include <memory> + +namespace tools { +namespace timers { + +class signal_programmer; + +// ------------------------------------------------------------------------ +// The "timer" class. +// ------------------------------------------------------------------------ + +class timer { + // Non-copyable. + timer(const timer&); + timer& operator=(const timer&); + + 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 timers +} // namespace tools + +#endif // !defined(TOOLS_TIMERS_HPP) diff --git a/unit/atf-src/tools/ui.cpp b/unit/atf-src/tools/ui.cpp new file mode 100644 index 0000000..56426ec --- /dev/null +++ b/unit/atf-src/tools/ui.cpp @@ -0,0 +1,172 @@ +// +// 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/ioctl.h> + +#include <termios.h> +#include <unistd.h> +} + +#include <cassert> +#include <sstream> + +#include "env.hpp" +#include "text.hpp" +#include "ui.hpp" + +namespace impl = tools::ui; +#define IMPL_NAME "tools::ui" + +static +size_t +terminal_width(void) +{ + static bool done = false; + static size_t width = 0; + + if (!done) { + if (tools::env::has("COLUMNS")) { + const std::string cols = tools::env::get("COLUMNS"); + if (cols.length() > 0) { + width = tools::text::to_type< size_t >(cols); + } + } else { + struct winsize ws; + if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) != -1) + width = ws.ws_col; + } + + if (width >= 80) + width -= 5; + + done = true; + } + + return width; +} + +static +std::string +format_paragraph(const std::string& text, + const std::string& tag, + const bool first, + const bool repeat, + const size_t col) +{ + assert(text.find('\n') == std::string::npos); + + const std::string pad(col - tag.length(), ' '); + const std::string fullpad(col, ' '); + + std::string formatted; + if (first || repeat) + formatted = tag + pad; + else + formatted = fullpad; + assert(formatted.length() == col); + size_t curcol = col; + + const size_t maxcol = terminal_width(); + + std::vector< std::string > words = tools::text::split(text, " "); + for (std::vector< std::string >::const_iterator iter = words.begin(); + iter != words.end(); iter++) { + const std::string& word = *iter; + + if (iter != words.begin() && maxcol > 0 && + curcol + word.length() + 1 > maxcol) { + if (repeat) + formatted += '\n' + tag + pad; + else + formatted += '\n' + fullpad; + curcol = col; + } else if (iter != words.begin()) { + formatted += ' '; + curcol++; + } + + formatted += word; + curcol += word.length(); + } + + return formatted; +} + +std::string +impl::format_error(const std::string& prog_name, const std::string& error) +{ + return format_text_with_tag("ERROR: " + error, prog_name + ": ", true); +} + +std::string +impl::format_info(const std::string& prog_name, const std::string& msg) +{ + return format_text_with_tag(msg, prog_name + ": ", true); +} + +std::string +impl::format_text(const std::string& text) +{ + return format_text_with_tag(text, "", false, 0); +} + +std::string +impl::format_text_with_tag(const std::string& text, const std::string& tag, + bool repeat, size_t col) +{ + assert(col == 0 || col >= tag.length()); + if (col == 0) + col = tag.length(); + + std::string formatted; + + std::vector< std::string > lines = tools::text::split(text, "\n"); + for (std::vector< std::string >::const_iterator iter = lines.begin(); + iter != lines.end(); iter++) { + const std::string& line = *iter; + + formatted += format_paragraph(line, tag, iter == lines.begin(), + repeat, col); + if (iter + 1 != lines.end()) { + if (repeat) + formatted += "\n" + tag + "\n"; + else + formatted += "\n\n"; + } + } + + return formatted; +} + +std::string +impl::format_warning(const std::string& prog_name, const std::string& error) +{ + return format_text_with_tag("WARNING: " + error, prog_name + ": ", true); +} diff --git a/unit/atf-src/tools/ui.hpp b/unit/atf-src/tools/ui.hpp new file mode 100644 index 0000000..4e0e431 --- /dev/null +++ b/unit/atf-src/tools/ui.hpp @@ -0,0 +1,105 @@ +// +// 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(TOOLS_UI_HPP) +#define TOOLS_UI_HPP + +#include <string> + +namespace tools { +namespace ui { + +//! +//! \brief Formats an error message to fit on screen. +//! +//! Given the program's name and an error message, properly formats it to +//! fit on screen. +//! +//! The program's name is not stored globally to prevent the usage of this +//! function from inside the library. Making it a explicit parameter +//! restricts its usage to the frontend. +//! +std::string format_error(const std::string&, const std::string&); + +//! +//! \brief Formats an informational message to fit on screen. +//! +//! Given the program's name and an informational message, properly formats +//! it to fit on screen. +//! +//! The program's name is not stored globally to prevent the usage of this +//! function from inside the library. Making it a explicit parameter +//! restricts its usage to the frontend. +//! +std::string format_info(const std::string&, const std::string&); + +//! +//! \brief Formats a block of text to fit nicely on screen. +//! +//! Given a text, which is composed of multiple paragraphs separated by +//! a single '\n' character, reformats it to fill on the current screen's +//! width with proper line wrapping. +//! +//! This is just a special case of format_text_with_tag, provided for +//! simplicity. +//! +std::string format_text(const std::string&); + +//! +//! \brief Formats a block of text to fit nicely on screen, prepending a +//! tag to it. +//! +//! Given a text, which is composed of multiple paragraphs separated by +//! a single '\n' character, reformats it to fill on the current screen's +//! width with proper line wrapping. The text is prepended with a tag; +//! i.e. a word that is printed at the beginning of the first paragraph and +//! optionally repeated at the beginning of each word. The last parameter +//! specifies the column on which the text should start, and that position +//! must be greater than the tag's length or 0, in which case it +//! automatically takes the correct value. +//! +std::string format_text_with_tag(const std::string&, const std::string&, + bool, size_t = 0); + +//! +//! \brief Formats a warning message to fit on screen. +//! +//! Given the program's name and a warning message, properly formats it to +//! fit on screen. +//! +//! The program's name is not stored globally to prevent the usage of this +//! function from inside the library. Making it a explicit parameter +//! restricts its usage to the frontend. +//! +std::string format_warning(const std::string&, const std::string&); + +} // namespace ui +} // namespace tools + +#endif // !defined(TOOLS_UI_HPP) diff --git a/unit/atf-src/tools/ui_test.cpp b/unit/atf-src/tools/ui_test.cpp new file mode 100644 index 0000000..e4d9050 --- /dev/null +++ b/unit/atf-src/tools/ui_test.cpp @@ -0,0 +1,462 @@ +// +// 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. +// + +#include <cstring> +#include <iostream> + +#include <atf-c++.hpp> + +#include "env.hpp" +#include "ui.hpp" + +// ------------------------------------------------------------------------ +// Test cases for the free functions. +// ------------------------------------------------------------------------ + +struct test { + const char *tc; + const char *tag; + bool repeat; + size_t col; + const char *fmt; + const char *result; +} tests[] = { + // + // wo_tag + // + + { + "wo_tag", + "", + false, + 0, + "12345", + "12345", + }, + + { + "wo_tag", + "", + false, + 0, + "12345 ", + "12345", + }, + + { + "wo_tag", + "", + false, + 0, + "12345 7890", + "12345 7890", + }, + + { + "wo_tag", + "", + false, + 0, + "12345 789012 45", + "12345 789012 45", + }, + + { + "wo_tag", + "", + false, + 0, + "12345 789012 456", + "12345 789012\n456", + }, + + { + "wo_tag", + "", + false, + 0, + "1234567890123456", + "1234567890123456", + }, + + // TODO(jmmv): Fix the code to pass this test... +// { +// "wo_tag", +// "", +// false, +// 0, +// " 2345678901234567", +// "\n2345678901234567", +// }, + + { + "wo_tag", + "", + false, + 0, + "12345 789012345 78", + "12345 789012345\n78", + }, + + // + // wo_tag_col + // + + { + "wo_tag_col", + "", + false, + 10, + "12345", + " 12345", + }, + + { + "wo_tag_col", + "", + false, + 10, + "12345 7890", + " 12345\n" + " 7890", + }, + + { + "wo_tag_col", + "", + false, + 10, + "1 3 5 7 9", + " 1 3 5\n" + " 7 9", + }, + + // + // w_tag_no_repeat + // + + { + "w_tag_no_repeat", + "1234: ", + false, + 0, + "789012345", + "1234: 789012345", + }, + + { + "w_tag_no_repeat", + "1234: ", + false, + 0, + "789 1234 56789", + "1234: 789 1234\n" + " 56789", + }, + + { + "w_tag_no_repeat", + "1234: ", + false, + 0, + "789012345", + "1234: 789012345", + }, + + { + "w_tag_no_repeat", + "1234: ", + false, + 0, + "789012345 7890", + "1234: 789012345\n" + " 7890", + }, + + // + // w_tag_repeat + // + + { + "w_tag_repeat", + "1234: ", + true, + 0, + "789012345", + "1234: 789012345", + }, + + { + "w_tag_repeat", + "1234: ", + true, + 0, + "789 1234 56789", + "1234: 789 1234\n" + "1234: 56789", + }, + + { + "w_tag_repeat", + "1234: ", + true, + 0, + "789012345", + "1234: 789012345", + }, + + { + "w_tag_no_repeat", + "1234: ", + true, + 0, + "789012345 7890", + "1234: 789012345\n" + "1234: 7890", + }, + + // + // w_tag_col + // + + { + "w_tag_col", + "1234:", + false, + 10, + "1 3 5", + "1234: 1 3 5", + }, + + { + "w_tag_col", + "1234:", + false, + 10, + "1 3 5 7 9", + "1234: 1 3 5\n" + " 7 9", + }, + + { + "w_tag_col", + "1234:", + true, + 10, + "1 3 5 7 9", + "1234: 1 3 5\n" + "1234: 7 9", + }, + + // + // paragraphs + // + + { + "paragraphs", + "", + false, + 0, + "1 3 5\n\n", + "1 3 5" + }, + + { + "paragraphs", + "", + false, + 0, + "1 3 5\n2 4 6", + "1 3 5\n\n2 4 6" + }, + + { + "paragraphs", + "", + false, + 0, + "1234 6789 123456\n2 4 6", + "1234 6789\n123456\n\n2 4 6" + }, + + { + "paragraphs", + "12: ", + false, + 0, + "56789 123456\n2 4 6", + "12: 56789\n 123456\n\n 2 4 6" + }, + + { + "paragraphs", + "12: ", + true, + 0, + "56789 123456\n2 4 6", + "12: 56789\n12: 123456\n12: \n12: 2 4 6" + }, + + { + "paragraphs", + "12:", + false, + 4, + "56789 123456\n2 4 6", + "12: 56789\n 123456\n\n 2 4 6" + }, + + { + "paragraphs", + "12:", + true, + 4, + "56789 123456\n2 4 6", + "12: 56789\n12: 123456\n12:\n12: 2 4 6" + }, + + // + // end + // + + { + NULL, + NULL, + false, + 0, + NULL, + NULL, + }, +}; + +static +void +run_tests(const char *tc) +{ + struct test *t; + + std::cout << "Running tests for " << tc << "\n"; + + tools::env::set("COLUMNS", "15"); + + for (t = &tests[0]; t->tc != NULL; t++) { + if (std::strcmp(t->tc, tc) == 0) { + std::cout << "\n"; + std::cout << "Testing with tag '" << t->tag << "', '" + << (t->repeat ? "repeat" : "no repeat") << "', col " + << t->col << "\n"; + std::cout << "Input: >>>" << t->fmt << "<<<\n"; + std::cout << "Expected output: >>>" << t->result << "<<<\n"; + + std::string result = tools::ui::format_text_with_tag(t->fmt, t->tag, + t->repeat, t->col); + std::cout << "Output : >>>" << result << "<<<\n"; + ATF_REQUIRE_EQ(t->result, result); + } + } +} + +ATF_TEST_CASE(wo_tag); +ATF_TEST_CASE_HEAD(wo_tag) +{ + set_md_var("descr", "Checks formatting without tags"); +} +ATF_TEST_CASE_BODY(wo_tag) +{ + run_tests("wo_tag"); +} + +ATF_TEST_CASE(wo_tag_col); +ATF_TEST_CASE_HEAD(wo_tag_col) +{ + set_md_var("descr", "Checks formatting without tags and with a non-zero " + "starting column"); +} +ATF_TEST_CASE_BODY(wo_tag_col) +{ + run_tests("wo_tag_col"); +} + +ATF_TEST_CASE(w_tag_no_repeat); +ATF_TEST_CASE_HEAD(w_tag_no_repeat) +{ + set_md_var("descr", "Checks formatting with a tag"); +} +ATF_TEST_CASE_BODY(w_tag_no_repeat) +{ + run_tests("w_tag_no_repeat"); +} + +ATF_TEST_CASE(w_tag_repeat); +ATF_TEST_CASE_HEAD(w_tag_repeat) +{ + set_md_var("descr", "Checks formatting with a tag and repeating it on " + "each line"); +} +ATF_TEST_CASE_BODY(w_tag_repeat) +{ + run_tests("w_tag_repeat"); +} + +ATF_TEST_CASE(w_tag_col); +ATF_TEST_CASE_HEAD(w_tag_col) +{ + set_md_var("descr", "Checks formatting with a tag and starting at a " + "column greater than its length"); +} +ATF_TEST_CASE_BODY(w_tag_col) +{ + run_tests("w_tag_col"); +} + +ATF_TEST_CASE(paragraphs); +ATF_TEST_CASE_HEAD(paragraphs) +{ + set_md_var("descr", "Checks formatting a string that contains multiple " + "paragraphs"); +} +ATF_TEST_CASE_BODY(paragraphs) +{ + run_tests("paragraphs"); +} + +// ------------------------------------------------------------------------ +// Main. +// ------------------------------------------------------------------------ + +ATF_INIT_TEST_CASES(tcs) +{ + // Add the test cases for the free functions. + ATF_ADD_TEST_CASE(tcs, wo_tag); + ATF_ADD_TEST_CASE(tcs, wo_tag_col); + ATF_ADD_TEST_CASE(tcs, w_tag_no_repeat); + ATF_ADD_TEST_CASE(tcs, w_tag_repeat); + ATF_ADD_TEST_CASE(tcs, w_tag_col); + ATF_ADD_TEST_CASE(tcs, paragraphs); +} diff --git a/unit/atf-src/tools/user.cpp b/unit/atf-src/tools/user.cpp new file mode 100644 index 0000000..85d7e50 --- /dev/null +++ b/unit/atf-src/tools/user.cpp @@ -0,0 +1,102 @@ +// +// 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 <pwd.h> +#include <unistd.h> +} + +#include <cassert> +#include <stdexcept> +#include <string> + +#include "user.hpp" + +namespace impl = tools::user; +#define IMPL_NAME "tools::user" + +uid_t +impl::euid(void) +{ + return ::geteuid(); +} + +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) +{ + static gid_t groups[NGROUPS_MAX]; + static int ngroups = -1; + bool found; + int i; + + if (ngroups == -1) { + ngroups = getgroups(NGROUPS_MAX, groups); + assert(ngroups >= 0); + } + + found = false; + for (i = 0; !found && i < ngroups; i++) + if (groups[i] == gid) + found = true; + return found; +} + +bool +impl::is_root(void) +{ + return ::geteuid() == 0; +} + +bool +impl::is_unprivileged(void) +{ + return ::geteuid() != 0; +} diff --git a/unit/atf-src/tools/user.hpp b/unit/atf-src/tools/user.hpp new file mode 100644 index 0000000..1758eb0 --- /dev/null +++ b/unit/atf-src/tools/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(TOOLS_USER_HPP) +#define TOOLS_USER_HPP + +extern "C" { +#include <sys/types.h> +} + +#include <utility> + +namespace tools { +namespace user { + +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 user +} // namespace tools + +#endif // !defined(TOOLS_USER_HPP) diff --git a/unit/atf-src/tools/user_test.cpp b/unit/atf-src/tools/user_test.cpp new file mode 100644 index 0000000..4c385d3 --- /dev/null +++ b/unit/atf-src/tools/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++.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 tools::user::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 tools::user::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 tools::user::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 tools::user::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/unit/atf-src/tools/zero_tcs_helper.c b/unit/atf-src/tools/zero_tcs_helper.c new file mode 100644 index 0000000..d4068b1 --- /dev/null +++ b/unit/atf-src/tools/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(); +} |