#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
use state_machines::state_machine;
state_machine! {
name: TrafficLight,
initial: Red,
dynamic: true,
states: [Red, Yellow, Green],
events {
next {
transition: { from: Red, to: Green }
transition: { from: Green, to: Yellow }
transition: { from: Yellow, to: Red }
}
}
}
#[test]
fn test_dynamic_dispatch_basic() {
let mut light = DynamicTrafficLight::new(());
let state: TrafficLightState = light.current_state();
assert_eq!(state, TrafficLightState::Red);
light.handle(TrafficLightEvent::Next).unwrap();
assert_eq!(light.current_state(), TrafficLightState::Green);
light.handle(TrafficLightEvent::Next).unwrap();
assert_eq!(light.current_state(), TrafficLightState::Yellow);
light.handle(TrafficLightEvent::Next).unwrap();
assert_eq!(light.current_state(), TrafficLightState::Red);
}
#[test]
fn test_get_available_events_tracks_current_state() {
let mut light = DynamicTrafficLight::new(());
let events = light.get_available_events();
assert_eq!(events.len(), 1);
assert_eq!(events[0].name(), "next");
light.handle(TrafficLightEvent::Next).unwrap();
let events = light.get_available_events();
assert_eq!(events.len(), 1);
assert_eq!(events[0].name(), "next");
}
#[test]
fn test_dynamic_dispatch_with_selected_initial_state() {
let mut light = DynamicTrafficLight::new_init_state((), TrafficLightState::Yellow);
assert_eq!(light.current_state(), TrafficLightState::Yellow);
assert_eq!(light.current_state().name(), "Yellow");
assert_eq!(light.current_state().to_string(), "Yellow");
light.handle(TrafficLightEvent::Next).unwrap();
assert_eq!(light.current_state(), TrafficLightState::Red);
}
#[derive(Debug)]
struct InitialDataWithoutDefault {
_value: u8,
}
state_machine! {
name: InitialData,
dynamic: true,
initial: InitialDataReady,
states: [
InitialDataReady(InitialDataWithoutDefault),
InitialDataDone,
],
events {
finish {
transition: { from: InitialDataDone, to: InitialDataDone }
}
}
}
#[test]
fn test_dynamic_new_allows_non_default_initial_state_data() {
let machine = DynamicInitialData::new(());
assert_eq!(machine.current_state(), InitialDataState::InitialDataReady);
assert!(machine.initial_data_ready_data().is_none());
}
#[test]
fn test_typestate_to_dynamic_conversion() {
let light = TrafficLight::new(());
let mut dynamic_light = light.into_dynamic();
assert_eq!(dynamic_light.current_state(), TrafficLightState::Red);
dynamic_light.handle(TrafficLightEvent::Next).unwrap();
assert_eq!(dynamic_light.current_state(), TrafficLightState::Green);
}
#[test]
fn test_dynamic_to_typestate_conversion() {
let mut light = DynamicTrafficLight::new(());
light.handle(TrafficLightEvent::Next).unwrap();
assert_eq!(light.current_state(), TrafficLightState::Green);
let typed_light = light.into_green().unwrap();
let _ = typed_light.next();
}
#[test]
fn test_event_enum() {
let event = TrafficLightEvent::Next;
assert_eq!(event.name(), "next");
}
#[cfg(feature = "async")]
state_machine! {
name: AsyncProcessor,
dynamic: true,
async: true,
initial: Idle,
states: [Idle, Processing, Done],
events {
start {
transition: { from: Idle, to: Processing }
}
finish {
transition: { from: Processing, to: Done }
}
}
}
#[cfg(feature = "async")]
#[test]
fn test_async_dynamic_dispatch() {
use pollster::block_on;
block_on(async {
let mut processor = DynamicAsyncProcessor::new(());
assert_eq!(processor.current_state(), AsyncProcessorState::Idle);
assert_eq!(processor.get_available_events().await[0].name(), "start");
processor.handle(AsyncProcessorEvent::Start).await.unwrap();
assert_eq!(processor.current_state(), AsyncProcessorState::Processing);
assert_eq!(processor.get_available_events().await[0].name(), "finish");
processor.handle(AsyncProcessorEvent::Finish).await.unwrap();
assert_eq!(processor.current_state(), AsyncProcessorState::Done);
});
}
use std::sync::atomic::{AtomicBool, Ordering};
static GUARD_ALLOWED: AtomicBool = AtomicBool::new(false);
static GUARD_BLOCKED: AtomicBool = AtomicBool::new(false);
state_machine! {
name: Guarded,
dynamic: true,
initial: Start,
states: [Start, End],
events {
proceed {
guards: [is_allowed],
unless: [is_blocked],
transition: { from: Start, to: End }
}
}
}
impl<C, S> Guarded<C, S> {
fn is_allowed(&self, _ctx: &C) -> bool {
GUARD_ALLOWED.load(Ordering::SeqCst)
}
fn is_blocked(&self, _ctx: &C) -> bool {
GUARD_BLOCKED.load(Ordering::SeqCst)
}
}
#[test]
fn test_guard_failure() {
use state_machines::DynamicError;
GUARD_ALLOWED.store(false, Ordering::SeqCst);
GUARD_BLOCKED.store(false, Ordering::SeqCst);
let mut machine = DynamicGuarded::new(());
assert!(machine.get_available_events().is_empty());
let result = machine.handle(GuardedEvent::Proceed);
assert!(result.is_err());
match result.unwrap_err() {
DynamicError::GuardFailed { guard, event } => {
assert_eq!(guard, "is_allowed");
assert_eq!(event, "proceed"); }
_ => panic!("Expected GuardFailed error"),
}
assert_eq!(machine.current_state(), GuardedState::Start);
GUARD_ALLOWED.store(true, Ordering::SeqCst);
GUARD_BLOCKED.store(true, Ordering::SeqCst);
assert!(machine.get_available_events().is_empty());
GUARD_BLOCKED.store(false, Ordering::SeqCst);
let events = machine.get_available_events();
assert_eq!(events.len(), 1);
assert_eq!(events[0].name(), "proceed");
machine.handle(GuardedEvent::Proceed).unwrap();
assert_eq!(machine.current_state(), GuardedState::End);
assert!(machine.get_available_events().is_empty());
}
#[derive(Debug)]
pub struct Payload;
state_machine! {
name: PayloadAvailability,
dynamic: true,
initial: Ready,
states: [Ready, Submitted],
events {
submit {
payload: Payload,
transition: { from: Ready, to: Submitted }
}
cancel {
transition: { from: Ready, to: Submitted }
}
}
}
#[test]
fn test_get_available_events_omits_payload_events() {
let machine = DynamicPayloadAvailability::new(());
let events = machine.get_available_events();
assert_eq!(events.len(), 1);
assert_eq!(events[0].name(), "cancel");
}
#[derive(Debug, Clone, Default, PartialEq)]
struct CounterData {
count: u32,
}
state_machine! {
name: Counter,
dynamic: true,
initial: Stopped,
states: [
Stopped,
Running(CounterData),
],
events {
start {
transition: { from: Stopped, to: Running }
}
stop {
transition: { from: Running, to: Stopped }
}
}
}
#[test]
fn test_dynamic_state_data_accessors() {
use state_machines::DynamicError;
let mut counter = DynamicCounter::new(());
assert!(counter.running_data().is_none());
assert!(counter.running_data_mut().is_none());
counter.handle(CounterEvent::Start).unwrap();
assert_eq!(counter.current_state(), CounterState::Running);
counter.set_running_data(CounterData { count: 42 }).unwrap();
assert_eq!(counter.running_data().unwrap().count, 42);
counter.running_data_mut().unwrap().count += 1;
assert_eq!(counter.running_data().unwrap().count, 43);
if let Some(data) = counter.running_data_mut() {
data.count = 100;
}
assert_eq!(counter.running_data().unwrap().count, 100);
counter.handle(CounterEvent::Stop).unwrap();
assert_eq!(counter.current_state(), CounterState::Stopped);
assert!(counter.running_data().is_none());
assert!(counter.running_data_mut().is_none());
let result = counter.set_running_data(CounterData { count: 99 });
assert!(result.is_err());
match result.unwrap_err() {
DynamicError::WrongState {
expected,
actual,
operation,
} => {
assert_eq!(expected, "Running");
assert_eq!(actual, "Stopped");
assert_eq!(operation, "set_running_data");
}
_ => panic!("Expected WrongState error"),
}
}
#[test]
fn test_dynamic_selected_state_starts_with_empty_data() {
let mut counter = DynamicCounter::new_init_state((), CounterState::Running);
assert_eq!(counter.current_state(), CounterState::Running);
assert!(counter.running_data().is_none());
counter.set_running_data(CounterData { count: 7 }).unwrap();
assert_eq!(counter.running_data().unwrap().count, 7);
}
#[test]
fn test_dynamic_state_data_with_typestate_conversion() {
let mut counter = DynamicCounter::new(());
counter.handle(CounterEvent::Start).unwrap();
counter.set_running_data(CounterData { count: 50 }).unwrap();
let typed = counter.into_running().unwrap();
assert_eq!(typed.running_data().count, 50);
let mut dynamic = typed.into_dynamic();
assert_eq!(dynamic.current_state(), CounterState::Running);
assert_eq!(dynamic.running_data().unwrap().count, 50);
dynamic.running_data_mut().unwrap().count = 75;
assert_eq!(dynamic.running_data().unwrap().count, 75);
}
#[test]
fn test_is_available_event_sync() {
GUARD_ALLOWED.store(true, Ordering::SeqCst);
GUARD_BLOCKED.store(false, Ordering::SeqCst);
let machine = DynamicGuarded::new(());
assert!(machine.is_available_event(&GuardedEvent::Proceed));
GUARD_ALLOWED.store(false, Ordering::SeqCst);
assert!(!machine.is_available_event(&GuardedEvent::Proceed));
GUARD_ALLOWED.store(true, Ordering::SeqCst);
GUARD_BLOCKED.store(true, Ordering::SeqCst);
assert!(!machine.is_available_event(&GuardedEvent::Proceed));
GUARD_ALLOWED.store(true, Ordering::SeqCst);
GUARD_BLOCKED.store(false, Ordering::SeqCst);
let mut moved = DynamicGuarded::new(());
moved.handle(GuardedEvent::Proceed).unwrap();
assert_eq!(moved.current_state(), GuardedState::End);
assert!(!moved.is_available_event(&GuardedEvent::Proceed));
let payload_machine = DynamicPayloadAvailability::new(());
assert!(payload_machine.is_available_event(&PayloadAvailabilityEvent::Submit(Payload)));
assert!(payload_machine.is_available_event(&PayloadAvailabilityEvent::Cancel));
}
#[cfg(feature = "async")]
static ASYNC_IS_ALLOWED: AtomicBool = AtomicBool::new(false);
#[cfg(feature = "async")]
#[derive(Debug)]
pub struct AsyncToken;
#[cfg(feature = "async")]
state_machine! {
name: AsyncGuardedAvailability,
dynamic: true,
async: true,
initial: AsyncStart,
states: [AsyncStart, AsyncEnd],
events {
proceed {
guards: [is_allowed],
transition: { from: AsyncStart, to: AsyncEnd }
}
submit {
payload: AsyncToken,
transition: { from: AsyncStart, to: AsyncEnd }
}
}
}
#[cfg(feature = "async")]
impl<C, S> AsyncGuardedAvailability<C, S> {
async fn is_allowed(&self, _ctx: &C) -> bool {
ASYNC_IS_ALLOWED.load(Ordering::SeqCst)
}
}
#[cfg(feature = "async")]
#[test]
fn test_is_available_event_async() {
use pollster::block_on;
block_on(async {
let machine = DynamicAsyncGuardedAvailability::new(());
ASYNC_IS_ALLOWED.store(true, Ordering::SeqCst);
assert!(
machine
.is_available_event(&AsyncGuardedAvailabilityEvent::Proceed)
.await
);
ASYNC_IS_ALLOWED.store(false, Ordering::SeqCst);
assert!(
!machine
.is_available_event(&AsyncGuardedAvailabilityEvent::Proceed)
.await
);
assert!(
machine
.is_available_event(&AsyncGuardedAvailabilityEvent::Submit(AsyncToken))
.await
);
let mut moved = DynamicAsyncGuardedAvailability::new(());
ASYNC_IS_ALLOWED.store(true, Ordering::SeqCst);
moved
.handle(AsyncGuardedAvailabilityEvent::Proceed)
.await
.unwrap();
assert_eq!(
moved.current_state(),
AsyncGuardedAvailabilityState::AsyncEnd
);
assert!(
!moved
.is_available_event(&AsyncGuardedAvailabilityEvent::Proceed)
.await
);
});
}