From d183247a311594eb312338e529e4c2594cca7c12 Mon Sep 17 00:00:00 2001 From: Timothy Stack Date: Thu, 1 Apr 2021 16:22:04 -0700 Subject: [PATCH] [tests] add some tests for time-ago stuff --- src/CMakeLists.txt | 64 +++++++------------- src/Makefile.am | 2 +- src/base/CMakeLists.txt | 48 +++++++++++++++ src/base/Makefile.am | 14 +++++ src/base/humanize.time.cc | 84 +++++++++++++------------- src/base/humanize.time.hh | 32 +++++++++- src/base/humanize.time.tests.cc | 102 ++++++++++++++++++++++++++++++++ src/base/test_base.cc | 33 +++++++++++ src/base/time_util.hh | 8 +++ src/field_overlay_source.cc | 9 ++- src/lnav.cc | 4 +- 11 files changed, 311 insertions(+), 89 deletions(-) create mode 100644 src/base/CMakeLists.txt create mode 100644 src/base/humanize.time.tests.cc create mode 100644 src/base/test_base.cc diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a09036a4..4995dcc0 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -13,6 +13,8 @@ set(VCS_PACKAGE_STRING "test") configure_file(config.cmake.h.in config.h) +add_subdirectory(base) + add_executable(bin2c bin2c.hh bin2c.c) target_link_libraries(bin2c ZLIB::zlib) @@ -226,6 +228,25 @@ add_custom_command( ) list(APPEND GEN_SRCS builtin-sh-scripts.h builtin-sh-scripts.cc) +add_library(cppfmt STATIC + + fmtlib/format.cc + fmtlib/os.cc + fmtlib/fmt/chrono.h + fmtlib/fmt/color.h + fmtlib/fmt/compile.h + fmtlib/fmt/core.h + fmtlib/fmt/format-inl.h + fmtlib/fmt/format.h + fmtlib/fmt/locale.h + fmtlib/fmt/os.h + fmtlib/fmt/ostream.h + fmtlib/fmt/posix.h + fmtlib/fmt/printf.h + fmtlib/fmt/ranges.h + fmtlib/fmt/time.h + ) + add_library(diag STATIC ${GEN_SRCS} @@ -235,7 +256,6 @@ add_library(diag STATIC ansi_scrubber.cc archive_manager.cc attr_line.cc - base/isc.cc bin2c.hh bookmarks.cc bottom_status_source.cc @@ -243,7 +263,6 @@ add_library(diag STATIC column_namer.cc command_executor.cc curl_looper.cc - base/date_time_scanner.cc db_sub_source.cc elem_to_json.cc environ_vtab.cc @@ -265,11 +284,7 @@ add_library(diag STATIC highlighter.cc hist_source.cc hotkeys.cc - base/humanize.cc - base/humanize.time.cc input_dispatcher.cc - base/intern_string.cc - base/is_utf8.cc json-extension-functions.cc yajlpp/json_op.cc yajlpp/json_ptr.cc @@ -277,8 +292,6 @@ add_library(diag STATIC listview_curses.cc lnav_commands.cc lnav_config.cc - base/lnav.gzip.cc - base/lnav_log.cc lnav_util.cc log_accel.cc log_actions.cc @@ -319,7 +332,6 @@ add_library(diag STATIC sql_util.cc state-extension-functions.cc styling.cc - base/string_util.cc string_attr_type.cc strnatcmp.c text_format.cc @@ -327,7 +339,6 @@ add_library(diag STATIC textfile_sub_source.cc textview_curses.cc top_status_source.cc - base/time_util.cc time-extension-functions.cc timer.cc unique_path.cc @@ -376,12 +387,8 @@ add_library(diag STATIC command_executor.hh column_namer.hh curl_looper.hh - base/date_time_scanner.hh doc_status_source.hh elem_to_json.hh - base/enum_util.hh - base/func_util.hh - base/future_util.hh field_overlay_source.hh file_collection.hh file_format.hh @@ -396,14 +403,7 @@ add_library(diag STATIC help_text_formatter.hh highlighter.hh hotkeys.hh - base/humanize.hh - base/humanize.time.hh input_dispatcher.hh - base/injector.hh - base/injector.bind.hh - base/intern_string.hh - base/is_utf8.hh - base/isc.hh k_merge_tree.h log_actions.hh log_data_helper.hh @@ -418,8 +418,6 @@ add_library(diag STATIC logfile.hh logfile_fwd.hh logfile_stats.hh - base/lrucache.hpp - base/math_util.hh optional.hpp papertrail_proc.hh plain_text_source.hh @@ -432,8 +430,6 @@ add_library(diag STATIC readline_possibilities.hh regexp_vtab.hh relative_time.hh - base/result.h - base/time_util.hh styling.hh ring_span.hh safe/accessmode.h @@ -486,22 +482,6 @@ add_library(diag STATIC ghc/fs_std_fwd.hpp ghc/fs_std_impl.hpp - fmtlib/format.cc - fmtlib/os.cc - fmtlib/fmt/chrono.h - fmtlib/fmt/color.h - fmtlib/fmt/compile.h - fmtlib/fmt/core.h - fmtlib/fmt/format-inl.h - fmtlib/fmt/format.h - fmtlib/fmt/locale.h - fmtlib/fmt/os.h - fmtlib/fmt/ostream.h - fmtlib/fmt/posix.h - fmtlib/fmt/printf.h - fmtlib/fmt/ranges.h - fmtlib/fmt/time.h - ww898/cp_utf8.hpp log_level_re.cc @@ -517,7 +497,7 @@ target_include_directories( ${CMAKE_CURRENT_BINARY_DIR} ) -target_link_libraries(diag ${lnav_LIBS}) +target_link_libraries(diag base ${lnav_LIBS}) target_compile_definitions(diag PRIVATE SQLITE_OMIT_LOAD_EXTENSION) check_library_exists(util openpty "" HAVE_LIBUTIL) diff --git a/src/Makefile.am b/src/Makefile.am index 6cfcc5d3..61ebb0f1 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,7 +1,7 @@ include $(top_srcdir)/aminclude_static.am -SUBDIRS = base fmtlib pcrepp pugixml yajl yajlpp . +SUBDIRS = fmtlib base pcrepp pugixml yajl yajlpp . bin_PROGRAMS = lnav diff --git a/src/base/CMakeLists.txt b/src/base/CMakeLists.txt new file mode 100644 index 00000000..3a492904 --- /dev/null +++ b/src/base/CMakeLists.txt @@ -0,0 +1,48 @@ + +add_library(base STATIC + ../config.h + + date_time_scanner.cc + humanize.cc + humanize.time.cc + intern_string.cc + is_utf8.cc + isc.cc + lnav.gzip.cc + lnav_log.cc + string_util.cc + time_util.cc + + + date_time_scanner.hh + enum_util.hh + func_util.hh + future_util.hh + humanize.hh + humanize.time.hh + injector.hh + injector.bind.hh + intern_string.hh + is_utf8.hh + isc.hh + lrucache.hpp + math_util.hh + result.h + time_util.hh + ) + +target_include_directories( + base + PUBLIC + . + .. + ../fmtlib + ${CMAKE_CURRENT_BINARY_DIR}/.. +) +target_link_libraries(base cppfmt PkgConfig::libpcre) + +add_executable(test_base + humanize.time.tests.cc + test_base.cc) +target_link_libraries(test_base base) +add_test(NAME test_base COMMAND test_base) diff --git a/src/base/Makefile.am b/src/base/Makefile.am index eeb8b33f..6d055431 100644 --- a/src/base/Makefile.am +++ b/src/base/Makefile.am @@ -46,3 +46,17 @@ libbase_a_SOURCES = \ lnav_log.cc \ string_util.cc \ time_util.cc + +check_PROGRAMS = \ + test_base + +test_base_SOURCES = \ + humanize.time.tests.cc \ + test_base.cc + +test_base_LDADD = \ + libbase.a \ + ../fmtlib/libcppfmt.a + +TESTS = \ + test_base diff --git a/src/base/humanize.time.cc b/src/base/humanize.time.cc index d4f23cdf..efde1226 100644 --- a/src/base/humanize.time.cc +++ b/src/base/humanize.time.cc @@ -29,48 +29,59 @@ #include "config.h" +#include + +#include "fmt/format.h" #include "time_util.hh" #include "humanize.time.hh" namespace humanize { namespace time { -std::string time_ago(time_t last_time, bool convert_local) +using namespace std::chrono_literals; + +point point::from_tv(const timeval &tv) { - time_t delta, current_time = ::time(nullptr); + return point(tv); +} + +std::string point::as_time_ago() +{ + struct timeval current_time = this->p_recent_point + .value_or(current_timeval()); const char *fmt; char buffer[64]; int amount; - if (convert_local) { - current_time = convert_log_time_to_local(current_time); + if (this->p_convert_to_local) { + current_time.tv_sec = convert_log_time_to_local(current_time.tv_sec); } - delta = current_time - last_time; - if (delta < 0) { + auto delta = std::chrono::seconds(current_time.tv_sec - this->p_past_point.tv_sec); + if (delta < 0s) { return "in the future"; - } else if (delta < 60) { + } else if (delta < 1min) { return "just now"; - } else if (delta < (60 * 2)) { + } else if (delta < 2min) { return "one minute ago"; - } else if (delta < (60 * 60)) { + } else if (delta < 1h) { fmt = "%d minutes ago"; - amount = delta / 60; - } else if (delta < (2 * 60 * 60)) { + amount = std::chrono::duration_cast(delta).count(); + } else if (delta < 2h) { return "one hour ago"; - } else if (delta < (24 * 60 * 60)) { + } else if (delta < 24h) { fmt = "%d hours ago"; - amount = delta / (60 * 60); - } else if (delta < (2 * 24 * 60 * 60)) { + amount = std::chrono::duration_cast(delta).count(); + } else if (delta < 48h) { return "one day ago"; - } else if (delta < (365 * 24 * 60 * 60)) { + } else if (delta < 365 * 24h) { fmt = "%d days ago"; - amount = delta / (24 * 60 * 60); - } else if (delta < (2 * 365 * 24 * 60 * 60)) { + amount = delta / 24h; + } else if (delta < (2 * 365 * 24h)) { return "over a year ago"; } else { fmt = "over %d years ago"; - amount = delta / (365 * 24 * 60 * 60); + amount = delta / (365 * 24h); } snprintf(buffer, sizeof(buffer), fmt, amount); @@ -78,42 +89,35 @@ std::string time_ago(time_t last_time, bool convert_local) return std::string(buffer); } -std::string precise_time_ago(const struct timeval &tv, bool convert_local) +std::string point::as_precise_time_ago() { struct timeval now, diff; - gettimeofday(&now, nullptr); - if (convert_local) { + now = this->p_recent_point.value_or(current_timeval()); + if (this->p_convert_to_local) { now.tv_sec = convert_log_time_to_local(now.tv_sec); } - timersub(&now, &tv, &diff); + timersub(&now, &this->p_past_point, &diff); if (diff.tv_sec < 0) { - return time_ago(tv.tv_sec); + return this->as_time_ago(); } else if (diff.tv_sec <= 1) { return "a second ago"; } else if (diff.tv_sec < (10 * 60)) { - char buf[64]; - if (diff.tv_sec < 60) { - snprintf(buf, sizeof(buf), - "%2ld seconds ago", - diff.tv_sec); - } else { - time_t seconds = diff.tv_sec % 60; - time_t minutes = diff.tv_sec / 60; - - snprintf(buf, sizeof(buf), - "%2ld minute%s and %2ld second%s ago", - minutes, - minutes > 1 ? "s" : "", - seconds, - seconds == 1 ? "" : "s"); + return fmt::format("{:2} seconds ago", diff.tv_sec); } - return std::string(buf); + time_t seconds = diff.tv_sec % 60; + time_t minutes = diff.tv_sec / 60; + + return fmt::format("{:2} minute{} and {:2} second{} ago", + minutes, + minutes > 1 ? "s" : "", + seconds, + seconds == 1 ? "" : "s"); } else { - return time_ago(tv.tv_sec, convert_local); + return this->as_time_ago(); } } diff --git a/src/base/humanize.time.hh b/src/base/humanize.time.hh index 72795ba2..f0ba7796 100644 --- a/src/base/humanize.time.hh +++ b/src/base/humanize.time.hh @@ -32,12 +32,40 @@ #include +#include "optional.hpp" + namespace humanize { namespace time { -std::string time_ago(time_t last_time, bool convert_local = false); +class point { +public: + static point from_tv(const struct timeval &tv); -std::string precise_time_ago(const struct timeval &tv, bool convert_local = false); + point &with_recent_point(const struct timeval &tv) + { + this->p_recent_point = tv; + return *this; + } + + point &with_convert_to_local(bool convert_to_local) + { + this->p_convert_to_local = convert_to_local; + return *this; + } + + std::string as_time_ago(); + + std::string as_precise_time_ago(); + +private: + explicit point(const struct timeval &tv) : p_past_point{tv.tv_sec, + tv.tv_usec} + {} + + struct timeval p_past_point; + nonstd::optional p_recent_point; + bool p_convert_to_local{false}; +}; } } diff --git a/src/base/humanize.time.tests.cc b/src/base/humanize.time.tests.cc new file mode 100644 index 00000000..d5ab4dcd --- /dev/null +++ b/src/base/humanize.time.tests.cc @@ -0,0 +1,102 @@ +/** + * Copyright (c) 2021, Timothy Stack + * + * 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 Timothy Stack 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 REGENTS 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 REGENTS 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 "config.h" + +#include +#include +#include "doctest.hh" + +#include "humanize.time.hh" + +TEST_CASE("time ago") +{ + using namespace std::chrono_literals; + + time_t t1 = 1610000000; + auto t1_chrono = std::chrono::seconds(t1); + + auto p1 = humanize::time::point::from_tv({t1, 0}) + .with_recent_point({t1 + 5, 0}); + + CHECK(p1.as_time_ago() == "just now"); + CHECK(p1.as_precise_time_ago() == " 5 seconds ago"); + + auto p2 = humanize::time::point::from_tv({t1, 0}) + .with_recent_point({t1 + 65, 0}); + + CHECK(p2.as_time_ago() == "one minute ago"); + CHECK(p2.as_precise_time_ago() == " 1 minute and 5 seconds ago"); + + auto p3 = humanize::time::point::from_tv({t1, 0}) + .with_recent_point({t1 + (3 * 60 + 5), 0}); + + CHECK(p3.as_time_ago() == "3 minutes ago"); + CHECK(p3.as_precise_time_ago() == " 3 minutes and 5 seconds ago"); + + auto p4 = humanize::time::point::from_tv({t1, 0}) + .with_recent_point({(t1_chrono + 65min).count(), 0}); + + CHECK(p4.as_time_ago() == "one hour ago"); + CHECK(p4.as_precise_time_ago() == "one hour ago"); + + auto p5 = humanize::time::point::from_tv({t1, 0}) + .with_recent_point({(t1_chrono + 3h).count(), 0}); + + CHECK(p5.as_time_ago() == "3 hours ago"); + CHECK(p5.as_precise_time_ago() == "3 hours ago"); + + auto p6 = humanize::time::point::from_tv({t1, 0}) + .with_recent_point({(t1_chrono + 25h).count(), 0}); + + CHECK(p6.as_time_ago() == "one day ago"); + CHECK(p6.as_precise_time_ago() == "one day ago"); + + auto p7 = humanize::time::point::from_tv({t1, 0}) + .with_recent_point({(t1_chrono + 50h).count(), 0}); + + CHECK(p7.as_time_ago() == "2 days ago"); + CHECK(p7.as_precise_time_ago() == "2 days ago"); + + auto p8 = humanize::time::point::from_tv({t1, 0}) + .with_recent_point({(t1_chrono + 370 * 24h).count(), 0}); + + CHECK(p8.as_time_ago() == "over a year ago"); + CHECK(p8.as_precise_time_ago() == "over a year ago"); + + auto p9 = humanize::time::point::from_tv({t1, 0}) + .with_recent_point({(t1_chrono + 800 * 24h).count(), 0}); + + CHECK(p9.as_time_ago() == "over 2 years ago"); + CHECK(p9.as_precise_time_ago() == "over 2 years ago"); + + CHECK(humanize::time::point::from_tv({1610000000, 0}) + .with_recent_point({1612000000, 0}) + .as_time_ago() == "23 days ago"); +} diff --git a/src/base/test_base.cc b/src/base/test_base.cc new file mode 100644 index 00000000..35855ca8 --- /dev/null +++ b/src/base/test_base.cc @@ -0,0 +1,33 @@ +/** + * Copyright (c) 2021, Timothy Stack + * + * 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 Timothy Stack 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 REGENTS 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 REGENTS 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 "config.h" + +#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN +#include "doctest.hh" diff --git a/src/base/time_util.hh b/src/base/time_util.hh index cc00bd89..57fcbdaf 100644 --- a/src/base/time_util.hh +++ b/src/base/time_util.hh @@ -127,6 +127,14 @@ inline mstime_t getmstime() { return tv.tv_sec * 1000ULL + tv.tv_usec / 1000ULL; } +inline struct timeval current_timeval() { + struct timeval retval; + + gettimeofday(&retval, nullptr); + + return retval; +} + inline time_t day_num(time_t ti) { return ti / (24 * 60 * 60); diff --git a/src/field_overlay_source.cc b/src/field_overlay_source.cc index de6c2cd4..e8324082 100644 --- a/src/field_overlay_source.cc +++ b/src/field_overlay_source.cc @@ -77,8 +77,9 @@ void field_overlay_source::build_summary_lines(const listview_curses &lv) first_line = lss.find_line(lss.at(vis_line_t(0))); last_line = lss.find_line(lss.at(lv.get_bottom())); last_time = "Last message: " ANSI_BOLD_START + - humanize::time::precise_time_ago( - last_line->get_timeval(), true) + ANSI_NORM; + humanize::time::point::from_tv(last_line->get_timeval()) + .with_convert_to_local(true) + .as_precise_time_ago() + ANSI_NORM; duration2str(last_line->get_time_in_millis() - first_line->get_time_in_millis(), time_span); @@ -285,7 +286,9 @@ void field_overlay_source::build_field_lines(const listview_curses &lv) time_line.with_attr(string_attr(time_lr, &view_curses::VC_STYLE, A_BOLD)); time_str.append(" -- "); time_lr.lr_start = time_str.length(); - time_str.append(humanize::time::precise_time_ago(ll->get_timeval(), true)); + time_str.append(humanize::time::point::from_tv(ll->get_timeval()) + .with_convert_to_local(true) + .as_precise_time_ago()); time_lr.lr_end = time_str.length(); time_line.with_attr(string_attr(time_lr, &view_curses::VC_STYLE, A_BOLD)); diff --git a/src/lnav.cc b/src/lnav.cc index fd4f1b21..0dd3ea99 100644 --- a/src/lnav.cc +++ b/src/lnav.cc @@ -1437,7 +1437,9 @@ static void looper() if (session_data.sd_save_time) { std::string ago; - ago = humanize::time::time_ago(session_data.sd_save_time); + ago = humanize::time::point::from_tv({ + (time_t) session_data.sd_save_time, 0}) + .as_time_ago(); lnav_data.ld_rl_view->set_value( ("restored session from " ANSI_BOLD_START) + ago +