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