aboutsummaryrefslogtreecommitdiff
path: root/atf-c++/detail
diff options
context:
space:
mode:
authorMarcel Moolenaar <marcel@FreeBSD.org>2012-09-04 23:07:32 +0000
committerMarcel Moolenaar <marcel@FreeBSD.org>2012-09-04 23:07:32 +0000
commit679bf1899d7d81eaa5b2e95cba72d5db6f7491a3 (patch)
tree748bcc46e1493df6fa88441f5e3783a5e2266e13 /atf-c++/detail
Diffstat (limited to 'atf-c++/detail')
-rw-r--r--atf-c++/detail/Atffile13
-rw-r--r--atf-c++/detail/Kyuafile13
-rw-r--r--atf-c++/detail/Makefile.am.inc99
-rw-r--r--atf-c++/detail/application.cpp345
-rw-r--r--atf-c++/detail/application.hpp115
-rw-r--r--atf-c++/detail/application_test.cpp94
-rw-r--r--atf-c++/detail/env.cpp73
-rw-r--r--atf-c++/detail/env.hpp84
-rw-r--r--atf-c++/detail/env_test.cpp91
-rw-r--r--atf-c++/detail/exceptions.cpp157
-rw-r--r--atf-c++/detail/exceptions.hpp99
-rw-r--r--atf-c++/detail/exceptions_test.cpp148
-rw-r--r--atf-c++/detail/expand.cpp81
-rw-r--r--atf-c++/detail/expand.hpp82
-rw-r--r--atf-c++/detail/expand_test.cpp272
-rw-r--r--atf-c++/detail/fs.cpp517
-rw-r--r--atf-c++/detail/fs.hpp391
-rw-r--r--atf-c++/detail/fs_test.cpp545
-rw-r--r--atf-c++/detail/parser.cpp384
-rw-r--r--atf-c++/detail/parser.hpp607
-rw-r--r--atf-c++/detail/parser_test.cpp1043
-rw-r--r--atf-c++/detail/process.cpp355
-rw-r--r--atf-c++/detail/process.hpp280
-rw-r--r--atf-c++/detail/process_test.cpp357
-rw-r--r--atf-c++/detail/sanity.hpp37
-rw-r--r--atf-c++/detail/sanity_test.cpp41
-rw-r--r--atf-c++/detail/test_helpers.cpp160
-rw-r--r--atf-c++/detail/test_helpers.hpp166
-rw-r--r--atf-c++/detail/text.cpp160
-rw-r--r--atf-c++/detail/text.hpp153
-rw-r--r--atf-c++/detail/text_test.cpp390
-rw-r--r--atf-c++/detail/ui.cpp173
-rw-r--r--atf-c++/detail/ui.hpp105
-rw-r--r--atf-c++/detail/ui_test.cpp462
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);
+}