xref: /minix3/external/bsd/kyua-cli/dist/cli/cmd_report.cpp (revision 84d9c625bfea59e274550651111ae9edfdc40fbd)
111be35a1SLionel Sambuc // Copyright 2011 Google Inc.
211be35a1SLionel Sambuc // All rights reserved.
311be35a1SLionel Sambuc //
411be35a1SLionel Sambuc // Redistribution and use in source and binary forms, with or without
511be35a1SLionel Sambuc // modification, are permitted provided that the following conditions are
611be35a1SLionel Sambuc // met:
711be35a1SLionel Sambuc //
811be35a1SLionel Sambuc // * Redistributions of source code must retain the above copyright
911be35a1SLionel Sambuc //   notice, this list of conditions and the following disclaimer.
1011be35a1SLionel Sambuc // * Redistributions in binary form must reproduce the above copyright
1111be35a1SLionel Sambuc //   notice, this list of conditions and the following disclaimer in the
1211be35a1SLionel Sambuc //   documentation and/or other materials provided with the distribution.
1311be35a1SLionel Sambuc // * Neither the name of Google Inc. nor the names of its contributors
1411be35a1SLionel Sambuc //   may be used to endorse or promote products derived from this software
1511be35a1SLionel Sambuc //   without specific prior written permission.
1611be35a1SLionel Sambuc //
1711be35a1SLionel Sambuc // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
1811be35a1SLionel Sambuc // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
1911be35a1SLionel Sambuc // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
2011be35a1SLionel Sambuc // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
2111be35a1SLionel Sambuc // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
2211be35a1SLionel Sambuc // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
2311be35a1SLionel Sambuc // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
2411be35a1SLionel Sambuc // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
2511be35a1SLionel Sambuc // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
2611be35a1SLionel Sambuc // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
2711be35a1SLionel Sambuc // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
2811be35a1SLionel Sambuc 
2911be35a1SLionel Sambuc #include "cli/cmd_report.hpp"
3011be35a1SLionel Sambuc 
3111be35a1SLionel Sambuc #include <cstddef>
3211be35a1SLionel Sambuc #include <cstdlib>
3311be35a1SLionel Sambuc #include <fstream>
3411be35a1SLionel Sambuc #include <map>
3511be35a1SLionel Sambuc #include <vector>
3611be35a1SLionel Sambuc 
3711be35a1SLionel Sambuc #include "cli/common.ipp"
3811be35a1SLionel Sambuc #include "engine/action.hpp"
3911be35a1SLionel Sambuc #include "engine/context.hpp"
4011be35a1SLionel Sambuc #include "engine/drivers/scan_action.hpp"
4111be35a1SLionel Sambuc #include "engine/test_result.hpp"
4211be35a1SLionel Sambuc #include "utils/cmdline/exceptions.hpp"
4311be35a1SLionel Sambuc #include "utils/cmdline/parser.ipp"
4411be35a1SLionel Sambuc #include "utils/defs.hpp"
4511be35a1SLionel Sambuc #include "utils/format/macros.hpp"
4611be35a1SLionel Sambuc #include "utils/optional.ipp"
4711be35a1SLionel Sambuc 
4811be35a1SLionel Sambuc namespace cmdline = utils::cmdline;
4911be35a1SLionel Sambuc namespace config = utils::config;
5011be35a1SLionel Sambuc namespace datetime = utils::datetime;
5111be35a1SLionel Sambuc namespace fs = utils::fs;
5211be35a1SLionel Sambuc namespace scan_action = engine::drivers::scan_action;
5311be35a1SLionel Sambuc 
5411be35a1SLionel Sambuc using cli::cmd_report;
5511be35a1SLionel Sambuc using utils::optional;
5611be35a1SLionel Sambuc 
5711be35a1SLionel Sambuc 
5811be35a1SLionel Sambuc namespace {
5911be35a1SLionel Sambuc 
6011be35a1SLionel Sambuc 
6111be35a1SLionel Sambuc /// Generates a plain-text report intended to be printed to the console.
6211be35a1SLionel Sambuc class console_hooks : public scan_action::base_hooks {
6311be35a1SLionel Sambuc     /// Indirection to print the output to the correct file stream.
6411be35a1SLionel Sambuc     cli::file_writer _writer;
6511be35a1SLionel Sambuc 
6611be35a1SLionel Sambuc     /// Whether to include the runtime context in the output or not.
6711be35a1SLionel Sambuc     const bool _show_context;
6811be35a1SLionel Sambuc 
6911be35a1SLionel Sambuc     /// Collection of result types to include in the report.
70*84d9c625SLionel Sambuc     const cli::result_types& _results_filters;
7111be35a1SLionel Sambuc 
7211be35a1SLionel Sambuc     /// The action ID loaded.
7311be35a1SLionel Sambuc     int64_t _action_id;
7411be35a1SLionel Sambuc 
7511be35a1SLionel Sambuc     /// The total run time of the tests.
7611be35a1SLionel Sambuc     datetime::delta _runtime;
7711be35a1SLionel Sambuc 
7811be35a1SLionel Sambuc     /// Representation of a single result.
7911be35a1SLionel Sambuc     struct result_data {
8011be35a1SLionel Sambuc         /// The relative path to the test program.
8111be35a1SLionel Sambuc         fs::path binary_path;
8211be35a1SLionel Sambuc 
8311be35a1SLionel Sambuc         /// The name of the test case.
8411be35a1SLionel Sambuc         std::string test_case_name;
8511be35a1SLionel Sambuc 
8611be35a1SLionel Sambuc         /// The result of the test case.
8711be35a1SLionel Sambuc         engine::test_result result;
8811be35a1SLionel Sambuc 
8911be35a1SLionel Sambuc         /// The duration of the test case execution.
9011be35a1SLionel Sambuc         datetime::delta duration;
9111be35a1SLionel Sambuc 
9211be35a1SLionel Sambuc         /// Constructs a new results data.
9311be35a1SLionel Sambuc         ///
9411be35a1SLionel Sambuc         /// \param binary_path_ The relative path to the test program.
9511be35a1SLionel Sambuc         /// \param test_case_name_ The name of the test case.
9611be35a1SLionel Sambuc         /// \param result_ The result of the test case.
9711be35a1SLionel Sambuc         /// \param duration_ The duration of the test case execution.
result_data__anon1b84012d0111::console_hooks::result_data9811be35a1SLionel Sambuc         result_data(const fs::path& binary_path_,
9911be35a1SLionel Sambuc                     const std::string& test_case_name_,
10011be35a1SLionel Sambuc                     const engine::test_result& result_,
10111be35a1SLionel Sambuc                     const datetime::delta& duration_) :
10211be35a1SLionel Sambuc             binary_path(binary_path_), test_case_name(test_case_name_),
10311be35a1SLionel Sambuc             result(result_), duration(duration_)
10411be35a1SLionel Sambuc         {
10511be35a1SLionel Sambuc         }
10611be35a1SLionel Sambuc     };
10711be35a1SLionel Sambuc 
10811be35a1SLionel Sambuc     /// Results received, broken down by their type.
10911be35a1SLionel Sambuc     ///
11011be35a1SLionel Sambuc     /// Note that this may not include all results, as keeping the whole list in
11111be35a1SLionel Sambuc     /// memory may be too much.
11211be35a1SLionel Sambuc     std::map< engine::test_result::result_type,
11311be35a1SLionel Sambuc               std::vector< result_data > > _results;
11411be35a1SLionel Sambuc 
11511be35a1SLionel Sambuc     /// Prints the execution context to the output.
11611be35a1SLionel Sambuc     ///
11711be35a1SLionel Sambuc     /// \param context The context to dump.
11811be35a1SLionel Sambuc     void
print_context(const engine::context & context)11911be35a1SLionel Sambuc     print_context(const engine::context& context)
12011be35a1SLionel Sambuc     {
12111be35a1SLionel Sambuc         _writer("===> Execution context");
12211be35a1SLionel Sambuc 
12311be35a1SLionel Sambuc         _writer(F("Current directory: %s") % context.cwd());
12411be35a1SLionel Sambuc         const std::map< std::string, std::string >& env = context.env();
12511be35a1SLionel Sambuc         if (env.empty())
12611be35a1SLionel Sambuc             _writer("No environment variables recorded");
12711be35a1SLionel Sambuc         else {
12811be35a1SLionel Sambuc             _writer("Environment variables:");
12911be35a1SLionel Sambuc             for (std::map< std::string, std::string >::const_iterator
13011be35a1SLionel Sambuc                      iter = env.begin(); iter != env.end(); iter++) {
13111be35a1SLionel Sambuc                 _writer(F("    %s=%s") % (*iter).first % (*iter).second);
13211be35a1SLionel Sambuc             }
13311be35a1SLionel Sambuc         }
13411be35a1SLionel Sambuc     }
13511be35a1SLionel Sambuc 
13611be35a1SLionel Sambuc     /// Counts how many results of a given type have been received.
13711be35a1SLionel Sambuc     std::size_t
count_results(const engine::test_result::result_type type)13811be35a1SLionel Sambuc     count_results(const engine::test_result::result_type type)
13911be35a1SLionel Sambuc     {
14011be35a1SLionel Sambuc         const std::map< engine::test_result::result_type,
14111be35a1SLionel Sambuc                         std::vector< result_data > >::const_iterator iter =
14211be35a1SLionel Sambuc             _results.find(type);
14311be35a1SLionel Sambuc         if (iter == _results.end())
14411be35a1SLionel Sambuc             return 0;
14511be35a1SLionel Sambuc         else
14611be35a1SLionel Sambuc             return (*iter).second.size();
14711be35a1SLionel Sambuc     }
14811be35a1SLionel Sambuc 
14911be35a1SLionel Sambuc     /// Prints a set of results.
15011be35a1SLionel Sambuc     void
print_results(const engine::test_result::result_type type,const char * title)15111be35a1SLionel Sambuc     print_results(const engine::test_result::result_type type,
15211be35a1SLionel Sambuc                   const char* title)
15311be35a1SLionel Sambuc     {
15411be35a1SLionel Sambuc         const std::map< engine::test_result::result_type,
15511be35a1SLionel Sambuc                         std::vector< result_data > >::const_iterator iter2 =
15611be35a1SLionel Sambuc             _results.find(type);
15711be35a1SLionel Sambuc         if (iter2 == _results.end())
15811be35a1SLionel Sambuc             return;
15911be35a1SLionel Sambuc         const std::vector< result_data >& all = (*iter2).second;
16011be35a1SLionel Sambuc 
16111be35a1SLionel Sambuc         _writer(F("===> %s") % title);
16211be35a1SLionel Sambuc         for (std::vector< result_data >::const_iterator iter = all.begin();
16311be35a1SLionel Sambuc              iter != all.end(); iter++) {
16411be35a1SLionel Sambuc             _writer(F("%s:%s  ->  %s  [%s]") % (*iter).binary_path %
16511be35a1SLionel Sambuc                     (*iter).test_case_name %
16611be35a1SLionel Sambuc                     cli::format_result((*iter).result) %
16711be35a1SLionel Sambuc                     cli::format_delta((*iter).duration));
16811be35a1SLionel Sambuc         }
16911be35a1SLionel Sambuc     }
17011be35a1SLionel Sambuc 
17111be35a1SLionel Sambuc public:
17211be35a1SLionel Sambuc     /// Constructor for the hooks.
17311be35a1SLionel Sambuc     ///
17411be35a1SLionel Sambuc     /// \param ui_ The user interface object of the caller command.
17511be35a1SLionel Sambuc     /// \param outfile_ The file to which to send the output.
17611be35a1SLionel Sambuc     /// \param show_context_ Whether to include the runtime context in
17711be35a1SLionel Sambuc     ///     the output or not.
17811be35a1SLionel Sambuc     /// \param results_filters_ The result types to include in the report.
17911be35a1SLionel Sambuc     ///     Cannot be empty.
console_hooks(cmdline::ui * ui_,const fs::path & outfile_,const bool show_context_,const cli::result_types & results_filters_)18011be35a1SLionel Sambuc     console_hooks(cmdline::ui* ui_, const fs::path& outfile_,
18111be35a1SLionel Sambuc                   const bool show_context_,
182*84d9c625SLionel Sambuc                   const cli::result_types& results_filters_) :
18311be35a1SLionel Sambuc         _writer(ui_, outfile_),
18411be35a1SLionel Sambuc         _show_context(show_context_),
18511be35a1SLionel Sambuc         _results_filters(results_filters_)
18611be35a1SLionel Sambuc     {
18711be35a1SLionel Sambuc         PRE(!results_filters_.empty());
18811be35a1SLionel Sambuc     }
18911be35a1SLionel Sambuc 
19011be35a1SLionel Sambuc     /// Callback executed when an action is found.
19111be35a1SLionel Sambuc     ///
19211be35a1SLionel Sambuc     /// \param action_id The identifier of the loaded action.
19311be35a1SLionel Sambuc     /// \param action The action loaded from the database.
19411be35a1SLionel Sambuc     void
got_action(const int64_t action_id,const engine::action & action)19511be35a1SLionel Sambuc     got_action(const int64_t action_id, const engine::action& action)
19611be35a1SLionel Sambuc     {
19711be35a1SLionel Sambuc         _action_id = action_id;
19811be35a1SLionel Sambuc         if (_show_context)
19911be35a1SLionel Sambuc             print_context(action.runtime_context());
20011be35a1SLionel Sambuc     }
20111be35a1SLionel Sambuc 
20211be35a1SLionel Sambuc     /// Callback executed when a test results is found.
20311be35a1SLionel Sambuc     ///
20411be35a1SLionel Sambuc     /// \param iter Container for the test result's data.
20511be35a1SLionel Sambuc     void
got_result(store::results_iterator & iter)20611be35a1SLionel Sambuc     got_result(store::results_iterator& iter)
20711be35a1SLionel Sambuc     {
20811be35a1SLionel Sambuc         _runtime += iter.duration();
20911be35a1SLionel Sambuc         const engine::test_result result = iter.result();
21011be35a1SLionel Sambuc         _results[result.type()].push_back(
21111be35a1SLionel Sambuc             result_data(iter.test_program()->relative_path(),
21211be35a1SLionel Sambuc                         iter.test_case_name(), iter.result(), iter.duration()));
21311be35a1SLionel Sambuc     }
21411be35a1SLionel Sambuc 
21511be35a1SLionel Sambuc     /// Prints the tests summary.
21611be35a1SLionel Sambuc     void
print_tests(void)21711be35a1SLionel Sambuc     print_tests(void)
21811be35a1SLionel Sambuc     {
21911be35a1SLionel Sambuc         using engine::test_result;
22011be35a1SLionel Sambuc         typedef std::map< test_result::result_type, const char* > types_map;
22111be35a1SLionel Sambuc 
22211be35a1SLionel Sambuc         types_map titles;
22311be35a1SLionel Sambuc         titles[engine::test_result::broken] = "Broken tests";
22411be35a1SLionel Sambuc         titles[engine::test_result::expected_failure] = "Expected failures";
22511be35a1SLionel Sambuc         titles[engine::test_result::failed] = "Failed tests";
22611be35a1SLionel Sambuc         titles[engine::test_result::passed] = "Passed tests";
22711be35a1SLionel Sambuc         titles[engine::test_result::skipped] = "Skipped tests";
22811be35a1SLionel Sambuc 
229*84d9c625SLionel Sambuc         for (cli::result_types::const_iterator iter = _results_filters.begin();
23011be35a1SLionel Sambuc              iter != _results_filters.end(); ++iter) {
23111be35a1SLionel Sambuc             const types_map::const_iterator match = titles.find(*iter);
23211be35a1SLionel Sambuc             INV_MSG(match != titles.end(), "Conditional does not match user "
23311be35a1SLionel Sambuc                     "input validation in parse_types()");
23411be35a1SLionel Sambuc             print_results((*match).first, (*match).second);
23511be35a1SLionel Sambuc         }
23611be35a1SLionel Sambuc 
23711be35a1SLionel Sambuc         const std::size_t broken = count_results(test_result::broken);
23811be35a1SLionel Sambuc         const std::size_t failed = count_results(test_result::failed);
23911be35a1SLionel Sambuc         const std::size_t passed = count_results(test_result::passed);
24011be35a1SLionel Sambuc         const std::size_t skipped = count_results(test_result::skipped);
24111be35a1SLionel Sambuc         const std::size_t xfail = count_results(test_result::expected_failure);
24211be35a1SLionel Sambuc         const std::size_t total = broken + failed + passed + skipped + xfail;
24311be35a1SLionel Sambuc 
24411be35a1SLionel Sambuc         _writer("===> Summary");
24511be35a1SLionel Sambuc         _writer(F("Action: %s") % _action_id);
24611be35a1SLionel Sambuc         _writer(F("Test cases: %s total, %s skipped, %s expected failures, "
24711be35a1SLionel Sambuc                   "%s broken, %s failed") %
24811be35a1SLionel Sambuc                 total % skipped % xfail % broken % failed);
24911be35a1SLionel Sambuc         _writer(F("Total time: %s") % cli::format_delta(_runtime));
25011be35a1SLionel Sambuc     }
25111be35a1SLionel Sambuc };
25211be35a1SLionel Sambuc 
25311be35a1SLionel Sambuc 
25411be35a1SLionel Sambuc }  // anonymous namespace
25511be35a1SLionel Sambuc 
25611be35a1SLionel Sambuc 
25711be35a1SLionel Sambuc const fs::path cli::file_writer::_stdout_path("/dev/stdout");
25811be35a1SLionel Sambuc const fs::path cli::file_writer::_stderr_path("/dev/stderr");
25911be35a1SLionel Sambuc 
26011be35a1SLionel Sambuc 
26111be35a1SLionel Sambuc /// Constructs a new file_writer wrapper.
26211be35a1SLionel Sambuc ///
26311be35a1SLionel Sambuc /// \param ui_ The UI object of the caller command.
26411be35a1SLionel Sambuc /// \param path_ The path to the output file.
file_writer(cmdline::ui * const ui_,const fs::path & path_)26511be35a1SLionel Sambuc cli::file_writer::file_writer(cmdline::ui* const ui_, const fs::path& path_) :
26611be35a1SLionel Sambuc     _ui(ui_), _output_path(path_)
26711be35a1SLionel Sambuc {
26811be35a1SLionel Sambuc     if (path_ != _stdout_path && path_ != _stderr_path) {
26911be35a1SLionel Sambuc         _output_file.reset(new std::ofstream(path_.c_str()));
27011be35a1SLionel Sambuc         if (!*(_output_file)) {
27111be35a1SLionel Sambuc             throw std::runtime_error(F("Cannot open output file %s") % path_);
27211be35a1SLionel Sambuc         }
27311be35a1SLionel Sambuc     }
27411be35a1SLionel Sambuc }
27511be35a1SLionel Sambuc 
27611be35a1SLionel Sambuc /// Destructor.
~file_writer(void)27711be35a1SLionel Sambuc cli::file_writer::~file_writer(void)
27811be35a1SLionel Sambuc {
27911be35a1SLionel Sambuc }
28011be35a1SLionel Sambuc 
28111be35a1SLionel Sambuc /// Writes a message to the selected output.
28211be35a1SLionel Sambuc ///
28311be35a1SLionel Sambuc /// \param message The message to write; should not include a termination
28411be35a1SLionel Sambuc ///     new line.
28511be35a1SLionel Sambuc void
operator ()(const std::string & message)28611be35a1SLionel Sambuc cli::file_writer::operator()(const std::string& message)
28711be35a1SLionel Sambuc {
28811be35a1SLionel Sambuc     if (_output_path == _stdout_path)
28911be35a1SLionel Sambuc         _ui->out(message);
29011be35a1SLionel Sambuc     else if (_output_path == _stderr_path)
29111be35a1SLionel Sambuc         _ui->err(message);
29211be35a1SLionel Sambuc     else {
29311be35a1SLionel Sambuc         INV(_output_file.get() != NULL);
29411be35a1SLionel Sambuc         (*_output_file) << message << '\n';
29511be35a1SLionel Sambuc     }
29611be35a1SLionel Sambuc }
29711be35a1SLionel Sambuc 
29811be35a1SLionel Sambuc 
29911be35a1SLionel Sambuc /// Default constructor for cmd_report.
cmd_report(void)30011be35a1SLionel Sambuc cmd_report::cmd_report(void) : cli_command(
30111be35a1SLionel Sambuc     "report", "", 0, 0,
30211be35a1SLionel Sambuc     "Generates a user-friendly, plain-text report with the result of a "
30311be35a1SLionel Sambuc     "previous action")
30411be35a1SLionel Sambuc {
30511be35a1SLionel Sambuc     add_option(store_option);
30611be35a1SLionel Sambuc     add_option(cmdline::bool_option(
30711be35a1SLionel Sambuc         "show-context", "Include the execution context in the report"));
30811be35a1SLionel Sambuc     add_option(cmdline::int_option(
30911be35a1SLionel Sambuc         "action", "The action to report; if not specified, defaults to the "
31011be35a1SLionel Sambuc         "latest action in the database", "id"));
31111be35a1SLionel Sambuc     add_option(cmdline::path_option(
31211be35a1SLionel Sambuc         "output", "The file to which to write the report",
31311be35a1SLionel Sambuc         "path", "/dev/stdout"));
314*84d9c625SLionel Sambuc     add_option(results_filter_option);
31511be35a1SLionel Sambuc }
31611be35a1SLionel Sambuc 
31711be35a1SLionel Sambuc 
31811be35a1SLionel Sambuc /// Entry point for the "report" subcommand.
31911be35a1SLionel Sambuc ///
32011be35a1SLionel Sambuc /// \param ui Object to interact with the I/O of the program.
32111be35a1SLionel Sambuc /// \param cmdline Representation of the command line to the subcommand.
32211be35a1SLionel Sambuc /// \param unused_user_config The runtime configuration of the program.
32311be35a1SLionel Sambuc ///
32411be35a1SLionel Sambuc /// \return 0 if everything is OK, 1 if the statement is invalid or if there is
32511be35a1SLionel Sambuc /// any other problem.
32611be35a1SLionel Sambuc int
run(cmdline::ui * ui,const cmdline::parsed_cmdline & cmdline,const config::tree & UTILS_UNUSED_PARAM (user_config))32711be35a1SLionel Sambuc cmd_report::run(cmdline::ui* ui, const cmdline::parsed_cmdline& cmdline,
32811be35a1SLionel Sambuc                 const config::tree& UTILS_UNUSED_PARAM(user_config))
32911be35a1SLionel Sambuc {
33011be35a1SLionel Sambuc     optional< int64_t > action_id;
33111be35a1SLionel Sambuc     if (cmdline.has_option("action"))
33211be35a1SLionel Sambuc         action_id = cmdline.get_option< cmdline::int_option >("action");
33311be35a1SLionel Sambuc 
334*84d9c625SLionel Sambuc     const result_types types = get_result_types(cmdline);
33511be35a1SLionel Sambuc     console_hooks hooks(
33611be35a1SLionel Sambuc         ui, cmdline.get_option< cmdline::path_option >("output"),
33711be35a1SLionel Sambuc         cmdline.has_option("show-context"), types);
33811be35a1SLionel Sambuc     scan_action::drive(store_path(cmdline), action_id, hooks);
33911be35a1SLionel Sambuc     hooks.print_tests();
34011be35a1SLionel Sambuc 
34111be35a1SLionel Sambuc     return EXIT_SUCCESS;
34211be35a1SLionel Sambuc }
343