def test_wrappedIdentifier(self): """ If the wrapped object has an C{identifier} method then its return value is returned by L{MethodSuffixOutputer.identifier}. """ world = AnimalWorld([]) outputer = MethodSuffixOutputer(world) self.assertEqual(world.identifier(), outputer.identifier())
def test_unicodeFallbackIdentifier(self): """ If the wrapped object has no C{identifier} method then the identifier generated by L{MethodSuffixOutputer.identifier} is a L{unicode} string. """ world = object() outputer = MethodSuffixOutputer(world) self.assertIsInstance(outputer.identifier(), unicode)
def test_fallbackIdentifier(self): """ If the wrapped object has no C{identifier} method then L{MethodSuffixOutputer.identifier} generates an identifier for the wrapped object and returns that. """ world = object() outputer = MethodSuffixOutputer(world) self.assertEqual(repr(world), outputer.identifier())
def test_fallbackIdentifierStable(self): """ If L{MethodSuffixOutputer} generates an identifier for the wrapped object then it generates the same identifier for all calls to C{identifier} regardless of changes to the wrapped object. """ world = ["first state"] outputer = MethodSuffixOutputer(world) firstIdentifier = outputer.identifier() world.append("second state") secondIdentifier = outputer.identifier() self.assertEqual(firstIdentifier, secondIdentifier)
def test_dispatch(self): """ When L{MethodSuffixOutputer.output} is called with an input and the wrapped object has a method named like I{output_INPUT} where I{INPUT} is the name of the input given, that method is called with the context object given. """ context = object() animals = [] world = AnimalWorld(animals) outputer = MethodSuffixOutputer(world) outputer.output(Output.aardvark, context) self.assertEqual([(Output.aardvark, context)], animals)
def test_FiniteStateInterpreterRepr(self): """ The result of L{_FiniteStateInterpreter.__repr__} is a string that includes the L{IOutputExecutor} provider that L{_FiniteStateInterpreter} can drive. """ fsm = constructFiniteStateMachine(Input, Output, MoreState, TRANSITIONS, self.initial, [Gravenstein], {Output.aardvark: IFood}, MethodSuffixOutputer(self.world), None) self.assertEqual(repr(fsm), "<FSM / %s>" % (MethodSuffixOutputer(self.world), ))
def build_convergence_loop_fsm(reactor, deployer): """ Create a convergence loop FSM. :param IReactorTime reactor: Used to schedule delays in the loop. :param IDeployer deployer: Used to discover local state and calcualte necessary changes to match desired configuration. """ I = ConvergenceLoopInputs O = ConvergenceLoopOutputs S = ConvergenceLoopStates table = TransitionTable() table = table.addTransition( S.STOPPED, I.STATUS_UPDATE, [O.STORE_INFO, O.CONVERGE], S.CONVERGING) table = table.addTransitions( S.CONVERGING, { I.STATUS_UPDATE: ([O.STORE_INFO], S.CONVERGING), I.STOP: ([], S.CONVERGING_STOPPING), I.ITERATION_DONE: ([O.CONVERGE], S.CONVERGING), }) table = table.addTransitions( S.CONVERGING_STOPPING, { I.STATUS_UPDATE: ([O.STORE_INFO], S.CONVERGING), I.ITERATION_DONE: ([], S.STOPPED), }) loop = ConvergenceLoop(reactor, deployer) fsm = constructFiniteStateMachine( inputs=I, outputs=O, states=S, initial=S.STOPPED, table=table, richInputs=[_ClientStatusUpdate], inputContext={}, world=MethodSuffixOutputer(loop)) loop.fsm = fsm return fsm
def __init__(self): self._fsm = constructFiniteStateMachine( inputs=Input, outputs=Output, states=State, table=table, initial=State.IDLE, richInputs=[RequestStart, InstanceStarted, StartFailed, RequestStop, InstanceStopped, StopFailed], inputContext={}, world=MethodSuffixOutputer(self))
def setup_driver(): """ Instantiate all of the driver components includes the following objects: trace, dbus, radio, state machine actions, finite state machine. Returns list of [fsm, radio, dbus] object references. """ trace = si446xtrace.Trace(1000) dbus = Si446xDbus(OBJECT_PATH, trace=trace) radio = Si446xRadio(device=0, callback=dbus.async_interrupt, trace=trace) print('init radio done') actions = Si446xFsmActionHandlers(radio, dbus) machine = constructFiniteStateMachine( inputs=Events, outputs=Actions, states=States, table=table, initial=States.S_SDN, richInputs=[], inputContext={}, world=MethodSuffixOutputer(actions), ) fsm = {'actions': actions, 'machine': machine, 'trace': trace} dbus.marry(fsm, radio) return [fsm, radio, dbus]
def test_noRepeatedTerminalLogging(self, logger): """ When the L{IFiniteStateMachine} receives an input in a terminal state (and does not generate an output or change to a different state, as is required in terminal states) it does not re-log the completion of its initialization event. """ # Accept this input in MoreState.blue but remain a terminal state (no # output, no state change). transitions = TRANSITIONS.addTransition(MoreState.blue, Input.apple, [], MoreState.blue) fsm = constructFiniteStateMachine( Input, Output, MoreState, transitions, MoreState.amber, [Gravenstein], {Output.aardvark: IFood}, MethodSuffixOutputer(AnimalWorld([])), logger) fsm.receive(Gravenstein()) howMany = len(logger.messages) fsm.receive(Gravenstein()) # No additional initialization messages please! self.assertEqual([], [ msg for msg in logger.messages[howMany:] if msg[u"action_type"] == u"fsm:initialize" ])
def main(): hardware = TurnstileController(digitalPin=0x13) turnstileFSM = constructFiniteStateMachine( inputs=TurnstileInput, outputs=TurnstileOutput, states=TurnstileState, table=table, initial=TurnstileState.LOCKED, richInputs={}, inputContext={}, world=MethodSuffixOutputer(Turnstile(hardware)), ) while True: if hardware.paymentMade(): hardware.resetNotification() turnstileFSM.receive(TurnstileInput.FARE_PAID) elif hardware.armTurned(): hardware.resetNotification() turnstileFSM.receive(TurnstileInput.ARM_TURNED) elif hardware.finishedLocking(): hardware.resetNotification() turnstileFSM.receive(TurnstileInput.ARM_LOCKED) elif hardware.finishedUnlocking(): hardware.resetNotification() turnstileFSM.receive(TurnstileInput.ARM_UNLOCKED) else: time.sleep(0.1)
def test_prefix(self): """ If a value is given for the optional second L{MethodSuffixOutputer} initializer argument then it is used instead of C{"output_"} as the method dispatch prefix. """ animals = [] class AlternatePrefixWorld(object): def foobar_AARDVARK(self, context): animals.append(context) context = object() world = AlternatePrefixWorld() outputer = MethodSuffixOutputer(world, "foobar_") outputer.output(Output.aardvark, context) self.assertEqual([context], animals)
def test_repr(self): """ The result of L{MethodSuffixOutputer.__repr__} is a string that mentions the wrapped object. """ world = object() self.assertEqual("<Output / %s>" % (world, ), repr(MethodSuffixOutputer(world)))
def setUp(self): self.animals = [] self.initial = MoreState.amber self.world = AnimalWorld(self.animals) self.fsm = constructFiniteStateMachine( Input, Output, MoreState, TRANSITIONS, self.initial, [Gravenstein], {Output.aardvark: IFood}, MethodSuffixOutputer(self.world))
def build_cluster_status_fsm(convergence_loop_fsm): """ Create a new cluster status FSM. The automatic reconnection logic is handled by the ``AgentLoopService``; the world object here just gets notified of disconnects, it need schedule the reconnect itself. :param convergence_loop_fsm: A convergence loop FSM as output by ``build_convergence_loop_fsm``. """ S = ClusterStatusStates I = ClusterStatusInputs O = ClusterStatusOutputs table = TransitionTable() # We may be shut down in any state, in which case we disconnect if # necessary. table = table.addTransitions( S.DISCONNECTED, { # Store the client, then wait for cluster status to be sent # over AMP: I.CONNECTED_TO_CONTROL_SERVICE: ([O.STORE_CLIENT], S.IGNORANT), I.SHUTDOWN: ([], S.SHUTDOWN), }) table = table.addTransitions( S.IGNORANT, { # We never told agent to start, so no need to tell it to stop: I.DISCONNECTED_FROM_CONTROL_SERVICE: ([], S.DISCONNECTED), # Tell agent latest cluster status, implicitly starting it: I.STATUS_UPDATE: ([O.UPDATE_STATUS], S.KNOWLEDGEABLE), I.SHUTDOWN: ([O.DISCONNECT], S.SHUTDOWN), }) table = table.addTransitions( S.KNOWLEDGEABLE, { # Tell agent latest cluster status: I.STATUS_UPDATE: ([O.UPDATE_STATUS], S.KNOWLEDGEABLE), I.DISCONNECTED_FROM_CONTROL_SERVICE: ([O.STOP], S.DISCONNECTED), I.SHUTDOWN: ([O.STOP, O.DISCONNECT], S.SHUTDOWN), }) table = table.addTransitions( S.SHUTDOWN, { I.DISCONNECTED_FROM_CONTROL_SERVICE: ([], S.SHUTDOWN), I.STATUS_UPDATE: ([], S.SHUTDOWN), }) return constructFiniteStateMachine( inputs=I, outputs=O, states=S, initial=S.DISCONNECTED, table=table, richInputs=[_ConnectedToControlService, _StatusUpdate], inputContext={}, world=MethodSuffixOutputer(ClusterStatus(convergence_loop_fsm)))
def test_logger(self): """ L{constructFiniteStateMachine} returns a FSM that also has a C{logger} attribute that is an L{eliot.Logger} instance. """ fsm = constructFiniteStateMachine( Input, Output, MoreState, TRANSITIONS, MoreState.amber, [Gravenstein], {Output.aardvark: IFood}, MethodSuffixOutputer(AnimalWorld([]))) self.assertIsInstance(fsm.logger, Logger)
def test_empty(self): """ L{_FiniteStateMachine._isTerminal} returns C{True} if a state that defines handling of no input symbols. """ fsm = constructFiniteStateMachine( Input, Output, MoreState, TRANSITIONS, MoreState.amber, [Gravenstein], {Output.aardvark: IFood}, MethodSuffixOutputer(AnimalWorld([]))) self.assertTrue(fsm._isTerminal(MoreState.blue))
def test_loggerOverride(self, logger): """ If an argument is given for the C{logger} argument to L{constructFiniteStateMachine} then that object is used as the logger of the resulting finite state machine. """ fsm = constructFiniteStateMachine( Input, Output, MoreState, TRANSITIONS, MoreState.amber, [Gravenstein], {Output.aardvark: IFood}, MethodSuffixOutputer(AnimalWorld([])), logger) self.assertIs(logger, fsm.logger)
def test_nextStateGivenSymbolInput(self, logger): """ L{IFiniteStateMachine.receive} changes L{IFiniteStateMachine.state} to the next state defined for the given symbolic input in the machine's current state. """ self.fsm = constructFiniteStateMachine( Input, Output, MoreState, TRANSITIONS, MoreState.amber, [Gravenstein], {}, MethodSuffixOutputer(AnimalWorld([])), logger) self.fsm.logger = logger self.fsm.receive(Input.apple) self.assertEqual(MoreState.blue, self.fsm.state)
def test_stateChange(self): """ L{_FiniteStateMachine._isTerminal} returns C{False} if a state defines handling of inputs that cause a state change. """ transitions = TRANSITIONS.addTransition(MoreState.blue, Input.apple, [], MoreState.amber) fsm = constructFiniteStateMachine( Input, Output, MoreState, transitions, MoreState.amber, [Gravenstein], {Output.aardvark: IFood}, MethodSuffixOutputer(AnimalWorld([]))) self.assertFalse(fsm._isTerminal(MoreState.blue))
def test_selfTransition(self): """ L{_FiniteStateMachine._isTerminal} returns C{True} if a state defines handling of inputs that generate no outputs and do not change the state of the machine. """ transitions = TRANSITIONS.addTransition(MoreState.blue, Input.apple, [], MoreState.blue) fsm = constructFiniteStateMachine( Input, Output, MoreState, transitions, MoreState.amber, [Gravenstein], {Output.aardvark: IFood}, MethodSuffixOutputer(AnimalWorld([]))) self.assertTrue(fsm._isTerminal(MoreState.blue))
def test_outputFromSymbolInput(self, logger): """ L{IFiniteStateMachine.receive} finds the transition for the symbol input in the machine's current state and returns the corresponding output. """ self.fsm = constructFiniteStateMachine( Input, Output, MoreState, TRANSITIONS, self.initial, [Gravenstein], {}, MethodSuffixOutputer(self.world)) self.fsm.logger = logger self.world.logger = logger self.assertEqual([Output.aardvark], self.fsm.receive(Input.apple))
def test_initializationLogging(self, logger): """ The initialization of the L{IFiniteStateMachine} created by L{constructFiniteStateMachine} is logged. """ constructFiniteStateMachine(Input, Output, MoreState, TRANSITIONS, MoreState.amber, [Gravenstein], {Output.aardvark: IFood}, MethodSuffixOutputer(AnimalWorld([])), logger) self.assertTrue( issuperset( logger.messages[0], { u"fsm_identifier": u"<AnimalWorld>", u"fsm_state": u"<MoreState=amber>", u"action_status": u"started", u"action_type": u"fsm:initialize", }))
def build_convergence_loop_fsm(reactor, deployer): """ Create a convergence loop FSM. Once cluster config+cluster state updates from control service are received the basic loop is: 1. Discover local state. 2. Calculate ``IStateChanges`` based on local state and cluster configuration and cluster state we received from control service. 3. Execute the change. 4. Sleep. However, if an update is received during sleep then we calculate based on that updated config+state whether a ``IStateChange`` needs to happen. If it does that means this change will have impact on what we do, so we interrupt the sleep. If calculation suggests a no-op then we keep sleeping. Notably we do **not** do a discovery of local state when an update is received while sleeping, since that is an expensive operation that can involve talking to external resources. Moreover an external update only implies external state/config changed, so we're not interested in the latest local state in trying to decide if this update requires us to do something; a recently cached version should suffice. :param IReactorTime reactor: Used to schedule delays in the loop. :param IDeployer deployer: Used to discover local state and calcualte necessary changes to match desired configuration. """ loop = ConvergenceLoop(reactor, deployer) fsm = constructFiniteStateMachine( inputs=ConvergenceLoopInputs, outputs=ConvergenceLoopOutputs, states=ConvergenceLoopStates, initial=ConvergenceLoopStates.STOPPED, table=_CONVERGENCE_LOOP_FSM_TABLE, richInputs=[_ClientStatusUpdate, _Sleep], inputContext={}, world=MethodSuffixOutputer(loop)) loop.fsm = fsm return fsm
def build_cluster_status_fsm(convergence_loop_fsm): """ Create a new cluster status FSM. The automatic reconnection logic is handled by the ``AgentLoopService``; the world object here just gets notified of disconnects, it need schedule the reconnect itself. :param convergence_loop_fsm: A convergence loop FSM as output by ``build_convergence_loop_fsm``. """ return constructFiniteStateMachine( inputs=ClusterStatusInputs, outputs=ClusterStatusOutputs, states=ClusterStatusStates, initial=ClusterStatusStates.DISCONNECTED, table=_CLUSTER_STATUS_FSM_TABLE, richInputs=[_ConnectedToControlService, _StatusUpdate], inputContext={}, world=MethodSuffixOutputer(ClusterStatus(convergence_loop_fsm)))
def test_terminalLogging(self, logger): """ When the L{IFiniteStateMachine} enters a terminal state the initialization action is finished successfully. """ fsm = constructFiniteStateMachine( Input, Output, MoreState, TRANSITIONS, MoreState.amber, [Gravenstein], {Output.aardvark: IFood}, MethodSuffixOutputer(AnimalWorld([])), logger) fsm.receive(Gravenstein()) (initialize, ) = LoggedAction.of_type(logger.messages, LOG_FSM_INITIALIZE) assertContainsFields( self, initialize.end_message, { u"fsm_terminal_state": u"<MoreState=blue>", u"action_status": u"succeeded", })
def test_terminalLogging(self, logger): """ When the L{IFiniteStateMachine} enters a terminal state the initialization action is finished successfully. """ fsm = constructFiniteStateMachine( Input, Output, MoreState, TRANSITIONS, MoreState.amber, [Gravenstein], {Output.aardvark: IFood}, MethodSuffixOutputer(AnimalWorld([])), logger) fsm.receive(Gravenstein()) self.assertTrue( issuperset( logger.messages[3], { u"fsm_terminal_state": u"<MoreState=blue>", # Prove it associates with the initialization action. u"action_type": u"fsm:initialize", u"action_status": u"succeeded", u"task_uuid": logger.messages[0][u"task_uuid"], u"task_level": u"/", }))
class State(Names): amber = NamedConstant() class MoreState(Names): amber = NamedConstant() blue = NamedConstant() class IRequiredByAardvark(Interface): pass NULL_WORLD = MethodSuffixOutputer(None) class TransitionTests(TestCase): """ Tests for L{Transition}. """ def test_str(self): """ The string representation of a L{Transition} includes the output and next state it represents. """ self.assertEqual("<Transition output='a' nextState='b'>", str(Transition("a", "b"))) def test_repr(self):
}) # end last transitions # begin outputer from machinist import MethodSuffixOutputer class Outputer(object): def output_ENGAGE_LOCK(self, engage): print("Engaging the lock.") def output_DISENGAGE_LOCK(self, disengage): print("Disengaging the lock.") outputer = MethodSuffixOutputer(Outputer()) # end outputer # begin construct from machinist import constructFiniteStateMachine turnstile = constructFiniteStateMachine( inputs=Input, outputs=Output, states=State, table=table, initial=State.LOCKED, richInputs=[], inputContext={}, world=outputer, )