From f10dd9e03bbf796eddc619b461e133bde58aeabd Mon Sep 17 00:00:00 2001 From: 0xFEEDC0DE64 Date: Sun, 14 Feb 2021 23:25:43 +0100 Subject: [PATCH] Added unit tests and qmake helper files --- espchrono.pri | 1 + espchrono_src.pri | 5 + test/espchronotestutils.cpp | 52 +++++ test/espchronotestutils.h | 63 ++++++ test/espchronotestutils.pri | 1 + test/espchronotestutils_src.pri | 5 + test/tst_espchrono.cpp | 375 ++++++++++++++++++++++++++++++++ test/tstespchrono.pro | 56 +++++ 8 files changed, 558 insertions(+) create mode 100644 espchrono.pri create mode 100644 espchrono_src.pri create mode 100644 test/espchronotestutils.cpp create mode 100644 test/espchronotestutils.h create mode 100644 test/espchronotestutils.pri create mode 100644 test/espchronotestutils_src.pri create mode 100644 test/tst_espchrono.cpp create mode 100644 test/tstespchrono.pro diff --git a/espchrono.pri b/espchrono.pri new file mode 100644 index 0000000..6916033 --- /dev/null +++ b/espchrono.pri @@ -0,0 +1 @@ +INCLUDEPATH += $$PWD/src diff --git a/espchrono_src.pri b/espchrono_src.pri new file mode 100644 index 0000000..7b210d9 --- /dev/null +++ b/espchrono_src.pri @@ -0,0 +1,5 @@ +HEADERS += \ + $$PWD/src/espchrono.h + +SOURCES += \ + $$PWD/src/espchrono.cpp diff --git a/test/espchronotestutils.cpp b/test/espchronotestutils.cpp new file mode 100644 index 0000000..bcda972 --- /dev/null +++ b/test/espchronotestutils.cpp @@ -0,0 +1,52 @@ +#include "espchronotestutils.h" + +using namespace std::chrono_literals; +using namespace date; + +const espchrono::time_zone testTimeZone{.offset=60min, .dayLightSavingMode=espchrono::DayLightSavingMode::EuropeanSummerTime}; + +espchrono::millis_clock::time_point wallClock{}; + +namespace QTest { +template<> +char *toString(const espchrono::DateTime &dateTime) +{ + return ::QTest::toString(espchrono::toString(dateTime)); +} + +template<> +char *toString(const espchrono::LocalDateTime &dateTime) +{ + return ::QTest::toString(espchrono::toString(dateTime)); +} + +template<> +char *toString(const espchrono::utc_clock::time_point &ts) +{ + return ::QTest::toString(espchrono::toDateTime(ts)); +} + +template<> +char *toString(const espchrono::local_clock::time_point &ts) +{ + return ::QTest::toString(espchrono::toDateTime(ts)); +} + +template<> +char *toString(const espchrono::seconds &val) +{ + return ::QTest::toString(espchrono::toDaypointString(val)); +} + +template<> +char *toString(const std::optional &val) +{ + return val ? ::QTest::toString(*val) : ::QTest::toString("(invalid)"); +} +} // namespace QTest + +// stub implementation to make unit tests happy +auto espchrono::millis_clock::now() noexcept -> time_point +{ + return wallClock; +} diff --git a/test/espchronotestutils.h b/test/espchronotestutils.h new file mode 100644 index 0000000..0ad52bf --- /dev/null +++ b/test/espchronotestutils.h @@ -0,0 +1,63 @@ +#pragma once + +#include +#include + +#include +#include + +#include + +#include "espchrono.h" +#include "cpputilstestutils.h" + +Q_DECLARE_METATYPE(espchrono::milliseconds) +Q_DECLARE_METATYPE(espchrono::seconds) +Q_DECLARE_METATYPE(espchrono::minutes) +Q_DECLARE_METATYPE(espchrono::hours) +Q_DECLARE_METATYPE(espchrono::utc_clock::time_point) +Q_DECLARE_METATYPE(espchrono::local_clock::time_point) +Q_DECLARE_METATYPE(espchrono::DateTime) +Q_DECLARE_METATYPE(espchrono::LocalDateTime) +Q_DECLARE_METATYPE(std::optional) + +extern const espchrono::time_zone testTimeZone; + +extern espchrono::millis_clock::time_point wallClock; + +namespace { +template +espchrono::local_clock::time_point makeLocal(T day, espchrono::minutes time) +{ + espchrono::local_clock::time_point localTime{date::sys_days{day}.time_since_epoch(), testTimeZone, false}; + localTime += time; + return localTime; +} + +template +espchrono::utc_clock::time_point makeUtcFromLocal(T day, espchrono::minutes time) +{ + const auto localTime = makeLocal(day, time); + return espchrono::localToUtc(localTime); +} +} // namespace + +namespace QTest { +template<> +char *toString(const espchrono::DateTime &dateTime); + +template<> +char *toString(const espchrono::LocalDateTime &dateTime); + +template<> +char *toString(const espchrono::utc_clock::time_point &ts); + +template<> +char *toString(const espchrono::local_clock::time_point &ts); + +template<> +char *toString(const espchrono::seconds &val); + +template<> +char *toString(const std::optional &val); +} // namespace QTest diff --git a/test/espchronotestutils.pri b/test/espchronotestutils.pri new file mode 100644 index 0000000..aba082d --- /dev/null +++ b/test/espchronotestutils.pri @@ -0,0 +1 @@ +INCLUDEPATH += $$PWD diff --git a/test/espchronotestutils_src.pri b/test/espchronotestutils_src.pri new file mode 100644 index 0000000..a34b56e --- /dev/null +++ b/test/espchronotestutils_src.pri @@ -0,0 +1,5 @@ +HEADERS += \ + $$PWD/espchronotestutils.h + +SOURCES += \ + $$PWD/espchronotestutils.cpp diff --git a/test/tst_espchrono.cpp b/test/tst_espchrono.cpp new file mode 100644 index 0000000..35997e8 --- /dev/null +++ b/test/tst_espchrono.cpp @@ -0,0 +1,375 @@ +#include +#include +#include + +#include +#include + +#include "espchronotestutils.h" +#include "espchrono.h" + +using namespace std::chrono_literals; +using namespace std::string_literals; +using namespace date; +using QTest::toString; + +class TstEspChrono : public QObject +{ + Q_OBJECT + +private slots: + void test_dateTimeUtcToString_data() + { + QTest::addColumn("dateTime"); + QTest::addColumn("expected"); + + QTest::addRow("monday") << espchrono::DateTime{ + 10_d/October/2020, + .hour=10, + .minute=20, + .second=30, + .dayOfWeek=espchrono::DateTime::DayOfWeek::Monday + } << "2020-10-10T10:20:30"s; + + QTest::addRow("tuesday") << espchrono::DateTime{ + 10_d/October/2020, + .hour=10, + .minute=20, + .second=30, + .dayOfWeek=espchrono::DateTime::DayOfWeek::Tuesday + } << "2020-10-10T10:20:30"s; + + QTest::addRow("wednesday") << espchrono::DateTime{ + 10_d/October/2020, + .hour=10, + .minute=20, + .second=30, + .dayOfWeek=espchrono::DateTime::DayOfWeek::Wednesday + } << "2020-10-10T10:20:30"s; + + QTest::addRow("thursday") << espchrono::DateTime{ + 10_d/October/2020, + .hour=10, + .minute=20, + .second=30, + .dayOfWeek=espchrono::DateTime::DayOfWeek::Thursday + } << "2020-10-10T10:20:30"s; + + QTest::addRow("friday") << espchrono::DateTime{ + 10_d/October/2020, + .hour=10, + .minute=20, + .second=30, + .dayOfWeek=espchrono::DateTime::DayOfWeek::Friday + } << "2020-10-10T10:20:30"s; + + QTest::addRow("aturday") << espchrono::DateTime{ + 10_d/October/2020, + .hour=10, + .minute=20, + .second=30, + .dayOfWeek=espchrono::DateTime::DayOfWeek::Saturday + } << "2020-10-10T10:20:30"s; + + QTest::addRow("unday") << espchrono::DateTime{ + 10_d/October/2020, + .hour=10, + .minute=20, + .second=30, + .dayOfWeek=espchrono::DateTime::DayOfWeek::Sunday + } << "2020-10-10T10:20:30"s; + + QTest::addRow("leading_zeros") << espchrono::DateTime{ + 1_d/January/2020, + .hour=1, + .minute=2, + .second=3, + .dayOfWeek=espchrono::DateTime::DayOfWeek::Monday + } << "2020-01-01T01:02:03"s; + } + + void test_dateTimeUtcToString() + { + QFETCH(espchrono::DateTime, dateTime); + QFETCH(std::string, expected); + + FIXEDCOMPARE(espchrono::toString(dateTime), expected); + } + + void test_dateTimeLocalToString_data() + { + QTest::addColumn("dateTime"); + QTest::addColumn("expected"); + + QTest::addRow("leading_zeros") << espchrono::LocalDateTime{ + espchrono::DateTime { + 1_d/January/2020, + .hour = 1, + .minute = 2, + .second = 3, + .dayOfWeek = espchrono::DateTime::DayOfWeek::Monday, + }, + .timezone = testTimeZone, + .dst = false + } << "2020-01-01T01:02:03 +01:00"s; + + QTest::addRow("leading_zeros_dst") << espchrono::LocalDateTime{ + espchrono::DateTime { + 1_d/January/2020, + .hour = 1, + .minute = 2, + .second = 3, + .dayOfWeek = espchrono::DateTime::DayOfWeek::Monday, + }, + .timezone = testTimeZone, + .dst = true + } << "2020-01-01T01:02:03 +02:00"s; + } + + void test_dateTimeLocalToString() + { + QFETCH(espchrono::LocalDateTime, dateTime); + QFETCH(std::string, expected); + + FIXEDCOMPARE(espchrono::toString(dateTime), expected); + } + + void test_toDateTimeUtc_data() + { + QTest::addColumn("time_point"); + QTest::addColumn("expected"); + + QTest::addRow("random") << espchrono::utc_clock::time_point{espchrono::seconds{123456}} << espchrono::DateTime{ + 2_d/January/1970, + .hour=10, .minute=17, .second=36, + .dayOfWeek=espchrono::DateTime::DayOfWeek::Friday + }; + + QTest::addRow("leap_year") << espchrono::utc_clock::time_point{espchrono::seconds{1582934400}} + << espchrono::DateTime{ + 29_d/February/2020, + .hour=0, .minute=0, .second=0, + .dayOfWeek=espchrono::DateTime::DayOfWeek::Saturday + }; + + QTest::addRow("normal_year") << espchrono::utc_clock::time_point{espchrono::seconds{1614556800}} + << espchrono::DateTime{ + 1_d/March/2021, + .hour=0, .minute=0, .second=0, + .dayOfWeek=espchrono::DateTime::DayOfWeek::Monday + }; + } + + void test_toDateTimeUtc() + { + QFETCH(espchrono::utc_clock::time_point, time_point); + QFETCH(espchrono::DateTime, expected); + + FIXEDCOMPARE(espchrono::toDateTime(time_point), expected); + } + + void test_toDateTimeLocal_data() + { + QTest::addColumn("time_point"); + QTest::addColumn("expected"); + + QTest::addRow("no_dst") << espchrono::local_clock::time_point(espchrono::seconds{123456}, testTimeZone, false) + << espchrono::LocalDateTime{ + espchrono::DateTime{ + 2_d/January/1970, + .hour=10, .minute=17, .second=36, + .dayOfWeek=espchrono::DateTime::DayOfWeek::Friday + }, + .timezone = testTimeZone, + .dst = false + }; + QTest::addRow("with_dst") << espchrono::local_clock::time_point(espchrono::seconds{123456}, testTimeZone, true) + << espchrono::LocalDateTime{ + espchrono::DateTime{ + 2_d/January/1970, + .hour=10, .minute=17, .second=36, + .dayOfWeek=espchrono::DateTime::DayOfWeek::Friday + }, + .timezone = testTimeZone, + .dst = true + }; + } + + void test_toDateTimeLocal() + { + QFETCH(espchrono::local_clock::time_point, time_point); + QFETCH(espchrono::LocalDateTime, expected); + + FIXEDCOMPARE(espchrono::toDateTime(time_point), expected); + } + + void test_toDaypointString_data() + { + QTest::addColumn("input"); + QTest::addColumn("expected"); + + QTest::addRow("00:00:00") << espchrono::seconds{} << "00:00:00"s; + QTest::addRow("05:00:00") << espchrono::seconds{5h} << "05:00:00"s; + QTest::addRow("05:04:00") << espchrono::seconds{5h+4min} << "05:04:00"s; + QTest::addRow("05:04:03") << espchrono::seconds{5h+4min+3s} << "05:04:03"s; + QTest::addRow("05:00:03") << espchrono::seconds{5h+3s} << "05:00:03"s; + QTest::addRow("23:59:59") << espchrono::seconds{23h+59min+59s} << "23:59:59"s; + QTest::addRow("-23:59:59") << espchrono::seconds{-23h-59min-59s} << "-23:59:59"s; + QTest::addRow("-00:59:59") << espchrono::seconds{-59min-59s} << "-00:59:59"s; + } + + void test_toDaypointString() + { + QFETCH(espchrono::seconds, input); + QFETCH(std::string, expected); + FIXEDCOMPARE(espchrono::toDaypointString(input), expected); + } + + void test_parseDaypoint_data() + { + QTest::addColumn("input"); + QTest::addColumn>("expected"); + + QTest::addRow("bullshit") << "bullshit"s << std::optional{}; + QTest::addRow("missing_minute") << "00:"s << std::optional{}; + QTest::addRow("zero") << "00:00"s << std::optional{0s}; + QTest::addRow("zero3") << "00:00:00"s << std::optional{0s}; + QTest::addRow("random") << "12:34:56"s << std::optional{12h+34min+56s}; + QTest::addRow("random2") << "12:34"s << std::optional{12h+34min}; +// QTest::addRow("negative") << "-12:34:56"s << std::optional{-12h-34min-56s}; +// QTest::addRow("negative_leading_zero") << "-00:34:56"s << std::optional{-34min-56s}; + } + + void test_parseDaypoint() + { + QFETCH(std::string, input); + QFETCH(std::optional, expected); + + FIXEDCOMPARE(espchrono::parseDaypoint(input), expected); + } + + void test_compareTimezones() + { + espchrono::time_zone a{ + .offset = 10min, + .dayLightSavingMode = espchrono::DayLightSavingMode::None + }; + espchrono::time_zone b{ + .offset = 10min, + .dayLightSavingMode = espchrono::DayLightSavingMode::None + }; + + FIXEDCOMPARE(a == b, true); + FIXEDCOMPARE(a != b, false); + + a.offset -= 1min; + + FIXEDCOMPARE(a == b, false); + FIXEDCOMPARE(a != b, true); + + a.dayLightSavingMode = espchrono::DayLightSavingMode::EuropeanSummerTime; + + FIXEDCOMPARE(a == b, false); + FIXEDCOMPARE(a != b, true); + + a.offset += 1min; + + FIXEDCOMPARE(a == b, false); + FIXEDCOMPARE(a != b, true); + + a.dayLightSavingMode = espchrono::DayLightSavingMode::None; + + FIXEDCOMPARE(a == b, true); + FIXEDCOMPARE(a != b, false); + } + + void test_compareLocalTimepoints() + { + espchrono::local_clock::time_point a { + espchrono::seconds{10}, + espchrono::time_zone{ + .offset = 1h, + .dayLightSavingMode = espchrono::DayLightSavingMode::EuropeanSummerTime + }, + false + }; + espchrono::local_clock::time_point b { + espchrono::seconds{10}, + espchrono::time_zone{ + .offset = 1h, + .dayLightSavingMode = espchrono::DayLightSavingMode::EuropeanSummerTime + }, + false + }; + + FIXEDCOMPARE(a == b, true); + FIXEDCOMPARE(a != b, false); + + a.timezone.offset -= 1h; + + FIXEDCOMPARE(a == b, false); + FIXEDCOMPARE(a != b, true); + + a.dst = true; + + FIXEDCOMPARE(a == b, false); + FIXEDCOMPARE(a != b, true); + + a.timezone.offset += 1h; + + FIXEDCOMPARE(a == b, false); + FIXEDCOMPARE(a != b, true); + + a.dst = false; + + FIXEDCOMPARE(a == b, true); + FIXEDCOMPARE(a != b, false); + + a -= 1h; + + FIXEDCOMPARE(a == b, false); + FIXEDCOMPARE(a != b, true); + } + + void test_utcToLocal_data() + { +// espchrono::utc_clock::time_point test{sys_days{2020_y/January/1}.time_since_epoch()}; +// for (int i = 0; i < 365; i++) +// { +// const auto result = espchrono::utcToLocal(test, testTimeZone); +// qDebug() << espchrono::toString(espchrono::toDateTime(test)) << espchrono::toString(espchrono::toDateTime(result)); +// test += 24h; +// } + + QTest::addColumn("utc"); + QTest::addColumn("expected"); + + QTest::addRow("test1") << espchrono::utc_clock::time_point{sys_days{2020_y/January/1}.time_since_epoch()} + << espchrono::local_clock::time_point{sys_days{2020_y/January/1}.time_since_epoch() + 1h, testTimeZone, false}; + QTest::addRow("test2") << espchrono::utc_clock::time_point{sys_days{2020_y/March/29}.time_since_epoch()} + << espchrono::local_clock::time_point{sys_days{2020_y/March/29}.time_since_epoch() + 1h, testTimeZone, false}; + QTest::addRow("test3") << espchrono::utc_clock::time_point{sys_days{2020_y/March/30}.time_since_epoch()} + << espchrono::local_clock::time_point{sys_days{2020_y/March/30}.time_since_epoch() + 2h, testTimeZone, true}; + QTest::addRow("test4") << espchrono::utc_clock::time_point{sys_days{2020_y/October/25}.time_since_epoch()} + << espchrono::local_clock::time_point{sys_days{2020_y/October/25}.time_since_epoch() + 2h, testTimeZone, true}; + QTest::addRow("test5") << espchrono::utc_clock::time_point{sys_days{2020_y/October/26}.time_since_epoch()} + << espchrono::local_clock::time_point{sys_days{2020_y/October/26}.time_since_epoch() + 1h, testTimeZone, false}; + QTest::addRow("test6") << espchrono::utc_clock::time_point{sys_days{2020_y/December/31}.time_since_epoch()} + << espchrono::local_clock::time_point{sys_days{2020_y/December/31}.time_since_epoch() + 1h, testTimeZone, false}; + + QTest::addRow("test7") << espchrono::utc_clock::time_point{sys_days{2020_y/November/11}.time_since_epoch() + 17h + 30min} + << espchrono::local_clock::time_point{sys_days{2020_y/November/11}.time_since_epoch() + 18h + 30min, testTimeZone, false}; + } + + void test_utcToLocal() + { + QFETCH(espchrono::utc_clock::time_point, utc); + QFETCH(espchrono::local_clock::time_point, expected); + + FIXEDCOMPARE(utcToLocal(utc, testTimeZone), expected); + } +}; + +QTEST_APPLESS_MAIN(TstEspChrono) + +#include "tst_espchrono.moc" diff --git a/test/tstespchrono.pro b/test/tstespchrono.pro new file mode 100644 index 0000000..3f10c9f --- /dev/null +++ b/test/tstespchrono.pro @@ -0,0 +1,56 @@ +TEMPLATE = app + +QT += core testlib +QT -= gui widgets + +CONFIG += c++17 qt console warn_on depend_includepath testcase +CONFIG -= app_bundle + +SOURCES += \ + tst_espchrono.cpp + +ESPCHRONO_DIR = $$PWD/.. + +include($$ESPCHRONO_DIR/espchrono.pri) +include($$ESPCHRONO_DIR/espchrono_src.pri) +include($$ESPCHRONO_DIR/test/espchronotestutils.pri) +include($$ESPCHRONO_DIR/test/espchronotestutils_src.pri) + +equals(CLONE_CPPUTILS, 1) { + CPPUTILS_DIR = $$PWD/cpputils + + message("Checking out cpputils...") + exists($$CPPUTILS_DIR/.git) { + system("git -C $$CPPUTILS_DIR pull") + } else { + system("git clone https://github.com/0xFEEDC0DE64/cpputils.git $$CPPUTILS_DIR") + } +} else: exists($$PWD/../../cpputils/src) { + CPPUTILS_DIR = $$PWD/../../cpputils +} else { + error("cpputils not found, please run git submodule update --init") +} + +include($$CPPUTILS_DIR/cpputils.pri) +include($$CPPUTILS_DIR/cpputils_src.pri) +include($$CPPUTILS_DIR/test/cpputilstestutils.pri) +include($$CPPUTILS_DIR/test/cpputilstestutils_src.pri) + +equals(CLONE_DATE, 1) { + DATE_DIR = $$PWD/date + + message("Checking out date...") + exists($$DATE_DIR/.git): { + system("git -C $$DATE_DIR pull") + } else { + system("git clone https://github.com/0xFEEDC0DE64/date.git $$DATE_DIR") + } +} else: exists($$PWD/../../date/include) { + DATE_DIR = $$PWD/../../date +} else { + error("date not found, please run git submodule update --init") +} + +include($$DATE_DIR/date.pri) + +QMAKE_CXXFLAGS += -Wno-missing-field-initializers