mirror of https://github.com/sysown/proxysql
Tests read_next_gtid() by buffer-stuffing, covering: - ST= bootstrap with single trxids and ranges - I1/I2 single trxid messages - I3/I4 range-based messages - I1 rejects range input (atoll parses only first number) - Unknown message type sets active=false and returns false - read_all_gtids() stops processing on unknown message - Empty buffer and incomplete message edge cases - Mixed message sequence with full GTID state verificationfeature/gtid-range-update
parent
dca01d4eaf
commit
307d70acb9
@ -0,0 +1,306 @@
|
||||
/**
|
||||
* @file gtid_server_data_unit-t.cpp
|
||||
* @brief Unit tests for GTID_Server_Data wire protocol parsing.
|
||||
*
|
||||
* Tests read_next_gtid() by stuffing messages directly into the internal
|
||||
* buffer, bypassing the network layer. Covers:
|
||||
* - ST= bootstrap messages (single trxid and ranges)
|
||||
* - I1/I2 single trxid incremental messages
|
||||
* - I3/I4 range-based incremental messages
|
||||
* - Unknown message type triggers disconnect (active = false)
|
||||
* - events_read counter accuracy
|
||||
*/
|
||||
|
||||
#include "tap.h"
|
||||
|
||||
#include "GTID_Server_Data.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
|
||||
static const char *UUID_A = "aaaaaaaa-0000-1111-2222-aaaaaaaaaaaa";
|
||||
static const char *UUID_A_STRIPPED = "aaaaaaaa000011112222aaaaaaaaaaaa";
|
||||
static const char *UUID_B = "bbbbbbbb-3333-4444-5555-bbbbbbbbbbbb";
|
||||
static const char *UUID_B_STRIPPED = "bbbbbbbb333344445555bbbbbbbbbbbb";
|
||||
|
||||
/**
|
||||
* @brief Helper: stuff a message string into sd's buffer and reset pos.
|
||||
*
|
||||
* Replaces the entire buffer content. The caller can stuff multiple
|
||||
* newline-delimited messages in a single call.
|
||||
*/
|
||||
static void stuff_buffer(GTID_Server_Data &sd, const std::string &msg) {
|
||||
size_t needed = msg.size();
|
||||
if (needed > sd.size) {
|
||||
sd.resize(needed);
|
||||
}
|
||||
memcpy(sd.data, msg.c_str(), needed);
|
||||
sd.len = needed;
|
||||
sd.pos = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief ST= bootstrap with single trxids.
|
||||
*/
|
||||
static void test_bootstrap_single() {
|
||||
GTID_Server_Data sd(nullptr, (char *)"127.0.0.1", 0, 3306);
|
||||
|
||||
std::string msg = std::string("ST=") + UUID_A + ":100," + UUID_B + ":200\n";
|
||||
stuff_buffer(sd, msg);
|
||||
|
||||
ok(sd.read_next_gtid() == true, "ST= bootstrap: returns true");
|
||||
ok(sd.active == true, "ST= bootstrap: active remains true");
|
||||
ok(sd.events_read == 1, "ST= bootstrap: events_read incremented");
|
||||
ok(sd.gtid_exists((char *)UUID_A_STRIPPED, 100) == true, "ST= bootstrap: UUID_A trxid 100 exists");
|
||||
ok(sd.gtid_exists((char *)UUID_B_STRIPPED, 200) == true, "ST= bootstrap: UUID_B trxid 200 exists");
|
||||
ok(sd.gtid_exists((char *)UUID_A_STRIPPED, 101) == false, "ST= bootstrap: UUID_A trxid 101 does not exist");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief ST= bootstrap with trxid ranges.
|
||||
*/
|
||||
static void test_bootstrap_range() {
|
||||
GTID_Server_Data sd(nullptr, (char *)"127.0.0.1", 0, 3306);
|
||||
|
||||
std::string msg = std::string("ST=") + UUID_A + ":1-100," + UUID_B + ":50-200\n";
|
||||
stuff_buffer(sd, msg);
|
||||
|
||||
ok(sd.read_next_gtid() == true, "ST= range: returns true");
|
||||
ok(sd.gtid_exists((char *)UUID_A_STRIPPED, 1) == true, "ST= range: UUID_A trxid 1 exists");
|
||||
ok(sd.gtid_exists((char *)UUID_A_STRIPPED, 50) == true, "ST= range: UUID_A trxid 50 exists");
|
||||
ok(sd.gtid_exists((char *)UUID_A_STRIPPED, 100) == true, "ST= range: UUID_A trxid 100 exists");
|
||||
ok(sd.gtid_exists((char *)UUID_A_STRIPPED, 101) == false, "ST= range: UUID_A trxid 101 does not exist");
|
||||
ok(sd.gtid_exists((char *)UUID_B_STRIPPED, 49) == false, "ST= range: UUID_B trxid 49 does not exist");
|
||||
ok(sd.gtid_exists((char *)UUID_B_STRIPPED, 50) == true, "ST= range: UUID_B trxid 50 exists");
|
||||
ok(sd.gtid_exists((char *)UUID_B_STRIPPED, 200) == true, "ST= range: UUID_B trxid 200 exists");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief I1= single trxid with UUID.
|
||||
*/
|
||||
static void test_i1_single_trxid() {
|
||||
GTID_Server_Data sd(nullptr, (char *)"127.0.0.1", 0, 3306);
|
||||
|
||||
std::string msg = std::string("I1=") + UUID_A_STRIPPED + ":42\n";
|
||||
stuff_buffer(sd, msg);
|
||||
|
||||
ok(sd.read_next_gtid() == true, "I1: returns true");
|
||||
ok(sd.active == true, "I1: active remains true");
|
||||
ok(sd.events_read == 1, "I1: events_read incremented");
|
||||
ok(sd.gtid_exists((char *)UUID_A_STRIPPED, 42) == true, "I1: trxid 42 exists");
|
||||
ok(sd.gtid_exists((char *)UUID_A_STRIPPED, 43) == false, "I1: trxid 43 does not exist");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief I1= must parse only a single trxid, not a range.
|
||||
*/
|
||||
static void test_i1_ignores_range() {
|
||||
GTID_Server_Data sd(nullptr, (char *)"127.0.0.1", 0, 3306);
|
||||
|
||||
// If someone sends a range via I1, atoll() parses only the first number
|
||||
std::string msg = std::string("I1=") + UUID_A_STRIPPED + ":10-20\n";
|
||||
stuff_buffer(sd, msg);
|
||||
|
||||
ok(sd.read_next_gtid() == true, "I1 range: returns true");
|
||||
ok(sd.gtid_exists((char *)UUID_A_STRIPPED, 10) == true, "I1 range: trxid 10 exists (atoll parses first number)");
|
||||
ok(sd.gtid_exists((char *)UUID_A_STRIPPED, 15) == false, "I1 range: trxid 15 does not exist (range not parsed)");
|
||||
ok(sd.gtid_exists((char *)UUID_A_STRIPPED, 20) == false, "I1 range: trxid 20 does not exist (range not parsed)");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief I2= single trxid, reusing UUID from previous I1.
|
||||
*/
|
||||
static void test_i2_reuse_uuid() {
|
||||
GTID_Server_Data sd(nullptr, (char *)"127.0.0.1", 0, 3306);
|
||||
|
||||
// First set uuid_server via I1
|
||||
std::string msg1 = std::string("I1=") + UUID_A_STRIPPED + ":10\n";
|
||||
stuff_buffer(sd, msg1);
|
||||
sd.read_next_gtid();
|
||||
|
||||
// Now I2 reuses uuid_server
|
||||
std::string msg2 = "I2=20\n";
|
||||
stuff_buffer(sd, msg2);
|
||||
|
||||
ok(sd.read_next_gtid() == true, "I2: returns true");
|
||||
ok(sd.active == true, "I2: active remains true");
|
||||
ok(sd.events_read == 2, "I2: events_read incremented to 2");
|
||||
ok(sd.gtid_exists((char *)UUID_A_STRIPPED, 20) == true, "I2: trxid 20 exists under UUID_A");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief I3= trxid range with UUID.
|
||||
*/
|
||||
static void test_i3_range() {
|
||||
GTID_Server_Data sd(nullptr, (char *)"127.0.0.1", 0, 3306);
|
||||
|
||||
std::string msg = std::string("I3=") + UUID_A_STRIPPED + ":100-200\n";
|
||||
stuff_buffer(sd, msg);
|
||||
|
||||
ok(sd.read_next_gtid() == true, "I3: returns true");
|
||||
ok(sd.active == true, "I3: active remains true");
|
||||
ok(sd.events_read == 1, "I3: events_read incremented");
|
||||
ok(sd.gtid_exists((char *)UUID_A_STRIPPED, 99) == false, "I3: trxid 99 does not exist");
|
||||
ok(sd.gtid_exists((char *)UUID_A_STRIPPED, 100) == true, "I3: trxid 100 exists");
|
||||
ok(sd.gtid_exists((char *)UUID_A_STRIPPED, 150) == true, "I3: trxid 150 exists");
|
||||
ok(sd.gtid_exists((char *)UUID_A_STRIPPED, 200) == true, "I3: trxid 200 exists");
|
||||
ok(sd.gtid_exists((char *)UUID_A_STRIPPED, 201) == false, "I3: trxid 201 does not exist");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief I4= trxid range, reusing UUID from previous I3.
|
||||
*/
|
||||
static void test_i4_range_reuse_uuid() {
|
||||
GTID_Server_Data sd(nullptr, (char *)"127.0.0.1", 0, 3306);
|
||||
|
||||
// Set uuid_server via I3
|
||||
std::string msg1 = std::string("I3=") + UUID_B_STRIPPED + ":10-20\n";
|
||||
stuff_buffer(sd, msg1);
|
||||
sd.read_next_gtid();
|
||||
|
||||
// I4 reuses uuid_server
|
||||
std::string msg2 = "I4=30-40\n";
|
||||
stuff_buffer(sd, msg2);
|
||||
|
||||
ok(sd.read_next_gtid() == true, "I4: returns true");
|
||||
ok(sd.active == true, "I4: active remains true");
|
||||
ok(sd.events_read == 2, "I4: events_read incremented to 2");
|
||||
ok(sd.gtid_exists((char *)UUID_B_STRIPPED, 30) == true, "I4: trxid 30 exists under UUID_B");
|
||||
ok(sd.gtid_exists((char *)UUID_B_STRIPPED, 35) == true, "I4: trxid 35 exists under UUID_B");
|
||||
ok(sd.gtid_exists((char *)UUID_B_STRIPPED, 40) == true, "I4: trxid 40 exists under UUID_B");
|
||||
ok(sd.gtid_exists((char *)UUID_B_STRIPPED, 41) == false, "I4: trxid 41 does not exist");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Unknown message type sets active=false and returns false.
|
||||
*/
|
||||
static void test_unknown_message_disconnects() {
|
||||
GTID_Server_Data sd(nullptr, (char *)"127.0.0.1", 0, 3306);
|
||||
|
||||
// First, send a valid message to confirm baseline
|
||||
std::string msg1 = std::string("I1=") + UUID_A_STRIPPED + ":10\n";
|
||||
stuff_buffer(sd, msg1);
|
||||
sd.read_next_gtid();
|
||||
ok(sd.active == true, "unknown: baseline active is true");
|
||||
ok(sd.events_read == 1, "unknown: baseline events_read is 1");
|
||||
|
||||
// Now send an unknown message type I9
|
||||
std::string msg2 = "I9=garbage\n";
|
||||
stuff_buffer(sd, msg2);
|
||||
|
||||
ok(sd.read_next_gtid() == false, "unknown: returns false");
|
||||
ok(sd.active == false, "unknown: active set to false (disconnect)");
|
||||
ok(sd.events_read == 1, "unknown: events_read NOT incremented");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Multiple messages in sequence: ST bootstrap, then I1, I3, I2, I4.
|
||||
*/
|
||||
static void test_mixed_sequence() {
|
||||
GTID_Server_Data sd(nullptr, (char *)"127.0.0.1", 0, 3306);
|
||||
|
||||
// Bootstrap
|
||||
std::string boot = std::string("ST=") + UUID_A + ":1-5\n";
|
||||
stuff_buffer(sd, boot);
|
||||
sd.read_next_gtid();
|
||||
ok(sd.events_read == 1, "mixed: bootstrap events_read=1");
|
||||
|
||||
// I1: single trxid, sets UUID to A
|
||||
std::string m1 = std::string("I1=") + UUID_A_STRIPPED + ":6\n";
|
||||
stuff_buffer(sd, m1);
|
||||
sd.read_next_gtid();
|
||||
ok(sd.gtid_exists((char *)UUID_A_STRIPPED, 6) == true, "mixed: I1 trxid 6 exists");
|
||||
|
||||
// I2: single trxid, reuses UUID A
|
||||
std::string m2 = "I2=7\n";
|
||||
stuff_buffer(sd, m2);
|
||||
sd.read_next_gtid();
|
||||
ok(sd.gtid_exists((char *)UUID_A_STRIPPED, 7) == true, "mixed: I2 trxid 7 exists");
|
||||
|
||||
// I3: range, sets UUID to B
|
||||
std::string m3 = std::string("I3=") + UUID_B_STRIPPED + ":100-110\n";
|
||||
stuff_buffer(sd, m3);
|
||||
sd.read_next_gtid();
|
||||
ok(sd.gtid_exists((char *)UUID_B_STRIPPED, 105) == true, "mixed: I3 trxid 105 exists");
|
||||
|
||||
// I4: range, reuses UUID B
|
||||
std::string m4 = "I4=111-120\n";
|
||||
stuff_buffer(sd, m4);
|
||||
sd.read_next_gtid();
|
||||
ok(sd.gtid_exists((char *)UUID_B_STRIPPED, 115) == true, "mixed: I4 trxid 115 exists");
|
||||
|
||||
ok(sd.events_read == 5, "mixed: events_read=5 after all messages");
|
||||
ok(sd.active == true, "mixed: still active after valid sequence");
|
||||
|
||||
// Verify the full GTID state
|
||||
ok(sd.gtid_exists((char *)UUID_A_STRIPPED, 1) == true, "mixed: UUID_A range start from bootstrap");
|
||||
ok(sd.gtid_exists((char *)UUID_A_STRIPPED, 5) == true, "mixed: UUID_A range end from bootstrap");
|
||||
ok(sd.gtid_exists((char *)UUID_B_STRIPPED, 100) == true, "mixed: UUID_B range start from I3");
|
||||
ok(sd.gtid_exists((char *)UUID_B_STRIPPED, 120) == true, "mixed: UUID_B range end from I4");
|
||||
ok(sd.gtid_exists((char *)UUID_B_STRIPPED, 99) == false, "mixed: UUID_B before range");
|
||||
ok(sd.gtid_exists((char *)UUID_B_STRIPPED, 121) == false, "mixed: UUID_B after range");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief read_all_gtids() stops on unknown message; earlier messages are processed.
|
||||
*/
|
||||
static void test_read_all_stops_on_unknown() {
|
||||
GTID_Server_Data sd(nullptr, (char *)"127.0.0.1", 0, 3306);
|
||||
|
||||
// Three messages: two valid, one unknown
|
||||
std::string msgs = std::string("I1=") + UUID_A_STRIPPED + ":10\n"
|
||||
+ "I2=11\n"
|
||||
+ "I9=bad\n";
|
||||
stuff_buffer(sd, msgs);
|
||||
|
||||
sd.read_all_gtids();
|
||||
|
||||
ok(sd.gtid_exists((char *)UUID_A_STRIPPED, 10) == true, "read_all: first message processed");
|
||||
ok(sd.gtid_exists((char *)UUID_A_STRIPPED, 11) == true, "read_all: second message processed");
|
||||
ok(sd.active == false, "read_all: active=false after unknown message");
|
||||
ok(sd.events_read == 2, "read_all: events_read=2 (unknown not counted)");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Empty buffer returns false, active stays true.
|
||||
*/
|
||||
static void test_empty_buffer() {
|
||||
GTID_Server_Data sd(nullptr, (char *)"127.0.0.1", 0, 3306);
|
||||
|
||||
ok(sd.read_next_gtid() == false, "empty: returns false");
|
||||
ok(sd.active == true, "empty: active remains true");
|
||||
ok(sd.events_read == 0, "empty: events_read stays 0");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Incomplete message (no newline) returns false, active stays true.
|
||||
*/
|
||||
static void test_incomplete_message() {
|
||||
GTID_Server_Data sd(nullptr, (char *)"127.0.0.1", 0, 3306);
|
||||
|
||||
std::string msg = std::string("I1=") + UUID_A_STRIPPED + ":42"; // no newline
|
||||
stuff_buffer(sd, msg);
|
||||
|
||||
ok(sd.read_next_gtid() == false, "incomplete: returns false (no newline)");
|
||||
ok(sd.active == true, "incomplete: active remains true");
|
||||
ok(sd.events_read == 0, "incomplete: events_read stays 0");
|
||||
}
|
||||
|
||||
int main() {
|
||||
plan(70);
|
||||
|
||||
test_bootstrap_single(); // 6 assertions
|
||||
test_bootstrap_range(); // 8 assertions
|
||||
test_i1_single_trxid(); // 5 assertions
|
||||
test_i1_ignores_range(); // 4 assertions
|
||||
test_i2_reuse_uuid(); // 4 assertions
|
||||
test_i3_range(); // 8 assertions
|
||||
test_i4_range_reuse_uuid(); // 7 assertions
|
||||
test_unknown_message_disconnects(); // 5 assertions
|
||||
test_mixed_sequence(); // 14 assertions
|
||||
test_read_all_stops_on_unknown(); // 4 assertions
|
||||
test_empty_buffer(); // 3 assertions
|
||||
test_incomplete_message(); // 3 assertions
|
||||
|
||||
return exit_status();
|
||||
}
|
||||
Loading…
Reference in new issue