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. 15 // 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. 28 // 29 30 extern "C" { 31 #include <sys/types.h> 32 #include <sys/param.h> 33 #include <sys/stat.h> 34 #include <sys/wait.h> 35 #include <unistd.h> 36 } 37 38 #include <algorithm> 39 #include <cassert> 40 #include <cerrno> 41 #include <cstdlib> 42 #include <cstring> 43 #include <fstream> 44 #include <iostream> 45 #include <map> 46 #include <string> 47 48 #include "application.hpp" 49 #include "atffile.hpp" 50 #include "config.hpp" 51 #include "config_file.hpp" 52 #include "env.hpp" 53 #include "exceptions.hpp" 54 #include "fs.hpp" 55 #include "parser.hpp" 56 #include "process.hpp" 57 #include "requirements.hpp" 58 #include "test-program.hpp" 59 #include "text.hpp" 60 61 namespace { 62 63 typedef std::map< std::string, std::string > vars_map; 64 65 } // anonymous namespace 66 67 class atf_run : public tools::application::app { 68 static const char* m_description; 69 70 vars_map m_cmdline_vars; 71 72 static vars_map::value_type parse_var(const std::string&); 73 74 void process_option(int, const char*); 75 std::string specific_args(void) const; 76 options_set specific_options(void) const; 77 78 void parse_vflag(const std::string&); 79 80 std::vector< std::string > conf_args(void) const; 81 82 size_t count_tps(std::vector< std::string >) const; 83 84 int run_test(const tools::fs::path&, const std::string &, 85 tools::test_program::atf_tps_writer&, 86 const vars_map&); 87 int run_test_directory(const tools::fs::path&, 88 tools::test_program::atf_tps_writer&); 89 int run_test_program(const tools::fs::path&, 90 const std::string tc, 91 tools::test_program::atf_tps_writer&, 92 const vars_map&); 93 94 tools::test_program::test_case_result get_test_case_result( 95 const std::string&, const tools::process::status&, 96 const tools::fs::path&) const; 97 98 public: 99 atf_run(void); 100 101 int main(void); 102 }; 103 104 static void 105 sanitize_gdb_env(void) 106 { 107 try { 108 tools::env::unset("TERM"); 109 } catch (...) { 110 // Just swallow exceptions here; they cannot propagate into C, which 111 // is where this function is called from, and even if these exceptions 112 // appear they are benign. 113 } 114 } 115 116 static void 117 dump_stacktrace(const tools::fs::path& tp, const tools::process::status& s, 118 const tools::fs::path& workdir, 119 tools::test_program::atf_tps_writer& w) 120 { 121 assert(s.signaled() && s.coredump()); 122 123 w.stderr_tc("Test program crashed; attempting to get stack trace"); 124 125 const tools::fs::path corename = workdir / 126 (tp.leaf_name().substr(0, MAXCOMLEN) + ".core"); 127 if (!tools::fs::exists(corename)) { 128 w.stderr_tc("Expected file " + corename.str() + " not found"); 129 return; 130 } 131 132 const tools::fs::path gdb(GDB); 133 const tools::fs::path gdbout = workdir / "gdb.out"; 134 const tools::process::argv_array args(gdb.leaf_name().c_str(), "-batch", 135 "-q", "-ex", "bt", tp.c_str(), 136 corename.c_str(), NULL); 137 tools::process::status status = tools::process::exec( 138 gdb, args, 139 tools::process::stream_redirect_path(gdbout), 140 tools::process::stream_redirect_path(tools::fs::path("/dev/null")), 141 sanitize_gdb_env); 142 if (!status.exited() || status.exitstatus() != EXIT_SUCCESS) { 143 w.stderr_tc("Execution of " GDB " failed"); 144 return; 145 } 146 147 std::ifstream input(gdbout.c_str()); 148 if (input) { 149 std::string line; 150 while (std::getline(input, line).good()) 151 w.stderr_tc(line); 152 input.close(); 153 } 154 155 w.stderr_tc("Stack trace complete"); 156 } 157 158 const char* atf_run::m_description = 159 "atf-run is a tool that runs tests programs and collects their " 160 "results."; 161 162 atf_run::atf_run(void) : 163 app(m_description, "atf-run(1)", "atf(7)") 164 { 165 } 166 167 void 168 atf_run::process_option(int ch, const char* arg) 169 { 170 switch (ch) { 171 case 'v': 172 parse_vflag(arg); 173 break; 174 175 default: 176 std::abort(); 177 } 178 } 179 180 std::string 181 atf_run::specific_args(void) 182 const 183 { 184 return "[test1 .. testN]"; 185 } 186 187 atf_run::options_set 188 atf_run::specific_options(void) 189 const 190 { 191 using tools::application::option; 192 options_set opts; 193 opts.insert(option('v', "var=value", "Sets the configuration variable " 194 "`var' to `value'; overrides " 195 "values in configuration files")); 196 return opts; 197 } 198 199 void 200 atf_run::parse_vflag(const std::string& str) 201 { 202 if (str.empty()) 203 throw std::runtime_error("-v requires a non-empty argument"); 204 205 std::vector< std::string > ws = tools::text::split(str, "="); 206 if (ws.size() == 1 && str[str.length() - 1] == '=') { 207 m_cmdline_vars[ws[0]] = ""; 208 } else { 209 if (ws.size() != 2) 210 throw std::runtime_error("-v requires an argument of the form " 211 "var=value"); 212 213 m_cmdline_vars[ws[0]] = ws[1]; 214 } 215 } 216 217 int 218 atf_run::run_test(const tools::fs::path& tp, 219 const std::string &tc, 220 tools::test_program::atf_tps_writer& w, 221 const vars_map& config) 222 { 223 tools::fs::file_info fi(tp); 224 225 int errcode; 226 if (fi.get_type() == tools::fs::file_info::dir_type) 227 errcode = run_test_directory(tp, w); 228 else { 229 const vars_map effective_config = 230 tools::config_file::merge_configs(config, m_cmdline_vars); 231 232 errcode = run_test_program(tp, tc, w, effective_config); 233 } 234 return errcode; 235 } 236 237 int 238 atf_run::run_test_directory(const tools::fs::path& tp, 239 tools::test_program::atf_tps_writer& w) 240 { 241 tools::atffile af = tools::read_atffile(tp / "Atffile"); 242 243 vars_map test_suite_vars; 244 { 245 vars_map::const_iterator iter = af.props().find("test-suite"); 246 assert(iter != af.props().end()); 247 test_suite_vars = tools::config_file::read_config_files((*iter).second); 248 } 249 250 bool ok = true; 251 for (std::vector< std::string >::const_iterator iter = af.tps().begin(); 252 iter != af.tps().end(); iter++) { 253 const bool result = run_test(tp / *iter, "", w, 254 tools::config_file::merge_configs(af.conf(), test_suite_vars)); 255 ok &= (result == EXIT_SUCCESS); 256 } 257 258 return ok ? EXIT_SUCCESS : EXIT_FAILURE; 259 } 260 261 tools::test_program::test_case_result 262 atf_run::get_test_case_result(const std::string& broken_reason, 263 const tools::process::status& s, 264 const tools::fs::path& resfile) 265 const 266 { 267 using tools::text::to_string; 268 using tools::test_program::read_test_case_result; 269 using tools::test_program::test_case_result; 270 271 if (!broken_reason.empty()) { 272 test_case_result tcr; 273 274 try { 275 tcr = read_test_case_result(resfile); 276 277 if (tcr.state() == "expected_timeout") { 278 return tcr; 279 } else { 280 return test_case_result("failed", -1, broken_reason); 281 } 282 } catch (const std::runtime_error&) { 283 return test_case_result("failed", -1, broken_reason); 284 } 285 } 286 287 if (s.exited()) { 288 test_case_result tcr; 289 290 try { 291 tcr = read_test_case_result(resfile); 292 } catch (const std::runtime_error& e) { 293 return test_case_result("failed", -1, "Test case exited " 294 "normally but failed to create the results file: " + 295 std::string(e.what())); 296 } 297 298 if (tcr.state() == "expected_death") { 299 return tcr; 300 } else if (tcr.state() == "expected_exit") { 301 if (tcr.value() == -1 || s.exitstatus() == tcr.value()) 302 return tcr; 303 else 304 return test_case_result("failed", -1, "Test case was " 305 "expected to exit with a " + to_string(tcr.value()) + 306 " error code but returned " + to_string(s.exitstatus())); 307 } else if (tcr.state() == "expected_failure") { 308 if (s.exitstatus() == EXIT_SUCCESS) 309 return tcr; 310 else 311 return test_case_result("failed", -1, "Test case returned an " 312 "error in expected_failure mode but it should not have"); 313 } else if (tcr.state() == "expected_signal") { 314 return test_case_result("failed", -1, "Test case exited cleanly " 315 "but was expected to receive a signal"); 316 } else if (tcr.state() == "failed") { 317 if (s.exitstatus() == EXIT_SUCCESS) 318 return test_case_result("failed", -1, "Test case " 319 "exited successfully but reported failure"); 320 else 321 return tcr; 322 } else if (tcr.state() == "passed") { 323 if (s.exitstatus() == EXIT_SUCCESS) 324 return tcr; 325 else 326 return test_case_result("failed", -1, "Test case exited as " 327 "passed but reported an error"); 328 } else if (tcr.state() == "skipped") { 329 if (s.exitstatus() == EXIT_SUCCESS) 330 return tcr; 331 else 332 return test_case_result("failed", -1, "Test case exited as " 333 "skipped but reported an error"); 334 } 335 } else if (s.signaled()) { 336 test_case_result tcr; 337 338 try { 339 tcr = read_test_case_result(resfile); 340 } catch (const std::runtime_error&) { 341 return test_case_result("failed", -1, "Test program received " 342 "signal " + tools::text::to_string(s.termsig()) + 343 (s.coredump() ? " (core dumped)" : "")); 344 } 345 346 if (tcr.state() == "expected_death") { 347 return tcr; 348 } else if (tcr.state() == "expected_signal") { 349 if (tcr.value() == -1 || s.termsig() == tcr.value()) 350 return tcr; 351 else 352 return test_case_result("failed", -1, "Test case was " 353 "expected to exit due to a " + to_string(tcr.value()) + 354 " signal but got " + to_string(s.termsig())); 355 } else { 356 return test_case_result("failed", -1, "Test program received " 357 "signal " + tools::text::to_string(s.termsig()) + 358 (s.coredump() ? " (core dumped)" : "") + " and created a " 359 "bogus results file"); 360 } 361 } 362 std::abort(); 363 return test_case_result(); 364 } 365 366 int 367 atf_run::run_test_program(const tools::fs::path& tp, 368 const std::string tc, 369 tools::test_program::atf_tps_writer& w, 370 const vars_map& config) 371 { 372 int errcode = EXIT_SUCCESS; 373 374 tools::test_program::metadata md; 375 try { 376 md = tools::test_program::get_metadata(tp, config); 377 } catch (const tools::parser::format_error& e) { 378 w.start_tp(tp.str(), 0); 379 w.end_tp("Invalid format for test case list: " + std::string(e.what())); 380 return EXIT_FAILURE; 381 } catch (const tools::parser::parse_errors& e) { 382 const std::string reason = tools::text::join(e, "; "); 383 w.start_tp(tp.str(), 0); 384 w.end_tp("Invalid format for test case list: " + reason); 385 return EXIT_FAILURE; 386 } 387 388 tools::fs::temp_dir resdir( 389 tools::fs::path(tools::config::get("atf_workdir")) / "atf-run.XXXXXX"); 390 391 size_t nseltcs; 392 if (tc.empty()) { 393 nseltcs = md.test_cases.size(); 394 } else { 395 nseltcs = 0; 396 for (std::map< std::string, vars_map >::const_iterator iter 397 = md.test_cases.begin(); iter != md.test_cases.end(); iter++) { 398 const std::string& tcname = (*iter).first; 399 if (tcname == tc) 400 nseltcs++; 401 } 402 if (nseltcs == 0) 403 throw std::runtime_error("No such test case"); 404 } 405 406 w.start_tp(tp.str(), nseltcs); 407 if (md.test_cases.empty()) { 408 w.end_tp("Bogus test program: reported 0 test cases"); 409 errcode = EXIT_FAILURE; 410 } else { 411 for (std::map< std::string, vars_map >::const_iterator iter 412 = md.test_cases.begin(); iter != md.test_cases.end(); iter++) { 413 const std::string& tcname = (*iter).first; 414 const vars_map& tcmd = (*iter).second; 415 416 if (! tc.empty() && tcname != tc) 417 continue; 418 419 w.start_tc(tcname); 420 421 try { 422 const std::string& reqfail = tools::check_requirements( 423 tcmd, config); 424 if (!reqfail.empty()) { 425 w.end_tc("skipped", reqfail); 426 continue; 427 } 428 } catch (const std::runtime_error& e) { 429 w.end_tc("failed", e.what()); 430 errcode = EXIT_FAILURE; 431 continue; 432 } 433 434 const std::pair< int, int > user = tools::get_required_user( 435 tcmd, config); 436 437 tools::fs::path resfile = resdir.get_path() / "tcr"; 438 assert(!tools::fs::exists(resfile)); 439 try { 440 const bool has_cleanup = tools::text::to_bool( 441 (*tcmd.find("has.cleanup")).second); 442 443 tools::fs::temp_dir workdir(tools::fs::path(tools::config::get( 444 "atf_workdir")) / "atf-run.XXXXXX"); 445 if (user.first != -1 && user.second != -1) { 446 if (::chown(workdir.get_path().c_str(), user.first, 447 user.second) == -1) { 448 throw tools::system_error("chown(" + 449 workdir.get_path().str() + ")", "chown(2) failed", 450 errno); 451 } 452 resfile = workdir.get_path() / "tcr"; 453 } 454 455 std::pair< std::string, const tools::process::status > s = 456 tools::test_program::run_test_case( 457 tp, tcname, "body", tcmd, config, 458 resfile, workdir.get_path(), w); 459 if (s.second.signaled() && s.second.coredump()) 460 dump_stacktrace(tp, s.second, workdir.get_path(), w); 461 if (has_cleanup) 462 (void)tools::test_program::run_test_case( 463 tp, tcname, "cleanup", tcmd, 464 config, resfile, workdir.get_path(), w); 465 466 // TODO: Force deletion of workdir. 467 468 tools::test_program::test_case_result tcr = 469 get_test_case_result(s.first, s.second, resfile); 470 471 w.end_tc(tcr.state(), tcr.reason()); 472 if (tcr.state() == "failed") 473 errcode = EXIT_FAILURE; 474 } catch (...) { 475 if (tools::fs::exists(resfile)) 476 tools::fs::remove(resfile); 477 throw; 478 } 479 if (tools::fs::exists(resfile)) 480 tools::fs::remove(resfile); 481 482 } 483 w.end_tp(""); 484 } 485 486 return errcode; 487 } 488 489 static void 490 colon_split(const std::string &s, std::string &tp, std::string &tc) 491 { 492 size_t colon_pos = s.rfind(':'); 493 if (colon_pos != std::string::npos && colon_pos < s.size() - 1) { 494 tp = s.substr(0, colon_pos); 495 tc = s.substr(colon_pos + 1); 496 } else { 497 tp = s; 498 tc = ""; 499 } 500 } 501 502 size_t 503 atf_run::count_tps(std::vector< std::string > tps) 504 const 505 { 506 size_t ntps = 0; 507 508 for (std::vector< std::string >::const_iterator iter = tps.begin(); 509 iter != tps.end(); iter++) { 510 std::string tpname, tcname; 511 colon_split(*iter, tpname, tcname); 512 tools::fs::path tp(tpname); 513 tools::fs::file_info fi(tp); 514 515 if (fi.get_type() == tools::fs::file_info::dir_type) { 516 tools::atffile af = tools::read_atffile(tp / "Atffile"); 517 std::vector< std::string > aux = af.tps(); 518 for (std::vector< std::string >::iterator i2 = aux.begin(); 519 i2 != aux.end(); i2++) 520 *i2 = (tp / *i2).str(); 521 ntps += count_tps(aux); 522 } else 523 ntps++; 524 } 525 526 return ntps; 527 } 528 529 static 530 void 531 call_hook(const std::string& tool, const std::string& hook) 532 { 533 const tools::fs::path sh(tools::config::get("atf_shell")); 534 const tools::fs::path hooks = 535 tools::fs::path(tools::config::get("atf_pkgdatadir")) / (tool + ".hooks"); 536 537 const tools::process::status s = 538 tools::process::exec(sh, 539 tools::process::argv_array(sh.c_str(), hooks.c_str(), 540 hook.c_str(), NULL), 541 tools::process::stream_inherit(), 542 tools::process::stream_inherit()); 543 544 545 if (!s.exited() || s.exitstatus() != EXIT_SUCCESS) 546 throw std::runtime_error("Failed to run the '" + hook + "' hook " 547 "for '" + tool + "'"); 548 } 549 550 int 551 atf_run::main(void) 552 { 553 tools::atffile af = tools::read_atffile(tools::fs::path("Atffile")); 554 555 std::vector< std::string > tps; 556 tps = af.tps(); 557 if (m_argc >= 1) { 558 // TODO: Ensure that the given test names are listed in the 559 // Atffile. Take into account that the file can be using globs. 560 tps.clear(); 561 for (int i = 0; i < m_argc; i++) 562 tps.push_back(m_argv[i]); 563 } 564 565 // Read configuration data for this test suite. 566 vars_map test_suite_vars; 567 { 568 vars_map::const_iterator iter = af.props().find("test-suite"); 569 assert(iter != af.props().end()); 570 test_suite_vars = tools::config_file::read_config_files((*iter).second); 571 } 572 573 tools::test_program::atf_tps_writer w(std::cout); 574 call_hook("atf-run", "info_start_hook"); 575 w.ntps(count_tps(tps)); 576 577 bool ok = true; 578 for (std::vector< std::string >::const_iterator iter = tps.begin(); 579 iter != tps.end(); iter++) { 580 std::string tp, tc; 581 colon_split(*iter, tp, tc); 582 const bool result = run_test(tools::fs::path(tp), tc, w, 583 tools::config_file::merge_configs(af.conf(), test_suite_vars)); 584 ok &= (result == EXIT_SUCCESS); 585 } 586 587 call_hook("atf-run", "info_end_hook"); 588 589 return ok ? EXIT_SUCCESS : EXIT_FAILURE; 590 } 591 592 int 593 main(int argc, char* const* argv) 594 { 595 return atf_run().run(argc, argv); 596 } 597