1
# Copyright 2005 Divmod, Inc. See LICENSE file for details
4
from epsilon import juice
5
from epsilon.test import iosim
6
from twisted.trial import unittest
7
from twisted.internet import protocol, defer
9
class TestProto(protocol.Protocol):
10
def __init__(self, onConnLost, dataToSend):
11
self.onConnLost = onConnLost
12
self.dataToSend = dataToSend
14
def connectionMade(self):
16
self.transport.write(self.dataToSend)
18
def dataReceived(self, bytes):
19
self.data.append(bytes)
20
self.transport.loseConnection()
22
def connectionLost(self, reason):
23
self.onConnLost.callback(self.data)
25
class SimpleSymmetricProtocol(juice.Juice):
27
def sendHello(self, text):
28
return self.sendCommand("hello",
31
def sendGoodbye(self):
32
return self.sendCommand("goodbye")
34
def juice_HELLO(self, box):
35
return juice.Box(hello=box['hello'])
37
def juice_GOODBYE(self, box):
38
return juice.QuitBox(goodbye='world')
40
class UnfriendlyGreeting(Exception):
41
"""Greeting was insufficiently kind.
44
class UnknownProtocol(Exception):
45
"""Asked to switch to the wrong protocol.
48
class Hello(juice.Command):
50
arguments = [('hello', juice.String())]
51
response = [('hello', juice.String())]
53
errors = {UnfriendlyGreeting: 'UNFRIENDLY'}
55
class Goodbye(juice.Command):
56
commandName = 'goodbye'
57
responseType = juice.QuitBox
59
class GetList(juice.Command):
60
commandName = 'getlist'
61
arguments = [('length', juice.Integer())]
62
response = [('body', juice.JuiceList([('x', juice.Integer())]))]
64
class TestSwitchProto(juice.ProtocolSwitchCommand):
65
commandName = 'Switch-Proto'
68
('name', juice.String()),
70
errors = {UnknownProtocol: 'UNKNOWN'}
72
class SingleUseFactory(protocol.ClientFactory):
73
def __init__(self, proto):
76
def buildProtocol(self, addr):
77
p, self.proto = self.proto, None
80
class SimpleSymmetricCommandProtocol(juice.Juice):
82
def __init__(self, issueGreeting, onConnLost=None):
83
juice.Juice.__init__(self, issueGreeting)
84
self.onConnLost = onConnLost
86
def sendHello(self, text):
87
return Hello(hello=text).do(self)
88
def sendGoodbye(self):
89
return Goodbye().do(self)
90
def command_HELLO(self, hello):
91
if hello.startswith('fuck'):
92
raise UnfriendlyGreeting("Don't be a dick.")
93
return dict(hello=hello)
94
def command_GETLIST(self, length):
95
return {'body': [dict(x=1)] * length}
96
def command_GOODBYE(self):
97
return dict(goodbye='world')
98
command_HELLO.command = Hello
99
command_GOODBYE.command = Goodbye
100
command_GETLIST.command = GetList
102
def switchToTestProtocol(self):
103
p = TestProto(self.onConnLost, SWITCH_CLIENT_DATA)
104
return TestSwitchProto(SingleUseFactory(p), name='test-proto').do(self).addCallback(lambda ign: p)
106
def command_SWITCH_PROTO(self, name):
107
if name == 'test-proto':
108
return TestProto(self.onConnLost, SWITCH_SERVER_DATA)
109
raise UnknownProtocol(name)
111
command_SWITCH_PROTO.command = TestSwitchProto
113
class DeferredSymmetricCommandProtocol(SimpleSymmetricCommandProtocol):
114
def command_SWITCH_PROTO(self, name):
115
if name == 'test-proto':
116
self.maybeLaterProto = TestProto(self.onConnLost, SWITCH_SERVER_DATA)
117
self.maybeLater = defer.Deferred()
118
return self.maybeLater
119
raise UnknownProtocol(name)
121
command_SWITCH_PROTO.command = TestSwitchProto
124
class SSPF: protocol = SimpleSymmetricProtocol
125
class SSSF(SSPF, protocol.ServerFactory): pass
126
class SSCF(SSPF, protocol.ClientFactory): pass
128
def connectedServerAndClient(ServerClass=lambda: SimpleSymmetricProtocol(True),
129
ClientClass=lambda: SimpleSymmetricProtocol(False),
131
"""Returns a 3-tuple: (client, server, pump)
133
return iosim.connectedServerAndClient(
134
ServerClass, ClientClass,
137
class TotallyDumbProtocol(protocol.Protocol):
139
def dataReceived(self, data):
142
class LiteralJuice(juice.Juice):
143
def __init__(self, issueGreeting):
144
juice.Juice.__init__(self, issueGreeting)
147
def juiceBoxReceived(self, box):
148
self.boxes.append(box)
151
class LiteralParsingTest(unittest.TestCase):
152
def testBasicRequestResponse(self):
153
c, s, p = connectedServerAndClient(ClientClass=TotallyDumbProtocol)
155
ASKTOK = 'hand-crafted-ask'
156
c.transport.write(("""-Command: HeLlO
159
World: this header is ignored
161
""" % (ASKTOK, HELLO,)).replace('\n','\r\n'))
163
asserts = {'hello': HELLO,
165
hdrs = [j.split(': ') for j in c.buf.split('\r\n')[:-2]]
166
self.assertEquals(len(asserts), len(hdrs))
169
self.assertEquals(v, asserts[k.lower()])
171
def testParsingRoundTrip(self):
172
c, s, p = connectedServerAndClient(ClientClass=lambda: LiteralJuice(False),
173
ServerClass=lambda: LiteralJuice(True))
175
SIMPLE = ('simple', 'test')
177
CR = ('crtest', 'test\r')
178
LF = ('lftest', 'hello\n')
179
NEWLINE = ('newline', 'test\r\none\r\ntwo')
180
NEWLINE2 = ('newline2', 'test\r\none\r\n two')
181
BLANKLINE = ('newline3', 'test\r\n\r\nblank\r\n\r\nline')
182
BODYTEST = (juice.BODY, 'blah\r\n\r\ntesttest')
189
[SIMPLE, CE, CR, LF],
191
[SIMPLE, NEWLINE, CE, NEWLINE2],
192
[BODYTEST, SIMPLE, NEWLINE]
195
for test in testData:
197
jb.update(dict(test))
200
self.assertEquals(s.boxes[-1], jb)
202
SWITCH_CLIENT_DATA = 'Success!'
203
SWITCH_SERVER_DATA = 'No, really. Success.'
205
class AppLevelTest(unittest.TestCase):
206
def testHelloWorld(self):
207
c, s, p = connectedServerAndClient()
210
c.sendHello(HELLO).addCallback(L.append)
212
self.assertEquals(L[0]['hello'], HELLO)
214
def testHelloWorldCommand(self):
215
c, s, p = connectedServerAndClient(
216
ServerClass=lambda: SimpleSymmetricCommandProtocol(True),
217
ClientClass=lambda: SimpleSymmetricCommandProtocol(False))
220
c.sendHello(HELLO).addCallback(L.append)
222
self.assertEquals(L[0]['hello'], HELLO)
224
def testHelloErrorHandling(self):
226
c, s, p = connectedServerAndClient(ServerClass=lambda: SimpleSymmetricCommandProtocol(True),
227
ClientClass=lambda: SimpleSymmetricCommandProtocol(False))
229
c.sendHello(HELLO).addErrback(L.append)
231
L[0].trap(UnfriendlyGreeting)
232
self.assertEquals(str(L[0].value), "Don't be a dick.")
234
def testJuiceListCommand(self):
235
c, s, p = connectedServerAndClient(ServerClass=lambda: SimpleSymmetricCommandProtocol(True),
236
ClientClass=lambda: SimpleSymmetricCommandProtocol(False))
238
GetList(length=10).do(c).addCallback(L.append)
240
values = L.pop().get('body')
241
self.assertEquals(values, [{'x': 1}] * 10)
243
def testFailEarlyOnArgSending(self):
244
okayCommand = Hello(Hello="What?")
245
self.assertRaises(RuntimeError, Hello)
247
def testSupportsVersion1(self):
248
c, s, p = connectedServerAndClient(ServerClass=lambda: juice.Juice(True),
249
ClientClass=lambda: juice.Juice(False))
250
negotiatedVersion = []
251
s.renegotiateVersion(1).addCallback(negotiatedVersion.append)
253
self.assertEquals(negotiatedVersion[0], 1)
254
self.assertEquals(c.protocolVersion, 1)
255
self.assertEquals(s.protocolVersion, 1)
257
def testProtocolSwitch(self, switcher=SimpleSymmetricCommandProtocol):
258
self.testSucceeded = False
260
serverDeferred = defer.Deferred()
261
serverProto = switcher(True, serverDeferred)
262
clientDeferred = defer.Deferred()
263
clientProto = switcher(False, clientDeferred)
264
c, s, p = connectedServerAndClient(ServerClass=lambda: serverProto,
265
ClientClass=lambda: clientProto)
267
switchDeferred = c.switchToTestProtocol()
269
def cbConnsLost(((serverSuccess, serverData), (clientSuccess, clientData))):
270
self.failUnless(serverSuccess)
271
self.failUnless(clientSuccess)
272
self.assertEquals(''.join(serverData), SWITCH_CLIENT_DATA)
273
self.assertEquals(''.join(clientData), SWITCH_SERVER_DATA)
274
self.testSucceeded = True
277
return defer.DeferredList([serverDeferred, clientDeferred]).addCallback(cbConnsLost)
279
switchDeferred.addCallback(cbSwitch)
281
if serverProto.maybeLater is not None:
282
serverProto.maybeLater.callback(serverProto.maybeLaterProto)
284
self.failUnless(self.testSucceeded)
286
def testProtocolSwitchDeferred(self):
287
return self.testProtocolSwitch(switcher=DeferredSymmetricCommandProtocol)