forked from lei.chen/myxce
test(raft-sim-conformance): conformance Restart persistence test (task 4.3)
This commit is contained in:
parent
b5b7e01b92
commit
5f67be4fa3
2 changed files with 117 additions and 1 deletions
|
|
@ -113,7 +113,7 @@
|
|||
- _Boundary: raft_sim.c, test_raft.c_
|
||||
- _Depends: 2.2, 3.5, 4.1_
|
||||
|
||||
- [ ] 4.3 RED+GREEN: Restart preserves persisted `term`/`votedFor`/log
|
||||
- [x] 4.3 RED+GREEN: Restart preserves persisted `term`/`votedFor`/log
|
||||
- Test: a Restart action re-inits a node while keeping `node.log[]`, recovered `log_len`, and last-persisted `(term, votedFor)`; volatile state resets per `raft.tla` `Restart`
|
||||
- Implement the Restart branch (feed `initial_log_len` + restored `(term, votedFor)` into `raft_init`)
|
||||
- Observable completion: a restarted node retains its log and term; no invariant violation results from the restart alone
|
||||
|
|
@ -227,3 +227,11 @@
|
|||
legitimate scenario surfaces it, report BLOCKED with the upstream cause rather than
|
||||
working around it. Seed 3 is clean over 600 steps and is used only to prove the 4.2
|
||||
scheduler mechanism reaches all action kinds.
|
||||
- **UPDATE (post-4.2): bug-1095/1096 FIXED in raft.c** (commit `fix(raft): vote-reply
|
||||
per-peer dedup + stale-term drop`). `raft_on_vote_reply` now tracks grants in a per-peer
|
||||
`vote_replies[]` set (idempotent under DuplicateMessage) and drops stale-term replies
|
||||
(DropStaleResponse), mirroring the existing pre-vote idiom; 47 raft / 264 lib tests pass.
|
||||
The library is now correct, so downstream tasks (5.x scenarios, 6.1 M-seed sweep, 7.2
|
||||
correct-direction) CAN and MUST assert zero invariant violations on the correct build.
|
||||
The user authorized this cross-boundary fix. The deliberately-broken-build fault switch
|
||||
(task 7) remains the way to deliberately re-introduce a divergence for the negative test.
|
||||
|
|
|
|||
|
|
@ -7854,6 +7854,113 @@ static void test_sim_4_2_step_halts_on_forced_violation(void)
|
|||
/* A step WAS recorded before applying (R1.4): the trace is non-empty. */
|
||||
TEST_ASSERT_TRUE(c.trace_len >= 1);
|
||||
}
|
||||
|
||||
/*
|
||||
* test_sim_4_3_restart_preserves_persistence (raft-sim-conformance task 4.3; Req 2.1)
|
||||
*
|
||||
* Conformance-harness Restart semantics (raft.tla `Restart`): a Restart re-inits a
|
||||
* node while keeping its DURABLE state — node->log[] entries, the recovered log_len
|
||||
* (== log_count), and the last-persisted (term, votedFor) — and RESETS the VOLATILE
|
||||
* consensus state (role -> FOLLOWER, votes, election bookkeeping, per-peer leader
|
||||
* progress). raft.tla resets commitIndex to 0 on restart; XCE deliberately persists
|
||||
* the committed LSN and restores it (a documented XCE durability deviation), so the
|
||||
* restarted node's commit_index is the restored durable value, never a regression.
|
||||
*
|
||||
* The Restart IMPL (raft_sim_restart_node) pre-exists from raft-commit-safety, so the
|
||||
* "RED" here is the ABSENCE of a conformance-restart test that pins these semantics.
|
||||
* Every assertion below names a SPECIFIC retained/reset value so a regression that
|
||||
* wiped the log, dropped the persisted term/votedFor, or failed to reset volatile
|
||||
* state would fail this test:
|
||||
* - a log-wiping restart fails the log_count / per-entry term assertions;
|
||||
* - a term/votedFor-losing restart fails the persisted (term, voted_for) assertions;
|
||||
* - a volatile-state-leaking restart (e.g. staying LEADER) fails the FOLLOWER /
|
||||
* votes_granted / election_decided assertions.
|
||||
* The post-restart raft_sim_check_all == RAFT_SIM_INV_NONE proves the restart alone
|
||||
* produces no invariant violation.
|
||||
*/
|
||||
static void test_sim_4_3_restart_preserves_persistence(void)
|
||||
{
|
||||
raft_sim_cluster_t c;
|
||||
memset(&c, 0, sizeof(c));
|
||||
|
||||
/* 3-node conformance cluster so the restart helper restores via raft_init with
|
||||
* the conformance callback set still wired (it preserves node->cbs). */
|
||||
TEST_ASSERT_EQUAL_INT(0, raft_sim_conformance_init(&c, 3, 2000, 0x4A33ULL));
|
||||
|
||||
/* Drive node 0 to LEADER so it holds genuine VOLATILE state (LEADER role, a
|
||||
* decided election, per-peer next/match progress) that the restart must reset,
|
||||
* plus a real persisted (term, voted_for) — its own self-vote in the won term. */
|
||||
raft_start_election(&c.nodes[0].state);
|
||||
for (int g = 0; g < 64 && c.bag_count > 0; g++)
|
||||
raft_sim_bag_deliver(&c, 0); /* drain election traffic; node 0 wins */
|
||||
TEST_ASSERT_EQUAL_INT(RAFT_LEADER, c.nodes[0].state.role);
|
||||
|
||||
/* Leader appends a few client entries so node 0 owns a NON-EMPTY durable log. */
|
||||
for (int e = 0; e < 3; e++) {
|
||||
uint64_t value = 0xC0DE0000ULL + (uint64_t)e;
|
||||
uint64_t out_index = 0;
|
||||
TEST_ASSERT_EQUAL_INT(RAFT_OK,
|
||||
raft_log_append(&c.nodes[0].state, &value, sizeof(value), &out_index));
|
||||
}
|
||||
TEST_ASSERT_EQUAL_INT(3, c.nodes[0].log_count);
|
||||
|
||||
/* Snapshot the DURABLE state a correct Restart must retain, plus a copy of the
|
||||
* log bytes/terms so a wipe is detectable byte-for-byte. */
|
||||
raft_sim_node_t *node = &c.nodes[0];
|
||||
uint64_t persisted_term = node->state.term;
|
||||
int persisted_voted_for = node->state.voted_for;
|
||||
int durable_log_count = node->log_count;
|
||||
uint64_t durable_commit = node->state.commit_index;
|
||||
raft_sim_entry_t durable_log[3];
|
||||
for (int i = 0; i < 3; i++)
|
||||
durable_log[i] = node->log[i];
|
||||
|
||||
/* Sanity: node 0 self-voted in the won term (a real persisted vote to retain). */
|
||||
TEST_ASSERT_EQUAL_INT(0, persisted_voted_for);
|
||||
TEST_ASSERT_TRUE(persisted_term > 0);
|
||||
/* Sanity: VOLATILE leader bookkeeping is genuinely set pre-restart, so the reset
|
||||
* assertions below are meaningful (not vacuously already-zero). */
|
||||
TEST_ASSERT_TRUE(node->state.election_decided == 1);
|
||||
TEST_ASSERT_TRUE(node->state.votes_granted >= 1);
|
||||
|
||||
/* ---- RESTART (raft.tla Restart) ---- */
|
||||
TEST_ASSERT_EQUAL_INT(0, raft_sim_restart_node(&c, 0));
|
||||
|
||||
/* DURABLE state RETAINED: log[] bytes/terms, log_count/log_len, (term, votedFor),
|
||||
* and the restored committed LSN (XCE durability deviation — never regresses). */
|
||||
TEST_ASSERT_EQUAL_INT(durable_log_count, node->log_count);
|
||||
TEST_ASSERT_EQUAL_UINT64((uint64_t)durable_log_count, node->state.log_len);
|
||||
for (int i = 0; i < durable_log_count; i++) {
|
||||
TEST_ASSERT_EQUAL_UINT64(durable_log[i].term, node->log[i].term);
|
||||
TEST_ASSERT_EQUAL_UINT(durable_log[i].len, node->log[i].len);
|
||||
TEST_ASSERT_EQUAL_INT(0,
|
||||
memcmp(durable_log[i].bytes, node->log[i].bytes, durable_log[i].len));
|
||||
}
|
||||
TEST_ASSERT_EQUAL_UINT64(persisted_term, node->state.term);
|
||||
TEST_ASSERT_EQUAL_INT(persisted_voted_for, node->state.voted_for);
|
||||
TEST_ASSERT_EQUAL_UINT64(durable_commit, node->state.commit_index);
|
||||
|
||||
/* VOLATILE state RESET to a fresh FOLLOWER: role, votes, election bookkeeping,
|
||||
* and per-peer leader progress all cleared by the re-init. */
|
||||
TEST_ASSERT_EQUAL_INT(RAFT_FOLLOWER, node->state.role);
|
||||
TEST_ASSERT_EQUAL_INT(0, node->state.votes_granted);
|
||||
TEST_ASSERT_EQUAL_INT(0, node->state.election_decided);
|
||||
TEST_ASSERT_EQUAL_INT(0, node->state.replies_received);
|
||||
TEST_ASSERT_EQUAL_INT(0, node->state.pre_vote_in_progress);
|
||||
for (int p = 0; p < node->state.n_peers; p++) {
|
||||
/* raft_init seeds next_index to log_len+1 and zeroes match_index (the
|
||||
* leader-progress reset). match_index back to 0 proves the leader progress
|
||||
* was discarded. */
|
||||
TEST_ASSERT_EQUAL_UINT64(0, node->state.match_index[p]);
|
||||
}
|
||||
|
||||
/* Partition flag and ctx wiring are preserved across the restart. */
|
||||
TEST_ASSERT_EQUAL_INT(0, node->partition);
|
||||
TEST_ASSERT_TRUE(node->ctx == &c);
|
||||
|
||||
/* The restart ALONE produces no invariant violation (Req 2.1). */
|
||||
TEST_ASSERT_EQUAL_INT(RAFT_SIM_INV_NONE, raft_sim_check_all(&c));
|
||||
}
|
||||
#endif /* XCE_TESTING */
|
||||
|
||||
/* ---------- test_raft entry point ---------- */
|
||||
|
|
@ -8062,6 +8169,7 @@ void test_raft(void)
|
|||
RUN_TEST(test_sim_4_1_trace_record_and_dump);
|
||||
RUN_TEST(test_sim_4_2_step_liveness_and_coverage);
|
||||
RUN_TEST(test_sim_4_2_step_halts_on_forced_violation);
|
||||
RUN_TEST(test_sim_4_3_restart_preserves_persistence);
|
||||
|
||||
/* Group A: basic election correctness (task 5.1) */
|
||||
RUN_TEST(test_proto_A1_single_node_self_elects);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue