Skip to content

Commit 805194e

Browse files
authored
Merge pull request #28 from poyrazK/test/raft-group-coverage
test: add raft_group_tests.cpp for RaftGroup coverage
2 parents 16f79b1 + 88320e1 commit 805194e

2 files changed

Lines changed: 240 additions & 0 deletions

File tree

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ if(BUILD_TESTS)
132132
add_cloudsql_test(distributed_txn_tests tests/distributed_txn_tests.cpp)
133133
add_cloudsql_test(analytics_tests tests/analytics_tests.cpp)
134134
add_cloudsql_test(raft_manager_tests tests/raft_manager_tests.cpp)
135+
add_cloudsql_test(raft_group_tests tests/raft_group_tests.cpp)
135136
add_cloudsql_test(raft_protocol_tests tests/raft_protocol_tests.cpp)
136137
add_cloudsql_test(columnar_table_tests tests/columnar_table_tests.cpp)
137138
add_cloudsql_test(storage_manager_tests tests/storage_manager_tests.cpp)

tests/raft_group_tests.cpp

Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
1+
/**
2+
* @file raft_group_tests.cpp
3+
* @brief Unit tests for RaftGroup consensus implementation
4+
*/
5+
6+
#include <gtest/gtest.h>
7+
8+
#include <atomic>
9+
#include <cstdio>
10+
#include <memory>
11+
#include <vector>
12+
13+
#include "common/cluster_manager.hpp"
14+
#include "common/config.hpp"
15+
#include "distributed/raft_group.hpp"
16+
#include "distributed/raft_manager.hpp"
17+
#include "network/rpc_server.hpp"
18+
19+
using namespace cloudsql;
20+
using namespace cloudsql::raft;
21+
using namespace cloudsql::cluster;
22+
using namespace cloudsql::network;
23+
24+
namespace {
25+
26+
class RaftGroupTests : public ::testing::Test {
27+
protected:
28+
void SetUp() override {
29+
config_.mode = config::RunMode::Coordinator;
30+
constexpr uint16_t TEST_PORT = 6200;
31+
config_.cluster_port = TEST_PORT;
32+
cm_ = std::make_unique<ClusterManager>(&config_);
33+
rpc_ = std::make_unique<RpcServer>(TEST_PORT);
34+
ASSERT_TRUE(rpc_->start()) << "RpcServer failed to start - port may be in use";
35+
manager_ = std::make_unique<RaftManager>("node1", *cm_, *rpc_);
36+
group_ = manager_->get_or_create_group(1);
37+
}
38+
39+
void TearDown() override {
40+
if (group_) {
41+
group_->stop();
42+
}
43+
if (manager_) {
44+
manager_->stop();
45+
}
46+
if (rpc_) {
47+
rpc_->stop();
48+
}
49+
// Cleanup state files for all possible group IDs
50+
std::remove("raft_group_1.state");
51+
std::remove("raft_group_2.state");
52+
std::remove("raft_group_3.state");
53+
}
54+
55+
config::Config config_;
56+
std::unique_ptr<ClusterManager> cm_;
57+
std::unique_ptr<RpcServer> rpc_;
58+
std::unique_ptr<RaftManager> manager_;
59+
std::shared_ptr<RaftGroup> group_;
60+
};
61+
62+
// ============= Constructor and Basic Tests =============
63+
64+
TEST_F(RaftGroupTests, ConstructorInitialState) {
65+
EXPECT_NE(group_, nullptr);
66+
EXPECT_FALSE(group_->is_leader());
67+
EXPECT_EQ(group_->group_id(), 1);
68+
}
69+
70+
TEST_F(RaftGroupTests, StartStopLifecycle) {
71+
group_->start();
72+
group_->stop();
73+
group_->start();
74+
group_->stop();
75+
SUCCEED(); // No crash
76+
}
77+
78+
// ============= State Machine Tests =============
79+
80+
TEST_F(RaftGroupTests, SetStateMachine) {
81+
// Should not crash when setting state machine
82+
group_->set_state_machine(nullptr);
83+
SUCCEED();
84+
}
85+
86+
// ============= Log Replication Tests =============
87+
88+
TEST_F(RaftGroupTests, ReplicateNotLeader) {
89+
// Before becoming leader, replicate should fail
90+
std::vector<uint8_t> data = {1, 2, 3};
91+
EXPECT_FALSE(group_->replicate(data));
92+
}
93+
94+
TEST_F(RaftGroupTests, ReplicateDataSize) {
95+
// Test with various data sizes
96+
std::vector<uint8_t> small_data = {1};
97+
std::vector<uint8_t> large_data(1024, 42);
98+
99+
// Both should fail when not leader
100+
EXPECT_FALSE(group_->replicate(small_data));
101+
EXPECT_FALSE(group_->replicate(large_data));
102+
}
103+
104+
// ============= Timeout Tests =============
105+
106+
// Note: get_random_timeout() is private and tested indirectly through
107+
// election timing behavior in integration tests
108+
109+
// ============= RPC Handler Serialization Tests =============
110+
111+
TEST_F(RaftGroupTests, RequestVoteArgsSerialization) {
112+
RequestVoteArgs args;
113+
args.term = 5;
114+
args.candidate_id = "node2";
115+
args.last_log_index = 10;
116+
args.last_log_term = 3;
117+
118+
auto serialized = args.serialize();
119+
120+
// Should have: 8 (term) + 8 (id_len) + id + 8 (last_log_index) + 8 (last_log_term)
121+
EXPECT_EQ(serialized.size(), 8 + 8 + 5 + 8 + 8);
122+
}
123+
124+
TEST_F(RaftGroupTests, AppendEntriesArgsStructure) {
125+
AppendEntriesArgs args;
126+
args.term = 1;
127+
args.leader_id = "leader1";
128+
args.prev_log_index = 5;
129+
args.prev_log_term = 1;
130+
args.leader_commit = 3;
131+
132+
// Verify all fields are set correctly
133+
EXPECT_EQ(args.term, 1);
134+
EXPECT_EQ(args.leader_id, "leader1");
135+
EXPECT_EQ(args.prev_log_index, 5);
136+
EXPECT_EQ(args.prev_log_term, 1);
137+
EXPECT_EQ(args.leader_commit, 3);
138+
EXPECT_TRUE(args.entries.empty());
139+
}
140+
141+
// ============= Log Entry Tests =============
142+
143+
TEST_F(RaftGroupTests, LogEntryDefaultValues) {
144+
LogEntry entry;
145+
EXPECT_EQ(entry.term, 0);
146+
EXPECT_EQ(entry.index, 0);
147+
EXPECT_TRUE(entry.data.empty());
148+
}
149+
150+
TEST_F(RaftGroupTests, LogEntryWithData) {
151+
LogEntry entry;
152+
entry.term = 1;
153+
entry.index = 5;
154+
entry.data = {1, 2, 3, 4, 5};
155+
156+
EXPECT_EQ(entry.term, 1);
157+
EXPECT_EQ(entry.index, 5);
158+
EXPECT_EQ(entry.data.size(), 5);
159+
}
160+
161+
// ============= Persistent State Tests =============
162+
163+
TEST_F(RaftGroupTests, PersistentStateDefaultValues) {
164+
RaftPersistentState state;
165+
EXPECT_EQ(state.current_term, 0);
166+
EXPECT_TRUE(state.voted_for.empty());
167+
EXPECT_TRUE(state.log.empty());
168+
}
169+
170+
TEST_F(RaftGroupTests, VolatileStateDefaultValues) {
171+
RaftVolatileState state;
172+
EXPECT_EQ(state.commit_index, 0);
173+
EXPECT_EQ(state.last_applied, 0);
174+
}
175+
176+
// ============= Leader State Tests =============
177+
178+
TEST_F(RaftGroupTests, LeaderStateEmpty) {
179+
LeaderState state;
180+
EXPECT_TRUE(state.next_index.empty());
181+
EXPECT_TRUE(state.match_index.empty());
182+
}
183+
184+
TEST_F(RaftGroupTests, LeaderStateWithPeers) {
185+
LeaderState state;
186+
state.next_index["node2"] = 1;
187+
state.next_index["node3"] = 1;
188+
state.match_index["node2"] = 0;
189+
state.match_index["node3"] = 0;
190+
191+
EXPECT_EQ(state.next_index.size(), 2);
192+
EXPECT_EQ(state.match_index.size(), 2);
193+
EXPECT_EQ(state.next_index["node2"], 1);
194+
EXPECT_EQ(state.match_index["node3"], 0);
195+
}
196+
197+
// ============= Vote Reply Tests =============
198+
199+
TEST_F(RaftGroupTests, RequestVoteReplyStructure) {
200+
RequestVoteReply reply;
201+
reply.term = 5;
202+
reply.vote_granted = true;
203+
204+
EXPECT_EQ(reply.term, 5);
205+
EXPECT_TRUE(reply.vote_granted);
206+
}
207+
208+
// ============= AppendEntries Reply Tests =============
209+
210+
TEST_F(RaftGroupTests, AppendEntriesReplyStructure) {
211+
AppendEntriesReply reply;
212+
reply.term = 3;
213+
reply.success = true;
214+
215+
EXPECT_EQ(reply.term, 3);
216+
EXPECT_TRUE(reply.success);
217+
}
218+
219+
// ============= NodeState Tests =============
220+
221+
TEST_F(RaftGroupTests, NodeStateEnum) {
222+
EXPECT_EQ(static_cast<uint8_t>(NodeState::Follower), 0);
223+
EXPECT_EQ(static_cast<uint8_t>(NodeState::Candidate), 1);
224+
EXPECT_EQ(static_cast<uint8_t>(NodeState::Leader), 2);
225+
EXPECT_EQ(static_cast<uint8_t>(NodeState::Shutdown), 3);
226+
}
227+
228+
// ============= Group ID Tests =============
229+
230+
TEST_F(RaftGroupTests, DifferentGroupIds) {
231+
auto group1 = manager_->get_or_create_group(1);
232+
auto group2 = manager_->get_or_create_group(2);
233+
234+
EXPECT_NE(group1, group2);
235+
EXPECT_EQ(group1->group_id(), 1);
236+
EXPECT_EQ(group2->group_id(), 2);
237+
}
238+
239+
} // namespace

0 commit comments

Comments
 (0)