From aa15315f9035ca2656cbc8adb541fea00b9c7570 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Poyraz=20K=C3=BC=C3=A7=C3=BCkarslan?= <83272398+PoyrazK@users.noreply.github.com> Date: Sun, 12 Apr 2026 18:14:07 +0300 Subject: [PATCH 1/3] test: add raft_group_tests.cpp for RaftGroup coverage Add 17 unit tests covering: - RaftGroup construction and initial state - Start/stop lifecycle - State machine interface - Log replication (replicate fails when not leader) - RPC serialization (RequestVoteArgs, AppendEntriesArgs) - Log entry structure and defaults - Persistent/volatile state structures - Leader state management - NodeState enum values - Group ID differentiation Part of ongoing coverage improvement effort. --- CMakeLists.txt | 1 + tests/raft_group_tests.cpp | 232 +++++++++++++++++++++++++++++++++++++ 2 files changed, 233 insertions(+) create mode 100644 tests/raft_group_tests.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 5e2087b..6e90d21 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -132,6 +132,7 @@ if(BUILD_TESTS) add_cloudsql_test(distributed_txn_tests tests/distributed_txn_tests.cpp) add_cloudsql_test(analytics_tests tests/analytics_tests.cpp) add_cloudsql_test(raft_manager_tests tests/raft_manager_tests.cpp) + add_cloudsql_test(raft_group_tests tests/raft_group_tests.cpp) add_cloudsql_test(raft_protocol_tests tests/raft_protocol_tests.cpp) add_cloudsql_test(columnar_table_tests tests/columnar_table_tests.cpp) add_cloudsql_test(storage_manager_tests tests/storage_manager_tests.cpp) diff --git a/tests/raft_group_tests.cpp b/tests/raft_group_tests.cpp new file mode 100644 index 0000000..42acbd0 --- /dev/null +++ b/tests/raft_group_tests.cpp @@ -0,0 +1,232 @@ +/** + * @file raft_group_tests.cpp + * @brief Unit tests for RaftGroup consensus implementation + */ + +#include + +#include +#include +#include +#include + +#include "common/cluster_manager.hpp" +#include "common/config.hpp" +#include "distributed/raft_group.hpp" +#include "distributed/raft_manager.hpp" +#include "network/rpc_server.hpp" + +using namespace cloudsql; +using namespace cloudsql::raft; +using namespace cloudsql::cluster; +using namespace cloudsql::network; + +namespace { + +class RaftGroupTests : public ::testing::Test { + protected: + void SetUp() override { + config_.mode = config::RunMode::Coordinator; + constexpr uint16_t TEST_PORT = 6200; + config_.cluster_port = TEST_PORT; + cm_ = std::make_unique(&config_); + rpc_ = std::make_unique(TEST_PORT); + ASSERT_TRUE(rpc_->start()) << "RpcServer failed to start - port may be in use"; + manager_ = std::make_unique("node1", *cm_, *rpc_); + group_ = manager_->get_or_create_group(1); + } + + void TearDown() override { + if (group_) { + group_->stop(); + } + if (manager_) { + manager_->stop(); + } + if (rpc_) { + rpc_->stop(); + } + // Cleanup state files + std::remove("raft_group_1.state"); + } + + config::Config config_; + std::unique_ptr cm_; + std::unique_ptr rpc_; + std::unique_ptr manager_; + std::shared_ptr group_; +}; + +// ============= Constructor and Basic Tests ============= + +TEST_F(RaftGroupTests, ConstructorInitialState) { + EXPECT_NE(group_, nullptr); + EXPECT_FALSE(group_->is_leader()); + EXPECT_EQ(group_->group_id(), 1); +} + +TEST_F(RaftGroupTests, StartStopLifecycle) { + group_->start(); + group_->stop(); + group_->start(); + group_->stop(); + SUCCEED(); // No crash +} + +// ============= State Machine Tests ============= + +TEST_F(RaftGroupTests, SetStateMachine) { + // Should not crash when setting state machine + group_->set_state_machine(nullptr); + SUCCEED(); +} + +// ============= Log Replication Tests ============= + +TEST_F(RaftGroupTests, ReplicateNotLeader) { + // Before becoming leader, replicate should fail + std::vector data = {1, 2, 3}; + EXPECT_FALSE(group_->replicate(data)); +} + +TEST_F(RaftGroupTests, ReplicateDataSize) { + // Test with various data sizes + std::vector small_data = {1}; + std::vector large_data(1024, 42); + + // Both should fail when not leader + EXPECT_FALSE(group_->replicate(small_data)); + EXPECT_FALSE(group_->replicate(large_data)); +} + +// ============= Timeout Tests ============= + +// Note: get_random_timeout() is private and tested indirectly through +// election timing behavior in integration tests + +// ============= RPC Handler Serialization Tests ============= + +TEST_F(RaftGroupTests, RequestVoteArgsSerialization) { + RequestVoteArgs args; + args.term = 5; + args.candidate_id = "node2"; + args.last_log_index = 10; + args.last_log_term = 3; + + auto serialized = args.serialize(); + + // Should have: 8 (term) + 8 (id_len) + id + 8 (last_log_index) + 8 (last_log_term) + EXPECT_EQ(serialized.size(), 8 + 8 + 5 + 8 + 8); +} + +TEST_F(RaftGroupTests, AppendEntriesArgsStructure) { + AppendEntriesArgs args; + args.term = 1; + args.leader_id = "leader1"; + args.prev_log_index = 5; + args.prev_log_term = 1; + args.leader_commit = 3; + + // Empty entries vector should be valid + EXPECT_TRUE(true); // Structure is valid +} + +// ============= Log Entry Tests ============= + +TEST_F(RaftGroupTests, LogEntryDefaultValues) { + LogEntry entry; + EXPECT_EQ(entry.term, 0); + EXPECT_EQ(entry.index, 0); + EXPECT_TRUE(entry.data.empty()); +} + +TEST_F(RaftGroupTests, LogEntryWithData) { + LogEntry entry; + entry.term = 1; + entry.index = 5; + entry.data = {1, 2, 3, 4, 5}; + + EXPECT_EQ(entry.term, 1); + EXPECT_EQ(entry.index, 5); + EXPECT_EQ(entry.data.size(), 5); +} + +// ============= Persistent State Tests ============= + +TEST_F(RaftGroupTests, PersistentStateDefaultValues) { + RaftPersistentState state; + EXPECT_EQ(state.current_term, 0); + EXPECT_TRUE(state.voted_for.empty()); + EXPECT_TRUE(state.log.empty()); +} + +TEST_F(RaftGroupTests, VolatileStateDefaultValues) { + RaftVolatileState state; + EXPECT_EQ(state.commit_index, 0); + EXPECT_EQ(state.last_applied, 0); +} + +// ============= Leader State Tests ============= + +TEST_F(RaftGroupTests, LeaderStateEmpty) { + LeaderState state; + EXPECT_TRUE(state.next_index.empty()); + EXPECT_TRUE(state.match_index.empty()); +} + +TEST_F(RaftGroupTests, LeaderStateWithPeers) { + LeaderState state; + state.next_index["node2"] = 1; + state.next_index["node3"] = 1; + state.match_index["node2"] = 0; + state.match_index["node3"] = 0; + + EXPECT_EQ(state.next_index.size(), 2); + EXPECT_EQ(state.match_index.size(), 2); + EXPECT_EQ(state.next_index["node2"], 1); + EXPECT_EQ(state.match_index["node3"], 0); +} + +// ============= Vote Reply Tests ============= + +TEST_F(RaftGroupTests, RequestVoteReplyStructure) { + RequestVoteReply reply; + reply.term = 5; + reply.vote_granted = true; + + EXPECT_EQ(reply.term, 5); + EXPECT_TRUE(reply.vote_granted); +} + +// ============= AppendEntries Reply Tests ============= + +TEST_F(RaftGroupTests, AppendEntriesReplyStructure) { + AppendEntriesReply reply; + reply.term = 3; + reply.success = true; + + EXPECT_EQ(reply.term, 3); + EXPECT_TRUE(reply.success); +} + +// ============= NodeState Tests ============= + +TEST_F(RaftGroupTests, NodeStateEnum) { + EXPECT_EQ(static_cast(NodeState::Follower), 0); + EXPECT_EQ(static_cast(NodeState::Candidate), 1); + EXPECT_EQ(static_cast(NodeState::Leader), 2); + EXPECT_EQ(static_cast(NodeState::Shutdown), 3); +} + +// ============= Group ID Tests ============= + +TEST_F(RaftGroupTests, DifferentGroupIds) { + auto group1 = manager_->get_or_create_group(1); + auto group2 = manager_->get_or_create_group(2); + + EXPECT_NE(group1, group2); + EXPECT_EQ(group1->group_id(), 1); + EXPECT_EQ(group2->group_id(), 2); +} + +} // namespace \ No newline at end of file From aa321c9c711fb4c1bcbb5d1ce788c53cba21f58b Mon Sep 17 00:00:00 2001 From: poyrazK <83272398+poyrazK@users.noreply.github.com> Date: Sun, 12 Apr 2026 15:55:26 +0000 Subject: [PATCH 2/3] style: automated clang-format fixes --- tests/raft_group_tests.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/raft_group_tests.cpp b/tests/raft_group_tests.cpp index 42acbd0..8c89969 100644 --- a/tests/raft_group_tests.cpp +++ b/tests/raft_group_tests.cpp @@ -24,7 +24,7 @@ using namespace cloudsql::network; namespace { class RaftGroupTests : public ::testing::Test { - protected: + protected: void SetUp() override { config_.mode = config::RunMode::Coordinator; constexpr uint16_t TEST_PORT = 6200; From 88320e11b2ff1ef6a93c2abb9b73bd812dd6100f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Poyraz=20K=C3=BC=C3=A7=C3=BCkarslan?= <83272398+PoyrazK@users.noreply.github.com> Date: Sun, 12 Apr 2026 19:09:03 +0300 Subject: [PATCH 3/3] Fix CodeRabbit review inline comments in raft_group_tests - TearDown: Add cleanup for raft_group_2.state and raft_group_3.state - AppendEntriesArgsStructure: Replace tautological EXPECT_TRUE(true) with real assertions on term, leader_id, prev_log_index, prev_log_term, leader_commit, and entries.empty() --- tests/raft_group_tests.cpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/tests/raft_group_tests.cpp b/tests/raft_group_tests.cpp index 8c89969..8f4afa4 100644 --- a/tests/raft_group_tests.cpp +++ b/tests/raft_group_tests.cpp @@ -46,8 +46,10 @@ class RaftGroupTests : public ::testing::Test { if (rpc_) { rpc_->stop(); } - // Cleanup state files + // Cleanup state files for all possible group IDs std::remove("raft_group_1.state"); + std::remove("raft_group_2.state"); + std::remove("raft_group_3.state"); } config::Config config_; @@ -127,8 +129,13 @@ TEST_F(RaftGroupTests, AppendEntriesArgsStructure) { args.prev_log_term = 1; args.leader_commit = 3; - // Empty entries vector should be valid - EXPECT_TRUE(true); // Structure is valid + // Verify all fields are set correctly + EXPECT_EQ(args.term, 1); + EXPECT_EQ(args.leader_id, "leader1"); + EXPECT_EQ(args.prev_log_index, 5); + EXPECT_EQ(args.prev_log_term, 1); + EXPECT_EQ(args.leader_commit, 3); + EXPECT_TRUE(args.entries.empty()); } // ============= Log Entry Tests =============