示例#1
0
 def test_single_processor(self):
     mock_processor = MagicMock()
     router = RoutingBackend(processors=[
         mock_processor
     ])
     router.send(self.sample_event)
     mock_processor.assert_called_once_with(self.sample_event)
示例#2
0
 def test_single_backend(self):
     mock_backend = MagicMock()
     router = RoutingBackend(backends={
         'mock0': mock_backend
     })
     router.send(self.sample_event)
     mock_backend.send.assert_called_once_with(self.sample_event)
示例#3
0
 def test_single_backend(self):
     mock_backend = MagicMock()
     router = RoutingBackend(backends={
         'mock0': mock_backend
     })
     router.send(self.sample_event)
     mock_backend.send.assert_called_once_with(self.sample_event)
示例#4
0
 def test_backend_failure(self):
     backends = {str(i): MagicMock() for i in range(5)}
     backends['1'].send.side_effect = RuntimeError
     router = RoutingBackend(backends=backends)
     router.send(self.sample_event)
     for backend in backends.itervalues():
         backend.send.assert_called_once_with(self.sample_event)
示例#5
0
 def test_single_processor(self):
     mock_processor = MagicMock()
     router = RoutingBackend(processors=[
         mock_processor
     ])
     router.send(self.sample_event)
     mock_processor.assert_called_once_with(self.sample_event)
示例#6
0
 def test_multiple_processors(self):
     processors = [MagicMock() for __ in range(5)]
     for processor in processors:
         processor.return_value = self.sample_event
     router = RoutingBackend(processors=processors)
     router.send(self.sample_event)
     for processor in processors:
         processor.assert_called_once_with(self.sample_event)
示例#7
0
 def test_multiple_backends(self):
     backends = {
         str(i): MagicMock()
         for i in range(5)
     }
     router = RoutingBackend(backends=backends)
     router.send(self.sample_event)
     for backend in backends.values():
         backend.send.assert_called_once_with(self.sample_event)
示例#8
0
 def test_multiple_backends(self):
     backends = {
         str(i): MagicMock()
         for i in range(5)
     }
     router = RoutingBackend(backends=backends)
     router.send(self.sample_event)
     for backend in backends.values():
         backend.send.assert_called_once_with(self.sample_event)
示例#9
0
 def test_backend_failure(self):
     backends = {
         str(i): MagicMock()
         for i in range(5)
     }
     backends['1'].send.side_effect = RuntimeError
     router = RoutingBackend(backends=backends)
     router.send(self.sample_event)
     for backend in six.itervalues(backends):
         backend.send.assert_called_once_with(self.sample_event)
示例#10
0
 def test_multiple_processors(self):
     processors = [
         MagicMock()
         for __ in range(5)
     ]
     for processor in processors:
         processor.return_value = self.sample_event
     router = RoutingBackend(processors=processors)
     router.send(self.sample_event)
     for processor in processors:
         processor.assert_called_once_with(self.sample_event)
示例#11
0
    def test_multiple_backends_and_processors(self):
        backends = {str(i): MagicMock() for i in range(5)}
        processors = [MagicMock() for __ in range(5)]
        for processor in processors:
            processor.return_value = self.sample_event

        router = RoutingBackend(backends=backends, processors=processors)
        router.send(self.sample_event)
        for processor in processors:
            processor.assert_called_once_with(self.sample_event)
        for backend in backends.values():
            backend.send.assert_called_once_with(self.sample_event)
示例#12
0
    def test_multiple_backends_and_processors(self):
        backends = {
            str(i): MagicMock()
            for i in range(5)
        }
        processors = [
            MagicMock()
            for __ in range(5)
        ]
        for processor in processors:
            processor.return_value = self.sample_event

        router = RoutingBackend(backends=backends, processors=processors)
        router.send(self.sample_event)
        for processor in processors:
            processor.assert_called_once_with(self.sample_event)
        for backend in backends.values():
            backend.send.assert_called_once_with(self.sample_event)
示例#13
0
    def test_nested_routing_with_abort(self):
        mock_abort_processing = MagicMock()
        mock_abort_processing.side_effect = EventEmissionExit

        left_backend = MagicMock()
        left_router = RoutingBackend(backends={'0': left_backend}, processors=[mock_abort_processing])

        right_backend = MagicMock()
        right_router = RoutingBackend(backends={'0': right_backend})

        root_router = RoutingBackend(backends={
            'left': left_router,
            'right': right_router,
        })

        root_router.send(self.sample_event)

        right_backend.send.assert_called_once_with(self.sample_event)
        self.assertEqual(len(left_backend.mock_calls), 0)
        mock_abort_processing.assert_called_once_with(self.sample_event)
示例#14
0
    def test_backend_call_order(self):
        class OrderRecordingBackend(object):
            """Keep track of the order that the backends are called in"""
            def __init__(self, name, call_order):
                self._name = name
                self._order = call_order

            def send(self, event):  # pylint: disable=unused-argument
                """Do nothing except record that this was called"""
                self._order.append(self._name)

        call_order = []
        backends = {
            str(i): OrderRecordingBackend(str(i), call_order)
            for i in range(5)
        }

        router = RoutingBackend(backends=backends)

        router.send(self.sample_event)
        self.assertEqual(call_order, ['0', '1', '2', '3', '4'])
示例#15
0
    def test_backend_call_order(self):

        class OrderRecordingBackend(object):
            """Keep track of the order that the backends are called in"""

            def __init__(self, name, call_order):
                self._name = name
                self._order = call_order

            def send(self, event):  # pylint: disable=unused-argument
                """Do nothing except record that this was called"""
                self._order.append(self._name)

        call_order = []
        backends = {
            str(i): OrderRecordingBackend(str(i), call_order)
            for i in range(5)
        }

        router = RoutingBackend(backends=backends)

        router.send(self.sample_event)
        self.assertEqual(call_order, ['0', '1', '2', '3', '4'])
示例#16
0
    def test_nested_routing_with_abort(self):
        mock_abort_processing = MagicMock()
        mock_abort_processing.side_effect = EventEmissionExit

        left_backend = MagicMock()
        left_router = RoutingBackend(backends={'0': left_backend}, processors=[mock_abort_processing])

        right_backend = MagicMock()
        right_router = RoutingBackend(backends={'0': right_backend})

        root_router = RoutingBackend(backends={
            'left': left_router,
            'right': right_router,
        })

        root_router.send(self.sample_event)

        right_backend.send.assert_called_once_with(self.sample_event)
        self.assertEqual(len(left_backend.mock_calls), 0)
        mock_abort_processing.assert_called_once_with(self.sample_event)
示例#17
0
 def test_backend_without_send(self):
     with self.assertRaisesRegexp(  # pylint: disable=deprecated-method,useless-suppression
             ValueError,
             r'Backend \w+ does not have a callable "send" method.'):
         RoutingBackend(backends={'a': object()})
示例#18
0
    def setUp(self):
        super(TestRoutingBackend, self).setUp()
        self.sample_event = {'name': sentinel.name}

        self.mock_backend = MagicMock()
        self.router = RoutingBackend(backends={'0': self.mock_backend})
示例#19
0
 def test_non_callable_processor_simple_type(self):
     with self.assertRaisesRegexp(ValueError,
                                  r'Processor \w+ is not callable.'):
         RoutingBackend(processors=['b'])
示例#20
0
 def test_non_callable_processor(self):
     with self.assertRaisesRegexp(ValueError,
                                  r'Processor \w+ is not callable.'):
         RoutingBackend(processors=[object()])
示例#21
0
 def test_backend_without_send(self):
     with self.assertRaisesRegexp(
             ValueError,
             r'Backend \w+ does not have a callable "send" method.'):
         RoutingBackend(backends={'a': object()})
示例#22
0
 def test_non_callable_backend(self):
     with self.assertRaisesRegexp(
             ValueError,
             r'Backend \w+ does not have a callable "send" method.'):
         RoutingBackend(backends={'a': 'b'})
示例#23
0
    def setUp(self):
        self.sample_event = {'name': sentinel.name}

        self.mock_backend = MagicMock()
        self.router = RoutingBackend(backends={'0': self.mock_backend})
示例#24
0
class TestRoutingBackend(TestCase):
    """Test the routing backend"""
    def setUp(self):
        self.sample_event = {'name': sentinel.name}

        self.mock_backend = MagicMock()
        self.router = RoutingBackend(backends={'0': self.mock_backend})

    def test_non_callable_backend(self):
        with self.assertRaisesRegexp(
                ValueError,
                r'Backend \w+ does not have a callable "send" method.'):
            RoutingBackend(backends={'a': 'b'})

    def test_backend_without_send(self):
        with self.assertRaisesRegexp(
                ValueError,
                r'Backend \w+ does not have a callable "send" method.'):
            RoutingBackend(backends={'a': object()})

    def test_non_callable_processor(self):
        with self.assertRaisesRegexp(ValueError,
                                     r'Processor \w+ is not callable.'):
            RoutingBackend(processors=[object()])

    def test_non_callable_processor_simple_type(self):
        with self.assertRaisesRegexp(ValueError,
                                     r'Processor \w+ is not callable.'):
            RoutingBackend(processors=['b'])

    def test_single_processor(self):
        mock_processor = MagicMock()
        router = RoutingBackend(processors=[mock_processor])
        router.send(self.sample_event)
        mock_processor.assert_called_once_with(self.sample_event)

    def test_single_backend(self):
        mock_backend = MagicMock()
        router = RoutingBackend(backends={'mock0': mock_backend})
        router.send(self.sample_event)
        mock_backend.send.assert_called_once_with(self.sample_event)

    def test_multiple_backends(self):
        backends = {str(i): MagicMock() for i in range(5)}
        router = RoutingBackend(backends=backends)
        router.send(self.sample_event)
        for backend in backends.values():
            backend.send.assert_called_once_with(self.sample_event)

    def test_backend_failure(self):
        backends = {str(i): MagicMock() for i in range(5)}
        backends['1'].send.side_effect = RuntimeError
        router = RoutingBackend(backends=backends)
        router.send(self.sample_event)
        for backend in backends.itervalues():
            backend.send.assert_called_once_with(self.sample_event)

    def test_multiple_processors(self):
        processors = [MagicMock() for __ in range(5)]
        for processor in processors:
            processor.return_value = self.sample_event
        router = RoutingBackend(processors=processors)
        router.send(self.sample_event)
        for processor in processors:
            processor.assert_called_once_with(self.sample_event)

    def test_multiple_backends_and_processors(self):
        backends = {str(i): MagicMock() for i in range(5)}
        processors = [MagicMock() for __ in range(5)]
        for processor in processors:
            processor.return_value = self.sample_event

        router = RoutingBackend(backends=backends, processors=processors)
        router.send(self.sample_event)
        for processor in processors:
            processor.assert_called_once_with(self.sample_event)
        for backend in backends.values():
            backend.send.assert_called_once_with(self.sample_event)

    def test_callable_class_processor(self):
        class SampleProcessor(object):
            """An event processing class"""
            def __call__(self, event):
                """Modify the event type"""
                event['name'] = sentinel.changed_name

        self.router.register_processor(SampleProcessor())
        self.router.send(self.sample_event)
        self.assert_single_event_emitted({'name': sentinel.changed_name})

    def assert_single_event_emitted(self, event):
        """Assert that the mock backend is called exactly once with the provided event"""
        self.mock_backend.send.assert_called_once_with(event)

    def test_function_processor(self):
        def change_name(event):
            """Modify the event type of the event"""
            event['name'] = sentinel.changed_name
            return event

        self.router.register_processor(change_name)
        self.router.send(self.sample_event)
        self.assert_single_event_emitted({'name': sentinel.changed_name})

    def test_processor_chain(self):
        def change_name(event):
            """Modify the event type of the event"""
            event['name'] = sentinel.changed_name
            return event

        def inject_fields(event):
            """Add a couple fields to the event"""
            event['other'] = sentinel.other
            event['to_remove'] = sentinel.to_remove
            return event

        def remove_field(event):
            """Remove a field to the event"""
            self.assertEquals(event['to_remove'], sentinel.to_remove)
            del event['to_remove']
            return event

        def ensure_modified_event(event):
            """Assert the first processor added a field to the event"""
            self.assertEquals(event['name'], sentinel.changed_name)
            self.assertEquals(event['other'], sentinel.other)
            return event

        self.router.register_processor(change_name)
        self.router.register_processor(inject_fields)
        self.router.register_processor(remove_field)
        self.router.register_processor(ensure_modified_event)
        self.router.send(self.sample_event)

        self.assert_single_event_emitted({
            'name': sentinel.changed_name,
            'other': sentinel.other
        })

    def test_processor_failure(self):
        def always_fail(event):  # pylint: disable=unused-argument
            """Always raises an error"""
            raise ValueError

        def change_name(event):
            """Modify the event type of the event"""
            event['name'] = sentinel.changed_name
            return event

        self.router.register_processor(always_fail)
        self.router.register_processor(change_name)
        self.router.send(self.sample_event)
        self.assert_single_event_emitted({'name': sentinel.changed_name})

    def test_processor_returns_none(self):
        def return_none(event):  # pylint: disable=unused-argument
            """Don't return the event"""
            pass

        self.router.register_processor(return_none)
        self.router.send(self.sample_event)
        self.assert_single_event_emitted(self.sample_event)

    def test_processor_modifies_the_same_event_object(self):
        def forget_return(event):
            """Modify the event without returning it"""
            event['name'] = sentinel.forgotten_return

        def ensure_name_changed(event):
            """Assert the event type has been modified even though the event wasn't returned"""
            self.assertEquals(event['name'], sentinel.forgotten_return)

        self.router.register_processor(forget_return)
        self.router.register_processor(ensure_name_changed)
        self.router.send(self.sample_event)
        self.assert_single_event_emitted({'name': sentinel.forgotten_return})

    def test_processor_abort(self):
        def abort_processing(event):  # pylint: disable=unused-argument
            """Always abort processing"""
            raise EventEmissionExit

        def fail_if_called(event):  # pylint: disable=unused-argument
            """Fail the test immediately if this is called"""
            self.fail('This processor should never be called')

        self.router.register_processor(abort_processing)
        self.router.register_processor(fail_if_called)
        self.router.send(self.sample_event)
        self.assertEqual(len(self.mock_backend.mock_calls), 0)

    def test_nested_routing_with_abort(self):
        mock_abort_processing = MagicMock()
        mock_abort_processing.side_effect = EventEmissionExit

        left_backend = MagicMock()
        left_router = RoutingBackend(backends={'0': left_backend},
                                     processors=[mock_abort_processing])

        right_backend = MagicMock()
        right_router = RoutingBackend(backends={'0': right_backend})

        root_router = RoutingBackend(backends={
            'left': left_router,
            'right': right_router,
        })

        root_router.send(self.sample_event)

        right_backend.send.assert_called_once_with(self.sample_event)
        self.assertEqual(len(left_backend.mock_calls), 0)
        mock_abort_processing.assert_called_once_with(self.sample_event)

    def test_backend_call_order(self):
        class OrderRecordingBackend(object):
            """Keep track of the order that the backends are called in"""
            def __init__(self, name, call_order):
                self._name = name
                self._order = call_order

            def send(self, event):  # pylint: disable=unused-argument
                """Do nothing except record that this was called"""
                self._order.append(self._name)

        call_order = []
        backends = {
            str(i): OrderRecordingBackend(str(i), call_order)
            for i in range(5)
        }

        router = RoutingBackend(backends=backends)

        router.send(self.sample_event)
        self.assertEqual(call_order, ['0', '1', '2', '3', '4'])
示例#25
0
 def __init__(self, backends=None, context_locator=None, processors=None):
     self.routing_backend = RoutingBackend(backends=backends, processors=processors)
     self.context_locator = context_locator or DefaultContextLocator()
示例#26
0
class Tracker(object):
    """
    Track application events.  Holds references to a set of backends that will
    be used to persist any events that are emitted.
    """
    def __init__(self, backends=None, context_locator=None, processors=None):
        self.routing_backend = RoutingBackend(backends=backends, processors=processors)
        self.context_locator = context_locator or DefaultContextLocator()

    @property
    def located_context(self):
        """
        The thread local context for this tracker.
        """
        return self.context_locator.get()

    def get_backend(self, name):
        """Gets the backend that was configured with `name`"""
        return self.backends[name]

    @property
    def processors(self):
        """The list of registered processors"""
        return self.routing_backend.processors

    @property
    def backends(self):
        """The dictionary of registered backends"""
        return self.routing_backend.backends

    def emit(self, name=None, data=None):
        """
        Emit an event annotated with the UTC time when this function was called.

        `name` is a unique identification string for an event that has
            already been registered.
        `data` is a dictionary mapping field names to the value to include in the event.
            Note that all values provided must be serializable.

        """
        event = {
            'name': name or UNKNOWN_EVENT_TYPE,
            'timestamp': datetime.now(UTC),
            'data': data or {},
            'context': self.resolve_context()
        }

        self.routing_backend.send(event)

    def resolve_context(self):
        """
        Create a new dictionary that corresponds to the union of all of the
        contexts that have been entered but not exited at this point.
        """
        merged = dict()
        for context in self.located_context.values():
            merged.update(context)
        return merged

    def enter_context(self, name, ctx):
        """
        Enter a named context.  Any events emitted after calling this
        method will contain all of the key-value pairs included in `ctx`
        unless overridden by a context that is entered after this call.
        """
        self.located_context[name] = ctx

    def exit_context(self, name):
        """
        Exit a named context.  This will remove all key-value pairs
        associated with this context from any events emitted after it
        is removed.
        """
        del self.located_context[name]

    @contextmanager
    def context(self, name, ctx):
        """
        Execute the block with the given context applied.  This manager
        ensures that the context is removed even if an exception is raised
        within the context.
        """
        self.enter_context(name, ctx)
        try:
            yield
        finally:
            self.exit_context(name)
示例#27
0
 def test_non_callable_processor_simple_type(self):
     with self.assertRaisesRegexp(  # pylint: disable=deprecated-method,useless-suppression
             ValueError, r'Processor \w+ is not callable.'):
         RoutingBackend(processors=['b'])
示例#28
0
class TestRoutingBackend(TestCase):
    """Test the routing backend"""

    def setUp(self):
        super(TestRoutingBackend, self).setUp()
        self.sample_event = {'name': sentinel.name}

        self.mock_backend = MagicMock()
        self.router = RoutingBackend(backends={'0': self.mock_backend})

    def test_non_callable_backend(self):
        with self.assertRaisesRegexp(  # pylint: disable=deprecated-method,useless-suppression
                ValueError, r'Backend \w+ does not have a callable "send" method.'):
            RoutingBackend(backends={
                'a': 'b'
            })

    def test_backend_without_send(self):
        with self.assertRaisesRegexp(  # pylint: disable=deprecated-method,useless-suppression
                ValueError, r'Backend \w+ does not have a callable "send" method.'):
            RoutingBackend(backends={
                'a': object()
            })

    def test_non_callable_processor(self):
        with self.assertRaisesRegexp(  # pylint: disable=deprecated-method,useless-suppression
                ValueError, r'Processor \w+ is not callable.'):
            RoutingBackend(processors=[
                object()
            ])

    def test_non_callable_processor_simple_type(self):
        with self.assertRaisesRegexp(  # pylint: disable=deprecated-method,useless-suppression
                ValueError, r'Processor \w+ is not callable.'):
            RoutingBackend(processors=[
                'b'
            ])

    def test_single_processor(self):
        mock_processor = MagicMock()
        router = RoutingBackend(processors=[
            mock_processor
        ])
        router.send(self.sample_event)
        mock_processor.assert_called_once_with(self.sample_event)

    def test_single_backend(self):
        mock_backend = MagicMock()
        router = RoutingBackend(backends={
            'mock0': mock_backend
        })
        router.send(self.sample_event)
        mock_backend.send.assert_called_once_with(self.sample_event)

    def test_multiple_backends(self):
        backends = {
            str(i): MagicMock()
            for i in range(5)
        }
        router = RoutingBackend(backends=backends)
        router.send(self.sample_event)
        for backend in backends.values():
            backend.send.assert_called_once_with(self.sample_event)

    def test_backend_failure(self):
        backends = {
            str(i): MagicMock()
            for i in range(5)
        }
        backends['1'].send.side_effect = RuntimeError
        router = RoutingBackend(backends=backends)
        router.send(self.sample_event)
        for backend in six.itervalues(backends):
            backend.send.assert_called_once_with(self.sample_event)

    def test_multiple_processors(self):
        processors = [
            MagicMock()
            for __ in range(5)
        ]
        for processor in processors:
            processor.return_value = self.sample_event
        router = RoutingBackend(processors=processors)
        router.send(self.sample_event)
        for processor in processors:
            processor.assert_called_once_with(self.sample_event)

    def test_multiple_backends_and_processors(self):
        backends = {
            str(i): MagicMock()
            for i in range(5)
        }
        processors = [
            MagicMock()
            for __ in range(5)
        ]
        for processor in processors:
            processor.return_value = self.sample_event

        router = RoutingBackend(backends=backends, processors=processors)
        router.send(self.sample_event)
        for processor in processors:
            processor.assert_called_once_with(self.sample_event)
        for backend in backends.values():
            backend.send.assert_called_once_with(self.sample_event)

    def test_callable_class_processor(self):
        class SampleProcessor(object):
            """An event processing class"""
            def __call__(self, event):
                """Modify the event type"""
                event['name'] = sentinel.changed_name

        self.router.register_processor(SampleProcessor())
        self.router.send(self.sample_event)
        self.assert_single_event_emitted({'name': sentinel.changed_name})

    def assert_single_event_emitted(self, event):
        """Assert that the mock backend is called exactly once with the provided event"""
        self.mock_backend.send.assert_called_once_with(event)

    def test_function_processor(self):
        def change_name(event):
            """Modify the event type of the event"""
            event['name'] = sentinel.changed_name
            return event

        self.router.register_processor(change_name)
        self.router.send(self.sample_event)
        self.assert_single_event_emitted({'name': sentinel.changed_name})

    def test_processor_chain(self):

        def change_name(event):
            """Modify the event type of the event"""
            event['name'] = sentinel.changed_name
            return event

        def inject_fields(event):
            """Add a couple fields to the event"""
            event['other'] = sentinel.other
            event['to_remove'] = sentinel.to_remove
            return event

        def remove_field(event):
            """Remove a field to the event"""
            self.assertEqual(event['to_remove'], sentinel.to_remove)
            del event['to_remove']
            return event

        def ensure_modified_event(event):
            """Assert the first processor added a field to the event"""
            self.assertEqual(event['name'], sentinel.changed_name)
            self.assertEqual(event['other'], sentinel.other)
            return event

        self.router.register_processor(change_name)
        self.router.register_processor(inject_fields)
        self.router.register_processor(remove_field)
        self.router.register_processor(ensure_modified_event)
        self.router.send(self.sample_event)

        self.assert_single_event_emitted(
            {
                'name': sentinel.changed_name,
                'other': sentinel.other
            }
        )

    def test_processor_failure(self):

        def always_fail(event):  # pylint: disable=unused-argument
            """Always raises an error"""
            raise ValueError

        def change_name(event):
            """Modify the event type of the event"""
            event['name'] = sentinel.changed_name
            return event

        self.router.register_processor(always_fail)
        self.router.register_processor(change_name)
        self.router.send(self.sample_event)
        self.assert_single_event_emitted({'name': sentinel.changed_name})

    def test_processor_returns_none(self):

        def return_none(event):  # pylint: disable=unused-argument
            """Don't return the event"""
            pass

        self.router.register_processor(return_none)
        self.router.send(self.sample_event)
        self.assert_single_event_emitted(self.sample_event)

    def test_processor_modifies_the_same_event_object(self):

        def forget_return(event):
            """Modify the event without returning it"""
            event['name'] = sentinel.forgotten_return

        def ensure_name_changed(event):
            """Assert the event type has been modified even though the event wasn't returned"""
            self.assertEqual(event['name'], sentinel.forgotten_return)

        self.router.register_processor(forget_return)
        self.router.register_processor(ensure_name_changed)
        self.router.send(self.sample_event)
        self.assert_single_event_emitted({'name': sentinel.forgotten_return})

    def test_processor_abort(self):

        def abort_processing(event):  # pylint: disable=unused-argument
            """Always abort processing"""
            raise EventEmissionExit

        def fail_if_called(event):  # pylint: disable=unused-argument
            """Fail the test immediately if this is called"""
            self.fail('This processor should never be called')

        self.router.register_processor(abort_processing)
        self.router.register_processor(fail_if_called)
        self.router.send(self.sample_event)
        self.assertEqual(len(self.mock_backend.mock_calls), 0)

    def test_nested_routing_with_abort(self):
        mock_abort_processing = MagicMock()
        mock_abort_processing.side_effect = EventEmissionExit

        left_backend = MagicMock()
        left_router = RoutingBackend(backends={'0': left_backend}, processors=[mock_abort_processing])

        right_backend = MagicMock()
        right_router = RoutingBackend(backends={'0': right_backend})

        root_router = RoutingBackend(backends={
            'left': left_router,
            'right': right_router,
        })

        root_router.send(self.sample_event)

        right_backend.send.assert_called_once_with(self.sample_event)
        self.assertEqual(len(left_backend.mock_calls), 0)
        mock_abort_processing.assert_called_once_with(self.sample_event)

    def test_backend_call_order(self):

        class OrderRecordingBackend(object):
            """Keep track of the order that the backends are called in"""

            def __init__(self, name, call_order):
                self._name = name
                self._order = call_order

            def send(self, event):  # pylint: disable=unused-argument
                """Do nothing except record that this was called"""
                self._order.append(self._name)

        call_order = []
        backends = {
            str(i): OrderRecordingBackend(str(i), call_order)
            for i in range(5)
        }

        router = RoutingBackend(backends=backends)

        router.send(self.sample_event)
        self.assertEqual(call_order, ['0', '1', '2', '3', '4'])