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.
31 #include <sys/types.h>
32 #include <sys/param.h>
48 #include "application.hpp"
49 #include "atffile.hpp"
51 #include "config_file.hpp"
53 #include "exceptions.hpp"
56 #include "process.hpp"
57 #include "requirements.hpp"
58 #include "test-program.hpp"
63 typedef std::map
< std::string
, std::string
> vars_map
;
65 } // anonymous namespace
67 class atf_run
: public tools::application::app
{
68 static const char* m_description
;
70 vars_map m_cmdline_vars
;
72 static vars_map::value_type
parse_var(const std::string
&);
74 void process_option(int, const char*);
75 std::string
specific_args(void) const;
76 options_set
specific_options(void) const;
78 void parse_vflag(const std::string
&);
80 std::vector
< std::string
> conf_args(void) const;
82 size_t count_tps(std::vector
< std::string
>) const;
84 int run_test(const tools::fs::path
&, tools::test_program::atf_tps_writer
&,
86 int run_test_directory(const tools::fs::path
&,
87 tools::test_program::atf_tps_writer
&);
88 int run_test_program(const tools::fs::path
&,
89 tools::test_program::atf_tps_writer
&,
92 tools::test_program::test_case_result
get_test_case_result(
93 const std::string
&, const tools::process::status
&,
94 const tools::fs::path
&) const;
103 sanitize_gdb_env(void)
106 tools::env::unset("TERM");
108 // Just swallow exceptions here; they cannot propagate into C, which
109 // is where this function is called from, and even if these exceptions
110 // appear they are benign.
115 dump_stacktrace(const tools::fs::path
& tp
, const tools::process::status
& s
,
116 const tools::fs::path
& workdir
,
117 tools::test_program::atf_tps_writer
& w
)
119 assert(s
.signaled() && s
.coredump());
121 w
.stderr_tc("Test program crashed; attempting to get stack trace");
123 const tools::fs::path corename
= workdir
/
124 (tp
.leaf_name().substr(0, MAXCOMLEN
) + ".core");
125 if (!tools::fs::exists(corename
)) {
126 w
.stderr_tc("Expected file " + corename
.str() + " not found");
130 const tools::fs::path
gdb(GDB
);
131 const tools::fs::path gdbout
= workdir
/ "gdb.out";
132 const tools::process::argv_array
args(gdb
.leaf_name().c_str(), "-batch",
133 "-q", "-ex", "bt", tp
.c_str(),
134 corename
.c_str(), NULL
);
135 tools::process::status status
= tools::process::exec(
137 tools::process::stream_redirect_path(gdbout
),
138 tools::process::stream_redirect_path(tools::fs::path("/dev/null")),
140 if (!status
.exited() || status
.exitstatus() != EXIT_SUCCESS
) {
141 w
.stderr_tc("Execution of " GDB
" failed");
145 std::ifstream
input(gdbout
.c_str());
148 while (std::getline(input
, line
).good())
153 w
.stderr_tc("Stack trace complete");
156 const char* atf_run::m_description
=
157 "atf-run is a tool that runs tests programs and collects their "
160 atf_run::atf_run(void) :
161 app(m_description
, "atf-run(1)", "atf(7)")
166 atf_run::process_option(int ch
, const char* arg
)
179 atf_run::specific_args(void)
182 return "[test-program1 .. test-programN]";
186 atf_run::specific_options(void)
189 using tools::application::option
;
191 opts
.insert(option('v', "var=value", "Sets the configuration variable "
192 "`var' to `value'; overrides "
193 "values in configuration files"));
198 atf_run::parse_vflag(const std::string
& str
)
201 throw std::runtime_error("-v requires a non-empty argument");
203 std::vector
< std::string
> ws
= tools::text::split(str
, "=");
204 if (ws
.size() == 1 && str
[str
.length() - 1] == '=') {
205 m_cmdline_vars
[ws
[0]] = "";
208 throw std::runtime_error("-v requires an argument of the form "
211 m_cmdline_vars
[ws
[0]] = ws
[1];
216 atf_run::run_test(const tools::fs::path
& tp
,
217 tools::test_program::atf_tps_writer
& w
,
218 const vars_map
& config
)
220 tools::fs::file_info
fi(tp
);
223 if (fi
.get_type() == tools::fs::file_info::dir_type
)
224 errcode
= run_test_directory(tp
, w
);
226 const vars_map effective_config
=
227 tools::config_file::merge_configs(config
, m_cmdline_vars
);
229 errcode
= run_test_program(tp
, w
, effective_config
);
235 atf_run::run_test_directory(const tools::fs::path
& tp
,
236 tools::test_program::atf_tps_writer
& w
)
238 tools::atffile af
= tools::read_atffile(tp
/ "Atffile");
240 vars_map test_suite_vars
;
242 vars_map::const_iterator iter
= af
.props().find("test-suite");
243 assert(iter
!= af
.props().end());
244 test_suite_vars
= tools::config_file::read_config_files((*iter
).second
);
248 for (std::vector
< std::string
>::const_iterator iter
= af
.tps().begin();
249 iter
!= af
.tps().end(); iter
++) {
250 const bool result
= run_test(tp
/ *iter
, w
,
251 tools::config_file::merge_configs(af
.conf(), test_suite_vars
));
252 ok
&= (result
== EXIT_SUCCESS
);
255 return ok
? EXIT_SUCCESS
: EXIT_FAILURE
;
258 tools::test_program::test_case_result
259 atf_run::get_test_case_result(const std::string
& broken_reason
,
260 const tools::process::status
& s
,
261 const tools::fs::path
& resfile
)
264 using tools::text::to_string
;
265 using tools::test_program::read_test_case_result
;
266 using tools::test_program::test_case_result
;
268 if (!broken_reason
.empty()) {
269 test_case_result tcr
;
272 tcr
= read_test_case_result(resfile
);
274 if (tcr
.state() == "expected_timeout") {
277 return test_case_result("failed", -1, broken_reason
);
279 } catch (const std::runtime_error
&) {
280 return test_case_result("failed", -1, broken_reason
);
285 test_case_result tcr
;
288 tcr
= read_test_case_result(resfile
);
289 } catch (const std::runtime_error
& e
) {
290 return test_case_result("failed", -1, "Test case exited "
291 "normally but failed to create the results file: " +
292 std::string(e
.what()));
295 if (tcr
.state() == "expected_death") {
297 } else if (tcr
.state() == "expected_exit") {
298 if (tcr
.value() == -1 || s
.exitstatus() == tcr
.value())
301 return test_case_result("failed", -1, "Test case was "
302 "expected to exit with a " + to_string(tcr
.value()) +
303 " error code but returned " + to_string(s
.exitstatus()));
304 } else if (tcr
.state() == "expected_failure") {
305 if (s
.exitstatus() == EXIT_SUCCESS
)
308 return test_case_result("failed", -1, "Test case returned an "
309 "error in expected_failure mode but it should not have");
310 } else if (tcr
.state() == "expected_signal") {
311 return test_case_result("failed", -1, "Test case exited cleanly "
312 "but was expected to receive a signal");
313 } else if (tcr
.state() == "failed") {
314 if (s
.exitstatus() == EXIT_SUCCESS
)
315 return test_case_result("failed", -1, "Test case "
316 "exited successfully but reported failure");
319 } else if (tcr
.state() == "passed") {
320 if (s
.exitstatus() == EXIT_SUCCESS
)
323 return test_case_result("failed", -1, "Test case exited as "
324 "passed but reported an error");
325 } else if (tcr
.state() == "skipped") {
326 if (s
.exitstatus() == EXIT_SUCCESS
)
329 return test_case_result("failed", -1, "Test case exited as "
330 "skipped but reported an error");
332 } else if (s
.signaled()) {
333 test_case_result tcr
;
336 tcr
= read_test_case_result(resfile
);
337 } catch (const std::runtime_error
&) {
338 return test_case_result("failed", -1, "Test program received "
339 "signal " + tools::text::to_string(s
.termsig()) +
340 (s
.coredump() ? " (core dumped)" : ""));
343 if (tcr
.state() == "expected_death") {
345 } else if (tcr
.state() == "expected_signal") {
346 if (tcr
.value() == -1 || s
.termsig() == tcr
.value())
349 return test_case_result("failed", -1, "Test case was "
350 "expected to exit due to a " + to_string(tcr
.value()) +
351 " signal but got " + to_string(s
.termsig()));
353 return test_case_result("failed", -1, "Test program received "
354 "signal " + tools::text::to_string(s
.termsig()) +
355 (s
.coredump() ? " (core dumped)" : "") + " and created a "
356 "bogus results file");
360 return test_case_result();
364 atf_run::run_test_program(const tools::fs::path
& tp
,
365 tools::test_program::atf_tps_writer
& w
,
366 const vars_map
& config
)
368 int errcode
= EXIT_SUCCESS
;
370 tools::test_program::metadata md
;
372 md
= tools::test_program::get_metadata(tp
, config
);
373 } catch (const tools::parser::format_error
& e
) {
374 w
.start_tp(tp
.str(), 0);
375 w
.end_tp("Invalid format for test case list: " + std::string(e
.what()));
377 } catch (const tools::parser::parse_errors
& e
) {
378 const std::string reason
= tools::text::join(e
, "; ");
379 w
.start_tp(tp
.str(), 0);
380 w
.end_tp("Invalid format for test case list: " + reason
);
384 tools::fs::temp_dir
resdir(
385 tools::fs::path(tools::config::get("atf_workdir")) / "atf-run.XXXXXX");
387 w
.start_tp(tp
.str(), md
.test_cases
.size());
388 if (md
.test_cases
.empty()) {
389 w
.end_tp("Bogus test program: reported 0 test cases");
390 errcode
= EXIT_FAILURE
;
392 for (std::map
< std::string
, vars_map
>::const_iterator iter
393 = md
.test_cases
.begin(); iter
!= md
.test_cases
.end(); iter
++) {
394 const std::string
& tcname
= (*iter
).first
;
395 const vars_map
& tcmd
= (*iter
).second
;
400 const std::string
& reqfail
= tools::check_requirements(
402 if (!reqfail
.empty()) {
403 w
.end_tc("skipped", reqfail
);
406 } catch (const std::runtime_error
& e
) {
407 w
.end_tc("failed", e
.what());
408 errcode
= EXIT_FAILURE
;
412 const std::pair
< int, int > user
= tools::get_required_user(
415 tools::fs::path resfile
= resdir
.get_path() / "tcr";
416 assert(!tools::fs::exists(resfile
));
418 const bool has_cleanup
= tools::text::to_bool(
419 (*tcmd
.find("has.cleanup")).second
);
421 tools::fs::temp_dir
workdir(tools::fs::path(tools::config::get(
422 "atf_workdir")) / "atf-run.XXXXXX");
423 if (user
.first
!= -1 && user
.second
!= -1) {
424 if (::chown(workdir
.get_path().c_str(), user
.first
,
425 user
.second
) == -1) {
426 throw tools::system_error("chown(" +
427 workdir
.get_path().str() + ")", "chown(2) failed",
430 resfile
= workdir
.get_path() / "tcr";
433 std::pair
< std::string
, const tools::process::status
> s
=
434 tools::test_program::run_test_case(
435 tp
, tcname
, "body", tcmd
, config
,
436 resfile
, workdir
.get_path(), w
);
437 if (s
.second
.signaled() && s
.second
.coredump())
438 dump_stacktrace(tp
, s
.second
, workdir
.get_path(), w
);
440 (void)tools::test_program::run_test_case(
441 tp
, tcname
, "cleanup", tcmd
,
442 config
, resfile
, workdir
.get_path(), w
);
444 // TODO: Force deletion of workdir.
446 tools::test_program::test_case_result tcr
=
447 get_test_case_result(s
.first
, s
.second
, resfile
);
449 w
.end_tc(tcr
.state(), tcr
.reason());
450 if (tcr
.state() == "failed")
451 errcode
= EXIT_FAILURE
;
453 if (tools::fs::exists(resfile
))
454 tools::fs::remove(resfile
);
457 if (tools::fs::exists(resfile
))
458 tools::fs::remove(resfile
);
468 atf_run::count_tps(std::vector
< std::string
> tps
)
473 for (std::vector
< std::string
>::const_iterator iter
= tps
.begin();
474 iter
!= tps
.end(); iter
++) {
475 tools::fs::path
tp(*iter
);
476 tools::fs::file_info
fi(tp
);
478 if (fi
.get_type() == tools::fs::file_info::dir_type
) {
479 tools::atffile af
= tools::read_atffile(tp
/ "Atffile");
480 std::vector
< std::string
> aux
= af
.tps();
481 for (std::vector
< std::string
>::iterator i2
= aux
.begin();
482 i2
!= aux
.end(); i2
++)
483 *i2
= (tp
/ *i2
).str();
484 ntps
+= count_tps(aux
);
494 call_hook(const std::string
& tool
, const std::string
& hook
)
496 const tools::fs::path
sh(tools::config::get("atf_shell"));
497 const tools::fs::path hooks
=
498 tools::fs::path(tools::config::get("atf_pkgdatadir")) / (tool
+ ".hooks");
500 const tools::process::status s
=
501 tools::process::exec(sh
,
502 tools::process::argv_array(sh
.c_str(), hooks
.c_str(),
504 tools::process::stream_inherit(),
505 tools::process::stream_inherit());
508 if (!s
.exited() || s
.exitstatus() != EXIT_SUCCESS
)
509 throw std::runtime_error("Failed to run the '" + hook
+ "' hook "
510 "for '" + tool
+ "'");
516 tools::atffile af
= tools::read_atffile(tools::fs::path("Atffile"));
518 std::vector
< std::string
> tps
;
521 // TODO: Ensure that the given test names are listed in the
522 // Atffile. Take into account that the file can be using globs.
524 for (int i
= 0; i
< m_argc
; i
++)
525 tps
.push_back(m_argv
[i
]);
528 // Read configuration data for this test suite.
529 vars_map test_suite_vars
;
531 vars_map::const_iterator iter
= af
.props().find("test-suite");
532 assert(iter
!= af
.props().end());
533 test_suite_vars
= tools::config_file::read_config_files((*iter
).second
);
536 tools::test_program::atf_tps_writer
w(std::cout
);
537 call_hook("atf-run", "info_start_hook");
538 w
.ntps(count_tps(tps
));
541 for (std::vector
< std::string
>::const_iterator iter
= tps
.begin();
542 iter
!= tps
.end(); iter
++) {
543 const bool result
= run_test(tools::fs::path(*iter
), w
,
544 tools::config_file::merge_configs(af
.conf(), test_suite_vars
));
545 ok
&= (result
== EXIT_SUCCESS
);
548 call_hook("atf-run", "info_end_hook");
550 return ok
? EXIT_SUCCESS
: EXIT_FAILURE
;
554 main(int argc
, char* const* argv
)
556 return atf_run().run(argc
, argv
);