feat(raft-sim-conformance): runtime fault-injection switch (G1 term-stamp, G3 truncate) (task 7.1)

This commit is contained in:
Rongsong Shen 2026-06-24 16:15:13 +00:00
parent 7087dc19d9
commit 62523892d9
4 changed files with 156 additions and 1 deletions

View file

@ -8547,6 +8547,99 @@ static void test_sim_5_5_stale_rejoin(void)
* cluster sizes, each capped at RAFT_SIM_SWEEP_STEPS steps; the harness is fixed-array
* (no allocation) so the whole batch stays within the lib test-suite time budget.
*/
/*
* test_sim_7_1_fault_switch_mechanism (raft-sim-conformance task 7.1; Req 7.1, 7.3):
* the deliberately-broken-build fault switch is RUNTIME-togglable, DEFAULT OFF, and
* observably corrupts the harness adapter (term->0 / truncate no-op) only when set.
* This is the MECHANISM check; the full both-directions invariant-violation demo is
* task 7.2. Single-node conformance cluster so raft_start_election forces LEADER
* without depending on bag delivery, mirroring test_sim_conformance_init_append_readback.
*/
static void test_sim_7_1_fault_switch_mechanism(void)
{
/* ---- DEFAULT OFF: the switch does not perturb a correct run ---- */
{
raft_sim_cluster_t c;
memset(&c, 0, sizeof(c));
TEST_ASSERT_EQUAL_INT(0, raft_sim_conformance_init(&c, 1, 2000, 0x7A17ULL));
/* fault_inject is zero-initialized -> OFF by default. */
TEST_ASSERT_EQUAL_UINT(0u, c.fault_inject);
raft_start_election(&c.nodes[0].state);
TEST_ASSERT_EQUAL_INT(RAFT_LEADER, c.nodes[0].state.role);
uint64_t value = 0xABCDEF0011223344ULL;
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_UINT64(1, out_index);
/* With the switch OFF the persisted term equals the leader's term (not 0). */
raft_sim_node_t *L = &c.nodes[0];
TEST_ASSERT_EQUAL_UINT64(L->state.term, L->log[0].term);
TEST_ASSERT_NOT_EQUAL(0, L->log[0].term);
}
/* ---- RAFT_SIM_BREAK_TERM_STAMP: on_persist_entry writes term 0 (emulates G1) ---- */
{
raft_sim_cluster_t c;
memset(&c, 0, sizeof(c));
TEST_ASSERT_EQUAL_INT(0, raft_sim_conformance_init(&c, 1, 2000, 0x7A17ULL));
raft_sim_set_fault_inject(&c, RAFT_SIM_BREAK_TERM_STAMP);
TEST_ASSERT_EQUAL_UINT(RAFT_SIM_BREAK_TERM_STAMP, c.fault_inject);
raft_start_election(&c.nodes[0].state);
TEST_ASSERT_EQUAL_INT(RAFT_LEADER, c.nodes[0].state.role);
uint64_t value = 0xABCDEF0011223344ULL;
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_UINT64(1, out_index);
raft_sim_node_t *L = &c.nodes[0];
/* The leader's term is non-zero, yet the adapter stamped the entry term 0. */
TEST_ASSERT_NOT_EQUAL(0, L->state.term);
TEST_ASSERT_EQUAL_UINT64(0, L->log[0].term);
/* on_get_entry_term reads back the corrupted term (0 => indistinguishable). */
TEST_ASSERT_EQUAL_UINT64(0, L->cbs.on_get_entry_term(L, 1));
/* Clearing the flag restores correct stamping within the SAME binary. */
raft_sim_set_fault_inject(&c, 0);
TEST_ASSERT_EQUAL_UINT(0u, c.fault_inject);
uint64_t value2 = 0x55667788AABBCCDDULL;
TEST_ASSERT_EQUAL_INT(RAFT_OK,
raft_log_append(&c.nodes[0].state, &value2, sizeof(value2), &out_index));
TEST_ASSERT_EQUAL_UINT64(2, out_index);
TEST_ASSERT_EQUAL_UINT64(L->state.term, L->log[1].term); /* term again correct */
TEST_ASSERT_NOT_EQUAL(0, L->log[1].term);
}
/* ---- RAFT_SIM_BREAK_TRUNCATE: on_truncate_from is a no-op (emulates G3) ---- */
{
raft_sim_cluster_t c;
memset(&c, 0, sizeof(c));
TEST_ASSERT_EQUAL_INT(0, raft_sim_conformance_init(&c, 1, 2000, 0x7A17ULL));
raft_sim_node_t *L = &c.nodes[0];
/* Seed two entries directly via the persist adapter (term != 0). */
uint64_t e1 = 0x1111111111111111ULL, e2 = 0x2222222222222222ULL;
TEST_ASSERT_EQUAL_INT(0, L->cbs.on_persist_entry(L, 1, 7, &e1, sizeof(e1)));
TEST_ASSERT_EQUAL_INT(0, L->cbs.on_persist_entry(L, 2, 7, &e2, sizeof(e2)));
TEST_ASSERT_EQUAL_INT(2, L->log_count);
/* With the switch OFF, truncate-from index 2 drops the suffix. */
TEST_ASSERT_EQUAL_INT(0, L->cbs.on_truncate_from(L, 2));
TEST_ASSERT_EQUAL_INT(1, L->log_count);
/* Re-seed, then with the switch ON the same truncate is a no-op: suffix stays. */
TEST_ASSERT_EQUAL_INT(0, L->cbs.on_persist_entry(L, 2, 7, &e2, sizeof(e2)));
TEST_ASSERT_EQUAL_INT(2, L->log_count);
raft_sim_set_fault_inject(&c, RAFT_SIM_BREAK_TRUNCATE);
TEST_ASSERT_EQUAL_INT(0, L->cbs.on_truncate_from(L, 2)); /* reports success */
TEST_ASSERT_EQUAL_INT(2, L->log_count); /* but suffix lingers */
TEST_ASSERT_EQUAL_UINT64(7, L->log[1].term); /* entry 2 still present */
}
}
#define RAFT_SIM_SWEEP_M 32 /* configured minimum number of randomized seeds (R6.2) */
#define RAFT_SIM_SWEEP_STEPS 400 /* per-run step cap — bounded for the suite budget (R6.4) */
@ -8841,6 +8934,7 @@ void test_raft(void)
RUN_TEST(test_sim_5_3_drop_dup_reorder);
RUN_TEST(test_sim_5_4_figure8);
RUN_TEST(test_sim_5_5_stale_rejoin);
RUN_TEST(test_sim_7_1_fault_switch_mechanism);
RUN_TEST(test_sim_6_1_seed_sweep);
/* Group A: basic election correctness (task 5.1) */