diff --git a/.cirrus.yml b/.cirrus.yml new file mode 100644 index 000000000000..f7e55eed37e4 --- /dev/null +++ b/.cirrus.yml @@ -0,0 +1,25 @@ +env: + ARCH: amd64 + +task: + matrix: + - name: 13.2-RELEASE + freebsd_instance: + image_family: freebsd-13-1 + - name: 13.2-STABLE + freebsd_instance: + image_family: freebsd-13-2-snap + - name: 12.4-STABLE + freebsd_instance: + image_family: freebsd-12-4-snap + - name: 12.4-RELEASE + freebsd_instance: + image_family: freebsd-12-4 + env: + DO: distcheck + install_script: + - sed -i.bak -e 's,pkg+http://pkg.FreeBSD.org/\${ABI}/quarterly,pkg+http://pkg.FreeBSD.org/\${ABI}/latest,' /etc/pkg/FreeBSD.conf + - ASSUME_ALWAYS_YES=yes pkg bootstrap -f + - pkg install -y autoconf automake atf lutok pkgconf sqlite3 + script: + - ./admin/travis-build.sh diff --git a/admin/travis-build.sh b/admin/travis-build.sh index e69f271c13f1..0ccf1cc41efb 100755 --- a/admin/travis-build.sh +++ b/admin/travis-build.sh @@ -1,98 +1,98 @@ #! /bin/sh # Copyright 2014 The Kyua Authors. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of Google Inc. nor the names of its contributors # may be used to endorse or promote products derived from this software # without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. set -e -x run_autoreconf() { if [ -d /usr/local/share/aclocal ]; then autoreconf -isv -I/usr/local/share/aclocal else autoreconf -isv fi } do_apidocs() { run_autoreconf || return 1 ./configure --with-doxygen || return 1 make check-api-docs } do_distcheck() { run_autoreconf || return 1 ./configure || return 1 sudo sysctl -w "kernel.core_pattern=core.%p" local archflags= [ "${ARCH?}" != i386 ] || archflags=-m32 cat >kyua.conf <>kyua.conf + echo "unprivileged_user = 'nobody'" >>kyua.conf local f= f="${f} CFLAGS='${archflags}'" f="${f} CPPFLAGS='-I/usr/local/include'" f="${f} CXXFLAGS='${archflags}'" f="${f} LDFLAGS='-L/usr/local/lib -Wl,-R/usr/local/lib'" f="${f} PKG_CONFIG_PATH='/usr/local/lib/pkgconfig'" f="${f} KYUA_CONFIG_FILE_FOR_CHECK=$(pwd)/kyua.conf" if [ "${AS_ROOT:-no}" = yes ]; then sudo -H PATH="${PATH}" make distcheck DISTCHECK_CONFIGURE_FLAGS="${f}" else make distcheck DISTCHECK_CONFIGURE_FLAGS="${f}" fi } do_style() { run_autoreconf || return 1 mkdir build cd build ../configure || return 1 make check-style } main() { if [ -z "${DO}" ]; then echo "DO must be defined" 1>&2 exit 1 fi for step in ${DO}; do "do_${DO}" || exit 1 done } main "${@}" diff --git a/cli/cmd_report_html.cpp b/cli/cmd_report_html.cpp index b2133a8de047..9c99e4348252 100644 --- a/cli/cmd_report_html.cpp +++ b/cli/cmd_report_html.cpp @@ -1,474 +1,475 @@ // Copyright 2012 The Kyua Authors. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // * Neither the name of Google Inc. nor the names of its contributors // may be used to endorse or promote products derived from this software // without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "cli/cmd_report_html.hpp" #include #include #include #include #include #include "cli/common.ipp" #include "drivers/scan_results.hpp" #include "engine/filters.hpp" #include "model/context.hpp" #include "model/metadata.hpp" #include "model/test_case.hpp" #include "model/test_program.hpp" #include "model/test_result.hpp" #include "store/layout.hpp" #include "store/read_transaction.hpp" #include "utils/cmdline/options.hpp" #include "utils/cmdline/parser.ipp" #include "utils/cmdline/ui.hpp" #include "utils/datetime.hpp" #include "utils/env.hpp" #include "utils/format/macros.hpp" #include "utils/fs/exceptions.hpp" #include "utils/fs/operations.hpp" #include "utils/fs/path.hpp" #include "utils/optional.ipp" +#include "utils/text/operations.hpp" #include "utils/text/templates.hpp" namespace cmdline = utils::cmdline; namespace config = utils::config; namespace datetime = utils::datetime; namespace fs = utils::fs; namespace layout = store::layout; namespace text = utils::text; using utils::optional; namespace { /// Creates the report's top directory and fails if it exists. /// /// \param directory The directory to create. /// \param force Whether to wipe an existing directory or not. /// /// \throw std::runtime_error If the directory already exists; this is a user /// error that the user must correct. /// \throw fs::error If the directory creation fails for any other reason. static void create_top_directory(const fs::path& directory, const bool force) { if (force) { if (fs::exists(directory)) fs::rm_r(directory); } try { fs::mkdir(directory, 0755); } catch (const fs::system_error& e) { if (e.original_errno() == EEXIST) throw std::runtime_error(F("Output directory '%s' already exists; " "maybe use --force?") % directory); else throw e; } } /// Generates a flat unique filename for a given test case. /// /// \param test_program The test program for which to genereate the name. /// \param test_case_name The test case name. /// /// \return A filename unique within a directory with a trailing HTML extension. static std::string test_case_filename(const model::test_program& test_program, const std::string& test_case_name) { static const char* special_characters = "/:"; std::string name = cli::format_test_case_id(test_program, test_case_name); std::string::size_type pos = name.find_first_of(special_characters); while (pos != std::string::npos) { name.replace(pos, 1, "_"); pos = name.find_first_of(special_characters, pos + 1); } return name + ".html"; } /// Adds a string to string map to the templates. /// /// \param [in,out] templates The templates to add the map to. /// \param props The map to add to the templates. /// \param key_vector Name of the template vector that holds the keys. /// \param value_vector Name of the template vector that holds the values. static void add_map(text::templates_def& templates, const config::properties_map& props, const std::string& key_vector, const std::string& value_vector) { templates.add_vector(key_vector); templates.add_vector(value_vector); for (config::properties_map::const_iterator iter = props.begin(); iter != props.end(); ++iter) { templates.add_to_vector(key_vector, (*iter).first); templates.add_to_vector(value_vector, (*iter).second); } } /// Generates an HTML report. class html_hooks : public drivers::scan_results::base_hooks { /// User interface object where to report progress. cmdline::ui* _ui; /// The top directory in which to create the HTML files. fs::path _directory; /// Collection of result types to include in the report. const cli::result_types& _results_filters; /// The start time of the first test. optional< utils::datetime::timestamp > _start_time; /// The end time of the last test. optional< utils::datetime::timestamp > _end_time; /// The total run time of the tests. Note that we cannot subtract _end_time /// from _start_time to compute this due to parallel execution. utils::datetime::delta _runtime; /// Templates accumulator to generate the index.html file. text::templates_def _summary_templates; /// Mapping of result types to the amount of tests with such result. std::map< model::test_result_type, std::size_t > _types_count; /// Generates a common set of templates for all of our files. /// /// \return A new templates object with common parameters. static text::templates_def common_templates(void) { text::templates_def templates; templates.add_variable("css", "report.css"); return templates; } /// Adds a test case result to the summary. /// /// \param test_program The test program with the test case to be added. /// \param test_case_name Name of the test case. /// \param result The result of the test case. /// \param has_detail If true, the result of the test case has not been /// filtered and therefore there exists a separate file for the test /// with all of its information. 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) { ++_types_count[result.type()]; if (!has_detail) return; std::string test_cases_vector; std::string test_cases_file_vector; switch (result.type()) { case model::test_result_broken: test_cases_vector = "broken_test_cases"; test_cases_file_vector = "broken_test_cases_file"; break; case model::test_result_expected_failure: test_cases_vector = "xfail_test_cases"; test_cases_file_vector = "xfail_test_cases_file"; break; case model::test_result_failed: test_cases_vector = "failed_test_cases"; test_cases_file_vector = "failed_test_cases_file"; break; case model::test_result_passed: test_cases_vector = "passed_test_cases"; test_cases_file_vector = "passed_test_cases_file"; break; case model::test_result_skipped: test_cases_vector = "skipped_test_cases"; test_cases_file_vector = "skipped_test_cases_file"; break; } INV(!test_cases_vector.empty()); INV(!test_cases_file_vector.empty()); _summary_templates.add_to_vector( test_cases_vector, cli::format_test_case_id(test_program, test_case_name)); _summary_templates.add_to_vector( test_cases_file_vector, test_case_filename(test_program, test_case_name)); } /// Instantiate a template to generate an HTML file in the output directory. /// /// \param templates The templates to use. /// \param template_name The name of the template. This is automatically /// searched for in the installed directory, so do not provide a path. /// \param output_name The name of the output file. This is a basename to /// be created within the output directory. /// /// \throw text::error If there is any problem applying the templates. void generate(const text::templates_def& templates, const std::string& template_name, const std::string& output_name) const { const fs::path miscdir(utils::getenv_with_default( "KYUA_MISCDIR", KYUA_MISCDIR)); const fs::path template_file = miscdir / template_name; const fs::path output_path(_directory / output_name); _ui->out(F("Generating %s") % output_path); text::instantiate(templates, template_file, output_path); } /// Gets the number of tests with a given result type. /// /// \param type The type to be queried. /// /// \return The number of tests of the given type, or 0 if none have yet /// been registered by add_to_summary(). std::size_t get_count(const model::test_result_type type) const { const std::map< model::test_result_type, std::size_t >::const_iterator iter = _types_count.find(type); if (iter == _types_count.end()) return 0; else return (*iter).second; } public: /// Constructor for the hooks. /// /// \param ui_ User interface object where to report progress. /// \param directory_ The directory in which to create the HTML files. /// \param results_filters_ The result types to include in the report. /// Cannot be empty. html_hooks(cmdline::ui* ui_, const fs::path& directory_, const cli::result_types& results_filters_) : _ui(ui_), _directory(directory_), _results_filters(results_filters_), _summary_templates(common_templates()) { PRE(!results_filters_.empty()); // Keep in sync with add_to_summary(). _summary_templates.add_vector("broken_test_cases"); _summary_templates.add_vector("broken_test_cases_file"); _summary_templates.add_vector("xfail_test_cases"); _summary_templates.add_vector("xfail_test_cases_file"); _summary_templates.add_vector("failed_test_cases"); _summary_templates.add_vector("failed_test_cases_file"); _summary_templates.add_vector("passed_test_cases"); _summary_templates.add_vector("passed_test_cases_file"); _summary_templates.add_vector("skipped_test_cases"); _summary_templates.add_vector("skipped_test_cases_file"); } /// Callback executed when the context is loaded. /// /// \param context The context loaded from the database. void got_context(const model::context& context) { text::templates_def templates = common_templates(); templates.add_variable("cwd", context.cwd().str()); add_map(templates, context.env(), "env_var", "env_var_value"); generate(templates, "context.html", "context.html"); } /// Callback executed when a test results is found. /// /// \param iter Container for the test result's data. void got_result(store::results_iterator& iter) { const model::test_program_ptr test_program = iter.test_program(); const std::string& test_case_name = iter.test_case_name(); const model::test_result result = iter.result(); if (std::find(_results_filters.begin(), _results_filters.end(), result.type()) == _results_filters.end()) { add_to_summary(*test_program, test_case_name, result, false); return; } add_to_summary(*test_program, test_case_name, result, true); if (!_start_time || _start_time.get() > iter.start_time()) _start_time = iter.start_time(); if (!_end_time || _end_time.get() < iter.end_time()) _end_time = iter.end_time(); const datetime::delta duration = iter.end_time() - iter.start_time(); _runtime += duration; text::templates_def templates = common_templates(); templates.add_variable("test_case", cli::format_test_case_id(*test_program, test_case_name)); templates.add_variable("test_program", test_program->absolute_path().str()); templates.add_variable("result", cli::format_result(result)); templates.add_variable("start_time", iter.start_time().to_iso8601_in_utc()); templates.add_variable("end_time", iter.end_time().to_iso8601_in_utc()); templates.add_variable("duration", cli::format_delta(duration)); const model::test_case& test_case = test_program->find(test_case_name); add_map(templates, test_case.get_metadata().to_properties(), "metadata_var", "metadata_value"); { const std::string stdout_text = iter.stdout_contents(); if (!stdout_text.empty()) - templates.add_variable("stdout", stdout_text); + templates.add_variable("stdout", text::escape_xml(stdout_text)); } { const std::string stderr_text = iter.stderr_contents(); if (!stderr_text.empty()) - templates.add_variable("stderr", stderr_text); + templates.add_variable("stderr", text::escape_xml(stderr_text)); } generate(templates, "test_result.html", test_case_filename(*test_program, test_case_name)); } /// Writes the index.html file in the output directory. /// /// This should only be called once all the processing has been done; /// i.e. when the scan_results driver returns. void write_summary(void) { const std::size_t n_passed = get_count(model::test_result_passed); const std::size_t n_failed = get_count(model::test_result_failed); const std::size_t n_skipped = get_count(model::test_result_skipped); const std::size_t n_xfail = get_count( model::test_result_expected_failure); const std::size_t n_broken = get_count(model::test_result_broken); const std::size_t n_bad = n_broken + n_failed; if (_start_time) { INV(_end_time); _summary_templates.add_variable( "start_time", _start_time.get().to_iso8601_in_utc()); _summary_templates.add_variable( "end_time", _end_time.get().to_iso8601_in_utc()); } else { _summary_templates.add_variable("start_time", "No tests run"); _summary_templates.add_variable("end_time", "No tests run"); } _summary_templates.add_variable("duration", cli::format_delta(_runtime)); _summary_templates.add_variable("passed_tests_count", F("%s") % n_passed); _summary_templates.add_variable("failed_tests_count", F("%s") % n_failed); _summary_templates.add_variable("skipped_tests_count", F("%s") % n_skipped); _summary_templates.add_variable("xfail_tests_count", F("%s") % n_xfail); _summary_templates.add_variable("broken_tests_count", F("%s") % n_broken); _summary_templates.add_variable("bad_tests_count", F("%s") % n_bad); generate(text::templates_def(), "report.css", "report.css"); generate(_summary_templates, "index.html", "index.html"); } }; } // anonymous namespace /// Default constructor for cmd_report_html. cli::cmd_report_html::cmd_report_html(void) : cli_command( "report-html", "", 0, 0, "Generates an HTML report with the result of a test suite run") { add_option(results_file_open_option); add_option(cmdline::bool_option( "force", "Wipe the output directory before generating the new report; " "use care")); add_option(cmdline::path_option( "output", "The directory in which to store the HTML files", "path", "html")); add_option(cmdline::list_option( "results-filter", "Comma-separated list of result types to include in " "the report", "types", "skipped,xfail,broken,failed")); } /// Entry point for the "report-html" subcommand. /// /// \param ui Object to interact with the I/O of the program. /// \param cmdline Representation of the command line to the subcommand. /// /// \return 0 if everything is OK, 1 if the statement is invalid or if there is /// any other problem. int cli::cmd_report_html::run(cmdline::ui* ui, const cmdline::parsed_cmdline& cmdline, const config::tree& /* user_config */) { const result_types types = get_result_types(cmdline); const fs::path results_file = layout::find_results( results_file_open(cmdline)); const fs::path directory = cmdline.get_option< cmdline::path_option >("output"); create_top_directory(directory, cmdline.has_option("force")); html_hooks hooks(ui, directory, types); drivers::scan_results::drive(results_file, std::set< engine::test_filter >(), hooks); hooks.write_summary(); return EXIT_SUCCESS; } diff --git a/doc/manbuild_test.sh b/doc/manbuild_test.sh old mode 100755 new mode 100644 diff --git a/integration/cmd_about_test.sh b/integration/cmd_about_test.sh old mode 100755 new mode 100644 diff --git a/integration/cmd_config_test.sh b/integration/cmd_config_test.sh old mode 100755 new mode 100644 diff --git a/integration/cmd_db_exec_test.sh b/integration/cmd_db_exec_test.sh old mode 100755 new mode 100644 diff --git a/integration/cmd_db_migrate_test.sh b/integration/cmd_db_migrate_test.sh old mode 100755 new mode 100644 diff --git a/integration/cmd_debug_test.sh b/integration/cmd_debug_test.sh old mode 100755 new mode 100644 diff --git a/integration/cmd_help_test.sh b/integration/cmd_help_test.sh old mode 100755 new mode 100644 diff --git a/integration/cmd_list_test.sh b/integration/cmd_list_test.sh old mode 100755 new mode 100644 diff --git a/integration/cmd_report_html_test.sh b/integration/cmd_report_html_test.sh old mode 100755 new mode 100644 diff --git a/integration/cmd_report_junit_test.sh b/integration/cmd_report_junit_test.sh old mode 100755 new mode 100644 diff --git a/integration/cmd_report_test.sh b/integration/cmd_report_test.sh old mode 100755 new mode 100644 diff --git a/integration/cmd_test_test.sh b/integration/cmd_test_test.sh old mode 100755 new mode 100644 diff --git a/integration/global_test.sh b/integration/global_test.sh old mode 100755 new mode 100644 diff --git a/integration/utils.sh b/integration/utils.sh old mode 100755 new mode 100644 index 99565a1c9857..d1462a5a9b01 --- a/integration/utils.sh +++ b/integration/utils.sh @@ -1,177 +1,177 @@ # Copyright 2011 The Kyua Authors. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of Google Inc. nor the names of its contributors # may be used to endorse or promote products derived from this software # without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # Subcommand to strip out the durations and timestamps in a report. # # This is to make the reports deterministic and thus easily testable. The # time deltas are replaced by the fixed string S.UUU and the timestamps are # replaced by the fixed strings YYYYMMDD.HHMMSS.ssssss and # YYYY-MM-DDTHH:MM:SS.ssssssZ depending on their original format. # # This variable should be used as shown here: # # atf_check ... -x kyua report "| ${utils_strip_times}" # # Use the utils_install_times_wrapper function to create a 'kyua' wrapper # script that automatically does this. # CHECK_STYLE_DISABLE utils_strip_times='sed -E \ - -e "s,( |\[|\")[0-9][0-9]*.[0-9][0-9][0-9](s]|s|\"),\1S.UUU\2,g" \ + -e "s,( |\[|\")[0-9][0-9]*\.[0-9][0-9][0-9](s]|s|\"),\1S.UUU\2,g" \ -e "s,[0-9]{8}-[0-9]{6}-[0-9]{6},YYYYMMDD-HHMMSS-ssssss,g" \ -e "s,[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]{6}Z,YYYY-MM-DDTHH:MM:SS.ssssssZ,g"' # CHECK_STYLE_ENABLE # Same as utils_strip_times but avoids stripping timestamp-based report IDs. # # This is to make the reports deterministic and thus easily testable. The # time deltas are replaced by the fixed string S.UUU and the timestamps are # replaced by the fixed string YYYY-MM-DDTHH:MM:SS.ssssssZ. # CHECK_STYLE_DISABLE utils_strip_times_but_not_ids='sed -E \ - -e "s,( |\[|\")[0-9][0-9]*.[0-9][0-9][0-9](s]|s|\"),\1S.UUU\2,g" \ + -e "s,( |\[|\")[0-9][0-9]*\.[0-9][0-9][0-9](s]|s|\"),\1S.UUU\2,g" \ -e "s,[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]{6}Z,YYYY-MM-DDTHH:MM:SS.ssssssZ,g"' # CHECK_STYLE_ENABLE # Computes the results id for a test suite run. # # The computed path is "generic" in the sense that it does not include a # real timestamp: it only includes a placeholder. This function should be # used along the utils_strip_times function so that the timestamps of # the real results files are stripped out. # # \param path Optional path to use; if not given, use the cwd. utils_results_id() { local test_suite_id="$(utils_test_suite_id "${@}")" echo "${test_suite_id}.YYYYMMDD-HHMMSS-ssssss" } # Computes the results file for a test suite run. # # The computed path is "generic" in the sense that it does not include a # real timestamp: it only includes a placeholder. This function should be # used along the utils_strip_times function so that the timestampts of the # real results files are stripped out. # # \param path Optional path to use; if not given, use the cwd. utils_results_file() { echo "${HOME}/.kyua/store/results.$(utils_results_id "${@}").db" } # Copies a helper binary from the source directory to the work directory. # # \param name The name of the binary to copy. # \param destination The target location for the binary; can be either # a directory name or a file name. utils_cp_helper() { local name="${1}"; shift local destination="${1}"; shift ln -s "$(atf_get_srcdir)"/helpers/"${name}" "${destination}" } # Creates a 'kyua' binary in the path that strips timing data off the output. # # Call this on test cases that wish to replace timing data in the *stdout* of # Kyua with the deterministic strings. This is to be used by tests that # validate the 'test' and 'report' subcommands. utils_install_times_wrapper() { [ ! -x kyua ] || return cat >kyua <kyua.tmpout result=\${?} cat kyua.tmpout | ${utils_strip_times} exit \${result} EOF chmod +x kyua PATH="$(pwd):${PATH}" } # Creates a 'kyua' binary in the path that makes the output of 'test' stable. # # Call this on test cases that wish to replace timing data with deterministic # strings and that need the result lines in the output to be sorted # lexicographically. The latter hides the indeterminism caused by parallel # execution so that the output can be verified. For these reasons, this is to # be used exclusively by tests that validate the 'test' subcommand. utils_install_stable_test_wrapper() { [ ! -x kyua ] || return cat >kyua <kyua.tmpout result=\${?} cat kyua.tmpout | ${utils_strip_times} >kyua.tmpout2 # Sort the test result lines but keep the rest intact. grep '[^ ]*:[^ ]*' kyua.tmpout2 | sort >kyua.tmpout3 grep -v '[^ ]*:[^ ]*' kyua.tmpout2 >kyua.tmpout4 cat kyua.tmpout3 kyua.tmpout4 exit \${result} EOF chmod +x kyua PATH="$(pwd):${PATH}" } # Defines a test case with a default head. utils_test_case() { local name="${1}"; shift atf_test_case "${name}" eval "${name}_head() { atf_set require.progs kyua }" } # Computes the test suite identifier for results files files. # # \param path Optional path to use; if not given, use the cwd. utils_test_suite_id() { local path= if [ ${#} -gt 0 ]; then path="$(cd ${1} && pwd)"; shift else path="$(pwd)" fi echo "${path}" | sed -e 's,^/,,' -e 's,/,_,g' } diff --git a/utils/datetime.cpp b/utils/datetime.cpp index ae3fdb62fe55..174d830031d2 100644 --- a/utils/datetime.cpp +++ b/utils/datetime.cpp @@ -1,613 +1,614 @@ // Copyright 2010 The Kyua Authors. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // * Neither the name of Google Inc. nor the names of its contributors // may be used to endorse or promote products derived from this software // without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "utils/datetime.hpp" extern "C" { #include #include } #include #include "utils/format/macros.hpp" #include "utils/optional.ipp" #include "utils/noncopyable.hpp" #include "utils/sanity.hpp" namespace datetime = utils::datetime; using utils::none; using utils::optional; namespace { /// Fake value for the current time. static optional< datetime::timestamp > mock_now = none; } // anonymous namespace /// Creates a zero time delta. datetime::delta::delta(void) : seconds(0), useconds(0) { } /// Creates a time delta. /// /// \param seconds_ The seconds in the delta. /// \param useconds_ The microseconds in the delta. /// /// \throw std::runtime_error If the input delta is negative. datetime::delta::delta(const int64_t seconds_, const unsigned long useconds_) : seconds(seconds_), useconds(useconds_) { if (seconds_ < 0) { throw std::runtime_error(F("Negative deltas are not supported by the " "datetime::delta class; got: %s") % (*this)); } } /// Converts a time expressed in microseconds to a delta. /// /// \param useconds The amount of microseconds representing the delta. /// /// \return A new delta object. /// /// \throw std::runtime_error If the input delta is negative. datetime::delta datetime::delta::from_microseconds(const int64_t useconds) { if (useconds < 0) { throw std::runtime_error(F("Negative deltas are not supported by the " "datetime::delta class; got: %sus") % useconds); } return delta(useconds / 1000000, useconds % 1000000); } /// Convers the delta to a flat representation expressed in microseconds. /// /// \return The amount of microseconds that corresponds to this delta. int64_t datetime::delta::to_microseconds(void) const { return seconds * 1000000 + useconds; } /// Checks if two time deltas are equal. /// /// \param other The object to compare to. /// /// \return True if the two time deltas are equals; false otherwise. bool datetime::delta::operator==(const datetime::delta& other) const { return seconds == other.seconds && useconds == other.useconds; } /// Checks if two time deltas are different. /// /// \param other The object to compare to. /// /// \return True if the two time deltas are different; false otherwise. bool datetime::delta::operator!=(const datetime::delta& other) const { return !(*this == other); } /// Checks if this time delta is shorter than another one. /// /// \param other The object to compare to. /// /// \return True if this time delta is shorter than other; false otherwise. bool datetime::delta::operator<(const datetime::delta& other) const { return seconds < other.seconds || (seconds == other.seconds && useconds < other.useconds); } /// Checks if this time delta is shorter than or equal to another one. /// /// \param other The object to compare to. /// /// \return True if this time delta is shorter than or equal to; false /// otherwise. bool datetime::delta::operator<=(const datetime::delta& other) const { return (*this) < other || (*this) == other; } /// Checks if this time delta is larger than another one. /// /// \param other The object to compare to. /// /// \return True if this time delta is larger than other; false otherwise. bool datetime::delta::operator>(const datetime::delta& other) const { return seconds > other.seconds || (seconds == other.seconds && useconds > other.useconds); } /// Checks if this time delta is larger than or equal to another one. /// /// \param other The object to compare to. /// /// \return True if this time delta is larger than or equal to; false /// otherwise. bool datetime::delta::operator>=(const datetime::delta& other) const { return (*this) > other || (*this) == other; } /// Adds a time delta to this one. /// /// \param other The time delta to add. /// /// \return The addition of this time delta with the other time delta. datetime::delta datetime::delta::operator+(const datetime::delta& other) const { return delta::from_microseconds(to_microseconds() + other.to_microseconds()); } /// Adds a time delta to this one and updates this with the result. /// /// \param other The time delta to add. /// /// \return The addition of this time delta with the other time delta. datetime::delta& datetime::delta::operator+=(const datetime::delta& other) { *this = *this + other; return *this; } /// Scales this delta by a positive integral factor. /// /// \param factor The scaling factor. /// /// \return The scaled delta. datetime::delta datetime::delta::operator*(const std::size_t factor) const { return delta::from_microseconds(to_microseconds() * factor); } /// Scales this delta by and updates this delta with the result. /// /// \param factor The scaling factor. /// /// \return The scaled delta as a reference to the input object. datetime::delta& datetime::delta::operator*=(const std::size_t factor) { *this = *this * factor; return *this; } /// Injects the object into a stream. /// /// \param output The stream into which to inject the object. /// \param object The object to format. /// /// \return The output stream. std::ostream& datetime::operator<<(std::ostream& output, const delta& object) { return (output << object.to_microseconds() << "us"); } namespace utils { namespace datetime { /// Internal representation for datetime::timestamp. struct timestamp::impl : utils::noncopyable { /// The raw timestamp as provided by libc. ::timeval data; /// Constructs an impl object from initialized data. /// /// \param data_ The raw timestamp to use. impl(const ::timeval& data_) : data(data_) { } }; } // namespace datetime } // namespace utils /// Constructs a new timestamp. /// /// \param pimpl_ An existing impl representation. datetime::timestamp::timestamp(std::shared_ptr< impl > pimpl_) : _pimpl(pimpl_) { } /// Constructs a timestamp from the amount of microseconds since the epoch. /// /// \param value Microseconds since the epoch in UTC. Must be positive. /// /// \return A new timestamp. datetime::timestamp datetime::timestamp::from_microseconds(const int64_t value) { PRE(value >= 0); ::timeval data; data.tv_sec = static_cast< time_t >(value / 1000000); data.tv_usec = static_cast< suseconds_t >(value % 1000000); return timestamp(std::shared_ptr< impl >(new impl(data))); } /// Constructs a timestamp based on user-friendly values. /// /// \param year The year in the [1900,inf) range. /// \param month The month in the [1,12] range. /// \param day The day in the [1,30] range. /// \param hour The hour in the [0,23] range. /// \param minute The minute in the [0,59] range. /// \param second The second in the [0,60] range. Yes, that is 60, which can be /// the case on leap seconds. /// \param microsecond The microsecond in the [0,999999] range. /// /// \return A new timestamp. datetime::timestamp datetime::timestamp::from_values(const int year, const int month, const int day, const int hour, const int minute, const int second, const int microsecond) { PRE(year >= 1900); PRE(month >= 1 && month <= 12); PRE(day >= 1 && day <= 30); PRE(hour >= 0 && hour <= 23); PRE(minute >= 0 && minute <= 59); PRE(second >= 0 && second <= 60); PRE(microsecond >= 0 && microsecond <= 999999); // The code below is quite convoluted. The problem is that we can't assume // that some fields (like tm_zone) of ::tm exist, and thus we can't blindly // set them from the code. Instead of detecting their presence in the // configure script, we just query the current time to initialize such // fields and then we override the ones we are interested in. (There might // be some better way to do this, but I don't know it and the documentation // does not shed much light into how to create your own fake date.) const time_t current_time = ::time(NULL); ::tm timedata; if (::gmtime_r(¤t_time, &timedata) == NULL) UNREACHABLE; timedata.tm_sec = second; timedata.tm_min = minute; timedata.tm_hour = hour; timedata.tm_mday = day; timedata.tm_mon = month - 1; timedata.tm_year = year - 1900; // Ignored: timedata.tm_wday // Ignored: timedata.tm_yday ::timeval data; data.tv_sec = ::mktime(&timedata); data.tv_usec = static_cast< suseconds_t >(microsecond); return timestamp(std::shared_ptr< impl >(new impl(data))); } /// Constructs a new timestamp representing the current time in UTC. /// /// \return A new timestamp. datetime::timestamp datetime::timestamp::now(void) { if (mock_now) return mock_now.get(); ::timeval data; { const int ret = ::gettimeofday(&data, NULL); INV(ret != -1); } return timestamp(std::shared_ptr< impl >(new impl(data))); } /// Formats a timestamp. /// /// \param format The format string to use as consumed by strftime(3). /// /// \return The formatted time. std::string datetime::timestamp::strftime(const std::string& format) const { ::tm timedata; // This conversion to time_t is necessary because tv_sec is not guaranteed // to be a time_t. For example, it isn't in NetBSD 5.x ::time_t epoch_seconds; epoch_seconds = _pimpl->data.tv_sec; if (::gmtime_r(&epoch_seconds, &timedata) == NULL) UNREACHABLE_MSG("gmtime_r(3) did not accept the value returned by " "gettimeofday(2)"); char buf[128]; if (::strftime(buf, sizeof(buf), format.c_str(), &timedata) == 0) UNREACHABLE_MSG("Arbitrary-long format strings are unimplemented"); return buf; } /// Formats a timestamp with the ISO 8601 standard and in UTC. /// /// \return A string with the formatted timestamp. std::string datetime::timestamp::to_iso8601_in_utc(void) const { return F("%s.%06sZ") % strftime("%Y-%m-%dT%H:%M:%S") % _pimpl->data.tv_usec; } /// Returns the number of microseconds since the epoch in UTC. /// /// \return A number of microseconds. int64_t datetime::timestamp::to_microseconds(void) const { return static_cast< int64_t >(_pimpl->data.tv_sec) * 1000000 + _pimpl->data.tv_usec; } /// Returns the number of seconds since the epoch in UTC. /// /// \return A number of seconds. int64_t datetime::timestamp::to_seconds(void) const { return static_cast< int64_t >(_pimpl->data.tv_sec); } /// Sets the current time for testing purposes. void datetime::set_mock_now(const int year, const int month, const int day, const int hour, const int minute, const int second, const int microsecond) { mock_now = timestamp::from_values(year, month, day, hour, minute, second, microsecond); } /// Sets the current time for testing purposes. /// /// \param mock_now_ The mock timestamp to set the time to. void datetime::set_mock_now(const timestamp& mock_now_) { mock_now = mock_now_; } /// Checks if two timestamps are equal. /// /// \param other The object to compare to. /// /// \return True if the two timestamps are equals; false otherwise. bool datetime::timestamp::operator==(const datetime::timestamp& other) const { return _pimpl->data.tv_sec == other._pimpl->data.tv_sec && _pimpl->data.tv_usec == other._pimpl->data.tv_usec; } /// Checks if two timestamps are different. /// /// \param other The object to compare to. /// /// \return True if the two timestamps are different; false otherwise. bool datetime::timestamp::operator!=(const datetime::timestamp& other) const { return !(*this == other); } /// Checks if a timestamp is before another. /// /// \param other The object to compare to. /// /// \return True if this timestamp comes before other; false otherwise. bool datetime::timestamp::operator<(const datetime::timestamp& other) const { return to_microseconds() < other.to_microseconds(); } /// Checks if a timestamp is before or equal to another. /// /// \param other The object to compare to. /// /// \return True if this timestamp comes before other or is equal to it; /// false otherwise. bool datetime::timestamp::operator<=(const datetime::timestamp& other) const { return to_microseconds() <= other.to_microseconds(); } /// Checks if a timestamp is after another. /// /// \param other The object to compare to. /// /// \return True if this timestamp comes after other; false otherwise; bool datetime::timestamp::operator>(const datetime::timestamp& other) const { return to_microseconds() > other.to_microseconds(); } /// Checks if a timestamp is after or equal to another. /// /// \param other The object to compare to. /// /// \return True if this timestamp comes after other or is equal to it; /// false otherwise. bool datetime::timestamp::operator>=(const datetime::timestamp& other) const { return to_microseconds() >= other.to_microseconds(); } /// Calculates the addition of a delta to a timestamp. /// /// \param other The delta to add. /// /// \return A new timestamp in the future. datetime::timestamp datetime::timestamp::operator+(const datetime::delta& other) const { return datetime::timestamp::from_microseconds(to_microseconds() + other.to_microseconds()); } /// Calculates the addition of a delta to this timestamp. /// /// \param other The delta to add. /// /// \return A reference to the modified timestamp. datetime::timestamp& datetime::timestamp::operator+=(const datetime::delta& other) { *this = *this + other; return *this; } /// Calculates the subtraction of a delta from a timestamp. /// /// \param other The delta to subtract. /// /// \return A new timestamp in the past. datetime::timestamp datetime::timestamp::operator-(const datetime::delta& other) const { return datetime::timestamp::from_microseconds(to_microseconds() - other.to_microseconds()); } /// Calculates the subtraction of a delta from this timestamp. /// /// \param other The delta to subtract. /// /// \return A reference to the modified timestamp. datetime::timestamp& datetime::timestamp::operator-=(const datetime::delta& other) { *this = *this - other; return *this; } /// Calculates the delta between two timestamps. /// /// \param other The subtrahend. /// /// \return The difference between this object and the other object. /// /// \throw std::runtime_error If the subtraction would result in a negative time /// delta, which are currently not supported. datetime::delta datetime::timestamp::operator-(const datetime::timestamp& other) const { - if ((*this) < other) { - throw std::runtime_error( - F("Cannot subtract %s from %s as it would result in a negative " - "datetime::delta, which are not supported") % other % (*this)); - } + /* + * XXX-BD: gettimeofday isn't necessarily monotonic so return the + * smallest non-zero delta if time went backwards. + */ + if ((*this) < other) + return datetime::delta::from_microseconds(1); return datetime::delta::from_microseconds(to_microseconds() - other.to_microseconds()); } /// Injects the object into a stream. /// /// \param output The stream into which to inject the object. /// \param object The object to format. /// /// \return The output stream. std::ostream& datetime::operator<<(std::ostream& output, const timestamp& object) { return (output << object.to_microseconds() << "us"); } diff --git a/utils/datetime_test.cpp b/utils/datetime_test.cpp index 9f8ff50cd0f8..a2d3a3c0cdad 100644 --- a/utils/datetime_test.cpp +++ b/utils/datetime_test.cpp @@ -1,593 +1,593 @@ // Copyright 2010 The Kyua Authors. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // * Neither the name of Google Inc. nor the names of its contributors // may be used to endorse or promote products derived from this software // without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "utils/datetime.hpp" extern "C" { #include #include } #include #include #include namespace datetime = utils::datetime; ATF_TEST_CASE_WITHOUT_HEAD(delta__defaults); ATF_TEST_CASE_BODY(delta__defaults) { const datetime::delta delta; ATF_REQUIRE_EQ(0, delta.seconds); ATF_REQUIRE_EQ(0, delta.useconds); } ATF_TEST_CASE_WITHOUT_HEAD(delta__overrides); ATF_TEST_CASE_BODY(delta__overrides) { const datetime::delta delta(1, 2); ATF_REQUIRE_EQ(1, delta.seconds); ATF_REQUIRE_EQ(2, delta.useconds); ATF_REQUIRE_THROW_RE( std::runtime_error, "Negative.*not supported.*-4999997us", datetime::delta(-5, 3)); } ATF_TEST_CASE_WITHOUT_HEAD(delta__from_microseconds); ATF_TEST_CASE_BODY(delta__from_microseconds) { { const datetime::delta delta = datetime::delta::from_microseconds(0); ATF_REQUIRE_EQ(0, delta.seconds); ATF_REQUIRE_EQ(0, delta.useconds); } { const datetime::delta delta = datetime::delta::from_microseconds( 999999); ATF_REQUIRE_EQ(0, delta.seconds); ATF_REQUIRE_EQ(999999, delta.useconds); } { const datetime::delta delta = datetime::delta::from_microseconds( 1000000); ATF_REQUIRE_EQ(1, delta.seconds); ATF_REQUIRE_EQ(0, delta.useconds); } { const datetime::delta delta = datetime::delta::from_microseconds( 10576293); ATF_REQUIRE_EQ(10, delta.seconds); ATF_REQUIRE_EQ(576293, delta.useconds); } { const datetime::delta delta = datetime::delta::from_microseconds( 123456789123456LL); ATF_REQUIRE_EQ(123456789, delta.seconds); ATF_REQUIRE_EQ(123456, delta.useconds); } ATF_REQUIRE_THROW_RE( std::runtime_error, "Negative.*not supported.*-12345us", datetime::delta::from_microseconds(-12345)); } ATF_TEST_CASE_WITHOUT_HEAD(delta__to_microseconds); ATF_TEST_CASE_BODY(delta__to_microseconds) { ATF_REQUIRE_EQ(0, datetime::delta(0, 0).to_microseconds()); ATF_REQUIRE_EQ(999999, datetime::delta(0, 999999).to_microseconds()); ATF_REQUIRE_EQ(1000000, datetime::delta(1, 0).to_microseconds()); ATF_REQUIRE_EQ(10576293, datetime::delta(10, 576293).to_microseconds()); ATF_REQUIRE_EQ(11576293, datetime::delta(10, 1576293).to_microseconds()); } ATF_TEST_CASE_WITHOUT_HEAD(delta__equals); ATF_TEST_CASE_BODY(delta__equals) { ATF_REQUIRE(datetime::delta() == datetime::delta()); ATF_REQUIRE(datetime::delta() == datetime::delta(0, 0)); ATF_REQUIRE(datetime::delta(1, 2) == datetime::delta(1, 2)); ATF_REQUIRE(!(datetime::delta() == datetime::delta(0, 1))); ATF_REQUIRE(!(datetime::delta() == datetime::delta(1, 0))); ATF_REQUIRE(!(datetime::delta(1, 2) == datetime::delta(2, 1))); } ATF_TEST_CASE_WITHOUT_HEAD(delta__differs); ATF_TEST_CASE_BODY(delta__differs) { ATF_REQUIRE(!(datetime::delta() != datetime::delta())); ATF_REQUIRE(!(datetime::delta() != datetime::delta(0, 0))); ATF_REQUIRE(!(datetime::delta(1, 2) != datetime::delta(1, 2))); ATF_REQUIRE(datetime::delta() != datetime::delta(0, 1)); ATF_REQUIRE(datetime::delta() != datetime::delta(1, 0)); ATF_REQUIRE(datetime::delta(1, 2) != datetime::delta(2, 1)); } ATF_TEST_CASE_WITHOUT_HEAD(delta__sorting); ATF_TEST_CASE_BODY(delta__sorting) { ATF_REQUIRE(!(datetime::delta() < datetime::delta())); ATF_REQUIRE( datetime::delta() <= datetime::delta()); ATF_REQUIRE(!(datetime::delta() > datetime::delta())); ATF_REQUIRE( datetime::delta() >= datetime::delta()); ATF_REQUIRE(!(datetime::delta(9, 8) < datetime::delta(9, 8))); ATF_REQUIRE( datetime::delta(9, 8) <= datetime::delta(9, 8)); ATF_REQUIRE(!(datetime::delta(9, 8) > datetime::delta(9, 8))); ATF_REQUIRE( datetime::delta(9, 8) >= datetime::delta(9, 8)); ATF_REQUIRE( datetime::delta(2, 5) < datetime::delta(4, 8)); ATF_REQUIRE( datetime::delta(2, 5) <= datetime::delta(4, 8)); ATF_REQUIRE(!(datetime::delta(2, 5) > datetime::delta(4, 8))); ATF_REQUIRE(!(datetime::delta(2, 5) >= datetime::delta(4, 8))); ATF_REQUIRE( datetime::delta(2, 5) < datetime::delta(2, 8)); ATF_REQUIRE( datetime::delta(2, 5) <= datetime::delta(2, 8)); ATF_REQUIRE(!(datetime::delta(2, 5) > datetime::delta(2, 8))); ATF_REQUIRE(!(datetime::delta(2, 5) >= datetime::delta(2, 8))); ATF_REQUIRE(!(datetime::delta(4, 8) < datetime::delta(2, 5))); ATF_REQUIRE(!(datetime::delta(4, 8) <= datetime::delta(2, 5))); ATF_REQUIRE( datetime::delta(4, 8) > datetime::delta(2, 5)); ATF_REQUIRE( datetime::delta(4, 8) >= datetime::delta(2, 5)); ATF_REQUIRE(!(datetime::delta(2, 8) < datetime::delta(2, 5))); ATF_REQUIRE(!(datetime::delta(2, 8) <= datetime::delta(2, 5))); ATF_REQUIRE( datetime::delta(2, 8) > datetime::delta(2, 5)); ATF_REQUIRE( datetime::delta(2, 8) >= datetime::delta(2, 5)); } ATF_TEST_CASE_WITHOUT_HEAD(delta__addition); ATF_TEST_CASE_BODY(delta__addition) { using datetime::delta; ATF_REQUIRE_EQ(delta(), delta() + delta()); ATF_REQUIRE_EQ(delta(0, 10), delta() + delta(0, 10)); ATF_REQUIRE_EQ(delta(10, 0), delta(10, 0) + delta()); ATF_REQUIRE_EQ(delta(1, 234567), delta(0, 1234567) + delta()); ATF_REQUIRE_EQ(delta(12, 34), delta(10, 20) + delta(2, 14)); } ATF_TEST_CASE_WITHOUT_HEAD(delta__addition_and_set); ATF_TEST_CASE_BODY(delta__addition_and_set) { using datetime::delta; { delta d; d += delta(3, 5); ATF_REQUIRE_EQ(delta(3, 5), d); } { delta d(1, 2); d += delta(3, 5); ATF_REQUIRE_EQ(delta(4, 7), d); } { delta d(1, 2); ATF_REQUIRE_EQ(delta(4, 7), (d += delta(3, 5))); } } ATF_TEST_CASE_WITHOUT_HEAD(delta__scale); ATF_TEST_CASE_BODY(delta__scale) { using datetime::delta; ATF_REQUIRE_EQ(delta(), delta() * 0); ATF_REQUIRE_EQ(delta(), delta() * 5); ATF_REQUIRE_EQ(delta(0, 30), delta(0, 10) * 3); ATF_REQUIRE_EQ(delta(17, 500000), delta(3, 500000) * 5); } ATF_TEST_CASE_WITHOUT_HEAD(delta__scale_and_set); ATF_TEST_CASE_BODY(delta__scale_and_set) { using datetime::delta; { delta d(3, 5); d *= 2; ATF_REQUIRE_EQ(delta(6, 10), d); } { delta d(8, 0); d *= 8; ATF_REQUIRE_EQ(delta(64, 0), d); } { delta d(3, 5); ATF_REQUIRE_EQ(delta(9, 15), (d *= 3)); } } ATF_TEST_CASE_WITHOUT_HEAD(delta__output); ATF_TEST_CASE_BODY(delta__output) { { std::ostringstream str; str << datetime::delta(15, 8791); ATF_REQUIRE_EQ("15008791us", str.str()); } { std::ostringstream str; str << datetime::delta(12345678, 0); ATF_REQUIRE_EQ("12345678000000us", str.str()); } } ATF_TEST_CASE_WITHOUT_HEAD(timestamp__copy); ATF_TEST_CASE_BODY(timestamp__copy) { const datetime::timestamp ts1 = datetime::timestamp::from_values( 2011, 2, 16, 19, 15, 30, 0); { const datetime::timestamp ts2 = ts1; const datetime::timestamp ts3 = datetime::timestamp::from_values( 2012, 2, 16, 19, 15, 30, 0); ATF_REQUIRE_EQ("2011", ts1.strftime("%Y")); ATF_REQUIRE_EQ("2011", ts2.strftime("%Y")); ATF_REQUIRE_EQ("2012", ts3.strftime("%Y")); } ATF_REQUIRE_EQ("2011", ts1.strftime("%Y")); } ATF_TEST_CASE_WITHOUT_HEAD(timestamp__from_microseconds); ATF_TEST_CASE_BODY(timestamp__from_microseconds) { const datetime::timestamp ts = datetime::timestamp::from_microseconds( 1328829351987654LL); ATF_REQUIRE_EQ("2012-02-09 23:15:51", ts.strftime("%Y-%m-%d %H:%M:%S")); ATF_REQUIRE_EQ(1328829351987654LL, ts.to_microseconds()); ATF_REQUIRE_EQ(1328829351, ts.to_seconds()); } ATF_TEST_CASE_WITHOUT_HEAD(timestamp__now__mock); ATF_TEST_CASE_BODY(timestamp__now__mock) { datetime::set_mock_now(2011, 2, 21, 18, 5, 10, 0); ATF_REQUIRE_EQ("2011-02-21 18:05:10", datetime::timestamp::now().strftime("%Y-%m-%d %H:%M:%S")); datetime::set_mock_now(datetime::timestamp::from_values( 2012, 3, 22, 19, 6, 11, 54321)); ATF_REQUIRE_EQ("2012-03-22 19:06:11", datetime::timestamp::now().strftime("%Y-%m-%d %H:%M:%S")); ATF_REQUIRE_EQ("2012-03-22 19:06:11", datetime::timestamp::now().strftime("%Y-%m-%d %H:%M:%S")); } ATF_TEST_CASE_WITHOUT_HEAD(timestamp__now__real); ATF_TEST_CASE_BODY(timestamp__now__real) { // This test is might fail if we happen to run at the crossing of one // day to the other and the two measures we pick of the current time // differ. This is so unlikely that I haven't bothered to do this in any // other way. const time_t just_before = ::time(NULL); const datetime::timestamp now = datetime::timestamp::now(); ::tm data; char buf[1024]; ATF_REQUIRE(::gmtime_r(&just_before, &data) != 0); ATF_REQUIRE(::strftime(buf, sizeof(buf), "%Y-%m-%d", &data) != 0); ATF_REQUIRE_EQ(buf, now.strftime("%Y-%m-%d")); ATF_REQUIRE(now.strftime("%Z") == "GMT" || now.strftime("%Z") == "UTC"); } ATF_TEST_CASE_WITHOUT_HEAD(timestamp__now__granularity); ATF_TEST_CASE_BODY(timestamp__now__granularity) { const datetime::timestamp first = datetime::timestamp::now(); ::usleep(1); const datetime::timestamp second = datetime::timestamp::now(); ATF_REQUIRE(first.to_microseconds() != second.to_microseconds()); } ATF_TEST_CASE_WITHOUT_HEAD(timestamp__strftime); ATF_TEST_CASE_BODY(timestamp__strftime) { const datetime::timestamp ts1 = datetime::timestamp::from_values( 2010, 12, 10, 8, 45, 50, 0); ATF_REQUIRE_EQ("2010-12-10", ts1.strftime("%Y-%m-%d")); ATF_REQUIRE_EQ("08:45:50", ts1.strftime("%H:%M:%S")); const datetime::timestamp ts2 = datetime::timestamp::from_values( 2011, 2, 16, 19, 15, 30, 0); ATF_REQUIRE_EQ("2011-02-16T19:15:30", ts2.strftime("%Y-%m-%dT%H:%M:%S")); } ATF_TEST_CASE_WITHOUT_HEAD(timestamp__to_iso8601_in_utc); ATF_TEST_CASE_BODY(timestamp__to_iso8601_in_utc) { const datetime::timestamp ts1 = datetime::timestamp::from_values( 2010, 12, 10, 8, 45, 50, 0); ATF_REQUIRE_EQ("2010-12-10T08:45:50.000000Z", ts1.to_iso8601_in_utc()); const datetime::timestamp ts2= datetime::timestamp::from_values( 2016, 7, 11, 17, 51, 28, 123456); ATF_REQUIRE_EQ("2016-07-11T17:51:28.123456Z", ts2.to_iso8601_in_utc()); } ATF_TEST_CASE_WITHOUT_HEAD(timestamp__to_microseconds); ATF_TEST_CASE_BODY(timestamp__to_microseconds) { const datetime::timestamp ts1 = datetime::timestamp::from_values( 2010, 12, 10, 8, 45, 50, 123456); ATF_REQUIRE_EQ(1291970750123456LL, ts1.to_microseconds()); } ATF_TEST_CASE_WITHOUT_HEAD(timestamp__to_seconds); ATF_TEST_CASE_BODY(timestamp__to_seconds) { const datetime::timestamp ts1 = datetime::timestamp::from_values( 2010, 12, 10, 8, 45, 50, 123456); ATF_REQUIRE_EQ(1291970750, ts1.to_seconds()); } ATF_TEST_CASE_WITHOUT_HEAD(timestamp__leap_second); ATF_TEST_CASE_BODY(timestamp__leap_second) { // This is actually a test for from_values(), which is the function that // includes assertions to validate the input parameters. const datetime::timestamp ts1 = datetime::timestamp::from_values( 2012, 6, 30, 23, 59, 60, 543); ATF_REQUIRE_EQ(1341100800, ts1.to_seconds()); } ATF_TEST_CASE_WITHOUT_HEAD(timestamp__equals); ATF_TEST_CASE_BODY(timestamp__equals) { ATF_REQUIRE(datetime::timestamp::from_microseconds(1291970750123456LL) == datetime::timestamp::from_microseconds(1291970750123456LL)); } ATF_TEST_CASE_WITHOUT_HEAD(timestamp__differs); ATF_TEST_CASE_BODY(timestamp__differs) { ATF_REQUIRE(datetime::timestamp::from_microseconds(1291970750123456LL) != datetime::timestamp::from_microseconds(1291970750123455LL)); } ATF_TEST_CASE_WITHOUT_HEAD(timestamp__sorting); ATF_TEST_CASE_BODY(timestamp__sorting) { { const datetime::timestamp ts1 = datetime::timestamp::from_microseconds( 1291970750123455LL); const datetime::timestamp ts2 = datetime::timestamp::from_microseconds( 1291970750123455LL); ATF_REQUIRE(!(ts1 < ts2)); ATF_REQUIRE( ts1 <= ts2); ATF_REQUIRE(!(ts1 > ts2)); ATF_REQUIRE( ts1 >= ts2); } { const datetime::timestamp ts1 = datetime::timestamp::from_microseconds( 1291970750123455LL); const datetime::timestamp ts2 = datetime::timestamp::from_microseconds( 1291970759123455LL); ATF_REQUIRE( ts1 < ts2); ATF_REQUIRE( ts1 <= ts2); ATF_REQUIRE(!(ts1 > ts2)); ATF_REQUIRE(!(ts1 >= ts2)); } { const datetime::timestamp ts1 = datetime::timestamp::from_microseconds( 1291970759123455LL); const datetime::timestamp ts2 = datetime::timestamp::from_microseconds( 1291970750123455LL); ATF_REQUIRE(!(ts1 < ts2)); ATF_REQUIRE(!(ts1 <= ts2)); ATF_REQUIRE( ts1 > ts2); ATF_REQUIRE( ts1 >= ts2); } } ATF_TEST_CASE_WITHOUT_HEAD(timestamp__add_delta); ATF_TEST_CASE_BODY(timestamp__add_delta) { using datetime::delta; using datetime::timestamp; ATF_REQUIRE_EQ(timestamp::from_values(2014, 12, 11, 21, 43, 30, 1234), timestamp::from_values(2014, 12, 11, 21, 43, 0, 0) + delta(30, 1234)); ATF_REQUIRE_EQ(timestamp::from_values(2014, 12, 11, 22, 43, 7, 100), timestamp::from_values(2014, 12, 11, 21, 43, 0, 0) + delta(3602, 5000100)); } ATF_TEST_CASE_WITHOUT_HEAD(timestamp__add_delta_and_set); ATF_TEST_CASE_BODY(timestamp__add_delta_and_set) { using datetime::delta; using datetime::timestamp; { timestamp ts = timestamp::from_values(2014, 12, 11, 21, 43, 0, 0); ts += delta(30, 1234); ATF_REQUIRE_EQ(timestamp::from_values(2014, 12, 11, 21, 43, 30, 1234), ts); } { timestamp ts = timestamp::from_values(2014, 12, 11, 21, 43, 0, 0); ATF_REQUIRE_EQ(timestamp::from_values(2014, 12, 11, 22, 43, 7, 100), ts += delta(3602, 5000100)); } } ATF_TEST_CASE_WITHOUT_HEAD(timestamp__subtract_delta); ATF_TEST_CASE_BODY(timestamp__subtract_delta) { using datetime::delta; using datetime::timestamp; ATF_REQUIRE_EQ(timestamp::from_values(2014, 12, 11, 21, 43, 10, 4321), timestamp::from_values(2014, 12, 11, 21, 43, 40, 5555) - delta(30, 1234)); ATF_REQUIRE_EQ(timestamp::from_values(2014, 12, 11, 20, 43, 1, 300), timestamp::from_values(2014, 12, 11, 21, 43, 8, 400) - delta(3602, 5000100)); } ATF_TEST_CASE_WITHOUT_HEAD(timestamp__subtract_delta_and_set); ATF_TEST_CASE_BODY(timestamp__subtract_delta_and_set) { using datetime::delta; using datetime::timestamp; { timestamp ts = timestamp::from_values(2014, 12, 11, 21, 43, 40, 5555); ts -= delta(30, 1234); ATF_REQUIRE_EQ(timestamp::from_values(2014, 12, 11, 21, 43, 10, 4321), ts); } { timestamp ts = timestamp::from_values(2014, 12, 11, 21, 43, 8, 400); ATF_REQUIRE_EQ(timestamp::from_values(2014, 12, 11, 20, 43, 1, 300), ts -= delta(3602, 5000100)); } } ATF_TEST_CASE_WITHOUT_HEAD(timestamp__subtraction); ATF_TEST_CASE_BODY(timestamp__subtraction) { const datetime::timestamp ts1 = datetime::timestamp::from_microseconds( 1291970750123456LL); const datetime::timestamp ts2 = datetime::timestamp::from_microseconds( 1291970750123468LL); const datetime::timestamp ts3 = datetime::timestamp::from_microseconds( 1291970850123456LL); ATF_REQUIRE_EQ(datetime::delta(0, 0), ts1 - ts1); ATF_REQUIRE_EQ(datetime::delta(0, 12), ts2 - ts1); ATF_REQUIRE_EQ(datetime::delta(100, 0), ts3 - ts1); ATF_REQUIRE_EQ(datetime::delta(99, 999988), ts3 - ts2); - ATF_REQUIRE_THROW_RE( - std::runtime_error, - "Cannot subtract 1291970850123456us from 1291970750123468us " - ".*negative datetime::delta.*not supported", - ts2 - ts3); + /* + * NOTE (ngie): behavior change for + * https://github.com/jmmv/kyua/issues/155 . + */ + ATF_REQUIRE_EQ(datetime::delta::from_microseconds(1), ts2 - ts3); } ATF_TEST_CASE_WITHOUT_HEAD(timestamp__output); ATF_TEST_CASE_BODY(timestamp__output) { { std::ostringstream str; str << datetime::timestamp::from_microseconds(1291970750123456LL); ATF_REQUIRE_EQ("1291970750123456us", str.str()); } { std::ostringstream str; str << datetime::timestamp::from_microseconds(1028309798759812LL); ATF_REQUIRE_EQ("1028309798759812us", str.str()); } } ATF_INIT_TEST_CASES(tcs) { ATF_ADD_TEST_CASE(tcs, delta__defaults); ATF_ADD_TEST_CASE(tcs, delta__overrides); ATF_ADD_TEST_CASE(tcs, delta__from_microseconds); ATF_ADD_TEST_CASE(tcs, delta__to_microseconds); ATF_ADD_TEST_CASE(tcs, delta__equals); ATF_ADD_TEST_CASE(tcs, delta__differs); ATF_ADD_TEST_CASE(tcs, delta__sorting); ATF_ADD_TEST_CASE(tcs, delta__addition); ATF_ADD_TEST_CASE(tcs, delta__addition_and_set); ATF_ADD_TEST_CASE(tcs, delta__scale); ATF_ADD_TEST_CASE(tcs, delta__scale_and_set); ATF_ADD_TEST_CASE(tcs, delta__output); ATF_ADD_TEST_CASE(tcs, timestamp__copy); ATF_ADD_TEST_CASE(tcs, timestamp__from_microseconds); ATF_ADD_TEST_CASE(tcs, timestamp__now__mock); ATF_ADD_TEST_CASE(tcs, timestamp__now__real); ATF_ADD_TEST_CASE(tcs, timestamp__now__granularity); ATF_ADD_TEST_CASE(tcs, timestamp__strftime); ATF_ADD_TEST_CASE(tcs, timestamp__to_iso8601_in_utc); ATF_ADD_TEST_CASE(tcs, timestamp__to_microseconds); ATF_ADD_TEST_CASE(tcs, timestamp__to_seconds); ATF_ADD_TEST_CASE(tcs, timestamp__leap_second); ATF_ADD_TEST_CASE(tcs, timestamp__equals); ATF_ADD_TEST_CASE(tcs, timestamp__differs); ATF_ADD_TEST_CASE(tcs, timestamp__sorting); ATF_ADD_TEST_CASE(tcs, timestamp__add_delta); ATF_ADD_TEST_CASE(tcs, timestamp__add_delta_and_set); ATF_ADD_TEST_CASE(tcs, timestamp__subtract_delta); ATF_ADD_TEST_CASE(tcs, timestamp__subtract_delta_and_set); ATF_ADD_TEST_CASE(tcs, timestamp__subtraction); ATF_ADD_TEST_CASE(tcs, timestamp__output); }