test(raft-log-replication): close Req 8.3 node-restart with sim restart primitive

raft_sim_restart_node models a process restart: the durable in-memory log[]
(WAL) and persisted (term, voted_for) survive, while volatile consensus state
is cleared by re-running raft_init (returns the node to FOLLOWER). Callbacks,
ctx, idx, and partition flag are preserved.

test_sim_9_5_node_restart_resumes_replication restarts a follower mid-replication
(partial 2-of-3 log), asserts the log survived (load-bearing: log_count==2 would
fail if wiped), role/term restored, then replication resumes and converges with
Log Matching. Closes the 8.3 'node restart while replication proceeds' sub-clause
(8/8 requirement sections now fully covered). ASAN green (220 tests).
This commit is contained in:
Rongsong Shen 2026-06-21 21:49:20 +00:00
parent 0dbccf4b0a
commit 3d6dc9cc56
4 changed files with 116 additions and 1 deletions

View file

@ -3613,6 +3613,75 @@ static void test_sim_9_4_chaotic_churn_g5_regression(void)
TEST_ASSERT_TRUE(c.nodes[2].state.term > leader_term);
}
/*
* test_sim_9_5_node_restart_resumes_replication (task 9.x; Req 8.3 — node restart
* while replication proceeds)
*
* A follower restarts MID-replication: its volatile consensus state is lost but
* its durable log (the in-memory log[], modelling the WAL) and persisted
* (term, voted_for) survive. Replication then resumes and converges with Log
* Matching intact. This closes the "node restart" sub-clause of Req 8.3 that the
* drop/dup/reorder test (9.3) did not chain.
*
* Load-bearing assertions:
* - immediately after restart the follower STILL holds its partial log
* (log_count preserved, NOT wiped to 0) and is back to FOLLOWER at the same
* durable term — proving the restart cleared volatile state while keeping the
* durable log;
* - replication resumes from the partial prefix and the follower converges to
* the leader's full log with Log Matching holding.
*/
static void test_sim_9_5_node_restart_resumes_replication(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);
/* Leader appends 3 entries. */
for (int e = 0; e < 3; e++) {
unsigned char payload[2] = { (unsigned char)('p' + e), 0x00 };
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_INT(3, c.nodes[0].log_count);
/* Replicate PARTIALLY to node 1 (entries 1 and 2), leaving entry 3 pending. */
sim_replicate_one(&c, 0, 1, 0);
sim_replicate_one(&c, 0, 1, 0);
TEST_ASSERT_EQUAL_INT(2, c.nodes[1].log_count);
uint64_t durable_term = c.nodes[1].state.term;
/* RESTART node 1 mid-replication. */
TEST_ASSERT_EQUAL_INT(0, raft_sim_restart_node(&c, 1));
/* Durable log survived the restart (NOT wiped); volatile state reset to a
* fresh FOLLOWER at the restored term and log length. */
TEST_ASSERT_EQUAL_INT(2, c.nodes[1].log_count);
TEST_ASSERT_EQUAL_INT(RAFT_FOLLOWER, c.nodes[1].state.role);
TEST_ASSERT_EQUAL_UINT64(durable_term, c.nodes[1].state.term);
TEST_ASSERT_EQUAL_UINT64(2, c.nodes[1].state.log_len); /* restored from log_count */
/* Resume replication; bounded loop drives the rebooted follower to converge. */
int rounds = 0;
while (c.nodes[1].log_count < c.nodes[0].log_count && rounds < 64) {
sim_replicate_one(&c, 0, 1, 0);
rounds++;
}
/* Converged across the restart; Log Matching holds. */
TEST_ASSERT_EQUAL_INT(3, c.nodes[1].log_count);
for (int i = 0; i < 3; i++)
TEST_ASSERT_EQUAL_UINT64(c.nodes[0].log[i].term, c.nodes[1].log[i].term);
TEST_ASSERT_EQUAL_INT(1, raft_sim_check_log_matching(&c));
}
/* ---------- Group A: Basic Election Correctness (task 5.1) ---------- */
/*
@ -5867,6 +5936,7 @@ void test_raft(void)
RUN_TEST(test_sim_9_2_divergent_suffix_truncation);
RUN_TEST(test_sim_9_3_drop_dup_reorder_convergence);
RUN_TEST(test_sim_9_4_chaotic_churn_g5_regression);
RUN_TEST(test_sim_9_5_node_restart_resumes_replication);
/* Group A: basic election correctness (task 5.1) */
RUN_TEST(test_proto_A1_single_node_self_elects);