Files
hsmc/hsmc/tests/det_current_state.rs
T
swaits 081bd28cca journal + determinism tests + Creusot verification scaffolding
Phase 1 — semantics doc
  docs/002. hsmc-semantics-formal.md

Phase 2 — deterministic execution journal
  hsmc/src/journal.rs (TraceEvent, JournalSink, ActionKind)
  hsmc-macros: __chart_journal! emission at every observable site
    (entry/exit/action/transition/timer arm-cancel-fire/during/emit/
    event delivery/terminate). Per-state during count helpers, stable
    chart-hash via FNV-1a. Action observation moved out of run_action
    and into call sites so ActionKind is accurate.
  Public API on the generated machine: journal(), take_journal(),
    clear_journal(), CHART_HASH.

Phase 3 — verification crate (Creusot scaffolding)
  verification/{Cargo.toml,mise.toml,requirements.txt,INVARIANTS.md}
  verification/src/{event_queue.rs,timer_table.rs} — Pearlite
    contracts on simplified mirrors of the runtime types. cargo
    creusot generates Coma IR cleanly; VCs not yet discharged.
  Correspondence tests drive the verified mirror and the runtime
    type with the same op sequences and assert observable parity.

Tests — 113 deterministic-flow tests across 22 files. Each compares a
  hand-built expected journal byte-for-byte against actual, anchored
  to a spec section: initial entry, lateral/self/up transitions,
  bubbling, timers, emit, ordering, durings, step/dispatch
  equivalence, root-targeting, current_state, termination.

Tooling — mise.toml (root): stable Rust + cargo subcommands.
  verification/mise.toml: pinned nightly-2025-11-13 + opam + python
  venv + uv. just verify is fully self-installing.

Bug fix — root state targetable by the chart's own name (e.g.
  `on(Trig) => ChartName;`) per spec §"How transitions work".

CHART_HASH — stable FNV-1a fingerprint over the chart's structural
  definition; mismatch on replay = different chart.
2026-04-27 20:15:05 -06:00

86 lines
2.2 KiB
Rust

#![allow(dead_code)]
//! Deterministic-flow tests for `current_state()` reflecting the innermost
//! active state at every point.
#![cfg(all(feature = "tokio", feature = "journal"))]
#![allow(unexpected_cfgs)]
use hsmc::statechart;
#[derive(Default)]
pub struct Ctx;
#[derive(Debug, Clone)]
pub enum Ev {
GoB,
UpToParent,
Halt,
}
statechart! {
Cur {
context: Ctx;
events: Ev;
default(Parent);
terminate(Halt);
state Parent {
entry: p_in;
exit: p_out;
default(A);
state A {
entry: a_in;
exit: a_out;
on(GoB) => B;
on(UpToParent) => Parent;
}
state B {
entry: b_in;
exit: b_out;
on(UpToParent) => Parent;
}
}
}
}
impl CurActions for CurActionContext<'_> {
async fn p_in(&mut self) {}
async fn p_out(&mut self) {}
async fn a_in(&mut self) {}
async fn a_out(&mut self) {}
async fn b_in(&mut self) {}
async fn b_out(&mut self) {}
}
#[tokio::test(flavor = "current_thread")]
async fn det_current_state_initial_is_default_leaf() {
tokio::task::LocalSet::new().run_until(async {
let mut m = Cur::new(Ctx);
// Drive initial entry by sending an event.
let _ = m.dispatch(Ev::GoB).await;
// Now in B.
assert_eq!(m.current_state(), CurState::B);
}).await;
}
#[tokio::test(flavor = "current_thread")]
async fn det_current_state_after_lateral_transition() {
tokio::task::LocalSet::new().run_until(async {
let mut m = Cur::new(Ctx);
let _ = m.dispatch(Ev::GoB).await;
assert_eq!(m.current_state(), CurState::B);
}).await;
}
#[tokio::test(flavor = "current_thread")]
async fn det_current_state_after_up_returns_composite() {
// Spec: current_state can return a composite if it became innermost via
// an up-transition.
tokio::task::LocalSet::new().run_until(async {
let mut m = Cur::new(Ctx);
let _ = m.dispatch(Ev::UpToParent).await;
assert_eq!(m.current_state(), CurState::Parent,
"after up-transition, current_state returns the composite Parent");
}).await;
}