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