1
# Copyright (c) 2008 Divmod. See LICENSE for details.
4
Tests for L{epsilon.amprouter}.
7
from zope.interface import implements
8
from zope.interface.verify import verifyObject
10
from twisted.python.failure import Failure
11
from twisted.protocols.amp import IBoxReceiver, IBoxSender
12
from twisted.trial.unittest import TestCase
14
from epsilon.amprouter import _ROUTE, RouteNotConnected, Router
19
A stub AMP box receiver which just keeps track of whether it has been
20
started or stopped and what boxes have been delivered to it.
22
@ivar sender: C{None} until C{startReceivingBoxes} is called, then a
23
reference to the L{IBoxSender} passed to that method.
25
@ivar reason: C{None} until {stopReceivingBoxes} is called, then a
26
reference to the L{Failure} passed to that method.
28
@ivar started: C{False} until C{startReceivingBoxes} is called, then
31
@ivar stopped: C{False} until C{stopReceivingBoxes} is called, then
34
implements(IBoxReceiver)
45
def startReceivingBoxes(self, sender):
50
def ampBoxReceived(self, box):
51
if self.started and not self.stopped:
52
self.boxes.append(box)
55
def stopReceivingBoxes(self, reason):
61
class CollectingSender:
63
An L{IBoxSender} which collects and saves boxes and errors sent to it.
65
implements(IBoxSender)
72
def sendBox(self, box):
74
Reject boxes with non-string keys or values; save all the rest in
77
for k, v in box.iteritems():
78
if not (isinstance(k, str) and isinstance(v, str)):
79
raise TypeError("Cannot send boxes containing non-strings")
80
self.boxes.append(box)
83
def unhandledError(self, failure):
84
self.errors.append(failure.getErrorMessage())
88
class RouteTests(TestCase):
90
Tests for L{Route}, the L{IBoxSender} which handles adding routing
91
information to outgoing boxes.
95
Create a route attached to a stub sender.
97
self.receiver = SomeReceiver()
98
self.sender = CollectingSender()
99
self.localName = u"foo"
100
self.remoteName = u"bar"
101
self.router = Router()
102
self.router.startReceivingBoxes(self.sender)
103
self.route = self.router.bindRoute(self.receiver, self.localName)
106
def test_interfaces(self):
108
L{Route} instances provide L{IBoxSender}.
110
self.assertTrue(verifyObject(IBoxSender, self.route))
113
def test_start(self):
115
L{Route.start} starts its L{IBoxReceiver}.
117
self.assertFalse(self.receiver.started)
119
self.assertTrue(self.receiver.started)
120
self.assertIdentical(self.receiver.sender, self.route)
125
L{Route.stop} stops its L{IBoxReceiver}.
128
self.assertFalse(self.receiver.stopped)
129
self.route.stop(Failure(RuntimeError("foo")))
130
self.assertTrue(self.receiver.stopped)
131
self.receiver.reason.trap(RuntimeError)
134
def test_sendBox(self):
136
L{Route.sendBox} adds the route name to the box before passing it on to
137
the underlying sender.
139
self.route.connectTo(self.remoteName)
140
self.route.sendBox({"foo": "bar"})
142
self.sender.boxes, [{_ROUTE: self.remoteName, "foo": "bar"}])
145
def test_sendUnroutedBox(self):
147
If C{Route.connectTo} is called with C{None}, no route name is added to
150
self.route.connectTo(None)
151
self.route.sendBox({"foo": "bar"})
153
self.sender.boxes, [{"foo": "bar"}])
156
def test_sendBoxWithoutConnection(self):
158
L{Route.sendBox} raises L{RouteNotConnected} if called before the
159
L{Route} is connected to a remote route name.
162
RouteNotConnected, self.route.sendBox, {'foo': 'bar'})
165
def test_unbind(self):
167
L{Route.unbind} removes the route from its router.
171
KeyError, self.router.ampBoxReceived, {_ROUTE: self.localName})
175
class RouterTests(TestCase):
177
Tests for L{Router}, the L{IBoxReceiver} which directs routed AMP boxes to
182
Create sender, router, receiver, and route objects.
184
self.sender = CollectingSender()
185
self.router = Router()
186
self.router.startReceivingBoxes(self.sender)
187
self.receiver = SomeReceiver()
188
self.route = self.router.bindRoute(self.receiver)
189
self.route.connectTo(u"foo")
192
def test_interfaces(self):
194
L{Router} instances provide L{IBoxReceiver}.
196
self.assertTrue(verifyObject(IBoxReceiver, self.router))
199
def test_uniqueRoutes(self):
201
L{Router.createRouteIdentifier} returns a new, different route
202
identifier on each call.
204
identifiers = [self.router.createRouteIdentifier() for x in range(10)]
205
self.assertEqual(len(set(identifiers)), len(identifiers))
210
L{Router.bind} returns a new L{Route} instance which will send boxes to
211
the L{Route}'s L{IBoxSender} after adding a C{_ROUTE} key to them.
213
self.route.sendBox({'foo': 'bar'})
216
[{_ROUTE: self.route.remoteRouteName, 'foo': 'bar'}])
218
self.route.unhandledError(Failure(Exception("some test exception")))
220
self.sender.errors, ["some test exception"])
223
def test_bindBeforeStart(self):
225
If a L{Route} is created with L{Router.bind} before the L{Router} is
226
started with L{Router.startReceivingBoxes}, the L{Route} is created
227
unstarted and only started when the L{Router} is started.
230
receiver = SomeReceiver()
231
route = router.bindRoute(receiver)
232
route.connectTo(u'quux')
233
self.assertFalse(receiver.started)
234
sender = CollectingSender()
235
router.startReceivingBoxes(sender)
236
self.assertTrue(receiver.started)
237
route.sendBox({'foo': 'bar'})
239
sender.boxes, [{_ROUTE: route.remoteRouteName, 'foo': 'bar'}])
240
router.ampBoxReceived({_ROUTE: route.localRouteName, 'baz': 'quux'})
241
self.assertEqual(receiver.boxes, [{'baz': 'quux'}])
244
def test_bindBeforeStartFinishAfterStart(self):
246
If a L{Route} is created with L{Router.connect} before the L{Router} is
247
started with L{Router.startReceivingBoxes} but the Deferred returned by
248
the connect thunk does not fire until after the router is started, the
249
L{IBoxReceiver} associated with the route is not started until that
250
Deferred fires and the route is associated with a remote route name.
253
receiver = SomeReceiver()
254
route = router.bindRoute(receiver)
255
sender = CollectingSender()
256
router.startReceivingBoxes(sender)
257
self.assertFalse(receiver.started)
258
route.connectTo(u"remoteName")
259
self.assertTrue(receiver.started)
260
receiver.sender.sendBox({'foo': 'bar'})
261
self.assertEqual(sender.boxes, [{_ROUTE: 'remoteName', 'foo': 'bar'}])
264
def test_ampBoxReceived(self):
266
L{Router.ampBoxReceived} passes on AMP boxes to the L{IBoxReceiver}
267
identified by the route key in the box.
269
firstReceiver = SomeReceiver()
270
firstRoute = self.router.bindRoute(firstReceiver)
273
secondReceiver = SomeReceiver()
274
secondRoute = self.router.bindRoute(secondReceiver)
277
self.router.ampBoxReceived(
278
{_ROUTE: firstRoute.localRouteName, 'foo': 'bar'})
279
self.router.ampBoxReceived(
280
{_ROUTE: secondRoute.localRouteName, 'baz': 'quux'})
282
self.assertEqual(firstReceiver.boxes, [{'foo': 'bar'}])
283
self.assertEqual(secondReceiver.boxes, [{'baz': 'quux'}])
286
def test_ampBoxReceivedDefaultRoute(self):
288
L{Router.ampBoxReceived} delivers boxes with no route to the default
291
sender = CollectingSender()
292
receiver = SomeReceiver()
294
router.startReceivingBoxes(sender)
295
router.bindRoute(receiver, None).start()
296
router.ampBoxReceived({'foo': 'bar'})
297
self.assertEqual(receiver.boxes, [{'foo': 'bar'}])
300
def test_stopReceivingBoxes(self):
302
L{Router.stopReceivingBoxes} calls the C{stop} method of each connected
305
sender = CollectingSender()
307
router.startReceivingBoxes(sender)
308
receiver = SomeReceiver()
309
router.bindRoute(receiver)
311
class DummyException(Exception):
314
self.assertFalse(receiver.stopped)
316
router.stopReceivingBoxes(Failure(DummyException()))
318
self.assertTrue(receiver.stopped)
319
receiver.reason.trap(DummyException)