Remove building with NOCRYPTO option
[minix.git] / external / bsd / bind / dist / unit / atf-src / atf-run / atf-run.cpp
blobe28e8fdf0778d87aca1f96c841abd8f9e90e28aa
1 //
2 // Automated Testing Framework (atf)
3 //
4 // Copyright (c) 2007 The NetBSD Foundation, Inc.
5 // All rights reserved.
6 //
7 // Redistribution and use in source and binary forms, with or without
8 // modification, are permitted provided that the following conditions
9 // are met:
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)
31 #include "bconfig.h"
32 #endif
34 extern "C" {
35 #include <sys/types.h>
36 #include <sys/param.h>
37 #include <sys/stat.h>
38 #include <sys/wait.h>
39 #include <unistd.h>
42 #include <algorithm>
43 #include <cerrno>
44 #include <cstdlib>
45 #include <cstring>
46 #include <fstream>
47 #include <iostream>
48 #include <map>
49 #include <string>
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"
64 #include "config.hpp"
65 #include "fs.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;
73 #else
74 static const std::string::size_type max_core_name_length = std::string::npos;
75 #endif
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;
103 public:
104 atf_run(void);
106 int main(void);
109 static void
110 sanitize_gdb_env(void)
112 try {
113 atf::env::unset("TERM");
114 } catch (...) {
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.
121 static void
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");
133 return;
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(
142 gdb, args,
143 atf::process::stream_redirect_path(gdbout),
144 atf::process::stream_redirect_path(atf::fs::path("/dev/null")),
145 sanitize_gdb_env);
146 if (!status.exited() || status.exitstatus() != EXIT_SUCCESS) {
147 w.stderr_tc("Execution of " GDB " failed");
148 return;
151 std::ifstream input(gdbout.c_str());
152 if (input) {
153 std::string line;
154 while (std::getline(input, line).good())
155 w.stderr_tc(line);
156 input.close();
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 "
164 "results.";
166 atf_run::atf_run(void) :
167 app(m_description, "atf-run(1)", "atf(7)")
171 void
172 atf_run::process_option(int ch, const char* arg)
174 switch (ch) {
175 case 'v':
176 parse_vflag(arg);
177 break;
179 default:
180 UNREACHABLE;
184 std::string
185 atf_run::specific_args(void)
186 const
188 return "[test-program1 .. test-programN]";
191 atf_run::options_set
192 atf_run::specific_options(void)
193 const
195 using atf::application::option;
196 options_set opts;
197 opts.insert(option('v', "var=value", "Sets the configuration variable "
198 "`var' to `value'; overrides "
199 "values in configuration files"));
200 return opts;
203 void
204 atf_run::parse_vflag(const std::string& str)
206 if (str.empty())
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]] = "";
212 } else {
213 if (ws.size() != 2)
214 throw std::runtime_error("-v requires an argument of the form "
215 "var=value");
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);
228 int errcode;
229 if (fi.get_type() == atf::fs::file_info::dir_type)
230 errcode = run_test_directory(tp, w);
231 else {
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);
237 return errcode;
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);
254 bool ok = true;
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)
269 const
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;
278 try {
279 tcr = read_test_case_result(resfile);
281 if (tcr.state() == "expected_timeout") {
282 return tcr;
283 } else {
284 return test_case_result("failed", -1, broken_reason);
286 } catch (const std::runtime_error&) {
287 return test_case_result("failed", -1, broken_reason);
291 if (s.exited()) {
292 test_case_result tcr;
294 try {
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") {
303 return tcr;
304 } else if (tcr.state() == "expected_exit") {
305 if (tcr.value() == -1 || s.exitstatus() == tcr.value())
306 return tcr;
307 else
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)
313 return tcr;
314 else
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");
324 else
325 return tcr;
326 } else if (tcr.state() == "passed") {
327 if (s.exitstatus() == EXIT_SUCCESS)
328 return tcr;
329 else
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)
334 return tcr;
335 else
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;
342 try {
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") {
351 return tcr;
352 } else if (tcr.state() == "expected_signal") {
353 if (tcr.value() == -1 || s.termsig() == tcr.value())
354 return tcr;
355 else
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()));
359 } else {
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");
366 UNREACHABLE;
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;
377 impl::metadata md;
378 try {
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()));
383 return EXIT_FAILURE;
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);
388 return EXIT_FAILURE;
391 impl::temp_dir resdir(atf::fs::path(atf::config::get("atf_workdir")) /
392 "atf-run.XXXXXX");
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;
398 } else {
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;
404 w.start_tc(tcname);
406 try {
407 const std::string& reqfail = impl::check_requirements(
408 tcmd, config);
409 if (!reqfail.empty()) {
410 w.end_tc("skipped", reqfail);
411 continue;
413 } catch (const std::runtime_error& e) {
414 w.end_tc("failed", e.what());
415 errcode = EXIT_FAILURE;
416 continue;
419 const std::pair< int, int > user = impl::get_required_user(
420 tcmd, config);
422 atf::fs::path resfile = resdir.get_path() / "tcr";
423 INV(!atf::fs::exists(resfile));
424 try {
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",
435 errno);
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);
445 if (has_cleanup)
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,
452 s.second, resfile);
454 w.end_tc(tcr.state(), tcr.reason());
455 if (tcr.state() == "failed")
456 errcode = EXIT_FAILURE;
457 } catch (...) {
458 if (atf::fs::exists(resfile))
459 atf::fs::remove(resfile);
460 throw;
462 if (atf::fs::exists(resfile))
463 atf::fs::remove(resfile);
466 w.end_tp("");
469 return errcode;
472 size_t
473 atf_run::count_tps(std::vector< std::string > tps)
474 const
476 size_t ntps = 0;
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);
490 } else
491 ntps++;
494 return ntps;
497 static
498 void
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(),
508 hook.c_str(), NULL),
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 + "'");
519 atf_run::main(void)
521 impl::atffile af = impl::read_atffile(atf::fs::path("Atffile"));
523 std::vector< std::string > tps;
524 tps = af.tps();
525 if (m_argc >= 1) {
526 // TODO: Ensure that the given test names are listed in the
527 // Atffile. Take into account that the file can be using globs.
528 tps.clear();
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));
546 bool ok = true;
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);