feat(raft-library): task 4.2 — sim deliver/partition/timeout/start-all; 4 tests
This commit is contained in:
parent
d2e2c1ef8a
commit
c1b07392df
2 changed files with 151 additions and 1 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue