From c1b07392df276b2c0822d0d98b8e5a6e6e83d38a Mon Sep 17 00:00:00 2001 From: Rongsong Shen Date: Sun, 31 May 2026 06:20:35 +0000 Subject: [PATCH] =?UTF-8?q?feat(raft-library):=20task=204.2=20=E2=80=94=20?= =?UTF-8?q?sim=20deliver/partition/timeout/start-all;=204=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .kiro/specs/raft-library/tasks.md | 2 +- src/lib/testsuite/test_raft.c | 150 ++++++++++++++++++++++++++++++ 2 files changed, 151 insertions(+), 1 deletion(-) diff --git a/.kiro/specs/raft-library/tasks.md b/.kiro/specs/raft-library/tasks.md index 22a50053..1709b287 100644 --- a/.kiro/specs/raft-library/tasks.md +++ b/.kiro/specs/raft-library/tasks.md @@ -77,7 +77,7 @@ - _Requirements: 8.1_ - _Boundary: raft_sim.c_ -- [ ] 4.2 Implement simulator network operations and test driving +- [x] 4.2 Implement simulator network operations and test driving - `raft_sim_deliver_pending(cluster)`: routes all buffered RPCs synchronously — for each buffered `(src, dst, term)` entry, calls `raft_on_vote_request` on `dst` then routes the reply back to `src` via `raft_on_vote_reply`; skips delivery if `dst` has `partition=1` - `raft_sim_start_all_elections(cluster)`: calls `raft_start_election` on each node in order 0..n-1 - `raft_sim_fire_timeout(cluster, node_idx)`: calls `raft_on_election_timeout` on the specified node diff --git a/src/lib/testsuite/test_raft.c b/src/lib/testsuite/test_raft.c index 30ffb061..c3d6231f 100644 --- a/src/lib/testsuite/test_raft.c +++ b/src/lib/testsuite/test_raft.c @@ -1540,6 +1540,150 @@ static void test_sim_trace_buf(void) TEST_ASSERT_GREATER_THAN_INT(0, c.nodes[0].trace_len); } +/* + * test_sim_3node_election_one_leader (task 4.2) + * + * Node 0 starts the election alone (priority=0, highest priority). + * Nodes 1 and 2 have not yet voted, so they grant node 0's request. + * After delivering pending RPCs, node 0 reaches quorum and becomes LEADER. + * + * Steps: + * 1. raft_sim_init(c, 3, 2000) + * 2. raft_start_election only for node 0 → buffers 2 vote-request RPCs + * 3. raft_sim_deliver_pending(c) → nodes 1 and 2 grant; node 0 wins + * 4. Assert c->leader_count == 1 (exactly one LEADER, no split brain) + * 5. Assert node 0 is RAFT_LEADER, nodes 1 and 2 are RAFT_FOLLOWER + */ +static void test_sim_3node_election_one_leader(void) +{ + raft_sim_cluster_t c; + memset(&c, 0, sizeof(c)); + + int rc = raft_sim_init(&c, 3, 2000); + TEST_ASSERT_EQUAL_INT(0, rc); + + /* Only node 0 starts an election (highest priority) */ + raft_start_election(&c.nodes[0].state); + + /* 2 RPCs buffered (to nodes 1 and 2) */ + TEST_ASSERT_EQUAL_INT(2, c.pending_count); + + /* Deliver: nodes 1 and 2 have not voted yet → both grant node 0 */ + raft_sim_deliver_pending(&c); + + /* Exactly one leader must exist */ + TEST_ASSERT_EQUAL_INT(1, c.leader_count); + + /* Node 0 must be the leader */ + TEST_ASSERT_EQUAL_INT(RAFT_LEADER, c.nodes[0].state.role); + + /* Nodes 1 and 2 are followers */ + TEST_ASSERT_EQUAL_INT(RAFT_FOLLOWER, c.nodes[1].state.role); + TEST_ASSERT_EQUAL_INT(RAFT_FOLLOWER, c.nodes[2].state.role); +} + +/* + * test_sim_start_all_elections_no_winner (task 4.2) + * + * All 3 nodes start elections simultaneously at the same term. + * Each node votes for itself; all peer requests are denied (already voted). + * No node reaches quorum — leader_count remains 0. + * + * This demonstrates that simultaneous same-term elections produce no winner + * and require a timeout + retry cycle to resolve. + */ +static void test_sim_start_all_elections_no_winner(void) +{ + raft_sim_cluster_t c; + memset(&c, 0, sizeof(c)); + + int rc = raft_sim_init(&c, 3, 2000); + TEST_ASSERT_EQUAL_INT(0, rc); + + /* All nodes start elections at term 1 — each votes for itself */ + raft_sim_start_all_elections(&c); + + /* 6 RPCs buffered: each of 3 nodes sends to 2 peers */ + TEST_ASSERT_EQUAL_INT(6, c.pending_count); + + /* Deliver: every node has voted_for=self, so all peer requests are denied */ + raft_sim_deliver_pending(&c); + + /* No leader — all denied each other */ + TEST_ASSERT_EQUAL_INT(0, c.leader_count); + + /* All nodes have stepped back to FOLLOWER after all-replied-no-quorum */ + TEST_ASSERT_EQUAL_INT(RAFT_FOLLOWER, c.nodes[0].state.role); + TEST_ASSERT_EQUAL_INT(RAFT_FOLLOWER, c.nodes[1].state.role); + TEST_ASSERT_EQUAL_INT(RAFT_FOLLOWER, c.nodes[2].state.role); +} + +/* + * test_sim_partition_no_quorum (task 4.2) + * + * Node 2 is isolated before it starts an election. + * Its vote-request RPCs are dropped during delivery (partition flag set). + * Node 2 receives no grants and remains CANDIDATE; leader_count stays 0. + * + * Steps: + * 1. raft_sim_init(c, 3, 2000) + * 2. raft_sim_partition(c, 2, 1) — isolate node 2 + * 3. raft_start_election(&c->nodes[2]) — node 2 starts election + * 4. raft_sim_deliver_pending(c) — RPCs to/from node 2 are dropped + * 5. Assert node 2 is still CANDIDATE (no quorum reached) + * 6. Assert c->leader_count == 0 + */ +static void test_sim_partition_no_quorum(void) +{ + raft_sim_cluster_t c; + memset(&c, 0, sizeof(c)); + + int rc = raft_sim_init(&c, 3, 2000); + TEST_ASSERT_EQUAL_INT(0, rc); + + /* Isolate node 2 */ + raft_sim_partition(&c, 2, 1); + TEST_ASSERT_EQUAL_INT(1, c.nodes[2].partition); + + /* Node 2 starts an election — becomes CANDIDATE and buffers RPCs */ + raft_start_election(&c.nodes[2].state); + TEST_ASSERT_EQUAL_INT(RAFT_CANDIDATE, c.nodes[2].state.role); + + /* Deliver: node 2 is partitioned → all its RPCs are dropped */ + raft_sim_deliver_pending(&c); + + /* Node 2 never received any grants — still CANDIDATE */ + TEST_ASSERT_EQUAL_INT(RAFT_CANDIDATE, c.nodes[2].state.role); + + /* No leader elected */ + TEST_ASSERT_EQUAL_INT(0, c.leader_count); +} + +/* + * test_sim_fire_timeout_steps_down (task 4.2) + * + * Node 0 starts an election (becomes CANDIDATE). + * raft_sim_fire_timeout fires the election timeout for node 0. + * Node 0 must step down to FOLLOWER. + */ +static void test_sim_fire_timeout_steps_down(void) +{ + raft_sim_cluster_t c; + memset(&c, 0, sizeof(c)); + + TEST_ASSERT_EQUAL_INT(0, raft_sim_init(&c, 3, 2000)); + + /* Start election — node 0 becomes CANDIDATE */ + raft_start_election(&c.nodes[0].state); + TEST_ASSERT_EQUAL_INT(RAFT_CANDIDATE, c.nodes[0].state.role); + + /* Fire timeout — no quorum was reached */ + raft_sim_fire_timeout(&c, 0); + + /* Node 0 must step down */ + TEST_ASSERT_EQUAL_INT(RAFT_FOLLOWER, c.nodes[0].state.role); +} + #endif /* XCE_TESTING */ /* ---------- test_raft entry point ---------- */ @@ -1599,5 +1743,11 @@ void test_raft(void) #ifdef XCE_TESTING RUN_TEST(test_sim_init_3node); RUN_TEST(test_sim_trace_buf); + + /* simulator network operations (task 4.2) */ + RUN_TEST(test_sim_3node_election_one_leader); + RUN_TEST(test_sim_start_all_elections_no_winner); + RUN_TEST(test_sim_partition_no_quorum); + RUN_TEST(test_sim_fire_timeout_steps_down); #endif }