def test_conditional_goto(circuit): """Test the EventCond + Goto combination.""" # Note: Goto(EventCond(...)) is wrong, Goto expects an FSM state, not an event class B4(edzed.FSM): STATES = ('A', 'B', 'C', 'D') b4 = B4('4states') init(circuit) event = edzed.EventCond(edzed.Goto('B'), edzed.Goto('D')) b4.event(event, value=False) assert b4.state == 'D' b4.event(event, value=True) assert b4.state == 'B'
def test_persistent_state(circuit): class Dummy(edzed.FSM): STATES = list("ABCDEF") def forbidden(): assert False mem = EventMemory('mem') fsm = Dummy( 'test', persistent=True, enter_D=forbidden, on_enter_D=edzed.Event(mem, '+D'), on_exit_D=edzed.Event(mem, '-D') ) assert fsm.key == "<Dummy 'test'>" storage = {fsm.key: ['D', None, {'x':'y', '_z':3}]} # (state, timer, sdata) circuit.set_persistent_data(storage) # enter_D and on_enter_D will be suppressed init(circuit) assert fsm.sdata == {'x':'y', '_z':3} assert fsm.state == 'D' assert mem.output is None fsm.event(edzed.Goto('F')) assert mem.output == ( '-D', # '_z' is not present in 'sdata', because it is considered private {'source': 'test', 'trigger': 'exit', 'state': 'D', 'sdata': {'x':'y'}, 'value': 'D'} ) del fsm.sdata['x'] assert storage == {fsm.key: ('F', None, {'_z':3})}
def test_goto(circuit): """Test the Goto special event.""" class B123(edzed.FSM): STATES = ('S1', 'S2', 'S3') EVENTS = [ ('step', 'S1', 'S2'), ('step', 'S2', 'S3'), ('step', 'S3', 'S1') ] b123 = B123('b123fsm') init(circuit) assert b123.state == 'S1' b123.event('step') assert b123.state == 'S2' b123.event(edzed.Goto('S1')) assert b123.state == 'S1' with pytest.raises(ValueError, match="Unknown state"): b123.event(edzed.Goto('S99'))
def test_no_transition_chaining2(circuit): """Test forbidden chained transition""" class ABC(edzed.FSM): STATES = ['A', 'B', 'C'] EVENTS = [ ('start', ['A'], 'B') ] # a FSM can't send events to itself this way abc = ABC('abc', on_enter_B=edzed.Event('abc', edzed.Goto('C'))) init(circuit) with pytest.raises(edzed.EdzedCircuitError, match='Forbidden recursive event'): abc.event('start')
class AfterRun(edzed.FSM): STATES = ['off', 'on', 'prepare_afterrun', 'afterrun'] EVENTS = [ ['start', ['off'], 'on'], ['stop', ['on'], 'prepare_afterrun'], ] TIMERS = { 'afterrun': (None, edzed.Goto('off')) } def enter_on(self): self.sdata['started'] = time.time() def enter_prepare_afterrun(self): duration = (time.time() - self.sdata.pop('started')) * (self.x_percentage / 100.0) self.event(edzed.Goto('afterrun'), duration=duration) def calc_output(self): return self.state != 'off'
def test_transition_chaining(circuit): """Test the chained transition A->B->C->D.""" class ABCD(edzed.FSM): STATES = ['A', 'B', 'C', 'D'] EVENTS = [ ('start', ['A'], 'B') ] def enter_B(self): self.event(edzed.Goto('C')) def enter_C(self): self.event(edzed.Goto('D')) def calc_output(self): assert self.state not in ('B', 'C') # no output for intermediate states return f'in_{self.state}' abcd = ABCD( 'abcd', on_enter_B=edzed.Event('mem', '+B'), on_exit_B=edzed.Event('mem', '-B'), on_enter_C=edzed.Event('mem', '+C'), on_exit_C=edzed.Event('mem', '-C'), on_exit_D=edzed.Event('mem', '-D'), ) mem = EventMemory('mem') init(circuit) assert abcd.state == 'A' assert abcd.output == 'in_A' assert mem.output is None abcd.event('start') assert abcd.state == 'D' assert abcd.output == 'in_D' assert mem.output is None # no events for intermediate states abcd.event(edzed.Goto('A')) assert abcd.state == 'A' assert abcd.output == 'in_A' assert mem.output == ( '-D', {'source': 'abcd', 'trigger': 'exit', 'state': 'D', 'sdata': {}, 'value': 'in_D'} )
def enter_off_0(self): self.event(edzed.Goto('off'), duration=(1.0 - self._dc) * PERIOD)
def enter_on_0(self): self.event(edzed.Goto('on'), duration=self._dc * PERIOD)
def enter_B(self): self.event(edzed.Goto('A'))
def enter_A(self): self.event(edzed.Goto('B'))
def exit_A(self): self.event(edzed.Goto('C')) # can't do this in exit_STATE (only in enter_STATE)
def enter_B(self): self.event(edzed.Goto('C')) # this is OK self.event(edzed.Goto('C')) # but only once!
def enter_C(self): self.event(edzed.Goto('D'))
def enter_prepare_afterrun(self): duration = (time.time() - self.sdata.pop('started')) * (self.x_percentage / 100.0) self.event(edzed.Goto('afterrun'), duration=duration)