xref: /netbsd-src/external/bsd/atf/dist/tools/atf-run.cpp (revision 274254cdae52594c1aa480a736aef78313d15c9c)
1 //
2 // Automated Testing Framework (atf)
3 //
4 // Copyright (c) 2007, 2008 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     void run_test_program_child(const atf::fs::path&,
226                                 atf::io::pipe&,
227                                 atf::io::pipe&,
228                                 atf::io::pipe&);
229     int run_test_program_parent(const atf::fs::path&,
230                                 atf::formats::atf_tps_writer&,
231                                 atf::io::pipe&,
232                                 atf::io::pipe&,
233                                 atf::io::pipe&,
234                                 pid_t);
235 
236 public:
237     atf_run(void);
238 
239     int main(void);
240 };
241 
242 const char* atf_run::m_description =
243     "atf-run is a tool that runs tests programs and collects their "
244     "results.";
245 
246 atf_run::atf_run(void) :
247     app(m_description, "atf-run(1)", "atf(7)")
248 {
249 }
250 
251 void
252 atf_run::process_option(int ch, const char* arg)
253 {
254     switch (ch) {
255     case 'v':
256         parse_vflag(arg);
257         break;
258 
259     default:
260         UNREACHABLE;
261     }
262 }
263 
264 std::string
265 atf_run::specific_args(void)
266     const
267 {
268     return "[test-program1 .. test-programN]";
269 }
270 
271 atf_run::options_set
272 atf_run::specific_options(void)
273     const
274 {
275     using atf::application::option;
276     options_set opts;
277     opts.insert(option('v', "var=value", "Sets the configuration variable "
278                                          "`var' to `value'; overrides "
279                                          "values in configuration files"));
280     return opts;
281 }
282 
283 void
284 atf_run::parse_vflag(const std::string& str)
285 {
286     if (str.empty())
287         throw std::runtime_error("-v requires a non-empty argument");
288 
289     std::vector< std::string > ws = atf::text::split(str, "=");
290     if (ws.size() == 1 && str[str.length() - 1] == '=') {
291         m_cmdline_vars[ws[0]] = "";
292     } else {
293         if (ws.size() != 2)
294             throw std::runtime_error("-v requires an argument of the form "
295                                      "var=value");
296 
297         m_cmdline_vars[ws[0]] = ws[1];
298     }
299 }
300 
301 int
302 atf_run::run_test(const atf::fs::path& tp,
303                   atf::formats::atf_tps_writer& w)
304 {
305     atf::fs::file_info fi(tp);
306 
307     int errcode;
308     if (fi.get_type() == atf::fs::file_info::dir_type)
309         errcode = run_test_directory(tp, w);
310     else
311         errcode = run_test_program(tp, w);
312     return errcode;
313 }
314 
315 int
316 atf_run::run_test_directory(const atf::fs::path& tp,
317                             atf::formats::atf_tps_writer& w)
318 {
319     atf::atffile af(tp / "Atffile");
320     m_atffile_vars = af.conf();
321 
322     atf::tests::vars_map oldvars = m_config_vars;
323     {
324         atf::tests::vars_map::const_iterator iter =
325             af.props().find("test-suite");
326         INV(iter != af.props().end());
327         read_config((*iter).second);
328     }
329 
330     bool ok = true;
331     for (std::vector< std::string >::const_iterator iter = af.tps().begin();
332          iter != af.tps().end(); iter++)
333         ok &= (run_test(tp / *iter, w) == EXIT_SUCCESS);
334 
335     m_config_vars = oldvars;
336 
337     return ok ? EXIT_SUCCESS : EXIT_FAILURE;
338 }
339 
340 std::vector< std::string >
341 atf_run::conf_args(void) const
342 {
343     using atf::tests::vars_map;
344 
345     atf::tests::vars_map vars;
346     std::vector< std::string > args;
347 
348     merge_maps(vars, m_atffile_vars);
349     merge_maps(vars, m_config_vars);
350     merge_maps(vars, m_cmdline_vars);
351 
352     for (vars_map::const_iterator i = vars.begin(); i != vars.end(); i++)
353         args.push_back("-v" + (*i).first + "=" + (*i).second);
354 
355     return args;
356 }
357 
358 void
359 atf_run::run_test_program_child(const atf::fs::path& tp,
360                                 atf::io::pipe& outpipe,
361                                 atf::io::pipe& errpipe,
362                                 atf::io::pipe& respipe)
363 {
364     // Remap stdout and stderr to point to the parent, who will capture
365     // everything sent to these.
366     outpipe.rend().close();
367     outpipe.wend().posix_remap(STDOUT_FILENO);
368     errpipe.rend().close();
369     errpipe.wend().posix_remap(STDERR_FILENO);
370 
371     // Remap the results file descriptor to point to the parent too.
372     // We use the 9th one (instead of a bigger one) because shell scripts
373     // can only use the [0..9] file descriptors in their redirections.
374     respipe.rend().close();
375     respipe.wend().posix_remap(9);
376 
377     // Prepare the test program's arguments.  We use dynamic memory and
378     // do not care to release it.  We are going to die anyway very soon,
379     // either due to exec(2) or to exit(3).
380     std::vector< std::string > confargs = conf_args();
381     char** args = new char*[4 + confargs.size()];
382     {
383         // 0: Program name.
384         std::string progname = tp.leaf_name();
385         args[0] = new char[progname.length() + 1];
386         std::strcpy(args[0], progname.c_str());
387 
388         // 1: The file descriptor to which the results will be printed.
389         args[1] = new char[4];
390         std::strcpy(args[1], "-r9");
391 
392         // 2: The directory where the test program lives.
393         atf::fs::path bp = tp.branch_path();
394         if (!bp.is_absolute())
395             bp = bp.to_absolute();
396         const char* dir = bp.c_str();
397         args[2] = new char[std::strlen(dir) + 3];
398         std::strcpy(args[2], "-s");
399         std::strcat(args[2], dir);
400 
401         // [3..last - 1]: Configuration arguments.
402         std::vector< std::string >::size_type i;
403         for (i = 0; i < confargs.size(); i++) {
404             const char* str = confargs[i].c_str();
405             args[3 + i] = new char[std::strlen(str) + 1];
406             std::strcpy(args[3 + i], str);
407         }
408 
409         // Last: Terminator.
410         args[3 + i] = NULL;
411     }
412 
413     // Do the real exec and report any errors to the parent through the
414     // only mechanism we can use: stderr.
415     // TODO Try to make this fail.
416     ::execv(tp.c_str(), args);
417     std::cerr << "Failed to execute `" << tp.str() << "': "
418               << std::strerror(errno) << std::endl;
419     std::exit(EXIT_FAILURE);
420 }
421 
422 int
423 atf_run::run_test_program_parent(const atf::fs::path& tp,
424                                  atf::formats::atf_tps_writer& w,
425                                  atf::io::pipe& outpipe,
426                                  atf::io::pipe& errpipe,
427                                  atf::io::pipe& respipe,
428                                  pid_t pid)
429 {
430     // Get the file descriptor and input stream of stdout.
431     outpipe.wend().close();
432     atf::io::unbuffered_istream outin(outpipe.rend());
433 
434     // Get the file descriptor and input stream of stderr.
435     errpipe.wend().close();
436     atf::io::unbuffered_istream errin(errpipe.rend());
437 
438     // Get the file descriptor and input stream of the results channel.
439     respipe.wend().close();
440     atf::io::pistream resin(respipe.rend());
441 
442     // Process the test case's output and multiplex it into our output
443     // stream as we read it.
444     muxer m(tp, w, resin);
445     std::string fmterr;
446     try {
447         m.read(outin, errin);
448     } catch (const atf::parser::parse_errors& e) {
449         fmterr = "There were errors parsing the output of the test "
450                  "program:";
451         for (atf::parser::parse_errors::const_iterator iter = e.begin();
452              iter != e.end(); iter++) {
453             fmterr += " Line " + atf::text::to_string((*iter).first) +
454                       ": " + (*iter).second + ".";
455         }
456     } catch (const atf::formats::format_error& e) {
457         fmterr = e.what();
458     } catch (...) {
459         UNREACHABLE;
460     }
461 
462     try {
463         outin.close();
464         errin.close();
465         resin.close();
466     } catch (...) {
467         UNREACHABLE;
468     }
469 
470     int code, status;
471     if (::waitpid(pid, &status, 0) != pid) {
472         m.finalize("waitpid(2) on the child process " +
473                    atf::text::to_string(pid) + " failed" +
474                    (fmterr.empty() ? "" : (".  " + fmterr)));
475         code = EXIT_FAILURE;
476     } else {
477         if (WIFEXITED(status)) {
478             code = WEXITSTATUS(status);
479             if (m.failed() > 0 && code == EXIT_SUCCESS) {
480                 code = EXIT_FAILURE;
481                 m.finalize("Test program returned success but some test "
482                            "cases failed" +
483                            (fmterr.empty() ? "" : (".  " + fmterr)));
484             } else {
485                 code = fmterr.empty() ? code : EXIT_FAILURE;
486                 m.finalize(fmterr);
487             }
488         } else if (WIFSIGNALED(status)) {
489             code = EXIT_FAILURE;
490             m.finalize("Test program received signal " +
491                        atf::text::to_string(WTERMSIG(status)) +
492                        (WCOREDUMP(status) ? " (core dumped)" : "") +
493                        (fmterr.empty() ? "" : (".  " + fmterr)));
494         } else
495             throw std::runtime_error
496                 ("Child process " + atf::text::to_string(pid) +
497                  " terminated with an unknown status condition " +
498                  atf::text::to_string(status));
499     }
500     return code;
501 }
502 
503 int
504 atf_run::run_test_program(const atf::fs::path& tp,
505                           atf::formats::atf_tps_writer& w)
506 {
507     int errcode;
508 
509     atf::io::pipe outpipe, errpipe, respipe;
510     pid_t pid = atf::process::fork();
511     if (pid == 0) {
512         run_test_program_child(tp, outpipe, errpipe, respipe);
513         UNREACHABLE;
514         errcode = EXIT_FAILURE;
515     } else {
516         errcode = run_test_program_parent(tp, w, outpipe, errpipe,
517                                           respipe, pid);
518     }
519 
520     return errcode;
521 }
522 
523 size_t
524 atf_run::count_tps(std::vector< std::string > tps)
525     const
526 {
527     size_t ntps = 0;
528 
529     for (std::vector< std::string >::const_iterator iter = tps.begin();
530          iter != tps.end(); iter++) {
531         atf::fs::path tp(*iter);
532         atf::fs::file_info fi(tp);
533 
534         if (fi.get_type() == atf::fs::file_info::dir_type) {
535             atf::atffile af = atf::atffile(tp / "Atffile");
536             std::vector< std::string > aux = af.tps();
537             for (std::vector< std::string >::iterator i2 = aux.begin();
538                  i2 != aux.end(); i2++)
539                 *i2 = (tp / *i2).str();
540             ntps += count_tps(aux);
541         } else
542             ntps++;
543     }
544 
545     return ntps;
546 }
547 
548 void
549 atf_run::read_one_config(const atf::fs::path& p)
550 {
551     std::ifstream is(p.c_str());
552     if (is) {
553         config reader(is);
554         reader.read();
555         merge_maps(m_config_vars, reader.get_vars());
556     }
557 }
558 
559 void
560 atf_run::read_config(const std::string& name)
561 {
562     std::vector< atf::fs::path > dirs;
563     dirs.push_back(atf::fs::path(atf::config::get("atf_confdir")));
564     if (atf::env::has("HOME"))
565         dirs.push_back(atf::fs::path(atf::env::get("HOME")) / ".atf");
566 
567     m_config_vars.clear();
568     for (std::vector< atf::fs::path >::const_iterator iter = dirs.begin();
569          iter != dirs.end(); iter++) {
570         read_one_config((*iter) / "common.conf");
571         read_one_config((*iter) / (name + ".conf"));
572     }
573 }
574 
575 static
576 void
577 call_hook(const std::string& tool, const std::string& hook)
578 {
579     std::string sh = atf::config::get("atf_shell");
580     atf::fs::path p = atf::fs::path(atf::config::get("atf_pkgdatadir")) /
581                       (tool + ".hooks");
582     std::string cmd = sh + " '" + p.str() + "' '" + hook + "'";
583     int exitcode = std::system(cmd.c_str());
584     if (!WIFEXITED(exitcode) || WEXITSTATUS(exitcode) != EXIT_SUCCESS)
585         throw std::runtime_error("Failed to run the '" + hook + "' hook "
586                                  "for '" + tool + "'; command was '" +
587                                  cmd + "'; exit code " +
588                                  atf::text::to_string(exitcode));
589 }
590 
591 int
592 atf_run::main(void)
593 {
594     atf::atffile af(atf::fs::path("Atffile"));
595     m_atffile_vars = af.conf();
596 
597     std::vector< std::string > tps;
598     tps = af.tps();
599     if (m_argc >= 1) {
600         // TODO: Ensure that the given test names are listed in the
601         // Atffile.  Take into account that the file can be using globs.
602         tps.clear();
603         for (int i = 0; i < m_argc; i++)
604             tps.push_back(m_argv[i]);
605     }
606 
607     // Read configuration data for this test suite.
608     {
609         atf::tests::vars_map::const_iterator iter =
610             af.props().find("test-suite");
611         INV(iter != af.props().end());
612         read_config((*iter).second);
613     }
614 
615     atf::formats::atf_tps_writer w(std::cout);
616     call_hook("atf-run", "info_start_hook");
617     w.ntps(count_tps(tps));
618 
619     bool ok = true;
620     for (std::vector< std::string >::const_iterator iter = tps.begin();
621          iter != tps.end(); iter++)
622         ok &= (run_test(atf::fs::path(*iter), w) == EXIT_SUCCESS);
623 
624     call_hook("atf-run", "info_end_hook");
625 
626     return ok ? EXIT_SUCCESS : EXIT_FAILURE;
627 }
628 
629 int
630 main(int argc, char* const* argv)
631 {
632     return atf_run().run(argc, argv);
633 }
634