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