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/stat.h> 33 #include <sys/time.h> 34 35 #include <fcntl.h> 36 #include <signal.h> 37 #include <unistd.h> 38 } 39 40 #include <cerrno> 41 #include <cstdlib> 42 #include <cstring> 43 #include <fstream> 44 #include <iostream> 45 46 #include "atf-c/defs.h" 47 48 #include "atf-c++/detail/env.hpp" 49 #include "atf-c++/detail/parser.hpp" 50 #include "atf-c++/detail/process.hpp" 51 #include "atf-c++/detail/sanity.hpp" 52 #include "atf-c++/detail/text.hpp" 53 54 #include "config.hpp" 55 #include "fs.hpp" 56 #include "io.hpp" 57 #include "requirements.hpp" 58 #include "signals.hpp" 59 #include "test-program.hpp" 60 #include "timer.hpp" 61 #include "user.hpp" 62 63 namespace impl = atf::atf_run; 64 namespace detail = atf::atf_run::detail; 65 66 namespace { 67 68 static void 69 check_stream(std::ostream& os) 70 { 71 // If we receive a signal while writing to the stream, the bad bit gets set. 72 // Things seem to behave fine afterwards if we clear such error condition. 73 // However, I'm not sure if it's safe to query errno at this point. 74 if (os.bad()) { 75 if (errno == EINTR) 76 os.clear(); 77 else 78 throw std::runtime_error("Failed"); 79 } 80 } 81 82 namespace atf_tp { 83 84 static const atf::parser::token_type eof_type = 0; 85 static const atf::parser::token_type nl_type = 1; 86 static const atf::parser::token_type text_type = 2; 87 static const atf::parser::token_type colon_type = 3; 88 static const atf::parser::token_type dblquote_type = 4; 89 90 class tokenizer : public atf::parser::tokenizer< std::istream > { 91 public: 92 tokenizer(std::istream& is, size_t curline) : 93 atf::parser::tokenizer< std::istream > 94 (is, true, eof_type, nl_type, text_type, curline) 95 { 96 add_delim(':', colon_type); 97 add_quote('"', dblquote_type); 98 } 99 }; 100 101 } // namespace atf_tp 102 103 class metadata_reader : public detail::atf_tp_reader { 104 impl::test_cases_map m_tcs; 105 106 void got_tc(const std::string& ident, const atf::tests::vars_map& props) 107 { 108 if (m_tcs.find(ident) != m_tcs.end()) 109 throw(std::runtime_error("Duplicate test case " + ident + 110 " in test program")); 111 m_tcs[ident] = props; 112 113 if (m_tcs[ident].find("has.cleanup") == m_tcs[ident].end()) 114 m_tcs[ident].insert(std::make_pair("has.cleanup", "false")); 115 116 if (m_tcs[ident].find("timeout") == m_tcs[ident].end()) 117 m_tcs[ident].insert(std::make_pair("timeout", "300")); 118 } 119 120 public: 121 metadata_reader(std::istream& is) : 122 detail::atf_tp_reader(is) 123 { 124 } 125 126 const impl::test_cases_map& 127 get_tcs(void) 128 const 129 { 130 return m_tcs; 131 } 132 }; 133 134 struct get_metadata_params { 135 const atf::fs::path& executable; 136 const atf::tests::vars_map& config; 137 138 get_metadata_params(const atf::fs::path& p_executable, 139 const atf::tests::vars_map& p_config) : 140 executable(p_executable), 141 config(p_config) 142 { 143 } 144 }; 145 146 struct test_case_params { 147 const atf::fs::path& executable; 148 const std::string& test_case_name; 149 const std::string& test_case_part; 150 const atf::tests::vars_map& metadata; 151 const atf::tests::vars_map& config; 152 const atf::fs::path& resfile; 153 const atf::fs::path& workdir; 154 155 test_case_params(const atf::fs::path& p_executable, 156 const std::string& p_test_case_name, 157 const std::string& p_test_case_part, 158 const atf::tests::vars_map& p_metadata, 159 const atf::tests::vars_map& p_config, 160 const atf::fs::path& p_resfile, 161 const atf::fs::path& p_workdir) : 162 executable(p_executable), 163 test_case_name(p_test_case_name), 164 test_case_part(p_test_case_part), 165 metadata(p_metadata), 166 config(p_config), 167 resfile(p_resfile), 168 workdir(p_workdir) 169 { 170 } 171 }; 172 173 static 174 std::string 175 generate_timestamp(void) 176 { 177 struct timeval tv; 178 if (gettimeofday(&tv, NULL) == -1) 179 return "0.0"; 180 181 char buf[32]; 182 const int len = snprintf(buf, sizeof(buf), "%ld.%ld", 183 static_cast< long >(tv.tv_sec), 184 static_cast< long >(tv.tv_usec)); 185 if (len >= static_cast< int >(sizeof(buf)) || len < 0) 186 return "0.0"; 187 else 188 return buf; 189 } 190 191 static 192 void 193 append_to_vector(std::vector< std::string >& v1, 194 const std::vector< std::string >& v2) 195 { 196 std::copy(v2.begin(), v2.end(), 197 std::back_insert_iterator< std::vector< std::string > >(v1)); 198 } 199 200 static 201 char** 202 vector_to_argv(const std::vector< std::string >& v) 203 { 204 char** argv = new char*[v.size() + 1]; 205 for (std::vector< std::string >::size_type i = 0; i < v.size(); i++) { 206 argv[i] = strdup(v[i].c_str()); 207 } 208 argv[v.size()] = NULL; 209 return argv; 210 } 211 212 static 213 void 214 exec_or_exit(const atf::fs::path& executable, 215 const std::vector< std::string >& argv) 216 { 217 // This leaks memory in case of a failure, but it is OK. Exiting will 218 // do the necessary cleanup. 219 char* const* native_argv = vector_to_argv(argv); 220 221 ::execv(executable.c_str(), native_argv); 222 223 const std::string message = "Failed to execute '" + executable.str() + 224 "': " + std::strerror(errno) + "\n"; 225 if (::write(STDERR_FILENO, message.c_str(), message.length()) == -1) 226 std::abort(); 227 std::exit(EXIT_FAILURE); 228 } 229 230 static 231 std::vector< std::string > 232 config_to_args(const atf::tests::vars_map& config) 233 { 234 std::vector< std::string > args; 235 236 for (atf::tests::vars_map::const_iterator iter = config.begin(); 237 iter != config.end(); iter++) 238 args.push_back("-v" + (*iter).first + "=" + (*iter).second); 239 240 return args; 241 } 242 243 static 244 void 245 silence_stdin(void) 246 { 247 ::close(STDIN_FILENO); 248 int fd = ::open("/dev/null", O_RDONLY); 249 if (fd == -1) 250 throw std::runtime_error("Could not open /dev/null"); 251 INV(fd == STDIN_FILENO); 252 } 253 254 static 255 void 256 prepare_child(const atf::fs::path& workdir) 257 { 258 const int ret = ::setpgid(::getpid(), 0); 259 INV(ret != -1); 260 261 ::umask(S_IWGRP | S_IWOTH); 262 263 for (int i = 1; i <= impl::last_signo; i++) 264 impl::reset(i); 265 266 atf::env::set("HOME", workdir.str()); 267 atf::env::unset("LANG"); 268 atf::env::unset("LC_ALL"); 269 atf::env::unset("LC_COLLATE"); 270 atf::env::unset("LC_CTYPE"); 271 atf::env::unset("LC_MESSAGES"); 272 atf::env::unset("LC_MONETARY"); 273 atf::env::unset("LC_NUMERIC"); 274 atf::env::unset("LC_TIME"); 275 atf::env::set("TZ", "UTC"); 276 277 atf::env::set("__RUNNING_INSIDE_ATF_RUN", "internal-yes-value"); 278 279 impl::change_directory(workdir); 280 281 silence_stdin(); 282 } 283 284 static 285 void 286 get_metadata_child(void* raw_params) 287 { 288 const get_metadata_params* params = 289 static_cast< const get_metadata_params* >(raw_params); 290 291 std::vector< std::string > argv; 292 argv.push_back(params->executable.leaf_name()); 293 argv.push_back("-l"); 294 argv.push_back("-s" + params->executable.branch_path().str()); 295 append_to_vector(argv, config_to_args(params->config)); 296 297 exec_or_exit(params->executable, argv); 298 } 299 300 void 301 run_test_case_child(void* raw_params) 302 { 303 const test_case_params* params = 304 static_cast< const test_case_params* >(raw_params); 305 306 const std::pair< int, int > user = impl::get_required_user( 307 params->metadata, params->config); 308 if (user.first != -1 && user.second != -1) 309 impl::drop_privileges(user); 310 311 // The input 'tp' parameter may be relative and become invalid once 312 // we change the current working directory. 313 const atf::fs::path absolute_executable = params->executable.to_absolute(); 314 315 // Prepare the test program's arguments. We use dynamic memory and 316 // do not care to release it. We are going to die anyway very soon, 317 // either due to exec(2) or to exit(3). 318 std::vector< std::string > argv; 319 argv.push_back(absolute_executable.leaf_name()); 320 argv.push_back("-r" + params->resfile.str()); 321 argv.push_back("-s" + absolute_executable.branch_path().str()); 322 append_to_vector(argv, config_to_args(params->config)); 323 argv.push_back(params->test_case_name + ":" + params->test_case_part); 324 325 prepare_child(params->workdir); 326 exec_or_exit(absolute_executable, argv); 327 } 328 329 static void 330 tokenize_result(const std::string& line, std::string& out_state, 331 std::string& out_arg, std::string& out_reason) 332 { 333 const std::string::size_type pos = line.find_first_of(":("); 334 if (pos == std::string::npos) { 335 out_state = line; 336 out_arg = ""; 337 out_reason = ""; 338 } else if (line[pos] == ':') { 339 out_state = line.substr(0, pos); 340 out_arg = ""; 341 out_reason = atf::text::trim(line.substr(pos + 1)); 342 } else if (line[pos] == '(') { 343 const std::string::size_type pos2 = line.find("):", pos); 344 if (pos2 == std::string::npos) 345 throw std::runtime_error("Invalid test case result '" + line + 346 "': unclosed optional argument"); 347 out_state = line.substr(0, pos); 348 out_arg = line.substr(pos + 1, pos2 - pos - 1); 349 out_reason = atf::text::trim(line.substr(pos2 + 2)); 350 } else 351 UNREACHABLE; 352 } 353 354 static impl::test_case_result 355 handle_result(const std::string& state, const std::string& arg, 356 const std::string& reason) 357 { 358 PRE(state == "passed"); 359 360 if (!arg.empty() || !reason.empty()) 361 throw std::runtime_error("The test case result '" + state + "' cannot " 362 "be accompanied by a reason nor an expected value"); 363 364 return impl::test_case_result(state, -1, reason); 365 } 366 367 static impl::test_case_result 368 handle_result_with_reason(const std::string& state, const std::string& arg, 369 const std::string& reason) 370 { 371 PRE(state == "expected_death" || state == "expected_failure" || 372 state == "expected_timeout" || state == "failed" || state == "skipped"); 373 374 if (!arg.empty() || reason.empty()) 375 throw std::runtime_error("The test case result '" + state + "' must " 376 "be accompanied by a reason but not by an expected value"); 377 378 return impl::test_case_result(state, -1, reason); 379 } 380 381 static impl::test_case_result 382 handle_result_with_reason_and_arg(const std::string& state, 383 const std::string& arg, 384 const std::string& reason) 385 { 386 PRE(state == "expected_exit" || state == "expected_signal"); 387 388 if (reason.empty()) 389 throw std::runtime_error("The test case result '" + state + "' must " 390 "be accompanied by a reason"); 391 392 int value; 393 if (arg.empty()) { 394 value = -1; 395 } else { 396 try { 397 value = atf::text::to_type< int >(arg); 398 } catch (const std::runtime_error&) { 399 throw std::runtime_error("The value '" + arg + "' passed to the '" + 400 state + "' state must be an integer"); 401 } 402 } 403 404 return impl::test_case_result(state, value, reason); 405 } 406 407 } // anonymous namespace 408 409 detail::atf_tp_reader::atf_tp_reader(std::istream& is) : 410 m_is(is) 411 { 412 } 413 414 detail::atf_tp_reader::~atf_tp_reader(void) 415 { 416 } 417 418 void 419 detail::atf_tp_reader::got_tc( 420 const std::string& ident ATF_DEFS_ATTRIBUTE_UNUSED, 421 const std::map< std::string, std::string >& md ATF_DEFS_ATTRIBUTE_UNUSED) 422 { 423 } 424 425 void 426 detail::atf_tp_reader::got_eof(void) 427 { 428 } 429 430 void 431 detail::atf_tp_reader::validate_and_insert(const std::string& name, 432 const std::string& value, const size_t lineno, 433 std::map< std::string, std::string >& md) 434 { 435 using atf::parser::parse_error; 436 437 if (value.empty()) 438 throw parse_error(lineno, "The value for '" + name +"' cannot be " 439 "empty"); 440 441 const std::string ident_regex = "^[_A-Za-z0-9]+$"; 442 const std::string integer_regex = "^[0-9]+$"; 443 444 if (name == "descr") { 445 // Any non-empty value is valid. 446 } else if (name == "has.cleanup") { 447 try { 448 (void)atf::text::to_bool(value); 449 } catch (const std::runtime_error&) { 450 throw parse_error(lineno, "The has.cleanup property requires a" 451 " boolean value"); 452 } 453 } else if (name == "ident") { 454 if (!atf::text::match(value, ident_regex)) 455 throw parse_error(lineno, "The identifier must match " + 456 ident_regex + "; was '" + value + "'"); 457 } else if (name == "require.arch") { 458 } else if (name == "require.config") { 459 } else if (name == "require.files") { 460 } else if (name == "require.machine") { 461 } else if (name == "require.memory") { 462 try { 463 (void)atf::text::to_bytes(value); 464 } catch (const std::runtime_error&) { 465 throw parse_error(lineno, "The require.memory property requires an " 466 "integer value representing an amount of bytes"); 467 } 468 } else if (name == "require.progs") { 469 } else if (name == "require.user") { 470 } else if (name == "timeout") { 471 if (!atf::text::match(value, integer_regex)) 472 throw parse_error(lineno, "The timeout property requires an integer" 473 " value"); 474 } else if (name == "use.fs") { 475 // Deprecated; ignore it. 476 } else if (name.size() > 2 && name[0] == 'X' && name[1] == '-') { 477 // Any non-empty value is valid. 478 } else { 479 throw parse_error(lineno, "Unknown property '" + name + "'"); 480 } 481 482 md.insert(std::make_pair(name, value)); 483 } 484 485 void 486 detail::atf_tp_reader::read(void) 487 { 488 using atf::parser::parse_error; 489 using namespace atf_tp; 490 491 std::pair< size_t, atf::parser::headers_map > hml = 492 atf::parser::read_headers(m_is, 1); 493 atf::parser::validate_content_type(hml.second, 494 "application/X-atf-tp", 1); 495 496 tokenizer tkz(m_is, hml.first); 497 atf::parser::parser< tokenizer > p(tkz); 498 499 try { 500 atf::parser::token t = p.expect(text_type, "property name"); 501 if (t.text() != "ident") 502 throw parse_error(t.lineno(), "First property of a test case " 503 "must be 'ident'"); 504 505 std::map< std::string, std::string > props; 506 do { 507 const std::string name = t.text(); 508 t = p.expect(colon_type, "`:'"); 509 const std::string value = atf::text::trim(p.rest_of_line()); 510 t = p.expect(nl_type, "new line"); 511 validate_and_insert(name, value, t.lineno(), props); 512 513 t = p.expect(eof_type, nl_type, text_type, "property name, new " 514 "line or eof"); 515 if (t.type() == nl_type || t.type() == eof_type) { 516 const std::map< std::string, std::string >::const_iterator 517 iter = props.find("ident"); 518 if (iter == props.end()) 519 throw parse_error(t.lineno(), "Test case definition did " 520 "not define an 'ident' property"); 521 ATF_PARSER_CALLBACK(p, got_tc((*iter).second, props)); 522 props.clear(); 523 524 if (t.type() == nl_type) { 525 t = p.expect(text_type, "property name"); 526 if (t.text() != "ident") 527 throw parse_error(t.lineno(), "First property of a " 528 "test case must be 'ident'"); 529 } 530 } 531 } while (t.type() != eof_type); 532 ATF_PARSER_CALLBACK(p, got_eof()); 533 } catch (const parse_error& pe) { 534 p.add_error(pe); 535 p.reset(nl_type); 536 } 537 } 538 539 impl::test_case_result 540 detail::parse_test_case_result(const std::string& line) 541 { 542 std::string state, arg, reason; 543 tokenize_result(line, state, arg, reason); 544 545 if (state == "expected_death") 546 return handle_result_with_reason(state, arg, reason); 547 else if (state.compare(0, 13, "expected_exit") == 0) 548 return handle_result_with_reason_and_arg(state, arg, reason); 549 else if (state.compare(0, 16, "expected_failure") == 0) 550 return handle_result_with_reason(state, arg, reason); 551 else if (state.compare(0, 15, "expected_signal") == 0) 552 return handle_result_with_reason_and_arg(state, arg, reason); 553 else if (state.compare(0, 16, "expected_timeout") == 0) 554 return handle_result_with_reason(state, arg, reason); 555 else if (state == "failed") 556 return handle_result_with_reason(state, arg, reason); 557 else if (state == "passed") 558 return handle_result(state, arg, reason); 559 else if (state == "skipped") 560 return handle_result_with_reason(state, arg, reason); 561 else 562 throw std::runtime_error("Unknown test case result type in: " + line); 563 } 564 565 impl::atf_tps_writer::atf_tps_writer(std::ostream& os) : 566 m_os(os) 567 { 568 atf::parser::headers_map hm; 569 atf::parser::attrs_map ct_attrs; 570 ct_attrs["version"] = "3"; 571 hm["Content-Type"] = 572 atf::parser::header_entry("Content-Type", "application/X-atf-tps", 573 ct_attrs); 574 atf::parser::write_headers(hm, m_os); 575 } 576 577 void 578 impl::atf_tps_writer::info(const std::string& what, const std::string& val) 579 { 580 m_os << "info: " << what << ", " << val << "\n"; 581 m_os.flush(); 582 } 583 584 void 585 impl::atf_tps_writer::ntps(size_t p_ntps) 586 { 587 m_os << "tps-count: " << p_ntps << "\n"; 588 m_os.flush(); 589 } 590 591 void 592 impl::atf_tps_writer::start_tp(const std::string& tp, size_t ntcs) 593 { 594 m_tpname = tp; 595 m_os << "tp-start: " << generate_timestamp() << ", " << tp << ", " 596 << ntcs << "\n"; 597 m_os.flush(); 598 } 599 600 void 601 impl::atf_tps_writer::end_tp(const std::string& reason) 602 { 603 PRE(reason.find('\n') == std::string::npos); 604 if (reason.empty()) 605 m_os << "tp-end: " << generate_timestamp() << ", " << m_tpname << "\n"; 606 else 607 m_os << "tp-end: " << generate_timestamp() << ", " << m_tpname 608 << ", " << reason << "\n"; 609 m_os.flush(); 610 } 611 612 void 613 impl::atf_tps_writer::start_tc(const std::string& tcname) 614 { 615 m_tcname = tcname; 616 m_os << "tc-start: " << generate_timestamp() << ", " << tcname << "\n"; 617 m_os.flush(); 618 } 619 620 void 621 impl::atf_tps_writer::stdout_tc(const std::string& line) 622 { 623 m_os << "tc-so:" << line << "\n"; 624 check_stream(m_os); 625 m_os.flush(); 626 check_stream(m_os); 627 } 628 629 void 630 impl::atf_tps_writer::stderr_tc(const std::string& line) 631 { 632 m_os << "tc-se:" << line << "\n"; 633 check_stream(m_os); 634 m_os.flush(); 635 check_stream(m_os); 636 } 637 638 void 639 impl::atf_tps_writer::end_tc(const std::string& state, 640 const std::string& reason) 641 { 642 std::string str = ", " + m_tcname + ", " + state; 643 if (!reason.empty()) 644 str += ", " + reason; 645 m_os << "tc-end: " << generate_timestamp() << str << "\n"; 646 m_os.flush(); 647 } 648 649 impl::metadata 650 impl::get_metadata(const atf::fs::path& executable, 651 const atf::tests::vars_map& config) 652 { 653 get_metadata_params params(executable, config); 654 atf::process::child child = 655 atf::process::fork(get_metadata_child, 656 atf::process::stream_capture(), 657 atf::process::stream_inherit(), 658 static_cast< void * >(¶ms)); 659 660 impl::pistream outin(child.stdout_fd()); 661 662 metadata_reader parser(outin); 663 parser.read(); 664 665 const atf::process::status status = child.wait(); 666 if (!status.exited() || status.exitstatus() != EXIT_SUCCESS) 667 throw atf::parser::format_error("Test program returned failure " 668 "exit status for test case list"); 669 670 return metadata(parser.get_tcs()); 671 } 672 673 impl::test_case_result 674 impl::read_test_case_result(const atf::fs::path& results_path) 675 { 676 std::ifstream results_file(results_path.c_str()); 677 if (!results_file) 678 throw std::runtime_error("Failed to open " + results_path.str()); 679 680 std::string line, extra_line; 681 std::getline(results_file, line); 682 if (!results_file.good()) 683 throw std::runtime_error("Results file is empty"); 684 685 while (std::getline(results_file, extra_line).good()) 686 line += "<<NEWLINE UNEXPECTED>>" + extra_line; 687 688 results_file.close(); 689 690 return detail::parse_test_case_result(line); 691 } 692 693 namespace { 694 695 static volatile bool terminate_poll; 696 697 static void 698 sigchld_handler(const int signo ATF_DEFS_ATTRIBUTE_UNUSED) 699 { 700 terminate_poll = true; 701 } 702 703 class child_muxer : public impl::muxer { 704 impl::atf_tps_writer& m_writer; 705 706 void 707 line_callback(const size_t index, const std::string& line) 708 { 709 switch (index) { 710 case 0: m_writer.stdout_tc(line); break; 711 case 1: m_writer.stderr_tc(line); break; 712 default: UNREACHABLE; 713 } 714 } 715 716 public: 717 child_muxer(const int* fds, const size_t nfds, 718 impl::atf_tps_writer& writer) : 719 muxer(fds, nfds), 720 m_writer(writer) 721 { 722 } 723 }; 724 725 } // anonymous namespace 726 727 std::pair< std::string, atf::process::status > 728 impl::run_test_case(const atf::fs::path& executable, 729 const std::string& test_case_name, 730 const std::string& test_case_part, 731 const atf::tests::vars_map& metadata, 732 const atf::tests::vars_map& config, 733 const atf::fs::path& resfile, 734 const atf::fs::path& workdir, 735 atf_tps_writer& writer) 736 { 737 // TODO: Capture termination signals and deliver them to the subprocess 738 // instead. Or maybe do something else; think about it. 739 740 test_case_params params(executable, test_case_name, test_case_part, 741 metadata, config, resfile, workdir); 742 atf::process::child child = 743 atf::process::fork(run_test_case_child, 744 atf::process::stream_capture(), 745 atf::process::stream_capture(), 746 static_cast< void * >(¶ms)); 747 748 terminate_poll = false; 749 750 const atf::tests::vars_map::const_iterator iter = metadata.find("timeout"); 751 INV(iter != metadata.end()); 752 const unsigned int timeout = 753 atf::text::to_type< unsigned int >((*iter).second); 754 const pid_t child_pid = child.pid(); 755 756 // Get the input stream of stdout and stderr. 757 impl::file_handle outfh = child.stdout_fd(); 758 impl::file_handle errfh = child.stderr_fd(); 759 760 bool timed_out = false; 761 762 // Process the test case's output and multiplex it into our output 763 // stream as we read it. 764 int fds[2] = {outfh.get(), errfh.get()}; 765 child_muxer mux(fds, 2, writer); 766 try { 767 child_timer timeout_timer(timeout, child_pid, terminate_poll); 768 signal_programmer sigchld(SIGCHLD, sigchld_handler); 769 mux.mux(terminate_poll); 770 timed_out = timeout_timer.fired(); 771 } catch (...) { 772 UNREACHABLE; 773 } 774 775 ::killpg(child_pid, SIGKILL); 776 mux.flush(); 777 atf::process::status status = child.wait(); 778 779 std::string reason; 780 781 if (timed_out) { 782 // Don't assume the child process has been signaled due to the timeout 783 // expiration as older versions did. The child process may have exited 784 // but we may have timed out due to a subchild process getting stuck. 785 reason = "Test case timed out after " + atf::text::to_string(timeout) + 786 " " + (timeout == 1 ? "second" : "seconds"); 787 } 788 789 return std::make_pair(reason, status); 790 } 791