1 // 2 // Automated Testing Framework (atf) 3 // 4 // Copyright (c) 2007, 2008 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 #if defined(HAVE_CONFIG_H) 31 #include "bconfig.h" 32 #endif 33 34 extern "C" { 35 #include <sys/types.h> 36 #include <sys/wait.h> 37 #include <unistd.h> 38 } 39 40 #include <cerrno> 41 #include <cstdlib> 42 #include <cstring> 43 #include <fstream> 44 #include <iostream> 45 #include <map> 46 #include <string> 47 48 #include "atf-c++/application.hpp" 49 #include "atf-c++/atffile.hpp" 50 #include "atf-c++/config.hpp" 51 #include "atf-c++/env.hpp" 52 #include "atf-c++/exceptions.hpp" 53 #include "atf-c++/formats.hpp" 54 #include "atf-c++/fs.hpp" 55 #include "atf-c++/io.hpp" 56 #include "atf-c++/parser.hpp" 57 #include "atf-c++/process.hpp" 58 #include "atf-c++/sanity.hpp" 59 #include "atf-c++/tests.hpp" 60 #include "atf-c++/text.hpp" 61 62 class config : public atf::formats::atf_config_reader { 63 atf::tests::vars_map m_vars; 64 65 void 66 got_var(const std::string& var, const std::string& name) 67 { 68 m_vars[var] = name; 69 } 70 71 public: 72 config(std::istream& is) : 73 atf::formats::atf_config_reader(is) 74 { 75 } 76 77 const atf::tests::vars_map& 78 get_vars(void) 79 const 80 { 81 return m_vars; 82 } 83 }; 84 85 class muxer : public atf::formats::atf_tcs_reader { 86 atf::fs::path m_tp; 87 atf::formats::atf_tps_writer m_writer; 88 89 bool m_inited, m_finalized; 90 size_t m_ntcs; 91 std::string m_tcname; 92 93 // Counters for the test cases run by the test program. 94 size_t m_passed, m_failed, m_skipped; 95 96 void 97 got_ntcs(size_t ntcs) 98 { 99 m_writer.start_tp(m_tp.str(), ntcs); 100 m_inited = true; 101 if (ntcs == 0) 102 throw atf::formats::format_error("Bogus test program: reported " 103 "0 test cases"); 104 } 105 106 void 107 got_tc_start(const std::string& tcname) 108 { 109 m_tcname = tcname; 110 m_writer.start_tc(tcname); 111 } 112 113 void 114 got_tc_end(const atf::tests::tcr& tcr) 115 { 116 const atf::tests::tcr::state& s = tcr.get_state(); 117 if (s == atf::tests::tcr::passed_state) { 118 m_passed++; 119 } else if (s == atf::tests::tcr::skipped_state) { 120 m_skipped++; 121 } else if (s == atf::tests::tcr::failed_state) { 122 m_failed++; 123 } else 124 UNREACHABLE; 125 126 m_writer.end_tc(tcr); 127 m_tcname = ""; 128 } 129 130 void 131 got_stdout_line(const std::string& line) 132 { 133 m_writer.stdout_tc(line); 134 } 135 136 void 137 got_stderr_line(const std::string& line) 138 { 139 m_writer.stderr_tc(line); 140 } 141 142 public: 143 muxer(const atf::fs::path& tp, atf::formats::atf_tps_writer& w, 144 atf::io::pistream& is) : 145 atf::formats::atf_tcs_reader(is), 146 m_tp(tp), 147 m_writer(w), 148 m_inited(false), 149 m_finalized(false), 150 m_passed(0), 151 m_failed(0), 152 m_skipped(0) 153 { 154 } 155 156 size_t 157 failed(void) 158 const 159 { 160 return m_failed; 161 } 162 163 void 164 finalize(const std::string& reason = "") 165 { 166 PRE(!m_finalized); 167 168 if (!m_inited) 169 m_writer.start_tp(m_tp.str(), 0); 170 if (!m_tcname.empty()) { 171 INV(!reason.empty()); 172 got_tc_end(atf::tests::tcr(atf::tests::tcr::failed_state, 173 "Bogus test program")); 174 } 175 176 m_writer.end_tp(reason); 177 m_finalized = true; 178 } 179 180 ~muxer(void) 181 { 182 // The following is incorrect because we cannot throw an exception 183 // from a destructor. Let's just hope that this never happens. 184 PRE(m_finalized); 185 } 186 }; 187 188 template< class K, class V > 189 void 190 merge_maps(std::map< K, V >& dest, const std::map< K, V >& src) 191 { 192 for (typename std::map< K, V >::const_iterator iter = src.begin(); 193 iter != src.end(); iter++) 194 dest[(*iter).first] = (*iter).second; 195 } 196 197 class atf_run : public atf::application::app { 198 static const char* m_description; 199 200 atf::tests::vars_map m_atffile_vars; 201 atf::tests::vars_map m_cmdline_vars; 202 atf::tests::vars_map m_config_vars; 203 204 static atf::tests::vars_map::value_type parse_var(const std::string&); 205 206 void process_option(int, const char*); 207 std::string specific_args(void) const; 208 options_set specific_options(void) const; 209 210 void parse_vflag(const std::string&); 211 212 void read_one_config(const atf::fs::path&); 213 void read_config(const std::string&); 214 std::vector< std::string > conf_args(void) const; 215 216 size_t count_tps(std::vector< std::string >) const; 217 218 int run_test(const atf::fs::path&, 219 atf::formats::atf_tps_writer&); 220 int run_test_directory(const atf::fs::path&, 221 atf::formats::atf_tps_writer&); 222 int run_test_program(const atf::fs::path&, 223 atf::formats::atf_tps_writer&); 224 225 void run_test_program_child(const atf::fs::path&, 226 atf::io::pipe&, 227 atf::io::pipe&, 228 atf::io::pipe&); 229 int run_test_program_parent(const atf::fs::path&, 230 atf::formats::atf_tps_writer&, 231 atf::io::pipe&, 232 atf::io::pipe&, 233 atf::io::pipe&, 234 pid_t); 235 236 public: 237 atf_run(void); 238 239 int main(void); 240 }; 241 242 const char* atf_run::m_description = 243 "atf-run is a tool that runs tests programs and collects their " 244 "results."; 245 246 atf_run::atf_run(void) : 247 app(m_description, "atf-run(1)", "atf(7)") 248 { 249 } 250 251 void 252 atf_run::process_option(int ch, const char* arg) 253 { 254 switch (ch) { 255 case 'v': 256 parse_vflag(arg); 257 break; 258 259 default: 260 UNREACHABLE; 261 } 262 } 263 264 std::string 265 atf_run::specific_args(void) 266 const 267 { 268 return "[test-program1 .. test-programN]"; 269 } 270 271 atf_run::options_set 272 atf_run::specific_options(void) 273 const 274 { 275 using atf::application::option; 276 options_set opts; 277 opts.insert(option('v', "var=value", "Sets the configuration variable " 278 "`var' to `value'; overrides " 279 "values in configuration files")); 280 return opts; 281 } 282 283 void 284 atf_run::parse_vflag(const std::string& str) 285 { 286 if (str.empty()) 287 throw std::runtime_error("-v requires a non-empty argument"); 288 289 std::vector< std::string > ws = atf::text::split(str, "="); 290 if (ws.size() == 1 && str[str.length() - 1] == '=') { 291 m_cmdline_vars[ws[0]] = ""; 292 } else { 293 if (ws.size() != 2) 294 throw std::runtime_error("-v requires an argument of the form " 295 "var=value"); 296 297 m_cmdline_vars[ws[0]] = ws[1]; 298 } 299 } 300 301 int 302 atf_run::run_test(const atf::fs::path& tp, 303 atf::formats::atf_tps_writer& w) 304 { 305 atf::fs::file_info fi(tp); 306 307 int errcode; 308 if (fi.get_type() == atf::fs::file_info::dir_type) 309 errcode = run_test_directory(tp, w); 310 else 311 errcode = run_test_program(tp, w); 312 return errcode; 313 } 314 315 int 316 atf_run::run_test_directory(const atf::fs::path& tp, 317 atf::formats::atf_tps_writer& w) 318 { 319 atf::atffile af(tp / "Atffile"); 320 m_atffile_vars = af.conf(); 321 322 atf::tests::vars_map oldvars = m_config_vars; 323 { 324 atf::tests::vars_map::const_iterator iter = 325 af.props().find("test-suite"); 326 INV(iter != af.props().end()); 327 read_config((*iter).second); 328 } 329 330 bool ok = true; 331 for (std::vector< std::string >::const_iterator iter = af.tps().begin(); 332 iter != af.tps().end(); iter++) 333 ok &= (run_test(tp / *iter, w) == EXIT_SUCCESS); 334 335 m_config_vars = oldvars; 336 337 return ok ? EXIT_SUCCESS : EXIT_FAILURE; 338 } 339 340 std::vector< std::string > 341 atf_run::conf_args(void) const 342 { 343 using atf::tests::vars_map; 344 345 atf::tests::vars_map vars; 346 std::vector< std::string > args; 347 348 merge_maps(vars, m_atffile_vars); 349 merge_maps(vars, m_config_vars); 350 merge_maps(vars, m_cmdline_vars); 351 352 for (vars_map::const_iterator i = vars.begin(); i != vars.end(); i++) 353 args.push_back("-v" + (*i).first + "=" + (*i).second); 354 355 return args; 356 } 357 358 void 359 atf_run::run_test_program_child(const atf::fs::path& tp, 360 atf::io::pipe& outpipe, 361 atf::io::pipe& errpipe, 362 atf::io::pipe& respipe) 363 { 364 // Remap stdout and stderr to point to the parent, who will capture 365 // everything sent to these. 366 outpipe.rend().close(); 367 outpipe.wend().posix_remap(STDOUT_FILENO); 368 errpipe.rend().close(); 369 errpipe.wend().posix_remap(STDERR_FILENO); 370 371 // Remap the results file descriptor to point to the parent too. 372 // We use the 9th one (instead of a bigger one) because shell scripts 373 // can only use the [0..9] file descriptors in their redirections. 374 respipe.rend().close(); 375 respipe.wend().posix_remap(9); 376 377 // Prepare the test program's arguments. We use dynamic memory and 378 // do not care to release it. We are going to die anyway very soon, 379 // either due to exec(2) or to exit(3). 380 std::vector< std::string > confargs = conf_args(); 381 char** args = new char*[4 + confargs.size()]; 382 { 383 // 0: Program name. 384 std::string progname = tp.leaf_name(); 385 args[0] = new char[progname.length() + 1]; 386 std::strcpy(args[0], progname.c_str()); 387 388 // 1: The file descriptor to which the results will be printed. 389 args[1] = new char[4]; 390 std::strcpy(args[1], "-r9"); 391 392 // 2: The directory where the test program lives. 393 atf::fs::path bp = tp.branch_path(); 394 if (!bp.is_absolute()) 395 bp = bp.to_absolute(); 396 const char* dir = bp.c_str(); 397 args[2] = new char[std::strlen(dir) + 3]; 398 std::strcpy(args[2], "-s"); 399 std::strcat(args[2], dir); 400 401 // [3..last - 1]: Configuration arguments. 402 std::vector< std::string >::size_type i; 403 for (i = 0; i < confargs.size(); i++) { 404 const char* str = confargs[i].c_str(); 405 args[3 + i] = new char[std::strlen(str) + 1]; 406 std::strcpy(args[3 + i], str); 407 } 408 409 // Last: Terminator. 410 args[3 + i] = NULL; 411 } 412 413 // Do the real exec and report any errors to the parent through the 414 // only mechanism we can use: stderr. 415 // TODO Try to make this fail. 416 ::execv(tp.c_str(), args); 417 std::cerr << "Failed to execute `" << tp.str() << "': " 418 << std::strerror(errno) << std::endl; 419 std::exit(EXIT_FAILURE); 420 } 421 422 int 423 atf_run::run_test_program_parent(const atf::fs::path& tp, 424 atf::formats::atf_tps_writer& w, 425 atf::io::pipe& outpipe, 426 atf::io::pipe& errpipe, 427 atf::io::pipe& respipe, 428 pid_t pid) 429 { 430 // Get the file descriptor and input stream of stdout. 431 outpipe.wend().close(); 432 atf::io::unbuffered_istream outin(outpipe.rend()); 433 434 // Get the file descriptor and input stream of stderr. 435 errpipe.wend().close(); 436 atf::io::unbuffered_istream errin(errpipe.rend()); 437 438 // Get the file descriptor and input stream of the results channel. 439 respipe.wend().close(); 440 atf::io::pistream resin(respipe.rend()); 441 442 // Process the test case's output and multiplex it into our output 443 // stream as we read it. 444 muxer m(tp, w, resin); 445 std::string fmterr; 446 try { 447 m.read(outin, errin); 448 } catch (const atf::parser::parse_errors& e) { 449 fmterr = "There were errors parsing the output of the test " 450 "program:"; 451 for (atf::parser::parse_errors::const_iterator iter = e.begin(); 452 iter != e.end(); iter++) { 453 fmterr += " Line " + atf::text::to_string((*iter).first) + 454 ": " + (*iter).second + "."; 455 } 456 } catch (const atf::formats::format_error& e) { 457 fmterr = e.what(); 458 } catch (...) { 459 UNREACHABLE; 460 } 461 462 try { 463 outin.close(); 464 errin.close(); 465 resin.close(); 466 } catch (...) { 467 UNREACHABLE; 468 } 469 470 int code, status; 471 if (::waitpid(pid, &status, 0) != pid) { 472 m.finalize("waitpid(2) on the child process " + 473 atf::text::to_string(pid) + " failed" + 474 (fmterr.empty() ? "" : (". " + fmterr))); 475 code = EXIT_FAILURE; 476 } else { 477 if (WIFEXITED(status)) { 478 code = WEXITSTATUS(status); 479 if (m.failed() > 0 && code == EXIT_SUCCESS) { 480 code = EXIT_FAILURE; 481 m.finalize("Test program returned success but some test " 482 "cases failed" + 483 (fmterr.empty() ? "" : (". " + fmterr))); 484 } else { 485 code = fmterr.empty() ? code : EXIT_FAILURE; 486 m.finalize(fmterr); 487 } 488 } else if (WIFSIGNALED(status)) { 489 code = EXIT_FAILURE; 490 m.finalize("Test program received signal " + 491 atf::text::to_string(WTERMSIG(status)) + 492 (WCOREDUMP(status) ? " (core dumped)" : "") + 493 (fmterr.empty() ? "" : (". " + fmterr))); 494 } else 495 throw std::runtime_error 496 ("Child process " + atf::text::to_string(pid) + 497 " terminated with an unknown status condition " + 498 atf::text::to_string(status)); 499 } 500 return code; 501 } 502 503 int 504 atf_run::run_test_program(const atf::fs::path& tp, 505 atf::formats::atf_tps_writer& w) 506 { 507 int errcode; 508 509 atf::io::pipe outpipe, errpipe, respipe; 510 pid_t pid = atf::process::fork(); 511 if (pid == 0) { 512 run_test_program_child(tp, outpipe, errpipe, respipe); 513 UNREACHABLE; 514 errcode = EXIT_FAILURE; 515 } else { 516 errcode = run_test_program_parent(tp, w, outpipe, errpipe, 517 respipe, pid); 518 } 519 520 return errcode; 521 } 522 523 size_t 524 atf_run::count_tps(std::vector< std::string > tps) 525 const 526 { 527 size_t ntps = 0; 528 529 for (std::vector< std::string >::const_iterator iter = tps.begin(); 530 iter != tps.end(); iter++) { 531 atf::fs::path tp(*iter); 532 atf::fs::file_info fi(tp); 533 534 if (fi.get_type() == atf::fs::file_info::dir_type) { 535 atf::atffile af = atf::atffile(tp / "Atffile"); 536 std::vector< std::string > aux = af.tps(); 537 for (std::vector< std::string >::iterator i2 = aux.begin(); 538 i2 != aux.end(); i2++) 539 *i2 = (tp / *i2).str(); 540 ntps += count_tps(aux); 541 } else 542 ntps++; 543 } 544 545 return ntps; 546 } 547 548 void 549 atf_run::read_one_config(const atf::fs::path& p) 550 { 551 std::ifstream is(p.c_str()); 552 if (is) { 553 config reader(is); 554 reader.read(); 555 merge_maps(m_config_vars, reader.get_vars()); 556 } 557 } 558 559 void 560 atf_run::read_config(const std::string& name) 561 { 562 std::vector< atf::fs::path > dirs; 563 dirs.push_back(atf::fs::path(atf::config::get("atf_confdir"))); 564 if (atf::env::has("HOME")) 565 dirs.push_back(atf::fs::path(atf::env::get("HOME")) / ".atf"); 566 567 m_config_vars.clear(); 568 for (std::vector< atf::fs::path >::const_iterator iter = dirs.begin(); 569 iter != dirs.end(); iter++) { 570 read_one_config((*iter) / "common.conf"); 571 read_one_config((*iter) / (name + ".conf")); 572 } 573 } 574 575 static 576 void 577 call_hook(const std::string& tool, const std::string& hook) 578 { 579 std::string sh = atf::config::get("atf_shell"); 580 atf::fs::path p = atf::fs::path(atf::config::get("atf_pkgdatadir")) / 581 (tool + ".hooks"); 582 std::string cmd = sh + " '" + p.str() + "' '" + hook + "'"; 583 int exitcode = std::system(cmd.c_str()); 584 if (!WIFEXITED(exitcode) || WEXITSTATUS(exitcode) != EXIT_SUCCESS) 585 throw std::runtime_error("Failed to run the '" + hook + "' hook " 586 "for '" + tool + "'; command was '" + 587 cmd + "'; exit code " + 588 atf::text::to_string(exitcode)); 589 } 590 591 int 592 atf_run::main(void) 593 { 594 atf::atffile af(atf::fs::path("Atffile")); 595 m_atffile_vars = af.conf(); 596 597 std::vector< std::string > tps; 598 tps = af.tps(); 599 if (m_argc >= 1) { 600 // TODO: Ensure that the given test names are listed in the 601 // Atffile. Take into account that the file can be using globs. 602 tps.clear(); 603 for (int i = 0; i < m_argc; i++) 604 tps.push_back(m_argv[i]); 605 } 606 607 // Read configuration data for this test suite. 608 { 609 atf::tests::vars_map::const_iterator iter = 610 af.props().find("test-suite"); 611 INV(iter != af.props().end()); 612 read_config((*iter).second); 613 } 614 615 atf::formats::atf_tps_writer w(std::cout); 616 call_hook("atf-run", "info_start_hook"); 617 w.ntps(count_tps(tps)); 618 619 bool ok = true; 620 for (std::vector< std::string >::const_iterator iter = tps.begin(); 621 iter != tps.end(); iter++) 622 ok &= (run_test(atf::fs::path(*iter), w) == EXIT_SUCCESS); 623 624 call_hook("atf-run", "info_end_hook"); 625 626 return ok ? EXIT_SUCCESS : EXIT_FAILURE; 627 } 628 629 int 630 main(int argc, char* const* argv) 631 { 632 return atf_run().run(argc, argv); 633 } 634