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

@ -455,6 +455,22 @@ void raft_snapshot(const raft_state_t *s, raft_snapshot_t *snap);
* failure traces. One raft_sim_action_t is recorded per step. */
#define RAFT_SIM_MAX_STEPS 1024
/* ---- deliberately-broken-build fault switch (raft-sim-conformance task 7.1) ----
* Runtime-togglable fault-injection flags, confined to this XCE_TESTING build and
* DEFAULT OFF (cluster.fault_inject is zero-initialized by the init routines, so
* neither legacy nor conformance runs are perturbed, and the default CI conformance
* sweep is unaffected — Req 7.3). The corruption lives ONLY in the harness ADAPTER
* callbacks (raft_sim.c); raft.c is never touched. Setting a flag reproduces a
* bug-851-class invariant divergence so the negative test (task 7.2) can assert that
* a broken adapter config FAILS an invariant while the correct config PASSES.
* - RAFT_SIM_BREAK_TERM_STAMP: on_persist_entry writes term 0 instead of the
* supplied term (emulates G1: entries carry term 0 so on_get_entry_term cannot
* distinguish same-index/different-term entries).
* - RAFT_SIM_BREAK_TRUNCATE: on_truncate_from is a no-op (emulates G3: a divergent
* suffix is never deleted). */
#define RAFT_SIM_BREAK_TERM_STAMP 0x1u
#define RAFT_SIM_BREAK_TRUNCATE 0x2u
/* Buffered RPC entry: one pending vote-request to route.
* last_log_term/last_log_index capture the CANDIDATE's advertised last-log tuple
* at SEND time (mirroring raft_sim_ae_t.leader_term) so the (lastLogTerm,
@ -633,6 +649,11 @@ typedef struct {
/* Replayable per-step action trace for failure dumps. */
raft_sim_action_t trace[RAFT_SIM_MAX_STEPS];
int trace_len;
/* Deliberately-broken-build fault switch (task 7.1). Bitmask of
* RAFT_SIM_BREAK_* flags consulted by the harness adapter callbacks via
* node->ctx. Zero (DEFAULT OFF) on init — legacy and conformance runs are
* unaffected unless a test explicitly sets it. */
unsigned fault_inject;
} raft_sim_cluster_t;
/*
@ -1094,6 +1115,16 @@ int raft_sim_scenario_figure8(raft_sim_cluster_t *c, int n, uint64_t seed);
*/
int raft_sim_scenario_stale_rejoin(raft_sim_cluster_t *c, int n, uint64_t seed);
/*
* raft_sim_set_fault_inject: set the cluster's deliberately-broken-build fault mask
* (raft-sim-conformance task 7.1). `flags` is a bitmask of RAFT_SIM_BREAK_* values;
* pass 0 to clear (restore correct adapter behavior). The flags are consulted only by
* the harness ADAPTER callbacks (raft_sim.c); raft.c is never modified. DEFAULT OFF —
* a cluster that never calls this behaves exactly as before. Provided so the negative
* test (task 7.2) can toggle the fault within a single binary.
*/
void raft_sim_set_fault_inject(raft_sim_cluster_t *c, unsigned flags);
#endif /* XCE_TESTING */
#endif /* RAFT_H */

View file

@ -106,6 +106,15 @@ static int sim_persist_entry(void *ctx, uint64_t index, uint64_t term,
if (index < 1 || index > RAFT_SIM_MAX_LOG)
return -1; /* out of fixed sim bounds */
/* Deliberately-broken-build fault switch (task 7.1): when RAFT_SIM_BREAK_TERM_STAMP
* is set on the cluster, stamp the entry with term 0 instead of `term`. This
* emulates bug-851 G1 (entries carry term 0 so on_get_entry_term can't distinguish
* same-index/different-term entries). Purely additive — DEFAULT OFF, so legacy and
* conformance runs that never set the flag are byte-identical to before. */
raft_sim_cluster_t *c = (raft_sim_cluster_t *)node->ctx;
if (c && (c->fault_inject & RAFT_SIM_BREAK_TERM_STAMP))
term = 0;
raft_sim_entry_t *slot = &node->log[index - 1];
slot->term = term;
@ -147,6 +156,15 @@ static int sim_truncate_from(void *ctx, uint64_t index)
if (index < 1)
return -1;
/* Deliberately-broken-build fault switch (task 7.1): when RAFT_SIM_BREAK_TRUNCATE
* is set on the cluster, make truncation a NO-OP so the divergent suffix is never
* deleted (emulates bug-851 G3). Returns 0 (success) without touching the log, so
* the library believes the truncate succeeded while the stale suffix lingers.
* Purely additive — DEFAULT OFF; runs that never set the flag are unaffected. */
raft_sim_cluster_t *c = (raft_sim_cluster_t *)node->ctx;
if (c && (c->fault_inject & RAFT_SIM_BREAK_TRUNCATE))
return 0;
for (uint64_t i = index; i <= (uint64_t)node->log_count; i++) {
if (i <= RAFT_SIM_MAX_LOG) {
raft_sim_entry_t *slot = &node->log[i - 1];
@ -2343,4 +2361,16 @@ int raft_sim_scenario_stale_rejoin(raft_sim_cluster_t *c, int n, uint64_t seed)
return sim_scen_check(c);
}
/*
* raft_sim_set_fault_inject: set the cluster's deliberately-broken-build fault mask
* (task 7.1). Consulted only by the harness adapter callbacks (sim_persist_entry /
* sim_truncate_from); raft.c is never touched. DEFAULT OFF — only a test that calls
* this enables the corruption, and passing 0 clears it again within the same binary.
*/
void raft_sim_set_fault_inject(raft_sim_cluster_t *c, unsigned flags)
{
if (c)
c->fault_inject = flags;
}
#endif /* XCE_TESTING */