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 * >(&params));
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 * >(&params));
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