diff options
| author | Marcel Moolenaar <marcel@FreeBSD.org> | 2012-09-04 23:07:32 +0000 |
|---|---|---|
| committer | Marcel Moolenaar <marcel@FreeBSD.org> | 2012-09-04 23:07:32 +0000 |
| commit | 679bf1899d7d81eaa5b2e95cba72d5db6f7491a3 (patch) | |
| tree | 748bcc46e1493df6fa88441f5e3783a5e2266e13 /atf-c++/detail | |
Diffstat (limited to 'atf-c++/detail')
34 files changed, 8092 insertions, 0 deletions
diff --git a/atf-c++/detail/Atffile b/atf-c++/detail/Atffile new file mode 100644 index 000000000000..ead6ec3dcac2 --- /dev/null +++ b/atf-c++/detail/Atffile @@ -0,0 +1,13 @@ +Content-Type: application/X-atf-atffile; version="1" + +prop: test-suite = atf + +tp: application_test +tp: env_test +tp: exceptions_test +tp: expand_test +tp: fs_test +tp: parser_test +tp: sanity_test +tp: text_test +tp: ui_test diff --git a/atf-c++/detail/Kyuafile b/atf-c++/detail/Kyuafile new file mode 100644 index 000000000000..472c1229d79d --- /dev/null +++ b/atf-c++/detail/Kyuafile @@ -0,0 +1,13 @@ +syntax("kyuafile", 1) + +test_suite("atf") + +atf_test_program{name="application_test"} +atf_test_program{name="env_test"} +atf_test_program{name="exceptions_test"} +atf_test_program{name="expand_test"} +atf_test_program{name="fs_test"} +atf_test_program{name="parser_test"} +atf_test_program{name="sanity_test"} +atf_test_program{name="text_test"} +atf_test_program{name="ui_test"} diff --git a/atf-c++/detail/Makefile.am.inc b/atf-c++/detail/Makefile.am.inc new file mode 100644 index 000000000000..fcadd77db1fe --- /dev/null +++ b/atf-c++/detail/Makefile.am.inc @@ -0,0 +1,99 @@ +# +# 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. +# + +libatf_c___la_SOURCES += atf-c++/detail/application.cpp \ + atf-c++/detail/application.hpp \ + atf-c++/detail/env.cpp \ + atf-c++/detail/env.hpp \ + atf-c++/detail/exceptions.cpp \ + atf-c++/detail/exceptions.hpp \ + atf-c++/detail/expand.cpp \ + atf-c++/detail/expand.hpp \ + atf-c++/detail/fs.cpp \ + atf-c++/detail/fs.hpp \ + atf-c++/detail/parser.cpp \ + atf-c++/detail/parser.hpp \ + atf-c++/detail/process.cpp \ + atf-c++/detail/process.hpp \ + atf-c++/detail/sanity.hpp \ + atf-c++/detail/text.cpp \ + atf-c++/detail/text.hpp \ + atf-c++/detail/ui.cpp \ + atf-c++/detail/ui.hpp + +tests_atf_c___detail_DATA = atf-c++/detail/Atffile \ + atf-c++/detail/Kyuafile +tests_atf_c___detaildir = $(pkgtestsdir)/atf-c++/detail +EXTRA_DIST += $(tests_atf_c___detail_DATA) + +noinst_LTLIBRARIES += atf-c++/detail/libtest_helpers.la +atf_c___detail_libtest_helpers_la_SOURCES = atf-c++/detail/test_helpers.cpp \ + atf-c++/detail/test_helpers.hpp + +tests_atf_c___detail_PROGRAMS = atf-c++/detail/application_test +atf_c___detail_application_test_SOURCES = atf-c++/detail/application_test.cpp +atf_c___detail_application_test_LDADD = atf-c++/detail/libtest_helpers.la $(ATF_CXX_LIBS) + +tests_atf_c___detail_PROGRAMS += atf-c++/detail/env_test +atf_c___detail_env_test_SOURCES = atf-c++/detail/env_test.cpp +atf_c___detail_env_test_LDADD = atf-c++/detail/libtest_helpers.la $(ATF_CXX_LIBS) + +tests_atf_c___detail_PROGRAMS += atf-c++/detail/exceptions_test +atf_c___detail_exceptions_test_SOURCES = atf-c++/detail/exceptions_test.cpp +atf_c___detail_exceptions_test_LDADD = atf-c++/detail/libtest_helpers.la $(ATF_CXX_LIBS) + +tests_atf_c___detail_PROGRAMS += atf-c++/detail/expand_test +atf_c___detail_expand_test_SOURCES = atf-c++/detail/expand_test.cpp +atf_c___detail_expand_test_LDADD = atf-c++/detail/libtest_helpers.la $(ATF_CXX_LIBS) + +tests_atf_c___detail_PROGRAMS += atf-c++/detail/fs_test +atf_c___detail_fs_test_SOURCES = atf-c++/detail/fs_test.cpp +atf_c___detail_fs_test_LDADD = atf-c++/detail/libtest_helpers.la $(ATF_CXX_LIBS) + +tests_atf_c___detail_PROGRAMS += atf-c++/detail/parser_test +atf_c___detail_parser_test_SOURCES = atf-c++/detail/parser_test.cpp +atf_c___detail_parser_test_LDADD = atf-c++/detail/libtest_helpers.la $(ATF_CXX_LIBS) + +tests_atf_c___detail_PROGRAMS += atf-c++/detail/process_test +atf_c___detail_process_test_SOURCES = atf-c++/detail/process_test.cpp +atf_c___detail_process_test_LDADD = atf-c++/detail/libtest_helpers.la $(ATF_CXX_LIBS) + +tests_atf_c___detail_PROGRAMS += atf-c++/detail/sanity_test +atf_c___detail_sanity_test_SOURCES = atf-c++/detail/sanity_test.cpp +atf_c___detail_sanity_test_LDADD = atf-c++/detail/libtest_helpers.la $(ATF_CXX_LIBS) + +tests_atf_c___detail_PROGRAMS += atf-c++/detail/text_test +atf_c___detail_text_test_SOURCES = atf-c++/detail/text_test.cpp +atf_c___detail_text_test_LDADD = atf-c++/detail/libtest_helpers.la $(ATF_CXX_LIBS) + +tests_atf_c___detail_PROGRAMS += atf-c++/detail/ui_test +atf_c___detail_ui_test_SOURCES = atf-c++/detail/ui_test.cpp +atf_c___detail_ui_test_LDADD = atf-c++/detail/libtest_helpers.la $(ATF_CXX_LIBS) + +# vim: syntax=make:noexpandtab:shiftwidth=8:softtabstop=8 diff --git a/atf-c++/detail/application.cpp b/atf-c++/detail/application.cpp new file mode 100644 index 000000000000..878b010bcde8 --- /dev/null +++ b/atf-c++/detail/application.cpp @@ -0,0 +1,345 @@ +// +// Automated Testing Framework (atf) +// +// Copyright (c) 2007 The NetBSD Foundation, Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND +// CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +// IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY +// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +// IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// + +#if defined(HAVE_CONFIG_H) +#include "bconfig.h" +#endif + +extern "C" { +#include <unistd.h> +} + +#include <cstdarg> +#include <cstdio> +#include <cstdlib> +#include <cstring> +#include <iostream> + +extern "C" { +#include "atf-c/defs.h" +} + +#include "application.hpp" +#include "sanity.hpp" +#include "ui.hpp" + +#if !defined(HAVE_VSNPRINTF_IN_STD) +namespace std { +using ::vsnprintf; +} +#endif // !defined(HAVE_VSNPRINTF_IN_STD) + +namespace impl = atf::application; +#define IMPL_NAME "atf::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, + const bool use_ui) : + m_hflag(false), + m_argc(-1), + m_argv(NULL), + m_prog_name(NULL), + m_description(description), + m_manpage(manpage), + m_global_manpage(global_manpage), + m_use_ui(use_ui) +{ +} + +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(); + if (m_use_ui) { + 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) +{ + PRE(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': + INV(m_use_ui); + 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) +{ + PRE(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(); + INV(!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) +{ + PRE(argc > 0); + PRE(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) { + INV(m_use_ui); + 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) { + if (m_use_ui) { + 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"; + } else { + std::cerr << m_prog_name << ": ERROR: " << e.what() << "\n"; + std::cerr << m_prog_name << ": See " << m_manpage << " for usage " + "details.\n"; + } + errcode = EXIT_FAILURE; + } catch (const std::runtime_error& e) { + if (m_use_ui) { + std::cerr << ui::format_error(m_prog_name, std::string(e.what())) + << "\n"; + } else { + std::cerr << m_prog_name << ": ERROR: " << e.what() << "\n"; + } + errcode = EXIT_FAILURE; + } catch (const std::exception& e) { + if (m_use_ui) { + std::cerr << ui::format_error(m_prog_name, std::string("Caught " + "unexpected error: ") + e.what() + "\n" + bug) << "\n"; + } else { + std::cerr << m_prog_name << ": ERROR: Caught unexpected error: " + << e.what() << "\n"; + } + errcode = EXIT_FAILURE; + } catch (...) { + if (m_use_ui) { + std::cerr << ui::format_error(m_prog_name, std::string("Caught " + "unknown error\n") + bug) << "\n"; + } else { + std::cerr << m_prog_name << ": ERROR: Caught unknown error\n"; + } + errcode = EXIT_FAILURE; + } + return errcode; +} diff --git a/atf-c++/detail/application.hpp b/atf-c++/detail/application.hpp new file mode 100644 index 000000000000..9d1f242004a3 --- /dev/null +++ b/atf-c++/detail/application.hpp @@ -0,0 +1,115 @@ +// +// Automated Testing Framework (atf) +// +// Copyright (c) 2007 The NetBSD Foundation, Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND +// CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +// IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY +// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +// IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// + +#if !defined(_ATF_CXX_APPLICATION_HPP_) +#define _ATF_CXX_APPLICATION_HPP_ + +#include <ostream> +#include <set> +#include <stdexcept> +#include <string> + +namespace atf { +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; + const bool m_use_ui; + + 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&, + bool = true); + virtual ~app(void); + + int run(int, char* const*); +}; + +} // namespace application +} // namespace atf + +#endif // !defined(_ATF_CXX_APPLICATION_HPP_) diff --git a/atf-c++/detail/application_test.cpp b/atf-c++/detail/application_test.cpp new file mode 100644 index 000000000000..2a788bfb2880 --- /dev/null +++ b/atf-c++/detail/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 "application.hpp" + +#include "../macros.hpp" + +class getopt_app : public atf::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/atf-c++/detail/env.cpp b/atf-c++/detail/env.cpp new file mode 100644 index 000000000000..5ca7f09c4f0a --- /dev/null +++ b/atf-c++/detail/env.cpp @@ -0,0 +1,73 @@ +// +// 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 "../../atf-c/error.h" + +#include "../../atf-c/detail/env.h" +} + +#include "env.hpp" +#include "exceptions.hpp" +#include "sanity.hpp" + +namespace impl = atf::env; +#define IMPL_NAME "atf::env" + +// ------------------------------------------------------------------------ +// Free functions. +// ------------------------------------------------------------------------ + +std::string +impl::get(const std::string& name) +{ + return atf_env_get(name.c_str()); +} + +bool +impl::has(const std::string& name) +{ + return atf_env_has(name.c_str()); +} + +void +impl::set(const std::string& name, const std::string& val) +{ + atf_error_t err = atf_env_set(name.c_str(), val.c_str()); + if (atf_is_error(err)) + throw_atf_error(err); +} + +void +impl::unset(const std::string& name) +{ + atf_error_t err = atf_env_unset(name.c_str()); + if (atf_is_error(err)) + throw_atf_error(err); +} diff --git a/atf-c++/detail/env.hpp b/atf-c++/detail/env.hpp new file mode 100644 index 000000000000..afdf69be5935 --- /dev/null +++ b/atf-c++/detail/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(_ATF_CXX_ENV_HPP_) +#define _ATF_CXX_ENV_HPP_ + +#include <string> + +namespace atf { +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 atf + +#endif // !defined(_ATF_CXX_ENV_HPP_) diff --git a/atf-c++/detail/env_test.cpp b/atf-c++/detail/env_test.cpp new file mode 100644 index 000000000000..a7b681d14e11 --- /dev/null +++ b/atf-c++/detail/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 "../macros.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(atf::env::has("PATH")); + ATF_REQUIRE(!atf::env::get("PATH").empty()); + + ATF_REQUIRE(!atf::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(atf::env::has("PATH")); + const std::string& oldval = atf::env::get("PATH"); + atf::env::set("PATH", "foo-bar"); + ATF_REQUIRE(atf::env::get("PATH") != oldval); + ATF_REQUIRE_EQ(atf::env::get("PATH"), "foo-bar"); + + ATF_REQUIRE(!atf::env::has("_UNDEFINED_VARIABLE_")); + atf::env::set("_UNDEFINED_VARIABLE_", "foo2-bar2"); + ATF_REQUIRE_EQ(atf::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(atf::env::has("PATH")); + atf::env::unset("PATH"); + ATF_REQUIRE(!atf::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/atf-c++/detail/exceptions.cpp b/atf-c++/detail/exceptions.cpp new file mode 100644 index 000000000000..79c5b489e747 --- /dev/null +++ b/atf-c++/detail/exceptions.cpp @@ -0,0 +1,157 @@ +// +// Automated Testing Framework (atf) +// +// Copyright (c) 2007 The NetBSD Foundation, Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND +// CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +// IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY +// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +// IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// + +#if defined(HAVE_CONFIG_H) +#include "bconfig.h" +#endif + +#include <cstdarg> +#include <cstdio> +#include <cstring> +#include <new> + +extern "C" { +#include "../../atf-c/error.h" +}; + +#include "exceptions.hpp" +#include "sanity.hpp" + +// ------------------------------------------------------------------------ +// The "system_error" type. +// ------------------------------------------------------------------------ + +atf::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) +{ +} + +atf::system_error::~system_error(void) + throw() +{ +} + +int +atf::system_error::code(void) + const + throw() +{ + return m_sys_err; +} + +const char* +atf::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"; + } +} + +// ------------------------------------------------------------------------ +// Free functions. +// ------------------------------------------------------------------------ + +static +void +throw_libc_error(atf_error_t err) +{ + PRE(atf_error_is(err, "libc")); + + const int ecode = atf_libc_error_code(err); + const std::string msg = atf_libc_error_msg(err); + atf_error_free(err); + throw atf::system_error("XXX", msg, ecode); +} + +static +void +throw_no_memory_error(atf_error_t err) +{ + PRE(atf_error_is(err, "no_memory")); + + atf_error_free(err); + throw std::bad_alloc(); +} + +static +void +throw_unknown_error(atf_error_t err) +{ + PRE(atf_is_error(err)); + + static char buf[4096]; + atf_error_format(err, buf, sizeof(buf)); + atf_error_free(err); + throw std::runtime_error(buf); +} + +void +atf::throw_atf_error(atf_error_t err) +{ + static struct handler { + const char* m_name; + void (*m_func)(atf_error_t); + } handlers[] = { + { "libc", throw_libc_error }, + { "no_memory", throw_no_memory_error }, + { NULL, throw_unknown_error }, + }; + + PRE(atf_is_error(err)); + + handler* h = handlers; + while (h->m_name != NULL) { + if (atf_error_is(err, h->m_name)) { + h->m_func(err); + UNREACHABLE; + } else + h++; + } + // XXX: I'm not sure that raising an "unknown" error is a wise thing + // to do here. The C++ binding is supposed to have feature parity + // with the C one, so all possible errors raised by the C library + // should have their counterpart in the C++ library. Still, removing + // this will require some code auditing that I can't afford at the + // moment. + INV(h->m_name == NULL && h->m_func != NULL); + h->m_func(err); + UNREACHABLE; +} diff --git a/atf-c++/detail/exceptions.hpp b/atf-c++/detail/exceptions.hpp new file mode 100644 index 000000000000..f655a84d8263 --- /dev/null +++ b/atf-c++/detail/exceptions.hpp @@ -0,0 +1,99 @@ +// +// Automated Testing Framework (atf) +// +// Copyright (c) 2007 The NetBSD Foundation, Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND +// CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +// IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY +// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +// IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// + +#if !defined(_ATF_CXX_EXCEPTIONS_HPP_) +#define _ATF_CXX_EXCEPTIONS_HPP_ + +#include <stdexcept> +#include <string> + +extern "C" { +struct atf_error; +} + +namespace atf { + +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(); +}; + +void throw_atf_error(struct atf_error *); + +} // namespace atf + +#endif // !defined(_ATF_CXX_EXCEPTIONS_HPP_) diff --git a/atf-c++/detail/exceptions_test.cpp b/atf-c++/detail/exceptions_test.cpp new file mode 100644 index 000000000000..821c192dd229 --- /dev/null +++ b/atf-c++/detail/exceptions_test.cpp @@ -0,0 +1,148 @@ +// +// 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 "../../atf-c/error.h" +} + +#include <cstdio> +#include <new> + +#include "../macros.hpp" + +#include "exceptions.hpp" +#include "sanity.hpp" + +// ------------------------------------------------------------------------ +// The "test" error. +// ------------------------------------------------------------------------ + +extern "C" { + +struct test_error_data { + const char* m_msg; +}; +typedef struct test_error_data test_error_data_t; + +static +void +test_format(const atf_error_t err, char *buf, size_t buflen) +{ + const test_error_data_t* data; + + PRE(atf_error_is(err, "test")); + + data = static_cast< const test_error_data_t * >(atf_error_data(err)); + snprintf(buf, buflen, "Message: %s", data->m_msg); +} + +static +atf_error_t +test_error(const char* msg) +{ + atf_error_t err; + test_error_data_t data; + + data.m_msg = msg; + + err = atf_error_new("test", &data, sizeof(data), test_format); + + return err; +} + +} // extern + +// ------------------------------------------------------------------------ +// Tests cases for the free functions. +// ------------------------------------------------------------------------ + +ATF_TEST_CASE(throw_atf_error_libc); +ATF_TEST_CASE_HEAD(throw_atf_error_libc) +{ + set_md_var("descr", "Tests the throw_atf_error function when raising " + "a libc error"); +} +ATF_TEST_CASE_BODY(throw_atf_error_libc) +{ + try { + atf::throw_atf_error(atf_libc_error(1, "System error 1")); + } catch (const atf::system_error& e) { + ATF_REQUIRE(e.code() == 1); + ATF_REQUIRE(std::string(e.what()).find("System error 1") != + std::string::npos); + } catch (const std::exception& e) { + ATF_FAIL(std::string("Got unexpected exception: ") + e.what()); + } +} + +ATF_TEST_CASE(throw_atf_error_no_memory); +ATF_TEST_CASE_HEAD(throw_atf_error_no_memory) +{ + set_md_var("descr", "Tests the throw_atf_error function when raising " + "a no_memory error"); +} +ATF_TEST_CASE_BODY(throw_atf_error_no_memory) +{ + try { + atf::throw_atf_error(atf_no_memory_error()); + } catch (const std::bad_alloc&) { + } catch (const std::exception& e) { + ATF_FAIL(std::string("Got unexpected exception: ") + e.what()); + } +} + +ATF_TEST_CASE(throw_atf_error_unknown); +ATF_TEST_CASE_HEAD(throw_atf_error_unknown) +{ + set_md_var("descr", "Tests the throw_atf_error function when raising " + "an unknown error"); +} +ATF_TEST_CASE_BODY(throw_atf_error_unknown) +{ + try { + atf::throw_atf_error(test_error("The message")); + } catch (const std::runtime_error& e) { + const std::string msg = e.what(); + ATF_REQUIRE(msg.find("The message") != std::string::npos); + } catch (const std::exception& e) { + ATF_FAIL(std::string("Got unexpected exception: ") + e.what()); + } +} + +// ------------------------------------------------------------------------ +// Main. +// ------------------------------------------------------------------------ + +ATF_INIT_TEST_CASES(tcs) +{ + // Add the test cases for the free functions. + ATF_ADD_TEST_CASE(tcs, throw_atf_error_libc); + ATF_ADD_TEST_CASE(tcs, throw_atf_error_no_memory); + ATF_ADD_TEST_CASE(tcs, throw_atf_error_unknown); +} diff --git a/atf-c++/detail/expand.cpp b/atf-c++/detail/expand.cpp new file mode 100644 index 000000000000..f6f9b6882ec6 --- /dev/null +++ b/atf-c++/detail/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 = atf::expand; +#define IMPL_NAME "atf::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 atf::text::match(candidate, glob_to_regex(glob)); +} diff --git a/atf-c++/detail/expand.hpp b/atf-c++/detail/expand.hpp new file mode 100644 index 000000000000..7f4071ee8472 --- /dev/null +++ b/atf-c++/detail/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(_ATF_CXX_EXPAND_HPP_) +#define _ATF_CXX_EXPAND_HPP_ + +#include <string> +#include <vector> + +namespace atf { +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 atf + +#endif // !defined(_ATF_CXX_EXPAND_HPP_) diff --git a/atf-c++/detail/expand_test.cpp b/atf-c++/detail/expand_test.cpp new file mode 100644 index 000000000000..222ab3a92c29 --- /dev/null +++ b/atf-c++/detail/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 "../macros.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 atf::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 atf::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 atf::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 atf::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 atf::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 atf::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/atf-c++/detail/fs.cpp b/atf-c++/detail/fs.cpp new file mode 100644 index 000000000000..3517e261f939 --- /dev/null +++ b/atf-c++/detail/fs.cpp @@ -0,0 +1,517 @@ +// +// Automated Testing Framework (atf) +// +// Copyright (c) 2007 The NetBSD Foundation, Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND +// CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +// IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY +// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +// IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// + +#if defined(HAVE_CONFIG_H) +#include "bconfig.h" +#endif + +extern "C" { +#include <sys/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 <cerrno> +#include <cstdlib> +#include <cstring> + +extern "C" { +#include "../../atf-c/error.h" +} + +#include "../utils.hpp" + +#include "exceptions.hpp" +#include "env.hpp" +#include "fs.hpp" +#include "process.hpp" +#include "sanity.hpp" +#include "text.hpp" + +namespace impl = atf::fs; +#define IMPL_NAME "atf::fs" + +// ------------------------------------------------------------------------ +// Auxiliary functions. +// ------------------------------------------------------------------------ + +static bool safe_access(const impl::path&, int, int); + +//! +//! \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) +{ + bool ok; + + atf_error_t err = atf_fs_eaccess(p.c_path(), mode); + if (atf_is_error(err)) { + if (atf_error_is(err, "libc")) { + if (atf_libc_error_code(err) == experr) { + atf_error_free(err); + ok = false; + } else { + atf::throw_atf_error(err); + // XXX Silence warning; maybe throw_atf_error should be + // an exception and not a function. + ok = false; + } + } else { + atf::throw_atf_error(err); + // XXX Silence warning; maybe throw_atf_error should be + // an exception and not a function. + ok = false; + } + } else + ok = true; + + return ok; +} + +// ------------------------------------------------------------------------ +// The "path" class. +// ------------------------------------------------------------------------ + +impl::path::path(const std::string& s) +{ + atf_error_t err = atf_fs_path_init_fmt(&m_path, "%s", s.c_str()); + if (atf_is_error(err)) + throw_atf_error(err); +} + +impl::path::path(const path& p) +{ + atf_error_t err = atf_fs_path_copy(&m_path, &p.m_path); + if (atf_is_error(err)) + throw_atf_error(err); +} + +impl::path::path(const atf_fs_path_t *p) +{ + atf_error_t err = atf_fs_path_copy(&m_path, p); + if (atf_is_error(err)) + throw_atf_error(err); +} + +impl::path::~path(void) +{ + atf_fs_path_fini(&m_path); +} + +const char* +impl::path::c_str(void) + const +{ + return atf_fs_path_cstring(&m_path); +} + +const atf_fs_path_t* +impl::path::c_path(void) + const +{ + return &m_path; +} + +std::string +impl::path::str(void) + const +{ + return c_str(); +} + +bool +impl::path::is_absolute(void) + const +{ + return atf_fs_path_is_absolute(&m_path); +} + +bool +impl::path::is_root(void) + const +{ + return atf_fs_path_is_root(&m_path); +} + +impl::path +impl::path::branch_path(void) + const +{ + atf_fs_path_t bp; + atf_error_t err; + + err = atf_fs_path_branch_path(&m_path, &bp); + if (atf_is_error(err)) + throw_atf_error(err); + + path p(atf_fs_path_cstring(&bp)); + atf_fs_path_fini(&bp); + return p; +} + +std::string +impl::path::leaf_name(void) + const +{ + atf_dynstr_t ln; + atf_error_t err; + + err = atf_fs_path_leaf_name(&m_path, &ln); + if (atf_is_error(err)) + throw_atf_error(err); + + std::string s(atf_dynstr_cstring(&ln)); + atf_dynstr_fini(&ln); + return s; +} + +impl::path +impl::path::to_absolute(void) + const +{ + atf_fs_path_t pa; + + atf_error_t err = atf_fs_path_to_absolute(&m_path, &pa); + if (atf_is_error(err)) + throw_atf_error(err); + + path p(atf_fs_path_cstring(&pa)); + atf_fs_path_fini(&pa); + return p; +} + +impl::path& +impl::path::operator=(const path& p) +{ + atf_fs_path_t tmp; + + atf_error_t err = atf_fs_path_init_fmt(&tmp, "%s", p.c_str()); + if (atf_is_error(err)) + throw_atf_error(err); + else { + atf_fs_path_fini(&m_path); + m_path = tmp; + } + + return *this; +} + +bool +impl::path::operator==(const path& p) + const +{ + return atf_equal_fs_path_fs_path(&m_path, &p.m_path); +} + +bool +impl::path::operator!=(const path& p) + const +{ + return !atf_equal_fs_path_fs_path(&m_path, &p.m_path); +} + +impl::path +impl::path::operator/(const std::string& p) + const +{ + path p2 = *this; + + atf_error_t err = atf_fs_path_append_fmt(&p2.m_path, "%s", p.c_str()); + if (atf_is_error(err)) + throw_atf_error(err); + + return p2; +} + +impl::path +impl::path::operator/(const path& p) + const +{ + path p2 = *this; + + atf_error_t err = atf_fs_path_append_fmt(&p2.m_path, "%s", + atf_fs_path_cstring(&p.m_path)); + if (atf_is_error(err)) + throw_atf_error(err); + + return p2; +} + +bool +impl::path::operator<(const path& p) + const +{ + const char *s1 = atf_fs_path_cstring(&m_path); + const char *s2 = atf_fs_path_cstring(&p.m_path); + return std::strcmp(s1, s2) < 0; +} + +// ------------------------------------------------------------------------ +// The "file_info" class. +// ------------------------------------------------------------------------ + +const int impl::file_info::blk_type = atf_fs_stat_blk_type; +const int impl::file_info::chr_type = atf_fs_stat_chr_type; +const int impl::file_info::dir_type = atf_fs_stat_dir_type; +const int impl::file_info::fifo_type = atf_fs_stat_fifo_type; +const int impl::file_info::lnk_type = atf_fs_stat_lnk_type; +const int impl::file_info::reg_type = atf_fs_stat_reg_type; +const int impl::file_info::sock_type = atf_fs_stat_sock_type; +const int impl::file_info::wht_type = atf_fs_stat_wht_type; + +impl::file_info::file_info(const path& p) +{ + atf_error_t err; + + err = atf_fs_stat_init(&m_stat, p.c_path()); + if (atf_is_error(err)) + throw_atf_error(err); +} + +impl::file_info::file_info(const file_info& fi) +{ + atf_fs_stat_copy(&m_stat, &fi.m_stat); +} + +impl::file_info::~file_info(void) +{ + atf_fs_stat_fini(&m_stat); +} + +dev_t +impl::file_info::get_device(void) + const +{ + return atf_fs_stat_get_device(&m_stat); +} + +ino_t +impl::file_info::get_inode(void) + const +{ + return atf_fs_stat_get_inode(&m_stat); +} + +mode_t +impl::file_info::get_mode(void) + const +{ + return atf_fs_stat_get_mode(&m_stat); +} + +off_t +impl::file_info::get_size(void) + const +{ + return atf_fs_stat_get_size(&m_stat); +} + +int +impl::file_info::get_type(void) + const +{ + return atf_fs_stat_get_type(&m_stat); +} + +bool +impl::file_info::is_owner_readable(void) + const +{ + return atf_fs_stat_is_owner_readable(&m_stat); +} + +bool +impl::file_info::is_owner_writable(void) + const +{ + return atf_fs_stat_is_owner_writable(&m_stat); +} + +bool +impl::file_info::is_owner_executable(void) + const +{ + return atf_fs_stat_is_owner_executable(&m_stat); +} + +bool +impl::file_info::is_group_readable(void) + const +{ + return atf_fs_stat_is_group_readable(&m_stat); +} + +bool +impl::file_info::is_group_writable(void) + const +{ + return atf_fs_stat_is_group_writable(&m_stat); +} + +bool +impl::file_info::is_group_executable(void) + const +{ + return atf_fs_stat_is_group_executable(&m_stat); +} + +bool +impl::file_info::is_other_readable(void) + const +{ + return atf_fs_stat_is_other_readable(&m_stat); +} + +bool +impl::file_info::is_other_writable(void) + const +{ + return atf_fs_stat_is_other_writable(&m_stat); +} + +bool +impl::file_info::is_other_executable(void) + const +{ + return atf_fs_stat_is_other_executable(&m_stat); +} + +// ------------------------------------------------------------------------ +// 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; +} + +// ------------------------------------------------------------------------ +// Free functions. +// ------------------------------------------------------------------------ + +bool +impl::exists(const path& p) +{ + atf_error_t err; + bool b; + + err = atf_fs_exists(p.c_path(), &b); + if (atf_is_error(err)) + throw_atf_error(err); + + return b; +} + +bool +impl::have_prog_in_path(const std::string& prog) +{ + PRE(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 (!atf::env::has("PATH")) + throw std::runtime_error("PATH not defined in the environment"); + std::vector< std::string > dirs = + atf::text::split(atf::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, atf_fs_access_x, EACCES); +} + +void +impl::remove(const path& p) +{ + if (file_info(p).get_type() == file_info::dir_type) + throw atf::system_error(IMPL_NAME "::remove(" + p.str() + ")", + "Is a directory", + EPERM); + if (::unlink(p.c_str()) == -1) + throw atf::system_error(IMPL_NAME "::remove(" + p.str() + ")", + "unlink(" + p.str() + ") failed", + errno); +} + +void +impl::rmdir(const path& p) +{ + atf_error_t err = atf_fs_rmdir(p.c_path()); + if (atf_is_error(err)) + throw_atf_error(err); +} diff --git a/atf-c++/detail/fs.hpp b/atf-c++/detail/fs.hpp new file mode 100644 index 000000000000..4ffb39b2a582 --- /dev/null +++ b/atf-c++/detail/fs.hpp @@ -0,0 +1,391 @@ +// +// Automated Testing Framework (atf) +// +// Copyright (c) 2007 The NetBSD Foundation, Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND +// CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +// IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY +// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +// IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// + +#if !defined(_ATF_CXX_FS_HPP_) +#define _ATF_CXX_FS_HPP_ + +extern "C" { +#include <sys/types.h> +} + +#include <map> +#include <memory> +#include <ostream> +#include <set> +#include <stdexcept> +#include <string> + +extern "C" { +#include "../../atf-c/detail/fs.h" +} + +namespace atf { + +namespace io { +class systembuf; +} // namespace io + +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. + //! + atf_fs_path_t m_path; + +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 Copy constructor. + //! + path(const path&); + + //! + //! \brief Copy constructor. + //! + path(const atf_fs_path_t *); + + //! + //! \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 pointer to the implementation data. + //! + const atf_fs_path_t* c_path(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 Assignment operator. + //! + path& operator=(const path&); + + //! + //! \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 { + atf_fs_stat_t m_stat; + +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 copy constructor. + //! + file_info(const file_info&); + + //! + //! \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; +}; + +// ------------------------------------------------------------------------ +// 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&); + +} // namespace fs +} // namespace atf + +#endif // !defined(_ATF_CXX_FS_HPP_) diff --git a/atf-c++/detail/fs_test.cpp b/atf-c++/detail/fs_test.cpp new file mode 100644 index 000000000000..6cf9bf6c636c --- /dev/null +++ b/atf-c++/detail/fs_test.cpp @@ -0,0 +1,545 @@ +// +// 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 <fstream> +#include <cerrno> +#include <cstdio> + +#include "../macros.hpp" + +#include "exceptions.hpp" +#include "fs.hpp" + +// ------------------------------------------------------------------------ +// Auxiliary functions. +// ------------------------------------------------------------------------ + +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 atf::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 atf::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 atf::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 atf::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 atf::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 atf::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 atf::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 atf::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 atf::fs::file_info; + using atf::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 atf::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 atf::fs::directory; + using atf::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 atf::fs::directory; + using atf::fs::file_info; + using atf::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 atf::fs::directory; + using atf::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 atf::fs::file_info; + using atf::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 atf::fs::file_info; + using atf::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 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 atf::fs::exists; + using atf::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 atf::fs::is_executable; + using atf::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 atf::fs::exists; + using atf::fs::path; + using atf::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(atf::system_error, remove(path("files/dir"))); + ATF_REQUIRE( exists(path("files/dir"))); +} + +// ------------------------------------------------------------------------ +// 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 free functions. + ATF_ADD_TEST_CASE(tcs, exists); + ATF_ADD_TEST_CASE(tcs, is_executable); + ATF_ADD_TEST_CASE(tcs, remove); +} diff --git a/atf-c++/detail/parser.cpp b/atf-c++/detail/parser.cpp new file mode 100644 index 000000000000..7e7f680c573f --- /dev/null +++ b/atf-c++/detail/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 <sstream> + +#include "parser.hpp" +#include "sanity.hpp" +#include "text.hpp" + +namespace impl = atf::parser; +#define IMPL_NAME "atf::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 atf::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 = atf::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); + PRE(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++) { + PRE((*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) +{ + PRE(!hm.empty()); + headers_map::const_iterator ct = hm.find("Content-Type"); + PRE(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 = atf::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/atf-c++/detail/parser.hpp b/atf-c++/detail/parser.hpp new file mode 100644 index 000000000000..f1595f531f55 --- /dev/null +++ b/atf-c++/detail/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(_ATF_CXX_PARSER_HPP_) +#define _ATF_CXX_PARSER_HPP_ + +#include <istream> +#include <map> +#include <ostream> +#include <stdexcept> +#include <string> +#include <utility> +#include <vector> + +namespace atf { +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.unget(); + 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.unget(); + } else if (ch == '\n') { + done = true; + if (text.empty()) + t = token(m_lineno, m_nl_type, "<<NEWLINE>>"); + else + m_is.unget(); + } 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 atf + +#endif // !defined(_ATF_CXX_PARSER_HPP_) diff --git a/atf-c++/detail/parser_test.cpp b/atf-c++/detail/parser_test.cpp new file mode 100644 index 000000000000..491c01457964 --- /dev/null +++ b/atf-c++/detail/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 "../macros.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 atf::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 atf::parser::parse_error; + using atf::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 atf::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 { \ + atf::parser::token t = tkz.next(); \ + ATF_REQUIRE(t.type() == ttype); \ + ATF_REQUIRE_EQ(t.text(), ttext); \ + } while (false); + +namespace minimal { + + static const atf::parser::token_type eof_type = 0; + static const atf::parser::token_type nl_type = 1; + static const atf::parser::token_type word_type = 2; + + class tokenizer : public atf::parser::tokenizer< std::istream > { + public: + tokenizer(std::istream& is, bool skipws) : + atf::parser::tokenizer< std::istream > + (is, skipws, eof_type, nl_type, word_type) + { + } + }; + +} + +namespace delims { + + static const atf::parser::token_type eof_type = 0; + static const atf::parser::token_type nl_type = 1; + static const atf::parser::token_type word_type = 2; + static const atf::parser::token_type plus_type = 3; + static const atf::parser::token_type minus_type = 4; + static const atf::parser::token_type equal_type = 5; + + class tokenizer : public atf::parser::tokenizer< std::istream > { + public: + tokenizer(std::istream& is, bool skipws) : + atf::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 atf::parser::token_type eof_type = 0; + static const atf::parser::token_type nl_type = 1; + static const atf::parser::token_type word_type = 2; + static const atf::parser::token_type var_type = 3; + static const atf::parser::token_type loop_type = 4; + static const atf::parser::token_type endloop_type = 5; + + class tokenizer : public atf::parser::tokenizer< std::istream > { + public: + tokenizer(std::istream& is, bool skipws) : + atf::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 atf::parser::token_type eof_type = 0; + static const atf::parser::token_type nl_type = 1; + static const atf::parser::token_type word_type = 2; + static const atf::parser::token_type dblquote_type = 3; + + class tokenizer : public atf::parser::tokenizer< std::istream > { + public: + tokenizer(std::istream& is, bool skipws) : + atf::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, atf::parser::headers_map > hml = + atf::parser::read_headers(m_is, 1); + atf::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/atf-c++/detail/process.cpp b/atf-c++/detail/process.cpp new file mode 100644 index 000000000000..deb1158bcf8d --- /dev/null +++ b/atf-c++/detail/process.cpp @@ -0,0 +1,355 @@ +// +// 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 <signal.h> + +#include "../../atf-c/error.h" + +#include "../../atf-c/detail/process.h" +} + +#include <iostream> + +#include "exceptions.hpp" +#include "process.hpp" +#include "sanity.hpp" + +namespace detail = atf::process::detail; +namespace impl = atf::process; +#define IMPL_NAME "atf::process" + +// ------------------------------------------------------------------------ +// Auxiliary functions. +// ------------------------------------------------------------------------ + +template< class C > +atf::utils::auto_array< const char* > +collection_to_argv(const C& c) +{ + atf::utils::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++; + } + INV(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; +} + +// ------------------------------------------------------------------------ +// 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::basic_stream::basic_stream(void) : + m_inited(false) +{ +} + +impl::basic_stream::~basic_stream(void) +{ + if (m_inited) + atf_process_stream_fini(&m_sb); +} + +const atf_process_stream_t* +impl::basic_stream::get_sb(void) + const +{ + INV(m_inited); + return &m_sb; +} + +impl::stream_capture::stream_capture(void) +{ + atf_error_t err = atf_process_stream_init_capture(&m_sb); + if (atf_is_error(err)) + throw_atf_error(err); + m_inited = true; +} + +impl::stream_connect::stream_connect(const int src_fd, const int tgt_fd) +{ + atf_error_t err = atf_process_stream_init_connect(&m_sb, src_fd, tgt_fd); + if (atf_is_error(err)) + throw_atf_error(err); + m_inited = true; +} + +impl::stream_inherit::stream_inherit(void) +{ + atf_error_t err = atf_process_stream_init_inherit(&m_sb); + if (atf_is_error(err)) + throw_atf_error(err); + m_inited = true; +} + +impl::stream_redirect_fd::stream_redirect_fd(const int fd) +{ + atf_error_t err = atf_process_stream_init_redirect_fd(&m_sb, fd); + if (atf_is_error(err)) + throw_atf_error(err); + m_inited = true; +} + +impl::stream_redirect_path::stream_redirect_path(const fs::path& p) +{ + atf_error_t err = atf_process_stream_init_redirect_path(&m_sb, p.c_path()); + if (atf_is_error(err)) + throw_atf_error(err); + m_inited = true; +} + +// ------------------------------------------------------------------------ +// The "status" type. +// ------------------------------------------------------------------------ + +impl::status::status(atf_process_status_t& s) : + m_status(s) +{ +} + +impl::status::~status(void) +{ + atf_process_status_fini(&m_status); +} + +bool +impl::status::exited(void) + const +{ + return atf_process_status_exited(&m_status); +} + +int +impl::status::exitstatus(void) + const +{ + return atf_process_status_exitstatus(&m_status); +} + +bool +impl::status::signaled(void) + const +{ + return atf_process_status_signaled(&m_status); +} + +int +impl::status::termsig(void) + const +{ + return atf_process_status_termsig(&m_status); +} + +bool +impl::status::coredump(void) + const +{ + return atf_process_status_coredump(&m_status); +} + +// ------------------------------------------------------------------------ +// The "child" type. +// ------------------------------------------------------------------------ + +impl::child::child(atf_process_child_t& c) : + m_child(c), + m_waited(false) +{ +} + +impl::child::~child(void) +{ + if (!m_waited) { + ::kill(atf_process_child_pid(&m_child), SIGTERM); + + atf_process_status_t s; + atf_error_t err = atf_process_child_wait(&m_child, &s); + INV(!atf_is_error(err)); + atf_process_status_fini(&s); + } +} + +impl::status +impl::child::wait(void) +{ + atf_process_status_t s; + + atf_error_t err = atf_process_child_wait(&m_child, &s); + if (atf_is_error(err)) + throw_atf_error(err); + + m_waited = true; + return status(s); +} + +pid_t +impl::child::pid(void) + const +{ + return atf_process_child_pid(&m_child); +} + +int +impl::child::stdout_fd(void) +{ + return atf_process_child_stdout(&m_child); +} + +int +impl::child::stderr_fd(void) +{ + return atf_process_child_stderr(&m_child); +} + +// ------------------------------------------------------------------------ +// 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/atf-c++/detail/process.hpp b/atf-c++/detail/process.hpp new file mode 100644 index 000000000000..6e33e0037d87 --- /dev/null +++ b/atf-c++/detail/process.hpp @@ -0,0 +1,280 @@ +// +// 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(_ATF_CXX_PROCESS_HPP_) +#define _ATF_CXX_PROCESS_HPP_ + +extern "C" { +#include <sys/types.h> + +#include "../../atf-c/error.h" + +#include "../../atf-c/detail/process.h" +} + +#include <string> +#include <vector> + +#include "exceptions.hpp" +#include "fs.hpp" + +#include "../utils.hpp" + +namespace atf { +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. + utils::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 basic_stream { +protected: + atf_process_stream_t m_sb; + bool m_inited; + + const atf_process_stream_t* get_sb(void) const; + +public: + basic_stream(void); + ~basic_stream(void); +}; + +class stream_capture : basic_stream { + // Allow access to the getters. + template< class OutStream, class ErrStream > friend + child fork(void (*)(void*), const OutStream&, const ErrStream&, void*); + template< class OutStream, class ErrStream > friend + status exec(const atf::fs::path&, const argv_array&, + const OutStream&, const ErrStream&, void (*)(void)); + +public: + stream_capture(void); +}; + +class stream_connect : basic_stream { + // Allow access to the getters. + template< class OutStream, class ErrStream > friend + child fork(void (*)(void*), const OutStream&, const ErrStream&, void*); + template< class OutStream, class ErrStream > friend + status exec(const atf::fs::path&, const argv_array&, + const OutStream&, const ErrStream&, void (*)(void)); + +public: + stream_connect(const int, const int); +}; + +class stream_inherit : basic_stream { + // Allow access to the getters. + template< class OutStream, class ErrStream > friend + child fork(void (*)(void*), const OutStream&, const ErrStream&, void*); + template< class OutStream, class ErrStream > friend + status exec(const atf::fs::path&, const argv_array&, + const OutStream&, const ErrStream&, void (*)(void)); + +public: + stream_inherit(void); +}; + +class stream_redirect_fd : basic_stream { + // Allow access to the getters. + template< class OutStream, class ErrStream > friend + child fork(void (*)(void*), const OutStream&, const ErrStream&, void*); + template< class OutStream, class ErrStream > friend + status exec(const atf::fs::path&, const argv_array&, + const OutStream&, const ErrStream&, void (*)(void)); + +public: + stream_redirect_fd(const int); +}; + +class stream_redirect_path : basic_stream { + // Allow access to the getters. + template< class OutStream, class ErrStream > friend + child fork(void (*)(void*), const OutStream&, const ErrStream&, void*); + template< class OutStream, class ErrStream > friend + status exec(const atf::fs::path&, const argv_array&, + const OutStream&, const ErrStream&, void (*)(void)); + +public: + stream_redirect_path(const fs::path&); +}; + +// ------------------------------------------------------------------------ +// The "status" type. +// ------------------------------------------------------------------------ + +class status { + atf_process_status_t m_status; + + friend class child; + template< class OutStream, class ErrStream > friend + status exec(const atf::fs::path&, const argv_array&, + const OutStream&, const ErrStream&, void (*)(void)); + + status(atf_process_status_t&); + +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 { + atf_process_child_t m_child; + bool m_waited; + + template< class OutStream, class ErrStream > friend + child fork(void (*)(void*), const OutStream&, const ErrStream&, void*); + + child(atf_process_child_t& c); + +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); +} // 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*), const OutStream& outsb, + const ErrStream& errsb, void* v) +{ + atf_process_child_t c; + + detail::flush_streams(); + atf_error_t err = atf_process_fork(&c, start, outsb.get_sb(), + errsb.get_sb(), v); + if (atf_is_error(err)) + throw_atf_error(err); + + return child(c); +} + +template< class OutStream, class ErrStream > +status +exec(const atf::fs::path& prog, const argv_array& argv, + const OutStream& outsb, const ErrStream& errsb, + void (*prehook)(void)) +{ + atf_process_status_t s; + + detail::flush_streams(); + atf_error_t err = atf_process_exec_array(&s, prog.c_path(), + argv.exec_argv(), + outsb.get_sb(), + errsb.get_sb(), + prehook); + if (atf_is_error(err)) + throw_atf_error(err); + + return status(s); +} + +template< class OutStream, class ErrStream > +status +exec(const atf::fs::path& prog, const argv_array& argv, + const OutStream& outsb, const ErrStream& errsb) +{ + return exec(prog, argv, outsb, errsb, NULL); +} + +} // namespace process +} // namespace atf + +#endif // !defined(_ATF_CXX_PROCESS_HPP_) diff --git a/atf-c++/detail/process_test.cpp b/atf-c++/detail/process_test.cpp new file mode 100644 index 000000000000..d13ab9453a8c --- /dev/null +++ b/atf-c++/detail/process_test.cpp @@ -0,0 +1,357 @@ +// +// 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 "../macros.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 +atf::process::status +exec_process_helpers(const atf::tests::tc& tc, const char* helper_name) +{ + using atf::process::exec; + + std::vector< std::string > argv; + argv.push_back(get_process_helpers_path(tc).leaf_name()); + argv.push_back(helper_name); + + return exec(get_process_helpers_path(tc), + atf::process::argv_array(argv), + atf::process::stream_inherit(), + atf::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 }; + atf::process::argv_array argv(carray); + + ATF_REQUIRE_EQ(argv.size(), 0); + } + + { + const char* const carray[] = { "arg0", NULL }; + atf::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 }; + atf::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; + atf::process::argv_array argv(col); + + ATF_REQUIRE_EQ(argv.size(), 0); + } + + { + std::vector< std::string > col; + col.push_back("arg0"); + atf::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"); + atf::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) +{ + atf::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) +{ + { + atf::process::argv_array argv("arg0", NULL); + + ATF_REQUIRE_EQ(argv.size(), 1); + ATF_REQUIRE_EQ(argv[0], std::string("arg0")); + } + + { + atf::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 atf::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 atf::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 atf::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 atf::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 atf::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 atf::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/atf-c++/detail/sanity.hpp b/atf-c++/detail/sanity.hpp new file mode 100644 index 000000000000..6021a6e6890c --- /dev/null +++ b/atf-c++/detail/sanity.hpp @@ -0,0 +1,37 @@ +// +// Automated Testing Framework (atf) +// +// Copyright (c) 2007 The NetBSD Foundation, Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND +// CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +// IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY +// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +// IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// + +#if !defined(_ATF_CXX_SANITY_HPP_) +#define _ATF_CXX_SANITY_HPP_ + +extern "C" { +#include "../../atf-c/detail/sanity.h" +} + +#endif // !defined(_ATF_CXX_SANITY_HPP_) diff --git a/atf-c++/detail/sanity_test.cpp b/atf-c++/detail/sanity_test.cpp new file mode 100644 index 000000000000..8d3a07eb8bd5 --- /dev/null +++ b/atf-c++/detail/sanity_test.cpp @@ -0,0 +1,41 @@ +// +// 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 "../macros.hpp" + +ATF_TEST_CASE_WITHOUT_HEAD(nothing); +ATF_TEST_CASE_BODY(nothing) +{ + // TODO +} + +ATF_INIT_TEST_CASES(tcs) +{ + ATF_ADD_TEST_CASE(tcs, nothing); +} diff --git a/atf-c++/detail/test_helpers.cpp b/atf-c++/detail/test_helpers.cpp new file mode 100644 index 000000000000..42bd711dc7d6 --- /dev/null +++ b/atf-c++/detail/test_helpers.cpp @@ -0,0 +1,160 @@ +// +// 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 <regex.h> +} + +#include <fstream> +#include <iostream> +#include <string> +#include <vector> + +#include "../check.hpp" +#include "../config.hpp" +#include "../macros.hpp" + +#include "fs.hpp" +#include "process.hpp" +#include "test_helpers.hpp" + +void +build_check_cxx_o_aux(const atf::fs::path& sfile, const char* failmsg, + const bool expect_pass) +{ + std::vector< std::string > optargs; + optargs.push_back("-I" + atf::config::get("atf_includedir")); + optargs.push_back("-Wall"); + optargs.push_back("-Werror"); + + const bool result = atf::check::build_cxx_o( + sfile.str(), "test.o", atf::process::argv_array(optargs)); + if ((expect_pass && !result) || (!expect_pass && result)) + ATF_FAIL(failmsg); +} + +void +build_check_cxx_o(const atf::tests::tc& tc, const char* sfile, + const char* failmsg, const bool expect_pass) +{ + const atf::fs::path sfilepath = + atf::fs::path(tc.get_config_var("srcdir")) / sfile; + build_check_cxx_o_aux(sfilepath, failmsg, expect_pass); +} + +void +header_check(const char *hdrname) +{ + std::ofstream srcfile("test.c"); + ATF_REQUIRE(srcfile); + srcfile << "#include <" << hdrname << ">\n"; + srcfile.close(); + + const std::string failmsg = std::string("Header check failed; ") + + hdrname + " is not self-contained"; + build_check_cxx_o_aux(atf::fs::path("test.c"), failmsg.c_str(), true); +} + +atf::fs::path +get_process_helpers_path(const atf::tests::tc& tc) +{ + return atf::fs::path(tc.get_config_var("srcdir")) / + ".." / "atf-c" / "detail" / "process_helpers"; +} + +bool +grep_file(const char* name, const char* regex) +{ + std::ifstream is(name); + ATF_REQUIRE(is); + + bool found = false; + + std::string line; + std::getline(is, line); + while (!found && is.good()) { + if (grep_string(line, regex)) + found = true; + else + std::getline(is, line); + } + + return found; +} + +bool +grep_string(const std::string& str, const char* regex) +{ + int res; + regex_t preg; + + std::cout << "Looking for '" << regex << "' in '" << str << "'\n"; + ATF_REQUIRE(::regcomp(&preg, regex, REG_EXTENDED) == 0); + + res = ::regexec(&preg, str.c_str(), 0, NULL, 0); + ATF_REQUIRE(res == 0 || res == REG_NOMATCH); + + ::regfree(&preg); + + return res == 0; +} + +void +test_helpers_detail::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"); + } +} diff --git a/atf-c++/detail/test_helpers.hpp b/atf-c++/detail/test_helpers.hpp new file mode 100644 index 000000000000..059a0a5b4b7b --- /dev/null +++ b/atf-c++/detail/test_helpers.hpp @@ -0,0 +1,166 @@ +// +// 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(TESTS_ATF_ATF_CXX_TEST_HELPERS_H) +# error "Cannot include test_helpers.hpp more than once." +#else +# define TESTS_ATF_ATF_CXX_TEST_HELPERS_H +#endif + +#include <cstdlib> +#include <iostream> +#include <sstream> +#include <utility> + +#include "../macros.hpp" +#include "../tests.hpp" +#include "parser.hpp" +#include "process.hpp" +#include "text.hpp" + +#define HEADER_TC(name, hdrname) \ + ATF_TEST_CASE(name); \ + ATF_TEST_CASE_HEAD(name) \ + { \ + set_md_var("descr", "Tests that the " hdrname " file can be " \ + "included on its own, without any prerequisites"); \ + } \ + ATF_TEST_CASE_BODY(name) \ + { \ + header_check(hdrname); \ + } + +#define BUILD_TC(name, sfile, descr, failmsg) \ + ATF_TEST_CASE(name); \ + ATF_TEST_CASE_HEAD(name) \ + { \ + set_md_var("descr", descr); \ + } \ + ATF_TEST_CASE_BODY(name) \ + { \ + build_check_cxx_o(*this, sfile, failmsg, true); \ + } + +#define BUILD_TC_FAIL(name, sfile, descr, failmsg) \ + ATF_TEST_CASE(name); \ + ATF_TEST_CASE_HEAD(name) \ + { \ + set_md_var("descr", descr); \ + } \ + ATF_TEST_CASE_BODY(name) \ + { \ + build_check_cxx_o(*this, sfile, failmsg, false); \ + } + +namespace atf { +namespace tests { +class tc; +} +} + +void header_check(const char*); +void build_check_cxx_o(const atf::tests::tc&, const char*, const char*, bool); +atf::fs::path get_process_helpers_path(const atf::tests::tc&); +bool grep_file(const char*, const char*); +bool grep_string(const std::string&, const char*); + +struct run_h_tc_data { + const atf::tests::vars_map& m_config; + + run_h_tc_data(const atf::tests::vars_map& config) : + m_config(config) {} +}; + +template< class TestCase > +void +run_h_tc_child(void* v) +{ + run_h_tc_data* data = static_cast< run_h_tc_data* >(v); + + TestCase tc; + tc.init(data->m_config); + tc.run("result"); + std::exit(EXIT_SUCCESS); +} + +template< class TestCase > +void +run_h_tc(atf::tests::vars_map config = atf::tests::vars_map()) +{ + run_h_tc_data data(config); + atf::process::child c = atf::process::fork( + run_h_tc_child< TestCase >, + atf::process::stream_redirect_path(atf::fs::path("stdout")), + atf::process::stream_redirect_path(atf::fs::path("stderr")), + &data); + const atf::process::status s = c.wait(); + ATF_REQUIRE(s.exited()); +} + +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 atf::parser::parse_errors& pes) { + for (std::vector< atf::parser::parse_error >::const_iterator iter = + pes.begin(); iter != pes.end(); iter++) + errors.push_back(*iter); + } catch (const atf::parser::parse_error& pe) { + ATF_FAIL("Raised a lonely parse error: " + + atf::text::to_string(pe.first) + ": " + pe.second); + } + + return std::make_pair(reader.m_calls, errors); +} + +void check_equal(const char*[], const string_vector&); + +} // 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/atf-c++/detail/text.cpp b/atf-c++/detail/text.cpp new file mode 100644 index 000000000000..66eebf0a77e7 --- /dev/null +++ b/atf-c++/detail/text.cpp @@ -0,0 +1,160 @@ +// +// 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 <cstring> + +extern "C" { +#include "../../atf-c/error.h" + +#include "../../atf-c/detail/text.h" +} + +#include "exceptions.hpp" +#include "text.hpp" + +namespace impl = atf::text; +#define IMPL_NAME "atf::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) +{ + bool b; + + atf_error_t err = atf_text_to_bool(str.c_str(), &b); + if (atf_is_error(err)) + throw_atf_error(err); + + return b; +} + +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/atf-c++/detail/text.hpp b/atf-c++/detail/text.hpp new file mode 100644 index 000000000000..6a1b027c1896 --- /dev/null +++ b/atf-c++/detail/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(_ATF_CXX_TEXT_HPP_) +#define _ATF_CXX_TEXT_HPP_ + +extern "C" { +#include <stdint.h> +} + +#include <sstream> +#include <stdexcept> +#include <string> +#include <vector> + +namespace atf { +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 atf + +#endif // !defined(_ATF_CXX_TEXT_HPP_) diff --git a/atf-c++/detail/text_test.cpp b/atf-c++/detail/text_test.cpp new file mode 100644 index 000000000000..b7c0ba1af827 --- /dev/null +++ b/atf-c++/detail/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 "../macros.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 atf::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 atf::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 atf::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 atf::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 atf::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 atf::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 atf::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 atf::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 atf::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 atf::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/atf-c++/detail/ui.cpp b/atf-c++/detail/ui.cpp new file mode 100644 index 000000000000..07bde4f87dba --- /dev/null +++ b/atf-c++/detail/ui.cpp @@ -0,0 +1,173 @@ +// +// 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 <sstream> + +#include "env.hpp" +#include "text.hpp" +#include "sanity.hpp" +#include "text.hpp" +#include "ui.hpp" + +namespace impl = atf::ui; +#define IMPL_NAME "atf::ui" + +static +size_t +terminal_width(void) +{ + static bool done = false; + static size_t width = 0; + + if (!done) { + if (atf::env::has("COLUMNS")) { + const std::string cols = atf::env::get("COLUMNS"); + if (cols.length() > 0) { + width = atf::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) +{ + PRE(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; + INV(formatted.length() == col); + size_t curcol = col; + + const size_t maxcol = terminal_width(); + + std::vector< std::string > words = atf::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) +{ + PRE(col == 0 || col >= tag.length()); + if (col == 0) + col = tag.length(); + + std::string formatted; + + std::vector< std::string > lines = atf::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/atf-c++/detail/ui.hpp b/atf-c++/detail/ui.hpp new file mode 100644 index 000000000000..1c81c56a963d --- /dev/null +++ b/atf-c++/detail/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(_ATF_CXX_UI_HPP_) +#define _ATF_CXX_UI_HPP_ + +#include <string> + +namespace atf { +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 atf + +#endif // !defined(_ATF_CXX_UI_HPP_) diff --git a/atf-c++/detail/ui_test.cpp b/atf-c++/detail/ui_test.cpp new file mode 100644 index 000000000000..918d028de150 --- /dev/null +++ b/atf-c++/detail/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 "../macros.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"; + + atf::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 = atf::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); +} |
