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 const int ret = ::setpgid(::getpid(), 0); 258 assert(ret != -1); 259 260 ::umask(S_IWGRP | S_IWOTH); 261 262 for (int i = 1; i <= tools::signals::last_signo; i++) 263 tools::signals::reset(i); 264 265 tools::env::set("HOME", workdir.str()); 266 tools::env::unset("LANG"); 267 tools::env::unset("LC_ALL"); 268 tools::env::unset("LC_COLLATE"); 269 tools::env::unset("LC_CTYPE"); 270 tools::env::unset("LC_MESSAGES"); 271 tools::env::unset("LC_MONETARY"); 272 tools::env::unset("LC_NUMERIC"); 273 tools::env::unset("LC_TIME"); 274 tools::env::set("TZ", "UTC"); 275 276 tools::env::set("__RUNNING_INSIDE_ATF_RUN", "internal-yes-value"); 277 278 tools::fs::change_directory(workdir); 279 280 silence_stdin(); 281 } 282 283 static 284 void 285 get_metadata_child(void* raw_params) 286 { 287 const get_metadata_params* params = 288 static_cast< const get_metadata_params* >(raw_params); 289 290 std::vector< std::string > argv; 291 argv.push_back(params->executable.leaf_name()); 292 argv.push_back("-l"); 293 argv.push_back("-s" + params->executable.branch_path().str()); 294 append_to_vector(argv, config_to_args(params->config)); 295 296 exec_or_exit(params->executable, argv); 297 } 298 299 void 300 run_test_case_child(void* raw_params) 301 { 302 const test_case_params* params = 303 static_cast< const test_case_params* >(raw_params); 304 305 const std::pair< int, int > user = tools::get_required_user( 306 params->metadata, params->config); 307 if (user.first != -1 && user.second != -1) 308 tools::user::drop_privileges(user); 309 310 // The input 'tp' parameter may be relative and become invalid once 311 // we change the current working directory. 312 const tools::fs::path absolute_executable = params->executable.to_absolute(); 313 314 // Prepare the test program's arguments. We use dynamic memory and 315 // do not care to release it. We are going to die anyway very soon, 316 // either due to exec(2) or to exit(3). 317 std::vector< std::string > argv; 318 argv.push_back(absolute_executable.leaf_name()); 319 argv.push_back("-r" + params->resfile.str()); 320 argv.push_back("-s" + absolute_executable.branch_path().str()); 321 append_to_vector(argv, config_to_args(params->config)); 322 argv.push_back(params->test_case_name + ":" + params->test_case_part); 323 324 prepare_child(params->workdir); 325 exec_or_exit(absolute_executable, argv); 326 } 327 328 static void 329 tokenize_result(const std::string& line, std::string& out_state, 330 std::string& out_arg, std::string& out_reason) 331 { 332 const std::string::size_type pos = line.find_first_of(":("); 333 if (pos == std::string::npos) { 334 out_state = line; 335 out_arg = ""; 336 out_reason = ""; 337 } else if (line[pos] == ':') { 338 out_state = line.substr(0, pos); 339 out_arg = ""; 340 out_reason = tools::text::trim(line.substr(pos + 1)); 341 } else if (line[pos] == '(') { 342 const std::string::size_type pos2 = line.find("):", pos); 343 if (pos2 == std::string::npos) 344 throw std::runtime_error("Invalid test case result '" + line + 345 "': unclosed optional argument"); 346 out_state = line.substr(0, pos); 347 out_arg = line.substr(pos + 1, pos2 - pos - 1); 348 out_reason = tools::text::trim(line.substr(pos2 + 2)); 349 } else 350 std::abort(); 351 } 352 353 static impl::test_case_result 354 handle_result(const std::string& state, const std::string& arg, 355 const std::string& reason) 356 { 357 assert(state == "passed"); 358 359 if (!arg.empty() || !reason.empty()) 360 throw std::runtime_error("The test case result '" + state + "' cannot " 361 "be accompanied by a reason nor an expected value"); 362 363 return impl::test_case_result(state, -1, reason); 364 } 365 366 static impl::test_case_result 367 handle_result_with_reason(const std::string& state, const std::string& arg, 368 const std::string& reason) 369 { 370 assert(state == "expected_death" || state == "expected_failure" || 371 state == "expected_timeout" || state == "failed" || state == "skipped"); 372 373 if (!arg.empty() || reason.empty()) 374 throw std::runtime_error("The test case result '" + state + "' must " 375 "be accompanied by a reason but not by an expected value"); 376 377 return impl::test_case_result(state, -1, reason); 378 } 379 380 static impl::test_case_result 381 handle_result_with_reason_and_arg(const std::string& state, 382 const std::string& arg, 383 const std::string& reason) 384 { 385 assert(state == "expected_exit" || state == "expected_signal"); 386 387 if (reason.empty()) 388 throw std::runtime_error("The test case result '" + state + "' must " 389 "be accompanied by a reason"); 390 391 int value; 392 if (arg.empty()) { 393 value = -1; 394 } else { 395 try { 396 value = tools::text::to_type< int >(arg); 397 } catch (const std::runtime_error&) { 398 throw std::runtime_error("The value '" + arg + "' passed to the '" + 399 state + "' state must be an integer"); 400 } 401 } 402 403 return impl::test_case_result(state, value, reason); 404 } 405 406 } // anonymous namespace 407 408 detail::atf_tp_reader::atf_tp_reader(std::istream& is) : 409 m_is(is) 410 { 411 } 412 413 detail::atf_tp_reader::~atf_tp_reader(void) 414 { 415 } 416 417 void 418 detail::atf_tp_reader::got_tc( 419 const std::string& ident __attribute__((__unused__)), 420 const std::map< std::string, std::string >& md __attribute__((__unused__))) 421 { 422 } 423 424 void 425 detail::atf_tp_reader::got_eof(void) 426 { 427 } 428 429 void 430 detail::atf_tp_reader::validate_and_insert(const std::string& name, 431 const std::string& value, const size_t lineno, 432 std::map< std::string, std::string >& md) 433 { 434 using tools::parser::parse_error; 435 436 if (value.empty()) 437 throw parse_error(lineno, "The value for '" + name +"' cannot be " 438 "empty"); 439 440 const std::string ident_regex = "^[_A-Za-z0-9]+$"; 441 const std::string integer_regex = "^[0-9]+$"; 442 443 if (name == "descr") { 444 // Any non-empty value is valid. 445 } else if (name == "has.cleanup") { 446 try { 447 (void)tools::text::to_bool(value); 448 } catch (const std::runtime_error&) { 449 throw parse_error(lineno, "The has.cleanup property requires a" 450 " boolean value"); 451 } 452 } else if (name == "ident") { 453 if (!tools::text::match(value, ident_regex)) 454 throw parse_error(lineno, "The identifier must match " + 455 ident_regex + "; was '" + value + "'"); 456 } else if (name == "require.arch") { 457 } else if (name == "require.config") { 458 } else if (name == "require.files") { 459 } else if (name == "require.machine") { 460 } else if (name == "require.memory") { 461 try { 462 (void)tools::text::to_bytes(value); 463 } catch (const std::runtime_error&) { 464 throw parse_error(lineno, "The require.memory property requires an " 465 "integer value representing an amount of bytes"); 466 } 467 } else if (name == "require.progs") { 468 } else if (name == "require.user") { 469 } else if (name == "timeout") { 470 if (!tools::text::match(value, integer_regex)) 471 throw parse_error(lineno, "The timeout property requires an integer" 472 " value"); 473 } else if (name == "use.fs") { 474 // Deprecated; ignore it. 475 } else if (name.size() > 2 && name[0] == 'X' && name[1] == '-') { 476 // Any non-empty value is valid. 477 } else { 478 throw parse_error(lineno, "Unknown property '" + name + "'"); 479 } 480 481 md.insert(std::make_pair(name, value)); 482 } 483 484 void 485 detail::atf_tp_reader::read(void) 486 { 487 using tools::parser::parse_error; 488 using namespace atf_tp; 489 490 std::pair< size_t, tools::parser::headers_map > hml = 491 tools::parser::read_headers(m_is, 1); 492 tools::parser::validate_content_type(hml.second, 493 "application/X-atf-tp", 1); 494 495 tokenizer tkz(m_is, hml.first); 496 tools::parser::parser< tokenizer > p(tkz); 497 498 try { 499 tools::parser::token t = p.expect(text_type, "property name"); 500 if (t.text() != "ident") 501 throw parse_error(t.lineno(), "First property of a test case " 502 "must be 'ident'"); 503 504 std::map< std::string, std::string > props; 505 do { 506 const std::string name = t.text(); 507 t = p.expect(colon_type, "`:'"); 508 const std::string value = tools::text::trim(p.rest_of_line()); 509 t = p.expect(nl_type, "new line"); 510 validate_and_insert(name, value, t.lineno(), props); 511 512 t = p.expect(eof_type, nl_type, text_type, "property name, new " 513 "line or eof"); 514 if (t.type() == nl_type || t.type() == eof_type) { 515 const std::map< std::string, std::string >::const_iterator 516 iter = props.find("ident"); 517 if (iter == props.end()) 518 throw parse_error(t.lineno(), "Test case definition did " 519 "not define an 'ident' property"); 520 ATF_PARSER_CALLBACK(p, got_tc((*iter).second, props)); 521 props.clear(); 522 523 if (t.type() == nl_type) { 524 t = p.expect(text_type, "property name"); 525 if (t.text() != "ident") 526 throw parse_error(t.lineno(), "First property of a " 527 "test case must be 'ident'"); 528 } 529 } 530 } while (t.type() != eof_type); 531 ATF_PARSER_CALLBACK(p, got_eof()); 532 } catch (const parse_error& pe) { 533 p.add_error(pe); 534 p.reset(nl_type); 535 } 536 } 537 538 impl::test_case_result 539 detail::parse_test_case_result(const std::string& line) 540 { 541 std::string state, arg, reason; 542 tokenize_result(line, state, arg, reason); 543 544 if (state == "expected_death") 545 return handle_result_with_reason(state, arg, reason); 546 else if (state.compare(0, 13, "expected_exit") == 0) 547 return handle_result_with_reason_and_arg(state, arg, reason); 548 else if (state.compare(0, 16, "expected_failure") == 0) 549 return handle_result_with_reason(state, arg, reason); 550 else if (state.compare(0, 15, "expected_signal") == 0) 551 return handle_result_with_reason_and_arg(state, arg, reason); 552 else if (state.compare(0, 16, "expected_timeout") == 0) 553 return handle_result_with_reason(state, arg, reason); 554 else if (state == "failed") 555 return handle_result_with_reason(state, arg, reason); 556 else if (state == "passed") 557 return handle_result(state, arg, reason); 558 else if (state == "skipped") 559 return handle_result_with_reason(state, arg, reason); 560 else 561 throw std::runtime_error("Unknown test case result type in: " + line); 562 } 563 564 impl::atf_tps_writer::atf_tps_writer(std::ostream& os) : 565 m_os(os) 566 { 567 tools::parser::headers_map hm; 568 tools::parser::attrs_map ct_attrs; 569 ct_attrs["version"] = "3"; 570 hm["Content-Type"] = 571 tools::parser::header_entry("Content-Type", "application/X-atf-tps", 572 ct_attrs); 573 tools::parser::write_headers(hm, m_os); 574 } 575 576 void 577 impl::atf_tps_writer::info(const std::string& what, const std::string& val) 578 { 579 m_os << "info: " << what << ", " << val << "\n"; 580 m_os.flush(); 581 } 582 583 void 584 impl::atf_tps_writer::ntps(size_t p_ntps) 585 { 586 m_os << "tps-count: " << p_ntps << "\n"; 587 m_os.flush(); 588 } 589 590 void 591 impl::atf_tps_writer::start_tp(const std::string& tp, size_t ntcs) 592 { 593 m_tpname = tp; 594 m_os << "tp-start: " << generate_timestamp() << ", " << tp << ", " 595 << ntcs << "\n"; 596 m_os.flush(); 597 } 598 599 void 600 impl::atf_tps_writer::end_tp(const std::string& reason) 601 { 602 assert(reason.find('\n') == std::string::npos); 603 if (reason.empty()) 604 m_os << "tp-end: " << generate_timestamp() << ", " << m_tpname << "\n"; 605 else 606 m_os << "tp-end: " << generate_timestamp() << ", " << m_tpname 607 << ", " << reason << "\n"; 608 m_os.flush(); 609 } 610 611 void 612 impl::atf_tps_writer::start_tc(const std::string& tcname) 613 { 614 m_tcname = tcname; 615 m_os << "tc-start: " << generate_timestamp() << ", " << tcname << "\n"; 616 m_os.flush(); 617 } 618 619 void 620 impl::atf_tps_writer::stdout_tc(const std::string& line) 621 { 622 m_os << "tc-so:" << line << "\n"; 623 check_stream(m_os); 624 m_os.flush(); 625 check_stream(m_os); 626 } 627 628 void 629 impl::atf_tps_writer::stderr_tc(const std::string& line) 630 { 631 m_os << "tc-se:" << line << "\n"; 632 check_stream(m_os); 633 m_os.flush(); 634 check_stream(m_os); 635 } 636 637 void 638 impl::atf_tps_writer::end_tc(const std::string& state, 639 const std::string& reason) 640 { 641 std::string str = ", " + m_tcname + ", " + state; 642 if (!reason.empty()) 643 str += ", " + reason; 644 m_os << "tc-end: " << generate_timestamp() << str << "\n"; 645 m_os.flush(); 646 } 647 648 impl::metadata 649 impl::get_metadata(const tools::fs::path& executable, 650 const vars_map& config) 651 { 652 get_metadata_params params(executable, config); 653 tools::process::child child = 654 tools::process::fork(get_metadata_child, 655 tools::process::stream_capture(), 656 tools::process::stream_inherit(), 657 static_cast< void * >(¶ms)); 658 659 tools::io::pistream outin(child.stdout_fd()); 660 661 metadata_reader parser(outin); 662 parser.read(); 663 664 const tools::process::status status = child.wait(); 665 if (!status.exited() || status.exitstatus() != EXIT_SUCCESS) 666 throw tools::parser::format_error("Test program returned failure " 667 "exit status for test case list"); 668 669 return metadata(parser.get_tcs()); 670 } 671 672 impl::test_case_result 673 impl::read_test_case_result(const tools::fs::path& results_path) 674 { 675 std::ifstream results_file(results_path.c_str()); 676 if (!results_file) 677 throw std::runtime_error("Failed to open " + results_path.str()); 678 679 std::string line, extra_line; 680 std::getline(results_file, line); 681 if (!results_file.good()) 682 throw std::runtime_error("Results file is empty"); 683 684 while (std::getline(results_file, extra_line).good()) 685 line += "<<NEWLINE UNEXPECTED>>" + extra_line; 686 687 results_file.close(); 688 689 return detail::parse_test_case_result(line); 690 } 691 692 namespace { 693 694 static volatile bool terminate_poll; 695 696 static void 697 sigchld_handler(const int signo __attribute__((__unused__))) 698 { 699 terminate_poll = true; 700 } 701 702 class child_muxer : public tools::io::muxer { 703 impl::atf_tps_writer& m_writer; 704 705 void 706 line_callback(const size_t index, const std::string& line) 707 { 708 switch (index) { 709 case 0: m_writer.stdout_tc(line); break; 710 case 1: m_writer.stderr_tc(line); break; 711 default: std::abort(); 712 } 713 } 714 715 public: 716 child_muxer(const int* fds, const size_t nfds, 717 impl::atf_tps_writer& writer) : 718 muxer(fds, nfds), 719 m_writer(writer) 720 { 721 } 722 }; 723 724 } // anonymous namespace 725 726 std::pair< std::string, tools::process::status > 727 impl::run_test_case(const tools::fs::path& executable, 728 const std::string& test_case_name, 729 const std::string& test_case_part, 730 const vars_map& metadata, 731 const vars_map& config, 732 const tools::fs::path& resfile, 733 const tools::fs::path& workdir, 734 atf_tps_writer& writer) 735 { 736 // TODO: Capture termination signals and deliver them to the subprocess 737 // instead. Or maybe do something else; think about it. 738 739 test_case_params params(executable, test_case_name, test_case_part, 740 metadata, config, resfile, workdir); 741 tools::process::child child = 742 tools::process::fork(run_test_case_child, 743 tools::process::stream_capture(), 744 tools::process::stream_capture(), 745 static_cast< void * >(¶ms)); 746 747 terminate_poll = false; 748 749 const vars_map::const_iterator iter = metadata.find("timeout"); 750 assert(iter != metadata.end()); 751 const unsigned int timeout = 752 tools::text::to_type< unsigned int >((*iter).second); 753 const pid_t child_pid = child.pid(); 754 755 // Get the input stream of stdout and stderr. 756 tools::io::file_handle outfh = child.stdout_fd(); 757 tools::io::file_handle errfh = child.stderr_fd(); 758 759 bool timed_out = false; 760 761 // Process the test case's output and multiplex it into our output 762 // stream as we read it. 763 int fds[2] = {outfh.get(), errfh.get()}; 764 child_muxer mux(fds, 2, writer); 765 try { 766 timers::child_timer timeout_timer(timeout, child_pid, terminate_poll); 767 signals::signal_programmer sigchld(SIGCHLD, sigchld_handler); 768 mux.mux(terminate_poll); 769 timed_out = timeout_timer.fired(); 770 } catch (...) { 771 std::abort(); 772 } 773 774 ::killpg(child_pid, SIGKILL); 775 mux.flush(); 776 tools::process::status status = child.wait(); 777 778 std::string reason; 779 780 if (timed_out) { 781 // Don't assume the child process has been signaled due to the timeout 782 // expiration as older versions did. The child process may have exited 783 // but we may have timed out due to a subchild process getting stuck. 784 reason = "Test case timed out after " + tools::text::to_string(timeout) + 785 " " + (timeout == 1 ? "second" : "seconds"); 786 } 787 788 return std::make_pair(reason, status); 789 } 790