feat(raft-log-replication): simulator AppendEntries delivery (task 8.2)

raft_sim_deliver_append_entries drains the AE queue, routes each buffered AE to
the (non-partitioned) destination via raft_on_append_entries_request, and routes
the result back to the source via raft_on_append_entries_reply — mirroring the
vote-RPC routing; partitioned endpoints are skipped.

Review-applied (HIGH): raft_sim_ae_t now captures the leader's SEND-time term
(leader_term) at buffer time and delivery uses it instead of the live source
state.term — without this, reorder/churn delivery (9.3/9.4) would inject the
wrong term and defeat UpdateTerm/stale-drop (G5). Smoke test asserts replication
to a connected follower + isolation of a partitioned one. ASAN green.
This commit is contained in:
Rongsong Shen 2026-06-21 16:40:11 +00:00
parent eeb95591ae
commit 0e8c6ed05a
4 changed files with 119 additions and 1 deletions

View file

@ -3041,6 +3041,65 @@ static void test_sim_fire_timeout_steps_down(void)
TEST_ASSERT_EQUAL_INT(RAFT_FOLLOWER, c.nodes[0].state.role);
}
/*
* test_sim_deliver_append_entries_replicates (task 8.2)
*
* Delivery smoke test for raft_sim_deliver_append_entries: a leader appends one
* entry and replicates it to a connected follower; a partitioned follower
* receives nothing.
*
* Steps:
* 1. raft_sim_init(c, 3, 2000); elect node 0 leader.
* 2. Partition node 2 (so it must not receive the entry).
* 3. Leader (node 0) raft_log_append one entry → lands in node 0's log[0].
* 4. raft_send_append_entries to node 1 (connected) and node 2 (partitioned)
* → buffers two AEs.
* 5. raft_sim_deliver_append_entries(c) routes them.
* 6. Assert node 1's in-memory log now holds the entry (log_count >= 1, term set).
* 7. Assert node 2 (partitioned) received nothing (log_count == 0).
*/
static void test_sim_deliver_append_entries_replicates(void)
{
raft_sim_cluster_t c;
memset(&c, 0, sizeof(c));
TEST_ASSERT_EQUAL_INT(0, raft_sim_init(&c, 3, 2000));
/* Elect node 0 leader. */
raft_start_election(&c.nodes[0].state);
raft_sim_deliver_pending(&c);
TEST_ASSERT_EQUAL_INT(RAFT_LEADER, c.nodes[0].state.role);
/* Isolate node 2 — it must receive nothing. */
raft_sim_partition(&c, 2, 1);
/* Leader appends one client entry; persisted into node 0's in-memory log. */
unsigned char payload[4] = { 0xDE, 0xAD, 0xBE, 0xEF };
uint64_t out_index = 0;
TEST_ASSERT_EQUAL_INT(RAFT_OK,
raft_log_append(&c.nodes[0].state, payload, sizeof(payload), &out_index));
TEST_ASSERT_EQUAL_UINT64(1, out_index);
TEST_ASSERT_EQUAL_INT(1, c.nodes[0].log_count);
/* Send AppendEntries to connected (1) and partitioned (2) followers. */
TEST_ASSERT_EQUAL_INT(RAFT_OK,
raft_send_append_entries(&c.nodes[0].state, 1, /*leader_commit=*/0));
TEST_ASSERT_EQUAL_INT(RAFT_OK,
raft_send_append_entries(&c.nodes[0].state, 2, /*leader_commit=*/0));
/* Deliver: node 1 receives + replies; node 2 is dropped (partitioned). */
raft_sim_deliver_append_entries(&c);
/* Connected follower replicated the entry — its term must equal the LEADER'S
* PERSISTED entry term (node 0's log[0]), the authoritative send-time term,
* not the leader's live state term (which only coincides here). */
TEST_ASSERT_EQUAL_INT(1, c.nodes[1].log_count);
TEST_ASSERT_EQUAL_UINT64(c.nodes[0].log[0].term, c.nodes[1].log[0].term);
/* Partitioned follower received nothing. */
TEST_ASSERT_EQUAL_INT(0, c.nodes[2].log_count);
}
/* ---------- Group A: Basic Election Correctness (task 5.1) ---------- */
/*
@ -5288,6 +5347,7 @@ void test_raft(void)
RUN_TEST(test_sim_start_all_elections_no_winner);
RUN_TEST(test_sim_partition_no_quorum);
RUN_TEST(test_sim_fire_timeout_steps_down);
RUN_TEST(test_sim_deliver_append_entries_replicates);
/* Group A: basic election correctness (task 5.1) */
RUN_TEST(test_proto_A1_single_node_self_elects);