Add e2e tests for temporal types (#241)
This commit is contained in:
parent
7e81a95b81
commit
18b801a722
@ -203,7 +203,8 @@ import_external_library(mgclient STATIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/mgclient/include
|
||||
CMAKE_ARGS -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER}
|
||||
-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}
|
||||
-DBUILD_TESTING=OFF)
|
||||
-DBUILD_TESTING=OFF
|
||||
-DBUILD_CPP_BINDINGS=ON)
|
||||
find_package(OpenSSL REQUIRED)
|
||||
target_link_libraries(mgclient INTERFACE ${OPENSSL_LIBRARIES})
|
||||
|
||||
|
@ -212,7 +212,7 @@ repo_clone_try_double "${primary_urls[rocksdb]}" "${secondary_urls[rocksdb]}" "r
|
||||
sed -i 's/TARGETS ${ROCKSDB_SHARED_LIB}/TARGETS ${ROCKSDB_SHARED_LIB} OPTIONAL/' rocksdb/CMakeLists.txt
|
||||
|
||||
# mgclient
|
||||
mgclient_tag="v1.2.0" # (2021-01-14)
|
||||
mgclient_tag="v1.3.0" # (2021-09-23)
|
||||
repo_clone_try_double "${primary_urls[mgclient]}" "${secondary_urls[mgclient]}" "mgclient" "$mgclient_tag"
|
||||
sed -i 's/\${CMAKE_INSTALL_LIBDIR}/lib/' mgclient/src/CMakeLists.txt
|
||||
|
||||
|
@ -469,7 +469,12 @@ std::pair<DateParameters, LocalTimeParameters> ParseLocalDateTimeParameters(std:
|
||||
|
||||
LocalDateTime::LocalDateTime(const int64_t microseconds) {
|
||||
auto chrono_microseconds = std::chrono::microseconds(microseconds);
|
||||
date = Date(chrono_microseconds.count());
|
||||
constexpr int64_t one_day_in_microseconds = std::chrono::microseconds{std::chrono::days{1}}.count();
|
||||
if (microseconds < 0 && (microseconds % one_day_in_microseconds != 0)) {
|
||||
date = Date(microseconds - one_day_in_microseconds);
|
||||
} else {
|
||||
date = Date(microseconds);
|
||||
}
|
||||
chrono_microseconds -= std::chrono::microseconds{date.MicrosecondsSinceEpoch()};
|
||||
local_time = LocalTime(chrono_microseconds.count());
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ add_subdirectory(memory)
|
||||
add_subdirectory(triggers)
|
||||
add_subdirectory(isolation_levels)
|
||||
add_subdirectory(streams)
|
||||
add_subdirectory(temporal_types)
|
||||
add_subdirectory(write_procedures)
|
||||
|
||||
copy_e2e_python_files(pytest_runner pytest_runner.sh "")
|
||||
|
3
tests/e2e/temporal_types/CMakeLists.txt
Normal file
3
tests/e2e/temporal_types/CMakeLists.txt
Normal file
@ -0,0 +1,3 @@
|
||||
add_executable(memgraph__e2e__temporal_roundtrip roundtrip.cpp)
|
||||
target_link_libraries(memgraph__e2e__temporal_roundtrip PUBLIC mgclient mg-utils gflags)
|
||||
|
219
tests/e2e/temporal_types/roundtrip.cpp
Normal file
219
tests/e2e/temporal_types/roundtrip.cpp
Normal file
@ -0,0 +1,219 @@
|
||||
#include <variant>
|
||||
|
||||
#include <gflags/gflags.h>
|
||||
#include "fmt/format.h"
|
||||
#include "mgclient.hpp"
|
||||
#include "utils/logging.hpp"
|
||||
#include "utils/temporal.hpp"
|
||||
|
||||
DEFINE_uint64(bolt_port, 0, "Bolt port arguments");
|
||||
|
||||
void MaybeExecuteQuery(mg::Client &client, const std::string &query, const std::string_view name) {
|
||||
auto executed = client.Execute(query);
|
||||
MG_ASSERT(executed, "Failed to execute {} query", name);
|
||||
client.DiscardAll();
|
||||
}
|
||||
|
||||
auto MaybeExecuteMatch(mg::Client &client, const std::string_view group) {
|
||||
auto executed = client.Execute(fmt::format("MATCH (u:{}) return u", group));
|
||||
MG_ASSERT(executed, "Failed to execute query");
|
||||
const auto result = client.FetchAll();
|
||||
MG_ASSERT(result, "Failed to fetch results");
|
||||
MG_ASSERT(result->size() == 1, "Failed to fetch correct result size");
|
||||
return result;
|
||||
}
|
||||
|
||||
auto GetItFromNodeProperty(const mg::ConstMap &props, const std::string_view property) {
|
||||
const auto it = props.find(property);
|
||||
MG_ASSERT(it != props.end(), "Failed to find property {}", property);
|
||||
return it;
|
||||
}
|
||||
|
||||
void RoundtripDuration(mg::Client &client, const std::string_view group, const std::string_view property,
|
||||
const std::string_view dur_str, const utils::Duration &expected) {
|
||||
const auto query = fmt::format("CREATE (:{} {{{}: DURATION({})}})", group, property, dur_str);
|
||||
MaybeExecuteQuery(client, query, "Duration");
|
||||
const auto result = MaybeExecuteMatch(client, group);
|
||||
const auto node = (*result)[0][0].ValueNode();
|
||||
const auto props = node.properties();
|
||||
const auto it = GetItFromNodeProperty(props, property);
|
||||
const auto dur = (*it).second.ValueDuration();
|
||||
MG_ASSERT(dur.months() == 0, "Received incorrect months in the duration");
|
||||
MG_ASSERT(dur.days() == expected.Days(), "Received incorrect days in the duration");
|
||||
MG_ASSERT(dur.seconds() == expected.SubDaysAsSeconds(), "Received incorrect seconds in the duration");
|
||||
MG_ASSERT(dur.nanoseconds() == expected.SubSecondsAsNanoseconds(), "Received incorrect nanoseconds in the duration");
|
||||
}
|
||||
|
||||
void RoundtripDate(mg::Client &client, const std::string_view group, const std::string_view property,
|
||||
const std::string_view date_str, const utils::Date &expected) {
|
||||
const auto query = fmt::format("CREATE (:{} {{{}: DATE({})}})", group, property, date_str);
|
||||
MaybeExecuteQuery(client, query, "Date");
|
||||
const auto result = MaybeExecuteMatch(client, group);
|
||||
const auto node = (*result)[0][0].ValueNode();
|
||||
const auto props = node.properties();
|
||||
const auto it = GetItFromNodeProperty(props, property);
|
||||
const auto date = (*it).second.ValueDate();
|
||||
MG_ASSERT(date.days() == expected.DaysSinceEpoch(), "Received incorrect days in the date roundtrip");
|
||||
}
|
||||
|
||||
struct LocalTimeParams {
|
||||
int64_t hours{0};
|
||||
int64_t minutes{0};
|
||||
int64_t seconds{0};
|
||||
int64_t subseconds{0};
|
||||
};
|
||||
|
||||
void RoundtripLocalTime(mg::Client &client, const std::string_view group, const std::string_view property,
|
||||
const std::string_view lt_str, const utils::LocalTime &expected) {
|
||||
const auto query = fmt::format("CREATE (:{} {{{}: LOCALTIME({})}})", group, property, lt_str);
|
||||
MaybeExecuteQuery(client, query, "LocalTime");
|
||||
const auto result = MaybeExecuteMatch(client, group);
|
||||
const auto node = (*result)[0][0].ValueNode();
|
||||
const auto props = node.properties();
|
||||
const auto it = GetItFromNodeProperty(props, property);
|
||||
const auto lt = (*it).second.ValueLocalTime();
|
||||
MG_ASSERT(lt.nanoseconds() == expected.NanosecondsSinceEpoch(),
|
||||
"Received incorrect nanoseconds in the LocalTime roundtrip");
|
||||
}
|
||||
|
||||
void RoundtripLocalDateTime(mg::Client &client, const std::string_view group, const std::string_view property,
|
||||
const std::string_view ldt_str, const utils::LocalDateTime &expected) {
|
||||
const auto query = fmt::format("CREATE (:{} {{{}: LOCALDATETIME({})}})", group, property, ldt_str);
|
||||
MaybeExecuteQuery(client, query, "LocalDateTime");
|
||||
const auto result = MaybeExecuteMatch(client, group);
|
||||
const auto node = (*result)[0][0].ValueNode();
|
||||
const auto props = node.properties();
|
||||
const auto it = GetItFromNodeProperty(props, property);
|
||||
const auto ldt = (*it).second.ValueLocalDateTime();
|
||||
MG_ASSERT(ldt.seconds() == expected.SecondsSinceEpoch(), "Received incorrect seconds in the LocalDateTime roundtrip");
|
||||
MG_ASSERT(ldt.nanoseconds() == expected.SubSecondsAsNanoseconds(),
|
||||
"Received incorrect nanoseconds in the LocalDateTime roundtrip");
|
||||
}
|
||||
|
||||
void TestDate(mg::Client &client) {
|
||||
auto date_query = [](auto year, auto month, auto day) {
|
||||
return fmt::format("\"{:0>2}-{:0>2}-{:0>2}\"", year, month, day);
|
||||
};
|
||||
auto date_query_map = [](auto year, auto month, auto day) {
|
||||
return fmt::format("{{year:{}, month:{}, day:{}}}", year, month, day);
|
||||
};
|
||||
RoundtripDate(client, "Person1", "dob", date_query(1960, 1, 12), utils::Date({1960, 1, 12}));
|
||||
RoundtripDate(client, "Person2", "dob", date_query(1970, 1, 1), utils::Date({1970, 1, 1}));
|
||||
RoundtripDate(client, "Person3", "dob", date_query(1971, 2, 2), utils::Date({1971, 2, 2}));
|
||||
RoundtripDate(client, "Person4", "dob", date_query(2021, 12, 9), utils::Date({2021, 12, 9}));
|
||||
|
||||
RoundtripDate(client, "PersonMap1", "dob", date_query_map(1970, 1, 1), utils::Date({1970, 1, 1}));
|
||||
RoundtripDate(client, "PersonMap2", "dob", date_query_map(1971, 6, 5), utils::Date({1971, 6, 5}));
|
||||
RoundtripDate(client, "PersonMap3", "dob", date_query_map(2000, 8, 9), utils::Date({2000, 8, 9}));
|
||||
RoundtripDate(client, "PersonMap4", "dob", date_query_map(2021, 12, 9), utils::Date({2021, 12, 9}));
|
||||
}
|
||||
|
||||
void TestLocalTime(mg::Client &client) {
|
||||
auto lt = [](auto h, auto m, auto s, auto ss) { return fmt::format("{:2>2}:{:0>2}:{:0>2}.{:0>6}", h, m, s, ss); };
|
||||
auto lt_query = [](auto lt_as_str) { return fmt::format("\"{}\"", lt_as_str); };
|
||||
auto lt_query_map = [](int h = 0, int m = 0, int s = 0, int ml = 0, int mi = 0) {
|
||||
return fmt::format("{{hour:{}, minute:{}, second:{}, millisecond:{}, microsecond:{}}}", h, m, s, ml, mi);
|
||||
};
|
||||
|
||||
const auto parse = [](const std::string_view query) {
|
||||
return utils::LocalTime(utils::ParseLocalTimeParameters(fmt::format("{}", query)).first);
|
||||
};
|
||||
|
||||
const auto str1 = lt(1, 3, 3, 33);
|
||||
RoundtripLocalTime(client, "LT1", "time", lt_query(str1), parse(str1));
|
||||
const auto str2 = lt(13, 4, 44, 1002);
|
||||
RoundtripLocalTime(client, "LT2", "time", lt_query(str2), parse(str2));
|
||||
const auto str3 = lt(18, 22, 21, 68010);
|
||||
RoundtripLocalTime(client, "LT3", "time", lt_query(str3), parse(str3));
|
||||
const auto str4 = lt(1, 3, 3, 33);
|
||||
RoundtripLocalTime(client, "LT4", "time", lt_query(str4), parse(str4));
|
||||
|
||||
const auto str5 = lt_query_map(10, 4, 22, 33, 99);
|
||||
RoundtripLocalTime(client, "LT5", "time", str5, utils::LocalTime({10, 4, 22, 33, 99}));
|
||||
const auto str6 = lt_query_map(0, 0, 21, 12, 88);
|
||||
RoundtripLocalTime(client, "LT6", "time", str6, utils::LocalTime({0, 0, 21, 12, 88}));
|
||||
const auto str7 = lt_query_map(8, 4, 22, 33, 99);
|
||||
RoundtripLocalTime(client, "LT7", "time", str7, utils::LocalTime({8, 4, 22, 33, 99}));
|
||||
const auto str8 = lt_query_map(23, 1, 0, 0, 0);
|
||||
RoundtripLocalTime(client, "LT8", "time", str8, utils::LocalTime({23, 1, 0, 0, 0}));
|
||||
}
|
||||
|
||||
void TestLocalDateTime(mg::Client &client) {
|
||||
auto ldt = [](auto y, auto mo, auto d, auto h, auto m, auto s) {
|
||||
return fmt::format("{:0>2}-{:0>2}-{:0>2}T{:0>2}:{:0>2}:{:0>2}", y, mo, d, h, m, s);
|
||||
};
|
||||
auto parse = [](const std::string_view str) {
|
||||
const auto [dt, lt] = utils::ParseLocalDateTimeParameters(str);
|
||||
return utils::LocalDateTime(dt, lt);
|
||||
};
|
||||
auto ldt_query = [](const std::string_view str) { return fmt::format("\"{}\"", str); };
|
||||
const auto str = ldt(1200, 8, 9, 12, 33, 1);
|
||||
RoundtripLocalDateTime(client, "LDT1", "time", ldt_query(str), parse(str));
|
||||
const auto str1 = ldt(1961, 6, 3, 11, 22, 10);
|
||||
RoundtripLocalDateTime(client, "LDT2", "time", ldt_query(str1), parse(str1));
|
||||
const auto str2 = ldt(1971, 1, 1, 15, 16, 2);
|
||||
RoundtripLocalDateTime(client, "LDT3", "time", ldt_query(str2), parse(str2));
|
||||
const auto str3 = ldt(2000, 1, 1, 2, 33, 1);
|
||||
RoundtripLocalDateTime(client, "LDT4", "time", ldt_query(str3), parse(str3));
|
||||
const auto str4 = ldt(2021, 9, 21, 16, 57, 1);
|
||||
RoundtripLocalDateTime(client, "LDT5", "time", ldt_query(str4), parse(str4));
|
||||
|
||||
RoundtripLocalDateTime(client, "Map_LDT1", "time", "{year:1960, month:10, day:1}",
|
||||
utils::LocalDateTime({1960, 10, 1}, {}));
|
||||
RoundtripLocalDateTime(client, "Map_LDT2", "time", "{year:1960, month:10, day:1, hour:1, minute:2, second:33}",
|
||||
utils::LocalDateTime({1960, 10, 1}, {1, 2, 33}));
|
||||
RoundtripLocalDateTime(client, "Map_LDT3", "time", "{hour:10}", utils::LocalDateTime({}, {.hours = 10}));
|
||||
RoundtripLocalDateTime(client, "Map_LDT4", "time", "{day:2}", utils::LocalDateTime({.days = 2}, {}));
|
||||
}
|
||||
|
||||
void TestDuration(mg::Client &client) {
|
||||
const auto dur = [](auto d, auto h, auto m, auto s, auto ss) {
|
||||
return fmt::format("\"P{}DT{}H{}M{}.{}S\"", d, h, m, s, ss);
|
||||
};
|
||||
|
||||
RoundtripDuration(client, "Runner1", "time", dur(3, 5, 6, 2, 1), utils::Duration({3, 5, 6, 2.1}));
|
||||
RoundtripDuration(client, "Runner2", "time", dur(8, 9, 8, 4, 12), utils::Duration({8, 9, 8, 4.12}));
|
||||
RoundtripDuration(client, "Runner3", "time", dur(10, 10, 12, 44, 44), utils::Duration({10, 10, 12, 44.44}));
|
||||
RoundtripDuration(client, "Runner4", "time", dur(23, 11, 13, 59, 100000), utils::Duration({23, 11, 13, 59, 100}));
|
||||
RoundtripDuration(client, "Runner5", "time", dur(0, 110, 14, 88, 400000), utils::Duration({0, 110, 14, 88, 400}));
|
||||
|
||||
// fractions
|
||||
RoundtripDuration(client, "Runner6", "time", "\"P4.5D\"", utils::Duration(utils::DurationParameters{.days = 4.5}));
|
||||
RoundtripDuration(client, "Runner7", "time", "\"PT9.3H\"", utils::Duration(utils::DurationParameters{.hours = 9.3}));
|
||||
RoundtripDuration(client, "Runner8", "time", "\"PT4.2M\"",
|
||||
utils::Duration(utils::DurationParameters{.minutes = 4.2}));
|
||||
RoundtripDuration(client, "Runner9", "time", "\"PT8.4S\"",
|
||||
utils::Duration(utils::DurationParameters{.seconds = 8.4}));
|
||||
|
||||
RoundtripDuration(client, "RunnerMap1", "time",
|
||||
"{day:0, hour:4, minute:1, second:44, millisecond:44, microsecond:22}",
|
||||
utils::Duration(utils::DurationParameters{0, 4, 1, 44, 44, 22}));
|
||||
RoundtripDuration(client, "RunnerMap2", "time", "{day:15}", utils::Duration(utils::DurationParameters{15}));
|
||||
RoundtripDuration(client, "RunnerMap3", "time", "{hour:2.5}",
|
||||
utils::Duration(utils::DurationParameters{.hours = 2.5}));
|
||||
RoundtripDuration(client, "RunnerMap4", "time", "{minute:10.5, second:44}",
|
||||
utils::Duration(utils::DurationParameters{.minutes = 10.5, .seconds = 44}));
|
||||
|
||||
RoundtripDuration(client, "NegRunnerMap1", "time",
|
||||
"{day:0, hour:-1, minute:-2, second:-20, millisecond:-4, microsecond:-15}",
|
||||
utils::Duration(utils::DurationParameters{0, -1, -2, -20, -4, -15}));
|
||||
RoundtripDuration(client, "NegRunnerMap2", "time", "{day:-15}", utils::Duration(utils::DurationParameters{-15}));
|
||||
RoundtripDuration(client, "NegRunnerMap3", "time", "{hour:-2.5}",
|
||||
utils::Duration(utils::DurationParameters{.hours = -2.5}));
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
gflags::SetUsageMessage("Memgraph E2E temporal types roundtrip");
|
||||
gflags::ParseCommandLineFlags(&argc, &argv, true);
|
||||
MG_ASSERT(FLAGS_bolt_port != 0);
|
||||
logging::RedirectToStderr();
|
||||
|
||||
mg::Client::Init();
|
||||
auto client = mg::Client::Connect({.port = static_cast<uint16_t>(FLAGS_bolt_port)});
|
||||
MG_ASSERT(client, "Failed to connect with memgraph");
|
||||
TestDate(*client);
|
||||
TestLocalTime(*client);
|
||||
TestLocalDateTime(*client);
|
||||
TestDuration(*client);
|
||||
return 0;
|
||||
}
|
14
tests/e2e/temporal_types/workloads.yaml
Normal file
14
tests/e2e/temporal_types/workloads.yaml
Normal file
@ -0,0 +1,14 @@
|
||||
bolt_port: &bolt_port "7687"
|
||||
template_cluster: &template_cluster
|
||||
cluster:
|
||||
main:
|
||||
args: ["--bolt_port", *bolt_port, "--log-level=TRACE"]
|
||||
log_file: "temporal-types-e2e.log"
|
||||
setup_queries: []
|
||||
validation_queries: []
|
||||
|
||||
workloads:
|
||||
- name: "Temporal"
|
||||
binary: "tests/e2e/temporal_types/memgraph__e2e__temporal_roundtrip"
|
||||
args: ["--bolt_port", *bolt_port]
|
||||
<<: *template_cluster
|
@ -124,6 +124,9 @@ TEST(TemporalTest, LocalDateTimeMicrosecondsSinceEpochConversion) {
|
||||
check_microseconds(utils::DateParameters{2020, 11, 22}, utils::LocalTimeParameters{0, 0, 0, 0, 0});
|
||||
check_microseconds(utils::DateParameters{1900, 2, 22}, utils::LocalTimeParameters{0, 0, 0, 0, 0});
|
||||
check_microseconds(utils::DateParameters{0, 1, 1}, utils::LocalTimeParameters{0, 0, 0, 0, 0});
|
||||
|
||||
check_microseconds(utils::DateParameters{1961, 1, 1}, utils::LocalTimeParameters{15, 44, 12});
|
||||
check_microseconds(utils::DateParameters{1969, 12, 31}, utils::LocalTimeParameters{23, 59, 59});
|
||||
{
|
||||
utils::LocalDateTime local_date_time(utils::DateParameters{1970, 1, 1}, utils::LocalTimeParameters{0, 0, 0, 0, 0});
|
||||
ASSERT_EQ(local_date_time.MicrosecondsSinceEpoch(), 0);
|
||||
@ -146,6 +149,13 @@ TEST(TemporalTest, LocalDateTimeMicrosecondsSinceEpochConversion) {
|
||||
utils::LocalDateTime local_date_time(utils::DateParameters{2021, 1, 1}, utils::LocalTimeParameters{0, 0, 0, 0, 0});
|
||||
ASSERT_GT(local_date_time.MicrosecondsSinceEpoch(), 0);
|
||||
}
|
||||
{
|
||||
// Assert ordering for dates prior the unix epoch.
|
||||
// If this test fails, our storage indexes will be incorrect.
|
||||
utils::LocalDateTime ldt({1969, 12, 31}, {0, 0, 0});
|
||||
utils::LocalDateTime ldt2({1969, 12, 31}, {23, 59, 59});
|
||||
ASSERT_LT(ldt.MicrosecondsSinceEpoch(), ldt2.MicrosecondsSinceEpoch());
|
||||
}
|
||||
}
|
||||
|
||||
TEST(TemporalTest, DurationConversion) {
|
||||
@ -527,6 +537,9 @@ TEST(TemporalTest, LocalDateTimeAdditionSubtraction) {
|
||||
ASSERT_EQ(one_day_after_unix_epoch, utils::LocalDateTime({1970, 1, 2}, {.hours = 12}));
|
||||
ASSERT_EQ(one_day_after_unix_epoch_symmetrical, one_day_after_unix_epoch);
|
||||
|
||||
const auto one_day_before_unix_epoch = utils::LocalDateTime({1969, 12, 31}, {23, 59, 59});
|
||||
ASSERT_EQ(one_day_before_unix_epoch + utils::Duration({.seconds = 1}), utils::LocalDateTime({1970, 1, 1}, {}));
|
||||
|
||||
one_day_after_unix_epoch = unix_epoch + utils::Duration({.days = 1});
|
||||
ASSERT_EQ(one_day_after_unix_epoch, utils::LocalDateTime({1970, 1, 2}, {.hours = 12}));
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user