gtid: Update unit test for `GTID_Set` and `TrxId_Interval`

Signed-off-by: Wazir Ahmed <wazir@proxysql.com>
feature/gtid-range-update
Wazir Ahmed 2 months ago
parent 57edeb5ce0
commit 06b6343dca

@ -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;

@ -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" ]
}

@ -1,171 +0,0 @@
#include <stdlib.h>
#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<int(void)> testFunctionCounts[] = {
testGtidIntervalFromString_Count,
testGtidIntervalContains_Count,
testGtidIntervalAppend_Count,
testGtidIntervalMerge_Count,
testGtidSetAdd_Count,
testGtidSetContains_Count,
};
std::function<void(void)> 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();
}

@ -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

@ -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();
}

@ -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();
}

@ -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();
}
Loading…
Cancel
Save