/** * @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 #include 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 Malformed bootstrap trxid ranges disconnect without counting an event. */ static void test_malformed_bootstrap_disconnects() { GTID_Server_Data sd(nullptr, (char *)"127.0.0.1", 0, 3306); std::string msg = std::string("ST=") + UUID_A + ":abc\n"; stuff_buffer(sd, msg); ok(sd.read_next_gtid() == false, "malformed ST: returns false"); ok(sd.active == false, "malformed ST: active set to false"); ok(sd.events_read == 0, "malformed ST: events_read NOT incremented"); ok(sd.gtid_exists((char *)UUID_A_STRIPPED, 0) == false, "malformed ST: trxid 0 was not added"); } /** * @brief Malformed I3 trxid ranges disconnect without counting an event. */ static void test_malformed_i3_disconnects() { GTID_Server_Data sd(nullptr, (char *)"127.0.0.1", 0, 3306); std::string msg = std::string("I3=") + UUID_A_STRIPPED + ":10-abc\n"; stuff_buffer(sd, msg); ok(sd.read_next_gtid() == false, "malformed I3: returns false"); ok(sd.active == false, "malformed I3: active set to false"); ok(sd.events_read == 0, "malformed I3: events_read NOT incremented"); ok(sd.gtid_exists((char *)UUID_A_STRIPPED, 0) == false, "malformed I3: trxid 0 was not added"); ok(sd.gtid_exists((char *)UUID_A_STRIPPED, 10) == false, "malformed I3: trxid 10 was not added"); } /** * @brief Malformed I4 trxid ranges disconnect and preserve earlier event count. */ static void test_malformed_i4_disconnects() { GTID_Server_Data sd(nullptr, (char *)"127.0.0.1", 0, 3306); std::string msg1 = std::string("I3=") + UUID_B_STRIPPED + ":10-20\n"; stuff_buffer(sd, msg1); sd.read_next_gtid(); std::string msg2 = "I4=30-40x\n"; stuff_buffer(sd, msg2); ok(sd.read_next_gtid() == false, "malformed I4: returns false"); ok(sd.active == false, "malformed I4: active set to false"); ok(sd.events_read == 1, "malformed I4: events_read NOT incremented"); ok(sd.gtid_exists((char *)UUID_B_STRIPPED, 30) == false, "malformed I4: trxid 30 was not added"); } /** * @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(83); 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_malformed_bootstrap_disconnects(); // 4 assertions test_malformed_i3_disconnects(); // 5 assertions test_malformed_i4_disconnects(); // 4 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(); }