xref: /netbsd-src/external/bsd/atf/dist/tools/atf-run.cpp (revision 3816d47b2c42fcd6e549e3407f842a5b1a1d23ad)
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.
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 #if defined(HAVE_CONFIG_H)
31 #include "bconfig.h"
32 #endif
33 
34 extern "C" {
35 #include <sys/types.h>
36 #include <sys/wait.h>
37 #include <unistd.h>
38 }
39 
40 #include <cerrno>
41 #include <cstdlib>
42 #include <cstring>
43 #include <fstream>
44 #include <iostream>
45 #include <map>
46 #include <string>
47 
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"
61 
62 class config : public atf::formats::atf_config_reader {
63     atf::tests::vars_map m_vars;
64 
65     void
66     got_var(const std::string& var, const std::string& name)
67     {
68         m_vars[var] = name;
69     }
70 
71 public:
72     config(std::istream& is) :
73         atf::formats::atf_config_reader(is)
74     {
75     }
76 
77     const atf::tests::vars_map&
78     get_vars(void)
79         const
80     {
81         return m_vars;
82     }
83 };
84 
85 class muxer : public atf::formats::atf_tcs_reader {
86     atf::fs::path m_tp;
87     atf::formats::atf_tps_writer m_writer;
88 
89     bool m_inited, m_finalized;
90     size_t m_ntcs;
91     std::string m_tcname;
92 
93     // Counters for the test cases run by the test program.
94     size_t m_passed, m_failed, m_skipped;
95 
96     void
97     got_ntcs(size_t ntcs)
98     {
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");
104     }
105 
106     void
107     got_tc_start(const std::string& tcname)
108     {
109         m_tcname = tcname;
110         m_writer.start_tc(tcname);
111     }
112 
113     void
114     got_tc_end(const atf::tests::tcr& tcr)
115     {
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;
125 
126         m_writer.end_tc(tcr);
127         m_tcname = "";
128     }
129 
130     void
131     got_stdout_line(const std::string& line)
132     {
133         m_writer.stdout_tc(line);
134     }
135 
136     void
137     got_stderr_line(const std::string& line)
138     {
139         m_writer.stderr_tc(line);
140     }
141 
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)
153     {
154     }
155 
156     size_t
157     failed(void)
158         const
159     {
160         return m_failed;
161     }
162 
163     void
164     finalize(const std::string& reason = "")
165     {
166         PRE(!m_finalized);
167 
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"));
174         }
175 
176         m_writer.end_tp(reason);
177         m_finalized = true;
178     }
179 
180     ~muxer(void)
181     {
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);
185     }
186 };
187 
188 template< class K, class V >
189 void
190 merge_maps(std::map< K, V >& dest, const std::map< K, V >& src)
191 {
192     for (typename std::map< K, V >::const_iterator iter = src.begin();
193          iter != src.end(); iter++)
194         dest[(*iter).first] = (*iter).second;
195 }
196 
197 class atf_run : public atf::application::app {
198     static const char* m_description;
199 
200     atf::tests::vars_map m_atffile_vars;
201     atf::tests::vars_map m_cmdline_vars;
202     atf::tests::vars_map m_config_vars;
203 
204     static atf::tests::vars_map::value_type parse_var(const std::string&);
205 
206     void process_option(int, const char*);
207     std::string specific_args(void) const;
208     options_set specific_options(void) const;
209 
210     void parse_vflag(const std::string&);
211 
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;
215 
216     size_t count_tps(std::vector< std::string >) const;
217 
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&);
224 
225     struct test_data {
226         const atf_run* m_this;
227         const atf::fs::path& m_tp;
228         atf::io::pipe& m_respipe;
229 
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)
235         {
236         }
237     };
238 
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&);
246 
247 public:
248     atf_run(void);
249 
250     int main(void);
251 };
252 
253 const char* atf_run::m_description =
254     "atf-run is a tool that runs tests programs and collects their "
255     "results.";
256 
257 atf_run::atf_run(void) :
258     app(m_description, "atf-run(1)", "atf(7)")
259 {
260 }
261 
262 void
263 atf_run::process_option(int ch, const char* arg)
264 {
265     switch (ch) {
266     case 'v':
267         parse_vflag(arg);
268         break;
269 
270     default:
271         UNREACHABLE;
272     }
273 }
274 
275 std::string
276 atf_run::specific_args(void)
277     const
278 {
279     return "[test-program1 .. test-programN]";
280 }
281 
282 atf_run::options_set
283 atf_run::specific_options(void)
284     const
285 {
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;
292 }
293 
294 void
295 atf_run::parse_vflag(const std::string& str)
296 {
297     if (str.empty())
298         throw std::runtime_error("-v requires a non-empty argument");
299 
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");
307 
308         m_cmdline_vars[ws[0]] = ws[1];
309     }
310 }
311 
312 int
313 atf_run::run_test(const atf::fs::path& tp,
314                   atf::formats::atf_tps_writer& w)
315 {
316     atf::fs::file_info fi(tp);
317 
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;
324 }
325 
326 int
327 atf_run::run_test_directory(const atf::fs::path& tp,
328                             atf::formats::atf_tps_writer& w)
329 {
330     atf::atffile af(tp / "Atffile");
331     m_atffile_vars = af.conf();
332 
333     atf::tests::vars_map oldvars = m_config_vars;
334     {
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);
339     }
340 
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);
345 
346     m_config_vars = oldvars;
347 
348     return ok ? EXIT_SUCCESS : EXIT_FAILURE;
349 }
350 
351 std::vector< std::string >
352 atf_run::conf_args(void) const
353 {
354     using atf::tests::vars_map;
355 
356     atf::tests::vars_map vars;
357     std::vector< std::string > args;
358 
359     merge_maps(vars, m_atffile_vars);
360     merge_maps(vars, m_config_vars);
361     merge_maps(vars, m_cmdline_vars);
362 
363     for (vars_map::const_iterator i = vars.begin(); i != vars.end(); i++)
364         args.push_back("-v" + (*i).first + "=" + (*i).second);
365 
366     return args;
367 }
368 
369 void
370 atf_run::route_run_test_program_child(void* v)
371 {
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;
375 }
376 
377 void
378 atf_run::run_test_program_child(const atf::fs::path& tp,
379                                 atf::io::pipe& respipe)
380     const
381 {
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);
387 
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()];
393     {
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());
398 
399         // 1: The file descriptor to which the results will be printed.
400         args[1] = new char[4];
401         std::strcpy(args[1], "-r9");
402 
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);
411 
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);
418         }
419 
420         // Last: Terminator.
421         args[3 + i] = NULL;
422     }
423 
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);
431 }
432 
433 int
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)
438 {
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);
444 
445     // Get the file descriptor and input stream of the results channel.
446     respipe.wend().close();
447     atf::io::pistream resin(respipe.rend());
448 
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 + ".";
462         }
463     } catch (const atf::formats::format_error& e) {
464         fmterr = e.what();
465     } catch (...) {
466         UNREACHABLE;
467     }
468 
469     try {
470         outin.close();
471         errin.close();
472         resin.close();
473     } catch (...) {
474         UNREACHABLE;
475     }
476 
477     const atf::process::status s = c.wait();
478 
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);
490         }
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;
502 }
503 
504 int
505 atf_run::run_test_program(const atf::fs::path& tp,
506                           atf::formats::atf_tps_writer& w)
507 {
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));
523 
524     return run_test_program_parent(tp, w, c, respipe);
525 }
526 
527 size_t
528 atf_run::count_tps(std::vector< std::string > tps)
529     const
530 {
531     size_t ntps = 0;
532 
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);
537 
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++;
547     }
548 
549     return ntps;
550 }
551 
552 void
553 atf_run::read_one_config(const atf::fs::path& p)
554 {
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());
560     }
561 }
562 
563 void
564 atf_run::read_config(const std::string& name)
565 {
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");
570 
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"));
576     }
577 }
578 
579 static
580 void
581 call_hook(const std::string& tool, const std::string& hook)
582 {
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");
586 
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());
593 
594 
595     if (!s.exited() || s.exitstatus() != EXIT_SUCCESS)
596         throw std::runtime_error("Failed to run the '" + hook + "' hook "
597                                  "for '" + tool + "'");
598 }
599 
600 int
601 atf_run::main(void)
602 {
603     atf::atffile af(atf::fs::path("Atffile"));
604     m_atffile_vars = af.conf();
605 
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]);
614     }
615 
616     // Read configuration data for this test suite.
617     {
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);
622     }
623 
624     atf::formats::atf_tps_writer w(std::cout);
625     call_hook("atf-run", "info_start_hook");
626     w.ntps(count_tps(tps));
627 
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);
632 
633     call_hook("atf-run", "info_end_hook");
634 
635     return ok ? EXIT_SUCCESS : EXIT_FAILURE;
636 }
637 
638 int
639 main(int argc, char* const* argv)
640 {
641     return atf_run().run(argc, argv);
642 }
643