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