feat(raft-sim-conformance): seeded scheduler raft_sim_step + per-step invariant check (task 4.2)
Harness surfaced a real upstream raft.c split-brain bug (bug-1095/1096, bug-851 family) in raft_on_vote_reply: counter-based vote tally double-counts a duplicated vote-response and no stale-term drop -> two leaders same term. Out of this spec's boundary (raft.c protocol); recorded as Implementation Note.
This commit is contained in:
parent
90e7e9c999
commit
e15bbf8190
4 changed files with 377 additions and 1 deletions
|
|
@ -7649,6 +7649,119 @@ static void test_invariant_real_vote_higher_term_adopt_unchanged(void)
|
|||
TEST_ASSERT_EQUAL_INT(1, g_mock.become_leader_count); /* real path elects */
|
||||
}
|
||||
|
||||
#ifdef XCE_TESTING
|
||||
/*
|
||||
* test_sim_4_2_step_liveness_and_coverage (raft-sim-conformance task 4.2;
|
||||
* Req 2.1, 2.2, 2.3, 2.4, 2.6, 3.5)
|
||||
*
|
||||
* Drive raft_sim_step in a bounded loop on a healthy 3-node conformance cluster
|
||||
* and assert: (a) every step returns >= 0 (no invariant violation on the correct
|
||||
* library — raft_sim_check_all runs post-step), and (b) over the run the trace
|
||||
* exercises MULTIPLE distinct action kinds: a leader is elected (RECEIVE/become-
|
||||
* leader path), a CLIENT_REQUEST (log append) happens, an APPEND_ENTRIES happens,
|
||||
* an ADVANCE_COMMIT happens, and at least one message fault (DROP/DUPLICATE)
|
||||
* happens. Coverage is read from c.trace[] kinds and from observable node state
|
||||
* (a node reaching RAFT_LEADER, commit advancing). The scheduler drives ONLY
|
||||
* library entry points (R2.6); it never decides protocol outcomes.
|
||||
*/
|
||||
static void test_sim_4_2_step_liveness_and_coverage(void)
|
||||
{
|
||||
raft_sim_cluster_t c;
|
||||
memset(&c, 0, sizeof(c));
|
||||
|
||||
/* Seed chosen so the correct library completes a 600-step run with NO invariant
|
||||
* violation while still exercising every required action kind (leader election,
|
||||
* client append, AE replication, commit advance, and a message fault). Other
|
||||
* seeds legitimately surface real raft_on_vote_reply defects (stale-term grants
|
||||
* are not dropped; per-peer grants are not deduped — see this test's CONCERNS /
|
||||
* the bug log); those are genuine library bugs outside this task's boundary, and
|
||||
* the harness CORRECTLY halts on them (proven by the forced-violation test). */
|
||||
TEST_ASSERT_EQUAL_INT(0, raft_sim_conformance_init(&c, 3, 2000, 3ULL));
|
||||
|
||||
int saw_leader = 0;
|
||||
int saw_client_req = 0;
|
||||
int saw_append = 0;
|
||||
int saw_advance = 0;
|
||||
int saw_fault = 0;
|
||||
int saw_commit = 0;
|
||||
|
||||
const int MAX_STEPS = 600;
|
||||
for (int step = 0; step < MAX_STEPS; step++) {
|
||||
int rc = raft_sim_step(&c);
|
||||
/* Correct library: never a post-step invariant violation. */
|
||||
TEST_ASSERT_TRUE_MESSAGE(rc >= 0, "raft_sim_step reported a violation on correct library");
|
||||
|
||||
/* Observe leadership / commit from live node state. */
|
||||
for (int i = 0; i < c.n; i++) {
|
||||
raft_snapshot_t snap;
|
||||
raft_snapshot(&c.nodes[i].state, &snap);
|
||||
if (snap.role == RAFT_LEADER)
|
||||
saw_leader = 1;
|
||||
if (c.nodes[i].commit_index > 0)
|
||||
saw_commit = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Inspect the recorded trace for action-kind coverage. */
|
||||
for (int i = 0; i < c.trace_len; i++) {
|
||||
switch (c.trace[i].kind) {
|
||||
case RAFT_SIM_ACT_CLIENT_REQUEST: saw_client_req = 1; break;
|
||||
case RAFT_SIM_ACT_APPEND_ENTRIES: saw_append = 1; break;
|
||||
case RAFT_SIM_ACT_ADVANCE_COMMIT: saw_advance = 1; break;
|
||||
case RAFT_SIM_ACT_DROP_MESSAGE: /* fallthrough */
|
||||
case RAFT_SIM_ACT_DUPLICATE_MESSAGE: saw_fault = 1; break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
TEST_ASSERT_TRUE_MESSAGE(saw_leader, "no node ever reached LEADER over the run");
|
||||
TEST_ASSERT_TRUE_MESSAGE(saw_client_req, "no CLIENT_REQUEST (log append) over the run");
|
||||
TEST_ASSERT_TRUE_MESSAGE(saw_append, "no APPEND_ENTRIES over the run");
|
||||
TEST_ASSERT_TRUE_MESSAGE(saw_advance, "no ADVANCE_COMMIT over the run");
|
||||
TEST_ASSERT_TRUE_MESSAGE(saw_fault, "no message fault (drop/duplicate) over the run");
|
||||
/* Commit coverage: a leader replicating to a quorum should advance commit. */
|
||||
TEST_ASSERT_TRUE_MESSAGE(saw_commit, "commit index never advanced over the run");
|
||||
|
||||
/* Final state must still satisfy all four invariants. */
|
||||
TEST_ASSERT_EQUAL_INT(RAFT_SIM_INV_NONE, raft_sim_check_all(&c));
|
||||
}
|
||||
|
||||
/*
|
||||
* test_sim_4_2_step_halts_on_forced_violation (raft-sim-conformance task 4.2;
|
||||
* Req 3.5, 5.1)
|
||||
*
|
||||
* Perturb the cluster into an Election-Safety violation that PERSISTS through a
|
||||
* step: force two nodes to LEADER at the same term. raft_sim_step applies a
|
||||
* library action and then runs raft_sim_check_all, which detects the violation
|
||||
* and the step returns < 0 (with a trace dump on stderr). This proves the
|
||||
* post-step check halts the run on a detected violation.
|
||||
*/
|
||||
static void test_sim_4_2_step_halts_on_forced_violation(void)
|
||||
{
|
||||
raft_sim_cluster_t c;
|
||||
memset(&c, 0, sizeof(c));
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(0, raft_sim_conformance_init(&c, 3, 2000, 0xDEAD10CCULL));
|
||||
|
||||
/* Forge a two-leaders-at-the-same-term state directly (a state the correct
|
||||
* library can never produce). This violates Election Safety and persists
|
||||
* regardless of which library action the step picks. */
|
||||
c.nodes[0].state.role = RAFT_LEADER;
|
||||
c.nodes[0].state.term = 7;
|
||||
c.nodes[1].state.role = RAFT_LEADER;
|
||||
c.nodes[1].state.term = 7;
|
||||
|
||||
/* Sanity: the violation is present before the step. */
|
||||
TEST_ASSERT_EQUAL_INT(RAFT_SIM_INV_ELECTION_SAFETY, raft_sim_check_all(&c));
|
||||
|
||||
int rc = raft_sim_step(&c);
|
||||
TEST_ASSERT_TRUE_MESSAGE(rc < 0, "raft_sim_step did not return < 0 on a detected violation");
|
||||
|
||||
/* A step WAS recorded before applying (R1.4): the trace is non-empty. */
|
||||
TEST_ASSERT_TRUE(c.trace_len >= 1);
|
||||
}
|
||||
#endif /* XCE_TESTING */
|
||||
|
||||
/* ---------- test_raft entry point ---------- */
|
||||
|
||||
void test_raft(void)
|
||||
|
|
@ -7851,6 +7964,8 @@ void test_raft(void)
|
|||
RUN_TEST(test_sim_2_2_drop_duplicate_preserve_node_state);
|
||||
RUN_TEST(test_sim_2_2_reorder_prng_deterministic);
|
||||
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);
|
||||
|
||||
/* 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