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

@ -869,6 +869,38 @@ raft_sim_invariant_t raft_sim_check_all(const raft_sim_cluster_t *c);
*/
const char *raft_sim_invariant_name(raft_sim_invariant_t inv);
/*
* raft_sim_action_kind_name: human-readable name for a raft_sim_action_kind_t, for
* the trace dumper (task 4.1) and scheduler diagnostics. Returns a static string;
* never NULL (an out-of-range value maps to "unknown").
*/
const char *raft_sim_action_kind_name(raft_sim_action_kind_t kind);
/*
* raft_sim_trace_record: append one chosen action to the cluster's replayable
* action trace (c->trace[]) at c->trace_len, then advance trace_len (Req 1.4 — the
* scheduler records the chosen action BEFORE applying it). The recorder only stores
* the action; the caller (scheduler, task 4.2) is responsible for recording each
* action just before it is applied. Bounded by RAFT_SIM_MAX_STEPS: on overflow it
* fails loud (assert) rather than writing out of bounds or silently dropping.
*/
void raft_sim_trace_record(raft_sim_cluster_t *c, raft_sim_action_t action);
/*
* raft_sim_dump_trace: print the replayable failure trace to `fp` (Req 5.1, 5.2):
* the run `seed`, the node count `n`, the failing-invariant NAME (via
* raft_sim_invariant_name(failing)), and every recorded action in c->trace[0..
* trace_len) on its own human-readable line (kind name, actor, msg_index, term,
* index).
*
* Quiet-on-success (Req 5.4): when failing == RAFT_SIM_INV_NONE and the
* RAFT_SIM_VERBOSE environment variable is unset, the dumper writes NOTHING. A
* caller that wants the trace on a clean run sets RAFT_SIM_VERBOSE; a real failure
* (failing != RAFT_SIM_INV_NONE) always dumps regardless of the env var.
*/
void raft_sim_dump_trace(const raft_sim_cluster_t *c, FILE *fp,
raft_sim_invariant_t failing);
/*
* raft_sim_advance_commit: drive raft_advance_commit on node_idx (task 6.1).
*

View file

@ -1,6 +1,8 @@
#ifdef XCE_TESTING
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "raft.h"
@ -937,6 +939,64 @@ const char *raft_sim_invariant_name(raft_sim_invariant_t inv)
return "unknown";
}
const char *raft_sim_action_kind_name(raft_sim_action_kind_t kind)
{
switch (kind) {
case RAFT_SIM_ACT_RESTART: return "restart";
case RAFT_SIM_ACT_TIMEOUT: return "timeout";
case RAFT_SIM_ACT_REQUEST_VOTE: return "request-vote";
case RAFT_SIM_ACT_BECOME_LEADER: return "become-leader";
case RAFT_SIM_ACT_CLIENT_REQUEST: return "client-request";
case RAFT_SIM_ACT_APPEND_ENTRIES: return "append-entries";
case RAFT_SIM_ACT_ADVANCE_COMMIT: return "advance-commit";
case RAFT_SIM_ACT_RECEIVE: return "receive";
case RAFT_SIM_ACT_DUPLICATE_MESSAGE: return "duplicate-message";
case RAFT_SIM_ACT_DROP_MESSAGE: return "drop-message";
}
return "unknown";
}
/*
* raft_sim_trace_record: append the chosen action to c->trace[] at trace_len and
* advance trace_len (Req 1.4 ordering — the scheduler calls this BEFORE applying the
* action). RAFT_SIM_MAX_STEPS bounds the buffer: an overflow is a harness bug (the
* step budget is fixed), so fail loud via assert rather than writing out of bounds
* or silently dropping the action.
*/
void raft_sim_trace_record(raft_sim_cluster_t *c, raft_sim_action_t action)
{
/* Fail loud on overflow — a full trace means the run exceeded its fixed step
* budget, which is a harness bug, not a silently-droppable condition. */
assert(c->trace_len < RAFT_SIM_MAX_STEPS);
if (c->trace_len >= RAFT_SIM_MAX_STEPS)
return; /* belt-and-suspenders: never write OOB if assert is compiled out */
c->trace[c->trace_len++] = action;
}
void raft_sim_dump_trace(const raft_sim_cluster_t *c, FILE *fp,
raft_sim_invariant_t failing)
{
/* Quiet-on-success (Req 5.4): a clean run dumps nothing unless verbose tracing
* was explicitly requested. A real failure always dumps. */
if (failing == RAFT_SIM_INV_NONE && getenv("RAFT_SIM_VERBOSE") == NULL)
return;
fprintf(fp, "raft-sim trace: seed=%llu n=%d failing-invariant=%s steps=%d\n",
(unsigned long long)c->seed, c->n,
raft_sim_invariant_name(failing), c->trace_len);
for (int i = 0; i < c->trace_len; i++) {
const raft_sim_action_t *a = &c->trace[i];
/* One human-readable line per action (Req 5.2): kind name, actor,
* msg_index, term, index. */
fprintf(fp,
" [%d] %s actor=%d msg_index=%d term=%llu index=%llu\n",
i, raft_sim_action_kind_name(a->kind), a->actor, a->msg_index,
(unsigned long long)a->term, (unsigned long long)a->index);
}
}
int raft_sim_restart_node(raft_sim_cluster_t *c, int node_idx)
{
if (node_idx < 0 || node_idx >= c->n)