gen_statem: GenStatem* type prefix + cleanup
- rename StatemRef/StatemCallError/StatemSendError -> GenStatem*
- move the inline unit test out of src; consolidate the Switch coverage
onto a single macro-driven harness in tests/gen_statem.rs
- drop the redundant hand-written Switch test machine and the two
untracked rejected-direction probes (succ_enums, typed_edges)
- rename examples statem_{fused,macro}.rs -> gen_statem_{fused,macro}.rs
- strip RFC/chunk/spike provenance and fix the mislabeled "throwaway"
example header and dead cross-references
This commit is contained in:
@@ -0,0 +1,108 @@
|
||||
//! gen_statem behaviour tests, driven through the `gen_statem!` macro: cast/call
|
||||
//! round-trip, `enter` firing on start and on every real transition (but not on
|
||||
//! a stay), and the machine-down path when a handler panics.
|
||||
|
||||
use smarm::gen_statem;
|
||||
use smarm::gen_statem::{CallError, Reply};
|
||||
use smarm::run;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
// A two-state machine: Flip toggles, calls read counters, Boom panics.
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
enum Switch {
|
||||
Off,
|
||||
On,
|
||||
}
|
||||
|
||||
struct Counts {
|
||||
flips: u32,
|
||||
enters: u32,
|
||||
}
|
||||
|
||||
enum Cast {
|
||||
Flip,
|
||||
}
|
||||
|
||||
enum Call {
|
||||
GetFlips(Reply<u32>),
|
||||
GetEnters(Reply<u32>),
|
||||
Boom(Reply<u32>),
|
||||
}
|
||||
|
||||
gen_statem! {
|
||||
machine: Sm { state: Switch, data: Counts };
|
||||
event: Ev { cast: Cast, call: Call };
|
||||
context(data, prev, cx);
|
||||
|
||||
enter {
|
||||
_ => data.enters += 1,
|
||||
}
|
||||
|
||||
on Switch::Off => {
|
||||
cast Cast::Flip => { data.flips += 1; Switch::On },
|
||||
}
|
||||
on Switch::On => {
|
||||
cast Cast::Flip => Switch::Off,
|
||||
}
|
||||
|
||||
// State-independent queries: reply, then stay via `prev`. Boom panics
|
||||
// (`boom()` is typed as a state tag so the arm stays well-formed).
|
||||
on _ => {
|
||||
call Call::GetFlips(r) => { r.reply(data.flips); prev },
|
||||
call Call::GetEnters(r) => { r.reply(data.enters); prev },
|
||||
call Call::Boom(_r) => boom(),
|
||||
}
|
||||
}
|
||||
|
||||
fn boom() -> Switch {
|
||||
panic!("boom")
|
||||
}
|
||||
// Casts are applied in order and a later call observes the accumulated data.
|
||||
#[test]
|
||||
fn cast_then_call_roundtrip() {
|
||||
let got = Arc::new(Mutex::new(0u32));
|
||||
let got2 = got.clone();
|
||||
run(move || {
|
||||
let sw = Sm::start(Switch::Off, Counts { flips: 0, enters: 0 });
|
||||
sw.send(Ev::Cast(Cast::Flip)).unwrap(); // Off -> On (flips = 1)
|
||||
sw.send(Ev::Cast(Cast::Flip)).unwrap(); // On -> Off (no flip count)
|
||||
let flips = sw.call(|r| Ev::Call(Call::GetFlips(r))).unwrap();
|
||||
*got2.lock().unwrap() = flips;
|
||||
});
|
||||
assert_eq!(*got.lock().unwrap(), 1, "turned On once across the two flips");
|
||||
}
|
||||
|
||||
// `enter` fires once on start and once per *real* transition; a stay (a call
|
||||
// that returns the current tag) does not re-enter.
|
||||
#[test]
|
||||
fn enter_on_start_and_each_transition_but_not_stay() {
|
||||
let got = Arc::new(Mutex::new((0u32, 0u32, 0u32)));
|
||||
let got2 = got.clone();
|
||||
run(move || {
|
||||
let sw = Sm::start(Switch::Off, Counts { flips: 0, enters: 0 }); // enter -> 1
|
||||
let after_start = sw.call(|r| Ev::Call(Call::GetEnters(r))).unwrap();
|
||||
// Two stays (the reads) must not bump enters.
|
||||
let _ = sw.call(|r| Ev::Call(Call::GetFlips(r))).unwrap();
|
||||
let still = sw.call(|r| Ev::Call(Call::GetEnters(r))).unwrap();
|
||||
sw.send(Ev::Cast(Cast::Flip)).unwrap(); // Off -> On -> enter -> 2
|
||||
let after_flip = sw.call(|r| Ev::Call(Call::GetEnters(r))).unwrap();
|
||||
*got2.lock().unwrap() = (after_start, still, after_flip);
|
||||
});
|
||||
assert_eq!(*got.lock().unwrap(), (1, 1, 2));
|
||||
}
|
||||
|
||||
// A handler that panics tears the loop down; the in-flight call's reply channel
|
||||
// closes as the stack unwinds, so the parked caller wakes with Down (mirrors
|
||||
// gen_server's panicking-handler path).
|
||||
#[test]
|
||||
fn call_to_panicking_handler_is_down() {
|
||||
let got = Arc::new(Mutex::new(None::<Result<u32, CallError>>));
|
||||
let got2 = got.clone();
|
||||
run(move || {
|
||||
let sw = Sm::start(Switch::Off, Counts { flips: 0, enters: 0 });
|
||||
let r = sw.call(|rep| Ev::Call(Call::Boom(rep)));
|
||||
*got2.lock().unwrap() = Some(r);
|
||||
});
|
||||
assert_eq!(*got.lock().unwrap(), Some(Err(CallError::Down)));
|
||||
}
|
||||
Reference in New Issue
Block a user