without `FMT_DEPRECATED_OSTREAM` macro, `UUID::to_sstring()` is
implemented using its `fmt::formatter`, which is not available
at the end of this header file where `UUID` is defined. at this moment,
we still use `FMT_DEPRECATED_OSTREAM` and {fmt} v9, so we can
still use `UUID::to_sstring()`, but in {fmt} v10, we cannot.
so, in this change, we change all callers of `UUID::to_sstring()`
to `fmt::to_string()`, so that we don't depend on
`FMT_DEPRECATED_OSTREAM` and {fmt} v9 anymore.
Signed-off-by: Kefu Chai <kefu.chai@scylladb.com>
289 lines
11 KiB
C++
289 lines
11 KiB
C++
/*
|
|
* Copyright (C) 2014-present ScyllaDB
|
|
*/
|
|
|
|
/*
|
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
|
*/
|
|
|
|
#define BOOST_TEST_MODULE core
|
|
|
|
#include <boost/test/unit_test.hpp>
|
|
#include <utility>
|
|
#include "utils/UUID_gen.hh"
|
|
#include "marshal_exception.hh"
|
|
|
|
BOOST_AUTO_TEST_CASE(test_generation_of_name_based_UUID) {
|
|
auto uuid = utils::UUID_gen::get_name_UUID("systembatchlog");
|
|
BOOST_REQUIRE_EQUAL(fmt::to_string(uuid), "0290003c-977e-397c-ac3e-fdfdc01d626b");
|
|
}
|
|
|
|
using utils::UUID;
|
|
|
|
BOOST_AUTO_TEST_CASE(test_UUID_comparison) {
|
|
static const std::initializer_list<std::pair<UUID, UUID>> uuid_pairs = {
|
|
|
|
{ UUID("ffeeddcc-aa99-8877-6655-443322110000"), UUID("00000000-0000-0000-0000-000000000000") },
|
|
{ UUID("0feeddcc-aa99-8877-6655-443322110000"), UUID("00000000-0000-0000-0000-000000000000") },
|
|
{ UUID("00000000-0000-0000-0000-000000000001"), UUID("00000000-0000-0000-0000-000000000000") },
|
|
|
|
{ UUID("ffeeddcc-aa99-8877-6655-443322110001"), UUID("ffeeddcc-aa99-8877-6655-443322110000") },
|
|
{ UUID("0feeddcc-aa99-8877-6655-443322110000"), UUID("0eeeddcc-aa99-8877-6655-443322110000") },
|
|
{ UUID("0290003c-987e-397c-ac3e-fdfdc01d626b"), UUID("0290003c-977e-397c-ac3e-fdfdc01d626b") },
|
|
|
|
};
|
|
|
|
for (auto& p : uuid_pairs) {
|
|
BOOST_REQUIRE_GT(p.first, p.second);
|
|
}
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(test_from_string) {
|
|
auto check = [] (sstring_view sv) {
|
|
auto uuid = UUID(sv);
|
|
BOOST_CHECK_EQUAL(uuid.version(), 4);
|
|
BOOST_CHECK_EQUAL(fmt::to_string(uuid), sv);
|
|
BOOST_CHECK_EQUAL((uuid.get_least_significant_bits() >> 62) & 0x3, 2);
|
|
};
|
|
|
|
check("b1415756-49c3-4fa8-9b72-d1b867b032af");
|
|
check("85859d5c-fcf3-4b0b-9089-197b8b06735c");
|
|
check("e596c2f2-d29d-44a0-bb89-0a90ff928490");
|
|
check("f28f86f5-cbc2-4526-ba25-db90c226ec6a");
|
|
check("ce84997b-6ea2-4468-9f02-8a65abf4141a");
|
|
|
|
// shorter than 16 bytes
|
|
BOOST_CHECK_THROW(UUID(""), marshal_exception);
|
|
BOOST_CHECK_THROW(UUID("dead-beef"), marshal_exception);
|
|
// longer than 16 bytes
|
|
BOOST_CHECK_THROW(UUID("ce84997b-6ea2-4468-9f02-8a65abf4141-long-long-ago"), marshal_exception);
|
|
// unconvertible string
|
|
BOOST_CHECK_THROW(UUID("hellowol-dea2-4468-9f02-8a65abf4141a"), marshal_exception);
|
|
// trailing garbage in msb
|
|
BOOST_CHECK_THROW(UUID("ce84997b-6ea2-wxyz-9f02-8a65abf4141a"), marshal_exception);
|
|
// trailing garbage in lsb
|
|
BOOST_CHECK_THROW(UUID("ce84997b-6ea2-4468-9f02-8a65abf4wxyz"), marshal_exception);
|
|
// spaces at the beginning
|
|
BOOST_CHECK_THROW(UUID(" 4997b-6ea2-wxyz-9f02-8a65abf4141a"), marshal_exception);
|
|
// spaces at the end
|
|
BOOST_CHECK_THROW(UUID("ce84997b-6ea2-4468-9f02-8a65abf4 "), marshal_exception);
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(test_make_random_uuid) {
|
|
std::vector<UUID> uuids;
|
|
for (auto i = 0; i < 100; i++) {
|
|
auto uuid = utils::make_random_uuid();
|
|
BOOST_CHECK_EQUAL(uuid.version(), 4);
|
|
BOOST_CHECK_EQUAL((uuid.get_least_significant_bits() >> 62) & 0x3, 2);
|
|
uuids.emplace_back(uuid);
|
|
}
|
|
std::sort(uuids.begin(), uuids.end());
|
|
BOOST_CHECK(std::unique(uuids.begin(), uuids.end()) == uuids.end());
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(test_get_time_uuid) {
|
|
using namespace std::chrono;
|
|
|
|
auto uuid = utils::UUID_gen::get_time_UUID();
|
|
BOOST_CHECK(uuid.is_timestamp());
|
|
|
|
auto tp = system_clock::now();
|
|
uuid = utils::UUID_gen::get_time_UUID(tp);
|
|
BOOST_CHECK(uuid.is_timestamp());
|
|
|
|
auto millis = duration_cast<milliseconds>(tp.time_since_epoch());
|
|
uuid = utils::UUID_gen::get_time_UUID(millis);
|
|
BOOST_CHECK(uuid.is_timestamp());
|
|
|
|
auto unix_timestamp = utils::UUID_gen::unix_timestamp(uuid);
|
|
BOOST_CHECK(unix_timestamp == millis);
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(test_uuid_to_uint32) {
|
|
// (gdb) p/x 0x3123223d ^ 0x17300 ^ 0x31e31215 ^ 0x98312
|
|
// $2 = 0xc8c03a
|
|
uint64_t x = 0x173003123223d;
|
|
uint64_t y = 0x9831231e31215;
|
|
uint32_t expected_id = 0xc8c03a;
|
|
auto uuid = utils::UUID(x, y);
|
|
uint32_t id = uuid_xor_to_uint32(uuid);
|
|
BOOST_CHECK(id == expected_id);
|
|
}
|
|
|
|
std::strong_ordering timeuuid_legacy_tri_compare(bytes_view o1, bytes_view o2) {
|
|
auto compare_pos = [&] (unsigned pos, int mask, std::strong_ordering ifequal) {
|
|
auto d = (o1[pos] & mask) <=> (o2[pos] & mask);
|
|
return d != 0 ? d : ifequal;
|
|
};
|
|
auto res = compare_pos(6, 0xf,
|
|
compare_pos(7, 0xff,
|
|
compare_pos(4, 0xff,
|
|
compare_pos(5, 0xff,
|
|
compare_pos(0, 0xff,
|
|
compare_pos(1, 0xff,
|
|
compare_pos(2, 0xff,
|
|
compare_pos(3, 0xff, std::strong_ordering::equal))))))));
|
|
if (res == 0) {
|
|
res = std::lexicographical_compare_three_way(o1.begin(), o1.end(), o2.begin(), o2.end(),
|
|
[] (const int8_t& a, const int8_t& b) { return a <=> b; });
|
|
}
|
|
return res;
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(test_timeuuid_msb_is_monotonic) {
|
|
using utils::UUID, utils::UUID_gen;
|
|
auto uuid = UUID_gen::get_time_UUID();
|
|
auto first = uuid.serialize();
|
|
int64_t scale_list[] = { 1, 10, 10000, 10000000, 0 };
|
|
auto str = [&scale_list](int64_t *scale) {
|
|
static std::string name_list[] = { " 100ns", "mc", "ms", "s" };
|
|
return name_list[scale - scale_list];
|
|
};
|
|
|
|
for (int64_t *scale = scale_list; *scale; scale++) {
|
|
int step = 1; /* + (random() % 169) ; */
|
|
auto prev = first;
|
|
for (int64_t i = 1; i < 3697; i += step) {
|
|
auto next = UUID(UUID_gen::create_time(UUID_gen::decimicroseconds{uuid.timestamp() + (i * *scale)}), 0).serialize();
|
|
bool t1 = utils::timeuuid_tri_compare(next, prev) > 0;
|
|
bool t2 = utils::timeuuid_tri_compare(next, first) > 0;
|
|
if (!t1 || !t2) {
|
|
BOOST_CHECK_MESSAGE(t1 && t2, format("a UUID {}{} later is not great than at test start: {} {}", i, str(scale), t1, t2));
|
|
}
|
|
prev = next;
|
|
}
|
|
}
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(test_timeuuid_tri_compare_legacy) {
|
|
using utils::UUID, utils::UUID_gen;
|
|
auto uuid = UUID_gen::get_time_UUID();
|
|
auto first = uuid.serialize();
|
|
int64_t scale_list[] = { 1, 10, 10000, 10000000, 0 };
|
|
auto str = [&scale_list](int64_t *scale) {
|
|
static std::string name_list[] = { " 100ns", "mc", "ms", "s" };
|
|
return name_list[scale - scale_list];
|
|
};
|
|
|
|
|
|
for (int64_t *scale = scale_list; *scale; scale++) {
|
|
int step = 1; /* + (random() % 169) ; */
|
|
auto prev = first;
|
|
for (int64_t i = 1; i < 3697; i += step) {
|
|
auto next = UUID(UUID_gen::create_time(UUID_gen::decimicroseconds{uuid.timestamp() + (i * *scale)}), 0).serialize();
|
|
bool t1 = utils::timeuuid_tri_compare(next, prev) == timeuuid_legacy_tri_compare(next, prev);
|
|
bool t2 = utils::timeuuid_tri_compare(next, first) == timeuuid_legacy_tri_compare(next, first);
|
|
if (!t1 || !t2) {
|
|
BOOST_CHECK_MESSAGE(t1 && t2, format("a UUID {}{} later violates compare order", i, str(scale)));
|
|
}
|
|
prev = next;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
BOOST_AUTO_TEST_CASE(test_timeuuid_submicro_is_monotonic) {
|
|
const int64_t PAD6 = 0xFFFF'FFFF'FFFF;
|
|
using namespace std::chrono;
|
|
using utils::UUID, utils::UUID_gen;
|
|
UUID current_timeuuid = UUID_gen::get_time_UUID();
|
|
// Node identifier must be set to avoid collisions
|
|
BOOST_CHECK((current_timeuuid.get_least_significant_bits() & PAD6) != 0);
|
|
int64_t micros = UUID_gen::micros_timestamp(current_timeuuid);
|
|
int maxsubmicro = (1 << 17) - 1;
|
|
int step = 1 + (random() % 169);
|
|
auto prev = UUID_gen::get_time_UUID_bytes_from_micros_and_submicros(std::chrono::microseconds{micros}, 0);
|
|
auto prev_timeuuid = UUID_gen::get_UUID(prev.data());
|
|
// Check prev_timeuuid node identifier is set. It uses
|
|
// a spoof MAC address, not the same as a standard UUID.
|
|
BOOST_CHECK((prev_timeuuid.get_least_significant_bits() & PAD6) != 0);
|
|
auto check_is_valid_timeuuid = [&](UUID uuid) {
|
|
// UUID is version 1
|
|
BOOST_CHECK(uuid.is_timestamp());
|
|
// UUID preserves the original microsecond time
|
|
BOOST_CHECK(UUID_gen::micros_timestamp(uuid) == micros);
|
|
// UUID 100nsec time grows monotonically
|
|
BOOST_CHECK(uuid.timestamp() >= prev_timeuuid.timestamp());
|
|
// UUID node is the same for all generated UUIDs
|
|
BOOST_CHECK((prev_timeuuid.get_least_significant_bits() & PAD6) ==
|
|
(uuid.get_least_significant_bits() & PAD6));
|
|
};
|
|
for (int i = 1; i <= maxsubmicro; i += step) {
|
|
auto uuid = UUID_gen::get_time_UUID_bytes_from_micros_and_submicros(
|
|
std::chrono::microseconds{micros}, i);
|
|
check_is_valid_timeuuid(UUID_gen::get_UUID(uuid.data()));
|
|
// UUID submicro part grows monotonically
|
|
BOOST_CHECK(utils::timeuuid_tri_compare({uuid.data(), 16}, {prev.data(), 16}) > 0);
|
|
prev = uuid;
|
|
}
|
|
BOOST_CHECK_EXCEPTION(UUID_gen::get_time_UUID_bytes_from_micros_and_submicros(std::chrono::microseconds{micros}, 2 * maxsubmicro),
|
|
utils::timeuuid_submicro_out_of_range, [](auto& x) -> bool { return true; });
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(test_min_time_uuid) {
|
|
using namespace std::chrono;
|
|
|
|
auto tp = system_clock::now();
|
|
auto millis = duration_cast<milliseconds>(tp.time_since_epoch());
|
|
auto uuid = utils::UUID_gen::min_time_UUID(millis);
|
|
BOOST_CHECK(uuid.is_timestamp());
|
|
|
|
auto unix_timestamp = utils::UUID_gen::unix_timestamp(uuid);
|
|
BOOST_CHECK(unix_timestamp == millis);
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(test_max_time_uuid) {
|
|
using namespace std::chrono;
|
|
|
|
auto tp = system_clock::now();
|
|
auto millis = duration_cast<milliseconds>(tp.time_since_epoch());
|
|
auto uuid = utils::UUID_gen::max_time_UUID(millis);
|
|
BOOST_CHECK(uuid.is_timestamp());
|
|
|
|
auto unix_timestamp = utils::UUID_gen::unix_timestamp(uuid);
|
|
BOOST_CHECK(unix_timestamp == millis);
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(test_negate) {
|
|
using namespace utils;
|
|
|
|
auto original_uuid = UUID_gen::get_time_UUID();
|
|
BOOST_TEST_MESSAGE(fmt::format("original_uuid: {}", original_uuid));
|
|
|
|
auto negated_uuid = UUID_gen::negate(original_uuid);
|
|
BOOST_TEST_MESSAGE(fmt::format("negated_uuid: {}", negated_uuid));
|
|
|
|
BOOST_REQUIRE(original_uuid != negated_uuid);
|
|
|
|
auto re_negated_uuid = UUID_gen::negate(negated_uuid);
|
|
BOOST_TEST_MESSAGE(fmt::format("re_negated_uuid: {}", re_negated_uuid));
|
|
|
|
BOOST_REQUIRE(original_uuid == re_negated_uuid);
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(test_null_uuid) {
|
|
// Verify that the default-constructed UUID is null
|
|
utils::UUID uuid;
|
|
BOOST_CHECK(uuid.is_null());
|
|
BOOST_CHECK(!uuid);
|
|
|
|
// Verify that the null_uuid is indeed null
|
|
uuid = utils::null_uuid();
|
|
BOOST_CHECK(uuid.is_null());
|
|
BOOST_CHECK(!uuid);
|
|
|
|
// Verify that the default constructed
|
|
// UUID and the null_uuid are equal.
|
|
BOOST_REQUIRE_EQUAL(UUID(), utils::null_uuid());
|
|
|
|
// Verify that a random uuid is not null
|
|
uuid = utils::make_random_uuid();
|
|
BOOST_CHECK(!uuid.is_null());
|
|
BOOST_CHECK(uuid);
|
|
|
|
// Verify that a time uuid is not null
|
|
uuid = utils::UUID_gen::get_time_UUID();
|
|
BOOST_CHECK(!uuid.is_null());
|
|
BOOST_CHECK(uuid);
|
|
}
|