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