feat(raft-sim-conformance): action trace recorder + raft_sim_dump_trace (task 4.1)

This commit is contained in:
Rongsong Shen 2026-06-24 13:10:31 +00:00
parent f0be46fb32
commit 90e7e9c999
4 changed files with 185 additions and 1 deletions

View file

@ -1,6 +1,7 @@
#include <errno.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unity/unity.h>
@ -5177,6 +5178,96 @@ static void test_sim_2_2_reorder_prng_deterministic(void)
TEST_ASSERT_TRUE(raft_sim_bag_pick(&a) < 0);
}
/*
* test_sim_4_1_trace_record_and_dump (raft-sim-conformance task 4.1; Req 1.4, 5.1, 5.2, 5.4)
*
* Observable completion for the action trace recorder + dumper:
* - raft_sim_trace_record appends each action to c->trace[] in ORDER and bumps
* trace_len; the recorded fields round-trip (R1.4).
* - raft_sim_dump_trace prints seed, node count, and the failing-invariant NAME,
* plus one human-readable line per recorded action containing the kind name,
* actor, and index (R5.1, R5.2).
* - Quiet-on-success (R5.4): with failing==RAFT_SIM_INV_NONE and RAFT_SIM_VERBOSE
* unset, the dumper writes nothing.
*
* The scheduler (raft_sim_step, task 4.2) does not exist yet, so the recorder is
* driven directly with a fixed hand-built action sequence.
*/
static void test_sim_4_1_trace_record_and_dump(void)
{
raft_sim_cluster_t c;
memset(&c, 0, sizeof(c));
c.seed = 0xD15EA5EULL;
c.n = 3;
/* A fixed, ordered action sequence: TIMEOUT on node 1, CLIENT_REQUEST on node 0,
* RECEIVE of bag message index 3. */
raft_sim_action_t a0 = { RAFT_SIM_ACT_TIMEOUT, 1, -1, 7, 0 };
raft_sim_action_t a1 = { RAFT_SIM_ACT_CLIENT_REQUEST, 0, -1, 7, 4 };
raft_sim_action_t a2 = { RAFT_SIM_ACT_RECEIVE, 2, 3, 7, 4 };
raft_sim_trace_record(&c, a0);
raft_sim_trace_record(&c, a1);
raft_sim_trace_record(&c, a2);
/* Recorded in order, length matches, fields round-trip (R1.4). */
TEST_ASSERT_EQUAL_INT(3, c.trace_len);
TEST_ASSERT_EQUAL_INT(RAFT_SIM_ACT_TIMEOUT, c.trace[0].kind);
TEST_ASSERT_EQUAL_INT(1, c.trace[0].actor);
TEST_ASSERT_EQUAL_INT(RAFT_SIM_ACT_CLIENT_REQUEST, c.trace[1].kind);
TEST_ASSERT_EQUAL_INT(0, c.trace[1].actor);
TEST_ASSERT_EQUAL_UINT64(4, c.trace[1].index);
TEST_ASSERT_EQUAL_INT(RAFT_SIM_ACT_RECEIVE, c.trace[2].kind);
TEST_ASSERT_EQUAL_INT(2, c.trace[2].actor);
TEST_ASSERT_EQUAL_INT(3, c.trace[2].msg_index);
/* Dump to an in-memory FILE* and assert the text carries seed, node count, the
* failing-invariant name, and a recognizable token per action (R5.1, R5.2). */
char buf[4096];
memset(buf, 0, sizeof(buf));
FILE *fp = fmemopen(buf, sizeof(buf), "w");
TEST_ASSERT_NOT_NULL(fp);
raft_sim_dump_trace(&c, fp, RAFT_SIM_INV_LOG_MATCHING);
fclose(fp);
TEST_ASSERT_TRUE(strlen(buf) > 0);
TEST_ASSERT_NOT_NULL(strstr(buf, "log-matching")); /* failing invariant name */
TEST_ASSERT_NOT_NULL(strstr(buf, "timeout")); /* action kind name */
TEST_ASSERT_NOT_NULL(strstr(buf, "client-request"));
TEST_ASSERT_NOT_NULL(strstr(buf, "receive"));
{
/* seed printed (hex, matching the dumper's own format) and node count. */
char seedtok[32];
snprintf(seedtok, sizeof(seedtok), "%llu",
(unsigned long long)c.seed);
char ntok[16];
snprintf(ntok, sizeof(ntok), "%d", c.n);
TEST_ASSERT_NOT_NULL(strstr(buf, seedtok));
TEST_ASSERT_NOT_NULL(strstr(buf, ntok));
}
/* Quiet-on-success (R5.4): no failure + RAFT_SIM_VERBOSE unset => nothing written. */
char *saved_verbose = getenv("RAFT_SIM_VERBOSE");
char saved_copy[256];
int had_verbose = (saved_verbose != NULL);
if (had_verbose) {
strncpy(saved_copy, saved_verbose, sizeof(saved_copy) - 1);
saved_copy[sizeof(saved_copy) - 1] = '\0';
}
unsetenv("RAFT_SIM_VERBOSE");
char qbuf[256];
memset(qbuf, 0, sizeof(qbuf));
FILE *qfp = fmemopen(qbuf, sizeof(qbuf), "w");
TEST_ASSERT_NOT_NULL(qfp);
raft_sim_dump_trace(&c, qfp, RAFT_SIM_INV_NONE);
fclose(qfp);
TEST_ASSERT_EQUAL_INT(0, (int)strlen(qbuf)); /* silent on success */
if (had_verbose)
setenv("RAFT_SIM_VERBOSE", saved_copy, 1);
}
/* ---------- Group A: Basic Election Correctness (task 5.1) ---------- */
/*
@ -7759,6 +7850,7 @@ void test_raft(void)
/* bag-only Drop/Duplicate/Reorder mutate only the bag (task 2.2) */
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);
/* Group A: basic election correctness (task 5.1) */
RUN_TEST(test_proto_A1_single_node_self_elects);