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