Home | History | Annotate | Line # | Download | only in tools
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