feat(raft-library): task 4.2 — sim deliver/partition/timeout/start-all; 4 tests

This commit is contained in:
Rongsong Shen 2026-05-31 06:20:35 +00:00
parent d2e2c1ef8a
commit c1b07392df
2 changed files with 151 additions and 1 deletions

View file

@ -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

View file

@ -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
}