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