Blame cli/cmd_report_html.cpp

Packit 209faa
// Copyright 2012 The Kyua Authors.
Packit 209faa
// All rights reserved.
Packit 209faa
//
Packit 209faa
// Redistribution and use in source and binary forms, with or without
Packit 209faa
// modification, are permitted provided that the following conditions are
Packit 209faa
// met:
Packit 209faa
//
Packit 209faa
// * Redistributions of source code must retain the above copyright
Packit 209faa
//   notice, this list of conditions and the following disclaimer.
Packit 209faa
// * Redistributions in binary form must reproduce the above copyright
Packit 209faa
//   notice, this list of conditions and the following disclaimer in the
Packit 209faa
//   documentation and/or other materials provided with the distribution.
Packit 209faa
// * Neither the name of Google Inc. nor the names of its contributors
Packit 209faa
//   may be used to endorse or promote products derived from this software
Packit 209faa
//   without specific prior written permission.
Packit 209faa
//
Packit 209faa
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
Packit 209faa
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
Packit 209faa
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
Packit 209faa
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
Packit 209faa
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
Packit 209faa
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
Packit 209faa
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
Packit 209faa
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
Packit 209faa
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
Packit 209faa
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
Packit 209faa
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Packit 209faa
Packit 209faa
#include "cli/cmd_report_html.hpp"
Packit 209faa
Packit 209faa
#include <algorithm>
Packit 209faa
#include <cerrno>
Packit 209faa
#include <cstdlib>
Packit 209faa
#include <set>
Packit 209faa
#include <stdexcept>
Packit 209faa
Packit 209faa
#include "cli/common.ipp"
Packit 209faa
#include "drivers/scan_results.hpp"
Packit 209faa
#include "engine/filters.hpp"
Packit 209faa
#include "model/context.hpp"
Packit 209faa
#include "model/metadata.hpp"
Packit 209faa
#include "model/test_case.hpp"
Packit 209faa
#include "model/test_program.hpp"
Packit 209faa
#include "model/test_result.hpp"
Packit 209faa
#include "store/layout.hpp"
Packit 209faa
#include "store/read_transaction.hpp"
Packit 209faa
#include "utils/cmdline/options.hpp"
Packit 209faa
#include "utils/cmdline/parser.ipp"
Packit 209faa
#include "utils/cmdline/ui.hpp"
Packit 209faa
#include "utils/datetime.hpp"
Packit 209faa
#include "utils/env.hpp"
Packit 209faa
#include "utils/format/macros.hpp"
Packit 209faa
#include "utils/fs/exceptions.hpp"
Packit 209faa
#include "utils/fs/operations.hpp"
Packit 209faa
#include "utils/fs/path.hpp"
Packit 209faa
#include "utils/optional.ipp"
Packit 209faa
#include "utils/text/templates.hpp"
Packit 209faa
Packit 209faa
namespace cmdline = utils::cmdline;
Packit 209faa
namespace config = utils::config;
Packit 209faa
namespace datetime = utils::datetime;
Packit 209faa
namespace fs = utils::fs;
Packit 209faa
namespace layout = store::layout;
Packit 209faa
namespace text = utils::text;
Packit 209faa
Packit 209faa
using utils::optional;
Packit 209faa
Packit 209faa
Packit 209faa
namespace {
Packit 209faa
Packit 209faa
Packit 209faa
/// Creates the report's top directory and fails if it exists.
Packit 209faa
///
Packit 209faa
/// \param directory The directory to create.
Packit 209faa
/// \param force Whether to wipe an existing directory or not.
Packit 209faa
///
Packit 209faa
/// \throw std::runtime_error If the directory already exists; this is a user
Packit 209faa
///     error that the user must correct.
Packit 209faa
/// \throw fs::error If the directory creation fails for any other reason.
Packit 209faa
static void
Packit 209faa
create_top_directory(const fs::path& directory, const bool force)
Packit 209faa
{
Packit 209faa
    if (force) {
Packit 209faa
        if (fs::exists(directory))
Packit 209faa
            fs::rm_r(directory);
Packit 209faa
    }
Packit 209faa
Packit 209faa
    try {
Packit 209faa
        fs::mkdir(directory, 0755);
Packit 209faa
    } catch (const fs::system_error& e) {
Packit 209faa
        if (e.original_errno() == EEXIST)
Packit 209faa
            throw std::runtime_error(F("Output directory '%s' already exists; "
Packit 209faa
                                       "maybe use --force?") %
Packit 209faa
                                     directory);
Packit 209faa
        else
Packit 209faa
            throw e;
Packit 209faa
    }
Packit 209faa
}
Packit 209faa
Packit 209faa
Packit 209faa
/// Generates a flat unique filename for a given test case.
Packit 209faa
///
Packit 209faa
/// \param test_program The test program for which to genereate the name.
Packit 209faa
/// \param test_case_name The test case name.
Packit 209faa
///
Packit 209faa
/// \return A filename unique within a directory with a trailing HTML extension.
Packit 209faa
static std::string
Packit 209faa
test_case_filename(const model::test_program& test_program,
Packit 209faa
                   const std::string& test_case_name)
Packit 209faa
{
Packit 209faa
    static const char* special_characters = "/:";
Packit 209faa
Packit 209faa
    std::string name = cli::format_test_case_id(test_program, test_case_name);
Packit 209faa
    std::string::size_type pos = name.find_first_of(special_characters);
Packit 209faa
    while (pos != std::string::npos) {
Packit 209faa
        name.replace(pos, 1, "_");
Packit 209faa
        pos = name.find_first_of(special_characters, pos + 1);
Packit 209faa
    }
Packit 209faa
    return name + ".html";
Packit 209faa
}
Packit 209faa
Packit 209faa
Packit 209faa
/// Adds a string to string map to the templates.
Packit 209faa
///
Packit 209faa
/// \param [in,out] templates The templates to add the map to.
Packit 209faa
/// \param props The map to add to the templates.
Packit 209faa
/// \param key_vector Name of the template vector that holds the keys.
Packit 209faa
/// \param value_vector Name of the template vector that holds the values.
Packit 209faa
static void
Packit 209faa
add_map(text::templates_def& templates, const config::properties_map& props,
Packit 209faa
        const std::string& key_vector, const std::string& value_vector)
Packit 209faa
{
Packit 209faa
    templates.add_vector(key_vector);
Packit 209faa
    templates.add_vector(value_vector);
Packit 209faa
Packit 209faa
    for (config::properties_map::const_iterator iter = props.begin();
Packit 209faa
         iter != props.end(); ++iter) {
Packit 209faa
        templates.add_to_vector(key_vector, (*iter).first);
Packit 209faa
        templates.add_to_vector(value_vector, (*iter).second);
Packit 209faa
    }
Packit 209faa
}
Packit 209faa
Packit 209faa
Packit 209faa
/// Generates an HTML report.
Packit 209faa
class html_hooks : public drivers::scan_results::base_hooks {
Packit 209faa
    /// User interface object where to report progress.
Packit 209faa
    cmdline::ui* _ui;
Packit 209faa
Packit 209faa
    /// The top directory in which to create the HTML files.
Packit 209faa
    fs::path _directory;
Packit 209faa
Packit 209faa
    /// Collection of result types to include in the report.
Packit 209faa
    const cli::result_types& _results_filters;
Packit 209faa
Packit 209faa
    /// The start time of the first test.
Packit 209faa
    optional< utils::datetime::timestamp > _start_time;
Packit 209faa
Packit 209faa
    /// The end time of the last test.
Packit 209faa
    optional< utils::datetime::timestamp > _end_time;
Packit 209faa
Packit 209faa
    /// The total run time of the tests.  Note that we cannot subtract _end_time
Packit 209faa
    /// from _start_time to compute this due to parallel execution.
Packit 209faa
    utils::datetime::delta _runtime;
Packit 209faa
Packit 209faa
    /// Templates accumulator to generate the index.html file.
Packit 209faa
    text::templates_def _summary_templates;
Packit 209faa
Packit 209faa
    /// Mapping of result types to the amount of tests with such result.
Packit 209faa
    std::map< model::test_result_type, std::size_t > _types_count;
Packit 209faa
Packit 209faa
    /// Generates a common set of templates for all of our files.
Packit 209faa
    ///
Packit 209faa
    /// \return A new templates object with common parameters.
Packit 209faa
    static text::templates_def
Packit 209faa
    common_templates(void)
Packit 209faa
    {
Packit 209faa
        text::templates_def templates;
Packit 209faa
        templates.add_variable("css", "report.css");
Packit 209faa
        return templates;
Packit 209faa
    }
Packit 209faa
Packit 209faa
    /// Adds a test case result to the summary.
Packit 209faa
    ///
Packit 209faa
    /// \param test_program The test program with the test case to be added.
Packit 209faa
    /// \param test_case_name Name of the test case.
Packit 209faa
    /// \param result The result of the test case.
Packit 209faa
    /// \param has_detail If true, the result of the test case has not been
Packit 209faa
    ///     filtered and therefore there exists a separate file for the test
Packit 209faa
    ///     with all of its information.
Packit 209faa
    void
Packit 209faa
    add_to_summary(const model::test_program& test_program,
Packit 209faa
                   const std::string& test_case_name,
Packit 209faa
                   const model::test_result& result,
Packit 209faa
                   const bool has_detail)
Packit 209faa
    {
Packit 209faa
        ++_types_count[result.type()];
Packit 209faa
Packit 209faa
        if (!has_detail)
Packit 209faa
            return;
Packit 209faa
Packit 209faa
        std::string test_cases_vector;
Packit 209faa
        std::string test_cases_file_vector;
Packit 209faa
        switch (result.type()) {
Packit 209faa
        case model::test_result_broken:
Packit 209faa
            test_cases_vector = "broken_test_cases";
Packit 209faa
            test_cases_file_vector = "broken_test_cases_file";
Packit 209faa
            break;
Packit 209faa
Packit 209faa
        case model::test_result_expected_failure:
Packit 209faa
            test_cases_vector = "xfail_test_cases";
Packit 209faa
            test_cases_file_vector = "xfail_test_cases_file";
Packit 209faa
            break;
Packit 209faa
Packit 209faa
        case model::test_result_failed:
Packit 209faa
            test_cases_vector = "failed_test_cases";
Packit 209faa
            test_cases_file_vector = "failed_test_cases_file";
Packit 209faa
            break;
Packit 209faa
Packit 209faa
        case model::test_result_passed:
Packit 209faa
            test_cases_vector = "passed_test_cases";
Packit 209faa
            test_cases_file_vector = "passed_test_cases_file";
Packit 209faa
            break;
Packit 209faa
Packit 209faa
        case model::test_result_skipped:
Packit 209faa
            test_cases_vector = "skipped_test_cases";
Packit 209faa
            test_cases_file_vector = "skipped_test_cases_file";
Packit 209faa
            break;
Packit 209faa
        }
Packit 209faa
        INV(!test_cases_vector.empty());
Packit 209faa
        INV(!test_cases_file_vector.empty());
Packit 209faa
Packit 209faa
        _summary_templates.add_to_vector(
Packit 209faa
            test_cases_vector,
Packit 209faa
            cli::format_test_case_id(test_program, test_case_name));
Packit 209faa
        _summary_templates.add_to_vector(
Packit 209faa
            test_cases_file_vector,
Packit 209faa
            test_case_filename(test_program, test_case_name));
Packit 209faa
    }
Packit 209faa
Packit 209faa
    /// Instantiate a template to generate an HTML file in the output directory.
Packit 209faa
    ///
Packit 209faa
    /// \param templates The templates to use.
Packit 209faa
    /// \param template_name The name of the template.  This is automatically
Packit 209faa
    ///     searched for in the installed directory, so do not provide a path.
Packit 209faa
    /// \param output_name The name of the output file.  This is a basename to
Packit 209faa
    ///     be created within the output directory.
Packit 209faa
    ///
Packit 209faa
    /// \throw text::error If there is any problem applying the templates.
Packit 209faa
    void
Packit 209faa
    generate(const text::templates_def& templates,
Packit 209faa
             const std::string& template_name,
Packit 209faa
             const std::string& output_name) const
Packit 209faa
    {
Packit 209faa
        const fs::path miscdir(utils::getenv_with_default(
Packit 209faa
             "KYUA_MISCDIR", KYUA_MISCDIR));
Packit 209faa
        const fs::path template_file = miscdir / template_name;
Packit 209faa
        const fs::path output_path(_directory / output_name);
Packit 209faa
Packit 209faa
        _ui->out(F("Generating %s") % output_path);
Packit 209faa
        text::instantiate(templates, template_file, output_path);
Packit 209faa
    }
Packit 209faa
Packit 209faa
    /// Gets the number of tests with a given result type.
Packit 209faa
    ///
Packit 209faa
    /// \param type The type to be queried.
Packit 209faa
    ///
Packit 209faa
    /// \return The number of tests of the given type, or 0 if none have yet
Packit 209faa
    /// been registered by add_to_summary().
Packit 209faa
    std::size_t
Packit 209faa
    get_count(const model::test_result_type type) const
Packit 209faa
    {
Packit 209faa
        const std::map< model::test_result_type, std::size_t >::const_iterator
Packit 209faa
            iter = _types_count.find(type);
Packit 209faa
        if (iter == _types_count.end())
Packit 209faa
            return 0;
Packit 209faa
        else
Packit 209faa
            return (*iter).second;
Packit 209faa
    }
Packit 209faa
Packit 209faa
public:
Packit 209faa
    /// Constructor for the hooks.
Packit 209faa
    ///
Packit 209faa
    /// \param ui_ User interface object where to report progress.
Packit 209faa
    /// \param directory_ The directory in which to create the HTML files.
Packit 209faa
    /// \param results_filters_ The result types to include in the report.
Packit 209faa
    ///     Cannot be empty.
Packit 209faa
    html_hooks(cmdline::ui* ui_, const fs::path& directory_,
Packit 209faa
               const cli::result_types& results_filters_) :
Packit 209faa
        _ui(ui_),
Packit 209faa
        _directory(directory_),
Packit 209faa
        _results_filters(results_filters_),
Packit 209faa
        _summary_templates(common_templates())
Packit 209faa
    {
Packit 209faa
        PRE(!results_filters_.empty());
Packit 209faa
Packit 209faa
        // Keep in sync with add_to_summary().
Packit 209faa
        _summary_templates.add_vector("broken_test_cases");
Packit 209faa
        _summary_templates.add_vector("broken_test_cases_file");
Packit 209faa
        _summary_templates.add_vector("xfail_test_cases");
Packit 209faa
        _summary_templates.add_vector("xfail_test_cases_file");
Packit 209faa
        _summary_templates.add_vector("failed_test_cases");
Packit 209faa
        _summary_templates.add_vector("failed_test_cases_file");
Packit 209faa
        _summary_templates.add_vector("passed_test_cases");
Packit 209faa
        _summary_templates.add_vector("passed_test_cases_file");
Packit 209faa
        _summary_templates.add_vector("skipped_test_cases");
Packit 209faa
        _summary_templates.add_vector("skipped_test_cases_file");
Packit 209faa
    }
Packit 209faa
Packit 209faa
    /// Callback executed when the context is loaded.
Packit 209faa
    ///
Packit 209faa
    /// \param context The context loaded from the database.
Packit 209faa
    void
Packit 209faa
    got_context(const model::context& context)
Packit 209faa
    {
Packit 209faa
        text::templates_def templates = common_templates();
Packit 209faa
        templates.add_variable("cwd", context.cwd().str());
Packit 209faa
        add_map(templates, context.env(), "env_var", "env_var_value");
Packit 209faa
        generate(templates, "context.html", "context.html");
Packit 209faa
    }
Packit 209faa
Packit 209faa
    /// Callback executed when a test results is found.
Packit 209faa
    ///
Packit 209faa
    /// \param iter Container for the test result's data.
Packit 209faa
    void
Packit 209faa
    got_result(store::results_iterator& iter)
Packit 209faa
    {
Packit 209faa
        const model::test_program_ptr test_program = iter.test_program();
Packit 209faa
        const std::string& test_case_name = iter.test_case_name();
Packit 209faa
        const model::test_result result = iter.result();
Packit 209faa
Packit 209faa
        if (std::find(_results_filters.begin(), _results_filters.end(),
Packit 209faa
                      result.type()) == _results_filters.end()) {
Packit 209faa
            add_to_summary(*test_program, test_case_name, result, false);
Packit 209faa
            return;
Packit 209faa
        }
Packit 209faa
Packit 209faa
        add_to_summary(*test_program, test_case_name, result, true);
Packit 209faa
Packit 209faa
        if (!_start_time || _start_time.get() > iter.start_time())
Packit 209faa
            _start_time = iter.start_time();
Packit 209faa
        if (!_end_time || _end_time.get() < iter.end_time())
Packit 209faa
            _end_time = iter.end_time();
Packit 209faa
Packit 209faa
        const datetime::delta duration = iter.end_time() - iter.start_time();
Packit 209faa
Packit 209faa
        _runtime += duration;
Packit 209faa
Packit 209faa
        text::templates_def templates = common_templates();
Packit 209faa
        templates.add_variable("test_case",
Packit 209faa
                               cli::format_test_case_id(*test_program,
Packit 209faa
                                                        test_case_name));
Packit 209faa
        templates.add_variable("test_program",
Packit 209faa
                               test_program->absolute_path().str());
Packit 209faa
        templates.add_variable("result", cli::format_result(result));
Packit 209faa
        templates.add_variable("start_time",
Packit 209faa
                               iter.start_time().to_iso8601_in_utc());
Packit 209faa
        templates.add_variable("end_time",
Packit 209faa
                               iter.end_time().to_iso8601_in_utc());
Packit 209faa
        templates.add_variable("duration", cli::format_delta(duration));
Packit 209faa
Packit 209faa
        const model::test_case& test_case = test_program->find(test_case_name);
Packit 209faa
        add_map(templates, test_case.get_metadata().to_properties(),
Packit 209faa
                "metadata_var", "metadata_value");
Packit 209faa
Packit 209faa
        {
Packit 209faa
            const std::string stdout_text = iter.stdout_contents();
Packit 209faa
            if (!stdout_text.empty())
Packit 209faa
                templates.add_variable("stdout", stdout_text);
Packit 209faa
        }
Packit 209faa
        {
Packit 209faa
            const std::string stderr_text = iter.stderr_contents();
Packit 209faa
            if (!stderr_text.empty())
Packit 209faa
                templates.add_variable("stderr", stderr_text);
Packit 209faa
        }
Packit 209faa
Packit 209faa
        generate(templates, "test_result.html",
Packit 209faa
                 test_case_filename(*test_program, test_case_name));
Packit 209faa
    }
Packit 209faa
Packit 209faa
    /// Writes the index.html file in the output directory.
Packit 209faa
    ///
Packit 209faa
    /// This should only be called once all the processing has been done;
Packit 209faa
    /// i.e. when the scan_results driver returns.
Packit 209faa
    void
Packit 209faa
    write_summary(void)
Packit 209faa
    {
Packit 209faa
        const std::size_t n_passed = get_count(model::test_result_passed);
Packit 209faa
        const std::size_t n_failed = get_count(model::test_result_failed);
Packit 209faa
        const std::size_t n_skipped = get_count(model::test_result_skipped);
Packit 209faa
        const std::size_t n_xfail = get_count(
Packit 209faa
            model::test_result_expected_failure);
Packit 209faa
        const std::size_t n_broken = get_count(model::test_result_broken);
Packit 209faa
Packit 209faa
        const std::size_t n_bad = n_broken + n_failed;
Packit 209faa
Packit 209faa
        if (_start_time) {
Packit 209faa
            INV(_end_time);
Packit 209faa
            _summary_templates.add_variable(
Packit 209faa
                "start_time", _start_time.get().to_iso8601_in_utc());
Packit 209faa
            _summary_templates.add_variable(
Packit 209faa
                "end_time", _end_time.get().to_iso8601_in_utc());
Packit 209faa
        } else {
Packit 209faa
            _summary_templates.add_variable("start_time", "No tests run");
Packit 209faa
            _summary_templates.add_variable("end_time", "No tests run");
Packit 209faa
        }
Packit 209faa
        _summary_templates.add_variable("duration",
Packit 209faa
                                        cli::format_delta(_runtime));
Packit 209faa
        _summary_templates.add_variable("passed_tests_count",
Packit 209faa
                                        F("%s") % n_passed);
Packit 209faa
        _summary_templates.add_variable("failed_tests_count",
Packit 209faa
                                        F("%s") % n_failed);
Packit 209faa
        _summary_templates.add_variable("skipped_tests_count",
Packit 209faa
                                        F("%s") % n_skipped);
Packit 209faa
        _summary_templates.add_variable("xfail_tests_count",
Packit 209faa
                                        F("%s") % n_xfail);
Packit 209faa
        _summary_templates.add_variable("broken_tests_count",
Packit 209faa
                                        F("%s") % n_broken);
Packit 209faa
        _summary_templates.add_variable("bad_tests_count", F("%s") % n_bad);
Packit 209faa
Packit 209faa
        generate(text::templates_def(), "report.css", "report.css");
Packit 209faa
        generate(_summary_templates, "index.html", "index.html");
Packit 209faa
    }
Packit 209faa
};
Packit 209faa
Packit 209faa
Packit 209faa
}  // anonymous namespace
Packit 209faa
Packit 209faa
Packit 209faa
/// Default constructor for cmd_report_html.
Packit 209faa
cli::cmd_report_html::cmd_report_html(void) : cli_command(
Packit 209faa
    "report-html", "", 0, 0,
Packit 209faa
    "Generates an HTML report with the result of a test suite run")
Packit 209faa
{
Packit 209faa
    add_option(results_file_open_option);
Packit 209faa
    add_option(cmdline::bool_option(
Packit 209faa
        "force", "Wipe the output directory before generating the new report; "
Packit 209faa
        "use care"));
Packit 209faa
    add_option(cmdline::path_option(
Packit 209faa
        "output", "The directory in which to store the HTML files",
Packit 209faa
        "path", "html"));
Packit 209faa
    add_option(cmdline::list_option(
Packit 209faa
        "results-filter", "Comma-separated list of result types to include in "
Packit 209faa
        "the report", "types", "skipped,xfail,broken,failed"));
Packit 209faa
}
Packit 209faa
Packit 209faa
Packit 209faa
/// Entry point for the "report-html" subcommand.
Packit 209faa
///
Packit 209faa
/// \param ui Object to interact with the I/O of the program.
Packit 209faa
/// \param cmdline Representation of the command line to the subcommand.
Packit 209faa
/// \param unused_user_config The runtime configuration of the program.
Packit 209faa
///
Packit 209faa
/// \return 0 if everything is OK, 1 if the statement is invalid or if there is
Packit 209faa
/// any other problem.
Packit 209faa
int
Packit 209faa
cli::cmd_report_html::run(cmdline::ui* ui,
Packit 209faa
                          const cmdline::parsed_cmdline& cmdline,
Packit 209faa
                          const config::tree& UTILS_UNUSED_PARAM(user_config))
Packit 209faa
{
Packit 209faa
    const result_types types = get_result_types(cmdline);
Packit 209faa
Packit 209faa
    const fs::path results_file = layout::find_results(
Packit 209faa
        results_file_open(cmdline));
Packit 209faa
Packit 209faa
    const fs::path directory =
Packit 209faa
        cmdline.get_option< cmdline::path_option >("output");
Packit 209faa
    create_top_directory(directory, cmdline.has_option("force"));
Packit 209faa
    html_hooks hooks(ui, directory, types);
Packit 209faa
    drivers::scan_results::drive(results_file,
Packit 209faa
                                 std::set< engine::test_filter >(),
Packit 209faa
                                 hooks);
Packit 209faa
    hooks.write_summary();
Packit 209faa
Packit 209faa
    return EXIT_SUCCESS;
Packit 209faa
}