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