tools/llvm: Do not build with symbols
[minix3.git] / external / bsd / atf / dist / atf-run / test-program.cpp
blob1e52aa5254c5a9eba59ad5b5e7da65b151f18892
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.
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.
30 extern "C" {
31 #include <sys/types.h>
32 #include <sys/stat.h>
33 #include <sys/time.h>
35 #include <fcntl.h>
36 #include <signal.h>
37 #include <unistd.h>
40 #include <cerrno>
41 #include <cstdlib>
42 #include <cstring>
43 #include <fstream>
44 #include <iostream>
46 #include "atf-c/defs.h"
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"
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"
63 namespace impl = atf::atf_run;
64 namespace detail = atf::atf_run::detail;
66 namespace {
68 static void
69 check_stream(std::ostream& os)
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");
82 namespace atf_tp {
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;
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)
96 add_delim(':', colon_type);
97 add_quote('"', dblquote_type);
101 } // namespace atf_tp
103 class metadata_reader : public detail::atf_tp_reader {
104 impl::test_cases_map m_tcs;
106 void got_tc(const std::string& ident, const atf::tests::vars_map& props)
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;
113 if (m_tcs[ident].find("has.cleanup") == m_tcs[ident].end())
114 m_tcs[ident].insert(std::make_pair("has.cleanup", "false"));
116 if (m_tcs[ident].find("timeout") == m_tcs[ident].end())
117 m_tcs[ident].insert(std::make_pair("timeout", "300"));
120 public:
121 metadata_reader(std::istream& is) :
122 detail::atf_tp_reader(is)
126 const impl::test_cases_map&
127 get_tcs(void)
128 const
130 return m_tcs;
134 struct get_metadata_params {
135 const atf::fs::path& executable;
136 const atf::tests::vars_map& config;
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)
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;
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)
173 static
174 std::string
175 generate_timestamp(void)
177 struct timeval tv;
178 if (gettimeofday(&tv, NULL) == -1)
179 return "0.0";
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;
191 static
192 void
193 append_to_vector(std::vector< std::string >& v1,
194 const std::vector< std::string >& v2)
196 std::copy(v2.begin(), v2.end(),
197 std::back_insert_iterator< std::vector< std::string > >(v1));
200 static
201 char**
202 vector_to_argv(const std::vector< std::string >& v)
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());
208 argv[v.size()] = NULL;
209 return argv;
212 static
213 void
214 exec_or_exit(const atf::fs::path& executable,
215 const std::vector< std::string >& argv)
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);
221 ::execv(executable.c_str(), native_argv);
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);
230 static
231 std::vector< std::string >
232 config_to_args(const atf::tests::vars_map& config)
234 std::vector< std::string > args;
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);
240 return args;
243 static
244 void
245 silence_stdin(void)
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);
254 static
255 void
256 prepare_child(const atf::fs::path& workdir)
258 #if !defined(__minix)
259 const int ret = ::setpgid(::getpid(), 0);
260 INV(ret != -1);
261 #endif /* defined(__minix) */
263 ::umask(S_IWGRP | S_IWOTH);
265 for (int i = 1; i <= impl::last_signo; i++)
266 impl::reset(i);
268 atf::env::set("HOME", workdir.str());
269 atf::env::unset("LANG");
270 atf::env::unset("LC_ALL");
271 atf::env::unset("LC_COLLATE");
272 atf::env::unset("LC_CTYPE");
273 atf::env::unset("LC_MESSAGES");
274 atf::env::unset("LC_MONETARY");
275 atf::env::unset("LC_NUMERIC");
276 atf::env::unset("LC_TIME");
277 atf::env::set("TZ", "UTC");
279 atf::env::set("__RUNNING_INSIDE_ATF_RUN", "internal-yes-value");
281 impl::change_directory(workdir);
283 silence_stdin();
286 static
287 void
288 get_metadata_child(void* raw_params)
290 const get_metadata_params* params =
291 static_cast< const get_metadata_params* >(raw_params);
293 std::vector< std::string > argv;
294 argv.push_back(params->executable.leaf_name());
295 argv.push_back("-l");
296 argv.push_back("-s" + params->executable.branch_path().str());
297 append_to_vector(argv, config_to_args(params->config));
299 exec_or_exit(params->executable, argv);
302 void
303 run_test_case_child(void* raw_params)
305 const test_case_params* params =
306 static_cast< const test_case_params* >(raw_params);
308 const std::pair< int, int > user = impl::get_required_user(
309 params->metadata, params->config);
310 if (user.first != -1 && user.second != -1)
311 impl::drop_privileges(user);
313 // The input 'tp' parameter may be relative and become invalid once
314 // we change the current working directory.
315 const atf::fs::path absolute_executable = params->executable.to_absolute();
317 // Prepare the test program's arguments. We use dynamic memory and
318 // do not care to release it. We are going to die anyway very soon,
319 // either due to exec(2) or to exit(3).
320 std::vector< std::string > argv;
321 argv.push_back(absolute_executable.leaf_name());
322 argv.push_back("-r" + params->resfile.str());
323 argv.push_back("-s" + absolute_executable.branch_path().str());
324 append_to_vector(argv, config_to_args(params->config));
325 argv.push_back(params->test_case_name + ":" + params->test_case_part);
327 prepare_child(params->workdir);
328 exec_or_exit(absolute_executable, argv);
331 static void
332 tokenize_result(const std::string& line, std::string& out_state,
333 std::string& out_arg, std::string& out_reason)
335 const std::string::size_type pos = line.find_first_of(":(");
336 if (pos == std::string::npos) {
337 out_state = line;
338 out_arg = "";
339 out_reason = "";
340 } else if (line[pos] == ':') {
341 out_state = line.substr(0, pos);
342 out_arg = "";
343 out_reason = atf::text::trim(line.substr(pos + 1));
344 } else if (line[pos] == '(') {
345 const std::string::size_type pos2 = line.find("):", pos);
346 if (pos2 == std::string::npos)
347 throw std::runtime_error("Invalid test case result '" + line +
348 "': unclosed optional argument");
349 out_state = line.substr(0, pos);
350 out_arg = line.substr(pos + 1, pos2 - pos - 1);
351 out_reason = atf::text::trim(line.substr(pos2 + 2));
352 } else
353 UNREACHABLE;
356 static impl::test_case_result
357 handle_result(const std::string& state, const std::string& arg,
358 const std::string& reason)
360 PRE(state == "passed");
362 if (!arg.empty() || !reason.empty())
363 throw std::runtime_error("The test case result '" + state + "' cannot "
364 "be accompanied by a reason nor an expected value");
366 return impl::test_case_result(state, -1, reason);
369 static impl::test_case_result
370 handle_result_with_reason(const std::string& state, const std::string& arg,
371 const std::string& reason)
373 PRE(state == "expected_death" || state == "expected_failure" ||
374 state == "expected_timeout" || state == "failed" || state == "skipped");
376 if (!arg.empty() || reason.empty())
377 throw std::runtime_error("The test case result '" + state + "' must "
378 "be accompanied by a reason but not by an expected value");
380 return impl::test_case_result(state, -1, reason);
383 static impl::test_case_result
384 handle_result_with_reason_and_arg(const std::string& state,
385 const std::string& arg,
386 const std::string& reason)
388 PRE(state == "expected_exit" || state == "expected_signal");
390 if (reason.empty())
391 throw std::runtime_error("The test case result '" + state + "' must "
392 "be accompanied by a reason");
394 int value;
395 if (arg.empty()) {
396 value = -1;
397 } else {
398 try {
399 value = atf::text::to_type< int >(arg);
400 } catch (const std::runtime_error&) {
401 throw std::runtime_error("The value '" + arg + "' passed to the '" +
402 state + "' state must be an integer");
406 return impl::test_case_result(state, value, reason);
409 } // anonymous namespace
411 detail::atf_tp_reader::atf_tp_reader(std::istream& is) :
412 m_is(is)
416 detail::atf_tp_reader::~atf_tp_reader(void)
420 void
421 detail::atf_tp_reader::got_tc(
422 const std::string& ident ATF_DEFS_ATTRIBUTE_UNUSED,
423 const std::map< std::string, std::string >& md ATF_DEFS_ATTRIBUTE_UNUSED)
427 void
428 detail::atf_tp_reader::got_eof(void)
432 void
433 detail::atf_tp_reader::validate_and_insert(const std::string& name,
434 const std::string& value, const size_t lineno,
435 std::map< std::string, std::string >& md)
437 using atf::parser::parse_error;
439 if (value.empty())
440 throw parse_error(lineno, "The value for '" + name +"' cannot be "
441 "empty");
443 const std::string ident_regex = "^[_A-Za-z0-9]+$";
444 const std::string integer_regex = "^[0-9]+$";
446 if (name == "descr") {
447 // Any non-empty value is valid.
448 } else if (name == "has.cleanup") {
449 try {
450 (void)atf::text::to_bool(value);
451 } catch (const std::runtime_error&) {
452 throw parse_error(lineno, "The has.cleanup property requires a"
453 " boolean value");
455 } else if (name == "ident") {
456 if (!atf::text::match(value, ident_regex))
457 throw parse_error(lineno, "The identifier must match " +
458 ident_regex + "; was '" + value + "'");
459 } else if (name == "require.arch") {
460 } else if (name == "require.config") {
461 } else if (name == "require.files") {
462 } else if (name == "require.machine") {
463 } else if (name == "require.memory") {
464 try {
465 (void)atf::text::to_bytes(value);
466 } catch (const std::runtime_error&) {
467 throw parse_error(lineno, "The require.memory property requires an "
468 "integer value representing an amount of bytes");
470 } else if (name == "require.progs") {
471 } else if (name == "require.user") {
472 } else if (name == "timeout") {
473 if (!atf::text::match(value, integer_regex))
474 throw parse_error(lineno, "The timeout property requires an integer"
475 " value");
476 } else if (name == "use.fs") {
477 // Deprecated; ignore it.
478 } else if (name.size() > 2 && name[0] == 'X' && name[1] == '-') {
479 // Any non-empty value is valid.
480 } else {
481 throw parse_error(lineno, "Unknown property '" + name + "'");
484 md.insert(std::make_pair(name, value));
487 void
488 detail::atf_tp_reader::read(void)
490 using atf::parser::parse_error;
491 using namespace atf_tp;
493 std::pair< size_t, atf::parser::headers_map > hml =
494 atf::parser::read_headers(m_is, 1);
495 atf::parser::validate_content_type(hml.second,
496 "application/X-atf-tp", 1);
498 tokenizer tkz(m_is, hml.first);
499 atf::parser::parser< tokenizer > p(tkz);
501 try {
502 atf::parser::token t = p.expect(text_type, "property name");
503 if (t.text() != "ident")
504 throw parse_error(t.lineno(), "First property of a test case "
505 "must be 'ident'");
507 std::map< std::string, std::string > props;
508 do {
509 const std::string name = t.text();
510 t = p.expect(colon_type, "`:'");
511 const std::string value = atf::text::trim(p.rest_of_line());
512 t = p.expect(nl_type, "new line");
513 validate_and_insert(name, value, t.lineno(), props);
515 t = p.expect(eof_type, nl_type, text_type, "property name, new "
516 "line or eof");
517 if (t.type() == nl_type || t.type() == eof_type) {
518 const std::map< std::string, std::string >::const_iterator
519 iter = props.find("ident");
520 if (iter == props.end())
521 throw parse_error(t.lineno(), "Test case definition did "
522 "not define an 'ident' property");
523 ATF_PARSER_CALLBACK(p, got_tc((*iter).second, props));
524 props.clear();
526 if (t.type() == nl_type) {
527 t = p.expect(text_type, "property name");
528 if (t.text() != "ident")
529 throw parse_error(t.lineno(), "First property of a "
530 "test case must be 'ident'");
533 } while (t.type() != eof_type);
534 ATF_PARSER_CALLBACK(p, got_eof());
535 } catch (const parse_error& pe) {
536 p.add_error(pe);
537 p.reset(nl_type);
541 impl::test_case_result
542 detail::parse_test_case_result(const std::string& line)
544 std::string state, arg, reason;
545 tokenize_result(line, state, arg, reason);
547 if (state == "expected_death")
548 return handle_result_with_reason(state, arg, reason);
549 else if (state.compare(0, 13, "expected_exit") == 0)
550 return handle_result_with_reason_and_arg(state, arg, reason);
551 else if (state.compare(0, 16, "expected_failure") == 0)
552 return handle_result_with_reason(state, arg, reason);
553 else if (state.compare(0, 15, "expected_signal") == 0)
554 return handle_result_with_reason_and_arg(state, arg, reason);
555 else if (state.compare(0, 16, "expected_timeout") == 0)
556 return handle_result_with_reason(state, arg, reason);
557 else if (state == "failed")
558 return handle_result_with_reason(state, arg, reason);
559 else if (state == "passed")
560 return handle_result(state, arg, reason);
561 else if (state == "skipped")
562 return handle_result_with_reason(state, arg, reason);
563 else
564 throw std::runtime_error("Unknown test case result type in: " + line);
567 impl::atf_tps_writer::atf_tps_writer(std::ostream& os) :
568 m_os(os)
570 atf::parser::headers_map hm;
571 atf::parser::attrs_map ct_attrs;
572 ct_attrs["version"] = "3";
573 hm["Content-Type"] =
574 atf::parser::header_entry("Content-Type", "application/X-atf-tps",
575 ct_attrs);
576 atf::parser::write_headers(hm, m_os);
579 void
580 impl::atf_tps_writer::info(const std::string& what, const std::string& val)
582 m_os << "info: " << what << ", " << val << "\n";
583 m_os.flush();
586 void
587 impl::atf_tps_writer::ntps(size_t p_ntps)
589 m_os << "tps-count: " << p_ntps << "\n";
590 m_os.flush();
593 void
594 impl::atf_tps_writer::start_tp(const std::string& tp, size_t ntcs)
596 m_tpname = tp;
597 m_os << "tp-start: " << generate_timestamp() << ", " << tp << ", "
598 << ntcs << "\n";
599 m_os.flush();
602 void
603 impl::atf_tps_writer::end_tp(const std::string& reason)
605 PRE(reason.find('\n') == std::string::npos);
606 if (reason.empty())
607 m_os << "tp-end: " << generate_timestamp() << ", " << m_tpname << "\n";
608 else
609 m_os << "tp-end: " << generate_timestamp() << ", " << m_tpname
610 << ", " << reason << "\n";
611 m_os.flush();
614 void
615 impl::atf_tps_writer::start_tc(const std::string& tcname)
617 m_tcname = tcname;
618 m_os << "tc-start: " << generate_timestamp() << ", " << tcname << "\n";
619 m_os.flush();
622 void
623 impl::atf_tps_writer::stdout_tc(const std::string& line)
625 m_os << "tc-so:" << line << "\n";
626 check_stream(m_os);
627 m_os.flush();
628 check_stream(m_os);
631 void
632 impl::atf_tps_writer::stderr_tc(const std::string& line)
634 m_os << "tc-se:" << line << "\n";
635 check_stream(m_os);
636 m_os.flush();
637 check_stream(m_os);
640 void
641 impl::atf_tps_writer::end_tc(const std::string& state,
642 const std::string& reason)
644 std::string str = ", " + m_tcname + ", " + state;
645 if (!reason.empty())
646 str += ", " + reason;
647 m_os << "tc-end: " << generate_timestamp() << str << "\n";
648 m_os.flush();
651 impl::metadata
652 impl::get_metadata(const atf::fs::path& executable,
653 const atf::tests::vars_map& config)
655 get_metadata_params params(executable, config);
656 atf::process::child child =
657 atf::process::fork(get_metadata_child,
658 atf::process::stream_capture(),
659 atf::process::stream_inherit(),
660 static_cast< void * >(&params));
662 impl::pistream outin(child.stdout_fd());
664 metadata_reader parser(outin);
665 parser.read();
667 const atf::process::status status = child.wait();
668 if (!status.exited() || status.exitstatus() != EXIT_SUCCESS)
669 throw atf::parser::format_error("Test program returned failure "
670 "exit status for test case list");
672 return metadata(parser.get_tcs());
675 impl::test_case_result
676 impl::read_test_case_result(const atf::fs::path& results_path)
678 std::ifstream results_file(results_path.c_str());
679 if (!results_file)
680 throw std::runtime_error("Failed to open " + results_path.str());
682 std::string line, extra_line;
683 std::getline(results_file, line);
684 if (!results_file.good())
685 throw std::runtime_error("Results file is empty");
687 while (std::getline(results_file, extra_line).good())
688 line += "<<NEWLINE UNEXPECTED>>" + extra_line;
690 results_file.close();
692 return detail::parse_test_case_result(line);
695 namespace {
697 static volatile bool terminate_poll;
699 static void
700 sigchld_handler(const int signo ATF_DEFS_ATTRIBUTE_UNUSED)
702 terminate_poll = true;
705 class child_muxer : public impl::muxer {
706 impl::atf_tps_writer& m_writer;
708 void
709 line_callback(const size_t index, const std::string& line)
711 switch (index) {
712 case 0: m_writer.stdout_tc(line); break;
713 case 1: m_writer.stderr_tc(line); break;
714 default: UNREACHABLE;
718 public:
719 child_muxer(const int* fds, const size_t nfds,
720 impl::atf_tps_writer& writer) :
721 muxer(fds, nfds),
722 m_writer(writer)
727 } // anonymous namespace
729 std::pair< std::string, atf::process::status >
730 impl::run_test_case(const atf::fs::path& executable,
731 const std::string& test_case_name,
732 const std::string& test_case_part,
733 const atf::tests::vars_map& metadata,
734 const atf::tests::vars_map& config,
735 const atf::fs::path& resfile,
736 const atf::fs::path& workdir,
737 atf_tps_writer& writer)
739 // TODO: Capture termination signals and deliver them to the subprocess
740 // instead. Or maybe do something else; think about it.
742 test_case_params params(executable, test_case_name, test_case_part,
743 metadata, config, resfile, workdir);
744 atf::process::child child =
745 atf::process::fork(run_test_case_child,
746 atf::process::stream_capture(),
747 atf::process::stream_capture(),
748 static_cast< void * >(&params));
750 terminate_poll = false;
752 const atf::tests::vars_map::const_iterator iter = metadata.find("timeout");
753 INV(iter != metadata.end());
754 const unsigned int timeout =
755 atf::text::to_type< unsigned int >((*iter).second);
756 const pid_t child_pid = child.pid();
758 // Get the input stream of stdout and stderr.
759 impl::file_handle outfh = child.stdout_fd();
760 impl::file_handle errfh = child.stderr_fd();
762 bool timed_out = false;
764 // Process the test case's output and multiplex it into our output
765 // stream as we read it.
766 int fds[2] = {outfh.get(), errfh.get()};
767 child_muxer mux(fds, 2, writer);
768 try {
769 child_timer timeout_timer(timeout, child_pid, terminate_poll);
770 signal_programmer sigchld(SIGCHLD, sigchld_handler);
771 mux.mux(terminate_poll);
772 timed_out = timeout_timer.fired();
773 } catch (...) {
774 UNREACHABLE;
777 ::killpg(child_pid, SIGKILL);
778 mux.flush();
779 atf::process::status status = child.wait();
781 std::string reason;
783 if (timed_out) {
784 // Don't assume the child process has been signaled due to the timeout
785 // expiration as older versions did. The child process may have exited
786 // but we may have timed out due to a subchild process getting stuck.
787 reason = "Test case timed out after " + atf::text::to_string(timeout) +
788 " " + (timeout == 1 ? "second" : "seconds");
791 return std::make_pair(reason, status);