1 // 2 // Automated Testing Framework (atf) 3 // 4 // Copyright (c) 2007, 2008, 2009, 2010 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 } 55 56 #include "atf-c++/application.hpp" 57 #include "atf-c++/env.hpp" 58 #include "atf-c++/exceptions.hpp" 59 #include "atf-c++/fs.hpp" 60 #include "atf-c++/io.hpp" 61 #include "atf-c++/parser.hpp" 62 #include "atf-c++/sanity.hpp" 63 #include "atf-c++/tests.hpp" 64 #include "atf-c++/text.hpp" 65 #include "atf-c++/user.hpp" 66 67 namespace impl = atf::tests; 68 namespace detail = atf::tests::detail; 69 #define IMPL_NAME "atf::tests" 70 71 // ------------------------------------------------------------------------ 72 // The "atf_tp_writer" class. 73 // ------------------------------------------------------------------------ 74 75 detail::atf_tp_writer::atf_tp_writer(std::ostream& os) : 76 m_os(os), 77 m_is_first(true) 78 { 79 atf::parser::headers_map hm; 80 atf::parser::attrs_map ct_attrs; 81 ct_attrs["version"] = "1"; 82 hm["Content-Type"] = atf::parser::header_entry("Content-Type", 83 "application/X-atf-tp", ct_attrs); 84 atf::parser::write_headers(hm, m_os); 85 } 86 87 void 88 detail::atf_tp_writer::start_tc(const std::string& ident) 89 { 90 if (!m_is_first) 91 m_os << std::endl; 92 m_os << "ident: " << ident << std::endl; 93 m_os.flush(); 94 } 95 96 void 97 detail::atf_tp_writer::end_tc(void) 98 { 99 if (m_is_first) 100 m_is_first = false; 101 } 102 103 void 104 detail::atf_tp_writer::tc_meta_data(const std::string& name, 105 const std::string& value) 106 { 107 PRE(name != "ident"); 108 m_os << name << ": " << value << std::endl; 109 m_os.flush(); 110 } 111 112 // ------------------------------------------------------------------------ 113 // The "tc" class. 114 // ------------------------------------------------------------------------ 115 116 static std::map< atf_tc_t*, impl::tc* > wraps; 117 static std::map< const atf_tc_t*, const impl::tc* > cwraps; 118 119 void 120 impl::tc::wrap_head(atf_tc_t *tc) 121 { 122 try { 123 std::map< atf_tc_t*, impl::tc* >::iterator iter = wraps.find(tc); 124 INV(iter != wraps.end()); 125 (*iter).second->head(); 126 } catch (const std::exception& e) { 127 std::cerr << "Caught unhandled exception: " + std::string(e.what()); 128 std::abort(); 129 } catch (...) { 130 std::cerr << "Caught unknown exception"; 131 std::abort(); 132 } 133 } 134 135 void 136 impl::tc::wrap_body(const atf_tc_t *tc) 137 { 138 try { 139 std::map< const atf_tc_t*, const impl::tc* >::const_iterator iter = 140 cwraps.find(tc); 141 INV(iter != cwraps.end()); 142 (*iter).second->body(); 143 } catch (const std::exception& e) { 144 fail("Caught unhandled exception: " + std::string(e.what())); 145 } catch (...) { 146 fail("Caught unknown exception"); 147 } 148 } 149 150 void 151 impl::tc::wrap_cleanup(const atf_tc_t *tc) 152 { 153 try { 154 std::map< const atf_tc_t*, const impl::tc* >::const_iterator iter = 155 cwraps.find(tc); 156 INV(iter != cwraps.end()); 157 (*iter).second->cleanup(); 158 } catch (const std::exception& e) { 159 std::cerr << "Caught unhandled exception: " + std::string(e.what()); 160 std::abort(); 161 } catch (...) { 162 std::cerr << "Caught unknown exception"; 163 std::abort(); 164 } 165 } 166 167 impl::tc::tc(const std::string& ident, const bool has_cleanup) : 168 m_ident(ident), 169 m_has_cleanup(has_cleanup) 170 { 171 } 172 173 impl::tc::~tc(void) 174 { 175 cwraps.erase(&m_tc); 176 wraps.erase(&m_tc); 177 178 atf_tc_fini(&m_tc); 179 atf_map_fini(&m_config); 180 } 181 182 void 183 impl::tc::init(const vars_map& config) 184 { 185 atf_error_t err; 186 187 err = atf_map_init(&m_config); 188 if (atf_is_error(err)) 189 throw_atf_error(err); 190 191 for (vars_map::const_iterator iter = config.begin(); 192 iter != config.end(); iter++) { 193 const char *var = (*iter).first.c_str(); 194 const char *val = (*iter).second.c_str(); 195 196 err = atf_map_insert(&m_config, var, ::strdup(val), true); 197 if (atf_is_error(err)) { 198 atf_map_fini(&m_config); 199 throw_atf_error(err); 200 } 201 } 202 203 wraps[&m_tc] = this; 204 cwraps[&m_tc] = this; 205 206 err = atf_tc_init(&m_tc, m_ident.c_str(), wrap_head, wrap_body, 207 m_has_cleanup ? wrap_cleanup : NULL, &m_config); 208 if (atf_is_error(err)) { 209 atf_map_fini(&m_config); 210 throw_atf_error(err); 211 } 212 } 213 214 bool 215 impl::tc::has_config_var(const std::string& var) 216 const 217 { 218 return atf_tc_has_config_var(&m_tc, var.c_str()); 219 } 220 221 bool 222 impl::tc::has_md_var(const std::string& var) 223 const 224 { 225 return atf_tc_has_md_var(&m_tc, var.c_str()); 226 } 227 228 const std::string 229 impl::tc::get_config_var(const std::string& var) 230 const 231 { 232 return atf_tc_get_config_var(&m_tc, var.c_str()); 233 } 234 235 const std::string 236 impl::tc::get_config_var(const std::string& var, const std::string& defval) 237 const 238 { 239 return atf_tc_get_config_var_wd(&m_tc, var.c_str(), defval.c_str()); 240 } 241 242 const std::string 243 impl::tc::get_md_var(const std::string& var) 244 const 245 { 246 return atf_tc_get_md_var(&m_tc, var.c_str()); 247 } 248 249 const impl::vars_map 250 impl::tc::get_md_vars(void) 251 const 252 { 253 vars_map vars; 254 255 atf_map_citer_t iter; 256 atf_map_for_each_c(iter, atf_tc_get_md_vars(&m_tc)) { 257 vars.insert(vars_map::value_type(atf_map_citer_key(iter), 258 static_cast< const char * >(atf_map_citer_data(iter)))); 259 } 260 261 return vars; 262 } 263 264 void 265 impl::tc::set_md_var(const std::string& var, const std::string& val) 266 { 267 atf_error_t err = atf_tc_set_md_var(&m_tc, var.c_str(), val.c_str()); 268 if (atf_is_error(err)) 269 throw_atf_error(err); 270 } 271 272 void 273 impl::tc::run(const fs::path& resfile) 274 const 275 { 276 atf_error_t err = atf_tc_run(&m_tc, resfile.c_path()); 277 if (atf_is_error(err)) 278 throw_atf_error(err); 279 } 280 281 void 282 impl::tc::run_cleanup(void) 283 const 284 { 285 atf_error_t err = atf_tc_cleanup(&m_tc); 286 if (atf_is_error(err)) 287 throw_atf_error(err); 288 } 289 290 void 291 impl::tc::head(void) 292 { 293 } 294 295 void 296 impl::tc::cleanup(void) 297 const 298 { 299 } 300 301 void 302 impl::tc::require_prog(const std::string& prog) 303 const 304 { 305 atf_tc_require_prog(prog.c_str()); 306 } 307 308 void 309 impl::tc::pass(void) 310 { 311 atf_tc_pass(); 312 } 313 314 void 315 impl::tc::fail(const std::string& reason) 316 { 317 atf_tc_fail("%s", reason.c_str()); 318 } 319 320 void 321 impl::tc::fail_nonfatal(const std::string& reason) 322 { 323 atf_tc_fail_nonfatal("%s", reason.c_str()); 324 } 325 326 void 327 impl::tc::skip(const std::string& reason) 328 { 329 atf_tc_skip("%s", reason.c_str()); 330 } 331 332 void 333 impl::tc::check_errno(const char* file, const int line, const int exp_errno, 334 const char* expr_str, const bool result) 335 { 336 atf_tc_check_errno(file, line, exp_errno, expr_str, result); 337 } 338 339 void 340 impl::tc::require_errno(const char* file, const int line, const int exp_errno, 341 const char* expr_str, const bool result) 342 { 343 atf_tc_require_errno(file, line, exp_errno, expr_str, result); 344 } 345 346 void 347 impl::tc::expect_pass(void) 348 { 349 atf_tc_expect_pass(); 350 } 351 352 void 353 impl::tc::expect_fail(const std::string& reason) 354 { 355 atf_tc_expect_fail("%s", reason.c_str()); 356 } 357 358 void 359 impl::tc::expect_exit(const int exitcode, const std::string& reason) 360 { 361 atf_tc_expect_exit(exitcode, "%s", reason.c_str()); 362 } 363 364 void 365 impl::tc::expect_signal(const int signo, const std::string& reason) 366 { 367 atf_tc_expect_signal(signo, "%s", reason.c_str()); 368 } 369 370 void 371 impl::tc::expect_death(const std::string& reason) 372 { 373 atf_tc_expect_death("%s", reason.c_str()); 374 } 375 376 void 377 impl::tc::expect_timeout(const std::string& reason) 378 { 379 atf_tc_expect_timeout("%s", reason.c_str()); 380 } 381 382 // ------------------------------------------------------------------------ 383 // The "tp" class. 384 // ------------------------------------------------------------------------ 385 386 class tp : public atf::application::app { 387 public: 388 typedef std::vector< impl::tc * > tc_vector; 389 390 private: 391 static const char* m_description; 392 393 bool m_lflag; 394 atf::fs::path m_resfile; 395 std::string m_srcdir_arg; 396 atf::fs::path m_srcdir; 397 398 atf::tests::vars_map m_vars; 399 400 std::string specific_args(void) const; 401 options_set specific_options(void) const; 402 void process_option(int, const char*); 403 404 void (*m_add_tcs)(tc_vector&); 405 tc_vector m_tcs; 406 407 void parse_vflag(const std::string&); 408 void handle_srcdir(void); 409 410 tc_vector init_tcs(void); 411 412 enum tc_part { 413 BODY, 414 CLEANUP, 415 }; 416 417 void list_tcs(void); 418 impl::tc* find_tc(tc_vector, const std::string&); 419 static std::pair< std::string, tc_part > process_tcarg(const std::string&); 420 int run_tc(const std::string&); 421 422 public: 423 tp(void (*)(tc_vector&)); 424 ~tp(void); 425 426 int main(void); 427 }; 428 429 const char* tp::m_description = 430 "This is an independent atf test program."; 431 432 tp::tp(void (*add_tcs)(tc_vector&)) : 433 app(m_description, "atf-test-program(1)", "atf(7)", false), 434 m_lflag(false), 435 m_resfile("/dev/stdout"), 436 m_srcdir("."), 437 m_add_tcs(add_tcs) 438 { 439 } 440 441 tp::~tp(void) 442 { 443 for (tc_vector::iterator iter = m_tcs.begin(); 444 iter != m_tcs.end(); iter++) { 445 impl::tc* tc = *iter; 446 447 delete tc; 448 } 449 } 450 451 std::string 452 tp::specific_args(void) 453 const 454 { 455 return "test_case"; 456 } 457 458 tp::options_set 459 tp::specific_options(void) 460 const 461 { 462 using atf::application::option; 463 options_set opts; 464 opts.insert(option('l', "", "List test cases and their purpose")); 465 opts.insert(option('r', "resfile", "The file to which the test program " 466 "will write the results of the " 467 "executed test case")); 468 opts.insert(option('s', "srcdir", "Directory where the test's data " 469 "files are located")); 470 opts.insert(option('v', "var=value", "Sets the configuration variable " 471 "`var' to `value'")); 472 return opts; 473 } 474 475 void 476 tp::process_option(int ch, const char* arg) 477 { 478 switch (ch) { 479 case 'l': 480 m_lflag = true; 481 break; 482 483 case 'r': 484 m_resfile = atf::fs::path(arg); 485 break; 486 487 case 's': 488 m_srcdir_arg = arg; 489 break; 490 491 case 'v': 492 parse_vflag(arg); 493 break; 494 495 default: 496 UNREACHABLE; 497 } 498 } 499 500 void 501 tp::parse_vflag(const std::string& str) 502 { 503 if (str.empty()) 504 throw std::runtime_error("-v requires a non-empty argument"); 505 506 std::vector< std::string > ws = atf::text::split(str, "="); 507 if (ws.size() == 1 && str[str.length() - 1] == '=') { 508 m_vars[ws[0]] = ""; 509 } else { 510 if (ws.size() != 2) 511 throw std::runtime_error("-v requires an argument of the form " 512 "var=value"); 513 514 m_vars[ws[0]] = ws[1]; 515 } 516 } 517 518 void 519 tp::handle_srcdir(void) 520 { 521 if (m_srcdir_arg.empty()) { 522 m_srcdir = atf::fs::path(m_argv0).branch_path(); 523 if (m_srcdir.leaf_name() == ".libs") 524 m_srcdir = m_srcdir.branch_path(); 525 } else 526 m_srcdir = atf::fs::path(m_srcdir_arg); 527 528 if (!atf::fs::exists(m_srcdir / m_prog_name)) 529 throw std::runtime_error("Cannot find the test program in the " 530 "source directory `" + m_srcdir.str() + "'"); 531 532 if (!m_srcdir.is_absolute()) 533 m_srcdir = m_srcdir.to_absolute(); 534 535 m_vars["srcdir"] = m_srcdir.str(); 536 } 537 538 tp::tc_vector 539 tp::init_tcs(void) 540 { 541 m_add_tcs(m_tcs); 542 for (tc_vector::iterator iter = m_tcs.begin(); 543 iter != m_tcs.end(); iter++) { 544 impl::tc* tc = *iter; 545 546 tc->init(m_vars); 547 } 548 return m_tcs; 549 } 550 551 // 552 // An auxiliary unary predicate that compares the given test case's 553 // identifier to the identifier stored in it. 554 // 555 class tc_equal_to_ident { 556 const std::string& m_ident; 557 558 public: 559 tc_equal_to_ident(const std::string& i) : 560 m_ident(i) 561 { 562 } 563 564 bool operator()(const impl::tc* tc) 565 { 566 return tc->get_md_var("ident") == m_ident; 567 } 568 }; 569 570 void 571 tp::list_tcs(void) 572 { 573 tc_vector tcs = init_tcs(); 574 detail::atf_tp_writer writer(std::cout); 575 576 for (tc_vector::const_iterator iter = tcs.begin(); 577 iter != tcs.end(); iter++) { 578 const impl::vars_map vars = (*iter)->get_md_vars(); 579 580 { 581 impl::vars_map::const_iterator iter2 = vars.find("ident"); 582 INV(iter2 != vars.end()); 583 writer.start_tc((*iter2).second); 584 } 585 586 for (impl::vars_map::const_iterator iter2 = vars.begin(); 587 iter2 != vars.end(); iter2++) { 588 const std::string& key = (*iter2).first; 589 if (key != "ident") 590 writer.tc_meta_data(key, (*iter2).second); 591 } 592 593 writer.end_tc(); 594 } 595 } 596 597 impl::tc* 598 tp::find_tc(tc_vector tcs, const std::string& name) 599 { 600 std::vector< std::string > ids; 601 for (tc_vector::iterator iter = tcs.begin(); 602 iter != tcs.end(); iter++) { 603 impl::tc* tc = *iter; 604 605 if (tc->get_md_var("ident") == name) 606 return tc; 607 } 608 throw atf::application::usage_error("Unknown test case `%s'", 609 name.c_str()); 610 } 611 612 std::pair< std::string, tp::tc_part > 613 tp::process_tcarg(const std::string& tcarg) 614 { 615 const std::string::size_type pos = tcarg.find(':'); 616 if (pos == std::string::npos) { 617 return std::make_pair(tcarg, BODY); 618 } else { 619 const std::string tcname = tcarg.substr(0, pos); 620 621 const std::string partname = tcarg.substr(pos + 1); 622 if (partname == "body") 623 return std::make_pair(tcname, BODY); 624 else if (partname == "cleanup") 625 return std::make_pair(tcname, CLEANUP); 626 else { 627 using atf::application::usage_error; 628 throw usage_error("Invalid test case part `%s'", partname.c_str()); 629 } 630 } 631 } 632 633 int 634 tp::run_tc(const std::string& tcarg) 635 { 636 const std::pair< std::string, tc_part > fields = process_tcarg(tcarg); 637 638 impl::tc* tc = find_tc(init_tcs(), fields.first); 639 640 try { 641 switch (fields.second) { 642 case BODY: 643 tc->run(m_resfile); 644 break; 645 case CLEANUP: 646 tc->run_cleanup(); 647 break; 648 default: 649 UNREACHABLE; 650 } 651 return EXIT_SUCCESS; 652 } catch (const std::runtime_error& e) { 653 std::cerr << "ERROR: " << e.what() << "\n"; 654 return EXIT_FAILURE; 655 } 656 } 657 658 int 659 tp::main(void) 660 { 661 using atf::application::usage_error; 662 663 int errcode; 664 665 handle_srcdir(); 666 667 if (m_lflag) { 668 if (m_argc > 0) 669 throw usage_error("Cannot provide test case names with -l"); 670 671 list_tcs(); 672 errcode = EXIT_SUCCESS; 673 } else { 674 if (m_argc == 0) 675 throw usage_error("Must provide a test case name"); 676 else if (m_argc > 1) 677 throw usage_error("Cannot provide more than one test case name"); 678 INV(m_argc == 1); 679 680 errcode = run_tc(m_argv[0]); 681 } 682 683 return errcode; 684 } 685 686 namespace atf { 687 namespace tests { 688 int run_tp(int, char* const*, void (*)(tp::tc_vector&)); 689 } 690 } 691 692 int 693 impl::run_tp(int argc, char* const* argv, void (*add_tcs)(tp::tc_vector&)) 694 { 695 return tp(add_tcs).run(argc, argv); 696 } 697