]> CyberLeo.Net >> Repos - FreeBSD/releng/10.0.git/blob - contrib/atf/atf-run/atf-run.cpp
- Copy stable/10 (r259064) to releng/10.0 as part of the
[FreeBSD/releng/10.0.git] / contrib / atf / atf-run / atf-run.cpp
1 //
2 // Automated Testing Framework (atf)
3 //
4 // Copyright (c) 2007 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/param.h>
37 #include <sys/stat.h>
38 #include <sys/wait.h>
39 #include <unistd.h>
40 }
41
42 #include <algorithm>
43 #include <cerrno>
44 #include <cstdlib>
45 #include <cstring>
46 #include <fstream>
47 #include <iostream>
48 #include <map>
49 #include <string>
50
51 #include "atf-c++/detail/application.hpp"
52 #include "atf-c++/config.hpp"
53 #include "atf-c++/tests.hpp"
54
55 #include "atf-c++/detail/env.hpp"
56 #include "atf-c++/detail/exceptions.hpp"
57 #include "atf-c++/detail/fs.hpp"
58 #include "atf-c++/detail/parser.hpp"
59 #include "atf-c++/detail/process.hpp"
60 #include "atf-c++/detail/sanity.hpp"
61 #include "atf-c++/detail/text.hpp"
62
63 #include "atffile.hpp"
64 #include "config.hpp"
65 #include "fs.hpp"
66 #include "requirements.hpp"
67 #include "test-program.hpp"
68
69 namespace impl = atf::atf_run;
70
71 #if defined(MAXCOMLEN)
72 static const std::string::size_type max_core_name_length = MAXCOMLEN;
73 #else
74 static const std::string::size_type max_core_name_length = std::string::npos;
75 #endif
76
77 class atf_run : public atf::application::app {
78     static const char* m_description;
79
80     atf::tests::vars_map m_cmdline_vars;
81
82     static atf::tests::vars_map::value_type parse_var(const std::string&);
83
84     void process_option(int, const char*);
85     std::string specific_args(void) const;
86     options_set specific_options(void) const;
87
88     void parse_vflag(const std::string&);
89
90     std::vector< std::string > conf_args(void) const;
91
92     size_t count_tps(std::vector< std::string >) const;
93
94     int run_test(const atf::fs::path&, impl::atf_tps_writer&,
95                  const atf::tests::vars_map&);
96     int run_test_directory(const atf::fs::path&, impl::atf_tps_writer&);
97     int run_test_program(const atf::fs::path&, impl::atf_tps_writer&,
98                          const atf::tests::vars_map&);
99
100     impl::test_case_result get_test_case_result(const std::string&,
101         const atf::process::status&, const atf::fs::path&) const;
102
103 public:
104     atf_run(void);
105
106     int main(void);
107 };
108
109 static void
110 sanitize_gdb_env(void)
111 {
112     try {
113         atf::env::unset("TERM");
114     } catch (...) {
115         // Just swallow exceptions here; they cannot propagate into C, which
116         // is where this function is called from, and even if these exceptions
117         // appear they are benign.
118     }
119 }
120
121 static void
122 dump_stacktrace(const atf::fs::path& tp, const atf::process::status& s,
123                 const atf::fs::path& workdir, impl::atf_tps_writer& w)
124 {
125     PRE(s.signaled() && s.coredump());
126
127     w.stderr_tc("Test program crashed; attempting to get stack trace");
128
129     const atf::fs::path corename = workdir /
130         (tp.leaf_name().substr(0, max_core_name_length) + ".core");
131     if (!atf::fs::exists(corename)) {
132         w.stderr_tc("Expected file " + corename.str() + " not found");
133         return;
134     }
135
136     const atf::fs::path gdb(GDB);
137     const atf::fs::path gdbout = workdir / "gdb.out";
138     const atf::process::argv_array args(gdb.leaf_name().c_str(), "-batch",
139                                         "-q", "-ex", "bt", tp.c_str(),
140                                         corename.c_str(), NULL);
141     atf::process::status status = atf::process::exec(
142         gdb, args,
143         atf::process::stream_redirect_path(gdbout),
144         atf::process::stream_redirect_path(atf::fs::path("/dev/null")),
145         sanitize_gdb_env);
146     if (!status.exited() || status.exitstatus() != EXIT_SUCCESS) {
147         w.stderr_tc("Execution of " GDB " failed");
148         return;
149     }
150
151     std::ifstream input(gdbout.c_str());
152     if (input) {
153         std::string line;
154         while (std::getline(input, line).good())
155             w.stderr_tc(line);
156         input.close();
157     }
158
159     w.stderr_tc("Stack trace complete");
160 }
161
162 const char* atf_run::m_description =
163     "atf-run is a tool that runs tests programs and collects their "
164     "results.";
165
166 atf_run::atf_run(void) :
167     app(m_description, "atf-run(1)", "atf(7)")
168 {
169 }
170
171 void
172 atf_run::process_option(int ch, const char* arg)
173 {
174     switch (ch) {
175     case 'v':
176         parse_vflag(arg);
177         break;
178
179     default:
180         UNREACHABLE;
181     }
182 }
183
184 std::string
185 atf_run::specific_args(void)
186     const
187 {
188     return "[test-program1 .. test-programN]";
189 }
190
191 atf_run::options_set
192 atf_run::specific_options(void)
193     const
194 {
195     using atf::application::option;
196     options_set opts;
197     opts.insert(option('v', "var=value", "Sets the configuration variable "
198                                          "`var' to `value'; overrides "
199                                          "values in configuration files"));
200     return opts;
201 }
202
203 void
204 atf_run::parse_vflag(const std::string& str)
205 {
206     if (str.empty())
207         throw std::runtime_error("-v requires a non-empty argument");
208
209     std::vector< std::string > ws = atf::text::split(str, "=");
210     if (ws.size() == 1 && str[str.length() - 1] == '=') {
211         m_cmdline_vars[ws[0]] = "";
212     } else {
213         if (ws.size() != 2)
214             throw std::runtime_error("-v requires an argument of the form "
215                                      "var=value");
216
217         m_cmdline_vars[ws[0]] = ws[1];
218     }
219 }
220
221 int
222 atf_run::run_test(const atf::fs::path& tp,
223                   impl::atf_tps_writer& w,
224                   const atf::tests::vars_map& config)
225 {
226     atf::fs::file_info fi(tp);
227
228     int errcode;
229     if (fi.get_type() == atf::fs::file_info::dir_type)
230         errcode = run_test_directory(tp, w);
231     else {
232         const atf::tests::vars_map effective_config =
233             impl::merge_configs(config, m_cmdline_vars);
234
235         errcode = run_test_program(tp, w, effective_config);
236     }
237     return errcode;
238 }
239
240 int
241 atf_run::run_test_directory(const atf::fs::path& tp,
242                             impl::atf_tps_writer& w)
243 {
244     impl::atffile af = impl::read_atffile(tp / "Atffile");
245
246     atf::tests::vars_map test_suite_vars;
247     {
248         atf::tests::vars_map::const_iterator iter =
249             af.props().find("test-suite");
250         INV(iter != af.props().end());
251         test_suite_vars = impl::read_config_files((*iter).second);
252     }
253
254     bool ok = true;
255     for (std::vector< std::string >::const_iterator iter = af.tps().begin();
256          iter != af.tps().end(); iter++) {
257         const bool result = run_test(tp / *iter, w,
258             impl::merge_configs(af.conf(), test_suite_vars));
259         ok &= (result == EXIT_SUCCESS);
260     }
261
262     return ok ? EXIT_SUCCESS : EXIT_FAILURE;
263 }
264
265 impl::test_case_result
266 atf_run::get_test_case_result(const std::string& broken_reason,
267                               const atf::process::status& s,
268                               const atf::fs::path& resfile)
269     const
270 {
271     using atf::text::to_string;
272     using impl::read_test_case_result;
273     using impl::test_case_result;
274
275     if (!broken_reason.empty()) {
276         test_case_result tcr;
277
278         try {
279             tcr = read_test_case_result(resfile);
280
281             if (tcr.state() == "expected_timeout") {
282                 return tcr;
283             } else {
284                 return test_case_result("failed", -1, broken_reason);
285             }
286         } catch (const std::runtime_error&) {
287             return test_case_result("failed", -1, broken_reason);
288         }
289     }
290
291     if (s.exited()) {
292         test_case_result tcr;
293
294         try {
295             tcr = read_test_case_result(resfile);
296         } catch (const std::runtime_error& e) {
297             return test_case_result("failed", -1, "Test case exited "
298                 "normally but failed to create the results file: " +
299                 std::string(e.what()));
300         }
301
302         if (tcr.state() == "expected_death") {
303             return tcr;
304         } else if (tcr.state() == "expected_exit") {
305             if (tcr.value() == -1 || s.exitstatus() == tcr.value())
306                 return tcr;
307             else
308                 return test_case_result("failed", -1, "Test case was "
309                     "expected to exit with a " + to_string(tcr.value()) +
310                     " error code but returned " + to_string(s.exitstatus()));
311         } else if (tcr.state() == "expected_failure") {
312             if (s.exitstatus() == EXIT_SUCCESS)
313                 return tcr;
314             else
315                 return test_case_result("failed", -1, "Test case returned an "
316                     "error in expected_failure mode but it should not have");
317         } else if (tcr.state() == "expected_signal") {
318             return test_case_result("failed", -1, "Test case exited cleanly "
319                 "but was expected to receive a signal");
320         } else if (tcr.state() == "failed") {
321             if (s.exitstatus() == EXIT_SUCCESS)
322                 return test_case_result("failed", -1, "Test case "
323                     "exited successfully but reported failure");
324             else
325                 return tcr;
326         } else if (tcr.state() == "passed") {
327             if (s.exitstatus() == EXIT_SUCCESS)
328                 return tcr;
329             else
330                 return test_case_result("failed", -1, "Test case exited as "
331                     "passed but reported an error");
332         } else if (tcr.state() == "skipped") {
333             if (s.exitstatus() == EXIT_SUCCESS)
334                 return tcr;
335             else
336                 return test_case_result("failed", -1, "Test case exited as "
337                     "skipped but reported an error");
338         }
339     } else if (s.signaled()) {
340         test_case_result tcr;
341
342         try {
343             tcr = read_test_case_result(resfile);
344         } catch (const std::runtime_error&) {
345             return test_case_result("failed", -1, "Test program received "
346                 "signal " + atf::text::to_string(s.termsig()) +
347                 (s.coredump() ? " (core dumped)" : ""));
348         }
349
350         if (tcr.state() == "expected_death") {
351             return tcr;
352         } else if (tcr.state() == "expected_signal") {
353             if (tcr.value() == -1 || s.termsig() == tcr.value())
354                 return tcr;
355             else
356                 return test_case_result("failed", -1, "Test case was "
357                     "expected to exit due to a " + to_string(tcr.value()) +
358                     " signal but got " + to_string(s.termsig()));
359         } else {
360             return test_case_result("failed", -1, "Test program received "
361                 "signal " + atf::text::to_string(s.termsig()) +
362                 (s.coredump() ? " (core dumped)" : "") + " and created a "
363                 "bogus results file");
364         }
365     }
366     UNREACHABLE;
367     return test_case_result();
368 }
369
370 int
371 atf_run::run_test_program(const atf::fs::path& tp,
372                           impl::atf_tps_writer& w,
373                           const atf::tests::vars_map& config)
374 {
375     int errcode = EXIT_SUCCESS;
376
377     impl::metadata md;
378     try {
379         md = impl::get_metadata(tp, config);
380     } catch (const atf::parser::format_error& e) {
381         w.start_tp(tp.str(), 0);
382         w.end_tp("Invalid format for test case list: " + std::string(e.what()));
383         return EXIT_FAILURE;
384     } catch (const atf::parser::parse_errors& e) {
385         const std::string reason = atf::text::join(e, "; ");
386         w.start_tp(tp.str(), 0);
387         w.end_tp("Invalid format for test case list: " + reason);
388         return EXIT_FAILURE;
389     }
390
391     impl::temp_dir resdir(atf::fs::path(atf::config::get("atf_workdir")) /
392                           "atf-run.XXXXXX");
393
394     w.start_tp(tp.str(), md.test_cases.size());
395     if (md.test_cases.empty()) {
396         w.end_tp("Bogus test program: reported 0 test cases");
397         errcode = EXIT_FAILURE;
398     } else {
399         for (std::map< std::string, atf::tests::vars_map >::const_iterator iter
400              = md.test_cases.begin(); iter != md.test_cases.end(); iter++) {
401             const std::string& tcname = (*iter).first;
402             const atf::tests::vars_map& tcmd = (*iter).second;
403
404             w.start_tc(tcname);
405
406             try {
407                 const std::string& reqfail = impl::check_requirements(
408                     tcmd, config);
409                 if (!reqfail.empty()) {
410                     w.end_tc("skipped", reqfail);
411                     continue;
412                 }
413             } catch (const std::runtime_error& e) {
414                 w.end_tc("failed", e.what());
415                 errcode = EXIT_FAILURE;
416                 continue;
417             }
418
419             const std::pair< int, int > user = impl::get_required_user(
420                 tcmd, config);
421
422             atf::fs::path resfile = resdir.get_path() / "tcr";
423             INV(!atf::fs::exists(resfile));
424             try {
425                 const bool has_cleanup = atf::text::to_bool(
426                     (*tcmd.find("has.cleanup")).second);
427
428                 impl::temp_dir workdir(atf::fs::path(atf::config::get(
429                     "atf_workdir")) / "atf-run.XXXXXX");
430                 if (user.first != -1 && user.second != -1) {
431                     if (::chown(workdir.get_path().c_str(), user.first,
432                                 user.second) == -1) {
433                         throw atf::system_error("chown(" +
434                             workdir.get_path().str() + ")", "chown(2) failed",
435                             errno);
436                     }
437                     resfile = workdir.get_path() / "tcr";
438                 }
439
440                 std::pair< std::string, const atf::process::status > s =
441                     impl::run_test_case(tp, tcname, "body", tcmd, config,
442                                             resfile, workdir.get_path(), w);
443                 if (s.second.signaled() && s.second.coredump())
444                     dump_stacktrace(tp, s.second, workdir.get_path(), w);
445                 if (has_cleanup)
446                     (void)impl::run_test_case(tp, tcname, "cleanup", tcmd,
447                             config, resfile, workdir.get_path(), w);
448
449                 // TODO: Force deletion of workdir.
450
451                 impl::test_case_result tcr = get_test_case_result(s.first,
452                     s.second, resfile);
453
454                 w.end_tc(tcr.state(), tcr.reason());
455                 if (tcr.state() == "failed")
456                     errcode = EXIT_FAILURE;
457             } catch (...) {
458                 if (atf::fs::exists(resfile))
459                     atf::fs::remove(resfile);
460                 throw;
461             }
462             if (atf::fs::exists(resfile))
463                 atf::fs::remove(resfile);
464
465         }
466         w.end_tp("");
467     }
468
469     return errcode;
470 }
471
472 size_t
473 atf_run::count_tps(std::vector< std::string > tps)
474     const
475 {
476     size_t ntps = 0;
477
478     for (std::vector< std::string >::const_iterator iter = tps.begin();
479          iter != tps.end(); iter++) {
480         atf::fs::path tp(*iter);
481         atf::fs::file_info fi(tp);
482
483         if (fi.get_type() == atf::fs::file_info::dir_type) {
484             impl::atffile af = impl::read_atffile(tp / "Atffile");
485             std::vector< std::string > aux = af.tps();
486             for (std::vector< std::string >::iterator i2 = aux.begin();
487                  i2 != aux.end(); i2++)
488                 *i2 = (tp / *i2).str();
489             ntps += count_tps(aux);
490         } else
491             ntps++;
492     }
493
494     return ntps;
495 }
496
497 static
498 void
499 call_hook(const std::string& tool, const std::string& hook)
500 {
501     const atf::fs::path sh(atf::config::get("atf_shell"));
502     const atf::fs::path hooks =
503         atf::fs::path(atf::config::get("atf_pkgdatadir")) / (tool + ".hooks");
504
505     const atf::process::status s =
506         atf::process::exec(sh,
507                            atf::process::argv_array(sh.c_str(), hooks.c_str(),
508                                                     hook.c_str(), NULL),
509                            atf::process::stream_inherit(),
510                            atf::process::stream_inherit());
511
512
513     if (!s.exited() || s.exitstatus() != EXIT_SUCCESS)
514         throw std::runtime_error("Failed to run the '" + hook + "' hook "
515                                  "for '" + tool + "'");
516 }
517
518 int
519 atf_run::main(void)
520 {
521     impl::atffile af = impl::read_atffile(atf::fs::path("Atffile"));
522
523     std::vector< std::string > tps;
524     tps = af.tps();
525     if (m_argc >= 1) {
526         // TODO: Ensure that the given test names are listed in the
527         // Atffile.  Take into account that the file can be using globs.
528         tps.clear();
529         for (int i = 0; i < m_argc; i++)
530             tps.push_back(m_argv[i]);
531     }
532
533     // Read configuration data for this test suite.
534     atf::tests::vars_map test_suite_vars;
535     {
536         atf::tests::vars_map::const_iterator iter =
537             af.props().find("test-suite");
538         INV(iter != af.props().end());
539         test_suite_vars = impl::read_config_files((*iter).second);
540     }
541
542     impl::atf_tps_writer w(std::cout);
543     call_hook("atf-run", "info_start_hook");
544     w.ntps(count_tps(tps));
545
546     bool ok = true;
547     for (std::vector< std::string >::const_iterator iter = tps.begin();
548          iter != tps.end(); iter++) {
549         const bool result = run_test(atf::fs::path(*iter), w,
550             impl::merge_configs(af.conf(), test_suite_vars));
551         ok &= (result == EXIT_SUCCESS);
552     }
553
554     call_hook("atf-run", "info_end_hook");
555
556     return ok ? EXIT_SUCCESS : EXIT_FAILURE;
557 }
558
559 int
560 main(int argc, char* const* argv)
561 {
562     return atf_run().run(argc, argv);
563 }