feat(raft-sim-conformance): optional TLC-consumable trace export, default-off (task 8.1)

This commit is contained in:
Rongsong Shen 2026-06-24 16:49:36 +00:00
parent ffeb7c2cf5
commit 95629797f8
4 changed files with 242 additions and 1 deletions

View file

@ -5362,6 +5362,98 @@ static void test_sim_4_1_trace_record_and_dump(void)
setenv("RAFT_SIM_VERBOSE", saved_copy, 1);
}
/*
* test_sim_8_1_export_trace_named_and_gated (raft-sim-conformance task 8.1;
* Req 8.1, 8.2, 8.3)
*
* raft_sim_export_trace emits a STABLE, line-oriented, machine-readable record of
* the harness's tracked raft.tla variables, gated OFF by default via the
* RAFT_SIM_EXPORT environment variable (mirroring the RAFT_SIM_VERBOSE idiom):
* - Export ON (RAFT_SIM_EXPORT set): a post-run cluster's export CONTAINS the
* spec-named tokens (currentTerm, state, log, commitIndex, messages — R8.2),
* the seed/n header, and at least one action name from the recorded trace
* (R8.1). Two exports of the same post-run state are byte-identical (stable).
* - Default OFF (RAFT_SIM_EXPORT unset): the export emits NOTHING (R8.3).
* The env var is saved and restored around the test so the rest of the suite is
* unperturbed.
*/
static void test_sim_8_1_export_trace_named_and_gated(void)
{
/* Save + clear the gate so we control it deterministically. */
char *saved_export = getenv("RAFT_SIM_EXPORT");
char saved_copy[256];
int had_export = (saved_export != NULL);
if (had_export) {
strncpy(saved_copy, saved_export, sizeof(saved_copy) - 1);
saved_copy[sizeof(saved_copy) - 1] = '\0';
}
/* Drive a short deterministic run so trace[] holds actions and nodes carry
* observable state (term/log/commit). Seed 3 is the one the 4.2 test proves
* completes a clean run on the correct library. */
raft_sim_cluster_t c;
memset(&c, 0, sizeof(c));
TEST_ASSERT_EQUAL_INT(0, raft_sim_conformance_init(&c, 3, 2000, 3ULL));
for (int step = 0; step < 80; step++) {
int rc = raft_sim_step(&c);
TEST_ASSERT_TRUE(rc >= 0);
}
TEST_ASSERT_TRUE(c.trace_len > 0); /* the run recorded actions to export */
/* ---- Export ON (R8.1, R8.2) ---- */
setenv("RAFT_SIM_EXPORT", "1", 1);
char buf[8192];
memset(buf, 0, sizeof(buf));
FILE *fp = fmemopen(buf, sizeof(buf), "w");
TEST_ASSERT_NOT_NULL(fp);
raft_sim_export_trace(&c, fp);
fclose(fp);
TEST_ASSERT_TRUE(strlen(buf) > 0);
/* raft.tla-named tracked variables (R8.2). */
TEST_ASSERT_NOT_NULL(strstr(buf, "currentTerm"));
TEST_ASSERT_NOT_NULL(strstr(buf, "state"));
TEST_ASSERT_NOT_NULL(strstr(buf, "log"));
TEST_ASSERT_NOT_NULL(strstr(buf, "commitIndex"));
TEST_ASSERT_NOT_NULL(strstr(buf, "messages"));
/* seed/n header (R8.1). */
{
char seedtok[32];
snprintf(seedtok, sizeof(seedtok), "%llu", (unsigned long long)c.seed);
TEST_ASSERT_NOT_NULL(strstr(buf, seedtok));
TEST_ASSERT_NOT_NULL(strstr(buf, "seed"));
TEST_ASSERT_NOT_NULL(strstr(buf, "n="));
}
/* At least one raft.tla Next action name appears (R8.1/R8.2). The scheduler
* always drives Timeout, so it is reliably present. */
TEST_ASSERT_NOT_NULL(strstr(buf, "Timeout"));
/* Stability: a second export of the same post-run state is byte-identical. */
char buf2[8192];
memset(buf2, 0, sizeof(buf2));
FILE *fp2 = fmemopen(buf2, sizeof(buf2), "w");
TEST_ASSERT_NOT_NULL(fp2);
raft_sim_export_trace(&c, fp2);
fclose(fp2);
TEST_ASSERT_EQUAL_INT(0, strcmp(buf, buf2)); /* deterministic / stable */
/* ---- Default OFF (R8.3): gate unset => emit NOTHING ---- */
unsetenv("RAFT_SIM_EXPORT");
char qbuf[256];
memset(qbuf, 0, sizeof(qbuf));
FILE *qfp = fmemopen(qbuf, sizeof(qbuf), "w");
TEST_ASSERT_NOT_NULL(qfp);
raft_sim_export_trace(&c, qfp);
fclose(qfp);
TEST_ASSERT_EQUAL_INT(0, (int)strlen(qbuf)); /* silent unless explicitly enabled */
if (had_export)
setenv("RAFT_SIM_EXPORT", saved_copy, 1);
else
unsetenv("RAFT_SIM_EXPORT");
}
/* ---------- Group A: Basic Election Correctness (task 5.1) ---------- */
/*
@ -9020,6 +9112,7 @@ 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_8_1_export_trace_named_and_gated);
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);