test(raft-sim-conformance): randomized M-seed sweep + all scenarios, zero violations (task 6.1)

This commit is contained in:
Rongsong Shen 2026-06-24 15:59:22 +00:00
parent a2933c274d
commit 47f68032b7
2 changed files with 102 additions and 1 deletions

View file

@ -168,7 +168,7 @@
## 6. CI batch and randomized seed sweep
- [ ] 6.1 RED+GREEN: randomized M-seed sweep with zero violations
- [x] 6.1 RED+GREEN: randomized M-seed sweep with zero violations
- Test: run `raft_sim_run` for a fixed `max_steps` across M seeds (M ≥ configured minimum) plus all targeted scenarios; fail if any seed/scenario violates any invariant
- Implement the sweep harness in `test_raft.c`; bound `max_steps`, M, and array sizes so the target fits the library test-suite budget
- Observable completion: the full batch runs clean and within budget

View file

@ -8526,6 +8526,106 @@ static void test_sim_5_5_stale_rejoin(void)
"post-stale-rejoin cluster reports more than one leader (Election Safety)");
}
}
/*
* test_sim_6_1_seed_sweep (raft-sim-conformance task 6.1; Req 6.1, 6.2, 6.4)
*
* CI conformance batch: run the randomized action scheduler (raft_sim_run) across a
* configured minimum of seeds for several cluster sizes, then run ALL FIVE targeted
* scenarios across a handful of seeds each — asserting ZERO invariant violations
* anywhere (R6.2).
*
* raft_sim_run drives raft_sim_step up to RAFT_SIM_SWEEP_STEPS actions, halting and
* returning < 0 on the FIRST invariant violation (the step already dumps the
* replayable trace to stderr) and returning 0 on a clean run. The library is correct
* (bug-1095/1096 fixed), so the entire batch MUST be clean: any rc < 0 here is a REAL
* divergence — NOT something to mask by shrinking M or skipping the offending seed.
* The failure message names the exact (seed, n) so the failing run is reproducible
* (R5/6.2): re-run raft_sim_conformance_init(&c, n, 2000, seed) + raft_sim_run.
*
* Bounds (R6.4): RAFT_SIM_SWEEP_M seeds (the configured minimum) over a small set of
* 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.
*/
#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) */
static void test_sim_6_1_seed_sweep(void)
{
/* Cluster sizes swept for the randomized batch. Raft rejects even peer counts
* (no quorum benefit, raft_init -EINVAL), so only ODD sizes <= RAFT_SIM_MAX_NODES. */
static const int sizes[] = { 3, 5, 7 };
const int election_timeout = 2000;
/* --- Randomized seed sweep: M >= configured minimum seeds x each cluster size --- */
for (int s = 0; s < RAFT_SIM_SWEEP_M; s++) {
/* Deterministic, well-spread seeds derived from the index s (documented):
* a large odd multiplier + offset so consecutive seeds differ in many bits. */
uint64_t seed = (uint64_t)s * 0x9E3779B97F4A7C15ULL + 0xD1B54A32D192ED03ULL;
for (size_t z = 0; z < sizeof(sizes) / sizeof(sizes[0]); z++) {
int n = sizes[z];
raft_sim_cluster_t c;
memset(&c, 0, sizeof(c));
TEST_ASSERT_EQUAL_INT(0,
raft_sim_conformance_init(&c, n, election_timeout, seed));
int rc = raft_sim_run(&c, RAFT_SIM_SWEEP_STEPS);
/* Zero violations through the whole run. On failure, name (seed, n) so the
* run is reproducible — the step already dumped the replayable trace. */
if (rc != 0) {
char msg[160];
snprintf(msg, sizeof(msg),
"seed sweep surfaced invariant violation rc=%d at seed=%llu n=%d "
"(reproduce: raft_sim_conformance_init(&c,%d,%d,%lluULL); "
"raft_sim_run(&c,%d))",
rc, (unsigned long long)seed, n,
n, election_timeout, (unsigned long long)seed, RAFT_SIM_SWEEP_STEPS);
TEST_FAIL_MESSAGE(msg);
}
}
}
/* --- All five targeted scenarios over a handful of seeds each (R6.2). Each
* scenario inits the caller's cluster internally from (n, seed) and returns 0 on
* zero violations, < 0 (dumping its trace) on any violation. --- */
static const uint64_t scen_seeds[] = { 1ULL, 3ULL, 7ULL, 0x4A33ULL, 0xBEEFULL };
for (size_t k = 0; k < sizeof(scen_seeds) / sizeof(scen_seeds[0]); k++) {
uint64_t seed = scen_seeds[k];
/* n chosen per seed index, alternating 3- and 5-node clusters. */
int n = (k % 2 == 0) ? 3 : 5;
struct { const char *name; int (*fn)(raft_sim_cluster_t *, int, uint64_t); } scenarios[] = {
{ "partition_heal", raft_sim_scenario_partition_heal },
{ "restart_churn", raft_sim_scenario_restart_churn },
{ "drop_dup_reorder",raft_sim_scenario_drop_dup_reorder},
{ "figure8", raft_sim_scenario_figure8 },
{ "stale_rejoin", raft_sim_scenario_stale_rejoin },
};
for (size_t i = 0; i < sizeof(scenarios) / sizeof(scenarios[0]); i++) {
raft_sim_cluster_t c;
memset(&c, 0, sizeof(c));
int rc = scenarios[i].fn(&c, n, seed);
if (rc != 0) {
char msg[192];
snprintf(msg, sizeof(msg),
"scenario '%s' surfaced violation rc=%d at seed=%lluULL n=%d "
"(reproduce: raft_sim_scenario_%s(&c,%d,%lluULL))",
scenarios[i].name, rc, (unsigned long long)seed, n,
scenarios[i].name, n, (unsigned long long)seed);
TEST_FAIL_MESSAGE(msg);
}
}
}
}
#endif /* XCE_TESTING */
/* ---------- test_raft entry point ---------- */
@ -8741,6 +8841,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_6_1_seed_sweep);
/* Group A: basic election correctness (task 5.1) */
RUN_TEST(test_proto_A1_single_node_self_elects);