2 // Automated Testing Framework (atf)
4 // Copyright (c) 2007 The NetBSD Foundation, Inc.
5 // All rights reserved.
7 // Redistribution and use in source and binary forms, with or without
8 // modification, are permitted provided that the following conditions
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.
31 #include <sys/types.h>
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"
57 #include "requirements.hpp"
58 #include "signals.hpp"
59 #include "test-program.hpp"
63 namespace impl
= atf::atf_run
;
64 namespace detail
= atf::atf_run::detail
;
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.
78 throw std::runtime_error("Failed");
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
> {
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"));
121 metadata_reader(std::istream
& is
) :
122 detail::atf_tp_reader(is
)
126 const impl::test_cases_map
&
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
),
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
),
175 generate_timestamp(void)
178 if (gettimeofday(&tv
, NULL
) == -1)
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)
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
));
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
;
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)
227 std::exit(EXIT_FAILURE
);
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
);
247 ::close(STDIN_FILENO
);
248 int fd
= ::open("/dev/null", O_RDONLY
);
250 throw std::runtime_error("Could not open /dev/null");
251 INV(fd
== STDIN_FILENO
);
256 prepare_child(const atf::fs::path
& workdir
)
258 #if !defined(__minix)
259 const int ret
= ::setpgid(::getpid(), 0);
261 #endif /* defined(__minix) */
263 ::umask(S_IWGRP
| S_IWOTH
);
265 for (int i
= 1; i
<= impl::last_signo
; 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
);
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
);
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
);
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
) {
340 } else if (line
[pos
] == ':') {
341 out_state
= line
.substr(0, pos
);
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));
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");
391 throw std::runtime_error("The test case result '" + state
+ "' must "
392 "be accompanied by a reason");
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
) :
416 detail::atf_tp_reader::~atf_tp_reader(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
)
428 detail::atf_tp_reader::got_eof(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
;
440 throw parse_error(lineno
, "The value for '" + name
+"' cannot be "
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") {
450 (void)atf::text::to_bool(value
);
451 } catch (const std::runtime_error
&) {
452 throw parse_error(lineno
, "The has.cleanup property requires a"
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") {
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"
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.
481 throw parse_error(lineno
, "Unknown property '" + name
+ "'");
484 md
.insert(std::make_pair(name
, value
));
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
);
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 "
507 std::map
< std::string
, std::string
> props
;
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 "
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
));
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
) {
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
);
564 throw std::runtime_error("Unknown test case result type in: " + line
);
567 impl::atf_tps_writer::atf_tps_writer(std::ostream
& os
) :
570 atf::parser::headers_map hm
;
571 atf::parser::attrs_map ct_attrs
;
572 ct_attrs
["version"] = "3";
574 atf::parser::header_entry("Content-Type", "application/X-atf-tps",
576 atf::parser::write_headers(hm
, m_os
);
580 impl::atf_tps_writer::info(const std::string
& what
, const std::string
& val
)
582 m_os
<< "info: " << what
<< ", " << val
<< "\n";
587 impl::atf_tps_writer::ntps(size_t p_ntps
)
589 m_os
<< "tps-count: " << p_ntps
<< "\n";
594 impl::atf_tps_writer::start_tp(const std::string
& tp
, size_t ntcs
)
597 m_os
<< "tp-start: " << generate_timestamp() << ", " << tp
<< ", "
603 impl::atf_tps_writer::end_tp(const std::string
& reason
)
605 PRE(reason
.find('\n') == std::string::npos
);
607 m_os
<< "tp-end: " << generate_timestamp() << ", " << m_tpname
<< "\n";
609 m_os
<< "tp-end: " << generate_timestamp() << ", " << m_tpname
610 << ", " << reason
<< "\n";
615 impl::atf_tps_writer::start_tc(const std::string
& tcname
)
618 m_os
<< "tc-start: " << generate_timestamp() << ", " << tcname
<< "\n";
623 impl::atf_tps_writer::stdout_tc(const std::string
& line
)
625 m_os
<< "tc-so:" << line
<< "\n";
632 impl::atf_tps_writer::stderr_tc(const std::string
& line
)
634 m_os
<< "tc-se:" << line
<< "\n";
641 impl::atf_tps_writer::end_tc(const std::string
& state
,
642 const std::string
& reason
)
644 std::string str
= ", " + m_tcname
+ ", " + state
;
646 str
+= ", " + reason
;
647 m_os
<< "tc-end: " << generate_timestamp() << str
<< "\n";
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 * >(¶ms
));
662 impl::pistream
outin(child
.stdout_fd());
664 metadata_reader
parser(outin
);
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());
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
);
697 static volatile bool terminate_poll
;
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
;
709 line_callback(const size_t index
, const std::string
& line
)
712 case 0: m_writer
.stdout_tc(line
); break;
713 case 1: m_writer
.stderr_tc(line
); break;
714 default: UNREACHABLE
;
719 child_muxer(const int* fds
, const size_t nfds
,
720 impl::atf_tps_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 * >(¶ms
));
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
);
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();
777 ::killpg(child_pid
, SIGKILL
);
779 atf::process::status status
= child
.wait();
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
);