Beispiel #1
0
    def test_abandoned_task_with_and_every(self):
        """
        Configuring abandonment criteria for a task that can run
        multiple times.
        """
        self.manager.update_configuration({
            't_alpha': {
                'run':      DevNullTask.name,
                'after':    ['creditsDepleted'],
                'andEvery': '_finishStep',
                'unless':   ['__deviceDisabled'],
            },
        })

        self.manager.fire('_finishStep')
        self.manager.fire('_finishStep')
        self.manager.fire('_finishStep')
        self.manager.fire('__deviceDisabled')
        self.manager.fire('creditsDepleted')
        ThreadingTaskRunner.join_all()

        # Because `__deviceDisabled` fired before any of the task
        # instances could start, they are all marked as abandoned.
        self.assertInstanceAbandoned('t_alpha#0')
        self.assertInstanceAbandoned('t_alpha#1')
        self.assertInstanceAbandoned('t_alpha#2')

        self.assertUnresolvedTasks([])
        self.assertUnresolvedInstances([])
Beispiel #2
0
    def test_skip_self_with_metadata(self):
        """
        A task marks itself as skipped and updates its metadata at
        runtime.
        """
        self.manager.update_configuration({
            't_skipper': {
                'after': ['integrity'],
                'run': SelfSkippingTask.name,
            },
        })

        # :py:class:`SelfSkippingTask` copies trigger kwargs to the
        # instance metadata when it marks itself as skipped.
        kwargs = {'foo': 'bar', 'baz': 'luhrmann'}

        self.manager.fire('integrity', kwargs)
        ThreadingTaskRunner.join_all()

        self.assertInstanceSkipped('t_skipper#0')

        task_instance = self.manager.storage.instances[
            't_skipper#0']  # type: TaskInstance
        self.assertDictEqual(
            task_instance.metadata['kwargs'],
            {'integrity': kwargs},
        )
Beispiel #3
0
    def test_abandoned_task(self):
        """
        Configuring a task so that it won't run if a certain trigger
        fires.
        """
        self.manager.update_configuration({
            't_alpha': {
                'run':      DevNullTask.name,
                'after':    ['__sessionFinalized'],
                'unless':   ['__finalizedWithoutSteps'],
            },
        })

        self.manager.fire('__finalizedWithoutSteps')
        ThreadingTaskRunner.join_all()
        self.assertInstanceAbandoned('t_alpha#0')

        # Abandoned tasks are considered resolved.
        self.assertUnresolvedTasks([])
        self.assertUnresolvedInstances([])

        self.manager.fire('__sessionFinalized')
        ThreadingTaskRunner.join_all()
        self.assertInstanceAbandoned('t_alpha#0')

        self.assertUnresolvedTasks([])
        self.assertUnresolvedInstances([])
Beispiel #4
0
    def test_retry_self_error_too_many_retries(self):
        """
        A task retries itself too many times.
        """
        with AttrPatcher(SelfRetryingTask, max_retries=2):
            self.manager.update_configuration({
                't_replay': {
                    'after': ['start'],
                    'run': SelfRetryingTask.name,
                    'withParams': {
                        'retries': {
                            'max': 3
                        }
                    },
                },
            })

            self.manager.fire('start')
            ThreadingTaskRunner.join_all()

        self.assertInstanceReplayed('t_replay#0')
        self.assertInstanceReplayed('t_replay#1')

        # After 2 replays (``SelfRetryingTask.max_retries``), the
        # task will refuse to retry itself again.
        self.assertInstanceFailed('t_replay#2', MaxRetriesExceeded)
Beispiel #5
0
    def test_hook_post_fire_with_skip(self):
        """
        Skipping a failed task instance may trigger the post-fire hook
        if the skipped task cascades.
        """
        self.manager.update_configuration({
            't_failingTask': {
                'after': [],
                'andEvery': 'fail',
                'run': FailingTask.name,
            },
        })

        ##
        # Set things up by running a task instance which will fail.
        del self.post_fire_calls[:]
        self.manager.fire('fail')
        ThreadingTaskRunner.join_all()

        # Quick sanity check (this also refreshes the task instance
        # metadata).
        self.assertInstanceFailed('t_failingTask#0', RuntimeError)

        # No surprises so far.
        self.assertListEqual(
            self.post_fire_calls,
            [
                ('fail', ['t_failingTask#0']),
            ],
        )

        ##
        # Skipping the task instance without cascade does not fire
        # any triggers, so the post-fire hook is not invoked.
        del self.post_fire_calls[:]
        self.manager.skip_failed_instance('t_failingTask#0', cascade=False)

        self.assertListEqual(self.post_fire_calls, [])

        ##
        # However, if we skip and cascade, this does fire a trigger.
        del self.post_fire_calls[:]
        self.manager.fire('fail')
        ThreadingTaskRunner.join_all()

        # Quick sanity check (this also refreshes the task instance
        # metadata).
        self.assertInstanceFailed('t_failingTask#1', RuntimeError)

        self.manager.skip_failed_instance('t_failingTask#1', cascade=True)

        self.assertListEqual(
            self.post_fire_calls,
            [
                ('fail', ['t_failingTask#1']),
                ('t_failingTask', []),
            ],
        )
Beispiel #6
0
    def test_cascade_one_shot(self):
        """
        A task that allows multiple causes cascades, triggering one-
        shot tasks.
        """
        self.manager.update_configuration({
            't_alpha': {
                'after': [],
                'andEvery': 'dataReceived',
                'run': PassThruTask.name,
            },

            # This task is configured as a one-shot.
            't_bravo': {
                'after': ['t_alpha'],
                'run': PassThruTask.name,
            },
        })

        self.manager.fire('dataReceived')
        ThreadingTaskRunner.join_all()

        self.assertInstanceFinished('t_alpha#0', {
            'kwargs': {
                'dataReceived': {}
            },
        })

        self.assertInstanceFinished('t_bravo#0', {
            'kwargs': {
                't_alpha': {
                    'kwargs': {
                        'dataReceived': {}
                    },
                },
            },
        })

        self.assertUnresolvedTasks([])
        self.assertUnresolvedInstances([])

        self.manager.fire('dataReceived')
        ThreadingTaskRunner.join_all()

        self.assertInstanceFinished('t_alpha#1', {
            'kwargs': {
                'dataReceived': {}
            },
        })

        # Since ``t_bravo`` is one-shot, a second instance is NOT
        # created.
        self.assertInstanceMissing('t_bravo#1')

        self.assertUnresolvedTasks([])
        self.assertUnresolvedInstances([])
    def test_skip_failed_instance_with_cascade(self):
        """
        Marking a failed task instance as skipped and forcing a
        cascade.
        """
        self.manager.update_configuration({
            't_alpha': {
                'after': ['dataReceived'],
                'run': FailingTask.name,
            },
            't_bravo': {
                'after': ['t_alpha'],
                'run': PassThruTask.name,
            },
        })

        self.manager.fire('dataReceived')
        ThreadingTaskRunner.join_all()

        self.assertInstanceFailed('t_alpha#0', RuntimeError)

        self.assertUnresolvedTasks(['t_alpha', 't_bravo'])
        self.assertUnresolvedInstances(['t_alpha#0'])

        self.manager.skip_failed_instance(
            failed_instance='t_alpha#0',

            # Tell the manager to cause a cascade, as if the task had
            # completed successfully.
            cascade=True,

            # Simulate the result to pass along to ``t_bravo``.
            result={
                'rating': '4.5',
                'popularity': '0.7',
            },
        )
        ThreadingTaskRunner.join_all()

        # ``t_alpha`` is still marked as skipped.
        self.assertInstanceSkipped('t_alpha#0')

        # ``t_bravo`` gets run with the simulated result.
        self.assertInstanceFinished('t_bravo#0', {
            'kwargs': {
                't_alpha': {
                    'rating': '4.5',
                    'popularity': '0.7',
                },
            },
        })

        self.assertUnresolvedTasks([])
        self.assertUnresolvedInstances([])
Beispiel #8
0
    def test_cascade_one_shot_reverse(self):
        """
        A task has ``andEvery`` set to the name of a one-shot task.
        """
        self.manager.update_configuration({
            # This task is configured as a one-shot.
            't_alpha': {
                'after': ['dataReceived'],
                'run': PassThruTask.name,
            },
            't_bravo': {
                'after': [],
                'andEvery': 't_alpha',
                'run': PassThruTask.name,
            },
        })

        self.manager.fire('dataReceived')
        ThreadingTaskRunner.join_all()

        self.assertInstanceFinished('t_alpha#0', {
            'kwargs': {
                'dataReceived': {}
            },
        })

        self.assertInstanceFinished('t_bravo#0', {
            'kwargs': {
                't_alpha': {
                    'kwargs': {
                        'dataReceived': {}
                    },
                },
            },
        })

        self.assertUnresolvedTasks([])
        self.assertUnresolvedInstances([])

        # Do it again.
        self.manager.fire('dataReceived')
        ThreadingTaskRunner.join_all()

        # Since ``t_alpha`` is a one-shot, it does NOT run a second
        # time.
        self.assertInstanceMissing('t_alpha#1')

        # Since ``t_alpha`` never runs a second time, it also does
        # not cause a second cascade.
        self.assertInstanceMissing('t_bravo#1')

        self.assertUnresolvedTasks([])
        self.assertUnresolvedInstances([])
    def test_cannot_skip_non_failed_instance(self):
        """
        Attempting to skip a task instance that didn't fail.
        """
        self.manager.update_configuration({
            't_alpha': {
                'after': ['dataReceived'],
                'run': PassThruTask.name,
            },
        })

        self.manager.fire('dataReceived')
        ThreadingTaskRunner.join_all()

        with self.assertRaises(ValueError):
            self.manager.skip_failed_instance('t_alpha#0')
Beispiel #10
0
    def test_abandoned_clone(self):
        """
        Creating a copy of an existing task instance should also copy
        its abandon state.

        This is a variation on the previous test, using a lower-level
        mechanism to create the instance.
        """
        self.manager.update_configuration({
            't_alpha': {
                'run':      DevNullTask.name,
                'after':    ['dataReceived'],
                'unless':   ['e_bureauCheck', '__finalizedWithoutSteps'],
            },
        })

        self.manager.fire('__finalizedWithoutSteps')
        ThreadingTaskRunner.join_all()
        # Abandonment criteria not satisfied yet; instance is not
        # abandoned.
        self.assertInstanceUnstarted('t_alpha#0')

        self.assertUnresolvedTasks(['t_alpha'])
        self.assertUnresolvedInstances(['t_alpha#0'])

        # Create a copy of the instance.
        with self.manager.storage.acquire_lock() as writable_storage: # type: BaseTriggerStorage
            writable_storage.clone_instance(writable_storage['t_alpha#0'])
            writable_storage.save()

        # Quick sanity check.
        self.assertInstanceUnstarted('t_alpha#1')

        self.assertUnresolvedTasks(['t_alpha'])
        self.assertUnresolvedInstances(['t_alpha#0', 't_alpha#1'])

        self.manager.fire('e_bureauCheck')
        ThreadingTaskRunner.join_all()

        # The cloned instance inherited its parent's abandon state.
        self.assertInstanceAbandoned('t_alpha#0')
        self.assertInstanceAbandoned('t_alpha#1')

        self.assertUnresolvedTasks([])
        self.assertUnresolvedInstances([])
Beispiel #11
0
    def test_hook_post_fire(self):
        """
        Subclassing the trigger manager with a post-fire hook.
        """
        self.manager.update_configuration({
            't_alpha': {
                'after': ['targetAcquired'],
                'run': DevNullTask.name,
            },
            't_bravo': {
                'after': ['targetAcquired', 't_alpha'],
                'run': DevNullTask.name,
            },
        })

        ##
        # Easy one first.  This trigger won't cause anything to run.
        del self.post_fire_calls[:]
        self.manager.fire('foobar')

        self.assertListEqual(
            self.post_fire_calls,
            [
                ('foobar', []),
            ],
        )

        ##
        # Let's turn up the intensity a bit and trigger a task.
        # Note that the task instance will cascade when it finishes.
        del self.post_fire_calls[:]
        self.manager.fire('targetAcquired')
        ThreadingTaskRunner.join_all()

        self.assertListEqual(
            self.post_fire_calls,
            [
                ('targetAcquired', ['t_alpha#0']),
                ('t_alpha', ['t_bravo#0']),
                ('t_bravo', []),
            ],
        )
Beispiel #12
0
    def test_hook_post_fire_with_replay(self):
        """
        Replaying a failed task instance does not trigger the post-fire
        hook.
        """
        self.manager.update_configuration({
            't_failingTask': {
                'after': ['fail'],
                'run': FailingTask.name,
            },
        })

        ##
        # Set things up by running a task instance which will fail.
        del self.post_fire_calls[:]
        self.manager.fire('fail')
        ThreadingTaskRunner.join_all()

        # Quick sanity check (this also refreshes the task instance
        # metadata).
        self.assertInstanceFailed('t_failingTask#0', RuntimeError)

        # No surprises so far.
        self.assertListEqual(
            self.post_fire_calls,
            [
                ('fail', ['t_failingTask#0']),
            ],
        )

        ##
        # Replaying the failed task doesn't fire any triggers, so
        # the post-fire hook does not get called.
        del self.post_fire_calls[:]
        self.manager.replay_failed_instance('t_failingTask#0')
        ThreadingTaskRunner.join_all()

        # Quick sanity check (this also refreshes the task instance
        # metadata).
        self.assertInstanceFailed('t_failingTask#1', RuntimeError)

        self.assertListEqual(self.post_fire_calls, [])
Beispiel #13
0
    def test_hook_post_replay(self):
        """
        Subclassing the trigger manager with a post-replay hook.
        """
        self.manager.update_configuration({
            't_failingTask': {
                'after': ['fail'],
                'run': FailingTask.name,
            },
        })

        ##
        # Set things up by running a task instance which will fail.
        del self.post_replay_calls[:]
        self.manager.fire('fail')
        ThreadingTaskRunner.join_all()

        # The task instance failed, but we haven't replayed
        # anything yet.
        self.assertListEqual(self.post_replay_calls, [])

        ##
        # Replaying the failed task will, naturally, invoke the
        # post-replay hook.
        del self.post_replay_calls[:]
        self.manager.replay_failed_instance('t_failingTask#0')
        ThreadingTaskRunner.join_all()

        # Quick sanity check (this also refreshes the task instance
        # metadata).
        self.assertInstanceFailed('t_failingTask#1', RuntimeError)

        # The post-replay hook was invoked.
        self.assertListEqual(self.post_replay_calls, ['t_failingTask#0'])

        ##
        # Skipping a failed task does not invoke the post-replay
        # hook, even if it cascades.
        del self.post_replay_calls[:]
        self.manager.skip_failed_instance('t_failingTask#1', cascade=True)
        self.assertListEqual(self.post_replay_calls, [])
Beispiel #14
0
    def test_tasks_are_one_shot_by_default(self):
        """
        Firing a trigger that has already been fired.
        """
        self.manager.update_configuration({
            't_alpha': {
                'after': ['_finishStep'],
                'run': PassThruTask.name,
            },
        })

        # Run the task once.
        self.manager.fire('_finishStep', {'counter': 1})
        ThreadingTaskRunner.join_all()

        self.assertInstanceFinished('t_alpha#0', {
            'kwargs': {
                '_finishStep': {
                    'counter': 1,
                },
            },
        })

        # Again! Again!
        self.manager.fire('_finishStep', {'counter': 2, 'extra': 'foo'})
        ThreadingTaskRunner.join_all()

        # There is no second result because the task only ran once.
        self.assertInstanceMissing('t_alpha#1')

        # We retain the result from the first (only) time the task was
        # run.
        self.assertInstanceFinished('t_alpha#0', {
            'kwargs': {
                '_finishStep': {
                    'counter': 1,
                },
            },
        })
Beispiel #15
0
    def test_skip_self(self):
        """
        A task marks itself as skipped at runtime.
        """
        self.manager.update_configuration({
            't_skipper': {
                'after': ['integrity'],
                'run': SelfSkippingTask.name,
            },
        })

        self.manager.fire('integrity')
        ThreadingTaskRunner.join_all()

        self.assertInstanceSkipped('t_skipper#0')

        # :py:class:`SelfSkippingTask` copies trigger kwargs to the
        # instance metadata when it marks itself as skipped.
        # This will make more sense in the next test.
        task_instance = self.manager.storage.instances[
            't_skipper#0']  # type: TaskInstance
        self.assertEqual(task_instance.metadata['kwargs'], {'integrity': {}})
Beispiel #16
0
    def test_abandoned_task_after_starting(self):
        """
        A task instance's abandonment criteria are satisfied after the
        instance started running.
        """
        self.manager.update_configuration({
            't_alpha': {
                'run':      DevNullTask.name,
                'after':    ['creditsDepleted'],
                'andEvery': '_finishStep',
                'unless':   ['__deviceDisabled'],
            },
        })

        self.manager.fire('creditsDepleted')
        self.manager.fire('_finishStep')

        # This satisfies the ``unless`` condition.
        self.manager.fire('__deviceDisabled')

        # This creates a new instance of the task, after the ``unless``
        # condition has been satisfied.
        self.manager.fire('_finishStep')

        ThreadingTaskRunner.join_all()

        # The first instance ran before `__deviceDisabled` fired, so it
        # is not marked as abandoned.
        self.assertInstanceFinished('t_alpha#0', {})

        # The second instance loaded its ``unless`` state from the
        # task configuration, so it was marked as abandoned as soon as
        # it was created.
        self.assertInstanceAbandoned('t_alpha#1')

        self.assertUnresolvedTasks([])
        self.assertUnresolvedInstances([])
    def test_skip_failed_instance(self):
        """
        Marking a failed task instance as skipped.
        """
        self.manager.update_configuration({
            't_alpha': {
                'after': ['dataReceived'],
                'run': FailingTask.name,
            },
            't_bravo': {
                'after': ['t_alpha'],
                'run': PassThruTask.name,
            },
        })

        self.manager.fire('dataReceived')
        ThreadingTaskRunner.join_all()

        self.assertInstanceFailed('t_alpha#0', RuntimeError)

        self.assertUnresolvedTasks(['t_alpha', 't_bravo'])

        # Failed tasks are considered unresolved.
        self.assertUnresolvedInstances(['t_alpha#0'])

        self.manager.skip_failed_instance('t_alpha#0')
        self.assertInstanceSkipped('t_alpha#0')

        # Skipping a task prevents it from cascading.
        self.assertInstanceMissing('t_bravo#0')

        # The skipped instance is considered resolved.
        # However, ``t_bravo`` is still unresolved, since it hasn't
        # run yet.
        self.assertUnresolvedTasks(['t_bravo'])
        self.assertUnresolvedInstances([])
Beispiel #18
0
    def test_retry_self(self):
        """
        A task retries itself at runtime.
        """
        with AttrPatcher(SelfRetryingTask, max_retries=2):
            self.manager.update_configuration({
                't_replay': {
                    'after': ['start'],
                    'run': SelfRetryingTask.name,
                    'withParams': {
                        'retries': {
                            'max': 2
                        }
                    },
                },
            })

            self.manager.fire('start')
            ThreadingTaskRunner.join_all()

        # Note that each retry creates a new task instance.
        self.assertInstanceReplayed('t_replay#0')
        self.assertInstanceReplayed('t_replay#1')
        self.assertInstanceFinished('t_replay#2', {'count': 2})
Beispiel #19
0
    def test_abandoned_task_complex(self):
        """
        Configuring a task so that it won't run if a set of triggers
        fire.
        """
        self.manager.update_configuration({
            't_alpha': {
                'run':      DevNullTask.name,
                'after':    ['dataReceived', '__sessionFinalized'],
                'unless':   ['e_bureauCheck', '__finalizedWithoutSteps'],
            },
        })

        self.manager.fire('__finalizedWithoutSteps')
        ThreadingTaskRunner.join_all()
        # Abandonment criteria not satisfied yet; instance is not
        # abandoned.
        self.assertInstanceUnstarted('t_alpha#0')

        self.assertUnresolvedTasks(['t_alpha'])
        self.assertUnresolvedInstances(['t_alpha#0'])

        self.manager.fire('__sessionFinalized')
        ThreadingTaskRunner.join_all()
        self.assertInstanceUnstarted('t_alpha#0')

        self.assertUnresolvedTasks(['t_alpha'])
        self.assertUnresolvedInstances(['t_alpha#0'])

        self.manager.fire('e_bureauCheck')
        ThreadingTaskRunner.join_all()
        # Instance is now abandoned.
        self.assertInstanceAbandoned('t_alpha#0')

        self.assertUnresolvedTasks([])
        self.assertUnresolvedInstances([])

        self.manager.fire('dataReceived')
        ThreadingTaskRunner.join_all()
        self.assertInstanceAbandoned('t_alpha#0')

        self.assertUnresolvedTasks([])
        self.assertUnresolvedInstances([])
Beispiel #20
0
    def test_fire_complex_trigger_kwargs(self):
        """
        Invoking a task that requires multiple triggers to fire,
        providing custom kwargs each time.
        """
        self.manager.update_configuration({
            # This task will be invoked without any kwargs.
            't_alpha': {
                'after': ['dataReceived', 'creditsDepleted'],
                'run': PassThruTask.name,
            },

            # We will provide kwargs to the ``dataReceived`` trigger,
            # which will override the ``dataReceived`` kwargs
            # configured here, but the other kwargs will be unaffected.
            't_bravo': {
                'after': ['dataReceived', 'creditsDepleted'],
                'run': PassThruTask.name,
                'withParams': {
                    # My Little Pony reboot references...
                    # I always knew one day it would come to this.
                    'dataReceived': {
                        'name': 'Rainbow Dash',
                        'species': 'unknown',
                    },
                    'creditsDepleted': {
                        'name': 'Twilight Sparkle',
                    },
                    'calibration': {
                        'name': 'Fluttershy',
                    },
                },
            },

            # Control group:  This task will not fire (requires extra
            # trigger).
            't_psych2': {
                'after': [
                    'creditsDepleted',
                    'dataReceived',
                    'registerComplete',
                ],
                'run': PassThruTask.name,
            },
        })

        # Let's see what happens if we specify kwargs for
        # ``dataReceived``...
        self.manager.fire('dataReceived', {'name': 'Rarity', 'leader': False})
        ThreadingTaskRunner.join_all()

        self.assertInstanceUnstarted('t_alpha#0')
        self.assertInstanceUnstarted('t_bravo#0')
        self.assertInstanceUnstarted('t_psych2#0')

        # Everything is unresolved at this point.
        self.assertUnresolvedTasks(['t_alpha', 't_bravo', 't_psych2'])

        # Each task has a corresponding unresolved instance.
        self.assertUnresolvedInstances(
            ['t_alpha#0', 't_bravo#0', 't_psych2#0'])

        # ... but none for ``creditsDepleted``.
        self.manager.fire('creditsDepleted')
        ThreadingTaskRunner.join_all()

        # ``t_alpha`` config does not have kwargs, so the task only
        # received the kwargs we provided for the ``dataReceived``
        # trigger.
        self.assertInstanceFinished(
            't_alpha#0', {
                'kwargs': {
                    'dataReceived': {
                        'leader': False,
                        'name': 'Rarity',
                    },
                    'creditsDepleted': {},
                },
            })

        # ``t_bravo`` config has kwargs.  We merged kwargs for
        # ``dataReceived``, but none of the other ones.
        self.assertInstanceFinished(
            't_bravo#0', {
                'kwargs': {
                    'dataReceived': {
                        'leader': False,
                        'name': 'Rarity',
                        'species': 'unknown',
                    },
                    'creditsDepleted': {
                        'name': 'Twilight Sparkle',
                    },
                    'calibration': {
                        'name': 'Fluttershy',
                    }
                },
            })

        self.assertInstanceUnstarted('t_psych2#0')

        # At this point, only ``t_psych2#0`` is unresolved.
        self.assertUnresolvedTasks(['t_psych2'])
        self.assertUnresolvedInstances(['t_psych2#0'])
Beispiel #21
0
    def test_redundant_trigger(self):
        """
        Firing a task whose trigger contains parts of its dependencies'
        triggers.

        This is useful when you want Task B to run after Task A, but
        you also want some of Task A's kwargs to be accessible to
        Task B.

        In these unit tests, we are using PassThruTask so that every
        task returns its kwargs, so this seems like a really weird
        use case.

        But, in a real-world scenario, tasks will return all kinds of
        different values, so the "redundant trigger" scenario may
        come up a lot more than you'd expect.
        """
        self.manager.update_configuration({
            't_alpha': {
                'after': ['dataReceived', 'creditsDepleted'],
                'run': DevNullTask.name,
            },
            't_bravo': {
                # ``dataReceived`` seems redundant here, since it's
                # included in the trigger for ``t_alpha``, but it's
                # actually very important, as we'll see later.
                'after': ['t_alpha', 'dataReceived'],
                'run': PassThruTask.name,
            },
        })

        self.manager.fire(
            trigger_name='dataReceived',
            trigger_kwargs={
                'section_timer': '42',
                'test_admin': 'bob'
            },
        )
        self.manager.fire('creditsDepleted')
        ThreadingTaskRunner.join_all()

        # Note that ``t_alpha`` is configured to run DevNullTask in
        # this test, so it returns an empty result.
        self.assertInstanceFinished('t_alpha#0', {})

        # Once ``t_alpha`` fires, ``t_bravo`` runs.
        self.assertInstanceFinished(
            't_bravo#0',
            {
                'kwargs': {
                    # As expected, ``t_alpha`` provided an empty result
                    # to ``t_bravo``.
                    't_alpha': {},

                    # The ``dataReceived`` kwargs are also passed along!
                    # If we didn't add ``dataReceived`` to the ``t_bravo``
                    # task's trigger, the task would have no way to access
                    # these values!
                    'dataReceived': {
                        'section_timer': '42',
                        'test_admin': 'bob',
                    },
                },
            })

        self.assertUnresolvedTasks([])
        self.assertUnresolvedInstances([])
Beispiel #22
0
    def test_fire_cascade_kwargs(self):
        """
        Firing a trigger successfully causes subsequent triggers to
        fire, with the result of the successful task acting as
        kwargs for the cascading tasks.
        """
        self.manager.update_configuration({
            't_alpha': {
                'after': ['dataReceived'],
                'run': PassThruTask.name,
            },
            't_bravo': {
                'after': ['t_alpha'],
                'run': PassThruTask.name,
                'withParams': {
                    # Inject some flags into the result of
                    # ``t_alpha``.
                    't_alpha': {
                        'partitionOnLetter': True,
                        'partitionOnNumber': False,
                    },
                    'arbitrary': {
                        'expected': 'maybe',
                    },
                },
            },
            't_charlie': {
                'after': ['t_alpha'],
                'run': PassThruTask.name,
            },
        })

        self.manager.fire('dataReceived', {'inattention': '4.5'})
        ThreadingTaskRunner.join_all()

        self.assertInstanceFinished('t_alpha#0', {
            'kwargs': {
                'dataReceived': {
                    'inattention': '4.5'
                },
            },
        })

        self.assertInstanceFinished(
            't_bravo#0',
            {
                'kwargs': {
                    't_alpha': {
                        # In a more realistic scenario, this would look
                        # like e.g., ``{"score": "red"}``, but we're trying
                        # to keep things simple for unit tests.
                        'kwargs': {
                            'dataReceived': {
                                'inattention': '4.5'
                            },
                        },

                        # Task kwargs get injected.
                        'partitionOnLetter': True,
                        'partitionOnNumber': False,
                    },

                    # As expected by this point, any extra kwargs included
                    # in the task configuration are passed along as well.
                    'arbitrary': {
                        'expected': 'maybe',
                    },
                },
            })

        self.assertInstanceFinished(
            't_charlie#0', {
                'kwargs': {
                    't_alpha': {
                        'kwargs': {
                            'dataReceived': {
                                'inattention': '4.5'
                            },
                        },
                    },
                },
            })

        # Everything is resolved at this point.
        self.assertUnresolvedTasks([])
        self.assertUnresolvedInstances([])
Beispiel #23
0
    def test_fire(self):
        """
        Firing a trigger causes tasks to run.
        """
        self.manager.update_configuration({
            # This task will be invoked without any kwargs.
            't_alpha': {
                'after': ['_finishStep'],
                'run': PassThruTask.name,
            },

            # This task will use its configured kwargs when it is
            # invoked.
            't_bravo': {
                'after': ['_finishStep'],
                'run': PassThruTask.name,
                'withParams': {
                    '_finishStep': {
                        'name': 'observations',
                    },
                    'extra': {
                        'stuff': 'arbitrary',
                    },
                },
            },

            # Control group:  This task will not get invoked (different
            # trigger).
            't_charlie': {
                'after': ['dataReceived'],
                'run': PassThruTask.name,
            },
        })

        self.manager.fire('_finishStep')
        ThreadingTaskRunner.join_all()

        # This task config has no kwargs, so no `name` was passed to
        # the task.
        self.assertInstanceFinished('t_alpha#0', {
            'kwargs': {
                '_finishStep': {},
            },
        })

        # The task config's kwargs get applied when the task runs.
        # Note that the keys do not have to match trigger names.
        self.assertInstanceFinished(
            't_bravo#0', {
                'kwargs': {
                    '_finishStep': {
                        'name': 'observations'
                    },
                    'extra': {
                        'stuff': 'arbitrary'
                    },
                },
            })

        # This task doesn't run because its trigger never fired.
        self.assertInstanceMissing('t_charlie#0')

        # ``t_charlie`` is the only unresolved task.
        self.assertUnresolvedTasks(['t_charlie'])

        # There are no unresolved instances, because the trigger
        # manager never created any instances for ``t_charlie`` (none
        # of its triggers fired).
        self.assertUnresolvedInstances([])
Beispiel #24
0
    def test_fire_cascade_complex_trigger(self):
        """
        Firing a trigger successfully causes cascade, but matching
        task has unsatisfied complex trigger.
        """
        self.manager.update_configuration({
            't_alpha': {
                'after': ['dataReceived', 'creditsDepleted'],
                'run': PassThruTask.name,
            },
            't_bravo': {
                # Note that this task will only run after
                # ``t_alpha`` has completed AND the ``registerComplete``
                # trigger is fired.
                'after': ['t_alpha', 'registerComplete'],
                'run': PassThruTask.name,
            },

            # Control group:  Will not run because it requires an extra
            # trigger that will not be fired during this test.
            't_charlie': {
                'after': ['t_alpha', 'demo'],
                'run': PassThruTask.name,
            },
        })

        self.manager.fire('dataReceived')
        ThreadingTaskRunner.join_all()

        # ``t_alpha#0`` was created to store kwargs.
        self.assertInstanceUnstarted('t_alpha#0')

        # The other tasks have no instances yet, since none of their
        # triggers fired.
        self.assertInstanceMissing('t_bravo#0')
        self.assertInstanceMissing('t_charlie#0')

        # Everything is unresolved at this point.
        self.assertUnresolvedTasks(['t_alpha', 't_bravo', 't_charlie'])

        # Only ``t_alpha`` has an instance, however.
        self.assertUnresolvedInstances(['t_alpha#0'])

        self.manager.fire('creditsDepleted')
        ThreadingTaskRunner.join_all()

        self.assertInstanceFinished('t_alpha#0', {
            'kwargs': {
                'dataReceived': {},
                'creditsDepleted': {},
            },
        })

        # New instances were created to store kwargs, but not all of
        # their triggers have fired yet.
        self.assertInstanceUnstarted('t_bravo#0')
        self.assertInstanceUnstarted('t_charlie#0')

        self.manager.fire('registerComplete')
        ThreadingTaskRunner.join_all()

        self.assertInstanceFinished(
            't_bravo#0', {
                'kwargs': {
                    't_alpha': {
                        'kwargs': {
                            'dataReceived': {},
                            'creditsDepleted': {},
                        },
                    },
                    'registerComplete': {},
                },
            })

        self.assertInstanceUnstarted('t_charlie#0')

        # ``t_charlie`` is still unresolved.
        self.assertUnresolvedTasks(['t_charlie'])
        self.assertUnresolvedInstances(['t_charlie#0'])
Beispiel #25
0
    def test_fire_cascade_complex_trigger_reverse(self):
        """
        Firing a trigger successfully causes cascade, and matching
        task's complex trigger is satisfied.

        This is basically a reverse of the previous test.
        """
        self.manager.update_configuration({
            't_alpha': {
                'after': ['dataReceived', 'creditsDepleted'],
                'run': PassThruTask.name,
            },
            't_bravo': {
                # Note that this task will only run after
                # ``t_alpha`` has completed AND the ``registerComplete``
                # trigger is fired.
                'after': ['t_alpha', 'registerComplete'],
                'run': PassThruTask.name,
            },

            # Control group:  Will not run because it requires an extra
            # trigger that will not be fired during this test.
            't_charlie': {
                'after': ['t_alpha', 'demo'],
                'run': PassThruTask.name,
            },
        })

        # This time, we'll fire ``registerComplete`` first and THEN
        # we'll trigger ``t_alpha``.
        self.manager.fire('registerComplete')
        ThreadingTaskRunner.join_all()

        self.assertInstanceMissing('t_alpha#0')
        self.assertInstanceUnstarted('t_bravo#0')
        self.assertInstanceMissing('t_charlie#0')

        self.assertUnresolvedTasks(['t_alpha', 't_bravo', 't_charlie'])
        self.assertUnresolvedInstances(['t_bravo#0'])

        self.manager.fire('dataReceived')
        ThreadingTaskRunner.join_all()

        self.assertInstanceUnstarted('t_alpha#0')
        self.assertInstanceUnstarted('t_bravo#0')
        self.assertInstanceMissing('t_charlie#0')

        self.manager.fire('creditsDepleted')
        ThreadingTaskRunner.join_all()

        self.assertInstanceFinished('t_alpha#0', {
            'kwargs': {
                'dataReceived': {},
                'creditsDepleted': {},
            },
        })

        self.assertInstanceFinished(
            't_bravo#0', {
                'kwargs': {
                    't_alpha': {
                        'kwargs': {
                            'dataReceived': {},
                            'creditsDepleted': {},
                        },
                    },
                    'registerComplete': {},
                },
            })

        self.assertInstanceUnstarted('t_charlie#0')

        self.assertUnresolvedTasks(['t_charlie'])
        self.assertUnresolvedInstances(['t_charlie#0'])
    def test_replay_failed_task_with_different_kwargs(self):
        """
        Replaying a failed task with different kwargs.
        """
        self.manager.update_configuration({
            't_alpha': {
                'after': ['dataReceived'],
                'run': FailingTask.name,
            },
            't_bravo': {
                'after': ['t_alpha'],
                'run': PassThruTask.name,
            },
        })

        # Note that we pass along some trigger kwargs; this will
        # be important later.
        self.manager.fire('dataReceived', {'random': 'filbert'})
        ThreadingTaskRunner.join_all()

        # Quick sanity check.
        self.assertInstanceFailed('t_alpha#0', RuntimeError)
        self.assertInstanceMissing('t_bravo#0')

        self.assertUnresolvedTasks(['t_alpha', 't_bravo'])
        self.assertUnresolvedInstances(['t_alpha#0'])

        # Simulate changing conditions that will cause the task to run
        # successfully when it is replayed.
        self.manager.update_configuration({
            't_alpha': {
                'after': ['dataReceived'],
                'run': PassThruTask.name,
            },
        })

        # When replaying a failed task, you have to specify which
        # triggers you want to change kwargs for!
        self.manager.replay_failed_instance(
            failed_instance='t_alpha#0',
            replacement_kwargs={
                # I think that's the last of them.
                # Or at least, that's the last one I'm using in this
                # test case.
                'dataReceived': {
                    'name': 'Princess Luna',
                },

                # You can put whatever you want here.
                # Just be careful not to cause another task failure!
                'extra': {
                    'unused': True,
                },
            },
        )
        ThreadingTaskRunner.join_all()

        # The original instance is marked as replayed.
        self.assertInstanceReplayed('t_alpha#0')

        # A new task instance was created for the replay.
        self.assertInstanceFinished(
            't_alpha#1',
            {
                'kwargs': {
                    # Note that the ``replacement_kwargs``
                    # completely replace the trigger kwargs!
                    'dataReceived': {
                        'name': 'Princess Luna',
                        # 'random': 'filbert',
                    },
                    'extra': {
                        'unused': True,
                    },
                },
            })

        # Once the replayed task finishes successfully, it cascades
        # like nothing unusual happened whatsoever.
        self.assertInstanceFinished(
            't_bravo#0', {
                'kwargs': {
                    't_alpha': {
                        'kwargs': {
                            'dataReceived': {
                                'name': 'Princess Luna',
                            },
                            'extra': {
                                'unused': True,
                            },
                        },
                    },
                },
            })

        self.assertUnresolvedTasks([])
        self.assertUnresolvedInstances([])
Beispiel #27
0
    def test_fire_complex_trigger(self):
        """
        Invoking a task that requires multiple triggers to fire.
        """
        self.manager.update_configuration({
            # This task will be invoked without any kwargs.
            't_alpha': {
                'after': ['dataReceived', 'creditsDepleted'],
                'run': PassThruTask.name,
            },

            # This task will use its configured kwargs when it is
            # invoked.
            't_bravo': {
                'after': ['dataReceived', 'creditsDepleted'],
                'run': PassThruTask.name,
                'withParams': {
                    # Remember:  execution order is always
                    # unpredictable in the trigger framework!
                    'dataReceived': {
                        'name': 'Han',
                    },
                    'calibration': {
                        'name': 'Greedo',
                    },
                },
            },

            # Control group:  This task will not fire (requires extra
            # trigger).
            't_psych2': {
                'after': [
                    'creditsDepleted',
                    'dataReceived',
                    'registerComplete',
                ],
                'run': PassThruTask.name,
            },
        })

        self.manager.fire('dataReceived')
        ThreadingTaskRunner.join_all()

        # We can't run matching tasks yet because they require both
        # ``dataReceived`` and ``creditsDepleted``.
        self.assertInstanceUnstarted('t_alpha#0')
        self.assertInstanceUnstarted('t_bravo#0')
        self.assertInstanceUnstarted('t_psych2#0')

        # Everything is unresolved at this point.
        self.assertUnresolvedTasks(['t_alpha', 't_bravo', 't_psych2'])

        # Each task has a corresponding unresolved instance.
        self.assertUnresolvedInstances(
            ['t_alpha#0', 't_bravo#0', 't_psych2#0'])

        # Once both triggers fire, then the task runs.
        # Note that the task receives both sets of kwargs.
        self.manager.fire('creditsDepleted')
        ThreadingTaskRunner.join_all()

        self.assertInstanceFinished('t_alpha#0', {
            'kwargs': {
                'dataReceived': {},
                'creditsDepleted': {},
            },
        })

        # The task config's kwargs get applied when the task runs.
        # Note that the keys do not have to match trigger names.
        self.assertInstanceFinished(
            't_bravo#0',
            {
                'kwargs': {
                    # Remember: order of operations doesn't matter to the
                    # triggers framework (good advice for us all, really).
                    'dataReceived': {
                        'name': 'Han'
                    },
                    'calibration': {
                        'name': 'Greedo'
                    },
                    'creditsDepleted': {},
                },
            })

        # ``t_psych2`` doesn't get run because it is still waiting for
        # the `registerComplete` trigger to fire.
        self.assertInstanceUnstarted('t_psych2#0')

        # At this point, only ``t_psych2#0`` is unresolved.
        self.assertUnresolvedTasks(['t_psych2'])
        self.assertUnresolvedInstances(['t_psych2#0'])
Beispiel #28
0
    def test_hook_post_skip(self):
        """
        Subclassing the trigger manager with a post-skip hook.
        """
        self.manager.update_configuration({
            't_failingTask': {
                'after': [],
                'andEvery': 'fail',
                'run': FailingTask.name,
            },
        })

        ##
        # Set things up by running a task instance which will fail.
        del self.post_skip_calls[:]
        self.manager.fire('fail')
        ThreadingTaskRunner.join_all()

        # Quick sanity check (this also refreshes the task instance
        # metadata).
        self.assertInstanceFailed('t_failingTask#0', RuntimeError)

        # The task instance failed, but we haven't skipped anything
        # yet.
        self.assertListEqual(self.post_skip_calls, [])

        ##
        # Replaying a failed task does not invoke the post-skip
        # hook.
        del self.post_skip_calls[:]
        self.manager.replay_failed_instance('t_failingTask#0')
        ThreadingTaskRunner.join_all()

        self.assertListEqual(self.post_skip_calls, [])

        # Quick sanity check (this also refreshes the task instance
        # metadata).
        self.assertInstanceFailed('t_failingTask#1', RuntimeError)

        ##
        # Skipping the failed instance will, naturally, invoke
        # the post-skip handler.
        self.manager.skip_failed_instance('t_failingTask#1', cascade=False)

        self.assertListEqual(
            self.post_skip_calls,
            [('t_failingTask#1', False)],
        )

        ##
        # Let's try it again, just to make sure that cascading is
        # handled correctly.
        del self.post_skip_calls[:]
        self.manager.fire('fail')
        ThreadingTaskRunner.join_all()

        # Quick sanity check (this also refreshes the task instance
        # metadata).
        self.assertInstanceFailed('t_failingTask#2', RuntimeError)

        self.manager.skip_failed_instance('t_failingTask#2', cascade=True)

        self.assertListEqual(
            self.post_skip_calls,
            [('t_failingTask#2', True)],
        )
Beispiel #29
0
    def test_fire_cascade(self):
        """
        Firing a trigger successfully causes subsequent triggers to
        fire.
        """
        self.manager.update_configuration({
            't_alpha': {
                'after': ['dataReceived'],
                'run': PassThruTask.name,
            },
            't_bravo': {
                'after': ['t_alpha'],
                'run': PassThruTask.name,
            },
            't_charlie': {
                'after': ['t_alpha'],
                'run': PassThruTask.name,
                'withParams': {
                    't_alpha': {
                        'foo': 'bar',

                        #
                        # Just to be tricky, try to replace the result
                        # of ``t_alpha`` that gets passed along to
                        # ``t_charlie``.
                        #
                        # The trigger framework will allow you to
                        # inject values, but you can't replace them.
                        #
                        # Relying on this is probably an anti-pattern,
                        # but the trigger framework won't judge you
                        # (although I can't say the same for your
                        # peers).
                        #
                        'kwargs': {
                            'hacked': True,
                        },
                    },
                    'geek_pun': {
                        'baz': 'luhrmann',
                    },
                },
            },
        })

        self.manager.fire('dataReceived')
        ThreadingTaskRunner.join_all()

        # As expected, this causes the ``t_alpha`` task to run.
        self.assertInstanceFinished('t_alpha#0', {
            'kwargs': {
                'dataReceived': {},
            },
        })

        # Then, once the task finishes, it fires its own name as a
        # trigger, causing the ``t_bravo`` and ``t_charlie`` tasks to run.
        self.assertInstanceFinished(
            't_bravo#0',
            {
                'kwargs': {
                    # ``t_alpha`` passed its result along.
                    't_alpha': {
                        'kwargs': {
                            'dataReceived': {},
                        },
                    },
                }
            })

        self.assertInstanceFinished(
            't_charlie#0',
            {
                'kwargs': {
                    # The task kwargs were merged into the instance kwargs
                    # recursively.
                    #
                    # The result is... confusing, which is why you probably
                    # shouldn't ever rely on this functionality.
                    't_alpha': {
                        'foo': 'bar',
                        'kwargs': {
                            'dataReceived': {},
                            'hacked': True,
                        },
                    },
                    'geek_pun': {
                        'baz': 'luhrmann'
                    },
                },
            })

        # Everything is resolved at this point.
        self.assertUnresolvedTasks([])
        self.assertUnresolvedInstances([])
    def test_replay_failed_instance(self):
        """
        Replaying a failed task instance (e.g., after fixing a problem
        with the stored data that caused the failure).
        """
        self.manager.update_configuration({
            't_alpha': {
                'after': ['dataReceived'],
                'run': FailingTask.name,
            },
            't_bravo': {
                'after': ['t_alpha'],
                'run': PassThruTask.name,
            },
        })

        self.manager.fire('dataReceived')
        ThreadingTaskRunner.join_all()

        # Quick sanity check.
        self.assertInstanceFailed('t_alpha#0', RuntimeError)
        self.assertInstanceMissing('t_bravo#0')

        self.assertUnresolvedTasks(['t_alpha', 't_bravo'])

        # Failed instances are considered unresolved.
        self.assertUnresolvedInstances(['t_alpha#0'])

        # Simulate changing conditions that will cause the task to run
        # successfully when it is replayed.
        self.manager.update_configuration({
            't_alpha': {
                'after': ['dataReceived'],
                'run': PassThruTask.name,
            },
        })

        self.manager.replay_failed_instance('t_alpha#0')
        ThreadingTaskRunner.join_all()

        # The original instance is marked as replayed.
        self.assertInstanceReplayed('t_alpha#0')

        # A new task instance was created for the replay.
        self.assertInstanceFinished('t_alpha#1', {
            'kwargs': {
                'dataReceived': {},
            },
        })

        # Once the replayed task finishes successfully, it cascades
        # like nothing unusual happened whatsoever.
        self.assertInstanceFinished('t_bravo#0', {
            'kwargs': {
                't_alpha': {
                    'kwargs': {
                        'dataReceived': {},
                    },
                },
            },
        })

        self.assertUnresolvedTasks([])
        # The replayed instance is considered resolved.
        self.assertUnresolvedInstances([])