Patrick Welche <prlw1@cam.ac.uk>
[netbsd-mini2440.git] / external / bsd / atf / dist / tools / atf-run.cpp
blobb3370f492cfdac146631ac16032950cb2e32872b
1 //
2 // Automated Testing Framework (atf)
3 //
4 // Copyright (c) 2007, 2008, 2009 The NetBSD Foundation, Inc.
5 // All rights reserved.
6 //
7 // Redistribution and use in source and binary forms, with or without
8 // modification, are permitted provided that the following conditions
9 // are met:
10 // 1. Redistributions of source code must retain the above copyright
11 // notice, this list of conditions and the following disclaimer.
12 // 2. Redistributions in binary form must reproduce the above copyright
13 // notice, this list of conditions and the following disclaimer in the
14 // documentation and/or other materials provided with the distribution.
16 // THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
17 // CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
18 // INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
19 // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 // IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
21 // DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22 // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
23 // GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24 // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
25 // IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
26 // OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
27 // IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 #if defined(HAVE_CONFIG_H)
31 #include "bconfig.h"
32 #endif
34 extern "C" {
35 #include <sys/types.h>
36 #include <sys/wait.h>
37 #include <unistd.h>
40 #include <cerrno>
41 #include <cstdlib>
42 #include <cstring>
43 #include <fstream>
44 #include <iostream>
45 #include <map>
46 #include <string>
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;
65 void
66 got_var(const std::string& var, const std::string& name)
68 m_vars[var] = name;
71 public:
72 config(std::istream& is) :
73 atf::formats::atf_config_reader(is)
77 const atf::tests::vars_map&
78 get_vars(void)
79 const
81 return m_vars;
85 class muxer : public atf::formats::atf_tcs_reader {
86 atf::fs::path m_tp;
87 atf::formats::atf_tps_writer m_writer;
89 bool m_inited, m_finalized;
90 size_t m_ntcs;
91 std::string m_tcname;
93 // Counters for the test cases run by the test program.
94 size_t m_passed, m_failed, m_skipped;
96 void
97 got_ntcs(size_t ntcs)
99 m_writer.start_tp(m_tp.str(), ntcs);
100 m_inited = true;
101 if (ntcs == 0)
102 throw atf::formats::format_error("Bogus test program: reported "
103 "0 test cases");
106 void
107 got_tc_start(const std::string& tcname)
109 m_tcname = tcname;
110 m_writer.start_tc(tcname);
113 void
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) {
118 m_passed++;
119 } else if (s == atf::tests::tcr::skipped_state) {
120 m_skipped++;
121 } else if (s == atf::tests::tcr::failed_state) {
122 m_failed++;
123 } else
124 UNREACHABLE;
126 m_writer.end_tc(tcr);
127 m_tcname = "";
130 void
131 got_stdout_line(const std::string& line)
133 m_writer.stdout_tc(line);
136 void
137 got_stderr_line(const std::string& line)
139 m_writer.stderr_tc(line);
142 public:
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),
146 m_tp(tp),
147 m_writer(w),
148 m_inited(false),
149 m_finalized(false),
150 m_passed(0),
151 m_failed(0),
152 m_skipped(0)
156 size_t
157 failed(void)
158 const
160 return m_failed;
163 void
164 finalize(const std::string& reason = "")
166 PRE(!m_finalized);
168 if (!m_inited)
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);
177 m_finalized = true;
180 ~muxer(void)
182 // The following is incorrect because we cannot throw an exception
183 // from a destructor. Let's just hope that this never happens.
184 PRE(m_finalized);
188 template< class K, class V >
189 void
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&);
225 struct test_data {
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) :
232 m_this(t),
233 m_tp(tp),
234 m_respipe(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&,
245 atf::io::pipe&);
247 public:
248 atf_run(void);
250 int main(void);
253 const char* atf_run::m_description =
254 "atf-run is a tool that runs tests programs and collects their "
255 "results.";
257 atf_run::atf_run(void) :
258 app(m_description, "atf-run(1)", "atf(7)")
262 void
263 atf_run::process_option(int ch, const char* arg)
265 switch (ch) {
266 case 'v':
267 parse_vflag(arg);
268 break;
270 default:
271 UNREACHABLE;
275 std::string
276 atf_run::specific_args(void)
277 const
279 return "[test-program1 .. test-programN]";
282 atf_run::options_set
283 atf_run::specific_options(void)
284 const
286 using atf::application::option;
287 options_set opts;
288 opts.insert(option('v', "var=value", "Sets the configuration variable "
289 "`var' to `value'; overrides "
290 "values in configuration files"));
291 return opts;
294 void
295 atf_run::parse_vflag(const std::string& str)
297 if (str.empty())
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]] = "";
303 } else {
304 if (ws.size() != 2)
305 throw std::runtime_error("-v requires an argument of the form "
306 "var=value");
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);
318 int errcode;
319 if (fi.get_type() == atf::fs::file_info::dir_type)
320 errcode = run_test_directory(tp, w);
321 else
322 errcode = run_test_program(tp, w);
323 return errcode;
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);
341 bool ok = true;
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);
366 return args;
369 void
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);
374 UNREACHABLE;
377 void
378 atf_run::run_test_program_child(const atf::fs::path& tp,
379 atf::io::pipe& respipe)
380 const
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()];
394 // 0: Program name.
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);
420 // Last: Terminator.
421 args[3 + i] = NULL;
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);
452 std::string fmterr;
453 try {
454 m.read(outin, errin);
455 } catch (const atf::parser::parse_errors& e) {
456 fmterr = "There were errors parsing the output of the test "
457 "program:";
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) {
464 fmterr = e.what();
465 } catch (...) {
466 UNREACHABLE;
469 try {
470 outin.close();
471 errin.close();
472 resin.close();
473 } catch (...) {
474 UNREACHABLE;
477 const atf::process::status s = c.wait();
479 int code;
480 if (s.exited()) {
481 code = s.exitstatus();
482 if (m.failed() > 0 && code == EXIT_SUCCESS) {
483 code = EXIT_FAILURE;
484 m.finalize("Test program returned success but some test "
485 "cases failed" +
486 (fmterr.empty() ? "" : (". " + fmterr)));
487 } else {
488 code = fmterr.empty() ? code : EXIT_FAILURE;
489 m.finalize(fmterr);
491 } else if (s.signaled()) {
492 code = EXIT_FAILURE;
493 m.finalize("Test program received signal " +
494 atf::text::to_string(s.termsig()) +
495 (s.coredump() ? " (core dumped)" : "") +
496 (fmterr.empty() ? "" : (". " + fmterr)));
497 } else
498 throw std::runtime_error
499 ("Child process " + atf::text::to_string(c.pid()) +
500 " terminated with an unknown status condition");
501 return code;
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
515 // temporary file.
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);
527 size_t
528 atf_run::count_tps(std::vector< std::string > tps)
529 const
531 size_t ntps = 0;
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);
545 } else
546 ntps++;
549 return ntps;
552 void
553 atf_run::read_one_config(const atf::fs::path& p)
555 std::ifstream is(p.c_str());
556 if (is) {
557 config reader(is);
558 reader.read();
559 merge_maps(m_config_vars, reader.get_vars());
563 void
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"));
579 static
580 void
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(),
590 hook.c_str(), NULL),
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 + "'");
601 atf_run::main(void)
603 atf::atffile af(atf::fs::path("Atffile"));
604 m_atffile_vars = af.conf();
606 std::vector< std::string > tps;
607 tps = af.tps();
608 if (m_argc >= 1) {
609 // TODO: Ensure that the given test names are listed in the
610 // Atffile. Take into account that the file can be using globs.
611 tps.clear();
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));
628 bool ok = true;
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);