2 // Automated Testing Framework (atf)
4 // Copyright (c) 2007 The NetBSD Foundation, Inc.
5 // All rights reserved.
7 // Redistribution and use in source and binary forms, with or without
8 // modification, are permitted provided that the following conditions
10 // 1. Redistributions of source code must retain the above copyright
11 // notice, this list of conditions and the following disclaimer.
12 // 2. Redistributions in binary form must reproduce the above copyright
13 // notice, this list of conditions and the following disclaimer in the
14 // documentation and/or other materials provided with the distribution.
16 // THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
17 // CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
18 // INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
19 // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 // IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
21 // DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22 // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
23 // GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24 // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
25 // IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
26 // OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
27 // IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 #if defined(HAVE_CONFIG_H)
35 #include <sys/types.h>
36 #include <sys/param.h>
51 #include "atf-c++/detail/application.hpp"
52 #include "atf-c++/config.hpp"
53 #include "atf-c++/tests.hpp"
55 #include "atf-c++/detail/env.hpp"
56 #include "atf-c++/detail/exceptions.hpp"
57 #include "atf-c++/detail/fs.hpp"
58 #include "atf-c++/detail/parser.hpp"
59 #include "atf-c++/detail/process.hpp"
60 #include "atf-c++/detail/sanity.hpp"
61 #include "atf-c++/detail/text.hpp"
63 #include "atffile.hpp"
66 #include "requirements.hpp"
67 #include "test-program.hpp"
69 namespace impl
= atf::atf_run
;
71 #if defined(MAXCOMLEN)
72 static const std::string::size_type max_core_name_length
= MAXCOMLEN
;
74 static const std::string::size_type max_core_name_length
= std::string::npos
;
77 class atf_run
: public atf::application::app
{
78 static const char* m_description
;
80 atf::tests::vars_map m_cmdline_vars
;
82 static atf::tests::vars_map::value_type
parse_var(const std::string
&);
84 void process_option(int, const char*);
85 std::string
specific_args(void) const;
86 options_set
specific_options(void) const;
88 void parse_vflag(const std::string
&);
90 std::vector
< std::string
> conf_args(void) const;
92 size_t count_tps(std::vector
< std::string
>) const;
94 int run_test(const atf::fs::path
&, impl::atf_tps_writer
&,
95 const atf::tests::vars_map
&);
96 int run_test_directory(const atf::fs::path
&, impl::atf_tps_writer
&);
97 int run_test_program(const atf::fs::path
&, impl::atf_tps_writer
&,
98 const atf::tests::vars_map
&);
100 impl::test_case_result
get_test_case_result(const std::string
&,
101 const atf::process::status
&, const atf::fs::path
&) const;
110 sanitize_gdb_env(void)
113 atf::env::unset("TERM");
115 // Just swallow exceptions here; they cannot propagate into C, which
116 // is where this function is called from, and even if these exceptions
117 // appear they are benign.
122 dump_stacktrace(const atf::fs::path
& tp
, const atf::process::status
& s
,
123 const atf::fs::path
& workdir
, impl::atf_tps_writer
& w
)
125 PRE(s
.signaled() && s
.coredump());
127 w
.stderr_tc("Test program crashed; attempting to get stack trace");
129 const atf::fs::path corename
= workdir
/
130 (tp
.leaf_name().substr(0, max_core_name_length
) + ".core");
131 if (!atf::fs::exists(corename
)) {
132 w
.stderr_tc("Expected file " + corename
.str() + " not found");
136 const atf::fs::path
gdb(GDB
);
137 const atf::fs::path gdbout
= workdir
/ "gdb.out";
138 const atf::process::argv_array
args(gdb
.leaf_name().c_str(), "-batch",
139 "-q", "-ex", "bt", tp
.c_str(),
140 corename
.c_str(), NULL
);
141 atf::process::status status
= atf::process::exec(
143 atf::process::stream_redirect_path(gdbout
),
144 atf::process::stream_redirect_path(atf::fs::path("/dev/null")),
146 if (!status
.exited() || status
.exitstatus() != EXIT_SUCCESS
) {
147 w
.stderr_tc("Execution of " GDB
" failed");
151 std::ifstream
input(gdbout
.c_str());
154 while (std::getline(input
, line
).good())
159 w
.stderr_tc("Stack trace complete");
162 const char* atf_run::m_description
=
163 "atf-run is a tool that runs tests programs and collects their "
166 atf_run::atf_run(void) :
167 app(m_description
, "atf-run(1)", "atf(7)")
172 atf_run::process_option(int ch
, const char* arg
)
185 atf_run::specific_args(void)
188 return "[test-program1 .. test-programN]";
192 atf_run::specific_options(void)
195 using atf::application::option
;
197 opts
.insert(option('v', "var=value", "Sets the configuration variable "
198 "`var' to `value'; overrides "
199 "values in configuration files"));
204 atf_run::parse_vflag(const std::string
& str
)
207 throw std::runtime_error("-v requires a non-empty argument");
209 std::vector
< std::string
> ws
= atf::text::split(str
, "=");
210 if (ws
.size() == 1 && str
[str
.length() - 1] == '=') {
211 m_cmdline_vars
[ws
[0]] = "";
214 throw std::runtime_error("-v requires an argument of the form "
217 m_cmdline_vars
[ws
[0]] = ws
[1];
222 atf_run::run_test(const atf::fs::path
& tp
,
223 impl::atf_tps_writer
& w
,
224 const atf::tests::vars_map
& config
)
226 atf::fs::file_info
fi(tp
);
229 if (fi
.get_type() == atf::fs::file_info::dir_type
)
230 errcode
= run_test_directory(tp
, w
);
232 const atf::tests::vars_map effective_config
=
233 impl::merge_configs(config
, m_cmdline_vars
);
235 errcode
= run_test_program(tp
, w
, effective_config
);
241 atf_run::run_test_directory(const atf::fs::path
& tp
,
242 impl::atf_tps_writer
& w
)
244 impl::atffile af
= impl::read_atffile(tp
/ "Atffile");
246 atf::tests::vars_map test_suite_vars
;
248 atf::tests::vars_map::const_iterator iter
=
249 af
.props().find("test-suite");
250 INV(iter
!= af
.props().end());
251 test_suite_vars
= impl::read_config_files((*iter
).second
);
255 for (std::vector
< std::string
>::const_iterator iter
= af
.tps().begin();
256 iter
!= af
.tps().end(); iter
++) {
257 const bool result
= run_test(tp
/ *iter
, w
,
258 impl::merge_configs(af
.conf(), test_suite_vars
));
259 ok
&= (result
== EXIT_SUCCESS
);
262 return ok
? EXIT_SUCCESS
: EXIT_FAILURE
;
265 impl::test_case_result
266 atf_run::get_test_case_result(const std::string
& broken_reason
,
267 const atf::process::status
& s
,
268 const atf::fs::path
& resfile
)
271 using atf::text::to_string
;
272 using impl::read_test_case_result
;
273 using impl::test_case_result
;
275 if (!broken_reason
.empty()) {
276 test_case_result tcr
;
279 tcr
= read_test_case_result(resfile
);
281 if (tcr
.state() == "expected_timeout") {
284 return test_case_result("failed", -1, broken_reason
);
286 } catch (const std::runtime_error
&) {
287 return test_case_result("failed", -1, broken_reason
);
292 test_case_result tcr
;
295 tcr
= read_test_case_result(resfile
);
296 } catch (const std::runtime_error
& e
) {
297 return test_case_result("failed", -1, "Test case exited "
298 "normally but failed to create the results file: " +
299 std::string(e
.what()));
302 if (tcr
.state() == "expected_death") {
304 } else if (tcr
.state() == "expected_exit") {
305 if (tcr
.value() == -1 || s
.exitstatus() == tcr
.value())
308 return test_case_result("failed", -1, "Test case was "
309 "expected to exit with a " + to_string(tcr
.value()) +
310 " error code but returned " + to_string(s
.exitstatus()));
311 } else if (tcr
.state() == "expected_failure") {
312 if (s
.exitstatus() == EXIT_SUCCESS
)
315 return test_case_result("failed", -1, "Test case returned an "
316 "error in expected_failure mode but it should not have");
317 } else if (tcr
.state() == "expected_signal") {
318 return test_case_result("failed", -1, "Test case exited cleanly "
319 "but was expected to receive a signal");
320 } else if (tcr
.state() == "failed") {
321 if (s
.exitstatus() == EXIT_SUCCESS
)
322 return test_case_result("failed", -1, "Test case "
323 "exited successfully but reported failure");
326 } else if (tcr
.state() == "passed") {
327 if (s
.exitstatus() == EXIT_SUCCESS
)
330 return test_case_result("failed", -1, "Test case exited as "
331 "passed but reported an error");
332 } else if (tcr
.state() == "skipped") {
333 if (s
.exitstatus() == EXIT_SUCCESS
)
336 return test_case_result("failed", -1, "Test case exited as "
337 "skipped but reported an error");
339 } else if (s
.signaled()) {
340 test_case_result tcr
;
343 tcr
= read_test_case_result(resfile
);
344 } catch (const std::runtime_error
&) {
345 return test_case_result("failed", -1, "Test program received "
346 "signal " + atf::text::to_string(s
.termsig()) +
347 (s
.coredump() ? " (core dumped)" : ""));
350 if (tcr
.state() == "expected_death") {
352 } else if (tcr
.state() == "expected_signal") {
353 if (tcr
.value() == -1 || s
.termsig() == tcr
.value())
356 return test_case_result("failed", -1, "Test case was "
357 "expected to exit due to a " + to_string(tcr
.value()) +
358 " signal but got " + to_string(s
.termsig()));
360 return test_case_result("failed", -1, "Test program received "
361 "signal " + atf::text::to_string(s
.termsig()) +
362 (s
.coredump() ? " (core dumped)" : "") + " and created a "
363 "bogus results file");
367 return test_case_result();
371 atf_run::run_test_program(const atf::fs::path
& tp
,
372 impl::atf_tps_writer
& w
,
373 const atf::tests::vars_map
& config
)
375 int errcode
= EXIT_SUCCESS
;
379 md
= impl::get_metadata(tp
, config
);
380 } catch (const atf::parser::format_error
& e
) {
381 w
.start_tp(tp
.str(), 0);
382 w
.end_tp("Invalid format for test case list: " + std::string(e
.what()));
384 } catch (const atf::parser::parse_errors
& e
) {
385 const std::string reason
= atf::text::join(e
, "; ");
386 w
.start_tp(tp
.str(), 0);
387 w
.end_tp("Invalid format for test case list: " + reason
);
391 impl::temp_dir
resdir(atf::fs::path(atf::config::get("atf_workdir")) /
394 w
.start_tp(tp
.str(), md
.test_cases
.size());
395 if (md
.test_cases
.empty()) {
396 w
.end_tp("Bogus test program: reported 0 test cases");
397 errcode
= EXIT_FAILURE
;
399 for (std::map
< std::string
, atf::tests::vars_map
>::const_iterator iter
400 = md
.test_cases
.begin(); iter
!= md
.test_cases
.end(); iter
++) {
401 const std::string
& tcname
= (*iter
).first
;
402 const atf::tests::vars_map
& tcmd
= (*iter
).second
;
407 const std::string
& reqfail
= impl::check_requirements(
409 if (!reqfail
.empty()) {
410 w
.end_tc("skipped", reqfail
);
413 } catch (const std::runtime_error
& e
) {
414 w
.end_tc("failed", e
.what());
415 errcode
= EXIT_FAILURE
;
419 const std::pair
< int, int > user
= impl::get_required_user(
422 atf::fs::path resfile
= resdir
.get_path() / "tcr";
423 INV(!atf::fs::exists(resfile
));
425 const bool has_cleanup
= atf::text::to_bool(
426 (*tcmd
.find("has.cleanup")).second
);
428 impl::temp_dir
workdir(atf::fs::path(atf::config::get(
429 "atf_workdir")) / "atf-run.XXXXXX");
430 if (user
.first
!= -1 && user
.second
!= -1) {
431 if (::chown(workdir
.get_path().c_str(), user
.first
,
432 user
.second
) == -1) {
433 throw atf::system_error("chown(" +
434 workdir
.get_path().str() + ")", "chown(2) failed",
437 resfile
= workdir
.get_path() / "tcr";
440 std::pair
< std::string
, const atf::process::status
> s
=
441 impl::run_test_case(tp
, tcname
, "body", tcmd
, config
,
442 resfile
, workdir
.get_path(), w
);
443 if (s
.second
.signaled() && s
.second
.coredump())
444 dump_stacktrace(tp
, s
.second
, workdir
.get_path(), w
);
446 (void)impl::run_test_case(tp
, tcname
, "cleanup", tcmd
,
447 config
, resfile
, workdir
.get_path(), w
);
449 // TODO: Force deletion of workdir.
451 impl::test_case_result tcr
= get_test_case_result(s
.first
,
454 w
.end_tc(tcr
.state(), tcr
.reason());
455 if (tcr
.state() == "failed")
456 errcode
= EXIT_FAILURE
;
458 if (atf::fs::exists(resfile
))
459 atf::fs::remove(resfile
);
462 if (atf::fs::exists(resfile
))
463 atf::fs::remove(resfile
);
473 atf_run::count_tps(std::vector
< std::string
> tps
)
478 for (std::vector
< std::string
>::const_iterator iter
= tps
.begin();
479 iter
!= tps
.end(); iter
++) {
480 atf::fs::path
tp(*iter
);
481 atf::fs::file_info
fi(tp
);
483 if (fi
.get_type() == atf::fs::file_info::dir_type
) {
484 impl::atffile af
= impl::read_atffile(tp
/ "Atffile");
485 std::vector
< std::string
> aux
= af
.tps();
486 for (std::vector
< std::string
>::iterator i2
= aux
.begin();
487 i2
!= aux
.end(); i2
++)
488 *i2
= (tp
/ *i2
).str();
489 ntps
+= count_tps(aux
);
499 call_hook(const std::string
& tool
, const std::string
& hook
)
501 const atf::fs::path
sh(atf::config::get("atf_shell"));
502 const atf::fs::path hooks
=
503 atf::fs::path(atf::config::get("atf_pkgdatadir")) / (tool
+ ".hooks");
505 const atf::process::status s
=
506 atf::process::exec(sh
,
507 atf::process::argv_array(sh
.c_str(), hooks
.c_str(),
509 atf::process::stream_inherit(),
510 atf::process::stream_inherit());
513 if (!s
.exited() || s
.exitstatus() != EXIT_SUCCESS
)
514 throw std::runtime_error("Failed to run the '" + hook
+ "' hook "
515 "for '" + tool
+ "'");
521 impl::atffile af
= impl::read_atffile(atf::fs::path("Atffile"));
523 std::vector
< std::string
> tps
;
526 // TODO: Ensure that the given test names are listed in the
527 // Atffile. Take into account that the file can be using globs.
529 for (int i
= 0; i
< m_argc
; i
++)
530 tps
.push_back(m_argv
[i
]);
533 // Read configuration data for this test suite.
534 atf::tests::vars_map test_suite_vars
;
536 atf::tests::vars_map::const_iterator iter
=
537 af
.props().find("test-suite");
538 INV(iter
!= af
.props().end());
539 test_suite_vars
= impl::read_config_files((*iter
).second
);
542 impl::atf_tps_writer
w(std::cout
);
543 call_hook("atf-run", "info_start_hook");
544 w
.ntps(count_tps(tps
));
547 for (std::vector
< std::string
>::const_iterator iter
= tps
.begin();
548 iter
!= tps
.end(); iter
++) {
549 const bool result
= run_test(atf::fs::path(*iter
), w
,
550 impl::merge_configs(af
.conf(), test_suite_vars
));
551 ok
&= (result
== EXIT_SUCCESS
);
554 call_hook("atf-run", "info_end_hook");
556 return ok
? EXIT_SUCCESS
: EXIT_FAILURE
;
560 main(int argc
, char* const* argv
)
562 return atf_run().run(argc
, argv
);