2 // Automated Testing Framework (atf)
4 // Copyright (c) 2007, 2008, 2009 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.
30 #if defined(HAVE_CONFIG_H)
35 #include <sys/types.h>
48 #include "atf-c++/application.hpp"
49 #include "atf-c++/atffile.hpp"
50 #include "atf-c++/config.hpp"
51 #include "atf-c++/env.hpp"
52 #include "atf-c++/exceptions.hpp"
53 #include "atf-c++/formats.hpp"
54 #include "atf-c++/fs.hpp"
55 #include "atf-c++/io.hpp"
56 #include "atf-c++/parser.hpp"
57 #include "atf-c++/process.hpp"
58 #include "atf-c++/sanity.hpp"
59 #include "atf-c++/tests.hpp"
60 #include "atf-c++/text.hpp"
62 class config
: public atf::formats::atf_config_reader
{
63 atf::tests::vars_map m_vars
;
66 got_var(const std::string
& var
, const std::string
& name
)
72 config(std::istream
& is
) :
73 atf::formats::atf_config_reader(is
)
77 const atf::tests::vars_map
&
85 class muxer
: public atf::formats::atf_tcs_reader
{
87 atf::formats::atf_tps_writer m_writer
;
89 bool m_inited
, m_finalized
;
93 // Counters for the test cases run by the test program.
94 size_t m_passed
, m_failed
, m_skipped
;
99 m_writer
.start_tp(m_tp
.str(), ntcs
);
102 throw atf::formats::format_error("Bogus test program: reported "
107 got_tc_start(const std::string
& tcname
)
110 m_writer
.start_tc(tcname
);
114 got_tc_end(const atf::tests::tcr
& tcr
)
116 const atf::tests::tcr::state
& s
= tcr
.get_state();
117 if (s
== atf::tests::tcr::passed_state
) {
119 } else if (s
== atf::tests::tcr::skipped_state
) {
121 } else if (s
== atf::tests::tcr::failed_state
) {
126 m_writer
.end_tc(tcr
);
131 got_stdout_line(const std::string
& line
)
133 m_writer
.stdout_tc(line
);
137 got_stderr_line(const std::string
& line
)
139 m_writer
.stderr_tc(line
);
143 muxer(const atf::fs::path
& tp
, atf::formats::atf_tps_writer
& w
,
144 atf::io::pistream
& is
) :
145 atf::formats::atf_tcs_reader(is
),
164 finalize(const std::string
& reason
= "")
169 m_writer
.start_tp(m_tp
.str(), 0);
170 if (!m_tcname
.empty()) {
171 INV(!reason
.empty());
172 got_tc_end(atf::tests::tcr(atf::tests::tcr::failed_state
,
173 "Bogus test program"));
176 m_writer
.end_tp(reason
);
182 // The following is incorrect because we cannot throw an exception
183 // from a destructor. Let's just hope that this never happens.
188 template< class K
, class V
>
190 merge_maps(std::map
< K
, V
>& dest
, const std::map
< K
, V
>& src
)
192 for (typename
std::map
< K
, V
>::const_iterator iter
= src
.begin();
193 iter
!= src
.end(); iter
++)
194 dest
[(*iter
).first
] = (*iter
).second
;
197 class atf_run
: public atf::application::app
{
198 static const char* m_description
;
200 atf::tests::vars_map m_atffile_vars
;
201 atf::tests::vars_map m_cmdline_vars
;
202 atf::tests::vars_map m_config_vars
;
204 static atf::tests::vars_map::value_type
parse_var(const std::string
&);
206 void process_option(int, const char*);
207 std::string
specific_args(void) const;
208 options_set
specific_options(void) const;
210 void parse_vflag(const std::string
&);
212 void read_one_config(const atf::fs::path
&);
213 void read_config(const std::string
&);
214 std::vector
< std::string
> conf_args(void) const;
216 size_t count_tps(std::vector
< std::string
>) const;
218 int run_test(const atf::fs::path
&,
219 atf::formats::atf_tps_writer
&);
220 int run_test_directory(const atf::fs::path
&,
221 atf::formats::atf_tps_writer
&);
222 int run_test_program(const atf::fs::path
&,
223 atf::formats::atf_tps_writer
&);
226 const atf_run
* m_this
;
227 const atf::fs::path
& m_tp
;
228 atf::io::pipe
& m_respipe
;
230 test_data(const atf_run
* t
, const atf::fs::path
& tp
,
231 atf::io::pipe
& respipe
) :
239 static void route_run_test_program_child(void *);
240 void run_test_program_child(const atf::fs::path
&,
241 atf::io::pipe
&) const;
242 int run_test_program_parent(const atf::fs::path
&,
243 atf::formats::atf_tps_writer
&,
244 atf::process::child
&,
253 const char* atf_run::m_description
=
254 "atf-run is a tool that runs tests programs and collects their "
257 atf_run::atf_run(void) :
258 app(m_description
, "atf-run(1)", "atf(7)")
263 atf_run::process_option(int ch
, const char* arg
)
276 atf_run::specific_args(void)
279 return "[test-program1 .. test-programN]";
283 atf_run::specific_options(void)
286 using atf::application::option
;
288 opts
.insert(option('v', "var=value", "Sets the configuration variable "
289 "`var' to `value'; overrides "
290 "values in configuration files"));
295 atf_run::parse_vflag(const std::string
& str
)
298 throw std::runtime_error("-v requires a non-empty argument");
300 std::vector
< std::string
> ws
= atf::text::split(str
, "=");
301 if (ws
.size() == 1 && str
[str
.length() - 1] == '=') {
302 m_cmdline_vars
[ws
[0]] = "";
305 throw std::runtime_error("-v requires an argument of the form "
308 m_cmdline_vars
[ws
[0]] = ws
[1];
313 atf_run::run_test(const atf::fs::path
& tp
,
314 atf::formats::atf_tps_writer
& w
)
316 atf::fs::file_info
fi(tp
);
319 if (fi
.get_type() == atf::fs::file_info::dir_type
)
320 errcode
= run_test_directory(tp
, w
);
322 errcode
= run_test_program(tp
, w
);
327 atf_run::run_test_directory(const atf::fs::path
& tp
,
328 atf::formats::atf_tps_writer
& w
)
330 atf::atffile
af(tp
/ "Atffile");
331 m_atffile_vars
= af
.conf();
333 atf::tests::vars_map oldvars
= m_config_vars
;
335 atf::tests::vars_map::const_iterator iter
=
336 af
.props().find("test-suite");
337 INV(iter
!= af
.props().end());
338 read_config((*iter
).second
);
342 for (std::vector
< std::string
>::const_iterator iter
= af
.tps().begin();
343 iter
!= af
.tps().end(); iter
++)
344 ok
&= (run_test(tp
/ *iter
, w
) == EXIT_SUCCESS
);
346 m_config_vars
= oldvars
;
348 return ok
? EXIT_SUCCESS
: EXIT_FAILURE
;
351 std::vector
< std::string
>
352 atf_run::conf_args(void) const
354 using atf::tests::vars_map
;
356 atf::tests::vars_map vars
;
357 std::vector
< std::string
> args
;
359 merge_maps(vars
, m_atffile_vars
);
360 merge_maps(vars
, m_config_vars
);
361 merge_maps(vars
, m_cmdline_vars
);
363 for (vars_map::const_iterator i
= vars
.begin(); i
!= vars
.end(); i
++)
364 args
.push_back("-v" + (*i
).first
+ "=" + (*i
).second
);
370 atf_run::route_run_test_program_child(void* v
)
372 test_data
* td
= static_cast< test_data
* >(v
);
373 td
->m_this
->run_test_program_child(td
->m_tp
, td
->m_respipe
);
378 atf_run::run_test_program_child(const atf::fs::path
& tp
,
379 atf::io::pipe
& respipe
)
382 // Remap the results file descriptor to point to the parent too.
383 // We use the 9th one (instead of a bigger one) because shell scripts
384 // can only use the [0..9] file descriptors in their redirections.
385 respipe
.rend().close();
386 respipe
.wend().posix_remap(9);
388 // Prepare the test program's arguments. We use dynamic memory and
389 // do not care to release it. We are going to die anyway very soon,
390 // either due to exec(2) or to exit(3).
391 std::vector
< std::string
> confargs
= conf_args();
392 char** args
= new char*[4 + confargs
.size()];
395 std::string progname
= tp
.leaf_name();
396 args
[0] = new char[progname
.length() + 1];
397 std::strcpy(args
[0], progname
.c_str());
399 // 1: The file descriptor to which the results will be printed.
400 args
[1] = new char[4];
401 std::strcpy(args
[1], "-r9");
403 // 2: The directory where the test program lives.
404 atf::fs::path bp
= tp
.branch_path();
405 if (!bp
.is_absolute())
406 bp
= bp
.to_absolute();
407 const char* dir
= bp
.c_str();
408 args
[2] = new char[std::strlen(dir
) + 3];
409 std::strcpy(args
[2], "-s");
410 std::strcat(args
[2], dir
);
412 // [3..last - 1]: Configuration arguments.
413 std::vector
< std::string
>::size_type i
;
414 for (i
= 0; i
< confargs
.size(); i
++) {
415 const char* str
= confargs
[i
].c_str();
416 args
[3 + i
] = new char[std::strlen(str
) + 1];
417 std::strcpy(args
[3 + i
], str
);
424 // Do the real exec and report any errors to the parent through the
425 // only mechanism we can use: stderr.
426 // TODO Try to make this fail.
427 ::execv(tp
.c_str(), args
);
428 std::cerr
<< "Failed to execute `" << tp
.str() << "': "
429 << std::strerror(errno
) << std::endl
;
430 std::exit(EXIT_FAILURE
);
434 atf_run::run_test_program_parent(const atf::fs::path
& tp
,
435 atf::formats::atf_tps_writer
& w
,
436 atf::process::child
& c
,
437 atf::io::pipe
& respipe
)
439 // Get the input stream of stdout and stderr.
440 atf::io::file_handle outfh
= c
.stdout_fd();
441 atf::io::unbuffered_istream
outin(outfh
);
442 atf::io::file_handle errfh
= c
.stderr_fd();
443 atf::io::unbuffered_istream
errin(errfh
);
445 // Get the file descriptor and input stream of the results channel.
446 respipe
.wend().close();
447 atf::io::pistream
resin(respipe
.rend());
449 // Process the test case's output and multiplex it into our output
450 // stream as we read it.
451 muxer
m(tp
, w
, resin
);
454 m
.read(outin
, errin
);
455 } catch (const atf::parser::parse_errors
& e
) {
456 fmterr
= "There were errors parsing the output of the test "
458 for (atf::parser::parse_errors::const_iterator iter
= e
.begin();
459 iter
!= e
.end(); iter
++) {
460 fmterr
+= " Line " + atf::text::to_string((*iter
).first
) +
461 ": " + (*iter
).second
+ ".";
463 } catch (const atf::formats::format_error
& e
) {
477 const atf::process::status s
= c
.wait();
481 code
= s
.exitstatus();
482 if (m
.failed() > 0 && code
== EXIT_SUCCESS
) {
484 m
.finalize("Test program returned success but some test "
486 (fmterr
.empty() ? "" : (". " + fmterr
)));
488 code
= fmterr
.empty() ? code
: EXIT_FAILURE
;
491 } else if (s
.signaled()) {
493 m
.finalize("Test program received signal " +
494 atf::text::to_string(s
.termsig()) +
495 (s
.coredump() ? " (core dumped)" : "") +
496 (fmterr
.empty() ? "" : (". " + fmterr
)));
498 throw std::runtime_error
499 ("Child process " + atf::text::to_string(c
.pid()) +
500 " terminated with an unknown status condition");
505 atf_run::run_test_program(const atf::fs::path
& tp
,
506 atf::formats::atf_tps_writer
& w
)
508 // XXX: This respipe is quite annoying. The fact that we cannot
509 // represent it as part of a portable fork API (which only supports
510 // stdin, stdout and stderr) and not even in our own fork API means
511 // that this will be a huge source of portability problems in the
512 // future, should we ever want to port ATF to Win32. I guess it'd
513 // be worth revisiting the decision of using a third file descriptor
514 // for results reporting sooner than later. Alternative: use a
516 atf::io::pipe respipe
;
517 test_data
td(this, tp
, respipe
);
518 atf::process::child c
=
519 atf::process::fork(route_run_test_program_child
,
520 atf::process::stream_capture(),
521 atf::process::stream_capture(),
522 static_cast< void * >(&td
));
524 return run_test_program_parent(tp
, w
, c
, respipe
);
528 atf_run::count_tps(std::vector
< std::string
> tps
)
533 for (std::vector
< std::string
>::const_iterator iter
= tps
.begin();
534 iter
!= tps
.end(); iter
++) {
535 atf::fs::path
tp(*iter
);
536 atf::fs::file_info
fi(tp
);
538 if (fi
.get_type() == atf::fs::file_info::dir_type
) {
539 atf::atffile af
= atf::atffile(tp
/ "Atffile");
540 std::vector
< std::string
> aux
= af
.tps();
541 for (std::vector
< std::string
>::iterator i2
= aux
.begin();
542 i2
!= aux
.end(); i2
++)
543 *i2
= (tp
/ *i2
).str();
544 ntps
+= count_tps(aux
);
553 atf_run::read_one_config(const atf::fs::path
& p
)
555 std::ifstream
is(p
.c_str());
559 merge_maps(m_config_vars
, reader
.get_vars());
564 atf_run::read_config(const std::string
& name
)
566 std::vector
< atf::fs::path
> dirs
;
567 dirs
.push_back(atf::fs::path(atf::config::get("atf_confdir")));
568 if (atf::env::has("HOME"))
569 dirs
.push_back(atf::fs::path(atf::env::get("HOME")) / ".atf");
571 m_config_vars
.clear();
572 for (std::vector
< atf::fs::path
>::const_iterator iter
= dirs
.begin();
573 iter
!= dirs
.end(); iter
++) {
574 read_one_config((*iter
) / "common.conf");
575 read_one_config((*iter
) / (name
+ ".conf"));
581 call_hook(const std::string
& tool
, const std::string
& hook
)
583 const atf::fs::path
sh(atf::config::get("atf_shell"));
584 const atf::fs::path hooks
=
585 atf::fs::path(atf::config::get("atf_pkgdatadir")) / (tool
+ ".hooks");
587 const atf::process::status s
=
588 atf::process::exec(sh
,
589 atf::process::argv_array(sh
.c_str(), hooks
.c_str(),
591 atf::process::stream_inherit(),
592 atf::process::stream_inherit());
595 if (!s
.exited() || s
.exitstatus() != EXIT_SUCCESS
)
596 throw std::runtime_error("Failed to run the '" + hook
+ "' hook "
597 "for '" + tool
+ "'");
603 atf::atffile
af(atf::fs::path("Atffile"));
604 m_atffile_vars
= af
.conf();
606 std::vector
< std::string
> tps
;
609 // TODO: Ensure that the given test names are listed in the
610 // Atffile. Take into account that the file can be using globs.
612 for (int i
= 0; i
< m_argc
; i
++)
613 tps
.push_back(m_argv
[i
]);
616 // Read configuration data for this test suite.
618 atf::tests::vars_map::const_iterator iter
=
619 af
.props().find("test-suite");
620 INV(iter
!= af
.props().end());
621 read_config((*iter
).second
);
624 atf::formats::atf_tps_writer
w(std::cout
);
625 call_hook("atf-run", "info_start_hook");
626 w
.ntps(count_tps(tps
));
629 for (std::vector
< std::string
>::const_iterator iter
= tps
.begin();
630 iter
!= tps
.end(); iter
++)
631 ok
&= (run_test(atf::fs::path(*iter
), w
) == EXIT_SUCCESS
);
633 call_hook("atf-run", "info_end_hook");
635 return ok
? EXIT_SUCCESS
: EXIT_FAILURE
;
639 main(int argc
, char* const* argv
)
641 return atf_run().run(argc
, argv
);