def test_run_send_fail(self): up_downs = [] runner = runners.FiniteRunner(self.jumper) it = runner.run_iter('jump') up_downs.append(six.next(it)) self.assertRaises(excp.NotFound, it.send, 'fail') it.close() self.assertEqual([('down', 'up')], up_downs)
def test_copy_initialized(self): j = self.jumper.copy() self.assertIsNone(j.current_state) r = runners.FiniteRunner(self.jumper) for i, transition in enumerate(r.run_iter('jump')): if i == 4: break self.assertIsNone(j.current_state) self.assertIsNotNone(self.jumper.current_state)
def test_run_iter(self): up_downs = [] runner = runners.FiniteRunner(self.jumper) for (old_state, new_state) in runner.run_iter('jump'): up_downs.append((old_state, new_state)) if len(up_downs) >= 3: break self.assertEqual([('down', 'up'), ('up', 'down'), ('down', 'up')], up_downs) self.assertFalse(self.jumper.terminated) self.assertEqual('up', self.jumper.current_state) self.jumper.process_event('fall') self.assertEqual('down', self.jumper.current_state)
def test_run_send(self): up_downs = [] runner = runners.FiniteRunner(self.jumper) it = runner.run_iter('jump') while True: up_downs.append(it.send(None)) if len(up_downs) >= 3: it.close() break self.assertEqual('up', self.jumper.current_state) self.assertFalse(self.jumper.terminated) self.assertEqual([('down', 'up'), ('up', 'down'), ('down', 'up')], up_downs) self.assertRaises(StopIteration, six.next, it)
def test_run(self): m = self._create_fsm('down', add_states=['up', 'down']) m.add_state('broken', terminal=True) m.add_transition('down', 'up', 'jump') m.add_transition('up', 'broken', 'hit-wall') m.add_reaction('up', 'jump', lambda *args: 'hit-wall') self.assertEqual(['broken', 'down', 'up'], sorted(m.states)) self.assertEqual(2, m.events) m.initialize() self.assertEqual('down', m.current_state) self.assertFalse(m.terminated) r = runners.FiniteRunner(m) r.run('jump') self.assertTrue(m.terminated) self.assertEqual('broken', m.current_state) self.assertRaises(excp.InvalidState, r.run, 'jump', initialize=False)
def run_iter(self, timeout=None): """Runs the engine using iteration (or die trying). :param timeout: timeout to wait for any atoms to complete (this timeout will be used during the waiting period that occurs after the waiting state is yielded when unfinished atoms are being waited on). Instead of running to completion in a blocking manner, this will return a generator which will yield back the various states that the engine is going through (and can be used to run multiple engines at once using a generator per engine). The iterator returned also responds to the ``send()`` method from :pep:`0342` and will attempt to suspend itself if a truthy value is sent in (the suspend may be delayed until all active atoms have finished). NOTE(harlowja): using the ``run_iter`` method will **not** retain the engine lock while executing so the user should ensure that there is only one entity using a returned engine iterator (one per engine) at a given time. """ self.compile() self.prepare() self.validate() last_state = None with _start_stop(self._task_executor, self._retry_executor): self._change_state(states.RUNNING) try: closed = False machine, memory = self._runtime.builder.build(timeout=timeout) r = runners.FiniteRunner(machine) for (_prior_state, new_state) in r.run_iter(builder.START): last_state = new_state # NOTE(harlowja): skip over meta-states. if new_state in builder.META_STATES: continue if new_state == states.FAILURE: failure.Failure.reraise_if_any(memory.failures) if closed: continue try: try_suspend = yield new_state except GeneratorExit: # The generator was closed, attempt to suspend and # continue looping until we have cleanly closed up # shop... closed = True self.suspend() else: if try_suspend: self.suspend() except Exception: with excutils.save_and_reraise_exception(): self._change_state(states.FAILURE) else: if last_state and last_state not in self.IGNORABLE_STATES: self._change_state(new_state) if last_state not in self.NO_RERAISING_STATES: it = itertools.chain( six.itervalues(self.storage.get_failures()), six.itervalues(self.storage.get_revert_failures())) failure.Failure.reraise_if_any(it)
def run_iter(self, timeout=None): """Runs the engine using iteration (or die trying). :param timeout: timeout to wait for any atoms to complete (this timeout will be used during the waiting period that occurs after the waiting state is yielded when unfinished atoms are being waited on). Instead of running to completion in a blocking manner, this will return a generator which will yield back the various states that the engine is going through (and can be used to run multiple engines at once using a generator per engine). The iterator returned also responds to the ``send()`` method from :pep:`0342` and will attempt to suspend itself if a truthy value is sent in (the suspend may be delayed until all active atoms have finished). NOTE(harlowja): using the ``run_iter`` method will **not** retain the engine lock while executing so the user should ensure that there is only one entity using a returned engine iterator (one per engine) at a given time. """ self.compile() self.prepare() self.validate() # Keep track of the last X state changes, which if a failure happens # are quite useful to log (and the performance of tracking this # should be negligible). last_transitions = collections.deque( maxlen=max(1, self.MAX_MACHINE_STATES_RETAINED)) with _start_stop(self._task_executor, self._retry_executor): self._change_state(states.RUNNING) try: closed = False machine, memory = self._runtime.builder.build(timeout=timeout) r = runners.FiniteRunner(machine) for transition in r.run_iter(builder.START): last_transitions.append(transition) _prior_state, new_state = transition # NOTE(harlowja): skip over meta-states if new_state in builder.META_STATES: continue if new_state == states.FAILURE: failure.Failure.reraise_if_any(memory.failures) if closed: continue try: try_suspend = yield new_state except GeneratorExit: # The generator was closed, attempt to suspend and # continue looping until we have cleanly closed up # shop... closed = True self.suspend() else: if try_suspend: self.suspend() except Exception: with excutils.save_and_reraise_exception(): LOG.exception( "Engine execution has failed, something" " bad must of happened (last" " %s machine transitions were %s)", last_transitions.maxlen, list(last_transitions)) self._change_state(states.FAILURE) else: if last_transitions: _prior_state, new_state = last_transitions[-1] if new_state not in self.IGNORABLE_STATES: self._change_state(new_state) if new_state not in self.NO_RERAISING_STATES: failures = self.storage.get_failures() more_failures = self.storage.get_revert_failures() fails = itertools.chain( six.itervalues(failures), six.itervalues(more_failures)) failure.Failure.reraise_if_any(fails)
def run(self, action): self.runner = runners.FiniteRunner(self.fsm) for (old, new) in self.runner.run_iter(action): print(old, '-->', new) self.show()
def test_bad_start_state(self): m = self._create_fsm('unknown', add_start=False) r = runners.FiniteRunner(m) self.assertRaises(excp.NotFound, r.run, 'unknown')
def _make_machine(self, flow, initial_state=None): runtime = self._make_runtime(flow, initial_state=initial_state) machine, memory = runtime.builder.build({}) machine_runner = runners.FiniteRunner(machine) return (runtime, machine, memory, machine_runner)