atf-run.cpp revision 1.1 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