import datatypes.fsm struct MyReceiver { mut: data []string } fn default_setup() (MyReceiver, fsm.StateMachine) { mut receiver := MyReceiver{} mut s := fsm.new() s.add_state('A', on_state_entry, on_state_run, on_state_exit) s.add_state('B', on_state_entry, on_state_run, on_state_exit) s.add_transition('A', 'B', condition_transition) return receiver, s } fn test_statemachine_number_of_callbacks_correct_when_single_transition() ? { mut receiver, mut s := default_setup() s.run(receiver)? assert receiver.data.len == 3 } fn test_statemachine_sequence_works_when_typical() ? { mut receiver, mut s := default_setup() s.run(receiver)? assert receiver.data[0] == 'on_state_exit: A -> B' assert receiver.data[1] == 'on_state_entry: A -> B' assert receiver.data[2] == 'on_state_run: A -> B' } fn test_statemachine_works_when_final_state() ? { mut receiver, mut s := default_setup() // current state `A`, with a possible transition to `B`: s.run(receiver)? // run should not error here // Note: run will now return error, because for state `B`, // there are no more transitions: s.run(receiver) or { assert true } s.run(receiver) or { assert true } assert receiver.data.len == 5 assert receiver.data[2] == 'on_state_run: A -> B' assert receiver.data[3] == 'on_state_run: B -> B' assert receiver.data[4] == 'on_state_run: B -> B' } fn test_simple_loop() ? { mut receiver, mut s := default_setup() // Add a transition back to `A` too: s.add_transition('B', 'A', condition_transition) // Run the FSM for a while. // It will loop forever between `A` -> `B` -> `A` -> `B` ... for _ in 0 .. 100 { s.run(receiver) or { assert false } } assert receiver.data[1] == 'on_state_entry: A -> B' assert receiver.data[4] == 'on_state_entry: B -> A' assert receiver.data[7] == 'on_state_entry: A -> B' assert receiver.data[10] == 'on_state_entry: B -> A' } // Helper functions fn on_state_entry(mut receiver MyReceiver, from string, to string) { receiver.data << 'on_state_entry: ' + from + ' -> ' + to } fn on_state_run(mut receiver MyReceiver, from string, to string) { receiver.data << 'on_state_run: ' + from + ' -> ' + to } fn on_state_exit(mut receiver MyReceiver, from string, to string) { receiver.data << 'on_state_exit: ' + from + ' -> ' + to } fn condition_transition(receiver &MyReceiver, from string, to string) bool { // The condition callback is a way to provide input to the FSM // It can return true or false, based on external events/state. // For these tests however, that is not used, and it simply always returns true. return true }