summaryrefslogtreecommitdiffstats
path: root/unit/atf-src/tools
diff options
context:
space:
mode:
Diffstat (limited to 'unit/atf-src/tools')
-rw-r--r--unit/atf-src/tools/Atffile25
-rw-r--r--unit/atf-src/tools/Kyuafile24
-rw-r--r--unit/atf-src/tools/Makefile.am.inc330
-rw-r--r--unit/atf-src/tools/application.cpp317
-rw-r--r--unit/atf-src/tools/application.hpp113
-rw-r--r--unit/atf-src/tools/application_test.cpp94
-rw-r--r--unit/atf-src/tools/atf-config.1184
-rw-r--r--unit/atf-src/tools/atf-config.cpp140
-rw-r--r--unit/atf-src/tools/atf-config_test.sh180
-rw-r--r--unit/atf-src/tools/atf-formats.5231
-rw-r--r--unit/atf-src/tools/atf-report.1168
-rw-r--r--unit/atf-src/tools/atf-report.cpp710
-rw-r--r--unit/atf-src/tools/atf-report_test.sh449
-rw-r--r--unit/atf-src/tools/atf-run.1202
-rw-r--r--unit/atf-src/tools/atf-run.cpp567
-rw-r--r--unit/atf-src/tools/atf-run_test.sh1134
-rw-r--r--unit/atf-src/tools/atf-version.156
-rw-r--r--unit/atf-src/tools/atf-version.cpp91
-rw-r--r--unit/atf-src/tools/atf.7.in192
-rw-r--r--unit/atf-src/tools/atffile.cpp348
-rw-r--r--unit/atf-src/tools/atffile.hpp92
-rw-r--r--unit/atf-src/tools/atffile_test.cpp636
-rw-r--r--unit/atf-src/tools/auto_array.hpp179
-rw-r--r--unit/atf-src/tools/auto_array_test.cpp303
-rw-r--r--unit/atf-src/tools/bad_metadata_helper.c38
-rw-r--r--unit/atf-src/tools/config.cpp135
-rw-r--r--unit/atf-src/tools/config.hpp75
-rw-r--r--unit/atf-src/tools/config_file.cpp223
-rw-r--r--unit/atf-src/tools/config_file.hpp66
-rw-r--r--unit/atf-src/tools/config_file_test.cpp395
-rw-r--r--unit/atf-src/tools/config_test.cpp221
-rw-r--r--unit/atf-src/tools/defs.hpp.in37
-rw-r--r--unit/atf-src/tools/env.cpp102
-rw-r--r--unit/atf-src/tools/env.hpp84
-rw-r--r--unit/atf-src/tools/env_test.cpp91
-rw-r--r--unit/atf-src/tools/exceptions.cpp74
-rw-r--r--unit/atf-src/tools/exceptions.hpp93
-rw-r--r--unit/atf-src/tools/expand.cpp81
-rw-r--r--unit/atf-src/tools/expand.hpp82
-rw-r--r--unit/atf-src/tools/expand_test.cpp272
-rw-r--r--unit/atf-src/tools/expect_helpers.c193
-rw-r--r--unit/atf-src/tools/fail_helper.cpp45
-rw-r--r--unit/atf-src/tools/fs.cpp744
-rw-r--r--unit/atf-src/tools/fs.hpp377
-rw-r--r--unit/atf-src/tools/fs_test.cpp743
-rwxr-xr-xunit/atf-src/tools/generate-revision.sh142
-rw-r--r--unit/atf-src/tools/io.cpp356
-rw-r--r--unit/atf-src/tools/io.hpp436
-rw-r--r--unit/atf-src/tools/io_test.cpp471
-rw-r--r--unit/atf-src/tools/misc_helpers.cpp448
-rw-r--r--unit/atf-src/tools/parser.cpp384
-rw-r--r--unit/atf-src/tools/parser.hpp607
-rw-r--r--unit/atf-src/tools/parser_test.cpp1043
-rw-r--r--unit/atf-src/tools/pass_helper.cpp44
-rw-r--r--unit/atf-src/tools/process.cpp492
-rw-r--r--unit/atf-src/tools/process.hpp324
-rw-r--r--unit/atf-src/tools/process_helpers.c117
-rw-r--r--unit/atf-src/tools/process_test.cpp360
-rw-r--r--unit/atf-src/tools/reader.cpp440
-rw-r--r--unit/atf-src/tools/reader.hpp89
-rw-r--r--unit/atf-src/tools/reader_test.cpp986
-rw-r--r--unit/atf-src/tools/requirements.cpp325
-rw-r--r--unit/atf-src/tools/requirements.hpp47
-rw-r--r--unit/atf-src/tools/requirements_test.cpp400
-rw-r--r--unit/atf-src/tools/sample/atf-run.hooks23
-rw-r--r--unit/atf-src/tools/sample/common.conf11
-rw-r--r--unit/atf-src/tools/several_tcs_helper.c67
-rw-r--r--unit/atf-src/tools/share/atf-run.hooks94
-rw-r--r--unit/atf-src/tools/signals.cpp146
-rw-r--r--unit/atf-src/tools/signals.hpp92
-rw-r--r--unit/atf-src/tools/signals_test.cpp275
-rw-r--r--unit/atf-src/tools/test-program.cpp790
-rw-r--r--unit/atf-src/tools/test-program.hpp157
-rw-r--r--unit/atf-src/tools/test_helpers.hpp116
-rw-r--r--unit/atf-src/tools/test_program_test.cpp1023
-rw-r--r--unit/atf-src/tools/tests-results.css199
-rw-r--r--unit/atf-src/tools/tests-results.dtd61
-rw-r--r--unit/atf-src/tools/tests-results.xsl564
-rw-r--r--unit/atf-src/tools/text.cpp158
-rw-r--r--unit/atf-src/tools/text.hpp153
-rw-r--r--unit/atf-src/tools/text_test.cpp390
-rw-r--r--unit/atf-src/tools/timers.cpp211
-rw-r--r--unit/atf-src/tools/timers.hpp83
-rw-r--r--unit/atf-src/tools/ui.cpp172
-rw-r--r--unit/atf-src/tools/ui.hpp105
-rw-r--r--unit/atf-src/tools/ui_test.cpp462
-rw-r--r--unit/atf-src/tools/user.cpp102
-rw-r--r--unit/atf-src/tools/user.hpp52
-rw-r--r--unit/atf-src/tools/user_test.cpp148
-rw-r--r--unit/atf-src/tools/zero_tcs_helper.c36
90 files changed, 24137 insertions, 0 deletions
diff --git a/unit/atf-src/tools/Atffile b/unit/atf-src/tools/Atffile
new file mode 100644
index 0000000..8896078
--- /dev/null
+++ b/unit/atf-src/tools/Atffile
@@ -0,0 +1,25 @@
+Content-Type: application/X-atf-atffile; version="1"
+
+prop: test-suite = atf
+
+tp: atf-config_test
+tp: atf-report_test
+tp: atf-run_test
+tp: application_test
+tp: atffile_test
+tp: auto_array_test
+tp: config_test
+tp: config_file_test
+tp: env_test
+tp: expand_test
+tp: fs_test
+tp: io_test
+tp: parser_test
+tp: process_test
+tp: reader_test
+tp: requirements_test
+tp: signals_test
+tp: test_program_test
+tp: text_test
+tp: ui_test
+tp: user_test
diff --git a/unit/atf-src/tools/Kyuafile b/unit/atf-src/tools/Kyuafile
new file mode 100644
index 0000000..09c5145
--- /dev/null
+++ b/unit/atf-src/tools/Kyuafile
@@ -0,0 +1,24 @@
+syntax("kyuafile", 1)
+
+test_suite("atf")
+
+atf_test_program{name="atf-config_test"}
+atf_test_program{name="atf-report_test"}
+atf_test_program{name="atf-run_test"}
+atf_test_program{name="application_test"}
+atf_test_program{name="atffile_test"}
+atf_test_program{name="auto_array_test"}
+atf_test_program{name="config_test"}
+atf_test_program{name="config_file_test"}
+atf_test_program{name="env_test"}
+atf_test_program{name="expand_test"}
+atf_test_program{name="fs_test"}
+atf_test_program{name="io_test"}
+atf_test_program{name="parser_test"}
+atf_test_program{name="process_test"}
+atf_test_program{name="reader_test"}
+atf_test_program{name="requirements_test"}
+atf_test_program{name="signals_test"}
+atf_test_program{name="test_program_test"}
+atf_test_program{name="text_test"}
+atf_test_program{name="ui_test"}
diff --git a/unit/atf-src/tools/Makefile.am.inc b/unit/atf-src/tools/Makefile.am.inc
new file mode 100644
index 0000000..00c3b13
--- /dev/null
+++ b/unit/atf-src/tools/Makefile.am.inc
@@ -0,0 +1,330 @@
+#
+# Automated Testing Framework (atf)
+#
+# Copyright (c) 2007 The NetBSD Foundation, Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
+# CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+# IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
+# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+# GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+# IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+# IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+
+TOOLS_CPPFLAGS = -I$(top_srcdir)/tools -I$(top_builddir)/tools
+TOOLS_LDADD = tools/libtools.a
+
+noinst_LIBRARIES = tools/libtools.a
+tools_libtools_a_SOURCES = tools/application.cpp \
+ tools/application.hpp \
+ tools/atffile.cpp \
+ tools/atffile.hpp \
+ tools/auto_array.hpp \
+ tools/config.cpp \
+ tools/config.hpp \
+ tools/config_file.cpp \
+ tools/config_file.hpp \
+ tools/env.cpp \
+ tools/env.hpp \
+ tools/exceptions.cpp \
+ tools/exceptions.hpp \
+ tools/expand.cpp \
+ tools/expand.hpp \
+ tools/fs.cpp \
+ tools/fs.hpp \
+ tools/io.cpp \
+ tools/io.hpp \
+ tools/parser.cpp \
+ tools/parser.hpp \
+ tools/process.cpp \
+ tools/process.hpp \
+ tools/reader.cpp \
+ tools/reader.hpp \
+ tools/requirements.cpp \
+ tools/requirements.hpp \
+ tools/signals.cpp \
+ tools/signals.hpp \
+ tools/test-program.cpp \
+ tools/test-program.hpp \
+ tools/test_helpers.hpp \
+ tools/text.cpp \
+ tools/text.hpp \
+ tools/timers.cpp \
+ tools/timers.hpp \
+ tools/ui.cpp \
+ tools/ui.hpp \
+ tools/user.cpp \
+ tools/user.hpp
+nodist_tools_libtools_a_SOURCES = tools/defs.hpp
+tools_libtools_a_CPPFLAGS = "-DATF_ARCH=\"$(atf_arch)\"" \
+ "-DATF_BUILD_CC=\"$(ATF_BUILD_CC)\"" \
+ "-DATF_BUILD_CFLAGS=\"$(ATF_BUILD_CFLAGS)\"" \
+ "-DATF_BUILD_CPP=\"$(ATF_BUILD_CPP)\"" \
+ "-DATF_BUILD_CPPFLAGS=\"$(ATF_BUILD_CPPFLAGS)\"" \
+ "-DATF_BUILD_CXX=\"$(ATF_BUILD_CXX)\"" \
+ "-DATF_BUILD_CXXFLAGS=\"$(ATF_BUILD_CXXFLAGS)\"" \
+ "-DATF_CONFDIR=\"$(atf_confdir)\"" \
+ "-DATF_INCLUDEDIR=\"$(includedir)\"" \
+ "-DATF_LIBDIR=\"$(libdir)\"" \
+ "-DATF_LIBEXECDIR=\"$(libexecdir)\"" \
+ "-DATF_MACHINE=\"$(atf_machine)\"" \
+ "-DATF_PKGDATADIR=\"$(pkgdatadir)\"" \
+ "-DATF_SHELL=\"$(ATF_SHELL)\"" \
+ "-DATF_WORKDIR=\"$(ATF_WORKDIR)\"" \
+ $(TOOLS_CPPFLAGS)
+
+# XXX For some reason, the nodist line above does not work as expected.
+# Work this problem around.
+DIST_HOOKS += kill-defs-hpp
+kill-defs-hpp:
+ rm -f $(distdir)/tools/defs.hpp
+
+
+
+bin_PROGRAMS += tools/atf-config
+tools_atf_config_SOURCES = tools/atf-config.cpp
+tools_atf_config_CPPFLAGS = $(TOOLS_CPPFLAGS)
+tools_atf_config_LDADD = $(TOOLS_LDADD)
+dist_man_MANS += tools/atf-config.1
+
+bin_PROGRAMS += tools/atf-report
+tools_atf_report_SOURCES = tools/atf-report.cpp
+tools_atf_report_CPPFLAGS = $(TOOLS_CPPFLAGS)
+tools_atf_report_LDADD = $(TOOLS_LDADD)
+dist_man_MANS += tools/atf-report.1
+
+bin_PROGRAMS += tools/atf-run
+tools_atf_run_CPPFLAGS = $(TOOLS_CPPFLAGS) "-DGDB=\"$(GDB)\""
+tools_atf_run_SOURCES = tools/atf-run.cpp
+tools_atf_run_LDADD = $(TOOLS_LDADD)
+dist_man_MANS += tools/atf-run.1
+
+bin_PROGRAMS += tools/atf-version
+tools_atf_version_SOURCES = tools/atf-version.cpp
+nodist_tools_atf_version_SOURCES = tools/revision.h
+tools_atf_version_CPPFLAGS = $(TOOLS_CPPFLAGS)
+tools_atf_version_LDADD = $(TOOLS_LDADD)
+dist_man_MANS += tools/atf-version.1
+
+EXTRA_DIST += tools/generate-revision.sh
+
+BUILT_SOURCES += tools/revision.h
+CLEANFILES += tools/revision.h
+tools/revision.h: tools/revision.h.stamp
+ @test -d tools || mkdir -p tools
+ @cmp -s tools/revision.h tools/revision.h.stamp || \
+ cp -p tools/revision.h.stamp tools/revision.h
+
+CLEANFILES += tools/revision.h.stamp
+PHONY_TARGETS += tools/revision.h.stamp
+tools/revision.h.stamp:
+ @test -d tools || mkdir -p tools
+ @$(top_srcdir)/tools/generate-revision.sh \
+ -g "$(GIT)" -r $(top_srcdir) -o tools/revision.h.stamp \
+ -v $(PACKAGE_VERSION)
+
+#man_MANS += tools/atf.7
+#CLEANFILES += tools/atf.7
+#EXTRA_DIST += tools/atf.7.in
+
+dist_man_MANS += tools/atf-formats.5
+
+tools/atf.7: $(srcdir)/tools/atf.7.in
+ test -d tools || mkdir -p tools
+ sed -e 's#__DOCDIR__#$(docdir)#g' \
+ -e 's#__TESTSDIR__#$(testsdir)#g' \
+ <$(srcdir)/tools/atf.7.in >tools/atf.7.tmp
+ mv tools/atf.7.tmp tools/atf.7
+
+
+
+cssdir = $(atf_cssdir)
+css_DATA = tools/tests-results.css
+EXTRA_DIST += $(css_DATA)
+
+dtddir = $(atf_dtddir)
+dtd_DATA = tools/tests-results.dtd
+EXTRA_DIST += $(dtd_DATA)
+
+egdir = $(atf_egdir)
+eg_DATA = tools/sample/atf-run.hooks
+eg_DATA += tools/sample/common.conf
+EXTRA_DIST += $(eg_DATA)
+
+hooksdir = $(pkgdatadir)
+hooks_DATA = tools/share/atf-run.hooks
+EXTRA_DIST += $(hooks_DATA)
+
+xsldir = $(atf_xsldir)
+xsl_DATA = tools/tests-results.xsl
+EXTRA_DIST += $(xsl_DATA)
+
+
+
+tests_tools_DATA = tools/Atffile tools/Kyuafile
+tests_toolsdir = $(pkgtestsdir)/tools
+EXTRA_DIST += $(tests_tools_DATA)
+
+tests_tools_SCRIPTS = tools/atf-config_test
+CLEANFILES += tools/atf-config_test
+EXTRA_DIST += tools/atf-config_test.sh
+tools/atf-config_test: $(srcdir)/tools/atf-config_test.sh
+ @test -d tools || mkdir -p tools
+ @src="$(srcdir)/tools/atf-config_test.sh"; \
+ dst="tools/atf-config_test"; $(BUILD_SH_TP)
+
+tests_tools_SCRIPTS += tools/atf-report_test
+CLEANFILES += tools/atf-report_test
+EXTRA_DIST += tools/atf-report_test.sh
+tools/atf-report_test: $(srcdir)/tools/atf-report_test.sh
+ @test -d tools || mkdir -p tools
+ @src="$(srcdir)/tools/atf-report_test.sh"; \
+ dst="tools/atf-report_test"; $(BUILD_SH_TP)
+
+tests_tools_SCRIPTS += tools/atf-run_test
+CLEANFILES += tools/atf-run_test
+EXTRA_DIST += tools/atf-run_test.sh
+tools/atf-run_test: $(srcdir)/tools/atf-run_test.sh
+ @test -d tools || mkdir -p tools
+ @src="$(srcdir)/tools/atf-run_test.sh"; \
+ dst="tools/atf-run_test"; $(BUILD_SH_TP)
+
+tests_tools_PROGRAMS = tools/application_test
+tools_application_test_SOURCES = tools/application_test.cpp
+tools_application_test_CPPFLAGS = $(TOOLS_CPPFLAGS)
+tools_application_test_LDADD = $(TOOLS_LDADD) $(ATF_CXX_LIBS)
+
+tests_tools_PROGRAMS += tools/atffile_test
+tools_atffile_test_SOURCES = tools/atffile_test.cpp
+tools_atffile_test_CPPFLAGS = $(TOOLS_CPPFLAGS)
+tools_atffile_test_LDADD = $(TOOLS_LDADD) $(ATF_CXX_LIBS)
+
+tests_tools_PROGRAMS += tools/auto_array_test
+tools_auto_array_test_SOURCES = tools/auto_array_test.cpp
+tools_auto_array_test_CPPFLAGS = $(TOOLS_CPPFLAGS)
+tools_auto_array_test_LDADD = $(TOOLS_LDADD) $(ATF_CXX_LIBS)
+
+tests_tools_PROGRAMS += tools/bad_metadata_helper
+tools_bad_metadata_helper_SOURCES = tools/bad_metadata_helper.c
+tools_bad_metadata_helper_LDADD = libatf-c.la
+
+tests_tools_PROGRAMS += tools/config_test
+tools_config_test_SOURCES = tools/config_test.cpp
+tools_config_test_CPPFLAGS = $(TOOLS_CPPFLAGS)
+tools_config_test_LDADD = $(TOOLS_LDADD) $(ATF_CXX_LIBS)
+
+tests_tools_PROGRAMS += tools/config_file_test
+tools_config_file_test_SOURCES = tools/config_file_test.cpp
+tools_config_file_test_CPPFLAGS = $(TOOLS_CPPFLAGS)
+tools_config_file_test_LDADD = $(TOOLS_LDADD) $(ATF_CXX_LIBS)
+
+tests_tools_PROGRAMS += tools/env_test
+tools_env_test_SOURCES = tools/env_test.cpp
+tools_env_test_CPPFLAGS = $(TOOLS_CPPFLAGS)
+tools_env_test_LDADD = $(TOOLS_LDADD) $(ATF_CXX_LIBS)
+
+tests_tools_PROGRAMS += tools/expand_test
+tools_expand_test_SOURCES = tools/expand_test.cpp
+tools_expand_test_CPPFLAGS = $(TOOLS_CPPFLAGS)
+tools_expand_test_LDADD = $(TOOLS_LDADD) $(ATF_CXX_LIBS)
+
+tests_tools_PROGRAMS += tools/expect_helpers
+tools_expect_helpers_SOURCES = tools/expect_helpers.c
+tools_expect_helpers_LDADD = libatf-c.la
+
+tests_tools_PROGRAMS += tools/fail_helper
+tools_fail_helper_SOURCES = tools/fail_helper.cpp
+tools_fail_helper_LDADD = $(ATF_CXX_LIBS)
+
+tests_tools_PROGRAMS += tools/fs_test
+tools_fs_test_SOURCES = tools/fs_test.cpp
+tools_fs_test_CPPFLAGS = $(TOOLS_CPPFLAGS)
+tools_fs_test_LDADD = $(TOOLS_LDADD) $(ATF_CXX_LIBS)
+
+tests_tools_PROGRAMS += tools/io_test
+tools_io_test_SOURCES = tools/io_test.cpp
+tools_io_test_CPPFLAGS = $(TOOLS_CPPFLAGS)
+tools_io_test_LDADD = $(TOOLS_LDADD) $(ATF_CXX_LIBS)
+
+tests_tools_PROGRAMS += tools/misc_helpers
+tools_misc_helpers_SOURCES = tools/misc_helpers.cpp
+tools_misc_helpers_CPPFLAGS = $(TOOLS_CPPFLAGS)
+tools_misc_helpers_LDADD = $(TOOLS_LDADD) $(ATF_CXX_LIBS)
+
+tests_tools_PROGRAMS += tools/parser_test
+tools_parser_test_SOURCES = tools/parser_test.cpp
+tools_parser_test_CPPFLAGS = $(TOOLS_CPPFLAGS)
+tools_parser_test_LDADD = $(TOOLS_LDADD) $(ATF_CXX_LIBS)
+
+tests_tools_PROGRAMS += tools/process_test
+tools_process_test_SOURCES = tools/process_test.cpp
+tools_process_test_CPPFLAGS = $(TOOLS_CPPFLAGS)
+tools_process_test_LDADD = $(TOOLS_LDADD) $(ATF_CXX_LIBS)
+
+tests_tools_PROGRAMS += tools/pass_helper
+tools_pass_helper_SOURCES = tools/pass_helper.cpp
+tools_pass_helper_LDADD = $(ATF_CXX_LIBS)
+
+tests_tools_PROGRAMS += tools/process_helpers
+tools_process_helpers_SOURCES = tools/process_helpers.c
+
+tests_tools_PROGRAMS += tools/reader_test
+tools_reader_test_SOURCES = tools/reader_test.cpp
+tools_reader_test_CPPFLAGS = $(TOOLS_CPPFLAGS)
+tools_reader_test_LDADD = $(TOOLS_LDADD) $(ATF_CXX_LIBS)
+
+tests_tools_PROGRAMS += tools/requirements_test
+tools_requirements_test_SOURCES = tools/requirements_test.cpp
+tools_requirements_test_CPPFLAGS = $(TOOLS_CPPFLAGS)
+tools_requirements_test_LDADD = $(TOOLS_LDADD) $(ATF_CXX_LIBS)
+
+tests_tools_PROGRAMS += tools/several_tcs_helper
+tools_several_tcs_helper_SOURCES = tools/several_tcs_helper.c
+tools_several_tcs_helper_LDADD = libatf-c.la
+
+tests_tools_PROGRAMS += tools/signals_test
+tools_signals_test_SOURCES = tools/signals_test.cpp tools/signals.cpp
+tools_signals_test_CPPFLAGS = $(TOOLS_CPPFLAGS)
+tools_signals_test_LDADD = $(TOOLS_LDADD) $(ATF_CXX_LIBS)
+
+tests_tools_PROGRAMS += tools/test_program_test
+tools_test_program_test_SOURCES = tools/test_program_test.cpp
+tools_test_program_test_CPPFLAGS = $(TOOLS_CPPFLAGS)
+tools_test_program_test_LDADD = $(TOOLS_LDADD) $(ATF_CXX_LIBS)
+
+tests_tools_PROGRAMS += tools/text_test
+tools_text_test_SOURCES = tools/text_test.cpp tools/text.cpp
+tools_text_test_CPPFLAGS = $(TOOLS_CPPFLAGS)
+tools_text_test_LDADD = $(TOOLS_LDADD) $(ATF_CXX_LIBS)
+
+tests_tools_PROGRAMS += tools/ui_test
+tools_ui_test_SOURCES = tools/ui_test.cpp tools/ui.cpp
+tools_ui_test_CPPFLAGS = $(TOOLS_CPPFLAGS)
+tools_ui_test_LDADD = $(TOOLS_LDADD) $(ATF_CXX_LIBS)
+
+tests_tools_PROGRAMS += tools/user_test
+tools_user_test_SOURCES = tools/user_test.cpp tools/user.cpp
+tools_user_test_CPPFLAGS = $(TOOLS_CPPFLAGS)
+tools_user_test_LDADD = $(TOOLS_LDADD) $(ATF_CXX_LIBS)
+
+tests_tools_PROGRAMS += tools/zero_tcs_helper
+tools_zero_tcs_helper_SOURCES = tools/zero_tcs_helper.c
+tools_zero_tcs_helper_LDADD = libatf-c.la
+
+# vim: syntax=make:noexpandtab:shiftwidth=8:softtabstop=8
diff --git a/unit/atf-src/tools/application.cpp b/unit/atf-src/tools/application.cpp
new file mode 100644
index 0000000..d32fd7e
--- /dev/null
+++ b/unit/atf-src/tools/application.cpp
@@ -0,0 +1,317 @@
+//
+// Automated Testing Framework (atf)
+//
+// Copyright (c) 2007 The NetBSD Foundation, Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
+// CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+// IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
+// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+// IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+
+#if defined(HAVE_CONFIG_H)
+#include "config.h"
+#endif
+
+extern "C" {
+#include <unistd.h>
+}
+
+#include <cassert>
+#include <cstdarg>
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+#include <iostream>
+
+#include "application.hpp"
+#include "defs.hpp"
+#include "ui.hpp"
+
+#if !defined(HAVE_VSNPRINTF_IN_STD)
+namespace std {
+using ::vsnprintf;
+}
+#endif // !defined(HAVE_VSNPRINTF_IN_STD)
+
+namespace impl = tools::application;
+#define IMPL_NAME "tools::application"
+
+// ------------------------------------------------------------------------
+// The "usage_error" class.
+// ------------------------------------------------------------------------
+
+impl::usage_error::usage_error(const char *fmt, ...)
+ throw() :
+ std::runtime_error("usage_error; message unformatted")
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ std::vsnprintf(m_text, sizeof(m_text), fmt, ap);
+ va_end(ap);
+}
+
+impl::usage_error::~usage_error(void)
+ throw()
+{
+}
+
+const char*
+impl::usage_error::what(void)
+ const throw()
+{
+ return m_text;
+}
+
+// ------------------------------------------------------------------------
+// The "application" class.
+// ------------------------------------------------------------------------
+
+impl::option::option(char ch,
+ const std::string& a,
+ const std::string& desc) :
+ m_character(ch),
+ m_argument(a),
+ m_description(desc)
+{
+}
+
+bool
+impl::option::operator<(const impl::option& o)
+ const
+{
+ return m_character < o.m_character;
+}
+
+impl::app::app(const std::string& description,
+ const std::string& manpage,
+ const std::string& global_manpage) :
+ m_hflag(false),
+ m_argc(-1),
+ m_argv(NULL),
+ m_prog_name(NULL),
+ m_description(description),
+ m_manpage(manpage),
+ m_global_manpage(global_manpage)
+{
+}
+
+impl::app::~app(void)
+{
+}
+
+bool
+impl::app::inited(void)
+{
+ return m_argc != -1;
+}
+
+impl::app::options_set
+impl::app::options(void)
+{
+ options_set opts = specific_options();
+ opts.insert(option('h', "", "Shows this help message"));
+ return opts;
+}
+
+std::string
+impl::app::specific_args(void)
+ const
+{
+ return "";
+}
+
+impl::app::options_set
+impl::app::specific_options(void)
+ const
+{
+ return options_set();
+}
+
+void
+impl::app::process_option(int ch ATF_DEFS_ATTRIBUTE_UNUSED,
+ const char* arg ATF_DEFS_ATTRIBUTE_UNUSED)
+{
+}
+
+void
+impl::app::process_options(void)
+{
+ assert(inited());
+
+ std::string optstr;
+#if defined(HAVE_GNU_GETOPT)
+ optstr += '+'; // Turn on POSIX behavior.
+#endif
+ optstr += ':';
+ {
+ options_set opts = options();
+ for (options_set::const_iterator iter = opts.begin();
+ iter != opts.end(); iter++) {
+ const option& opt = (*iter);
+
+ optstr += opt.m_character;
+ if (!opt.m_argument.empty())
+ optstr += ':';
+ }
+ }
+
+ int ch;
+ const int old_opterr = ::opterr;
+ ::opterr = 0;
+ while ((ch = ::getopt(m_argc, m_argv, optstr.c_str())) != -1) {
+ switch (ch) {
+ case 'h':
+ m_hflag = true;
+ break;
+
+ case ':':
+ throw usage_error("Option -%c requires an argument.",
+ ::optopt);
+
+ case '?':
+ throw usage_error("Unknown option -%c.", ::optopt);
+
+ default:
+ process_option(ch, ::optarg);
+ }
+ }
+ m_argc -= ::optind;
+ m_argv += ::optind;
+
+ // Clear getopt state just in case the test wants to use it.
+ opterr = old_opterr;
+ optind = 1;
+#if defined(HAVE_OPTRESET)
+ optreset = 1;
+#endif
+}
+
+void
+impl::app::usage(std::ostream& os)
+{
+ assert(inited());
+
+ std::string args = specific_args();
+ if (!args.empty())
+ args = " " + args;
+ os << ui::format_text_with_tag(std::string(m_prog_name) + " [options]" +
+ args, "Usage: ", false) << "\n\n"
+ << ui::format_text(m_description) << "\n\n";
+
+ options_set opts = options();
+ assert(!opts.empty());
+ os << "Available options:\n";
+ size_t coldesc = 0;
+ for (options_set::const_iterator iter = opts.begin();
+ iter != opts.end(); iter++) {
+ const option& opt = (*iter);
+
+ if (opt.m_argument.length() + 1 > coldesc)
+ coldesc = opt.m_argument.length() + 1;
+ }
+ for (options_set::const_iterator iter = opts.begin();
+ iter != opts.end(); iter++) {
+ const option& opt = (*iter);
+
+ std::string tag = std::string(" -") + opt.m_character;
+ if (opt.m_argument.empty())
+ tag += " ";
+ else
+ tag += " " + opt.m_argument + " ";
+ os << ui::format_text_with_tag(opt.m_description, tag, false,
+ coldesc + 10) << "\n";
+ }
+ os << "\n";
+
+ std::string gmp;
+ if (!m_global_manpage.empty())
+ gmp = " and " + m_global_manpage;
+ os << ui::format_text("For more details please see " + m_manpage +
+ gmp + ".")
+ << "\n";
+}
+
+int
+impl::app::run(int argc, char* const* argv)
+{
+ assert(argc > 0);
+ assert(argv != NULL);
+
+ m_argc = argc;
+ m_argv = argv;
+
+ m_argv0 = m_argv[0];
+
+ m_prog_name = std::strrchr(m_argv[0], '/');
+ if (m_prog_name == NULL)
+ m_prog_name = m_argv[0];
+ else
+ m_prog_name++;
+
+ // Libtool workaround: if running from within the source tree (binaries
+ // that are not installed yet), skip the "lt-" prefix added to files in
+ // the ".libs" directory to show the real (not temporary) name.
+ if (std::strncmp(m_prog_name, "lt-", 3) == 0)
+ m_prog_name += 3;
+
+ const std::string bug =
+ std::string("This is probably a bug in ") + m_prog_name +
+ " or one of the libraries it uses. Please report this problem to "
+ PACKAGE_BUGREPORT " and provide as many details as possible "
+ "describing how you got to this condition.";
+
+ int errcode;
+ try {
+ int oldargc = m_argc;
+
+ process_options();
+
+ if (m_hflag) {
+ if (oldargc != 2)
+ throw usage_error("-h must be given alone.");
+
+ usage(std::cout);
+ errcode = EXIT_SUCCESS;
+ } else
+ errcode = main();
+ } catch (const usage_error& e) {
+ std::cerr << ui::format_error(m_prog_name, e.what()) << "\n"
+ << ui::format_info(m_prog_name, std::string("Type `") +
+ m_prog_name + " -h' for more details.")
+ << "\n";
+ errcode = EXIT_FAILURE;
+ } catch (const std::runtime_error& e) {
+ std::cerr << ui::format_error(m_prog_name, std::string(e.what()))
+ << "\n";
+ errcode = EXIT_FAILURE;
+ } catch (const std::exception& e) {
+ std::cerr << ui::format_error(m_prog_name, std::string("Caught "
+ "unexpected error: ") + e.what() + "\n" + bug) << "\n";
+ errcode = EXIT_FAILURE;
+ } catch (...) {
+ std::cerr << ui::format_error(m_prog_name, std::string("Caught "
+ "unknown error\n") + bug) << "\n";
+ errcode = EXIT_FAILURE;
+ }
+ return errcode;
+}
diff --git a/unit/atf-src/tools/application.hpp b/unit/atf-src/tools/application.hpp
new file mode 100644
index 0000000..5a8d57c
--- /dev/null
+++ b/unit/atf-src/tools/application.hpp
@@ -0,0 +1,113 @@
+//
+// Automated Testing Framework (atf)
+//
+// Copyright (c) 2007 The NetBSD Foundation, Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
+// CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+// IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
+// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+// IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+
+#if !defined(TOOLS_APPLICATION_HPP)
+#define TOOLS_APPLICATION_HPP
+
+#include <ostream>
+#include <set>
+#include <stdexcept>
+#include <string>
+
+namespace tools {
+namespace application {
+
+// ------------------------------------------------------------------------
+// The "usage_error" class.
+// ------------------------------------------------------------------------
+
+class usage_error : public std::runtime_error {
+ char m_text[4096];
+
+public:
+ usage_error(const char*, ...) throw();
+ ~usage_error(void) throw();
+
+ const char* what(void) const throw();
+};
+
+// ------------------------------------------------------------------------
+// The "option" class.
+// ------------------------------------------------------------------------
+
+class option {
+ char m_character;
+ std::string m_argument;
+ std::string m_description;
+
+ friend class app;
+
+public:
+ option(char, const std::string&, const std::string&);
+
+ bool operator<(const option&) const;
+};
+
+// ------------------------------------------------------------------------
+// The "app" class.
+// ------------------------------------------------------------------------
+
+class app {
+ bool m_hflag;
+
+ void process_options(void);
+ void usage(std::ostream&);
+
+ bool inited(void);
+
+protected:
+ typedef std::set< option > options_set;
+
+ int m_argc;
+ char* const* m_argv;
+
+ const char* m_argv0;
+ const char* m_prog_name;
+ std::string m_description;
+ std::string m_manpage, m_global_manpage;
+
+ options_set options(void);
+
+ // To be redefined.
+ virtual std::string specific_args(void) const;
+ virtual options_set specific_options(void) const;
+ virtual void process_option(int, const char*);
+ virtual int main(void) = 0;
+
+public:
+ app(const std::string&, const std::string&, const std::string&);
+ virtual ~app(void);
+
+ int run(int, char* const*);
+};
+
+} // namespace application
+} // namespace tools
+
+#endif // !defined(TOOLS_APPLICATION_HPP)
diff --git a/unit/atf-src/tools/application_test.cpp b/unit/atf-src/tools/application_test.cpp
new file mode 100644
index 0000000..a9013cd
--- /dev/null
+++ b/unit/atf-src/tools/application_test.cpp
@@ -0,0 +1,94 @@
+//
+// Automated Testing Framework (atf)
+//
+// Copyright (c) 2009 The NetBSD Foundation, Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
+// CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+// IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
+// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+// IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+
+extern "C" {
+#include <unistd.h>
+}
+
+#include <atf-c++.hpp>
+
+#include "application.hpp"
+
+class getopt_app : public tools::application::app {
+public:
+ getopt_app(void) : app("description", "manpage", "other") {}
+
+ int main(void)
+ {
+ // Provide an option that is unknown to the application driver and
+ // one that is, together with an argument that would be swallowed by
+ // the test program option if it were recognized.
+ int argc = 4;
+ char arg1[] = "progname";
+ char arg2[] = "-Z";
+ char arg3[] = "-s";
+ char arg4[] = "foo";
+ char *const argv[] = { arg1, arg2, arg3, arg4, NULL };
+
+ int ch;
+ bool zflag;
+
+ // Given that this obviously is an application, and that we used the
+ // same driver to start, we can test getopt(3) right here without doing
+ // any fancy stuff.
+ zflag = false;
+ while ((ch = ::getopt(argc, argv, ":Z")) != -1) {
+ switch (ch) {
+ case 'Z':
+ zflag = true;
+ break;
+
+ case '?':
+ default:
+ if (optopt != 's')
+ ATF_FAIL("Unexpected unknown option found");
+ }
+ }
+
+ ATF_REQUIRE(zflag);
+ ATF_REQUIRE_EQ(1, argc - optind);
+ ATF_REQUIRE_EQ(std::string("foo"), argv[optind]);
+
+ return 0;
+ }
+};
+
+ATF_TEST_CASE_WITHOUT_HEAD(getopt);
+ATF_TEST_CASE_BODY(getopt)
+{
+ int argc = 1;
+ char arg1[] = "progname";
+ char *const argv[] = { arg1, NULL };
+ ATF_REQUIRE_EQ(0, getopt_app().run(argc, argv));
+}
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ ATF_ADD_TEST_CASE(tcs, getopt);
+}
diff --git a/unit/atf-src/tools/atf-config.1 b/unit/atf-src/tools/atf-config.1
new file mode 100644
index 0000000..deae6f6
--- /dev/null
+++ b/unit/atf-src/tools/atf-config.1
@@ -0,0 +1,184 @@
+.\"
+.\" Automated Testing Framework (atf)
+.\"
+.\" Copyright (c) 2007 The NetBSD Foundation, Inc.
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
+.\" CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+.\" INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+.\" MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+.\" IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
+.\" DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+.\" GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+.\" INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+.\" IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+.\" OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+.\" IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+.\"
+.Dd March 14, 2009
+.Dt ATF-CONFIG 1
+.Os
+.Sh NAME
+.Nm atf-config
+.Nd queries static configuration information of ATF
+.Sh SYNOPSIS
+.Nm
+.Op Fl t
+.Op Ar var1 Op Ar .. varN
+.Nm
+.Fl h
+.Sh DESCRIPTION
+.Nm
+is a utility that queries static configuration information of ATF.
+Static configuration refers to all those values for settings that
+were built into the ATF binaries at build time.
+.Pp
+In the first synopsis form,
+.Nm
+will print variable-value pairs for all built-in static variables if
+no variable names are provided as arguments.
+If any is provided, it will only print the variable-value pairs for
+those variables.
+The output of the utility does not use the
+.Sq =
+symbol to separate the variable name from its corresponding value in
+an attempt to avoid sourcing the output in shell scripts or Makefiles.
+If you need to do that, the
+.Fl t
+flag allows you to query the value of individual variables without any
+surrounding text.
+.Pp
+In the second synopsis form,
+.Nm
+will print information about all supported options and their purpose.
+.Pp
+The following options are available:
+.Bl -tag -width flag
+.It Fl h
+Shows a short summary of all available options and their purpose.
+.It Fl t
+Changes the output of the utility to show the variable values, one
+per line, without the variable names.
+.El
+.Ss Static configuration variables
+The following list describes all the variables that are part of ATF's
+static configuration:
+.Bl -tag -width atfXbuildXcppflagsXX
+.It Va atf_arch
+The architecture name detected by ATF.
+This is derived from
+.Va atf_machine
+because it is a subset of it.
+Given that this name might be misdetected, it is provided to the user
+as a configuration variable so that he can fix its value temporarily
+until a real fix is incorporated into mainstream sources.
+.It Va atf_build_cc
+The C compiler used by the ATF checks that provide build-time tests.
+.It Va atf_build_cflags
+The C compiler flags used by the ATF checks that provide build-time tests.
+.It Va atf_build_cpp
+The C/C++ preprocessor used by the ATF checks that provide build-time tests.
+.It Va atf_build_cppflags
+The C/C++ preprocessor flags used by the ATF checks that provide build-time
+tests.
+.It Va atf_build_cxx
+The C++ compiler used by the ATF checks that provide build-time tests.
+.It Va atf_build_cxxflags
+The C++ compiler flags used by the ATF checks that provide build-time tests.
+.It Va atf_confdir
+The path to the directory that contains the system-wide configuration
+files for ATF.
+.It Va atf_includedir
+The path to the directory that contains the ATF header files.
+.It Va atf_libdir
+The path to the directory that contains the ATF libraries.
+.It Va atf_libexecdir
+The path to the directory that contains the auxiliary utilities of ATF,
+used internally by the public tools.
+.It Va atf_machine
+The machine type name detected by ATF.
+This should not be tunable but is provided for symmetry with
+.Va atf_arch .
+.It Va atf_pkgdatadir
+The path to the directory that contains the files that form the ATF's
+shell-scripting library.
+.It Va atf_shell
+The path to the shell interpreter that will be used by ATF.
+.It Va atf_workdir
+The path to the temporary directory that the utilities and the test
+programs will use to store temporary files in.
+.El
+.Sh ENVIRONMENT
+Every variable that is part of the static configuration can be
+overridden at run-time by defining an environment variable.
+This environment variable has the exact same name as the one shown by
+.Nm
+except that the name is all composed of uppercase letters.
+.Pp
+In general, empty values in the environment will be ignored unless
+otherwise noted below.
+.Pp
+The recognized environment variables are:
+.Bl -tag -width ATFXBUILDXCPPFLAGSXX
+.It Ev ATF_ARCH
+Overrides the built-in value of
+.Va atf_arch .
+.It Ev ATF_BUILD_CC
+Overrides the built-in value of
+.Va atf_build_cc .
+.It Ev ATF_BUILD_CFLAGS
+Overrides the built-in value of
+.Va atf_build_cflags .
+Empty values are allowed.
+.It Ev ATF_BUILD_CPP
+Overrides the built-in value of
+.Va atf_build_cpp .
+.It Ev ATF_BUILD_CPPFLAGS
+Overrides the built-in value of
+.Va atf_build_cppflags .
+Empty values are allowed.
+.It Ev ATF_BUILD_CXX
+Overrides the built-in value of
+.Va atf_build_cxx .
+.It Ev ATF_BUILD_CXXFLAGS
+Overrides the built-in value of
+.Va atf_build_cxxflags .
+Empty values are allowed.
+.It Ev ATF_CONFDIR
+Overrides the built-in value of
+.Va atf_confdir .
+.It Ev ATF_INCLUDEDIR
+Overrides the built-in value of
+.Va atf_includedir .
+.It Ev ATF_LIBDIR
+Overrides the built-in value of
+.Va atf_libdir .
+.It Ev ATF_LIBEXECDIR
+Overrides the built-in value of
+.Va atf_libexecdir .
+.It Ev ATF_MACHINE
+Overrides the built-in value of
+.Va atf_machine .
+.It Ev ATF_PKGDATADIR
+Overrides the built-in value of
+.Va atf_pkgdatadir .
+.It Ev ATF_SHELL
+Overrides the built-in value of
+.Va atf_shell .
+.It Ev ATF_WORKDIR
+Overrides the built-in value of
+.Va atf_workdir .
+.El
+.Sh SEE ALSO
+.Xr atf 7
diff --git a/unit/atf-src/tools/atf-config.cpp b/unit/atf-src/tools/atf-config.cpp
new file mode 100644
index 0000000..4d82d55
--- /dev/null
+++ b/unit/atf-src/tools/atf-config.cpp
@@ -0,0 +1,140 @@
+//
+// Automated Testing Framework (atf)
+//
+// Copyright (c) 2007 The NetBSD Foundation, Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
+// CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+// IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
+// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+// IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+
+#include <cstdlib>
+#include <iostream>
+#include <map>
+#include <string>
+
+#include "application.hpp"
+#include "config.hpp"
+#include "defs.hpp"
+
+class atf_config : public tools::application::app {
+ static const char* m_description;
+
+ bool m_tflag;
+
+ void process_option(int, const char*);
+ std::string specific_args(void) const;
+ options_set specific_options(void) const;
+
+ std::string format_var(const std::string&, const std::string&);
+
+public:
+ atf_config(void);
+
+ int main(void);
+};
+
+const char* atf_config::m_description =
+ "atf-config is a tool that queries the value of several "
+ "installation-specific configuration values of the atf. "
+ "It can be used by external tools to discover where specific "
+ "internal atf files are installed.";
+
+atf_config::atf_config(void) :
+ app(m_description, "atf-config(1)", "atf(7)"),
+ m_tflag(false)
+{
+}
+
+void
+atf_config::process_option(int ch, const char* arg ATF_DEFS_ATTRIBUTE_UNUSED)
+{
+ switch (ch) {
+ case 't':
+ m_tflag = true;
+ break;
+
+ default:
+ std::abort();
+ }
+}
+
+std::string
+atf_config::specific_args(void)
+ const
+{
+ return "[var1 [.. varN]]";
+}
+
+atf_config::options_set
+atf_config::specific_options(void)
+ const
+{
+ using tools::application::option;
+ options_set opts;
+ opts.insert(option('t', "", "Terse output: show values only"));
+ return opts;
+}
+
+std::string
+atf_config::format_var(const std::string& name, const std::string& val)
+{
+ std::string str;
+
+ if (m_tflag)
+ str = val;
+ else
+ str = name + " : " + val;
+
+ return str;
+}
+
+int
+atf_config::main(void)
+{
+ if (m_argc < 1) {
+ std::map< std::string, std::string > cv = tools::config::get_all();
+
+ for (std::map< std::string, std::string >::const_iterator iter =
+ cv.begin(); iter != cv.end(); iter++)
+ std::cout << format_var((*iter).first, (*iter).second) << "\n";
+ } else {
+ for (int i = 0; i < m_argc; i++) {
+ if (!tools::config::has(m_argv[i]))
+ throw std::runtime_error(std::string("Unknown variable `") +
+ m_argv[i] + "'");
+ }
+
+ for (int i = 0; i < m_argc; i++) {
+ std::cout << format_var(m_argv[i], tools::config::get(m_argv[i]))
+ << "\n";
+ }
+ }
+
+ return EXIT_SUCCESS;
+}
+
+int
+main(int argc, char* const* argv)
+{
+ return atf_config().run(argc, argv);
+}
diff --git a/unit/atf-src/tools/atf-config_test.sh b/unit/atf-src/tools/atf-config_test.sh
new file mode 100644
index 0000000..5d6505a
--- /dev/null
+++ b/unit/atf-src/tools/atf-config_test.sh
@@ -0,0 +1,180 @@
+#
+# Automated Testing Framework (atf)
+#
+# Copyright (c) 2007 The NetBSD Foundation, Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
+# CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+# IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
+# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+# GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+# IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+# IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+
+all_vars="atf_arch \
+ atf_build_cc \
+ atf_build_cflags \
+ atf_build_cpp \
+ atf_build_cppflags \
+ atf_build_cxx \
+ atf_build_cxxflags \
+ atf_confdir \
+ atf_includedir \
+ atf_libdir \
+ atf_libexecdir \
+ atf_machine \
+ atf_pkgdatadir \
+ atf_shell \
+ atf_workdir"
+all_vars_no=15
+
+atf_test_case list_all
+list_all_head()
+{
+ atf_set "descr" "Tests that at atf-config prints all expected" \
+ "variables, and not more"
+}
+list_all_body()
+{
+ atf_check -s eq:0 -o save:stdout -e empty atf-config
+ atf_check -s eq:0 -o empty -e empty \
+ test "$(wc -l stdout | awk '{ print $1 }')" = "${all_vars_no}"
+ for v in ${all_vars}; do
+ atf_check -s eq:0 -o ignore -e empty grep "${v}" stdout
+ done
+}
+
+atf_test_case query_one
+query_one_head()
+{
+ atf_set "descr" "Tests that querying a single variable works"
+}
+query_one_body()
+{
+ for v in ${all_vars}; do
+ atf_check -s eq:0 -o save:stdout -o match:"${v}" -e empty \
+ atf-config "${v}"
+ atf_check -s eq:0 -o empty -e empty \
+ test "$(wc -l stdout | awk '{ print $1 }')" = 1
+ done
+}
+
+atf_test_case query_one_terse
+query_one_terse_head()
+{
+ atf_set "descr" "Tests that querying a single variable in terse mode" \
+ "works"
+}
+query_one_terse_body()
+{
+ for v in ${all_vars}; do
+ atf_check -s eq:0 -o save:stdout -o match:"${v}" -e empty \
+ atf-config "${v}"
+ atf_check -s eq:0 -o empty -e empty \
+ test "$(wc -l stdout | awk '{ print $1 }')" = 1
+ atf_check -s eq:0 -o save:stdout -e empty cut -d ' ' -f 3- stdout
+ atf_check -s eq:0 -o empty -e empty mv stdout expout
+ atf_check -s eq:0 -o file:expout -e empty atf-config -t "${v}"
+ done
+}
+
+atf_test_case query_multiple
+query_multiple_head()
+{
+ atf_set "descr" "Tests that querying multiple variables works"
+}
+query_multiple_body()
+{
+ atf_check -s eq:0 -o save:stdout -o match:'atf_libexecdir' \
+ -o match:'atf_shell' -e empty atf-config atf_libexecdir atf_shell
+ atf_check -s eq:0 -o empty -e empty \
+ test "$(wc -l stdout | awk '{ print $1 }')" = 2
+}
+
+atf_test_case query_unknown
+query_unknown_head()
+{
+ atf_set "descr" "Tests that querying an unknown variable delivers" \
+ "the correct error"
+}
+query_unknown_body()
+{
+ atf_check -s eq:1 -o empty -e match:'Unknown variable.*non_existent' \
+ atf-config non_existent
+}
+
+atf_test_case query_mixture
+query_mixture_head()
+{
+ atf_set "descr" "Tests that querying a known and an unknown variable" \
+ "delivers the correct error"
+}
+query_mixture_body()
+{
+ for v in ${all_vars}; do
+ atf_check -s eq:1 -o empty -e match:'Unknown variable.*non_existent' \
+ atf-config "${v}" non_existent
+ atf_check -s eq:1 -o empty -e match:'Unknown variable.*non_existent' \
+ atf-config non_existent "${v}"
+ done
+}
+
+atf_test_case override_env
+override_env_head()
+{
+ atf_set "descr" "Tests that build-time variables can be overriden" \
+ "through their corresponding environment variables"
+}
+override_env_body()
+{
+ for v in ${all_vars}; do
+ V=$(echo ${v} | tr '[a-z]' '[A-Z]')
+ atf_check -s eq:0 -o save:stdout -e empty -x "${V}=testval atf-config"
+ atf_check -s eq:0 -o empty -e empty mv stdout all
+
+ atf_check -s eq:0 -o save:stdout -e empty grep "^${v} : " all
+ atf_check -s eq:0 -o empty -e empty mv stdout affected
+ atf_check -s eq:0 -o save:stdout -e empty grep -v "^${v} : " all
+ atf_check -s eq:0 -o empty -e empty mv stdout unaffected
+
+ atf_check -s eq:0 -o empty -e empty \
+ test "$(wc -l affected | awk '{ print $1 }')" = 1
+ atf_check -s eq:0 -o empty -e empty \
+ test "$(wc -l unaffected | awk '{ print $1 }')" = \
+ "$((${all_vars_no} -1))"
+
+ atf_check -s eq:0 -o ignore -e empty grep "^${v} : testval$" affected
+ atf_check -s eq:1 -o empty -e empty grep ' : testval$' unaffected
+ done
+}
+
+atf_init_test_cases()
+{
+ atf_add_test_case list_all
+
+ atf_add_test_case query_one
+ atf_add_test_case query_one_terse
+ atf_add_test_case query_multiple
+ atf_add_test_case query_unknown
+ atf_add_test_case query_mixture
+
+ atf_add_test_case override_env
+}
+
+# vim: syntax=sh:expandtab:shiftwidth=4:softtabstop=4
diff --git a/unit/atf-src/tools/atf-formats.5 b/unit/atf-src/tools/atf-formats.5
new file mode 100644
index 0000000..bb919f4
--- /dev/null
+++ b/unit/atf-src/tools/atf-formats.5
@@ -0,0 +1,231 @@
+.\"
+.\" Automated Testing Framework (atf)
+.\"
+.\" Copyright (c) 2007 The NetBSD Foundation, Inc.
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
+.\" CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+.\" INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+.\" MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+.\" IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
+.\" DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+.\" GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+.\" INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+.\" IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+.\" OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+.\" IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+.\"
+.Dd December 20, 2011
+.Dt ATF-FORMATS 5
+.Os
+.Sh NAME
+.Nm atf-formats
+.Nd machine-parseable data formats used by ATF
+.Sh DESCRIPTION
+This manual page describes the multiple data formats used in ATF.
+These formats affect configuration files, control files and any data that
+is externalized or internalized by the tools.
+.Pp
+Data files are always organized as follows:
+.Bd -literal -offset indent
+Header1: Value1 \\
+ ... | head
+HeaderN: ValueN /
+ mandatory blank line
+Free-form text contents \\
+ ... | body
+ ... /
+.Ed
+.Pp
+A file must always contain a
+.Sq Content-Type
+header and must always separate that header from the body with a blank
+line, even if the body is empty.
+.Pp
+The
+.Sq Content-Type
+is always of the form:
+.Bd -literal -offset indent
+Content-Type: application/X-atf-<subtype>; version="<version>"
+.Ed
+.Pp
+where
+.Sq subtype
+indicates the specific file format and
+.Sq version
+its format version.
+This header must be the first one of the file.
+.Pp
+The main purpose of the
+.Sq Content-Type
+header, aside from determining the format used in the file, is to allow
+future changes to a given format.
+Whenever an incompatible change is made, the version is bumped by one.
+By keeping the header in the first line, future versions may even remove
+the need for such a header -- e.g. if some format was replaced by XML
+files, which have their own mandatory header.
+.Pp
+The rest of this document details the different format types.
+.Ss Format: application/X-atf-atffile, version: 1
+Atffiles are logically divided into three sections:
+.Bl -bullet
+.It
+Test programs: the list of test programs that define the test suite
+described by the Atffile.
+.It
+Meta-data properties: these define some constant values applicable to
+all the test programs defined in the file.
+In some sense they define the properties that describe the test suite.
+.It
+Configuration variables: defaults for configuration variables that
+can be overridden through configuration files or the command line.
+.El
+.Pp
+The grammar for Atffiles is the following:
+.Bd -literal -offset indent
+DATA ::= ( ( CONF | PROP | TP )? COMMENT? NEWLINE )* EOF
+CONF ::= 'conf:' WORD EQUAL STRING
+PROP ::= 'prop:' WORD EQUAL STRING
+TP ::= TPFILE | TPGLOB
+TPFILE ::= 'tp: ' STRING
+TPGLOB ::= 'tp-glob: ' STRING
+STRING ::= WORD | '"' WORD* '"'
+.Ed
+.Pp
+The meaning of the constructions above is:
+.Bl -tag -width TPGLOBXX
+.It CONF
+Definition of a configuration variable.
+.It PROP
+Definition of a meta-data property.
+.It TPFILE
+Addition of a test program into the test suite.
+The string is taken literally as the program's name, and this program
+must exist.
+.It TPGLOB
+Addition of multiple test programs into the test suite.
+The string is taken as a glob pattern, which may have or not have any
+matches in the current directory.
+.El
+.Pp
+An example:
+.Bd -literal -offset indent
+prop: test-suite = utilities
+
+conf: unprivileged-user = nobody
+
+tp: t_cp
+tp: t_mv
+tp: t_df
+tp-glob: t_dir_*
+.Ed
+.Ss Format: application/X-atf-config, version: 1
+Configuration files are very simple: they only contain a list of variable
+name/variable value pairs.
+Their grammar is:
+.Bd -literal -offset indent
+DATA ::= ( VAR? COMMENT? NEWLINE )* EOF
+VAR ::= WORD EQUAL STRING
+COMMENT ::= HASH WORD*
+STRING ::= WORD | '"' WORD* '"'
+.Ed
+.Pp
+An example:
+.Bd -literal -offset indent
+# This is the system-wide configuration file for ATF.
+# The above and this line are comments placed on their own line.
+
+var1 = this is a variable value
+var2 = this is another one # Optional comment at the end.
+.Ed
+.Ss Format: application/X-atf-tps, version: 3
+The
+.Sq application/X-atf-tps
+format multiplexes the standard output, standard error and results output
+streams from multiple test programs into a single data file.
+This format is used by
+.Xr atf-run 1
+to report the execution of several test programs and is later parsed by
+.Xr atf-report 1
+to inform the user of this process.
+It has the following grammar:
+.Bd -literal -offset indent
+DATA ::= INFO* TPS-COUNT TP-STANZA* INFO* EOF
+INFO ::= 'info' COLON STRING COMMA STRING NEWLINE
+TPS-COUNT ::= 'tps-count' COLON POSITIVE-NUMBER NEWLINE
+TP-STANZA ::= TP-START TC-STANZA* TC-END
+TP-START ::= 'tp-start' COLON TIMESTAMP COMMA STRING COMMA
+ POSITIVE-NUMBER NEWLINE
+TP-END ::= 'tc-end' COLON TIMESTAMP COMMA STRING (COMMA STRING)?
+TC-STANZA ::= TC-START (TC-SO | TC-SE)* TC-END
+TC-START ::= 'tc-start' COLON TIMESTAMP COMMA STRING NEWLINE
+TC-SO ::= 'tc-so' COLON STRING NEWLINE
+TC-SE ::= 'tc-se' COLON STRING NEWLINE
+TC-END ::= 'tc-end' COLON TIMESTAMP COMMA STRING COMMA TCR NEWLINE
+TCR ::= 'passed' | ('failed' | 'skipped') COMMA STRING
+TIMESTAMP ::= [0-9]+.[0-9]+
+.Ed
+.Pp
+The meaning of the constructions above is:
+.Bl -tag -width TPSXCOUNTXX
+.It TPS-COUNT
+Indicates the number of test programs that will be executed.
+There will be this exact amount of
+.Sq TP-STANZA
+constructions following it.
+.It TP-START
+Indicates the beginning of a test program.
+This includes the program's name and the amount of test cases that
+will follow.
+.It TP-END
+Indicates the completion of a test program.
+This is followed by the program's name and, if the program ended
+prematurely, an error message indicating the reason of its failure.
+A successful execution of a test program (regardless of the status of its
+test cases) must not be accompanied by any reason.
+.It TC-START
+Indicates the beginning of a test case.
+This is accompanied by the test case's name.
+.It TC-SO
+Contains a text line sent to the standard output stream during the
+execution of the test case.
+Leading and trailing space is preserved.
+.It TC-SE
+Contains a text line sent to the standard error stream during the
+execution of the test case.
+Leading and trailing space is preserved.
+.It TC-END
+Indicates the completion of a test case.
+This is accompanied by the test case's name, its result and the reason
+associated with this result (if applicable).
+.El
+.Pp
+An example:
+.Bd -literal -offset indent
+tps-count: 2
+tp-start: calculator, 1324318951.838923, 2
+tc-start: add, 1324318951.839101
+tc-end: add, 1324318951.839500, passed
+tc-start: subtract, 1324318951.840001
+tc-so: 3-2 expected to return 1 but got 0
+tc-end: subtract, 1324318952.000123, failed, Calculated an unexpected value
+tp-end: calculator, 1324318952.002301
+tp-start: files, 1, 1324318952.502348
+tc-start: copy, 1324318952.508291
+tc-se: could not find the cp(1) utility
+tc-end: copy, 1324318953.203145, skipped
+tp-end: files, 1324318953.203800
+.Ed
+.Sh SEE ALSO
+.Xr atf 7
diff --git a/unit/atf-src/tools/atf-report.1 b/unit/atf-src/tools/atf-report.1
new file mode 100644
index 0000000..df03af0
--- /dev/null
+++ b/unit/atf-src/tools/atf-report.1
@@ -0,0 +1,168 @@
+.\"
+.\" Automated Testing Framework (atf)
+.\"
+.\" Copyright (c) 2007 The NetBSD Foundation, Inc.
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
+.\" CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+.\" INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+.\" MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+.\" IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
+.\" DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+.\" GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+.\" INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+.\" IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+.\" OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+.\" IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+.\"
+.Dd December 16, 2011
+.Dt ATF-REPORT 1
+.Os
+.Sh NAME
+.Nm atf-report
+.Nd transforms the output of atf-run to different formats
+.Sh SYNOPSIS
+.Nm
+.Op Fl o Ar fmt1:path1 Op .. Fl o Ar fmtN:pathN
+.Nm
+.Fl h
+.Sh DESCRIPTION
+.Nm
+reads the output of
+.Nm atf-run
+and transforms it to different formats.
+Some of these are user-friendly and others are machine-parseable, which
+opens a wide range of possibilities to analyze the results of a test
+suite's execution.
+See
+.Sx Output formats
+below for more details on which these formats are.
+.Pp
+In the first synopsis form,
+.Nm
+reads the output of
+.Nm atf-run
+through its standard input and, if no
+.Fl o
+options are given, prints a user-friendly report on its standard
+output using the
+.Sq ticker
+format.
+If
+.Fl o
+options are provided (more than one are allowed), they specify the complete
+list of reports to generate.
+They are all generated simultaneously, and for obvious reasons, two reports
+cannot be written to the same file.
+Note that the default output is suppressed when
+.Fl o
+is provided.
+.Pp
+In the second synopsis form,
+.Nm
+will print information about all supported options and their purpose.
+.Pp
+The following options are available:
+.Bl -tag -width XoXfmtXpathXX
+.It Fl h
+Shows a short summary of all available options and their purpose.
+.It Fl o Ar fmt:path
+Adds a new output format.
+.Ar fmt
+is one of the formats described later on in
+.Sx Output formats .
+.Ar path
+specifies where the report will be written to.
+Depending on the chosen format, this may refer to a single file or to
+a directory.
+For those formats that write to a single file, specifying a
+.Sq -
+as the path will redirect the report to the standard output.
+.El
+.Ss Output formats
+The following output formats are allowed:
+.Bl -tag -width tickerXX
+.It csv
+A machine-parseable Comma-Separated Values (CSV) file.
+This file contains the results for all test cases and test programs.
+Test cases are logged using the following syntax:
+.Bd -literal -offset indent
+tc, duration, test-program, test-case, result[, reason]
+.Ed
+.Pp
+The
+.Sq result
+field for test cases is always one of
+.Sq passed ,
+.Sq skipped
+or
+.Sq failed .
+The last two are always followed by a reason.
+.Pp
+Test programs are logged with the following syntax:
+.Bd -literal -offset indent
+tp, duration, test-program, result[, reason]
+.Ed
+.Pp
+In this case, the
+.Sq result
+can be one of:
+.Sq passed ,
+which denotes test programs that ran without any failure;
+.Sq failed ,
+which refers to test programs in which one or more test cases failed;
+or
+.Sq bogus ,
+which mentions those test programs that failed to execute by some reason.
+The reason field is only available in the last case.
+.Pp
+The time required to execute each test case and test program is
+also provided.
+You should not rely on the order of the entries in the resulting output.
+.It ticker
+A user-friendly report that shows the progress of the test suite's
+execution as it operates.
+This type of report should always be redirected to a virtual terminal,
+not a file, as it may use control sequences that will make the output
+unreadable in regular files.
+.It xml
+A report contained in a single XML file.
+Ideal for later processing with
+.Xr xsltproc 1
+to generate nice HTML reports.
+.El
+.Sh EXAMPLES
+The most simple way of running a test suite is to pipe the output of
+.Nm atf-run
+through
+.Nm
+without any additional flags.
+This will use the default output format, which is suitable to most users:
+.Bd -literal -offset indent
+atf-run | atf-report
+.Ed
+.Pp
+In some situations, it may be interesting to get a machine-parseable file
+aside from the standard report.
+This can be done as follows:
+.Bd -literal -offset indent
+atf-run | atf-report -o csv:testsuite.csv -o ticker:-
+.Ed
+.Pp
+Or if the standard report is not desired, thus achieving completely silent
+operation:
+atf-run | atf-report -o csv:testsuite.csv
+.Sh SEE ALSO
+.Xr atf-run 1 ,
+.Xr atf 7
diff --git a/unit/atf-src/tools/atf-report.cpp b/unit/atf-src/tools/atf-report.cpp
new file mode 100644
index 0000000..cb065f9
--- /dev/null
+++ b/unit/atf-src/tools/atf-report.cpp
@@ -0,0 +1,710 @@
+//
+// Automated Testing Framework (atf)
+//
+// Copyright (c) 2007 The NetBSD Foundation, Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
+// CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+// IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
+// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+// IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+
+extern "C" {
+#include <sys/time.h>
+}
+
+#include <cctype>
+#include <cstdlib>
+#include <fstream>
+#include <iomanip>
+#include <iostream>
+#include <memory>
+#include <sstream>
+#include <utility>
+#include <vector>
+
+#include "application.hpp"
+#include "defs.hpp"
+#include "fs.hpp"
+#include "reader.hpp"
+#include "text.hpp"
+#include "ui.hpp"
+
+typedef std::auto_ptr< std::ostream > ostream_ptr;
+
+static ostream_ptr
+open_outfile(const tools::fs::path& path)
+{
+ ostream_ptr osp;
+ if (path.str() == "-")
+ osp = ostream_ptr(new std::ofstream("/dev/stdout"));
+ else
+ osp = ostream_ptr(new std::ofstream(path.c_str()));
+ if (!(*osp))
+ throw std::runtime_error("Could not create file " + path.str());
+ return osp;
+}
+
+static std::string
+format_tv(struct timeval* tv)
+{
+ std::ostringstream output;
+ output << static_cast< long >(tv->tv_sec) << '.'
+ << std::setfill('0') << std::setw(6)
+ << static_cast< long >(tv->tv_usec);
+ return output.str();
+}
+
+// ------------------------------------------------------------------------
+// The "writer" interface.
+// ------------------------------------------------------------------------
+
+//!
+//! \brief A base class that defines an output format.
+//!
+//! The writer base class defines a generic interface to output formats.
+//! This is meant to be subclassed, and each subclass can redefine any
+//! method to format the information as it wishes.
+//!
+//! This class is not tied to a output stream nor a file because, depending
+//! on the output format, we will want to write to a single file or to
+//! multiple ones.
+//!
+class writer {
+public:
+ writer(void) {}
+ virtual ~writer(void) {}
+
+ virtual void write_info(const std::string&, const std::string&) {}
+ virtual void write_ntps(size_t) {}
+ virtual void write_tp_start(const std::string&, size_t) {}
+ virtual void write_tp_end(struct timeval*, const std::string&) {}
+ virtual void write_tc_start(const std::string&) {}
+ virtual void write_tc_stdout_line(const std::string&) {}
+ virtual void write_tc_stderr_line(const std::string&) {}
+ virtual void write_tc_end(const std::string&, struct timeval*,
+ const std::string&) {}
+ virtual void write_eof(void) {}
+};
+
+// ------------------------------------------------------------------------
+// The "csv_writer" class.
+// ------------------------------------------------------------------------
+
+//!
+//! \brief A very simple plain-text output format.
+//!
+//! The csv_writer class implements a very simple plain-text output
+//! format that summarizes the results of each executed test case. The
+//! results are meant to be easily parseable by third-party tools, hence
+//! they are formatted as a CSV file.
+//!
+class csv_writer : public writer {
+ ostream_ptr m_os;
+ bool m_failed;
+
+ std::string m_tpname;
+ std::string m_tcname;
+
+public:
+ csv_writer(const tools::fs::path& p) :
+ m_os(open_outfile(p))
+ {
+ }
+
+ virtual
+ void
+ write_tp_start(const std::string& name,
+ size_t ntcs ATF_DEFS_ATTRIBUTE_UNUSED)
+ {
+ m_tpname = name;
+ m_failed = false;
+ }
+
+ virtual
+ void
+ write_tp_end(struct timeval* tv, const std::string& reason)
+ {
+ const std::string timestamp = format_tv(tv);
+
+ if (!reason.empty())
+ (*m_os) << "tp, " << timestamp << ", " << m_tpname << ", bogus, "
+ << reason << "\n";
+ else if (m_failed)
+ (*m_os) << "tp, " << timestamp << ", "<< m_tpname << ", failed\n";
+ else
+ (*m_os) << "tp, " << timestamp << ", "<< m_tpname << ", passed\n";
+ }
+
+ virtual
+ void
+ write_tc_start(const std::string& name)
+ {
+ m_tcname = name;
+ }
+
+ virtual
+ void
+ write_tc_end(const std::string& state, struct timeval* tv,
+ const std::string& reason)
+ {
+ std::string str = m_tpname + ", " + m_tcname + ", " + state;
+ if (!reason.empty())
+ str += ", " + reason;
+ (*m_os) << "tc, " << format_tv(tv) << ", " << str << "\n";
+
+ if (state == "failed")
+ m_failed = true;
+ }
+};
+
+// ------------------------------------------------------------------------
+// The "ticker_writer" class.
+// ------------------------------------------------------------------------
+
+//!
+//! \brief A console-friendly output format.
+//!
+//! The ticker_writer class implements a formatter that is user-friendly
+//! in the sense that it shows the execution of test cases in an easy to
+//! read format. It is not meant to be parseable and its format can
+//! freely change across releases.
+//!
+class ticker_writer : public writer {
+ ostream_ptr m_os;
+
+ size_t m_curtp, m_ntps;
+ size_t m_tcs_passed, m_tcs_failed, m_tcs_skipped, m_tcs_expected_failures;
+ std::string m_tcname, m_tpname;
+ std::vector< std::string > m_failed_tcs;
+ std::map< std::string, std::string > m_expected_failures_tcs;
+ std::vector< std::string > m_failed_tps;
+
+ void
+ write_info(const std::string& what, const std::string& val)
+ {
+ if (what == "tests.root") {
+ (*m_os) << "Tests root: " << val << "\n\n";
+ }
+ }
+
+ void
+ write_ntps(size_t ntps)
+ {
+ m_curtp = 1;
+ m_tcs_passed = 0;
+ m_tcs_failed = 0;
+ m_tcs_skipped = 0;
+ m_tcs_expected_failures = 0;
+ m_ntps = ntps;
+ }
+
+ void
+ write_tp_start(const std::string& tp, size_t ntcs)
+ {
+ using tools::text::to_string;
+ using tools::ui::format_text;
+
+ m_tpname = tp;
+
+ (*m_os) << format_text(tp + " (" + to_string(m_curtp) +
+ "/" + to_string(m_ntps) + "): " +
+ to_string(ntcs) + " test cases")
+ << "\n";
+ (*m_os).flush();
+ }
+
+ void
+ write_tp_end(struct timeval* tv, const std::string& reason)
+ {
+ using tools::ui::format_text_with_tag;
+
+ m_curtp++;
+
+ if (!reason.empty()) {
+ (*m_os) << format_text_with_tag("BOGUS TEST PROGRAM: Cannot "
+ "trust its results because "
+ "of `" + reason + "'",
+ m_tpname + ": ", false)
+ << "\n";
+ m_failed_tps.push_back(m_tpname);
+ }
+ (*m_os) << "[" << format_tv(tv) << "s]\n\n";
+ (*m_os).flush();
+
+ m_tpname.clear();
+ }
+
+ void
+ write_tc_start(const std::string& tcname)
+ {
+ m_tcname = tcname;
+
+ (*m_os) << " " + tcname + ": ";
+ (*m_os).flush();
+ }
+
+ void
+ write_tc_end(const std::string& state, struct timeval* tv,
+ const std::string& reason)
+ {
+ std::string str;
+
+ (*m_os) << "[" << format_tv(tv) << "s] ";
+
+ if (state == "expected_death" || state == "expected_exit" ||
+ state == "expected_failure" || state == "expected_signal" ||
+ state == "expected_timeout") {
+ str = "Expected failure: " + reason;
+ m_tcs_expected_failures++;
+ m_expected_failures_tcs[m_tpname + ":" + m_tcname] = reason;
+ } else if (state == "failed") {
+ str = "Failed: " + reason;
+ m_tcs_failed++;
+ m_failed_tcs.push_back(m_tpname + ":" + m_tcname);
+ } else if (state == "passed") {
+ str = "Passed.";
+ m_tcs_passed++;
+ } else if (state == "skipped") {
+ str = "Skipped: " + reason;
+ m_tcs_skipped++;
+ } else
+ std::abort();
+
+ // XXX Wrap text. format_text_with_tag does not currently allow
+ // to specify the current column, which is needed because we have
+ // already printed the tc's name.
+ (*m_os) << str << '\n';
+
+ m_tcname = "";
+ }
+
+ static void
+ write_expected_failures(const std::map< std::string, std::string >& xfails,
+ std::ostream& os)
+ {
+ using tools::ui::format_text;
+ using tools::ui::format_text_with_tag;
+
+ os << format_text("Test cases for known bugs:") << "\n";
+
+ for (std::map< std::string, std::string >::const_iterator iter =
+ xfails.begin(); iter != xfails.end(); iter++) {
+ const std::string& name = (*iter).first;
+ const std::string& reason = (*iter).second;
+
+ os << format_text_with_tag(reason, " " + name + ": ", false)
+ << "\n";
+ }
+ }
+
+ void
+ write_eof(void)
+ {
+ using tools::text::join;
+ using tools::text::to_string;
+ using tools::ui::format_text;
+ using tools::ui::format_text_with_tag;
+
+ if (!m_failed_tps.empty()) {
+ (*m_os) << format_text("Failed (bogus) test programs:")
+ << "\n";
+ (*m_os) << format_text_with_tag(join(m_failed_tps, ", "),
+ " ", false) << "\n\n";
+ }
+
+ if (!m_expected_failures_tcs.empty()) {
+ write_expected_failures(m_expected_failures_tcs, *m_os);
+ (*m_os) << "\n";
+ }
+
+ if (!m_failed_tcs.empty()) {
+ (*m_os) << format_text("Failed test cases:") << "\n";
+ (*m_os) << format_text_with_tag(join(m_failed_tcs, ", "),
+ " ", false) << "\n\n";
+ }
+
+ (*m_os) << format_text("Summary for " + to_string(m_ntps) +
+ " test programs:") << "\n";
+ (*m_os) << format_text_with_tag(to_string(m_tcs_passed) +
+ " passed test cases.",
+ " ", false) << "\n";
+ (*m_os) << format_text_with_tag(to_string(m_tcs_failed) +
+ " failed test cases.",
+ " ", false) << "\n";
+ (*m_os) << format_text_with_tag(to_string(m_tcs_expected_failures) +
+ " expected failed test cases.",
+ " ", false) << "\n";
+ (*m_os) << format_text_with_tag(to_string(m_tcs_skipped) +
+ " skipped test cases.",
+ " ", false) << "\n";
+ }
+
+public:
+ ticker_writer(const tools::fs::path& p) :
+ m_os(open_outfile(p))
+ {
+ }
+};
+
+// ------------------------------------------------------------------------
+// The "xml" class.
+// ------------------------------------------------------------------------
+
+//!
+//! \brief A single-file XML output format.
+//!
+//! The xml_writer class implements a formatter that prints the results
+//! of test cases in an XML format easily parseable later on by other
+//! utilities.
+//!
+class xml_writer : public writer {
+ ostream_ptr m_os;
+
+ std::string m_tcname, m_tpname;
+
+ static
+ std::string
+ attrval(const std::string& str)
+ {
+ return str;
+ }
+
+ static
+ std::string
+ elemval(const std::string& str)
+ {
+ std::ostringstream buf;
+ for (std::string::const_iterator iter = str.begin();
+ iter != str.end(); iter++) {
+ const int character = static_cast< unsigned char >(*iter);
+ if (character == '&') {
+ buf << "&amp;";
+ } else if (character == '<') {
+ buf << "&lt;";
+ } else if (character == '>') {
+ buf << "&gt;";
+ } else if (std::isalnum(character) || std::ispunct(character) ||
+ std::isspace(character)) {
+ buf << static_cast< char >(character);
+ } else {
+ buf << "&amp;#" << character << ";";
+ }
+ }
+ return buf.str();
+ }
+
+ void
+ write_info(const std::string& what, const std::string& val)
+ {
+ (*m_os) << "<info class=\"" << what << "\">" << val << "</info>\n";
+ }
+
+ void
+ write_tp_start(const std::string& tp,
+ size_t ntcs ATF_DEFS_ATTRIBUTE_UNUSED)
+ {
+ (*m_os) << "<tp id=\"" << attrval(tp) << "\">\n";
+ }
+
+ void
+ write_tp_end(struct timeval* tv, const std::string& reason)
+ {
+ if (!reason.empty())
+ (*m_os) << "<failed>" << elemval(reason) << "</failed>\n";
+ (*m_os) << "<tp-time>" << format_tv(tv) << "</tp-time>";
+ (*m_os) << "</tp>\n";
+ }
+
+ void
+ write_tc_start(const std::string& tcname)
+ {
+ (*m_os) << "<tc id=\"" << attrval(tcname) << "\">\n";
+ }
+
+ void
+ write_tc_stdout_line(const std::string& line)
+ {
+ (*m_os) << "<so>" << elemval(line) << "</so>\n";
+ }
+
+ void
+ write_tc_stderr_line(const std::string& line)
+ {
+ (*m_os) << "<se>" << elemval(line) << "</se>\n";
+ }
+
+ void
+ write_tc_end(const std::string& state, struct timeval* tv,
+ const std::string& reason)
+ {
+ std::string str;
+
+ if (state == "expected_death" || state == "expected_exit" ||
+ state == "expected_failure" || state == "expected_signal" ||
+ state == "expected_timeout") {
+ (*m_os) << "<" << state << ">" << elemval(reason)
+ << "</" << state << ">\n";
+ } else if (state == "passed") {
+ (*m_os) << "<passed />\n";
+ } else if (state == "failed") {
+ (*m_os) << "<failed>" << elemval(reason) << "</failed>\n";
+ } else if (state == "skipped") {
+ (*m_os) << "<skipped>" << elemval(reason) << "</skipped>\n";
+ } else
+ std::abort();
+ (*m_os) << "<tc-time>" << format_tv(tv) << "</tc-time>";
+ (*m_os) << "</tc>\n";
+ }
+
+ void
+ write_eof(void)
+ {
+ (*m_os) << "</tests-results>\n";
+ }
+
+public:
+ xml_writer(const tools::fs::path& p) :
+ m_os(open_outfile(p))
+ {
+ (*m_os) << "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\n"
+ << "<!DOCTYPE tests-results PUBLIC "
+ "\"-//NetBSD//DTD ATF Tests Results 0.1//EN\" "
+ "\"http://www.NetBSD.org/XML/atf/tests-results.dtd\">\n\n"
+ "<tests-results>\n";
+ }
+};
+
+// ------------------------------------------------------------------------
+// The "converter" class.
+// ------------------------------------------------------------------------
+
+//!
+//! \brief A reader that redirects events to multiple writers.
+//!
+//! The converter class implements an atf_tps_reader that, for each event
+//! raised by the parser, redirects it to multiple writers so that they
+//! can reformat it according to their output rules.
+//!
+class converter : public tools::atf_report::atf_tps_reader {
+ typedef std::vector< writer* > outs_vector;
+ outs_vector m_outs;
+
+ void
+ got_info(const std::string& what, const std::string& val)
+ {
+ for (outs_vector::iterator iter = m_outs.begin();
+ iter != m_outs.end(); iter++)
+ (*iter)->write_info(what, val);
+ }
+
+ void
+ got_ntps(size_t ntps)
+ {
+ for (outs_vector::iterator iter = m_outs.begin();
+ iter != m_outs.end(); iter++)
+ (*iter)->write_ntps(ntps);
+ }
+
+ void
+ got_tp_start(const std::string& tp, size_t ntcs)
+ {
+ for (outs_vector::iterator iter = m_outs.begin();
+ iter != m_outs.end(); iter++)
+ (*iter)->write_tp_start(tp, ntcs);
+ }
+
+ void
+ got_tp_end(struct timeval* tv, const std::string& reason)
+ {
+ for (outs_vector::iterator iter = m_outs.begin();
+ iter != m_outs.end(); iter++)
+ (*iter)->write_tp_end(tv, reason);
+ }
+
+ void
+ got_tc_start(const std::string& tcname)
+ {
+ for (outs_vector::iterator iter = m_outs.begin();
+ iter != m_outs.end(); iter++)
+ (*iter)->write_tc_start(tcname);
+ }
+
+ void
+ got_tc_stdout_line(const std::string& line)
+ {
+ for (outs_vector::iterator iter = m_outs.begin();
+ iter != m_outs.end(); iter++)
+ (*iter)->write_tc_stdout_line(line);
+ }
+
+ void
+ got_tc_stderr_line(const std::string& line)
+ {
+ for (outs_vector::iterator iter = m_outs.begin();
+ iter != m_outs.end(); iter++)
+ (*iter)->write_tc_stderr_line(line);
+ }
+
+ void
+ got_tc_end(const std::string& state, struct timeval* tv,
+ const std::string& reason)
+ {
+ for (outs_vector::iterator iter = m_outs.begin();
+ iter != m_outs.end(); iter++)
+ (*iter)->write_tc_end(state, tv, reason);
+ }
+
+ void
+ got_eof(void)
+ {
+ for (outs_vector::iterator iter = m_outs.begin();
+ iter != m_outs.end(); iter++)
+ (*iter)->write_eof();
+ }
+
+public:
+ converter(std::istream& is) :
+ tools::atf_report::atf_tps_reader(is)
+ {
+ }
+
+ ~converter(void)
+ {
+ for (outs_vector::iterator iter = m_outs.begin();
+ iter != m_outs.end(); iter++)
+ delete *iter;
+ }
+
+ void
+ add_output(const std::string& fmt, const tools::fs::path& p)
+ {
+ if (fmt == "csv") {
+ m_outs.push_back(new csv_writer(p));
+ } else if (fmt == "ticker") {
+ m_outs.push_back(new ticker_writer(p));
+ } else if (fmt == "xml") {
+ m_outs.push_back(new xml_writer(p));
+ } else
+ throw std::runtime_error("Unknown format `" + fmt + "'");
+ }
+};
+
+// ------------------------------------------------------------------------
+// The "atf_report" class.
+// ------------------------------------------------------------------------
+
+class atf_report : public tools::application::app {
+ static const char* m_description;
+
+ typedef std::pair< std::string, tools::fs::path > fmt_path_pair;
+ std::vector< fmt_path_pair > m_oflags;
+
+ void process_option(int, const char*);
+ options_set specific_options(void) const;
+
+public:
+ atf_report(void);
+
+ int main(void);
+};
+
+const char* atf_report::m_description =
+ "atf-report is a tool that parses the output of atf-run and "
+ "generates user-friendly reports in multiple different formats.";
+
+atf_report::atf_report(void) :
+ app(m_description, "atf-report(1)", "atf(7)")
+{
+}
+
+void
+atf_report::process_option(int ch, const char* arg)
+{
+ switch (ch) {
+ case 'o':
+ {
+ std::string str(arg);
+ std::string::size_type pos = str.find(':');
+ if (pos == std::string::npos)
+ throw std::runtime_error("Syntax error in -o option");
+ else {
+ std::string fmt = str.substr(0, pos);
+ tools::fs::path path = tools::fs::path(str.substr(pos + 1));
+ m_oflags.push_back(fmt_path_pair(fmt, path));
+ }
+ }
+ break;
+
+ default:
+ std::abort();
+ }
+}
+
+atf_report::options_set
+atf_report::specific_options(void)
+ const
+{
+ using tools::application::option;
+ options_set opts;
+ opts.insert(option('o', "fmt:path", "Adds a new output file; multiple "
+ "ones can be specified, and a - "
+ "path means stdout"));
+ return opts;
+}
+
+int
+atf_report::main(void)
+{
+ if (m_argc > 0)
+ throw std::runtime_error("No arguments allowed");
+
+ if (m_oflags.empty())
+ m_oflags.push_back(fmt_path_pair("ticker", tools::fs::path("-")));
+
+ // Look for path duplicates.
+ std::set< tools::fs::path > paths;
+ for (std::vector< fmt_path_pair >::const_iterator iter = m_oflags.begin();
+ iter != m_oflags.end(); iter++) {
+ tools::fs::path p = (*iter).second;
+ if (p == tools::fs::path("/dev/stdout"))
+ p = tools::fs::path("-");
+ if (paths.find(p) != paths.end())
+ throw std::runtime_error("The file `" + p.str() + "' was "
+ "specified more than once");
+ paths.insert((*iter).second);
+ }
+
+ // Generate the output files.
+ converter cnv(std::cin);
+ for (std::vector< fmt_path_pair >::const_iterator iter = m_oflags.begin();
+ iter != m_oflags.end(); iter++)
+ cnv.add_output((*iter).first, (*iter).second);
+ cnv.read();
+
+ return EXIT_SUCCESS;
+}
+
+int
+main(int argc, char* const* argv)
+{
+ return atf_report().run(argc, argv);
+}
diff --git a/unit/atf-src/tools/atf-report_test.sh b/unit/atf-src/tools/atf-report_test.sh
new file mode 100644
index 0000000..9f0f47f
--- /dev/null
+++ b/unit/atf-src/tools/atf-report_test.sh
@@ -0,0 +1,449 @@
+#
+# Automated Testing Framework (atf)
+#
+# Copyright (c) 2007 The NetBSD Foundation, Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
+# CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+# IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
+# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+# GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+# IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+# IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+
+create_helpers()
+{
+ mkdir dir1
+ cp $(atf_get_srcdir)/pass_helper dir1/tp1
+ cp $(atf_get_srcdir)/fail_helper dir1/tp2
+ cp $(atf_get_srcdir)/pass_helper tp3
+ cp $(atf_get_srcdir)/fail_helper tp4
+
+ cat >tp5 <<EOF
+#! $(atf-config -t atf_shell)
+echo foo
+EOF
+ chmod +x tp5
+
+ cat >Atffile <<EOF
+Content-Type: application/X-atf-atffile; version="1"
+
+prop: test-suite = atf
+
+tp: dir1
+tp: tp3
+tp: tp4
+tp: tp5
+EOF
+
+ cat >dir1/Atffile <<EOF
+Content-Type: application/X-atf-atffile; version="1"
+
+prop: test-suite = atf
+
+tp: tp1
+tp: tp2
+EOF
+}
+
+run_helpers()
+{
+ mkdir etc
+ cat >etc/atf-run.hooks <<EOF
+#! $(atf-config -t atf_shell)
+
+info_start_hook()
+{
+ atf_tps_writer_info "startinfo" "A value"
+}
+
+info_end_hook()
+{
+ atf_tps_writer_info "endinfo" "Another value"
+}
+EOF
+ echo "Using atf-run to run helpers"
+ ATF_CONFDIR=$(pwd)/etc atf-run >tps.out 2>/dev/null
+ rm -rf etc
+}
+
+atf_test_case default
+default_head()
+{
+ atf_set "descr" "Checks that the default output uses the ticker" \
+ "format"
+}
+default_body()
+{
+ create_helpers
+ run_helpers
+
+ # Check that the default output uses the ticker format.
+ atf_check -s eq:0 -o match:'test cases' -o match:'Failed test cases' \
+ -o match:'Summary for' -e empty -x 'atf-report <tps.out'
+}
+
+# XXX The test for all expect_ values should be intermixed with the other
+# tests. However, to do that, we need to migrate to using C helpers for
+# simplicity in raising signals...
+atf_test_case expect
+expect_body()
+{
+ ln -s "$(atf_get_srcdir)/expect_helpers" .
+ cat >Atffile <<EOF
+Content-Type: application/X-atf-atffile; version="1"
+
+prop: test-suite = atf
+
+tp: expect_helpers
+EOF
+ run_helpers
+
+# NO_CHECK_STYLE_BEGIN
+ cat >expout <<EOF
+tc, #.#, expect_helpers, death_and_exit, expected_death, Exit case
+tc, #.#, expect_helpers, death_and_signal, expected_death, Signal case
+tc, #.#, expect_helpers, death_but_pass, failed, Test case was expected to terminate abruptly but it continued execution
+tc, #.#, expect_helpers, exit_any_and_exit, expected_exit, Call will exit
+tc, #.#, expect_helpers, exit_but_pass, failed, Test case was expected to exit cleanly but it continued execution
+tc, #.#, expect_helpers, exit_code_and_exit, expected_exit, Call will exit
+tc, #.#, expect_helpers, fail_and_fail_check, expected_failure, And fail again: 2 checks failed as expected; see output for more details
+tc, #.#, expect_helpers, fail_and_fail_requirement, expected_failure, Fail reason: The failure
+tc, #.#, expect_helpers, fail_but_pass, failed, Test case was expecting a failure but none were raised
+tc, #.#, expect_helpers, pass_and_pass, passed
+tc, #.#, expect_helpers, pass_but_fail_check, failed, 1 checks failed; see output for more details
+tc, #.#, expect_helpers, pass_but_fail_requirement, failed, Some reason
+tc, #.#, expect_helpers, signal_any_and_signal, expected_signal, Call will signal
+tc, #.#, expect_helpers, signal_but_pass, failed, Test case was expected to receive a termination signal but it continued execution
+tc, #.#, expect_helpers, signal_no_and_signal, expected_signal, Call will signal
+tc, #.#, expect_helpers, timeout_and_hang, expected_timeout, Will overrun
+tc, #.#, expect_helpers, timeout_but_pass, failed, Test case was expected to hang but it continued execution
+tp, #.#, expect_helpers, failed
+EOF
+# NO_CHECK_STYLE_END
+ atf_check -s eq:0 -o file:expout -e empty -x \
+ "atf-report -o csv:- <tps.out | " \
+ "sed -E -e 's/[0-9]+.[0-9]{6}, /#.#, /'"
+
+# NO_CHECK_STYLE_BEGIN
+ cat >expout <<EOF
+expect_helpers (1/1): 17 test cases
+ death_and_exit: [#.#s] Expected failure: Exit case
+ death_and_signal: [#.#s] Expected failure: Signal case
+ death_but_pass: [#.#s] Failed: Test case was expected to terminate abruptly but it continued execution
+ exit_any_and_exit: [#.#s] Expected failure: Call will exit
+ exit_but_pass: [#.#s] Failed: Test case was expected to exit cleanly but it continued execution
+ exit_code_and_exit: [#.#s] Expected failure: Call will exit
+ fail_and_fail_check: [#.#s] Expected failure: And fail again: 2 checks failed as expected; see output for more details
+ fail_and_fail_requirement: [#.#s] Expected failure: Fail reason: The failure
+ fail_but_pass: [#.#s] Failed: Test case was expecting a failure but none were raised
+ pass_and_pass: [#.#s] Passed.
+ pass_but_fail_check: [#.#s] Failed: 1 checks failed; see output for more details
+ pass_but_fail_requirement: [#.#s] Failed: Some reason
+ signal_any_and_signal: [#.#s] Expected failure: Call will signal
+ signal_but_pass: [#.#s] Failed: Test case was expected to receive a termination signal but it continued execution
+ signal_no_and_signal: [#.#s] Expected failure: Call will signal
+ timeout_and_hang: [#.#s] Expected failure: Will overrun
+ timeout_but_pass: [#.#s] Failed: Test case was expected to hang but it continued execution
+[#.#s]
+
+Test cases for known bugs:
+ expect_helpers:death_and_exit: Exit case
+ expect_helpers:death_and_signal: Signal case
+ expect_helpers:exit_any_and_exit: Call will exit
+ expect_helpers:exit_code_and_exit: Call will exit
+ expect_helpers:fail_and_fail_check: And fail again: 2 checks failed as expected; see output for more details
+ expect_helpers:fail_and_fail_requirement: Fail reason: The failure
+ expect_helpers:signal_any_and_signal: Call will signal
+ expect_helpers:signal_no_and_signal: Call will signal
+ expect_helpers:timeout_and_hang: Will overrun
+
+Failed test cases:
+ expect_helpers:death_but_pass, expect_helpers:exit_but_pass, expect_helpers:fail_but_pass, expect_helpers:pass_but_fail_check, expect_helpers:pass_but_fail_requirement, expect_helpers:signal_but_pass, expect_helpers:timeout_but_pass
+
+Summary for 1 test programs:
+ 1 passed test cases.
+ 7 failed test cases.
+ 9 expected failed test cases.
+ 0 skipped test cases.
+EOF
+# NO_CHECK_STYLE_END
+ atf_check -s eq:0 -o file:expout -e empty -x \
+ "atf-report -o ticker:- <tps.out | " \
+ "sed -E -e 's/[0-9]+.[0-9]{6}/#.#/'"
+
+ # Just ensure that this does not crash for now...
+ atf_check -s eq:0 -o ignore -e empty -x "atf-report -o xml:- <tps.out"
+}
+
+atf_test_case oflag
+oflag_head()
+{
+ atf_set "descr" "Checks that the -o flag works"
+}
+oflag_body()
+{
+ create_helpers
+ run_helpers
+
+ # Get the default output.
+ atf_check -s eq:0 -o save:stdout -e empty -x 'atf-report <tps.out'
+ mv stdout defout
+
+ # Check that changing the stdout output works.
+ atf_check -s eq:0 -o save:stdout -e empty -x 'atf-report -o csv:- <tps.out'
+ atf_check -s eq:1 -o empty -e empty cmp -s defout stdout
+ cp stdout expcsv
+
+ # Check that sending the output to a file does not write to stdout.
+ atf_check -s eq:0 -o empty -e empty -x 'atf-report -o csv:fmt.out <tps.out'
+ atf_check -s eq:0 -o empty -e empty cmp -s expcsv fmt.out
+ rm -f fmt.out
+
+ # Check that defining two outputs using the same format works.
+ atf_check -s eq:0 -o empty -e empty -x \
+ 'atf-report -o csv:fmt.out -o csv:fmt2.out <tps.out'
+ atf_check -s eq:0 -o empty -e empty cmp -s expcsv fmt.out
+ atf_check -s eq:0 -o empty -e empty cmp -s fmt.out fmt2.out
+ rm -f fmt.out fmt2.out
+
+ # Check that defining two outputs using different formats works.
+ atf_check -s eq:0 -o empty -e empty -x \
+ 'atf-report -o csv:fmt.out -o ticker:fmt2.out <tps.out'
+ atf_check -s eq:0 -o empty -e empty cmp -s expcsv fmt.out
+ atf_check -s eq:1 -o empty -e empty cmp -s fmt.out fmt2.out
+ atf_check -s eq:0 -o ignore -e empty grep "test cases" fmt2.out
+ atf_check -s eq:0 -o ignore -e empty grep "Failed test cases" fmt2.out
+ atf_check -s eq:0 -o ignore -e empty grep "Summary for" fmt2.out
+ rm -f fmt.out fmt2.out
+
+ # Check that defining two outputs over the same file does not work.
+ atf_check -s eq:1 -o empty -e match:'more than once' -x \
+ 'atf-report -o csv:fmt.out -o ticker:fmt.out <tps.out'
+ rm -f fmt.out
+
+ # Check that defining two outputs over stdout (but using different
+ # paths) does not work.
+ atf_check -s eq:1 -o empty -e match:'more than once' -x \
+ 'atf-report -o csv:- -o ticker:/dev/stdout <tps.out'
+ rm -f fmt.out
+}
+
+atf_test_case output_csv
+output_csv_head()
+{
+ atf_set "descr" "Checks the CSV output format"
+}
+output_csv_body()
+{
+ create_helpers
+ run_helpers
+
+# NO_CHECK_STYLE_BEGIN
+ cat >expout <<EOF
+tc, #.#, dir1/tp1, main, passed
+tp, #.#, dir1/tp1, passed
+tc, #.#, dir1/tp2, main, failed, This always fails
+tp, #.#, dir1/tp2, failed
+tc, #.#, tp3, main, passed
+tp, #.#, tp3, passed
+tc, #.#, tp4, main, failed, This always fails
+tp, #.#, tp4, failed
+tp, #.#, tp5, bogus, Invalid format for test case list: 1: Unexpected token \`<<NEWLINE>>'; expected \`:'
+EOF
+# NO_CHECK_STYLE_END
+
+ atf_check -s eq:0 -o file:expout -e empty -x \
+ "atf-report -o csv:- <tps.out | sed -E -e 's/[0-9]+.[0-9]{6}, /#.#, /'"
+}
+
+atf_test_case output_ticker
+output_ticker_head()
+{
+ atf_set "descr" "Checks the ticker output format"
+}
+output_ticker_body()
+{
+ create_helpers
+ run_helpers
+
+# NO_CHECK_STYLE_BEGIN
+ cat >expout <<EOF
+dir1/tp1 (1/5): 1 test cases
+ main: [#.#s] Passed.
+[#.#s]
+
+dir1/tp2 (2/5): 1 test cases
+ main: [#.#s] Failed: This always fails
+[#.#s]
+
+tp3 (3/5): 1 test cases
+ main: [#.#s] Passed.
+[#.#s]
+
+tp4 (4/5): 1 test cases
+ main: [#.#s] Failed: This always fails
+[#.#s]
+
+tp5 (5/5): 0 test cases
+tp5: BOGUS TEST PROGRAM: Cannot trust its results because of \`Invalid format for test case list: 1: Unexpected token \`<<NEWLINE>>'; expected \`:''
+[#.#s]
+
+Failed (bogus) test programs:
+ tp5
+
+Failed test cases:
+ dir1/tp2:main, tp4:main
+
+Summary for 5 test programs:
+ 2 passed test cases.
+ 2 failed test cases.
+ 0 expected failed test cases.
+ 0 skipped test cases.
+EOF
+
+ atf_check -s eq:0 -o file:expout -e empty -x \
+ "atf-report -o ticker:- <tps.out | sed -E -e 's/[0-9]+.[0-9]{6}/#.#/'"
+}
+# NO_CHECK_STYLE_END
+
+atf_test_case output_xml
+output_xml_head()
+{
+ atf_set "descr" "Checks the XML output format"
+}
+output_xml_body()
+{
+ create_helpers
+ run_helpers
+
+# NO_CHECK_STYLE_BEGIN
+ cat >expout <<EOF
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!DOCTYPE tests-results PUBLIC "-//NetBSD//DTD ATF Tests Results 0.1//EN" "http://www.NetBSD.org/XML/atf/tests-results.dtd">
+
+<tests-results>
+<info class="startinfo">A value</info>
+<tp id="dir1/tp1">
+<tc id="main">
+<passed />
+<tc-time>#.#</tc-time></tc>
+<tp-time>#.#</tp-time></tp>
+<tp id="dir1/tp2">
+<tc id="main">
+<failed>This always fails</failed>
+<tc-time>#.#</tc-time></tc>
+<tp-time>#.#</tp-time></tp>
+<tp id="tp3">
+<tc id="main">
+<passed />
+<tc-time>#.#</tc-time></tc>
+<tp-time>#.#</tp-time></tp>
+<tp id="tp4">
+<tc id="main">
+<failed>This always fails</failed>
+<tc-time>#.#</tc-time></tc>
+<tp-time>#.#</tp-time></tp>
+<tp id="tp5">
+<failed>Invalid format for test case list: 1: Unexpected token \`&lt;&lt;NEWLINE&gt;&gt;'; expected \`:'</failed>
+<tp-time>#.#</tp-time></tp>
+<info class="endinfo">Another value</info>
+</tests-results>
+EOF
+# NO_CHECK_STYLE_END
+
+ atf_check -s eq:0 -o file:expout -e empty -x \
+ "atf-report -o xml:- < tps.out | sed -E -e 's/>[0-9]+.[0-9]{6}</>#.#</'"
+}
+
+atf_test_case output_xml_space
+output_xml_space_head()
+{
+ atf_set "descr" "Checks that the XML output format properly preserves" \
+ "leading and trailing whitespace in stdout and stderr" \
+ "lines"
+}
+output_xml_space_body()
+{
+ export TESTCASE=diff
+ cp $(atf_get_srcdir)/misc_helpers .
+ cat >Atffile <<EOF
+Content-Type: application/X-atf-atffile; version="1"
+
+prop: test-suite = atf
+
+tp: misc_helpers
+EOF
+
+# NO_CHECK_STYLE_BEGIN
+ cat >expout <<EOF
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!DOCTYPE tests-results PUBLIC "-//NetBSD//DTD ATF Tests Results 0.1//EN" "http://www.NetBSD.org/XML/atf/tests-results.dtd">
+
+<tests-results>
+<info class="startinfo">A value</info>
+<tp id="misc_helpers">
+<tc id="diff">
+<so>--- a 2007-11-04 14:00:41.000000000 +0100</so>
+<so>+++ b 2007-11-04 14:00:48.000000000 +0100</so>
+<so>@@ -1,7 +1,7 @@</so>
+<so> This test is meant to simulate a diff.</so>
+<so> Blank space at beginning of context lines must be preserved.</so>
+<so> </so>
+<so>-First original line.</so>
+<so>-Second original line.</so>
+<so>+First modified line.</so>
+<so>+Second modified line.</so>
+<so> </so>
+<so> EOF</so>
+<passed />
+<tc-time>#.#</tc-time></tc>
+<tp-time>#.#</tp-time></tp>
+<info class="endinfo">Another value</info>
+</tests-results>
+EOF
+# NO_CHECK_STYLE_END
+
+ run_helpers
+ atf_check -s eq:0 -o file:expout -e empty -x \
+ "atf-report -o xml:- <tps.out | sed -E -e 's/>[0-9]+.[0-9]{6}</>#.#</'"
+}
+
+atf_test_case too_many_args
+too_many_args_body()
+{
+ cat >experr <<EOF
+atf-report: ERROR: No arguments allowed
+EOF
+ atf_check -s eq:1 -o empty -e file:experr atf-report foo
+}
+
+atf_init_test_cases()
+{
+ atf_add_test_case default
+ atf_add_test_case expect
+ atf_add_test_case oflag
+ atf_add_test_case output_csv
+ atf_add_test_case output_ticker
+ atf_add_test_case output_xml
+ atf_add_test_case output_xml_space
+ atf_add_test_case too_many_args
+}
+
+# vim: syntax=sh:expandtab:shiftwidth=4:softtabstop=4
diff --git a/unit/atf-src/tools/atf-run.1 b/unit/atf-src/tools/atf-run.1
new file mode 100644
index 0000000..d593f47
--- /dev/null
+++ b/unit/atf-src/tools/atf-run.1
@@ -0,0 +1,202 @@
+.\"
+.\" Automated Testing Framework (atf)
+.\"
+.\" Copyright (c) 2007 The NetBSD Foundation, Inc.
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
+.\" CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+.\" INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+.\" MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+.\" IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
+.\" DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+.\" GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+.\" INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+.\" IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+.\" OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+.\" IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+.\"
+.Dd November 1, 2010
+.Dt ATF-RUN 1
+.Os
+.Sh NAME
+.Nm atf-run
+.Nd executes a collection of test programs
+.Sh SYNOPSIS
+.Nm
+.Op Fl v Ar var1=value1 Op .. Fl v Ar varN=valueN
+.Op Ar test_program1 Op Ar .. test_programN
+.Nm
+.Fl h
+.Sh DESCRIPTION
+.Nm
+executes a collection of test programs or, in other words, a complete
+test suite.
+The results of each test program are collected by the tool, and are then
+multiplexed into a single machine-parseable report; see
+.Xr atf-formats 5
+for more details.
+This report can later be transformed into many different and saner formats
+using the
+.Nm atf-report
+tool.
+.Pp
+The list of test programs to execute is read from an
+.Pa Atffile
+present in the current directory.
+This file describes the test suite stored in the directory it lives in,
+which aside from the list of test programs also includes meta-data and
+configuration variables.
+.Pp
+.Nm
+is also in charge of reading the configuration files that tune the behavior
+of each test program and passing down the necessary variables to them.
+More details on how this is done are given in the
+.Sx Configuration
+section.
+.Pp
+In the first synopsis form,
+.Nm
+parses the
+.Pa Atffile
+in the current directory and runs all the test programs specified in it.
+If any test program names are given as part of the command line, those are
+the ones executed instead of the complete list.
+.Pp
+In the second synopsis form,
+.Nm
+will print information about all supported options and their purpose.
+.Pp
+The following options are available:
+.Bl -tag -width XvXvarXvalueXX
+.It Fl h
+Shows a short summary of all available options and their purpose.
+.It Fl v Ar var=value
+Sets the configuration variable
+.Ar var
+to the given value
+.Ar value .
+.El
+.Ss Configuration
+.Nm
+reads configuration data from multiple places.
+After all of these places have been analyzed, a list of variable-value
+pairs are passed to the test programs to be run.
+.Pp
+The following locations are scanned for configuration data, in order.
+Items down the list override values defined above them:
+.Bl -enum
+.It
+Configuration variables defined in the
+.Pa Atffile .
+.It
+Configuration variables defined in the system-wide configuration file
+shared among all test suites.
+This lives in
+.Pa ${ATF_CONFDIR}/common.conf .
+.It
+Configuration variables defined in the system-wide test-suite-specific
+configuration file.
+This lives in
+.Pa ${ATF_CONFDIR}/<test-suite>.conf .
+.It
+Configuration variables defined in the user-specific configuration file
+shared among all test suites.
+This lives in
+.Pa ${HOME}/.atf/common.conf .
+.It
+Configuration variables defined in the user-specific test-suite-specific
+configuration file.
+This lives in
+.Pa ${HOME}/.atf/<test-suite>.conf .
+.It
+Configuration variables provided as part of the command line through the
+.Fl v
+option.
+.El
+.Pp
+The value of
+.Va ATF_CONFDIR
+in the above list determined as detailed in
+.Xr atf-config 1 .
+.Pp
+The following configuration variables are globally recognized:
+.Bl -tag -width XunprivilegedXuserXX
+.It Va unprivileged-user
+The name of the system user that atf-run will drop root privileges into
+for test cases defining
+.Sq require.user=unprivileged .
+Note that this is
+.Em not provided for security purposes ;
+this feature is only for the convenience of the user.
+.El
+.Ss Hooks
+.Nm Ns 's
+internal behavior can be customized by the system administrator and the
+user by means of hooks.
+These hooks are written in the shell script language for simplicity and
+are stored in the following files, which are read in the order provided
+below:
+.Bl -enum
+.It
+${ATF_CONFDIR}/atf-run.hooks
+.It
+${HOME}/.atf/atf-run.hooks
+.El
+.Pp
+The following hooks are supported:
+.Bl -tag -width infoXstartXhookXX
+.It info_start_hook
+Called before
+.Nm
+executes any test program.
+The purpose of this hook is to write additional
+.Sq info
+stanzas to the top of the output report; these are defined by the
+.Sq application/X-atf-tps format
+described in
+.Xr atf-formats 5 .
+Always use the
+.Sq atf_tps_writer_info
+function to print these.
+.Pp
+This takes no parameters.
+.It info_end_hook
+Similar to
+.Sq info_start_hook
+but executed after all test programs have been run so that additional
+.Sq info
+stanzas can be added to the bottom of the output report.
+.Pp
+This takes no parameters.
+.El
+.Pp
+All hooks are accompanied by a function named
+.Sq default_<hook_name>
+that can be executed by them to invoke the default behavior built into
+.Nm .
+For example, in order to extend the default
+.Sq info_start_hook
+hook, we could write the following function:
+.Bd -literal -offset indent
+info_start_hook()
+{
+ default_info_start_hook "${@}"
+
+ atf_tps_writer_info "uptime" "$(uptime)"
+}
+.Ed
+.Sh SEE ALSO
+.Xr atf-report 1 ,
+.Xr atf-test-program 1 ,
+.Xr atf 7
diff --git a/unit/atf-src/tools/atf-run.cpp b/unit/atf-src/tools/atf-run.cpp
new file mode 100644
index 0000000..a05b681
--- /dev/null
+++ b/unit/atf-src/tools/atf-run.cpp
@@ -0,0 +1,567 @@
+//
+// Automated Testing Framework (atf)
+//
+// Copyright (c) 2007 The NetBSD Foundation, Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
+// CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+// IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
+// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+// IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+
+#if defined(HAVE_CONFIG_H)
+#include "config.h"
+#endif
+
+extern "C" {
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <unistd.h>
+}
+
+#include <algorithm>
+#include <cassert>
+#include <cerrno>
+#include <cstdlib>
+#include <cstring>
+#include <fstream>
+#include <iostream>
+#include <map>
+#include <string>
+
+#include "application.hpp"
+#include "atffile.hpp"
+#include "config.hpp"
+#include "config_file.hpp"
+#include "env.hpp"
+#include "exceptions.hpp"
+#include "fs.hpp"
+#include "parser.hpp"
+#include "process.hpp"
+#include "requirements.hpp"
+#include "test-program.hpp"
+#include "text.hpp"
+
+namespace {
+
+typedef std::map< std::string, std::string > vars_map;
+
+} // anonymous namespace
+
+#if defined(MAXCOMLEN)
+static const std::string::size_type max_core_name_length = MAXCOMLEN;
+#else
+static const std::string::size_type max_core_name_length = std::string::npos;
+#endif
+
+class atf_run : public tools::application::app {
+ static const char* m_description;
+
+ vars_map m_cmdline_vars;
+
+ static vars_map::value_type parse_var(const std::string&);
+
+ void process_option(int, const char*);
+ std::string specific_args(void) const;
+ options_set specific_options(void) const;
+
+ void parse_vflag(const std::string&);
+
+ std::vector< std::string > conf_args(void) const;
+
+ size_t count_tps(std::vector< std::string >) const;
+
+ int run_test(const tools::fs::path&, tools::test_program::atf_tps_writer&,
+ const vars_map&);
+ int run_test_directory(const tools::fs::path&,
+ tools::test_program::atf_tps_writer&);
+ int run_test_program(const tools::fs::path&,
+ tools::test_program::atf_tps_writer&,
+ const vars_map&);
+
+ tools::test_program::test_case_result get_test_case_result(
+ const std::string&, const tools::process::status&,
+ const tools::fs::path&) const;
+
+public:
+ atf_run(void);
+
+ int main(void);
+};
+
+static void
+sanitize_gdb_env(void)
+{
+ try {
+ tools::env::unset("TERM");
+ } catch (...) {
+ // Just swallow exceptions here; they cannot propagate into C, which
+ // is where this function is called from, and even if these exceptions
+ // appear they are benign.
+ }
+}
+
+static void
+dump_stacktrace(const tools::fs::path& tp, const tools::process::status& s,
+ const tools::fs::path& workdir,
+ tools::test_program::atf_tps_writer& w)
+{
+ assert(s.signaled() && s.coredump());
+
+ w.stderr_tc("Test program crashed; attempting to get stack trace");
+
+ const tools::fs::path corename = workdir /
+ (tp.leaf_name().substr(0, max_core_name_length) + ".core");
+ if (!tools::fs::exists(corename)) {
+ w.stderr_tc("Expected file " + corename.str() + " not found");
+ return;
+ }
+
+ const tools::fs::path gdb(GDB);
+ const tools::fs::path gdbout = workdir / "gdb.out";
+ const tools::process::argv_array args(gdb.leaf_name().c_str(), "-batch",
+ "-q", "-ex", "bt", tp.c_str(),
+ corename.c_str(), NULL);
+ tools::process::status status = tools::process::exec(
+ gdb, args,
+ tools::process::stream_redirect_path(gdbout),
+ tools::process::stream_redirect_path(tools::fs::path("/dev/null")),
+ sanitize_gdb_env);
+ if (!status.exited() || status.exitstatus() != EXIT_SUCCESS) {
+ w.stderr_tc("Execution of " GDB " failed");
+ return;
+ }
+
+ std::ifstream input(gdbout.c_str());
+ if (input) {
+ std::string line;
+ while (std::getline(input, line).good())
+ w.stderr_tc(line);
+ input.close();
+ }
+
+ w.stderr_tc("Stack trace complete");
+}
+
+const char* atf_run::m_description =
+ "atf-run is a tool that runs tests programs and collects their "
+ "results.";
+
+atf_run::atf_run(void) :
+ app(m_description, "atf-run(1)", "atf(7)")
+{
+}
+
+void
+atf_run::process_option(int ch, const char* arg)
+{
+ switch (ch) {
+ case 'v':
+ parse_vflag(arg);
+ break;
+
+ default:
+ std::abort();
+ }
+}
+
+std::string
+atf_run::specific_args(void)
+ const
+{
+ return "[test-program1 .. test-programN]";
+}
+
+atf_run::options_set
+atf_run::specific_options(void)
+ const
+{
+ using tools::application::option;
+ options_set opts;
+ opts.insert(option('v', "var=value", "Sets the configuration variable "
+ "`var' to `value'; overrides "
+ "values in configuration files"));
+ return opts;
+}
+
+void
+atf_run::parse_vflag(const std::string& str)
+{
+ if (str.empty())
+ throw std::runtime_error("-v requires a non-empty argument");
+
+ std::vector< std::string > ws = tools::text::split(str, "=");
+ if (ws.size() == 1 && str[str.length() - 1] == '=') {
+ m_cmdline_vars[ws[0]] = "";
+ } else {
+ if (ws.size() != 2)
+ throw std::runtime_error("-v requires an argument of the form "
+ "var=value");
+
+ m_cmdline_vars[ws[0]] = ws[1];
+ }
+}
+
+int
+atf_run::run_test(const tools::fs::path& tp,
+ tools::test_program::atf_tps_writer& w,
+ const vars_map& config)
+{
+ tools::fs::file_info fi(tp);
+
+ int errcode;
+ if (fi.get_type() == tools::fs::file_info::dir_type)
+ errcode = run_test_directory(tp, w);
+ else {
+ const vars_map effective_config =
+ tools::config_file::merge_configs(config, m_cmdline_vars);
+
+ errcode = run_test_program(tp, w, effective_config);
+ }
+ return errcode;
+}
+
+int
+atf_run::run_test_directory(const tools::fs::path& tp,
+ tools::test_program::atf_tps_writer& w)
+{
+ tools::atffile af = tools::read_atffile(tp / "Atffile");
+
+ vars_map test_suite_vars;
+ {
+ vars_map::const_iterator iter = af.props().find("test-suite");
+ assert(iter != af.props().end());
+ test_suite_vars = tools::config_file::read_config_files((*iter).second);
+ }
+
+ bool ok = true;
+ for (std::vector< std::string >::const_iterator iter = af.tps().begin();
+ iter != af.tps().end(); iter++) {
+ const bool result = run_test(tp / *iter, w,
+ tools::config_file::merge_configs(af.conf(), test_suite_vars));
+ ok &= (result == EXIT_SUCCESS);
+ }
+
+ return ok ? EXIT_SUCCESS : EXIT_FAILURE;
+}
+
+tools::test_program::test_case_result
+atf_run::get_test_case_result(const std::string& broken_reason,
+ const tools::process::status& s,
+ const tools::fs::path& resfile)
+ const
+{
+ using tools::text::to_string;
+ using tools::test_program::read_test_case_result;
+ using tools::test_program::test_case_result;
+
+ if (!broken_reason.empty()) {
+ test_case_result tcr;
+
+ try {
+ tcr = read_test_case_result(resfile);
+
+ if (tcr.state() == "expected_timeout") {
+ return tcr;
+ } else {
+ return test_case_result("failed", -1, broken_reason);
+ }
+ } catch (const std::runtime_error&) {
+ return test_case_result("failed", -1, broken_reason);
+ }
+ }
+
+ if (s.exited()) {
+ test_case_result tcr;
+
+ try {
+ tcr = read_test_case_result(resfile);
+ } catch (const std::runtime_error& e) {
+ return test_case_result("failed", -1, "Test case exited "
+ "normally but failed to create the results file: " +
+ std::string(e.what()));
+ }
+
+ if (tcr.state() == "expected_death") {
+ return tcr;
+ } else if (tcr.state() == "expected_exit") {
+ if (tcr.value() == -1 || s.exitstatus() == tcr.value())
+ return tcr;
+ else
+ return test_case_result("failed", -1, "Test case was "
+ "expected to exit with a " + to_string(tcr.value()) +
+ " error code but returned " + to_string(s.exitstatus()));
+ } else if (tcr.state() == "expected_failure") {
+ if (s.exitstatus() == EXIT_SUCCESS)
+ return tcr;
+ else
+ return test_case_result("failed", -1, "Test case returned an "
+ "error in expected_failure mode but it should not have");
+ } else if (tcr.state() == "expected_signal") {
+ return test_case_result("failed", -1, "Test case exited cleanly "
+ "but was expected to receive a signal");
+ } else if (tcr.state() == "failed") {
+ if (s.exitstatus() == EXIT_SUCCESS)
+ return test_case_result("failed", -1, "Test case "
+ "exited successfully but reported failure");
+ else
+ return tcr;
+ } else if (tcr.state() == "passed") {
+ if (s.exitstatus() == EXIT_SUCCESS)
+ return tcr;
+ else
+ return test_case_result("failed", -1, "Test case exited as "
+ "passed but reported an error");
+ } else if (tcr.state() == "skipped") {
+ if (s.exitstatus() == EXIT_SUCCESS)
+ return tcr;
+ else
+ return test_case_result("failed", -1, "Test case exited as "
+ "skipped but reported an error");
+ }
+ } else if (s.signaled()) {
+ test_case_result tcr;
+
+ try {
+ tcr = read_test_case_result(resfile);
+ } catch (const std::runtime_error&) {
+ return test_case_result("failed", -1, "Test program received "
+ "signal " + tools::text::to_string(s.termsig()) +
+ (s.coredump() ? " (core dumped)" : ""));
+ }
+
+ if (tcr.state() == "expected_death") {
+ return tcr;
+ } else if (tcr.state() == "expected_signal") {
+ if (tcr.value() == -1 || s.termsig() == tcr.value())
+ return tcr;
+ else
+ return test_case_result("failed", -1, "Test case was "
+ "expected to exit due to a " + to_string(tcr.value()) +
+ " signal but got " + to_string(s.termsig()));
+ } else {
+ return test_case_result("failed", -1, "Test program received "
+ "signal " + tools::text::to_string(s.termsig()) +
+ (s.coredump() ? " (core dumped)" : "") + " and created a "
+ "bogus results file");
+ }
+ }
+ std::abort();
+ return test_case_result();
+}
+
+int
+atf_run::run_test_program(const tools::fs::path& tp,
+ tools::test_program::atf_tps_writer& w,
+ const vars_map& config)
+{
+ int errcode = EXIT_SUCCESS;
+
+ tools::test_program::metadata md;
+ try {
+ md = tools::test_program::get_metadata(tp, config);
+ } catch (const tools::parser::format_error& e) {
+ w.start_tp(tp.str(), 0);
+ w.end_tp("Invalid format for test case list: " + std::string(e.what()));
+ return EXIT_FAILURE;
+ } catch (const tools::parser::parse_errors& e) {
+ const std::string reason = tools::text::join(e, "; ");
+ w.start_tp(tp.str(), 0);
+ w.end_tp("Invalid format for test case list: " + reason);
+ return EXIT_FAILURE;
+ }
+
+ tools::fs::temp_dir resdir(
+ tools::fs::path(tools::config::get("atf_workdir")) / "atf-run.XXXXXX");
+
+ w.start_tp(tp.str(), md.test_cases.size());
+ if (md.test_cases.empty()) {
+ w.end_tp("Bogus test program: reported 0 test cases");
+ errcode = EXIT_FAILURE;
+ } else {
+ for (std::map< std::string, vars_map >::const_iterator iter
+ = md.test_cases.begin(); iter != md.test_cases.end(); iter++) {
+ const std::string& tcname = (*iter).first;
+ const vars_map& tcmd = (*iter).second;
+
+ w.start_tc(tcname);
+
+ try {
+ const std::string& reqfail = tools::check_requirements(
+ tcmd, config);
+ if (!reqfail.empty()) {
+ w.end_tc("skipped", reqfail);
+ continue;
+ }
+ } catch (const std::runtime_error& e) {
+ w.end_tc("failed", e.what());
+ errcode = EXIT_FAILURE;
+ continue;
+ }
+
+ const std::pair< int, int > user = tools::get_required_user(
+ tcmd, config);
+
+ tools::fs::path resfile = resdir.get_path() / "tcr";
+ assert(!tools::fs::exists(resfile));
+ try {
+ const bool has_cleanup = tools::text::to_bool(
+ (*tcmd.find("has.cleanup")).second);
+
+ tools::fs::temp_dir workdir(tools::fs::path(tools::config::get(
+ "atf_workdir")) / "atf-run.XXXXXX");
+ if (user.first != -1 && user.second != -1) {
+ if (::chown(workdir.get_path().c_str(), user.first,
+ user.second) == -1) {
+ throw tools::system_error("chown(" +
+ workdir.get_path().str() + ")", "chown(2) failed",
+ errno);
+ }
+ resfile = workdir.get_path() / "tcr";
+ }
+
+ std::pair< std::string, const tools::process::status > s =
+ tools::test_program::run_test_case(
+ tp, tcname, "body", tcmd, config,
+ resfile, workdir.get_path(), w);
+ if (s.second.signaled() && s.second.coredump())
+ dump_stacktrace(tp, s.second, workdir.get_path(), w);
+ if (has_cleanup)
+ (void)tools::test_program::run_test_case(
+ tp, tcname, "cleanup", tcmd,
+ config, resfile, workdir.get_path(), w);
+
+ // TODO: Force deletion of workdir.
+
+ tools::test_program::test_case_result tcr =
+ get_test_case_result(s.first, s.second, resfile);
+
+ w.end_tc(tcr.state(), tcr.reason());
+ if (tcr.state() == "failed")
+ errcode = EXIT_FAILURE;
+ } catch (...) {
+ if (tools::fs::exists(resfile))
+ tools::fs::remove(resfile);
+ throw;
+ }
+ if (tools::fs::exists(resfile))
+ tools::fs::remove(resfile);
+
+ }
+ w.end_tp("");
+ }
+
+ return errcode;
+}
+
+size_t
+atf_run::count_tps(std::vector< std::string > tps)
+ const
+{
+ size_t ntps = 0;
+
+ for (std::vector< std::string >::const_iterator iter = tps.begin();
+ iter != tps.end(); iter++) {
+ tools::fs::path tp(*iter);
+ tools::fs::file_info fi(tp);
+
+ if (fi.get_type() == tools::fs::file_info::dir_type) {
+ tools::atffile af = tools::read_atffile(tp / "Atffile");
+ std::vector< std::string > aux = af.tps();
+ for (std::vector< std::string >::iterator i2 = aux.begin();
+ i2 != aux.end(); i2++)
+ *i2 = (tp / *i2).str();
+ ntps += count_tps(aux);
+ } else
+ ntps++;
+ }
+
+ return ntps;
+}
+
+static
+void
+call_hook(const std::string& tool, const std::string& hook)
+{
+ const tools::fs::path sh(tools::config::get("atf_shell"));
+ const tools::fs::path hooks =
+ tools::fs::path(tools::config::get("atf_pkgdatadir")) / (tool + ".hooks");
+
+ const tools::process::status s =
+ tools::process::exec(sh,
+ tools::process::argv_array(sh.c_str(), hooks.c_str(),
+ hook.c_str(), NULL),
+ tools::process::stream_inherit(),
+ tools::process::stream_inherit());
+
+
+ if (!s.exited() || s.exitstatus() != EXIT_SUCCESS)
+ throw std::runtime_error("Failed to run the '" + hook + "' hook "
+ "for '" + tool + "'");
+}
+
+int
+atf_run::main(void)
+{
+ tools::atffile af = tools::read_atffile(tools::fs::path("Atffile"));
+
+ std::vector< std::string > tps;
+ tps = af.tps();
+ if (m_argc >= 1) {
+ // TODO: Ensure that the given test names are listed in the
+ // Atffile. Take into account that the file can be using globs.
+ tps.clear();
+ for (int i = 0; i < m_argc; i++)
+ tps.push_back(m_argv[i]);
+ }
+
+ // Read configuration data for this test suite.
+ vars_map test_suite_vars;
+ {
+ vars_map::const_iterator iter = af.props().find("test-suite");
+ assert(iter != af.props().end());
+ test_suite_vars = tools::config_file::read_config_files((*iter).second);
+ }
+
+ tools::test_program::atf_tps_writer w(std::cout);
+ call_hook("atf-run", "info_start_hook");
+ w.ntps(count_tps(tps));
+
+ bool ok = true;
+ for (std::vector< std::string >::const_iterator iter = tps.begin();
+ iter != tps.end(); iter++) {
+ const bool result = run_test(tools::fs::path(*iter), w,
+ tools::config_file::merge_configs(af.conf(), test_suite_vars));
+ ok &= (result == EXIT_SUCCESS);
+ }
+
+ call_hook("atf-run", "info_end_hook");
+
+ return ok ? EXIT_SUCCESS : EXIT_FAILURE;
+}
+
+int
+main(int argc, char* const* argv)
+{
+ return atf_run().run(argc, argv);
+}
diff --git a/unit/atf-src/tools/atf-run_test.sh b/unit/atf-src/tools/atf-run_test.sh
new file mode 100644
index 0000000..afd013e
--- /dev/null
+++ b/unit/atf-src/tools/atf-run_test.sh
@@ -0,0 +1,1134 @@
+#
+# Automated Testing Framework (atf)
+#
+# Copyright (c) 2007 The NetBSD Foundation, Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
+# CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+# IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
+# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+# GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+# IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+# IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+
+create_atffile()
+{
+ ATF_CONFDIR="$(pwd)"; export ATF_CONFDIR
+
+ cat >Atffile <<EOF
+Content-Type: application/X-atf-atffile; version="1"
+
+prop: test-suite = atf
+
+EOF
+ for f in "${@}"; do
+ echo "tp: ${f}" >>Atffile
+ done
+}
+
+create_helper()
+{
+ cp $(atf_get_srcdir)/misc_helpers helper
+ create_atffile helper
+ TESTCASE=${1}; export TESTCASE
+}
+
+create_helper_stdin()
+{
+ # TODO: This really, really, really must use real test programs.
+ cat >${1} <<EOF
+#! $(atf-config -t atf_shell)
+while [ \${#} -gt 0 ]; do
+ case \${1} in
+ -l)
+ echo 'Content-Type: application/X-atf-tp; version="1"'
+ echo
+EOF
+ cnt=1
+ while [ ${cnt} -le ${2} ]; do
+ echo "echo 'ident: tc${cnt}'" >>${1}
+ [ ${cnt} -lt ${2} ] && echo "echo" >>${1}
+ cnt=$((${cnt} + 1))
+ done
+cat >>${1} <<EOF
+ exit 0
+ ;;
+ -r*)
+ resfile=\$(echo \${1} | cut -d r -f 2-)
+ ;;
+ esac
+ testcase=\$(echo \${1} | cut -d : -f 1)
+ shift
+done
+EOF
+ cat >>${1}
+}
+
+create_mount_helper()
+{
+ cat >${1} <<EOF
+#! /usr/bin/env atf-sh
+
+do_mount() {
+ platform=\$(uname)
+ case \${platform} in
+ Linux|NetBSD)
+ mount -t tmpfs tmpfs \${1} || atf_fail "Mount failed"
+ ;;
+ FreeBSD)
+ mdmfs -s 16m md \${1} || atf_fail "Mount failed"
+ ;;
+ SunOS)
+ mount -F tmpfs tmpfs \$(pwd)/\${1} || atf_fail "Mount failed"
+ ;;
+ *)
+ atf_fail "create_mount_helper called for an unsupported platform."
+ ;;
+ esac
+}
+
+atf_test_case main
+main_head() {
+ atf_set "require.user" "root"
+}
+main_body() {
+EOF
+ cat >>${1}
+ cat >>${1} <<EOF
+}
+
+atf_init_test_cases()
+{
+ atf_add_test_case main
+}
+EOF
+}
+
+atf_test_case no_warnings
+no_warnings_head()
+{
+ atf_set "descr" "Tests that atf-run suppresses warnings about not running" \
+ "within atf-run"
+}
+no_warnings_body()
+{
+ create_helper pass
+ atf_check -s eq:0 -o ignore -e not-match:'WARNING.*atf-run' atf-run helper
+}
+
+atf_test_case config
+config_head()
+{
+ atf_set "descr" "Tests that the config files are read in the correct" \
+ "order"
+}
+config_body()
+{
+ create_helper config
+
+ mkdir etc
+ mkdir .atf
+
+ echo "First: read system-wide common.conf."
+ cat >etc/common.conf <<EOF
+Content-Type: application/X-atf-config; version="1"
+
+1st = "sw common"
+2nd = "sw common"
+3rd = "sw common"
+4th = "sw common"
+EOF
+ atf_check -s eq:0 \
+ -o match:'1st: sw common' \
+ -o match:'2nd: sw common' \
+ -o match:'3rd: sw common' \
+ -o match:'4th: sw common' \
+ -e ignore -x \
+ "ATF_CONFDIR=$(pwd)/etc HOME=$(pwd) atf-run helper"
+
+ echo "Second: read system-wide <test-suite>.conf."
+ cat >etc/atf.conf <<EOF
+Content-Type: application/X-atf-config; version="1"
+
+1st = "sw atf"
+EOF
+ atf_check -s eq:0 \
+ -o match:'1st: sw atf' \
+ -o match:'2nd: sw common' \
+ -o match:'3rd: sw common' \
+ -o match:'4th: sw common' \
+ -e ignore -x \
+ "ATF_CONFDIR=$(pwd)/etc HOME=$(pwd) atf-run helper"
+
+ echo "Third: read user-specific common.conf."
+ cat >.atf/common.conf <<EOF
+Content-Type: application/X-atf-config; version="1"
+
+2nd = "us common"
+EOF
+ atf_check -s eq:0 \
+ -o match:'1st: sw atf' \
+ -o match:'2nd: us common' \
+ -o match:'3rd: sw common' \
+ -o match:'4th: sw common' \
+ -e ignore -x \
+ "ATF_CONFDIR=$(pwd)/etc HOME=$(pwd) atf-run helper"
+
+ echo "Fourth: read user-specific <test-suite>.conf."
+ cat >.atf/atf.conf <<EOF
+Content-Type: application/X-atf-config; version="1"
+
+3rd = "us atf"
+EOF
+ atf_check -s eq:0 \
+ -o match:'1st: sw atf' \
+ -o match:'2nd: us common' \
+ -o match:'3rd: us atf' \
+ -o match:'4th: sw common' \
+ -e ignore -x \
+ "ATF_CONFDIR=$(pwd)/etc HOME=$(pwd) atf-run helper"
+}
+
+atf_test_case vflag
+vflag_head()
+{
+ atf_set "descr" "Tests that the -v flag works and that it properly" \
+ "overrides the values in configuration files"
+}
+vflag_body()
+{
+ create_helper testvar
+
+ echo "Checking that 'testvar' is not defined."
+ atf_check -s eq:1 -o ignore -e ignore -x \
+ "ATF_CONFDIR=$(pwd)/etc atf-run helper"
+
+ echo "Checking that defining 'testvar' trough '-v' works."
+ atf_check -s eq:0 -o match:'testvar: a value' -e ignore -x \
+ "ATF_CONFDIR=$(pwd)/etc atf-run -v testvar='a value' helper"
+
+ echo "Checking that defining 'testvar' trough the configuration" \
+ "file works."
+ mkdir etc
+ cat >etc/common.conf <<EOF
+Content-Type: application/X-atf-config; version="1"
+
+testvar = "value in conf file"
+EOF
+ atf_check -s eq:0 -o match:'testvar: value in conf file' -e ignore -x \
+ "ATF_CONFDIR=$(pwd)/etc atf-run helper"
+
+ echo "Checking that defining 'testvar' trough -v overrides the" \
+ "configuration file."
+ atf_check -s eq:0 -o match:'testvar: a value' -e ignore -x \
+ "ATF_CONFDIR=$(pwd)/etc atf-run -v testvar='a value' helper"
+}
+
+atf_test_case atffile
+atffile_head()
+{
+ atf_set "descr" "Tests that the variables defined by the Atffile" \
+ "are recognized and that they take the lowest priority"
+}
+atffile_body()
+{
+ create_helper testvar
+
+ echo "Checking that 'testvar' is not defined."
+ atf_check -s eq:1 -o ignore -e ignore -x \
+ "ATF_CONFDIR=$(pwd)/etc atf-run helper"
+
+ echo "Checking that defining 'testvar' trough the Atffile works."
+ echo 'conf: testvar = "a value"' >>Atffile
+ atf_check -s eq:0 -o match:'testvar: a value' -e ignore -x \
+ "ATF_CONFDIR=$(pwd)/etc atf-run helper"
+
+ echo "Checking that defining 'testvar' trough the configuration" \
+ "file overrides the one in the Atffile."
+ mkdir etc
+ cat >etc/common.conf <<EOF
+Content-Type: application/X-atf-config; version="1"
+
+testvar = "value in conf file"
+EOF
+ atf_check -s eq:0 -o match:'testvar: value in conf file' -e ignore -x \
+ "ATF_CONFDIR=$(pwd)/etc atf-run helper"
+ rm -rf etc
+
+ echo "Checking that defining 'testvar' trough -v overrides the" \
+ "one in the Atffile."
+ atf_check -s eq:0 -o match:'testvar: new value' -e ignore -x \
+ "ATF_CONFDIR=$(pwd)/etc atf-run -v testvar='new value' helper"
+}
+
+atf_test_case atffile_recursive
+atffile_recursive_head()
+{
+ atf_set "descr" "Tests that variables defined by an Atffile are not" \
+ "inherited by other Atffiles."
+}
+atffile_recursive_body()
+{
+ create_helper testvar
+
+ mkdir dir
+ mv Atffile helper dir
+
+ echo "Checking that 'testvar' is not inherited."
+ create_atffile dir
+ echo 'conf: testvar = "a value"' >> Atffile
+ atf_check -s eq:1 -o ignore -e ignore -x "ATF_CONFDIR=$(pwd)/etc atf-run"
+
+ echo "Checking that defining 'testvar' in the correct Atffile works."
+ echo 'conf: testvar = "a value"' >>dir/Atffile
+ atf_check -s eq:0 -o match:'testvar: a value' -e ignore -x \
+ "ATF_CONFDIR=$(pwd)/etc atf-run"
+}
+
+atf_test_case fds
+fds_head()
+{
+ atf_set "descr" "Tests that all streams are properly captured"
+}
+fds_body()
+{
+ create_helper fds
+
+ atf_check -s eq:0 \
+ -o match:'^tc-so:msg1 to stdout$' \
+ -o match:'^tc-so:msg2 to stdout$' \
+ -o match:'^tc-se:msg1 to stderr$' \
+ -o match:'^tc-se:msg2 to stderr$' \
+ -e empty atf-run
+}
+
+atf_test_case mux_streams
+mux_streams_head()
+{
+ atf_set "descr" "Tests for a race condition in stream multiplexing"
+}
+mux_streams_body()
+{
+ create_helper mux_streams
+
+ for i in 1 2 3 4 5; do
+ echo "Attempt ${i}"
+ atf_check -s eq:0 -o match:'stdout 9999' -o match:'stderr 9999' atf-run
+ done
+}
+
+atf_test_case expect
+expect_head()
+{
+ atf_set "descr" "Tests the processing of test case results and the" \
+ "expect features"
+}
+expect_body()
+{
+ ln -s "$(atf_get_srcdir)/expect_helpers" .
+ create_atffile expect_helpers
+
+ atf_check -s eq:1 \
+ -o match:'death_and_exit, expected_death' \
+ -o match:'death_and_signal, expected_death' \
+ -o match:'death_but_pass, failed' \
+ -o match:'exit_any_and_exit, expected_exit' \
+ -o match:'exit_but_pass, failed' \
+ -o match:'exit_code_and_exit, expected_exit' \
+ -o match:'fail_and_fail_check, expected_failure' \
+ -o match:'fail_and_fail_requirement, expected_failure' \
+ -o match:'fail_but_pass, failed' \
+ -o match:'pass_and_pass, passed' \
+ -o match:'pass_but_fail_check, failed' \
+ -o match:'pass_but_fail_requirement, failed' \
+ -o match:'signal_any_and_signal, expected_signal' \
+ -o match:'signal_but_pass, failed' \
+ -o match:'signal_no_and_signal, expected_signal' \
+ -o match:'timeout_and_hang, expected_timeout' \
+ -o match:'timeout_but_pass, failed' \
+ -e empty atf-run
+}
+
+atf_test_case missing_results
+missing_results_head()
+{
+ atf_set "descr" "Ensures that atf-run correctly handles test cases that " \
+ "do not create the results file"
+}
+missing_results_body()
+{
+ create_helper_stdin helper 1 <<EOF
+test -f \${resfile} && echo "resfile found"
+exit 0
+EOF
+ chmod +x helper
+
+ create_atffile helper
+
+ re='^tc-end: [0-9][0-9]*\.[0-9]*, tc1,'
+ atf_check -s eq:1 \
+ -o match:"${re} failed,.*failed to create" \
+ -o not-match:'resfile found' \
+ -e empty atf-run
+}
+
+atf_test_case broken_results
+broken_results_head()
+{
+ atf_set "descr" "Ensures that atf-run reports test programs that" \
+ "provide a bogus results output as broken programs"
+}
+broken_results_body()
+{
+ # We produce two errors from the header to ensure that the parse
+ # errors are printed on a single line on the output file. Printing
+ # them on separate lines would be incorrect.
+ create_helper_stdin helper 1 <<EOF
+echo 'line 1' >\${resfile}
+echo 'line 2' >>\${resfile}
+exit 0
+EOF
+ chmod +x helper
+
+ create_atffile helper
+
+ re='^tc-end: [0-9][0-9]*\.[0-9]*, tc1,'
+ atf_check -s eq:1 -o match:"${re} .*line 1.*line 2" -e empty atf-run
+}
+
+atf_test_case broken_tp_list
+broken_tp_list_head()
+{
+ atf_set "descr" "Ensures that atf-run reports test programs that" \
+ "provide a bogus test case list"
+}
+broken_tp_list_body()
+{
+ cat >helper <<EOF
+#! $(atf-config -t atf_shell)
+while [ \${#} -gt 0 ]; do
+ if [ \${1} = -l ]; then
+ echo 'Content-Type: application/X-atf-tp; version="1"'
+ echo
+ echo 'foo: bar'
+ exit 0
+ else
+ shift
+ fi
+done
+exit 0
+EOF
+ chmod +x helper
+
+ create_atffile helper
+
+ re='^tp-end: [0-9][0-9]*\.[0-9]*, helper,'
+ re="${re} Invalid format for test case list:.*First property.*ident"
+ atf_check -s eq:1 -o match:"${re}" -e empty atf-run
+}
+
+atf_test_case zero_tcs
+zero_tcs_head()
+{
+ atf_set "descr" "Ensures that atf-run reports test programs without" \
+ "test cases as errors"
+}
+zero_tcs_body()
+{
+ create_helper_stdin helper 0 <<EOF
+echo 'Content-Type: application/X-atf-tp; version="1"'
+echo
+exit 1
+EOF
+ chmod +x helper
+
+ create_atffile helper
+
+ re='^tp-end: [0-9][0-9]*\.[0-9]*, helper,'
+ atf_check -s eq:1 \
+ -o match:"${re} .*Invalid format for test case list" \
+ -e empty atf-run
+}
+
+atf_test_case exit_codes
+exit_codes_head()
+{
+ atf_set "descr" "Ensures that atf-run reports bogus exit codes for" \
+ "programs correctly"
+}
+exit_codes_body()
+{
+ create_helper_stdin helper 1 <<EOF
+echo "failed: Yes, it failed" >\${resfile}
+exit 0
+EOF
+ chmod +x helper
+
+ create_atffile helper
+
+ re='^tc-end: [0-9][0-9]*\.[0-9]*, tc1,'
+ atf_check -s eq:1 \
+ -o match:"${re} .*exited successfully.*reported failure" \
+ -e empty atf-run
+}
+
+atf_test_case signaled
+signaled_head()
+{
+ atf_set "descr" "Ensures that atf-run reports test program's crashes" \
+ "correctly regardless of their actual results"
+}
+signaled_body()
+{
+ create_helper_stdin helper 2 <<EOF
+echo "passed" >\${resfile}
+case \${testcase} in
+ tc1) ;;
+ tc2) echo "Killing myself!" ; kill -9 \$\$ ;;
+esac
+EOF
+ chmod +x helper
+
+ create_atffile helper
+
+ re='^tc-end: [0-9][0-9]*\.[0-9]*, tc2,'
+ atf_check -s eq:1 -o match:"${re} .*received signal 9" \
+ -e empty atf-run
+}
+
+atf_test_case hooks
+hooks_head()
+{
+ atf_set "descr" "Checks that the default hooks work and that they" \
+ "can be overriden by the user"
+}
+hooks_body()
+{
+ cp $(atf_get_srcdir)/pass_helper helper
+ create_atffile helper
+
+ mkdir atf
+ mkdir .atf
+
+ echo "Checking default hooks"
+ atf_check -s eq:0 -o match:'^info: time.start, ' \
+ -o match:'^info: time.end, ' -e empty -x \
+ "ATF_CONFDIR=$(pwd)/atf atf-run"
+
+ echo "Checking the system-wide info_start hook"
+ cat >atf/atf-run.hooks <<EOF
+info_start_hook()
+{
+ atf_tps_writer_info "test" "sw value"
+}
+EOF
+ atf_check -s eq:0 \
+ -o match:'^info: test, sw value' \
+ -o not-match:'^info: time.start, ' \
+ -o match:'^info: time.end, ' \
+ -e empty -x \
+ "ATF_CONFDIR=$(pwd)/atf atf-run"
+
+ echo "Checking the user-specific info_start hook"
+ cat >.atf/atf-run.hooks <<EOF
+info_start_hook()
+{
+ atf_tps_writer_info "test" "user value"
+}
+EOF
+ atf_check -s eq:0 \
+ -o match:'^info: test, user value' \
+ -o not-match:'^info: time.start, ' \
+ -o match:'^info: time.end, ' \
+ -e empty -x \
+ "ATF_CONFDIR=$(pwd)/atf atf-run"
+
+ rm atf/atf-run.hooks
+ rm .atf/atf-run.hooks
+
+ echo "Checking the system-wide info_end hook"
+ cat >atf/atf-run.hooks <<EOF
+info_end_hook()
+{
+ atf_tps_writer_info "test" "sw value"
+}
+EOF
+ atf_check -s eq:0 \
+ -o match:'^info: time.start, ' \
+ -o not-match:'^info: time.end, ' \
+ -o match:'^info: test, sw value' \
+ -e empty -x \
+ "ATF_CONFDIR=$(pwd)/atf atf-run"
+
+ echo "Checking the user-specific info_end hook"
+ cat >.atf/atf-run.hooks <<EOF
+info_end_hook()
+{
+ atf_tps_writer_info "test" "user value"
+}
+EOF
+ atf_check -s eq:0 \
+ -o match:'^info: time.start, ' \
+ -o not-match:'^info: time.end, ' \
+ -o match:'^info: test, user value' \
+ -e empty -x \
+ "ATF_CONFDIR=$(pwd)/atf atf-run"
+}
+
+atf_test_case isolation_env
+isolation_env_head()
+{
+ atf_set "descr" "Tests that atf-run sets a set of environment variables" \
+ "to known sane values"
+}
+isolation_env_body()
+{
+ undef_vars="LANG LC_ALL LC_COLLATE LC_CTYPE LC_MESSAGES LC_MONETARY \
+ LC_NUMERIC LC_TIME"
+ def_vars="HOME TZ"
+
+ mangleenv="env"
+ for v in ${undef_vars} ${def_vars}; do
+ mangleenv="${mangleenv} ${v}=bogus-value"
+ done
+
+ create_helper env_list
+ create_atffile helper
+
+ # We must ignore stderr in this call (instead of specifying -e empty)
+ # because, when atf-run invokes the shell to run the hooks, we may get
+ # error messages about an invalid locale. This happens, at least, when
+ # the shell is bash 4.x.
+ atf_check -s eq:0 -o save:stdout -e ignore ${mangleenv} atf-run helper
+
+ for v in ${undef_vars}; do
+ atf_check -s eq:1 -o empty -e empty grep "^tc-so:${v}=" stdout
+ done
+
+ for v in ${def_vars}; do
+ atf_check -s eq:0 -o ignore -e empty grep "^tc-so:${v}=" stdout
+ done
+
+ atf_check -s eq:0 -o ignore -e empty grep "^tc-so:TZ=UTC" stdout
+}
+
+atf_test_case isolation_home
+isolation_home_head()
+{
+ atf_set "descr" "Tests that atf-run sets HOME to a sane and valid value"
+}
+isolation_home_body()
+{
+ create_helper env_home
+ create_atffile helper
+ atf_check -s eq:0 -o ignore -e ignore env HOME=foo atf-run helper
+}
+
+atf_test_case isolation_stdin
+isolation_stdin_head()
+{
+ atf_set "descr" "Tests that atf-run nullifies the stdin of test cases"
+}
+isolation_stdin_body()
+{
+ create_helper read_stdin
+ create_atffile helper
+ atf_check -s eq:0 -o ignore -e ignore -x 'echo hello world | atf-run helper'
+}
+
+atf_test_case isolation_umask
+isolation_umask_head()
+{
+ atf_set "descr" "Tests that atf-run sets the umask to a known value"
+}
+isolation_umask_body()
+{
+ create_helper umask
+ create_atffile helper
+
+ atf_check -s eq:0 -o match:'umask: 0022' -e ignore -x \
+ "umask 0000 && atf-run helper"
+}
+
+atf_test_case cleanup_pass
+cleanup_pass_head()
+{
+ atf_set "descr" "Tests that atf-run calls the cleanup routine of the test" \
+ "case when the test case result is passed"
+}
+cleanup_pass_body()
+{
+ create_helper cleanup_states
+ create_atffile helper
+
+ atf_check -s eq:0 -o match:'cleanup_states, passed' -e ignore atf-run \
+ -v state=pass -v statedir=$(pwd) helper
+ test -f to-stay || atf_fail "Test case body did not run correctly"
+ if [ -f to-delete ]; then
+ atf_fail "Test case cleanup did not run correctly"
+ fi
+}
+
+atf_test_case cleanup_fail
+cleanup_fail_head()
+{
+ atf_set "descr" "Tests that atf-run calls the cleanup routine of the test" \
+ "case when the test case result is failed"
+}
+cleanup_fail_body()
+{
+ create_helper cleanup_states
+ create_atffile helper
+
+ atf_check -s eq:1 -o match:'cleanup_states, failed' -e ignore atf-run \
+ -v state=fail -v statedir=$(pwd) helper
+ test -f to-stay || atf_fail "Test case body did not run correctly"
+ if [ -f to-delete ]; then
+ atf_fail "Test case cleanup did not run correctly"
+ fi
+}
+
+atf_test_case cleanup_skip
+cleanup_skip_head()
+{
+ atf_set "descr" "Tests that atf-run calls the cleanup routine of the test" \
+ "case when the test case result is skipped"
+}
+cleanup_skip_body()
+{
+ create_helper cleanup_states
+ create_atffile helper
+
+ atf_check -s eq:0 -o match:'cleanup_states, skipped' -e ignore atf-run \
+ -v state=skip -v statedir=$(pwd) helper
+ test -f to-stay || atf_fail "Test case body did not run correctly"
+ if [ -f to-delete ]; then
+ atf_fail "Test case cleanup did not run correctly"
+ fi
+}
+
+atf_test_case cleanup_curdir
+cleanup_curdir_head()
+{
+ atf_set "descr" "Tests that atf-run calls the cleanup routine in the same" \
+ "work directory as the body so that they can share data"
+}
+cleanup_curdir_body()
+{
+ create_helper cleanup_curdir
+ create_atffile helper
+
+ atf_check -s eq:0 -o match:'cleanup_curdir, passed' \
+ -o match:'Old value: 1234' -e ignore atf-run helper
+}
+
+atf_test_case cleanup_signal
+cleanup_signal_head()
+{
+ atf_set "descr" "Tests that atf-run calls the cleanup routine if it gets" \
+ "a termination signal while running the body"
+}
+cleanup_signal_body()
+{
+ : # TODO: Write this.
+}
+
+atf_test_case cleanup_mount
+cleanup_mount_head()
+{
+ atf_set "descr" "Tests that the removal algorithm does not cross" \
+ "mount points"
+ atf_set "require.user" "root"
+}
+cleanup_mount_body()
+{
+ ROOT="$(pwd)/root"; export ROOT
+
+ create_mount_helper helper <<EOF
+echo \$(pwd) >\${ROOT}
+mkdir foo
+mkdir foo/bar
+mkdir foo/bar/mnt
+do_mount foo/bar/mnt
+mkdir foo/baz
+do_mount foo/baz
+mkdir foo/baz/foo
+mkdir foo/baz/foo/bar
+do_mount foo/baz/foo/bar
+EOF
+ create_atffile helper
+ chmod +x helper
+
+ platform=$(uname)
+ case ${platform} in
+ Linux|FreeBSD|NetBSD|SunOS)
+ ;;
+ *)
+ # XXX Possibly specify in meta-data too.
+ atf_skip "Test unimplemented in this platform (${platform})"
+ ;;
+ esac
+
+ atf_check -s eq:0 -o match:"main, passed" -e ignore atf-run helper
+ mount | grep $(cat root) && atf_fail "Some file systems remain mounted"
+ atf_check -s eq:1 -o empty -e empty test -d $(cat root)/foo
+}
+
+atf_test_case cleanup_symlink
+cleanup_symlink_head()
+{
+ atf_set "descr" "Tests that the removal algorithm does not follow" \
+ "symlinks, which may live in another device and thus" \
+ "be treated as mount points"
+ atf_set "require.user" "root"
+}
+cleanup_symlink_body()
+{
+ ROOT="$(pwd)/root"; export ROOT
+
+ create_mount_helper helper <<EOF
+echo \$(pwd) >\${ROOT}
+atf_check -s eq:0 -o empty -e empty mkdir foo
+atf_check -s eq:0 -o empty -e empty mkdir foo/bar
+do_mount foo/bar
+atf_check -s eq:0 -o empty -e empty touch a
+atf_check -s eq:0 -o empty -e empty ln -s "\$(pwd)/a" foo/bar
+EOF
+ create_atffile helper
+ chmod +x helper
+
+ platform=$(uname)
+ case ${platform} in
+ Linux|FreeBSD|NetBSD|SunOS)
+ ;;
+ *)
+ # XXX Possibly specify in meta-data too.
+ atf_skip "Test unimplemented in this platform (${platform})"
+ ;;
+ esac
+
+ atf_check -s eq:0 -o match:"main, passed" -e ignore atf-run helper
+ mount | grep $(cat root) && atf_fail "Some file systems remain mounted"
+ atf_check -s eq:1 -o empty -e empty test -d $(cat root)/foo
+}
+
+atf_test_case require_arch
+require_arch_head()
+{
+ atf_set "descr" "Tests that atf-run validates the require.arch property"
+}
+require_arch_body()
+{
+ create_helper require_arch
+ create_atffile helper
+
+ echo "Checking for the real architecture"
+ arch=$(atf-config -t atf_arch)
+ atf_check -s eq:0 -o match:"${TESTCASE}, passed" -e ignore atf-run \
+ -v arch="${arch}" helper
+ atf_check -s eq:0 -o match:"${TESTCASE}, passed" -e ignore atf-run \
+ -v arch="foo ${arch}" helper
+ atf_check -s eq:0 -o match:"${TESTCASE}, passed" -e ignore atf-run \
+ -v arch="${arch} foo" helper
+
+ echo "Checking for a fictitious architecture"
+ arch=fictitious
+ export ATF_ARCH=fictitious
+ atf_check -s eq:0 -o match:"${TESTCASE}, passed" -e ignore atf-run \
+ -v arch="${arch}" helper
+ atf_check -s eq:0 -o match:"${TESTCASE}, passed" -e ignore atf-run \
+ -v arch="foo ${arch}" helper
+ atf_check -s eq:0 -o match:"${TESTCASE}, passed" -e ignore atf-run \
+ -v arch="${arch} foo" helper
+
+ echo "Triggering some failures"
+ atf_check -s eq:0 -o match:"${TESTCASE}, skipped, .*foo.*architecture" \
+ -e ignore atf-run -v arch="foo" helper
+ atf_check -s eq:0 \
+ -o match:"${TESTCASE}, skipped, .*foo bar.*architectures" -e ignore \
+ atf-run -v arch="foo bar" helper
+ atf_check -s eq:0 \
+ -o match:"${TESTCASE}, skipped, .*fictitiousxxx.*architecture" \
+ -e ignore atf-run -v arch="${arch}xxx" helper
+}
+
+atf_test_case require_config
+require_config_head()
+{
+ atf_set "descr" "Tests that atf-run validates the require.config property"
+}
+require_config_body()
+{
+ create_helper require_config
+ create_atffile helper
+
+ atf_check -s eq:0 -o match:"${TESTCASE}, skipped, .*var1.*not defined" \
+ -e ignore atf-run helper
+ atf_check -s eq:0 -o match:"${TESTCASE}, skipped, .*var2.*not defined" \
+ -e ignore atf-run -v var1=foo helper
+ atf_check -s eq:0 -o match:"${TESTCASE}, passed" -e ignore atf-run \
+ -v var1=a -v var2=' ' helper
+}
+
+atf_test_case require_files
+require_files_head()
+{
+ atf_set "descr" "Tests that atf-run validates the require.files property"
+}
+require_files_body()
+{
+ create_helper require_files
+ create_atffile helper
+
+ touch i-exist
+
+ echo "Checking absolute paths"
+ atf_check -s eq:0 -o match:"${TESTCASE}, passed" -e ignore atf-run \
+ -v files='/bin/cp' helper
+ atf_check -s eq:0 -o match:"${TESTCASE}, passed" -e ignore atf-run \
+ -v files="$(pwd)/i-exist" helper
+ atf_check -s eq:0 \
+ -o match:"${TESTCASE}, skipped, .*/dont-exist" \
+ -e ignore atf-run -v files="$(pwd)/i-exist $(pwd)/dont-exist" helper
+
+ echo "Checking that relative paths are not allowed"
+ atf_check -s eq:1 \
+ -o match:"${TESTCASE}, failed, Relative paths.*not allowed.*hello" \
+ -e ignore atf-run -v files='hello' helper
+ atf_check -s eq:1 \
+ -o match:"${TESTCASE}, failed, Relative paths.*not allowed.*a/b" \
+ -e ignore atf-run -v files='a/b' helper
+}
+
+atf_test_case require_machine
+require_machine_head()
+{
+ atf_set "descr" "Tests that atf-run validates the require.machine property"
+}
+require_machine_body()
+{
+ create_helper require_machine
+ create_atffile helper
+
+ echo "Checking for the real machine type"
+ machine=$(atf-config -t atf_machine)
+ atf_check -s eq:0 -o match:"${TESTCASE}, passed" -e ignore atf-run \
+ -v machine="${machine}" helper
+ atf_check -s eq:0 -o match:"${TESTCASE}, passed" -e ignore atf-run \
+ -v machine="foo ${machine}" helper
+ atf_check -s eq:0 -o match:"${TESTCASE}, passed" -e ignore atf-run \
+ -v machine="${machine} foo" helper
+
+ echo "Checking for a fictitious machine type"
+ machine=fictitious
+ export ATF_MACHINE=fictitious
+ atf_check -s eq:0 -o match:"${TESTCASE}, passed" -e ignore atf-run \
+ -v machine="${machine}" helper
+ atf_check -s eq:0 -o match:"${TESTCASE}, passed" -e ignore atf-run \
+ -v machine="foo ${machine}" helper
+ atf_check -s eq:0 -o match:"${TESTCASE}, passed" -e ignore atf-run \
+ -v machine="${machine} foo" helper
+
+ echo "Triggering some failures"
+ atf_check -s eq:0 -o match:"${TESTCASE}, skipped, .*foo.*machine type" \
+ -e ignore atf-run -v machine="foo" helper
+ atf_check -s eq:0 \
+ -o match:"${TESTCASE}, skipped, .*foo bar.*machine types" -e ignore \
+ atf-run -v machine="foo bar" helper
+ atf_check -s eq:0 \
+ -o match:"${TESTCASE}, skipped, .*fictitiousxxx.*machine type" \
+ -e ignore atf-run -v machine="${machine}xxx" helper
+}
+
+atf_test_case require_progs
+require_progs_head()
+{
+ atf_set "descr" "Tests that atf-run validates the require.progs property"
+}
+require_progs_body()
+{
+ create_helper require_progs
+ create_atffile helper
+
+ echo "Checking absolute paths"
+ atf_check -s eq:0 -o match:"${TESTCASE}, passed" -e ignore atf-run \
+ -v progs='/bin/cp' helper
+ atf_check -s eq:0 \
+ -o match:"${TESTCASE}, skipped, .*/bin/__non-existent__.*PATH" \
+ -e ignore atf-run -v progs='/bin/__non-existent__' helper
+
+ echo "Checking that relative paths are not allowed"
+ atf_check -s eq:1 \
+ -o match:"${TESTCASE}, failed, Relative paths.*not allowed.*bin/cp" \
+ -e ignore atf-run -v progs='bin/cp' helper
+
+ echo "Check plain file names, searching them in the PATH."
+ atf_check -s eq:0 -o match:"${TESTCASE}, passed" -e ignore atf-run \
+ -v progs='cp' helper
+ atf_check -s eq:0 \
+ -o match:"${TESTCASE}, skipped, .*__non-existent__.*PATH" -e ignore \
+ atf-run -v progs='__non-existent__' helper
+}
+
+atf_test_case require_user_root
+require_user_root_head()
+{
+ atf_set "descr" "Tests that atf-run validates the require.user property" \
+ "when it is set to 'root'"
+}
+require_user_root_body()
+{
+ create_helper require_user
+ create_atffile helper
+
+ if [ $(id -u) -eq 0 ]; then
+ exp=passed
+ else
+ exp=skipped
+ fi
+ atf_check -s eq:0 -o match:"${TESTCASE}, ${exp}" -e ignore atf-run \
+ -v user=root helper
+}
+
+atf_test_case require_user_unprivileged
+require_user_unprivileged_head()
+{
+ atf_set "descr" "Tests that atf-run validates the require.user property" \
+ "when it is set to 'root'"
+}
+require_user_unprivileged_body()
+{
+ create_helper require_user
+ create_atffile helper
+
+ if [ $(id -u) -eq 0 ]; then
+ exp=skipped
+ else
+ exp=passed
+ fi
+ atf_check -s eq:0 -o match:"${TESTCASE}, ${exp}" -e ignore atf-run \
+ -v user=unprivileged helper
+}
+
+atf_test_case require_user_bad
+require_user_bad_head()
+{
+ atf_set "descr" "Tests that atf-run validates the require.user property" \
+ "when it is set to 'root'"
+}
+require_user_bad_body()
+{
+ create_helper require_user
+ create_atffile helper
+
+ atf_check -s eq:1 -o match:"${TESTCASE}, failed, Invalid value.*foobar" \
+ -e ignore atf-run -v user=foobar helper
+}
+
+atf_test_case timeout
+timeout_head()
+{
+ atf_set "descr" "Tests that atf-run kills a test case that times out"
+}
+timeout_body()
+{
+ create_helper timeout
+ create_atffile helper
+
+ atf_check -s eq:1 \
+ -o match:"${TESTCASE}, failed, .*timed out after 1 second" -e ignore \
+ atf-run -v statedir=$(pwd) helper
+ if [ -f finished ]; then
+ atf_fail "Test case was not killed after time out"
+ fi
+}
+
+atf_test_case timeout_forkexit
+timeout_forkexit_head()
+{
+ atf_set "descr" "Tests that atf-run deals gracefully with a test program" \
+ "that forks, exits, but the child process hangs"
+}
+timeout_forkexit_body()
+{
+ create_helper timeout_forkexit
+ create_atffile helper
+
+ atf_check -s eq:0 -o match:"${TESTCASE}, passed" -e ignore atf-run \
+ -v statedir=$(pwd) helper
+ test -f parent-finished || atf_fail "Parent did not exit as expected"
+ test -f child-finished && atf_fail "Subprocess exited but it should have" \
+ "been forcibly terminated" || true
+}
+
+atf_test_case ignore_deprecated_use_fs
+ignore_deprecated_use_fs_head()
+{
+ atf_set "descr" "Tests that atf-run ignores the deprecated use.fs property"
+}
+ignore_deprecated_use_fs_body()
+{
+ create_helper use_fs
+ create_atffile helper
+
+ atf_check -s eq:0 -o ignore -e ignore atf-run helper
+}
+
+atf_init_test_cases()
+{
+ atf_add_test_case no_warnings
+ atf_add_test_case config
+ atf_add_test_case vflag
+ atf_add_test_case atffile
+ atf_add_test_case atffile_recursive
+ atf_add_test_case expect
+ atf_add_test_case fds
+ atf_add_test_case mux_streams
+ atf_add_test_case missing_results
+ atf_add_test_case broken_results
+ atf_add_test_case broken_tp_list
+ atf_add_test_case zero_tcs
+ atf_add_test_case exit_codes
+ atf_add_test_case signaled
+ atf_add_test_case hooks
+ atf_add_test_case isolation_env
+ atf_add_test_case isolation_home
+ atf_add_test_case isolation_stdin
+ atf_add_test_case isolation_umask
+ atf_add_test_case cleanup_pass
+ atf_add_test_case cleanup_fail
+ atf_add_test_case cleanup_skip
+ atf_add_test_case cleanup_curdir
+ atf_add_test_case cleanup_signal
+ atf_add_test_case cleanup_mount
+ atf_add_test_case cleanup_symlink
+ atf_add_test_case require_arch
+ atf_add_test_case require_config
+ atf_add_test_case require_files
+ atf_add_test_case require_machine
+ atf_add_test_case require_progs
+ atf_add_test_case require_user_root
+ atf_add_test_case require_user_unprivileged
+ atf_add_test_case require_user_bad
+ atf_add_test_case timeout
+ atf_add_test_case timeout_forkexit
+ atf_add_test_case ignore_deprecated_use_fs
+}
+
+# vim: syntax=sh:expandtab:shiftwidth=4:softtabstop=4
diff --git a/unit/atf-src/tools/atf-version.1 b/unit/atf-src/tools/atf-version.1
new file mode 100644
index 0000000..136f13c
--- /dev/null
+++ b/unit/atf-src/tools/atf-version.1
@@ -0,0 +1,56 @@
+.\"
+.\" Automated Testing Framework (atf)
+.\"
+.\" Copyright (c) 2007 The NetBSD Foundation, Inc.
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
+.\" CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+.\" INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+.\" MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+.\" IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
+.\" DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+.\" GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+.\" INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+.\" IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+.\" OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+.\" IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+.\"
+.Dd September 16, 2007
+.Dt ATF-VERSION 1
+.Os
+.Sh NAME
+.Nm atf-version
+.Nd shows information about the installed ATF version
+.Sh SYNOPSIS
+.Nm
+.Nm
+.Fl h
+.Sh DESCRIPTION
+.Nm
+is a utility that prints information about the version of ATF currently
+installed in the system.
+.Pp
+In the first synopsis form,
+.Nm
+shows the release version of the ATF package installed in the system (the
+installation that corresponds to the instance of
+.Nm
+being executed) and also shows information related to the repository
+revision used to build that package.
+.Pp
+In the second synopsis form,
+.Nm
+will print information about all supported options and their purpose.
+.Sh SEE ALSO
+.Xr atf 7
diff --git a/unit/atf-src/tools/atf-version.cpp b/unit/atf-src/tools/atf-version.cpp
new file mode 100644
index 0000000..b93f33c
--- /dev/null
+++ b/unit/atf-src/tools/atf-version.cpp
@@ -0,0 +1,91 @@
+//
+// Automated Testing Framework (atf)
+//
+// Copyright (c) 2007 The NetBSD Foundation, Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
+// CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+// IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
+// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+// IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+
+#if defined(HAVE_CONFIG_H)
+#include "config.h"
+#endif
+
+#include <cstdlib>
+#include <iostream>
+
+#include "application.hpp"
+#include "revision.h"
+#include "ui.hpp"
+
+class atf_version : public tools::application::app {
+ static const char* m_description;
+
+public:
+ atf_version(void);
+
+ int main(void);
+};
+
+const char* atf_version::m_description =
+ "atf-version is a tool that shows information about the currently "
+ "installed version of ATF.";
+
+atf_version::atf_version(void) :
+ app(m_description, "atf-version(1)", "atf(7)")
+{
+}
+
+int
+atf_version::main(void)
+{
+ using tools::ui::format_text;
+ using tools::ui::format_text_with_tag;
+
+ std::cout << PACKAGE_STRING " (" PACKAGE_TARNAME "-" PACKAGE_VERSION
+ ")\n" PACKAGE_COPYRIGHT "\n\n";
+
+#if defined(PACKAGE_REVISION_TYPE_DIST)
+ std::cout << format_text("Built from a distribution file; no revision "
+ "information available.") << "\n";
+#elif defined(PACKAGE_REVISION_TYPE_GIT)
+ std::cout << format_text_with_tag(PACKAGE_REVISION_BRANCH, "Branch: ",
+ false) << "\n";
+ std::cout << format_text_with_tag(PACKAGE_REVISION_BASE
+# if PACKAGE_REVISION_MODIFIED
+ " (locally modified)"
+# endif
+ " " PACKAGE_REVISION_DATE,
+ "Base revision: ", false) << "\n";
+#else
+# error "Unknown PACKAGE_REVISION_TYPE value"
+#endif
+
+ return EXIT_SUCCESS;
+}
+
+int
+main(int argc, char* const* argv)
+{
+ return atf_version().run(argc, argv);
+}
diff --git a/unit/atf-src/tools/atf.7.in b/unit/atf-src/tools/atf.7.in
new file mode 100644
index 0000000..b11054c
--- /dev/null
+++ b/unit/atf-src/tools/atf.7.in
@@ -0,0 +1,192 @@
+.\"
+.\" Automated Testing Framework (atf)
+.\"
+.\" Copyright (c) 2007 The NetBSD Foundation, Inc.
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
+.\" CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+.\" INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+.\" MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+.\" IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
+.\" DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+.\" GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+.\" INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+.\" IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+.\" OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+.\" IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+.\"
+.Dd August 28, 2010
+.Dt ATF 7
+.Os
+.Sh NAME
+.Nm ATF
+.Nd introduction to the Automated Testing Framework
+.Sh DESCRIPTION
+.Em IMPORTANT: If you are here because you want to know how to run the tests in
+.Pa __TESTSDIR__ ,
+.Em you most likely want to read the
+.Xr tests 7
+.Em manual page instead.
+.Pp
+The Automated Testing Framework
+.Pf ( Nm )
+is a collection of libraries and utilities designed to ease unattended
+application testing in the hands of developers and end users of a specific
+piece of software.
+.Pp
+As regards developers,
+.Nm
+provides the necessary means to easily create
+test suites composed of multiple test programs, which in turn are a
+collection of test cases.
+It also attempts to simplify the debugging of problems when these test
+cases detect an error by providing as much information as possible
+about the failure.
+.Pp
+As regards users, it simplifies the process of running the test suites and,
+in special, encourages end users to run them often: they do not need to
+have source trees around nor any other development tools installed to be
+able to certify that a given piece of software works on their machine as
+advertised.
+.Pp
+If your operating systems distributes
+.Nm ,
+it is possible that it provides an introductory
+.Xr tests 7
+manual page.
+You are encouraged to read it now.
+.Ss License
+.Nm
+is distributed under the terms of the TNF License, a 2-clause BSD license.
+For more details please see:
+.Bd -literal -offset indent
+.Pa __DOCDIR__/COPYING
+.Ed
+.Ss Components
+.Nm
+is a highly modular piece of software.
+It provides a couple of libraries to ease the implementation of test
+programs: one for the C and C++ languages and another one for shell
+scripts.
+It also includes multiple small utilities that follow the principle of
+doing a single thing but doing it right.
+This section outlines which these components are.
+.Pp
+Public utilities:
+.Bl -tag -width atfXtestXprogramXXXXX
+.It Xr atf-check 1
+Executes a command and checks that its exit code, its standard output
+and its standard error output match pre-specified expected values.
+.It Xr atf-config 1
+Queries static configuration information.
+.It Xr atf-report 1
+Converts the output of
+.Nm atf-run
+to user-friendly and/or machine-parseable reports.
+.It Xr atf-run 1
+Automates the execution of a series of test programs and collects their
+results in a unified report.
+.It Xr atf-sh 1
+Shell interpreter for shell-based test programs.
+.El
+.Pp
+Programming interfaces:
+.Bl -tag -width atfXtestXprogramXXXXX
+.It Xr atf-c-api 3
+C programming interface for test programs.
+.It Xr atf-c++-api 3
+C++ programming interface for test programs.
+.It Xr atf-sh-api 3
+POSIX shell programming interface for test programs.
+.El
+.Pp
+Other:
+.Bl -tag -width atfXtestXprogramXXXXX
+.It Xr atf-formats 5
+Description of the machine-parseable data formats used by the tools.
+.It Xr atf-test-case 4
+Generic description of test cases, independent of the language they are
+implemented in.
+.It Xr atf-test-program 1
+Common interface provided by the test programs written using the
+.Nm
+libraries.
+.El
+.Ss Recommended reading order
+For end users wishing to run tests:
+.Bl -enum -compact
+.It
+.Xr tests 7
+(only if provided by your operating system).
+.It
+.Xr atf-test-program 1
+.It
+.Xr atf-run 1
+.It
+.Xr atf-report 1
+.It
+.Xr atf-config 1
+.El
+.Pp
+For developers wanting to write their own tests:
+.Bl -enum -compact
+.It
+Everything recommended to users.
+.It
+.Xr atf-test-case 4
+.It
+.Xr atf-c-api 3
+.It
+.Xr atf-c++-api 3
+.It
+.Xr atf-sh-api 3
+.It
+.Xr atf-sh 1
+.It
+.Xr atf-check 1
+.El
+.Pp
+For those interested in
+.Nm
+internals:
+.Bl -enum -compact
+.It
+Everything recommended to users.
+.It
+Everything recommended to developers.
+.It
+.Xr atf-formats 5
+.El
+.Sh SEE ALSO
+.Xr tests 7
+.Sh HISTORY
+.Nm
+started as a Google Summer of Code 2007 project mentored by The NetBSD
+Foundation.
+Its original goal was to provide a testing framework for The NetBSD
+Operating System, but it grew as an independent project because the
+framework itself did not need to be tied to a specific operating system.
+.Pp
+For more details on this subject, please see:
+.Bd -literal -offset indent
+.Pa __DOCDIR__/NEWS
+.Pa __DOCDIR__/ROADMAP
+.Ed
+.Sh AUTHORS
+For more details on the people that made
+.Nm
+possible, please see:
+.Bd -literal -offset indent
+.Pa __DOCDIR__/AUTHORS
+.Ed
diff --git a/unit/atf-src/tools/atffile.cpp b/unit/atf-src/tools/atffile.cpp
new file mode 100644
index 0000000..b04c59e
--- /dev/null
+++ b/unit/atf-src/tools/atffile.cpp
@@ -0,0 +1,348 @@
+//
+// Automated Testing Framework (atf)
+//
+// Copyright (c) 2007 The NetBSD Foundation, Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
+// CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+// IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
+// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+// IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+
+#include <cassert>
+#include <cstdlib>
+#include <fstream>
+
+#include "atffile.hpp"
+#include "defs.hpp"
+#include "exceptions.hpp"
+#include "expand.hpp"
+#include "parser.hpp"
+
+namespace impl = tools;
+namespace detail = tools::detail;
+
+namespace {
+
+typedef std::map< std::string, std::string > vars_map;
+
+} // anonymous namespace
+
+// ------------------------------------------------------------------------
+// The "atf_atffile" auxiliary parser.
+// ------------------------------------------------------------------------
+
+namespace atf_atffile {
+
+static const tools::parser::token_type eof_type = 0;
+static const tools::parser::token_type nl_type = 1;
+static const tools::parser::token_type text_type = 2;
+static const tools::parser::token_type colon_type = 3;
+static const tools::parser::token_type conf_type = 4;
+static const tools::parser::token_type dblquote_type = 5;
+static const tools::parser::token_type equal_type = 6;
+static const tools::parser::token_type hash_type = 7;
+static const tools::parser::token_type prop_type = 8;
+static const tools::parser::token_type tp_type = 9;
+static const tools::parser::token_type tp_glob_type = 10;
+
+class tokenizer : public tools::parser::tokenizer< std::istream > {
+public:
+ tokenizer(std::istream& is, size_t curline) :
+ tools::parser::tokenizer< std::istream >
+ (is, true, eof_type, nl_type, text_type, curline)
+ {
+ add_delim(':', colon_type);
+ add_delim('=', equal_type);
+ add_delim('#', hash_type);
+ add_quote('"', dblquote_type);
+ add_keyword("conf", conf_type);
+ add_keyword("prop", prop_type);
+ add_keyword("tp", tp_type);
+ add_keyword("tp-glob", tp_glob_type);
+ }
+};
+
+} // namespace atf_atffile
+
+// ------------------------------------------------------------------------
+// The "atf_atffile_reader" class.
+// ------------------------------------------------------------------------
+
+detail::atf_atffile_reader::atf_atffile_reader(std::istream& is) :
+ m_is(is)
+{
+}
+
+detail::atf_atffile_reader::~atf_atffile_reader(void)
+{
+}
+
+void
+detail::atf_atffile_reader::got_conf(
+ const std::string& name ATF_DEFS_ATTRIBUTE_UNUSED,
+ const std::string& val ATF_DEFS_ATTRIBUTE_UNUSED)
+{
+}
+
+void
+detail::atf_atffile_reader::got_prop(
+ const std::string& name ATF_DEFS_ATTRIBUTE_UNUSED,
+ const std::string& val ATF_DEFS_ATTRIBUTE_UNUSED)
+{
+}
+
+void
+detail::atf_atffile_reader::got_tp(
+ const std::string& name ATF_DEFS_ATTRIBUTE_UNUSED,
+ bool isglob ATF_DEFS_ATTRIBUTE_UNUSED)
+{
+}
+
+void
+detail::atf_atffile_reader::got_eof(void)
+{
+}
+
+void
+detail::atf_atffile_reader::read(void)
+{
+ using tools::parser::parse_error;
+ using namespace atf_atffile;
+
+ std::pair< size_t, tools::parser::headers_map > hml =
+ tools::parser::read_headers(m_is, 1);
+ tools::parser::validate_content_type(hml.second,
+ "application/X-atf-atffile", 1);
+
+ tokenizer tkz(m_is, hml.first);
+ tools::parser::parser< tokenizer > p(tkz);
+
+ for (;;) {
+ try {
+ tools::parser::token t =
+ p.expect(conf_type, hash_type, prop_type, tp_type,
+ tp_glob_type, nl_type, eof_type,
+ "conf, #, prop, tp, tp-glob, a new line or eof");
+ if (t.type() == eof_type)
+ break;
+
+ if (t.type() == conf_type) {
+ t = p.expect(colon_type, "`:'");
+
+ t = p.expect(text_type, "variable name");
+ std::string var = t.text();
+
+ t = p.expect(equal_type, "equal sign");
+
+ t = p.expect(text_type, "word or quoted string");
+ ATF_PARSER_CALLBACK(p, got_conf(var, t.text()));
+ } else if (t.type() == hash_type) {
+ (void)p.rest_of_line();
+ } else if (t.type() == prop_type) {
+ t = p.expect(colon_type, "`:'");
+
+ t = p.expect(text_type, "property name");
+ std::string name = t.text();
+
+ t = p.expect(equal_type, "equale sign");
+
+ t = p.expect(text_type, "word or quoted string");
+ ATF_PARSER_CALLBACK(p, got_prop(name, t.text()));
+ } else if (t.type() == tp_type) {
+ t = p.expect(colon_type, "`:'");
+
+ t = p.expect(text_type, "word or quoted string");
+ ATF_PARSER_CALLBACK(p, got_tp(t.text(), false));
+ } else if (t.type() == tp_glob_type) {
+ t = p.expect(colon_type, "`:'");
+
+ t = p.expect(text_type, "word or quoted string");
+ ATF_PARSER_CALLBACK(p, got_tp(t.text(), true));
+ } else if (t.type() == nl_type) {
+ continue;
+ } else
+ std::abort();
+
+ t = p.expect(nl_type, hash_type, eof_type,
+ "new line or comment");
+ if (t.type() == hash_type) {
+ (void)p.rest_of_line();
+ t = p.next();
+ } else if (t.type() == eof_type)
+ break;
+ } catch (const parse_error& pe) {
+ p.add_error(pe);
+ p.reset(nl_type);
+ }
+ }
+
+ ATF_PARSER_CALLBACK(p, got_eof());
+}
+
+// ------------------------------------------------------------------------
+// The "reader" helper class.
+// ------------------------------------------------------------------------
+
+class reader : public detail::atf_atffile_reader {
+ const tools::fs::directory& m_dir;
+ vars_map m_conf, m_props;
+ std::vector< std::string > m_tps;
+
+ void
+ got_tp(const std::string& name, bool isglob)
+ {
+ if (isglob) {
+ std::vector< std::string > ms =
+ tools::expand::expand_glob(name, m_dir.names());
+ // Cannot use m_tps.insert(iterator, begin, end) here because it
+ // does not work under Solaris.
+ for (std::vector< std::string >::const_iterator iter = ms.begin();
+ iter != ms.end(); iter++)
+ m_tps.push_back(*iter);
+ } else {
+ if (m_dir.find(name) == m_dir.end())
+ throw tools::not_found_error< tools::fs::path >
+ ("Cannot locate the " + name + " file",
+ tools::fs::path(name));
+ m_tps.push_back(name);
+ }
+ }
+
+ void
+ got_prop(const std::string& name, const std::string& val)
+ {
+ m_props[name] = val;
+ }
+
+ void
+ got_conf(const std::string& var, const std::string& val)
+ {
+ m_conf[var] = val;
+ }
+
+public:
+ reader(std::istream& is, const tools::fs::directory& dir) :
+ detail::atf_atffile_reader(is),
+ m_dir(dir)
+ {
+ }
+
+ const vars_map&
+ conf(void)
+ const
+ {
+ return m_conf;
+ }
+
+ const vars_map&
+ props(void)
+ const
+ {
+ return m_props;
+ }
+
+ const std::vector< std::string >&
+ tps(void)
+ const
+ {
+ return m_tps;
+ }
+};
+
+// ------------------------------------------------------------------------
+// The "atffile" class.
+// ------------------------------------------------------------------------
+
+impl::atffile::atffile(const vars_map& config_vars,
+ const std::vector< std::string >& test_program_names,
+ const vars_map& properties) :
+ m_conf(config_vars),
+ m_tps(test_program_names),
+ m_props(properties)
+{
+ assert(properties.find("test-suite") != properties.end());
+}
+
+const std::vector< std::string >&
+impl::atffile::tps(void)
+ const
+{
+ return m_tps;
+}
+
+const vars_map&
+impl::atffile::conf(void)
+ const
+{
+ return m_conf;
+}
+
+const vars_map&
+impl::atffile::props(void)
+ const
+{
+ return m_props;
+}
+
+// ------------------------------------------------------------------------
+// Free functions.
+// ------------------------------------------------------------------------
+
+// XXX Glob expansion and file existance checks certainly do not belong in
+// a *parser*. This needs to be taken out...
+impl::atffile
+impl::read_atffile(const tools::fs::path& filename)
+{
+ // Scan the directory where the atffile lives in to gather a list of
+ // all possible test programs in it.
+ tools::fs::directory dir(filename.branch_path());
+ dir.erase(filename.leaf_name());
+ tools::fs::directory::iterator iter = dir.begin();
+ while (iter != dir.end()) {
+ const std::string& name = (*iter).first;
+ const tools::fs::file_info& fi = (*iter).second;
+
+ // Discard hidden files and non-executable ones so that they are
+ // not candidates for glob matching.
+ if (name[0] == '.' || (!fi.is_owner_executable() &&
+ !fi.is_group_executable()))
+ dir.erase(iter++);
+ else
+ iter++;
+ }
+
+ // Parse the atffile.
+ std::ifstream is(filename.c_str());
+ if (!is)
+ throw tools::not_found_error< tools::fs::path >
+ ("Cannot open Atffile", filename);
+ reader r(is, dir);
+ r.read();
+ is.close();
+
+ // Sanity checks.
+ if (r.props().find("test-suite") == r.props().end())
+ throw tools::not_found_error< std::string >
+ ("Undefined property `test-suite'", "test-suite");
+
+ return atffile(r.conf(), r.tps(), r.props());
+}
diff --git a/unit/atf-src/tools/atffile.hpp b/unit/atf-src/tools/atffile.hpp
new file mode 100644
index 0000000..f326b24
--- /dev/null
+++ b/unit/atf-src/tools/atffile.hpp
@@ -0,0 +1,92 @@
+//
+// Automated Testing Framework (atf)
+//
+// Copyright (c) 2007 The NetBSD Foundation, Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
+// CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+// IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
+// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+// IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+
+#if !defined(TOOLS_ATFFILE_HPP)
+#define TOOLS_ATFFILE_HPP
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "fs.hpp"
+
+namespace tools {
+
+// ------------------------------------------------------------------------
+// The "atf_atffile_reader" class.
+// ------------------------------------------------------------------------
+
+namespace detail {
+
+class atf_atffile_reader {
+ std::istream& m_is;
+
+protected:
+ virtual void got_conf(const std::string&, const std::string &);
+ virtual void got_prop(const std::string&, const std::string &);
+ virtual void got_tp(const std::string&, bool);
+ virtual void got_eof(void);
+
+public:
+ atf_atffile_reader(std::istream&);
+ virtual ~atf_atffile_reader(void);
+
+ void read(void);
+};
+
+} // namespace detail
+
+// ------------------------------------------------------------------------
+// The "atffile" class.
+// ------------------------------------------------------------------------
+
+class atffile {
+ std::map< std::string, std::string > m_conf;
+ std::vector< std::string > m_tps;
+ std::map< std::string, std::string > m_props;
+
+public:
+ atffile(const std::map< std::string, std::string >&,
+ const std::vector< std::string >&,
+ const std::map< std::string, std::string >&);
+
+ const std::map< std::string, std::string >& conf(void) const;
+ const std::vector< std::string >& tps(void) const;
+ const std::map< std::string, std::string >& props(void) const;
+};
+
+// ------------------------------------------------------------------------
+// Free functions.
+// ------------------------------------------------------------------------
+
+atffile read_atffile(const tools::fs::path&);
+
+} // namespace tools
+
+#endif // !defined(TOOLS_ATFFILE_HPP)
diff --git a/unit/atf-src/tools/atffile_test.cpp b/unit/atf-src/tools/atffile_test.cpp
new file mode 100644
index 0000000..cb358df
--- /dev/null
+++ b/unit/atf-src/tools/atffile_test.cpp
@@ -0,0 +1,636 @@
+//
+// Automated Testing Framework (atf)
+//
+// Copyright (c) 2009 The NetBSD Foundation, Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
+// CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+// IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
+// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+// IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+
+extern "C" {
+#include <sys/types.h>
+#include <sys/stat.h>
+}
+
+#include <algorithm>
+#include <fstream>
+#include <memory>
+
+#include <atf-c++.hpp>
+
+#include "atffile.hpp"
+#include "exceptions.hpp"
+#include "test_helpers.hpp"
+
+namespace detail = tools::detail;
+
+// ------------------------------------------------------------------------
+// Auxiliary functions.
+// ------------------------------------------------------------------------
+
+namespace {
+
+typedef std::map< std::string, std::string > vars_map;
+
+static
+std::auto_ptr< std::ofstream >
+new_atffile(void)
+{
+ std::auto_ptr< std::ofstream > os(new std::ofstream("Atffile"));
+ ATF_REQUIRE(*os);
+
+ (*os) << "Content-Type: application/X-atf-atffile; version=\"1\"\n\n";
+ return os;
+}
+
+static
+void
+touch_exec(const char* name)
+{
+ std::ofstream os(name);
+ ATF_REQUIRE(os);
+ os.close();
+ ATF_REQUIRE(::chmod(name, S_IRWXU) != -1);
+}
+
+static inline
+bool
+is_in(const std::string& value, const std::vector< std::string >& v)
+{
+ return std::find(v.begin(), v.end(), value) != v.end();
+}
+
+} // anonymous namespace
+
+// ------------------------------------------------------------------------
+// Tests cases for the "atffile" parser.
+// ------------------------------------------------------------------------
+
+class atffile_reader : protected detail::atf_atffile_reader {
+ void
+ got_conf(const std::string& name, const std::string& val)
+ {
+ m_calls.push_back("got_conf(" + name + ", " + val + ")");
+ }
+
+ void
+ got_prop(const std::string& name, const std::string& val)
+ {
+ m_calls.push_back("got_prop(" + name + ", " + val + ")");
+ }
+
+ void
+ got_tp(const std::string& name, bool isglob)
+ {
+ m_calls.push_back("got_tp(" + name + ", " + (isglob ? "true" : "false")
+ + ")");
+ }
+
+ void
+ got_eof(void)
+ {
+ m_calls.push_back("got_eof()");
+ }
+
+public:
+ atffile_reader(std::istream& is) :
+ detail::atf_atffile_reader(is)
+ {
+ }
+
+ void
+ read(void)
+ {
+ atf_atffile_reader::read();
+ }
+
+ std::vector< std::string > m_calls;
+};
+
+ATF_TEST_CASE_WITHOUT_HEAD(atffile_1);
+ATF_TEST_CASE_BODY(atffile_1)
+{
+ const char* input =
+ "Content-Type: application/X-atf-atffile; version=\"1\"\n"
+ "\n"
+ ;
+
+ const char* exp_calls[] = {
+ "got_eof()",
+ NULL
+ };
+
+ const char* exp_errors[] = {
+ NULL
+ };
+
+ do_parser_test< atffile_reader >(input, exp_calls, exp_errors);
+}
+
+ATF_TEST_CASE_WITHOUT_HEAD(atffile_2);
+ATF_TEST_CASE_BODY(atffile_2)
+{
+ const char* input =
+ "Content-Type: application/X-atf-atffile; version=\"1\"\n"
+ "\n"
+ "# This is a comment on a line of its own.\n"
+ "# And this is another one.\n"
+ "\n"
+ " # Another after some whitespace.\n"
+ "\n"
+ "# The last one after an empty line.\n"
+ ;
+
+ const char* exp_calls[] = {
+ "got_eof()",
+ NULL
+ };
+
+ const char* exp_errors[] = {
+ NULL
+ };
+
+ do_parser_test< atffile_reader >(input, exp_calls, exp_errors);
+}
+
+ATF_TEST_CASE_WITHOUT_HEAD(atffile_3);
+ATF_TEST_CASE_BODY(atffile_3)
+{
+ const char* input =
+ "Content-Type: application/X-atf-atffile; version=\"1\"\n"
+ "\n"
+ "conf: var1=value1\n"
+ "conf: var2 = value2\n"
+ "conf: var3 = value3\n"
+ "conf: var4 = value4\n"
+ "\n"
+ "conf:var5=value5\n"
+ " conf:var6=value6\n"
+ "\n"
+ "conf: var7 = \"This is a long value.\"\n"
+ "conf: var8 = \"Single-word\"\n"
+ "conf: var9 = \" Single-word \"\n"
+ "conf: var10 = Single-word\n"
+ ;
+
+ const char* exp_calls[] = {
+ "got_conf(var1, value1)",
+ "got_conf(var2, value2)",
+ "got_conf(var3, value3)",
+ "got_conf(var4, value4)",
+ "got_conf(var5, value5)",
+ "got_conf(var6, value6)",
+ "got_conf(var7, This is a long value.)",
+ "got_conf(var8, Single-word)",
+ "got_conf(var9, Single-word )",
+ "got_conf(var10, Single-word)",
+ "got_eof()",
+ NULL
+ };
+
+ const char* exp_errors[] = {
+ NULL
+ };
+
+ do_parser_test< atffile_reader >(input, exp_calls, exp_errors);
+}
+
+ATF_TEST_CASE_WITHOUT_HEAD(atffile_4);
+ATF_TEST_CASE_BODY(atffile_4)
+{
+ const char* input =
+ "Content-Type: application/X-atf-atffile; version=\"1\"\n"
+ "\n"
+ "prop: var1=value1\n"
+ "prop: var2 = value2\n"
+ "prop: var3 = value3\n"
+ "prop: var4 = value4\n"
+ "\n"
+ "prop:var5=value5\n"
+ " prop:var6=value6\n"
+ "\n"
+ "prop: var7 = \"This is a long value.\"\n"
+ "prop: var8 = \"Single-word\"\n"
+ "prop: var9 = \" Single-word \"\n"
+ "prop: var10 = Single-word\n"
+ ;
+
+ const char* exp_calls[] = {
+ "got_prop(var1, value1)",
+ "got_prop(var2, value2)",
+ "got_prop(var3, value3)",
+ "got_prop(var4, value4)",
+ "got_prop(var5, value5)",
+ "got_prop(var6, value6)",
+ "got_prop(var7, This is a long value.)",
+ "got_prop(var8, Single-word)",
+ "got_prop(var9, Single-word )",
+ "got_prop(var10, Single-word)",
+ "got_eof()",
+ NULL
+ };
+
+ const char* exp_errors[] = {
+ NULL
+ };
+
+ do_parser_test< atffile_reader >(input, exp_calls, exp_errors);
+}
+
+ATF_TEST_CASE_WITHOUT_HEAD(atffile_5);
+ATF_TEST_CASE_BODY(atffile_5)
+{
+ const char* input =
+ "Content-Type: application/X-atf-atffile; version=\"1\"\n"
+ "\n"
+ "tp:foo\n"
+ "tp: foo\n"
+ "tp: foo\n"
+ "tp: foo\n"
+ "tp: foo\n"
+ "tp: \"name with spaces\"\n"
+ "tp: \"single-word\"\n"
+ "tp: single-word\n"
+ "\n"
+ "tp-glob:foo*?bar\n"
+ "tp-glob: foo*?bar\n"
+ "tp-glob: foo*?bar\n"
+ "tp-glob: foo*?bar\n"
+ "tp-glob: foo*?bar\n"
+ "tp-glob: \"glob * with ? spaces\"\n"
+ "tp-glob: \"single-*-word\"\n"
+ "tp-glob: single-*-word\n"
+ ;
+
+ const char* exp_calls[] = {
+ "got_tp(foo, false)",
+ "got_tp(foo, false)",
+ "got_tp(foo, false)",
+ "got_tp(foo, false)",
+ "got_tp(foo, false)",
+ "got_tp(name with spaces, false)",
+ "got_tp(single-word, false)",
+ "got_tp(single-word, false)",
+ "got_tp(foo*?bar, true)",
+ "got_tp(foo*?bar, true)",
+ "got_tp(foo*?bar, true)",
+ "got_tp(foo*?bar, true)",
+ "got_tp(foo*?bar, true)",
+ "got_tp(glob * with ? spaces, true)",
+ "got_tp(single-*-word, true)",
+ "got_tp(single-*-word, true)",
+ "got_eof()",
+ NULL
+ };
+
+ const char* exp_errors[] = {
+ NULL
+ };
+
+ do_parser_test< atffile_reader >(input, exp_calls, exp_errors);
+}
+
+ATF_TEST_CASE_WITHOUT_HEAD(atffile_6);
+ATF_TEST_CASE_BODY(atffile_6)
+{
+ const char* input =
+ "Content-Type: application/X-atf-atffile; version=\"1\"\n"
+ "\n"
+ "prop: foo = bar # A comment.\n"
+ "conf: foo = bar # A comment.\n"
+ "tp: foo # A comment.\n"
+ "tp-glob: foo # A comment.\n"
+ ;
+
+ const char* exp_calls[] = {
+ "got_prop(foo, bar)",
+ "got_conf(foo, bar)",
+ "got_tp(foo, false)",
+ "got_tp(foo, true)",
+ "got_eof()",
+ NULL
+ };
+
+ const char* exp_errors[] = {
+ NULL
+ };
+
+ do_parser_test< atffile_reader >(input, exp_calls, exp_errors);
+}
+
+ATF_TEST_CASE_WITHOUT_HEAD(atffile_50);
+ATF_TEST_CASE_BODY(atffile_50)
+{
+ const char* input =
+ "Content-Type: application/X-atf-atffile; version=\"1\"\n"
+ "\n"
+ "foo\n"
+ ;
+
+ const char* exp_calls[] = {
+ NULL
+ };
+
+ // NO_CHECK_STYLE_BEGIN
+ const char* exp_errors[] = {
+ "3: Unexpected token `foo'; expected conf, #, prop, tp, tp-glob, a new line or eof",
+ NULL
+ };
+ // NO_CHECK_STYLE_END
+
+ do_parser_test< atffile_reader >(input, exp_calls, exp_errors);
+}
+
+ATF_TEST_CASE_WITHOUT_HEAD(atffile_51);
+ATF_TEST_CASE_BODY(atffile_51)
+{
+ const char* input =
+ "Content-Type: application/X-atf-atffile; version=\"1\"\n"
+ "\n"
+ "foo bar\n"
+ "baz\n"
+ ;
+
+ const char* exp_calls[] = {
+ NULL
+ };
+
+ // NO_CHECK_STYLE_BEGIN
+ const char* exp_errors[] = {
+ "3: Unexpected token `foo'; expected conf, #, prop, tp, tp-glob, a new line or eof",
+ "4: Unexpected token `baz'; expected conf, #, prop, tp, tp-glob, a new line or eof",
+ NULL
+ };
+ // NO_CHECK_STYLE_END
+
+ do_parser_test< atffile_reader >(input, exp_calls, exp_errors);
+}
+
+ATF_TEST_CASE_WITHOUT_HEAD(atffile_52);
+ATF_TEST_CASE_BODY(atffile_52)
+{
+ const char* input =
+ "Content-Type: application/X-atf-atffile; version=\"1\"\n"
+ "\n"
+ "conf\n"
+ "conf:\n"
+ "conf: foo =\n"
+ "conf: bar = # A comment.\n"
+ "\n"
+ "prop\n"
+ "prop:\n"
+ "prop: foo =\n"
+ "prop: bar = # A comment.\n"
+ "\n"
+ "tp\n"
+ "tp:\n"
+ "tp: # A comment.\n"
+ "\n"
+ "tp-glob\n"
+ "tp-glob:\n"
+ "tp-glob: # A comment.\n"
+ ;
+
+ const char* exp_calls[] = {
+ NULL
+ };
+
+ const char* exp_errors[] = {
+ "3: Unexpected token `<<NEWLINE>>'; expected `:'",
+ "4: Unexpected token `<<NEWLINE>>'; expected variable name",
+ "5: Unexpected token `<<NEWLINE>>'; expected word or quoted string",
+ "6: Unexpected token `#'; expected word or quoted string",
+ "8: Unexpected token `<<NEWLINE>>'; expected `:'",
+ "9: Unexpected token `<<NEWLINE>>'; expected property name",
+ "10: Unexpected token `<<NEWLINE>>'; expected word or quoted string",
+ "11: Unexpected token `#'; expected word or quoted string",
+ "13: Unexpected token `<<NEWLINE>>'; expected `:'",
+ "14: Unexpected token `<<NEWLINE>>'; expected word or quoted string",
+ "15: Unexpected token `#'; expected word or quoted string",
+ "17: Unexpected token `<<NEWLINE>>'; expected `:'",
+ "18: Unexpected token `<<NEWLINE>>'; expected word or quoted string",
+ "19: Unexpected token `#'; expected word or quoted string",
+ NULL
+ };
+
+ do_parser_test< atffile_reader >(input, exp_calls, exp_errors);
+}
+
+ATF_TEST_CASE_WITHOUT_HEAD(atffile_53);
+ATF_TEST_CASE_BODY(atffile_53)
+{
+ const char* input =
+ "Content-Type: application/X-atf-atffile; version=\"1\"\n"
+ "\n"
+ "prop: foo = \"Correct value\" # With comment.\n"
+ "\n"
+ "prop: bar = # A comment.\n"
+ "\n"
+ "prop: baz = \"Last variable\"\n"
+ "\n"
+ "# End of file.\n"
+ ;
+
+ const char* exp_calls[] = {
+ "got_prop(foo, Correct value)",
+ NULL
+ };
+
+ const char* exp_errors[] = {
+ "5: Unexpected token `#'; expected word or quoted string",
+ NULL
+ };
+
+ do_parser_test< atffile_reader >(input, exp_calls, exp_errors);
+}
+
+ATF_TEST_CASE_WITHOUT_HEAD(atffile_54);
+ATF_TEST_CASE_BODY(atffile_54)
+{
+ const char* input =
+ "Content-Type: application/X-atf-atffile; version=\"1\"\n"
+ "\n"
+ "prop: foo = \"\n"
+ "prop: bar = \"text\n"
+ "prop: baz = \"te\\\"xt\n"
+ "prop: last = \"\\\"\n"
+ ;
+
+ const char* exp_calls[] = {
+ NULL
+ };
+
+ const char* exp_errors[] = {
+ "3: Missing double quotes before end of line",
+ "4: Missing double quotes before end of line",
+ "5: Missing double quotes before end of line",
+ "6: Missing double quotes before end of line",
+ NULL
+ };
+
+ do_parser_test< atffile_reader >(input, exp_calls, exp_errors);
+}
+
+// ------------------------------------------------------------------------
+// Tests cases for the "atffile" class.
+// ------------------------------------------------------------------------
+
+ATF_TEST_CASE(atffile_getters);
+ATF_TEST_CASE_HEAD(atffile_getters) {}
+ATF_TEST_CASE_BODY(atffile_getters) {
+ vars_map config_vars;
+ config_vars["config-var-1"] = "value 1";
+
+ std::vector< std::string > test_program_names;
+ test_program_names.push_back("test-program-1");
+
+ vars_map properties;
+ properties["test-suite"] = "a test name";
+
+ const tools::atffile atffile(config_vars, test_program_names, properties);
+ ATF_REQUIRE(config_vars == atffile.conf());
+ ATF_REQUIRE(test_program_names == atffile.tps());
+ ATF_REQUIRE(properties == atffile.props());
+}
+
+// ------------------------------------------------------------------------
+// Tests cases for the free functions.
+// ------------------------------------------------------------------------
+
+ATF_TEST_CASE_WITHOUT_HEAD(read_ok_simple);
+ATF_TEST_CASE_BODY(read_ok_simple) {
+ std::auto_ptr< std::ofstream > os = new_atffile();
+ (*os) << "prop: test-suite = foo\n";
+ (*os) << "tp: tp-1\n";
+ (*os) << "conf: var1 = value1\n";
+ (*os) << "tp: tp-2\n";
+ (*os) << "tp: tp-3\n";
+ (*os) << "prop: prop1 = propvalue1\n";
+ (*os) << "conf: var2 = value2\n";
+ (*os).close();
+
+ touch_exec("tp-1");
+ touch_exec("tp-2");
+ touch_exec("tp-3");
+
+ const tools::atffile atffile = tools::read_atffile(
+ tools::fs::path("Atffile"));
+ ATF_REQUIRE_EQ(2, atffile.conf().size());
+ ATF_REQUIRE_EQ("value1", atffile.conf().find("var1")->second);
+ ATF_REQUIRE_EQ("value2", atffile.conf().find("var2")->second);
+ ATF_REQUIRE_EQ(3, atffile.tps().size());
+ ATF_REQUIRE(is_in("tp-1", atffile.tps()));
+ ATF_REQUIRE(is_in("tp-2", atffile.tps()));
+ ATF_REQUIRE(is_in("tp-3", atffile.tps()));
+ ATF_REQUIRE_EQ(2, atffile.props().size());
+ ATF_REQUIRE_EQ("foo", atffile.props().find("test-suite")->second);
+ ATF_REQUIRE_EQ("propvalue1", atffile.props().find("prop1")->second);
+}
+
+ATF_TEST_CASE_WITHOUT_HEAD(read_ok_some_globs);
+ATF_TEST_CASE_BODY(read_ok_some_globs) {
+ std::auto_ptr< std::ofstream > os = new_atffile();
+ (*os) << "prop: test-suite = foo\n";
+ (*os) << "tp: foo\n";
+ (*os) << "tp-glob: *K*\n";
+ (*os) << "tp: bar\n";
+ (*os) << "tp-glob: t_*\n";
+ (*os).close();
+
+ touch_exec("foo");
+ touch_exec("bar");
+ touch_exec("aK");
+ touch_exec("KKKKK");
+ touch_exec("t_hello");
+ touch_exec("zzzt_hello");
+
+ const tools::atffile atffile = tools::read_atffile(
+ tools::fs::path("Atffile"));
+ ATF_REQUIRE_EQ(5, atffile.tps().size());
+ ATF_REQUIRE(is_in("foo", atffile.tps()));
+ ATF_REQUIRE(is_in("bar", atffile.tps()));
+ ATF_REQUIRE(is_in("aK", atffile.tps()));
+ ATF_REQUIRE(is_in("KKKKK", atffile.tps()));
+ ATF_REQUIRE(is_in("t_hello", atffile.tps()));
+}
+
+ATF_TEST_CASE_WITHOUT_HEAD(read_missing_test_suite);
+ATF_TEST_CASE_BODY(read_missing_test_suite) {
+ std::auto_ptr< std::ofstream > os = new_atffile();
+ (*os).close();
+
+ try {
+ (void)tools::read_atffile(tools::fs::path("Atffile"));
+ ATF_FAIL("Missing property 'test-suite' did not raise an error");
+ } catch (const tools::not_found_error< std::string >& e) {
+ ATF_REQUIRE_EQ("test-suite", e.get_value());
+ }
+}
+
+ATF_TEST_CASE_WITHOUT_HEAD(read_missing_test_program);
+ATF_TEST_CASE_BODY(read_missing_test_program) {
+ std::auto_ptr< std::ofstream > os = new_atffile();
+ (*os) << "tp: foo\n";
+ (*os) << "tp: bar\n";
+ (*os) << "tp: baz\n";
+ (*os).close();
+
+ touch_exec("foo");
+ touch_exec("baz");
+
+ try {
+ (void)tools::read_atffile(tools::fs::path("Atffile"));
+ ATF_FAIL("Missing file 'bar' did not raise an error");
+ } catch (const tools::not_found_error< tools::fs::path >& e) {
+ ATF_REQUIRE_EQ("bar", e.get_value().str());
+ }
+}
+
+// ------------------------------------------------------------------------
+// Main.
+// ------------------------------------------------------------------------
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ // Add the test cases for the parser class.
+ ATF_ADD_TEST_CASE(tcs, atffile_1);
+ ATF_ADD_TEST_CASE(tcs, atffile_2);
+ ATF_ADD_TEST_CASE(tcs, atffile_3);
+ ATF_ADD_TEST_CASE(tcs, atffile_4);
+ ATF_ADD_TEST_CASE(tcs, atffile_5);
+ ATF_ADD_TEST_CASE(tcs, atffile_6);
+ ATF_ADD_TEST_CASE(tcs, atffile_50);
+ ATF_ADD_TEST_CASE(tcs, atffile_51);
+ ATF_ADD_TEST_CASE(tcs, atffile_52);
+ ATF_ADD_TEST_CASE(tcs, atffile_53);
+ ATF_ADD_TEST_CASE(tcs, atffile_54);
+
+ // Add the test cases for the atffile class.
+ ATF_ADD_TEST_CASE(tcs, atffile_getters);
+
+ // Add the test cases for the free functions.
+ ATF_ADD_TEST_CASE(tcs, read_ok_simple);
+ ATF_ADD_TEST_CASE(tcs, read_ok_some_globs);
+ ATF_ADD_TEST_CASE(tcs, read_missing_test_suite);
+ ATF_ADD_TEST_CASE(tcs, read_missing_test_program);
+}
diff --git a/unit/atf-src/tools/auto_array.hpp b/unit/atf-src/tools/auto_array.hpp
new file mode 100644
index 0000000..773f875
--- /dev/null
+++ b/unit/atf-src/tools/auto_array.hpp
@@ -0,0 +1,179 @@
+//
+// Automated Testing Framework (atf)
+//
+// Copyright (c) 2007 The NetBSD Foundation, Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
+// CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+// IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
+// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+// IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+
+#if !defined(TOOLS_AUTO_ARRAY_HPP)
+#define TOOLS_AUTO_ARRAY_HPP
+
+#include <cstddef>
+
+namespace tools {
+
+// ------------------------------------------------------------------------
+// The "auto_array" class.
+// ------------------------------------------------------------------------
+
+template< class T >
+struct auto_array_ref {
+ T* m_ptr;
+
+ explicit auto_array_ref(T*);
+};
+
+template< class T >
+auto_array_ref< T >::auto_array_ref(T* ptr) :
+ m_ptr(ptr)
+{
+}
+
+template< class T >
+class auto_array {
+ T* m_ptr;
+
+public:
+ auto_array(T* = NULL) throw();
+ auto_array(auto_array< T >&) throw();
+ auto_array(auto_array_ref< T >) throw();
+ ~auto_array(void) throw();
+
+ T* get(void) throw();
+ const T* get(void) const throw();
+ T* release(void) throw();
+ void reset(T* = NULL) throw();
+
+ auto_array< T >& operator=(auto_array< T >&) throw();
+ auto_array< T >& operator=(auto_array_ref< T >) throw();
+
+ T& operator[](int) throw();
+ operator auto_array_ref< T >(void) throw();
+};
+
+template< class T >
+auto_array< T >::auto_array(T* ptr)
+ throw() :
+ m_ptr(ptr)
+{
+}
+
+template< class T >
+auto_array< T >::auto_array(auto_array< T >& ptr)
+ throw() :
+ m_ptr(ptr.release())
+{
+}
+
+template< class T >
+auto_array< T >::auto_array(auto_array_ref< T > ref)
+ throw() :
+ m_ptr(ref.m_ptr)
+{
+}
+
+template< class T >
+auto_array< T >::~auto_array(void)
+ throw()
+{
+ if (m_ptr != NULL)
+ delete [] m_ptr;
+}
+
+template< class T >
+T*
+auto_array< T >::get(void)
+ throw()
+{
+ return m_ptr;
+}
+
+template< class T >
+const T*
+auto_array< T >::get(void)
+ const throw()
+{
+ return m_ptr;
+}
+
+template< class T >
+T*
+auto_array< T >::release(void)
+ throw()
+{
+ T* ptr = m_ptr;
+ m_ptr = NULL;
+ return ptr;
+}
+
+template< class T >
+void
+auto_array< T >::reset(T* ptr)
+ throw()
+{
+ if (m_ptr != NULL)
+ delete [] m_ptr;
+ m_ptr = ptr;
+}
+
+template< class T >
+auto_array< T >&
+auto_array< T >::operator=(auto_array< T >& ptr)
+ throw()
+{
+ reset(ptr.release());
+ return *this;
+}
+
+template< class T >
+auto_array< T >&
+auto_array< T >::operator=(auto_array_ref< T > ref)
+ throw()
+{
+ if (m_ptr != ref.m_ptr) {
+ delete [] m_ptr;
+ m_ptr = ref.m_ptr;
+ }
+ return *this;
+}
+
+template< class T >
+T&
+auto_array< T >::operator[](int pos)
+ throw()
+{
+ return m_ptr[pos];
+}
+
+template< class T >
+auto_array< T >::operator auto_array_ref< T >(void)
+ throw()
+{
+ return auto_array_ref< T >(release());
+}
+
+} // namespace tools
+
+#endif // !defined(TOOLS_AUTO_ARRAY_HPP)
diff --git a/unit/atf-src/tools/auto_array_test.cpp b/unit/atf-src/tools/auto_array_test.cpp
new file mode 100644
index 0000000..56bbae5
--- /dev/null
+++ b/unit/atf-src/tools/auto_array_test.cpp
@@ -0,0 +1,303 @@
+//
+// Automated Testing Framework (atf)
+//
+// Copyright (c) 2007 The NetBSD Foundation, Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
+// CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+// IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
+// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+// IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+
+extern "C" {
+#include <sys/types.h>
+}
+
+#include <iostream>
+
+#include <atf-c++.hpp>
+
+#include "auto_array.hpp"
+#include "defs.hpp"
+
+// ------------------------------------------------------------------------
+// Tests for the "auto_array" class.
+// ------------------------------------------------------------------------
+
+class test_array {
+public:
+ int m_value;
+
+ static ssize_t m_nblocks;
+
+ static
+ tools::auto_array< test_array >
+ do_copy(tools::auto_array< test_array >& ta)
+ {
+ return tools::auto_array< test_array >(ta);
+ }
+
+ void* operator new(size_t size ATF_DEFS_ATTRIBUTE_UNUSED)
+ {
+ ATF_FAIL("New called but should have been new[]");
+ return new int(5);
+ }
+
+ void* operator new[](size_t size)
+ {
+ m_nblocks++;
+ void* mem = ::operator new(size);
+ std::cout << "Allocated 'test_array' object " << mem << "\n";
+ return mem;
+ }
+
+ void operator delete(void* mem ATF_DEFS_ATTRIBUTE_UNUSED)
+ {
+ ATF_FAIL("Delete called but should have been delete[]");
+ }
+
+ void operator delete[](void* mem)
+ {
+ std::cout << "Releasing 'test_array' object " << mem << "\n";
+ if (m_nblocks == 0)
+ ATF_FAIL("Unbalanced delete[]");
+ m_nblocks--;
+ ::operator delete(mem);
+ }
+};
+
+ssize_t test_array::m_nblocks = 0;
+
+ATF_TEST_CASE(auto_array_scope);
+ATF_TEST_CASE_HEAD(auto_array_scope)
+{
+ set_md_var("descr", "Tests the automatic scope handling in the "
+ "auto_array smart pointer class");
+}
+ATF_TEST_CASE_BODY(auto_array_scope)
+{
+ using tools::auto_array;
+
+ ATF_REQUIRE_EQ(test_array::m_nblocks, 0);
+ {
+ auto_array< test_array > t(new test_array[10]);
+ ATF_REQUIRE_EQ(test_array::m_nblocks, 1);
+ }
+ ATF_REQUIRE_EQ(test_array::m_nblocks, 0);
+}
+
+ATF_TEST_CASE(auto_array_copy);
+ATF_TEST_CASE_HEAD(auto_array_copy)
+{
+ set_md_var("descr", "Tests the auto_array smart pointer class' copy "
+ "constructor");
+}
+ATF_TEST_CASE_BODY(auto_array_copy)
+{
+ using tools::auto_array;
+
+ ATF_REQUIRE_EQ(test_array::m_nblocks, 0);
+ {
+ auto_array< test_array > t1(new test_array[10]);
+ ATF_REQUIRE_EQ(test_array::m_nblocks, 1);
+
+ {
+ auto_array< test_array > t2(t1);
+ ATF_REQUIRE_EQ(test_array::m_nblocks, 1);
+ }
+ ATF_REQUIRE_EQ(test_array::m_nblocks, 0);
+ }
+ ATF_REQUIRE_EQ(test_array::m_nblocks, 0);
+}
+
+ATF_TEST_CASE(auto_array_copy_ref);
+ATF_TEST_CASE_HEAD(auto_array_copy_ref)
+{
+ set_md_var("descr", "Tests the auto_array smart pointer class' copy "
+ "constructor through the auxiliary auto_array_ref object");
+}
+ATF_TEST_CASE_BODY(auto_array_copy_ref)
+{
+ using tools::auto_array;
+
+ ATF_REQUIRE_EQ(test_array::m_nblocks, 0);
+ {
+ auto_array< test_array > t1(new test_array[10]);
+ ATF_REQUIRE_EQ(test_array::m_nblocks, 1);
+
+ {
+ auto_array< test_array > t2 = test_array::do_copy(t1);
+ ATF_REQUIRE_EQ(test_array::m_nblocks, 1);
+ }
+ ATF_REQUIRE_EQ(test_array::m_nblocks, 0);
+ }
+ ATF_REQUIRE_EQ(test_array::m_nblocks, 0);
+}
+
+ATF_TEST_CASE(auto_array_get);
+ATF_TEST_CASE_HEAD(auto_array_get)
+{
+ set_md_var("descr", "Tests the auto_array smart pointer class' get "
+ "method");
+}
+ATF_TEST_CASE_BODY(auto_array_get)
+{
+ using tools::auto_array;
+
+ test_array* ta = new test_array[10];
+ auto_array< test_array > t(ta);
+ ATF_REQUIRE_EQ(t.get(), ta);
+}
+
+ATF_TEST_CASE(auto_array_release);
+ATF_TEST_CASE_HEAD(auto_array_release)
+{
+ set_md_var("descr", "Tests the auto_array smart pointer class' release "
+ "method");
+}
+ATF_TEST_CASE_BODY(auto_array_release)
+{
+ using tools::auto_array;
+
+ test_array* ta1 = new test_array[10];
+ {
+ auto_array< test_array > t(ta1);
+ ATF_REQUIRE_EQ(test_array::m_nblocks, 1);
+ test_array* ta2 = t.release();
+ ATF_REQUIRE_EQ(ta2, ta1);
+ ATF_REQUIRE_EQ(test_array::m_nblocks, 1);
+ }
+ ATF_REQUIRE_EQ(test_array::m_nblocks, 1);
+ delete [] ta1;
+}
+
+ATF_TEST_CASE(auto_array_reset);
+ATF_TEST_CASE_HEAD(auto_array_reset)
+{
+ set_md_var("descr", "Tests the auto_array smart pointer class' reset "
+ "method");
+}
+ATF_TEST_CASE_BODY(auto_array_reset)
+{
+ using tools::auto_array;
+
+ test_array* ta1 = new test_array[10];
+ test_array* ta2 = new test_array[10];
+ ATF_REQUIRE_EQ(test_array::m_nblocks, 2);
+
+ {
+ auto_array< test_array > t(ta1);
+ ATF_REQUIRE_EQ(test_array::m_nblocks, 2);
+ t.reset(ta2);
+ ATF_REQUIRE_EQ(test_array::m_nblocks, 1);
+ t.reset();
+ ATF_REQUIRE_EQ(test_array::m_nblocks, 0);
+ }
+ ATF_REQUIRE_EQ(test_array::m_nblocks, 0);
+}
+
+ATF_TEST_CASE(auto_array_assign);
+ATF_TEST_CASE_HEAD(auto_array_assign)
+{
+ set_md_var("descr", "Tests the auto_array smart pointer class' "
+ "assignment operator");
+}
+ATF_TEST_CASE_BODY(auto_array_assign)
+{
+ using tools::auto_array;
+
+ ATF_REQUIRE_EQ(test_array::m_nblocks, 0);
+ {
+ auto_array< test_array > t1(new test_array[10]);
+ ATF_REQUIRE_EQ(test_array::m_nblocks, 1);
+
+ {
+ auto_array< test_array > t2;
+ t2 = t1;
+ ATF_REQUIRE_EQ(test_array::m_nblocks, 1);
+ }
+ ATF_REQUIRE_EQ(test_array::m_nblocks, 0);
+ }
+ ATF_REQUIRE_EQ(test_array::m_nblocks, 0);
+}
+
+ATF_TEST_CASE(auto_array_assign_ref);
+ATF_TEST_CASE_HEAD(auto_array_assign_ref)
+{
+ set_md_var("descr", "Tests the auto_array smart pointer class' "
+ "assignment operator through the auxiliary auto_array_ref "
+ "object");
+}
+ATF_TEST_CASE_BODY(auto_array_assign_ref)
+{
+ using tools::auto_array;
+
+ ATF_REQUIRE_EQ(test_array::m_nblocks, 0);
+ {
+ auto_array< test_array > t1(new test_array[10]);
+ ATF_REQUIRE_EQ(test_array::m_nblocks, 1);
+
+ {
+ auto_array< test_array > t2;
+ t2 = test_array::do_copy(t1);
+ ATF_REQUIRE_EQ(test_array::m_nblocks, 1);
+ }
+ ATF_REQUIRE_EQ(test_array::m_nblocks, 0);
+ }
+ ATF_REQUIRE_EQ(test_array::m_nblocks, 0);
+}
+
+ATF_TEST_CASE(auto_array_access);
+ATF_TEST_CASE_HEAD(auto_array_access)
+{
+ set_md_var("descr", "Tests the auto_array smart pointer class' access "
+ "operator");
+}
+ATF_TEST_CASE_BODY(auto_array_access)
+{
+ using tools::auto_array;
+
+ auto_array< test_array > t(new test_array[10]);
+
+ for (int i = 0; i < 10; i++)
+ t[i].m_value = i * 2;
+
+ for (int i = 0; i < 10; i++)
+ ATF_REQUIRE_EQ(t[i].m_value, i * 2);
+}
+
+// ------------------------------------------------------------------------
+// Main.
+// ------------------------------------------------------------------------
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ // Add the test for the "auto_array" class.
+ ATF_ADD_TEST_CASE(tcs, auto_array_scope);
+ ATF_ADD_TEST_CASE(tcs, auto_array_copy);
+ ATF_ADD_TEST_CASE(tcs, auto_array_copy_ref);
+ ATF_ADD_TEST_CASE(tcs, auto_array_get);
+ ATF_ADD_TEST_CASE(tcs, auto_array_release);
+ ATF_ADD_TEST_CASE(tcs, auto_array_reset);
+ ATF_ADD_TEST_CASE(tcs, auto_array_assign);
+ ATF_ADD_TEST_CASE(tcs, auto_array_assign_ref);
+ ATF_ADD_TEST_CASE(tcs, auto_array_access);
+}
diff --git a/unit/atf-src/tools/bad_metadata_helper.c b/unit/atf-src/tools/bad_metadata_helper.c
new file mode 100644
index 0000000..0f7fcb9
--- /dev/null
+++ b/unit/atf-src/tools/bad_metadata_helper.c
@@ -0,0 +1,38 @@
+/*
+ * Automated Testing Framework (atf)
+ *
+ * Copyright (c) 2010 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
+ * CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+ * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+ * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+int
+main(void)
+{
+ printf("incorrectly formatted metadata\n");
+ return EXIT_SUCCESS;
+}
diff --git a/unit/atf-src/tools/config.cpp b/unit/atf-src/tools/config.cpp
new file mode 100644
index 0000000..95bcf55
--- /dev/null
+++ b/unit/atf-src/tools/config.cpp
@@ -0,0 +1,135 @@
+//
+// Automated Testing Framework (atf)
+//
+// Copyright (c) 2007 The NetBSD Foundation, Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
+// CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+// IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
+// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+// IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+
+#include <cassert>
+#include <map>
+
+#include "config.hpp"
+#include "env.hpp"
+#include "text.hpp"
+
+static std::map< std::string, std::string > m_variables;
+
+static struct var {
+ const char *name;
+ const char *default_value;
+ bool can_be_empty;
+} vars[] = {
+ { "ATF_ARCH", ATF_ARCH, false, },
+ { "ATF_BUILD_CC", ATF_BUILD_CC, false, },
+ { "ATF_BUILD_CFLAGS", ATF_BUILD_CFLAGS, true, },
+ { "ATF_BUILD_CPP", ATF_BUILD_CPP, false, },
+ { "ATF_BUILD_CPPFLAGS", ATF_BUILD_CPPFLAGS, true, },
+ { "ATF_BUILD_CXX", ATF_BUILD_CXX, false, },
+ { "ATF_BUILD_CXXFLAGS", ATF_BUILD_CXXFLAGS, true, },
+ { "ATF_CONFDIR", ATF_CONFDIR, false, },
+ { "ATF_INCLUDEDIR", ATF_INCLUDEDIR, false, },
+ { "ATF_LIBDIR", ATF_LIBDIR, false, },
+ { "ATF_LIBEXECDIR", ATF_LIBEXECDIR, false, },
+ { "ATF_MACHINE", ATF_MACHINE, false, },
+ { "ATF_PKGDATADIR", ATF_PKGDATADIR, false, },
+ { "ATF_SHELL", ATF_SHELL, false, },
+ { "ATF_WORKDIR", ATF_WORKDIR, false, },
+ { NULL, NULL, false, },
+};
+
+//
+// Adds all predefined standard build-time variables to the m_variables
+// map, considering the values a user may have provided in the environment.
+//
+// Can only be called once during the program's lifetime.
+//
+static
+void
+init_variables(void)
+{
+ assert(m_variables.empty());
+
+ for (struct var* v = vars; v->name != NULL; v++) {
+ const std::string varname = tools::text::to_lower(v->name);
+
+ if (tools::env::has(v->name)) {
+ const std::string envval = tools::env::get(v->name);
+ if (envval.empty() && !v->can_be_empty)
+ m_variables[varname] = v->default_value;
+ else
+ m_variables[varname] = envval;
+ } else {
+ m_variables[varname] = v->default_value;
+ }
+ }
+
+ assert(!m_variables.empty());
+}
+
+const std::string&
+tools::config::get(const std::string& varname)
+{
+ if (m_variables.empty())
+ init_variables();
+
+ assert(has(varname));
+ return m_variables[varname];
+}
+
+const std::map< std::string, std::string >&
+tools::config::get_all(void)
+{
+ if (m_variables.empty())
+ init_variables();
+
+ return m_variables;
+}
+
+bool
+tools::config::has(const std::string& varname)
+{
+ if (m_variables.empty())
+ init_variables();
+
+ return m_variables.find(varname) != m_variables.end();
+}
+
+namespace tools {
+namespace config {
+//
+// Auxiliary function for the t_config test program so that it can
+// revert the configuration's global status to an empty state and
+// do new tests from there on.
+//
+// Ideally this shouldn't be part of the production library... but
+// this is so small that it does not matter.
+//
+void
+__reinit(void)
+{
+ m_variables.clear();
+}
+} // namespace config
+} // namespace tools
diff --git a/unit/atf-src/tools/config.hpp b/unit/atf-src/tools/config.hpp
new file mode 100644
index 0000000..0a43873
--- /dev/null
+++ b/unit/atf-src/tools/config.hpp
@@ -0,0 +1,75 @@
+//
+// Automated Testing Framework (atf)
+//
+// Copyright (c) 2007 The NetBSD Foundation, Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
+// CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+// IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
+// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+// IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+
+#if !defined(TOOLS_CONFIG_HPP)
+#define TOOLS_CONFIG_HPP
+
+#include <map>
+#include <string>
+
+namespace tools {
+
+namespace config {
+
+//!
+//! \brief Gets a build-time configuration variable's value.
+//!
+//! Given the name of a build-time configuration variable, returns its
+//! textual value. The user is free to override these by setting their
+//! corresponding environment variables. Therefore always use this
+//! interface to get the value of these variables.
+//!
+//! \pre The variable must exist.
+//!
+const std::string& get(const std::string&);
+
+//!
+//! \brief Returns all the build-time configuration variables.
+//!
+//! Returns a name to value map containing all build-time configuration
+//! variables.
+//!
+const std::map< std::string, std::string >& get_all(void);
+
+//!
+//! \brief Checks whether a build-time configuration variable exists.
+//!
+//! Given the name of a build-time configuration variable, checks
+//! whether it is defined and returns a boolean indicating this
+//! condition. The program only has to use this function to sanity-check
+//! a variable name provided by the user. Otherwise it can assume that
+//! the variables are defined.
+//!
+bool has(const std::string&);
+
+} // namespace config
+
+} // namespace tools
+
+#endif // !defined(TOOLS_CONFIG_HPP)
diff --git a/unit/atf-src/tools/config_file.cpp b/unit/atf-src/tools/config_file.cpp
new file mode 100644
index 0000000..3802c06
--- /dev/null
+++ b/unit/atf-src/tools/config_file.cpp
@@ -0,0 +1,223 @@
+//
+// Automated Testing Framework (atf)
+//
+// Copyright (c) 2007 The NetBSD Foundation, Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
+// CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+// IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
+// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+// IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+
+#include <cstdlib>
+#include <fstream>
+#include <vector>
+
+#include "config.hpp"
+#include "config_file.hpp"
+#include "defs.hpp"
+#include "env.hpp"
+#include "fs.hpp"
+#include "parser.hpp"
+
+namespace impl = tools::config_file;
+namespace detail = tools::config_file::detail;
+
+namespace {
+
+typedef std::map< std::string, std::string > vars_map;
+
+namespace atf_config {
+
+static const tools::parser::token_type eof_type = 0;
+static const tools::parser::token_type nl_type = 1;
+static const tools::parser::token_type text_type = 2;
+static const tools::parser::token_type dblquote_type = 3;
+static const tools::parser::token_type equal_type = 4;
+static const tools::parser::token_type hash_type = 5;
+
+class tokenizer : public tools::parser::tokenizer< std::istream > {
+public:
+ tokenizer(std::istream& is, size_t curline) :
+ tools::parser::tokenizer< std::istream >
+ (is, true, eof_type, nl_type, text_type, curline)
+ {
+ add_delim('=', equal_type);
+ add_delim('#', hash_type);
+ add_quote('"', dblquote_type);
+ }
+};
+
+} // namespace atf_config
+
+class config_reader : public detail::atf_config_reader {
+ vars_map m_vars;
+
+ void
+ got_var(const std::string& var, const std::string& name)
+ {
+ m_vars[var] = name;
+ }
+
+public:
+ config_reader(std::istream& is) :
+ atf_config_reader(is)
+ {
+ }
+
+ const vars_map&
+ get_vars(void)
+ const
+ {
+ return m_vars;
+ }
+};
+
+template< class K, class V >
+static
+void
+merge_maps(std::map< K, V >& dest, const std::map< K, V >& src)
+{
+ for (typename std::map< K, V >::const_iterator iter = src.begin();
+ iter != src.end(); iter++)
+ dest[(*iter).first] = (*iter).second;
+}
+
+static
+void
+merge_config_file(const tools::fs::path& config_path,
+ vars_map& config)
+{
+ std::ifstream is(config_path.c_str());
+ if (is) {
+ config_reader reader(is);
+ reader.read();
+ merge_maps(config, reader.get_vars());
+ }
+}
+
+static
+std::vector< tools::fs::path >
+get_config_dirs(void)
+{
+ std::vector< tools::fs::path > dirs;
+ dirs.push_back(tools::fs::path(tools::config::get("atf_confdir")));
+ if (tools::env::has("HOME"))
+ dirs.push_back(tools::fs::path(tools::env::get("HOME")) / ".atf");
+ return dirs;
+}
+
+} // anonymous namespace
+
+detail::atf_config_reader::atf_config_reader(std::istream& is) :
+ m_is(is)
+{
+}
+
+detail::atf_config_reader::~atf_config_reader(void)
+{
+}
+
+void
+detail::atf_config_reader::got_var(
+ const std::string& var ATF_DEFS_ATTRIBUTE_UNUSED,
+ const std::string& val ATF_DEFS_ATTRIBUTE_UNUSED)
+{
+}
+
+void
+detail::atf_config_reader::got_eof(void)
+{
+}
+
+void
+detail::atf_config_reader::read(void)
+{
+ using tools::parser::parse_error;
+ using namespace atf_config;
+
+ std::pair< size_t, tools::parser::headers_map > hml =
+ tools::parser::read_headers(m_is, 1);
+ tools::parser::validate_content_type(hml.second,
+ "application/X-atf-config", 1);
+
+ tokenizer tkz(m_is, hml.first);
+ tools::parser::parser< tokenizer > p(tkz);
+
+ for (;;) {
+ try {
+ tools::parser::token t = p.expect(eof_type, hash_type, text_type,
+ nl_type,
+ "eof, #, new line or text");
+ if (t.type() == eof_type)
+ break;
+
+ if (t.type() == hash_type) {
+ (void)p.rest_of_line();
+ t = p.expect(nl_type, "new line");
+ } else if (t.type() == text_type) {
+ std::string name = t.text();
+
+ t = p.expect(equal_type, "equal sign");
+
+ t = p.expect(text_type, "word or quoted string");
+ ATF_PARSER_CALLBACK(p, got_var(name, t.text()));
+
+ t = p.expect(nl_type, hash_type, "new line or comment");
+ if (t.type() == hash_type) {
+ (void)p.rest_of_line();
+ t = p.expect(nl_type, "new line");
+ }
+ } else if (t.type() == nl_type) {
+ } else
+ std::abort();
+ } catch (const parse_error& pe) {
+ p.add_error(pe);
+ p.reset(nl_type);
+ }
+ }
+
+ ATF_PARSER_CALLBACK(p, got_eof());
+}
+
+vars_map
+impl::merge_configs(const vars_map& lower,
+ const vars_map& upper)
+{
+ vars_map merged = lower;
+ merge_maps(merged, upper);
+ return merged;
+}
+
+vars_map
+impl::read_config_files(const std::string& test_suite_name)
+{
+ vars_map config;
+
+ const std::vector< tools::fs::path > dirs = get_config_dirs();
+ for (std::vector< tools::fs::path >::const_iterator iter = dirs.begin();
+ iter != dirs.end(); iter++) {
+ merge_config_file((*iter) / "common.conf", config);
+ merge_config_file((*iter) / (test_suite_name + ".conf"), config);
+ }
+
+ return config;
+}
diff --git a/unit/atf-src/tools/config_file.hpp b/unit/atf-src/tools/config_file.hpp
new file mode 100644
index 0000000..0c68bcb
--- /dev/null
+++ b/unit/atf-src/tools/config_file.hpp
@@ -0,0 +1,66 @@
+//
+// Automated Testing Framework (atf)
+//
+// Copyright (c) 2010 The NetBSD Foundation, Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
+// CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+// IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
+// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+// IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+
+#if !defined(TOOLS_CONFIG_FILE_HPP)
+#define TOOLS_CONFIG_FILE_HPP
+
+#include <map>
+#include <string>
+#include <vector>
+
+namespace tools {
+namespace config_file {
+
+namespace detail {
+
+class atf_config_reader {
+ std::istream& m_is;
+
+protected:
+ virtual void got_var(const std::string&, const std::string &);
+ virtual void got_eof(void);
+
+public:
+ atf_config_reader(std::istream&);
+ virtual ~atf_config_reader(void);
+
+ void read(void);
+};
+
+} // namespace detail
+
+std::map< std::string, std::string > merge_configs(
+ const std::map< std::string, std::string >&,
+ const std::map< std::string, std::string >&);
+std::map< std::string, std::string > read_config_files(const std::string&);
+
+} // namespace config_file
+} // namespace tools
+
+#endif // !defined(TOOLS_CONFIG_FILE_HPP)
diff --git a/unit/atf-src/tools/config_file_test.cpp b/unit/atf-src/tools/config_file_test.cpp
new file mode 100644
index 0000000..f042634
--- /dev/null
+++ b/unit/atf-src/tools/config_file_test.cpp
@@ -0,0 +1,395 @@
+//
+// Automated Testing Framework (atf)
+//
+// Copyright (c) 2010 The NetBSD Foundation, Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
+// CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+// IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
+// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+// IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+
+#include <atf-c++.hpp>
+
+#include "config.hpp"
+#include "config_file.hpp"
+#include "env.hpp"
+#include "test_helpers.hpp"
+
+namespace impl = tools::config_file;
+namespace detail = tools::config_file::detail;
+
+namespace {
+
+typedef std::map< std::string, std::string > vars_map;
+
+} // anonymous namespace
+
+namespace tools {
+namespace config {
+
+void __reinit(void);
+
+} // namespace config
+} // namespace atf
+
+// -------------------------------------------------------------------------
+// Tests for the "config" parser.
+// -------------------------------------------------------------------------
+
+class config_reader : protected detail::atf_config_reader {
+ void
+ got_var(const std::string& name, const std::string& val)
+ {
+ m_calls.push_back("got_var(" + name + ", " + val + ")");
+ }
+
+ void
+ got_eof(void)
+ {
+ m_calls.push_back("got_eof()");
+ }
+
+public:
+ config_reader(std::istream& is) :
+ detail::atf_config_reader(is)
+ {
+ }
+
+ void
+ read(void)
+ {
+ atf_config_reader::read();
+ }
+
+ std::vector< std::string > m_calls;
+};
+
+ATF_TEST_CASE_WITHOUT_HEAD(config_1);
+ATF_TEST_CASE_BODY(config_1)
+{
+ const char* input =
+ "Content-Type: application/X-atf-config; version=\"1\"\n"
+ "\n"
+ ;
+
+ const char* exp_calls[] = {
+ "got_eof()",
+ NULL
+ };
+
+ const char* exp_errors[] = {
+ NULL
+ };
+
+ do_parser_test< config_reader >(input, exp_calls, exp_errors);
+}
+
+ATF_TEST_CASE_WITHOUT_HEAD(config_2);
+ATF_TEST_CASE_BODY(config_2)
+{
+ const char* input =
+ "Content-Type: application/X-atf-config; version=\"1\"\n"
+ "\n"
+ "# This is a comment on a line of its own.\n"
+ "# And this is another one.\n"
+ "\n"
+ " # Another after some whitespace.\n"
+ "\n"
+ "# The last one after an empty line.\n"
+ ;
+
+ const char* exp_calls[] = {
+ "got_eof()",
+ NULL
+ };
+
+ const char* exp_errors[] = {
+ NULL
+ };
+
+ do_parser_test< config_reader >(input, exp_calls, exp_errors);
+}
+
+ATF_TEST_CASE_WITHOUT_HEAD(config_3);
+ATF_TEST_CASE_BODY(config_3)
+{
+ const char* input =
+ "Content-Type: application/X-atf-config; version=\"1\"\n"
+ "\n"
+ "var1=value1\n"
+ "var2 = value2\n"
+ "var3 = value3\n"
+ "var4 = value4\n"
+ "\n"
+ "var5=value5\n"
+ " var6=value6\n"
+ "\n"
+ "var7 = \"This is a long value.\"\n"
+ "var8 = \"Single-word\"\n"
+ "var9 = \" Single-word \"\n"
+ "var10 = Single-word\n"
+ ;
+
+ const char* exp_calls[] = {
+ "got_var(var1, value1)",
+ "got_var(var2, value2)",
+ "got_var(var3, value3)",
+ "got_var(var4, value4)",
+ "got_var(var5, value5)",
+ "got_var(var6, value6)",
+ "got_var(var7, This is a long value.)",
+ "got_var(var8, Single-word)",
+ "got_var(var9, Single-word )",
+ "got_var(var10, Single-word)",
+ "got_eof()",
+ NULL
+ };
+
+ const char* exp_errors[] = {
+ NULL
+ };
+
+ do_parser_test< config_reader >(input, exp_calls, exp_errors);
+}
+
+ATF_TEST_CASE_WITHOUT_HEAD(config_4);
+ATF_TEST_CASE_BODY(config_4)
+{
+ const char* input =
+ "Content-Type: application/X-atf-config; version=\"1\"\n"
+ "\n"
+ "foo = bar # A comment.\n"
+ ;
+
+ const char* exp_calls[] = {
+ "got_var(foo, bar)",
+ "got_eof()",
+ NULL
+ };
+
+ const char* exp_errors[] = {
+ NULL
+ };
+
+ do_parser_test< config_reader >(input, exp_calls, exp_errors);
+}
+
+ATF_TEST_CASE_WITHOUT_HEAD(config_50);
+ATF_TEST_CASE_BODY(config_50)
+{
+ const char* input =
+ "Content-Type: application/X-atf-config; version=\"1\"\n"
+ "\n"
+ "foo\n"
+ ;
+
+ const char* exp_calls[] = {
+ NULL
+ };
+
+ const char* exp_errors[] = {
+ "3: Unexpected token `<<NEWLINE>>'; expected equal sign",
+ NULL
+ };
+
+ do_parser_test< config_reader >(input, exp_calls, exp_errors);
+}
+
+ATF_TEST_CASE_WITHOUT_HEAD(config_51);
+ATF_TEST_CASE_BODY(config_51)
+{
+ const char* input =
+ "Content-Type: application/X-atf-config; version=\"1\"\n"
+ "\n"
+ "foo bar\n"
+ "baz\n"
+ ;
+
+ const char* exp_calls[] = {
+ NULL
+ };
+
+ const char* exp_errors[] = {
+ "3: Unexpected token `bar'; expected equal sign",
+ "4: Unexpected token `<<NEWLINE>>'; expected equal sign",
+ NULL
+ };
+
+ do_parser_test< config_reader >(input, exp_calls, exp_errors);
+}
+
+ATF_TEST_CASE_WITHOUT_HEAD(config_52);
+ATF_TEST_CASE_BODY(config_52)
+{
+ const char* input =
+ "Content-Type: application/X-atf-config; version=\"1\"\n"
+ "\n"
+ "foo =\n"
+ "bar = # A comment.\n"
+ ;
+
+ const char* exp_calls[] = {
+ NULL
+ };
+
+ const char* exp_errors[] = {
+ "3: Unexpected token `<<NEWLINE>>'; expected word or quoted string",
+ "4: Unexpected token `#'; expected word or quoted string",
+ NULL
+ };
+
+ do_parser_test< config_reader >(input, exp_calls, exp_errors);
+}
+
+ATF_TEST_CASE_WITHOUT_HEAD(config_53);
+ATF_TEST_CASE_BODY(config_53)
+{
+ const char* input =
+ "Content-Type: application/X-atf-config; version=\"1\"\n"
+ "\n"
+ "foo = \"Correct value\" # With comment.\n"
+ "\n"
+ "bar = # A comment.\n"
+ "\n"
+ "baz = \"Last variable\"\n"
+ "\n"
+ "# End of file.\n"
+ ;
+
+ const char* exp_calls[] = {
+ "got_var(foo, Correct value)",
+ NULL
+ };
+
+ const char* exp_errors[] = {
+ "5: Unexpected token `#'; expected word or quoted string",
+ NULL
+ };
+
+ do_parser_test< config_reader >(input, exp_calls, exp_errors);
+}
+
+ATF_TEST_CASE_WITHOUT_HEAD(config_54);
+ATF_TEST_CASE_BODY(config_54)
+{
+ const char* input =
+ "Content-Type: application/X-atf-config; version=\"1\"\n"
+ "\n"
+ "foo = \"\n"
+ "bar = \"text\n"
+ "baz = \"te\\\"xt\n"
+ "last = \"\\\"\n"
+ ;
+
+ const char* exp_calls[] = {
+ NULL
+ };
+
+ const char* exp_errors[] = {
+ "3: Missing double quotes before end of line",
+ "4: Missing double quotes before end of line",
+ "5: Missing double quotes before end of line",
+ "6: Missing double quotes before end of line",
+ NULL
+ };
+
+ do_parser_test< config_reader >(input, exp_calls, exp_errors);
+}
+
+// -------------------------------------------------------------------------
+// Tests for the free functions.
+// -------------------------------------------------------------------------
+
+ATF_TEST_CASE(merge_configs_both_empty);
+ATF_TEST_CASE_HEAD(merge_configs_both_empty) {}
+ATF_TEST_CASE_BODY(merge_configs_both_empty) {
+ vars_map lower, upper;
+
+ ATF_REQUIRE(impl::merge_configs(lower, upper).empty());
+}
+
+ATF_TEST_CASE(merge_configs_lower_empty);
+ATF_TEST_CASE_HEAD(merge_configs_lower_empty) {}
+ATF_TEST_CASE_BODY(merge_configs_lower_empty) {
+ vars_map lower, upper;
+ upper["var"] = "value";
+
+ vars_map merged = impl::merge_configs(lower, upper);
+ ATF_REQUIRE_EQ("value", merged["var"]);
+}
+
+ATF_TEST_CASE(merge_configs_upper_empty);
+ATF_TEST_CASE_HEAD(merge_configs_upper_empty) {}
+ATF_TEST_CASE_BODY(merge_configs_upper_empty) {
+ vars_map lower, upper;
+ lower["var"] = "value";
+
+ vars_map merged = impl::merge_configs(lower, upper);
+ ATF_REQUIRE_EQ("value", merged["var"]);
+}
+
+ATF_TEST_CASE(merge_configs_mixed);
+ATF_TEST_CASE_HEAD(merge_configs_mixed) {}
+ATF_TEST_CASE_BODY(merge_configs_mixed) {
+ vars_map lower, upper;
+ lower["var1"] = "value1";
+ lower["var2"] = "value2-l";
+ upper["var2"] = "value2-u";
+ upper["var3"] = "value3";
+
+ vars_map merged = impl::merge_configs(lower, upper);
+ ATF_REQUIRE_EQ("value1", merged["var1"]);
+ ATF_REQUIRE_EQ("value2-u", merged["var2"]);
+ ATF_REQUIRE_EQ("value3", merged["var3"]);
+}
+
+ATF_TEST_CASE(read_config_files_none);
+ATF_TEST_CASE_HEAD(read_config_files_none) {}
+ATF_TEST_CASE_BODY(read_config_files_none) {
+ tools::env::set("ATF_CONFDIR", ".");
+ tools::config::__reinit();
+ ATF_REQUIRE(vars_map() == impl::read_config_files("test-suite"));
+}
+
+// -------------------------------------------------------------------------
+// Main.
+// -------------------------------------------------------------------------
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ ATF_ADD_TEST_CASE(tcs, config_1);
+ ATF_ADD_TEST_CASE(tcs, config_2);
+ ATF_ADD_TEST_CASE(tcs, config_3);
+ ATF_ADD_TEST_CASE(tcs, config_4);
+ ATF_ADD_TEST_CASE(tcs, config_50);
+ ATF_ADD_TEST_CASE(tcs, config_51);
+ ATF_ADD_TEST_CASE(tcs, config_52);
+ ATF_ADD_TEST_CASE(tcs, config_53);
+ ATF_ADD_TEST_CASE(tcs, config_54);
+
+ ATF_ADD_TEST_CASE(tcs, merge_configs_both_empty);
+ ATF_ADD_TEST_CASE(tcs, merge_configs_lower_empty);
+ ATF_ADD_TEST_CASE(tcs, merge_configs_upper_empty);
+ ATF_ADD_TEST_CASE(tcs, merge_configs_mixed);
+
+ ATF_ADD_TEST_CASE(tcs, read_config_files_none);
+}
diff --git a/unit/atf-src/tools/config_test.cpp b/unit/atf-src/tools/config_test.cpp
new file mode 100644
index 0000000..d7d6719
--- /dev/null
+++ b/unit/atf-src/tools/config_test.cpp
@@ -0,0 +1,221 @@
+//
+// Automated Testing Framework (atf)
+//
+// Copyright (c) 2007 The NetBSD Foundation, Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
+// CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+// IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
+// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+// IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+
+#include <cstring>
+#include <iostream>
+
+#include <atf-c++.hpp>
+
+#include "config.hpp"
+#include "env.hpp"
+#include "exceptions.hpp"
+
+static const char *test_value = "env-value";
+
+static struct varnames {
+ const char *lc;
+ const char *uc;
+ bool can_be_empty;
+} all_vars[] = {
+ { "atf_arch", "ATF_ARCH", false },
+ { "atf_build_cc", "ATF_BUILD_CC", false },
+ { "atf_build_cflags", "ATF_BUILD_CFLAGS", true },
+ { "atf_build_cpp", "ATF_BUILD_CPP", false },
+ { "atf_build_cppflags", "ATF_BUILD_CPPFLAGS", true },
+ { "atf_build_cxx", "ATF_BUILD_CXX", false },
+ { "atf_build_cxxflags", "ATF_BUILD_CXXFLAGS", true },
+ { "atf_confdir", "ATF_CONFDIR", false },
+ { "atf_includedir", "ATF_INCLUDEDIR", false },
+ { "atf_libdir", "ATF_LIBDIR", false },
+ { "atf_libexecdir", "ATF_LIBEXECDIR", false },
+ { "atf_machine", "ATF_MACHINE", false },
+ { "atf_pkgdatadir", "ATF_PKGDATADIR", false },
+ { "atf_shell", "ATF_SHELL", false },
+ { "atf_workdir", "ATF_WORKDIR", false },
+ { NULL, NULL, false }
+};
+
+// ------------------------------------------------------------------------
+// Auxiliary functions.
+// ------------------------------------------------------------------------
+
+namespace tools {
+ namespace config {
+ void __reinit(void);
+ }
+}
+
+static
+void
+set_env_var(const char* name, const char* val)
+{
+ try {
+ tools::env::set(name, val);
+ } catch (const tools::system_error&) {
+ ATF_FAIL(std::string("set_env_var(") + name + ", " + val +
+ ") failed");
+ }
+}
+
+static
+void
+unset_env_var(const char* name)
+{
+ try {
+ tools::env::unset(name);
+ } catch (const tools::system_error&) {
+ ATF_FAIL(std::string("unset_env_var(") + name + ") failed");
+ }
+}
+
+static
+size_t
+all_vars_count(void)
+{
+ size_t count = 0;
+ for (const struct varnames* v = all_vars; v->lc != NULL; v++)
+ count++;
+ return count;
+}
+
+static
+void
+unset_all(void)
+{
+ for (const struct varnames* v = all_vars; v->lc != NULL; v++)
+ unset_env_var(v->uc);
+}
+
+static
+void
+compare_one(const char* var, const char* expvalue)
+{
+ std::cout << "Checking that " << var << " is set to " << expvalue << "\n";
+
+ for (const struct varnames* v = all_vars; v->lc != NULL; v++) {
+ if (std::strcmp(v->lc, var) == 0)
+ ATF_REQUIRE_EQ(tools::config::get(v->lc), test_value);
+ else
+ ATF_REQUIRE(tools::config::get(v->lc) != test_value);
+ }
+}
+
+// ------------------------------------------------------------------------
+// Test cases for the free functions.
+// ------------------------------------------------------------------------
+
+ATF_TEST_CASE(get);
+ATF_TEST_CASE_HEAD(get)
+{
+ set_md_var("descr", "Tests the config::get function");
+}
+ATF_TEST_CASE_BODY(get)
+{
+ // Unset all known environment variables and make sure the built-in
+ // values do not match the bogus value we will use for testing.
+ unset_all();
+ tools::config::__reinit();
+ for (const struct varnames* v = all_vars; v->lc != NULL; v++)
+ ATF_REQUIRE(tools::config::get(v->lc) != test_value);
+
+ // Test the behavior of empty values.
+ for (const struct varnames* v = all_vars; v->lc != NULL; v++) {
+ unset_all();
+ if (!tools::config::get(v->lc).empty()) {
+ set_env_var(v->uc, "");
+ tools::config::__reinit();
+ if (v->can_be_empty)
+ ATF_REQUIRE(tools::config::get(v->lc).empty());
+ else
+ ATF_REQUIRE(!tools::config::get(v->lc).empty());
+ }
+ }
+
+ // Check if the ATF_ARCH variable is recognized.
+ for (const struct varnames* v = all_vars; v->lc != NULL; v++) {
+ unset_all();
+ set_env_var(v->uc, test_value);
+ tools::config::__reinit();
+ compare_one(v->lc, test_value);
+ }
+}
+
+ATF_TEST_CASE(get_all);
+ATF_TEST_CASE_HEAD(get_all)
+{
+ set_md_var("descr", "Tests the config::get_all function");
+}
+ATF_TEST_CASE_BODY(get_all)
+{
+ tools::config::__reinit();
+
+ // Check that the valid variables, and only those, are returned.
+ std::map< std::string, std::string > vars = tools::config::get_all();
+ ATF_REQUIRE_EQ(vars.size(), all_vars_count());
+ for (const struct varnames* v = all_vars; v->lc != NULL; v++)
+ ATF_REQUIRE(vars.find(v->lc) != vars.end());
+}
+
+ATF_TEST_CASE(has);
+ATF_TEST_CASE_HEAD(has)
+{
+ set_md_var("descr", "Tests the config::has function");
+}
+ATF_TEST_CASE_BODY(has)
+{
+ tools::config::__reinit();
+
+ // Check for all the variables that must exist.
+ for (const struct varnames* v = all_vars; v->lc != NULL; v++)
+ ATF_REQUIRE(tools::config::has(v->lc));
+
+ // Same as above, but using uppercase (which is incorrect).
+ for (const struct varnames* v = all_vars; v->lc != NULL; v++)
+ ATF_REQUIRE(!tools::config::has(v->uc));
+
+ // Check for some other variables that cannot exist.
+ ATF_REQUIRE(!tools::config::has("foo"));
+ ATF_REQUIRE(!tools::config::has("BAR"));
+ ATF_REQUIRE(!tools::config::has("atf_foo"));
+ ATF_REQUIRE(!tools::config::has("ATF_BAR"));
+ ATF_REQUIRE(!tools::config::has("atf_shel"));
+ ATF_REQUIRE(!tools::config::has("atf_shells"));
+}
+
+// ------------------------------------------------------------------------
+// Main.
+// ------------------------------------------------------------------------
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ // Add the test cases for the free functions.
+ ATF_ADD_TEST_CASE(tcs, has);
+ ATF_ADD_TEST_CASE(tcs, get);
+ ATF_ADD_TEST_CASE(tcs, get_all);
+}
diff --git a/unit/atf-src/tools/defs.hpp.in b/unit/atf-src/tools/defs.hpp.in
new file mode 100644
index 0000000..9542795
--- /dev/null
+++ b/unit/atf-src/tools/defs.hpp.in
@@ -0,0 +1,37 @@
+//
+// Automated Testing Framework (atf)
+//
+// Copyright (c) 2008 The NetBSD Foundation, Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
+// CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+// IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
+// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+// IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+
+#if !defined(TOOLS_DEFS_HPP)
+#define TOOLS_DEFS_HPP
+
+#define ATF_DEFS_ATTRIBUTE_FORMAT_PRINTF(a, b) @ATTRIBUTE_FORMAT_PRINTF@
+#define ATF_DEFS_ATTRIBUTE_NORETURN @ATTRIBUTE_NORETURN@
+#define ATF_DEFS_ATTRIBUTE_UNUSED @ATTRIBUTE_UNUSED@
+
+#endif /* !defined(TOOLS_DEFS_HPP) */
diff --git a/unit/atf-src/tools/env.cpp b/unit/atf-src/tools/env.cpp
new file mode 100644
index 0000000..856ef98
--- /dev/null
+++ b/unit/atf-src/tools/env.cpp
@@ -0,0 +1,102 @@
+//
+// Automated Testing Framework (atf)
+//
+// Copyright (c) 2007 The NetBSD Foundation, Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
+// CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+// IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
+// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+// IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+
+#if defined(HAVE_CONFIG_H)
+#include "config.h"
+#endif
+
+extern "C" {
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+}
+
+#include <cassert>
+
+#include "env.hpp"
+#include "exceptions.hpp"
+
+namespace impl = tools::env;
+#define IMPL_NAME "tools::env"
+
+// ------------------------------------------------------------------------
+// Free functions.
+// ------------------------------------------------------------------------
+
+std::string
+impl::get(const std::string& name)
+{
+ const char* val = getenv(name.c_str());
+ assert(val != NULL);
+ return val;
+}
+
+bool
+impl::has(const std::string& name)
+{
+ return getenv(name.c_str()) != NULL;
+}
+
+void
+impl::set(const std::string& name, const std::string& val)
+{
+#if defined(HAVE_SETENV)
+ if (setenv(name.c_str(), val.c_str(), 1) == -1)
+ throw tools::system_error(IMPL_NAME "::set",
+ "Cannot set environment variable '" + name +
+ "' to '" + val + "'",
+ errno);
+#elif defined(HAVE_PUTENV)
+ const std::string buf = name + "=" + val;
+ if (putenv(strdup(buf.c_str())) == -1)
+ throw tools::system_error(IMPL_NAME "::set",
+ "Cannot set environment variable '" + name +
+ "' to '" + val + "'",
+ errno);
+#else
+# error "Don't know how to set an environment variable."
+#endif
+}
+
+void
+impl::unset(const std::string& name)
+{
+#if defined(HAVE_UNSETENV)
+ unsetenv(name.c_str());
+#elif defined(HAVE_PUTENV)
+ const std::string buf = name + "=";
+
+ if (putenv(strdup(buf.c_str())) == -1)
+ throw tools::system_error(IMPL_NAME "::unset",
+ "Cannot unset environment variable '" +
+ name + "'", errno);
+#else
+# error "Don't know how to unset an environment variable."
+#endif
+}
diff --git a/unit/atf-src/tools/env.hpp b/unit/atf-src/tools/env.hpp
new file mode 100644
index 0000000..f87a58f
--- /dev/null
+++ b/unit/atf-src/tools/env.hpp
@@ -0,0 +1,84 @@
+//
+// Automated Testing Framework (atf)
+//
+// Copyright (c) 2007 The NetBSD Foundation, Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
+// CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+// IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
+// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+// IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+
+#if !defined(TOOLS_ENV_HPP)
+#define TOOLS_ENV_HPP
+
+#include <string>
+
+namespace tools {
+namespace env {
+
+// ------------------------------------------------------------------------
+// Free functions.
+// ------------------------------------------------------------------------
+
+//!
+//! \brief Returns the value of an environment variable.
+//!
+//! Returns the value of the specified environment variable. The variable
+//! must be defined.
+//!
+std::string get(const std::string&);
+
+//!
+//! \brief Checks if the environment has a variable.
+//!
+//! Checks if the environment has a given variable.
+//!
+bool has(const std::string&);
+
+//!
+//! \brief Sets an environment variable to a given value.
+//!
+//! Sets the specified environment variable to the given value. Note that
+//! variables set to the empty string are different to undefined ones.
+//!
+//! Be aware that this alters the program's global status, which in general
+//! is a bad thing to do due to the side-effects it may have. There are
+//! some legitimate usages for this function, though.
+//!
+void set(const std::string&, const std::string&);
+
+//!
+//! \brief Unsets an environment variable.
+//!
+//! Unsets the specified environment variable Note that undefined
+//! variables are different to those defined but set to an empty value.
+//!
+//! Be aware that this alters the program's global status, which in general
+//! is a bad thing to do due to the side-effects it may have. There are
+//! some legitimate usages for this function, though.
+//!
+void unset(const std::string&);
+
+} // namespace env
+} // namespace tools
+
+#endif // !defined(TOOLS_ENV_HPP)
diff --git a/unit/atf-src/tools/env_test.cpp b/unit/atf-src/tools/env_test.cpp
new file mode 100644
index 0000000..441a16f
--- /dev/null
+++ b/unit/atf-src/tools/env_test.cpp
@@ -0,0 +1,91 @@
+//
+// Automated Testing Framework (atf)
+//
+// Copyright (c) 2007 The NetBSD Foundation, Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
+// CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+// IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
+// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+// IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+
+#include <atf-c++.hpp>
+
+#include "env.hpp"
+
+// ------------------------------------------------------------------------
+// Test cases for the free functions.
+// ------------------------------------------------------------------------
+
+ATF_TEST_CASE(has_get);
+ATF_TEST_CASE_HEAD(has_get)
+{
+ set_md_var("descr", "Tests the has and get functions");
+}
+ATF_TEST_CASE_BODY(has_get)
+{
+ ATF_REQUIRE(tools::env::has("PATH"));
+ ATF_REQUIRE(!tools::env::get("PATH").empty());
+
+ ATF_REQUIRE(!tools::env::has("_UNDEFINED_VARIABLE_"));
+}
+
+ATF_TEST_CASE(set);
+ATF_TEST_CASE_HEAD(set)
+{
+ set_md_var("descr", "Tests the set function");
+}
+ATF_TEST_CASE_BODY(set)
+{
+ ATF_REQUIRE(tools::env::has("PATH"));
+ const std::string& oldval = tools::env::get("PATH");
+ tools::env::set("PATH", "foo-bar");
+ ATF_REQUIRE(tools::env::get("PATH") != oldval);
+ ATF_REQUIRE_EQ(tools::env::get("PATH"), "foo-bar");
+
+ ATF_REQUIRE(!tools::env::has("_UNDEFINED_VARIABLE_"));
+ tools::env::set("_UNDEFINED_VARIABLE_", "foo2-bar2");
+ ATF_REQUIRE_EQ(tools::env::get("_UNDEFINED_VARIABLE_"), "foo2-bar2");
+}
+
+ATF_TEST_CASE(unset);
+ATF_TEST_CASE_HEAD(unset)
+{
+ set_md_var("descr", "Tests the unset function");
+}
+ATF_TEST_CASE_BODY(unset)
+{
+ ATF_REQUIRE(tools::env::has("PATH"));
+ tools::env::unset("PATH");
+ ATF_REQUIRE(!tools::env::has("PATH"));
+}
+
+// ------------------------------------------------------------------------
+// Main.
+// ------------------------------------------------------------------------
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ // Add the test cases for the free functions.
+ ATF_ADD_TEST_CASE(tcs, has_get);
+ ATF_ADD_TEST_CASE(tcs, set);
+ ATF_ADD_TEST_CASE(tcs, unset);
+}
diff --git a/unit/atf-src/tools/exceptions.cpp b/unit/atf-src/tools/exceptions.cpp
new file mode 100644
index 0000000..8587121
--- /dev/null
+++ b/unit/atf-src/tools/exceptions.cpp
@@ -0,0 +1,74 @@
+//
+// Automated Testing Framework (atf)
+//
+// Copyright (c) 2007 The NetBSD Foundation, Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
+// CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+// IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
+// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+// IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+
+#include <cstring>
+
+#include "exceptions.hpp"
+
+// ------------------------------------------------------------------------
+// The "system_error" type.
+// ------------------------------------------------------------------------
+
+tools::system_error::system_error(const std::string& who,
+ const std::string& message,
+ int sys_err) :
+ std::runtime_error(who + ": " + message),
+ m_sys_err(sys_err)
+{
+}
+
+tools::system_error::~system_error(void)
+ throw()
+{
+}
+
+int
+tools::system_error::code(void)
+ const
+ throw()
+{
+ return m_sys_err;
+}
+
+const char*
+tools::system_error::what(void)
+ const
+ throw()
+{
+ try {
+ if (m_message.length() == 0) {
+ m_message = std::string(std::runtime_error::what()) + ": ";
+ m_message += ::strerror(m_sys_err);
+ }
+
+ return m_message.c_str();
+ } catch (...) {
+ return "Unable to format system_error message";
+ }
+}
diff --git a/unit/atf-src/tools/exceptions.hpp b/unit/atf-src/tools/exceptions.hpp
new file mode 100644
index 0000000..4346b7b
--- /dev/null
+++ b/unit/atf-src/tools/exceptions.hpp
@@ -0,0 +1,93 @@
+//
+// Automated Testing Framework (atf)
+//
+// Copyright (c) 2007 The NetBSD Foundation, Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
+// CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+// IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
+// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+// IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+
+#if !defined(TOOLS_EXCEPTIONS_HPP)
+#define TOOLS_EXCEPTIONS_HPP
+
+#include <stdexcept>
+#include <string>
+
+namespace tools {
+
+template< class T >
+class not_found_error :
+ public std::runtime_error
+{
+ T m_value;
+
+public:
+ not_found_error(const std::string& message, const T& value) throw();
+
+ virtual ~not_found_error(void) throw();
+
+ const T& get_value(void) const throw();
+};
+
+template< class T >
+inline
+not_found_error< T >::not_found_error(const std::string& message,
+ const T& value)
+ throw() :
+ std::runtime_error(message),
+ m_value(value)
+{
+}
+
+template< class T >
+inline
+not_found_error< T >::~not_found_error(void)
+ throw()
+{
+}
+
+template< class T >
+inline
+const T&
+not_found_error< T >::get_value(void)
+ const
+ throw()
+{
+ return m_value;
+}
+
+class system_error : public std::runtime_error {
+ int m_sys_err;
+ mutable std::string m_message;
+
+public:
+ system_error(const std::string&, const std::string&, int);
+ ~system_error(void) throw();
+
+ int code(void) const throw();
+ const char* what(void) const throw();
+};
+
+} // namespace tools
+
+#endif // !defined(TOOLS_EXCEPTIONS_HPP)
diff --git a/unit/atf-src/tools/expand.cpp b/unit/atf-src/tools/expand.cpp
new file mode 100644
index 0000000..3ae27d0
--- /dev/null
+++ b/unit/atf-src/tools/expand.cpp
@@ -0,0 +1,81 @@
+//
+// Automated Testing Framework (atf)
+//
+// Copyright (c) 2007 The NetBSD Foundation, Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
+// CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+// IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
+// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+// IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+
+#include <stdexcept>
+
+#include "expand.hpp"
+#include "text.hpp"
+
+namespace impl = tools::expand;
+#define IMPL_NAME "tools::expand"
+
+// ------------------------------------------------------------------------
+// Auxiliary functions.
+// ------------------------------------------------------------------------
+
+namespace {
+
+std::string
+glob_to_regex(const std::string& glob)
+{
+ std::string regex;
+ regex.reserve(glob.length() * 2);
+
+ regex += '^';
+ for (std::string::const_iterator iter = glob.begin(); iter != glob.end();
+ iter++) {
+ switch (*iter) {
+ case '*': regex += ".*"; break;
+ case '?': regex += "."; break;
+ default: regex += *iter;
+ }
+ }
+ regex += '$';
+
+ return regex;
+}
+
+} // anonymous namespace
+
+// ------------------------------------------------------------------------
+// Free functions.
+// ------------------------------------------------------------------------
+
+bool
+impl::is_glob(const std::string& glob)
+{
+ // NOTE: Keep this in sync with glob_to_regex!
+ return glob.find_first_of("*?") != std::string::npos;
+}
+
+bool
+impl::matches_glob(const std::string& glob, const std::string& candidate)
+{
+ return tools::text::match(candidate, glob_to_regex(glob));
+}
diff --git a/unit/atf-src/tools/expand.hpp b/unit/atf-src/tools/expand.hpp
new file mode 100644
index 0000000..b813544
--- /dev/null
+++ b/unit/atf-src/tools/expand.hpp
@@ -0,0 +1,82 @@
+//
+// Automated Testing Framework (atf)
+//
+// Copyright (c) 2007 The NetBSD Foundation, Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
+// CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+// IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
+// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+// IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+
+#if !defined(TOOLS_EXPAND_HPP)
+#define TOOLS_EXPAND_HPP
+
+#include <string>
+#include <vector>
+
+namespace tools {
+namespace expand {
+
+// ------------------------------------------------------------------------
+// Free functions.
+// ------------------------------------------------------------------------
+
+//!
+//! \brief Checks if the given string is a glob pattern.
+//!
+//! Returns true if the given string is a glob pattern; i.e. if it contains
+//! any character that will be expanded by expand_glob.
+//!
+bool is_glob(const std::string&);
+
+//!
+//! \brief Checks if a given string matches a glob pattern.
+//!
+//! Given a glob pattern and a string, checks whether the former matches
+//! the latter. Returns a boolean indicating this condition.
+//!
+bool matches_glob(const std::string&, const std::string&);
+
+//!
+//! \brief Expands a glob pattern among multiple candidates.
+//!
+//! Given a glob pattern and a set of candidate strings, checks which of
+//! those strings match the glob pattern and returns them.
+//!
+template< class T >
+std::vector< std::string > expand_glob(const std::string& glob,
+ const T& candidates)
+{
+ std::vector< std::string > exps;
+
+ for (typename T::const_iterator iter = candidates.begin();
+ iter != candidates.end(); iter++)
+ if (matches_glob(glob, *iter))
+ exps.push_back(*iter);
+
+ return exps;
+}
+
+} // namespace expand
+} // namespace tools
+
+#endif // !defined(TOOLS_EXPAND_HPP)
diff --git a/unit/atf-src/tools/expand_test.cpp b/unit/atf-src/tools/expand_test.cpp
new file mode 100644
index 0000000..fb59de8
--- /dev/null
+++ b/unit/atf-src/tools/expand_test.cpp
@@ -0,0 +1,272 @@
+//
+// Automated Testing Framework (atf)
+//
+// Copyright (c) 2007 The NetBSD Foundation, Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
+// CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+// IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
+// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+// IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+
+#include <cstring>
+
+#include <atf-c++.hpp>
+
+#include "expand.hpp"
+
+// XXX Many of the tests here are duplicated in atf-c/t_expand. Should
+// find a way to easily share them, or maybe remove the ones here.
+
+// ------------------------------------------------------------------------
+// Test cases for the free functions.
+// ------------------------------------------------------------------------
+
+ATF_TEST_CASE(is_glob);
+ATF_TEST_CASE_HEAD(is_glob)
+{
+ set_md_var("descr", "Tests the is_glob function.");
+}
+ATF_TEST_CASE_BODY(is_glob)
+{
+ using tools::expand::is_glob;
+
+ ATF_REQUIRE(!is_glob(""));
+ ATF_REQUIRE(!is_glob("a"));
+ ATF_REQUIRE(!is_glob("foo"));
+
+ ATF_REQUIRE( is_glob("*"));
+ ATF_REQUIRE( is_glob("a*"));
+ ATF_REQUIRE( is_glob("*a"));
+ ATF_REQUIRE( is_glob("a*b"));
+
+ ATF_REQUIRE( is_glob("?"));
+ ATF_REQUIRE( is_glob("a?"));
+ ATF_REQUIRE( is_glob("?a"));
+ ATF_REQUIRE( is_glob("a?b"));
+}
+
+ATF_TEST_CASE(matches_glob_plain);
+ATF_TEST_CASE_HEAD(matches_glob_plain)
+{
+ set_md_var("descr", "Tests the matches_glob function by using plain "
+ "text strings as patterns only.");
+}
+ATF_TEST_CASE_BODY(matches_glob_plain)
+{
+ using tools::expand::matches_glob;
+
+ ATF_REQUIRE( matches_glob("", ""));
+ ATF_REQUIRE(!matches_glob("a", ""));
+ ATF_REQUIRE(!matches_glob("", "a"));
+
+ ATF_REQUIRE( matches_glob("ab", "ab"));
+ ATF_REQUIRE(!matches_glob("abc", "ab"));
+ ATF_REQUIRE(!matches_glob("ab", "abc"));
+}
+
+ATF_TEST_CASE(matches_glob_star);
+ATF_TEST_CASE_HEAD(matches_glob_star)
+{
+ set_md_var("descr", "Tests the matches_glob function by using the '*' "
+ "meta-character as part of the pattern.");
+}
+ATF_TEST_CASE_BODY(matches_glob_star)
+{
+ using tools::expand::matches_glob;
+
+ ATF_REQUIRE( matches_glob("*", ""));
+ ATF_REQUIRE( matches_glob("*", "a"));
+ ATF_REQUIRE( matches_glob("*", "ab"));
+
+ ATF_REQUIRE(!matches_glob("a*", ""));
+ ATF_REQUIRE( matches_glob("a*", "a"));
+ ATF_REQUIRE( matches_glob("a*", "aa"));
+ ATF_REQUIRE( matches_glob("a*", "ab"));
+ ATF_REQUIRE( matches_glob("a*", "abc"));
+ ATF_REQUIRE(!matches_glob("a*", "ba"));
+
+ ATF_REQUIRE( matches_glob("*a", "a"));
+ ATF_REQUIRE( matches_glob("*a", "ba"));
+ ATF_REQUIRE(!matches_glob("*a", "bc"));
+ ATF_REQUIRE(!matches_glob("*a", "bac"));
+
+ ATF_REQUIRE( matches_glob("*ab", "ab"));
+ ATF_REQUIRE( matches_glob("*ab", "aab"));
+ ATF_REQUIRE( matches_glob("*ab", "aaab"));
+ ATF_REQUIRE( matches_glob("*ab", "bab"));
+ ATF_REQUIRE(!matches_glob("*ab", "bcb"));
+ ATF_REQUIRE(!matches_glob("*ab", "bacb"));
+
+ ATF_REQUIRE( matches_glob("a*b", "ab"));
+ ATF_REQUIRE( matches_glob("a*b", "acb"));
+ ATF_REQUIRE( matches_glob("a*b", "acdeb"));
+ ATF_REQUIRE(!matches_glob("a*b", "acdebz"));
+ ATF_REQUIRE(!matches_glob("a*b", "zacdeb"));
+}
+
+ATF_TEST_CASE(matches_glob_question);
+ATF_TEST_CASE_HEAD(matches_glob_question)
+{
+ set_md_var("descr", "Tests the matches_glob function by using the '?' "
+ "meta-character as part of the pattern.");
+}
+ATF_TEST_CASE_BODY(matches_glob_question)
+{
+ using tools::expand::matches_glob;
+
+ ATF_REQUIRE(!matches_glob("?", ""));
+ ATF_REQUIRE( matches_glob("?", "a"));
+ ATF_REQUIRE(!matches_glob("?", "ab"));
+
+ ATF_REQUIRE( matches_glob("?", "b"));
+ ATF_REQUIRE( matches_glob("?", "c"));
+
+ ATF_REQUIRE( matches_glob("a?", "ab"));
+ ATF_REQUIRE( matches_glob("a?", "ac"));
+ ATF_REQUIRE(!matches_glob("a?", "ca"));
+
+ ATF_REQUIRE( matches_glob("???", "abc"));
+ ATF_REQUIRE( matches_glob("???", "def"));
+ ATF_REQUIRE(!matches_glob("???", "a"));
+ ATF_REQUIRE(!matches_glob("???", "ab"));
+ ATF_REQUIRE(!matches_glob("???", "abcd"));
+}
+
+ATF_TEST_CASE(expand_glob_base);
+ATF_TEST_CASE_HEAD(expand_glob_base)
+{
+ set_md_var("descr", "Tests the expand_glob function with random "
+ "patterns.");
+}
+ATF_TEST_CASE_BODY(expand_glob_base)
+{
+ using tools::expand::expand_glob;
+
+ std::vector< std::string > candidates;
+ candidates.push_back("foo");
+ candidates.push_back("bar");
+ candidates.push_back("baz");
+ candidates.push_back("foobar");
+ candidates.push_back("foobarbaz");
+ candidates.push_back("foobarbazfoo");
+
+ std::vector< std::string > exps;
+
+ exps = expand_glob("foo", candidates);
+ ATF_REQUIRE_EQ(exps.size(), 1);
+ ATF_REQUIRE(exps[0] == "foo");
+
+ exps = expand_glob("bar", candidates);
+ ATF_REQUIRE_EQ(exps.size(), 1);
+ ATF_REQUIRE(exps[0] == "bar");
+
+ exps = expand_glob("foo*", candidates);
+ ATF_REQUIRE_EQ(exps.size(), 4);
+ ATF_REQUIRE(exps[0] == "foo");
+ ATF_REQUIRE(exps[1] == "foobar");
+ ATF_REQUIRE(exps[2] == "foobarbaz");
+ ATF_REQUIRE(exps[3] == "foobarbazfoo");
+
+ exps = expand_glob("*foo", candidates);
+ ATF_REQUIRE_EQ(exps.size(), 2);
+ ATF_REQUIRE(exps[0] == "foo");
+ ATF_REQUIRE(exps[1] == "foobarbazfoo");
+
+ exps = expand_glob("*foo*", candidates);
+ ATF_REQUIRE_EQ(exps.size(), 4);
+ ATF_REQUIRE(exps[0] == "foo");
+ ATF_REQUIRE(exps[1] == "foobar");
+ ATF_REQUIRE(exps[2] == "foobarbaz");
+ ATF_REQUIRE(exps[3] == "foobarbazfoo");
+
+ exps = expand_glob("ba", candidates);
+ ATF_REQUIRE_EQ(exps.size(), 0);
+
+ exps = expand_glob("ba*", candidates);
+ ATF_REQUIRE_EQ(exps.size(), 2);
+ ATF_REQUIRE(exps[0] == "bar");
+ ATF_REQUIRE(exps[1] == "baz");
+
+ exps = expand_glob("*ba", candidates);
+ ATF_REQUIRE_EQ(exps.size(), 0);
+
+ exps = expand_glob("*ba*", candidates);
+ ATF_REQUIRE_EQ(exps.size(), 5);
+ ATF_REQUIRE(exps[0] == "bar");
+ ATF_REQUIRE(exps[1] == "baz");
+ ATF_REQUIRE(exps[2] == "foobar");
+ ATF_REQUIRE(exps[3] == "foobarbaz");
+ ATF_REQUIRE(exps[4] == "foobarbazfoo");
+}
+
+ATF_TEST_CASE(expand_glob_tps);
+ATF_TEST_CASE_HEAD(expand_glob_tps)
+{
+ set_md_var("descr", "Tests the expand_glob function with patterns that "
+ "match typical test program names. This is just a subcase "
+ "of expand_base, but it is nice to make sure that it really "
+ "works.");
+}
+ATF_TEST_CASE_BODY(expand_glob_tps)
+{
+ using tools::expand::expand_glob;
+
+ std::vector< std::string > candidates;
+ candidates.push_back("Atffile");
+ candidates.push_back("h_foo");
+ candidates.push_back("t_foo");
+ candidates.push_back("t_bar");
+ candidates.push_back("t_baz");
+ candidates.push_back("foo_helper");
+ candidates.push_back("foo_test");
+ candidates.push_back("bar_test");
+ candidates.push_back("baz_test");
+
+ std::vector< std::string > exps;
+
+ exps = expand_glob("t_*", candidates);
+ ATF_REQUIRE_EQ(exps.size(), 3);
+ ATF_REQUIRE(exps[0] == "t_foo");
+ ATF_REQUIRE(exps[1] == "t_bar");
+ ATF_REQUIRE(exps[2] == "t_baz");
+
+ exps = expand_glob("*_test", candidates);
+ ATF_REQUIRE_EQ(exps.size(), 3);
+ ATF_REQUIRE(exps[0] == "foo_test");
+ ATF_REQUIRE(exps[1] == "bar_test");
+ ATF_REQUIRE(exps[2] == "baz_test");
+}
+
+// ------------------------------------------------------------------------
+// Main.
+// ------------------------------------------------------------------------
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ // Add the tests for the free functions.
+ ATF_ADD_TEST_CASE(tcs, is_glob);
+ ATF_ADD_TEST_CASE(tcs, matches_glob_plain);
+ ATF_ADD_TEST_CASE(tcs, matches_glob_star);
+ ATF_ADD_TEST_CASE(tcs, matches_glob_question);
+ ATF_ADD_TEST_CASE(tcs, expand_glob_base);
+ ATF_ADD_TEST_CASE(tcs, expand_glob_tps);
+}
diff --git a/unit/atf-src/tools/expect_helpers.c b/unit/atf-src/tools/expect_helpers.c
new file mode 100644
index 0000000..b38ccf5
--- /dev/null
+++ b/unit/atf-src/tools/expect_helpers.c
@@ -0,0 +1,193 @@
+/*
+ * Automated Testing Framework (atf)
+ *
+ * Copyright (c) 2010 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
+ * CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+ * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+ * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <signal.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <atf-c.h>
+
+ATF_TC_WITHOUT_HEAD(pass_and_pass);
+ATF_TC_BODY(pass_and_pass, tc)
+{
+ atf_tc_expect_pass();
+}
+
+ATF_TC_WITHOUT_HEAD(pass_but_fail_requirement);
+ATF_TC_BODY(pass_but_fail_requirement, tc)
+{
+ atf_tc_expect_pass();
+ atf_tc_fail("Some reason");
+}
+
+ATF_TC_WITHOUT_HEAD(pass_but_fail_check);
+ATF_TC_BODY(pass_but_fail_check, tc)
+{
+ atf_tc_expect_pass();
+ atf_tc_fail_nonfatal("Some reason");
+}
+
+ATF_TC_WITHOUT_HEAD(fail_and_fail_requirement);
+ATF_TC_BODY(fail_and_fail_requirement, tc)
+{
+ atf_tc_expect_fail("Fail %s", "reason");
+ atf_tc_fail("The failure");
+ atf_tc_expect_pass();
+}
+
+ATF_TC_WITHOUT_HEAD(fail_and_fail_check);
+ATF_TC_BODY(fail_and_fail_check, tc)
+{
+ atf_tc_expect_fail("Fail first");
+ atf_tc_fail_nonfatal("abc");
+ atf_tc_expect_pass();
+
+ atf_tc_expect_fail("And fail again");
+ atf_tc_fail_nonfatal("def");
+ atf_tc_expect_pass();
+}
+
+ATF_TC_WITHOUT_HEAD(fail_but_pass);
+ATF_TC_BODY(fail_but_pass, tc)
+{
+ atf_tc_expect_fail("Fail first");
+ atf_tc_fail_nonfatal("abc");
+ atf_tc_expect_pass();
+
+ atf_tc_expect_fail("Will not fail");
+ atf_tc_expect_pass();
+
+ atf_tc_expect_fail("And fail again");
+ atf_tc_fail_nonfatal("def");
+ atf_tc_expect_pass();
+}
+
+ATF_TC_WITHOUT_HEAD(exit_any_and_exit);
+ATF_TC_BODY(exit_any_and_exit, tc)
+{
+ atf_tc_expect_exit(-1, "Call will exit");
+ exit(EXIT_SUCCESS);
+}
+
+ATF_TC_WITHOUT_HEAD(exit_code_and_exit);
+ATF_TC_BODY(exit_code_and_exit, tc)
+{
+ atf_tc_expect_exit(123, "Call will exit");
+ exit(123);
+}
+
+ATF_TC_WITHOUT_HEAD(exit_but_pass);
+ATF_TC_BODY(exit_but_pass, tc)
+{
+ atf_tc_expect_exit(-1, "Call won't exit");
+}
+
+ATF_TC_WITHOUT_HEAD(signal_any_and_signal);
+ATF_TC_BODY(signal_any_and_signal, tc)
+{
+ atf_tc_expect_signal(-1, "Call will signal");
+ kill(getpid(), SIGKILL);
+}
+
+ATF_TC_WITHOUT_HEAD(signal_no_and_signal);
+ATF_TC_BODY(signal_no_and_signal, tc)
+{
+ atf_tc_expect_signal(SIGHUP, "Call will signal");
+ kill(getpid(), SIGHUP);
+}
+
+ATF_TC_WITHOUT_HEAD(signal_but_pass);
+ATF_TC_BODY(signal_but_pass, tc)
+{
+ atf_tc_expect_signal(-1, "Call won't signal");
+}
+
+ATF_TC_WITHOUT_HEAD(death_and_exit);
+ATF_TC_BODY(death_and_exit, tc)
+{
+ atf_tc_expect_death("Exit case");
+ exit(123);
+}
+
+ATF_TC_WITHOUT_HEAD(death_and_signal);
+ATF_TC_BODY(death_and_signal, tc)
+{
+ atf_tc_expect_death("Signal case");
+ kill(getpid(), SIGKILL);
+}
+
+ATF_TC_WITHOUT_HEAD(death_but_pass);
+ATF_TC_BODY(death_but_pass, tc)
+{
+ atf_tc_expect_death("Call won't die");
+}
+
+ATF_TC(timeout_and_hang);
+ATF_TC_HEAD(timeout_and_hang, tc)
+{
+ atf_tc_set_md_var(tc, "timeout", "1");
+}
+ATF_TC_BODY(timeout_and_hang, tc)
+{
+ atf_tc_expect_timeout("Will overrun");
+ sleep(5);
+}
+
+ATF_TC(timeout_but_pass);
+ATF_TC_HEAD(timeout_but_pass, tc)
+{
+ atf_tc_set_md_var(tc, "timeout", "1");
+}
+ATF_TC_BODY(timeout_but_pass, tc)
+{
+ atf_tc_expect_timeout("Will just exit");
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+ ATF_TP_ADD_TC(tp, pass_and_pass);
+ ATF_TP_ADD_TC(tp, pass_but_fail_requirement);
+ ATF_TP_ADD_TC(tp, pass_but_fail_check);
+ ATF_TP_ADD_TC(tp, fail_and_fail_requirement);
+ ATF_TP_ADD_TC(tp, fail_and_fail_check);
+ ATF_TP_ADD_TC(tp, fail_but_pass);
+ ATF_TP_ADD_TC(tp, exit_any_and_exit);
+ ATF_TP_ADD_TC(tp, exit_code_and_exit);
+ ATF_TP_ADD_TC(tp, exit_but_pass);
+ ATF_TP_ADD_TC(tp, signal_any_and_signal);
+ ATF_TP_ADD_TC(tp, signal_no_and_signal);
+ ATF_TP_ADD_TC(tp, signal_but_pass);
+ ATF_TP_ADD_TC(tp, death_and_exit);
+ ATF_TP_ADD_TC(tp, death_and_signal);
+ ATF_TP_ADD_TC(tp, death_but_pass);
+ ATF_TP_ADD_TC(tp, timeout_and_hang);
+ ATF_TP_ADD_TC(tp, timeout_but_pass);
+
+ return atf_no_error();
+}
diff --git a/unit/atf-src/tools/fail_helper.cpp b/unit/atf-src/tools/fail_helper.cpp
new file mode 100644
index 0000000..736b5e8
--- /dev/null
+++ b/unit/atf-src/tools/fail_helper.cpp
@@ -0,0 +1,45 @@
+//
+// Automated Testing Framework (atf)
+//
+// Copyright (c) 2007 The NetBSD Foundation, Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
+// CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+// IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
+// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+// IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+
+#include <atf-c++.hpp>
+
+ATF_TEST_CASE(main);
+ATF_TEST_CASE_HEAD(main)
+{
+ set_md_var("descr", "Helper test case that always fails");
+}
+ATF_TEST_CASE_BODY(main)
+{
+ fail("This always fails");
+}
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ ATF_ADD_TEST_CASE(tcs, main);
+}
diff --git a/unit/atf-src/tools/fs.cpp b/unit/atf-src/tools/fs.cpp
new file mode 100644
index 0000000..deed28c
--- /dev/null
+++ b/unit/atf-src/tools/fs.cpp
@@ -0,0 +1,744 @@
+//
+// Automated Testing Framework (atf)
+//
+// Copyright (c) 2007 The NetBSD Foundation, Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
+// CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+// IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
+// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+// IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+
+#if defined(HAVE_CONFIG_H)
+#include "config.h"
+#endif
+
+extern "C" {
+#include <sys/param.h>
+#include <sys/types.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+
+#include <dirent.h>
+#include <libgen.h>
+#include <unistd.h>
+}
+
+#include <cassert>
+#include <cerrno>
+#include <cstdlib>
+#include <cstring>
+
+#include "auto_array.hpp"
+#include "env.hpp"
+#include "exceptions.hpp"
+#include "fs.hpp"
+#include "process.hpp"
+#include "text.hpp"
+#include "user.hpp"
+
+namespace impl = tools::fs;
+#define IMPL_NAME "tools::fs"
+
+// ------------------------------------------------------------------------
+// Auxiliary functions.
+// ------------------------------------------------------------------------
+
+static void cleanup_aux(const impl::path&, dev_t, bool);
+static void cleanup_aux_dir(const impl::path&, const impl::file_info&,
+ bool);
+static void do_unmount(const impl::path&);
+static bool safe_access(const impl::path&, int, int);
+
+static const int access_f = 1 << 0;
+static const int access_r = 1 << 1;
+static const int access_w = 1 << 2;
+static const int access_x = 1 << 3;
+
+//!
+//! An implementation of access(2) but using the effective user value
+//! instead of the real one. Also avoids false positives for root when
+//! asking for execute permissions, which appear in SunOS.
+//!
+static
+void
+eaccess(const tools::fs::path& p, int mode)
+{
+ assert(mode & access_f || mode & access_r ||
+ mode & access_w || mode & access_x);
+
+ struct stat st;
+ if (lstat(p.c_str(), &st) == -1)
+ throw tools::system_error(IMPL_NAME "::eaccess",
+ "Cannot get information from file " +
+ p.str(), errno);
+
+ /* Early return if we are only checking for existence and the file
+ * exists (stat call returned). */
+ if (mode & access_f)
+ return;
+
+ bool ok = false;
+ if (tools::user::is_root()) {
+ if (!ok && !(mode & access_x)) {
+ /* Allow root to read/write any file. */
+ ok = true;
+ }
+
+ if (!ok && (st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))) {
+ /* Allow root to execute the file if any of its execution bits
+ * are set. */
+ ok = true;
+ }
+ } else {
+ if (!ok && (tools::user::euid() == st.st_uid)) {
+ ok = ((mode & access_r) && (st.st_mode & S_IRUSR)) ||
+ ((mode & access_w) && (st.st_mode & S_IWUSR)) ||
+ ((mode & access_x) && (st.st_mode & S_IXUSR));
+ }
+ if (!ok && tools::user::is_member_of_group(st.st_gid)) {
+ ok = ((mode & access_r) && (st.st_mode & S_IRGRP)) ||
+ ((mode & access_w) && (st.st_mode & S_IWGRP)) ||
+ ((mode & access_x) && (st.st_mode & S_IXGRP));
+ }
+ if (!ok && ((tools::user::euid() != st.st_uid) &&
+ !tools::user::is_member_of_group(st.st_gid))) {
+ ok = ((mode & access_r) && (st.st_mode & S_IROTH)) ||
+ ((mode & access_w) && (st.st_mode & S_IWOTH)) ||
+ ((mode & access_x) && (st.st_mode & S_IXOTH));
+ }
+ }
+
+ if (!ok)
+ throw tools::system_error(IMPL_NAME "::eaccess", "Access check failed",
+ EACCES);
+}
+
+//!
+//! \brief A controlled version of access(2).
+//!
+//! This function reimplements the standard access(2) system call to
+//! safely control its exit status and raise an exception in case of
+//! failure.
+//!
+static
+bool
+safe_access(const impl::path& p, int mode, int experr)
+{
+ try {
+ eaccess(p, mode);
+ return true;
+ } catch (const tools::system_error& e) {
+ if (e.code() == experr)
+ return false;
+ else
+ throw e;
+ }
+}
+
+// The cleanup routines below are tricky: they are executed immediately after
+// a test case's death, and after we have forcibly killed any stale processes.
+// However, even if the processes are dead, this does not mean that the file
+// system we are scanning is stable. In particular, if the test case has
+// mounted file systems through fuse/puffs, the fact that the processes died
+// does not mean that the file system is truly unmounted.
+//
+// The code below attempts to cope with this by catching errors and either
+// ignoring them or retrying the actions on the same file/directory a few times
+// before giving up.
+static const int max_retries = 5;
+static const int retry_delay_in_seconds = 1;
+
+// The erase parameter in this routine is to control nested mount points.
+// We want to descend into a mount point to unmount anything that is
+// mounted under it, but we do not want to delete any files while doing
+// this traversal. In other words, we erase files until we cross the
+// first mount point, and after that point we only scan and unmount.
+static
+void
+cleanup_aux(const impl::path& p, dev_t parent_device, bool erase)
+{
+ try {
+ impl::file_info fi(p);
+
+ if (fi.get_type() == impl::file_info::dir_type)
+ cleanup_aux_dir(p, fi, fi.get_device() == parent_device);
+
+ if (fi.get_device() != parent_device)
+ do_unmount(p);
+
+ if (erase) {
+ if (fi.get_type() == impl::file_info::dir_type)
+ impl::rmdir(p);
+ else
+ impl::remove(p);
+ }
+ } catch (const tools::system_error& e) {
+ if (e.code() != ENOENT && e.code() != ENOTDIR)
+ throw e;
+ }
+}
+
+static
+void
+cleanup_aux_dir(const impl::path& p, const impl::file_info& fi,
+ bool erase)
+{
+ if (erase && ((fi.get_mode() & S_IRWXU) != S_IRWXU)) {
+ int retries = max_retries;
+retry_chmod:
+ if (chmod(p.c_str(), fi.get_mode() | S_IRWXU) == -1) {
+ if (retries > 0) {
+ retries--;
+ ::sleep(retry_delay_in_seconds);
+ goto retry_chmod;
+ } else {
+ throw tools::system_error(IMPL_NAME "::cleanup(" +
+ p.str() + ")", "chmod(2) failed",
+ errno);
+ }
+ }
+ }
+
+ std::set< std::string > subdirs;
+ {
+ bool ok = false;
+ int retries = max_retries;
+ while (!ok) {
+ assert(retries > 0);
+ try {
+ const impl::directory d(p);
+ subdirs = d.names();
+ ok = true;
+ } catch (const tools::system_error& e) {
+ retries--;
+ if (retries == 0)
+ throw e;
+ ::sleep(retry_delay_in_seconds);
+ }
+ }
+ assert(ok);
+ }
+
+ for (std::set< std::string >::const_iterator iter = subdirs.begin();
+ iter != subdirs.end(); iter++) {
+ const std::string& name = *iter;
+ if (name != "." && name != "..")
+ cleanup_aux(p / name, fi.get_device(), erase);
+ }
+}
+
+static
+void
+do_unmount(const impl::path& in_path)
+{
+ // At least, FreeBSD's unmount(2) requires the path to be absolute.
+ // Let's make it absolute in all cases just to be safe that this does
+ // not affect other systems.
+ const impl::path& abs_path = in_path.is_absolute() ?
+ in_path : in_path.to_absolute();
+
+#if defined(HAVE_UNMOUNT)
+ int retries = max_retries;
+retry_unmount:
+ if (unmount(abs_path.c_str(), 0) == -1) {
+ if (errno == EBUSY && retries > 0) {
+ retries--;
+ ::sleep(retry_delay_in_seconds);
+ goto retry_unmount;
+ } else {
+ throw tools::system_error(IMPL_NAME "::cleanup(" + in_path.str() +
+ ")", "unmount(2) failed", errno);
+ }
+ }
+#else
+ // We could use umount(2) instead if it was available... but
+ // trying to do so under, e.g. Linux, is a nightmare because we
+ // also have to update /etc/mtab to match what we did. It is
+ // stools::fser to just leave the system-specific umount(8) tool deal
+ // with it, at least for now.
+
+ const impl::path prog("umount");
+ tools::process::argv_array argv("umount", abs_path.c_str(), NULL);
+
+ tools::process::status s = tools::process::exec(prog, argv,
+ tools::process::stream_inherit(), tools::process::stream_inherit());
+ if (!s.exited() || s.exitstatus() != EXIT_SUCCESS)
+ throw std::runtime_error("Call to unmount failed");
+#endif
+}
+
+static
+std::string
+normalize(const std::string& in)
+{
+ assert(!in.empty());
+
+ std::string out;
+
+ std::string::size_type pos = 0;
+ do {
+ const std::string::size_type next_pos = in.find('/', pos);
+
+ const std::string component = in.substr(pos, next_pos - pos);
+ if (!component.empty()) {
+ if (pos == 0)
+ out += component;
+ else if (component != ".")
+ out += "/" + component;
+ }
+
+ if (next_pos == std::string::npos)
+ pos = next_pos;
+ else
+ pos = next_pos + 1;
+ } while (pos != std::string::npos);
+
+ return out.empty() ? "/" : out;
+}
+
+// ------------------------------------------------------------------------
+// The "path" class.
+// ------------------------------------------------------------------------
+
+impl::path::path(const std::string& s) :
+ m_data(normalize(s))
+{
+}
+
+impl::path::~path(void)
+{
+}
+
+const char*
+impl::path::c_str(void)
+ const
+{
+ return m_data.c_str();
+}
+
+std::string
+impl::path::str(void)
+ const
+{
+ return m_data;
+}
+
+bool
+impl::path::is_absolute(void)
+ const
+{
+ return !m_data.empty() && m_data[0] == '/';
+}
+
+bool
+impl::path::is_root(void)
+ const
+{
+ return m_data == "/";
+}
+
+impl::path
+impl::path::branch_path(void)
+ const
+{
+ const std::string::size_type endpos = m_data.rfind('/');
+ if (endpos == std::string::npos)
+ return path(".");
+ else if (endpos == 0)
+ return path("/");
+ else
+ return path(m_data.substr(0, endpos));
+}
+
+std::string
+impl::path::leaf_name(void)
+ const
+{
+ std::string::size_type begpos = m_data.rfind('/');
+ if (begpos == std::string::npos)
+ begpos = 0;
+ else
+ begpos++;
+
+ return m_data.substr(begpos);
+}
+
+impl::path
+impl::path::to_absolute(void)
+ const
+{
+ assert(!is_absolute());
+ return get_current_dir() / m_data;
+}
+
+bool
+impl::path::operator==(const path& p)
+ const
+{
+ return m_data == p.m_data;
+}
+
+bool
+impl::path::operator!=(const path& p)
+ const
+{
+ return m_data != p.m_data;
+}
+
+impl::path
+impl::path::operator/(const std::string& p)
+ const
+{
+ return path(m_data + "/" + normalize(p));
+}
+
+impl::path
+impl::path::operator/(const path& p)
+ const
+{
+ return path(m_data) / p.m_data;
+}
+
+bool
+impl::path::operator<(const path& p)
+ const
+{
+ return std::strcmp(m_data.c_str(), p.m_data.c_str()) < 0;
+}
+
+// ------------------------------------------------------------------------
+// The "file_info" class.
+// ------------------------------------------------------------------------
+
+const int impl::file_info::blk_type = 1;
+const int impl::file_info::chr_type = 2;
+const int impl::file_info::dir_type = 3;
+const int impl::file_info::fifo_type = 4;
+const int impl::file_info::lnk_type = 5;
+const int impl::file_info::reg_type = 6;
+const int impl::file_info::sock_type = 7;
+const int impl::file_info::wht_type = 8;
+
+impl::file_info::file_info(const path& p)
+{
+ if (lstat(p.c_str(), &m_sb) == -1)
+ throw system_error(IMPL_NAME "::file_info",
+ "Cannot get information of " + p.str() + "; " +
+ "lstat(2) failed", errno);
+
+ int type = m_sb.st_mode & S_IFMT;
+ switch (type) {
+ case S_IFBLK: m_type = blk_type; break;
+ case S_IFCHR: m_type = chr_type; break;
+ case S_IFDIR: m_type = dir_type; break;
+ case S_IFIFO: m_type = fifo_type; break;
+ case S_IFLNK: m_type = lnk_type; break;
+ case S_IFREG: m_type = reg_type; break;
+ case S_IFSOCK: m_type = sock_type; break;
+#if defined(S_IFWHT)
+ case S_IFWHT: m_type = wht_type; break;
+#endif
+ default:
+ throw system_error(IMPL_NAME "::file_info", "Unknown file type "
+ "error", EINVAL);
+ }
+}
+
+impl::file_info::~file_info(void)
+{
+}
+
+dev_t
+impl::file_info::get_device(void)
+ const
+{
+ return m_sb.st_dev;
+}
+
+ino_t
+impl::file_info::get_inode(void)
+ const
+{
+ return m_sb.st_ino;
+}
+
+mode_t
+impl::file_info::get_mode(void)
+ const
+{
+ return m_sb.st_mode & ~S_IFMT;
+}
+
+off_t
+impl::file_info::get_size(void)
+ const
+{
+ return m_sb.st_size;
+}
+
+int
+impl::file_info::get_type(void)
+ const
+{
+ return m_type;
+}
+
+bool
+impl::file_info::is_owner_readable(void)
+ const
+{
+ return m_sb.st_mode & S_IRUSR;
+}
+
+bool
+impl::file_info::is_owner_writable(void)
+ const
+{
+ return m_sb.st_mode & S_IWUSR;
+}
+
+bool
+impl::file_info::is_owner_executable(void)
+ const
+{
+ return m_sb.st_mode & S_IXUSR;
+}
+
+bool
+impl::file_info::is_group_readable(void)
+ const
+{
+ return m_sb.st_mode & S_IRGRP;
+}
+
+bool
+impl::file_info::is_group_writable(void)
+ const
+{
+ return m_sb.st_mode & S_IWGRP;
+}
+
+bool
+impl::file_info::is_group_executable(void)
+ const
+{
+ return m_sb.st_mode & S_IXGRP;
+}
+
+bool
+impl::file_info::is_other_readable(void)
+ const
+{
+ return m_sb.st_mode & S_IROTH;
+}
+
+bool
+impl::file_info::is_other_writable(void)
+ const
+{
+ return m_sb.st_mode & S_IWOTH;
+}
+
+bool
+impl::file_info::is_other_executable(void)
+ const
+{
+ return m_sb.st_mode & S_IXOTH;
+}
+
+// ------------------------------------------------------------------------
+// The "directory" class.
+// ------------------------------------------------------------------------
+
+impl::directory::directory(const path& p)
+{
+ DIR* dp = ::opendir(p.c_str());
+ if (dp == NULL)
+ throw system_error(IMPL_NAME "::directory::directory(" +
+ p.str() + ")", "opendir(3) failed", errno);
+
+ struct dirent* dep;
+ while ((dep = ::readdir(dp)) != NULL) {
+ path entryp = p / dep->d_name;
+ insert(value_type(dep->d_name, file_info(entryp)));
+ }
+
+ if (::closedir(dp) == -1)
+ throw system_error(IMPL_NAME "::directory::directory(" +
+ p.str() + ")", "closedir(3) failed", errno);
+}
+
+std::set< std::string >
+impl::directory::names(void)
+ const
+{
+ std::set< std::string > ns;
+
+ for (const_iterator iter = begin(); iter != end(); iter++)
+ ns.insert((*iter).first);
+
+ return ns;
+}
+
+// ------------------------------------------------------------------------
+// The "temp_dir" class.
+// ------------------------------------------------------------------------
+
+impl::temp_dir::temp_dir(const path& p)
+{
+ tools::auto_array< char > buf(new char[p.str().length() + 1]);
+ std::strcpy(buf.get(), p.c_str());
+ if (::mkdtemp(buf.get()) == NULL)
+ throw tools::system_error(IMPL_NAME "::temp_dir::temp_dir(" +
+ p.str() + ")", "mkdtemp(3) failed",
+ errno);
+
+ m_path.reset(new path(buf.get()));
+}
+
+impl::temp_dir::~temp_dir(void)
+{
+ cleanup(*m_path);
+}
+
+const impl::path&
+impl::temp_dir::get_path(void)
+ const
+{
+ return *m_path;
+}
+
+// ------------------------------------------------------------------------
+// Free functions.
+// ------------------------------------------------------------------------
+
+bool
+impl::exists(const path& p)
+{
+ try {
+ eaccess(p, access_f);
+ return true;
+ } catch (const system_error& e) {
+ if (e.code() == ENOENT)
+ return false;
+ else
+ throw;
+ }
+}
+
+bool
+impl::have_prog_in_path(const std::string& prog)
+{
+ assert(prog.find('/') == std::string::npos);
+
+ // Do not bother to provide a default value for PATH. If it is not
+ // there something is broken in the user's environment.
+ if (!tools::env::has("PATH"))
+ throw std::runtime_error("PATH not defined in the environment");
+ std::vector< std::string > dirs =
+ tools::text::split(tools::env::get("PATH"), ":");
+
+ bool found = false;
+ for (std::vector< std::string >::const_iterator iter = dirs.begin();
+ !found && iter != dirs.end(); iter++) {
+ const path& dir = path(*iter);
+
+ if (is_executable(dir / prog))
+ found = true;
+ }
+ return found;
+}
+
+bool
+impl::is_executable(const path& p)
+{
+ if (!exists(p))
+ return false;
+ return safe_access(p, access_x, EACCES);
+}
+
+void
+impl::remove(const path& p)
+{
+ if (file_info(p).get_type() == file_info::dir_type)
+ throw tools::system_error(IMPL_NAME "::remove(" + p.str() + ")",
+ "Is a directory",
+ EPERM);
+ if (::unlink(p.c_str()) == -1)
+ throw tools::system_error(IMPL_NAME "::remove(" + p.str() + ")",
+ "unlink(" + p.str() + ") failed",
+ errno);
+}
+
+void
+impl::rmdir(const path& p)
+{
+ if (::rmdir(p.c_str())) {
+ if (errno == EEXIST) {
+ /* Some operating systems (e.g. OpenSolaris 200906) return
+ * EEXIST instead of ENOTEMPTY for non-empty directories.
+ * Homogenize the return value so that callers don't need
+ * to bother about differences in operating systems. */
+ errno = ENOTEMPTY;
+ }
+ throw system_error(IMPL_NAME "::rmdir", "Cannot remove directory",
+ errno);
+ }
+}
+
+impl::path
+impl::change_directory(const path& dir)
+{
+ path olddir = get_current_dir();
+
+ if (olddir != dir) {
+ if (::chdir(dir.c_str()) == -1)
+ throw tools::system_error(IMPL_NAME "::chdir(" + dir.str() + ")",
+ "chdir(2) failed", errno);
+ }
+
+ return olddir;
+}
+
+void
+impl::cleanup(const path& p)
+{
+ impl::file_info fi(p);
+ cleanup_aux(p, fi.get_device(), true);
+}
+
+impl::path
+impl::get_current_dir(void)
+{
+ std::auto_ptr< char > cwd;
+#if defined(HAVE_GETCWD_DYN)
+ cwd.reset(getcwd(NULL, 0));
+#else
+ cwd.reset(getcwd(NULL, MAXPATHLEN));
+#endif
+ if (cwd.get() == NULL)
+ throw tools::system_error(IMPL_NAME "::get_current_dir()",
+ "getcwd() failed", errno);
+
+ return path(cwd.get());
+}
diff --git a/unit/atf-src/tools/fs.hpp b/unit/atf-src/tools/fs.hpp
new file mode 100644
index 0000000..f45f8f1
--- /dev/null
+++ b/unit/atf-src/tools/fs.hpp
@@ -0,0 +1,377 @@
+//
+// Automated Testing Framework (atf)
+//
+// Copyright (c) 2007 The NetBSD Foundation, Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
+// CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+// IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
+// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+// IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+
+#if !defined(TOOLS_FS_HPP)
+#define TOOLS_FS_HPP
+
+extern "C" {
+#include <sys/types.h>
+#include <sys/stat.h>
+}
+
+#include <map>
+#include <memory>
+#include <ostream>
+#include <set>
+#include <stdexcept>
+#include <string>
+
+namespace tools {
+namespace fs {
+
+// ------------------------------------------------------------------------
+// The "path" class.
+// ------------------------------------------------------------------------
+
+//!
+//! \brief A class to represent a path to a file.
+//!
+//! The path class represents the route to a file or directory in the
+//! file system. All file manipulation operations use this class to
+//! represent their arguments as it takes care of normalizing user-provided
+//! strings and ensures they are valid.
+//!
+//! It is important to note that the file pointed to by a path need not
+//! exist.
+//!
+class path {
+ //!
+ //! \brief Internal representation of a path.
+ //!
+ std::string m_data;
+
+public:
+ //! \brief Constructs a new path from a user-provided string.
+ //!
+ //! This constructor takes a string, either provided by the program's
+ //! code or by the user and constructs a new path object. The string
+ //! is normalized to not contain multiple delimiters together and to
+ //! remove any trailing one.
+ //!
+ //! The input string cannot be empty.
+ //!
+ explicit path(const std::string&);
+
+ //!
+ //! \brief Destructor for the path class.
+ //!
+ ~path(void);
+
+ //!
+ //! \brief Returns a pointer to a C-style string representing this path.
+ //!
+ const char* c_str(void) const;
+
+ //!
+ //! \brief Returns a string representing this path.
+ //! XXX Really needed?
+ //!
+ std::string str(void) const;
+
+ //!
+ //! \brief Returns the branch path of this path.
+ //!
+ //! Calculates and returns the branch path of this path. In other
+ //! words, it returns what the standard ::dirname function would return.
+ //!
+ path branch_path(void) const;
+
+ //!
+ //! \brief Returns the leaf name of this path.
+ //!
+ //! Calculates and returns the leaf name of this path. In other words,
+ //! it returns what the standard ::basename function would return.
+ //!
+ std::string leaf_name(void) const;
+
+ //!
+ //! \brief Checks whether this path is absolute or not.
+ //!
+ //! Returns a boolean indicating if this is an absolute path or not;
+ //! i.e. if it starts with a slash.
+ //!
+ bool is_absolute(void) const;
+
+ //!
+ //! \brief Checks whether this path points to the root directory or not.
+ //!
+ //! Returns a boolean indicating if this is path points to the root
+ //! directory or not. The checks made by this are extremely simple (so
+ //! the results cannot always be trusted) but they are enough for our
+ //! modest sanity-checking needs. I.e. "/../" could return false.
+ //!
+ bool is_root(void) const;
+
+ //!
+ //! \brief Converts the path to be absolute.
+ //!
+ //! \pre The path was not absolute.
+ //!
+ path to_absolute(void) const;
+
+ //!
+ //! \brief Checks if two paths are equal.
+ //!
+ bool operator==(const path&) const;
+
+ //!
+ //! \brief Checks if two paths are different.
+ //!
+ bool operator!=(const path&) const;
+
+ //!
+ //! \brief Concatenates a path with a string.
+ //!
+ //! Constructs a new path object that is the concatenation of the
+ //! left-hand path with the right-hand string. The string is normalized
+ //! before the concatenation, and a path delimiter is introduced between
+ //! the two components if needed.
+ //!
+ path operator/(const std::string&) const;
+
+ //!
+ //! \brief Concatenates a path with another path.
+ //!
+ //! Constructs a new path object that is the concatenation of the
+ //! left-hand path with the right-hand one. A path delimiter is
+ //! introduced between the two components if needed.
+ //!
+ path operator/(const path&) const;
+
+ //!
+ //! \brief Checks if a path has to be sorted before another one
+ //! lexicographically.
+ //!
+ bool operator<(const path&) const;
+};
+
+// ------------------------------------------------------------------------
+// The "file_info" class.
+// ------------------------------------------------------------------------
+
+class directory;
+
+//!
+//! \brief A class that contains information about a file.
+//!
+//! The file_info class holds information about an specific file that
+//! exists in the file system.
+//!
+class file_info {
+ int m_type;
+ struct stat m_sb;
+
+public:
+ //!
+ //! \brief The file's type.
+ //!
+ static const int blk_type;
+ static const int chr_type;
+ static const int dir_type;
+ static const int fifo_type;
+ static const int lnk_type;
+ static const int reg_type;
+ static const int sock_type;
+ static const int wht_type;
+
+ //!
+ //! \brief Constructs a new file_info based on a given file.
+ //!
+ //! This constructor creates a new file_info object and fills it with
+ //! the data returned by ::stat when run on the given file, which must
+ //! exist.
+ //!
+ explicit file_info(const path&);
+
+ //!
+ //! \brief The destructor.
+ //!
+ ~file_info(void);
+
+ //!
+ //! \brief Returns the device containing the file.
+ //!
+ dev_t get_device(void) const;
+
+ //!
+ //! \brief Returns the file's inode.
+ //!
+ ino_t get_inode(void) const;
+
+ //!
+ //! \brief Returns the file's permissions.
+ //!
+ mode_t get_mode(void) const;
+
+ //!
+ //! \brief Returns the file's size.
+ //!
+ off_t get_size(void) const;
+
+ //!
+ //! \brief Returns the file's type.
+ //!
+ int get_type(void) const;
+
+ //!
+ //! \brief Returns whether the file is readable by its owner or not.
+ //!
+ bool is_owner_readable(void) const;
+
+ //!
+ //! \brief Returns whether the file is writable by its owner or not.
+ //!
+ bool is_owner_writable(void) const;
+
+ //!
+ //! \brief Returns whether the file is executable by its owner or not.
+ //!
+ bool is_owner_executable(void) const;
+
+ //!
+ //! \brief Returns whether the file is readable by the users belonging
+ //! to its group or not.
+ //!
+ bool is_group_readable(void) const;
+
+ //!
+ //! \brief Returns whether the file is writable the users belonging to
+ //! its group or not.
+ //!
+ bool is_group_writable(void) const;
+
+ //!
+ //! \brief Returns whether the file is executable by the users
+ //! belonging to its group or not.
+ //!
+ bool is_group_executable(void) const;
+
+ //!
+ //! \brief Returns whether the file is readable by people different
+ //! than the owner and those belonging to the group or not.
+ //!
+ bool is_other_readable(void) const;
+
+ //!
+ //! \brief Returns whether the file is write by people different
+ //! than the owner and those belonging to the group or not.
+ //!
+ bool is_other_writable(void) const;
+
+ //!
+ //! \brief Returns whether the file is executable by people different
+ //! than the owner and those belonging to the group or not.
+ //!
+ bool is_other_executable(void) const;
+};
+
+// ------------------------------------------------------------------------
+// The "directory" class.
+// ------------------------------------------------------------------------
+
+//!
+//! \brief A class representing a file system directory.
+//!
+//! The directory class represents a group of files in the file system and
+//! corresponds to exactly one directory.
+//!
+class directory : public std::map< std::string, file_info > {
+public:
+ //!
+ //! \brief Constructs a new directory.
+ //!
+ //! Constructs a new directory object representing the given path.
+ //! The directory must exist at creation time as the contents of the
+ //! class are gathered from it.
+ //!
+ directory(const path&);
+
+ //!
+ //! \brief Returns the file names of the files in the directory.
+ //!
+ //! Returns the leaf names of all files contained in the directory.
+ //! I.e. the keys of the directory map.
+ //!
+ std::set< std::string > names(void) const;
+};
+
+// ------------------------------------------------------------------------
+// The "temp_dir" class.
+// ------------------------------------------------------------------------
+
+class temp_dir {
+ std::auto_ptr< tools::fs::path > m_path;
+
+public:
+ temp_dir(const tools::fs::path&);
+ ~temp_dir(void);
+
+ const tools::fs::path& get_path(void) const;
+};
+
+// ------------------------------------------------------------------------
+// Free functions.
+// ------------------------------------------------------------------------
+
+//!
+//! \brief Checks if the given path exists.
+//!
+bool exists(const path&);
+
+//!
+//! \brief Looks for the given program in the PATH.
+//!
+//! Given a program name (without slashes) looks for it in the path and
+//! returns its full path name if found, otherwise an empty path.
+//!
+bool have_prog_in_path(const std::string&);
+
+//!
+//! \brief Checks if the given path exists, is accessible and is executable.
+//!
+bool is_executable(const path&);
+
+//!
+//! \brief Removes a given file.
+//!
+void remove(const path&);
+
+//!
+//! \brief Removes an empty directory.
+//!
+void rmdir(const path&);
+
+tools::fs::path change_directory(const tools::fs::path&);
+void cleanup(const tools::fs::path&);
+tools::fs::path get_current_dir(void);
+
+} // namespace fs
+} // namespace tools
+
+#endif // !defined(TOOLS_FS_HPP)
diff --git a/unit/atf-src/tools/fs_test.cpp b/unit/atf-src/tools/fs_test.cpp
new file mode 100644
index 0000000..5f482a4
--- /dev/null
+++ b/unit/atf-src/tools/fs_test.cpp
@@ -0,0 +1,743 @@
+//
+// Automated Testing Framework (atf)
+//
+// Copyright (c) 2007 The NetBSD Foundation, Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
+// CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+// IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
+// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+// IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+
+extern "C" {
+#include <sys/types.h>
+#include <sys/stat.h>
+}
+
+#include <cerrno>
+#include <cstdio>
+#include <fstream>
+
+#include <atf-c++.hpp>
+
+#include "exceptions.hpp"
+#include "fs.hpp"
+#include "user.hpp"
+
+// ------------------------------------------------------------------------
+// Auxiliary functions.
+// ------------------------------------------------------------------------
+
+static
+void
+create_file(const char *name)
+{
+ std::ofstream os(name);
+ os.close();
+}
+
+static
+void
+create_files(void)
+{
+ ::mkdir("files", 0755);
+ ::mkdir("files/dir", 0755);
+
+ std::ofstream os("files/reg");
+ os.close();
+
+ // TODO: Should create all other file types (blk, chr, fifo, lnk, sock)
+ // and test for them... but the underlying file system may not support
+ // most of these. Specially as we are working on /tmp, which can be
+ // mounted with flags such as "nodev". See how to deal with this
+ // situation.
+}
+
+// ------------------------------------------------------------------------
+// Test cases for the "path" class.
+// ------------------------------------------------------------------------
+
+ATF_TEST_CASE(path_normalize);
+ATF_TEST_CASE_HEAD(path_normalize)
+{
+ set_md_var("descr", "Tests the path's normalization");
+}
+ATF_TEST_CASE_BODY(path_normalize)
+{
+ using tools::fs::path;
+
+ ATF_REQUIRE_EQ(path(".").str(), ".");
+ ATF_REQUIRE_EQ(path("..").str(), "..");
+
+ ATF_REQUIRE_EQ(path("foo").str(), "foo");
+ ATF_REQUIRE_EQ(path("foo/bar").str(), "foo/bar");
+ ATF_REQUIRE_EQ(path("foo/bar/").str(), "foo/bar");
+
+ ATF_REQUIRE_EQ(path("/foo").str(), "/foo");
+ ATF_REQUIRE_EQ(path("/foo/bar").str(), "/foo/bar");
+ ATF_REQUIRE_EQ(path("/foo/bar/").str(), "/foo/bar");
+
+ ATF_REQUIRE_EQ(path("///foo").str(), "/foo");
+ ATF_REQUIRE_EQ(path("///foo///bar").str(), "/foo/bar");
+ ATF_REQUIRE_EQ(path("///foo///bar///").str(), "/foo/bar");
+}
+
+ATF_TEST_CASE(path_is_absolute);
+ATF_TEST_CASE_HEAD(path_is_absolute)
+{
+ set_md_var("descr", "Tests the path::is_absolute function");
+}
+ATF_TEST_CASE_BODY(path_is_absolute)
+{
+ using tools::fs::path;
+
+ ATF_REQUIRE( path("/").is_absolute());
+ ATF_REQUIRE( path("////").is_absolute());
+ ATF_REQUIRE( path("////a").is_absolute());
+ ATF_REQUIRE( path("//a//").is_absolute());
+ ATF_REQUIRE(!path("a////").is_absolute());
+ ATF_REQUIRE(!path("../foo").is_absolute());
+}
+
+ATF_TEST_CASE(path_is_root);
+ATF_TEST_CASE_HEAD(path_is_root)
+{
+ set_md_var("descr", "Tests the path::is_root function");
+}
+ATF_TEST_CASE_BODY(path_is_root)
+{
+ using tools::fs::path;
+
+ ATF_REQUIRE( path("/").is_root());
+ ATF_REQUIRE( path("////").is_root());
+ ATF_REQUIRE(!path("////a").is_root());
+ ATF_REQUIRE(!path("//a//").is_root());
+ ATF_REQUIRE(!path("a////").is_root());
+ ATF_REQUIRE(!path("../foo").is_root());
+}
+
+ATF_TEST_CASE(path_branch_path);
+ATF_TEST_CASE_HEAD(path_branch_path)
+{
+ set_md_var("descr", "Tests the path::branch_path function");
+}
+ATF_TEST_CASE_BODY(path_branch_path)
+{
+ using tools::fs::path;
+
+ ATF_REQUIRE_EQ(path(".").branch_path().str(), ".");
+ ATF_REQUIRE_EQ(path("foo").branch_path().str(), ".");
+ ATF_REQUIRE_EQ(path("foo/bar").branch_path().str(), "foo");
+ ATF_REQUIRE_EQ(path("/foo").branch_path().str(), "/");
+ ATF_REQUIRE_EQ(path("/foo/bar").branch_path().str(), "/foo");
+}
+
+ATF_TEST_CASE(path_leaf_name);
+ATF_TEST_CASE_HEAD(path_leaf_name)
+{
+ set_md_var("descr", "Tests the path::leaf_name function");
+}
+ATF_TEST_CASE_BODY(path_leaf_name)
+{
+ using tools::fs::path;
+
+ ATF_REQUIRE_EQ(path(".").leaf_name(), ".");
+ ATF_REQUIRE_EQ(path("foo").leaf_name(), "foo");
+ ATF_REQUIRE_EQ(path("foo/bar").leaf_name(), "bar");
+ ATF_REQUIRE_EQ(path("/foo").leaf_name(), "foo");
+ ATF_REQUIRE_EQ(path("/foo/bar").leaf_name(), "bar");
+}
+
+ATF_TEST_CASE(path_compare_equal);
+ATF_TEST_CASE_HEAD(path_compare_equal)
+{
+ set_md_var("descr", "Tests the comparison for equality between paths");
+}
+ATF_TEST_CASE_BODY(path_compare_equal)
+{
+ using tools::fs::path;
+
+ ATF_REQUIRE(path("/") == path("///"));
+ ATF_REQUIRE(path("/a") == path("///a"));
+ ATF_REQUIRE(path("/a") == path("///a///"));
+
+ ATF_REQUIRE(path("a/b/c") == path("a//b//c"));
+ ATF_REQUIRE(path("a/b/c") == path("a//b//c///"));
+}
+
+ATF_TEST_CASE(path_compare_different);
+ATF_TEST_CASE_HEAD(path_compare_different)
+{
+ set_md_var("descr", "Tests the comparison for difference between paths");
+}
+ATF_TEST_CASE_BODY(path_compare_different)
+{
+ using tools::fs::path;
+
+ ATF_REQUIRE(path("/") != path("//a/"));
+ ATF_REQUIRE(path("/a") != path("a///"));
+
+ ATF_REQUIRE(path("a/b/c") != path("a/b"));
+ ATF_REQUIRE(path("a/b/c") != path("a//b"));
+ ATF_REQUIRE(path("a/b/c") != path("/a/b/c"));
+ ATF_REQUIRE(path("a/b/c") != path("/a//b//c"));
+}
+
+ATF_TEST_CASE(path_concat);
+ATF_TEST_CASE_HEAD(path_concat)
+{
+ set_md_var("descr", "Tests the concatenation of multiple paths");
+}
+ATF_TEST_CASE_BODY(path_concat)
+{
+ using tools::fs::path;
+
+ ATF_REQUIRE_EQ((path("foo") / "bar").str(), "foo/bar");
+ ATF_REQUIRE_EQ((path("foo/") / "/bar").str(), "foo/bar");
+ ATF_REQUIRE_EQ((path("foo/") / "/bar/baz").str(), "foo/bar/baz");
+ ATF_REQUIRE_EQ((path("foo/") / "///bar///baz").str(), "foo/bar/baz");
+}
+
+ATF_TEST_CASE(path_to_absolute);
+ATF_TEST_CASE_HEAD(path_to_absolute)
+{
+ set_md_var("descr", "Tests the conversion of a relative path to an "
+ "absolute one");
+}
+ATF_TEST_CASE_BODY(path_to_absolute)
+{
+ using tools::fs::file_info;
+ using tools::fs::path;
+
+ create_files();
+
+ {
+ const path p(".");
+ path pa = p.to_absolute();
+ ATF_REQUIRE(pa.is_absolute());
+
+ file_info fi(p);
+ file_info fia(pa);
+ ATF_REQUIRE_EQ(fi.get_device(), fia.get_device());
+ ATF_REQUIRE_EQ(fi.get_inode(), fia.get_inode());
+ }
+
+ {
+ const path p("files/reg");
+ path pa = p.to_absolute();
+ ATF_REQUIRE(pa.is_absolute());
+
+ file_info fi(p);
+ file_info fia(pa);
+ ATF_REQUIRE_EQ(fi.get_device(), fia.get_device());
+ ATF_REQUIRE_EQ(fi.get_inode(), fia.get_inode());
+ }
+}
+
+ATF_TEST_CASE(path_op_less);
+ATF_TEST_CASE_HEAD(path_op_less)
+{
+ set_md_var("descr", "Tests that the path's less-than operator works");
+}
+ATF_TEST_CASE_BODY(path_op_less)
+{
+ using tools::fs::path;
+
+ create_files();
+
+ ATF_REQUIRE(!(path("aaa") < path("aaa")));
+
+ ATF_REQUIRE( path("aab") < path("abc"));
+ ATF_REQUIRE(!(path("abc") < path("aab")));
+}
+
+// ------------------------------------------------------------------------
+// Test cases for the "directory" class.
+// ------------------------------------------------------------------------
+
+ATF_TEST_CASE(directory_read);
+ATF_TEST_CASE_HEAD(directory_read)
+{
+ set_md_var("descr", "Tests the directory class creation, which reads "
+ "the contents of a directory");
+}
+ATF_TEST_CASE_BODY(directory_read)
+{
+ using tools::fs::directory;
+ using tools::fs::path;
+
+ create_files();
+
+ directory d(path("files"));
+ ATF_REQUIRE_EQ(d.size(), 4);
+ ATF_REQUIRE(d.find(".") != d.end());
+ ATF_REQUIRE(d.find("..") != d.end());
+ ATF_REQUIRE(d.find("dir") != d.end());
+ ATF_REQUIRE(d.find("reg") != d.end());
+}
+
+ATF_TEST_CASE(directory_file_info);
+ATF_TEST_CASE_HEAD(directory_file_info)
+{
+ set_md_var("descr", "Tests that the file_info objects attached to the "
+ "directory are valid");
+}
+ATF_TEST_CASE_BODY(directory_file_info)
+{
+ using tools::fs::directory;
+ using tools::fs::file_info;
+ using tools::fs::path;
+
+ create_files();
+
+ directory d(path("files"));
+
+ {
+ directory::const_iterator iter = d.find("dir");
+ ATF_REQUIRE(iter != d.end());
+ const file_info& fi = (*iter).second;
+ ATF_REQUIRE(fi.get_type() == file_info::dir_type);
+ }
+
+ {
+ directory::const_iterator iter = d.find("reg");
+ ATF_REQUIRE(iter != d.end());
+ const file_info& fi = (*iter).second;
+ ATF_REQUIRE(fi.get_type() == file_info::reg_type);
+ }
+}
+
+ATF_TEST_CASE(directory_names);
+ATF_TEST_CASE_HEAD(directory_names)
+{
+ set_md_var("descr", "Tests the directory's names method");
+}
+ATF_TEST_CASE_BODY(directory_names)
+{
+ using tools::fs::directory;
+ using tools::fs::path;
+
+ create_files();
+
+ directory d(path("files"));
+ std::set< std::string > ns = d.names();
+ ATF_REQUIRE_EQ(ns.size(), 4);
+ ATF_REQUIRE(ns.find(".") != ns.end());
+ ATF_REQUIRE(ns.find("..") != ns.end());
+ ATF_REQUIRE(ns.find("dir") != ns.end());
+ ATF_REQUIRE(ns.find("reg") != ns.end());
+}
+
+// ------------------------------------------------------------------------
+// Test cases for the "file_info" class.
+// ------------------------------------------------------------------------
+
+ATF_TEST_CASE(file_info_stat);
+ATF_TEST_CASE_HEAD(file_info_stat)
+{
+ set_md_var("descr", "Tests the file_info creation and its basic contents");
+}
+ATF_TEST_CASE_BODY(file_info_stat)
+{
+ using tools::fs::file_info;
+ using tools::fs::path;
+
+ create_files();
+
+ {
+ path p("files/dir");
+ file_info fi(p);
+ ATF_REQUIRE(fi.get_type() == file_info::dir_type);
+ }
+
+ {
+ path p("files/reg");
+ file_info fi(p);
+ ATF_REQUIRE(fi.get_type() == file_info::reg_type);
+ }
+}
+
+ATF_TEST_CASE(file_info_perms);
+ATF_TEST_CASE_HEAD(file_info_perms)
+{
+ set_md_var("descr", "Tests the file_info methods to get the file's "
+ "permissions");
+}
+ATF_TEST_CASE_BODY(file_info_perms)
+{
+ using tools::fs::file_info;
+ using tools::fs::path;
+
+ path p("file");
+
+ std::ofstream os(p.c_str());
+ os.close();
+
+#define perms(ur, uw, ux, gr, gw, gx, othr, othw, othx) \
+ { \
+ file_info fi(p); \
+ ATF_REQUIRE(fi.is_owner_readable() == ur); \
+ ATF_REQUIRE(fi.is_owner_writable() == uw); \
+ ATF_REQUIRE(fi.is_owner_executable() == ux); \
+ ATF_REQUIRE(fi.is_group_readable() == gr); \
+ ATF_REQUIRE(fi.is_group_writable() == gw); \
+ ATF_REQUIRE(fi.is_group_executable() == gx); \
+ ATF_REQUIRE(fi.is_other_readable() == othr); \
+ ATF_REQUIRE(fi.is_other_writable() == othw); \
+ ATF_REQUIRE(fi.is_other_executable() == othx); \
+ }
+
+ ::chmod(p.c_str(), 0000);
+ perms(false, false, false, false, false, false, false, false, false);
+
+ ::chmod(p.c_str(), 0001);
+ perms(false, false, false, false, false, false, false, false, true);
+
+ ::chmod(p.c_str(), 0010);
+ perms(false, false, false, false, false, true, false, false, false);
+
+ ::chmod(p.c_str(), 0100);
+ perms(false, false, true, false, false, false, false, false, false);
+
+ ::chmod(p.c_str(), 0002);
+ perms(false, false, false, false, false, false, false, true, false);
+
+ ::chmod(p.c_str(), 0020);
+ perms(false, false, false, false, true, false, false, false, false);
+
+ ::chmod(p.c_str(), 0200);
+ perms(false, true, false, false, false, false, false, false, false);
+
+ ::chmod(p.c_str(), 0004);
+ perms(false, false, false, false, false, false, true, false, false);
+
+ ::chmod(p.c_str(), 0040);
+ perms(false, false, false, true, false, false, false, false, false);
+
+ ::chmod(p.c_str(), 0400);
+ perms(true, false, false, false, false, false, false, false, false);
+
+ ::chmod(p.c_str(), 0644);
+ perms(true, true, false, true, false, false, true, false, false);
+
+ ::chmod(p.c_str(), 0755);
+ perms(true, true, true, true, false, true, true, false, true);
+
+ ::chmod(p.c_str(), 0777);
+ perms(true, true, true, true, true, true, true, true, true);
+
+#undef perms
+}
+
+// ------------------------------------------------------------------------
+// Test cases for the "temp_dir" class.
+// ------------------------------------------------------------------------
+
+ATF_TEST_CASE(temp_dir_raii);
+ATF_TEST_CASE_HEAD(temp_dir_raii)
+{
+ set_md_var("descr", "Tests the RAII behavior of the temp_dir class");
+}
+ATF_TEST_CASE_BODY(temp_dir_raii)
+{
+ tools::fs::path t1("non-existent");
+ tools::fs::path t2("non-existent");
+
+ {
+ tools::fs::path tmpl("testdir.XXXXXX");
+ tools::fs::temp_dir td1(tmpl);
+ tools::fs::temp_dir td2(tmpl);
+ t1 = td1.get_path();
+ t2 = td2.get_path();
+ ATF_REQUIRE(t1.str().find("XXXXXX") == std::string::npos);
+ ATF_REQUIRE(t2.str().find("XXXXXX") == std::string::npos);
+ ATF_REQUIRE(t1 != t2);
+ ATF_REQUIRE(!tools::fs::exists(tmpl));
+ ATF_REQUIRE( tools::fs::exists(t1));
+ ATF_REQUIRE( tools::fs::exists(t2));
+
+ tools::fs::file_info fi1(t1);
+ ATF_REQUIRE( fi1.is_owner_readable());
+ ATF_REQUIRE( fi1.is_owner_writable());
+ ATF_REQUIRE( fi1.is_owner_executable());
+ ATF_REQUIRE(!fi1.is_group_readable());
+ ATF_REQUIRE(!fi1.is_group_writable());
+ ATF_REQUIRE(!fi1.is_group_executable());
+ ATF_REQUIRE(!fi1.is_other_readable());
+ ATF_REQUIRE(!fi1.is_other_writable());
+ ATF_REQUIRE(!fi1.is_other_executable());
+
+ tools::fs::file_info fi2(t2);
+ ATF_REQUIRE( fi2.is_owner_readable());
+ ATF_REQUIRE( fi2.is_owner_writable());
+ ATF_REQUIRE( fi2.is_owner_executable());
+ ATF_REQUIRE(!fi2.is_group_readable());
+ ATF_REQUIRE(!fi2.is_group_writable());
+ ATF_REQUIRE(!fi2.is_group_executable());
+ ATF_REQUIRE(!fi2.is_other_readable());
+ ATF_REQUIRE(!fi2.is_other_writable());
+ ATF_REQUIRE(!fi2.is_other_executable());
+ }
+
+ ATF_REQUIRE(t1.str() != "non-existent");
+ ATF_REQUIRE(!tools::fs::exists(t1));
+ ATF_REQUIRE(t2.str() != "non-existent");
+ ATF_REQUIRE(!tools::fs::exists(t2));
+}
+
+
+// ------------------------------------------------------------------------
+// Test cases for the free functions.
+// ------------------------------------------------------------------------
+
+ATF_TEST_CASE(exists);
+ATF_TEST_CASE_HEAD(exists)
+{
+ set_md_var("descr", "Tests the exists function");
+}
+ATF_TEST_CASE_BODY(exists)
+{
+ using tools::fs::exists;
+ using tools::fs::path;
+
+ create_files();
+
+ ATF_REQUIRE( exists(path("files")));
+ ATF_REQUIRE(!exists(path("file")));
+ ATF_REQUIRE(!exists(path("files2")));
+
+ ATF_REQUIRE( exists(path("files/.")));
+ ATF_REQUIRE( exists(path("files/..")));
+ ATF_REQUIRE( exists(path("files/dir")));
+ ATF_REQUIRE( exists(path("files/reg")));
+ ATF_REQUIRE(!exists(path("files/foo")));
+}
+
+ATF_TEST_CASE(is_executable);
+ATF_TEST_CASE_HEAD(is_executable)
+{
+ set_md_var("descr", "Tests the is_executable function");
+}
+ATF_TEST_CASE_BODY(is_executable)
+{
+ using tools::fs::is_executable;
+ using tools::fs::path;
+
+ create_files();
+
+ ATF_REQUIRE( is_executable(path("files")));
+ ATF_REQUIRE( is_executable(path("files/.")));
+ ATF_REQUIRE( is_executable(path("files/..")));
+ ATF_REQUIRE( is_executable(path("files/dir")));
+
+ ATF_REQUIRE(!is_executable(path("non-existent")));
+
+ ATF_REQUIRE(!is_executable(path("files/reg")));
+ ATF_REQUIRE(::chmod("files/reg", 0755) != -1);
+ ATF_REQUIRE( is_executable(path("files/reg")));
+}
+
+ATF_TEST_CASE(remove);
+ATF_TEST_CASE_HEAD(remove)
+{
+ set_md_var("descr", "Tests the remove function");
+}
+ATF_TEST_CASE_BODY(remove)
+{
+ using tools::fs::exists;
+ using tools::fs::path;
+ using tools::fs::remove;
+
+ create_files();
+
+ ATF_REQUIRE( exists(path("files/reg")));
+ remove(path("files/reg"));
+ ATF_REQUIRE(!exists(path("files/reg")));
+
+ ATF_REQUIRE( exists(path("files/dir")));
+ ATF_REQUIRE_THROW(tools::system_error, remove(path("files/dir")));
+ ATF_REQUIRE( exists(path("files/dir")));
+}
+
+ATF_TEST_CASE(cleanup);
+ATF_TEST_CASE_HEAD(cleanup)
+{
+ set_md_var("descr", "Tests the cleanup function");
+}
+ATF_TEST_CASE_BODY(cleanup)
+{
+ using tools::fs::cleanup;
+
+ ::mkdir("root", 0755);
+ ::mkdir("root/dir", 0755);
+ ::mkdir("root/dir/1", 0100);
+ ::mkdir("root/dir/2", 0644);
+ create_file("root/reg");
+
+ tools::fs::path p("root");
+ ATF_REQUIRE(tools::fs::exists(p));
+ ATF_REQUIRE(tools::fs::exists(p / "dir"));
+ ATF_REQUIRE(tools::fs::exists(p / "dir/1"));
+ ATF_REQUIRE(tools::fs::exists(p / "dir/2"));
+ ATF_REQUIRE(tools::fs::exists(p / "reg"));
+ cleanup(p);
+ ATF_REQUIRE(!tools::fs::exists(p));
+}
+
+ATF_TEST_CASE(cleanup_eacces_on_root);
+ATF_TEST_CASE_HEAD(cleanup_eacces_on_root)
+{
+ set_md_var("descr", "Tests the cleanup function");
+}
+ATF_TEST_CASE_BODY(cleanup_eacces_on_root)
+{
+ using tools::fs::cleanup;
+
+ ::mkdir("aux", 0755);
+ ::mkdir("aux/root", 0755);
+ ATF_REQUIRE(::chmod("aux", 0555) != -1);
+
+ try {
+ cleanup(tools::fs::path("aux/root"));
+ ATF_REQUIRE(tools::user::is_root());
+ } catch (const tools::system_error& e) {
+ ATF_REQUIRE(!tools::user::is_root());
+ ATF_REQUIRE_EQ(EACCES, e.code());
+ }
+}
+
+ATF_TEST_CASE(cleanup_eacces_on_subdir);
+ATF_TEST_CASE_HEAD(cleanup_eacces_on_subdir)
+{
+ set_md_var("descr", "Tests the cleanup function");
+}
+ATF_TEST_CASE_BODY(cleanup_eacces_on_subdir)
+{
+ using tools::fs::cleanup;
+
+ ::mkdir("root", 0755);
+ ::mkdir("root/1", 0755);
+ ::mkdir("root/1/2", 0755);
+ ::mkdir("root/1/2/3", 0755);
+ ATF_REQUIRE(::chmod("root/1/2", 0555) != -1);
+ ATF_REQUIRE(::chmod("root/1", 0555) != -1);
+
+ const tools::fs::path p("root");
+ cleanup(p);
+ ATF_REQUIRE(!tools::fs::exists(p));
+}
+
+ATF_TEST_CASE(change_directory);
+ATF_TEST_CASE_HEAD(change_directory)
+{
+ set_md_var("descr", "Tests the change_directory function");
+}
+ATF_TEST_CASE_BODY(change_directory)
+{
+ using tools::fs::change_directory;
+ using tools::fs::get_current_dir;
+
+ ::mkdir("files", 0755);
+ ::mkdir("files/dir", 0755);
+ create_file("files/reg");
+
+ const tools::fs::path old = get_current_dir();
+
+ ATF_REQUIRE_THROW(tools::system_error,
+ change_directory(tools::fs::path("files/reg")));
+ ATF_REQUIRE(get_current_dir() == old);
+
+ tools::fs::path old2 = change_directory(tools::fs::path("files"));
+ ATF_REQUIRE(old2 == old);
+ tools::fs::path old3 = change_directory(tools::fs::path("dir"));
+ ATF_REQUIRE(old3 == old2 / "files");
+ tools::fs::path old4 = change_directory(tools::fs::path("../.."));
+ ATF_REQUIRE(old4 == old3 / "dir");
+ ATF_REQUIRE(get_current_dir() == old);
+}
+
+ATF_TEST_CASE(get_current_dir);
+ATF_TEST_CASE_HEAD(get_current_dir)
+{
+ set_md_var("descr", "Tests the get_current_dir function");
+}
+ATF_TEST_CASE_BODY(get_current_dir)
+{
+ using tools::fs::change_directory;
+ using tools::fs::get_current_dir;
+
+ ::mkdir("files", 0755);
+ ::mkdir("files/dir", 0755);
+ create_file("files/reg");
+
+ tools::fs::path curdir = get_current_dir();
+ change_directory(tools::fs::path("."));
+ ATF_REQUIRE(get_current_dir() == curdir);
+ change_directory(tools::fs::path("files"));
+ ATF_REQUIRE(get_current_dir() == curdir / "files");
+ change_directory(tools::fs::path("dir"));
+ ATF_REQUIRE(get_current_dir() == curdir / "files/dir");
+ change_directory(tools::fs::path(".."));
+ ATF_REQUIRE(get_current_dir() == curdir / "files");
+ change_directory(tools::fs::path(".."));
+ ATF_REQUIRE(get_current_dir() == curdir);
+}
+
+// ------------------------------------------------------------------------
+// Main.
+// ------------------------------------------------------------------------
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ // Add the tests for the "path" class.
+ ATF_ADD_TEST_CASE(tcs, path_normalize);
+ ATF_ADD_TEST_CASE(tcs, path_is_absolute);
+ ATF_ADD_TEST_CASE(tcs, path_is_root);
+ ATF_ADD_TEST_CASE(tcs, path_branch_path);
+ ATF_ADD_TEST_CASE(tcs, path_leaf_name);
+ ATF_ADD_TEST_CASE(tcs, path_compare_equal);
+ ATF_ADD_TEST_CASE(tcs, path_compare_different);
+ ATF_ADD_TEST_CASE(tcs, path_concat);
+ ATF_ADD_TEST_CASE(tcs, path_to_absolute);
+ ATF_ADD_TEST_CASE(tcs, path_op_less);
+
+ // Add the tests for the "file_info" class.
+ ATF_ADD_TEST_CASE(tcs, file_info_stat);
+ ATF_ADD_TEST_CASE(tcs, file_info_perms);
+
+ // Add the tests for the "directory" class.
+ ATF_ADD_TEST_CASE(tcs, directory_read);
+ ATF_ADD_TEST_CASE(tcs, directory_names);
+ ATF_ADD_TEST_CASE(tcs, directory_file_info);
+
+ // Add the tests for the "temp_dir" class.
+ ATF_ADD_TEST_CASE(tcs, temp_dir_raii);
+
+ // Add the tests for the free functions.
+ ATF_ADD_TEST_CASE(tcs, exists);
+ ATF_ADD_TEST_CASE(tcs, is_executable);
+ ATF_ADD_TEST_CASE(tcs, remove);
+ ATF_ADD_TEST_CASE(tcs, cleanup);
+ ATF_ADD_TEST_CASE(tcs, cleanup_eacces_on_root);
+ ATF_ADD_TEST_CASE(tcs, cleanup_eacces_on_subdir);
+ ATF_ADD_TEST_CASE(tcs, change_directory);
+ ATF_ADD_TEST_CASE(tcs, get_current_dir);
+}
diff --git a/unit/atf-src/tools/generate-revision.sh b/unit/atf-src/tools/generate-revision.sh
new file mode 100755
index 0000000..169be85
--- /dev/null
+++ b/unit/atf-src/tools/generate-revision.sh
@@ -0,0 +1,142 @@
+#! /bin/sh
+#
+# Automated Testing Framework (atf)
+#
+# Copyright (c) 2007 The NetBSD Foundation, Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
+# CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+# IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
+# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+# GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+# IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+# IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+
+#
+# Generates a header file with information about the revision used to
+# build ATF.
+#
+
+set -e
+
+Prog_Name=${0##*/}
+
+GIT=
+ROOT=
+
+#
+# err message
+#
+err() {
+ echo "${Prog_Name}: ${@}" 1>&2
+ exit 1
+}
+
+#
+# call_git args
+#
+call_git() {
+ ( cd "${ROOT}" && "${GIT}" "${@}" )
+}
+
+#
+# generate_from_dist revfile version
+#
+generate_from_dist() {
+ revfile=${1}; shift
+ version=${1}; shift
+
+ >${revfile}
+
+ echo "#define PACKAGE_REVISION_TYPE_DIST" >>${revfile}
+}
+
+#
+# generate_from_git revfile
+#
+generate_from_git() {
+ revfile=${1}
+
+ rev_base_id=$(call_git rev-parse HEAD)
+ rev_branch=$(call_git branch | grep '^\* ' | cut -d ' ' -f 2-)
+ rev_date=$(call_git log -1 | grep '^Date:' | sed -e 's,^Date:[ \t]*,,')
+ if [ -z "$(call_git status -s)" ]; then
+ rev_modified=false
+ else
+ rev_modified=true
+ fi
+
+ >${revfile}
+
+ echo "#define PACKAGE_REVISION_TYPE_GIT" >>${revfile}
+
+ echo "#define PACKAGE_REVISION_BRANCH \"${rev_branch}\"" >>${revfile}
+ echo "#define PACKAGE_REVISION_BASE \"${rev_base_id}\"" >>${revfile}
+
+ if [ ${rev_modified} = true ]; then
+ echo "#define PACKAGE_REVISION_MODIFIED 1" >>${revfile}
+ fi
+
+ echo "#define PACKAGE_REVISION_DATE \"${rev_date}\"" >>${revfile}
+}
+
+#
+# main
+#
+# Entry point.
+#
+main() {
+ outfile=
+ version=
+ while getopts :g:r:o:v: arg; do
+ case ${arg} in
+ g)
+ GIT=${OPTARG}
+ ;;
+ o)
+ outfile=${OPTARG}
+ ;;
+ r)
+ ROOT=${OPTARG}
+ ;;
+ v)
+ version=${OPTARG}
+ ;;
+ *)
+ err "Unknown option ${arg}"
+ ;;
+ esac
+ done
+ [ -n "${ROOT}" ] || \
+ err "Must specify the top-level source directory with -r"
+ [ -n "${outfile}" ] || \
+ err "Must specify an output file with -o"
+ [ -n "${version}" ] || \
+ err "Must specify a version number with -v"
+
+ if [ -n "${GIT}" -a -d ${ROOT}/.git ]; then
+ generate_from_git ${outfile}
+ else
+ generate_from_dist ${outfile} ${version}
+ fi
+}
+
+main "${@}"
+
+# vim: syntax=sh:expandtab:shiftwidth=4:softtabstop=4
diff --git a/unit/atf-src/tools/io.cpp b/unit/atf-src/tools/io.cpp
new file mode 100644
index 0000000..4cf8f6a
--- /dev/null
+++ b/unit/atf-src/tools/io.cpp
@@ -0,0 +1,356 @@
+//
+// Automated Testing Framework (atf)
+//
+// Copyright (c) 2007 The NetBSD Foundation, Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
+// CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+// IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
+// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+// IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+
+extern "C" {
+#include <fcntl.h>
+#include <poll.h>
+#include <signal.h>
+#include <unistd.h>
+}
+
+#include <cassert>
+#include <cerrno>
+#include <cstring>
+
+#include "auto_array.hpp"
+#include "exceptions.hpp"
+#include "io.hpp"
+
+namespace impl = tools::io;
+#define IMPL_NAME "tools::io"
+
+// ------------------------------------------------------------------------
+// The "file_handle" class.
+// ------------------------------------------------------------------------
+
+impl::file_handle::file_handle(void) :
+ m_handle(invalid_value())
+{
+}
+
+impl::file_handle::file_handle(handle_type h) :
+ m_handle(h)
+{
+ assert(m_handle != invalid_value());
+}
+
+impl::file_handle::file_handle(const file_handle& fh) :
+ m_handle(fh.m_handle)
+{
+ fh.m_handle = invalid_value();
+}
+
+impl::file_handle::~file_handle(void)
+{
+ if (is_valid())
+ close();
+}
+
+impl::file_handle&
+impl::file_handle::operator=(const file_handle& fh)
+{
+ m_handle = fh.m_handle;
+ fh.m_handle = invalid_value();
+
+ return *this;
+}
+
+bool
+impl::file_handle::is_valid(void)
+ const
+{
+ return m_handle != invalid_value();
+}
+
+void
+impl::file_handle::close(void)
+{
+ assert(is_valid());
+
+ ::close(m_handle);
+
+ m_handle = invalid_value();
+}
+
+impl::file_handle::handle_type
+impl::file_handle::disown(void)
+{
+ assert(is_valid());
+
+ handle_type h = m_handle;
+ m_handle = invalid_value();
+ return h;
+}
+
+impl::file_handle::handle_type
+impl::file_handle::get(void)
+ const
+{
+ assert(is_valid());
+
+ return m_handle;
+}
+
+void
+impl::file_handle::posix_remap(handle_type h)
+{
+ assert(is_valid());
+
+ if (m_handle == h)
+ return;
+
+ if (::dup2(m_handle, h) == -1)
+ throw tools::system_error(IMPL_NAME "::file_handle::posix_remap",
+ "dup2(2) failed", errno);
+
+ if (::close(m_handle) == -1) {
+ ::close(h);
+ throw tools::system_error(IMPL_NAME "::file_handle::posix_remap",
+ "close(2) failed", errno);
+ }
+
+ m_handle = h;
+}
+
+impl::file_handle::handle_type
+impl::file_handle::invalid_value(void)
+{
+ return -1;
+}
+
+// ------------------------------------------------------------------------
+// The "systembuf" class.
+// ------------------------------------------------------------------------
+
+impl::systembuf::systembuf(handle_type h, std::size_t bufsize) :
+ m_handle(h),
+ m_bufsize(bufsize),
+ m_read_buf(NULL),
+ m_write_buf(NULL)
+{
+ assert(m_handle >= 0);
+ assert(m_bufsize > 0);
+
+ try {
+ m_read_buf = new char[bufsize];
+ m_write_buf = new char[bufsize];
+ } catch (...) {
+ if (m_read_buf != NULL)
+ delete [] m_read_buf;
+ if (m_write_buf != NULL)
+ delete [] m_write_buf;
+ throw;
+ }
+
+ setp(m_write_buf, m_write_buf + m_bufsize);
+}
+
+impl::systembuf::~systembuf(void)
+{
+ delete [] m_read_buf;
+ delete [] m_write_buf;
+}
+
+impl::systembuf::int_type
+impl::systembuf::underflow(void)
+{
+ assert(gptr() >= egptr());
+
+ bool ok;
+ ssize_t cnt = ::read(m_handle, m_read_buf, m_bufsize);
+ ok = (cnt != -1 && cnt != 0);
+
+ if (!ok)
+ return traits_type::eof();
+ else {
+ setg(m_read_buf, m_read_buf, m_read_buf + cnt);
+ return traits_type::to_int_type(*gptr());
+ }
+}
+
+impl::systembuf::int_type
+impl::systembuf::overflow(int c)
+{
+ assert(pptr() >= epptr());
+ if (sync() == -1)
+ return traits_type::eof();
+ if (!traits_type::eq_int_type(c, traits_type::eof())) {
+ traits_type::assign(*pptr(), c);
+ pbump(1);
+ }
+ return traits_type::not_eof(c);
+}
+
+int
+impl::systembuf::sync(void)
+{
+ ssize_t cnt = pptr() - pbase();
+
+ bool ok;
+ ok = ::write(m_handle, pbase(), cnt) == cnt;
+
+ if (ok)
+ pbump(-cnt);
+ return ok ? 0 : -1;
+}
+
+// ------------------------------------------------------------------------
+// The "pistream" class.
+// ------------------------------------------------------------------------
+
+impl::pistream::pistream(const int fd) :
+ std::istream(NULL),
+ m_systembuf(fd)
+{
+ rdbuf(&m_systembuf);
+}
+
+// ------------------------------------------------------------------------
+// The "muxer" class.
+// ------------------------------------------------------------------------
+
+static int
+safe_poll(struct pollfd fds[], nfds_t nfds, int timeout)
+{
+ int ret = ::poll(fds, nfds, timeout);
+ if (ret == -1) {
+ if (errno == EINTR)
+ ret = 0;
+ else
+ throw tools::system_error(IMPL_NAME "::safe_poll", "poll(2) failed",
+ errno);
+ }
+ assert(ret >= 0);
+ return ret;
+}
+
+static size_t
+safe_read(const int fd, void* buffer, const size_t nbytes,
+ const bool report_errors)
+{
+ int ret;
+ while ((ret = ::read(fd, buffer, nbytes)) == -1 && errno == EINTR) {}
+ if (ret == -1) {
+ assert(errno != EINTR);
+
+ if (report_errors)
+ throw tools::system_error(IMPL_NAME "::safe_read", "read(2) failed",
+ errno);
+ else
+ ret = 0;
+ }
+ assert(ret >= 0);
+ return static_cast< size_t >(ret);
+}
+
+impl::muxer::muxer(const int* fds, const size_t nfds, const size_t bufsize) :
+ m_fds(fds),
+ m_nfds(nfds),
+ m_bufsize(bufsize),
+ m_buffers(new std::string[nfds])
+{
+}
+
+impl::muxer::~muxer(void)
+{
+}
+
+size_t
+impl::muxer::read_one(const size_t index, const int fd, std::string& accum,
+ const bool report_errors)
+{
+ tools::auto_array< char > buffer(new char[m_bufsize]);
+ const size_t nbytes = safe_read(fd, buffer.get(), m_bufsize - 1,
+ report_errors);
+ assert(nbytes < m_bufsize);
+ buffer[nbytes] = '\0';
+
+ std::string line(accum);
+
+ size_t line_start = 0;
+ for (size_t i = 0; i < nbytes; i++) {
+ if (buffer[i] == '\n') {
+ line_callback(index, line);
+ line.clear();
+ accum.clear();
+ line_start = i + 1;
+ } else if (buffer[i] == '\r') {
+ // Do nothing.
+ } else {
+ line.append(1, buffer[i]);
+ }
+ }
+ accum.append(&buffer[line_start]);
+
+ return nbytes;
+}
+
+void
+impl::muxer::mux(volatile const bool& terminate)
+{
+ tools::auto_array< struct pollfd > poll_fds(new struct pollfd[m_nfds]);
+ for (size_t i = 0; i < m_nfds; i++) {
+ poll_fds[i].fd = m_fds[i];
+ poll_fds[i].events = POLLIN;
+ }
+
+ size_t nactive = m_nfds;
+ while (nactive > 0 && !terminate) {
+ int ret;
+ while (!terminate && (ret = safe_poll(poll_fds.get(), 2, 250)) == 0) {}
+
+ for (size_t i = 0; !terminate && i < m_nfds; i++) {
+ if (poll_fds[i].events == 0)
+ continue;
+
+ if (poll_fds[i].revents & POLLHUP) {
+ // Any data still available at this point will be processed by
+ // a call to the flush method.
+ poll_fds[i].events = 0;
+
+ assert(nactive >= 1);
+ nactive--;
+ } else if (poll_fds[i].revents & (POLLIN | POLLRDNORM | POLLRDBAND |
+ POLLPRI)) {
+ (void)read_one(i, poll_fds[i].fd, m_buffers[i], true);
+ }
+ }
+ }
+}
+
+void
+impl::muxer::flush(void)
+{
+ for (size_t i = 0; i < m_nfds; i++) {
+ while (read_one(i, m_fds[i], m_buffers[i], false) > 0) {}
+
+ if (!m_buffers[i].empty())
+ line_callback(i, m_buffers[i]);
+ }
+}
diff --git a/unit/atf-src/tools/io.hpp b/unit/atf-src/tools/io.hpp
new file mode 100644
index 0000000..3fb417b
--- /dev/null
+++ b/unit/atf-src/tools/io.hpp
@@ -0,0 +1,436 @@
+//
+// Automated Testing Framework (atf)
+//
+// Copyright (c) 2007 The NetBSD Foundation, Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
+// CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+// IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
+// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+// IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+
+#if !defined(TOOLS_IO_HPP)
+#define TOOLS_IO_HPP
+
+#include <istream>
+#include <ostream>
+#include <streambuf>
+
+#include "auto_array.hpp"
+#include "fs.hpp"
+
+namespace tools {
+namespace io {
+
+// ------------------------------------------------------------------------
+// The "file_handle" class.
+// ------------------------------------------------------------------------
+
+//!
+//! \brief Simple RAII model for system file handles.
+//!
+//! The \a file_handle class is a simple RAII model for native system file
+//! handles. This class wraps one of such handles grabbing its ownership,
+//! and automaticaly closes it upon destruction. It is basically used
+//! inside the library to avoid leaking open file handles, shall an
+//! unexpected execution trace occur.
+//!
+//! A \a file_handle object can be copied but doing so invalidates the
+//! source object. There can only be a single valid \a file_handle object
+//! for a given system file handle. This is similar to std::auto_ptr\<\>'s
+//! semantics.
+//!
+//! This class also provides some convenience methods to issue special file
+//! operations under their respective platforms.
+//!
+class file_handle
+{
+public:
+ //!
+ //! \brief Opaque name for the native handle type.
+ //!
+ //! Each operating system identifies file handles using a specific type.
+ //! The \a handle_type type is used to transparently refer to file
+ //! handles regarless of the operating system in which this class is
+ //! used.
+ //!
+ //! If this class is used in a POSIX system, \a NativeSystemHandle is
+ //! an integer type while it is a \a HANDLE in a Win32 system.
+ //!
+ typedef int handle_type;
+
+ //!
+ //! \brief Constructs an invalid file handle.
+ //!
+ //! This constructor creates a new \a file_handle object that represents
+ //! an invalid file handle. An invalid file handle can be copied but
+ //! cannot be manipulated in any way (except checking for its validity).
+ //!
+ //! \see is_valid()
+ //!
+ file_handle(void);
+
+ //!
+ //! \brief Constructs a new file handle from a native file handle.
+ //!
+ //! This constructor creates a new \a file_handle object that takes
+ //! ownership of the given \a h native file handle. The user must not
+ //! close \a h on his own during the lifetime of the new object.
+ //! Ownership can be reclaimed using disown().
+ //!
+ //! \pre The native file handle must be valid; a close operation must
+ //! succeed on it.
+ //!
+ //! \see disown()
+ //!
+ file_handle(handle_type h);
+
+ //!
+ //! \brief Copy constructor; invalidates the source handle.
+ //!
+ //! This copy constructor creates a new file handle from a given one.
+ //! Ownership of the native file handle is transferred to the new
+ //! object, effectively invalidating the source file handle. This
+ //! avoids having two live \a file_handle objects referring to the
+ //! same native file handle. The source file handle need not be
+ //! valid in the name of simplicity.
+ //!
+ //! \post The source file handle is invalid.
+ //! \post The new file handle owns the source's native file handle.
+ //!
+ file_handle(const file_handle& fh);
+
+ //!
+ //! \brief Releases resources if the handle is valid.
+ //!
+ //! If the file handle is valid, the destructor closes it.
+ //!
+ //! \see is_valid()
+ //!
+ ~file_handle(void);
+
+ //!
+ //! \brief Assignment operator; invalidates the source handle.
+ //!
+ //! This assignment operator transfers ownership of the RHS file
+ //! handle to the LHS one, effectively invalidating the source file
+ //! handle. This avoids having two live \a file_handle objects
+ //! referring to the same native file handle. The source file
+ //! handle need not be valid in the name of simplicity.
+ //!
+ //! \post The RHS file handle is invalid.
+ //! \post The LHS file handle owns RHS' native file handle.
+ //! \return A reference to the LHS file handle.
+ //!
+ file_handle& operator=(const file_handle& fh);
+
+ //!
+ //! \brief Checks whether the file handle is valid or not.
+ //!
+ //! Returns a boolean indicating whether the file handle is valid or
+ //! not. If the file handle is invalid, no other applications can be
+ //! executed other than the destructor.
+ //!
+ //! \return True if the file handle is valid; false otherwise.
+ //!
+ bool is_valid(void) const;
+
+ //!
+ //! \brief Closes the file handle.
+ //!
+ //! Explicitly closes the file handle, which must be valid. Upon
+ //! exit, the handle is not valid any more.
+ //!
+ //! \pre The file handle is valid.
+ //! \post The file handle is invalid.
+ //! \post The native file handle is closed.
+ //!
+ void close(void);
+
+ //!
+ //! \brief Reclaims ownership of the native file handle.
+ //!
+ //! Explicitly reclaims ownership of the native file handle contained
+ //! in the \a file_handle object, returning the native file handle.
+ //! The caller is responsible of closing it later on.
+ //!
+ //! \pre The file handle is valid.
+ //! \post The file handle is invalid.
+ //! \return The native file handle.
+ //!
+ handle_type disown(void);
+
+ //!
+ //! \brief Gets the native file handle.
+ //!
+ //! Returns the native file handle for the \a file_handle object.
+ //! The caller can issue any operation on it except closing it.
+ //! If closing is required, disown() shall be used.
+ //!
+ //! \pre The file handle is valid.
+ //! \return The native file handle.
+ //!
+ handle_type get(void) const;
+
+ //!
+ //! \brief Changes the native file handle to the given one.
+ //!
+ //! Given a new native file handle \a h, this operation assigns this
+ //! handle to the current object, closing its old native file handle.
+ //! In other words, it first calls dup2() to remap the old handle to
+ //! the new one and then closes the old handle.
+ //!
+ //! If \a h matches the current value of the handle, this is a no-op.
+ //! This is done for simplicity, to avoid the caller having to check
+ //! this condition on its own.
+ //!
+ //! If \a h is open, it is automatically closed by dup2().
+ //!
+ //! This operation is only available in POSIX systems.
+ //!
+ //! \pre The file handle is valid.
+ //! \pre The native file handle \a h is valid; i.e., it must be
+ //! closeable.
+ //! \post The file handle's native file handle is \a h.
+ //! \throw system_error If the internal remapping operation fails.
+ //!
+ void posix_remap(handle_type h);
+
+private:
+ //!
+ //! \brief Internal handle value.
+ //!
+ //! This variable holds the native handle value for the file handle
+ //! hold by this object. It is interesting to note that this needs
+ //! to be mutable because the copy constructor and the assignment
+ //! operator invalidate the source object.
+ //!
+ mutable handle_type m_handle;
+
+ //!
+ //! \brief Constant function representing an invalid handle value.
+ //!
+ //! Returns the platform-specific handle value that represents an
+ //! invalid handle. This is a constant function rather than a regular
+ //! constant because, in the latter case, we cannot define it under
+ //! Win32 due to the value being of a complex type.
+ //!
+ static handle_type invalid_value(void);
+};
+
+// ------------------------------------------------------------------------
+// The "systembuf" class.
+// ------------------------------------------------------------------------
+
+//!
+//! \brief std::streambuf implementation for system file handles.
+//!
+//! systembuf provides a std::streambuf implementation for system file
+//! handles. Contrarywise to file_handle, this class does \b not take
+//! ownership of the native file handle; this should be taken care of
+//! somewhere else.
+//!
+//! This class follows the expected semantics of a std::streambuf object.
+//! However, it is not copyable to avoid introducing inconsistences with
+//! the on-disk file and the in-memory buffers.
+//!
+class systembuf : public std::streambuf
+{
+ // Non-copyable.
+ systembuf(const systembuf&);
+ systembuf& operator=(const systembuf&);
+
+public:
+ typedef int handle_type;
+
+ //!
+ //! \brief Constructs a new systembuf for the given file handle.
+ //!
+ //! This constructor creates a new systembuf object that reads or
+ //! writes data from/to the \a h native file handle. This handle
+ //! is \b not owned by the created systembuf object; the code
+ //! should take care of it externally.
+ //!
+ //! This class buffers input and output; the buffer size may be
+ //! tuned through the \a bufsize parameter, which defaults to 8192
+ //! bytes.
+ //!
+ //! \see pistream.
+ //!
+ explicit systembuf(handle_type h, std::size_t bufsize = 8192);
+ ~systembuf(void);
+
+private:
+ //!
+ //! \brief Native file handle used by the systembuf object.
+ //!
+ handle_type m_handle;
+
+ //!
+ //! \brief Internal buffer size used during read and write operations.
+ //!
+ std::size_t m_bufsize;
+
+ //!
+ //! \brief Internal buffer used during read operations.
+ //!
+ char* m_read_buf;
+
+ //!
+ //! \brief Internal buffer used during write operations.
+ //!
+ char* m_write_buf;
+
+protected:
+ //!
+ //! \brief Reads new data from the native file handle.
+ //!
+ //! This operation is called by input methods when there are no more
+ //! data in the input buffer. The function fills the buffer with new
+ //! data, if available.
+ //!
+ //! \pre All input positions are exhausted (gptr() >= egptr()).
+ //! \post The input buffer has new data, if available.
+ //! \returns traits_type::eof() if a read error occurrs or there are
+ //! no more data to be read. Otherwise returns
+ //! traits_type::to_int_type(*gptr()).
+ //!
+ virtual int_type underflow(void);
+
+ //!
+ //! \brief Makes room in the write buffer for additional data.
+ //!
+ //! This operation is called by output methods when there is no more
+ //! space in the output buffer to hold a new element. The function
+ //! first flushes the buffer's contents to disk and then clears it to
+ //! leave room for more characters. The given \a c character is
+ //! stored at the beginning of the new space.
+ //!
+ //! \pre All output positions are exhausted (pptr() >= epptr()).
+ //! \post The output buffer has more space if no errors occurred
+ //! during the write to disk.
+ //! \post *(pptr() - 1) is \a c.
+ //! \returns traits_type::eof() if a write error occurrs. Otherwise
+ //! returns traits_type::not_eof(c).
+ //!
+ virtual int_type overflow(int c);
+
+ //!
+ //! \brief Flushes the output buffer to disk.
+ //!
+ //! Synchronizes the systembuf buffers with the contents of the file
+ //! associated to this object through the native file handle. The
+ //! output buffer is flushed to disk and cleared to leave new room
+ //! for more data.
+ //!
+ //! \returns 0 on success, -1 if an error occurred.
+ //!
+ virtual int sync(void);
+};
+
+// ------------------------------------------------------------------------
+// The "pistream" class.
+// ------------------------------------------------------------------------
+
+//!
+//! \brief Child process' output stream.
+//!
+//! The pistream class represents an output communication channel with the
+//! child process. The child process writes data to this stream and the
+//! parent process can read it through the pistream object. In other
+//! words, from the child's point of view, the communication channel is an
+//! output one, but from the parent's point of view it is an input one;
+//! hence the confusing pistream name.
+//!
+//! pistream objects cannot be copied because they own the file handle
+//! they use to communicate with the child and because they buffer data
+//! that flows through the communication channel.
+//!
+//! A pistream object behaves as a std::istream stream in all senses.
+//! The class is only provided because it must provide a method to let
+//! the caller explicitly close the communication channel.
+//!
+//! \remark <b>Blocking remarks</b>: Functions that read data from this
+//! stream can block if the associated file handle blocks during the read.
+//! As this class is used to communicate with child processes through
+//! anonymous pipes, the most typical blocking condition happens when the
+//! child has no more data to send to the pipe's system buffer. When
+//! this happens, the buffer eventually empties and the system blocks
+//! until the writer generates some data.
+//!
+class pistream : public std::istream
+{
+ // Non-copyable.
+ pistream(const pistream&);
+ pistream& operator=(const pistream&);
+
+ //!
+ //! \brief The systembuf object used to manage this stream's data.
+ //!
+ systembuf m_systembuf;
+
+public:
+ //!
+ //! \brief Creates a new process' output stream.
+ //!
+ //! Given a file handle, this constructor creates a new pistream
+ //! object that owns the given file handle \a fh. Ownership of
+ //! \a fh is transferred to the created pistream object.
+ //!
+ //! \pre \a fh is valid.
+ //! \post \a fh is invalid.
+ //! \post The new pistream object owns \a fh.
+ //!
+ explicit pistream(const int);
+};
+
+// ------------------------------------------------------------------------
+// The "muxer" class.
+// ------------------------------------------------------------------------
+
+class muxer {
+ // Non-copyable.
+ muxer(const muxer&);
+ muxer& operator=(const muxer&);
+
+ const int* m_fds;
+ const size_t m_nfds;
+
+ const size_t m_bufsize;
+ tools::auto_array< std::string > m_buffers;
+
+protected:
+ virtual void line_callback(const size_t, const std::string&) = 0;
+
+ size_t read_one(const size_t, const int, std::string&, const bool);
+
+public:
+ muxer(const int*, const size_t, const size_t bufsize = 1024);
+ virtual ~muxer(void);
+
+ void mux(volatile const bool&);
+ void flush(void);
+};
+
+} // namespace io
+} // namespace tools
+
+#endif // !defined(TOOLS_IO_HPP)
diff --git a/unit/atf-src/tools/io_test.cpp b/unit/atf-src/tools/io_test.cpp
new file mode 100644
index 0000000..b55428f
--- /dev/null
+++ b/unit/atf-src/tools/io_test.cpp
@@ -0,0 +1,471 @@
+//
+// Automated Testing Framework (atf)
+//
+// Copyright (c) 2007 The NetBSD Foundation, Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
+// CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+// IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
+// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+// IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+
+extern "C" {
+#include <sys/stat.h>
+#include <sys/wait.h>
+
+#include <fcntl.h>
+#include <unistd.h>
+}
+
+#include <cassert>
+#include <cerrno>
+#include <cstddef>
+#include <cstdlib>
+#include <cstring>
+#include <fstream>
+#include <iostream>
+#include <istream>
+#include <ostream>
+
+#include <atf-c++.hpp>
+
+#include "io.hpp"
+#include "signals.hpp"
+
+// ------------------------------------------------------------------------
+// Auxiliary functions.
+// ------------------------------------------------------------------------
+
+static
+void
+systembuf_check_data(std::istream& is, std::size_t length)
+{
+ char ch = 'A', chr;
+ std::size_t cnt = 0;
+ while (is >> chr) {
+ ATF_REQUIRE_EQ(ch, chr);
+ if (ch == 'Z')
+ ch = 'A';
+ else
+ ch++;
+ cnt++;
+ }
+ ATF_REQUIRE_EQ(cnt, length);
+}
+
+static
+void
+systembuf_write_data(std::ostream& os, std::size_t length)
+{
+ char ch = 'A';
+ for (std::size_t i = 0; i < length; i++) {
+ os << ch;
+ if (ch == 'Z')
+ ch = 'A';
+ else
+ ch++;
+ }
+ os.flush();
+}
+
+static
+void
+systembuf_test_read(std::size_t length, std::size_t bufsize)
+{
+ using tools::io::systembuf;
+
+ std::ofstream f("test_read.txt");
+ systembuf_write_data(f, length);
+ f.close();
+
+ int fd = ::open("test_read.txt", O_RDONLY);
+ ATF_REQUIRE(fd != -1);
+ systembuf sb(fd, bufsize);
+ std::istream is(&sb);
+ systembuf_check_data(is, length);
+ ::close(fd);
+ ::unlink("test_read.txt");
+}
+
+static
+void
+systembuf_test_write(std::size_t length, std::size_t bufsize)
+{
+ using tools::io::systembuf;
+
+ int fd = ::open("test_write.txt", O_WRONLY | O_CREAT | O_TRUNC,
+ S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
+ ATF_REQUIRE(fd != -1);
+ systembuf sb(fd, bufsize);
+ std::ostream os(&sb);
+ systembuf_write_data(os, length);
+ ::close(fd);
+
+ std::ifstream is("test_write.txt");
+ systembuf_check_data(is, length);
+ is.close();
+ ::unlink("test_write.txt");
+}
+
+// ------------------------------------------------------------------------
+// Test cases for the "file_handle" class.
+// ------------------------------------------------------------------------
+
+ATF_TEST_CASE(file_handle_ctor);
+ATF_TEST_CASE_HEAD(file_handle_ctor)
+{
+ set_md_var("descr", "Tests file_handle's constructors");
+}
+ATF_TEST_CASE_BODY(file_handle_ctor)
+{
+ using tools::io::file_handle;
+
+ file_handle fh1;
+ ATF_REQUIRE(!fh1.is_valid());
+
+ file_handle fh2(STDOUT_FILENO);
+ ATF_REQUIRE(fh2.is_valid());
+ fh2.disown();
+}
+
+ATF_TEST_CASE(file_handle_copy);
+ATF_TEST_CASE_HEAD(file_handle_copy)
+{
+ set_md_var("descr", "Tests file_handle's copy constructor");
+}
+ATF_TEST_CASE_BODY(file_handle_copy)
+{
+ using tools::io::file_handle;
+
+ file_handle fh1;
+ file_handle fh2(STDOUT_FILENO);
+
+ file_handle fh3(fh2);
+ ATF_REQUIRE(!fh2.is_valid());
+ ATF_REQUIRE(fh3.is_valid());
+
+ fh1 = fh3;
+ ATF_REQUIRE(!fh3.is_valid());
+ ATF_REQUIRE(fh1.is_valid());
+
+ fh1.disown();
+}
+
+ATF_TEST_CASE(file_handle_get);
+ATF_TEST_CASE_HEAD(file_handle_get)
+{
+ set_md_var("descr", "Tests the file_handle::get method");
+}
+ATF_TEST_CASE_BODY(file_handle_get)
+{
+ using tools::io::file_handle;
+
+ file_handle fh1(STDOUT_FILENO);
+ ATF_REQUIRE_EQ(fh1.get(), STDOUT_FILENO);
+}
+
+ATF_TEST_CASE(file_handle_posix_remap);
+ATF_TEST_CASE_HEAD(file_handle_posix_remap)
+{
+ set_md_var("descr", "Tests the file_handle::posix_remap method");
+}
+ATF_TEST_CASE_BODY(file_handle_posix_remap)
+{
+ using tools::io::file_handle;
+
+ int pfd[2];
+
+ ATF_REQUIRE(::pipe(pfd) != -1);
+ file_handle rend(pfd[0]);
+ file_handle wend(pfd[1]);
+
+ ATF_REQUIRE(rend.get() != 10);
+ ATF_REQUIRE(wend.get() != 10);
+ wend.posix_remap(10);
+ ATF_REQUIRE_EQ(wend.get(), 10);
+ ATF_REQUIRE(::write(wend.get(), "test-posix-remap", 16) != -1);
+ {
+ char buf[17];
+ ATF_REQUIRE_EQ(::read(rend.get(), buf, sizeof(buf)), 16);
+ buf[16] = '\0';
+ ATF_REQUIRE(std::strcmp(buf, "test-posix-remap") == 0);
+ }
+
+ // Redo previous to ensure that remapping over the same descriptor
+ // has no side-effects.
+ ATF_REQUIRE_EQ(wend.get(), 10);
+ wend.posix_remap(10);
+ ATF_REQUIRE_EQ(wend.get(), 10);
+ ATF_REQUIRE(::write(wend.get(), "test-posix-remap", 16) != -1);
+ {
+ char buf[17];
+ ATF_REQUIRE_EQ(::read(rend.get(), buf, sizeof(buf)), 16);
+ buf[16] = '\0';
+ ATF_REQUIRE(std::strcmp(buf, "test-posix-remap") == 0);
+ }
+}
+
+// ------------------------------------------------------------------------
+// Test cases for the "systembuf" class.
+// ------------------------------------------------------------------------
+
+ATF_TEST_CASE(systembuf_short_read);
+ATF_TEST_CASE_HEAD(systembuf_short_read)
+{
+ set_md_var("descr", "Tests that a short read (one that fits in the "
+ "internal buffer) works when using systembuf");
+}
+ATF_TEST_CASE_BODY(systembuf_short_read)
+{
+ systembuf_test_read(64, 1024);
+}
+
+ATF_TEST_CASE(systembuf_long_read);
+ATF_TEST_CASE_HEAD(systembuf_long_read)
+{
+ set_md_var("descr", "Tests that a long read (one that does not fit in "
+ "the internal buffer) works when using systembuf");
+}
+ATF_TEST_CASE_BODY(systembuf_long_read)
+{
+ systembuf_test_read(64 * 1024, 1024);
+}
+
+ATF_TEST_CASE(systembuf_short_write);
+ATF_TEST_CASE_HEAD(systembuf_short_write)
+{
+ set_md_var("descr", "Tests that a short write (one that fits in the "
+ "internal buffer) works when using systembuf");
+}
+ATF_TEST_CASE_BODY(systembuf_short_write)
+{
+ systembuf_test_write(64, 1024);
+}
+
+ATF_TEST_CASE(systembuf_long_write);
+ATF_TEST_CASE_HEAD(systembuf_long_write)
+{
+ set_md_var("descr", "Tests that a long write (one that does not fit "
+ "in the internal buffer) works when using systembuf");
+}
+ATF_TEST_CASE_BODY(systembuf_long_write)
+{
+ systembuf_test_write(64 * 1024, 1024);
+}
+
+// ------------------------------------------------------------------------
+// Test cases for the "pistream" class.
+// ------------------------------------------------------------------------
+
+ATF_TEST_CASE(pistream);
+ATF_TEST_CASE_HEAD(pistream)
+{
+ set_md_var("descr", "Tests the pistream class");
+}
+ATF_TEST_CASE_BODY(pistream)
+{
+ using tools::io::file_handle;
+ using tools::io::pistream;
+ using tools::io::systembuf;
+
+ int fds[2];
+ ATF_REQUIRE(::pipe(fds) != -1);
+
+ pistream rend(fds[0]);
+
+ systembuf wbuf(fds[1]);
+ std::ostream wend(&wbuf);
+
+ // XXX This assumes that the pipe's buffer is big enough to accept
+ // the data written without blocking!
+ wend << "1Test 1message\n";
+ wend.flush();
+ std::string tmp;
+ rend >> tmp;
+ ATF_REQUIRE_EQ(tmp, "1Test");
+ rend >> tmp;
+ ATF_REQUIRE_EQ(tmp, "1message");
+}
+
+// ------------------------------------------------------------------------
+// Tests for the "muxer" class.
+// ------------------------------------------------------------------------
+
+namespace {
+
+static void
+check_stream(std::ostream& os)
+{
+ // If we receive a signal while writing to the stream, the bad bit gets set.
+ // Things seem to behave fine afterwards if we clear such error condition.
+ // However, I'm not sure if it's safe to query errno at this point.
+ ATF_REQUIRE(os.good() || (os.bad() && errno == EINTR));
+ os.clear();
+}
+
+class mock_muxer : public tools::io::muxer {
+ void line_callback(const size_t index, const std::string& line)
+ {
+ // The following should be enabled but causes the output to be so big
+ // that it is annoying. Reenable at some point if we make atf store
+ // the output of the test cases in some other way (e.g. only if a test
+ // failes), because this message is the only help in seeing how the
+ // test fails.
+ //std::cout << "line_callback(" << index << ", " << line << ")\n";
+ check_stream(std::cout);
+ switch (index) {
+ case 0: lines0.push_back(line); break;
+ case 1: lines1.push_back(line); break;
+ default: ATF_REQUIRE(false);
+ }
+ }
+
+public:
+ mock_muxer(const int* fds, const size_t nfds, const size_t bufsize) :
+ muxer(fds, nfds, bufsize) {}
+
+ std::vector< std::string > lines0;
+ std::vector< std::string > lines1;
+};
+
+static bool child_finished = false;
+static void sigchld_handler(int signo)
+{
+ assert(signo == SIGCHLD);
+ child_finished = true;
+}
+
+static void
+child_printer(const int pipeout[2], const int pipeerr[2],
+ const size_t iterations)
+{
+ ::close(pipeout[0]);
+ ::close(pipeerr[0]);
+ ATF_REQUIRE(::dup2(pipeout[1], STDOUT_FILENO) != -1);
+ ATF_REQUIRE(::dup2(pipeerr[1], STDERR_FILENO) != -1);
+ ::close(pipeout[1]);
+ ::close(pipeerr[1]);
+
+ for (size_t i = 0; i < iterations; i++) {
+ std::cout << "stdout " << i << "\n";
+ std::cerr << "stderr " << i << "\n";
+ }
+
+ std::cout << "stdout eof\n";
+ std::cerr << "stderr eof\n";
+ std::exit(EXIT_SUCCESS);
+}
+
+static void
+muxer_test(const size_t bufsize, const size_t iterations)
+{
+ int pipeout[2], pipeerr[2];
+ ATF_REQUIRE(pipe(pipeout) != -1);
+ ATF_REQUIRE(pipe(pipeerr) != -1);
+
+ tools::signals::signal_programmer sigchld(SIGCHLD, sigchld_handler);
+
+ std::cout.flush();
+ std::cerr.flush();
+
+ pid_t pid = ::fork();
+ ATF_REQUIRE(pid != -1);
+ if (pid == 0) {
+ sigchld.unprogram();
+ child_printer(pipeout, pipeerr, iterations);
+ std::abort();
+ }
+ ::close(pipeout[1]);
+ ::close(pipeerr[1]);
+
+ int fds[2] = {pipeout[0], pipeerr[0]};
+ mock_muxer mux(fds, 2, bufsize);
+
+ mux.mux(child_finished);
+ check_stream(std::cout);
+ std::cout << "mux done\n";
+
+ mux.flush();
+ std::cout << "flush done\n";
+ check_stream(std::cout);
+
+ sigchld.unprogram();
+ int status;
+ ATF_REQUIRE(::waitpid(pid, &status, 0) != -1);
+ ATF_REQUIRE(WIFEXITED(status));
+ ATF_REQUIRE(WEXITSTATUS(status) == EXIT_SUCCESS);
+
+ ATF_REQUIRE(std::cout.good());
+ ATF_REQUIRE(std::cerr.good());
+ for (size_t i = 0; i < iterations; i++) {
+ std::ostringstream exp0, exp1;
+ exp0 << "stdout " << i;
+ exp1 << "stderr " << i;
+
+ ATF_REQUIRE(mux.lines0.size() > i);
+ ATF_REQUIRE_EQ(exp0.str(), mux.lines0[i]);
+ ATF_REQUIRE(mux.lines1.size() > i);
+ ATF_REQUIRE_EQ(exp1.str(), mux.lines1[i]);
+ }
+ ATF_REQUIRE_EQ("stdout eof", mux.lines0[iterations]);
+ ATF_REQUIRE_EQ("stderr eof", mux.lines1[iterations]);
+ std::cout << "all done\n";
+}
+
+} // anonymous namespace
+
+ATF_TEST_CASE_WITHOUT_HEAD(muxer_small_buffer);
+ATF_TEST_CASE_BODY(muxer_small_buffer)
+{
+ muxer_test(4, 20000);
+}
+
+ATF_TEST_CASE_WITHOUT_HEAD(muxer_large_buffer);
+ATF_TEST_CASE_BODY(muxer_large_buffer)
+{
+ muxer_test(1024, 50000);
+}
+
+// ------------------------------------------------------------------------
+// Main.
+// ------------------------------------------------------------------------
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ // Add the tests for the "file_handle" class.
+ ATF_ADD_TEST_CASE(tcs, file_handle_ctor);
+ ATF_ADD_TEST_CASE(tcs, file_handle_copy);
+ ATF_ADD_TEST_CASE(tcs, file_handle_get);
+ ATF_ADD_TEST_CASE(tcs, file_handle_posix_remap);
+
+ // Add the tests for the "systembuf" class.
+ ATF_ADD_TEST_CASE(tcs, systembuf_short_read);
+ ATF_ADD_TEST_CASE(tcs, systembuf_long_read);
+ ATF_ADD_TEST_CASE(tcs, systembuf_short_write);
+ ATF_ADD_TEST_CASE(tcs, systembuf_long_write);
+
+ // Add the tests for the "pistream" class.
+ ATF_ADD_TEST_CASE(tcs, pistream);
+
+ // Add the tests for the "muxer" class.
+ ATF_ADD_TEST_CASE(tcs, muxer_small_buffer);
+ ATF_ADD_TEST_CASE(tcs, muxer_large_buffer);
+}
diff --git a/unit/atf-src/tools/misc_helpers.cpp b/unit/atf-src/tools/misc_helpers.cpp
new file mode 100644
index 0000000..8ec4dde
--- /dev/null
+++ b/unit/atf-src/tools/misc_helpers.cpp
@@ -0,0 +1,448 @@
+//
+// Automated Testing Framework (atf)
+//
+// Copyright (c) 2007 The NetBSD Foundation, Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
+// CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+// IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
+// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+// IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+
+extern "C" {
+#include <sys/stat.h>
+
+#include <signal.h>
+#include <unistd.h>
+}
+
+#include <cstdlib>
+#include <fstream>
+#include <iomanip>
+#include <ios>
+#include <iostream>
+#include <string>
+
+#include <atf-c++.hpp>
+
+#include "env.hpp"
+#include "fs.hpp"
+#include "process.hpp"
+
+// ------------------------------------------------------------------------
+// Auxiliary functions.
+// ------------------------------------------------------------------------
+
+static
+void
+touch(const std::string& path)
+{
+ std::ofstream os(path.c_str());
+ if (!os)
+ ATF_FAIL("Could not create file " + path);
+ os.close();
+}
+
+// ------------------------------------------------------------------------
+// Helper tests for "atf-run_test".
+// ------------------------------------------------------------------------
+
+ATF_TEST_CASE(pass);
+ATF_TEST_CASE_HEAD(pass)
+{
+ set_md_var("descr", "Helper test case for the t_integration test program");
+}
+ATF_TEST_CASE_BODY(pass)
+{
+}
+
+ATF_TEST_CASE(config);
+ATF_TEST_CASE_HEAD(config)
+{
+ set_md_var("descr", "Helper test case for the t_integration test program");
+}
+ATF_TEST_CASE_BODY(config)
+{
+ std::cout << "1st: " << get_config_var("1st") << "\n";
+ std::cout << "2nd: " << get_config_var("2nd") << "\n";
+ std::cout << "3rd: " << get_config_var("3rd") << "\n";
+ std::cout << "4th: " << get_config_var("4th") << "\n";
+}
+
+ATF_TEST_CASE(fds);
+ATF_TEST_CASE_HEAD(fds)
+{
+ set_md_var("descr", "Helper test case for the t_integration test program");
+}
+ATF_TEST_CASE_BODY(fds)
+{
+ std::cout << "msg1 to stdout" << "\n";
+ std::cout << "msg2 to stdout" << "\n";
+ std::cerr << "msg1 to stderr" << "\n";
+ std::cerr << "msg2 to stderr" << "\n";
+}
+
+ATF_TEST_CASE_WITHOUT_HEAD(mux_streams);
+ATF_TEST_CASE_BODY(mux_streams)
+{
+ for (size_t i = 0; i < 10000; i++) {
+ switch (i % 5) {
+ case 0:
+ std::cout << "stdout " << i << "\n";
+ break;
+ case 1:
+ std::cerr << "stderr " << i << "\n";
+ break;
+ case 2:
+ std::cout << "stdout " << i << "\n";
+ std::cerr << "stderr " << i << "\n";
+ break;
+ case 3:
+ std::cout << "stdout " << i << "\n";
+ std::cout << "stdout " << i << "\n";
+ std::cerr << "stderr " << i << "\n";
+ break;
+ case 4:
+ std::cout << "stdout " << i << "\n";
+ std::cerr << "stderr " << i << "\n";
+ std::cerr << "stderr " << i << "\n";
+ break;
+ default:
+ std::abort();
+ }
+ }
+}
+
+ATF_TEST_CASE(testvar);
+ATF_TEST_CASE_HEAD(testvar)
+{
+ set_md_var("descr", "Helper test case for the t_integration test program");
+}
+ATF_TEST_CASE_BODY(testvar)
+{
+ if (!has_config_var("testvar"))
+ fail("testvar variable not defined");
+ std::cout << "testvar: " << get_config_var("testvar") << "\n";
+}
+
+ATF_TEST_CASE(env_list);
+ATF_TEST_CASE_HEAD(env_list)
+{
+ set_md_var("descr", "Helper test case for the t_integration test program");
+}
+ATF_TEST_CASE_BODY(env_list)
+{
+ const tools::process::status s =
+ tools::process::exec(tools::fs::path("env"),
+ tools::process::argv_array("env", NULL),
+ tools::process::stream_inherit(),
+ tools::process::stream_inherit());
+ ATF_REQUIRE(s.exited());
+ ATF_REQUIRE(s.exitstatus() == EXIT_SUCCESS);
+}
+
+ATF_TEST_CASE(env_home);
+ATF_TEST_CASE_HEAD(env_home)
+{
+ set_md_var("descr", "Helper test case for the t_integration test program");
+}
+ATF_TEST_CASE_BODY(env_home)
+{
+ ATF_REQUIRE(tools::env::has("HOME"));
+ tools::fs::path p(tools::env::get("HOME"));
+ tools::fs::file_info fi1(p);
+ tools::fs::file_info fi2(tools::fs::path("."));
+ ATF_REQUIRE_EQ(fi1.get_device(), fi2.get_device());
+ ATF_REQUIRE_EQ(fi1.get_inode(), fi2.get_inode());
+}
+
+ATF_TEST_CASE(read_stdin);
+ATF_TEST_CASE_HEAD(read_stdin)
+{
+ set_md_var("descr", "Helper test case for the t_integration test program");
+}
+ATF_TEST_CASE_BODY(read_stdin)
+{
+ char buf[100];
+ ssize_t len = ::read(STDIN_FILENO, buf, sizeof(buf) - 1);
+ ATF_REQUIRE(len != -1);
+
+ buf[len + 1] = '\0';
+ for (ssize_t i = 0; i < len; i++) {
+ if (buf[i] != '\0') {
+ fail("The stdin of the test case does not seem to be /dev/zero; "
+ "got '" + std::string(buf) + "'");
+ }
+ }
+}
+
+ATF_TEST_CASE(umask);
+ATF_TEST_CASE_HEAD(umask)
+{
+ set_md_var("descr", "Helper test case for the t_integration test program");
+}
+ATF_TEST_CASE_BODY(umask)
+{
+ mode_t m = ::umask(0);
+ std::cout << "umask: " << std::setw(4) << std::setfill('0')
+ << std::oct << m << "\n";
+ (void)::umask(m);
+}
+
+ATF_TEST_CASE_WITH_CLEANUP(cleanup_states);
+ATF_TEST_CASE_HEAD(cleanup_states)
+{
+ set_md_var("descr", "Helper test case for the t_integration test program");
+}
+ATF_TEST_CASE_BODY(cleanup_states)
+{
+ touch(get_config_var("statedir") + "/to-delete");
+ touch(get_config_var("statedir") + "/to-stay");
+
+ if (get_config_var("state") == "fail")
+ ATF_FAIL("On purpose");
+ else if (get_config_var("state") == "skip")
+ ATF_SKIP("On purpose");
+}
+ATF_TEST_CASE_CLEANUP(cleanup_states)
+{
+ tools::fs::remove(tools::fs::path(get_config_var("statedir") + "/to-delete"));
+}
+
+ATF_TEST_CASE_WITH_CLEANUP(cleanup_curdir);
+ATF_TEST_CASE_HEAD(cleanup_curdir)
+{
+ set_md_var("descr", "Helper test case for the t_integration test program");
+}
+ATF_TEST_CASE_BODY(cleanup_curdir)
+{
+ std::ofstream os("oldvalue");
+ if (!os)
+ ATF_FAIL("Failed to create oldvalue file");
+ os << 1234;
+ os.close();
+}
+ATF_TEST_CASE_CLEANUP(cleanup_curdir)
+{
+ std::ifstream is("oldvalue");
+ if (is) {
+ int i;
+ is >> i;
+ std::cout << "Old value: " << i << "\n";
+ is.close();
+ }
+}
+
+ATF_TEST_CASE(require_arch);
+ATF_TEST_CASE_HEAD(require_arch)
+{
+ set_md_var("descr", "Helper test case for the t_integration test program");
+ set_md_var("require.arch", get_config_var("arch", "not-set"));
+}
+ATF_TEST_CASE_BODY(require_arch)
+{
+}
+
+ATF_TEST_CASE(require_config);
+ATF_TEST_CASE_HEAD(require_config)
+{
+ set_md_var("descr", "Helper test case for the t_integration test program");
+ set_md_var("require.config", "var1 var2");
+}
+ATF_TEST_CASE_BODY(require_config)
+{
+ std::cout << "var1: " << get_config_var("var1") << "\n";
+ std::cout << "var2: " << get_config_var("var2") << "\n";
+}
+
+ATF_TEST_CASE(require_files);
+ATF_TEST_CASE_HEAD(require_files)
+{
+ set_md_var("descr", "Helper test case for the t_integration test program");
+ set_md_var("require.files", get_config_var("files", "not-set"));
+}
+ATF_TEST_CASE_BODY(require_files)
+{
+}
+
+ATF_TEST_CASE(require_machine);
+ATF_TEST_CASE_HEAD(require_machine)
+{
+ set_md_var("descr", "Helper test case for the t_integration test program");
+ set_md_var("require.machine", get_config_var("machine", "not-set"));
+}
+ATF_TEST_CASE_BODY(require_machine)
+{
+}
+
+ATF_TEST_CASE(require_progs);
+ATF_TEST_CASE_HEAD(require_progs)
+{
+ set_md_var("descr", "Helper test case for the t_integration test program");
+ set_md_var("require.progs", get_config_var("progs", "not-set"));
+}
+ATF_TEST_CASE_BODY(require_progs)
+{
+}
+
+ATF_TEST_CASE(require_user);
+ATF_TEST_CASE_HEAD(require_user)
+{
+ set_md_var("descr", "Helper test case for the t_integration test program");
+ set_md_var("require.user", get_config_var("user", "not-set"));
+}
+ATF_TEST_CASE_BODY(require_user)
+{
+}
+
+ATF_TEST_CASE(timeout);
+ATF_TEST_CASE_HEAD(timeout)
+{
+ set_md_var("descr", "Helper test case for the t_integration test program");
+ set_md_var("timeout", "1");
+}
+ATF_TEST_CASE_BODY(timeout)
+{
+ sleep(10);
+ touch(get_config_var("statedir") + "/finished");
+}
+
+ATF_TEST_CASE(timeout_forkexit);
+ATF_TEST_CASE_HEAD(timeout_forkexit)
+{
+ set_md_var("descr", "Helper test case for the t_integration test program");
+}
+ATF_TEST_CASE_BODY(timeout_forkexit)
+{
+ pid_t pid = fork();
+ ATF_REQUIRE(pid != -1);
+
+ if (pid == 0) {
+ sigset_t mask;
+ sigemptyset(&mask);
+
+ std::cout << "Waiting in subprocess\n";
+ std::cout.flush();
+ ::sigsuspend(&mask);
+
+ touch(get_config_var("statedir") + "/child-finished");
+ std::cout << "Subprocess exiting\n";
+ std::cout.flush();
+ exit(EXIT_SUCCESS);
+ } else {
+ // Don't wait for the child process and let atf-run deal with it.
+ touch(get_config_var("statedir") + "/parent-finished");
+ std::cout << "Parent process exiting\n";
+ ATF_PASS();
+ }
+}
+
+ATF_TEST_CASE(use_fs);
+ATF_TEST_CASE_HEAD(use_fs)
+{
+ set_md_var("descr", "Helper test case for the t_integration test program");
+ set_md_var("use.fs", "this-is-deprecated");
+}
+ATF_TEST_CASE_BODY(use_fs)
+{
+ touch("test-file");
+}
+
+// ------------------------------------------------------------------------
+// Helper tests for "atf-report_test".
+// ------------------------------------------------------------------------
+
+ATF_TEST_CASE(diff);
+ATF_TEST_CASE_HEAD(diff)
+{
+ set_md_var("descr", "Helper test case for the t_integration program");
+}
+ATF_TEST_CASE_BODY(diff)
+{
+ std::cout << "--- a 2007-11-04 14:00:41.000000000 +0100\n";
+ std::cout << "+++ b 2007-11-04 14:00:48.000000000 +0100\n";
+ std::cout << "@@ -1,7 +1,7 @@\n";
+ std::cout << " This test is meant to simulate a diff.\n";
+ std::cout << " Blank space at beginning of context lines must be "
+ "preserved.\n";
+ std::cout << " \n";
+ std::cout << "-First original line.\n";
+ std::cout << "-Second original line.\n";
+ std::cout << "+First modified line.\n";
+ std::cout << "+Second modified line.\n";
+ std::cout << " \n";
+ std::cout << " EOF\n";
+}
+
+// ------------------------------------------------------------------------
+// Main.
+// ------------------------------------------------------------------------
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ std::string which = tools::env::get("TESTCASE");
+
+ // Add helper tests for atf-run_test.
+ if (which == "pass")
+ ATF_ADD_TEST_CASE(tcs, pass);
+ if (which == "config")
+ ATF_ADD_TEST_CASE(tcs, config);
+ if (which == "fds")
+ ATF_ADD_TEST_CASE(tcs, fds);
+ if (which == "mux_streams")
+ ATF_ADD_TEST_CASE(tcs, mux_streams);
+ if (which == "testvar")
+ ATF_ADD_TEST_CASE(tcs, testvar);
+ if (which == "env_list")
+ ATF_ADD_TEST_CASE(tcs, env_list);
+ if (which == "env_home")
+ ATF_ADD_TEST_CASE(tcs, env_home);
+ if (which == "read_stdin")
+ ATF_ADD_TEST_CASE(tcs, read_stdin);
+ if (which == "umask")
+ ATF_ADD_TEST_CASE(tcs, umask);
+ if (which == "cleanup_states")
+ ATF_ADD_TEST_CASE(tcs, cleanup_states);
+ if (which == "cleanup_curdir")
+ ATF_ADD_TEST_CASE(tcs, cleanup_curdir);
+ if (which == "require_arch")
+ ATF_ADD_TEST_CASE(tcs, require_arch);
+ if (which == "require_config")
+ ATF_ADD_TEST_CASE(tcs, require_config);
+ if (which == "require_files")
+ ATF_ADD_TEST_CASE(tcs, require_files);
+ if (which == "require_machine")
+ ATF_ADD_TEST_CASE(tcs, require_machine);
+ if (which == "require_progs")
+ ATF_ADD_TEST_CASE(tcs, require_progs);
+ if (which == "require_user")
+ ATF_ADD_TEST_CASE(tcs, require_user);
+ if (which == "timeout")
+ ATF_ADD_TEST_CASE(tcs, timeout);
+ if (which == "timeout_forkexit")
+ ATF_ADD_TEST_CASE(tcs, timeout_forkexit);
+ if (which == "use_fs")
+ ATF_ADD_TEST_CASE(tcs, use_fs);
+
+ // Add helper tests for atf-report_test.
+ if (which == "diff")
+ ATF_ADD_TEST_CASE(tcs, diff);
+}
diff --git a/unit/atf-src/tools/parser.cpp b/unit/atf-src/tools/parser.cpp
new file mode 100644
index 0000000..e6b3a3b
--- /dev/null
+++ b/unit/atf-src/tools/parser.cpp
@@ -0,0 +1,384 @@
+//
+// Automated Testing Framework (atf)
+//
+// Copyright (c) 2007 The NetBSD Foundation, Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
+// CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+// IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
+// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+// IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+
+#include <cassert>
+#include <sstream>
+
+#include "parser.hpp"
+#include "text.hpp"
+
+namespace impl = tools::parser;
+#define IMPL_NAME "tools::parser"
+
+// ------------------------------------------------------------------------
+// The "parse_error" class.
+// ------------------------------------------------------------------------
+
+impl::parse_error::parse_error(size_t line, std::string msg) :
+ std::runtime_error(msg),
+ std::pair< size_t, std::string >(line, msg)
+{
+}
+
+impl::parse_error::~parse_error(void)
+ throw()
+{
+}
+
+const char*
+impl::parse_error::what(void)
+ const throw()
+{
+ try {
+ std::ostringstream oss;
+ oss << "LONELY PARSE ERROR: " << first << ": " << second;
+ m_msg = oss.str();
+ return m_msg.c_str();
+ } catch (...) {
+ return "Could not format message for parsing error.";
+ }
+}
+
+impl::parse_error::operator std::string(void)
+ const
+{
+ return tools::text::to_string(first) + ": " + second;
+}
+
+// ------------------------------------------------------------------------
+// The "parse_errors" class.
+// ------------------------------------------------------------------------
+
+impl::parse_errors::parse_errors(void) :
+ std::runtime_error("No parsing errors yet")
+{
+ m_msg.clear();
+}
+
+impl::parse_errors::~parse_errors(void)
+ throw()
+{
+}
+
+const char*
+impl::parse_errors::what(void)
+ const throw()
+{
+ try {
+ m_msg = tools::text::join(*this, "\n");
+ return m_msg.c_str();
+ } catch (...) {
+ return "Could not format messages for parsing errors.";
+ }
+}
+
+// ------------------------------------------------------------------------
+// The "format_error" class.
+// ------------------------------------------------------------------------
+
+impl::format_error::format_error(const std::string& w) :
+ std::runtime_error(w.c_str())
+{
+}
+
+// ------------------------------------------------------------------------
+// The "token" class.
+// ------------------------------------------------------------------------
+
+impl::token::token(void) :
+ m_inited(false)
+{
+}
+
+impl::token::token(size_t p_line,
+ const token_type& p_type,
+ const std::string& p_text) :
+ m_inited(true),
+ m_line(p_line),
+ m_type(p_type),
+ m_text(p_text)
+{
+}
+
+size_t
+impl::token::lineno(void)
+ const
+{
+ return m_line;
+}
+
+const impl::token_type&
+impl::token::type(void)
+ const
+{
+ return m_type;
+}
+
+const std::string&
+impl::token::text(void)
+ const
+{
+ return m_text;
+}
+
+impl::token::operator bool(void)
+ const
+{
+ return m_inited;
+}
+
+bool
+impl::token::operator!(void)
+ const
+{
+ return !m_inited;
+}
+
+// ------------------------------------------------------------------------
+// The "header_entry" class.
+// ------------------------------------------------------------------------
+
+impl::header_entry::header_entry(void)
+{
+}
+
+impl::header_entry::header_entry(const std::string& n, const std::string& v,
+ attrs_map as) :
+ m_name(n),
+ m_value(v),
+ m_attrs(as)
+{
+}
+
+const std::string&
+impl::header_entry::name(void) const
+{
+ return m_name;
+}
+
+const std::string&
+impl::header_entry::value(void) const
+{
+ return m_value;
+}
+
+const impl::attrs_map&
+impl::header_entry::attrs(void) const
+{
+ return m_attrs;
+}
+
+bool
+impl::header_entry::has_attr(const std::string& n) const
+{
+ return m_attrs.find(n) != m_attrs.end();
+}
+
+const std::string&
+impl::header_entry::get_attr(const std::string& n) const
+{
+ attrs_map::const_iterator iter = m_attrs.find(n);
+ assert(iter != m_attrs.end());
+ return (*iter).second;
+}
+
+// ------------------------------------------------------------------------
+// The header tokenizer.
+// ------------------------------------------------------------------------
+
+namespace header {
+
+static const impl::token_type eof_type = 0;
+static const impl::token_type nl_type = 1;
+static const impl::token_type text_type = 2;
+static const impl::token_type colon_type = 3;
+static const impl::token_type semicolon_type = 4;
+static const impl::token_type dblquote_type = 5;
+static const impl::token_type equal_type = 6;
+
+class tokenizer : public impl::tokenizer< std::istream > {
+public:
+ tokenizer(std::istream& is, size_t curline) :
+ impl::tokenizer< std::istream >
+ (is, true, eof_type, nl_type, text_type, curline)
+ {
+ add_delim(';', semicolon_type);
+ add_delim(':', colon_type);
+ add_delim('=', equal_type);
+ add_quote('"', dblquote_type);
+ }
+};
+
+static
+impl::parser< header::tokenizer >&
+read(impl::parser< header::tokenizer >& p, impl::header_entry& he)
+{
+ using namespace header;
+
+ impl::token t = p.expect(text_type, nl_type, "a header name");
+ if (t.type() == nl_type) {
+ he = impl::header_entry();
+ return p;
+ }
+ std::string hdr_name = t.text();
+
+ t = p.expect(colon_type, "`:'");
+
+ t = p.expect(text_type, "a textual value");
+ std::string hdr_value = t.text();
+
+ impl::attrs_map attrs;
+
+ for (;;) {
+ t = p.expect(eof_type, semicolon_type, nl_type,
+ "eof, `;' or new line");
+ if (t.type() == eof_type || t.type() == nl_type)
+ break;
+
+ t = p.expect(text_type, "an attribute name");
+ std::string attr_name = t.text();
+
+ t = p.expect(equal_type, "`='");
+
+ t = p.expect(text_type, "word or quoted string");
+ std::string attr_value = t.text();
+ attrs[attr_name] = attr_value;
+ }
+
+ he = impl::header_entry(hdr_name, hdr_value, attrs);
+
+ return p;
+}
+
+static
+std::ostream&
+write(std::ostream& os, const impl::header_entry& he)
+{
+ std::string line = he.name() + ": " + he.value();
+ impl::attrs_map as = he.attrs();
+ for (impl::attrs_map::const_iterator iter = as.begin(); iter != as.end();
+ iter++) {
+ assert((*iter).second.find('\"') == std::string::npos);
+ line += "; " + (*iter).first + "=\"" + (*iter).second + "\"";
+ }
+
+ os << line << "\n";
+
+ return os;
+}
+
+} // namespace header
+
+// ------------------------------------------------------------------------
+// Free functions.
+// ------------------------------------------------------------------------
+
+std::pair< size_t, impl::headers_map >
+impl::read_headers(std::istream& is, size_t curline)
+{
+ using impl::format_error;
+
+ headers_map hm;
+
+ //
+ // Grammar
+ //
+ // header = entry+ nl
+ // entry = line nl
+ // line = text colon text
+ // (semicolon (text equal (text | dblquote string dblquote)))*
+ // string = quoted_string
+ //
+
+ header::tokenizer tkz(is, curline);
+ impl::parser< header::tokenizer > p(tkz);
+
+ bool first = true;
+ for (;;) {
+ try {
+ header_entry he;
+ if (!header::read(p, he).good() || he.name().empty())
+ break;
+
+ if (first && he.name() != "Content-Type")
+ throw format_error("Could not determine content type");
+ else
+ first = false;
+
+ hm[he.name()] = he;
+ } catch (const impl::parse_error& pe) {
+ p.add_error(pe);
+ p.reset(header::nl_type);
+ }
+ }
+
+ if (!is.good())
+ throw format_error("Unexpected end of stream");
+
+ return std::pair< size_t, headers_map >(tkz.lineno(), hm);
+}
+
+void
+impl::write_headers(const impl::headers_map& hm, std::ostream& os)
+{
+ assert(!hm.empty());
+ headers_map::const_iterator ct = hm.find("Content-Type");
+ assert(ct != hm.end());
+ header::write(os, (*ct).second);
+ for (headers_map::const_iterator iter = hm.begin(); iter != hm.end();
+ iter++) {
+ if ((*iter).first != "Content-Type")
+ header::write(os, (*iter).second);
+ }
+ os << "\n";
+}
+
+void
+impl::validate_content_type(const impl::headers_map& hm, const std::string& fmt,
+ int version)
+{
+ using impl::format_error;
+
+ headers_map::const_iterator iter = hm.find("Content-Type");
+ if (iter == hm.end())
+ throw format_error("Could not determine content type");
+
+ const header_entry& he = (*iter).second;
+ if (he.value() != fmt)
+ throw format_error("Mismatched content type: expected `" + fmt +
+ "' but got `" + he.value() + "'");
+
+ if (!he.has_attr("version"))
+ throw format_error("Could not determine version");
+ const std::string& vstr = tools::text::to_string(version);
+ if (he.get_attr("version") != vstr)
+ throw format_error("Mismatched version: expected `" +
+ vstr + "' but got `" +
+ he.get_attr("version") + "'");
+}
diff --git a/unit/atf-src/tools/parser.hpp b/unit/atf-src/tools/parser.hpp
new file mode 100644
index 0000000..eb00cb4
--- /dev/null
+++ b/unit/atf-src/tools/parser.hpp
@@ -0,0 +1,607 @@
+//
+// Automated Testing Framework (atf)
+//
+// Copyright (c) 2007 The NetBSD Foundation, Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
+// CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+// IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
+// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+// IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+
+#if !defined(TOOLS_PARSER_HPP)
+#define TOOLS_PARSER_HPP
+
+#include <istream>
+#include <map>
+#include <ostream>
+#include <stdexcept>
+#include <string>
+#include <utility>
+#include <vector>
+
+namespace tools {
+namespace parser {
+
+// ------------------------------------------------------------------------
+// The "parse_error" class.
+// ------------------------------------------------------------------------
+
+class parse_error : public std::runtime_error,
+ public std::pair< size_t, std::string > {
+ mutable std::string m_msg;
+
+public:
+ parse_error(size_t, std::string);
+ ~parse_error(void) throw();
+
+ const char* what(void) const throw();
+
+ operator std::string(void) const;
+};
+
+// ------------------------------------------------------------------------
+// The "parse_errors" class.
+// ------------------------------------------------------------------------
+
+class parse_errors : public std::runtime_error,
+ public std::vector< parse_error > {
+ std::vector< parse_error > m_errors;
+ mutable std::string m_msg;
+
+public:
+ parse_errors(void);
+ ~parse_errors(void) throw();
+
+ const char* what(void) const throw();
+};
+
+// ------------------------------------------------------------------------
+// The "format_error" class.
+// ------------------------------------------------------------------------
+
+class format_error : public std::runtime_error {
+public:
+ format_error(const std::string&);
+};
+
+// ------------------------------------------------------------------------
+// The "token" class.
+// ------------------------------------------------------------------------
+
+typedef int token_type;
+
+//!
+//! \brief Representation of a read token.
+//!
+//! A pair that contains the information of a token read from a stream.
+//! It contains the token's type and its associated data, if any.
+//!
+struct token {
+ bool m_inited;
+ size_t m_line;
+ token_type m_type;
+ std::string m_text;
+
+public:
+ token(void);
+ token(size_t, const token_type&, const std::string& = "");
+
+ size_t lineno(void) const;
+ const token_type& type(void) const;
+ const std::string& text(void) const;
+
+ operator bool(void) const;
+ bool operator!(void) const;
+};
+
+// ------------------------------------------------------------------------
+// The "tokenizer" class.
+// ------------------------------------------------------------------------
+
+//!
+//! \brief A stream tokenizer.
+//!
+//! This template implements an extremely simple, line-oriented stream
+//! tokenizer. It is only able to recognize one character-long delimiters,
+//! random-length keywords, skip whitespace and, anything that does not
+//! match these rules is supposed to be a word.
+//!
+//! Parameter IS: The input stream's type.
+//!
+template< class IS >
+class tokenizer {
+ IS& m_is;
+ size_t m_lineno;
+ token m_la;
+
+ bool m_skipws;
+ token_type m_eof_type, m_nl_type, m_text_type;
+
+ std::map< char, token_type > m_delims_map;
+ std::string m_delims_str;
+
+ char m_quotech;
+ token_type m_quotetype;
+
+ std::map< std::string, token_type > m_keywords_map;
+
+ token_type alloc_type(void);
+
+ template< class TKZ >
+ friend
+ class parser;
+
+public:
+ tokenizer(IS&, bool, const token_type&, const token_type&,
+ const token_type&, size_t = 1);
+
+ size_t lineno(void) const;
+
+ void add_delim(char, const token_type&);
+ void add_keyword(const std::string&, const token_type&);
+ void add_quote(char, const token_type&);
+
+ token next(void);
+ std::string rest_of_line(void);
+};
+
+template< class IS >
+tokenizer< IS >::tokenizer(IS& p_is,
+ bool p_skipws,
+ const token_type& p_eof_type,
+ const token_type& p_nl_type,
+ const token_type& p_text_type,
+ size_t p_lineno) :
+ m_is(p_is),
+ m_lineno(p_lineno),
+ m_skipws(p_skipws),
+ m_eof_type(p_eof_type),
+ m_nl_type(p_nl_type),
+ m_text_type(p_text_type),
+ m_quotech(-1)
+{
+}
+
+template< class IS >
+size_t
+tokenizer< IS >::lineno(void)
+ const
+{
+ return m_lineno;
+}
+
+template< class IS >
+void
+tokenizer< IS >::add_delim(char delim, const token_type& type)
+{
+ m_delims_map[delim] = type;
+ m_delims_str += delim;
+}
+
+template< class IS >
+void
+tokenizer< IS >::add_keyword(const std::string& keyword,
+ const token_type& type)
+{
+ m_keywords_map[keyword] = type;
+}
+
+template< class IS >
+void
+tokenizer< IS >::add_quote(char ch, const token_type& type)
+{
+ m_quotech = ch;
+ m_quotetype = type;
+}
+
+template< class IS >
+token
+tokenizer< IS >::next(void)
+{
+ if (m_la) {
+ token t = m_la;
+ m_la = token();
+ if (t.type() == m_nl_type)
+ m_lineno++;
+ return t;
+ }
+
+ char ch;
+ std::string text;
+
+ bool done = false, quoted = false;
+ token t(m_lineno, m_eof_type, "<<EOF>>");
+ while (!done && m_is.get(ch).good()) {
+ if (ch == m_quotech) {
+ if (text.empty()) {
+ bool escaped = false;
+ while (!done && m_is.get(ch).good()) {
+ if (!escaped) {
+ if (ch == '\\')
+ escaped = true;
+ else if (ch == '\n') {
+ m_la = token(m_lineno, m_nl_type, "<<NEWLINE>>");
+ throw parse_error(t.lineno(),
+ "Missing double quotes before "
+ "end of line");
+ } else if (ch == m_quotech)
+ done = true;
+ else
+ text += ch;
+ } else {
+ text += ch;
+ escaped = false;
+ }
+ }
+ if (!m_is.good())
+ throw parse_error(t.lineno(),
+ "Missing double quotes before "
+ "end of file");
+ t = token(m_lineno, m_text_type, text);
+ quoted = true;
+ } else {
+ m_is.putback(ch);
+ done = true;
+ }
+ } else {
+ typename std::map< char, token_type >::const_iterator idelim;
+ idelim = m_delims_map.find(ch);
+ if (idelim != m_delims_map.end()) {
+ done = true;
+ if (text.empty())
+ t = token(m_lineno, (*idelim).second,
+ std::string("") + ch);
+ else
+ m_is.putback(ch);
+ } else if (ch == '\n') {
+ done = true;
+ if (text.empty())
+ t = token(m_lineno, m_nl_type, "<<NEWLINE>>");
+ else
+ m_is.putback(ch);
+ } else if (m_skipws && (ch == ' ' || ch == '\t')) {
+ if (!text.empty())
+ done = true;
+ } else
+ text += ch;
+ }
+ }
+
+ if (!quoted && !text.empty()) {
+ typename std::map< std::string, token_type >::const_iterator ikw;
+ ikw = m_keywords_map.find(text);
+ if (ikw != m_keywords_map.end())
+ t = token(m_lineno, (*ikw).second, text);
+ else
+ t = token(m_lineno, m_text_type, text);
+ }
+
+ if (t.type() == m_nl_type)
+ m_lineno++;
+
+ return t;
+}
+
+template< class IS >
+std::string
+tokenizer< IS >::rest_of_line(void)
+{
+ std::string str;
+ while (m_is.good() && m_is.peek() != '\n')
+ str += m_is.get();
+ return str;
+}
+
+// ------------------------------------------------------------------------
+// The "parser" class.
+// ------------------------------------------------------------------------
+
+template< class TKZ >
+class parser {
+ TKZ& m_tkz;
+ token m_last;
+ parse_errors m_errors;
+ bool m_thrown;
+
+public:
+ parser(TKZ& tkz);
+ ~parser(void);
+
+ bool good(void) const;
+ void add_error(const parse_error&);
+ bool has_errors(void) const;
+
+ token next(void);
+ std::string rest_of_line(void);
+ token reset(const token_type&);
+
+ token
+ expect(const token_type&,
+ const std::string&);
+
+ token
+ expect(const token_type&,
+ const token_type&,
+ const std::string&);
+
+ token
+ expect(const token_type&,
+ const token_type&,
+ const token_type&,
+ const std::string&);
+
+ token
+ expect(const token_type&,
+ const token_type&,
+ const token_type&,
+ const token_type&,
+ const std::string&);
+
+ token
+ expect(const token_type&,
+ const token_type&,
+ const token_type&,
+ const token_type&,
+ const token_type&,
+ const token_type&,
+ const token_type&,
+ const std::string&);
+
+ token
+ expect(const token_type&,
+ const token_type&,
+ const token_type&,
+ const token_type&,
+ const token_type&,
+ const token_type&,
+ const token_type&,
+ const token_type&,
+ const std::string&);
+};
+
+template< class TKZ >
+parser< TKZ >::parser(TKZ& tkz) :
+ m_tkz(tkz),
+ m_thrown(false)
+{
+}
+
+template< class TKZ >
+parser< TKZ >::~parser(void)
+{
+ if (!m_errors.empty() && !m_thrown)
+ throw m_errors;
+}
+
+template< class TKZ >
+bool
+parser< TKZ >::good(void)
+ const
+{
+ return m_tkz.m_is.good();
+}
+
+template< class TKZ >
+void
+parser< TKZ >::add_error(const parse_error& pe)
+{
+ m_errors.push_back(pe);
+}
+
+template< class TKZ >
+bool
+parser< TKZ >::has_errors(void)
+ const
+{
+ return !m_errors.empty();
+}
+
+template< class TKZ >
+token
+parser< TKZ >::next(void)
+{
+ token t = m_tkz.next();
+
+ m_last = t;
+
+ if (t.type() == m_tkz.m_eof_type) {
+ if (!m_errors.empty()) {
+ m_thrown = true;
+ throw m_errors;
+ }
+ }
+
+ return t;
+}
+
+template< class TKZ >
+std::string
+parser< TKZ >::rest_of_line(void)
+{
+ return m_tkz.rest_of_line();
+}
+
+template< class TKZ >
+token
+parser< TKZ >::reset(const token_type& stop)
+{
+ token t = m_last;
+
+ while (t.type() != m_tkz.m_eof_type && t.type() != stop)
+ t = next();
+
+ return t;
+}
+
+template< class TKZ >
+token
+parser< TKZ >::expect(const token_type& t1,
+ const std::string& textual)
+{
+ token t = next();
+
+ if (t.type() != t1)
+ throw parse_error(t.lineno(),
+ "Unexpected token `" + t.text() +
+ "'; expected " + textual);
+
+ return t;
+}
+
+template< class TKZ >
+token
+parser< TKZ >::expect(const token_type& t1,
+ const token_type& t2,
+ const std::string& textual)
+{
+ token t = next();
+
+ if (t.type() != t1 && t.type() != t2)
+ throw parse_error(t.lineno(),
+ "Unexpected token `" + t.text() +
+ "'; expected " + textual);
+
+ return t;
+}
+
+template< class TKZ >
+token
+parser< TKZ >::expect(const token_type& t1,
+ const token_type& t2,
+ const token_type& t3,
+ const std::string& textual)
+{
+ token t = next();
+
+ if (t.type() != t1 && t.type() != t2 && t.type() != t3)
+ throw parse_error(t.lineno(),
+ "Unexpected token `" + t.text() +
+ "'; expected " + textual);
+
+ return t;
+}
+
+template< class TKZ >
+token
+parser< TKZ >::expect(const token_type& t1,
+ const token_type& t2,
+ const token_type& t3,
+ const token_type& t4,
+ const std::string& textual)
+{
+ token t = next();
+
+ if (t.type() != t1 && t.type() != t2 && t.type() != t3 &&
+ t.type() != t4)
+ throw parse_error(t.lineno(),
+ "Unexpected token `" + t.text() +
+ "'; expected " + textual);
+
+ return t;
+}
+
+template< class TKZ >
+token
+parser< TKZ >::expect(const token_type& t1,
+ const token_type& t2,
+ const token_type& t3,
+ const token_type& t4,
+ const token_type& t5,
+ const token_type& t6,
+ const token_type& t7,
+ const std::string& textual)
+{
+ token t = next();
+
+ if (t.type() != t1 && t.type() != t2 && t.type() != t3 &&
+ t.type() != t4 && t.type() != t5 && t.type() != t6 &&
+ t.type() != t7)
+ throw parse_error(t.lineno(),
+ "Unexpected token `" + t.text() +
+ "'; expected " + textual);
+
+ return t;
+}
+
+template< class TKZ >
+token
+parser< TKZ >::expect(const token_type& t1,
+ const token_type& t2,
+ const token_type& t3,
+ const token_type& t4,
+ const token_type& t5,
+ const token_type& t6,
+ const token_type& t7,
+ const token_type& t8,
+ const std::string& textual)
+{
+ token t = next();
+
+ if (t.type() != t1 && t.type() != t2 && t.type() != t3 &&
+ t.type() != t4 && t.type() != t5 && t.type() != t6 &&
+ t.type() != t7 && t.type() != t8)
+ throw parse_error(t.lineno(),
+ "Unexpected token `" + t.text() +
+ "'; expected " + textual);
+
+ return t;
+}
+
+#define ATF_PARSER_CALLBACK(parser, func) \
+ do { \
+ if (!(parser).has_errors()) \
+ func; \
+ } while (false)
+
+// ------------------------------------------------------------------------
+// Header parsing.
+// ------------------------------------------------------------------------
+
+typedef std::map< std::string, std::string > attrs_map;
+
+class header_entry {
+ std::string m_name;
+ std::string m_value;
+ attrs_map m_attrs;
+
+public:
+ header_entry(void);
+ header_entry(const std::string&, const std::string&,
+ attrs_map = attrs_map());
+
+ const std::string& name(void) const;
+ const std::string& value(void) const;
+ const attrs_map& attrs(void) const;
+ bool has_attr(const std::string&) const;
+ const std::string& get_attr(const std::string&) const;
+};
+
+typedef std::map< std::string, header_entry > headers_map;
+
+std::pair< size_t, headers_map > read_headers(std::istream&, size_t);
+void write_headers(const headers_map&, std::ostream&);
+void validate_content_type(const headers_map&, const std::string&, int);
+
+} // namespace parser
+} // namespace tools
+
+#endif // !defined(TOOLS_PARSER_HPP)
diff --git a/unit/atf-src/tools/parser_test.cpp b/unit/atf-src/tools/parser_test.cpp
new file mode 100644
index 0000000..434a1a6
--- /dev/null
+++ b/unit/atf-src/tools/parser_test.cpp
@@ -0,0 +1,1043 @@
+//
+// Automated Testing Framework (atf)
+//
+// Copyright (c) 2007 The NetBSD Foundation, Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
+// CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+// IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
+// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+// IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+
+#include <sstream>
+
+#include <atf-c++.hpp>
+
+#include "parser.hpp"
+#include "test_helpers.hpp"
+
+// ------------------------------------------------------------------------
+// Tests for the "parse_error" class.
+// ------------------------------------------------------------------------
+
+ATF_TEST_CASE(parse_error_to_string);
+ATF_TEST_CASE_HEAD(parse_error_to_string)
+{
+ set_md_var("descr", "Tests the parse_error conversion to strings");
+}
+ATF_TEST_CASE_BODY(parse_error_to_string)
+{
+ using tools::parser::parse_error;
+
+ const parse_error e(123, "This is the message");
+ ATF_REQUIRE_EQ("123: This is the message", std::string(e));
+}
+
+// ------------------------------------------------------------------------
+// Tests for the "parse_errors" class.
+// ------------------------------------------------------------------------
+
+ATF_TEST_CASE(parse_errors_what);
+ATF_TEST_CASE_HEAD(parse_errors_what)
+{
+ set_md_var("descr", "Tests the parse_errors description");
+}
+ATF_TEST_CASE_BODY(parse_errors_what)
+{
+ using tools::parser::parse_error;
+ using tools::parser::parse_errors;
+
+ parse_errors es;
+ es.push_back(parse_error(2, "Second error"));
+ es.push_back(parse_error(1, "First error"));
+
+ ATF_REQUIRE_EQ("2: Second error\n1: First error", std::string(es.what()));
+}
+
+// ------------------------------------------------------------------------
+// Tests for the "token" class.
+// ------------------------------------------------------------------------
+
+ATF_TEST_CASE(token_getters);
+ATF_TEST_CASE_HEAD(token_getters)
+{
+ set_md_var("descr", "Tests the token getters");
+}
+ATF_TEST_CASE_BODY(token_getters)
+{
+ using tools::parser::token;
+
+ {
+ token t(10, 0);
+ ATF_REQUIRE_EQ(t.lineno(), 10);
+ ATF_REQUIRE_EQ(t.type(), 0);
+ ATF_REQUIRE(t.text().empty());
+ }
+
+ {
+ token t(10, 0, "foo");
+ ATF_REQUIRE_EQ(t.lineno(), 10);
+ ATF_REQUIRE_EQ(t.type(), 0);
+ ATF_REQUIRE_EQ(t.text(), "foo");
+ }
+
+ {
+ token t(20, 1);
+ ATF_REQUIRE_EQ(t.lineno(), 20);
+ ATF_REQUIRE_EQ(t.type(), 1);
+ ATF_REQUIRE(t.text().empty());
+ }
+
+ {
+ token t(20, 1, "bar");
+ ATF_REQUIRE_EQ(t.lineno(), 20);
+ ATF_REQUIRE_EQ(t.type(), 1);
+ ATF_REQUIRE_EQ(t.text(), "bar");
+ }
+}
+
+// ------------------------------------------------------------------------
+// Tests for the "tokenizer" class.
+// ------------------------------------------------------------------------
+
+#define EXPECT(tkz, ttype, ttext) \
+ do { \
+ tools::parser::token t = tkz.next(); \
+ ATF_REQUIRE(t.type() == ttype); \
+ ATF_REQUIRE_EQ(t.text(), ttext); \
+ } while (false);
+
+namespace minimal {
+
+ static const tools::parser::token_type eof_type = 0;
+ static const tools::parser::token_type nl_type = 1;
+ static const tools::parser::token_type word_type = 2;
+
+ class tokenizer : public tools::parser::tokenizer< std::istream > {
+ public:
+ tokenizer(std::istream& is, bool skipws) :
+ tools::parser::tokenizer< std::istream >
+ (is, skipws, eof_type, nl_type, word_type)
+ {
+ }
+ };
+
+}
+
+namespace delims {
+
+ static const tools::parser::token_type eof_type = 0;
+ static const tools::parser::token_type nl_type = 1;
+ static const tools::parser::token_type word_type = 2;
+ static const tools::parser::token_type plus_type = 3;
+ static const tools::parser::token_type minus_type = 4;
+ static const tools::parser::token_type equal_type = 5;
+
+ class tokenizer : public tools::parser::tokenizer< std::istream > {
+ public:
+ tokenizer(std::istream& is, bool skipws) :
+ tools::parser::tokenizer< std::istream >
+ (is, skipws, eof_type, nl_type, word_type)
+ {
+ add_delim('+', plus_type);
+ add_delim('-', minus_type);
+ add_delim('=', equal_type);
+ }
+ };
+
+}
+
+namespace keywords {
+
+ static const tools::parser::token_type eof_type = 0;
+ static const tools::parser::token_type nl_type = 1;
+ static const tools::parser::token_type word_type = 2;
+ static const tools::parser::token_type var_type = 3;
+ static const tools::parser::token_type loop_type = 4;
+ static const tools::parser::token_type endloop_type = 5;
+
+ class tokenizer : public tools::parser::tokenizer< std::istream > {
+ public:
+ tokenizer(std::istream& is, bool skipws) :
+ tools::parser::tokenizer< std::istream >
+ (is, skipws, eof_type, nl_type, word_type)
+ {
+ add_keyword("var", var_type);
+ add_keyword("loop", loop_type);
+ add_keyword("endloop", endloop_type);
+ }
+ };
+
+}
+
+namespace quotes {
+
+ static const tools::parser::token_type eof_type = 0;
+ static const tools::parser::token_type nl_type = 1;
+ static const tools::parser::token_type word_type = 2;
+ static const tools::parser::token_type dblquote_type = 3;
+
+ class tokenizer : public tools::parser::tokenizer< std::istream > {
+ public:
+ tokenizer(std::istream& is, bool skipws) :
+ tools::parser::tokenizer< std::istream >
+ (is, skipws, eof_type, nl_type, word_type)
+ {
+ add_quote('"', dblquote_type);
+ }
+ };
+
+}
+
+ATF_TEST_CASE(tokenizer_minimal_nows);
+ATF_TEST_CASE_HEAD(tokenizer_minimal_nows)
+{
+ set_md_var("descr", "Tests the tokenizer class using a minimal parser "
+ "and not skipping whitespace");
+}
+ATF_TEST_CASE_BODY(tokenizer_minimal_nows)
+{
+ using namespace minimal;
+
+ {
+ std::istringstream iss("");
+ tokenizer mt(iss, false);
+
+ EXPECT(mt, eof_type, "<<EOF>>");
+ EXPECT(mt, eof_type, "<<EOF>>");
+ }
+
+ {
+ std::istringstream iss("\n");
+ tokenizer mt(iss, false);
+
+ EXPECT(mt, nl_type, "<<NEWLINE>>");
+ EXPECT(mt, eof_type, "<<EOF>>");
+ EXPECT(mt, eof_type, "<<EOF>>");
+ }
+
+ {
+ std::istringstream iss("\n\n\n");
+ tokenizer mt(iss, false);
+
+ EXPECT(mt, nl_type, "<<NEWLINE>>");
+ EXPECT(mt, nl_type, "<<NEWLINE>>");
+ EXPECT(mt, nl_type, "<<NEWLINE>>");
+ EXPECT(mt, eof_type, "<<EOF>>");
+ EXPECT(mt, eof_type, "<<EOF>>");
+ }
+
+ {
+ std::istringstream iss("line 1");
+ tokenizer mt(iss, false);
+
+ EXPECT(mt, word_type, "line 1");
+ EXPECT(mt, eof_type, "<<EOF>>");
+ EXPECT(mt, eof_type, "<<EOF>>");
+ }
+
+ {
+ std::istringstream iss("line 1\n");
+ tokenizer mt(iss, false);
+
+ EXPECT(mt, word_type, "line 1");
+ EXPECT(mt, nl_type, "<<NEWLINE>>");
+ EXPECT(mt, eof_type, "<<EOF>>");
+ EXPECT(mt, eof_type, "<<EOF>>");
+ }
+
+ {
+ std::istringstream iss("line 1\nline 2");
+ tokenizer mt(iss, false);
+
+ EXPECT(mt, word_type, "line 1");
+ EXPECT(mt, nl_type, "<<NEWLINE>>");
+ EXPECT(mt, word_type, "line 2");
+ EXPECT(mt, eof_type, "<<EOF>>");
+ EXPECT(mt, eof_type, "<<EOF>>");
+ }
+
+ {
+ std::istringstream iss("line 1\nline 2\nline 3\n");
+ tokenizer mt(iss, false);
+
+ EXPECT(mt, word_type, "line 1");
+ EXPECT(mt, nl_type, "<<NEWLINE>>");
+ EXPECT(mt, word_type, "line 2");
+ EXPECT(mt, nl_type, "<<NEWLINE>>");
+ EXPECT(mt, word_type, "line 3");
+ EXPECT(mt, nl_type, "<<NEWLINE>>");
+ EXPECT(mt, eof_type, "<<EOF>>");
+ EXPECT(mt, eof_type, "<<EOF>>");
+ }
+}
+
+ATF_TEST_CASE(tokenizer_minimal_ws);
+ATF_TEST_CASE_HEAD(tokenizer_minimal_ws)
+{
+ set_md_var("descr", "Tests the tokenizer class using a minimal parser "
+ "and skipping whitespace");
+}
+ATF_TEST_CASE_BODY(tokenizer_minimal_ws)
+{
+ using namespace minimal;
+
+ {
+ std::istringstream iss("");
+ minimal::tokenizer mt(iss, true);
+
+ EXPECT(mt, eof_type, "<<EOF>>");
+ EXPECT(mt, eof_type, "<<EOF>>");
+ }
+
+ {
+ std::istringstream iss(" \t ");
+ tokenizer mt(iss, true);
+
+ EXPECT(mt, eof_type, "<<EOF>>");
+ EXPECT(mt, eof_type, "<<EOF>>");
+ }
+
+ {
+ std::istringstream iss("\n");
+ tokenizer mt(iss, true);
+
+ EXPECT(mt, nl_type, "<<NEWLINE>>");
+ EXPECT(mt, eof_type, "<<EOF>>");
+ EXPECT(mt, eof_type, "<<EOF>>");
+ }
+
+ {
+ std::istringstream iss(" \t \n \t ");
+ tokenizer mt(iss, true);
+
+ EXPECT(mt, nl_type, "<<NEWLINE>>");
+ EXPECT(mt, eof_type, "<<EOF>>");
+ EXPECT(mt, eof_type, "<<EOF>>");
+ }
+
+ {
+ std::istringstream iss("\n\n\n");
+ tokenizer mt(iss, true);
+
+ EXPECT(mt, nl_type, "<<NEWLINE>>");
+ EXPECT(mt, nl_type, "<<NEWLINE>>");
+ EXPECT(mt, nl_type, "<<NEWLINE>>");
+ EXPECT(mt, eof_type, "<<EOF>>");
+ EXPECT(mt, eof_type, "<<EOF>>");
+ }
+
+ {
+ std::istringstream iss("line 1");
+ tokenizer mt(iss, true);
+
+ EXPECT(mt, word_type, "line");
+ EXPECT(mt, word_type, "1");
+ EXPECT(mt, eof_type, "<<EOF>>");
+ EXPECT(mt, eof_type, "<<EOF>>");
+ }
+
+ {
+ std::istringstream iss(" \tline\t 1\t");
+ tokenizer mt(iss, true);
+
+ EXPECT(mt, word_type, "line");
+ EXPECT(mt, word_type, "1");
+ EXPECT(mt, eof_type, "<<EOF>>");
+ EXPECT(mt, eof_type, "<<EOF>>");
+ }
+
+ {
+ std::istringstream iss("line 1\n");
+ tokenizer mt(iss, true);
+
+ EXPECT(mt, word_type, "line");
+ EXPECT(mt, word_type, "1");
+ EXPECT(mt, nl_type, "<<NEWLINE>>");
+ EXPECT(mt, eof_type, "<<EOF>>");
+ EXPECT(mt, eof_type, "<<EOF>>");
+ }
+
+ {
+ std::istringstream iss("line 1\nline 2");
+ tokenizer mt(iss, true);
+
+ EXPECT(mt, word_type, "line");
+ EXPECT(mt, word_type, "1");
+ EXPECT(mt, nl_type, "<<NEWLINE>>");
+ EXPECT(mt, word_type, "line");
+ EXPECT(mt, word_type, "2");
+ EXPECT(mt, eof_type, "<<EOF>>");
+ EXPECT(mt, eof_type, "<<EOF>>");
+ }
+
+ {
+ std::istringstream iss("line 1\nline 2\nline 3\n");
+ tokenizer mt(iss, true);
+
+ EXPECT(mt, word_type, "line");
+ EXPECT(mt, word_type, "1");
+ EXPECT(mt, nl_type, "<<NEWLINE>>");
+ EXPECT(mt, word_type, "line");
+ EXPECT(mt, word_type, "2");
+ EXPECT(mt, nl_type, "<<NEWLINE>>");
+ EXPECT(mt, word_type, "line");
+ EXPECT(mt, word_type, "3");
+ EXPECT(mt, nl_type, "<<NEWLINE>>");
+ EXPECT(mt, eof_type, "<<EOF>>");
+ EXPECT(mt, eof_type, "<<EOF>>");
+ }
+
+ {
+ std::istringstream iss(" \t line \t 1\n\tline\t2\n line 3 \n");
+ tokenizer mt(iss, true);
+
+ EXPECT(mt, word_type, "line");
+ EXPECT(mt, word_type, "1");
+ EXPECT(mt, nl_type, "<<NEWLINE>>");
+ EXPECT(mt, word_type, "line");
+ EXPECT(mt, word_type, "2");
+ EXPECT(mt, nl_type, "<<NEWLINE>>");
+ EXPECT(mt, word_type, "line");
+ EXPECT(mt, word_type, "3");
+ EXPECT(mt, nl_type, "<<NEWLINE>>");
+ EXPECT(mt, eof_type, "<<EOF>>");
+ EXPECT(mt, eof_type, "<<EOF>>");
+ }
+}
+
+ATF_TEST_CASE(tokenizer_delims_nows);
+ATF_TEST_CASE_HEAD(tokenizer_delims_nows)
+{
+ set_md_var("descr", "Tests the tokenizer class using a parser with some "
+ "additional delimiters and not skipping whitespace");
+}
+ATF_TEST_CASE_BODY(tokenizer_delims_nows)
+{
+ using namespace delims;
+
+ {
+ std::istringstream iss("+-=");
+ tokenizer mt(iss, false);
+
+ EXPECT(mt, plus_type, "+");
+ EXPECT(mt, minus_type, "-");
+ EXPECT(mt, equal_type, "=");
+ EXPECT(mt, eof_type, "<<EOF>>");
+ EXPECT(mt, eof_type, "<<EOF>>");
+ }
+
+ {
+ std::istringstream iss("+++");
+ tokenizer mt(iss, false);
+
+ EXPECT(mt, plus_type, "+");
+ EXPECT(mt, plus_type, "+");
+ EXPECT(mt, plus_type, "+");
+ EXPECT(mt, eof_type, "<<EOF>>");
+ EXPECT(mt, eof_type, "<<EOF>>");
+ }
+
+ {
+ std::istringstream iss("\n+\n++\n");
+ tokenizer mt(iss, false);
+
+ EXPECT(mt, nl_type, "<<NEWLINE>>");
+ EXPECT(mt, plus_type, "+");
+ EXPECT(mt, nl_type, "<<NEWLINE>>");
+ EXPECT(mt, plus_type, "+");
+ EXPECT(mt, plus_type, "+");
+ EXPECT(mt, nl_type, "<<NEWLINE>>");
+ EXPECT(mt, eof_type, "<<EOF>>");
+ EXPECT(mt, eof_type, "<<EOF>>");
+ }
+
+ {
+ std::istringstream iss("foo+bar=baz");
+ tokenizer mt(iss, false);
+
+ EXPECT(mt, word_type, "foo");
+ EXPECT(mt, plus_type, "+");
+ EXPECT(mt, word_type, "bar");
+ EXPECT(mt, equal_type, "=");
+ EXPECT(mt, word_type, "baz");
+ EXPECT(mt, eof_type, "<<EOF>>");
+ EXPECT(mt, eof_type, "<<EOF>>");
+ }
+
+ {
+ std::istringstream iss(" foo\t+\tbar = baz ");
+ tokenizer mt(iss, false);
+
+ EXPECT(mt, word_type, " foo\t");
+ EXPECT(mt, plus_type, "+");
+ EXPECT(mt, word_type, "\tbar ");
+ EXPECT(mt, equal_type, "=");
+ EXPECT(mt, word_type, " baz ");
+ EXPECT(mt, eof_type, "<<EOF>>");
+ EXPECT(mt, eof_type, "<<EOF>>");
+ }
+}
+
+ATF_TEST_CASE(tokenizer_delims_ws);
+ATF_TEST_CASE_HEAD(tokenizer_delims_ws)
+{
+ set_md_var("descr", "Tests the tokenizer class using a parser with some "
+ "additional delimiters and skipping whitespace");
+}
+ATF_TEST_CASE_BODY(tokenizer_delims_ws)
+{
+ using namespace delims;
+
+ {
+ std::istringstream iss(" foo\t+\tbar = baz ");
+ tokenizer mt(iss, true);
+
+ EXPECT(mt, word_type, "foo");
+ EXPECT(mt, plus_type, "+");
+ EXPECT(mt, word_type, "bar");
+ EXPECT(mt, equal_type, "=");
+ EXPECT(mt, word_type, "baz");
+ EXPECT(mt, eof_type, "<<EOF>>");
+ EXPECT(mt, eof_type, "<<EOF>>");
+ }
+}
+
+ATF_TEST_CASE(tokenizer_keywords_nows);
+ATF_TEST_CASE_HEAD(tokenizer_keywords_nows)
+{
+ set_md_var("descr", "Tests the tokenizer class using a parser with some "
+ "additional keywords and not skipping whitespace");
+}
+ATF_TEST_CASE_BODY(tokenizer_keywords_nows)
+{
+ using namespace keywords;
+
+ {
+ std::istringstream iss("var");
+ tokenizer mt(iss, false);
+
+ EXPECT(mt, var_type, "var");
+ EXPECT(mt, eof_type, "<<EOF>>");
+ EXPECT(mt, eof_type, "<<EOF>>");
+ }
+
+ {
+ std::istringstream iss("va");
+ tokenizer mt(iss, false);
+
+ EXPECT(mt, word_type, "va");
+ EXPECT(mt, eof_type, "<<EOF>>");
+ EXPECT(mt, eof_type, "<<EOF>>");
+ }
+
+ {
+ std::istringstream iss("vara");
+ tokenizer mt(iss, false);
+
+ EXPECT(mt, word_type, "vara");
+ EXPECT(mt, eof_type, "<<EOF>>");
+ EXPECT(mt, eof_type, "<<EOF>>");
+ }
+
+ {
+ std::istringstream iss("var ");
+ tokenizer mt(iss, false);
+
+ EXPECT(mt, word_type, "var ");
+ EXPECT(mt, eof_type, "<<EOF>>");
+ EXPECT(mt, eof_type, "<<EOF>>");
+ }
+
+ {
+ std::istringstream iss("var\nloop\nendloop");
+ tokenizer mt(iss, false);
+
+ EXPECT(mt, var_type, "var");
+ EXPECT(mt, nl_type, "<<NEWLINE>>");
+ EXPECT(mt, loop_type, "loop");
+ EXPECT(mt, nl_type, "<<NEWLINE>>");
+ EXPECT(mt, endloop_type, "endloop");
+ EXPECT(mt, eof_type, "<<EOF>>");
+ EXPECT(mt, eof_type, "<<EOF>>");
+ }
+}
+
+ATF_TEST_CASE(tokenizer_keywords_ws);
+ATF_TEST_CASE_HEAD(tokenizer_keywords_ws)
+{
+ set_md_var("descr", "Tests the tokenizer class using a parser with some "
+ "additional keywords and not skipping whitespace");
+}
+ATF_TEST_CASE_BODY(tokenizer_keywords_ws)
+{
+ using namespace keywords;
+
+ {
+ std::istringstream iss("var ");
+ tokenizer mt(iss, true);
+
+ EXPECT(mt, var_type, "var");
+ EXPECT(mt, eof_type, "<<EOF>>");
+ EXPECT(mt, eof_type, "<<EOF>>");
+ }
+
+ {
+ std::istringstream iss(" var \n\tloop\t\n \tendloop \t");
+ tokenizer mt(iss, true);
+
+ EXPECT(mt, var_type, "var");
+ EXPECT(mt, nl_type, "<<NEWLINE>>");
+ EXPECT(mt, loop_type, "loop");
+ EXPECT(mt, nl_type, "<<NEWLINE>>");
+ EXPECT(mt, endloop_type, "endloop");
+ EXPECT(mt, eof_type, "<<EOF>>");
+ EXPECT(mt, eof_type, "<<EOF>>");
+ }
+
+ {
+ std::istringstream iss("var loop endloop");
+ tokenizer mt(iss, true);
+
+ EXPECT(mt, var_type, "var");
+ EXPECT(mt, loop_type, "loop");
+ EXPECT(mt, endloop_type, "endloop");
+ EXPECT(mt, eof_type, "<<EOF>>");
+ EXPECT(mt, eof_type, "<<EOF>>");
+ }
+}
+
+ATF_TEST_CASE(tokenizer_quotes_nows);
+ATF_TEST_CASE_HEAD(tokenizer_quotes_nows)
+{
+ set_md_var("descr", "Tests the tokenizer class using a parser with "
+ "quoted strings and not skipping whitespace");
+}
+ATF_TEST_CASE_BODY(tokenizer_quotes_nows)
+{
+ using namespace quotes;
+
+ {
+ std::istringstream iss("var");
+ tokenizer mt(iss, false);
+
+ EXPECT(mt, word_type, "var");
+ EXPECT(mt, eof_type, "<<EOF>>");
+ EXPECT(mt, eof_type, "<<EOF>>");
+ }
+
+ {
+ std::istringstream iss("\"var\"");
+ tokenizer mt(iss, false);
+
+ EXPECT(mt, word_type, "var");
+ EXPECT(mt, eof_type, "<<EOF>>");
+ EXPECT(mt, eof_type, "<<EOF>>");
+ }
+
+ {
+ std::istringstream iss("var1\"var2\"");
+ tokenizer mt(iss, false);
+
+ EXPECT(mt, word_type, "var1");
+ EXPECT(mt, word_type, "var2");
+ EXPECT(mt, eof_type, "<<EOF>>");
+ EXPECT(mt, eof_type, "<<EOF>>");
+ }
+
+ {
+ std::istringstream iss("var1\" var2 \"");
+ tokenizer mt(iss, false);
+
+ EXPECT(mt, word_type, "var1");
+ EXPECT(mt, word_type, " var2 ");
+ EXPECT(mt, eof_type, "<<EOF>>");
+ EXPECT(mt, eof_type, "<<EOF>>");
+ }
+}
+
+ATF_TEST_CASE(tokenizer_quotes_ws);
+ATF_TEST_CASE_HEAD(tokenizer_quotes_ws)
+{
+ set_md_var("descr", "Tests the tokenizer class using a parser with "
+ "quoted strings and skipping whitespace");
+}
+ATF_TEST_CASE_BODY(tokenizer_quotes_ws)
+{
+ using namespace quotes;
+
+ {
+ std::istringstream iss(" var ");
+ tokenizer mt(iss, true);
+
+ EXPECT(mt, word_type, "var");
+ EXPECT(mt, eof_type, "<<EOF>>");
+ EXPECT(mt, eof_type, "<<EOF>>");
+ }
+
+ {
+ std::istringstream iss(" \"var\" ");
+ tokenizer mt(iss, true);
+
+ EXPECT(mt, word_type, "var");
+ EXPECT(mt, eof_type, "<<EOF>>");
+ EXPECT(mt, eof_type, "<<EOF>>");
+ }
+
+ {
+ std::istringstream iss(" var1 \"var2\" ");
+ tokenizer mt(iss, true);
+
+ EXPECT(mt, word_type, "var1");
+ EXPECT(mt, word_type, "var2");
+ EXPECT(mt, eof_type, "<<EOF>>");
+ EXPECT(mt, eof_type, "<<EOF>>");
+ }
+
+ {
+ std::istringstream iss(" var1 \" var2 \" ");
+ tokenizer mt(iss, true);
+
+ EXPECT(mt, word_type, "var1");
+ EXPECT(mt, word_type, " var2 ");
+ EXPECT(mt, eof_type, "<<EOF>>");
+ EXPECT(mt, eof_type, "<<EOF>>");
+ }
+}
+
+// ------------------------------------------------------------------------
+// Tests for the headers parser.
+// ------------------------------------------------------------------------
+
+class header_reader {
+ std::istream& m_is;
+
+public:
+ header_reader(std::istream& is) :
+ m_is(is)
+ {
+ }
+
+ void
+ read(void)
+ {
+ std::pair< size_t, tools::parser::headers_map > hml =
+ tools::parser::read_headers(m_is, 1);
+ tools::parser::validate_content_type(hml.second,
+ "application/X-atf-headers-test", 1234);
+ }
+
+ std::vector< std::string > m_calls;
+};
+
+ATF_TEST_CASE_WITHOUT_HEAD(headers_1);
+ATF_TEST_CASE_BODY(headers_1)
+{
+ const char* input =
+ ""
+ ;
+
+ const char* exp_calls[] = {
+ NULL
+ };
+
+ const char* exp_errors[] = {
+ "1: Unexpected token `<<EOF>>'; expected a header name",
+ NULL
+ };
+
+ do_parser_test< header_reader >(input, exp_calls, exp_errors);
+}
+
+ATF_TEST_CASE_WITHOUT_HEAD(headers_2);
+ATF_TEST_CASE_BODY(headers_2)
+{
+ const char* input =
+ "Content-Type\n"
+ ;
+
+ const char* exp_calls[] = {
+ NULL
+ };
+
+ const char* exp_errors[] = {
+ "1: Unexpected token `<<NEWLINE>>'; expected `:'",
+ NULL
+ };
+
+ do_parser_test< header_reader >(input, exp_calls, exp_errors);
+}
+
+ATF_TEST_CASE_WITHOUT_HEAD(headers_3);
+ATF_TEST_CASE_BODY(headers_3)
+{
+ const char* input =
+ "Content-Type:\n"
+ ;
+
+ const char* exp_calls[] = {
+ NULL
+ };
+
+ const char* exp_errors[] = {
+ "1: Unexpected token `<<NEWLINE>>'; expected a textual value",
+ NULL
+ };
+
+ do_parser_test< header_reader >(input, exp_calls, exp_errors);
+}
+
+ATF_TEST_CASE_WITHOUT_HEAD(headers_4);
+ATF_TEST_CASE_BODY(headers_4)
+{
+ const char* input =
+ "Content-Type: application/X-atf-headers-test\n"
+ ;
+
+ const char* exp_calls[] = {
+ NULL
+ };
+
+ const char* exp_errors[] = {
+ "2: Unexpected token `<<EOF>>'; expected a header name",
+ NULL
+ };
+
+ do_parser_test< header_reader >(input, exp_calls, exp_errors);
+}
+
+ATF_TEST_CASE_WITHOUT_HEAD(headers_5);
+ATF_TEST_CASE_BODY(headers_5)
+{
+ const char* input =
+ "Content-Type: application/X-atf-headers-test;\n"
+ ;
+
+ const char* exp_calls[] = {
+ NULL
+ };
+
+ const char* exp_errors[] = {
+ "1: Unexpected token `<<NEWLINE>>'; expected an attribute name",
+ NULL
+ };
+
+ do_parser_test< header_reader >(input, exp_calls, exp_errors);
+}
+
+ATF_TEST_CASE_WITHOUT_HEAD(headers_6);
+ATF_TEST_CASE_BODY(headers_6)
+{
+ const char* input =
+ "Content-Type: application/X-atf-headers-test; version\n"
+ ;
+
+ const char* exp_calls[] = {
+ NULL
+ };
+
+ const char* exp_errors[] = {
+ "1: Unexpected token `<<NEWLINE>>'; expected `='",
+ NULL
+ };
+
+ do_parser_test< header_reader >(input, exp_calls, exp_errors);
+}
+
+ATF_TEST_CASE_WITHOUT_HEAD(headers_7);
+ATF_TEST_CASE_BODY(headers_7)
+{
+ const char* input =
+ "Content-Type: application/X-atf-headers-test; version=\n"
+ ;
+
+ const char* exp_calls[] = {
+ NULL
+ };
+
+ const char* exp_errors[] = {
+ "1: Unexpected token `<<NEWLINE>>'; expected word or quoted string",
+ NULL
+ };
+
+ do_parser_test< header_reader >(input, exp_calls, exp_errors);
+}
+
+ATF_TEST_CASE_WITHOUT_HEAD(headers_8);
+ATF_TEST_CASE_BODY(headers_8)
+{
+ const char* input =
+ "Content-Type: application/X-atf-headers-test; version=\"1234\n"
+ ;
+
+ const char* exp_calls[] = {
+ NULL
+ };
+
+ const char* exp_errors[] = {
+ "1: Missing double quotes before end of line",
+ NULL
+ };
+
+ do_parser_test< header_reader >(input, exp_calls, exp_errors);
+}
+
+ATF_TEST_CASE_WITHOUT_HEAD(headers_9);
+ATF_TEST_CASE_BODY(headers_9)
+{
+ const char* input =
+ "Content-Type: application/X-atf-headers-test; version=1234\"\n"
+ ;
+
+ const char* exp_calls[] = {
+ NULL
+ };
+
+ const char* exp_errors[] = {
+ "1: Missing double quotes before end of line",
+ NULL
+ };
+
+ do_parser_test< header_reader >(input, exp_calls, exp_errors);
+}
+
+ATF_TEST_CASE_WITHOUT_HEAD(headers_10);
+ATF_TEST_CASE_BODY(headers_10)
+{
+ const char* input =
+ "Content-Type: application/X-atf-headers-test; version=1234\n"
+ ;
+
+ const char* exp_calls[] = {
+ NULL
+ };
+
+ const char* exp_errors[] = {
+ "2: Unexpected token `<<EOF>>'; expected a header name",
+ NULL
+ };
+
+ do_parser_test< header_reader >(input, exp_calls, exp_errors);
+}
+
+ATF_TEST_CASE_WITHOUT_HEAD(headers_11);
+ATF_TEST_CASE_BODY(headers_11)
+{
+ const char* input =
+ "Content-Type: application/X-atf-headers-test; version=\"1234\"\n"
+ ;
+
+ const char* exp_calls[] = {
+ NULL
+ };
+
+ const char* exp_errors[] = {
+ "2: Unexpected token `<<EOF>>'; expected a header name",
+ NULL
+ };
+
+ do_parser_test< header_reader >(input, exp_calls, exp_errors);
+}
+
+ATF_TEST_CASE_WITHOUT_HEAD(headers_12);
+ATF_TEST_CASE_BODY(headers_12)
+{
+ const char* input =
+ "Content-Type: application/X-atf-headers-test; version=\"1234\"\n"
+ "a b\n"
+ "a-b:\n"
+ "a-b: foo;\n"
+ "a-b: foo; var\n"
+ "a-b: foo; var=\n"
+ "a-b: foo; var=\"a\n"
+ "a-b: foo; var=a\"\n"
+ "a-b: foo; var=\"a\";\n"
+ "a-b: foo; var=\"a\"; second\n"
+ "a-b: foo; var=\"a\"; second=\n"
+ "a-b: foo; var=\"a\"; second=\"b\n"
+ "a-b: foo; var=\"a\"; second=b\"\n"
+ "a-b: foo; var=\"a\"; second=\"b\"\n"
+ ;
+
+ const char* exp_calls[] = {
+ NULL
+ };
+
+ const char* exp_errors[] = {
+ "2: Unexpected token `b'; expected `:'",
+ "3: Unexpected token `<<NEWLINE>>'; expected a textual value",
+ "4: Unexpected token `<<NEWLINE>>'; expected an attribute name",
+ "5: Unexpected token `<<NEWLINE>>'; expected `='",
+ "6: Unexpected token `<<NEWLINE>>'; expected word or quoted string",
+ "7: Missing double quotes before end of line",
+ "8: Missing double quotes before end of line",
+ "9: Unexpected token `<<NEWLINE>>'; expected an attribute name",
+ "10: Unexpected token `<<NEWLINE>>'; expected `='",
+ "11: Unexpected token `<<NEWLINE>>'; expected word or quoted string",
+ "12: Missing double quotes before end of line",
+ "13: Missing double quotes before end of line",
+ NULL
+ };
+
+ do_parser_test< header_reader >(input, exp_calls, exp_errors);
+}
+
+// ------------------------------------------------------------------------
+// Main.
+// ------------------------------------------------------------------------
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ // Add test cases for the "parse_error" class.
+ ATF_ADD_TEST_CASE(tcs, parse_error_to_string);
+
+ // Add test cases for the "parse_errors" class.
+ ATF_ADD_TEST_CASE(tcs, parse_errors_what);
+
+ // Add test cases for the "token" class.
+ ATF_ADD_TEST_CASE(tcs, token_getters);
+
+ // Add test cases for the "tokenizer" class.
+ ATF_ADD_TEST_CASE(tcs, tokenizer_minimal_nows);
+ ATF_ADD_TEST_CASE(tcs, tokenizer_minimal_ws);
+ ATF_ADD_TEST_CASE(tcs, tokenizer_delims_nows);
+ ATF_ADD_TEST_CASE(tcs, tokenizer_delims_ws);
+ ATF_ADD_TEST_CASE(tcs, tokenizer_keywords_nows);
+ ATF_ADD_TEST_CASE(tcs, tokenizer_keywords_ws);
+ ATF_ADD_TEST_CASE(tcs, tokenizer_quotes_nows);
+ ATF_ADD_TEST_CASE(tcs, tokenizer_quotes_ws);
+
+ // Add the tests for the headers parser.
+
+ // Add the test cases for the header file.
+ ATF_ADD_TEST_CASE(tcs, headers_1);
+ ATF_ADD_TEST_CASE(tcs, headers_2);
+ ATF_ADD_TEST_CASE(tcs, headers_3);
+ ATF_ADD_TEST_CASE(tcs, headers_4);
+ ATF_ADD_TEST_CASE(tcs, headers_5);
+ ATF_ADD_TEST_CASE(tcs, headers_6);
+ ATF_ADD_TEST_CASE(tcs, headers_7);
+ ATF_ADD_TEST_CASE(tcs, headers_8);
+ ATF_ADD_TEST_CASE(tcs, headers_9);
+ ATF_ADD_TEST_CASE(tcs, headers_10);
+ ATF_ADD_TEST_CASE(tcs, headers_11);
+ ATF_ADD_TEST_CASE(tcs, headers_12);
+}
diff --git a/unit/atf-src/tools/pass_helper.cpp b/unit/atf-src/tools/pass_helper.cpp
new file mode 100644
index 0000000..4fa75b4
--- /dev/null
+++ b/unit/atf-src/tools/pass_helper.cpp
@@ -0,0 +1,44 @@
+//
+// Automated Testing Framework (atf)
+//
+// Copyright (c) 2007 The NetBSD Foundation, Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
+// CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+// IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
+// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+// IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+
+#include <atf-c++.hpp>
+
+ATF_TEST_CASE(main);
+ATF_TEST_CASE_HEAD(main)
+{
+ set_md_var("descr", "Helper test case that always passes");
+}
+ATF_TEST_CASE_BODY(main)
+{
+}
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ ATF_ADD_TEST_CASE(tcs, main);
+}
diff --git a/unit/atf-src/tools/process.cpp b/unit/atf-src/tools/process.cpp
new file mode 100644
index 0000000..987a60e
--- /dev/null
+++ b/unit/atf-src/tools/process.cpp
@@ -0,0 +1,492 @@
+//
+// Automated Testing Framework (atf)
+//
+// Copyright (c) 2008 The NetBSD Foundation, Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
+// CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+// IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
+// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+// IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+
+extern "C" {
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include <fcntl.h>
+#include <signal.h>
+}
+
+#include <cassert>
+#include <cstdarg>
+#include <cerrno>
+#include <cstring>
+#include <iostream>
+
+#include "defs.hpp"
+#include "exceptions.hpp"
+#include "text.hpp"
+#include "process.hpp"
+
+namespace detail = tools::process::detail;
+namespace impl = tools::process;
+#define IMPL_NAME "tools::process"
+
+// ------------------------------------------------------------------------
+// Auxiliary functions.
+// ------------------------------------------------------------------------
+
+template< class C >
+tools::auto_array< const char* >
+collection_to_argv(const C& c)
+{
+ tools::auto_array< const char* > argv(new const char*[c.size() + 1]);
+
+ std::size_t pos = 0;
+ for (typename C::const_iterator iter = c.begin(); iter != c.end();
+ iter++) {
+ argv[pos] = (*iter).c_str();
+ pos++;
+ }
+ assert(pos == c.size());
+ argv[pos] = NULL;
+
+ return argv;
+}
+
+template< class C >
+C
+argv_to_collection(const char* const* argv)
+{
+ C c;
+
+ for (const char* const* iter = argv; *iter != NULL; iter++)
+ c.push_back(std::string(*iter));
+
+ return c;
+}
+
+static
+void
+safe_dup(const int oldfd, const int newfd)
+{
+ if (oldfd != newfd) {
+ if (dup2(oldfd, newfd) == -1) {
+ throw tools::system_error(IMPL_NAME "::safe_dup",
+ "Could not allocate file descriptor",
+ errno);
+ } else {
+ ::close(oldfd);
+ }
+ }
+}
+
+static
+int
+const_execvp(const char *file, const char *const *argv)
+{
+#define UNCONST(a) ((void *)(unsigned long)(const void *)(a))
+ return ::execvp(file, (char* const*)(UNCONST(argv)));
+#undef UNCONST
+}
+
+void
+detail::do_exec(void *v)
+{
+ struct exec_args *ea = reinterpret_cast<struct exec_args *>(v);
+
+ if (ea->m_prehook != NULL)
+ ea->m_prehook();
+
+ const int ret = const_execvp(ea->m_prog.c_str(), ea->m_argv.exec_argv());
+ const int errnocopy = errno;
+ assert(ret == -1);
+ std::cerr << "exec(" << ea->m_prog.str() << ") failed: "
+ << std::strerror(errnocopy) << "\n";
+ std::exit(EXIT_FAILURE);
+}
+
+// ------------------------------------------------------------------------
+// The "argv_array" type.
+// ------------------------------------------------------------------------
+
+impl::argv_array::argv_array(void) :
+ m_exec_argv(collection_to_argv(m_args))
+{
+}
+
+impl::argv_array::argv_array(const char* arg1, ...)
+{
+ m_args.push_back(arg1);
+
+ {
+ va_list ap;
+ const char* nextarg;
+
+ va_start(ap, arg1);
+ while ((nextarg = va_arg(ap, const char*)) != NULL)
+ m_args.push_back(nextarg);
+ va_end(ap);
+ }
+
+ ctor_init_exec_argv();
+}
+
+impl::argv_array::argv_array(const char* const* ca) :
+ m_args(argv_to_collection< args_vector >(ca)),
+ m_exec_argv(collection_to_argv(m_args))
+{
+}
+
+impl::argv_array::argv_array(const argv_array& a) :
+ m_args(a.m_args),
+ m_exec_argv(collection_to_argv(m_args))
+{
+}
+
+void
+impl::argv_array::ctor_init_exec_argv(void)
+{
+ m_exec_argv = collection_to_argv(m_args);
+}
+
+const char* const*
+impl::argv_array::exec_argv(void)
+ const
+{
+ return m_exec_argv.get();
+}
+
+impl::argv_array::size_type
+impl::argv_array::size(void)
+ const
+{
+ return m_args.size();
+}
+
+const char*
+impl::argv_array::operator[](int idx)
+ const
+{
+ return m_args[idx].c_str();
+}
+
+impl::argv_array::const_iterator
+impl::argv_array::begin(void)
+ const
+{
+ return m_args.begin();
+}
+
+impl::argv_array::const_iterator
+impl::argv_array::end(void)
+ const
+{
+ return m_args.end();
+}
+
+impl::argv_array&
+impl::argv_array::operator=(const argv_array& a)
+{
+ if (this != &a) {
+ m_args = a.m_args;
+ m_exec_argv = collection_to_argv(m_args);
+ }
+ return *this;
+}
+
+// ------------------------------------------------------------------------
+// The "stream" types.
+// ------------------------------------------------------------------------
+
+impl::stream_capture::stream_capture(void)
+{
+ for (int i = 0; i < 2; i++)
+ m_pipefds[i] = -1;
+}
+
+impl::stream_capture::~stream_capture(void)
+{
+ for (int i = 0; i < 2; i++)
+ if (m_pipefds[i] != -1)
+ ::close(m_pipefds[i]);
+}
+
+void
+impl::stream_capture::prepare(void)
+{
+ if (pipe(m_pipefds) == -1)
+ throw system_error(IMPL_NAME "::stream_capture::prepare",
+ "Failed to create pipe", errno);
+}
+
+int
+impl::stream_capture::connect_parent(void)
+{
+ ::close(m_pipefds[1]); m_pipefds[1] = -1;
+ const int fd = m_pipefds[0];
+ m_pipefds[0] = -1;
+ return fd;
+}
+
+void
+impl::stream_capture::connect_child(const int fd)
+{
+ ::close(m_pipefds[0]); m_pipefds[0] = -1;
+ if (m_pipefds[1] != fd) {
+ safe_dup(m_pipefds[1], fd);
+ }
+ m_pipefds[1] = -1;
+}
+
+impl::stream_connect::stream_connect(const int src_fd, const int tgt_fd) :
+ m_src_fd(src_fd), m_tgt_fd(tgt_fd)
+{
+}
+
+void
+impl::stream_connect::prepare(void)
+{
+}
+
+int
+impl::stream_connect::connect_parent(void)
+{
+ return -1;
+}
+
+void
+impl::stream_connect::connect_child(const int fd ATF_DEFS_ATTRIBUTE_UNUSED)
+{
+ safe_dup(m_tgt_fd, m_src_fd);
+}
+
+impl::stream_inherit::stream_inherit(void)
+{
+}
+
+void
+impl::stream_inherit::prepare(void)
+{
+}
+
+int
+impl::stream_inherit::connect_parent(void)
+{
+ return -1;
+}
+
+void
+impl::stream_inherit::connect_child(const int fd ATF_DEFS_ATTRIBUTE_UNUSED)
+{
+}
+
+impl::stream_redirect_fd::stream_redirect_fd(const int fd) :
+ m_fd(fd)
+{
+}
+
+void
+impl::stream_redirect_fd::prepare(void)
+{
+}
+
+int
+impl::stream_redirect_fd::connect_parent(void)
+{
+ return -1;
+}
+
+void
+impl::stream_redirect_fd::connect_child(const int fd)
+{
+ safe_dup(m_fd, fd);
+}
+
+impl::stream_redirect_path::stream_redirect_path(const tools::fs::path& p) :
+ m_path(p)
+{
+}
+
+void
+impl::stream_redirect_path::prepare(void)
+{
+}
+
+int
+impl::stream_redirect_path::connect_parent(void)
+{
+ return -1;
+}
+
+void
+impl::stream_redirect_path::connect_child(const int fd)
+{
+ const int aux = ::open(m_path.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0644);
+ if (aux == -1)
+ throw system_error(IMPL_NAME "::stream_redirect_path::connect_child",
+ "Could not create " + m_path.str(), errno);
+ else
+ safe_dup(aux, fd);
+}
+
+// ------------------------------------------------------------------------
+// The "status" type.
+// ------------------------------------------------------------------------
+
+impl::status::status(int s) :
+ m_status(s)
+{
+}
+
+impl::status::~status(void)
+{
+}
+
+bool
+impl::status::exited(void)
+ const
+{
+ int mutable_status = m_status;
+ return WIFEXITED(mutable_status);
+}
+
+int
+impl::status::exitstatus(void)
+ const
+{
+ assert(exited());
+ int mutable_status = m_status;
+ return WEXITSTATUS(mutable_status);
+}
+
+bool
+impl::status::signaled(void)
+ const
+{
+ int mutable_status = m_status;
+ return WIFSIGNALED(mutable_status);
+}
+
+int
+impl::status::termsig(void)
+ const
+{
+ assert(signaled());
+ int mutable_status = m_status;
+ return WTERMSIG(mutable_status);
+}
+
+bool
+impl::status::coredump(void)
+ const
+{
+ assert(signaled());
+#if defined(WCOREDUMP)
+ int mutable_status = m_status;
+ return WCOREDUMP(mutable_status);
+#else
+ return false;
+#endif
+}
+
+// ------------------------------------------------------------------------
+// The "child" type.
+// ------------------------------------------------------------------------
+
+impl::child::child(const pid_t pid_arg, const int stdout_fd_arg,
+ const int stderr_fd_arg) :
+ m_pid(pid_arg),
+ m_stdout(stdout_fd_arg),
+ m_stderr(stderr_fd_arg),
+ m_waited(false)
+{
+}
+
+impl::child::~child(void)
+{
+ if (!m_waited) {
+ ::kill(m_pid, SIGTERM);
+ (void)wait();
+
+ if (m_stdout != -1)
+ ::close(m_stdout);
+ if (m_stderr != -1)
+ ::close(m_stderr);
+ }
+}
+
+impl::status
+impl::child::wait(void)
+{
+ int s;
+
+ if (::waitpid(m_pid, &s, 0) == -1)
+ throw system_error(IMPL_NAME "::child::wait", "Failed waiting for "
+ "process " + text::to_string(m_pid), errno);
+
+ if (m_stdout != -1)
+ ::close(m_stdout); m_stdout = -1;
+ if (m_stderr != -1)
+ ::close(m_stderr); m_stderr = -1;
+
+ m_waited = true;
+ return status(s);
+}
+
+pid_t
+impl::child::pid(void)
+ const
+{
+ return m_pid;
+}
+
+int
+impl::child::stdout_fd(void)
+{
+ return m_stdout;
+}
+
+int
+impl::child::stderr_fd(void)
+{
+ return m_stderr;
+}
+
+// ------------------------------------------------------------------------
+// Free functions.
+// ------------------------------------------------------------------------
+
+void
+detail::flush_streams(void)
+{
+ // This is a weird hack to ensure that the output of the parent process
+ // is flushed before executing a child which prevents, for example, the
+ // output of the atf-run hooks to appear before the output of atf-run
+ // itself.
+ //
+ // TODO: This should only be executed when inheriting the stdout or
+ // stderr file descriptors. However, the flushing is specific to the
+ // iostreams, so we cannot do it from the C library where all the process
+ // logic is performed. Come up with a better design.
+ std::cout.flush();
+ std::cerr.flush();
+}
diff --git a/unit/atf-src/tools/process.hpp b/unit/atf-src/tools/process.hpp
new file mode 100644
index 0000000..2ebd865
--- /dev/null
+++ b/unit/atf-src/tools/process.hpp
@@ -0,0 +1,324 @@
+//
+// Automated Testing Framework (atf)
+//
+// Copyright (c) 2008 The NetBSD Foundation, Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
+// CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+// IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
+// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+// IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+
+#if !defined(TOOLS_PROCESS_HPP)
+#define TOOLS_PROCESS_HPP
+
+extern "C" {
+#include <sys/types.h>
+
+#include <unistd.h>
+}
+
+#include <cerrno>
+#include <cstdlib>
+#include <iostream>
+#include <string>
+#include <vector>
+
+#include "auto_array.hpp"
+#include "exceptions.hpp"
+#include "fs.hpp"
+
+namespace tools {
+namespace process {
+
+class child;
+class status;
+
+// ------------------------------------------------------------------------
+// The "argv_array" type.
+// ------------------------------------------------------------------------
+
+class argv_array {
+ typedef std::vector< std::string > args_vector;
+ args_vector m_args;
+
+ // TODO: This is immutable, so we should be able to use
+ // std::tr1::shared_array instead when it becomes widely available.
+ // The reason would be to remove all copy constructors and assignment
+ // operators from this class.
+ auto_array< const char* > m_exec_argv;
+ void ctor_init_exec_argv(void);
+
+public:
+ typedef args_vector::const_iterator const_iterator;
+ typedef args_vector::size_type size_type;
+
+ argv_array(void);
+ argv_array(const char*, ...);
+ explicit argv_array(const char* const*);
+ template< class C > explicit argv_array(const C&);
+ argv_array(const argv_array&);
+
+ const char* const* exec_argv(void) const;
+ size_type size(void) const;
+ const char* operator[](int) const;
+
+ const_iterator begin(void) const;
+ const_iterator end(void) const;
+
+ argv_array& operator=(const argv_array&);
+};
+
+template< class C >
+argv_array::argv_array(const C& c)
+{
+ for (typename C::const_iterator iter = c.begin(); iter != c.end();
+ iter++)
+ m_args.push_back(*iter);
+ ctor_init_exec_argv();
+}
+
+// ------------------------------------------------------------------------
+// The "stream" types.
+// ------------------------------------------------------------------------
+
+class stream_capture {
+ int m_pipefds[2];
+
+ // Allow access to the getters.
+ template< class OutStream, class ErrStream > friend
+ child fork(void (*)(void*), OutStream, ErrStream, void*);
+ template< class OutStream, class ErrStream > friend
+ status exec(const tools::fs::path&, const argv_array&,
+ const OutStream&, const ErrStream&, void (*)(void));
+
+ void prepare(void);
+ int connect_parent(void);
+ void connect_child(const int);
+
+public:
+ stream_capture(void);
+ ~stream_capture(void);
+};
+
+class stream_connect {
+ int m_src_fd;
+ int m_tgt_fd;
+
+ // Allow access to the getters.
+ template< class OutStream, class ErrStream > friend
+ child fork(void (*)(void*), OutStream, ErrStream, void*);
+ template< class OutStream, class ErrStream > friend
+ status exec(const tools::fs::path&, const argv_array&,
+ const OutStream&, const ErrStream&, void (*)(void));
+
+ void prepare(void);
+ int connect_parent(void);
+ void connect_child(const int);
+
+public:
+ stream_connect(const int, const int);
+};
+
+class stream_inherit {
+ // Allow access to the getters.
+ template< class OutStream, class ErrStream > friend
+ child fork(void (*)(void*), OutStream, ErrStream, void*);
+ template< class OutStream, class ErrStream > friend
+ status exec(const tools::fs::path&, const argv_array&,
+ const OutStream&, const ErrStream&, void (*)(void));
+
+ void prepare(void);
+ int connect_parent(void);
+ void connect_child(const int);
+
+public:
+ stream_inherit(void);
+};
+
+class stream_redirect_fd {
+ int m_fd;
+
+ // Allow access to the getters.
+ template< class OutStream, class ErrStream > friend
+ child fork(void (*)(void*), OutStream, ErrStream, void*);
+ template< class OutStream, class ErrStream > friend
+ status exec(const tools::fs::path&, const argv_array&,
+ const OutStream&, const ErrStream&, void (*)(void));
+
+ void prepare(void);
+ int connect_parent(void);
+ void connect_child(const int);
+
+public:
+ stream_redirect_fd(const int);
+};
+
+class stream_redirect_path {
+ const tools::fs::path m_path;
+
+ // Allow access to the getters.
+ template< class OutStream, class ErrStream > friend
+ child fork(void (*)(void*), OutStream, ErrStream, void*);
+ template< class OutStream, class ErrStream > friend
+ status exec(const tools::fs::path&, const argv_array&,
+ const OutStream&, const ErrStream&, void (*)(void));
+
+ void prepare(void);
+ int connect_parent(void);
+ void connect_child(const int);
+
+public:
+ stream_redirect_path(const tools::fs::path&);
+};
+
+// ------------------------------------------------------------------------
+// The "status" type.
+// ------------------------------------------------------------------------
+
+class status {
+ int m_status;
+
+ friend class child;
+ template< class OutStream, class ErrStream > friend
+ status exec(const tools::fs::path&, const argv_array&,
+ const OutStream&, const ErrStream&, void (*)(void));
+
+ status(int);
+
+public:
+ ~status(void);
+
+ bool exited(void) const;
+ int exitstatus(void) const;
+
+ bool signaled(void) const;
+ int termsig(void) const;
+ bool coredump(void) const;
+};
+
+// ------------------------------------------------------------------------
+// The "child" type.
+// ------------------------------------------------------------------------
+
+class child {
+ pid_t m_pid;
+
+ int m_stdout;
+ int m_stderr;
+
+ bool m_waited;
+
+ template< class OutStream, class ErrStream > friend
+ child fork(void (*)(void*), OutStream, ErrStream, void*);
+
+ child(const pid_t, const int, const int);
+
+public:
+ ~child(void);
+
+ status wait(void);
+
+ pid_t pid(void) const;
+ int stdout_fd(void);
+ int stderr_fd(void);
+};
+
+// ------------------------------------------------------------------------
+// Free functions.
+// ------------------------------------------------------------------------
+
+namespace detail {
+void flush_streams(void);
+
+struct exec_args {
+ const tools::fs::path m_prog;
+ const argv_array& m_argv;
+ void (*m_prehook)(void);
+};
+
+void do_exec(void *);
+} // namespace detail
+
+// TODO: The void* cookie can probably be templatized, thus also allowing
+// const data structures.
+template< class OutStream, class ErrStream >
+child
+fork(void (*start)(void*), OutStream outsb, ErrStream errsb, void* v)
+{
+ detail::flush_streams();
+
+ outsb.prepare();
+ errsb.prepare();
+
+ pid_t pid = ::fork();
+ if (pid == -1) {
+ throw system_error("tools::process::child::fork",
+ "Failed to fork", errno);
+ } else if (pid == 0) {
+ try {
+ outsb.connect_child(STDOUT_FILENO);
+ errsb.connect_child(STDERR_FILENO);
+ start(v);
+ std::abort();
+ } catch (...) {
+ std::cerr << "Unhandled error while running subprocess\n";
+ std::exit(EXIT_FAILURE);
+ }
+ } else {
+ const int stdout_fd = outsb.connect_parent();
+ const int stderr_fd = errsb.connect_parent();
+ return child(pid, stdout_fd, stderr_fd);
+ }
+}
+
+template< class OutStream, class ErrStream >
+status
+exec(const tools::fs::path& prog, const argv_array& argv,
+ const OutStream& outsb, const ErrStream& errsb,
+ void (*prehook)(void))
+{
+ struct detail::exec_args ea = { prog, argv, prehook };
+ child c = fork(detail::do_exec, outsb, errsb, &ea);
+
+again:
+ try {
+ return c.wait();
+ } catch (const system_error& e) {
+ if (e.code() == EINTR)
+ goto again;
+ else
+ throw e;
+ }
+}
+
+template< class OutStream, class ErrStream >
+status
+exec(const tools::fs::path& prog, const argv_array& argv,
+ const OutStream& outsb, const ErrStream& errsb)
+{
+ return exec(prog, argv, outsb, errsb, NULL);
+}
+
+} // namespace process
+} // namespace tools
+
+#endif // !defined(TOOLS_PROCESS_HPP)
diff --git a/unit/atf-src/tools/process_helpers.c b/unit/atf-src/tools/process_helpers.c
new file mode 100644
index 0000000..61f1b67
--- /dev/null
+++ b/unit/atf-src/tools/process_helpers.c
@@ -0,0 +1,117 @@
+/*
+ * Automated Testing Framework (atf)
+ *
+ * Copyright (c) 2008 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
+ * CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+ * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+ * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/types.h>
+
+#include <assert.h> /* NO_CHECK_STYLE */
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+static
+int
+h_echo(const char *msg)
+{
+ printf("%s\n", msg);
+ return EXIT_SUCCESS;
+}
+
+static
+int
+h_exit_failure(void)
+{
+ return EXIT_FAILURE;
+}
+
+static
+int
+h_exit_signal(void)
+{
+ kill(getpid(), SIGKILL);
+ assert(0); /* NO_CHECK_STYLE */
+ return EXIT_FAILURE;
+}
+
+static
+int
+h_exit_success(void)
+{
+ return EXIT_SUCCESS;
+}
+
+static
+int
+h_stdout_stderr(const char *id)
+{
+ fprintf(stdout, "Line 1 to stdout for %s\n", id);
+ fprintf(stdout, "Line 2 to stdout for %s\n", id);
+ fprintf(stderr, "Line 1 to stderr for %s\n", id);
+ fprintf(stderr, "Line 2 to stderr for %s\n", id);
+
+ return EXIT_SUCCESS;
+}
+
+static
+void
+check_args(const int argc, const char *const argv[], const int required)
+{
+ if (argc < required) {
+ fprintf(stderr, "Usage: %s helper-name [args]\n", argv[0]);
+ exit(EXIT_FAILURE);
+ }
+}
+
+int
+main(int argc, const char *const argv[])
+{
+ int exitcode;
+
+ check_args(argc, argv, 2);
+
+ if (strcmp(argv[1], "echo") == 0) {
+ check_args(argc, argv, 3);
+ exitcode = h_echo(argv[2]);
+ } else if (strcmp(argv[1], "exit-failure") == 0)
+ exitcode = h_exit_failure();
+ else if (strcmp(argv[1], "exit-signal") == 0)
+ exitcode = h_exit_signal();
+ else if (strcmp(argv[1], "exit-success") == 0)
+ exitcode = h_exit_success();
+ else if (strcmp(argv[1], "stdout-stderr") == 0) {
+ check_args(argc, argv, 3);
+ exitcode = h_stdout_stderr(argv[2]);
+ } else {
+ fprintf(stderr, "%s: Unknown helper %s\n", argv[0], argv[1]);
+ exitcode = EXIT_FAILURE;
+ }
+
+ return exitcode;
+}
diff --git a/unit/atf-src/tools/process_test.cpp b/unit/atf-src/tools/process_test.cpp
new file mode 100644
index 0000000..70da919
--- /dev/null
+++ b/unit/atf-src/tools/process_test.cpp
@@ -0,0 +1,360 @@
+//
+// Automated Testing Framework (atf)
+//
+// Copyright (c) 2008 The NetBSD Foundation, Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
+// CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+// IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
+// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+// IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+
+#include <cstdlib>
+#include <cstring>
+
+#include <atf-c++.hpp>
+
+#include "process.hpp"
+#include "test_helpers.hpp"
+
+// TODO: Testing the fork function is a huge task and I'm afraid of
+// copy/pasting tons of stuff from the C version. I'd rather not do that
+// until some code can be shared, which cannot happen until the C++ binding
+// is cleaned by a fair amount. Instead... just rely (at the moment) on
+// the system tests for the tools using this module.
+
+// ------------------------------------------------------------------------
+// Auxiliary functions.
+// ------------------------------------------------------------------------
+
+static
+std::size_t
+array_size(const char* const* array)
+{
+ std::size_t size = 0;
+
+ for (const char* const* ptr = array; *ptr != NULL; ptr++)
+ size++;
+
+ return size;
+}
+
+static
+tools::process::status
+exec_process_helpers(const atf::tests::tc& tc, const char* helper_name)
+{
+ using tools::process::exec;
+
+ const tools::fs::path helpers = tools::fs::path(tc.get_config_var("srcdir")) /
+ "process_helpers";
+
+ std::vector< std::string > argv;
+ argv.push_back(helpers.leaf_name());
+ argv.push_back(helper_name);
+
+ return exec(helpers,
+ tools::process::argv_array(argv),
+ tools::process::stream_inherit(),
+ tools::process::stream_inherit());
+}
+
+// ------------------------------------------------------------------------
+// Tests for the "argv_array" type.
+// ------------------------------------------------------------------------
+
+ATF_TEST_CASE(argv_array_init_carray);
+ATF_TEST_CASE_HEAD(argv_array_init_carray)
+{
+ set_md_var("descr", "Tests that argv_array is correctly constructed "
+ "from a C-style array of strings");
+}
+ATF_TEST_CASE_BODY(argv_array_init_carray)
+{
+ {
+ const char* const carray[] = { NULL };
+ tools::process::argv_array argv(carray);
+
+ ATF_REQUIRE_EQ(argv.size(), 0);
+ }
+
+ {
+ const char* const carray[] = { "arg0", NULL };
+ tools::process::argv_array argv(carray);
+
+ ATF_REQUIRE_EQ(argv.size(), 1);
+ ATF_REQUIRE(std::strcmp(argv[0], carray[0]) == 0);
+ }
+
+ {
+ const char* const carray[] = { "arg0", "arg1", "arg2", NULL };
+ tools::process::argv_array argv(carray);
+
+ ATF_REQUIRE_EQ(argv.size(), 3);
+ ATF_REQUIRE(std::strcmp(argv[0], carray[0]) == 0);
+ ATF_REQUIRE(std::strcmp(argv[1], carray[1]) == 0);
+ ATF_REQUIRE(std::strcmp(argv[2], carray[2]) == 0);
+ }
+}
+
+ATF_TEST_CASE(argv_array_init_col);
+ATF_TEST_CASE_HEAD(argv_array_init_col)
+{
+ set_md_var("descr", "Tests that argv_array is correctly constructed "
+ "from a string collection");
+}
+ATF_TEST_CASE_BODY(argv_array_init_col)
+{
+ {
+ std::vector< std::string > col;
+ tools::process::argv_array argv(col);
+
+ ATF_REQUIRE_EQ(argv.size(), 0);
+ }
+
+ {
+ std::vector< std::string > col;
+ col.push_back("arg0");
+ tools::process::argv_array argv(col);
+
+ ATF_REQUIRE_EQ(argv.size(), 1);
+ ATF_REQUIRE_EQ(argv[0], col[0]);
+ }
+
+ {
+ std::vector< std::string > col;
+ col.push_back("arg0");
+ col.push_back("arg1");
+ col.push_back("arg2");
+ tools::process::argv_array argv(col);
+
+ ATF_REQUIRE_EQ(argv.size(), 3);
+ ATF_REQUIRE_EQ(argv[0], col[0]);
+ ATF_REQUIRE_EQ(argv[1], col[1]);
+ ATF_REQUIRE_EQ(argv[2], col[2]);
+ }
+}
+
+ATF_TEST_CASE(argv_array_init_empty);
+ATF_TEST_CASE_HEAD(argv_array_init_empty)
+{
+ set_md_var("descr", "Tests that argv_array is correctly constructed "
+ "by the default constructor");
+}
+ATF_TEST_CASE_BODY(argv_array_init_empty)
+{
+ tools::process::argv_array argv;
+
+ ATF_REQUIRE_EQ(argv.size(), 0);
+}
+
+ATF_TEST_CASE(argv_array_init_varargs);
+ATF_TEST_CASE_HEAD(argv_array_init_varargs)
+{
+ set_md_var("descr", "Tests that argv_array is correctly constructed "
+ "from a variable list of arguments");
+}
+ATF_TEST_CASE_BODY(argv_array_init_varargs)
+{
+ {
+ tools::process::argv_array argv("arg0", NULL);
+
+ ATF_REQUIRE_EQ(argv.size(), 1);
+ ATF_REQUIRE_EQ(argv[0], std::string("arg0"));
+ }
+
+ {
+ tools::process::argv_array argv("arg0", "arg1", "arg2", NULL);
+
+ ATF_REQUIRE_EQ(argv.size(), 3);
+ ATF_REQUIRE_EQ(argv[0], std::string("arg0"));
+ ATF_REQUIRE_EQ(argv[1], std::string("arg1"));
+ ATF_REQUIRE_EQ(argv[2], std::string("arg2"));
+ }
+}
+
+ATF_TEST_CASE(argv_array_assign);
+ATF_TEST_CASE_HEAD(argv_array_assign)
+{
+ set_md_var("descr", "Tests that assigning an argv_array works");
+}
+ATF_TEST_CASE_BODY(argv_array_assign)
+{
+ using tools::process::argv_array;
+
+ const char* const carray1[] = { "arg1", NULL };
+ const char* const carray2[] = { "arg1", "arg2", NULL };
+
+ std::auto_ptr< argv_array > argv1(new argv_array(carray1));
+ std::auto_ptr< argv_array > argv2(new argv_array(carray2));
+
+ *argv2 = *argv1;
+ ATF_REQUIRE_EQ(argv2->size(), argv1->size());
+ ATF_REQUIRE(std::strcmp((*argv2)[0], (*argv1)[0]) == 0);
+
+ ATF_REQUIRE(argv2->exec_argv() != argv1->exec_argv());
+ argv1.release();
+ {
+ const char* const* eargv2 = argv2->exec_argv();
+ ATF_REQUIRE(std::strcmp(eargv2[0], carray1[0]) == 0);
+ ATF_REQUIRE_EQ(eargv2[1], static_cast< const char* >(NULL));
+ }
+
+ argv2.release();
+}
+
+ATF_TEST_CASE(argv_array_copy);
+ATF_TEST_CASE_HEAD(argv_array_copy)
+{
+ set_md_var("descr", "Tests that copying an argv_array constructed from "
+ "a C-style array of strings works");
+}
+ATF_TEST_CASE_BODY(argv_array_copy)
+{
+ using tools::process::argv_array;
+
+ const char* const carray[] = { "arg0", NULL };
+
+ std::auto_ptr< argv_array > argv1(new argv_array(carray));
+ std::auto_ptr< argv_array > argv2(new argv_array(*argv1));
+
+ ATF_REQUIRE_EQ(argv2->size(), argv1->size());
+ ATF_REQUIRE(std::strcmp((*argv2)[0], (*argv1)[0]) == 0);
+
+ ATF_REQUIRE(argv2->exec_argv() != argv1->exec_argv());
+ argv1.release();
+ {
+ const char* const* eargv2 = argv2->exec_argv();
+ ATF_REQUIRE(std::strcmp(eargv2[0], carray[0]) == 0);
+ ATF_REQUIRE_EQ(eargv2[1], static_cast< const char* >(NULL));
+ }
+
+ argv2.release();
+}
+
+ATF_TEST_CASE(argv_array_exec_argv);
+ATF_TEST_CASE_HEAD(argv_array_exec_argv)
+{
+ set_md_var("descr", "Tests that the exec argv provided by an argv_array "
+ "is correct");
+}
+ATF_TEST_CASE_BODY(argv_array_exec_argv)
+{
+ using tools::process::argv_array;
+
+ {
+ argv_array argv;
+ const char* const* eargv = argv.exec_argv();
+ ATF_REQUIRE_EQ(array_size(eargv), 0);
+ ATF_REQUIRE_EQ(eargv[0], static_cast< const char* >(NULL));
+ }
+
+ {
+ const char* const carray[] = { "arg0", NULL };
+ argv_array argv(carray);
+ const char* const* eargv = argv.exec_argv();
+ ATF_REQUIRE_EQ(array_size(eargv), 1);
+ ATF_REQUIRE(std::strcmp(eargv[0], "arg0") == 0);
+ ATF_REQUIRE_EQ(eargv[1], static_cast< const char* >(NULL));
+ }
+
+ {
+ std::vector< std::string > col;
+ col.push_back("arg0");
+ argv_array argv(col);
+ const char* const* eargv = argv.exec_argv();
+ ATF_REQUIRE_EQ(array_size(eargv), 1);
+ ATF_REQUIRE(std::strcmp(eargv[0], "arg0") == 0);
+ ATF_REQUIRE_EQ(eargv[1], static_cast< const char* >(NULL));
+ }
+}
+
+ATF_TEST_CASE(argv_array_iter);
+ATF_TEST_CASE_HEAD(argv_array_iter)
+{
+ set_md_var("descr", "Tests that an argv_array can be iterated");
+}
+ATF_TEST_CASE_BODY(argv_array_iter)
+{
+ using tools::process::argv_array;
+
+ std::vector< std::string > vector;
+ vector.push_back("arg0");
+ vector.push_back("arg1");
+ vector.push_back("arg2");
+
+ argv_array argv(vector);
+ ATF_REQUIRE_EQ(argv.size(), 3);
+ std::vector< std::string >::size_type pos = 0;
+ for (argv_array::const_iterator iter = argv.begin(); iter != argv.end();
+ iter++) {
+ ATF_REQUIRE_EQ(*iter, vector[pos]);
+ pos++;
+ }
+}
+
+// ------------------------------------------------------------------------
+// Tests cases for the free functions.
+// ------------------------------------------------------------------------
+
+ATF_TEST_CASE(exec_failure);
+ATF_TEST_CASE_HEAD(exec_failure)
+{
+ set_md_var("descr", "Tests execing a command that reports failure");
+}
+ATF_TEST_CASE_BODY(exec_failure)
+{
+ const tools::process::status s = exec_process_helpers(*this, "exit-failure");
+ ATF_REQUIRE(s.exited());
+ ATF_REQUIRE_EQ(s.exitstatus(), EXIT_FAILURE);
+}
+
+ATF_TEST_CASE(exec_success);
+ATF_TEST_CASE_HEAD(exec_success)
+{
+ set_md_var("descr", "Tests execing a command that reports success");
+}
+ATF_TEST_CASE_BODY(exec_success)
+{
+ const tools::process::status s = exec_process_helpers(*this, "exit-success");
+ ATF_REQUIRE(s.exited());
+ ATF_REQUIRE_EQ(s.exitstatus(), EXIT_SUCCESS);
+}
+
+// ------------------------------------------------------------------------
+// Main.
+// ------------------------------------------------------------------------
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ // Add the test cases for the "argv_array" type.
+ ATF_ADD_TEST_CASE(tcs, argv_array_assign);
+ ATF_ADD_TEST_CASE(tcs, argv_array_copy);
+ ATF_ADD_TEST_CASE(tcs, argv_array_exec_argv);
+ ATF_ADD_TEST_CASE(tcs, argv_array_init_carray);
+ ATF_ADD_TEST_CASE(tcs, argv_array_init_col);
+ ATF_ADD_TEST_CASE(tcs, argv_array_init_empty);
+ ATF_ADD_TEST_CASE(tcs, argv_array_init_varargs);
+ ATF_ADD_TEST_CASE(tcs, argv_array_iter);
+
+ // Add the test cases for the free functions.
+ ATF_ADD_TEST_CASE(tcs, exec_failure);
+ ATF_ADD_TEST_CASE(tcs, exec_success);
+}
diff --git a/unit/atf-src/tools/reader.cpp b/unit/atf-src/tools/reader.cpp
new file mode 100644
index 0000000..67fce66
--- /dev/null
+++ b/unit/atf-src/tools/reader.cpp
@@ -0,0 +1,440 @@
+//
+// Automated Testing Framework (atf)
+//
+// Copyright (c) 2007 The NetBSD Foundation, Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
+// CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+// IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
+// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+// IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+
+extern "C" {
+#include <sys/time.h>
+}
+
+#include <cassert>
+#include <cstdlib>
+#include <map>
+#include <sstream>
+#include <utility>
+
+#include "defs.hpp"
+#include "parser.hpp"
+#include "reader.hpp"
+#include "text.hpp"
+
+namespace impl = tools::atf_report;
+#define IMPL_NAME "tools::atf_report"
+
+// ------------------------------------------------------------------------
+// Auxiliary functions.
+// ------------------------------------------------------------------------
+
+template< typename Type >
+Type
+string_to_int(const std::string& str)
+{
+ std::istringstream ss(str);
+ Type s;
+ ss >> s;
+
+ return s;
+}
+
+// ------------------------------------------------------------------------
+// The "atf_tps" auxiliary parser.
+// ------------------------------------------------------------------------
+
+namespace atf_tps {
+
+static const tools::parser::token_type eof_type = 0;
+static const tools::parser::token_type nl_type = 1;
+static const tools::parser::token_type text_type = 2;
+static const tools::parser::token_type colon_type = 3;
+static const tools::parser::token_type comma_type = 4;
+static const tools::parser::token_type tps_count_type = 5;
+static const tools::parser::token_type tp_start_type = 6;
+static const tools::parser::token_type tp_end_type = 7;
+static const tools::parser::token_type tc_start_type = 8;
+static const tools::parser::token_type tc_so_type = 9;
+static const tools::parser::token_type tc_se_type = 10;
+static const tools::parser::token_type tc_end_type = 11;
+static const tools::parser::token_type passed_type = 12;
+static const tools::parser::token_type failed_type = 13;
+static const tools::parser::token_type skipped_type = 14;
+static const tools::parser::token_type info_type = 16;
+static const tools::parser::token_type expected_death_type = 17;
+static const tools::parser::token_type expected_exit_type = 18;
+static const tools::parser::token_type expected_failure_type = 19;
+static const tools::parser::token_type expected_signal_type = 20;
+static const tools::parser::token_type expected_timeout_type = 21;
+
+class tokenizer : public tools::parser::tokenizer< std::istream > {
+public:
+ tokenizer(std::istream& is, size_t curline) :
+ tools::parser::tokenizer< std::istream >
+ (is, true, eof_type, nl_type, text_type, curline)
+ {
+ add_delim(':', colon_type);
+ add_delim(',', comma_type);
+ add_keyword("tps-count", tps_count_type);
+ add_keyword("tp-start", tp_start_type);
+ add_keyword("tp-end", tp_end_type);
+ add_keyword("tc-start", tc_start_type);
+ add_keyword("tc-so", tc_so_type);
+ add_keyword("tc-se", tc_se_type);
+ add_keyword("tc-end", tc_end_type);
+ add_keyword("passed", passed_type);
+ add_keyword("failed", failed_type);
+ add_keyword("skipped", skipped_type);
+ add_keyword("info", info_type);
+ add_keyword("expected_death", expected_death_type);
+ add_keyword("expected_exit", expected_exit_type);
+ add_keyword("expected_failure", expected_failure_type);
+ add_keyword("expected_signal", expected_signal_type);
+ add_keyword("expected_timeout", expected_timeout_type);
+ }
+};
+
+} // namespace atf_tps
+
+struct timeval
+read_timeval(tools::parser::parser< atf_tps::tokenizer >& parser)
+{
+ using namespace atf_tps;
+
+ tools::parser::token t = parser.expect(text_type, "timestamp");
+ const std::string::size_type divider = t.text().find('.');
+ if (divider == std::string::npos || divider == 0 ||
+ divider == t.text().length() - 1)
+ throw tools::parser::parse_error(t.lineno(),
+ "Malformed timestamp value " + t.text());
+
+ struct timeval tv;
+ tv.tv_sec = string_to_int< long >(t.text().substr(0, divider));
+ tv.tv_usec = string_to_int< long >(t.text().substr(divider + 1));
+ return tv;
+}
+
+// ------------------------------------------------------------------------
+// The "atf_tps_reader" class.
+// ------------------------------------------------------------------------
+
+impl::atf_tps_reader::atf_tps_reader(std::istream& is) :
+ m_is(is)
+{
+}
+
+impl::atf_tps_reader::~atf_tps_reader(void)
+{
+}
+
+void
+impl::atf_tps_reader::got_info(
+ const std::string& what ATF_DEFS_ATTRIBUTE_UNUSED,
+ const std::string& val ATF_DEFS_ATTRIBUTE_UNUSED)
+{
+}
+
+void
+impl::atf_tps_reader::got_ntps(size_t ntps ATF_DEFS_ATTRIBUTE_UNUSED)
+{
+}
+
+void
+impl::atf_tps_reader::got_tp_start(
+ const std::string& tp ATF_DEFS_ATTRIBUTE_UNUSED,
+ size_t ntcs ATF_DEFS_ATTRIBUTE_UNUSED)
+{
+}
+
+void
+impl::atf_tps_reader::got_tp_end(
+ struct timeval* tv ATF_DEFS_ATTRIBUTE_UNUSED,
+ const std::string& reason ATF_DEFS_ATTRIBUTE_UNUSED)
+{
+}
+
+void
+impl::atf_tps_reader::got_tc_start(
+ const std::string& tcname ATF_DEFS_ATTRIBUTE_UNUSED)
+{
+}
+
+void
+impl::atf_tps_reader::got_tc_stdout_line(
+ const std::string& line ATF_DEFS_ATTRIBUTE_UNUSED)
+{
+}
+
+void
+impl::atf_tps_reader::got_tc_stderr_line(
+ const std::string& line ATF_DEFS_ATTRIBUTE_UNUSED)
+{
+}
+
+void
+impl::atf_tps_reader::got_tc_end(
+ const std::string& state ATF_DEFS_ATTRIBUTE_UNUSED,
+ struct timeval* tv ATF_DEFS_ATTRIBUTE_UNUSED,
+ const std::string& reason ATF_DEFS_ATTRIBUTE_UNUSED)
+{
+}
+
+void
+impl::atf_tps_reader::got_eof(void)
+{
+}
+
+void
+impl::atf_tps_reader::read_info(void* pptr)
+{
+ using tools::parser::parse_error;
+ using namespace atf_tps;
+
+ tools::parser::parser< tokenizer >& p =
+ *reinterpret_cast< tools::parser::parser< tokenizer >* >
+ (pptr);
+
+ (void)p.expect(colon_type, "`:'");
+
+ tools::parser::token t = p.expect(text_type, "info property name");
+ (void)p.expect(comma_type, "`,'");
+ got_info(t.text(), tools::text::trim(p.rest_of_line()));
+
+ (void)p.expect(nl_type, "new line");
+}
+
+void
+impl::atf_tps_reader::read_tp(void* pptr)
+{
+ using tools::parser::parse_error;
+ using namespace atf_tps;
+
+ tools::parser::parser< tokenizer >& p =
+ *reinterpret_cast< tools::parser::parser< tokenizer >* >
+ (pptr);
+
+ tools::parser::token t = p.expect(tp_start_type,
+ "start of test program");
+
+ t = p.expect(colon_type, "`:'");
+
+ struct timeval s1 = read_timeval(p);
+
+ t = p.expect(comma_type, "`,'");
+
+ t = p.expect(text_type, "test program name");
+ std::string tpname = t.text();
+
+ t = p.expect(comma_type, "`,'");
+
+ t = p.expect(text_type, "number of test programs");
+ size_t ntcs = string_to_int< std::size_t >(t.text());
+
+ t = p.expect(nl_type, "new line");
+
+ ATF_PARSER_CALLBACK(p, got_tp_start(tpname, ntcs));
+
+ size_t i = 0;
+ while (p.good() && i < ntcs) {
+ try {
+ read_tc(&p);
+ i++;
+ } catch (const parse_error& pe) {
+ p.add_error(pe);
+ p.reset(nl_type);
+ }
+ }
+ t = p.expect(tp_end_type, "end of test program");
+
+ t = p.expect(colon_type, "`:'");
+
+ struct timeval s2 = read_timeval(p);
+
+ struct timeval s3;
+ timersub(&s2, &s1, &s3);
+
+ t = p.expect(comma_type, "`,'");
+
+ t = p.expect(text_type, "test program name");
+ if (t.text() != tpname)
+ throw parse_error(t.lineno(), "Test program name used in "
+ "terminator does not match "
+ "opening");
+
+ t = p.expect(nl_type, comma_type,
+ "new line or comma_type");
+ std::string reason;
+ if (t.type() == comma_type) {
+ reason = tools::text::trim(p.rest_of_line());
+ if (reason.empty())
+ throw parse_error(t.lineno(),
+ "Empty reason for failed test program");
+ t = p.next();
+ }
+
+ ATF_PARSER_CALLBACK(p, got_tp_end(&s3, reason));
+}
+
+void
+impl::atf_tps_reader::read_tc(void* pptr)
+{
+ using tools::parser::parse_error;
+ using namespace atf_tps;
+
+ tools::parser::parser< tokenizer >& p =
+ *reinterpret_cast< tools::parser::parser< tokenizer >* >
+ (pptr);
+
+ tools::parser::token t = p.expect(tc_start_type, "start of test case");
+
+ t = p.expect(colon_type, "`:'");
+
+ struct timeval s1 = read_timeval(p);
+
+ t = p.expect(comma_type, "`,'");
+
+ t = p.expect(text_type, "test case name");
+ std::string tcname = t.text();
+
+ ATF_PARSER_CALLBACK(p, got_tc_start(tcname));
+
+ t = p.expect(nl_type, "new line");
+
+ t = p.expect(tc_end_type, tc_so_type, tc_se_type,
+ "end of test case or test case's stdout/stderr line");
+ while (t.type() != tc_end_type &&
+ (t.type() == tc_so_type || t.type() == tc_se_type)) {
+ tools::parser::token t2 = t;
+
+ t = p.expect(colon_type, "`:'");
+
+ std::string line = p.rest_of_line();
+
+ if (t2.type() == tc_so_type) {
+ ATF_PARSER_CALLBACK(p, got_tc_stdout_line(line));
+ } else {
+ assert(t2.type() == tc_se_type);
+ ATF_PARSER_CALLBACK(p, got_tc_stderr_line(line));
+ }
+
+ t = p.expect(nl_type, "new line");
+
+ t = p.expect(tc_end_type, tc_so_type, tc_se_type,
+ "end of test case or test case's stdout/stderr line");
+ }
+
+ t = p.expect(colon_type, "`:'");
+
+ struct timeval s2 = read_timeval(p);
+
+ struct timeval s3;
+ timersub(&s2, &s1, &s3);
+
+ t = p.expect(comma_type, "`,'");
+
+ t = p.expect(text_type, "test case name");
+ if (t.text() != tcname)
+ throw parse_error(t.lineno(),
+ "Test case name used in terminator does not "
+ "match opening");
+
+ t = p.expect(comma_type, "`,'");
+
+ t = p.expect(expected_death_type, expected_exit_type, expected_failure_type,
+ expected_signal_type, expected_timeout_type, passed_type, failed_type,
+ skipped_type, "expected_{death,exit,failure,signal,timeout}, failed, "
+ "passed or skipped");
+ if (t.type() == passed_type) {
+ ATF_PARSER_CALLBACK(p, got_tc_end("passed", &s3, ""));
+ } else {
+ std::string state;
+ if (t.type() == expected_death_type) state = "expected_death";
+ else if (t.type() == expected_exit_type) state = "expected_exit";
+ else if (t.type() == expected_failure_type) state = "expected_failure";
+ else if (t.type() == expected_signal_type) state = "expected_signal";
+ else if (t.type() == expected_timeout_type) state = "expected_timeout";
+ else if (t.type() == failed_type) state = "failed";
+ else if (t.type() == skipped_type) state = "skipped";
+ else std::abort();
+
+ t = p.expect(comma_type, "`,'");
+ std::string reason = tools::text::trim(p.rest_of_line());
+ if (reason.empty())
+ throw parse_error(t.lineno(), "Empty reason for " + state +
+ " test case result");
+ ATF_PARSER_CALLBACK(p, got_tc_end(state, &s3, reason));
+ }
+
+ t = p.expect(nl_type, "new line");
+}
+
+void
+impl::atf_tps_reader::read(void)
+{
+ using tools::parser::parse_error;
+ using namespace atf_tps;
+
+ std::pair< size_t, tools::parser::headers_map > hml =
+ tools::parser::read_headers(m_is, 1);
+ tools::parser::validate_content_type(hml.second,
+ "application/X-atf-tps", 3);
+
+ tokenizer tkz(m_is, hml.first);
+ tools::parser::parser< tokenizer > p(tkz);
+
+ try {
+ tools::parser::token t;
+
+ while ((t = p.expect(tps_count_type, info_type, "tps-count or info "
+ "field")).type() == info_type)
+ read_info(&p);
+
+ t = p.expect(colon_type, "`:'");
+
+ t = p.expect(text_type, "number of test programs");
+ size_t ntps = string_to_int< std::size_t >(t.text());
+ ATF_PARSER_CALLBACK(p, got_ntps(ntps));
+
+ t = p.expect(nl_type, "new line");
+
+ size_t i = 0;
+ while (p.good() && i < ntps) {
+ try {
+ read_tp(&p);
+ i++;
+ } catch (const parse_error& pe) {
+ p.add_error(pe);
+ p.reset(nl_type);
+ }
+ }
+
+ while ((t = p.expect(eof_type, info_type, "end of stream or info "
+ "field")).type() == info_type)
+ read_info(&p);
+ ATF_PARSER_CALLBACK(p, got_eof());
+ } catch (const parse_error& pe) {
+ p.add_error(pe);
+ p.reset(nl_type);
+ }
+}
diff --git a/unit/atf-src/tools/reader.hpp b/unit/atf-src/tools/reader.hpp
new file mode 100644
index 0000000..34d5d25
--- /dev/null
+++ b/unit/atf-src/tools/reader.hpp
@@ -0,0 +1,89 @@
+//
+// Automated Testing Framework (atf)
+//
+// Copyright (c) 2007 The NetBSD Foundation, Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
+// CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+// IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
+// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+// IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+
+#if !defined(TOOLS_FORMATS_HPP)
+#define TOOLS_FORMATS_HPP
+
+extern "C" {
+#include <sys/time.h>
+}
+
+#include <istream>
+#include <string>
+
+namespace tools {
+namespace atf_report {
+
+struct test_case_result {
+ enum state_enum {
+ PASSED,
+ FAILED,
+ SKIPPED,
+ };
+ const state_enum state;
+ const std::string& reason;
+
+ test_case_result(const state_enum p_state, const std::string& p_reason) :
+ state(p_state),
+ reason(p_reason)
+ {
+ }
+};
+
+class atf_tps_reader {
+ std::istream& m_is;
+
+ void read_info(void*);
+ void read_tp(void*);
+ void read_tc(void*);
+
+protected:
+ virtual void got_info(const std::string&, const std::string&);
+ virtual void got_ntps(size_t);
+ virtual void got_tp_start(const std::string&, size_t);
+ virtual void got_tp_end(struct timeval*, const std::string&);
+
+ virtual void got_tc_start(const std::string&);
+ virtual void got_tc_stdout_line(const std::string&);
+ virtual void got_tc_stderr_line(const std::string&);
+ virtual void got_tc_end(const std::string&, struct timeval*,
+ const std::string&);
+ virtual void got_eof(void);
+
+public:
+ atf_tps_reader(std::istream&);
+ virtual ~atf_tps_reader(void);
+
+ void read(void);
+};
+
+} // namespace atf_report
+} // namespace tools
+
+#endif // !defined(TOOLS_FORMATS_HPP)
diff --git a/unit/atf-src/tools/reader_test.cpp b/unit/atf-src/tools/reader_test.cpp
new file mode 100644
index 0000000..745b0bb
--- /dev/null
+++ b/unit/atf-src/tools/reader_test.cpp
@@ -0,0 +1,986 @@
+//
+// Automated Testing Framework (atf)
+//
+// Copyright (c) 2010 The NetBSD Foundation, Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
+// CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+// IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
+// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+// IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+
+#include <iostream>
+#include <sstream>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <atf-c++.hpp>
+
+#include "parser.hpp"
+#include "reader.hpp"
+#include "test_helpers.hpp"
+#include "text.hpp"
+
+namespace impl = tools::atf_report;
+
+class tps_reader : protected impl::atf_tps_reader {
+ void
+ got_info(const std::string& what, const std::string& val)
+ {
+ m_calls.push_back("got_info(" + what + ", " + val + ")");
+ }
+
+ void
+ got_ntps(size_t ntps)
+ {
+ m_calls.push_back("got_ntps(" + tools::text::to_string(ntps) + ")");
+ }
+
+ void
+ got_tp_start(const std::string& tpname, size_t ntcs)
+ {
+ m_calls.push_back("got_tp_start(" + tpname + ", " +
+ tools::text::to_string(ntcs) + ")");
+ }
+
+ void
+ got_tp_end(struct timeval* tv ATF_DEFS_ATTRIBUTE_UNUSED,
+ const std::string& reason)
+ {
+ m_calls.push_back("got_tp_end(" + reason + ")");
+ }
+
+ void
+ got_tc_start(const std::string& tcname)
+ {
+ m_calls.push_back("got_tc_start(" + tcname + ")");
+ }
+
+ void
+ got_tc_end(const std::string& state,
+ struct timeval* tv ATF_DEFS_ATTRIBUTE_UNUSED,
+ const std::string& reason)
+ {
+ const std::string r = state + (reason.empty() ? "" : ", " + reason);
+ m_calls.push_back("got_tc_end(" + r + ")");
+ }
+
+ void
+ got_tc_stdout_line(const std::string& line)
+ {
+ m_calls.push_back("got_tc_stdout_line(" + line + ")");
+ }
+
+ void
+ got_tc_stderr_line(const std::string& line)
+ {
+ m_calls.push_back("got_tc_stderr_line(" + line + ")");
+ }
+
+ void
+ got_eof(void)
+ {
+ m_calls.push_back("got_eof()");
+ }
+
+public:
+ tps_reader(std::istream& is) :
+ impl::atf_tps_reader(is)
+ {
+ }
+
+ void
+ read(void)
+ {
+ atf_tps_reader::read();
+ }
+
+ std::vector< std::string > m_calls;
+};
+
+ATF_TEST_CASE_WITHOUT_HEAD(tps_1);
+ATF_TEST_CASE_BODY(tps_1)
+{
+ const char* input =
+ "Content-Type: application/X-atf-tps; version=\"3\"\n"
+ "\n"
+ "tps-count: 0\n"
+ ;
+
+ const char* exp_calls[] = {
+ "got_ntps(0)",
+ "got_eof()",
+ NULL
+ };
+
+ const char* exp_errors[] = {
+ NULL
+ };
+
+ do_parser_test< tps_reader >(input, exp_calls, exp_errors);
+}
+
+ATF_TEST_CASE_WITHOUT_HEAD(tps_2);
+ATF_TEST_CASE_BODY(tps_2)
+{
+ const char* input =
+ "Content-Type: application/X-atf-tps; version=\"3\"\n"
+ "\n"
+ "tps-count: 2\n"
+ "tp-start: 123.456, first-prog, 0\n"
+ "tp-end: 123.567, first-prog\n"
+ "tp-start: 123.678, second-prog, 0\n"
+ "tp-end: 123.789, second-prog, This program failed\n"
+ ;
+
+ const char* exp_calls[] = {
+ "got_ntps(2)",
+ "got_tp_start(first-prog, 0)",
+ "got_tp_end()",
+ "got_tp_start(second-prog, 0)",
+ "got_tp_end(This program failed)",
+ "got_eof()",
+ NULL
+ };
+
+ const char* exp_errors[] = {
+ NULL
+ };
+
+ do_parser_test< tps_reader >(input, exp_calls, exp_errors);
+}
+
+ATF_TEST_CASE_WITHOUT_HEAD(tps_3);
+ATF_TEST_CASE_BODY(tps_3)
+{
+ const char* input =
+ "Content-Type: application/X-atf-tps; version=\"3\"\n"
+ "\n"
+ "tps-count: 2\n"
+ "tp-start: 123.123, first-prog, 3\n"
+ "tc-start: 123.234, first-test\n"
+ "tc-end: 123.345, first-test, passed\n"
+ "tc-start: 123.456, second-test\n"
+ "tc-end: 123.567, second-test, skipped, Testing skipped reason\n"
+ "tc-start: 123.678, third.test\n"
+ "tc-end: 123.789, third.test, failed, Testing failed reason\n"
+ "tp-end: 123.890, first-prog\n"
+ "tp-start: 124.901, second-prog, 3\n"
+ "tc-start: 124.1012, first-test\n"
+ "tc-so:first stdout line for 1st test\n"
+ "tc-se:first stderr line for 1st test\n"
+ "tc-so:second stdout line for 1st test\n"
+ "tc-se:second stderr line for 1st test\n"
+ "tc-end: 124.1123, first-test, passed\n"
+ "tc-start: 124.1234, second-test\n"
+ "tc-so:first stdout line for 2nd test\n"
+ "tc-se:first stderr line for 2nd test\n"
+ "tc-so:second stdout line for 2nd test\n"
+ "tc-se:second stderr line for 2nd test\n"
+ "tc-end: 124.1345, second-test, skipped, Testing skipped reason\n"
+ "tc-start: 124.1456, third.test\n"
+ "tc-so:first stdout line for 3rd test\n"
+ "tc-se:first stderr line for 3rd test\n"
+ "tc-so:second stdout line for 3rd test\n"
+ "tc-se:second stderr line for 3rd test\n"
+ "tc-end: 124.1567, third.test, failed, Testing failed reason\n"
+ "tp-end: 124.1678, second-prog, This program failed\n"
+ ;
+
+ const char* exp_calls[] = {
+ "got_ntps(2)",
+ "got_tp_start(first-prog, 3)",
+ "got_tc_start(first-test)",
+ "got_tc_end(passed)",
+ "got_tc_start(second-test)",
+ "got_tc_end(skipped, Testing skipped reason)",
+ "got_tc_start(third.test)",
+ "got_tc_end(failed, Testing failed reason)",
+ "got_tp_end()",
+ "got_tp_start(second-prog, 3)",
+ "got_tc_start(first-test)",
+ "got_tc_stdout_line(first stdout line for 1st test)",
+ "got_tc_stderr_line(first stderr line for 1st test)",
+ "got_tc_stdout_line(second stdout line for 1st test)",
+ "got_tc_stderr_line(second stderr line for 1st test)",
+ "got_tc_end(passed)",
+ "got_tc_start(second-test)",
+ "got_tc_stdout_line(first stdout line for 2nd test)",
+ "got_tc_stderr_line(first stderr line for 2nd test)",
+ "got_tc_stdout_line(second stdout line for 2nd test)",
+ "got_tc_stderr_line(second stderr line for 2nd test)",
+ "got_tc_end(skipped, Testing skipped reason)",
+ "got_tc_start(third.test)",
+ "got_tc_stdout_line(first stdout line for 3rd test)",
+ "got_tc_stderr_line(first stderr line for 3rd test)",
+ "got_tc_stdout_line(second stdout line for 3rd test)",
+ "got_tc_stderr_line(second stderr line for 3rd test)",
+ "got_tc_end(failed, Testing failed reason)",
+ "got_tp_end(This program failed)",
+ "got_eof()",
+ NULL
+ };
+
+ const char* exp_errors[] = {
+ NULL
+ };
+
+ do_parser_test< tps_reader >(input, exp_calls, exp_errors);
+}
+
+ATF_TEST_CASE_WITHOUT_HEAD(tps_4);
+ATF_TEST_CASE_BODY(tps_4)
+{
+ const char* input =
+ "Content-Type: application/X-atf-tps; version=\"3\"\n"
+ "\n"
+ "info: a, foo\n"
+ "info: b, bar\n"
+ "info: c, baz\n"
+ "tps-count: 2\n"
+ "tp-start: 234.1, first-prog, 3\n"
+ "tc-start: 234.12, first-test\n"
+ "tc-end: 234.23, first-test, passed\n"
+ "tc-start: 234.34, second-test\n"
+ "tc-end: 234.45, second-test, skipped, Testing skipped reason\n"
+ "tc-start: 234.56, third-test\n"
+ "tc-end: 234.67, third-test, failed, Testing failed reason\n"
+ "tp-end: 234.78, first-prog\n"
+ "tp-start: 234.89, second-prog, 3\n"
+ "tc-start: 234.90, first-test\n"
+ "tc-so:first stdout line for 1st test\n"
+ "tc-se:first stderr line for 1st test\n"
+ "tc-so:second stdout line for 1st test\n"
+ "tc-se:second stderr line for 1st test\n"
+ "tc-end: 234.101, first-test, passed\n"
+ "tc-start: 234.112, second-test\n"
+ "tc-so:first stdout line for 2nd test\n"
+ "tc-se:first stderr line for 2nd test\n"
+ "tc-so:second stdout line for 2nd test\n"
+ "tc-se:second stderr line for 2nd test\n"
+ "tc-end: 234.123, second-test, skipped, Testing skipped reason\n"
+ "tc-start: 234.134, third-test\n"
+ "tc-so:first stdout line for 3rd test\n"
+ "tc-se:first stderr line for 3rd test\n"
+ "tc-so:second stdout line for 3rd test\n"
+ "tc-se:second stderr line for 3rd test\n"
+ "tc-end: 234.145, third-test, failed, Testing failed reason\n"
+ "tp-end: 234.156, second-prog, This program failed\n"
+ "info: d, foo\n"
+ "info: e, bar\n"
+ "info: f, baz\n"
+ ;
+
+ const char* exp_calls[] = {
+ "got_info(a, foo)",
+ "got_info(b, bar)",
+ "got_info(c, baz)",
+ "got_ntps(2)",
+ "got_tp_start(first-prog, 3)",
+ "got_tc_start(first-test)",
+ "got_tc_end(passed)",
+ "got_tc_start(second-test)",
+ "got_tc_end(skipped, Testing skipped reason)",
+ "got_tc_start(third-test)",
+ "got_tc_end(failed, Testing failed reason)",
+ "got_tp_end()",
+ "got_tp_start(second-prog, 3)",
+ "got_tc_start(first-test)",
+ "got_tc_stdout_line(first stdout line for 1st test)",
+ "got_tc_stderr_line(first stderr line for 1st test)",
+ "got_tc_stdout_line(second stdout line for 1st test)",
+ "got_tc_stderr_line(second stderr line for 1st test)",
+ "got_tc_end(passed)",
+ "got_tc_start(second-test)",
+ "got_tc_stdout_line(first stdout line for 2nd test)",
+ "got_tc_stderr_line(first stderr line for 2nd test)",
+ "got_tc_stdout_line(second stdout line for 2nd test)",
+ "got_tc_stderr_line(second stderr line for 2nd test)",
+ "got_tc_end(skipped, Testing skipped reason)",
+ "got_tc_start(third-test)",
+ "got_tc_stdout_line(first stdout line for 3rd test)",
+ "got_tc_stderr_line(first stderr line for 3rd test)",
+ "got_tc_stdout_line(second stdout line for 3rd test)",
+ "got_tc_stderr_line(second stderr line for 3rd test)",
+ "got_tc_end(failed, Testing failed reason)",
+ "got_tp_end(This program failed)",
+ "got_info(d, foo)",
+ "got_info(e, bar)",
+ "got_info(f, baz)",
+ "got_eof()",
+ NULL
+ };
+
+ const char* exp_errors[] = {
+ NULL
+ };
+
+ do_parser_test< tps_reader >(input, exp_calls, exp_errors);
+}
+
+ATF_TEST_CASE_WITHOUT_HEAD(tps_5);
+ATF_TEST_CASE_BODY(tps_5)
+{
+ const char* input =
+ "Content-Type: application/X-atf-tps; version=\"3\"\n"
+ "\n"
+ "tps-count: 1\n"
+ "tp-start: 345.123, the-prog, 1\n"
+ "tc-start: 345.134, the-test\n"
+ "tc-so:--- a 2007-11-04 14:00:41.000000000 +0100\n"
+ "tc-so:+++ b 2007-11-04 14:00:48.000000000 +0100\n"
+ "tc-so:@@ -1,7 +1,7 @@\n"
+ "tc-so: This test is meant to simulate a diff.\n"
+ "tc-so: Blank space at beginning of context lines must be preserved.\n"
+ "tc-so: \n"
+ "tc-so:-First original line.\n"
+ "tc-so:-Second original line.\n"
+ "tc-so:+First modified line.\n"
+ "tc-so:+Second modified line.\n"
+ "tc-so: \n"
+ "tc-so: EOF\n"
+ "tc-end: 345.145, the-test, passed\n"
+ "tp-end: 345.156, the-prog\n"
+ ;
+
+ // NO_CHECK_STYLE_BEGIN
+ const char* exp_calls[] = {
+ "got_ntps(1)",
+ "got_tp_start(the-prog, 1)",
+ "got_tc_start(the-test)",
+ "got_tc_stdout_line(--- a 2007-11-04 14:00:41.000000000 +0100)",
+ "got_tc_stdout_line(+++ b 2007-11-04 14:00:48.000000000 +0100)",
+ "got_tc_stdout_line(@@ -1,7 +1,7 @@)",
+ "got_tc_stdout_line( This test is meant to simulate a diff.)",
+ "got_tc_stdout_line( Blank space at beginning of context lines must be preserved.)",
+ "got_tc_stdout_line( )",
+ "got_tc_stdout_line(-First original line.)",
+ "got_tc_stdout_line(-Second original line.)",
+ "got_tc_stdout_line(+First modified line.)",
+ "got_tc_stdout_line(+Second modified line.)",
+ "got_tc_stdout_line( )",
+ "got_tc_stdout_line( EOF)",
+ "got_tc_end(passed)",
+ "got_tp_end()",
+ "got_eof()",
+ NULL
+ };
+ // NO_CHECK_STYLE_END
+
+ const char* exp_errors[] = {
+ NULL
+ };
+
+ do_parser_test< tps_reader >(input, exp_calls, exp_errors);
+}
+
+ATF_TEST_CASE_WITHOUT_HEAD(tps_6);
+ATF_TEST_CASE_BODY(tps_6)
+{
+ const char* input =
+ "Content-Type: application/X-atf-tps; version=\"3\"\n"
+ "\n"
+ "tps-count: 1\n"
+ "tp-start: 321.1, the-prog, 8\n"
+ "tc-start: 321.12, one\n"
+ "tc-end: 321.23, one, expected_death, The reason\n"
+ "tc-start: 321.34, two\n"
+ "tc-end: 321.45, two, expected_exit, This would be an exit\n"
+ "tc-start: 321.56, three\n"
+ "tc-end: 321.67, three, expected_failure, And this a failure\n"
+ "tc-start: 321.78, four\n"
+ "tc-end: 321.89, four, expected_signal, And this a signal\n"
+ "tc-start: 321.90, five\n"
+ "tc-end: 321.101, five, failed, Another reason\n"
+ "tc-start: 321.112, six\n"
+ "tc-end: 321.123, six, passed\n"
+ "tc-start: 321.134, seven\n"
+ "tc-end: 321.145, seven, skipped, Skipping it\n"
+ "tc-start: 321.156, eight\n"
+ "tc-end: 321.167, eight, expected_timeout, Some hang reason\n"
+ "tp-end: 321.178, the-prog\n"
+ ;
+
+ // NO_CHECK_STYLE_BEGIN
+ const char* exp_calls[] = {
+ "got_ntps(1)",
+ "got_tp_start(the-prog, 8)",
+ "got_tc_start(one)",
+ "got_tc_end(expected_death, The reason)",
+ "got_tc_start(two)",
+ "got_tc_end(expected_exit, This would be an exit)",
+ "got_tc_start(three)",
+ "got_tc_end(expected_failure, And this a failure)",
+ "got_tc_start(four)",
+ "got_tc_end(expected_signal, And this a signal)",
+ "got_tc_start(five)",
+ "got_tc_end(failed, Another reason)",
+ "got_tc_start(six)",
+ "got_tc_end(passed)",
+ "got_tc_start(seven)",
+ "got_tc_end(skipped, Skipping it)",
+ "got_tc_start(eight)",
+ "got_tc_end(expected_timeout, Some hang reason)",
+ "got_tp_end()",
+ "got_eof()",
+ NULL
+ };
+ // NO_CHECK_STYLE_END
+
+ const char* exp_errors[] = {
+ NULL
+ };
+
+ do_parser_test< tps_reader >(input, exp_calls, exp_errors);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(tps_50);
+ATF_TEST_CASE_BODY(tps_50)
+{
+ const char* input =
+ "Content-Type: application/X-atf-tps; version=\"3\"\n"
+ "\n"
+ "foo\n"
+ ;
+
+ const char* exp_calls[] = {
+ NULL
+ };
+
+ const char* exp_errors[] = {
+ "3: Unexpected token `foo'; expected tps-count or info field",
+ NULL
+ };
+
+ do_parser_test< tps_reader >(input, exp_calls, exp_errors);
+}
+
+ATF_TEST_CASE_WITHOUT_HEAD(tps_51);
+ATF_TEST_CASE_BODY(tps_51)
+{
+ const char* input =
+ "Content-Type: application/X-atf-tps; version=\"3\"\n"
+ "\n"
+ "tps-count\n"
+ ;
+
+ const char* exp_calls[] = {
+ NULL
+ };
+
+ const char* exp_errors[] = {
+ "3: Unexpected token `<<NEWLINE>>'; expected `:'",
+ NULL
+ };
+
+ do_parser_test< tps_reader >(input, exp_calls, exp_errors);
+}
+
+ATF_TEST_CASE_WITHOUT_HEAD(tps_52);
+ATF_TEST_CASE_BODY(tps_52)
+{
+ const char* input =
+ "Content-Type: application/X-atf-tps; version=\"3\"\n"
+ "\n"
+ "tps-count:\n"
+ ;
+
+ const char* exp_calls[] = {
+ NULL
+ };
+
+ const char* exp_errors[] = {
+ "3: Unexpected token `<<NEWLINE>>'; expected number of test programs",
+ NULL
+ };
+
+ do_parser_test< tps_reader >(input, exp_calls, exp_errors);
+}
+
+ATF_TEST_CASE_WITHOUT_HEAD(tps_53);
+ATF_TEST_CASE_BODY(tps_53)
+{
+ const char* input =
+ "Content-Type: application/X-atf-tps; version=\"3\"\n"
+ "\n"
+ "tps-count: 1\n"
+ "foo\n"
+ ;
+
+ const char* exp_calls[] = {
+ "got_ntps(1)",
+ NULL
+ };
+
+ const char* exp_errors[] = {
+ "4: Unexpected token `foo'; expected start of test program",
+ NULL
+ };
+
+ do_parser_test< tps_reader >(input, exp_calls, exp_errors);
+}
+
+ATF_TEST_CASE_WITHOUT_HEAD(tps_54);
+ATF_TEST_CASE_BODY(tps_54)
+{
+ const char* input =
+ "Content-Type: application/X-atf-tps; version=\"3\"\n"
+ "\n"
+ "tps-count: 1\n"
+ "foo\n"
+ "tp-start\n"
+ "tp-start:\n"
+ "tp-start: 123\n"
+ "tp-start: 123.\n"
+ "tp-start: 123.456\n"
+ "tp-start: 123.456,\n"
+ "tp-start: 123.456, foo\n"
+ "tp-start: 123.456, foo,\n"
+ "tp-start: 123.456, foo, 0\n"
+ "bar\n"
+ "tp-start: 456.789, foo, 0\n"
+ "tp-end\n"
+ "tp-start: 777.777, foo, 0\n"
+ "tp-end:\n"
+ "tp-start: 777.777, foo, 0\n"
+ "tp-end: 777\n"
+ "tp-start: 777.777, foo, 0\n"
+ "tp-end: 777.\n"
+ "tp-start: 777.777, foo, 0\n"
+ "tp-end: 777.888\n"
+ "tp-start: 777.777, foo, 0\n"
+ "tp-end: 777.888, \n"
+ "tp-start: 777.777, foo, 0\n"
+ "tp-end: 777.888, bar\n"
+ "tp-start: 777.777, foo, 0\n"
+ "tp-end: 777.888, foo,\n"
+ ;
+
+ const char* exp_calls[] = {
+ "got_ntps(1)",
+ NULL
+ };
+
+ const char* exp_errors[] = {
+ "4: Unexpected token `foo'; expected start of test program",
+ "5: Unexpected token `<<NEWLINE>>'; expected `:'",
+ "6: Unexpected token `<<NEWLINE>>'; expected timestamp",
+ "7: Malformed timestamp value 123",
+ "8: Malformed timestamp value 123.",
+ "9: Unexpected token `<<NEWLINE>>'; expected `,'",
+ "10: Unexpected token `<<NEWLINE>>'; expected test program name",
+ "11: Unexpected token `<<NEWLINE>>'; expected `,'",
+ "12: Unexpected token `<<NEWLINE>>'; expected number of test programs",
+ "14: Unexpected token `bar'; expected end of test program",
+ "16: Unexpected token `<<NEWLINE>>'; expected `:'",
+ "18: Unexpected token `<<NEWLINE>>'; expected timestamp",
+ "20: Malformed timestamp value 777",
+ "22: Malformed timestamp value 777.",
+ "24: Unexpected token `<<NEWLINE>>'; expected `,'",
+
+ "26: Unexpected token `<<NEWLINE>>'; expected test program name",
+ "28: Test program name used in terminator does not match opening",
+ "30: Empty reason for failed test program",
+ NULL
+ };
+
+ do_parser_test< tps_reader >(input, exp_calls, exp_errors);
+}
+
+ATF_TEST_CASE_WITHOUT_HEAD(tps_55);
+ATF_TEST_CASE_BODY(tps_55)
+{
+ const char* input =
+ "Content-Type: application/X-atf-tps; version=\"3\"\n"
+ "\n"
+ "tps-count: 1\n"
+ "tp-start: 100.200, foo, 1\n"
+ "foo\n"
+ "tc-start\n"
+ "tc-start:\n"
+ "tc-start: 111\n"
+ "tc-start: 111.\n"
+ "tc-start: 111.222\n"
+ "tc-start: 111.222,\n"
+ "tc-start: 111.222, foo\n"
+ "bar\n"
+ "tc-start: 111.333, foo\n"
+ "tc-end\n"
+ "tc-start: 111.444, foo\n"
+ "tc-end:\n"
+ "tc-start: 111.444, foo\n"
+ "tc-end: 111\n"
+ "tc-start: 111.444, foo\n"
+ "tc-end: 111.\n"
+ "tc-start: 111.444, foo\n"
+ "tc-end: 111.555\n"
+ "tc-start: 111.444, foo\n"
+ "tc-end: 111.555, \n"
+ "tc-start: 111.444, foo\n"
+ "tc-end: 111.555, bar\n"
+ "tc-start: 111.444, foo\n"
+ "tc-end: 111.555, foo\n"
+ "tc-start: 111.444, foo\n"
+ "tc-end: 111.555, foo,\n"
+ "tp-end: 111.666, foo\n"
+ ;
+
+ const char* exp_calls[] = {
+ "got_ntps(1)",
+ "got_tp_start(foo, 1)",
+ NULL
+ };
+
+ // NO_CHECK_STYLE_BEGIN
+ const char* exp_errors[] = {
+ "5: Unexpected token `foo'; expected start of test case",
+ "6: Unexpected token `<<NEWLINE>>'; expected `:'",
+ "7: Unexpected token `<<NEWLINE>>'; expected timestamp",
+ "8: Malformed timestamp value 111",
+ "9: Malformed timestamp value 111.",
+ "10: Unexpected token `<<NEWLINE>>'; expected `,'",
+ "11: Unexpected token `<<NEWLINE>>'; expected test case name",
+ "13: Unexpected token `bar'; expected end of test case or test case's stdout/stderr line",
+ "15: Unexpected token `<<NEWLINE>>'; expected `:'",
+ "17: Unexpected token `<<NEWLINE>>'; expected timestamp",
+ "19: Malformed timestamp value 111",
+ "21: Malformed timestamp value 111.",
+ "23: Unexpected token `<<NEWLINE>>'; expected `,'",
+ "25: Unexpected token `<<NEWLINE>>'; expected test case name",
+ "27: Test case name used in terminator does not match opening",
+ "29: Unexpected token `<<NEWLINE>>'; expected `,'",
+ "31: Unexpected token `<<NEWLINE>>'; expected expected_{death,exit,failure,signal,timeout}, failed, passed or skipped",
+ "32: Unexpected token `tp-end'; expected start of test case",
+ NULL
+ };
+ // NO_CHECK_STYLE_END
+
+ do_parser_test< tps_reader >(input, exp_calls, exp_errors);
+}
+
+ATF_TEST_CASE_WITHOUT_HEAD(tps_56);
+ATF_TEST_CASE_BODY(tps_56)
+{
+ const char* input =
+ "Content-Type: application/X-atf-tps; version=\"3\"\n"
+ "\n"
+ "tps-count: 1\n"
+ "tp-start: 111.222, foo, 1\n"
+ "tc-start: 111.333, foo\n"
+ "tc-end: 111.444, foo, passe\n"
+ "tc-start: 111.333, foo\n"
+ "tc-end: 111.444, foo, passed,\n"
+ "tc-start: 111.555, bar\n"
+ "tc-end: 111.666, bar, failed\n"
+ "tc-start: 111.555, bar\n"
+ "tc-end: 111.666, bar, failed,\n"
+ "tc-start: 111.555, baz\n"
+ "tc-end: 111.666, baz, skipped\n"
+ "tc-start: 111.555, baz\n"
+ "tc-end: 111.666, baz, skipped,\n"
+ "tp-end: 111.777, foo\n"
+ ;
+
+ const char* exp_calls[] = {
+ "got_ntps(1)",
+ "got_tp_start(foo, 1)",
+ "got_tc_start(foo)",
+ NULL
+ };
+
+ // NO_CHECK_STYLE_BEGIN
+ const char* exp_errors[] = {
+ "6: Unexpected token `passe'; expected expected_{death,exit,failure,signal,timeout}, failed, passed or skipped",
+ "8: Unexpected token `,'; expected new line",
+ "10: Unexpected token `<<NEWLINE>>'; expected `,'",
+ "12: Empty reason for failed test case result",
+ "14: Unexpected token `<<NEWLINE>>'; expected `,'",
+ "16: Empty reason for skipped test case result",
+ "17: Unexpected token `tp-end'; expected start of test case",
+ NULL
+ };
+ // NO_CHECK_STYLE_END
+
+ do_parser_test< tps_reader >(input, exp_calls, exp_errors);
+}
+
+ATF_TEST_CASE_WITHOUT_HEAD(tps_57);
+ATF_TEST_CASE_BODY(tps_57)
+{
+ const char* input =
+ "Content-Type: application/X-atf-tps; version=\"3\"\n"
+ "\n"
+ "tps-count: 2\n"
+ "tp-start: 111.222, foo, 0\n"
+ "tp-end: 111.333, foo\n"
+ ;
+
+ const char* exp_calls[] = {
+ "got_ntps(2)",
+ "got_tp_start(foo, 0)",
+ "got_tp_end()",
+ NULL
+ };
+
+ const char* exp_errors[] = {
+ "6: Unexpected token `<<EOF>>'; expected start of test program",
+ NULL
+ };
+
+ do_parser_test< tps_reader >(input, exp_calls, exp_errors);
+}
+
+ATF_TEST_CASE_WITHOUT_HEAD(tps_58);
+ATF_TEST_CASE_BODY(tps_58)
+{
+ const char* input =
+ "Content-Type: application/X-atf-tps; version=\"3\"\n"
+ "\n"
+ "tps-count: 1\n"
+ "tp-start: 111.222, foo, 0\n"
+ "tp-end: 111.333, foo\n"
+ "tp-start: 111.444, bar, 0\n"
+ "tp-end: 111.555, bar\n"
+ ;
+
+ const char* exp_calls[] = {
+ "got_ntps(1)",
+ "got_tp_start(foo, 0)",
+ "got_tp_end()",
+ NULL
+ };
+
+ const char* exp_errors[] = {
+ "6: Unexpected token `tp-start'; expected end of stream or info field",
+ NULL
+ };
+
+ do_parser_test< tps_reader >(input, exp_calls, exp_errors);
+}
+
+ATF_TEST_CASE_WITHOUT_HEAD(tps_59);
+ATF_TEST_CASE_BODY(tps_59)
+{
+ const char* input =
+ "Content-Type: application/X-atf-tps; version=\"3\"\n"
+ "\n"
+ "info\n"
+ ;
+
+ const char* exp_calls[] = {
+ NULL
+ };
+
+ const char* exp_errors[] = {
+ "3: Unexpected token `<<NEWLINE>>'; expected `:'",
+ NULL
+ };
+
+ do_parser_test< tps_reader >(input, exp_calls, exp_errors);
+}
+
+ATF_TEST_CASE_WITHOUT_HEAD(tps_60);
+ATF_TEST_CASE_BODY(tps_60)
+{
+ const char* input =
+ "Content-Type: application/X-atf-tps; version=\"3\"\n"
+ "\n"
+ "info:\n"
+ ;
+
+ const char* exp_calls[] = {
+ NULL
+ };
+
+ const char* exp_errors[] = {
+ "3: Unexpected token `<<NEWLINE>>'; expected info property name",
+ NULL
+ };
+
+ do_parser_test< tps_reader >(input, exp_calls, exp_errors);
+}
+
+ATF_TEST_CASE_WITHOUT_HEAD(tps_61);
+ATF_TEST_CASE_BODY(tps_61)
+{
+ const char* input =
+ "Content-Type: application/X-atf-tps; version=\"3\"\n"
+ "\n"
+ "info: a\n"
+ ;
+
+ const char* exp_calls[] = {
+ NULL
+ };
+
+ const char* exp_errors[] = {
+ "3: Unexpected token `<<NEWLINE>>'; expected `,'",
+ NULL
+ };
+
+ do_parser_test< tps_reader >(input, exp_calls, exp_errors);
+}
+
+ATF_TEST_CASE_WITHOUT_HEAD(tps_62);
+ATF_TEST_CASE_BODY(tps_62)
+{
+ const char* input =
+ "Content-Type: application/X-atf-tps; version=\"3\"\n"
+ "\n"
+ "info: a,\n"
+ ;
+
+ const char* exp_calls[] = {
+ "got_info(a, )",
+ NULL
+ };
+
+ const char* exp_errors[] = {
+ "4: Unexpected token `<<EOF>>'; expected tps-count or info field",
+ NULL
+ };
+
+ do_parser_test< tps_reader >(input, exp_calls, exp_errors);
+}
+
+ATF_TEST_CASE_WITHOUT_HEAD(tps_63);
+ATF_TEST_CASE_BODY(tps_63)
+{
+ const char* input =
+ "Content-Type: application/X-atf-tps; version=\"3\"\n"
+ "\n"
+ "info: a, b\n"
+ ;
+
+ const char* exp_calls[] = {
+ "got_info(a, b)",
+ NULL
+ };
+
+ const char* exp_errors[] = {
+ "4: Unexpected token `<<EOF>>'; expected tps-count or info field",
+ NULL
+ };
+
+ do_parser_test< tps_reader >(input, exp_calls, exp_errors);
+}
+
+ATF_TEST_CASE_WITHOUT_HEAD(tps_64);
+ATF_TEST_CASE_BODY(tps_64)
+{
+ const char* input =
+ "Content-Type: application/X-atf-tps; version=\"3\"\n"
+ "\n"
+ "info: a, b\n"
+ "info: a.b.c.def, g\n"
+ "tps-count\n"
+ ;
+
+ const char* exp_calls[] = {
+ "got_info(a, b)",
+ "got_info(a.b.c.def, g)",
+ NULL
+ };
+
+ const char* exp_errors[] = {
+ "5: Unexpected token `<<NEWLINE>>'; expected `:'",
+ NULL
+ };
+
+ do_parser_test< tps_reader >(input, exp_calls, exp_errors);
+}
+
+ATF_TEST_CASE_WITHOUT_HEAD(tps_65);
+ATF_TEST_CASE_BODY(tps_65)
+{
+ const char* input =
+ "Content-Type: application/X-atf-tps; version=\"3\"\n"
+ "\n"
+ "info: a, b\n"
+ "tps-count:\n"
+ ;
+
+ const char* exp_calls[] = {
+ "got_info(a, b)",
+ NULL
+ };
+
+ const char* exp_errors[] = {
+ "4: Unexpected token `<<NEWLINE>>'; expected number of test programs",
+ NULL
+ };
+
+ do_parser_test< tps_reader >(input, exp_calls, exp_errors);
+}
+
+ATF_TEST_CASE_WITHOUT_HEAD(tps_66);
+ATF_TEST_CASE_BODY(tps_66)
+{
+ const char* input =
+ "Content-Type: application/X-atf-tps; version=\"3\"\n"
+ "\n"
+ "info: a, b\n"
+ "tps-count: 0\n"
+ "info\n"
+ ;
+
+ const char* exp_calls[] = {
+ "got_info(a, b)",
+ "got_ntps(0)",
+ NULL
+ };
+
+ const char* exp_errors[] = {
+ "5: Unexpected token `<<NEWLINE>>'; expected `:'",
+ NULL
+ };
+
+ do_parser_test< tps_reader >(input, exp_calls, exp_errors);
+}
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ ATF_ADD_TEST_CASE(tcs, tps_1);
+ ATF_ADD_TEST_CASE(tcs, tps_2);
+ ATF_ADD_TEST_CASE(tcs, tps_3);
+ ATF_ADD_TEST_CASE(tcs, tps_4);
+ ATF_ADD_TEST_CASE(tcs, tps_5);
+ ATF_ADD_TEST_CASE(tcs, tps_6);
+ ATF_ADD_TEST_CASE(tcs, tps_50);
+ ATF_ADD_TEST_CASE(tcs, tps_51);
+ ATF_ADD_TEST_CASE(tcs, tps_52);
+ ATF_ADD_TEST_CASE(tcs, tps_53);
+ ATF_ADD_TEST_CASE(tcs, tps_54);
+ ATF_ADD_TEST_CASE(tcs, tps_55);
+ ATF_ADD_TEST_CASE(tcs, tps_56);
+ ATF_ADD_TEST_CASE(tcs, tps_57);
+ ATF_ADD_TEST_CASE(tcs, tps_58);
+ ATF_ADD_TEST_CASE(tcs, tps_59);
+ ATF_ADD_TEST_CASE(tcs, tps_60);
+ ATF_ADD_TEST_CASE(tcs, tps_61);
+ ATF_ADD_TEST_CASE(tcs, tps_62);
+ ATF_ADD_TEST_CASE(tcs, tps_63);
+ ATF_ADD_TEST_CASE(tcs, tps_64);
+ ATF_ADD_TEST_CASE(tcs, tps_65);
+ ATF_ADD_TEST_CASE(tcs, tps_66);
+}
diff --git a/unit/atf-src/tools/requirements.cpp b/unit/atf-src/tools/requirements.cpp
new file mode 100644
index 0000000..97fcdc8
--- /dev/null
+++ b/unit/atf-src/tools/requirements.cpp
@@ -0,0 +1,325 @@
+//
+// Automated Testing Framework (atf)
+//
+// Copyright (c) 2007 The NetBSD Foundation, Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
+// CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+// IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
+// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+// IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+
+extern "C" {
+#include <sys/param.h>
+#include <sys/sysctl.h>
+}
+
+#include <cassert>
+#include <cerrno>
+#include <cstdlib>
+#include <cstring>
+#include <stdexcept>
+
+#include "config.hpp"
+#include "defs.hpp"
+#include "env.hpp"
+#include "fs.hpp"
+#include "requirements.hpp"
+#include "text.hpp"
+#include "user.hpp"
+
+namespace impl = tools;
+
+namespace {
+
+typedef std::map< std::string, std::string > vars_map;
+
+static
+bool
+has_program(const tools::fs::path& program)
+{
+ bool found = false;
+
+ if (program.is_absolute()) {
+ found = tools::fs::is_executable(program);
+ } else {
+ if (program.str().find('/') != std::string::npos)
+ throw std::runtime_error("Relative paths are not allowed "
+ "when searching for a program (" +
+ program.str() + ")");
+
+ const std::vector< std::string > dirs = tools::text::split(
+ tools::env::get("PATH"), ":");
+ for (std::vector< std::string >::const_iterator iter = dirs.begin();
+ !found && iter != dirs.end(); iter++) {
+ const tools::fs::path& p = tools::fs::path(*iter) / program;
+ if (tools::fs::is_executable(p))
+ found = true;
+ }
+ }
+
+ return found;
+}
+
+static
+std::string
+check_arch(const std::string& arches)
+{
+ const std::vector< std::string > v = tools::text::split(arches, " ");
+
+ for (std::vector< std::string >::const_iterator iter = v.begin();
+ iter != v.end(); iter++) {
+ if ((*iter) == tools::config::get("atf_arch"))
+ return "";
+ }
+
+ if (v.size() == 1)
+ return "Requires the '" + arches + "' architecture";
+ else
+ return "Requires one of the '" + arches + "' architectures";
+}
+
+static
+std::string
+check_config(const std::string& variables, const vars_map& config)
+{
+ const std::vector< std::string > v = tools::text::split(variables, " ");
+ for (std::vector< std::string >::const_iterator iter = v.begin();
+ iter != v.end(); iter++) {
+ if (config.find((*iter)) == config.end())
+ return "Required configuration variable '" + (*iter) + "' not "
+ "defined";
+ }
+ return "";
+}
+
+static
+std::string
+check_files(const std::string& progs)
+{
+ const std::vector< std::string > v = tools::text::split(progs, " ");
+ for (std::vector< std::string >::const_iterator iter = v.begin();
+ iter != v.end(); iter++) {
+ const tools::fs::path file(*iter);
+ if (!file.is_absolute())
+ throw std::runtime_error("Relative paths are not allowed when "
+ "checking for a required file (" + file.str() + ")");
+ if (!tools::fs::exists(file))
+ return "Required file '" + file.str() + "' not found";
+ }
+ return "";
+}
+
+static
+std::string
+check_machine(const std::string& machines)
+{
+ const std::vector< std::string > v = tools::text::split(machines, " ");
+
+ for (std::vector< std::string >::const_iterator iter = v.begin();
+ iter != v.end(); iter++) {
+ if ((*iter) == tools::config::get("atf_machine"))
+ return "";
+ }
+
+ if (v.size() == 1)
+ return "Requires the '" + machines + "' machine type";
+ else
+ return "Requires one of the '" + machines + "' machine types";
+}
+
+#if defined(__APPLE__) || defined(__FreeBSD__) || defined(__NetBSD__)
+static
+std::string
+check_memory_sysctl(const int64_t needed, const char* sysctl_variable)
+{
+ int64_t available;
+ std::size_t available_length = sizeof(available);
+ if (::sysctlbyname(sysctl_variable, &available, &available_length,
+ NULL, 0) == -1) {
+ const char* e = std::strerror(errno);
+ return "Failed to get sysctl(hw.usermem64) value: " + std::string(e);
+ }
+
+ if (available < needed) {
+ return "Not enough memory; needed " + tools::text::to_string(needed) +
+ ", available " + tools::text::to_string(available);
+ } else
+ return "";
+}
+# if defined(__APPLE__)
+static
+std::string
+check_memory_darwin(const int64_t needed)
+{
+ return check_memory_sysctl(needed, "hw.usermem");
+}
+# elif defined(__FreeBSD__)
+static
+std::string
+check_memory_freebsd(const int64_t needed)
+{
+ return check_memory_sysctl(needed, "hw.usermem");
+}
+# elif defined(__NetBSD__)
+static
+std::string
+check_memory_netbsd(const int64_t needed)
+{
+ return check_memory_sysctl(needed, "hw.usermem64");
+}
+# else
+# error "Conditional error"
+# endif
+#else
+static
+std::string
+check_memory_unknown(const int64_t needed ATF_DEFS_ATTRIBUTE_UNUSED)
+{
+ return "";
+}
+#endif
+
+static
+std::string
+check_memory(const std::string& raw_memory)
+{
+ const int64_t needed = tools::text::to_bytes(raw_memory);
+
+#if defined(__APPLE__)
+ return check_memory_darwin(needed);
+#elif defined(__FreeBSD__)
+ return check_memory_freebsd(needed);
+#elif defined(__NetBSD__)
+ return check_memory_netbsd(needed);
+#else
+ return check_memory_unknown(needed);
+#endif
+}
+
+static
+std::string
+check_progs(const std::string& progs)
+{
+ const std::vector< std::string > v = tools::text::split(progs, " ");
+ for (std::vector< std::string >::const_iterator iter = v.begin();
+ iter != v.end(); iter++) {
+ if (!has_program(tools::fs::path(*iter)))
+ return "Required program '" + (*iter) + "' not found in the PATH";
+ }
+ return "";
+}
+
+static
+std::string
+check_user(const std::string& user, const vars_map& config)
+{
+ if (user == "root") {
+ if (!tools::user::is_root())
+ return "Requires root privileges";
+ else
+ return "";
+ } else if (user == "unprivileged") {
+ if (tools::user::is_root()) {
+ const vars_map::const_iterator iter = config.find(
+ "unprivileged-user");
+ if (iter == config.end())
+ return "Requires an unprivileged user and the "
+ "'unprivileged-user' configuration variable is not set";
+ else {
+ const std::string& unprivileged_user = (*iter).second;
+ try {
+ (void)tools::user::get_user_ids(unprivileged_user);
+ return "";
+ } catch (const std::runtime_error& e) {
+ return "Failed to get information for user " +
+ unprivileged_user;
+ }
+ }
+ } else
+ return "";
+ } else
+ throw std::runtime_error("Invalid value '" + user + "' for property "
+ "require.user");
+}
+
+} // anonymous namespace
+
+std::string
+impl::check_requirements(const vars_map& metadata,
+ const vars_map& config)
+{
+ std::string failure_reason = "";
+
+ for (vars_map::const_iterator iter = metadata.begin();
+ failure_reason.empty() && iter != metadata.end(); iter++) {
+ const std::string& name = (*iter).first;
+ const std::string& value = (*iter).second;
+ assert(!value.empty()); // Enforced by application/X-atf-tp parser.
+
+ if (name == "require.arch")
+ failure_reason = check_arch(value);
+ else if (name == "require.config")
+ failure_reason = check_config(value, config);
+ else if (name == "require.files")
+ failure_reason = check_files(value);
+ else if (name == "require.machine")
+ failure_reason = check_machine(value);
+ else if (name == "require.memory")
+ failure_reason = check_memory(value);
+ else if (name == "require.progs")
+ failure_reason = check_progs(value);
+ else if (name == "require.user")
+ failure_reason = check_user(value, config);
+ else {
+ // Unknown require.* properties are forbidden by the
+ // application/X-atf-tp parser.
+ assert(failure_reason.find("require.") != 0);
+ }
+ }
+
+ return failure_reason;
+}
+
+std::pair< int, int >
+impl::get_required_user(const vars_map& metadata,
+ const vars_map& config)
+{
+ const vars_map::const_iterator user = metadata.find(
+ "require.user");
+ if (user == metadata.end())
+ return std::make_pair(-1, -1);
+
+ if ((*user).second == "unprivileged") {
+ if (tools::user::is_root()) {
+ const vars_map::const_iterator iter = config.find(
+ "unprivileged-user");
+ try {
+ return tools::user::get_user_ids((*iter).second);
+ } catch (const std::exception& e) {
+ std::abort(); // This has been validated by check_user.
+ }
+ } else {
+ return std::make_pair(-1, -1);
+ }
+ } else
+ return std::make_pair(-1, -1);
+}
diff --git a/unit/atf-src/tools/requirements.hpp b/unit/atf-src/tools/requirements.hpp
new file mode 100644
index 0000000..1eefdaf
--- /dev/null
+++ b/unit/atf-src/tools/requirements.hpp
@@ -0,0 +1,47 @@
+//
+// Automated Testing Framework (atf)
+//
+// Copyright (c) 2010 The NetBSD Foundation, Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
+// CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+// IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
+// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+// IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+
+#if !defined(TOOLS_REQUIREMENTS_HPP)
+#define TOOLS_REQUIREMENTS_HPP
+
+#include <map>
+#include <string>
+#include <utility>
+
+namespace tools {
+
+std::string check_requirements(const std::map< std::string, std::string >&,
+ const std::map< std::string, std::string >&);
+std::pair< int, int > get_required_user(
+ const std::map< std::string, std::string >&,
+ const std::map< std::string, std::string >&);
+
+} // namespace tools
+
+#endif // !defined(TOOLS_REQUIREMENTS_HPP)
diff --git a/unit/atf-src/tools/requirements_test.cpp b/unit/atf-src/tools/requirements_test.cpp
new file mode 100644
index 0000000..6d37d75
--- /dev/null
+++ b/unit/atf-src/tools/requirements_test.cpp
@@ -0,0 +1,400 @@
+//
+// Automated Testing Framework (atf)
+//
+// Copyright (c) 2010 The NetBSD Foundation, Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
+// CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+// IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
+// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+// IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+
+#include <atf-c++.hpp>
+
+#include "config.hpp"
+#include "requirements.hpp"
+#include "text.hpp"
+#include "user.hpp"
+
+namespace impl = tools;
+
+// -------------------------------------------------------------------------
+// Auxiliary functions.
+// -------------------------------------------------------------------------
+
+namespace {
+
+typedef std::map< std::string, std::string > vars_map;
+
+const vars_map no_config;
+
+void
+do_check(const std::string& expected, const vars_map& metadata,
+ const vars_map& config = no_config)
+{
+ const std::string actual = impl::check_requirements(metadata, config);
+ if (!tools::text::match(actual, expected))
+ ATF_FAIL("Requirements failure reason \"" + actual + "\" does not "
+ "match \"" + expected + "\"");
+}
+
+} // anonymous namespace
+
+// -------------------------------------------------------------------------
+// Tests for the require.arch metadata property.
+// -------------------------------------------------------------------------
+
+ATF_TEST_CASE(require_arch_one_ok);
+ATF_TEST_CASE_HEAD(require_arch_one_ok) {}
+ATF_TEST_CASE_BODY(require_arch_one_ok) {
+ vars_map metadata;
+ metadata["require.arch"] = tools::config::get("atf_arch");
+ do_check("", metadata);
+}
+
+ATF_TEST_CASE(require_arch_one_fail);
+ATF_TEST_CASE_HEAD(require_arch_one_fail) {}
+ATF_TEST_CASE_BODY(require_arch_one_fail) {
+ vars_map metadata;
+ metadata["require.arch"] = "__fake_arch__";
+ do_check("Requires the '__fake_arch__' architecture", metadata);
+}
+
+ATF_TEST_CASE(require_arch_many_ok);
+ATF_TEST_CASE_HEAD(require_arch_many_ok) {}
+ATF_TEST_CASE_BODY(require_arch_many_ok) {
+ vars_map metadata;
+ metadata["require.arch"] = "__foo__ " + tools::config::get("atf_arch") +
+ " __bar__";
+ do_check("", metadata);
+}
+
+ATF_TEST_CASE(require_arch_many_fail);
+ATF_TEST_CASE_HEAD(require_arch_many_fail) {}
+ATF_TEST_CASE_BODY(require_arch_many_fail) {
+ vars_map metadata;
+ metadata["require.arch"] = "__foo__ __bar__ __baz__";
+ do_check("Requires one of the '__foo__ __bar__ __baz__' architectures",
+ metadata);
+}
+
+// -------------------------------------------------------------------------
+// Tests for the require.config metadata property.
+// -------------------------------------------------------------------------
+
+ATF_TEST_CASE(require_config_one_ok);
+ATF_TEST_CASE_HEAD(require_config_one_ok) {}
+ATF_TEST_CASE_BODY(require_config_one_ok) {
+ vars_map metadata, config;
+ metadata["require.config"] = "var1";
+ config["var1"] = "some-value";
+ do_check("", metadata, config);
+}
+
+ATF_TEST_CASE(require_config_one_fail);
+ATF_TEST_CASE_HEAD(require_config_one_fail) {}
+ATF_TEST_CASE_BODY(require_config_one_fail) {
+ vars_map metadata, config;
+ metadata["require.config"] = "var1";
+ do_check("Required configuration variable 'var1' not defined", metadata,
+ config);
+}
+
+ATF_TEST_CASE(require_config_many_ok);
+ATF_TEST_CASE_HEAD(require_config_many_ok) {}
+ATF_TEST_CASE_BODY(require_config_many_ok) {
+ vars_map metadata, config;
+ metadata["require.config"] = "var1 var2 var3";
+ config["var1"] = "first-value";
+ config["var2"] = "second-value";
+ config["var3"] = "third-value";
+ do_check("", metadata, config);
+}
+
+ATF_TEST_CASE(require_config_many_fail);
+ATF_TEST_CASE_HEAD(require_config_many_fail) {}
+ATF_TEST_CASE_BODY(require_config_many_fail) {
+ vars_map metadata, config;
+ metadata["require.config"] = "var1 var2 var3";
+ config["var1"] = "first-value";
+ config["var3"] = "third-value";
+ do_check("Required configuration variable 'var2' not defined", metadata,
+ config);
+}
+
+// -------------------------------------------------------------------------
+// Tests for the require.files metadata property.
+// -------------------------------------------------------------------------
+
+ATF_TEST_CASE_WITHOUT_HEAD(require_files_one_ok);
+ATF_TEST_CASE_BODY(require_files_one_ok) {
+ vars_map metadata;
+ metadata["require.files"] = "/bin/ls";
+ do_check("", metadata);
+}
+
+ATF_TEST_CASE_WITHOUT_HEAD(require_files_one_missing);
+ATF_TEST_CASE_BODY(require_files_one_missing) {
+ vars_map metadata;
+ metadata["require.files"] = "/non-existent/foo";
+ do_check("Required file '/non-existent/foo' not found", metadata);
+}
+
+ATF_TEST_CASE_WITHOUT_HEAD(require_files_one_fail);
+ATF_TEST_CASE_BODY(require_files_one_fail) {
+ vars_map metadata;
+ metadata["require.files"] = "/bin/cp this-is-relative";
+ ATF_REQUIRE_THROW_RE(std::runtime_error, "Relative.*(this-is-relative)",
+ impl::check_requirements(metadata, no_config));
+}
+
+ATF_TEST_CASE_WITHOUT_HEAD(require_files_many_ok);
+ATF_TEST_CASE_BODY(require_files_many_ok) {
+ vars_map metadata;
+ metadata["require.files"] = "/bin/ls /bin/cp";
+ do_check("", metadata);
+}
+
+ATF_TEST_CASE_WITHOUT_HEAD(require_files_many_missing);
+ATF_TEST_CASE_BODY(require_files_many_missing) {
+ vars_map metadata;
+ metadata["require.files"] = "/bin/ls /non-existent/bar /bin/cp";
+ do_check("Required file '/non-existent/bar' not found", metadata);
+}
+
+ATF_TEST_CASE_WITHOUT_HEAD(require_files_many_fail);
+ATF_TEST_CASE_BODY(require_files_many_fail) {
+ vars_map metadata;
+ metadata["require.files"] = "/bin/cp also-relative";
+ ATF_REQUIRE_THROW_RE(std::runtime_error, "Relative.*(also-relative)",
+ impl::check_requirements(metadata, no_config));
+}
+
+// -------------------------------------------------------------------------
+// Tests for the require.machine metadata property.
+// -------------------------------------------------------------------------
+
+ATF_TEST_CASE(require_machine_one_ok);
+ATF_TEST_CASE_HEAD(require_machine_one_ok) {}
+ATF_TEST_CASE_BODY(require_machine_one_ok) {
+ vars_map metadata;
+ metadata["require.machine"] = tools::config::get("atf_machine");
+ do_check("", metadata);
+}
+
+ATF_TEST_CASE(require_machine_one_fail);
+ATF_TEST_CASE_HEAD(require_machine_one_fail) {}
+ATF_TEST_CASE_BODY(require_machine_one_fail) {
+ vars_map metadata;
+ metadata["require.machine"] = "__fake_machine__";
+ do_check("Requires the '__fake_machine__' machine type", metadata);
+}
+
+ATF_TEST_CASE(require_machine_many_ok);
+ATF_TEST_CASE_HEAD(require_machine_many_ok) {}
+ATF_TEST_CASE_BODY(require_machine_many_ok) {
+ vars_map metadata;
+ metadata["require.machine"] = "__foo__ " +
+ tools::config::get("atf_machine") + " __bar__";
+ do_check("", metadata);
+}
+
+ATF_TEST_CASE(require_machine_many_fail);
+ATF_TEST_CASE_HEAD(require_machine_many_fail) {}
+ATF_TEST_CASE_BODY(require_machine_many_fail) {
+ vars_map metadata;
+ metadata["require.machine"] = "__foo__ __bar__ __baz__";
+ do_check("Requires one of the '__foo__ __bar__ __baz__' machine types",
+ metadata);
+}
+
+// -------------------------------------------------------------------------
+// Tests for the require.memory metadata property.
+// -------------------------------------------------------------------------
+
+ATF_TEST_CASE_WITHOUT_HEAD(require_memory_ok);
+ATF_TEST_CASE_BODY(require_memory_ok) {
+ vars_map metadata;
+ metadata["require.memory"] = "1m";
+ do_check("", metadata);
+}
+
+ATF_TEST_CASE_WITHOUT_HEAD(require_memory_not_enough);
+ATF_TEST_CASE_BODY(require_memory_not_enough) {
+ vars_map metadata;
+ metadata["require.memory"] = "128t";
+#if defined(__APPLE__) || defined(__FreeBSD__) || defined(__NetBSD__)
+ do_check("Not enough memory; needed 140737488355328, available [0-9]*",
+ metadata);
+#else
+ skip("Don't know how to check for the amount of physical memory");
+#endif
+}
+
+ATF_TEST_CASE_WITHOUT_HEAD(require_memory_fail);
+ATF_TEST_CASE_BODY(require_memory_fail) {
+ vars_map metadata;
+ metadata["require.memory"] = "foo";
+ ATF_REQUIRE_THROW(std::runtime_error,
+ impl::check_requirements(metadata, no_config));
+}
+
+// -------------------------------------------------------------------------
+// Tests for the require.progs metadata property.
+// -------------------------------------------------------------------------
+
+ATF_TEST_CASE(require_progs_one_ok);
+ATF_TEST_CASE_HEAD(require_progs_one_ok) {}
+ATF_TEST_CASE_BODY(require_progs_one_ok) {
+ vars_map metadata;
+ metadata["require.progs"] = "cp";
+ do_check("", metadata);
+}
+
+ATF_TEST_CASE(require_progs_one_missing);
+ATF_TEST_CASE_HEAD(require_progs_one_missing) {}
+ATF_TEST_CASE_BODY(require_progs_one_missing) {
+ vars_map metadata;
+ metadata["require.progs"] = "cp __non-existent__";
+ do_check("Required program '__non-existent__' not found in the PATH",
+ metadata);
+}
+
+ATF_TEST_CASE(require_progs_one_fail);
+ATF_TEST_CASE_HEAD(require_progs_one_fail) {}
+ATF_TEST_CASE_BODY(require_progs_one_fail) {
+ vars_map metadata;
+ metadata["require.progs"] = "bin/cp";
+ ATF_REQUIRE_THROW(std::runtime_error,
+ impl::check_requirements(metadata, no_config));
+}
+
+ATF_TEST_CASE(require_progs_many_ok);
+ATF_TEST_CASE_HEAD(require_progs_many_ok) {}
+ATF_TEST_CASE_BODY(require_progs_many_ok) {
+ vars_map metadata;
+ metadata["require.progs"] = "cp ls mv";
+ do_check("", metadata);
+}
+
+ATF_TEST_CASE(require_progs_many_missing);
+ATF_TEST_CASE_HEAD(require_progs_many_missing) {}
+ATF_TEST_CASE_BODY(require_progs_many_missing) {
+ vars_map metadata;
+ metadata["require.progs"] = "mv ls __foo__ cp";
+ do_check("Required program '__foo__' not found in the PATH", metadata);
+}
+
+ATF_TEST_CASE(require_progs_many_fail);
+ATF_TEST_CASE_HEAD(require_progs_many_fail) {}
+ATF_TEST_CASE_BODY(require_progs_many_fail) {
+ vars_map metadata;
+ metadata["require.progs"] = "ls cp ../bin/cp";
+ ATF_REQUIRE_THROW(std::runtime_error,
+ impl::check_requirements(metadata, no_config));
+}
+
+// -------------------------------------------------------------------------
+// Tests for the require.user metadata property.
+// -------------------------------------------------------------------------
+
+ATF_TEST_CASE(require_user_root);
+ATF_TEST_CASE_HEAD(require_user_root) {}
+ATF_TEST_CASE_BODY(require_user_root) {
+ vars_map metadata;
+ metadata["require.user"] = "root";
+ if (tools::user::is_root())
+ do_check("", metadata);
+ else
+ do_check("Requires root privileges", metadata);
+}
+
+ATF_TEST_CASE(require_user_unprivileged);
+ATF_TEST_CASE_HEAD(require_user_unprivileged) {}
+ATF_TEST_CASE_BODY(require_user_unprivileged) {
+ vars_map metadata;
+ metadata["require.user"] = "unprivileged";
+ if (tools::user::is_root())
+ do_check("Requires an unprivileged user and the 'unprivileged-user' "
+ "configuration variable is not set", metadata);
+ else
+ do_check("", metadata);
+}
+
+ATF_TEST_CASE(require_user_fail);
+ATF_TEST_CASE_HEAD(require_user_fail) {}
+ATF_TEST_CASE_BODY(require_user_fail) {
+ vars_map metadata;
+ metadata["require.user"] = "nobody";
+ ATF_REQUIRE_THROW(std::runtime_error,
+ impl::check_requirements(metadata, no_config));
+}
+
+// -------------------------------------------------------------------------
+// Main.
+// -------------------------------------------------------------------------
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ // Add test cases for require.arch.
+ ATF_ADD_TEST_CASE(tcs, require_arch_one_ok);
+ ATF_ADD_TEST_CASE(tcs, require_arch_one_fail);
+ ATF_ADD_TEST_CASE(tcs, require_arch_many_ok);
+ ATF_ADD_TEST_CASE(tcs, require_arch_many_fail);
+
+ // Add test cases for require.config.
+ ATF_ADD_TEST_CASE(tcs, require_config_one_ok);
+ ATF_ADD_TEST_CASE(tcs, require_config_one_fail);
+ ATF_ADD_TEST_CASE(tcs, require_config_many_ok);
+ ATF_ADD_TEST_CASE(tcs, require_config_many_fail);
+
+ // Add test cases for require.files.
+ ATF_ADD_TEST_CASE(tcs, require_files_one_ok);
+ ATF_ADD_TEST_CASE(tcs, require_files_one_missing);
+ ATF_ADD_TEST_CASE(tcs, require_files_one_fail);
+ ATF_ADD_TEST_CASE(tcs, require_files_many_ok);
+ ATF_ADD_TEST_CASE(tcs, require_files_many_missing);
+ ATF_ADD_TEST_CASE(tcs, require_files_many_fail);
+
+ // Add test cases for require.machine.
+ ATF_ADD_TEST_CASE(tcs, require_machine_one_ok);
+ ATF_ADD_TEST_CASE(tcs, require_machine_one_fail);
+ ATF_ADD_TEST_CASE(tcs, require_machine_many_ok);
+ ATF_ADD_TEST_CASE(tcs, require_machine_many_fail);
+
+ // Add test cases for require.memory.
+ ATF_ADD_TEST_CASE(tcs, require_memory_ok);
+ ATF_ADD_TEST_CASE(tcs, require_memory_not_enough);
+ ATF_ADD_TEST_CASE(tcs, require_memory_fail);
+
+ // Add test cases for require.progs.
+ ATF_ADD_TEST_CASE(tcs, require_progs_one_ok);
+ ATF_ADD_TEST_CASE(tcs, require_progs_one_missing);
+ ATF_ADD_TEST_CASE(tcs, require_progs_one_fail);
+ ATF_ADD_TEST_CASE(tcs, require_progs_many_ok);
+ ATF_ADD_TEST_CASE(tcs, require_progs_many_missing);
+ ATF_ADD_TEST_CASE(tcs, require_progs_many_fail);
+
+ // Add test cases for require.user.
+ ATF_ADD_TEST_CASE(tcs, require_user_root);
+ ATF_ADD_TEST_CASE(tcs, require_user_unprivileged);
+ ATF_ADD_TEST_CASE(tcs, require_user_fail);
+}
diff --git a/unit/atf-src/tools/sample/atf-run.hooks b/unit/atf-src/tools/sample/atf-run.hooks
new file mode 100644
index 0000000..86d187d
--- /dev/null
+++ b/unit/atf-src/tools/sample/atf-run.hooks
@@ -0,0 +1,23 @@
+#
+# Definition of custom hooks for atf-run.
+#
+# Uncomment any hooks that you want to override and add your own code
+# to them. Some sample calls are included in them. Be very careful
+# with what you print from here.
+#
+# See atf-run(1) for more details.
+#
+
+#info_start_hook()
+#{
+# default_info_start_hook "${@}"
+#
+# atf_tps_writer_info "extra.info" "An example"
+#}
+
+#info_end_hook()
+#{
+# default_info_end_hook "${@}"
+#
+# atf_tps_writer_info "extra.info" "An example"
+#}
diff --git a/unit/atf-src/tools/sample/common.conf b/unit/atf-src/tools/sample/common.conf
new file mode 100644
index 0000000..464ee96
--- /dev/null
+++ b/unit/atf-src/tools/sample/common.conf
@@ -0,0 +1,11 @@
+Content-Type: application/X-atf-config; version="1"
+
+#
+# Sample configuration file for properties affecting all test suites.
+#
+
+# When running the test suite as root, some tests require to switch to
+# an unprivileged user to perform extra checks. Set this variable to
+# the user you want to use in those cases. If not set, those tests will
+# be skipped.
+#unprivileged-user = "_atf"
diff --git a/unit/atf-src/tools/several_tcs_helper.c b/unit/atf-src/tools/several_tcs_helper.c
new file mode 100644
index 0000000..d287c81
--- /dev/null
+++ b/unit/atf-src/tools/several_tcs_helper.c
@@ -0,0 +1,67 @@
+/*
+ * Automated Testing Framework (atf)
+ *
+ * Copyright (c) 2010 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
+ * CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+ * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+ * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <atf-c.h>
+
+ATF_TC(first);
+ATF_TC_HEAD(first, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "Description 1");
+}
+ATF_TC_BODY(first, tc)
+{
+}
+
+ATF_TC_WITH_CLEANUP(second);
+ATF_TC_HEAD(second, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "Description 2");
+ atf_tc_set_md_var(tc, "timeout", "500");
+ atf_tc_set_md_var(tc, "X-property", "Custom property");
+}
+ATF_TC_BODY(second, tc)
+{
+}
+ATF_TC_CLEANUP(second, tc)
+{
+}
+
+ATF_TC_WITHOUT_HEAD(third);
+ATF_TC_BODY(third, tc)
+{
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+ ATF_TP_ADD_TC(tp, first);
+ ATF_TP_ADD_TC(tp, second);
+ ATF_TP_ADD_TC(tp, third);
+
+ return atf_no_error();
+}
diff --git a/unit/atf-src/tools/share/atf-run.hooks b/unit/atf-src/tools/share/atf-run.hooks
new file mode 100644
index 0000000..c94f3bc
--- /dev/null
+++ b/unit/atf-src/tools/share/atf-run.hooks
@@ -0,0 +1,94 @@
+#
+# Automated Testing Framework (atf)
+#
+# Copyright (c) 2007 The NetBSD Foundation, Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
+# CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+# IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
+# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+# GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+# IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+# IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+
+atf_tps_writer_info()
+{
+ class=${1}; shift
+ echo "info: ${class}, $*"
+}
+
+info_start_hook()
+{
+ default_info_start_hook "${@}"
+}
+
+default_info_start_hook()
+{
+ atf_tps_writer_info "atf.version" $(atf-version | head -n 1)
+
+ atf_tps_writer_info "tests.root" $(pwd)
+
+ atf_tps_writer_info "time.start" $(date)
+
+ atf_tps_writer_info "uname.sysname" $(uname -s)
+ atf_tps_writer_info "uname.nodename" $(uname -n)
+ atf_tps_writer_info "uname.release" $(uname -r)
+ atf_tps_writer_info "uname.version" $(uname -v)
+ atf_tps_writer_info "uname.machine" $(uname -m)
+
+ # Add all the environment variables to the report. We have to be
+ # careful with those that span over multiple lines; otherwise their
+ # values could be printed as multiple different variables (one per
+ # line), which is incorrect.
+ oldifs="${IFS}"
+ IFS='
+'
+ set -- $(env)
+ val=${1}; shift
+ while [ ${#} -gt 0 ]; do
+ if echo "${1}" | grep '^[a-zA-Z0-0_][a-zA-Z0-9_]*=' >/dev/null; then
+ atf_tps_writer_info "env" "${val}"
+ val="${1}"
+ else
+ val="${val} ${1}"
+ fi
+ shift
+ done
+ atf_tps_writer_info "env" "${val}"
+ IFS="${oldifs}"
+}
+
+info_end_hook()
+{
+ default_info_end_hook "${@}"
+}
+
+default_info_end_hook()
+{
+ atf_tps_writer_info "time.end" $(date)
+}
+
+sitehooks=$(atf-config -t atf_confdir)/atf-run.hooks
+userhooks=${HOME}/.atf/atf-run.hooks
+[ -f ${sitehooks} ] && . ${sitehooks}
+[ -f ${userhooks} ] && . ${userhooks}
+
+eval ${1}
+
+# vim: syntax=sh:expandtab:shiftwidth=4:softtabstop=4
diff --git a/unit/atf-src/tools/signals.cpp b/unit/atf-src/tools/signals.cpp
new file mode 100644
index 0000000..1e6c1ff
--- /dev/null
+++ b/unit/atf-src/tools/signals.cpp
@@ -0,0 +1,146 @@
+//
+// Automated Testing Framework (atf)
+//
+// Copyright (c) 2007 The NetBSD Foundation, Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
+// CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+// IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
+// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+// IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+
+#if defined(HAVE_CONFIG_H)
+#include "config.h"
+#endif
+
+extern "C" {
+#include <signal.h>
+#include <unistd.h>
+}
+
+#include <cerrno>
+#include <cstdlib>
+
+#include "exceptions.hpp"
+#include "signals.hpp"
+
+namespace impl = tools::signals;
+#define IMPL_NAME "tools::signals"
+
+const int impl::last_signo = LAST_SIGNO;
+
+// ------------------------------------------------------------------------
+// The "signal_holder" class.
+// ------------------------------------------------------------------------
+
+namespace {
+
+static bool happened[LAST_SIGNO + 1];
+
+static
+void
+holder_handler(const int signo)
+{
+ happened[signo] = true;
+}
+
+} // anonymous namespace
+
+impl::signal_holder::signal_holder(const int signo) :
+ m_signo(signo),
+ m_sp(NULL)
+{
+ happened[signo] = false;
+ m_sp = new signal_programmer(m_signo, holder_handler);
+}
+
+impl::signal_holder::~signal_holder(void)
+{
+ if (m_sp != NULL)
+ delete m_sp;
+
+ if (happened[m_signo])
+ ::kill(::getpid(), m_signo);
+}
+
+void
+impl::signal_holder::process(void)
+{
+ if (happened[m_signo]) {
+ delete m_sp;
+ m_sp = NULL;
+ happened[m_signo] = false;
+ ::kill(::getpid(), m_signo);
+ m_sp = new signal_programmer(m_signo, holder_handler);
+ }
+}
+
+// ------------------------------------------------------------------------
+// The "signal_programmer" class.
+// ------------------------------------------------------------------------
+
+impl::signal_programmer::signal_programmer(const int signo, const handler h) :
+ m_signo(signo),
+ m_handler(h),
+ m_programmed(false)
+{
+ struct ::sigaction sa;
+
+ sa.sa_handler = m_handler;
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = 0;
+
+ if (::sigaction(m_signo, &sa, &m_oldsa) == -1)
+ throw tools::system_error(IMPL_NAME, "Could not install handler for "
+ "signal", errno);
+ m_programmed = true;
+}
+
+impl::signal_programmer::~signal_programmer(void)
+{
+ unprogram();
+}
+
+void
+impl::signal_programmer::unprogram(void)
+{
+ if (m_programmed) {
+ if (::sigaction(m_signo, &m_oldsa, NULL) == -1)
+ std::abort();
+ m_programmed = false;
+ }
+}
+
+// ------------------------------------------------------------------------
+// Free functions.
+// ------------------------------------------------------------------------
+
+void
+impl::reset(const int signo)
+{
+ struct ::sigaction sa;
+
+ sa.sa_handler = SIG_DFL;
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = 0;
+
+ (void)::sigaction(signo, &sa, NULL);
+}
diff --git a/unit/atf-src/tools/signals.hpp b/unit/atf-src/tools/signals.hpp
new file mode 100644
index 0000000..549a2ca
--- /dev/null
+++ b/unit/atf-src/tools/signals.hpp
@@ -0,0 +1,92 @@
+//
+// Automated Testing Framework (atf)
+//
+// Copyright (c) 2007 The NetBSD Foundation, Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
+// CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+// IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
+// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+// IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+
+#if !defined(TOOLS_SIGNALS_HPP)
+#define TOOLS_SIGNALS_HPP
+
+extern "C" {
+#include <signal.h>
+}
+
+namespace tools {
+namespace signals {
+
+extern const int last_signo;
+typedef void (*handler)(const int);
+
+class signal_programmer;
+
+// ------------------------------------------------------------------------
+// The "signal_holder" class.
+// ------------------------------------------------------------------------
+
+//
+// A RAII model to hold a signal while the object is alive.
+//
+class signal_holder {
+ const int m_signo;
+ signal_programmer* m_sp;
+
+public:
+ signal_holder(const int);
+ ~signal_holder(void);
+
+ void process(void);
+};
+
+// ------------------------------------------------------------------------
+// The "signal_programmer" class.
+// ------------------------------------------------------------------------
+
+//
+// A RAII model to program a signal while the object is alive.
+//
+class signal_programmer {
+ const int m_signo;
+ const handler m_handler;
+ bool m_programmed;
+ struct sigaction m_oldsa;
+
+public:
+ signal_programmer(const int, const handler);
+ ~signal_programmer(void);
+
+ void unprogram(void);
+};
+
+// ------------------------------------------------------------------------
+// Free functions.
+// ------------------------------------------------------------------------
+
+void reset(const int);
+
+} // namespace signals
+} // namespace tools
+
+#endif // !defined(TOOLS_SIGNALS_HPP)
diff --git a/unit/atf-src/tools/signals_test.cpp b/unit/atf-src/tools/signals_test.cpp
new file mode 100644
index 0000000..aa6a8e5
--- /dev/null
+++ b/unit/atf-src/tools/signals_test.cpp
@@ -0,0 +1,275 @@
+//
+// Automated Testing Framework (atf)
+//
+// Copyright (c) 2008 The NetBSD Foundation, Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
+// CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+// IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
+// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+// IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+
+extern "C" {
+#include <sys/types.h>
+#include <signal.h>
+#include <unistd.h>
+}
+
+#include <cerrno>
+#include <cstdlib>
+#include <iostream>
+
+#include <atf-c++.hpp>
+
+#include "defs.hpp"
+#include "exceptions.hpp"
+#include "process.hpp"
+#include "signals.hpp"
+
+// ------------------------------------------------------------------------
+// Auxiliary functions.
+// ------------------------------------------------------------------------
+
+namespace sigusr1 {
+ static bool happened = false;
+
+ static
+ void
+ handler(int signo ATF_DEFS_ATTRIBUTE_UNUSED)
+ {
+ happened = true;
+ }
+
+ static
+ void
+ program(void)
+ {
+ struct sigaction sa;
+ sa.sa_handler = handler;
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = 0;
+ if (::sigaction(SIGUSR1, &sa, NULL) == -1)
+ throw tools::system_error("sigusr1::program",
+ "sigaction(2) failed", errno);
+ }
+} // namespace sigusr1
+
+namespace sigusr1_2 {
+ static bool happened = false;
+
+ static
+ void
+ handler(int signo ATF_DEFS_ATTRIBUTE_UNUSED)
+ {
+ happened = true;
+ }
+} // namespace sigusr1_2
+
+// ------------------------------------------------------------------------
+// Tests for the "signal_holder" class.
+// ------------------------------------------------------------------------
+
+ATF_TEST_CASE(signal_holder_preserve);
+ATF_TEST_CASE_HEAD(signal_holder_preserve)
+{
+ set_md_var("descr", "Tests that signal_holder preserves the original "
+ "signal handler and restores it upon destruction");
+}
+ATF_TEST_CASE_BODY(signal_holder_preserve)
+{
+ using tools::signals::signal_holder;
+
+ sigusr1::program();
+
+ sigusr1::happened = false;
+ ::kill(::getpid(), SIGUSR1);
+ ATF_REQUIRE(sigusr1::happened);
+
+ {
+ signal_holder hld(SIGUSR1);
+ ::kill(::getpid(), SIGUSR1);
+ }
+
+ sigusr1::happened = false;
+ ::kill(::getpid(), SIGUSR1);
+ ATF_REQUIRE(sigusr1::happened);
+}
+
+ATF_TEST_CASE(signal_holder_destructor);
+ATF_TEST_CASE_HEAD(signal_holder_destructor)
+{
+ set_md_var("descr", "Tests that signal_holder processes a pending "
+ "signal upon destruction");
+}
+ATF_TEST_CASE_BODY(signal_holder_destructor)
+{
+ using tools::signals::signal_holder;
+
+ sigusr1::program();
+
+ sigusr1::happened = false;
+ ::kill(::getpid(), SIGUSR1);
+ ATF_REQUIRE(sigusr1::happened);
+
+ {
+ signal_holder hld(SIGUSR1);
+
+ sigusr1::happened = false;
+ ::kill(::getpid(), SIGUSR1);
+ ATF_REQUIRE(!sigusr1::happened);
+ }
+ ATF_REQUIRE(sigusr1::happened);
+}
+
+ATF_TEST_CASE(signal_holder_process);
+ATF_TEST_CASE_HEAD(signal_holder_process)
+{
+ set_md_var("descr", "Tests that signal_holder's process method works "
+ "to process a delayed signal explicitly");
+}
+ATF_TEST_CASE_BODY(signal_holder_process)
+{
+ using tools::signals::signal_holder;
+
+ sigusr1::program();
+
+ sigusr1::happened = false;
+ ::kill(::getpid(), SIGUSR1);
+ ATF_REQUIRE(sigusr1::happened);
+
+ {
+ signal_holder hld(SIGUSR1);
+
+ sigusr1::happened = false;
+ ::kill(::getpid(), SIGUSR1);
+ ATF_REQUIRE(!sigusr1::happened);
+
+ hld.process();
+ ATF_REQUIRE(sigusr1::happened);
+
+ sigusr1::happened = false;
+ }
+ ATF_REQUIRE(!sigusr1::happened);
+}
+
+// ------------------------------------------------------------------------
+// Tests for the "signal_programmer" class.
+// ------------------------------------------------------------------------
+
+ATF_TEST_CASE(signal_programmer_program);
+ATF_TEST_CASE_HEAD(signal_programmer_program)
+{
+ set_md_var("descr", "Tests that signal_programmer correctly installs a "
+ "handler");
+}
+ATF_TEST_CASE_BODY(signal_programmer_program)
+{
+ using tools::signals::signal_programmer;
+
+ signal_programmer sp(SIGUSR1, sigusr1_2::handler);
+
+ sigusr1_2::happened = false;
+ ::kill(::getpid(), SIGUSR1);
+ ATF_REQUIRE(sigusr1_2::happened);
+}
+
+ATF_TEST_CASE(signal_programmer_preserve);
+ATF_TEST_CASE_HEAD(signal_programmer_preserve)
+{
+ set_md_var("descr", "Tests that signal_programmer uninstalls the "
+ "handler during destruction");
+}
+ATF_TEST_CASE_BODY(signal_programmer_preserve)
+{
+ using tools::signals::signal_programmer;
+
+ sigusr1::program();
+ sigusr1::happened = false;
+
+ {
+ signal_programmer sp(SIGUSR1, sigusr1_2::handler);
+
+ sigusr1_2::happened = false;
+ ::kill(::getpid(), SIGUSR1);
+ ATF_REQUIRE(sigusr1_2::happened);
+ }
+
+ ATF_REQUIRE(!sigusr1::happened);
+ ::kill(::getpid(), SIGUSR1);
+ ATF_REQUIRE(sigusr1::happened);
+}
+
+// ------------------------------------------------------------------------
+// Tests cases for the free functions.
+// ------------------------------------------------------------------------
+
+static
+void
+reset_child(void *v ATF_DEFS_ATTRIBUTE_UNUSED)
+{
+ sigusr1::program();
+
+ sigusr1::happened = false;
+ tools::signals::reset(SIGUSR1);
+ kill(::getpid(), SIGUSR1);
+
+ if (sigusr1::happened) {
+ std::cerr << "Signal was not resetted correctly\n";
+ std::abort();
+ } else {
+ std::exit(EXIT_SUCCESS);
+ }
+}
+
+ATF_TEST_CASE(reset);
+ATF_TEST_CASE_HEAD(reset)
+{
+ set_md_var("descr", "Tests the reset function");
+}
+ATF_TEST_CASE_BODY(reset)
+{
+ tools::process::child c =
+ tools::process::fork(reset_child, tools::process::stream_inherit(),
+ tools::process::stream_inherit(), NULL);
+
+ const tools::process::status s = c.wait();
+ ATF_REQUIRE(s.exited() || s.signaled());
+ ATF_REQUIRE(!s.signaled() || s.termsig() == SIGUSR1);
+}
+
+// ------------------------------------------------------------------------
+// Main.
+// ------------------------------------------------------------------------
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ // Add the tests for the "signal_holder" class.
+ ATF_ADD_TEST_CASE(tcs, signal_holder_preserve);
+ ATF_ADD_TEST_CASE(tcs, signal_holder_destructor);
+ ATF_ADD_TEST_CASE(tcs, signal_holder_process);
+
+ // Add the tests for the "signal_programmer" class.
+ ATF_ADD_TEST_CASE(tcs, signal_programmer_program);
+ ATF_ADD_TEST_CASE(tcs, signal_programmer_preserve);
+
+ // Add the test cases for the free functions.
+ ATF_ADD_TEST_CASE(tcs, reset);
+}
diff --git a/unit/atf-src/tools/test-program.cpp b/unit/atf-src/tools/test-program.cpp
new file mode 100644
index 0000000..91ad78a
--- /dev/null
+++ b/unit/atf-src/tools/test-program.cpp
@@ -0,0 +1,790 @@
+//
+// Automated Testing Framework (atf)
+//
+// Copyright (c) 2007 The NetBSD Foundation, Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
+// CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+// IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
+// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+// IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+
+extern "C" {
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+
+#include <fcntl.h>
+#include <signal.h>
+#include <unistd.h>
+}
+
+#include <cassert>
+#include <cerrno>
+#include <cstdlib>
+#include <cstring>
+#include <fstream>
+#include <iostream>
+
+#include "config_file.hpp"
+#include "defs.hpp"
+#include "env.hpp"
+#include "fs.hpp"
+#include "io.hpp"
+#include "parser.hpp"
+#include "process.hpp"
+#include "requirements.hpp"
+#include "signals.hpp"
+#include "test-program.hpp"
+#include "text.hpp"
+#include "timers.hpp"
+#include "user.hpp"
+
+namespace impl = tools::test_program;
+namespace detail = tools::test_program::detail;
+
+namespace {
+
+typedef std::map< std::string, std::string > vars_map;
+
+static void
+check_stream(std::ostream& os)
+{
+ // If we receive a signal while writing to the stream, the bad bit gets set.
+ // Things seem to behave fine afterwards if we clear such error condition.
+ // However, I'm not sure if it's safe to query errno at this point.
+ if (os.bad()) {
+ if (errno == EINTR)
+ os.clear();
+ else
+ throw std::runtime_error("Failed");
+ }
+}
+
+namespace atf_tp {
+
+static const tools::parser::token_type eof_type = 0;
+static const tools::parser::token_type nl_type = 1;
+static const tools::parser::token_type text_type = 2;
+static const tools::parser::token_type colon_type = 3;
+static const tools::parser::token_type dblquote_type = 4;
+
+class tokenizer : public tools::parser::tokenizer< std::istream > {
+public:
+ tokenizer(std::istream& is, size_t curline) :
+ tools::parser::tokenizer< std::istream >
+ (is, true, eof_type, nl_type, text_type, curline)
+ {
+ add_delim(':', colon_type);
+ add_quote('"', dblquote_type);
+ }
+};
+
+} // namespace atf_tp
+
+class metadata_reader : public detail::atf_tp_reader {
+ impl::test_cases_map m_tcs;
+
+ void got_tc(const std::string& ident, const vars_map& props)
+ {
+ if (m_tcs.find(ident) != m_tcs.end())
+ throw(std::runtime_error("Duplicate test case " + ident +
+ " in test program"));
+ m_tcs[ident] = props;
+
+ if (m_tcs[ident].find("has.cleanup") == m_tcs[ident].end())
+ m_tcs[ident].insert(std::make_pair("has.cleanup", "false"));
+
+ if (m_tcs[ident].find("timeout") == m_tcs[ident].end())
+ m_tcs[ident].insert(std::make_pair("timeout", "300"));
+ }
+
+public:
+ metadata_reader(std::istream& is) :
+ detail::atf_tp_reader(is)
+ {
+ }
+
+ const impl::test_cases_map&
+ get_tcs(void)
+ const
+ {
+ return m_tcs;
+ }
+};
+
+struct get_metadata_params {
+ const tools::fs::path& executable;
+ const vars_map& config;
+
+ get_metadata_params(const tools::fs::path& p_executable,
+ const vars_map& p_config) :
+ executable(p_executable),
+ config(p_config)
+ {
+ }
+};
+
+struct test_case_params {
+ const tools::fs::path& executable;
+ const std::string& test_case_name;
+ const std::string& test_case_part;
+ const vars_map& metadata;
+ const vars_map& config;
+ const tools::fs::path& resfile;
+ const tools::fs::path& workdir;
+
+ test_case_params(const tools::fs::path& p_executable,
+ const std::string& p_test_case_name,
+ const std::string& p_test_case_part,
+ const vars_map& p_metadata,
+ const vars_map& p_config,
+ const tools::fs::path& p_resfile,
+ const tools::fs::path& p_workdir) :
+ executable(p_executable),
+ test_case_name(p_test_case_name),
+ test_case_part(p_test_case_part),
+ metadata(p_metadata),
+ config(p_config),
+ resfile(p_resfile),
+ workdir(p_workdir)
+ {
+ }
+};
+
+static
+std::string
+generate_timestamp(void)
+{
+ struct timeval tv;
+ if (gettimeofday(&tv, NULL) == -1)
+ return "0.0";
+
+ char buf[32];
+ const int len = snprintf(buf, sizeof(buf), "%ld.%ld",
+ static_cast< long >(tv.tv_sec),
+ static_cast< long >(tv.tv_usec));
+ if (len >= static_cast< int >(sizeof(buf)) || len < 0)
+ return "0.0";
+ else
+ return buf;
+}
+
+static
+void
+append_to_vector(std::vector< std::string >& v1,
+ const std::vector< std::string >& v2)
+{
+ std::copy(v2.begin(), v2.end(),
+ std::back_insert_iterator< std::vector< std::string > >(v1));
+}
+
+static
+char**
+vector_to_argv(const std::vector< std::string >& v)
+{
+ char** argv = new char*[v.size() + 1];
+ for (std::vector< std::string >::size_type i = 0; i < v.size(); i++) {
+ argv[i] = strdup(v[i].c_str());
+ }
+ argv[v.size()] = NULL;
+ return argv;
+}
+
+static
+void
+exec_or_exit(const tools::fs::path& executable,
+ const std::vector< std::string >& argv)
+{
+ // This leaks memory in case of a failure, but it is OK. Exiting will
+ // do the necessary cleanup.
+ char* const* native_argv = vector_to_argv(argv);
+
+ ::execv(executable.c_str(), native_argv);
+
+ const std::string message = "Failed to execute '" + executable.str() +
+ "': " + std::strerror(errno) + "\n";
+ if (::write(STDERR_FILENO, message.c_str(), message.length()) == -1)
+ std::abort();
+ std::exit(EXIT_FAILURE);
+}
+
+static
+std::vector< std::string >
+config_to_args(const vars_map& config)
+{
+ std::vector< std::string > args;
+
+ for (vars_map::const_iterator iter = config.begin();
+ iter != config.end(); iter++)
+ args.push_back("-v" + (*iter).first + "=" + (*iter).second);
+
+ return args;
+}
+
+static
+void
+silence_stdin(void)
+{
+ ::close(STDIN_FILENO);
+ int fd = ::open("/dev/null", O_RDONLY);
+ if (fd == -1)
+ throw std::runtime_error("Could not open /dev/null");
+ assert(fd == STDIN_FILENO);
+}
+
+static
+void
+prepare_child(const tools::fs::path& workdir)
+{
+ const int ret = ::setpgid(::getpid(), 0);
+ assert(ret != -1);
+
+ ::umask(S_IWGRP | S_IWOTH);
+
+ for (int i = 1; i <= tools::signals::last_signo; i++)
+ tools::signals::reset(i);
+
+ tools::env::set("HOME", workdir.str());
+ tools::env::unset("LANG");
+ tools::env::unset("LC_ALL");
+ tools::env::unset("LC_COLLATE");
+ tools::env::unset("LC_CTYPE");
+ tools::env::unset("LC_MESSAGES");
+ tools::env::unset("LC_MONETARY");
+ tools::env::unset("LC_NUMERIC");
+ tools::env::unset("LC_TIME");
+ tools::env::set("TZ", "UTC");
+
+ tools::env::set("__RUNNING_INSIDE_ATF_RUN", "internal-yes-value");
+
+ tools::fs::change_directory(workdir);
+
+ silence_stdin();
+}
+
+static
+void
+get_metadata_child(void* raw_params)
+{
+ const get_metadata_params* params =
+ static_cast< const get_metadata_params* >(raw_params);
+
+ std::vector< std::string > argv;
+ argv.push_back(params->executable.leaf_name());
+ argv.push_back("-l");
+ argv.push_back("-s" + params->executable.branch_path().str());
+ append_to_vector(argv, config_to_args(params->config));
+
+ exec_or_exit(params->executable, argv);
+}
+
+void
+run_test_case_child(void* raw_params)
+{
+ const test_case_params* params =
+ static_cast< const test_case_params* >(raw_params);
+
+ const std::pair< int, int > user = tools::get_required_user(
+ params->metadata, params->config);
+ if (user.first != -1 && user.second != -1)
+ tools::user::drop_privileges(user);
+
+ // The input 'tp' parameter may be relative and become invalid once
+ // we change the current working directory.
+ const tools::fs::path absolute_executable = params->executable.to_absolute();
+
+ // Prepare the test program's arguments. We use dynamic memory and
+ // do not care to release it. We are going to die anyway very soon,
+ // either due to exec(2) or to exit(3).
+ std::vector< std::string > argv;
+ argv.push_back(absolute_executable.leaf_name());
+ argv.push_back("-r" + params->resfile.str());
+ argv.push_back("-s" + absolute_executable.branch_path().str());
+ append_to_vector(argv, config_to_args(params->config));
+ argv.push_back(params->test_case_name + ":" + params->test_case_part);
+
+ prepare_child(params->workdir);
+ exec_or_exit(absolute_executable, argv);
+}
+
+static void
+tokenize_result(const std::string& line, std::string& out_state,
+ std::string& out_arg, std::string& out_reason)
+{
+ const std::string::size_type pos = line.find_first_of(":(");
+ if (pos == std::string::npos) {
+ out_state = line;
+ out_arg = "";
+ out_reason = "";
+ } else if (line[pos] == ':') {
+ out_state = line.substr(0, pos);
+ out_arg = "";
+ out_reason = tools::text::trim(line.substr(pos + 1));
+ } else if (line[pos] == '(') {
+ const std::string::size_type pos2 = line.find("):", pos);
+ if (pos2 == std::string::npos)
+ throw std::runtime_error("Invalid test case result '" + line +
+ "': unclosed optional argument");
+ out_state = line.substr(0, pos);
+ out_arg = line.substr(pos + 1, pos2 - pos - 1);
+ out_reason = tools::text::trim(line.substr(pos2 + 2));
+ } else
+ std::abort();
+}
+
+static impl::test_case_result
+handle_result(const std::string& state, const std::string& arg,
+ const std::string& reason)
+{
+ assert(state == "passed");
+
+ if (!arg.empty() || !reason.empty())
+ throw std::runtime_error("The test case result '" + state + "' cannot "
+ "be accompanied by a reason nor an expected value");
+
+ return impl::test_case_result(state, -1, reason);
+}
+
+static impl::test_case_result
+handle_result_with_reason(const std::string& state, const std::string& arg,
+ const std::string& reason)
+{
+ assert(state == "expected_death" || state == "expected_failure" ||
+ state == "expected_timeout" || state == "failed" || state == "skipped");
+
+ if (!arg.empty() || reason.empty())
+ throw std::runtime_error("The test case result '" + state + "' must "
+ "be accompanied by a reason but not by an expected value");
+
+ return impl::test_case_result(state, -1, reason);
+}
+
+static impl::test_case_result
+handle_result_with_reason_and_arg(const std::string& state,
+ const std::string& arg,
+ const std::string& reason)
+{
+ assert(state == "expected_exit" || state == "expected_signal");
+
+ if (reason.empty())
+ throw std::runtime_error("The test case result '" + state + "' must "
+ "be accompanied by a reason");
+
+ int value;
+ if (arg.empty()) {
+ value = -1;
+ } else {
+ try {
+ value = tools::text::to_type< int >(arg);
+ } catch (const std::runtime_error&) {
+ throw std::runtime_error("The value '" + arg + "' passed to the '" +
+ state + "' state must be an integer");
+ }
+ }
+
+ return impl::test_case_result(state, value, reason);
+}
+
+} // anonymous namespace
+
+detail::atf_tp_reader::atf_tp_reader(std::istream& is) :
+ m_is(is)
+{
+}
+
+detail::atf_tp_reader::~atf_tp_reader(void)
+{
+}
+
+void
+detail::atf_tp_reader::got_tc(
+ const std::string& ident ATF_DEFS_ATTRIBUTE_UNUSED,
+ const std::map< std::string, std::string >& md ATF_DEFS_ATTRIBUTE_UNUSED)
+{
+}
+
+void
+detail::atf_tp_reader::got_eof(void)
+{
+}
+
+void
+detail::atf_tp_reader::validate_and_insert(const std::string& name,
+ const std::string& value, const size_t lineno,
+ std::map< std::string, std::string >& md)
+{
+ using tools::parser::parse_error;
+
+ if (value.empty())
+ throw parse_error(lineno, "The value for '" + name +"' cannot be "
+ "empty");
+
+ const std::string ident_regex = "^[_A-Za-z0-9]+$";
+ const std::string integer_regex = "^[0-9]+$";
+
+ if (name == "descr") {
+ // Any non-empty value is valid.
+ } else if (name == "has.cleanup") {
+ try {
+ (void)tools::text::to_bool(value);
+ } catch (const std::runtime_error&) {
+ throw parse_error(lineno, "The has.cleanup property requires a"
+ " boolean value");
+ }
+ } else if (name == "ident") {
+ if (!tools::text::match(value, ident_regex))
+ throw parse_error(lineno, "The identifier must match " +
+ ident_regex + "; was '" + value + "'");
+ } else if (name == "require.arch") {
+ } else if (name == "require.config") {
+ } else if (name == "require.files") {
+ } else if (name == "require.machine") {
+ } else if (name == "require.memory") {
+ try {
+ (void)tools::text::to_bytes(value);
+ } catch (const std::runtime_error&) {
+ throw parse_error(lineno, "The require.memory property requires an "
+ "integer value representing an amount of bytes");
+ }
+ } else if (name == "require.progs") {
+ } else if (name == "require.user") {
+ } else if (name == "timeout") {
+ if (!tools::text::match(value, integer_regex))
+ throw parse_error(lineno, "The timeout property requires an integer"
+ " value");
+ } else if (name == "use.fs") {
+ // Deprecated; ignore it.
+ } else if (name.size() > 2 && name[0] == 'X' && name[1] == '-') {
+ // Any non-empty value is valid.
+ } else {
+ throw parse_error(lineno, "Unknown property '" + name + "'");
+ }
+
+ md.insert(std::make_pair(name, value));
+}
+
+void
+detail::atf_tp_reader::read(void)
+{
+ using tools::parser::parse_error;
+ using namespace atf_tp;
+
+ std::pair< size_t, tools::parser::headers_map > hml =
+ tools::parser::read_headers(m_is, 1);
+ tools::parser::validate_content_type(hml.second,
+ "application/X-atf-tp", 1);
+
+ tokenizer tkz(m_is, hml.first);
+ tools::parser::parser< tokenizer > p(tkz);
+
+ try {
+ tools::parser::token t = p.expect(text_type, "property name");
+ if (t.text() != "ident")
+ throw parse_error(t.lineno(), "First property of a test case "
+ "must be 'ident'");
+
+ std::map< std::string, std::string > props;
+ do {
+ const std::string name = t.text();
+ t = p.expect(colon_type, "`:'");
+ const std::string value = tools::text::trim(p.rest_of_line());
+ t = p.expect(nl_type, "new line");
+ validate_and_insert(name, value, t.lineno(), props);
+
+ t = p.expect(eof_type, nl_type, text_type, "property name, new "
+ "line or eof");
+ if (t.type() == nl_type || t.type() == eof_type) {
+ const std::map< std::string, std::string >::const_iterator
+ iter = props.find("ident");
+ if (iter == props.end())
+ throw parse_error(t.lineno(), "Test case definition did "
+ "not define an 'ident' property");
+ ATF_PARSER_CALLBACK(p, got_tc((*iter).second, props));
+ props.clear();
+
+ if (t.type() == nl_type) {
+ t = p.expect(text_type, "property name");
+ if (t.text() != "ident")
+ throw parse_error(t.lineno(), "First property of a "
+ "test case must be 'ident'");
+ }
+ }
+ } while (t.type() != eof_type);
+ ATF_PARSER_CALLBACK(p, got_eof());
+ } catch (const parse_error& pe) {
+ p.add_error(pe);
+ p.reset(nl_type);
+ }
+}
+
+impl::test_case_result
+detail::parse_test_case_result(const std::string& line)
+{
+ std::string state, arg, reason;
+ tokenize_result(line, state, arg, reason);
+
+ if (state == "expected_death")
+ return handle_result_with_reason(state, arg, reason);
+ else if (state.compare(0, 13, "expected_exit") == 0)
+ return handle_result_with_reason_and_arg(state, arg, reason);
+ else if (state.compare(0, 16, "expected_failure") == 0)
+ return handle_result_with_reason(state, arg, reason);
+ else if (state.compare(0, 15, "expected_signal") == 0)
+ return handle_result_with_reason_and_arg(state, arg, reason);
+ else if (state.compare(0, 16, "expected_timeout") == 0)
+ return handle_result_with_reason(state, arg, reason);
+ else if (state == "failed")
+ return handle_result_with_reason(state, arg, reason);
+ else if (state == "passed")
+ return handle_result(state, arg, reason);
+ else if (state == "skipped")
+ return handle_result_with_reason(state, arg, reason);
+ else
+ throw std::runtime_error("Unknown test case result type in: " + line);
+}
+
+impl::atf_tps_writer::atf_tps_writer(std::ostream& os) :
+ m_os(os)
+{
+ tools::parser::headers_map hm;
+ tools::parser::attrs_map ct_attrs;
+ ct_attrs["version"] = "3";
+ hm["Content-Type"] =
+ tools::parser::header_entry("Content-Type", "application/X-atf-tps",
+ ct_attrs);
+ tools::parser::write_headers(hm, m_os);
+}
+
+void
+impl::atf_tps_writer::info(const std::string& what, const std::string& val)
+{
+ m_os << "info: " << what << ", " << val << "\n";
+ m_os.flush();
+}
+
+void
+impl::atf_tps_writer::ntps(size_t p_ntps)
+{
+ m_os << "tps-count: " << p_ntps << "\n";
+ m_os.flush();
+}
+
+void
+impl::atf_tps_writer::start_tp(const std::string& tp, size_t ntcs)
+{
+ m_tpname = tp;
+ m_os << "tp-start: " << generate_timestamp() << ", " << tp << ", "
+ << ntcs << "\n";
+ m_os.flush();
+}
+
+void
+impl::atf_tps_writer::end_tp(const std::string& reason)
+{
+ assert(reason.find('\n') == std::string::npos);
+ if (reason.empty())
+ m_os << "tp-end: " << generate_timestamp() << ", " << m_tpname << "\n";
+ else
+ m_os << "tp-end: " << generate_timestamp() << ", " << m_tpname
+ << ", " << reason << "\n";
+ m_os.flush();
+}
+
+void
+impl::atf_tps_writer::start_tc(const std::string& tcname)
+{
+ m_tcname = tcname;
+ m_os << "tc-start: " << generate_timestamp() << ", " << tcname << "\n";
+ m_os.flush();
+}
+
+void
+impl::atf_tps_writer::stdout_tc(const std::string& line)
+{
+ m_os << "tc-so:" << line << "\n";
+ check_stream(m_os);
+ m_os.flush();
+ check_stream(m_os);
+}
+
+void
+impl::atf_tps_writer::stderr_tc(const std::string& line)
+{
+ m_os << "tc-se:" << line << "\n";
+ check_stream(m_os);
+ m_os.flush();
+ check_stream(m_os);
+}
+
+void
+impl::atf_tps_writer::end_tc(const std::string& state,
+ const std::string& reason)
+{
+ std::string str = ", " + m_tcname + ", " + state;
+ if (!reason.empty())
+ str += ", " + reason;
+ m_os << "tc-end: " << generate_timestamp() << str << "\n";
+ m_os.flush();
+}
+
+impl::metadata
+impl::get_metadata(const tools::fs::path& executable,
+ const vars_map& config)
+{
+ get_metadata_params params(executable, config);
+ tools::process::child child =
+ tools::process::fork(get_metadata_child,
+ tools::process::stream_capture(),
+ tools::process::stream_inherit(),
+ static_cast< void * >(&params));
+
+ tools::io::pistream outin(child.stdout_fd());
+
+ metadata_reader parser(outin);
+ parser.read();
+
+ const tools::process::status status = child.wait();
+ if (!status.exited() || status.exitstatus() != EXIT_SUCCESS)
+ throw tools::parser::format_error("Test program returned failure "
+ "exit status for test case list");
+
+ return metadata(parser.get_tcs());
+}
+
+impl::test_case_result
+impl::read_test_case_result(const tools::fs::path& results_path)
+{
+ std::ifstream results_file(results_path.c_str());
+ if (!results_file)
+ throw std::runtime_error("Failed to open " + results_path.str());
+
+ std::string line, extra_line;
+ std::getline(results_file, line);
+ if (!results_file.good())
+ throw std::runtime_error("Results file is empty");
+
+ while (std::getline(results_file, extra_line).good())
+ line += "<<NEWLINE UNEXPECTED>>" + extra_line;
+
+ results_file.close();
+
+ return detail::parse_test_case_result(line);
+}
+
+namespace {
+
+static volatile bool terminate_poll;
+
+static void
+sigchld_handler(const int signo ATF_DEFS_ATTRIBUTE_UNUSED)
+{
+ terminate_poll = true;
+}
+
+class child_muxer : public tools::io::muxer {
+ impl::atf_tps_writer& m_writer;
+
+ void
+ line_callback(const size_t index, const std::string& line)
+ {
+ switch (index) {
+ case 0: m_writer.stdout_tc(line); break;
+ case 1: m_writer.stderr_tc(line); break;
+ default: std::abort();
+ }
+ }
+
+public:
+ child_muxer(const int* fds, const size_t nfds,
+ impl::atf_tps_writer& writer) :
+ muxer(fds, nfds),
+ m_writer(writer)
+ {
+ }
+};
+
+} // anonymous namespace
+
+std::pair< std::string, tools::process::status >
+impl::run_test_case(const tools::fs::path& executable,
+ const std::string& test_case_name,
+ const std::string& test_case_part,
+ const vars_map& metadata,
+ const vars_map& config,
+ const tools::fs::path& resfile,
+ const tools::fs::path& workdir,
+ atf_tps_writer& writer)
+{
+ // TODO: Capture termination signals and deliver them to the subprocess
+ // instead. Or maybe do something else; think about it.
+
+ test_case_params params(executable, test_case_name, test_case_part,
+ metadata, config, resfile, workdir);
+ tools::process::child child =
+ tools::process::fork(run_test_case_child,
+ tools::process::stream_capture(),
+ tools::process::stream_capture(),
+ static_cast< void * >(&params));
+
+ terminate_poll = false;
+
+ const vars_map::const_iterator iter = metadata.find("timeout");
+ assert(iter != metadata.end());
+ const unsigned int timeout =
+ tools::text::to_type< unsigned int >((*iter).second);
+ const pid_t child_pid = child.pid();
+
+ // Get the input stream of stdout and stderr.
+ tools::io::file_handle outfh = child.stdout_fd();
+ tools::io::file_handle errfh = child.stderr_fd();
+
+ bool timed_out = false;
+
+ // Process the test case's output and multiplex it into our output
+ // stream as we read it.
+ int fds[2] = {outfh.get(), errfh.get()};
+ child_muxer mux(fds, 2, writer);
+ try {
+ timers::child_timer timeout_timer(timeout, child_pid, terminate_poll);
+ signals::signal_programmer sigchld(SIGCHLD, sigchld_handler);
+ mux.mux(terminate_poll);
+ timed_out = timeout_timer.fired();
+ } catch (...) {
+ std::abort();
+ }
+
+ ::killpg(child_pid, SIGKILL);
+ mux.flush();
+ tools::process::status status = child.wait();
+
+ std::string reason;
+
+ if (timed_out) {
+ // Don't assume the child process has been signaled due to the timeout
+ // expiration as older versions did. The child process may have exited
+ // but we may have timed out due to a subchild process getting stuck.
+ reason = "Test case timed out after " + tools::text::to_string(timeout) +
+ " " + (timeout == 1 ? "second" : "seconds");
+ }
+
+ return std::make_pair(reason, status);
+}
diff --git a/unit/atf-src/tools/test-program.hpp b/unit/atf-src/tools/test-program.hpp
new file mode 100644
index 0000000..f2f1972
--- /dev/null
+++ b/unit/atf-src/tools/test-program.hpp
@@ -0,0 +1,157 @@
+//
+// Automated Testing Framework (atf)
+//
+// Copyright (c) 2010 The NetBSD Foundation, Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
+// CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+// IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
+// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+// IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+
+#if !defined(TOOLS_TEST_PROGRAM_HPP)
+#define TOOLS_TEST_PROGRAM_HPP
+
+#include <map>
+#include <string>
+
+#include "fs.hpp"
+#include "process.hpp"
+
+namespace tools {
+namespace test_program {
+
+struct test_case_result {
+ std::string m_state;
+ int m_value;
+ std::string m_reason;
+
+public:
+ test_case_result(void) :
+ m_state("UNINITIALIZED"),
+ m_value(-1),
+ m_reason("")
+ {
+ }
+
+ test_case_result(const std::string& p_state, const int p_value,
+ const std::string& p_reason) :
+ m_state(p_state),
+ m_value(p_value),
+ m_reason(p_reason)
+ {
+ }
+
+ const std::string&
+ state(void) const
+ {
+ return m_state;
+ }
+
+ int
+ value(void) const
+ {
+ return m_value;
+ }
+
+ const std::string&
+ reason(void) const
+ {
+ return m_reason;
+ }
+};
+
+namespace detail {
+
+class atf_tp_reader {
+ std::istream& m_is;
+
+ void validate_and_insert(const std::string&, const std::string&,
+ const size_t,
+ std::map< std::string, std::string >&);
+
+protected:
+ virtual void got_tc(const std::string&,
+ const std::map< std::string, std::string >&);
+ virtual void got_eof(void);
+
+public:
+ atf_tp_reader(std::istream&);
+ virtual ~atf_tp_reader(void);
+
+ void read(void);
+};
+
+test_case_result parse_test_case_result(const std::string&);
+
+} // namespace detail
+
+class atf_tps_writer {
+ std::ostream& m_os;
+
+ std::string m_tpname, m_tcname;
+
+public:
+ atf_tps_writer(std::ostream&);
+
+ void info(const std::string&, const std::string&);
+ void ntps(size_t);
+
+ void start_tp(const std::string&, size_t);
+ void end_tp(const std::string&);
+
+ void start_tc(const std::string&);
+ void stdout_tc(const std::string&);
+ void stderr_tc(const std::string&);
+ void end_tc(const std::string&, const std::string&);
+};
+
+typedef std::map< std::string, std::map< std::string, std::string > >
+ test_cases_map;
+
+struct metadata {
+ test_cases_map test_cases;
+
+ metadata(void)
+ {
+ }
+
+ metadata(const test_cases_map& p_test_cases) :
+ test_cases(p_test_cases)
+ {
+ }
+};
+
+class atf_tps_writer;
+
+metadata get_metadata(const tools::fs::path&,
+ const std::map< std::string, std::string >&);
+test_case_result read_test_case_result(const tools::fs::path&);
+std::pair< std::string, tools::process::status > run_test_case(
+ const tools::fs::path&, const std::string&, const std::string&,
+ const std::map< std::string, std::string >&,
+ const std::map< std::string, std::string >&,
+ const tools::fs::path&, const tools::fs::path&, atf_tps_writer&);
+
+} // namespace test_program
+} // namespace tools
+
+#endif // !defined(TOOLS_TEST_PROGRAM_HPP)
diff --git a/unit/atf-src/tools/test_helpers.hpp b/unit/atf-src/tools/test_helpers.hpp
new file mode 100644
index 0000000..3ac92d0
--- /dev/null
+++ b/unit/atf-src/tools/test_helpers.hpp
@@ -0,0 +1,116 @@
+//
+// Automated Testing Framework (atf)
+//
+// Copyright (c) 2009 The NetBSD Foundation, Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
+// CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+// IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
+// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+// IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+
+#if defined(TOOLS_TEST_HELPERS_H)
+# error "Cannot include test_helpers.hpp more than once."
+#else
+# define TOOLS_TEST_HELPERS_H
+#endif
+
+#include <iostream>
+#include <sstream>
+#include <utility>
+
+#include <atf-c++.hpp>
+
+#include "parser.hpp"
+#include "text.hpp"
+
+namespace test_helpers_detail {
+
+typedef std::vector< std::string > string_vector;
+
+template< class Reader >
+std::pair< string_vector, string_vector >
+do_read(const char* input)
+{
+ string_vector errors;
+
+ std::istringstream is(input);
+ Reader reader(is);
+ try {
+ reader.read();
+ } catch (const tools::parser::parse_errors& pes) {
+ for (std::vector< tools::parser::parse_error >::const_iterator iter =
+ pes.begin(); iter != pes.end(); iter++)
+ errors.push_back(*iter);
+ } catch (const tools::parser::parse_error& pe) {
+ ATF_FAIL("Raised a lonely parse error: " +
+ tools::text::to_string(pe.first) + ": " + pe.second);
+ }
+
+ return std::make_pair(reader.m_calls, errors);
+}
+
+void
+check_equal(const char* expected[], const string_vector& actual)
+{
+ const char** expected_iter = expected;
+ string_vector::const_iterator actual_iter = actual.begin();
+
+ bool equals = true;
+ while (equals && *expected_iter != NULL && actual_iter != actual.end()) {
+ if (*expected_iter != *actual_iter) {
+ equals = false;
+ } else {
+ expected_iter++;
+ actual_iter++;
+ }
+ }
+ if (equals && ((*expected_iter == NULL && actual_iter != actual.end()) ||
+ (*expected_iter != NULL && actual_iter == actual.end())))
+ equals = false;
+
+ if (!equals) {
+ std::cerr << "EXPECTED:\n";
+ for (expected_iter = expected; *expected_iter != NULL; expected_iter++)
+ std::cerr << *expected_iter << "\n";
+
+ std::cerr << "ACTUAL:\n";
+ for (actual_iter = actual.begin(); actual_iter != actual.end();
+ actual_iter++)
+ std::cerr << *actual_iter << "\n";
+
+ ATF_FAIL("Expected results differ to actual values");
+ }
+}
+
+} // namespace test_helpers_detail
+
+template< class Reader >
+void
+do_parser_test(const char* input, const char* exp_calls[],
+ const char* exp_errors[])
+{
+ const std::pair< test_helpers_detail::string_vector,
+ test_helpers_detail::string_vector >
+ actual = test_helpers_detail::do_read< Reader >(input);
+ test_helpers_detail::check_equal(exp_calls, actual.first);
+ test_helpers_detail::check_equal(exp_errors, actual.second);
+}
diff --git a/unit/atf-src/tools/test_program_test.cpp b/unit/atf-src/tools/test_program_test.cpp
new file mode 100644
index 0000000..693790d
--- /dev/null
+++ b/unit/atf-src/tools/test_program_test.cpp
@@ -0,0 +1,1023 @@
+//
+// Automated Testing Framework (atf)
+//
+// Copyright (c) 2010 The NetBSD Foundation, Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
+// CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+// IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
+// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+// IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+
+#include <fstream>
+#include <iostream>
+
+#include <atf-c++.hpp>
+
+#include "parser.hpp"
+#include "test-program.hpp"
+#include "test_helpers.hpp"
+#include "text.hpp"
+
+namespace impl = tools::test_program;
+namespace detail = tools::test_program::detail;
+
+// -------------------------------------------------------------------------
+// Auxiliary functions.
+// -------------------------------------------------------------------------
+
+namespace {
+
+typedef std::map< std::string, std::string > vars_map;
+
+static
+tools::fs::path
+get_helper(const atf::tests::tc& tc, const char* name)
+{
+ return tools::fs::path(tc.get_config_var("srcdir")) / name;
+}
+
+static
+void
+check_property(const vars_map& props, const char* name, const char* value)
+{
+ const vars_map::const_iterator iter = props.find(name);
+ ATF_REQUIRE(iter != props.end());
+ ATF_REQUIRE_EQ(value, (*iter).second);
+}
+
+static void
+check_result(const char* exp_state, const int exp_value, const char* exp_reason,
+ const impl::test_case_result& tcr)
+{
+ ATF_REQUIRE_EQ(exp_state, tcr.state());
+ ATF_REQUIRE_EQ(exp_value, tcr.value());
+ ATF_REQUIRE_EQ(exp_reason, tcr.reason());
+}
+
+static
+void
+write_test_case_result(const char *results_path, const std::string& contents)
+{
+ std::ofstream results_file(results_path);
+ ATF_REQUIRE(results_file);
+
+ results_file << contents;
+}
+
+static
+void
+print_indented(const std::string& str)
+{
+ std::vector< std::string > ws = tools::text::split(str, "\n");
+ for (std::vector< std::string >::const_iterator iter = ws.begin();
+ iter != ws.end(); iter++)
+ std::cout << ">>" << *iter << "<<\n";
+}
+
+// XXX Should this string handling and verbosity level be part of the
+// ATF_REQUIRE_EQ macro? It may be hard to predict sometimes that a
+// string can have newlines in it, and so the error message generated
+// at the moment will be bogus if there are some.
+static
+void
+check_match(const atf::tests::tc& tc, const std::string& str,
+ const std::string& exp)
+{
+ if (!tools::text::match(str, exp)) {
+ std::cout << "String match check failed.\n"
+ << "Adding >> and << to delimit the string boundaries "
+ "below.\n";
+ std::cout << "GOT:\n";
+ print_indented(str);
+ std::cout << "EXPECTED:\n";
+ print_indented(exp);
+ tc.fail("Constructed string differs from the expected one");
+ }
+}
+
+} // anonymous namespace
+
+// -------------------------------------------------------------------------
+// Tests for the "tp" reader.
+// -------------------------------------------------------------------------
+
+class tp_reader : protected detail::atf_tp_reader {
+ void
+ got_tc(const std::string& ident,
+ const std::map< std::string, std::string >& md)
+ {
+ std::string call = "got_tc(" + ident + ", {";
+ for (std::map< std::string, std::string >::const_iterator iter =
+ md.begin(); iter != md.end(); iter++) {
+ if (iter != md.begin())
+ call += ", ";
+ call += (*iter).first + '=' + (*iter).second;
+ }
+ call += "})";
+ m_calls.push_back(call);
+ }
+
+ void
+ got_eof(void)
+ {
+ m_calls.push_back("got_eof()");
+ }
+
+public:
+ tp_reader(std::istream& is) :
+ detail::atf_tp_reader(is)
+ {
+ }
+
+ void
+ read(void)
+ {
+ atf_tp_reader::read();
+ }
+
+ std::vector< std::string > m_calls;
+};
+
+ATF_TEST_CASE_WITHOUT_HEAD(tp_1);
+ATF_TEST_CASE_BODY(tp_1)
+{
+ const char* input =
+ "Content-Type: application/X-atf-tp; version=\"1\"\n"
+ "\n"
+ "ident: test_case_1\n"
+ "\n"
+ "ident: test_case_2\n"
+ "\n"
+ "ident: test_case_3\n"
+ ;
+
+ const char* exp_calls[] = {
+ "got_tc(test_case_1, {ident=test_case_1})",
+ "got_tc(test_case_2, {ident=test_case_2})",
+ "got_tc(test_case_3, {ident=test_case_3})",
+ "got_eof()",
+ NULL
+ };
+
+ const char* exp_errors[] = {
+ NULL
+ };
+
+ do_parser_test< tp_reader >(input, exp_calls, exp_errors);
+}
+
+ATF_TEST_CASE_WITHOUT_HEAD(tp_2);
+ATF_TEST_CASE_BODY(tp_2)
+{
+ const char* input =
+ "Content-Type: application/X-atf-tp; version=\"1\"\n"
+ "\n"
+ "ident: test_case_1\n"
+ "descr: This is the description\n"
+ "timeout: 300\n"
+ "\n"
+ "ident: test_case_2\n"
+ "\n"
+ "ident: test_case_3\n"
+ "X-prop1: A custom property\n"
+ "descr: Third test case\n"
+ ;
+
+ // NO_CHECK_STYLE_BEGIN
+ const char* exp_calls[] = {
+ "got_tc(test_case_1, {descr=This is the description, ident=test_case_1, timeout=300})",
+ "got_tc(test_case_2, {ident=test_case_2})",
+ "got_tc(test_case_3, {X-prop1=A custom property, descr=Third test case, ident=test_case_3})",
+ "got_eof()",
+ NULL
+ };
+ // NO_CHECK_STYLE_END
+
+ const char* exp_errors[] = {
+ NULL
+ };
+
+ do_parser_test< tp_reader >(input, exp_calls, exp_errors);
+}
+
+ATF_TEST_CASE_WITHOUT_HEAD(tp_3);
+ATF_TEST_CASE_BODY(tp_3)
+{
+ const char* input =
+ "Content-Type: application/X-atf-tp; version=\"1\"\n"
+ "\n"
+ "ident: single_test\n"
+ "descr: Some description\n"
+ "timeout: 300\n"
+ "require.arch: thearch\n"
+ "require.config: foo-bar\n"
+ "require.files: /a/1 /b/2\n"
+ "require.machine: themachine\n"
+ "require.progs: /bin/cp mv\n"
+ "require.user: root\n"
+ ;
+
+ // NO_CHECK_STYLE_BEGIN
+ const char* exp_calls[] = {
+ "got_tc(single_test, {descr=Some description, ident=single_test, require.arch=thearch, require.config=foo-bar, require.files=/a/1 /b/2, require.machine=themachine, require.progs=/bin/cp mv, require.user=root, timeout=300})",
+ "got_eof()",
+ NULL
+ };
+ // NO_CHECK_STYLE_END
+
+ const char* exp_errors[] = {
+ NULL
+ };
+
+ do_parser_test< tp_reader >(input, exp_calls, exp_errors);
+}
+
+ATF_TEST_CASE_WITHOUT_HEAD(tp_4);
+ATF_TEST_CASE_BODY(tp_4)
+{
+ const char* input =
+ "Content-Type: application/X-atf-tp; version=\"1\"\n"
+ "\n"
+ "ident: single_test \n"
+ "descr: Some description \n"
+ ;
+
+ const char* exp_calls[] = {
+ "got_tc(single_test, {descr=Some description, ident=single_test})",
+ "got_eof()",
+ NULL
+ };
+
+ const char* exp_errors[] = {
+ NULL
+ };
+
+ do_parser_test< tp_reader >(input, exp_calls, exp_errors);
+}
+
+ATF_TEST_CASE_WITHOUT_HEAD(tp_50);
+ATF_TEST_CASE_BODY(tp_50)
+{
+ const char* input =
+ "Content-Type: application/X-atf-tp; version=\"1\"\n"
+ "\n"
+ ;
+
+ const char* exp_calls[] = {
+ NULL
+ };
+
+ const char* exp_errors[] = {
+ "3: Unexpected token `<<EOF>>'; expected property name",
+ NULL
+ };
+
+ do_parser_test< tp_reader >(input, exp_calls, exp_errors);
+}
+
+ATF_TEST_CASE_WITHOUT_HEAD(tp_51);
+ATF_TEST_CASE_BODY(tp_51)
+{
+ const char* input =
+ "Content-Type: application/X-atf-tp; version=\"1\"\n"
+ "\n"
+ "\n"
+ "\n"
+ "\n"
+ ;
+
+ const char* exp_calls[] = {
+ NULL
+ };
+
+ const char* exp_errors[] = {
+ "3: Unexpected token `<<NEWLINE>>'; expected property name",
+ NULL
+ };
+
+ do_parser_test< tp_reader >(input, exp_calls, exp_errors);
+}
+
+ATF_TEST_CASE_WITHOUT_HEAD(tp_52);
+ATF_TEST_CASE_BODY(tp_52)
+{
+ const char* input =
+ "Content-Type: application/X-atf-tp; version=\"1\"\n"
+ "\n"
+ "ident: test1\n"
+ "ident: test2\n"
+ ;
+
+ const char* exp_calls[] = {
+ "got_tc(test1, {ident=test1})",
+ "got_eof()",
+ NULL
+ };
+
+ const char* exp_errors[] = {
+ NULL
+ };
+
+ do_parser_test< tp_reader >(input, exp_calls, exp_errors);
+}
+
+ATF_TEST_CASE_WITHOUT_HEAD(tp_53);
+ATF_TEST_CASE_BODY(tp_53)
+{
+ const char* input =
+ "Content-Type: application/X-atf-tp; version=\"1\"\n"
+ "\n"
+ "descr: Out of order\n"
+ "ident: test1\n"
+ ;
+
+ const char* exp_calls[] = {
+ NULL
+ };
+
+ const char* exp_errors[] = {
+ "3: First property of a test case must be 'ident'",
+ NULL
+ };
+
+ do_parser_test< tp_reader >(input, exp_calls, exp_errors);
+}
+
+ATF_TEST_CASE_WITHOUT_HEAD(tp_54);
+ATF_TEST_CASE_BODY(tp_54)
+{
+ const char* input =
+ "Content-Type: application/X-atf-tp; version=\"1\"\n"
+ "\n"
+ "ident:\n"
+ ;
+
+ const char* exp_calls[] = {
+ NULL
+ };
+
+ const char* exp_errors[] = {
+ "3: The value for 'ident' cannot be empty",
+ NULL
+ };
+
+ do_parser_test< tp_reader >(input, exp_calls, exp_errors);
+}
+
+ATF_TEST_CASE_WITHOUT_HEAD(tp_55);
+ATF_TEST_CASE_BODY(tp_55)
+{
+ const char* input =
+ "Content-Type: application/X-atf-tp; version=\"1\"\n"
+ "\n"
+ "ident: +*,\n"
+ ;
+
+ const char* exp_calls[] = {
+ NULL
+ };
+
+ const char* exp_errors[] = {
+ "3: The identifier must match ^[_A-Za-z0-9]+$; was '+*,'",
+ NULL
+ };
+
+ do_parser_test< tp_reader >(input, exp_calls, exp_errors);
+}
+
+ATF_TEST_CASE_WITHOUT_HEAD(tp_56);
+ATF_TEST_CASE_BODY(tp_56)
+{
+ const char* input =
+ "Content-Type: application/X-atf-tp; version=\"1\"\n"
+ "\n"
+ "ident: test\n"
+ "timeout: hello\n"
+ ;
+
+ const char* exp_calls[] = {
+ NULL
+ };
+
+ const char* exp_errors[] = {
+ "4: The timeout property requires an integer value",
+ NULL
+ };
+
+ do_parser_test< tp_reader >(input, exp_calls, exp_errors);
+}
+
+ATF_TEST_CASE_WITHOUT_HEAD(tp_57);
+ATF_TEST_CASE_BODY(tp_57)
+{
+ const char* input =
+ "Content-Type: application/X-atf-tp; version=\"1\"\n"
+ "\n"
+ "ident: test\n"
+ "unknown: property\n"
+ ;
+
+ const char* exp_calls[] = {
+ NULL
+ };
+
+ const char* exp_errors[] = {
+ "4: Unknown property 'unknown'",
+ NULL
+ };
+
+ do_parser_test< tp_reader >(input, exp_calls, exp_errors);
+}
+
+ATF_TEST_CASE_WITHOUT_HEAD(tp_58);
+ATF_TEST_CASE_BODY(tp_58)
+{
+ const char* input =
+ "Content-Type: application/X-atf-tp; version=\"1\"\n"
+ "\n"
+ "ident: test\n"
+ "X-foo:\n"
+ ;
+
+ const char* exp_calls[] = {
+ NULL
+ };
+
+ const char* exp_errors[] = {
+ "4: The value for 'X-foo' cannot be empty",
+ NULL
+ };
+
+ do_parser_test< tp_reader >(input, exp_calls, exp_errors);
+}
+
+ATF_TEST_CASE_WITHOUT_HEAD(tp_59);
+ATF_TEST_CASE_BODY(tp_59)
+{
+ const char* input =
+ "Content-Type: application/X-atf-tp; version=\"1\"\n"
+ "\n"
+ "\n"
+ "ident: test\n"
+ "timeout: 300\n"
+ ;
+
+ const char* exp_calls[] = {
+ NULL
+ };
+
+ const char* exp_errors[] = {
+ "3: Unexpected token `<<NEWLINE>>'; expected property name",
+ NULL
+ };
+
+ do_parser_test< tp_reader >(input, exp_calls, exp_errors);
+}
+
+ATF_TEST_CASE_WITHOUT_HEAD(tp_60);
+ATF_TEST_CASE_BODY(tp_60)
+{
+ const char* input =
+ "Content-Type: application/X-atf-tp; version=\"1\"\n"
+ "\n"
+ "ident: test\n"
+ "require.memory: 12345D\n"
+ ;
+
+ const char* exp_calls[] = {
+ NULL
+ };
+
+ const char* exp_errors[] = {
+ "4: The require.memory property requires an integer value representing"
+ " an amount of bytes",
+ NULL
+ };
+
+ do_parser_test< tp_reader >(input, exp_calls, exp_errors);
+}
+
+// -------------------------------------------------------------------------
+// Tests for the "tps" writer.
+// -------------------------------------------------------------------------
+
+ATF_TEST_CASE(atf_tps_writer);
+ATF_TEST_CASE_HEAD(atf_tps_writer)
+{
+ set_md_var("descr", "Verifies the application/X-atf-tps writer");
+}
+ATF_TEST_CASE_BODY(atf_tps_writer)
+{
+ std::ostringstream expss;
+ std::ostringstream ss;
+ const char *ts_regex = "[0-9]+\\.[0-9]{1,6}, ";
+
+#define RESET \
+ expss.str(""); \
+ ss.str("")
+
+#define CHECK \
+ check_match(*this, ss.str(), expss.str())
+
+ {
+ RESET;
+
+ impl::atf_tps_writer w(ss);
+ expss << "Content-Type: application/X-atf-tps; version=\"3\"\n\n";
+ CHECK;
+ }
+
+ {
+ RESET;
+
+ impl::atf_tps_writer w(ss);
+ expss << "Content-Type: application/X-atf-tps; version=\"3\"\n\n";
+ CHECK;
+
+ w.info("foo", "bar");
+ expss << "info: foo, bar\n";
+ CHECK;
+
+ w.info("baz", "second info");
+ expss << "info: baz, second info\n";
+ CHECK;
+ }
+
+ {
+ RESET;
+
+ impl::atf_tps_writer w(ss);
+ expss << "Content-Type: application/X-atf-tps; version=\"3\"\n\n";
+ CHECK;
+
+ w.ntps(0);
+ expss << "tps-count: 0\n";
+ CHECK;
+ }
+
+ {
+ RESET;
+
+ impl::atf_tps_writer w(ss);
+ expss << "Content-Type: application/X-atf-tps; version=\"3\"\n\n";
+ CHECK;
+
+ w.ntps(123);
+ expss << "tps-count: 123\n";
+ CHECK;
+ }
+
+ {
+ RESET;
+
+ impl::atf_tps_writer w(ss);
+ expss << "Content-Type: application/X-atf-tps; version=\"3\"\n\n";
+ CHECK;
+
+ w.ntps(2);
+ expss << "tps-count: 2\n";
+ CHECK;
+
+ w.start_tp("foo", 0);
+ expss << "tp-start: " << ts_regex << "foo, 0\n";
+ CHECK;
+
+ w.end_tp("");
+ expss << "tp-end: " << ts_regex << "foo\n";
+ CHECK;
+
+ w.start_tp("bar", 0);
+ expss << "tp-start: " << ts_regex << "bar, 0\n";
+ CHECK;
+
+ w.end_tp("failed program");
+ expss << "tp-end: " << ts_regex << "bar, failed program\n";
+ CHECK;
+ }
+
+ {
+ RESET;
+
+ impl::atf_tps_writer w(ss);
+ expss << "Content-Type: application/X-atf-tps; version=\"3\"\n\n";
+ CHECK;
+
+ w.ntps(1);
+ expss << "tps-count: 1\n";
+ CHECK;
+
+ w.start_tp("foo", 1);
+ expss << "tp-start: " << ts_regex << "foo, 1\n";
+ CHECK;
+
+ w.start_tc("brokentc");
+ expss << "tc-start: " << ts_regex << "brokentc\n";
+ CHECK;
+
+ w.end_tp("aborted");
+ expss << "tp-end: " << ts_regex << "foo, aborted\n";
+ CHECK;
+ }
+
+ {
+ RESET;
+
+ impl::atf_tps_writer w(ss);
+ expss << "Content-Type: application/X-atf-tps; version=\"3\"\n\n";
+ CHECK;
+
+ w.ntps(1);
+ expss << "tps-count: 1\n";
+ CHECK;
+
+ w.start_tp("thetp", 3);
+ expss << "tp-start: " << ts_regex << "thetp, 3\n";
+ CHECK;
+
+ w.start_tc("passtc");
+ expss << "tc-start: " << ts_regex << "passtc\n";
+ CHECK;
+
+ w.end_tc("passed", "");
+ expss << "tc-end: " << ts_regex << "passtc, passed\n";
+ CHECK;
+
+ w.start_tc("failtc");
+ expss << "tc-start: " << ts_regex << "failtc\n";
+ CHECK;
+
+ w.end_tc("failed", "The reason");
+ expss << "tc-end: " << ts_regex << "failtc, failed, The reason\n";
+ CHECK;
+
+ w.start_tc("skiptc");
+ expss << "tc-start: " << ts_regex << "skiptc\n";
+ CHECK;
+
+ w.end_tc("skipped", "The reason");
+ expss << "tc-end: " << ts_regex << "skiptc, skipped, The reason\n";
+ CHECK;
+
+ w.end_tp("");
+ expss << "tp-end: " << ts_regex << "thetp\n";
+ CHECK;
+ }
+
+ {
+ RESET;
+
+ impl::atf_tps_writer w(ss);
+ expss << "Content-Type: application/X-atf-tps; version=\"3\"\n\n";
+ CHECK;
+
+ w.ntps(1);
+ expss << "tps-count: 1\n";
+ CHECK;
+
+ w.start_tp("thetp", 1);
+ expss << "tp-start: " << ts_regex << "thetp, 1\n";
+ CHECK;
+
+ w.start_tc("thetc");
+ expss << "tc-start: " << ts_regex << "thetc\n";
+ CHECK;
+
+ w.stdout_tc("a line");
+ expss << "tc-so:a line\n";
+ CHECK;
+
+ w.stdout_tc("another line");
+ expss << "tc-so:another line\n";
+ CHECK;
+
+ w.stderr_tc("an error message");
+ expss << "tc-se:an error message\n";
+ CHECK;
+
+ w.end_tc("passed", "");
+ expss << "tc-end: " << ts_regex << "thetc, passed\n";
+ CHECK;
+
+ w.end_tp("");
+ expss << "tp-end: " << ts_regex << "thetp\n";
+ CHECK;
+ }
+
+ {
+ RESET;
+
+ impl::atf_tps_writer w(ss);
+ expss << "Content-Type: application/X-atf-tps; version=\"3\"\n\n";
+ CHECK;
+
+ w.ntps(1);
+ expss << "tps-count: 1\n";
+ CHECK;
+
+ w.start_tp("thetp", 0);
+ expss << "tp-start: " << ts_regex << "thetp, 0\n";
+ CHECK;
+
+ w.end_tp("");
+ expss << "tp-end: " << ts_regex << "thetp\n";
+ CHECK;
+
+ w.info("foo", "bar");
+ expss << "info: foo, bar\n";
+ CHECK;
+
+ w.info("baz", "second value");
+ expss << "info: baz, second value\n";
+ CHECK;
+ }
+
+#undef CHECK
+#undef RESET
+}
+
+// -------------------------------------------------------------------------
+// Tests for the free functions.
+// -------------------------------------------------------------------------
+
+ATF_TEST_CASE(get_metadata_bad);
+ATF_TEST_CASE_HEAD(get_metadata_bad) {}
+ATF_TEST_CASE_BODY(get_metadata_bad) {
+ const tools::fs::path executable = get_helper(*this, "bad_metadata_helper");
+ ATF_REQUIRE_THROW(tools::parser::parse_errors,
+ impl::get_metadata(executable, vars_map()));
+}
+
+ATF_TEST_CASE(get_metadata_zero_tcs);
+ATF_TEST_CASE_HEAD(get_metadata_zero_tcs) {}
+ATF_TEST_CASE_BODY(get_metadata_zero_tcs) {
+ const tools::fs::path executable = get_helper(*this, "zero_tcs_helper");
+ ATF_REQUIRE_THROW(tools::parser::parse_errors,
+ impl::get_metadata(executable, vars_map()));
+}
+
+ATF_TEST_CASE(get_metadata_several_tcs);
+ATF_TEST_CASE_HEAD(get_metadata_several_tcs) {}
+ATF_TEST_CASE_BODY(get_metadata_several_tcs) {
+ const tools::fs::path executable = get_helper(*this, "several_tcs_helper");
+ const impl::metadata md = impl::get_metadata(executable, vars_map());
+ ATF_REQUIRE_EQ(3, md.test_cases.size());
+
+ {
+ const impl::test_cases_map::const_iterator iter =
+ md.test_cases.find("first");
+ ATF_REQUIRE(iter != md.test_cases.end());
+
+ ATF_REQUIRE_EQ(4, (*iter).second.size());
+ check_property((*iter).second, "descr", "Description 1");
+ check_property((*iter).second, "has.cleanup", "false");
+ check_property((*iter).second, "ident", "first");
+ check_property((*iter).second, "timeout", "300");
+ }
+
+ {
+ const impl::test_cases_map::const_iterator iter =
+ md.test_cases.find("second");
+ ATF_REQUIRE(iter != md.test_cases.end());
+
+ ATF_REQUIRE_EQ(5, (*iter).second.size());
+ check_property((*iter).second, "descr", "Description 2");
+ check_property((*iter).second, "has.cleanup", "true");
+ check_property((*iter).second, "ident", "second");
+ check_property((*iter).second, "timeout", "500");
+ check_property((*iter).second, "X-property", "Custom property");
+ }
+
+ {
+ const impl::test_cases_map::const_iterator iter =
+ md.test_cases.find("third");
+ ATF_REQUIRE(iter != md.test_cases.end());
+
+ ATF_REQUIRE_EQ(3, (*iter).second.size());
+ check_property((*iter).second, "has.cleanup", "false");
+ check_property((*iter).second, "ident", "third");
+ check_property((*iter).second, "timeout", "300");
+ }
+}
+
+ATF_TEST_CASE_WITHOUT_HEAD(parse_test_case_result_expected_death);
+ATF_TEST_CASE_BODY(parse_test_case_result_expected_death) {
+ check_result("expected_death", -1, "foo bar",
+ detail::parse_test_case_result("expected_death: foo bar"));
+
+ ATF_REQUIRE_THROW(std::runtime_error,
+ detail::parse_test_case_result("expected_death"));
+ ATF_REQUIRE_THROW(std::runtime_error,
+ detail::parse_test_case_result("expected_death(3): foo"));
+}
+
+ATF_TEST_CASE_WITHOUT_HEAD(parse_test_case_result_expected_exit);
+ATF_TEST_CASE_BODY(parse_test_case_result_expected_exit) {
+ check_result("expected_exit", -1, "foo bar",
+ detail::parse_test_case_result("expected_exit: foo bar"));
+ check_result("expected_exit", -1, "foo bar",
+ detail::parse_test_case_result("expected_exit(): foo bar"));
+ check_result("expected_exit", 5, "foo bar",
+ detail::parse_test_case_result("expected_exit(5): foo bar"));
+
+ ATF_REQUIRE_THROW(std::runtime_error,
+ detail::parse_test_case_result("expected_exit"));
+ ATF_REQUIRE_THROW(std::runtime_error,
+ detail::parse_test_case_result("expected_exit("));
+}
+
+ATF_TEST_CASE_WITHOUT_HEAD(parse_test_case_result_expected_failure);
+ATF_TEST_CASE_BODY(parse_test_case_result_expected_failure) {
+ check_result("expected_failure", -1, "foo bar",
+ detail::parse_test_case_result("expected_failure: foo bar"));
+
+ ATF_REQUIRE_THROW(std::runtime_error,
+ detail::parse_test_case_result("expected_failure"));
+ ATF_REQUIRE_THROW(std::runtime_error,
+ detail::parse_test_case_result("expected_failure(3): foo"));
+}
+
+ATF_TEST_CASE_WITHOUT_HEAD(parse_test_case_result_expected_signal);
+ATF_TEST_CASE_BODY(parse_test_case_result_expected_signal) {
+ check_result("expected_signal", -1, "foo bar",
+ detail::parse_test_case_result("expected_signal: foo bar"));
+ check_result("expected_signal", -1, "foo bar",
+ detail::parse_test_case_result("expected_signal(): foo bar"));
+ check_result("expected_signal", 5, "foo bar",
+ detail::parse_test_case_result("expected_signal(5): foo bar"));
+
+ ATF_REQUIRE_THROW(std::runtime_error,
+ detail::parse_test_case_result("expected_signal"));
+ ATF_REQUIRE_THROW(std::runtime_error,
+ detail::parse_test_case_result("expected_signal("));
+}
+
+ATF_TEST_CASE_WITHOUT_HEAD(parse_test_case_result_expected_timeout);
+ATF_TEST_CASE_BODY(parse_test_case_result_expected_timeout) {
+ check_result("expected_timeout", -1, "foo bar",
+ detail::parse_test_case_result("expected_timeout: foo bar"));
+
+ ATF_REQUIRE_THROW(std::runtime_error,
+ detail::parse_test_case_result("expected_timeout"));
+ ATF_REQUIRE_THROW(std::runtime_error,
+ detail::parse_test_case_result("expected_timeout(3): foo"));
+}
+
+ATF_TEST_CASE_WITHOUT_HEAD(parse_test_case_result_failed);
+ATF_TEST_CASE_BODY(parse_test_case_result_failed) {
+ check_result("failed", -1, "foo bar",
+ detail::parse_test_case_result("failed: foo bar"));
+
+ ATF_REQUIRE_THROW(std::runtime_error,
+ detail::parse_test_case_result("failed"));
+ ATF_REQUIRE_THROW(std::runtime_error,
+ detail::parse_test_case_result("failed(3): foo"));
+}
+
+ATF_TEST_CASE_WITHOUT_HEAD(parse_test_case_result_passed);
+ATF_TEST_CASE_BODY(parse_test_case_result_passed) {
+ check_result("passed", -1, "",
+ detail::parse_test_case_result("passed"));
+
+ ATF_REQUIRE_THROW(std::runtime_error,
+ detail::parse_test_case_result("passed: foo"));
+ ATF_REQUIRE_THROW(std::runtime_error,
+ detail::parse_test_case_result("passed(3): foo"));
+}
+
+ATF_TEST_CASE_WITHOUT_HEAD(parse_test_case_result_skipped);
+ATF_TEST_CASE_BODY(parse_test_case_result_skipped) {
+ check_result("skipped", -1, "foo bar",
+ detail::parse_test_case_result("skipped: foo bar"));
+
+ ATF_REQUIRE_THROW(std::runtime_error,
+ detail::parse_test_case_result("skipped"));
+ ATF_REQUIRE_THROW(std::runtime_error,
+ detail::parse_test_case_result("skipped(3): foo"));
+}
+
+ATF_TEST_CASE_WITHOUT_HEAD(parse_test_case_result_unknown);
+ATF_TEST_CASE_BODY(parse_test_case_result_unknown) {
+ ATF_REQUIRE_THROW(std::runtime_error,
+ detail::parse_test_case_result("foo"));
+ ATF_REQUIRE_THROW(std::runtime_error,
+ detail::parse_test_case_result("bar: foo"));
+ ATF_REQUIRE_THROW(std::runtime_error,
+ detail::parse_test_case_result("baz: foo"));
+}
+
+ATF_TEST_CASE_WITHOUT_HEAD(read_test_case_result_failed);
+ATF_TEST_CASE_BODY(read_test_case_result_failed) {
+ write_test_case_result("resfile", "failed: foo bar\n");
+ const impl::test_case_result tcr = impl::read_test_case_result(
+ tools::fs::path("resfile"));
+ ATF_REQUIRE_EQ("failed", tcr.state());
+ ATF_REQUIRE_EQ("foo bar", tcr.reason());
+}
+
+ATF_TEST_CASE_WITHOUT_HEAD(read_test_case_result_skipped);
+ATF_TEST_CASE_BODY(read_test_case_result_skipped) {
+ write_test_case_result("resfile", "skipped: baz bar\n");
+ const impl::test_case_result tcr = impl::read_test_case_result(
+ tools::fs::path("resfile"));
+ ATF_REQUIRE_EQ("skipped", tcr.state());
+ ATF_REQUIRE_EQ("baz bar", tcr.reason());
+}
+
+
+ATF_TEST_CASE(read_test_case_result_no_file);
+ATF_TEST_CASE_HEAD(read_test_case_result_no_file) {}
+ATF_TEST_CASE_BODY(read_test_case_result_no_file) {
+ ATF_REQUIRE_THROW(std::runtime_error,
+ impl::read_test_case_result(tools::fs::path("resfile")));
+}
+
+ATF_TEST_CASE_WITHOUT_HEAD(read_test_case_result_empty_file);
+ATF_TEST_CASE_BODY(read_test_case_result_empty_file) {
+ write_test_case_result("resfile", "");
+ ATF_REQUIRE_THROW(std::runtime_error,
+ impl::read_test_case_result(tools::fs::path("resfile")));
+}
+
+ATF_TEST_CASE_WITHOUT_HEAD(read_test_case_result_invalid);
+ATF_TEST_CASE_BODY(read_test_case_result_invalid) {
+ write_test_case_result("resfile", "passed: hello\n");
+ ATF_REQUIRE_THROW(std::runtime_error,
+ impl::read_test_case_result(tools::fs::path("resfile")));
+}
+
+ATF_TEST_CASE_WITHOUT_HEAD(read_test_case_result_multiline);
+ATF_TEST_CASE_BODY(read_test_case_result_multiline) {
+ write_test_case_result("resfile", "skipped: foo\nbar\n");
+ const impl::test_case_result tcr = impl::read_test_case_result(
+ tools::fs::path("resfile"));
+ ATF_REQUIRE_EQ("skipped", tcr.state());
+ ATF_REQUIRE_EQ("foo<<NEWLINE UNEXPECTED>>bar", tcr.reason());
+}
+
+// -------------------------------------------------------------------------
+// Main.
+// -------------------------------------------------------------------------
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ ATF_ADD_TEST_CASE(tcs, tp_1);
+ ATF_ADD_TEST_CASE(tcs, tp_2);
+ ATF_ADD_TEST_CASE(tcs, tp_3);
+ ATF_ADD_TEST_CASE(tcs, tp_4);
+ ATF_ADD_TEST_CASE(tcs, tp_50);
+ ATF_ADD_TEST_CASE(tcs, tp_51);
+ ATF_ADD_TEST_CASE(tcs, tp_52);
+ ATF_ADD_TEST_CASE(tcs, tp_53);
+ ATF_ADD_TEST_CASE(tcs, tp_54);
+ ATF_ADD_TEST_CASE(tcs, tp_55);
+ ATF_ADD_TEST_CASE(tcs, tp_56);
+ ATF_ADD_TEST_CASE(tcs, tp_57);
+ ATF_ADD_TEST_CASE(tcs, tp_58);
+ ATF_ADD_TEST_CASE(tcs, tp_59);
+ ATF_ADD_TEST_CASE(tcs, tp_60);
+
+ ATF_ADD_TEST_CASE(tcs, atf_tps_writer);
+
+ ATF_ADD_TEST_CASE(tcs, get_metadata_bad);
+ ATF_ADD_TEST_CASE(tcs, get_metadata_zero_tcs);
+ ATF_ADD_TEST_CASE(tcs, get_metadata_several_tcs);
+
+ ATF_ADD_TEST_CASE(tcs, parse_test_case_result_expected_death);
+ ATF_ADD_TEST_CASE(tcs, parse_test_case_result_expected_exit);
+ ATF_ADD_TEST_CASE(tcs, parse_test_case_result_expected_failure);
+ ATF_ADD_TEST_CASE(tcs, parse_test_case_result_expected_signal);
+ ATF_ADD_TEST_CASE(tcs, parse_test_case_result_expected_timeout);
+ ATF_ADD_TEST_CASE(tcs, parse_test_case_result_failed);
+ ATF_ADD_TEST_CASE(tcs, parse_test_case_result_passed);
+ ATF_ADD_TEST_CASE(tcs, parse_test_case_result_skipped);
+ ATF_ADD_TEST_CASE(tcs, parse_test_case_result_unknown);
+
+ ATF_ADD_TEST_CASE(tcs, read_test_case_result_failed);
+ ATF_ADD_TEST_CASE(tcs, read_test_case_result_skipped);
+ ATF_ADD_TEST_CASE(tcs, read_test_case_result_no_file);
+ ATF_ADD_TEST_CASE(tcs, read_test_case_result_empty_file);
+ ATF_ADD_TEST_CASE(tcs, read_test_case_result_multiline);
+ ATF_ADD_TEST_CASE(tcs, read_test_case_result_invalid);
+
+ // TODO: Add tests for run_test_case once all the missing functionality
+ // is implemented.
+}
diff --git a/unit/atf-src/tools/tests-results.css b/unit/atf-src/tools/tests-results.css
new file mode 100644
index 0000000..24fc12c
--- /dev/null
+++ b/unit/atf-src/tools/tests-results.css
@@ -0,0 +1,199 @@
+/*
+ * Automated Testing Framework (atf)
+ *
+ * Copyright (c) 2007 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
+ * CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+ * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+ * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+body {
+ margin: 0 0 0 0;
+}
+
+.nobr {
+ white-space: nowrap;
+}
+
+h1 {
+ background: black;
+ color: white;
+ font-family: Arial;
+ font-size: 24pt;
+ padding: 5pt;
+}
+
+h2 {
+ background: #eeeeee;
+ font-family: Arial;
+ font-size: 16pt;
+ margin-left: 10pt;
+ margin-right: 10pt;
+ padding: 3pt;
+}
+
+h3 {
+ font-family: Arial;
+ font-size: 12pt;
+ margin-left: 20pt;
+ margin-right: 20pt;
+ padding: 3pt;
+}
+
+p.details {
+ margin-left: 20pt;
+ margin-right: 20pt;
+}
+
+p.term {
+ margin-left: 40pt;
+ margin-right: 40pt;
+}
+
+pre.so {
+ margin-left: 40pt;
+ margin-right: 40pt;
+}
+
+pre.se {
+ margin-left: 40pt;
+ margin-right: 40pt;
+}
+
+table.summary {
+ border-collapse: collapse;
+ border-color: black;
+ border-style: solid;
+ border-width: 1pt;
+ margin-left: auto;
+ margin-right: auto;
+}
+
+table.summary th {
+ background: #aaaadd;
+ border-style: solid;
+ border-width: 1pt;
+ padding: 3pt 6pt 3pt 6pt;
+}
+
+table.summary td {
+ border-style: solid;
+ border-width: 1pt;
+ padding: 3pt 6pt 3pt 6pt;
+}
+
+table.summary td.numeric p {
+ text-align: right;
+}
+
+table.summary td.numeric-error p {
+ text-align: right;
+ color: red;
+}
+
+table.summary td.numeric-warning p {
+ text-align: right;
+ color: #aaaa00;
+}
+
+table.summary tr.group {
+ background: #dddddd;
+}
+
+table.summary tr.entry td p {
+ margin-left: 10pt;
+}
+
+table.tcs-summary {
+ border-collapse: collapse;
+ border-color: black;
+ border-style: solid;
+ border-width: 1pt;
+ margin-left: auto;
+ margin-right: auto;
+ width: 80%;
+}
+
+table.tcs-summary td {
+ border-style: solid;
+ border-width: 1pt;
+ padding: 3pt 6pt 3pt 6pt;
+}
+
+table.tcs-summary th {
+ background: #aaaadd;
+ border-style: solid;
+ border-width: 1pt;
+ padding: 3pt 6pt 3pt 6pt;
+}
+
+table.tcs-summary td.numeric {
+ width: 1pt;
+}
+
+table.tcs-summary td.numeric p {
+ text-align: right;
+}
+
+table.tcs-summary td.tp-numeric {
+ background: #dddddd;
+ width: 1pt;
+}
+
+table.tcs-summary td.tp-numeric p {
+ text-align: right;
+}
+
+table.tcs-summary td.tp-id {
+ background: #dddddd;
+ font-weight: bold;
+ width: 1pt;
+}
+
+table.tcs-summary td.tc-id p {
+ margin-left: 10pt;
+}
+
+table.tcs-summary td.tcr-passed {
+ background: #aaffaa;
+ width: 1pt;
+}
+
+table.tcs-summary td.tcr-failed {
+ background: #ffaaaa;
+ width: 1pt;
+}
+
+table.tcs-summary td.tcr-skipped {
+ background: #ffffaa;
+ width: 1pt;
+}
+
+table.tcs-summary td.tcr-xfail {
+ background: #ffaaff;
+ width: 1pt;
+}
+
+table.tcs-summary td.tcr-reason {
+ width: 100%;
+}
diff --git a/unit/atf-src/tools/tests-results.dtd b/unit/atf-src/tools/tests-results.dtd
new file mode 100644
index 0000000..c16f7ed
--- /dev/null
+++ b/unit/atf-src/tools/tests-results.dtd
@@ -0,0 +1,61 @@
+<!--
+ ++ Automated Testing Framework (atf)
+ ++
+ ++ Copyright (c) 2007 The NetBSD Foundation, Inc.
+ ++ All rights reserved.
+ ++
+ ++ Redistribution and use in source and binary forms, with or without
+ ++ modification, are permitted provided that the following conditions
+ ++ are met:
+ ++ 1. Redistributions of source code must retain the above copyright
+ ++ notice, this list of conditions and the following disclaimer.
+ ++ 2. Redistributions in binary form must reproduce the above copyright
+ ++ notice, this list of conditions and the following disclaimer in the
+ ++ documentation and/or other materials provided with the distribution.
+ ++
+ ++ THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
+ ++ CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ ++ INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ ++ MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ ++ IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
+ ++ DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ ++ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+ ++ GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ ++ INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+ ++ IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ ++ OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ ++ IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ -->
+
+<!-- TODO(1.0): Set the version of the DTD to 1.0 -->
+
+<!--
+ ++ PUBLIC: -//NetBSD//DTD ATF Tests Results 0.1//EN
+ ++ URI: http://www.NetBSD.org/XML/atf/tests-results.dtd
+ -->
+
+<!ELEMENT tests-results (info*, tp*, info*)>
+
+<!ELEMENT info (#PCDATA)>
+<!ATTLIST info class CDATA #REQUIRED>
+
+<!ELEMENT tp (tc*, failed?)>
+<!ATTLIST tp id ID #REQUIRED>
+
+<!ELEMENT tc ((so, se)*, (passed, failed, skipped))>
+<!ATTLIST tc id NMTOKEN #REQUIRED>
+
+<!ELEMENT expected_death (#PCDATA)>
+<!ELEMENT expected_exit (#PCDATA)>
+<!ELEMENT expected_failure (#PCDATA)>
+<!ELEMENT expected_signal (#PCDATA)>
+<!ELEMENT expected_timeout (#PCDATA)>
+<!ELEMENT passed EMPTY>
+<!ELEMENT failed (#PCDATA)>
+<!ELEMENT skipped (#PCDATA)>
+<!ELEMENT so (#PCDATA)>
+<!ELEMENT se (#PCDATA)>
+
+<!ENTITY amp "&#047;">
+<!ENTITY lt "&#074;">
+<!ENTITY gt "&#076;">
diff --git a/unit/atf-src/tools/tests-results.xsl b/unit/atf-src/tools/tests-results.xsl
new file mode 100644
index 0000000..91b77c5
--- /dev/null
+++ b/unit/atf-src/tools/tests-results.xsl
@@ -0,0 +1,564 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!DOCTYPE xsl:stylesheet [<!ENTITY nbsp "&#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.
+ -->
+
+<xsl:stylesheet version="1.0"
+ xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+
+ <!-- Parameters that can be overriden by the user. -->
+ <xsl:param name="global.css">tests-results.css</xsl:param>
+ <xsl:param name="global.title">ATF Tests Results</xsl:param>
+
+ <xsl:variable name="ntps"
+ select="count(tests-results/tp)" />
+ <xsl:variable name="ntps-failed"
+ select="count(tests-results/tp/failed)" />
+ <xsl:variable name="ntcs"
+ select="count(tests-results/tp/tc)" />
+ <xsl:variable name="ntcs-passed"
+ select="count(tests-results/tp/tc/passed)" />
+ <xsl:variable name="ntcs-failed"
+ select="count(tests-results/tp/tc/failed)" />
+ <xsl:variable name="ntcs-skipped"
+ select="count(tests-results/tp/tc/skipped)" />
+ <xsl:variable name="ntcs-xfail"
+ select="count(tests-results/tp/tc/expected_death) +
+ count(tests-results/tp/tc/expected_exit) +
+ count(tests-results/tp/tc/expected_failure) +
+ count(tests-results/tp/tc/expected_signal) +
+ count(tests-results/tp/tc/expected_timeout)" />
+
+ <xsl:template match="/">
+ <xsl:copy>
+ <xsl:apply-templates select="@*|node()" />
+ </xsl:copy>
+ </xsl:template>
+
+ <xsl:template match="tests-results">
+ <html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <meta http-equiv="Content-Type"
+ content="text/html; charset=iso-8859-1" />
+ <link rel="stylesheet" type="text/css" href="{$global.css}" />
+
+ <title><xsl:value-of select="$global.title" /></title>
+ </head>
+
+ <body>
+ <h1><xsl:value-of select="$global.title" /></h1>
+
+ <xsl:call-template name="info-top" />
+ <xsl:call-template name="tcs-summary" />
+ <xsl:if test="$ntcs-failed > 0">
+ <xsl:call-template name="failed-tcs-summary" />
+ </xsl:if>
+ <xsl:if test="$ntcs-xfail > 0">
+ <xsl:call-template name="xfail-tcs-summary" />
+ </xsl:if>
+ <xsl:if test="$ntcs-skipped > 0">
+ <xsl:call-template name="skipped-tcs-summary" />
+ </xsl:if>
+ <xsl:if test="$ntps-failed > 0">
+ <xsl:call-template name="failed-tps-summary" />
+ </xsl:if>
+ <xsl:call-template name="info-bottom" />
+
+ <xsl:apply-templates select="tp" mode="details" />
+ </body>
+ </html>
+ </xsl:template>
+
+ <xsl:template name="info-top">
+ <h2>Execution summary</h2>
+
+ <table class="summary">
+ <tr>
+ <th class="nobr"><p>Item</p></th>
+ <th class="nobr"><p>Value</p></th>
+ </tr>
+
+ <tr class="group">
+ <td colspan="2"><p>ATF</p></td>
+ </tr>
+ <tr class="entry">
+ <td><p>Version</p></td>
+ <td><p><xsl:apply-templates
+ select="info[@class = 'atf.version']" /></p></td>
+ </tr>
+
+ <tr class="group">
+ <td colspan="2"><p>Timings</p></td>
+ </tr>
+ <tr class="entry">
+ <td><p>Start time of tests</p></td>
+ <td><p><xsl:apply-templates
+ select="info[@class = 'time.start']" /></p></td>
+ </tr>
+ <tr class="entry">
+ <td><p>End time of tests</p></td>
+ <td><p><xsl:apply-templates
+ select="info[@class = 'time.end']" /></p></td>
+ </tr>
+
+ <tr class="group">
+ <td colspan="2"><p>System information</p></td>
+ </tr>
+ <tr class="entry">
+ <td><p>Host name</p></td>
+ <td><p><xsl:apply-templates
+ select="info[@class = 'uname.nodename']" /></p></td>
+ </tr>
+ <tr class="entry">
+ <td><p>Operating system</p></td>
+ <td><p><xsl:apply-templates
+ select="info[@class = 'uname.sysname']" /></p></td>
+ </tr>
+ <tr class="entry">
+ <td><p>Operating system release</p></td>
+ <td><p><xsl:apply-templates
+ select="info[@class = 'uname.release']" /></p></td>
+ </tr>
+ <tr class="entry">
+ <td><p>Operating system version</p></td>
+ <td><p><xsl:apply-templates
+ select="info[@class = 'uname.version']" /></p></td>
+ </tr>
+ <tr class="entry">
+ <td><p>Platform</p></td>
+ <td><p><xsl:apply-templates
+ select="info[@class = 'uname.machine']" /></p></td>
+ </tr>
+
+ <tr class="group">
+ <td colspan="2"><p>Tests results</p></td>
+ </tr>
+ <tr class="entry">
+ <td><p>Root</p></td>
+ <td><p><xsl:value-of
+ select="info[@class = 'tests.root']" /></p></td>
+ </tr>
+ <tr class="entry">
+ <td><p>Test programs</p></td>
+ <td class="numeric"><p><xsl:value-of select="$ntps" /></p></td>
+ </tr>
+ <tr class="entry">
+ <xsl:choose>
+ <xsl:when test="$ntps-failed > 0">
+ <td><p><a href="#failed-tps-summary">Bogus test
+ programs</a></p></td>
+ <td class="numeric-error">
+ <p><xsl:value-of select="$ntps-failed" /></p>
+ </td>
+ </xsl:when>
+ <xsl:otherwise>
+ <td><p>Bogus test programs</p></td>
+ <td class="numeric">
+ <p><xsl:value-of select="$ntps-failed" /></p>
+ </td>
+ </xsl:otherwise>
+ </xsl:choose>
+ </tr>
+ <tr class="entry">
+ <td><p>Test cases</p></td>
+ <td class="numeric"><p><xsl:value-of select="$ntcs" /></p></td>
+ </tr>
+ <tr class="entry">
+ <td><p>Passed test cases</p></td>
+ <td class="numeric"><p><xsl:value-of select="$ntcs-passed" /></p></td>
+ </tr>
+ <tr class="entry">
+ <xsl:choose>
+ <xsl:when test="$ntcs-failed > 0">
+ <td><p><a href="#failed-tcs-summary">Failed test
+ cases</a></p></td>
+ <td class="numeric-error">
+ <p><xsl:value-of select="$ntcs-failed" /></p>
+ </td>
+ </xsl:when>
+ <xsl:otherwise>
+ <td><p>Failed test cases</p></td>
+ <td class="numeric">
+ <p><xsl:value-of select="$ntcs-failed" /></p>
+ </td>
+ </xsl:otherwise>
+ </xsl:choose>
+ </tr>
+ <tr class="entry">
+ <xsl:choose>
+ <xsl:when test="$ntcs-xfail > 0">
+ <td><p><a href="#xfail-tcs-summary">Expected
+ failures</a></p></td>
+ <td class="numeric-warning">
+ <p><xsl:value-of select="$ntcs-xfail" /></p>
+ </td>
+ </xsl:when>
+ <xsl:otherwise>
+ <td><p>Expected failures</p></td>
+ <td class="numeric">
+ <p><xsl:value-of select="$ntcs-xfail" /></p>
+ </td>
+ </xsl:otherwise>
+ </xsl:choose>
+ </tr>
+ <tr class="entry">
+ <xsl:choose>
+ <xsl:when test="$ntcs-skipped > 0">
+ <td><p><a href="#skipped-tcs-summary">Skipped test
+ cases</a></p></td>
+ <td class="numeric-warning">
+ <p><xsl:value-of select="$ntcs-skipped" /></p>
+ </td>
+ </xsl:when>
+ <xsl:otherwise>
+ <td><p>Skipped test cases</p></td>
+ <td class="numeric">
+ <p><xsl:value-of select="$ntcs-skipped" /></p>
+ </td>
+ </xsl:otherwise>
+ </xsl:choose>
+ </tr>
+
+ <tr class="group">
+ <td colspan="2"><p><a href="#execution-details">See more execution
+ details</a></p></td>
+ </tr>
+ </table>
+ </xsl:template>
+
+ <xsl:template name="info-bottom">
+ <a name="execution-details" />
+ <h2 id="execution-details">Execution details</h2>
+
+ <h3>Environment variables</h3>
+
+ <ul>
+ <xsl:apply-templates select="info[@class = 'env']">
+ <xsl:sort />
+ </xsl:apply-templates>
+ </ul>
+ </xsl:template>
+
+ <xsl:template match="info[@class = 'env']">
+ <li>
+ <p><xsl:apply-templates /></p>
+ </li>
+ </xsl:template>
+
+ <xsl:template name="tcs-summary">
+ <h2>Test cases summary</h2>
+
+ <table class="tcs-summary">
+ <tr>
+ <th class="nobr"><p>Test case</p></th>
+ <th class="nobr"><p>Result</p></th>
+ <th class="nobr"><p>Reason</p></th>
+ <th class="nobr"><p>Duration</p></th>
+ </tr>
+ <xsl:apply-templates select="tp" mode="summary">
+ <xsl:with-param name="which">all</xsl:with-param>
+ </xsl:apply-templates>
+ </table>
+ </xsl:template>
+
+ <xsl:template name="xfail-tcs-summary">
+ <a name="xfail-tcs-summary" />
+ <h2 id="xfail-tcs-summary">Expected failures summary</h2>
+
+ <table class="tcs-summary">
+ <tr>
+ <th class="nobr"><p>Test case</p></th>
+ <th class="nobr"><p>Result</p></th>
+ <th class="nobr"><p>Reason</p></th>
+ <th class="nobr"><p>Duration</p></th>
+ </tr>
+ <xsl:apply-templates select="tp" mode="summary">
+ <xsl:with-param name="which">xfail</xsl:with-param>
+ </xsl:apply-templates>
+ </table>
+ </xsl:template>
+
+ <xsl:template name="failed-tcs-summary">
+ <a name="failed-tcs-summary" />
+ <h2 id="failed-tcs-summary">Failed test cases summary</h2>
+
+ <table class="tcs-summary">
+ <tr>
+ <th class="nobr"><p>Test case</p></th>
+ <th class="nobr"><p>Result</p></th>
+ <th class="nobr"><p>Reason</p></th>
+ <th class="nobr"><p>Duration</p></th>
+ </tr>
+ <xsl:apply-templates select="tp" mode="summary">
+ <xsl:with-param name="which">failed</xsl:with-param>
+ </xsl:apply-templates>
+ </table>
+ </xsl:template>
+
+ <xsl:template name="failed-tps-summary">
+ <a name="failed-tps-summary" />
+ <h2 id="failed-tps-summary">Bogus test programs summary</h2>
+
+ <table class="tcs-summary">
+ <tr>
+ <th class="nobr">Test program</th>
+ </tr>
+ <xsl:apply-templates select="tp" mode="summary">
+ <xsl:with-param name="which">bogus</xsl:with-param>
+ </xsl:apply-templates>
+ </table>
+ </xsl:template>
+
+ <xsl:template name="skipped-tcs-summary">
+ <a name="skipped-tcs-summary" />
+ <h2 id="skipped-tcs-summary">Skipped test cases summary</h2>
+
+ <table class="tcs-summary">
+ <tr>
+ <th class="nobr"><p>Test case</p></th>
+ <th class="nobr"><p>Result</p></th>
+ <th class="nobr"><p>Reason</p></th>
+ <th class="nobr"><p>Duration</p></th>
+ </tr>
+ <xsl:apply-templates select="tp" mode="summary">
+ <xsl:with-param name="which">skipped</xsl:with-param>
+ </xsl:apply-templates>
+ </table>
+ </xsl:template>
+
+ <xsl:template match="tp" mode="summary">
+ <xsl:param name="which" />
+
+ <xsl:variable name="chosen">
+ <xsl:choose>
+ <xsl:when test="$which = 'bogus' and failed">yes</xsl:when>
+ <xsl:when test="$which = 'passed' and tc/passed">yes</xsl:when>
+ <xsl:when test="$which = 'failed' and tc/failed">yes</xsl:when>
+ <xsl:when test="$which = 'xfail' and
+ tc/expected_death">yes</xsl:when>
+ <xsl:when test="$which = 'xfail' and
+ tc/expected_exit">yes</xsl:when>
+ <xsl:when test="$which = 'xfail' and
+ tc/expected_failure">yes</xsl:when>
+ <xsl:when test="$which = 'xfail' and
+ tc/expected_signal">yes</xsl:when>
+ <xsl:when test="$which = 'xfail' and
+ tc/expected_timeout">yes</xsl:when>
+ <xsl:when test="$which = 'skipped' and tc/skipped">yes</xsl:when>
+ <xsl:when test="$which = 'all'">yes</xsl:when>
+ <xsl:otherwise>no</xsl:otherwise>
+ </xsl:choose>
+ </xsl:variable>
+
+ <xsl:if test="$chosen = 'yes'">
+ <tr>
+ <td class="tp-id" colspan="3">
+ <p><xsl:value-of select="@id" /></p>
+ </td>
+ <td class="tp-numeric">
+ <p><xsl:value-of select="tp-time" />s</p>
+ </td>
+ </tr>
+ <xsl:if test="$which != 'bogus'">
+ <xsl:apply-templates select="tc" mode="summary">
+ <xsl:with-param name="which" select="$which" />
+ </xsl:apply-templates>
+ </xsl:if>
+ </xsl:if>
+ </xsl:template>
+
+ <xsl:template match="tc" mode="summary">
+ <xsl:param name="which" />
+
+ <xsl:variable name="full-id"
+ select="concat(translate(../@id, '/', '_'), '_', @id)" />
+
+ <xsl:variable name="chosen">
+ <xsl:choose>
+ <xsl:when test="$which = 'passed' and ./passed">yes</xsl:when>
+ <xsl:when test="$which = 'failed' and ./failed">yes</xsl:when>
+ <xsl:when test="$which = 'xfail' and
+ ./expected_death">yes</xsl:when>
+ <xsl:when test="$which = 'xfail' and
+ ./expected_exit">yes</xsl:when>
+ <xsl:when test="$which = 'xfail' and
+ ./expected_failure">yes</xsl:when>
+ <xsl:when test="$which = 'xfail' and
+ ./expected_signal">yes</xsl:when>
+ <xsl:when test="$which = 'xfail' and
+ ./expected_timeout">yes</xsl:when>
+ <xsl:when test="$which = 'skipped' and ./skipped">yes</xsl:when>
+ <xsl:when test="$which = 'all'">yes</xsl:when>
+ <xsl:otherwise>no</xsl:otherwise>
+ </xsl:choose>
+ </xsl:variable>
+
+ <xsl:if test="$chosen = 'yes'">
+ <tr>
+ <td class="tc-id">
+ <xsl:choose>
+ <xsl:when test="expected_death|expected_exit|expected_failure|
+ expected_signal|expected_timeout|failed|skipped">
+ <p><a href="#{$full-id}"><xsl:value-of select="@id" /></a></p>
+ </xsl:when>
+ <xsl:otherwise>
+ <p><xsl:value-of select="@id" /></p>
+ </xsl:otherwise>
+ </xsl:choose>
+ </td>
+ <xsl:apply-templates select="expected_death|expected_exit|
+ expected_failure|expected_timeout|
+ expected_signal|failed|passed|
+ skipped" mode="tc" />
+ <td class="numeric">
+ <p><xsl:value-of select="tc-time" />s</p>
+ </td>
+ </tr>
+ </xsl:if>
+ </xsl:template>
+
+ <xsl:template match="passed" mode="tc">
+ <td class="tcr-passed"><p class="nobr">Passed</p></td>
+ <td class="tcr-reason"><p>N/A</p></td>
+ </xsl:template>
+
+ <xsl:template match="expected_death" mode="tc">
+ <td class="tcr-xfail"><p class="nobr">Expected death</p></td>
+ <td class="tcr-reason"><p><xsl:apply-templates /></p></td>
+ </xsl:template>
+
+ <xsl:template match="expected_exit" mode="tc">
+ <td class="tcr-xfail"><p class="nobr">Expected exit</p></td>
+ <td class="tcr-reason"><p><xsl:apply-templates /></p></td>
+ </xsl:template>
+
+ <xsl:template match="expected_failure" mode="tc">
+ <td class="tcr-xfail"><p class="nobr">Expected failure</p></td>
+ <td class="tcr-reason"><p><xsl:apply-templates /></p></td>
+ </xsl:template>
+
+ <xsl:template match="expected_timeout" mode="tc">
+ <td class="tcr-xfail"><p class="nobr">Expected timeout</p></td>
+ <td class="tcr-reason"><p><xsl:apply-templates /></p></td>
+ </xsl:template>
+
+ <xsl:template match="expected_signal" mode="tc">
+ <td class="tcr-xfail"><p class="nobr">Expected signal</p></td>
+ <td class="tcr-reason"><p><xsl:apply-templates /></p></td>
+ </xsl:template>
+
+ <xsl:template match="failed" mode="tc">
+ <td class="tcr-failed"><p class="nobr">Failed</p></td>
+ <td class="tcr-reason"><p><xsl:apply-templates /></p></td>
+ </xsl:template>
+
+ <xsl:template match="skipped" mode="tc">
+ <td class="tcr-skipped"><p class="nobr">Skipped</p></td>
+ <td class="tcr-reason"><p><xsl:apply-templates /></p></td>
+ </xsl:template>
+
+ <xsl:template match="tp" mode="details">
+ <xsl:apply-templates select="tc[expected_death|expected_exit|
+ expected_failure|expected_signal|
+ expected_timeout|failed|skipped]"
+ mode="details" />
+ </xsl:template>
+
+ <xsl:template match="failed" mode="details">
+ <p class="term"><strong>FAILED</strong>: <xsl:apply-templates /></p>
+ </xsl:template>
+
+ <xsl:template match="expected_death|expected_exit|expected_failure|
+ expected_signal|expected_timeout" mode="details">
+ <p class="term"><strong>XFAIL</strong>: <xsl:apply-templates /></p>
+ </xsl:template>
+
+ <xsl:template match="skipped" mode="details">
+ <p class="term"><strong>SKIPPED</strong>: <xsl:apply-templates /></p>
+ </xsl:template>
+
+ <xsl:template match="tc" mode="details">
+ <xsl:variable name="full-id"
+ select="concat(translate(../@id, '/', '_'), '_', @id)" />
+
+ <a name="{$full-id}" />
+ <h2 id="{$full-id}">Test case:
+ <xsl:value-of select="../@id" /><xsl:text>/</xsl:text>
+ <xsl:value-of select="@id" /></h2>
+
+ <xsl:if test="tc-time">
+ <p class="details">Duration:
+ <xsl:apply-templates select="tc-time" mode="details" /></p>
+ </xsl:if>
+
+ <h3>Termination reason</h3>
+ <xsl:apply-templates select="expected_death|expected_exit|expected_failure|
+ expected_signal|expected_timeout|
+ failed|skipped"
+ mode="details" />
+
+ <xsl:if test="so">
+ <h3>Standard output stream</h3>
+ <pre class="so"><xsl:apply-templates select="so" mode="details" /></pre>
+ </xsl:if>
+
+ <xsl:if test="se">
+ <h3>Standard error stream</h3>
+ <pre class="se"><xsl:apply-templates select="se" mode="details" /></pre>
+ </xsl:if>
+ </xsl:template>
+
+ <xsl:template match="tc-time" mode="details">
+ <xsl:apply-templates /> seconds
+ </xsl:template>
+
+ <xsl:template match="so" mode="details">
+ <xsl:apply-templates />
+ <xsl:if test="position() != last()">
+ <xsl:text>
+</xsl:text>
+ </xsl:if>
+ </xsl:template>
+
+ <xsl:template match="se" mode="details">
+ <xsl:apply-templates />
+ <xsl:if test="position() != last()">
+ <xsl:text>
+</xsl:text>
+ </xsl:if>
+ </xsl:template>
+
+ <xsl:template match="@*|node()" priority="-1">
+ <xsl:copy>
+ <xsl:apply-templates select="@*|node()" />
+ </xsl:copy>
+ </xsl:template>
+
+</xsl:stylesheet>
diff --git a/unit/atf-src/tools/text.cpp b/unit/atf-src/tools/text.cpp
new file mode 100644
index 0000000..9084f7e
--- /dev/null
+++ b/unit/atf-src/tools/text.cpp
@@ -0,0 +1,158 @@
+//
+// Automated Testing Framework (atf)
+//
+// Copyright (c) 2007 The NetBSD Foundation, Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
+// CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+// IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
+// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+// IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+
+extern "C" {
+#include <regex.h>
+}
+
+#include <cctype>
+#include <cerrno>
+#include <cstring>
+
+#include "exceptions.hpp"
+#include "text.hpp"
+
+namespace impl = tools::text;
+#define IMPL_NAME "tools::text"
+
+char*
+impl::duplicate(const char* str)
+{
+ char* copy = new char[std::strlen(str) + 1];
+ std::strcpy(copy, str);
+ return copy;
+}
+
+bool
+impl::match(const std::string& str, const std::string& regex)
+{
+ bool found;
+
+ // Special case: regcomp does not like empty regular expressions.
+ if (regex.empty()) {
+ found = str.empty();
+ } else {
+ ::regex_t preg;
+
+ if (::regcomp(&preg, regex.c_str(), REG_EXTENDED) != 0)
+ throw std::runtime_error("Invalid regular expression '" + regex +
+ "'");
+
+ const int res = ::regexec(&preg, str.c_str(), 0, NULL, 0);
+ regfree(&preg);
+ if (res != 0 && res != REG_NOMATCH)
+ throw std::runtime_error("Invalid regular expression " + regex);
+
+ found = res == 0;
+ }
+
+ return found;
+}
+
+std::string
+impl::to_lower(const std::string& str)
+{
+ std::string lc;
+ for (std::string::const_iterator iter = str.begin(); iter != str.end();
+ iter++)
+ lc += std::tolower(*iter);
+ return lc;
+}
+
+std::vector< std::string >
+impl::split(const std::string& str, const std::string& delim)
+{
+ std::vector< std::string > words;
+
+ std::string::size_type pos = 0, newpos = 0;
+ while (pos < str.length() && newpos != std::string::npos) {
+ newpos = str.find(delim, pos);
+ if (newpos != pos)
+ words.push_back(str.substr(pos, newpos - pos));
+ pos = newpos + delim.length();
+ }
+
+ return words;
+}
+
+std::string
+impl::trim(const std::string& str)
+{
+ std::string::size_type pos1 = str.find_first_not_of(" \t");
+ std::string::size_type pos2 = str.find_last_not_of(" \t");
+
+ if (pos1 == std::string::npos && pos2 == std::string::npos)
+ return "";
+ else if (pos1 == std::string::npos)
+ return str.substr(0, str.length() - pos2);
+ else if (pos2 == std::string::npos)
+ return str.substr(pos1);
+ else
+ return str.substr(pos1, pos2 - pos1 + 1);
+}
+
+bool
+impl::to_bool(const std::string& str)
+{
+ const std::string lower = to_lower(str);
+ if (lower == "yes" || lower == "true")
+ return true;
+ else if (lower == "no" || lower == "false")
+ return false;
+ else {
+ // XXX Not really a libc error.
+ throw system_error(IMPL_NAME "::to_bool", "Cannot convert string "
+ "'" + str + "' to boolean", EINVAL);
+ }
+}
+
+int64_t
+impl::to_bytes(std::string str)
+{
+ if (str.empty())
+ throw std::runtime_error("Empty value");
+
+ const char unit = str[str.length() - 1];
+ int64_t multiplier;
+ switch (unit) {
+ case 'k': case 'K': multiplier = 1 << 10; break;
+ case 'm': case 'M': multiplier = 1 << 20; break;
+ case 'g': case 'G': multiplier = 1 << 30; break;
+ case 't': case 'T': multiplier = int64_t(1) << 40; break;
+ default:
+ if (!std::isdigit(unit))
+ throw std::runtime_error(std::string("Unknown size unit '") + unit
+ + "'");
+ multiplier = 1;
+ }
+ if (multiplier != 1)
+ str.erase(str.length() - 1);
+
+ return to_type< int64_t >(str) * multiplier;
+}
diff --git a/unit/atf-src/tools/text.hpp b/unit/atf-src/tools/text.hpp
new file mode 100644
index 0000000..0b3dbaa
--- /dev/null
+++ b/unit/atf-src/tools/text.hpp
@@ -0,0 +1,153 @@
+//
+// Automated Testing Framework (atf)
+//
+// Copyright (c) 2007 The NetBSD Foundation, Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
+// CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+// IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
+// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+// IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+
+#if !defined(TOOLS_TEXT_HPP)
+#define TOOLS_TEXT_HPP
+
+extern "C" {
+#include <stdint.h>
+}
+
+#include <sstream>
+#include <stdexcept>
+#include <string>
+#include <vector>
+
+namespace tools {
+namespace text {
+
+//!
+//! \brief Duplicates a C string using the new[] allocator.
+//!
+//! Replaces the functionality of strdup by using the new[] allocator and
+//! thus allowing the resulting memory to be managed by utils::auto_array.
+//!
+char* duplicate(const char*);
+
+//!
+//! \brief Joins multiple words into a string.
+//!
+//! Joins a list of words into a string, separating them using the provided
+//! separator. Empty words are not omitted.
+//!
+template< class T >
+std::string
+join(const T& words, const std::string& separator)
+{
+ std::string str;
+
+ typename T::const_iterator iter = words.begin();
+ bool done = iter == words.end();
+ while (!done) {
+ str += *iter;
+ iter++;
+ if (iter != words.end())
+ str += separator;
+ else
+ done = true;
+ }
+
+ return str;
+}
+
+//!
+//! \brief Checks if the string matches a regular expression.
+//!
+bool match(const std::string&, const std::string&);
+
+//!
+//! \brief Splits a string into words.
+//!
+//! Splits the given string into multiple words, all separated by the
+//! given delimiter. Multiple occurrences of the same delimiter are
+//! not condensed so that rejoining the words later on using the same
+//! delimiter results in the original string.
+//!
+std::vector< std::string > split(const std::string&, const std::string&);
+
+//!
+//! \brief Removes whitespace from the beginning and end of a string.
+//!
+std::string trim(const std::string&);
+
+//!
+//! \brief Converts a string to a boolean value.
+//!
+bool to_bool(const std::string&);
+
+//!
+//! \brief Converts the given string to a bytes size.
+//!
+int64_t to_bytes(std::string);
+
+//!
+//! \brief Changes the case of a string to lowercase.
+//!
+//! Returns a new string that is a lowercased version of the original
+//! one.
+//!
+std::string to_lower(const std::string&);
+
+//!
+//! \brief Converts the given object to a string.
+//!
+//! Returns a string with the representation of the given object. There
+//! must exist an operator<< method for that object.
+//!
+template< class T >
+std::string
+to_string(const T& ob)
+{
+ std::ostringstream ss;
+ ss << ob;
+ return ss.str();
+}
+
+//!
+//! \brief Converts the given string to another type.
+//!
+//! Attempts to convert the given string to the requested type. Throws
+//! an exception if the conversion failed.
+//!
+template< class T >
+T
+to_type(const std::string& str)
+{
+ std::istringstream ss(str);
+ T value;
+ ss >> value;
+ if (!ss.eof() || (ss.eof() && (ss.fail() || ss.bad())))
+ throw std::runtime_error("Cannot convert string to requested type");
+ return value;
+}
+
+} // namespace text
+} // namespace tools
+
+#endif // !defined(TOOLS_TEXT_HPP)
diff --git a/unit/atf-src/tools/text_test.cpp b/unit/atf-src/tools/text_test.cpp
new file mode 100644
index 0000000..0fa31d8
--- /dev/null
+++ b/unit/atf-src/tools/text_test.cpp
@@ -0,0 +1,390 @@
+//
+// Automated Testing Framework (atf)
+//
+// Copyright (c) 2007 The NetBSD Foundation, Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
+// CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+// IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
+// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+// IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+
+#include <cstring>
+#include <set>
+#include <vector>
+
+#include <atf-c++.hpp>
+
+#include "text.hpp"
+
+// ------------------------------------------------------------------------
+// Test cases for the free functions.
+// ------------------------------------------------------------------------
+
+ATF_TEST_CASE(duplicate);
+ATF_TEST_CASE_HEAD(duplicate)
+{
+ set_md_var("descr", "Tests the duplicate function");
+}
+ATF_TEST_CASE_BODY(duplicate)
+{
+ using tools::text::duplicate;
+
+ const char* orig = "foo";
+
+ char* copy = duplicate(orig);
+ ATF_REQUIRE_EQ(std::strlen(copy), 3);
+ ATF_REQUIRE(std::strcmp(copy, "foo") == 0);
+
+ std::strcpy(copy, "bar");
+ ATF_REQUIRE(std::strcmp(copy, "bar") == 0);
+ ATF_REQUIRE(std::strcmp(orig, "foo") == 0);
+}
+
+ATF_TEST_CASE(join);
+ATF_TEST_CASE_HEAD(join)
+{
+ set_md_var("descr", "Tests the join function");
+}
+ATF_TEST_CASE_BODY(join)
+{
+ using tools::text::join;
+
+ // First set of tests using a non-sorted collection, std::vector.
+ {
+ std::vector< std::string > words;
+ std::string str;
+
+ words.clear();
+ str = join(words, ",");
+ ATF_REQUIRE_EQ(str, "");
+
+ words.clear();
+ words.push_back("");
+ str = join(words, ",");
+ ATF_REQUIRE_EQ(str, "");
+
+ words.clear();
+ words.push_back("");
+ words.push_back("");
+ str = join(words, ",");
+ ATF_REQUIRE_EQ(str, ",");
+
+ words.clear();
+ words.push_back("foo");
+ words.push_back("");
+ words.push_back("baz");
+ str = join(words, ",");
+ ATF_REQUIRE_EQ(str, "foo,,baz");
+
+ words.clear();
+ words.push_back("foo");
+ words.push_back("bar");
+ words.push_back("baz");
+ str = join(words, ",");
+ ATF_REQUIRE_EQ(str, "foo,bar,baz");
+ }
+
+ // Second set of tests using a sorted collection, std::set.
+ {
+ std::set< std::string > words;
+ std::string str;
+
+ words.clear();
+ str = join(words, ",");
+ ATF_REQUIRE_EQ(str, "");
+
+ words.clear();
+ words.insert("");
+ str = join(words, ",");
+ ATF_REQUIRE_EQ(str, "");
+
+ words.clear();
+ words.insert("foo");
+ words.insert("");
+ words.insert("baz");
+ str = join(words, ",");
+ ATF_REQUIRE_EQ(str, ",baz,foo");
+
+ words.clear();
+ words.insert("foo");
+ words.insert("bar");
+ words.insert("baz");
+ str = join(words, ",");
+ ATF_REQUIRE_EQ(str, "bar,baz,foo");
+ }
+}
+
+ATF_TEST_CASE(match);
+ATF_TEST_CASE_HEAD(match)
+{
+ set_md_var("descr", "Tests the match function");
+}
+ATF_TEST_CASE_BODY(match)
+{
+ using tools::text::match;
+
+ ATF_REQUIRE_THROW(std::runtime_error, match("", "["));
+
+ ATF_REQUIRE(match("", ""));
+ ATF_REQUIRE(!match("foo", ""));
+
+ ATF_REQUIRE(match("", ".*"));
+ ATF_REQUIRE(match("", "[a-z]*"));
+
+ ATF_REQUIRE(match("hello", "hello"));
+ ATF_REQUIRE(match("hello", "[a-z]+"));
+ ATF_REQUIRE(match("hello", "^[a-z]+$"));
+
+ ATF_REQUIRE(!match("hello", "helooo"));
+ ATF_REQUIRE(!match("hello", "[a-z]+5"));
+ ATF_REQUIRE(!match("hello", "^ [a-z]+$"));
+}
+
+ATF_TEST_CASE(split);
+ATF_TEST_CASE_HEAD(split)
+{
+ set_md_var("descr", "Tests the split function");
+}
+ATF_TEST_CASE_BODY(split)
+{
+ using tools::text::split;
+
+ std::vector< std::string > words;
+
+ words = split("", " ");
+ ATF_REQUIRE_EQ(words.size(), 0);
+
+ words = split(" ", " ");
+ ATF_REQUIRE_EQ(words.size(), 0);
+
+ words = split(" ", " ");
+ ATF_REQUIRE_EQ(words.size(), 0);
+
+ words = split("a b", " ");
+ ATF_REQUIRE_EQ(words.size(), 2);
+ ATF_REQUIRE_EQ(words[0], "a");
+ ATF_REQUIRE_EQ(words[1], "b");
+
+ words = split("a b c d", " ");
+ ATF_REQUIRE_EQ(words.size(), 4);
+ ATF_REQUIRE_EQ(words[0], "a");
+ ATF_REQUIRE_EQ(words[1], "b");
+ ATF_REQUIRE_EQ(words[2], "c");
+ ATF_REQUIRE_EQ(words[3], "d");
+
+ words = split("foo bar", " ");
+ ATF_REQUIRE_EQ(words.size(), 2);
+ ATF_REQUIRE_EQ(words[0], "foo");
+ ATF_REQUIRE_EQ(words[1], "bar");
+
+ words = split("foo bar baz foobar", " ");
+ ATF_REQUIRE_EQ(words.size(), 4);
+ ATF_REQUIRE_EQ(words[0], "foo");
+ ATF_REQUIRE_EQ(words[1], "bar");
+ ATF_REQUIRE_EQ(words[2], "baz");
+ ATF_REQUIRE_EQ(words[3], "foobar");
+
+ words = split(" foo bar", " ");
+ ATF_REQUIRE_EQ(words.size(), 2);
+ ATF_REQUIRE_EQ(words[0], "foo");
+ ATF_REQUIRE_EQ(words[1], "bar");
+
+ words = split("foo bar", " ");
+ ATF_REQUIRE_EQ(words.size(), 2);
+ ATF_REQUIRE_EQ(words[0], "foo");
+ ATF_REQUIRE_EQ(words[1], "bar");
+
+ words = split("foo bar ", " ");
+ ATF_REQUIRE_EQ(words.size(), 2);
+ ATF_REQUIRE_EQ(words[0], "foo");
+ ATF_REQUIRE_EQ(words[1], "bar");
+
+ words = split(" foo bar ", " ");
+ ATF_REQUIRE_EQ(words.size(), 2);
+ ATF_REQUIRE_EQ(words[0], "foo");
+ ATF_REQUIRE_EQ(words[1], "bar");
+}
+
+ATF_TEST_CASE(split_delims);
+ATF_TEST_CASE_HEAD(split_delims)
+{
+ set_md_var("descr", "Tests the split function using different delimiters");
+}
+ATF_TEST_CASE_BODY(split_delims)
+{
+ using tools::text::split;
+
+ std::vector< std::string > words;
+
+ words = split("", "/");
+ ATF_REQUIRE_EQ(words.size(), 0);
+
+ words = split(" ", "/");
+ ATF_REQUIRE_EQ(words.size(), 1);
+ ATF_REQUIRE_EQ(words[0], " ");
+
+ words = split(" ", "/");
+ ATF_REQUIRE_EQ(words.size(), 1);
+ ATF_REQUIRE_EQ(words[0], " ");
+
+ words = split("a/b", "/");
+ ATF_REQUIRE_EQ(words.size(), 2);
+ ATF_REQUIRE_EQ(words[0], "a");
+ ATF_REQUIRE_EQ(words[1], "b");
+
+ words = split("aLONGDELIMbcdLONGDELIMef", "LONGDELIM");
+ ATF_REQUIRE_EQ(words.size(), 3);
+ ATF_REQUIRE_EQ(words[0], "a");
+ ATF_REQUIRE_EQ(words[1], "bcd");
+ ATF_REQUIRE_EQ(words[2], "ef");
+}
+
+ATF_TEST_CASE(trim);
+ATF_TEST_CASE_HEAD(trim)
+{
+ set_md_var("descr", "Tests the trim function");
+}
+ATF_TEST_CASE_BODY(trim)
+{
+ using tools::text::trim;
+
+ ATF_REQUIRE_EQ(trim(""), "");
+ ATF_REQUIRE_EQ(trim(" "), "");
+ ATF_REQUIRE_EQ(trim("\t"), "");
+
+ ATF_REQUIRE_EQ(trim(" foo"), "foo");
+ ATF_REQUIRE_EQ(trim("\t foo"), "foo");
+ ATF_REQUIRE_EQ(trim(" \tfoo"), "foo");
+ ATF_REQUIRE_EQ(trim("foo\t "), "foo");
+ ATF_REQUIRE_EQ(trim("foo \t"), "foo");
+
+ ATF_REQUIRE_EQ(trim("foo bar"), "foo bar");
+ ATF_REQUIRE_EQ(trim("\t foo bar"), "foo bar");
+ ATF_REQUIRE_EQ(trim(" \tfoo bar"), "foo bar");
+ ATF_REQUIRE_EQ(trim("foo bar\t "), "foo bar");
+ ATF_REQUIRE_EQ(trim("foo bar \t"), "foo bar");
+}
+
+ATF_TEST_CASE(to_bool);
+ATF_TEST_CASE_HEAD(to_bool)
+{
+ set_md_var("descr", "Tests the to_string function");
+}
+ATF_TEST_CASE_BODY(to_bool)
+{
+ using tools::text::to_bool;
+
+ ATF_REQUIRE(to_bool("true"));
+ ATF_REQUIRE(to_bool("TRUE"));
+ ATF_REQUIRE(to_bool("yes"));
+ ATF_REQUIRE(to_bool("YES"));
+
+ ATF_REQUIRE(!to_bool("false"));
+ ATF_REQUIRE(!to_bool("FALSE"));
+ ATF_REQUIRE(!to_bool("no"));
+ ATF_REQUIRE(!to_bool("NO"));
+
+ ATF_REQUIRE_THROW(std::runtime_error, to_bool(""));
+ ATF_REQUIRE_THROW(std::runtime_error, to_bool("tru"));
+ ATF_REQUIRE_THROW(std::runtime_error, to_bool("true2"));
+ ATF_REQUIRE_THROW(std::runtime_error, to_bool("fals"));
+ ATF_REQUIRE_THROW(std::runtime_error, to_bool("false2"));
+}
+
+ATF_TEST_CASE(to_bytes);
+ATF_TEST_CASE_HEAD(to_bytes)
+{
+ set_md_var("descr", "Tests the to_bytes function");
+}
+ATF_TEST_CASE_BODY(to_bytes)
+{
+ using tools::text::to_bytes;
+
+ ATF_REQUIRE_EQ(0, to_bytes("0"));
+ ATF_REQUIRE_EQ(12345, to_bytes("12345"));
+ ATF_REQUIRE_EQ(2 * 1024, to_bytes("2k"));
+ ATF_REQUIRE_EQ(4 * 1024 * 1024, to_bytes("4m"));
+ ATF_REQUIRE_EQ(int64_t(8) * 1024 * 1024 * 1024, to_bytes("8g"));
+ ATF_REQUIRE_EQ(int64_t(16) * 1024 * 1024 * 1024 * 1024, to_bytes("16t"));
+
+ ATF_REQUIRE_THROW_RE(std::runtime_error, "Empty", to_bytes(""));
+ ATF_REQUIRE_THROW_RE(std::runtime_error, "Unknown size unit 'd'",
+ to_bytes("12d"));
+ ATF_REQUIRE_THROW(std::runtime_error, to_bytes(" "));
+ ATF_REQUIRE_THROW(std::runtime_error, to_bytes(" k"));
+}
+
+ATF_TEST_CASE(to_string);
+ATF_TEST_CASE_HEAD(to_string)
+{
+ set_md_var("descr", "Tests the to_string function");
+}
+ATF_TEST_CASE_BODY(to_string)
+{
+ using tools::text::to_string;
+
+ ATF_REQUIRE_EQ(to_string('a'), "a");
+ ATF_REQUIRE_EQ(to_string("a"), "a");
+ ATF_REQUIRE_EQ(to_string(5), "5");
+}
+
+ATF_TEST_CASE(to_type);
+ATF_TEST_CASE_HEAD(to_type)
+{
+ set_md_var("descr", "Tests the to_type function");
+}
+ATF_TEST_CASE_BODY(to_type)
+{
+ using tools::text::to_type;
+
+ ATF_REQUIRE_EQ(to_type< int >("0"), 0);
+ ATF_REQUIRE_EQ(to_type< int >("1234"), 1234);
+ ATF_REQUIRE_THROW(std::runtime_error, to_type< int >(" "));
+ ATF_REQUIRE_THROW(std::runtime_error, to_type< int >("0 a"));
+ ATF_REQUIRE_THROW(std::runtime_error, to_type< int >("a"));
+
+ ATF_REQUIRE_EQ(to_type< float >("0.5"), 0.5);
+ ATF_REQUIRE_EQ(to_type< float >("1234.5"), 1234.5);
+ ATF_REQUIRE_THROW(std::runtime_error, to_type< float >("0.5 a"));
+ ATF_REQUIRE_THROW(std::runtime_error, to_type< float >("a"));
+
+ ATF_REQUIRE_EQ(to_type< std::string >("a"), "a");
+}
+
+// ------------------------------------------------------------------------
+// Main.
+// ------------------------------------------------------------------------
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ // Add the test cases for the free functions.
+ ATF_ADD_TEST_CASE(tcs, duplicate);
+ ATF_ADD_TEST_CASE(tcs, join);
+ ATF_ADD_TEST_CASE(tcs, match);
+ ATF_ADD_TEST_CASE(tcs, split);
+ ATF_ADD_TEST_CASE(tcs, split_delims);
+ ATF_ADD_TEST_CASE(tcs, trim);
+ ATF_ADD_TEST_CASE(tcs, to_bool);
+ ATF_ADD_TEST_CASE(tcs, to_bytes);
+ ATF_ADD_TEST_CASE(tcs, to_string);
+ ATF_ADD_TEST_CASE(tcs, to_type);
+}
diff --git a/unit/atf-src/tools/timers.cpp b/unit/atf-src/tools/timers.cpp
new file mode 100644
index 0000000..a5334f4
--- /dev/null
+++ b/unit/atf-src/tools/timers.cpp
@@ -0,0 +1,211 @@
+//
+// Automated Testing Framework (atf)
+//
+// Copyright (c) 2010 The NetBSD Foundation, Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
+// CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+// IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
+// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+// IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+
+#if defined(HAVE_CONFIG_H)
+# include <config.h>
+#endif
+
+extern "C" {
+#include <sys/time.h>
+}
+
+#include <cassert>
+#include <cerrno>
+#include <csignal>
+#include <ctime>
+
+#include "defs.hpp"
+#include "exceptions.hpp"
+#include "signals.hpp"
+#include "timers.hpp"
+
+namespace impl = tools::timers;
+#define IMPL_NAME "tools::timers"
+
+#if !defined(HAVE_TIMER_T)
+static impl::timer* compat_handle;
+#endif
+
+// ------------------------------------------------------------------------
+// Auxiliary functions.
+// ------------------------------------------------------------------------
+
+#if defined(HAVE_TIMER_T)
+static
+void
+handler(const int signo ATF_DEFS_ATTRIBUTE_UNUSED, siginfo_t* si,
+ void* uc ATF_DEFS_ATTRIBUTE_UNUSED)
+{
+ impl::timer* timer = static_cast< impl::timer* >(si->si_value.sival_ptr);
+ timer->set_fired();
+ timer->timeout_callback();
+}
+#else
+static
+void
+handler(const int signo ATF_DEFS_ATTRIBUTE_UNUSED,
+ siginfo_t* si ATF_DEFS_ATTRIBUTE_UNUSED,
+ void* uc ATF_DEFS_ATTRIBUTE_UNUSED)
+{
+ compat_handle->set_fired();
+ compat_handle->timeout_callback();
+}
+#endif
+
+// ------------------------------------------------------------------------
+// The "timer" class.
+// ------------------------------------------------------------------------
+
+struct impl::timer::impl {
+#if defined(HAVE_TIMER_T)
+ ::timer_t m_timer;
+ ::itimerspec m_old_it;
+#else
+ ::itimerval m_old_it;
+#endif
+
+ struct ::sigaction m_old_sa;
+ volatile bool m_fired;
+
+ impl(void) : m_fired(false)
+ {
+ }
+};
+
+impl::timer::timer(const unsigned int seconds) :
+ m_pimpl(new impl())
+{
+ struct ::sigaction sa;
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = SA_SIGINFO;
+ sa.sa_sigaction = ::handler;
+ if (::sigaction(SIGALRM, &sa, &m_pimpl->m_old_sa) == -1)
+ throw tools::system_error(IMPL_NAME "::timer::timer",
+ "Failed to set signal handler", errno);
+
+#if defined(HAVE_TIMER_T)
+ struct ::sigevent se;
+ se.sigev_notify = SIGEV_SIGNAL;
+ se.sigev_signo = SIGALRM;
+ se.sigev_value.sival_ptr = static_cast< void* >(this);
+ se.sigev_notify_function = NULL;
+ se.sigev_notify_attributes = NULL;
+ if (::timer_create(CLOCK_MONOTONIC, &se, &m_pimpl->m_timer) == -1) {
+ ::sigaction(SIGALRM, &m_pimpl->m_old_sa, NULL);
+ throw tools::system_error(IMPL_NAME "::timer::timer",
+ "Failed to create timer", errno);
+ }
+
+ struct ::itimerspec it;
+ it.it_interval.tv_sec = 0;
+ it.it_interval.tv_nsec = 0;
+ it.it_value.tv_sec = seconds;
+ it.it_value.tv_nsec = 0;
+ if (::timer_settime(m_pimpl->m_timer, 0, &it, &m_pimpl->m_old_it) == -1) {
+ ::sigaction(SIGALRM, &m_pimpl->m_old_sa, NULL);
+ ::timer_delete(m_pimpl->m_timer);
+ throw tools::system_error(IMPL_NAME "::timer::timer",
+ "Failed to program timer", errno);
+ }
+#else
+ ::itimerval it;
+ it.it_interval.tv_sec = 0;
+ it.it_interval.tv_usec = 0;
+ it.it_value.tv_sec = seconds;
+ it.it_value.tv_usec = 0;
+ if (::setitimer(ITIMER_REAL, &it, &m_pimpl->m_old_it) == -1) {
+ ::sigaction(SIGALRM, &m_pimpl->m_old_sa, NULL);
+ throw tools::system_error(IMPL_NAME "::timer::timer",
+ "Failed to program timer", errno);
+ }
+ assert(compat_handle == NULL);
+ compat_handle = this;
+#endif
+}
+
+impl::timer::~timer(void)
+{
+#if defined(HAVE_TIMER_T)
+ {
+ const int ret = ::timer_delete(m_pimpl->m_timer);
+ assert(ret != -1);
+ }
+#else
+ {
+ const int ret = ::setitimer(ITIMER_REAL, &m_pimpl->m_old_it, NULL);
+ assert(ret != -1);
+ }
+#endif
+ const int ret = ::sigaction(SIGALRM, &m_pimpl->m_old_sa, NULL);
+ assert(ret != -1);
+
+#if !defined(HAVE_TIMER_T)
+ compat_handle = NULL;
+#endif
+}
+
+bool
+impl::timer::fired(void)
+ const
+{
+ return m_pimpl->m_fired;
+}
+
+void
+impl::timer::set_fired(void)
+{
+ m_pimpl->m_fired = true;
+}
+
+// ------------------------------------------------------------------------
+// The "child_timer" class.
+// ------------------------------------------------------------------------
+
+impl::child_timer::child_timer(const unsigned int seconds, const pid_t pid,
+ volatile bool& terminate) :
+ timer(seconds),
+ m_pid(pid),
+ m_terminate(terminate)
+{
+}
+
+impl::child_timer::~child_timer(void)
+{
+}
+
+void
+impl::child_timer::timeout_callback(void)
+{
+ static const timespec ts = { 1, 0 };
+ m_terminate = true;
+ ::kill(-m_pid, SIGTERM);
+ ::nanosleep(&ts, NULL);
+ if (::kill(-m_pid, 0) != -1)
+ ::kill(-m_pid, SIGKILL);
+}
diff --git a/unit/atf-src/tools/timers.hpp b/unit/atf-src/tools/timers.hpp
new file mode 100644
index 0000000..bf43ffb
--- /dev/null
+++ b/unit/atf-src/tools/timers.hpp
@@ -0,0 +1,83 @@
+//
+// Automated Testing Framework (atf)
+//
+// Copyright (c) 2010 The NetBSD Foundation, Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
+// CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+// IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
+// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+// IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+
+#if !defined(TOOLS_TIMERS_HPP)
+#define TOOLS_TIMERS_HPP
+
+extern "C" {
+#include <sys/types.h>
+}
+
+#include <memory>
+
+namespace tools {
+namespace timers {
+
+class signal_programmer;
+
+// ------------------------------------------------------------------------
+// The "timer" class.
+// ------------------------------------------------------------------------
+
+class timer {
+ // Non-copyable.
+ timer(const timer&);
+ timer& operator=(const timer&);
+
+ struct impl;
+ std::auto_ptr< impl > m_pimpl;
+
+public:
+ timer(const unsigned int);
+ virtual ~timer(void);
+
+ bool fired(void) const;
+ void set_fired(void);
+ virtual void timeout_callback(void) = 0;
+};
+
+// ------------------------------------------------------------------------
+// The "child_timer" class.
+// ------------------------------------------------------------------------
+
+class child_timer : public timer {
+ const pid_t m_pid;
+ volatile bool& m_terminate;
+
+public:
+ child_timer(const unsigned int, const pid_t, volatile bool&);
+ virtual ~child_timer(void);
+
+ void timeout_callback(void);
+};
+
+} // namespace timers
+} // namespace tools
+
+#endif // !defined(TOOLS_TIMERS_HPP)
diff --git a/unit/atf-src/tools/ui.cpp b/unit/atf-src/tools/ui.cpp
new file mode 100644
index 0000000..56426ec
--- /dev/null
+++ b/unit/atf-src/tools/ui.cpp
@@ -0,0 +1,172 @@
+//
+// Automated Testing Framework (atf)
+//
+// Copyright (c) 2007 The NetBSD Foundation, Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
+// CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+// IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
+// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+// IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+
+extern "C" {
+#include <sys/ioctl.h>
+
+#include <termios.h>
+#include <unistd.h>
+}
+
+#include <cassert>
+#include <sstream>
+
+#include "env.hpp"
+#include "text.hpp"
+#include "ui.hpp"
+
+namespace impl = tools::ui;
+#define IMPL_NAME "tools::ui"
+
+static
+size_t
+terminal_width(void)
+{
+ static bool done = false;
+ static size_t width = 0;
+
+ if (!done) {
+ if (tools::env::has("COLUMNS")) {
+ const std::string cols = tools::env::get("COLUMNS");
+ if (cols.length() > 0) {
+ width = tools::text::to_type< size_t >(cols);
+ }
+ } else {
+ struct winsize ws;
+ if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) != -1)
+ width = ws.ws_col;
+ }
+
+ if (width >= 80)
+ width -= 5;
+
+ done = true;
+ }
+
+ return width;
+}
+
+static
+std::string
+format_paragraph(const std::string& text,
+ const std::string& tag,
+ const bool first,
+ const bool repeat,
+ const size_t col)
+{
+ assert(text.find('\n') == std::string::npos);
+
+ const std::string pad(col - tag.length(), ' ');
+ const std::string fullpad(col, ' ');
+
+ std::string formatted;
+ if (first || repeat)
+ formatted = tag + pad;
+ else
+ formatted = fullpad;
+ assert(formatted.length() == col);
+ size_t curcol = col;
+
+ const size_t maxcol = terminal_width();
+
+ std::vector< std::string > words = tools::text::split(text, " ");
+ for (std::vector< std::string >::const_iterator iter = words.begin();
+ iter != words.end(); iter++) {
+ const std::string& word = *iter;
+
+ if (iter != words.begin() && maxcol > 0 &&
+ curcol + word.length() + 1 > maxcol) {
+ if (repeat)
+ formatted += '\n' + tag + pad;
+ else
+ formatted += '\n' + fullpad;
+ curcol = col;
+ } else if (iter != words.begin()) {
+ formatted += ' ';
+ curcol++;
+ }
+
+ formatted += word;
+ curcol += word.length();
+ }
+
+ return formatted;
+}
+
+std::string
+impl::format_error(const std::string& prog_name, const std::string& error)
+{
+ return format_text_with_tag("ERROR: " + error, prog_name + ": ", true);
+}
+
+std::string
+impl::format_info(const std::string& prog_name, const std::string& msg)
+{
+ return format_text_with_tag(msg, prog_name + ": ", true);
+}
+
+std::string
+impl::format_text(const std::string& text)
+{
+ return format_text_with_tag(text, "", false, 0);
+}
+
+std::string
+impl::format_text_with_tag(const std::string& text, const std::string& tag,
+ bool repeat, size_t col)
+{
+ assert(col == 0 || col >= tag.length());
+ if (col == 0)
+ col = tag.length();
+
+ std::string formatted;
+
+ std::vector< std::string > lines = tools::text::split(text, "\n");
+ for (std::vector< std::string >::const_iterator iter = lines.begin();
+ iter != lines.end(); iter++) {
+ const std::string& line = *iter;
+
+ formatted += format_paragraph(line, tag, iter == lines.begin(),
+ repeat, col);
+ if (iter + 1 != lines.end()) {
+ if (repeat)
+ formatted += "\n" + tag + "\n";
+ else
+ formatted += "\n\n";
+ }
+ }
+
+ return formatted;
+}
+
+std::string
+impl::format_warning(const std::string& prog_name, const std::string& error)
+{
+ return format_text_with_tag("WARNING: " + error, prog_name + ": ", true);
+}
diff --git a/unit/atf-src/tools/ui.hpp b/unit/atf-src/tools/ui.hpp
new file mode 100644
index 0000000..4e0e431
--- /dev/null
+++ b/unit/atf-src/tools/ui.hpp
@@ -0,0 +1,105 @@
+//
+// Automated Testing Framework (atf)
+//
+// Copyright (c) 2007 The NetBSD Foundation, Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
+// CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+// IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
+// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+// IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+
+#if !defined(TOOLS_UI_HPP)
+#define TOOLS_UI_HPP
+
+#include <string>
+
+namespace tools {
+namespace ui {
+
+//!
+//! \brief Formats an error message to fit on screen.
+//!
+//! Given the program's name and an error message, properly formats it to
+//! fit on screen.
+//!
+//! The program's name is not stored globally to prevent the usage of this
+//! function from inside the library. Making it a explicit parameter
+//! restricts its usage to the frontend.
+//!
+std::string format_error(const std::string&, const std::string&);
+
+//!
+//! \brief Formats an informational message to fit on screen.
+//!
+//! Given the program's name and an informational message, properly formats
+//! it to fit on screen.
+//!
+//! The program's name is not stored globally to prevent the usage of this
+//! function from inside the library. Making it a explicit parameter
+//! restricts its usage to the frontend.
+//!
+std::string format_info(const std::string&, const std::string&);
+
+//!
+//! \brief Formats a block of text to fit nicely on screen.
+//!
+//! Given a text, which is composed of multiple paragraphs separated by
+//! a single '\n' character, reformats it to fill on the current screen's
+//! width with proper line wrapping.
+//!
+//! This is just a special case of format_text_with_tag, provided for
+//! simplicity.
+//!
+std::string format_text(const std::string&);
+
+//!
+//! \brief Formats a block of text to fit nicely on screen, prepending a
+//! tag to it.
+//!
+//! Given a text, which is composed of multiple paragraphs separated by
+//! a single '\n' character, reformats it to fill on the current screen's
+//! width with proper line wrapping. The text is prepended with a tag;
+//! i.e. a word that is printed at the beginning of the first paragraph and
+//! optionally repeated at the beginning of each word. The last parameter
+//! specifies the column on which the text should start, and that position
+//! must be greater than the tag's length or 0, in which case it
+//! automatically takes the correct value.
+//!
+std::string format_text_with_tag(const std::string&, const std::string&,
+ bool, size_t = 0);
+
+//!
+//! \brief Formats a warning message to fit on screen.
+//!
+//! Given the program's name and a warning message, properly formats it to
+//! fit on screen.
+//!
+//! The program's name is not stored globally to prevent the usage of this
+//! function from inside the library. Making it a explicit parameter
+//! restricts its usage to the frontend.
+//!
+std::string format_warning(const std::string&, const std::string&);
+
+} // namespace ui
+} // namespace tools
+
+#endif // !defined(TOOLS_UI_HPP)
diff --git a/unit/atf-src/tools/ui_test.cpp b/unit/atf-src/tools/ui_test.cpp
new file mode 100644
index 0000000..e4d9050
--- /dev/null
+++ b/unit/atf-src/tools/ui_test.cpp
@@ -0,0 +1,462 @@
+//
+// Automated Testing Framework (atf)
+//
+// Copyright (c) 2009 The NetBSD Foundation, Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
+// CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+// IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
+// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+// IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+
+#include <cstring>
+#include <iostream>
+
+#include <atf-c++.hpp>
+
+#include "env.hpp"
+#include "ui.hpp"
+
+// ------------------------------------------------------------------------
+// Test cases for the free functions.
+// ------------------------------------------------------------------------
+
+struct test {
+ const char *tc;
+ const char *tag;
+ bool repeat;
+ size_t col;
+ const char *fmt;
+ const char *result;
+} tests[] = {
+ //
+ // wo_tag
+ //
+
+ {
+ "wo_tag",
+ "",
+ false,
+ 0,
+ "12345",
+ "12345",
+ },
+
+ {
+ "wo_tag",
+ "",
+ false,
+ 0,
+ "12345 ",
+ "12345",
+ },
+
+ {
+ "wo_tag",
+ "",
+ false,
+ 0,
+ "12345 7890",
+ "12345 7890",
+ },
+
+ {
+ "wo_tag",
+ "",
+ false,
+ 0,
+ "12345 789012 45",
+ "12345 789012 45",
+ },
+
+ {
+ "wo_tag",
+ "",
+ false,
+ 0,
+ "12345 789012 456",
+ "12345 789012\n456",
+ },
+
+ {
+ "wo_tag",
+ "",
+ false,
+ 0,
+ "1234567890123456",
+ "1234567890123456",
+ },
+
+ // TODO(jmmv): Fix the code to pass this test...
+// {
+// "wo_tag",
+// "",
+// false,
+// 0,
+// " 2345678901234567",
+// "\n2345678901234567",
+// },
+
+ {
+ "wo_tag",
+ "",
+ false,
+ 0,
+ "12345 789012345 78",
+ "12345 789012345\n78",
+ },
+
+ //
+ // wo_tag_col
+ //
+
+ {
+ "wo_tag_col",
+ "",
+ false,
+ 10,
+ "12345",
+ " 12345",
+ },
+
+ {
+ "wo_tag_col",
+ "",
+ false,
+ 10,
+ "12345 7890",
+ " 12345\n"
+ " 7890",
+ },
+
+ {
+ "wo_tag_col",
+ "",
+ false,
+ 10,
+ "1 3 5 7 9",
+ " 1 3 5\n"
+ " 7 9",
+ },
+
+ //
+ // w_tag_no_repeat
+ //
+
+ {
+ "w_tag_no_repeat",
+ "1234: ",
+ false,
+ 0,
+ "789012345",
+ "1234: 789012345",
+ },
+
+ {
+ "w_tag_no_repeat",
+ "1234: ",
+ false,
+ 0,
+ "789 1234 56789",
+ "1234: 789 1234\n"
+ " 56789",
+ },
+
+ {
+ "w_tag_no_repeat",
+ "1234: ",
+ false,
+ 0,
+ "789012345",
+ "1234: 789012345",
+ },
+
+ {
+ "w_tag_no_repeat",
+ "1234: ",
+ false,
+ 0,
+ "789012345 7890",
+ "1234: 789012345\n"
+ " 7890",
+ },
+
+ //
+ // w_tag_repeat
+ //
+
+ {
+ "w_tag_repeat",
+ "1234: ",
+ true,
+ 0,
+ "789012345",
+ "1234: 789012345",
+ },
+
+ {
+ "w_tag_repeat",
+ "1234: ",
+ true,
+ 0,
+ "789 1234 56789",
+ "1234: 789 1234\n"
+ "1234: 56789",
+ },
+
+ {
+ "w_tag_repeat",
+ "1234: ",
+ true,
+ 0,
+ "789012345",
+ "1234: 789012345",
+ },
+
+ {
+ "w_tag_no_repeat",
+ "1234: ",
+ true,
+ 0,
+ "789012345 7890",
+ "1234: 789012345\n"
+ "1234: 7890",
+ },
+
+ //
+ // w_tag_col
+ //
+
+ {
+ "w_tag_col",
+ "1234:",
+ false,
+ 10,
+ "1 3 5",
+ "1234: 1 3 5",
+ },
+
+ {
+ "w_tag_col",
+ "1234:",
+ false,
+ 10,
+ "1 3 5 7 9",
+ "1234: 1 3 5\n"
+ " 7 9",
+ },
+
+ {
+ "w_tag_col",
+ "1234:",
+ true,
+ 10,
+ "1 3 5 7 9",
+ "1234: 1 3 5\n"
+ "1234: 7 9",
+ },
+
+ //
+ // paragraphs
+ //
+
+ {
+ "paragraphs",
+ "",
+ false,
+ 0,
+ "1 3 5\n\n",
+ "1 3 5"
+ },
+
+ {
+ "paragraphs",
+ "",
+ false,
+ 0,
+ "1 3 5\n2 4 6",
+ "1 3 5\n\n2 4 6"
+ },
+
+ {
+ "paragraphs",
+ "",
+ false,
+ 0,
+ "1234 6789 123456\n2 4 6",
+ "1234 6789\n123456\n\n2 4 6"
+ },
+
+ {
+ "paragraphs",
+ "12: ",
+ false,
+ 0,
+ "56789 123456\n2 4 6",
+ "12: 56789\n 123456\n\n 2 4 6"
+ },
+
+ {
+ "paragraphs",
+ "12: ",
+ true,
+ 0,
+ "56789 123456\n2 4 6",
+ "12: 56789\n12: 123456\n12: \n12: 2 4 6"
+ },
+
+ {
+ "paragraphs",
+ "12:",
+ false,
+ 4,
+ "56789 123456\n2 4 6",
+ "12: 56789\n 123456\n\n 2 4 6"
+ },
+
+ {
+ "paragraphs",
+ "12:",
+ true,
+ 4,
+ "56789 123456\n2 4 6",
+ "12: 56789\n12: 123456\n12:\n12: 2 4 6"
+ },
+
+ //
+ // end
+ //
+
+ {
+ NULL,
+ NULL,
+ false,
+ 0,
+ NULL,
+ NULL,
+ },
+};
+
+static
+void
+run_tests(const char *tc)
+{
+ struct test *t;
+
+ std::cout << "Running tests for " << tc << "\n";
+
+ tools::env::set("COLUMNS", "15");
+
+ for (t = &tests[0]; t->tc != NULL; t++) {
+ if (std::strcmp(t->tc, tc) == 0) {
+ std::cout << "\n";
+ std::cout << "Testing with tag '" << t->tag << "', '"
+ << (t->repeat ? "repeat" : "no repeat") << "', col "
+ << t->col << "\n";
+ std::cout << "Input: >>>" << t->fmt << "<<<\n";
+ std::cout << "Expected output: >>>" << t->result << "<<<\n";
+
+ std::string result = tools::ui::format_text_with_tag(t->fmt, t->tag,
+ t->repeat, t->col);
+ std::cout << "Output : >>>" << result << "<<<\n";
+ ATF_REQUIRE_EQ(t->result, result);
+ }
+ }
+}
+
+ATF_TEST_CASE(wo_tag);
+ATF_TEST_CASE_HEAD(wo_tag)
+{
+ set_md_var("descr", "Checks formatting without tags");
+}
+ATF_TEST_CASE_BODY(wo_tag)
+{
+ run_tests("wo_tag");
+}
+
+ATF_TEST_CASE(wo_tag_col);
+ATF_TEST_CASE_HEAD(wo_tag_col)
+{
+ set_md_var("descr", "Checks formatting without tags and with a non-zero "
+ "starting column");
+}
+ATF_TEST_CASE_BODY(wo_tag_col)
+{
+ run_tests("wo_tag_col");
+}
+
+ATF_TEST_CASE(w_tag_no_repeat);
+ATF_TEST_CASE_HEAD(w_tag_no_repeat)
+{
+ set_md_var("descr", "Checks formatting with a tag");
+}
+ATF_TEST_CASE_BODY(w_tag_no_repeat)
+{
+ run_tests("w_tag_no_repeat");
+}
+
+ATF_TEST_CASE(w_tag_repeat);
+ATF_TEST_CASE_HEAD(w_tag_repeat)
+{
+ set_md_var("descr", "Checks formatting with a tag and repeating it on "
+ "each line");
+}
+ATF_TEST_CASE_BODY(w_tag_repeat)
+{
+ run_tests("w_tag_repeat");
+}
+
+ATF_TEST_CASE(w_tag_col);
+ATF_TEST_CASE_HEAD(w_tag_col)
+{
+ set_md_var("descr", "Checks formatting with a tag and starting at a "
+ "column greater than its length");
+}
+ATF_TEST_CASE_BODY(w_tag_col)
+{
+ run_tests("w_tag_col");
+}
+
+ATF_TEST_CASE(paragraphs);
+ATF_TEST_CASE_HEAD(paragraphs)
+{
+ set_md_var("descr", "Checks formatting a string that contains multiple "
+ "paragraphs");
+}
+ATF_TEST_CASE_BODY(paragraphs)
+{
+ run_tests("paragraphs");
+}
+
+// ------------------------------------------------------------------------
+// Main.
+// ------------------------------------------------------------------------
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ // Add the test cases for the free functions.
+ ATF_ADD_TEST_CASE(tcs, wo_tag);
+ ATF_ADD_TEST_CASE(tcs, wo_tag_col);
+ ATF_ADD_TEST_CASE(tcs, w_tag_no_repeat);
+ ATF_ADD_TEST_CASE(tcs, w_tag_repeat);
+ ATF_ADD_TEST_CASE(tcs, w_tag_col);
+ ATF_ADD_TEST_CASE(tcs, paragraphs);
+}
diff --git a/unit/atf-src/tools/user.cpp b/unit/atf-src/tools/user.cpp
new file mode 100644
index 0000000..85d7e50
--- /dev/null
+++ b/unit/atf-src/tools/user.cpp
@@ -0,0 +1,102 @@
+//
+// Automated Testing Framework (atf)
+//
+// Copyright (c) 2007 The NetBSD Foundation, Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
+// CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+// IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
+// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+// IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+
+extern "C" {
+#include <sys/param.h>
+#include <sys/types.h>
+
+#include <limits.h>
+#include <pwd.h>
+#include <unistd.h>
+}
+
+#include <cassert>
+#include <stdexcept>
+#include <string>
+
+#include "user.hpp"
+
+namespace impl = tools::user;
+#define IMPL_NAME "tools::user"
+
+uid_t
+impl::euid(void)
+{
+ return ::geteuid();
+}
+
+void
+impl::drop_privileges(const std::pair< int, int > ids)
+{
+ if (::setgid(ids.second) == -1)
+ throw std::runtime_error("Failed to drop group privileges");
+ if (::setuid(ids.first) == -1)
+ throw std::runtime_error("Failed to drop user privileges");
+}
+
+std::pair< int, int >
+impl::get_user_ids(const std::string& user)
+{
+ const struct passwd* pw = ::getpwnam(user.c_str());
+ if (pw == NULL)
+ throw std::runtime_error("Failed to get information for user " + user);
+ return std::make_pair(pw->pw_uid, pw->pw_gid);
+}
+
+bool
+impl::is_member_of_group(gid_t gid)
+{
+ static gid_t groups[NGROUPS_MAX];
+ static int ngroups = -1;
+ bool found;
+ int i;
+
+ if (ngroups == -1) {
+ ngroups = getgroups(NGROUPS_MAX, groups);
+ assert(ngroups >= 0);
+ }
+
+ found = false;
+ for (i = 0; !found && i < ngroups; i++)
+ if (groups[i] == gid)
+ found = true;
+ return found;
+}
+
+bool
+impl::is_root(void)
+{
+ return ::geteuid() == 0;
+}
+
+bool
+impl::is_unprivileged(void)
+{
+ return ::geteuid() != 0;
+}
diff --git a/unit/atf-src/tools/user.hpp b/unit/atf-src/tools/user.hpp
new file mode 100644
index 0000000..1758eb0
--- /dev/null
+++ b/unit/atf-src/tools/user.hpp
@@ -0,0 +1,52 @@
+//
+// Automated Testing Framework (atf)
+//
+// Copyright (c) 2007 The NetBSD Foundation, Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
+// CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+// IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
+// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+// IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+
+#if !defined(TOOLS_USER_HPP)
+#define TOOLS_USER_HPP
+
+extern "C" {
+#include <sys/types.h>
+}
+
+#include <utility>
+
+namespace tools {
+namespace user {
+
+uid_t euid(void);
+void drop_privileges(const std::pair< int, int >);
+std::pair< int, int > get_user_ids(const std::string&);
+bool is_member_of_group(gid_t);
+bool is_root(void);
+bool is_unprivileged(void);
+
+} // namespace user
+} // namespace tools
+
+#endif // !defined(TOOLS_USER_HPP)
diff --git a/unit/atf-src/tools/user_test.cpp b/unit/atf-src/tools/user_test.cpp
new file mode 100644
index 0000000..4c385d3
--- /dev/null
+++ b/unit/atf-src/tools/user_test.cpp
@@ -0,0 +1,148 @@
+//
+// Automated Testing Framework (atf)
+//
+// Copyright (c) 2007 The NetBSD Foundation, Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
+// CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+// IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
+// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+// IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+
+extern "C" {
+#include <sys/param.h>
+#include <sys/types.h>
+#include <limits.h>
+#include <unistd.h>
+}
+
+#include <iostream>
+#include <set>
+
+#include <atf-c++.hpp>
+
+#include "user.hpp"
+
+// ------------------------------------------------------------------------
+// Test cases for the free functions.
+// ------------------------------------------------------------------------
+
+ATF_TEST_CASE(euid);
+ATF_TEST_CASE_HEAD(euid)
+{
+ set_md_var("descr", "Tests the euid function");
+}
+ATF_TEST_CASE_BODY(euid)
+{
+ using tools::user::euid;
+
+ ATF_REQUIRE_EQ(euid(), ::geteuid());
+}
+
+ATF_TEST_CASE(is_member_of_group);
+ATF_TEST_CASE_HEAD(is_member_of_group)
+{
+ set_md_var("descr", "Tests the is_member_of_group function");
+}
+ATF_TEST_CASE_BODY(is_member_of_group)
+{
+ using tools::user::is_member_of_group;
+
+ std::set< gid_t > groups;
+ gid_t maxgid = 0;
+ {
+ gid_t gids[NGROUPS_MAX];
+ int ngids = ::getgroups(NGROUPS_MAX, gids);
+ if (ngids == -1)
+ ATF_FAIL("Call to ::getgroups failed");
+ for (int i = 0; i < ngids; i++) {
+ groups.insert(gids[i]);
+ if (gids[i] > maxgid)
+ maxgid = gids[i];
+ }
+ std::cout << "User belongs to " << ngids << " groups\n";
+ std::cout << "Last GID is " << maxgid << "\n";
+ }
+
+ const gid_t maxgid_limit = 1 << 16;
+ if (maxgid > maxgid_limit) {
+ std::cout << "Test truncated from " << maxgid << " groups to "
+ << maxgid_limit << " to keep the run time reasonable "
+ "enough\n";
+ maxgid = maxgid_limit;
+ }
+
+ for (gid_t g = 0; g <= maxgid; g++) {
+ if (groups.find(g) == groups.end()) {
+ std::cout << "Checking if user does not belong to group "
+ << g << "\n";
+ ATF_REQUIRE(!is_member_of_group(g));
+ } else {
+ std::cout << "Checking if user belongs to group " << g << "\n";
+ ATF_REQUIRE(is_member_of_group(g));
+ }
+ }
+}
+
+ATF_TEST_CASE(is_root);
+ATF_TEST_CASE_HEAD(is_root)
+{
+ set_md_var("descr", "Tests the is_root function");
+}
+ATF_TEST_CASE_BODY(is_root)
+{
+ using tools::user::is_root;
+
+ if (::geteuid() == 0) {
+ ATF_REQUIRE(is_root());
+ } else {
+ ATF_REQUIRE(!is_root());
+ }
+}
+
+ATF_TEST_CASE(is_unprivileged);
+ATF_TEST_CASE_HEAD(is_unprivileged)
+{
+ set_md_var("descr", "Tests the is_unprivileged function");
+}
+ATF_TEST_CASE_BODY(is_unprivileged)
+{
+ using tools::user::is_unprivileged;
+
+ if (::geteuid() != 0) {
+ ATF_REQUIRE(is_unprivileged());
+ } else {
+ ATF_REQUIRE(!is_unprivileged());
+ }
+}
+
+// ------------------------------------------------------------------------
+// Main.
+// ------------------------------------------------------------------------
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ // Add the tests for the free functions.
+ ATF_ADD_TEST_CASE(tcs, euid);
+ ATF_ADD_TEST_CASE(tcs, is_member_of_group);
+ ATF_ADD_TEST_CASE(tcs, is_root);
+ ATF_ADD_TEST_CASE(tcs, is_unprivileged);
+}
diff --git a/unit/atf-src/tools/zero_tcs_helper.c b/unit/atf-src/tools/zero_tcs_helper.c
new file mode 100644
index 0000000..d4068b1
--- /dev/null
+++ b/unit/atf-src/tools/zero_tcs_helper.c
@@ -0,0 +1,36 @@
+/*
+ * Automated Testing Framework (atf)
+ *
+ * Copyright (c) 2010 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
+ * CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+ * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+ * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <atf-c.h>
+
+ATF_TP_ADD_TCS(tp)
+{
+ if (tp != NULL) {} /* Use tp. */
+ return atf_no_error();
+}