xref: /freebsd/contrib/kyua/cli/cmd_report_html.cpp (revision 6b13d60bf49ee40626d7e3a5d5a80519f0067307)
1b0d29bc4SBrooks Davis // Copyright 2012 The Kyua Authors.
2b0d29bc4SBrooks Davis // All rights reserved.
3b0d29bc4SBrooks Davis //
4b0d29bc4SBrooks Davis // Redistribution and use in source and binary forms, with or without
5b0d29bc4SBrooks Davis // modification, are permitted provided that the following conditions are
6b0d29bc4SBrooks Davis // met:
7b0d29bc4SBrooks Davis //
8b0d29bc4SBrooks Davis // * Redistributions of source code must retain the above copyright
9b0d29bc4SBrooks Davis //   notice, this list of conditions and the following disclaimer.
10b0d29bc4SBrooks Davis // * Redistributions in binary form must reproduce the above copyright
11b0d29bc4SBrooks Davis //   notice, this list of conditions and the following disclaimer in the
12b0d29bc4SBrooks Davis //   documentation and/or other materials provided with the distribution.
13b0d29bc4SBrooks Davis // * Neither the name of Google Inc. nor the names of its contributors
14b0d29bc4SBrooks Davis //   may be used to endorse or promote products derived from this software
15b0d29bc4SBrooks Davis //   without specific prior written permission.
16b0d29bc4SBrooks Davis //
17b0d29bc4SBrooks Davis // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18b0d29bc4SBrooks Davis // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19b0d29bc4SBrooks Davis // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20b0d29bc4SBrooks Davis // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21b0d29bc4SBrooks Davis // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22b0d29bc4SBrooks Davis // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23b0d29bc4SBrooks Davis // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24b0d29bc4SBrooks Davis // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25b0d29bc4SBrooks Davis // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26b0d29bc4SBrooks Davis // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27b0d29bc4SBrooks Davis // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28b0d29bc4SBrooks Davis 
29b0d29bc4SBrooks Davis #include "cli/cmd_report_html.hpp"
30b0d29bc4SBrooks Davis 
31b0d29bc4SBrooks Davis #include <algorithm>
32b0d29bc4SBrooks Davis #include <cerrno>
33b0d29bc4SBrooks Davis #include <cstdlib>
34b0d29bc4SBrooks Davis #include <set>
35b0d29bc4SBrooks Davis #include <stdexcept>
36b0d29bc4SBrooks Davis 
37b0d29bc4SBrooks Davis #include "cli/common.ipp"
38b0d29bc4SBrooks Davis #include "drivers/scan_results.hpp"
39b0d29bc4SBrooks Davis #include "engine/filters.hpp"
40b0d29bc4SBrooks Davis #include "model/context.hpp"
41b0d29bc4SBrooks Davis #include "model/metadata.hpp"
42b0d29bc4SBrooks Davis #include "model/test_case.hpp"
43b0d29bc4SBrooks Davis #include "model/test_program.hpp"
44b0d29bc4SBrooks Davis #include "model/test_result.hpp"
45b0d29bc4SBrooks Davis #include "store/layout.hpp"
46b0d29bc4SBrooks Davis #include "store/read_transaction.hpp"
47b0d29bc4SBrooks Davis #include "utils/cmdline/options.hpp"
48b0d29bc4SBrooks Davis #include "utils/cmdline/parser.ipp"
49b0d29bc4SBrooks Davis #include "utils/cmdline/ui.hpp"
50b0d29bc4SBrooks Davis #include "utils/datetime.hpp"
51b0d29bc4SBrooks Davis #include "utils/env.hpp"
52b0d29bc4SBrooks Davis #include "utils/format/macros.hpp"
53b0d29bc4SBrooks Davis #include "utils/fs/exceptions.hpp"
54b0d29bc4SBrooks Davis #include "utils/fs/operations.hpp"
55b0d29bc4SBrooks Davis #include "utils/fs/path.hpp"
56b0d29bc4SBrooks Davis #include "utils/optional.ipp"
57*6b13d60bSMuhammad Moinur Rahman #include "utils/text/operations.hpp"
58b0d29bc4SBrooks Davis #include "utils/text/templates.hpp"
59b0d29bc4SBrooks Davis 
60b0d29bc4SBrooks Davis namespace cmdline = utils::cmdline;
61b0d29bc4SBrooks Davis namespace config = utils::config;
62b0d29bc4SBrooks Davis namespace datetime = utils::datetime;
63b0d29bc4SBrooks Davis namespace fs = utils::fs;
64b0d29bc4SBrooks Davis namespace layout = store::layout;
65b0d29bc4SBrooks Davis namespace text = utils::text;
66b0d29bc4SBrooks Davis 
67b0d29bc4SBrooks Davis using utils::optional;
68b0d29bc4SBrooks Davis 
69b0d29bc4SBrooks Davis 
70b0d29bc4SBrooks Davis namespace {
71b0d29bc4SBrooks Davis 
72b0d29bc4SBrooks Davis 
73b0d29bc4SBrooks Davis /// Creates the report's top directory and fails if it exists.
74b0d29bc4SBrooks Davis ///
75b0d29bc4SBrooks Davis /// \param directory The directory to create.
76b0d29bc4SBrooks Davis /// \param force Whether to wipe an existing directory or not.
77b0d29bc4SBrooks Davis ///
78b0d29bc4SBrooks Davis /// \throw std::runtime_error If the directory already exists; this is a user
79b0d29bc4SBrooks Davis ///     error that the user must correct.
80b0d29bc4SBrooks Davis /// \throw fs::error If the directory creation fails for any other reason.
81b0d29bc4SBrooks Davis static void
create_top_directory(const fs::path & directory,const bool force)82b0d29bc4SBrooks Davis create_top_directory(const fs::path& directory, const bool force)
83b0d29bc4SBrooks Davis {
84b0d29bc4SBrooks Davis     if (force) {
85b0d29bc4SBrooks Davis         if (fs::exists(directory))
86b0d29bc4SBrooks Davis             fs::rm_r(directory);
87b0d29bc4SBrooks Davis     }
88b0d29bc4SBrooks Davis 
89b0d29bc4SBrooks Davis     try {
90b0d29bc4SBrooks Davis         fs::mkdir(directory, 0755);
91b0d29bc4SBrooks Davis     } catch (const fs::system_error& e) {
92b0d29bc4SBrooks Davis         if (e.original_errno() == EEXIST)
93b0d29bc4SBrooks Davis             throw std::runtime_error(F("Output directory '%s' already exists; "
94b0d29bc4SBrooks Davis                                        "maybe use --force?") %
95b0d29bc4SBrooks Davis                                      directory);
96b0d29bc4SBrooks Davis         else
97b0d29bc4SBrooks Davis             throw e;
98b0d29bc4SBrooks Davis     }
99b0d29bc4SBrooks Davis }
100b0d29bc4SBrooks Davis 
101b0d29bc4SBrooks Davis 
102b0d29bc4SBrooks Davis /// Generates a flat unique filename for a given test case.
103b0d29bc4SBrooks Davis ///
104b0d29bc4SBrooks Davis /// \param test_program The test program for which to genereate the name.
105b0d29bc4SBrooks Davis /// \param test_case_name The test case name.
106b0d29bc4SBrooks Davis ///
107b0d29bc4SBrooks Davis /// \return A filename unique within a directory with a trailing HTML extension.
108b0d29bc4SBrooks Davis static std::string
test_case_filename(const model::test_program & test_program,const std::string & test_case_name)109b0d29bc4SBrooks Davis test_case_filename(const model::test_program& test_program,
110b0d29bc4SBrooks Davis                    const std::string& test_case_name)
111b0d29bc4SBrooks Davis {
112b0d29bc4SBrooks Davis     static const char* special_characters = "/:";
113b0d29bc4SBrooks Davis 
114b0d29bc4SBrooks Davis     std::string name = cli::format_test_case_id(test_program, test_case_name);
115b0d29bc4SBrooks Davis     std::string::size_type pos = name.find_first_of(special_characters);
116b0d29bc4SBrooks Davis     while (pos != std::string::npos) {
117b0d29bc4SBrooks Davis         name.replace(pos, 1, "_");
118b0d29bc4SBrooks Davis         pos = name.find_first_of(special_characters, pos + 1);
119b0d29bc4SBrooks Davis     }
120b0d29bc4SBrooks Davis     return name + ".html";
121b0d29bc4SBrooks Davis }
122b0d29bc4SBrooks Davis 
123b0d29bc4SBrooks Davis 
124b0d29bc4SBrooks Davis /// Adds a string to string map to the templates.
125b0d29bc4SBrooks Davis ///
126b0d29bc4SBrooks Davis /// \param [in,out] templates The templates to add the map to.
127b0d29bc4SBrooks Davis /// \param props The map to add to the templates.
128b0d29bc4SBrooks Davis /// \param key_vector Name of the template vector that holds the keys.
129b0d29bc4SBrooks Davis /// \param value_vector Name of the template vector that holds the values.
130b0d29bc4SBrooks Davis static void
add_map(text::templates_def & templates,const config::properties_map & props,const std::string & key_vector,const std::string & value_vector)131b0d29bc4SBrooks Davis add_map(text::templates_def& templates, const config::properties_map& props,
132b0d29bc4SBrooks Davis         const std::string& key_vector, const std::string& value_vector)
133b0d29bc4SBrooks Davis {
134b0d29bc4SBrooks Davis     templates.add_vector(key_vector);
135b0d29bc4SBrooks Davis     templates.add_vector(value_vector);
136b0d29bc4SBrooks Davis 
137b0d29bc4SBrooks Davis     for (config::properties_map::const_iterator iter = props.begin();
138b0d29bc4SBrooks Davis          iter != props.end(); ++iter) {
139b0d29bc4SBrooks Davis         templates.add_to_vector(key_vector, (*iter).first);
140b0d29bc4SBrooks Davis         templates.add_to_vector(value_vector, (*iter).second);
141b0d29bc4SBrooks Davis     }
142b0d29bc4SBrooks Davis }
143b0d29bc4SBrooks Davis 
144b0d29bc4SBrooks Davis 
145b0d29bc4SBrooks Davis /// Generates an HTML report.
146b0d29bc4SBrooks Davis class html_hooks : public drivers::scan_results::base_hooks {
147b0d29bc4SBrooks Davis     /// User interface object where to report progress.
148b0d29bc4SBrooks Davis     cmdline::ui* _ui;
149b0d29bc4SBrooks Davis 
150b0d29bc4SBrooks Davis     /// The top directory in which to create the HTML files.
151b0d29bc4SBrooks Davis     fs::path _directory;
152b0d29bc4SBrooks Davis 
153b0d29bc4SBrooks Davis     /// Collection of result types to include in the report.
154b0d29bc4SBrooks Davis     const cli::result_types& _results_filters;
155b0d29bc4SBrooks Davis 
156b0d29bc4SBrooks Davis     /// The start time of the first test.
157b0d29bc4SBrooks Davis     optional< utils::datetime::timestamp > _start_time;
158b0d29bc4SBrooks Davis 
159b0d29bc4SBrooks Davis     /// The end time of the last test.
160b0d29bc4SBrooks Davis     optional< utils::datetime::timestamp > _end_time;
161b0d29bc4SBrooks Davis 
162b0d29bc4SBrooks Davis     /// The total run time of the tests.  Note that we cannot subtract _end_time
163b0d29bc4SBrooks Davis     /// from _start_time to compute this due to parallel execution.
164b0d29bc4SBrooks Davis     utils::datetime::delta _runtime;
165b0d29bc4SBrooks Davis 
166b0d29bc4SBrooks Davis     /// Templates accumulator to generate the index.html file.
167b0d29bc4SBrooks Davis     text::templates_def _summary_templates;
168b0d29bc4SBrooks Davis 
169b0d29bc4SBrooks Davis     /// Mapping of result types to the amount of tests with such result.
170b0d29bc4SBrooks Davis     std::map< model::test_result_type, std::size_t > _types_count;
171b0d29bc4SBrooks Davis 
172b0d29bc4SBrooks Davis     /// Generates a common set of templates for all of our files.
173b0d29bc4SBrooks Davis     ///
174b0d29bc4SBrooks Davis     /// \return A new templates object with common parameters.
175b0d29bc4SBrooks Davis     static text::templates_def
common_templates(void)176b0d29bc4SBrooks Davis     common_templates(void)
177b0d29bc4SBrooks Davis     {
178b0d29bc4SBrooks Davis         text::templates_def templates;
179b0d29bc4SBrooks Davis         templates.add_variable("css", "report.css");
180b0d29bc4SBrooks Davis         return templates;
181b0d29bc4SBrooks Davis     }
182b0d29bc4SBrooks Davis 
183b0d29bc4SBrooks Davis     /// Adds a test case result to the summary.
184b0d29bc4SBrooks Davis     ///
185b0d29bc4SBrooks Davis     /// \param test_program The test program with the test case to be added.
186b0d29bc4SBrooks Davis     /// \param test_case_name Name of the test case.
187b0d29bc4SBrooks Davis     /// \param result The result of the test case.
188b0d29bc4SBrooks Davis     /// \param has_detail If true, the result of the test case has not been
189b0d29bc4SBrooks Davis     ///     filtered and therefore there exists a separate file for the test
190b0d29bc4SBrooks Davis     ///     with all of its information.
191b0d29bc4SBrooks Davis     void
add_to_summary(const model::test_program & test_program,const std::string & test_case_name,const model::test_result & result,const bool has_detail)192b0d29bc4SBrooks Davis     add_to_summary(const model::test_program& test_program,
193b0d29bc4SBrooks Davis                    const std::string& test_case_name,
194b0d29bc4SBrooks Davis                    const model::test_result& result,
195b0d29bc4SBrooks Davis                    const bool has_detail)
196b0d29bc4SBrooks Davis     {
197b0d29bc4SBrooks Davis         ++_types_count[result.type()];
198b0d29bc4SBrooks Davis 
199b0d29bc4SBrooks Davis         if (!has_detail)
200b0d29bc4SBrooks Davis             return;
201b0d29bc4SBrooks Davis 
202b0d29bc4SBrooks Davis         std::string test_cases_vector;
203b0d29bc4SBrooks Davis         std::string test_cases_file_vector;
204b0d29bc4SBrooks Davis         switch (result.type()) {
205b0d29bc4SBrooks Davis         case model::test_result_broken:
206b0d29bc4SBrooks Davis             test_cases_vector = "broken_test_cases";
207b0d29bc4SBrooks Davis             test_cases_file_vector = "broken_test_cases_file";
208b0d29bc4SBrooks Davis             break;
209b0d29bc4SBrooks Davis 
210b0d29bc4SBrooks Davis         case model::test_result_expected_failure:
211b0d29bc4SBrooks Davis             test_cases_vector = "xfail_test_cases";
212b0d29bc4SBrooks Davis             test_cases_file_vector = "xfail_test_cases_file";
213b0d29bc4SBrooks Davis             break;
214b0d29bc4SBrooks Davis 
215b0d29bc4SBrooks Davis         case model::test_result_failed:
216b0d29bc4SBrooks Davis             test_cases_vector = "failed_test_cases";
217b0d29bc4SBrooks Davis             test_cases_file_vector = "failed_test_cases_file";
218b0d29bc4SBrooks Davis             break;
219b0d29bc4SBrooks Davis 
220b0d29bc4SBrooks Davis         case model::test_result_passed:
221b0d29bc4SBrooks Davis             test_cases_vector = "passed_test_cases";
222b0d29bc4SBrooks Davis             test_cases_file_vector = "passed_test_cases_file";
223b0d29bc4SBrooks Davis             break;
224b0d29bc4SBrooks Davis 
225b0d29bc4SBrooks Davis         case model::test_result_skipped:
226b0d29bc4SBrooks Davis             test_cases_vector = "skipped_test_cases";
227b0d29bc4SBrooks Davis             test_cases_file_vector = "skipped_test_cases_file";
228b0d29bc4SBrooks Davis             break;
229b0d29bc4SBrooks Davis         }
230b0d29bc4SBrooks Davis         INV(!test_cases_vector.empty());
231b0d29bc4SBrooks Davis         INV(!test_cases_file_vector.empty());
232b0d29bc4SBrooks Davis 
233b0d29bc4SBrooks Davis         _summary_templates.add_to_vector(
234b0d29bc4SBrooks Davis             test_cases_vector,
235b0d29bc4SBrooks Davis             cli::format_test_case_id(test_program, test_case_name));
236b0d29bc4SBrooks Davis         _summary_templates.add_to_vector(
237b0d29bc4SBrooks Davis             test_cases_file_vector,
238b0d29bc4SBrooks Davis             test_case_filename(test_program, test_case_name));
239b0d29bc4SBrooks Davis     }
240b0d29bc4SBrooks Davis 
241b0d29bc4SBrooks Davis     /// Instantiate a template to generate an HTML file in the output directory.
242b0d29bc4SBrooks Davis     ///
243b0d29bc4SBrooks Davis     /// \param templates The templates to use.
244b0d29bc4SBrooks Davis     /// \param template_name The name of the template.  This is automatically
245b0d29bc4SBrooks Davis     ///     searched for in the installed directory, so do not provide a path.
246b0d29bc4SBrooks Davis     /// \param output_name The name of the output file.  This is a basename to
247b0d29bc4SBrooks Davis     ///     be created within the output directory.
248b0d29bc4SBrooks Davis     ///
249b0d29bc4SBrooks Davis     /// \throw text::error If there is any problem applying the templates.
250b0d29bc4SBrooks Davis     void
generate(const text::templates_def & templates,const std::string & template_name,const std::string & output_name) const251b0d29bc4SBrooks Davis     generate(const text::templates_def& templates,
252b0d29bc4SBrooks Davis              const std::string& template_name,
253b0d29bc4SBrooks Davis              const std::string& output_name) const
254b0d29bc4SBrooks Davis     {
255b0d29bc4SBrooks Davis         const fs::path miscdir(utils::getenv_with_default(
256b0d29bc4SBrooks Davis              "KYUA_MISCDIR", KYUA_MISCDIR));
257b0d29bc4SBrooks Davis         const fs::path template_file = miscdir / template_name;
258b0d29bc4SBrooks Davis         const fs::path output_path(_directory / output_name);
259b0d29bc4SBrooks Davis 
260b0d29bc4SBrooks Davis         _ui->out(F("Generating %s") % output_path);
261b0d29bc4SBrooks Davis         text::instantiate(templates, template_file, output_path);
262b0d29bc4SBrooks Davis     }
263b0d29bc4SBrooks Davis 
264b0d29bc4SBrooks Davis     /// Gets the number of tests with a given result type.
265b0d29bc4SBrooks Davis     ///
266b0d29bc4SBrooks Davis     /// \param type The type to be queried.
267b0d29bc4SBrooks Davis     ///
268b0d29bc4SBrooks Davis     /// \return The number of tests of the given type, or 0 if none have yet
269b0d29bc4SBrooks Davis     /// been registered by add_to_summary().
270b0d29bc4SBrooks Davis     std::size_t
get_count(const model::test_result_type type) const271b0d29bc4SBrooks Davis     get_count(const model::test_result_type type) const
272b0d29bc4SBrooks Davis     {
273b0d29bc4SBrooks Davis         const std::map< model::test_result_type, std::size_t >::const_iterator
274b0d29bc4SBrooks Davis             iter = _types_count.find(type);
275b0d29bc4SBrooks Davis         if (iter == _types_count.end())
276b0d29bc4SBrooks Davis             return 0;
277b0d29bc4SBrooks Davis         else
278b0d29bc4SBrooks Davis             return (*iter).second;
279b0d29bc4SBrooks Davis     }
280b0d29bc4SBrooks Davis 
281b0d29bc4SBrooks Davis public:
282b0d29bc4SBrooks Davis     /// Constructor for the hooks.
283b0d29bc4SBrooks Davis     ///
284b0d29bc4SBrooks Davis     /// \param ui_ User interface object where to report progress.
285b0d29bc4SBrooks Davis     /// \param directory_ The directory in which to create the HTML files.
286b0d29bc4SBrooks Davis     /// \param results_filters_ The result types to include in the report.
287b0d29bc4SBrooks Davis     ///     Cannot be empty.
html_hooks(cmdline::ui * ui_,const fs::path & directory_,const cli::result_types & results_filters_)288b0d29bc4SBrooks Davis     html_hooks(cmdline::ui* ui_, const fs::path& directory_,
289b0d29bc4SBrooks Davis                const cli::result_types& results_filters_) :
290b0d29bc4SBrooks Davis         _ui(ui_),
291b0d29bc4SBrooks Davis         _directory(directory_),
292b0d29bc4SBrooks Davis         _results_filters(results_filters_),
293b0d29bc4SBrooks Davis         _summary_templates(common_templates())
294b0d29bc4SBrooks Davis     {
295b0d29bc4SBrooks Davis         PRE(!results_filters_.empty());
296b0d29bc4SBrooks Davis 
297b0d29bc4SBrooks Davis         // Keep in sync with add_to_summary().
298b0d29bc4SBrooks Davis         _summary_templates.add_vector("broken_test_cases");
299b0d29bc4SBrooks Davis         _summary_templates.add_vector("broken_test_cases_file");
300b0d29bc4SBrooks Davis         _summary_templates.add_vector("xfail_test_cases");
301b0d29bc4SBrooks Davis         _summary_templates.add_vector("xfail_test_cases_file");
302b0d29bc4SBrooks Davis         _summary_templates.add_vector("failed_test_cases");
303b0d29bc4SBrooks Davis         _summary_templates.add_vector("failed_test_cases_file");
304b0d29bc4SBrooks Davis         _summary_templates.add_vector("passed_test_cases");
305b0d29bc4SBrooks Davis         _summary_templates.add_vector("passed_test_cases_file");
306b0d29bc4SBrooks Davis         _summary_templates.add_vector("skipped_test_cases");
307b0d29bc4SBrooks Davis         _summary_templates.add_vector("skipped_test_cases_file");
308b0d29bc4SBrooks Davis     }
309b0d29bc4SBrooks Davis 
310b0d29bc4SBrooks Davis     /// Callback executed when the context is loaded.
311b0d29bc4SBrooks Davis     ///
312b0d29bc4SBrooks Davis     /// \param context The context loaded from the database.
313b0d29bc4SBrooks Davis     void
got_context(const model::context & context)314b0d29bc4SBrooks Davis     got_context(const model::context& context)
315b0d29bc4SBrooks Davis     {
316b0d29bc4SBrooks Davis         text::templates_def templates = common_templates();
317b0d29bc4SBrooks Davis         templates.add_variable("cwd", context.cwd().str());
318b0d29bc4SBrooks Davis         add_map(templates, context.env(), "env_var", "env_var_value");
319b0d29bc4SBrooks Davis         generate(templates, "context.html", "context.html");
320b0d29bc4SBrooks Davis     }
321b0d29bc4SBrooks Davis 
322b0d29bc4SBrooks Davis     /// Callback executed when a test results is found.
323b0d29bc4SBrooks Davis     ///
324b0d29bc4SBrooks Davis     /// \param iter Container for the test result's data.
325b0d29bc4SBrooks Davis     void
got_result(store::results_iterator & iter)326b0d29bc4SBrooks Davis     got_result(store::results_iterator& iter)
327b0d29bc4SBrooks Davis     {
328b0d29bc4SBrooks Davis         const model::test_program_ptr test_program = iter.test_program();
329b0d29bc4SBrooks Davis         const std::string& test_case_name = iter.test_case_name();
330b0d29bc4SBrooks Davis         const model::test_result result = iter.result();
331b0d29bc4SBrooks Davis 
332b0d29bc4SBrooks Davis         if (std::find(_results_filters.begin(), _results_filters.end(),
333b0d29bc4SBrooks Davis                       result.type()) == _results_filters.end()) {
334b0d29bc4SBrooks Davis             add_to_summary(*test_program, test_case_name, result, false);
335b0d29bc4SBrooks Davis             return;
336b0d29bc4SBrooks Davis         }
337b0d29bc4SBrooks Davis 
338b0d29bc4SBrooks Davis         add_to_summary(*test_program, test_case_name, result, true);
339b0d29bc4SBrooks Davis 
340b0d29bc4SBrooks Davis         if (!_start_time || _start_time.get() > iter.start_time())
341b0d29bc4SBrooks Davis             _start_time = iter.start_time();
342b0d29bc4SBrooks Davis         if (!_end_time || _end_time.get() < iter.end_time())
343b0d29bc4SBrooks Davis             _end_time = iter.end_time();
344b0d29bc4SBrooks Davis 
345b0d29bc4SBrooks Davis         const datetime::delta duration = iter.end_time() - iter.start_time();
346b0d29bc4SBrooks Davis 
347b0d29bc4SBrooks Davis         _runtime += duration;
348b0d29bc4SBrooks Davis 
349b0d29bc4SBrooks Davis         text::templates_def templates = common_templates();
350b0d29bc4SBrooks Davis         templates.add_variable("test_case",
351b0d29bc4SBrooks Davis                                cli::format_test_case_id(*test_program,
352b0d29bc4SBrooks Davis                                                         test_case_name));
353b0d29bc4SBrooks Davis         templates.add_variable("test_program",
354b0d29bc4SBrooks Davis                                test_program->absolute_path().str());
355b0d29bc4SBrooks Davis         templates.add_variable("result", cli::format_result(result));
356b0d29bc4SBrooks Davis         templates.add_variable("start_time",
357b0d29bc4SBrooks Davis                                iter.start_time().to_iso8601_in_utc());
358b0d29bc4SBrooks Davis         templates.add_variable("end_time",
359b0d29bc4SBrooks Davis                                iter.end_time().to_iso8601_in_utc());
360b0d29bc4SBrooks Davis         templates.add_variable("duration", cli::format_delta(duration));
361b0d29bc4SBrooks Davis 
362b0d29bc4SBrooks Davis         const model::test_case& test_case = test_program->find(test_case_name);
363b0d29bc4SBrooks Davis         add_map(templates, test_case.get_metadata().to_properties(),
364b0d29bc4SBrooks Davis                 "metadata_var", "metadata_value");
365b0d29bc4SBrooks Davis 
366b0d29bc4SBrooks Davis         {
367b0d29bc4SBrooks Davis             const std::string stdout_text = iter.stdout_contents();
368b0d29bc4SBrooks Davis             if (!stdout_text.empty())
369*6b13d60bSMuhammad Moinur Rahman                 templates.add_variable("stdout", text::escape_xml(stdout_text));
370b0d29bc4SBrooks Davis         }
371b0d29bc4SBrooks Davis         {
372b0d29bc4SBrooks Davis             const std::string stderr_text = iter.stderr_contents();
373b0d29bc4SBrooks Davis             if (!stderr_text.empty())
374*6b13d60bSMuhammad Moinur Rahman                 templates.add_variable("stderr", text::escape_xml(stderr_text));
375b0d29bc4SBrooks Davis         }
376b0d29bc4SBrooks Davis 
377b0d29bc4SBrooks Davis         generate(templates, "test_result.html",
378b0d29bc4SBrooks Davis                  test_case_filename(*test_program, test_case_name));
379b0d29bc4SBrooks Davis     }
380b0d29bc4SBrooks Davis 
381b0d29bc4SBrooks Davis     /// Writes the index.html file in the output directory.
382b0d29bc4SBrooks Davis     ///
383b0d29bc4SBrooks Davis     /// This should only be called once all the processing has been done;
384b0d29bc4SBrooks Davis     /// i.e. when the scan_results driver returns.
385b0d29bc4SBrooks Davis     void
write_summary(void)386b0d29bc4SBrooks Davis     write_summary(void)
387b0d29bc4SBrooks Davis     {
388b0d29bc4SBrooks Davis         const std::size_t n_passed = get_count(model::test_result_passed);
389b0d29bc4SBrooks Davis         const std::size_t n_failed = get_count(model::test_result_failed);
390b0d29bc4SBrooks Davis         const std::size_t n_skipped = get_count(model::test_result_skipped);
391b0d29bc4SBrooks Davis         const std::size_t n_xfail = get_count(
392b0d29bc4SBrooks Davis             model::test_result_expected_failure);
393b0d29bc4SBrooks Davis         const std::size_t n_broken = get_count(model::test_result_broken);
394b0d29bc4SBrooks Davis 
395b0d29bc4SBrooks Davis         const std::size_t n_bad = n_broken + n_failed;
396b0d29bc4SBrooks Davis 
397b0d29bc4SBrooks Davis         if (_start_time) {
398b0d29bc4SBrooks Davis             INV(_end_time);
399b0d29bc4SBrooks Davis             _summary_templates.add_variable(
400b0d29bc4SBrooks Davis                 "start_time", _start_time.get().to_iso8601_in_utc());
401b0d29bc4SBrooks Davis             _summary_templates.add_variable(
402b0d29bc4SBrooks Davis                 "end_time", _end_time.get().to_iso8601_in_utc());
403b0d29bc4SBrooks Davis         } else {
404b0d29bc4SBrooks Davis             _summary_templates.add_variable("start_time", "No tests run");
405b0d29bc4SBrooks Davis             _summary_templates.add_variable("end_time", "No tests run");
406b0d29bc4SBrooks Davis         }
407b0d29bc4SBrooks Davis         _summary_templates.add_variable("duration",
408b0d29bc4SBrooks Davis                                         cli::format_delta(_runtime));
409b0d29bc4SBrooks Davis         _summary_templates.add_variable("passed_tests_count",
410b0d29bc4SBrooks Davis                                         F("%s") % n_passed);
411b0d29bc4SBrooks Davis         _summary_templates.add_variable("failed_tests_count",
412b0d29bc4SBrooks Davis                                         F("%s") % n_failed);
413b0d29bc4SBrooks Davis         _summary_templates.add_variable("skipped_tests_count",
414b0d29bc4SBrooks Davis                                         F("%s") % n_skipped);
415b0d29bc4SBrooks Davis         _summary_templates.add_variable("xfail_tests_count",
416b0d29bc4SBrooks Davis                                         F("%s") % n_xfail);
417b0d29bc4SBrooks Davis         _summary_templates.add_variable("broken_tests_count",
418b0d29bc4SBrooks Davis                                         F("%s") % n_broken);
419b0d29bc4SBrooks Davis         _summary_templates.add_variable("bad_tests_count", F("%s") % n_bad);
420b0d29bc4SBrooks Davis 
421b0d29bc4SBrooks Davis         generate(text::templates_def(), "report.css", "report.css");
422b0d29bc4SBrooks Davis         generate(_summary_templates, "index.html", "index.html");
423b0d29bc4SBrooks Davis     }
424b0d29bc4SBrooks Davis };
425b0d29bc4SBrooks Davis 
426b0d29bc4SBrooks Davis 
427b0d29bc4SBrooks Davis }  // anonymous namespace
428b0d29bc4SBrooks Davis 
429b0d29bc4SBrooks Davis 
430b0d29bc4SBrooks Davis /// Default constructor for cmd_report_html.
cmd_report_html(void)431b0d29bc4SBrooks Davis cli::cmd_report_html::cmd_report_html(void) : cli_command(
432b0d29bc4SBrooks Davis     "report-html", "", 0, 0,
433b0d29bc4SBrooks Davis     "Generates an HTML report with the result of a test suite run")
434b0d29bc4SBrooks Davis {
435b0d29bc4SBrooks Davis     add_option(results_file_open_option);
436b0d29bc4SBrooks Davis     add_option(cmdline::bool_option(
437b0d29bc4SBrooks Davis         "force", "Wipe the output directory before generating the new report; "
438b0d29bc4SBrooks Davis         "use care"));
439b0d29bc4SBrooks Davis     add_option(cmdline::path_option(
440b0d29bc4SBrooks Davis         "output", "The directory in which to store the HTML files",
441b0d29bc4SBrooks Davis         "path", "html"));
442b0d29bc4SBrooks Davis     add_option(cmdline::list_option(
443b0d29bc4SBrooks Davis         "results-filter", "Comma-separated list of result types to include in "
444b0d29bc4SBrooks Davis         "the report", "types", "skipped,xfail,broken,failed"));
445b0d29bc4SBrooks Davis }
446b0d29bc4SBrooks Davis 
447b0d29bc4SBrooks Davis 
448b0d29bc4SBrooks Davis /// Entry point for the "report-html" subcommand.
449b0d29bc4SBrooks Davis ///
450b0d29bc4SBrooks Davis /// \param ui Object to interact with the I/O of the program.
451b0d29bc4SBrooks Davis /// \param cmdline Representation of the command line to the subcommand.
452b0d29bc4SBrooks Davis ///
453b0d29bc4SBrooks Davis /// \return 0 if everything is OK, 1 if the statement is invalid or if there is
454b0d29bc4SBrooks Davis /// any other problem.
455b0d29bc4SBrooks Davis int
run(cmdline::ui * ui,const cmdline::parsed_cmdline & cmdline,const config::tree &)456b0d29bc4SBrooks Davis cli::cmd_report_html::run(cmdline::ui* ui,
457b0d29bc4SBrooks Davis                           const cmdline::parsed_cmdline& cmdline,
458b0d29bc4SBrooks Davis                           const config::tree& /* user_config */)
459b0d29bc4SBrooks Davis {
460b0d29bc4SBrooks Davis     const result_types types = get_result_types(cmdline);
461b0d29bc4SBrooks Davis 
462b0d29bc4SBrooks Davis     const fs::path results_file = layout::find_results(
463b0d29bc4SBrooks Davis         results_file_open(cmdline));
464b0d29bc4SBrooks Davis 
465b0d29bc4SBrooks Davis     const fs::path directory =
466b0d29bc4SBrooks Davis         cmdline.get_option< cmdline::path_option >("output");
467b0d29bc4SBrooks Davis     create_top_directory(directory, cmdline.has_option("force"));
468b0d29bc4SBrooks Davis     html_hooks hooks(ui, directory, types);
469b0d29bc4SBrooks Davis     drivers::scan_results::drive(results_file,
470b0d29bc4SBrooks Davis                                  std::set< engine::test_filter >(),
471b0d29bc4SBrooks Davis                                  hooks);
472b0d29bc4SBrooks Davis     hooks.write_summary();
473b0d29bc4SBrooks Davis 
474b0d29bc4SBrooks Davis     return EXIT_SUCCESS;
475b0d29bc4SBrooks Davis }
476