xref: /netbsd-src/external/bsd/kyua-cli/dist/cli/cmd_report.cpp (revision f39f9c9b2b3d39fa4e71f38ebea4c5d12192a641)
16b3a42afSjmmv // Copyright 2011 Google Inc.
26b3a42afSjmmv // All rights reserved.
36b3a42afSjmmv //
46b3a42afSjmmv // Redistribution and use in source and binary forms, with or without
56b3a42afSjmmv // modification, are permitted provided that the following conditions are
66b3a42afSjmmv // met:
76b3a42afSjmmv //
86b3a42afSjmmv // * Redistributions of source code must retain the above copyright
96b3a42afSjmmv //   notice, this list of conditions and the following disclaimer.
106b3a42afSjmmv // * Redistributions in binary form must reproduce the above copyright
116b3a42afSjmmv //   notice, this list of conditions and the following disclaimer in the
126b3a42afSjmmv //   documentation and/or other materials provided with the distribution.
136b3a42afSjmmv // * Neither the name of Google Inc. nor the names of its contributors
146b3a42afSjmmv //   may be used to endorse or promote products derived from this software
156b3a42afSjmmv //   without specific prior written permission.
166b3a42afSjmmv //
176b3a42afSjmmv // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
186b3a42afSjmmv // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
196b3a42afSjmmv // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
206b3a42afSjmmv // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
216b3a42afSjmmv // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
226b3a42afSjmmv // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
236b3a42afSjmmv // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
246b3a42afSjmmv // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
256b3a42afSjmmv // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
266b3a42afSjmmv // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
276b3a42afSjmmv // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
286b3a42afSjmmv 
296b3a42afSjmmv #include "cli/cmd_report.hpp"
306b3a42afSjmmv 
316b3a42afSjmmv #include <cstddef>
326b3a42afSjmmv #include <cstdlib>
336b3a42afSjmmv #include <fstream>
346b3a42afSjmmv #include <map>
356b3a42afSjmmv #include <vector>
366b3a42afSjmmv 
376b3a42afSjmmv #include "cli/common.ipp"
386b3a42afSjmmv #include "engine/action.hpp"
396b3a42afSjmmv #include "engine/context.hpp"
406b3a42afSjmmv #include "engine/drivers/scan_action.hpp"
416b3a42afSjmmv #include "engine/test_result.hpp"
426b3a42afSjmmv #include "utils/cmdline/exceptions.hpp"
436b3a42afSjmmv #include "utils/cmdline/parser.ipp"
446b3a42afSjmmv #include "utils/defs.hpp"
456b3a42afSjmmv #include "utils/format/macros.hpp"
466b3a42afSjmmv #include "utils/optional.ipp"
476b3a42afSjmmv 
486b3a42afSjmmv namespace cmdline = utils::cmdline;
496b3a42afSjmmv namespace config = utils::config;
506b3a42afSjmmv namespace datetime = utils::datetime;
516b3a42afSjmmv namespace fs = utils::fs;
526b3a42afSjmmv namespace scan_action = engine::drivers::scan_action;
536b3a42afSjmmv 
546b3a42afSjmmv using cli::cmd_report;
556b3a42afSjmmv using utils::optional;
566b3a42afSjmmv 
576b3a42afSjmmv 
586b3a42afSjmmv namespace {
596b3a42afSjmmv 
606b3a42afSjmmv 
616b3a42afSjmmv /// Generates a plain-text report intended to be printed to the console.
626b3a42afSjmmv class console_hooks : public scan_action::base_hooks {
636b3a42afSjmmv     /// Indirection to print the output to the correct file stream.
646b3a42afSjmmv     cli::file_writer _writer;
656b3a42afSjmmv 
666b3a42afSjmmv     /// Whether to include the runtime context in the output or not.
676b3a42afSjmmv     const bool _show_context;
686b3a42afSjmmv 
696b3a42afSjmmv     /// Collection of result types to include in the report.
70*f39f9c9bSjmmv     const cli::result_types& _results_filters;
716b3a42afSjmmv 
726b3a42afSjmmv     /// The action ID loaded.
736b3a42afSjmmv     int64_t _action_id;
746b3a42afSjmmv 
756b3a42afSjmmv     /// The total run time of the tests.
766b3a42afSjmmv     datetime::delta _runtime;
776b3a42afSjmmv 
786b3a42afSjmmv     /// Representation of a single result.
796b3a42afSjmmv     struct result_data {
806b3a42afSjmmv         /// The relative path to the test program.
816b3a42afSjmmv         fs::path binary_path;
826b3a42afSjmmv 
836b3a42afSjmmv         /// The name of the test case.
846b3a42afSjmmv         std::string test_case_name;
856b3a42afSjmmv 
866b3a42afSjmmv         /// The result of the test case.
876b3a42afSjmmv         engine::test_result result;
886b3a42afSjmmv 
896b3a42afSjmmv         /// The duration of the test case execution.
906b3a42afSjmmv         datetime::delta duration;
916b3a42afSjmmv 
926b3a42afSjmmv         /// Constructs a new results data.
936b3a42afSjmmv         ///
946b3a42afSjmmv         /// \param binary_path_ The relative path to the test program.
956b3a42afSjmmv         /// \param test_case_name_ The name of the test case.
966b3a42afSjmmv         /// \param result_ The result of the test case.
976b3a42afSjmmv         /// \param duration_ The duration of the test case execution.
result_data__anon3c141dea0111::console_hooks::result_data986b3a42afSjmmv         result_data(const fs::path& binary_path_,
996b3a42afSjmmv                     const std::string& test_case_name_,
1006b3a42afSjmmv                     const engine::test_result& result_,
1016b3a42afSjmmv                     const datetime::delta& duration_) :
1026b3a42afSjmmv             binary_path(binary_path_), test_case_name(test_case_name_),
1036b3a42afSjmmv             result(result_), duration(duration_)
1046b3a42afSjmmv         {
1056b3a42afSjmmv         }
1066b3a42afSjmmv     };
1076b3a42afSjmmv 
1086b3a42afSjmmv     /// Results received, broken down by their type.
1096b3a42afSjmmv     ///
1106b3a42afSjmmv     /// Note that this may not include all results, as keeping the whole list in
1116b3a42afSjmmv     /// memory may be too much.
1126b3a42afSjmmv     std::map< engine::test_result::result_type,
1136b3a42afSjmmv               std::vector< result_data > > _results;
1146b3a42afSjmmv 
1156b3a42afSjmmv     /// Prints the execution context to the output.
1166b3a42afSjmmv     ///
1176b3a42afSjmmv     /// \param context The context to dump.
1186b3a42afSjmmv     void
print_context(const engine::context & context)1196b3a42afSjmmv     print_context(const engine::context& context)
1206b3a42afSjmmv     {
1216b3a42afSjmmv         _writer("===> Execution context");
1226b3a42afSjmmv 
1236b3a42afSjmmv         _writer(F("Current directory: %s") % context.cwd());
1246b3a42afSjmmv         const std::map< std::string, std::string >& env = context.env();
1256b3a42afSjmmv         if (env.empty())
1266b3a42afSjmmv             _writer("No environment variables recorded");
1276b3a42afSjmmv         else {
1286b3a42afSjmmv             _writer("Environment variables:");
1296b3a42afSjmmv             for (std::map< std::string, std::string >::const_iterator
1306b3a42afSjmmv                      iter = env.begin(); iter != env.end(); iter++) {
1316b3a42afSjmmv                 _writer(F("    %s=%s") % (*iter).first % (*iter).second);
1326b3a42afSjmmv             }
1336b3a42afSjmmv         }
1346b3a42afSjmmv     }
1356b3a42afSjmmv 
1366b3a42afSjmmv     /// Counts how many results of a given type have been received.
1376b3a42afSjmmv     std::size_t
count_results(const engine::test_result::result_type type)1386b3a42afSjmmv     count_results(const engine::test_result::result_type type)
1396b3a42afSjmmv     {
1406b3a42afSjmmv         const std::map< engine::test_result::result_type,
1416b3a42afSjmmv                         std::vector< result_data > >::const_iterator iter =
1426b3a42afSjmmv             _results.find(type);
1436b3a42afSjmmv         if (iter == _results.end())
1446b3a42afSjmmv             return 0;
1456b3a42afSjmmv         else
1466b3a42afSjmmv             return (*iter).second.size();
1476b3a42afSjmmv     }
1486b3a42afSjmmv 
1496b3a42afSjmmv     /// Prints a set of results.
1506b3a42afSjmmv     void
print_results(const engine::test_result::result_type type,const char * title)1516b3a42afSjmmv     print_results(const engine::test_result::result_type type,
1526b3a42afSjmmv                   const char* title)
1536b3a42afSjmmv     {
1546b3a42afSjmmv         const std::map< engine::test_result::result_type,
1556b3a42afSjmmv                         std::vector< result_data > >::const_iterator iter2 =
1566b3a42afSjmmv             _results.find(type);
1576b3a42afSjmmv         if (iter2 == _results.end())
1586b3a42afSjmmv             return;
1596b3a42afSjmmv         const std::vector< result_data >& all = (*iter2).second;
1606b3a42afSjmmv 
1616b3a42afSjmmv         _writer(F("===> %s") % title);
1626b3a42afSjmmv         for (std::vector< result_data >::const_iterator iter = all.begin();
1636b3a42afSjmmv              iter != all.end(); iter++) {
1646b3a42afSjmmv             _writer(F("%s:%s  ->  %s  [%s]") % (*iter).binary_path %
1656b3a42afSjmmv                     (*iter).test_case_name %
1666b3a42afSjmmv                     cli::format_result((*iter).result) %
1676b3a42afSjmmv                     cli::format_delta((*iter).duration));
1686b3a42afSjmmv         }
1696b3a42afSjmmv     }
1706b3a42afSjmmv 
1716b3a42afSjmmv public:
1726b3a42afSjmmv     /// Constructor for the hooks.
1736b3a42afSjmmv     ///
1746b3a42afSjmmv     /// \param ui_ The user interface object of the caller command.
1756b3a42afSjmmv     /// \param outfile_ The file to which to send the output.
1766b3a42afSjmmv     /// \param show_context_ Whether to include the runtime context in
1776b3a42afSjmmv     ///     the output or not.
1786b3a42afSjmmv     /// \param results_filters_ The result types to include in the report.
1796b3a42afSjmmv     ///     Cannot be empty.
console_hooks(cmdline::ui * ui_,const fs::path & outfile_,const bool show_context_,const cli::result_types & results_filters_)1806b3a42afSjmmv     console_hooks(cmdline::ui* ui_, const fs::path& outfile_,
1816b3a42afSjmmv                   const bool show_context_,
182*f39f9c9bSjmmv                   const cli::result_types& results_filters_) :
1836b3a42afSjmmv         _writer(ui_, outfile_),
1846b3a42afSjmmv         _show_context(show_context_),
1856b3a42afSjmmv         _results_filters(results_filters_)
1866b3a42afSjmmv     {
1876b3a42afSjmmv         PRE(!results_filters_.empty());
1886b3a42afSjmmv     }
1896b3a42afSjmmv 
1906b3a42afSjmmv     /// Callback executed when an action is found.
1916b3a42afSjmmv     ///
1926b3a42afSjmmv     /// \param action_id The identifier of the loaded action.
1936b3a42afSjmmv     /// \param action The action loaded from the database.
1946b3a42afSjmmv     void
got_action(const int64_t action_id,const engine::action & action)1956b3a42afSjmmv     got_action(const int64_t action_id, const engine::action& action)
1966b3a42afSjmmv     {
1976b3a42afSjmmv         _action_id = action_id;
1986b3a42afSjmmv         if (_show_context)
1996b3a42afSjmmv             print_context(action.runtime_context());
2006b3a42afSjmmv     }
2016b3a42afSjmmv 
2026b3a42afSjmmv     /// Callback executed when a test results is found.
2036b3a42afSjmmv     ///
2046b3a42afSjmmv     /// \param iter Container for the test result's data.
2056b3a42afSjmmv     void
got_result(store::results_iterator & iter)2066b3a42afSjmmv     got_result(store::results_iterator& iter)
2076b3a42afSjmmv     {
2086b3a42afSjmmv         _runtime += iter.duration();
2096b3a42afSjmmv         const engine::test_result result = iter.result();
2106b3a42afSjmmv         _results[result.type()].push_back(
2116b3a42afSjmmv             result_data(iter.test_program()->relative_path(),
2126b3a42afSjmmv                         iter.test_case_name(), iter.result(), iter.duration()));
2136b3a42afSjmmv     }
2146b3a42afSjmmv 
2156b3a42afSjmmv     /// Prints the tests summary.
2166b3a42afSjmmv     void
print_tests(void)2176b3a42afSjmmv     print_tests(void)
2186b3a42afSjmmv     {
2196b3a42afSjmmv         using engine::test_result;
2206b3a42afSjmmv         typedef std::map< test_result::result_type, const char* > types_map;
2216b3a42afSjmmv 
2226b3a42afSjmmv         types_map titles;
2236b3a42afSjmmv         titles[engine::test_result::broken] = "Broken tests";
2246b3a42afSjmmv         titles[engine::test_result::expected_failure] = "Expected failures";
2256b3a42afSjmmv         titles[engine::test_result::failed] = "Failed tests";
2266b3a42afSjmmv         titles[engine::test_result::passed] = "Passed tests";
2276b3a42afSjmmv         titles[engine::test_result::skipped] = "Skipped tests";
2286b3a42afSjmmv 
229*f39f9c9bSjmmv         for (cli::result_types::const_iterator iter = _results_filters.begin();
2306b3a42afSjmmv              iter != _results_filters.end(); ++iter) {
2316b3a42afSjmmv             const types_map::const_iterator match = titles.find(*iter);
2326b3a42afSjmmv             INV_MSG(match != titles.end(), "Conditional does not match user "
2336b3a42afSjmmv                     "input validation in parse_types()");
2346b3a42afSjmmv             print_results((*match).first, (*match).second);
2356b3a42afSjmmv         }
2366b3a42afSjmmv 
2376b3a42afSjmmv         const std::size_t broken = count_results(test_result::broken);
2386b3a42afSjmmv         const std::size_t failed = count_results(test_result::failed);
2396b3a42afSjmmv         const std::size_t passed = count_results(test_result::passed);
2406b3a42afSjmmv         const std::size_t skipped = count_results(test_result::skipped);
2416b3a42afSjmmv         const std::size_t xfail = count_results(test_result::expected_failure);
2426b3a42afSjmmv         const std::size_t total = broken + failed + passed + skipped + xfail;
2436b3a42afSjmmv 
2446b3a42afSjmmv         _writer("===> Summary");
2456b3a42afSjmmv         _writer(F("Action: %s") % _action_id);
2466b3a42afSjmmv         _writer(F("Test cases: %s total, %s skipped, %s expected failures, "
2476b3a42afSjmmv                   "%s broken, %s failed") %
2486b3a42afSjmmv                 total % skipped % xfail % broken % failed);
2496b3a42afSjmmv         _writer(F("Total time: %s") % cli::format_delta(_runtime));
2506b3a42afSjmmv     }
2516b3a42afSjmmv };
2526b3a42afSjmmv 
2536b3a42afSjmmv 
2546b3a42afSjmmv }  // anonymous namespace
2556b3a42afSjmmv 
2566b3a42afSjmmv 
2576b3a42afSjmmv const fs::path cli::file_writer::_stdout_path("/dev/stdout");
2586b3a42afSjmmv const fs::path cli::file_writer::_stderr_path("/dev/stderr");
2596b3a42afSjmmv 
2606b3a42afSjmmv 
2616b3a42afSjmmv /// Constructs a new file_writer wrapper.
2626b3a42afSjmmv ///
2636b3a42afSjmmv /// \param ui_ The UI object of the caller command.
2646b3a42afSjmmv /// \param path_ The path to the output file.
file_writer(cmdline::ui * const ui_,const fs::path & path_)2656b3a42afSjmmv cli::file_writer::file_writer(cmdline::ui* const ui_, const fs::path& path_) :
2666b3a42afSjmmv     _ui(ui_), _output_path(path_)
2676b3a42afSjmmv {
2686b3a42afSjmmv     if (path_ != _stdout_path && path_ != _stderr_path) {
2696b3a42afSjmmv         _output_file.reset(new std::ofstream(path_.c_str()));
2706b3a42afSjmmv         if (!*(_output_file)) {
2716b3a42afSjmmv             throw std::runtime_error(F("Cannot open output file %s") % path_);
2726b3a42afSjmmv         }
2736b3a42afSjmmv     }
2746b3a42afSjmmv }
2756b3a42afSjmmv 
2766b3a42afSjmmv /// Destructor.
~file_writer(void)2776b3a42afSjmmv cli::file_writer::~file_writer(void)
2786b3a42afSjmmv {
2796b3a42afSjmmv }
2806b3a42afSjmmv 
2816b3a42afSjmmv /// Writes a message to the selected output.
2826b3a42afSjmmv ///
2836b3a42afSjmmv /// \param message The message to write; should not include a termination
2846b3a42afSjmmv ///     new line.
2856b3a42afSjmmv void
operator ()(const std::string & message)2866b3a42afSjmmv cli::file_writer::operator()(const std::string& message)
2876b3a42afSjmmv {
2886b3a42afSjmmv     if (_output_path == _stdout_path)
2896b3a42afSjmmv         _ui->out(message);
2906b3a42afSjmmv     else if (_output_path == _stderr_path)
2916b3a42afSjmmv         _ui->err(message);
2926b3a42afSjmmv     else {
2936b3a42afSjmmv         INV(_output_file.get() != NULL);
2946b3a42afSjmmv         (*_output_file) << message << '\n';
2956b3a42afSjmmv     }
2966b3a42afSjmmv }
2976b3a42afSjmmv 
2986b3a42afSjmmv 
2996b3a42afSjmmv /// Default constructor for cmd_report.
cmd_report(void)3006b3a42afSjmmv cmd_report::cmd_report(void) : cli_command(
3016b3a42afSjmmv     "report", "", 0, 0,
3026b3a42afSjmmv     "Generates a user-friendly, plain-text report with the result of a "
3036b3a42afSjmmv     "previous action")
3046b3a42afSjmmv {
3056b3a42afSjmmv     add_option(store_option);
3066b3a42afSjmmv     add_option(cmdline::bool_option(
3076b3a42afSjmmv         "show-context", "Include the execution context in the report"));
3086b3a42afSjmmv     add_option(cmdline::int_option(
3096b3a42afSjmmv         "action", "The action to report; if not specified, defaults to the "
3106b3a42afSjmmv         "latest action in the database", "id"));
3116b3a42afSjmmv     add_option(cmdline::path_option(
3126b3a42afSjmmv         "output", "The file to which to write the report",
3136b3a42afSjmmv         "path", "/dev/stdout"));
314*f39f9c9bSjmmv     add_option(results_filter_option);
3156b3a42afSjmmv }
3166b3a42afSjmmv 
3176b3a42afSjmmv 
3186b3a42afSjmmv /// Entry point for the "report" subcommand.
3196b3a42afSjmmv ///
3206b3a42afSjmmv /// \param ui Object to interact with the I/O of the program.
3216b3a42afSjmmv /// \param cmdline Representation of the command line to the subcommand.
3226b3a42afSjmmv /// \param unused_user_config The runtime configuration of the program.
3236b3a42afSjmmv ///
3246b3a42afSjmmv /// \return 0 if everything is OK, 1 if the statement is invalid or if there is
3256b3a42afSjmmv /// any other problem.
3266b3a42afSjmmv int
run(cmdline::ui * ui,const cmdline::parsed_cmdline & cmdline,const config::tree & UTILS_UNUSED_PARAM (user_config))3276b3a42afSjmmv cmd_report::run(cmdline::ui* ui, const cmdline::parsed_cmdline& cmdline,
3286b3a42afSjmmv                 const config::tree& UTILS_UNUSED_PARAM(user_config))
3296b3a42afSjmmv {
3306b3a42afSjmmv     optional< int64_t > action_id;
3316b3a42afSjmmv     if (cmdline.has_option("action"))
3326b3a42afSjmmv         action_id = cmdline.get_option< cmdline::int_option >("action");
3336b3a42afSjmmv 
334*f39f9c9bSjmmv     const result_types types = get_result_types(cmdline);
3356b3a42afSjmmv     console_hooks hooks(
3366b3a42afSjmmv         ui, cmdline.get_option< cmdline::path_option >("output"),
3376b3a42afSjmmv         cmdline.has_option("show-context"), types);
3386b3a42afSjmmv     scan_action::drive(store_path(cmdline), action_id, hooks);
3396b3a42afSjmmv     hooks.print_tests();
3406b3a42afSjmmv 
3416b3a42afSjmmv     return EXIT_SUCCESS;
3426b3a42afSjmmv }
343