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 30extern "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 63namespace impl = atf::atf_run; 64namespace detail = atf::atf_run::detail; 65 66namespace { 67 68static void 69check_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 82namespace atf_tp { 83 84static const atf::parser::token_type eof_type = 0; 85static const atf::parser::token_type nl_type = 1; 86static const atf::parser::token_type text_type = 2; 87static const atf::parser::token_type colon_type = 3; 88static const atf::parser::token_type dblquote_type = 4; 89 90class tokenizer : public atf::parser::tokenizer< std::istream > { 91public: 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 103class 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 120public: 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 134struct 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 146struct 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 173static 174std::string 175generate_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 191static 192void 193append_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 200static 201char** 202vector_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 212static 213void 214exec_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 230static 231std::vector< std::string > 232config_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 243static 244void 245silence_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 254static 255void 256prepare_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 284static 285void 286get_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 300void 301run_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 329static void 330tokenize_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 354static impl::test_case_result 355handle_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 367static impl::test_case_result 368handle_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 381static impl::test_case_result 382handle_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 409detail::atf_tp_reader::atf_tp_reader(std::istream& is) : 410 m_is(is) 411{ 412} 413 414detail::atf_tp_reader::~atf_tp_reader(void) 415{ 416} 417 418void 419detail::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 425void 426detail::atf_tp_reader::got_eof(void) 427{ 428} 429 430void 431detail::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 485void 486detail::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 539impl::test_case_result 540detail::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 565impl::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 577void 578impl::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 584void 585impl::atf_tps_writer::ntps(size_t p_ntps) 586{ 587 m_os << "tps-count: " << p_ntps << "\n"; 588 m_os.flush(); 589} 590 591void 592impl::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 600void 601impl::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 612void 613impl::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 620void 621impl::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 629void 630impl::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 638void 639impl::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 649impl::metadata 650impl::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 673impl::test_case_result 674impl::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 693namespace { 694 695static volatile bool terminate_poll; 696 697static void 698sigchld_handler(const int signo ATF_DEFS_ATTRIBUTE_UNUSED) 699{ 700 terminate_poll = true; 701} 702 703class 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 716public: 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 727std::pair< std::string, atf::process::status > 728impl::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