#![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(());
assert_eq!(light.current_state(), "Red");
light.handle(TrafficLightEvent::Next).unwrap();
assert_eq!(light.current_state(), "Green");
light.handle(TrafficLightEvent::Next).unwrap();
assert_eq!(light.current_state(), "Yellow");
light.handle(TrafficLightEvent::Next).unwrap();
assert_eq!(light.current_state(), "Red");
}
#[test]
fn test_typestate_to_dynamic_conversion() {
let light = TrafficLight::new(());
let mut dynamic_light = light.into_dynamic();
assert_eq!(dynamic_light.current_state(), "Red");
dynamic_light.handle(TrafficLightEvent::Next).unwrap();
assert_eq!(dynamic_light.current_state(), "Green");
}
#[test]
fn test_dynamic_to_typestate_conversion() {
let mut light = DynamicTrafficLight::new(());
light.handle(TrafficLightEvent::Next).unwrap();
assert_eq!(light.current_state(), "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(), "Idle");
processor.handle(AsyncProcessorEvent::Start).await.unwrap();
assert_eq!(processor.current_state(), "Processing");
processor.handle(AsyncProcessorEvent::Finish).await.unwrap();
assert_eq!(processor.current_state(), "Done");
});
}
use std::sync::atomic::{AtomicBool, Ordering};
static GUARD_ALLOWED: AtomicBool = AtomicBool::new(false);
state_machine! {
name: Guarded,
dynamic: true,
initial: Start,
states: [Start, End],
events {
proceed {
guards: [is_allowed],
transition: { from: Start, to: End }
}
}
}
impl<C, S> Guarded<C, S> {
fn is_allowed(&self, _ctx: &C) -> bool {
GUARD_ALLOWED.load(Ordering::SeqCst)
}
}
#[test]
fn test_guard_failure() {
use state_machines::DynamicError;
GUARD_ALLOWED.store(false, Ordering::SeqCst);
let mut machine = DynamicGuarded::new(());
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(), "Start");
GUARD_ALLOWED.store(true, Ordering::SeqCst);
machine.handle(GuardedEvent::Proceed).unwrap();
assert_eq!(machine.current_state(), "End");
}
#[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(), "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(), "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_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(), "Running");
assert_eq!(dynamic.running_data().unwrap().count, 50);
dynamic.running_data_mut().unwrap().count = 75;
assert_eq!(dynamic.running_data().unwrap().count, 75);
}