Files
scylladb/test/boost/UUID_test.cc
Kefu Chai 3e84d43f93 treewide: use seastar::format() or fmt::format() explicitly
before this change, we rely on `using namespace seastar` to use
`seastar::format()` without qualifying the `format()` with its
namespace. this works fine until we changed the parameter type
of format string `seastar::format()` from `const char*` to
`fmt::format_string<...>`. this change practically invited
`seastar::format()` to the club of `std::format()` and `fmt::format()`,
where all members accept a templated parameter as its `fmt`
parameter. and `seastar::format()` is not the best candidate anymore.
despite that argument-dependent lookup (ADT for short) favors the
function which is in the same namespace as its parameter, but
`using namespace` makes `seastar::format()` more competitive,
so both `std::format()` and `seastar::format()` are considered
as the condidates.

that is what is happening scylladb in quite a few caller sites of
`format()`, hence ADT is not able to tell which function the winner
in the name lookup:

```
/__w/scylladb/scylladb/mutation/mutation_fragment_stream_validator.cc:265:12: error: call to 'format' is ambiguous
  265 |     return format("{} ({}.{} {})", _name_view, s.ks_name(), s.cf_name(), s.id());
      |            ^~~~~~
/usr/bin/../lib/gcc/x86_64-redhat-linux/14/../../../../include/c++/14/format:4290:5: note: candidate function [with _Args = <const std::basic_string_view<char> &, const seastar::basic_sstring<char, unsigned int, 15> &, const seastar::basic_sstring<char, unsigned int, 15> &, const utils::tagged_uuid<table_id_tag> &>]
 4290 |     format(format_string<_Args...> __fmt, _Args&&... __args)
      |     ^
/__w/scylladb/scylladb/seastar/include/seastar/core/print.hh:143:1: note: candidate function [with A = <const std::basic_string_view<char> &, const seastar::basic_sstring<char, unsigned int, 15> &, const seastar::basic_sstring<char, unsigned int, 15> &, const utils::tagged_uuid<table_id_tag> &>]
  143 | format(fmt::format_string<A...> fmt, A&&... a) {
      | ^
```

in this change, we

change all `format()` to either `fmt::format()` or `seastar::format()`
with following rules:
- if the caller expects an `sstring` or `std::string_view`, change to
  `seastar::format()`
- if the caller expects an `std::string`, change to `fmt::format()`.
  because, `sstring::operator std::basic_string` would incur a deep
  copy.

we will need another change to enable scylladb to compile with the
latest seastar. namely, to pass the format string as a templated
parameter down to helper functions which format their parameters.
to miminize the scope of this change, let's include that change when
bumping up the seastar submodule. as that change will depend on
the seastar change.

Signed-off-by: Kefu Chai <kefu.chai@scylladb.com>
2024-09-11 23:21:40 +03:00

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, seastar::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, seastar::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);
}