[tests] add some tests for time-ago stuff

This commit is contained in:
Timothy Stack 2021-04-01 16:22:04 -07:00
parent 32ddc76624
commit d183247a31
11 changed files with 311 additions and 89 deletions

View File

@ -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)

View File

@ -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

48
src/base/CMakeLists.txt Normal file
View File

@ -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)

View File

@ -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

View File

@ -29,48 +29,59 @@
#include "config.h"
#include <chrono>
#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<std::chrono::minutes>(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<std::chrono::hours>(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();
}
}

View File

@ -32,12 +32,40 @@
#include <string>
#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<struct timeval> p_recent_point;
bool p_convert_to_local{false};
};
}
}

View File

@ -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 <chrono>
#include <iostream>
#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");
}

33
src/base/test_base.cc Normal file
View File

@ -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"

View File

@ -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);

View File

@ -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));

View File

@ -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 +