From 06b6343dca1cfb8c7d296758bbdb097581fde8d5 Mon Sep 17 00:00:00 2001 From: Wazir Ahmed Date: Wed, 1 Apr 2026 01:59:52 +0530 Subject: [PATCH] gtid: Update unit test for `GTID_Set` and `TrxId_Interval` Signed-off-by: Wazir Ahmed --- lib/proxysql_gtid.cpp | 2 +- test/tap/groups/groups.json | 6 +- test/tap/tests/unit-gtid_interval-t.cpp | 171 -------- test/tap/tests/unit/Makefile | 3 +- test/tap/tests/unit/gtid_set_unit-t.cpp | 312 +++++++++++++++ .../tests/unit/gtid_trxid_interval_unit-t.cpp | 202 ++++++++++ test/tap/tests/unit/gtid_utils_unit-t.cpp | 372 ------------------ 7 files changed, 520 insertions(+), 548 deletions(-) delete mode 100644 test/tap/tests/unit-gtid_interval-t.cpp create mode 100644 test/tap/tests/unit/gtid_set_unit-t.cpp create mode 100644 test/tap/tests/unit/gtid_trxid_interval_unit-t.cpp delete mode 100644 test/tap/tests/unit/gtid_utils_unit-t.cpp diff --git a/lib/proxysql_gtid.cpp b/lib/proxysql_gtid.cpp index e4d5308b1..486d37cef 100644 --- a/lib/proxysql_gtid.cpp +++ b/lib/proxysql_gtid.cpp @@ -59,7 +59,7 @@ const std::string TrxId_Interval::to_string(void) { // Attempts to append a new interval to this interval's end. Returns true if the append succeded, false otherwise. const bool TrxId_Interval::append(const TrxId_Interval& other) { - if (other.end >= end && other.start <= (end+1)) { + if (other.start >= start && other.end >= end && other.start <= (end+1)) { // other overlaps interval at end end = other.end; return true; diff --git a/test/tap/groups/groups.json b/test/tap/groups/groups.json index bb58a1c7f..eefc3fe3f 100644 --- a/test/tap/groups/groups.json +++ b/test/tap/groups/groups.json @@ -52,7 +52,6 @@ "genai_stats_parsing_unit-t" : [ "ai-g1" ], "genai_thread_unit-t" : [ "ai-g1" ], "glovars_unit-t" : [ "unit-tests-g1" ], - "gtid_utils_unit-t" : [ "unit-tests-g1" ], "hostgroup_routing_unit-t" : [ "unit-tests-g1" ], "hostgroups_unit-t" : [ "unit-tests-g1" ], "issue5384-t" : [ "legacy-g4","mysql84-g4","mysql-auto_increment_delay_multiplex=0-g4","mysql-multiplexing=false-g4","mysql-query_digests=0-g4","mysql-query_digests_keep_comment=1-g4" ], @@ -369,7 +368,8 @@ "test_wexecvp_syscall_failures-t" : [ "legacy-g4","mysql84-g4","mysql-auto_increment_delay_multiplex=0-g4","mysql-multiplexing=false-g4","mysql-query_digests=0-g4","mysql-query_digests_keep_comment=1-g4" ], "transaction_state_unit-t" : [ "unit-tests-g1" ], "unit-strip_schema_from_query-t" : [ "unit-tests-g1" ], - "unit-gtid_interval-t": [ "unit-tests-g1" ], "vector_db_performance-t" : [ "ai-g1" ], - "vector_features-t" : [ "ai-g1" ] + "vector_features-t" : [ "ai-g1" ], + "gtid_trxid_interval_unit-t" : [ "unit-tests-g1" ], + "gtid_set_unit-t" : [ "unit-tests-g1" ] } diff --git a/test/tap/tests/unit-gtid_interval-t.cpp b/test/tap/tests/unit-gtid_interval-t.cpp deleted file mode 100644 index a345b09d1..000000000 --- a/test/tap/tests/unit-gtid_interval-t.cpp +++ /dev/null @@ -1,171 +0,0 @@ -#include - -#include "tap.h" -#include "unit_test.h" -#include "proxysql_gtid.h" - -using std::string; - -int testGtidIntervalFromString_Count() { - return 4; -} -void testGtidIntervalFromString() { - ok(gtid_interval_t("123-456") == gtid_interval_t(123, 456), "GTID interval from range C string"); - ok(gtid_interval_t(std::string("789-1234")) == gtid_interval_t(789, 1234), "GTID interval from range C++ string"); - ok(gtid_interval_t("111") == gtid_interval_t(111, 111), "GTID interval from single GTID C string"); - ok(gtid_interval_t(std::string("222")) == gtid_interval_t(222, 222), "GTID interval from single GTID C++ string"); -} - -int testGtidIntervalContains_Count() { - return 8; -} -void testGtidIntervalContains() { - auto iv = gtid_interval_t(123, 456); - - ok(iv.contains(123), "GTID interval contains start"); - ok(iv.contains(456), "GTID interval contains end"); - ok(iv.contains(300), "GTID interval contains middle"); - ok(!iv.contains(100), "GTID interval doesn't contain before start"); - ok(!iv.contains(500), "GTID interval doesn't contain past end"); - ok(!iv.contains(gtid_interval_t(100, 300)), "GTID interval doesn't contain range before start"); - ok(!iv.contains(gtid_interval_t(300, 500)), "GTID interval doesn't contain range past end"); - ok(iv.contains(gtid_interval_t(150, 310)), "GTID interval contains range"); -} - -int testGtidIntervalAppend_Count() { - return 7; -} -void testGtidIntervalAppend() { - auto iv = gtid_interval_t(123, 456); - - ok(!iv.append(gtid_interval_t(90, 100)), "cannot append before range start"); - ok(!iv.append(gtid_interval_t(100, 200)), "cannot append at start"); - ok(!iv.append(gtid_interval_t(500, 600)), "cannot append past end"); - ok(iv.append(gtid_interval_t(457, 490)), "append"); - ok(iv.to_string() == "123-490", "append result"); - - iv = gtid_interval_t(123, 456); - ok(iv.append(gtid_interval_t(200, 600)), "append with overlap"); - ok(iv.to_string() == "123-600", "append with overlap result"); -} - -int testGtidIntervalMerge_Count() { - return 14; -} -void testGtidIntervalMerge() { - auto iv = gtid_interval_t(123, 456); - ok(!iv.merge(gtid_interval_t(90, 100)), "cannot merge before range start"); - ok(!iv.merge(gtid_interval_t(500, 600)), "cannot merge past range end"); - ok(iv.merge(gtid_interval_t(90, 200)), "merge at start"); - auto want = gtid_interval_t(90, 456); - ok(iv == want, "merge at start result"); - - iv = gtid_interval_t(123, 456); - ok(iv.merge(gtid_interval_t(300, 500)), "merge at end"); - want = gtid_interval_t(123, 500); - ok(iv == want, "merge at end result"); - - iv = gtid_interval_t(123, 456); - ok(iv.merge(gtid_interval_t(200, 300)), "merge at middle"); - want = gtid_interval_t(123, 456); - ok(iv == want, "merge at middle result"); - - iv = gtid_interval_t(123, 456); - ok(iv.merge(gtid_interval_t(100, 500)), "merge overlap"); - want = gtid_interval_t(100, 500); - ok(iv == want, "merge overlap result"); - - iv = gtid_interval_t(123, 456); - ok(iv.merge(gtid_interval_t(100, 122)), "merge append at start"); - want = gtid_interval_t(100, 456); - ok(iv == want, "merge append at start result"); - - iv = gtid_interval_t(123, 456); - want = gtid_interval_t(123, 600); - ok(iv.merge(gtid_interval_t(457, 600)), "merge append at end"); - ok(iv == want, "merge append at end result"); -} - -int testGtidSetAdd_Count() { - return 9; -} -void testGtidSetAdd() { - gtid_set_t gtid_set; - - ok(gtid_set.add("aaaaaaaa000011112222aaaaaaaaaaaa", gtid_interval_t(10, 20)), "new GTID range for server A"); - ok(!gtid_set.add("aaaaaaaa000011112222aaaaaaaaaaaa", gtid_t(14)), "single GTID range for server A, contained"); - ok(gtid_set.add("aaaaaaaa000011112222aaaaaaaaaaaa", "9-22"), "new GTID range with partial overlap for server A"); - ok(gtid_set.add("aaaaaaaa000011112222aaaaaaaaaaaa", std::string("18-30")), "new GTID range with partial overlap for server A"); - ok(!gtid_set.add("aaaaaaaa000011112222aaaaaaaaaaaa", gtid_interval_t(15, 22)), "GTID range is already fully contained for server A"); - ok(gtid_set.add("aaaaaaaa000011112222aaaaaaaaaaaa", gtid_t(40), gtid_t(50)), "new GTID range with gap for server A"); - ok(gtid_set.add("bbbbbbbb333344445555bbbbbbbbbbbb", gtid_interval_t(10, 30)), "new GTID range for server B"); - ok(gtid_set.add("bbbbbbbb333344445555bbbbbbbbbbbb", "31-50"), "new GTID range for server B"); - - ok(gtid_set.to_string() == "aaaaaaaa-0000-1111-2222-aaaaaaaaaaaa:10-30,aaaaaaaa-0000-1111-2222-aaaaaaaaaaaa:40-50,bbbbbbbb-3333-4444-5555-bbbbbbbbbbbb:10-50", "add interval result"); -} - -int testGtidSetContains_Count() { - return 19; -} -void testGtidSetContains() { - gtid_set_t gtid_set; - - ok(gtid_set.add("aaaaaaaa000011112222aaaaaaaaaaaa", "10-20"), "new GTID range for server A"); - ok(gtid_set.add("aaaaaaaa000011112222aaaaaaaaaaaa", "30-40"), "split GTID range for server A"); - ok(gtid_set.add("bbbbbbbb333344445555bbbbbbbbbbbb", gtid_interval_t(100, 200)), "new GTID range for server B"); - - ok(!gtid_set.has_gtid("aaaaaaaa000011112222aaaaaaaaaaaa", 7), "before GTID range for server A"); - ok(gtid_set.has_gtid("aaaaaaaa000011112222aaaaaaaaaaaa", 10), "in GTID range for server A"); - ok(gtid_set.has_gtid("aaaaaaaa000011112222aaaaaaaaaaaa", 16), "in GTID range for server A"); - ok(gtid_set.has_gtid("aaaaaaaa000011112222aaaaaaaaaaaa", 20), "in GTID range for server A"); - ok(!gtid_set.has_gtid("aaaaaaaa000011112222aaaaaaaaaaaa", 55), "past GTID range for server A"); - - ok(!gtid_set.has_gtid("bbbbbbbb333344445555bbbbbbbbbbbb", 74), "before GTID range for server B"); - ok(gtid_set.has_gtid("bbbbbbbb333344445555bbbbbbbbbbbb", 100), "in GTID range for server B"); - ok(gtid_set.has_gtid("bbbbbbbb333344445555bbbbbbbbbbbb", 168), "in GTID range for server B"); - ok(gtid_set.has_gtid("bbbbbbbb333344445555bbbbbbbbbbbb", 200), "in GTID range for server B"); - ok(!gtid_set.has_gtid("bbbbbbbb333344445555bbbbbbbbbbbb", 201), "past GTID range for server B"); - - gtid_set.clear(); - - ok(!gtid_set.has_gtid("aaaaaaaa000011112222aaaaaaaaaaaa", 7), "no GTID ranges after clear for server A"); - ok(!gtid_set.has_gtid("aaaaaaaa000011112222aaaaaaaaaaaa", 16), "no GTID ranges after clear for server A"); - ok(!gtid_set.has_gtid("aaaaaaaa000011112222aaaaaaaaaaaa", 55), "no GTID ranges after clear for server A"); - - ok(!gtid_set.has_gtid("bbbbbbbb333344445555bbbbbbbbbbbb", 74), "no GTID ranges after clear for server B"); - ok(!gtid_set.has_gtid("bbbbbbbb333344445555bbbbbbbbbbbb", 168), "no GTID ranges after clear for server B"); - ok(!gtid_set.has_gtid("bbbbbbbb333344445555bbbbbbbbbbbb", 345), "no GTID ranges after clear for server B"); -} - -std::function testFunctionCounts[] = { - testGtidIntervalFromString_Count, - testGtidIntervalContains_Count, - testGtidIntervalAppend_Count, - testGtidIntervalMerge_Count, - testGtidSetAdd_Count, - testGtidSetContains_Count, -}; -std::function testFunctions[] = { - testGtidIntervalFromString, - testGtidIntervalContains, - testGtidIntervalAppend, - testGtidIntervalMerge, - testGtidSetAdd, - testGtidSetContains, -}; - -int main(int argc, char** argv) { - // Set up unit tests... - int n = 0; - for (auto f : testFunctionCounts) { - n += f(); - } - plan(n); - - // ...and run them. - for (auto f : testFunctions) { - f(); - } - - return exit_status(); -} diff --git a/test/tap/tests/unit/Makefile b/test/tap/tests/unit/Makefile index 30f678877..7306a52b5 100644 --- a/test/tap/tests/unit/Makefile +++ b/test/tap/tests/unit/Makefile @@ -285,7 +285,8 @@ UNIT_TESTS := smoke_test-t query_cache_unit-t query_processor_unit-t \ pgsql_tokenizer_unit-t \ ezoption_parser_unit-t \ genai_discovery_schema_unit-t \ - gtid_utils_unit-t \ + gtid_trxid_interval_unit-t \ + gtid_set_unit-t \ genai_mysql_catalog_unit-t \ admin_disk_upgrade_unit-t \ glovars_unit-t diff --git a/test/tap/tests/unit/gtid_set_unit-t.cpp b/test/tap/tests/unit/gtid_set_unit-t.cpp new file mode 100644 index 000000000..47b373dec --- /dev/null +++ b/test/tap/tests/unit/gtid_set_unit-t.cpp @@ -0,0 +1,312 @@ +/** + * @file gtid_set_unit-t.cpp + * @brief Unit tests for GTID_Set. + * + * Tests the pure data-structure operations in lib/proxysql_gtid.cpp: + * - GTID_Set::add() overloads — add intervals, single trxids, string ranges + * - GTID_Set::has_gtid() — membership checks across UUID-keyed intervals + * - GTID_Set::to_string() — serialization to MySQL GTID format + * - GTID_Set::clear() — reset to empty state + * - GTID_Set::copy() — deep copy with independence + * + */ + +#include "tap.h" + +#include "proxysql_gtid.h" + +static const std::string UUID_A = "aaaaaaaa000011112222aaaaaaaaaaaa"; +static const std::string UUID_B = "bbbbbbbb333344445555bbbbbbbbbbbb"; + +/** + * @brief add() with TrxId_Interval: new range, append fast-path, partial overlap, gap. + */ +static void test_add_interval() { + GTID_Set gs; + + ok(gs.add(UUID_A, TrxId_Interval(10, 20)), "add interval: new range for UUID A"); + ok(gs.add(UUID_A, TrxId_Interval(15, 22)), "add interval: contained range handled via append fast-path"); + ok(gs.add(UUID_A, TrxId_Interval(9, 22)), "add interval: partial overlap extends"); + ok(gs.add(UUID_A, TrxId_Interval(40, 50)), "add interval: gap creates new interval"); + ok(gs.map.size() == 1, "add interval: one UUID"); + ok(gs.map[UUID_A].size() == 2, "add interval: two intervals"); + auto it = gs.map[UUID_A].begin(); + ok(it->start == 9 && it->end == 22, "add interval: first interval is [9,22]"); + ++it; + ok(it->start == 40 && it->end == 50, "add interval: second interval is [40,50]"); +} + +/** + * @brief add() with a single trxid_t: new entry and duplicate does not create an extra interval. + */ +static void test_add_trxid() { + GTID_Set gs; + + ok(gs.add(UUID_A, trxid_t(14)), "add single: new trxid"); + gs.add(UUID_A, trxid_t(14)); + ok(gs.map[UUID_A].size() == 1, "add single: duplicate does not create new interval"); +} + +/** + * @brief add() with C string range for single and multiple UUIDs. + */ +static void test_add_cstring_range() { + GTID_Set gs; + + ok(gs.add(UUID_A, "9-22"), "add C string range: new range"); + ok(gs.add(UUID_B, "31-50"), "add C string range: new range for UUID B"); + ok(gs.map.size() == 2, "add C string range: two UUIDs"); + auto it_a = gs.map[UUID_A].begin(); + ok(it_a->start == 9 && it_a->end == 22, "add C string range: UUID A interval is [9,22]"); + auto it_b = gs.map[UUID_B].begin(); + ok(it_b->start == 31 && it_b->end == 50, "add C string range: UUID B interval is [31,50]"); +} + +/** + * @brief add() with std::string range. + */ +static void test_add_string_range() { + GTID_Set gs; + + ok(gs.add(UUID_A, std::string("18-30")), "add string range: new range"); + ok(gs.map.size() == 1, "add string range: one UUID"); + auto it = gs.map[UUID_A].begin(); + ok(it->start == 18 && it->end == 30, "add string range: interval is [18,30]"); +} + +/** + * @brief add() with explicit start, end parameters. + */ +static void test_add_start_end() { + GTID_Set gs; + + ok(gs.add(UUID_A, trxid_t(40), trxid_t(50)), "add start,end: new range"); + ok(gs.map.size() == 1, "add start,end: one UUID"); + auto it = gs.map[UUID_A].begin(); + ok(it->start == 40 && it->end == 50, "add start,end: interval is [40,50]"); +} + +/** + * @brief Multiple UUIDs are tracked independently in the same GTID_Set. + */ +static void test_add_multi_uuid() { + GTID_Set gs; + + gs.add(UUID_A, TrxId_Interval(10, 20)); + gs.add(UUID_B, TrxId_Interval(30, 40)); + + ok(gs.map.count(UUID_A) == 1, "multi-uuid: UUID A exists"); + ok(gs.map.count(UUID_B) == 1, "multi-uuid: UUID B exists"); + ok(gs.map.size() == 2, "multi-uuid: exactly two UUIDs"); +} + +/** + * @brief add() with consecutive trxid values (1,2,3) merges into a single interval [1,3]. + */ +static void test_add_consecutive_trxid() { + GTID_Set gs; + + gs.add(UUID_A, trxid_t(1)); + gs.add(UUID_A, trxid_t(2)); + gs.add(UUID_A, trxid_t(3)); + + ok(gs.map[UUID_A].size() == 1, "consecutive adds: merged into one interval"); + auto& iv = gs.map[UUID_A].front(); + ok(iv.start == 1 && iv.end == 3, "consecutive adds: interval is [1,3]"); +} + +/** + * @brief add() with non-consecutive trxid values (1,5) produces two separate intervals. + */ +static void test_add_non_consecutive_trxid() { + GTID_Set gs; + + gs.add(UUID_A, trxid_t(1)); + gs.add(UUID_A, trxid_t(5)); + + ok(gs.map[UUID_A].size() == 2, "non-consecutive adds: two separate intervals"); +} + +/** + * @brief add() with reverse-order trxid values (5,4,3,2,1) still produces [1,5]. + */ +static void test_add_reverse_order_trxid() { + GTID_Set gs; + + for (trxid_t i = 5; i >= 1; i--) { + gs.add(UUID_A, i); + } + + ok(gs.map[UUID_A].size() == 1, "reverse order: merged into one interval"); + auto& iv = gs.map[UUID_A].front(); + ok(iv.start == 1 && iv.end == 5, "reverse order: interval is [1,5]"); +} + +/** + * @brief add() with a duplicate trxid does not create a new interval. + */ +static void test_add_duplicate_trxid() { + GTID_Set gs; + + gs.add(UUID_A, trxid_t(3)); + gs.add(UUID_A, trxid_t(3)); + + ok(gs.map[UUID_A].size() == 1, "duplicate single: still one interval"); + auto& iv = gs.map[UUID_A].front(); + ok(iv.start == 3 && iv.end == 3, "duplicate single: interval unchanged [3,3]"); +} + +/** + * @brief add() merges two intervals when a trxid fills the gap between them. + */ +static void test_add_merge_trxid() { + GTID_Set gs; + + gs.add(UUID_A, trxid_t(1)); + gs.add(UUID_A, trxid_t(3)); + ok(gs.map[UUID_A].size() == 2, "bridge merge: two intervals before bridge"); + + gs.add(UUID_A, trxid_t(2)); + ok(gs.map[UUID_A].size() == 1, "bridge merge: merged into one interval"); + auto& iv = gs.map[UUID_A].front(); + ok(iv.start == 1 && iv.end == 3, "bridge merge: interval is [1,3]"); +} + +/** + * @brief add() merges two intervals when an interval fills the gap between them. + */ +static void test_add_merge_interval() { + GTID_Set gs; + + gs.add(UUID_A, TrxId_Interval(1, 10)); + gs.add(UUID_A, TrxId_Interval(20, 30)); + ok(gs.map[UUID_A].size() == 2, "gap fill: two intervals before fill"); + + gs.add(UUID_A, TrxId_Interval(11, 19)); + ok(gs.map[UUID_A].size() == 1, "gap fill: merged into one interval"); + auto& iv = gs.map[UUID_A].front(); + ok(iv.start == 1 && iv.end == 30, "gap fill: interval is [1,30]"); +} + +/** + * @brief has_gtid() at boundaries, in gap, past ranges, and for unknown UUIDs. + */ +static void test_has_gtid() { + GTID_Set gs; + gs.add(UUID_A, "10-20"); + gs.add(UUID_A, "30-40"); + gs.add(UUID_B, TrxId_Interval(100, 200)); + + ok(!gs.has_gtid("cccccccc666677778888cccccccccccc", 10), "has_gtid: unknown UUID returns false"); + + ok(!gs.has_gtid(UUID_A, 7), "has_gtid: before range"); + ok(gs.has_gtid(UUID_A, 10), "has_gtid: at start"); + ok(gs.has_gtid(UUID_A, 16), "has_gtid: in range"); + ok(gs.has_gtid(UUID_A, 20), "has_gtid: at end"); + ok(!gs.has_gtid(UUID_A, 25), "has_gtid: in gap"); + ok(!gs.has_gtid(UUID_A, 55), "has_gtid: past all ranges"); + + ok(!gs.has_gtid(UUID_B, 74), "has_gtid: before range (UUID B)"); + ok(gs.has_gtid(UUID_B, 100), "has_gtid: at start (UUID B)"); + ok(gs.has_gtid(UUID_B, 168), "has_gtid: in range (UUID B)"); + ok(gs.has_gtid(UUID_B, 200), "has_gtid: at end (UUID B)"); + ok(!gs.has_gtid(UUID_B, 201), "has_gtid: past range (UUID B)"); +} + +/** + * @brief clear() removes all entries; has_gtid() returns false afterwards. + */ +static void test_clear() { + GTID_Set gs; + + gs.add(UUID_A, "10-20"); + gs.add(UUID_B, "100-200"); + + gs.clear(); + + ok(!gs.has_gtid(UUID_A, 16), "clear: UUID A gone"); + ok(!gs.has_gtid(UUID_B, 168), "clear: UUID B gone"); + ok(gs.map.empty(), "clear: map is empty"); +} + +/** + * @brief to_string() serialization: empty set, single interval, and multiple intervals/UUIDs. + */ +static void test_to_string() { + // empty set + { + GTID_Set gs; + ok(gs.to_string().empty(), "to_string: empty set returns empty string"); + } + + // single UUID with single interval + { + GTID_Set gs; + gs.add("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", TrxId_Interval(1, 10)); + std::string result = gs.to_string(); + std::string expected = "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa:1-10"; + ok(result == expected, "to_string: single UUID single interval matches"); + } + + // multiple intervals and UUIDs + { + GTID_Set gs; + gs.add(UUID_A, TrxId_Interval(10, 20)); + gs.add(UUID_A, "9-22"); + gs.add(UUID_A, std::string("18-30")); + gs.add(UUID_A, TrxId_Interval(40, 50)); + gs.add(UUID_B, TrxId_Interval(10, 30)); + gs.add(UUID_B, "31-50"); + std::string result = gs.to_string(); + ok(result.find("aaaaaaaa-0000-1111-2222-aaaaaaaaaaaa:9-30") != std::string::npos, + "to_string: contains UUID A range 9-30"); + ok(result.find("aaaaaaaa-0000-1111-2222-aaaaaaaaaaaa:40-50") != std::string::npos, + "to_string: contains UUID A range 40-50"); + ok(result.find("bbbbbbbb-3333-4444-5555-bbbbbbbbbbbb:10-50") != std::string::npos, + "to_string: contains UUID B range 10-50"); + } +} + +/** + * @brief copy() produces a deep copy with the same data, and modifying the copy does not affect the original. + */ +static void test_copy() { + GTID_Set gs; + + gs.add(UUID_A, TrxId_Interval(10, 20)); + gs.add(UUID_B, TrxId_Interval(30, 40)); + + GTID_Set cp = gs.copy(); + + ok(cp.map.size() == 2, "copy: same number of UUIDs"); + ok(cp.has_gtid(UUID_A, 15), "copy: has UUID A trxid"); + ok(cp.has_gtid(UUID_B, 35), "copy: has UUID B trxid"); + + cp.add(UUID_A, TrxId_Interval(30, 40)); + + ok(!gs.has_gtid(UUID_A, 35), "copy: original not affected by copy mutation"); + ok(cp.has_gtid(UUID_A, 35), "copy: copy has new data"); +} + +int main() { + plan(62); + + test_add_interval(); // 8 assertions + test_add_trxid(); // 2 assertions + test_add_cstring_range(); // 5 assertions + test_add_string_range(); // 3 assertions + test_add_start_end(); // 3 assertions + test_add_multi_uuid(); // 3 assertions + test_add_consecutive_trxid(); // 2 assertions + test_add_non_consecutive_trxid(); // 1 assertion + test_add_reverse_order_trxid(); // 2 assertions + test_add_duplicate_trxid(); // 2 assertions + test_add_merge_trxid(); // 3 assertions + test_add_merge_interval(); // 3 assertions + test_has_gtid(); // 12 assertions + test_clear(); // 3 assertions + test_to_string(); // 5 assertions + test_copy(); // 5 assertions + + return exit_status(); +} diff --git a/test/tap/tests/unit/gtid_trxid_interval_unit-t.cpp b/test/tap/tests/unit/gtid_trxid_interval_unit-t.cpp new file mode 100644 index 000000000..e2ed97f36 --- /dev/null +++ b/test/tap/tests/unit/gtid_trxid_interval_unit-t.cpp @@ -0,0 +1,202 @@ +/** + * @file trxid_interval_unit-t.cpp + * @brief Unit tests for TrxId_Interval. + * + * Tests the pure data-structure operations in lib/proxysql_gtid.cpp: + * - TrxId_Interval constructors — from range, single, C string, C++ string + * - contains() — membership checks for trxid and interval + * - to_string() — serialization to "start-end" or "n" format + * - append() — extend interval at end + * - merge() — merge two overlapping/adjacent intervals + * - cmp() and comparison operators + * + */ + +#include "tap.h" + +#include "proxysql_gtid.h" + +/** + * @brief Construct from (start, end) in normal and reversed order. + * + * Verifies that reversed arguments are swapped so start <= end. + */ +static void test_construct_from_range() { + TrxId_Interval iv(123, 456); + ok(iv.start == 123 && iv.end == 456, "construct from (start, end): normal order"); + + TrxId_Interval iv_rev(456, 123); + ok(iv_rev.start == 123 && iv_rev.end == 456, "construct from (start, end): reversed order swaps"); +} + +/** + * @brief Construct from a single trxid_t value. + * + * Produces a degenerate interval [n,n]. + */ +static void test_construct_from_single() { + TrxId_Interval iv(42); + ok(iv.start == 42 && iv.end == 42, "construct from single trxid: [42,42]"); +} + +/** + * @brief Construct from string literal in "start-end" and "n" formats. + */ +static void test_construct_from_string_literal() { + TrxId_Interval iv_range("123-456"); + ok(iv_range.start == 123 && iv_range.end == 456, "construct from string literal range: 123-456"); + + TrxId_Interval iv_single("111"); + ok(iv_single.start == 111 && iv_single.end == 111, "construct from string literal single: 111"); +} + +/** + * @brief Construct from std::string in "start-end" and "n" formats. + */ +static void test_construct_from_string() { + TrxId_Interval iv_range(std::string("789-1234")); + ok(iv_range.start == 789 && iv_range.end == 1234, "construct from C++ string range: 789-1234"); + + TrxId_Interval iv_single(std::string("222")); + ok(iv_single.start == 222 && iv_single.end == 222, "construct from C++ string single: 222"); +} + +/** + * @brief Construct from negative values. + */ +static void test_construct_negative() { + TrxId_Interval iv(-10, -5); + ok(iv.start == -10 && iv.end == -5, "construct from negative range: [-10,-5]"); +} + +/** + * @brief contains(trxid) at boundaries, middle, and outside the interval. + */ +static void test_contains_trxid() { + TrxId_Interval iv(123, 456); + + ok(iv.contains(123), "contains trxid: start boundary"); + ok(iv.contains(456), "contains trxid: end boundary"); + ok(iv.contains(300), "contains trxid: middle"); + ok(!iv.contains(100), "contains trxid: before start"); + ok(!iv.contains(500), "contains trxid: past end"); +} + +/** + * @brief contains(TrxId_Interval) for partial, full, and exact containment. + */ +static void test_contains_interval() { + TrxId_Interval iv(123, 456); + + ok(!iv.contains(TrxId_Interval(100, 300)), "contains interval: range before start"); + ok(!iv.contains(TrxId_Interval(300, 500)), "contains interval: range past end"); + ok(iv.contains(TrxId_Interval(150, 310)), "contains interval: fully contained"); + ok(iv.contains(TrxId_Interval(123, 456)), "contains interval: exact match"); +} + +/** + * @brief to_string() produces "start-end" for ranges and "n" for single values. + */ +static void test_to_string() { + ok(TrxId_Interval(123, 456).to_string() == "123-456", "to_string: range format"); + ok(TrxId_Interval(111).to_string() == "111", "to_string: single format"); + ok(TrxId_Interval(0, 0).to_string() == "0", "to_string: zero interval"); +} + +/** + * @brief append() extends the interval at end, rejects before start and past end. + */ +static void test_append() { + TrxId_Interval iv(123, 456); + + ok(!iv.append(TrxId_Interval(90, 100)), "append: before start, rejected"); + ok(!iv.append(TrxId_Interval(100, 200)), "append: overlapping at start, rejected"); + ok(!iv.append(TrxId_Interval(500, 600)), "append: past end with gap, rejected"); + + ok(iv.append(TrxId_Interval(457, 490)), "append: adjacent at end"); + ok(iv.to_string() == "123-490", "append: adjacent result"); + + TrxId_Interval iv2(123, 456); + ok(iv2.append(TrxId_Interval(200, 600)), "append: overlapping at end"); + ok(iv2.to_string() == "123-600", "append: overlapping result"); +} + +/** + * @brief merge() handles all overlap/adjacency cases and rejects non-overlapping intervals. + */ +static void test_merge() { + { + TrxId_Interval iv(123, 456); + ok(!iv.merge(TrxId_Interval(90, 100)), "merge: no overlap before, rejected"); + } + { + TrxId_Interval iv(123, 456); + ok(!iv.merge(TrxId_Interval(500, 600)), "merge: no overlap after, rejected"); + } + { + TrxId_Interval iv(123, 456); + ok(iv.merge(TrxId_Interval(200, 300)), "merge: contained in middle"); + ok(iv.start == 123 && iv.end == 456, "merge: contained result unchanged"); + } + { + TrxId_Interval iv(123, 456); + ok(iv.merge(TrxId_Interval(90, 200)), "merge: overlap at start"); + ok(iv.start == 90 && iv.end == 456, "merge: overlap at start result"); + } + { + TrxId_Interval iv(123, 456); + ok(iv.merge(TrxId_Interval(300, 500)), "merge: overlap at end"); + ok(iv.start == 123 && iv.end == 500, "merge: overlap at end result"); + } + { + TrxId_Interval iv(123, 456); + ok(iv.merge(TrxId_Interval(100, 500)), "merge: full overlap"); + ok(iv.start == 100 && iv.end == 500, "merge: full overlap result"); + } + { + TrxId_Interval iv(123, 456); + ok(iv.merge(TrxId_Interval(100, 122)), "merge: append at start (adjacent)"); + ok(iv.start == 100 && iv.end == 456, "merge: append at start result"); + } + { + TrxId_Interval iv(123, 456); + ok(iv.merge(TrxId_Interval(457, 600)), "merge: append at end (adjacent)"); + ok(iv.start == 123 && iv.end == 600, "merge: append at end result"); + } +} + +/** + * @brief cmp() and operator<, operator==, operator!= for strict weak ordering. + */ +static void test_cmp_operators() { + TrxId_Interval a(10, 20); + TrxId_Interval b(10, 30); + TrxId_Interval c(20, 30); + TrxId_Interval d(10, 20); + + ok(a < b, "operator<: same start, smaller end"); + ok(a < c, "operator<: smaller start"); + ok(!(b < a), "operator<: not reversed"); + ok(a == d, "operator==: identical"); + ok(!(a == b), "operator==: different"); + ok(a != b, "operator!=: different"); + ok(!(a != d), "operator!=: identical"); +} + +int main() { + plan(48); + + test_construct_from_range(); // 2 assertions + test_construct_from_single(); // 1 assertion + test_construct_from_string_literal(); // 2 assertions + test_construct_from_string(); // 2 assertions + test_construct_negative(); // 1 assertion + test_contains_trxid(); // 5 assertions + test_contains_interval(); // 4 assertions + test_to_string(); // 3 assertions + test_append(); // 7 assertions + test_merge(); // 14 assertions + test_cmp_operators(); // 7 assertions + + return exit_status(); +} diff --git a/test/tap/tests/unit/gtid_utils_unit-t.cpp b/test/tap/tests/unit/gtid_utils_unit-t.cpp deleted file mode 100644 index 59a90b74f..000000000 --- a/test/tap/tests/unit/gtid_utils_unit-t.cpp +++ /dev/null @@ -1,372 +0,0 @@ -/** - * @file gtid_utils_unit-t.cpp - * @brief Unit tests for GTID helper functions. - * - * Tests the pure data-structure operations in lib/GTID_Server_Data.cpp: - * - addGtid() — add a single GTID, merge intervals - * - addGtidInterval() — add a [from,to] interval - * - gtid_executed_to_string() — serialize a gtid_set_t to MySQL format - * - * These are free functions that operate on gtid_set_t (an unordered_map - * of UUID -> list of intervals) and require no I/O or event-loop setup. - */ - -#include "tap.h" -#include "test_globals.h" -#include "test_init.h" - -#include "proxysql.h" -#include "proxysql_gtid.h" - -// Free function declarations (defined in lib/GTID_Server_Data.cpp, -// declared in include/Base_HostGroups_Manager.h) -extern void addGtid(const gtid_t& gtid, gtid_set_t& gtid_executed); -extern bool addGtidInterval(gtid_set_t& gtid_executed, std::string server_uuid, - int64_t txid_start, int64_t txid_end); -extern std::string gtid_executed_to_string(gtid_set_t& gtid_executed); - - -// ========================================================================= -// addGtid tests -// ========================================================================= - -/** - * @brief Adding a single GTID to an empty set creates one interval [n,n]. - */ -static void test_addGtid_single_to_empty() { - gtid_set_t gs; - gtid_t g = std::make_pair(std::string("uuid1"), (int64_t)5); - addGtid(g, gs); - - ok(gs.count("uuid1") == 1, - "addGtid: uuid1 key exists after first insert"); - ok(gs["uuid1"].size() == 1, - "addGtid: exactly one interval after first insert"); - auto &iv = gs["uuid1"].front(); - ok(iv.first == 5 && iv.second == 5, - "addGtid: interval is [5,5]"); -} - -/** - * @brief Consecutive GTIDs merge into a single interval. - */ -static void test_addGtid_consecutive_merge() { - gtid_set_t gs; - std::string uuid = "uuid1"; - // Insert 1, 2, 3 in order - for (int64_t i = 1; i <= 3; i++) { - addGtid(std::make_pair(uuid, i), gs); - } - - ok(gs[uuid].size() == 1, - "addGtid consecutive: merged into one interval"); - auto &iv = gs[uuid].front(); - ok(iv.first == 1 && iv.second == 3, - "addGtid consecutive: interval is [1,3]"); -} - -/** - * @brief Non-consecutive GTIDs produce separate intervals. - */ -static void test_addGtid_non_consecutive() { - gtid_set_t gs; - std::string uuid = "uuid1"; - addGtid(std::make_pair(uuid, (int64_t)1), gs); - addGtid(std::make_pair(uuid, (int64_t)5), gs); - - ok(gs[uuid].size() == 2, - "addGtid non-consecutive: two separate intervals"); -} - -/** - * @brief Duplicate GTID is a no-op. - */ -static void test_addGtid_duplicate() { - gtid_set_t gs; - std::string uuid = "uuid1"; - addGtid(std::make_pair(uuid, (int64_t)3), gs); - addGtid(std::make_pair(uuid, (int64_t)3), gs); - - ok(gs[uuid].size() == 1, - "addGtid duplicate: still one interval"); - auto &iv = gs[uuid].front(); - ok(iv.first == 3 && iv.second == 3, - "addGtid duplicate: interval unchanged [3,3]"); -} - -/** - * @brief Multiple UUIDs are tracked independently. - */ -static void test_addGtid_multiple_uuids() { - gtid_set_t gs; - addGtid(std::make_pair(std::string("aaa"), (int64_t)1), gs); - addGtid(std::make_pair(std::string("bbb"), (int64_t)2), gs); - - ok(gs.size() == 2, - "addGtid multiple UUIDs: two entries in map"); - ok(gs["aaa"].front().first == 1, - "addGtid multiple UUIDs: aaa has trxid 1"); - ok(gs["bbb"].front().first == 2, - "addGtid multiple UUIDs: bbb has trxid 2"); -} - -/** - * @brief Adding a GTID that bridges two intervals merges them. - */ -static void test_addGtid_bridge_merge() { - gtid_set_t gs; - std::string uuid = "uuid1"; - // Create two separate intervals: [1,1] and [3,3] - addGtid(std::make_pair(uuid, (int64_t)1), gs); - addGtid(std::make_pair(uuid, (int64_t)3), gs); - ok(gs[uuid].size() == 2, - "addGtid bridge: two intervals before bridge"); - - // Insert 2 — should merge [1,1] and [3,3] into [1,3] - addGtid(std::make_pair(uuid, (int64_t)2), gs); - ok(gs[uuid].size() == 1, - "addGtid bridge: merged into one interval after bridge"); - auto &iv = gs[uuid].front(); - ok(iv.first == 1 && iv.second == 3, - "addGtid bridge: interval is [1,3]"); -} - -/** - * @brief Adding GTIDs in reverse order still merges correctly. - */ -static void test_addGtid_reverse_order() { - gtid_set_t gs; - std::string uuid = "uuid1"; - for (int64_t i = 5; i >= 1; i--) { - addGtid(std::make_pair(uuid, i), gs); - } - - ok(gs[uuid].size() == 1, - "addGtid reverse: merged into one interval"); - auto &iv = gs[uuid].front(); - ok(iv.first == 1 && iv.second == 5, - "addGtid reverse: interval is [1,5]"); -} - -/** - * @brief GTID inside an existing interval is a no-op. - */ -static void test_addGtid_within_interval() { - gtid_set_t gs; - std::string uuid = "uuid1"; - addGtid(std::make_pair(uuid, (int64_t)1), gs); - addGtid(std::make_pair(uuid, (int64_t)2), gs); - addGtid(std::make_pair(uuid, (int64_t)3), gs); - // Now [1,3] exists. Adding 2 again should be a no-op. - addGtid(std::make_pair(uuid, (int64_t)2), gs); - - ok(gs[uuid].size() == 1, - "addGtid within: still one interval"); - auto &iv = gs[uuid].front(); - ok(iv.first == 1 && iv.second == 3, - "addGtid within: interval unchanged [1,3]"); -} - - -// ========================================================================= -// addGtidInterval tests -// ========================================================================= - -/** - * @brief Adding an interval to an empty set. - */ -static void test_addGtidInterval_empty() { - gtid_set_t gs; - bool updated = addGtidInterval(gs, "uuid1", 1, 10); - - ok(updated == true, - "addGtidInterval empty: returns true (updated)"); - ok(gs["uuid1"].size() == 1, - "addGtidInterval empty: one interval"); - auto &iv = gs["uuid1"].front(); - ok(iv.first == 1 && iv.second == 10, - "addGtidInterval empty: interval is [1,10]"); -} - -/** - * @brief Updating an existing interval with the same start but larger end. - */ -static void test_addGtidInterval_update_same_start() { - gtid_set_t gs; - addGtidInterval(gs, "uuid1", 1, 10); - bool updated = addGtidInterval(gs, "uuid1", 1, 19); - - ok(updated == true, - "addGtidInterval update: returns true when end changed"); - ok(gs["uuid1"].size() == 1, - "addGtidInterval update: still one interval"); - auto &iv = gs["uuid1"].front(); - ok(iv.first == 1 && iv.second == 19, - "addGtidInterval update: interval is [1,19]"); -} - -/** - * @brief Re-adding an identical interval returns false (no update). - */ -static void test_addGtidInterval_duplicate() { - gtid_set_t gs; - addGtidInterval(gs, "uuid1", 1, 10); - bool updated = addGtidInterval(gs, "uuid1", 1, 10); - - ok(updated == false, - "addGtidInterval duplicate: returns false (no change)"); - ok(gs["uuid1"].size() == 1, - "addGtidInterval duplicate: still one interval"); -} - -/** - * @brief Adding a non-overlapping interval creates a second entry. - */ -static void test_addGtidInterval_separate() { - gtid_set_t gs; - addGtidInterval(gs, "uuid1", 1, 10); - bool updated = addGtidInterval(gs, "uuid1", 20, 30); - - ok(updated == true, - "addGtidInterval separate: returns true"); - ok(gs["uuid1"].size() == 2, - "addGtidInterval separate: two intervals"); -} - -/** - * @brief Multiple UUIDs are tracked independently. - */ -static void test_addGtidInterval_multi_uuid() { - gtid_set_t gs; - addGtidInterval(gs, "aaa", 1, 5); - addGtidInterval(gs, "bbb", 10, 20); - - ok(gs.size() == 2, - "addGtidInterval multi-uuid: two UUIDs"); - ok(gs["aaa"].front().second == 5, - "addGtidInterval multi-uuid: aaa interval correct"); - ok(gs["bbb"].front().first == 10, - "addGtidInterval multi-uuid: bbb interval correct"); -} - - -// ========================================================================= -// gtid_executed_to_string tests -// ========================================================================= - -/** - * @brief Empty set produces empty string. - */ -static void test_toString_empty() { - gtid_set_t gs; - std::string result = gtid_executed_to_string(gs); - ok(result.empty(), - "toString empty: returns empty string"); -} - -/** - * @brief Single UUID with a single interval. - * - * gtid_executed_to_string inserts dashes into the UUID at positions - * 8, 13, 18, 23 to produce standard MySQL GTID format: - * xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx:from-to - * - * The stored UUID must be 32 hex chars (no dashes). - */ -static void test_toString_single_uuid_single_interval() { - gtid_set_t gs; - // 32-char hex UUID (no dashes) — the format stored internally - std::string uuid = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; - gs[uuid].emplace_back(1, 10); - - std::string result = gtid_executed_to_string(gs); - std::string expected = "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa:1-10"; - ok(result == expected, - "toString single interval: '%s' == '%s'", - result.c_str(), expected.c_str()); -} - -/** - * @brief Single UUID with multiple intervals. - */ -static void test_toString_single_uuid_multi_interval() { - gtid_set_t gs; - std::string uuid = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"; - gs[uuid].emplace_back(1, 5); - gs[uuid].emplace_back(10, 20); - - std::string result = gtid_executed_to_string(gs); - // Each interval gets its own "uuid:from-to" segment, comma-separated - std::string expected = - "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb:1-5," - "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb:10-20"; - ok(result == expected, - "toString multi-interval: '%s' == '%s'", - result.c_str(), expected.c_str()); -} - -/** - * @brief Multiple UUIDs. - * - * Since gtid_set_t is an unordered_map, iteration order is not - * guaranteed. We check that both UUIDs appear and the total length - * is correct. - */ -static void test_toString_multi_uuid() { - gtid_set_t gs; - std::string uuid_a = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; - std::string uuid_b = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"; - gs[uuid_a].emplace_back(1, 5); - gs[uuid_b].emplace_back(10, 20); - - std::string result = gtid_executed_to_string(gs); - - // Both UUIDs should appear (with dashes) - std::string dashed_a = "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"; - std::string dashed_b = "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb"; - ok(result.find(dashed_a) != std::string::npos, - "toString multi-uuid: contains uuid_a"); - ok(result.find(dashed_b) != std::string::npos, - "toString multi-uuid: contains uuid_b"); - // Should have exactly one comma separating the two entries - size_t comma_count = 0; - for (char c : result) { if (c == ',') comma_count++; } - ok(comma_count == 1, - "toString multi-uuid: exactly one comma separator"); -} - - -// ========================================================================= -// main -// ========================================================================= - -int main() { - plan(37); - test_init_minimal(); - - // addGtid tests - test_addGtid_single_to_empty(); // 3 checks - test_addGtid_consecutive_merge(); // 2 checks - test_addGtid_non_consecutive(); // 1 check - test_addGtid_duplicate(); // 2 checks - test_addGtid_multiple_uuids(); // 3 checks - test_addGtid_bridge_merge(); // 3 checks - test_addGtid_reverse_order(); // 2 checks - test_addGtid_within_interval(); // 2 checks - - // addGtidInterval tests - test_addGtidInterval_empty(); // 3 checks - test_addGtidInterval_update_same_start(); // 3 checks - test_addGtidInterval_duplicate(); // 2 checks - test_addGtidInterval_separate(); // 2 checks - test_addGtidInterval_multi_uuid(); // 3 checks - - // gtid_executed_to_string tests - test_toString_empty(); // 1 check - test_toString_single_uuid_single_interval(); // 1 check - test_toString_single_uuid_multi_interval(); // 1 check - test_toString_multi_uuid(); // 3 checks - - test_cleanup_minimal(); - return exit_status(); -}