1ee44dd6cSjmmv //
2ee44dd6cSjmmv // Automated Testing Framework (atf)
3ee44dd6cSjmmv //
4ee44dd6cSjmmv // Copyright (c) 2007 The NetBSD Foundation, Inc.
5ee44dd6cSjmmv // All rights reserved.
6ee44dd6cSjmmv //
7ee44dd6cSjmmv // Redistribution and use in source and binary forms, with or without
8ee44dd6cSjmmv // modification, are permitted provided that the following conditions
9ee44dd6cSjmmv // are met:
10ee44dd6cSjmmv // 1. Redistributions of source code must retain the above copyright
11ee44dd6cSjmmv //    notice, this list of conditions and the following disclaimer.
12ee44dd6cSjmmv // 2. Redistributions in binary form must reproduce the above copyright
13ee44dd6cSjmmv //    notice, this list of conditions and the following disclaimer in the
14ee44dd6cSjmmv //    documentation and/or other materials provided with the distribution.
15ee44dd6cSjmmv //
16ee44dd6cSjmmv // THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
17ee44dd6cSjmmv // CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
18ee44dd6cSjmmv // INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
19ee44dd6cSjmmv // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20ee44dd6cSjmmv // IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
21ee44dd6cSjmmv // DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22ee44dd6cSjmmv // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
23ee44dd6cSjmmv // GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24ee44dd6cSjmmv // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
25ee44dd6cSjmmv // IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
26ee44dd6cSjmmv // OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
27ee44dd6cSjmmv // IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28ee44dd6cSjmmv //
29ee44dd6cSjmmv 
30ee44dd6cSjmmv extern "C" {
31ee44dd6cSjmmv #include <sys/types.h>
32ee44dd6cSjmmv #include <sys/stat.h>
33ee44dd6cSjmmv #include <sys/time.h>
34ee44dd6cSjmmv 
35ee44dd6cSjmmv #include <fcntl.h>
36ee44dd6cSjmmv #include <signal.h>
37ee44dd6cSjmmv #include <unistd.h>
38ee44dd6cSjmmv }
39ee44dd6cSjmmv 
40ee44dd6cSjmmv #include <cassert>
41ee44dd6cSjmmv #include <cerrno>
42ee44dd6cSjmmv #include <cstdlib>
43ee44dd6cSjmmv #include <cstring>
44ee44dd6cSjmmv #include <fstream>
45ee44dd6cSjmmv #include <iostream>
46ee44dd6cSjmmv 
47ee44dd6cSjmmv #include "config_file.hpp"
48ee44dd6cSjmmv #include "env.hpp"
49ee44dd6cSjmmv #include "fs.hpp"
50ee44dd6cSjmmv #include "io.hpp"
51ee44dd6cSjmmv #include "parser.hpp"
52ee44dd6cSjmmv #include "process.hpp"
53ee44dd6cSjmmv #include "requirements.hpp"
54ee44dd6cSjmmv #include "signals.hpp"
55ee44dd6cSjmmv #include "test-program.hpp"
56ee44dd6cSjmmv #include "text.hpp"
57ee44dd6cSjmmv #include "timers.hpp"
58ee44dd6cSjmmv #include "user.hpp"
59ee44dd6cSjmmv 
60ee44dd6cSjmmv namespace impl = tools::test_program;
61ee44dd6cSjmmv namespace detail = tools::test_program::detail;
62ee44dd6cSjmmv 
63ee44dd6cSjmmv namespace {
64ee44dd6cSjmmv 
65ee44dd6cSjmmv typedef std::map< std::string, std::string > vars_map;
66ee44dd6cSjmmv 
67ee44dd6cSjmmv static void
check_stream(std::ostream & os)68ee44dd6cSjmmv check_stream(std::ostream& os)
69ee44dd6cSjmmv {
70ee44dd6cSjmmv     // If we receive a signal while writing to the stream, the bad bit gets set.
71ee44dd6cSjmmv     // Things seem to behave fine afterwards if we clear such error condition.
72ee44dd6cSjmmv     // However, I'm not sure if it's safe to query errno at this point.
73ee44dd6cSjmmv     if (os.bad()) {
74ee44dd6cSjmmv         if (errno == EINTR)
75ee44dd6cSjmmv             os.clear();
76ee44dd6cSjmmv         else
77ee44dd6cSjmmv             throw std::runtime_error("Failed");
78ee44dd6cSjmmv     }
79ee44dd6cSjmmv }
80ee44dd6cSjmmv 
81ee44dd6cSjmmv namespace atf_tp {
82ee44dd6cSjmmv 
83ee44dd6cSjmmv static const tools::parser::token_type eof_type = 0;
84ee44dd6cSjmmv static const tools::parser::token_type nl_type = 1;
85ee44dd6cSjmmv static const tools::parser::token_type text_type = 2;
86ee44dd6cSjmmv static const tools::parser::token_type colon_type = 3;
87ee44dd6cSjmmv static const tools::parser::token_type dblquote_type = 4;
88ee44dd6cSjmmv 
89ee44dd6cSjmmv class tokenizer : public tools::parser::tokenizer< std::istream > {
90ee44dd6cSjmmv public:
tokenizer(std::istream & is,size_t curline)91ee44dd6cSjmmv     tokenizer(std::istream& is, size_t curline) :
92ee44dd6cSjmmv         tools::parser::tokenizer< std::istream >
93ee44dd6cSjmmv             (is, true, eof_type, nl_type, text_type, curline)
94ee44dd6cSjmmv     {
95ee44dd6cSjmmv         add_delim(':', colon_type);
96ee44dd6cSjmmv         add_quote('"', dblquote_type);
97ee44dd6cSjmmv     }
98ee44dd6cSjmmv };
99ee44dd6cSjmmv 
100ee44dd6cSjmmv } // namespace atf_tp
101ee44dd6cSjmmv 
102ee44dd6cSjmmv class metadata_reader : public detail::atf_tp_reader {
103ee44dd6cSjmmv     impl::test_cases_map m_tcs;
104ee44dd6cSjmmv 
got_tc(const std::string & ident,const vars_map & props)105ee44dd6cSjmmv     void got_tc(const std::string& ident, const vars_map& props)
106ee44dd6cSjmmv     {
107ee44dd6cSjmmv         if (m_tcs.find(ident) != m_tcs.end())
108ee44dd6cSjmmv             throw(std::runtime_error("Duplicate test case " + ident +
109ee44dd6cSjmmv                                      " in test program"));
110ee44dd6cSjmmv         m_tcs[ident] = props;
111ee44dd6cSjmmv 
112ee44dd6cSjmmv         if (m_tcs[ident].find("has.cleanup") == m_tcs[ident].end())
113ee44dd6cSjmmv             m_tcs[ident].insert(std::make_pair("has.cleanup", "false"));
114ee44dd6cSjmmv 
115ee44dd6cSjmmv         if (m_tcs[ident].find("timeout") == m_tcs[ident].end())
116ee44dd6cSjmmv             m_tcs[ident].insert(std::make_pair("timeout", "300"));
117ee44dd6cSjmmv     }
118ee44dd6cSjmmv 
119ee44dd6cSjmmv public:
metadata_reader(std::istream & is)120ee44dd6cSjmmv     metadata_reader(std::istream& is) :
121ee44dd6cSjmmv         detail::atf_tp_reader(is)
122ee44dd6cSjmmv     {
123ee44dd6cSjmmv     }
124ee44dd6cSjmmv 
125ee44dd6cSjmmv     const impl::test_cases_map&
get_tcs(void) const126ee44dd6cSjmmv     get_tcs(void)
127ee44dd6cSjmmv         const
128ee44dd6cSjmmv     {
129ee44dd6cSjmmv         return m_tcs;
130ee44dd6cSjmmv     }
131ee44dd6cSjmmv };
132ee44dd6cSjmmv 
133ee44dd6cSjmmv struct get_metadata_params {
134ee44dd6cSjmmv     const tools::fs::path& executable;
135ee44dd6cSjmmv     const vars_map& config;
136ee44dd6cSjmmv 
get_metadata_params__anon9e3f624b0111::get_metadata_params137ee44dd6cSjmmv     get_metadata_params(const tools::fs::path& p_executable,
138ee44dd6cSjmmv                         const vars_map& p_config) :
139ee44dd6cSjmmv         executable(p_executable),
140ee44dd6cSjmmv         config(p_config)
141ee44dd6cSjmmv     {
142ee44dd6cSjmmv     }
143ee44dd6cSjmmv };
144ee44dd6cSjmmv 
145ee44dd6cSjmmv struct test_case_params {
146ee44dd6cSjmmv     const tools::fs::path& executable;
147ee44dd6cSjmmv     const std::string& test_case_name;
148ee44dd6cSjmmv     const std::string& test_case_part;
149ee44dd6cSjmmv     const vars_map& metadata;
150ee44dd6cSjmmv     const vars_map& config;
151ee44dd6cSjmmv     const tools::fs::path& resfile;
152ee44dd6cSjmmv     const tools::fs::path& workdir;
153ee44dd6cSjmmv 
test_case_params__anon9e3f624b0111::test_case_params154ee44dd6cSjmmv     test_case_params(const tools::fs::path& p_executable,
155ee44dd6cSjmmv                      const std::string& p_test_case_name,
156ee44dd6cSjmmv                      const std::string& p_test_case_part,
157ee44dd6cSjmmv                      const vars_map& p_metadata,
158ee44dd6cSjmmv                      const vars_map& p_config,
159ee44dd6cSjmmv                      const tools::fs::path& p_resfile,
160ee44dd6cSjmmv                      const tools::fs::path& p_workdir) :
161ee44dd6cSjmmv         executable(p_executable),
162ee44dd6cSjmmv         test_case_name(p_test_case_name),
163ee44dd6cSjmmv         test_case_part(p_test_case_part),
164ee44dd6cSjmmv         metadata(p_metadata),
165ee44dd6cSjmmv         config(p_config),
166ee44dd6cSjmmv         resfile(p_resfile),
167ee44dd6cSjmmv         workdir(p_workdir)
168ee44dd6cSjmmv     {
169ee44dd6cSjmmv     }
170ee44dd6cSjmmv };
171ee44dd6cSjmmv 
172ee44dd6cSjmmv static
173ee44dd6cSjmmv std::string
generate_timestamp(void)174ee44dd6cSjmmv generate_timestamp(void)
175ee44dd6cSjmmv {
176ee44dd6cSjmmv     struct timeval tv;
177ee44dd6cSjmmv     if (gettimeofday(&tv, NULL) == -1)
178ee44dd6cSjmmv         return "0.0";
179ee44dd6cSjmmv 
180ee44dd6cSjmmv     char buf[32];
181ee44dd6cSjmmv     const int len = snprintf(buf, sizeof(buf), "%ld.%ld",
182ee44dd6cSjmmv                              static_cast< long >(tv.tv_sec),
183ee44dd6cSjmmv                              static_cast< long >(tv.tv_usec));
184ee44dd6cSjmmv     if (len >= static_cast< int >(sizeof(buf)) || len < 0)
185ee44dd6cSjmmv         return "0.0";
186ee44dd6cSjmmv     else
187ee44dd6cSjmmv         return buf;
188ee44dd6cSjmmv }
189ee44dd6cSjmmv 
190ee44dd6cSjmmv static
191ee44dd6cSjmmv void
append_to_vector(std::vector<std::string> & v1,const std::vector<std::string> & v2)192ee44dd6cSjmmv append_to_vector(std::vector< std::string >& v1,
193ee44dd6cSjmmv                  const std::vector< std::string >& v2)
194ee44dd6cSjmmv {
195ee44dd6cSjmmv     std::copy(v2.begin(), v2.end(),
196ee44dd6cSjmmv               std::back_insert_iterator< std::vector< std::string > >(v1));
197ee44dd6cSjmmv }
198ee44dd6cSjmmv 
199ee44dd6cSjmmv static
200ee44dd6cSjmmv char**
vector_to_argv(const std::vector<std::string> & v)201ee44dd6cSjmmv vector_to_argv(const std::vector< std::string >& v)
202ee44dd6cSjmmv {
203ee44dd6cSjmmv     char** argv = new char*[v.size() + 1];
204ee44dd6cSjmmv     for (std::vector< std::string >::size_type i = 0; i < v.size(); i++) {
205ee44dd6cSjmmv         argv[i] = strdup(v[i].c_str());
206ee44dd6cSjmmv     }
207ee44dd6cSjmmv     argv[v.size()] = NULL;
208ee44dd6cSjmmv     return argv;
209ee44dd6cSjmmv }
210ee44dd6cSjmmv 
211ee44dd6cSjmmv static
212ee44dd6cSjmmv void
exec_or_exit(const tools::fs::path & executable,const std::vector<std::string> & argv)213ee44dd6cSjmmv exec_or_exit(const tools::fs::path& executable,
214ee44dd6cSjmmv              const std::vector< std::string >& argv)
215ee44dd6cSjmmv {
216ee44dd6cSjmmv     // This leaks memory in case of a failure, but it is OK.  Exiting will
217ee44dd6cSjmmv     // do the necessary cleanup.
218ee44dd6cSjmmv     char* const* native_argv = vector_to_argv(argv);
219ee44dd6cSjmmv 
220ee44dd6cSjmmv     ::execv(executable.c_str(), native_argv);
221ee44dd6cSjmmv 
222ee44dd6cSjmmv     const std::string message = "Failed to execute '" + executable.str() +
223ee44dd6cSjmmv         "': " + std::strerror(errno) + "\n";
224ee44dd6cSjmmv     if (::write(STDERR_FILENO, message.c_str(), message.length()) == -1)
225ee44dd6cSjmmv         std::abort();
226ee44dd6cSjmmv     std::exit(EXIT_FAILURE);
227ee44dd6cSjmmv }
228ee44dd6cSjmmv 
229ee44dd6cSjmmv static
230ee44dd6cSjmmv std::vector< std::string >
config_to_args(const vars_map & config)231ee44dd6cSjmmv config_to_args(const vars_map& config)
232ee44dd6cSjmmv {
233ee44dd6cSjmmv     std::vector< std::string > args;
234ee44dd6cSjmmv 
235ee44dd6cSjmmv     for (vars_map::const_iterator iter = config.begin();
236ee44dd6cSjmmv          iter != config.end(); iter++)
237ee44dd6cSjmmv         args.push_back("-v" + (*iter).first + "=" + (*iter).second);
238ee44dd6cSjmmv 
239ee44dd6cSjmmv     return args;
240ee44dd6cSjmmv }
241ee44dd6cSjmmv 
242ee44dd6cSjmmv static
243ee44dd6cSjmmv void
silence_stdin(void)244ee44dd6cSjmmv silence_stdin(void)
245ee44dd6cSjmmv {
246ee44dd6cSjmmv     ::close(STDIN_FILENO);
247ee44dd6cSjmmv     int fd = ::open("/dev/null", O_RDONLY);
248ee44dd6cSjmmv     if (fd == -1)
249ee44dd6cSjmmv         throw std::runtime_error("Could not open /dev/null");
250ee44dd6cSjmmv     assert(fd == STDIN_FILENO);
251ee44dd6cSjmmv }
252ee44dd6cSjmmv 
253ee44dd6cSjmmv static
254ee44dd6cSjmmv void
prepare_child(const tools::fs::path & workdir)255ee44dd6cSjmmv prepare_child(const tools::fs::path& workdir)
256ee44dd6cSjmmv {
257ee44dd6cSjmmv     const int ret = ::setpgid(::getpid(), 0);
258ee44dd6cSjmmv     assert(ret != -1);
259ee44dd6cSjmmv 
260ee44dd6cSjmmv     ::umask(S_IWGRP | S_IWOTH);
261ee44dd6cSjmmv 
262ee44dd6cSjmmv     for (int i = 1; i <= tools::signals::last_signo; i++)
263ee44dd6cSjmmv         tools::signals::reset(i);
264ee44dd6cSjmmv 
265ee44dd6cSjmmv     tools::env::set("HOME", workdir.str());
266ee44dd6cSjmmv     tools::env::unset("LANG");
267ee44dd6cSjmmv     tools::env::unset("LC_ALL");
268ee44dd6cSjmmv     tools::env::unset("LC_COLLATE");
269ee44dd6cSjmmv     tools::env::unset("LC_CTYPE");
270ee44dd6cSjmmv     tools::env::unset("LC_MESSAGES");
271ee44dd6cSjmmv     tools::env::unset("LC_MONETARY");
272ee44dd6cSjmmv     tools::env::unset("LC_NUMERIC");
273ee44dd6cSjmmv     tools::env::unset("LC_TIME");
274ee44dd6cSjmmv     tools::env::set("TZ", "UTC");
275ee44dd6cSjmmv 
276ee44dd6cSjmmv     tools::env::set("__RUNNING_INSIDE_ATF_RUN", "internal-yes-value");
277ee44dd6cSjmmv 
278ee44dd6cSjmmv     tools::fs::change_directory(workdir);
279ee44dd6cSjmmv 
280ee44dd6cSjmmv     silence_stdin();
281ee44dd6cSjmmv }
282ee44dd6cSjmmv 
283ee44dd6cSjmmv static
284ee44dd6cSjmmv void
get_metadata_child(void * raw_params)285ee44dd6cSjmmv get_metadata_child(void* raw_params)
286ee44dd6cSjmmv {
287ee44dd6cSjmmv     const get_metadata_params* params =
288ee44dd6cSjmmv         static_cast< const get_metadata_params* >(raw_params);
289ee44dd6cSjmmv 
290ee44dd6cSjmmv     std::vector< std::string > argv;
291ee44dd6cSjmmv     argv.push_back(params->executable.leaf_name());
292ee44dd6cSjmmv     argv.push_back("-l");
293ee44dd6cSjmmv     argv.push_back("-s" + params->executable.branch_path().str());
294ee44dd6cSjmmv     append_to_vector(argv, config_to_args(params->config));
295ee44dd6cSjmmv 
296ee44dd6cSjmmv     exec_or_exit(params->executable, argv);
297ee44dd6cSjmmv }
298ee44dd6cSjmmv 
299ee44dd6cSjmmv void
run_test_case_child(void * raw_params)300ee44dd6cSjmmv run_test_case_child(void* raw_params)
301ee44dd6cSjmmv {
302ee44dd6cSjmmv     const test_case_params* params =
303ee44dd6cSjmmv         static_cast< const test_case_params* >(raw_params);
304ee44dd6cSjmmv 
305ee44dd6cSjmmv     const std::pair< int, int > user = tools::get_required_user(
306ee44dd6cSjmmv         params->metadata, params->config);
307*5983470cSchristos     if (user.first != -1 && user.second != -1) {
308*5983470cSchristos         tools::fs::change_ownership(params->workdir, user);
309ee44dd6cSjmmv         tools::user::drop_privileges(user);
310*5983470cSchristos     }
311ee44dd6cSjmmv 
312ee44dd6cSjmmv     // The input 'tp' parameter may be relative and become invalid once
313ee44dd6cSjmmv     // we change the current working directory.
314ee44dd6cSjmmv     const tools::fs::path absolute_executable = params->executable.to_absolute();
315ee44dd6cSjmmv 
316ee44dd6cSjmmv     // Prepare the test program's arguments.  We use dynamic memory and
317ee44dd6cSjmmv     // do not care to release it.  We are going to die anyway very soon,
318ee44dd6cSjmmv     // either due to exec(2) or to exit(3).
319ee44dd6cSjmmv     std::vector< std::string > argv;
320ee44dd6cSjmmv     argv.push_back(absolute_executable.leaf_name());
321ee44dd6cSjmmv     argv.push_back("-r" + params->resfile.str());
322ee44dd6cSjmmv     argv.push_back("-s" + absolute_executable.branch_path().str());
323ee44dd6cSjmmv     append_to_vector(argv, config_to_args(params->config));
324ee44dd6cSjmmv     argv.push_back(params->test_case_name + ":" + params->test_case_part);
325ee44dd6cSjmmv 
326ee44dd6cSjmmv     prepare_child(params->workdir);
327ee44dd6cSjmmv     exec_or_exit(absolute_executable, argv);
328ee44dd6cSjmmv }
329ee44dd6cSjmmv 
330ee44dd6cSjmmv static void
tokenize_result(const std::string & line,std::string & out_state,std::string & out_arg,std::string & out_reason)331ee44dd6cSjmmv tokenize_result(const std::string& line, std::string& out_state,
332ee44dd6cSjmmv                 std::string& out_arg, std::string& out_reason)
333ee44dd6cSjmmv {
334ee44dd6cSjmmv     const std::string::size_type pos = line.find_first_of(":(");
335ee44dd6cSjmmv     if (pos == std::string::npos) {
336ee44dd6cSjmmv         out_state = line;
337ee44dd6cSjmmv         out_arg = "";
338ee44dd6cSjmmv         out_reason = "";
339ee44dd6cSjmmv     } else if (line[pos] == ':') {
340ee44dd6cSjmmv         out_state = line.substr(0, pos);
341ee44dd6cSjmmv         out_arg = "";
342ee44dd6cSjmmv         out_reason = tools::text::trim(line.substr(pos + 1));
343ee44dd6cSjmmv     } else if (line[pos] == '(') {
344ee44dd6cSjmmv         const std::string::size_type pos2 = line.find("):", pos);
345ee44dd6cSjmmv         if (pos2 == std::string::npos)
346ee44dd6cSjmmv             throw std::runtime_error("Invalid test case result '" + line +
347ee44dd6cSjmmv                 "': unclosed optional argument");
348ee44dd6cSjmmv         out_state = line.substr(0, pos);
349ee44dd6cSjmmv         out_arg = line.substr(pos + 1, pos2 - pos - 1);
350ee44dd6cSjmmv         out_reason = tools::text::trim(line.substr(pos2 + 2));
351ee44dd6cSjmmv     } else
352ee44dd6cSjmmv         std::abort();
353ee44dd6cSjmmv }
354ee44dd6cSjmmv 
355ee44dd6cSjmmv static impl::test_case_result
handle_result(const std::string & state,const std::string & arg,const std::string & reason)356ee44dd6cSjmmv handle_result(const std::string& state, const std::string& arg,
357ee44dd6cSjmmv               const std::string& reason)
358ee44dd6cSjmmv {
359ee44dd6cSjmmv     assert(state == "passed");
360ee44dd6cSjmmv 
361ee44dd6cSjmmv     if (!arg.empty() || !reason.empty())
362ee44dd6cSjmmv         throw std::runtime_error("The test case result '" + state + "' cannot "
363ee44dd6cSjmmv             "be accompanied by a reason nor an expected value");
364ee44dd6cSjmmv 
365ee44dd6cSjmmv     return impl::test_case_result(state, -1, reason);
366ee44dd6cSjmmv }
367ee44dd6cSjmmv 
368ee44dd6cSjmmv static impl::test_case_result
handle_result_with_reason(const std::string & state,const std::string & arg,const std::string & reason)369ee44dd6cSjmmv handle_result_with_reason(const std::string& state, const std::string& arg,
370ee44dd6cSjmmv                           const std::string& reason)
371ee44dd6cSjmmv {
372ee44dd6cSjmmv     assert(state == "expected_death" || state == "expected_failure" ||
373ee44dd6cSjmmv         state == "expected_timeout" || state == "failed" || state == "skipped");
374ee44dd6cSjmmv 
375ee44dd6cSjmmv     if (!arg.empty() || reason.empty())
376ee44dd6cSjmmv         throw std::runtime_error("The test case result '" + state + "' must "
377ee44dd6cSjmmv             "be accompanied by a reason but not by an expected value");
378ee44dd6cSjmmv 
379ee44dd6cSjmmv     return impl::test_case_result(state, -1, reason);
380ee44dd6cSjmmv }
381ee44dd6cSjmmv 
382ee44dd6cSjmmv static impl::test_case_result
handle_result_with_reason_and_arg(const std::string & state,const std::string & arg,const std::string & reason)383ee44dd6cSjmmv handle_result_with_reason_and_arg(const std::string& state,
384ee44dd6cSjmmv                                   const std::string& arg,
385ee44dd6cSjmmv                                   const std::string& reason)
386ee44dd6cSjmmv {
387ee44dd6cSjmmv     assert(state == "expected_exit" || state == "expected_signal");
388ee44dd6cSjmmv 
389ee44dd6cSjmmv     if (reason.empty())
390ee44dd6cSjmmv         throw std::runtime_error("The test case result '" + state + "' must "
391ee44dd6cSjmmv             "be accompanied by a reason");
392ee44dd6cSjmmv 
393ee44dd6cSjmmv     int value;
394ee44dd6cSjmmv     if (arg.empty()) {
395ee44dd6cSjmmv         value = -1;
396ee44dd6cSjmmv     } else {
397ee44dd6cSjmmv         try {
398ee44dd6cSjmmv             value = tools::text::to_type< int >(arg);
399ee44dd6cSjmmv         } catch (const std::runtime_error&) {
400ee44dd6cSjmmv             throw std::runtime_error("The value '" + arg + "' passed to the '" +
401ee44dd6cSjmmv                 state + "' state must be an integer");
402ee44dd6cSjmmv         }
403ee44dd6cSjmmv     }
404ee44dd6cSjmmv 
405ee44dd6cSjmmv     return impl::test_case_result(state, value, reason);
406ee44dd6cSjmmv }
407ee44dd6cSjmmv 
408ee44dd6cSjmmv } // anonymous namespace
409ee44dd6cSjmmv 
atf_tp_reader(std::istream & is)410ee44dd6cSjmmv detail::atf_tp_reader::atf_tp_reader(std::istream& is) :
411ee44dd6cSjmmv     m_is(is)
412ee44dd6cSjmmv {
413ee44dd6cSjmmv }
414ee44dd6cSjmmv 
~atf_tp_reader(void)415ee44dd6cSjmmv detail::atf_tp_reader::~atf_tp_reader(void)
416ee44dd6cSjmmv {
417ee44dd6cSjmmv }
418ee44dd6cSjmmv 
419ee44dd6cSjmmv void
got_tc(const std::string & ident,const std::map<std::string,std::string> & md)420ee44dd6cSjmmv detail::atf_tp_reader::got_tc(
421de996de9Sjmmv     const std::string& ident __attribute__((__unused__)),
422de996de9Sjmmv     const std::map< std::string, std::string >& md __attribute__((__unused__)))
423ee44dd6cSjmmv {
424ee44dd6cSjmmv }
425ee44dd6cSjmmv 
426ee44dd6cSjmmv void
got_eof(void)427ee44dd6cSjmmv detail::atf_tp_reader::got_eof(void)
428ee44dd6cSjmmv {
429ee44dd6cSjmmv }
430ee44dd6cSjmmv 
431ee44dd6cSjmmv void
validate_and_insert(const std::string & name,const std::string & value,const size_t lineno,std::map<std::string,std::string> & md)432ee44dd6cSjmmv detail::atf_tp_reader::validate_and_insert(const std::string& name,
433ee44dd6cSjmmv     const std::string& value, const size_t lineno,
434ee44dd6cSjmmv     std::map< std::string, std::string >& md)
435ee44dd6cSjmmv {
436ee44dd6cSjmmv     using tools::parser::parse_error;
437ee44dd6cSjmmv 
438ee44dd6cSjmmv     if (value.empty())
439ee44dd6cSjmmv         throw parse_error(lineno, "The value for '" + name +"' cannot be "
440ee44dd6cSjmmv                           "empty");
441ee44dd6cSjmmv 
442ee44dd6cSjmmv     const std::string ident_regex = "^[_A-Za-z0-9]+$";
443ee44dd6cSjmmv     const std::string integer_regex = "^[0-9]+$";
444ee44dd6cSjmmv 
445ee44dd6cSjmmv     if (name == "descr") {
446ee44dd6cSjmmv         // Any non-empty value is valid.
447ee44dd6cSjmmv     } else if (name == "has.cleanup") {
448ee44dd6cSjmmv         try {
449ee44dd6cSjmmv             (void)tools::text::to_bool(value);
450ee44dd6cSjmmv         } catch (const std::runtime_error&) {
451ee44dd6cSjmmv             throw parse_error(lineno, "The has.cleanup property requires a"
452ee44dd6cSjmmv                               " boolean value");
453ee44dd6cSjmmv         }
454ee44dd6cSjmmv     } else if (name == "ident") {
455ee44dd6cSjmmv         if (!tools::text::match(value, ident_regex))
456ee44dd6cSjmmv             throw parse_error(lineno, "The identifier must match " +
457ee44dd6cSjmmv                               ident_regex + "; was '" + value + "'");
458ee44dd6cSjmmv     } else if (name == "require.arch") {
459ee44dd6cSjmmv     } else if (name == "require.config") {
460ee44dd6cSjmmv     } else if (name == "require.files") {
461ee44dd6cSjmmv     } else if (name == "require.machine") {
462ee44dd6cSjmmv     } else if (name == "require.memory") {
463ee44dd6cSjmmv         try {
464ee44dd6cSjmmv             (void)tools::text::to_bytes(value);
465ee44dd6cSjmmv         } catch (const std::runtime_error&) {
466ee44dd6cSjmmv             throw parse_error(lineno, "The require.memory property requires an "
467ee44dd6cSjmmv                               "integer value representing an amount of bytes");
468ee44dd6cSjmmv         }
469ee44dd6cSjmmv     } else if (name == "require.progs") {
470ee44dd6cSjmmv     } else if (name == "require.user") {
471ee44dd6cSjmmv     } else if (name == "timeout") {
472ee44dd6cSjmmv         if (!tools::text::match(value, integer_regex))
473ee44dd6cSjmmv             throw parse_error(lineno, "The timeout property requires an integer"
474ee44dd6cSjmmv                               " value");
475ee44dd6cSjmmv     } else if (name == "use.fs") {
476ee44dd6cSjmmv         // Deprecated; ignore it.
477ee44dd6cSjmmv     } else if (name.size() > 2 && name[0] == 'X' && name[1] == '-') {
478ee44dd6cSjmmv         // Any non-empty value is valid.
479ee44dd6cSjmmv     } else {
480ee44dd6cSjmmv         throw parse_error(lineno, "Unknown property '" + name + "'");
481ee44dd6cSjmmv     }
482ee44dd6cSjmmv 
483ee44dd6cSjmmv     md.insert(std::make_pair(name, value));
484ee44dd6cSjmmv }
485ee44dd6cSjmmv 
486ee44dd6cSjmmv void
read(void)487ee44dd6cSjmmv detail::atf_tp_reader::read(void)
488ee44dd6cSjmmv {
489ee44dd6cSjmmv     using tools::parser::parse_error;
490ee44dd6cSjmmv     using namespace atf_tp;
491ee44dd6cSjmmv 
492ee44dd6cSjmmv     std::pair< size_t, tools::parser::headers_map > hml =
493ee44dd6cSjmmv         tools::parser::read_headers(m_is, 1);
494ee44dd6cSjmmv     tools::parser::validate_content_type(hml.second,
495ee44dd6cSjmmv         "application/X-atf-tp", 1);
496ee44dd6cSjmmv 
497ee44dd6cSjmmv     tokenizer tkz(m_is, hml.first);
498ee44dd6cSjmmv     tools::parser::parser< tokenizer > p(tkz);
499ee44dd6cSjmmv 
500ee44dd6cSjmmv     try {
501ee44dd6cSjmmv         tools::parser::token t = p.expect(text_type, "property name");
502ee44dd6cSjmmv         if (t.text() != "ident")
503ee44dd6cSjmmv             throw parse_error(t.lineno(), "First property of a test case "
504ee44dd6cSjmmv                               "must be 'ident'");
505ee44dd6cSjmmv 
506ee44dd6cSjmmv         std::map< std::string, std::string > props;
507ee44dd6cSjmmv         do {
508ee44dd6cSjmmv             const std::string name = t.text();
509ee44dd6cSjmmv             t = p.expect(colon_type, "`:'");
510ee44dd6cSjmmv             const std::string value = tools::text::trim(p.rest_of_line());
511ee44dd6cSjmmv             t = p.expect(nl_type, "new line");
512ee44dd6cSjmmv             validate_and_insert(name, value, t.lineno(), props);
513ee44dd6cSjmmv 
514ee44dd6cSjmmv             t = p.expect(eof_type, nl_type, text_type, "property name, new "
515ee44dd6cSjmmv                          "line or eof");
516ee44dd6cSjmmv             if (t.type() == nl_type || t.type() == eof_type) {
517ee44dd6cSjmmv                 const std::map< std::string, std::string >::const_iterator
518ee44dd6cSjmmv                     iter = props.find("ident");
519ee44dd6cSjmmv                 if (iter == props.end())
520ee44dd6cSjmmv                     throw parse_error(t.lineno(), "Test case definition did "
521ee44dd6cSjmmv                                       "not define an 'ident' property");
522ee44dd6cSjmmv                 ATF_PARSER_CALLBACK(p, got_tc((*iter).second, props));
523ee44dd6cSjmmv                 props.clear();
524ee44dd6cSjmmv 
525ee44dd6cSjmmv                 if (t.type() == nl_type) {
526ee44dd6cSjmmv                     t = p.expect(text_type, "property name");
527ee44dd6cSjmmv                     if (t.text() != "ident")
528ee44dd6cSjmmv                         throw parse_error(t.lineno(), "First property of a "
529ee44dd6cSjmmv                                           "test case must be 'ident'");
530ee44dd6cSjmmv                 }
531ee44dd6cSjmmv             }
532ee44dd6cSjmmv         } while (t.type() != eof_type);
533ee44dd6cSjmmv         ATF_PARSER_CALLBACK(p, got_eof());
534ee44dd6cSjmmv     } catch (const parse_error& pe) {
535ee44dd6cSjmmv         p.add_error(pe);
536ee44dd6cSjmmv         p.reset(nl_type);
537ee44dd6cSjmmv     }
538ee44dd6cSjmmv }
539ee44dd6cSjmmv 
540ee44dd6cSjmmv impl::test_case_result
parse_test_case_result(const std::string & line)541ee44dd6cSjmmv detail::parse_test_case_result(const std::string& line)
542ee44dd6cSjmmv {
543ee44dd6cSjmmv     std::string state, arg, reason;
544ee44dd6cSjmmv     tokenize_result(line, state, arg, reason);
545ee44dd6cSjmmv 
546ee44dd6cSjmmv     if (state == "expected_death")
547ee44dd6cSjmmv         return handle_result_with_reason(state, arg, reason);
548ee44dd6cSjmmv     else if (state.compare(0, 13, "expected_exit") == 0)
549ee44dd6cSjmmv         return handle_result_with_reason_and_arg(state, arg, reason);
550ee44dd6cSjmmv     else if (state.compare(0, 16, "expected_failure") == 0)
551ee44dd6cSjmmv         return handle_result_with_reason(state, arg, reason);
552ee44dd6cSjmmv     else if (state.compare(0, 15, "expected_signal") == 0)
553ee44dd6cSjmmv         return handle_result_with_reason_and_arg(state, arg, reason);
554ee44dd6cSjmmv     else if (state.compare(0, 16, "expected_timeout") == 0)
555ee44dd6cSjmmv         return handle_result_with_reason(state, arg, reason);
556ee44dd6cSjmmv     else if (state == "failed")
557ee44dd6cSjmmv         return handle_result_with_reason(state, arg, reason);
558ee44dd6cSjmmv     else if (state == "passed")
559ee44dd6cSjmmv         return handle_result(state, arg, reason);
560ee44dd6cSjmmv     else if (state == "skipped")
561ee44dd6cSjmmv         return handle_result_with_reason(state, arg, reason);
562ee44dd6cSjmmv     else
563ee44dd6cSjmmv         throw std::runtime_error("Unknown test case result type in: " + line);
564ee44dd6cSjmmv }
565ee44dd6cSjmmv 
atf_tps_writer(std::ostream & os)566ee44dd6cSjmmv impl::atf_tps_writer::atf_tps_writer(std::ostream& os) :
567ee44dd6cSjmmv     m_os(os)
568ee44dd6cSjmmv {
569ee44dd6cSjmmv     tools::parser::headers_map hm;
570ee44dd6cSjmmv     tools::parser::attrs_map ct_attrs;
571ee44dd6cSjmmv     ct_attrs["version"] = "3";
572ee44dd6cSjmmv     hm["Content-Type"] =
573ee44dd6cSjmmv         tools::parser::header_entry("Content-Type", "application/X-atf-tps",
574ee44dd6cSjmmv                                   ct_attrs);
575ee44dd6cSjmmv     tools::parser::write_headers(hm, m_os);
576ee44dd6cSjmmv }
577ee44dd6cSjmmv 
578ee44dd6cSjmmv void
info(const std::string & what,const std::string & val)579ee44dd6cSjmmv impl::atf_tps_writer::info(const std::string& what, const std::string& val)
580ee44dd6cSjmmv {
581ee44dd6cSjmmv     m_os << "info: " << what << ", " << val << "\n";
582ee44dd6cSjmmv     m_os.flush();
583ee44dd6cSjmmv }
584ee44dd6cSjmmv 
585ee44dd6cSjmmv void
ntps(size_t p_ntps)586ee44dd6cSjmmv impl::atf_tps_writer::ntps(size_t p_ntps)
587ee44dd6cSjmmv {
588ee44dd6cSjmmv     m_os << "tps-count: " << p_ntps << "\n";
589ee44dd6cSjmmv     m_os.flush();
590ee44dd6cSjmmv }
591ee44dd6cSjmmv 
592ee44dd6cSjmmv void
start_tp(const std::string & tp,size_t ntcs)593ee44dd6cSjmmv impl::atf_tps_writer::start_tp(const std::string& tp, size_t ntcs)
594ee44dd6cSjmmv {
595ee44dd6cSjmmv     m_tpname = tp;
596ee44dd6cSjmmv     m_os << "tp-start: " << generate_timestamp() << ", " << tp << ", "
597ee44dd6cSjmmv          << ntcs << "\n";
598ee44dd6cSjmmv     m_os.flush();
599ee44dd6cSjmmv }
600ee44dd6cSjmmv 
601ee44dd6cSjmmv void
end_tp(const std::string & reason)602ee44dd6cSjmmv impl::atf_tps_writer::end_tp(const std::string& reason)
603ee44dd6cSjmmv {
604ee44dd6cSjmmv     assert(reason.find('\n') == std::string::npos);
605ee44dd6cSjmmv     if (reason.empty())
606ee44dd6cSjmmv         m_os << "tp-end: " << generate_timestamp() << ", " << m_tpname << "\n";
607ee44dd6cSjmmv     else
608ee44dd6cSjmmv         m_os << "tp-end: " << generate_timestamp() << ", " << m_tpname
609ee44dd6cSjmmv              << ", " << reason << "\n";
610ee44dd6cSjmmv     m_os.flush();
611ee44dd6cSjmmv }
612ee44dd6cSjmmv 
613ee44dd6cSjmmv void
start_tc(const std::string & tcname)614ee44dd6cSjmmv impl::atf_tps_writer::start_tc(const std::string& tcname)
615ee44dd6cSjmmv {
616ee44dd6cSjmmv     m_tcname = tcname;
617ee44dd6cSjmmv     m_os << "tc-start: " << generate_timestamp() << ", " << tcname << "\n";
618ee44dd6cSjmmv     m_os.flush();
619ee44dd6cSjmmv }
620ee44dd6cSjmmv 
621ee44dd6cSjmmv void
stdout_tc(const std::string & line)622ee44dd6cSjmmv impl::atf_tps_writer::stdout_tc(const std::string& line)
623ee44dd6cSjmmv {
624ee44dd6cSjmmv     m_os << "tc-so:" << line << "\n";
625ee44dd6cSjmmv     check_stream(m_os);
626ee44dd6cSjmmv     m_os.flush();
627ee44dd6cSjmmv     check_stream(m_os);
628ee44dd6cSjmmv }
629ee44dd6cSjmmv 
630ee44dd6cSjmmv void
stderr_tc(const std::string & line)631ee44dd6cSjmmv impl::atf_tps_writer::stderr_tc(const std::string& line)
632ee44dd6cSjmmv {
633ee44dd6cSjmmv     m_os << "tc-se:" << line << "\n";
634ee44dd6cSjmmv     check_stream(m_os);
635ee44dd6cSjmmv     m_os.flush();
636ee44dd6cSjmmv     check_stream(m_os);
637ee44dd6cSjmmv }
638ee44dd6cSjmmv 
639ee44dd6cSjmmv void
end_tc(const std::string & state,const std::string & reason)640ee44dd6cSjmmv impl::atf_tps_writer::end_tc(const std::string& state,
641ee44dd6cSjmmv                              const std::string& reason)
642ee44dd6cSjmmv {
643ee44dd6cSjmmv     std::string str =  ", " + m_tcname + ", " + state;
644ee44dd6cSjmmv     if (!reason.empty())
645ee44dd6cSjmmv         str += ", " + reason;
646ee44dd6cSjmmv     m_os << "tc-end: " << generate_timestamp() << str << "\n";
647ee44dd6cSjmmv     m_os.flush();
648ee44dd6cSjmmv }
649ee44dd6cSjmmv 
650ee44dd6cSjmmv impl::metadata
get_metadata(const tools::fs::path & executable,const vars_map & config)651ee44dd6cSjmmv impl::get_metadata(const tools::fs::path& executable,
652ee44dd6cSjmmv                    const vars_map& config)
653ee44dd6cSjmmv {
654ee44dd6cSjmmv     get_metadata_params params(executable, config);
655ee44dd6cSjmmv     tools::process::child child =
656ee44dd6cSjmmv         tools::process::fork(get_metadata_child,
657ee44dd6cSjmmv                            tools::process::stream_capture(),
658ee44dd6cSjmmv                            tools::process::stream_inherit(),
659ee44dd6cSjmmv                            static_cast< void * >(&params));
660ee44dd6cSjmmv 
661ee44dd6cSjmmv     tools::io::pistream outin(child.stdout_fd());
662ee44dd6cSjmmv 
663ee44dd6cSjmmv     metadata_reader parser(outin);
664ee44dd6cSjmmv     parser.read();
665ee44dd6cSjmmv 
666ee44dd6cSjmmv     const tools::process::status status = child.wait();
667ee44dd6cSjmmv     if (!status.exited() || status.exitstatus() != EXIT_SUCCESS)
668ee44dd6cSjmmv         throw tools::parser::format_error("Test program returned failure "
6696660df14Schristos 	    "exit status " + status.str() + " for test case list");
670ee44dd6cSjmmv 
671ee44dd6cSjmmv     return metadata(parser.get_tcs());
672ee44dd6cSjmmv }
673ee44dd6cSjmmv 
674ee44dd6cSjmmv impl::test_case_result
read_test_case_result(const tools::fs::path & results_path)675ee44dd6cSjmmv impl::read_test_case_result(const tools::fs::path& results_path)
676ee44dd6cSjmmv {
677ee44dd6cSjmmv     std::ifstream results_file(results_path.c_str());
678ee44dd6cSjmmv     if (!results_file)
679ee44dd6cSjmmv         throw std::runtime_error("Failed to open " + results_path.str());
680ee44dd6cSjmmv 
681ee44dd6cSjmmv     std::string line, extra_line;
682ee44dd6cSjmmv     std::getline(results_file, line);
683ee44dd6cSjmmv     if (!results_file.good())
684ee44dd6cSjmmv         throw std::runtime_error("Results file is empty");
685ee44dd6cSjmmv 
686ee44dd6cSjmmv     while (std::getline(results_file, extra_line).good())
687ee44dd6cSjmmv         line += "<<NEWLINE UNEXPECTED>>" + extra_line;
688ee44dd6cSjmmv 
689ee44dd6cSjmmv     results_file.close();
690ee44dd6cSjmmv 
691ee44dd6cSjmmv     return detail::parse_test_case_result(line);
692ee44dd6cSjmmv }
693ee44dd6cSjmmv 
694ee44dd6cSjmmv namespace {
695ee44dd6cSjmmv 
696ee44dd6cSjmmv static volatile bool terminate_poll;
697ee44dd6cSjmmv 
698ee44dd6cSjmmv static void
sigchld_handler(const int signo)699de996de9Sjmmv sigchld_handler(const int signo __attribute__((__unused__)))
700ee44dd6cSjmmv {
701ee44dd6cSjmmv     terminate_poll = true;
702ee44dd6cSjmmv }
703ee44dd6cSjmmv 
704ee44dd6cSjmmv class child_muxer : public tools::io::muxer {
705ee44dd6cSjmmv     impl::atf_tps_writer& m_writer;
706ee44dd6cSjmmv 
707ee44dd6cSjmmv     void
line_callback(const size_t index,const std::string & line)708ee44dd6cSjmmv     line_callback(const size_t index, const std::string& line)
709ee44dd6cSjmmv     {
710ee44dd6cSjmmv         switch (index) {
711ee44dd6cSjmmv         case 0: m_writer.stdout_tc(line); break;
712ee44dd6cSjmmv         case 1: m_writer.stderr_tc(line); break;
713ee44dd6cSjmmv         default: std::abort();
714ee44dd6cSjmmv         }
715ee44dd6cSjmmv     }
716ee44dd6cSjmmv 
717ee44dd6cSjmmv public:
child_muxer(const int * fds,const size_t nfds,impl::atf_tps_writer & writer)718ee44dd6cSjmmv     child_muxer(const int* fds, const size_t nfds,
719ee44dd6cSjmmv                 impl::atf_tps_writer& writer) :
720ee44dd6cSjmmv         muxer(fds, nfds),
721ee44dd6cSjmmv         m_writer(writer)
722ee44dd6cSjmmv     {
723ee44dd6cSjmmv     }
724ee44dd6cSjmmv };
725ee44dd6cSjmmv 
726ee44dd6cSjmmv } // anonymous namespace
727ee44dd6cSjmmv 
728ee44dd6cSjmmv std::pair< std::string, tools::process::status >
run_test_case(const tools::fs::path & executable,const std::string & test_case_name,const std::string & test_case_part,const vars_map & metadata,const vars_map & config,const tools::fs::path & resfile,const tools::fs::path & workdir,atf_tps_writer & writer)729ee44dd6cSjmmv impl::run_test_case(const tools::fs::path& executable,
730ee44dd6cSjmmv                     const std::string& test_case_name,
731ee44dd6cSjmmv                     const std::string& test_case_part,
732ee44dd6cSjmmv                     const vars_map& metadata,
733ee44dd6cSjmmv                     const vars_map& config,
734ee44dd6cSjmmv                     const tools::fs::path& resfile,
735ee44dd6cSjmmv                     const tools::fs::path& workdir,
736ee44dd6cSjmmv                     atf_tps_writer& writer)
737ee44dd6cSjmmv {
738ee44dd6cSjmmv     // TODO: Capture termination signals and deliver them to the subprocess
739ee44dd6cSjmmv     // instead.  Or maybe do something else; think about it.
740ee44dd6cSjmmv 
741ee44dd6cSjmmv     test_case_params params(executable, test_case_name, test_case_part,
742ee44dd6cSjmmv                             metadata, config, resfile, workdir);
743ee44dd6cSjmmv     tools::process::child child =
744ee44dd6cSjmmv         tools::process::fork(run_test_case_child,
745ee44dd6cSjmmv                            tools::process::stream_capture(),
746ee44dd6cSjmmv                            tools::process::stream_capture(),
747ee44dd6cSjmmv                            static_cast< void * >(&params));
748ee44dd6cSjmmv 
749ee44dd6cSjmmv     terminate_poll = false;
750ee44dd6cSjmmv 
751ee44dd6cSjmmv     const vars_map::const_iterator iter = metadata.find("timeout");
752ee44dd6cSjmmv     assert(iter != metadata.end());
753ee44dd6cSjmmv     const unsigned int timeout =
754ee44dd6cSjmmv         tools::text::to_type< unsigned int >((*iter).second);
755ee44dd6cSjmmv     const pid_t child_pid = child.pid();
756ee44dd6cSjmmv 
757ee44dd6cSjmmv     // Get the input stream of stdout and stderr.
758ee44dd6cSjmmv     tools::io::file_handle outfh = child.stdout_fd();
759ee44dd6cSjmmv     tools::io::file_handle errfh = child.stderr_fd();
760ee44dd6cSjmmv 
761ee44dd6cSjmmv     bool timed_out = false;
762ee44dd6cSjmmv 
763ee44dd6cSjmmv     // Process the test case's output and multiplex it into our output
764ee44dd6cSjmmv     // stream as we read it.
765ee44dd6cSjmmv     int fds[2] = {outfh.get(), errfh.get()};
766ee44dd6cSjmmv     child_muxer mux(fds, 2, writer);
767ee44dd6cSjmmv     try {
768ee44dd6cSjmmv         timers::child_timer timeout_timer(timeout, child_pid, terminate_poll);
769ee44dd6cSjmmv         signals::signal_programmer sigchld(SIGCHLD, sigchld_handler);
770ee44dd6cSjmmv         mux.mux(terminate_poll);
771ee44dd6cSjmmv         timed_out = timeout_timer.fired();
772ee44dd6cSjmmv     } catch (...) {
773ee44dd6cSjmmv         std::abort();
774ee44dd6cSjmmv     }
775ee44dd6cSjmmv 
776ee44dd6cSjmmv     ::killpg(child_pid, SIGKILL);
777ee44dd6cSjmmv     mux.flush();
778ee44dd6cSjmmv     tools::process::status status = child.wait();
779ee44dd6cSjmmv 
780ee44dd6cSjmmv     std::string reason;
781ee44dd6cSjmmv 
782ee44dd6cSjmmv     if (timed_out) {
783ee44dd6cSjmmv         // Don't assume the child process has been signaled due to the timeout
784ee44dd6cSjmmv         // expiration as older versions did.  The child process may have exited
785ee44dd6cSjmmv         // but we may have timed out due to a subchild process getting stuck.
786ee44dd6cSjmmv         reason = "Test case timed out after " + tools::text::to_string(timeout) +
787ee44dd6cSjmmv             " " + (timeout == 1 ? "second" : "seconds");
788ee44dd6cSjmmv     }
789ee44dd6cSjmmv 
790ee44dd6cSjmmv     return std::make_pair(reason, status);
791ee44dd6cSjmmv }
792