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 extern "C" { 31 #include <sys/types.h> 32 #include <sys/stat.h> 33 #include <sys/time.h> 34 #include <sys/wait.h> 35 #include <signal.h> 36 #include <unistd.h> 37 } 38 39 #include <algorithm> 40 #include <cctype> 41 #include <cerrno> 42 #include <cstdlib> 43 #include <cstring> 44 #include <fstream> 45 #include <iostream> 46 #include <map> 47 #include <memory> 48 #include <sstream> 49 #include <stdexcept> 50 #include <vector> 51 52 extern "C" { 53 #include "atf-c/error.h" 54 #include "atf-c/object.h" 55 } 56 57 #include "atf-c++/application.hpp" 58 #include "atf-c++/config.hpp" 59 #include "atf-c++/env.hpp" 60 #include "atf-c++/exceptions.hpp" 61 #include "atf-c++/expand.hpp" 62 #include "atf-c++/formats.hpp" 63 #include "atf-c++/fs.hpp" 64 #include "atf-c++/io.hpp" 65 #include "atf-c++/sanity.hpp" 66 #include "atf-c++/signals.hpp" 67 #include "atf-c++/tests.hpp" 68 #include "atf-c++/text.hpp" 69 #include "atf-c++/ui.hpp" 70 #include "atf-c++/user.hpp" 71 72 namespace impl = atf::tests; 73 #define IMPL_NAME "atf::tests" 74 75 // ------------------------------------------------------------------------ 76 // Auxiliary stuff for the timeout implementation. 77 // ------------------------------------------------------------------------ 78 79 namespace timeout { 80 static pid_t current_body = 0; 81 static bool killed = false; 82 83 void 84 sigalrm_handler(int signo) 85 { 86 PRE(signo == SIGALRM); 87 88 if (current_body != 0) { 89 ::killpg(current_body, SIGTERM); 90 killed = true; 91 } 92 } 93 } // namespace timeout 94 95 // ------------------------------------------------------------------------ 96 // The "tcr" class. 97 // ------------------------------------------------------------------------ 98 99 const impl::tcr::state impl::tcr::passed_state = atf_tcr_passed_state; 100 const impl::tcr::state impl::tcr::failed_state = atf_tcr_failed_state; 101 const impl::tcr::state impl::tcr::skipped_state = atf_tcr_skipped_state; 102 103 impl::tcr::tcr(state s) 104 { 105 PRE(s == passed_state); 106 107 atf_error_t err = atf_tcr_init(&m_tcr, s); 108 if (atf_is_error(err)) 109 throw_atf_error(err); 110 } 111 112 impl::tcr::tcr(state s, const std::string& r) 113 { 114 PRE(s == failed_state || s == skipped_state); 115 PRE(!r.empty()); 116 117 atf_error_t err = atf_tcr_init_reason_fmt(&m_tcr, s, "%s", r.c_str()); 118 if (atf_is_error(err)) 119 throw_atf_error(err); 120 } 121 122 impl::tcr::tcr(const tcr& o) 123 { 124 if (o.get_state() == passed_state) 125 atf_tcr_init(&m_tcr, o.get_state()); 126 else 127 atf_tcr_init_reason_fmt(&m_tcr, o.get_state(), "%s", 128 o.get_reason().c_str()); 129 } 130 131 impl::tcr::~tcr(void) 132 { 133 atf_tcr_fini(&m_tcr); 134 } 135 136 impl::tcr::state 137 impl::tcr::get_state(void) 138 const 139 { 140 return atf_tcr_get_state(&m_tcr); 141 } 142 143 const std::string 144 impl::tcr::get_reason(void) 145 const 146 { 147 const atf_dynstr_t* r = atf_tcr_get_reason(&m_tcr); 148 return atf_dynstr_cstring(r); 149 } 150 151 impl::tcr& 152 impl::tcr::operator=(const tcr& o) 153 { 154 if (this != &o) { 155 atf_tcr_fini(&m_tcr); 156 157 if (o.get_state() == passed_state) 158 atf_tcr_init(&m_tcr, o.get_state()); 159 else 160 atf_tcr_init_reason_fmt(&m_tcr, o.get_state(), "%s", 161 o.get_reason().c_str()); 162 } 163 return *this; 164 } 165 166 // ------------------------------------------------------------------------ 167 // The "tc" class. 168 // ------------------------------------------------------------------------ 169 170 static std::map< atf_tc_t*, impl::tc* > wraps; 171 static std::map< const atf_tc_t*, const impl::tc* > cwraps; 172 173 void 174 impl::tc::wrap_head(atf_tc_t *tc) 175 { 176 std::map< atf_tc_t*, impl::tc* >::iterator iter = wraps.find(tc); 177 INV(iter != wraps.end()); 178 (*iter).second->head(); 179 } 180 181 void 182 impl::tc::wrap_body(const atf_tc_t *tc) 183 { 184 std::map< const atf_tc_t*, const impl::tc* >::const_iterator iter = 185 cwraps.find(tc); 186 INV(iter != cwraps.end()); 187 (*iter).second->body(); 188 } 189 190 void 191 impl::tc::wrap_cleanup(const atf_tc_t *tc) 192 { 193 std::map< const atf_tc_t*, const impl::tc* >::const_iterator iter = 194 cwraps.find(tc); 195 INV(iter != cwraps.end()); 196 (*iter).second->cleanup(); 197 } 198 199 impl::tc::tc(const std::string& ident) : 200 m_ident(ident) 201 { 202 } 203 204 impl::tc::~tc(void) 205 { 206 cwraps.erase(&m_tc); 207 wraps.erase(&m_tc); 208 209 atf_tc_fini(&m_tc); 210 atf_map_fini(&m_config); 211 } 212 213 void 214 impl::tc::init(const vars_map& config) 215 { 216 atf_error_t err; 217 218 err = atf_map_init(&m_config); 219 if (atf_is_error(err)) 220 throw_atf_error(err); 221 222 for (vars_map::const_iterator iter = config.begin(); 223 iter != config.end(); iter++) { 224 const char *var = (*iter).first.c_str(); 225 const char *val = (*iter).second.c_str(); 226 227 err = atf_map_insert(&m_config, var, ::strdup(val), true); 228 if (atf_is_error(err)) { 229 atf_map_fini(&m_config); 230 throw_atf_error(err); 231 } 232 } 233 234 wraps[&m_tc] = this; 235 cwraps[&m_tc] = this; 236 237 err = atf_tc_init(&m_tc, m_ident.c_str(), wrap_head, wrap_body, 238 wrap_cleanup, &m_config); 239 if (atf_is_error(err)) { 240 atf_map_fini(&m_config); 241 throw_atf_error(err); 242 } 243 } 244 245 bool 246 impl::tc::has_config_var(const std::string& var) 247 const 248 { 249 return atf_tc_has_config_var(&m_tc, var.c_str()); 250 } 251 252 bool 253 impl::tc::has_md_var(const std::string& var) 254 const 255 { 256 return atf_tc_has_md_var(&m_tc, var.c_str()); 257 } 258 259 const std::string 260 impl::tc::get_config_var(const std::string& var) 261 const 262 { 263 return atf_tc_get_config_var(&m_tc, var.c_str()); 264 } 265 266 const std::string 267 impl::tc::get_config_var(const std::string& var, const std::string& defval) 268 const 269 { 270 return atf_tc_get_config_var_wd(&m_tc, var.c_str(), defval.c_str()); 271 } 272 273 const std::string 274 impl::tc::get_md_var(const std::string& var) 275 const 276 { 277 return atf_tc_get_md_var(&m_tc, var.c_str()); 278 } 279 280 void 281 impl::tc::set_md_var(const std::string& var, const std::string& val) 282 { 283 atf_error_t err = atf_tc_set_md_var(&m_tc, var.c_str(), val.c_str()); 284 if (atf_is_error(err)) 285 throw_atf_error(err); 286 } 287 288 impl::tcr 289 impl::tc::run(const fs::path& workdirbase) 290 const 291 { 292 atf_tcr_t tcrc; 293 tcr tcrr(tcr::failed_state, "UNINITIALIZED"); 294 295 atf_error_t err = atf_tc_run(&m_tc, &tcrc, workdirbase.c_path()); 296 if (atf_is_error(err)) 297 throw_atf_error(err); 298 299 if (atf_tcr_has_reason(&tcrc)) { 300 const atf_dynstr_t* r = atf_tcr_get_reason(&tcrc); 301 tcrr = tcr(atf_tcr_get_state(&tcrc), atf_dynstr_cstring(r)); 302 } else { 303 tcrr = tcr(atf_tcr_get_state(&tcrc)); 304 } 305 306 atf_tcr_fini(&tcrc); 307 return tcrr; 308 } 309 310 void 311 impl::tc::cleanup(void) 312 const 313 { 314 } 315 316 void 317 impl::tc::require_prog(const std::string& prog) 318 const 319 { 320 PRE(!prog.empty()); 321 322 fs::path p(prog); 323 324 if (p.is_absolute()) { 325 if (!fs::is_executable(p)) 326 skip("The required program " + prog + " could not be found"); 327 } else { 328 INV(p.branch_path() == fs::path(".")); 329 if (!fs::have_prog_in_path(prog)) 330 skip("The required program " + prog + " could not be found in " 331 "the PATH"); 332 } 333 } 334 335 void 336 impl::tc::pass(void) 337 { 338 atf_tc_pass(); 339 } 340 341 void 342 impl::tc::fail(const std::string& reason) 343 { 344 atf_tc_fail("%s", reason.c_str()); 345 } 346 347 void 348 impl::tc::skip(const std::string& reason) 349 { 350 atf_tc_skip("%s", reason.c_str()); 351 } 352 353 // ------------------------------------------------------------------------ 354 // The "tp" class. 355 // ------------------------------------------------------------------------ 356 357 class tp : public atf::application::app { 358 public: 359 typedef std::vector< impl::tc * > tc_vector; 360 361 private: 362 static const char* m_description; 363 364 bool m_lflag; 365 int m_results_fd; 366 std::auto_ptr< std::ostream > m_results_os; 367 atf::fs::path m_srcdir; 368 atf::fs::path m_workdir; 369 std::vector< std::string > m_tcnames; 370 371 atf::tests::vars_map m_vars; 372 373 std::string specific_args(void) const; 374 options_set specific_options(void) const; 375 void process_option(int, const char*); 376 377 void (*m_add_tcs)(tc_vector&); 378 tc_vector m_tcs; 379 380 void parse_vflag(const std::string&); 381 void handle_srcdir(void); 382 383 tc_vector init_tcs(void); 384 static tc_vector filter_tcs(tc_vector, 385 const std::vector< std::string >&); 386 387 std::ostream& results_stream(void); 388 389 int list_tcs(void); 390 int run_tcs(void); 391 392 public: 393 tp(void (*)(tc_vector&)); 394 ~tp(void); 395 396 int main(void); 397 }; 398 399 const char* tp::m_description = 400 "This is an independent atf test program."; 401 402 tp::tp(void (*add_tcs)(tc_vector&)) : 403 app(m_description, "atf-test-program(1)", "atf(7)"), 404 m_lflag(false), 405 m_results_fd(STDOUT_FILENO), 406 m_srcdir("."), 407 m_workdir(atf::config::get("atf_workdir")), 408 m_add_tcs(add_tcs) 409 { 410 } 411 412 tp::~tp(void) 413 { 414 for (tc_vector::iterator iter = m_tcs.begin(); 415 iter != m_tcs.end(); iter++) { 416 impl::tc* tc = *iter; 417 418 delete tc; 419 } 420 } 421 422 std::string 423 tp::specific_args(void) 424 const 425 { 426 return "[test_case1 [.. test_caseN]]"; 427 } 428 429 tp::options_set 430 tp::specific_options(void) 431 const 432 { 433 using atf::application::option; 434 options_set opts; 435 opts.insert(option('l', "", "List test cases and their purpose")); 436 opts.insert(option('r', "fd", "The file descriptor to which the test " 437 "program will send the results of the " 438 "test cases")); 439 opts.insert(option('s', "srcdir", "Directory where the test's data " 440 "files are located")); 441 opts.insert(option('v', "var=value", "Sets the configuration variable " 442 "`var' to `value'")); 443 opts.insert(option('w', "workdir", "Directory where the test's " 444 "temporary files are located")); 445 return opts; 446 } 447 448 void 449 tp::process_option(int ch, const char* arg) 450 { 451 switch (ch) { 452 case 'l': 453 m_lflag = true; 454 break; 455 456 case 'r': 457 { 458 std::istringstream ss(arg); 459 ss >> m_results_fd; 460 } 461 break; 462 463 case 's': 464 m_srcdir = atf::fs::path(arg); 465 break; 466 467 case 'v': 468 parse_vflag(arg); 469 break; 470 471 case 'w': 472 m_workdir = atf::fs::path(arg); 473 break; 474 475 default: 476 UNREACHABLE; 477 } 478 } 479 480 void 481 tp::parse_vflag(const std::string& str) 482 { 483 if (str.empty()) 484 throw std::runtime_error("-v requires a non-empty argument"); 485 486 std::vector< std::string > ws = atf::text::split(str, "="); 487 if (ws.size() == 1 && str[str.length() - 1] == '=') { 488 m_vars[ws[0]] = ""; 489 } else { 490 if (ws.size() != 2) 491 throw std::runtime_error("-v requires an argument of the form " 492 "var=value"); 493 494 m_vars[ws[0]] = ws[1]; 495 } 496 } 497 498 void 499 tp::handle_srcdir(void) 500 { 501 if (!atf::fs::exists(m_srcdir / m_prog_name)) 502 throw std::runtime_error("Cannot find the test program in the " 503 "source directory `" + m_srcdir.str() + "'"); 504 505 if (!m_srcdir.is_absolute()) 506 m_srcdir = m_srcdir.to_absolute(); 507 508 m_vars["srcdir"] = m_srcdir.str(); 509 } 510 511 tp::tc_vector 512 tp::init_tcs(void) 513 { 514 m_add_tcs(m_tcs); 515 for (tc_vector::iterator iter = m_tcs.begin(); 516 iter != m_tcs.end(); iter++) { 517 impl::tc* tc = *iter; 518 519 tc->init(m_vars); 520 } 521 return m_tcs; 522 } 523 524 // 525 // An auxiliary unary predicate that compares the given test case's 526 // identifier to the identifier stored in it. 527 // 528 class tc_equal_to_ident { 529 const std::string& m_ident; 530 531 public: 532 tc_equal_to_ident(const std::string& i) : 533 m_ident(i) 534 { 535 } 536 537 bool operator()(const impl::tc* tc) 538 { 539 return tc->get_md_var("ident") == m_ident; 540 } 541 }; 542 543 tp::tc_vector 544 tp::filter_tcs(tc_vector tcs, const std::vector< std::string >& tcnames) 545 { 546 tc_vector tcso; 547 548 if (tcnames.empty()) { 549 // Special case: added for efficiency because this is the most 550 // typical situation. 551 tcso = tcs; 552 } else { 553 // Collect all the test cases' identifiers. 554 std::vector< std::string > ids; 555 for (tc_vector::iterator iter = tcs.begin(); 556 iter != tcs.end(); iter++) { 557 impl::tc* tc = *iter; 558 559 ids.push_back(tc->get_md_var("ident")); 560 } 561 562 // Iterate over all names provided by the user and, for each one, 563 // expand it as if it were a glob pattern. Collect all expansions. 564 std::vector< std::string > exps; 565 for (std::vector< std::string >::const_iterator iter = tcnames.begin(); 566 iter != tcnames.end(); iter++) { 567 const std::string& glob = *iter; 568 569 std::vector< std::string > ms = 570 atf::expand::expand_glob(glob, ids); 571 if (ms.empty()) 572 throw std::runtime_error("Unknown test case `" + glob + "'"); 573 exps.insert(exps.end(), ms.begin(), ms.end()); 574 } 575 576 // For each expansion, locate its corresponding test case and add 577 // it to the output set. 578 for (std::vector< std::string >::const_iterator iter = exps.begin(); 579 iter != exps.end(); iter++) { 580 const std::string& name = *iter; 581 582 tc_vector::iterator tciter = 583 std::find_if(tcs.begin(), tcs.end(), tc_equal_to_ident(name)); 584 INV(tciter != tcs.end()); 585 tcso.push_back(*tciter); 586 } 587 } 588 589 return tcso; 590 } 591 592 int 593 tp::list_tcs(void) 594 { 595 tc_vector tcs = filter_tcs(init_tcs(), m_tcnames); 596 597 std::string::size_type maxlen = 0; 598 for (tc_vector::const_iterator iter = tcs.begin(); 599 iter != tcs.end(); iter++) { 600 const impl::tc* tc = *iter; 601 602 if (maxlen < tc->get_md_var("ident").length()) 603 maxlen = tc->get_md_var("ident").length(); 604 } 605 606 for (tc_vector::const_iterator iter = tcs.begin(); 607 iter != tcs.end(); iter++) { 608 const impl::tc* tc = *iter; 609 610 std::cout << atf::ui::format_text_with_tag(tc->get_md_var("descr"), 611 tc->get_md_var("ident"), 612 false, maxlen + 4) 613 << std::endl; 614 } 615 616 return EXIT_SUCCESS; 617 } 618 619 std::ostream& 620 tp::results_stream(void) 621 { 622 if (m_results_fd == STDOUT_FILENO) 623 return std::cout; 624 else if (m_results_fd == STDERR_FILENO) 625 return std::cerr; 626 else 627 return *m_results_os; 628 } 629 630 int 631 tp::run_tcs(void) 632 { 633 tc_vector tcs = filter_tcs(init_tcs(), m_tcnames); 634 635 if (!atf::fs::exists(m_workdir)) 636 throw std::runtime_error("Cannot find the work directory `" + 637 m_workdir.str() + "'"); 638 639 int errcode = EXIT_SUCCESS; 640 641 atf::signals::signal_holder sighup(SIGHUP); 642 atf::signals::signal_holder sigint(SIGINT); 643 atf::signals::signal_holder sigterm(SIGTERM); 644 645 atf::formats::atf_tcs_writer w(results_stream(), std::cout, std::cerr, 646 tcs.size()); 647 for (tc_vector::iterator iter = tcs.begin(); 648 iter != tcs.end(); iter++) { 649 impl::tc* tc = *iter; 650 651 w.start_tc(tc->get_md_var("ident")); 652 impl::tcr tcr = tc->run(m_workdir); 653 w.end_tc(tcr); 654 655 sighup.process(); 656 sigint.process(); 657 sigterm.process(); 658 659 if (tcr.get_state() == impl::tcr::failed_state) 660 errcode = EXIT_FAILURE; 661 } 662 663 return errcode; 664 } 665 666 int 667 tp::main(void) 668 { 669 int errcode; 670 671 handle_srcdir(); 672 673 for (int i = 0; i < m_argc; i++) 674 m_tcnames.push_back(m_argv[i]); 675 676 if (m_lflag) 677 errcode = list_tcs(); 678 else { 679 if (m_results_fd != STDOUT_FILENO && m_results_fd != STDERR_FILENO) { 680 atf::io::file_handle fh(m_results_fd); 681 m_results_os = 682 std::auto_ptr< std::ostream >(new atf::io::postream(fh)); 683 } 684 errcode = run_tcs(); 685 } 686 687 return errcode; 688 } 689 690 namespace atf { 691 namespace tests { 692 int run_tp(int, char* const*, void (*)(tp::tc_vector&)); 693 } 694 } 695 696 int 697 impl::run_tp(int argc, char* const* argv, void (*add_tcs)(tp::tc_vector&)) 698 { 699 return tp(add_tcs).run(argc, argv); 700 } 701