1
1
# Copyright (c) 2005 Divmod, Inc.
2
# Copyright (c) 2007-2008 Twisted Matrix Laboratories.
2
# Copyright (c) 2007-2009 Twisted Matrix Laboratories.
3
3
# See LICENSE for details.
6
6
Tests for L{twisted.protocols.amp}.
9
from zope.interface.verify import verifyObject
11
from twisted.python.util import setIDFunction
9
12
from twisted.python import filepath
10
13
from twisted.python.failure import Failure
11
14
from twisted.protocols import amp
14
17
from twisted.test import iosim
15
18
from twisted.test.proto_helpers import StringTransport
21
from twisted.internet import ssl
24
if ssl and not ssl.supported:
28
skipSSL = "SSL not available"
18
33
class TestProto(protocol.Protocol):
35
A trivial protocol for use in testing where a L{Protocol} is expected.
37
@ivar instanceId: the id of this instance
38
@ivar onConnLost: deferred that will fired when the connection is lost
39
@ivar dataToSend: data to send on the protocol
19
44
def __init__(self, onConnLost, dataToSend):
20
45
self.onConnLost = onConnLost
21
46
self.dataToSend = dataToSend
47
self.instanceId = TestProto.instanceCount
48
TestProto.instanceCount = TestProto.instanceCount + 1
23
50
def connectionMade(self):
32
59
self.onConnLost.callback(self.data)
64
Custom repr for testing to avoid coupling amp tests with repr from
67
Returns a string which contains a unique identifier that can be looked
68
up using the instanceId property::
72
return "<TestProto #%d>" % (self.instanceId,)
36
76
class SimpleSymmetricProtocol(amp.AMP):
146
186
arguments = [('length', amp.Integer())]
147
187
response = [('body', amp.AmpList([('x', amp.Integer())]))]
189
class DontRejectMe(amp.Command):
190
commandName = 'dontrejectme'
192
('magicWord', amp.Unicode()),
193
('list', amp.AmpList([('name', amp.Unicode())], optional=True)),
195
response = [('response', amp.Unicode())]
149
197
class SecuredPing(amp.Command):
150
198
# XXX TODO: actually make this refuse to send over an insecure connection
151
199
response = [('pinged', amp.Boolean())]
227
275
return {'body': [dict(x=1)] * length}
228
276
GetList.responder(cmdGetlist)
278
def okiwont(self, magicWord, list):
279
return dict(response=u'%s accepted' % (list[0]['name']))
280
DontRejectMe.responder(okiwont)
230
282
def waitforit(self):
231
283
self.waiting = defer.Deferred()
232
284
return self.waiting
654
706
SWITCH_SERVER_DATA = 'No, really. Success.'
657
from twisted.test.proto_helpers import StringTransport
660
709
class BinaryProtocolTests(unittest.TestCase):
662
711
Tests for L{amp.BinaryBoxProtocol}.
1025
1074
self.assertEquals(L[0]['Print'], HELLO_UNICODE)
1077
def test_callRemoteStringRequiresAnswerFalse(self):
1079
L{BoxDispatcher.callRemoteString} returns C{None} if C{requiresAnswer}
1082
c, s, p = connectedServerAndClient()
1083
ret = c.callRemoteString("WTF", requiresAnswer=False)
1084
self.assertIdentical(ret, None)
1028
1087
def test_unknownCommandLow(self):
1030
1089
Verify that unknown commands using low-level APIs will be rejected with an
1109
1167
Verify that the various Box objects repr properly, for debugging.
1111
self.assertEquals(type(repr(amp._TLSBox())), str)
1112
1169
self.assertEquals(type(repr(amp._SwitchBox('a'))), str)
1113
1170
self.assertEquals(type(repr(amp.QuitBox())), str)
1114
1171
self.assertEquals(type(repr(amp.AmpBox())), str)
1115
1172
self.failUnless("AmpBox" in repr(amp.AmpBox()))
1175
def test_innerProtocolInRepr(self):
1177
Verify that L{AMP} objects output their innerProtocol when set.
1179
otherProto = TestProto(None, "outgoing data")
1181
a.innerProtocol = otherProto
1183
return {a: 0x1234}.get(obj, id(obj))
1184
self.addCleanup(setIDFunction, setIDFunction(fakeID))
1187
repr(a), "<AMP inner <TestProto #%d> at 0x1234>" % (
1188
otherProto.instanceId,))
1191
def test_innerProtocolNotInRepr(self):
1193
Verify that L{AMP} objects do not output 'inner' when no innerProtocol
1198
return {a: 0x4321}.get(obj, id(obj))
1199
self.addCleanup(setIDFunction, setIDFunction(fakeID))
1200
self.assertEquals(repr(a), "<AMP at 0x4321>")
1203
def test_simpleSSLRepr(self):
1205
L{amp._TLSBox.__repr__} returns a string.
1207
self.assertEquals(type(repr(amp._TLSBox())), str)
1209
test_simpleSSLRepr.skip = skipSSL
1117
1212
def test_keyTooLong(self):
1119
1214
Verify that a key that is too long will immediately raise a synchronous
1146
1241
self.failIf(tl.isKey)
1147
1242
self.failUnless(tl.isLocal)
1148
self.failUnlessIdentical(tl.keyName, 'hello')
1243
self.assertEquals(tl.keyName, 'hello')
1149
1244
self.failUnlessIdentical(tl.value, x)
1150
1245
self.failUnless(str(len(x)) in repr(tl))
1151
1246
self.failUnless("value" in repr(tl))
1332
1427
self.assertEquals(values, [{'x': 1}] * 10)
1430
def test_optionalAmpListOmitted(self):
1432
Test that sending a command with an omitted AmpList argument that is
1433
designated as optional does not raise an InvalidSignature error.
1435
dontRejectMeCommand = DontRejectMe(magicWord=u'please')
1438
def test_optionalAmpListPresent(self):
1440
Sanity check that optional AmpList arguments are processed normally.
1442
dontRejectMeCommand = DontRejectMe(magicWord=u'please',
1443
list=[{'name': 'foo'}])
1444
c, s, p = connectedServerAndClient(
1445
ServerClass=SimpleSymmetricCommandProtocol,
1446
ClientClass=SimpleSymmetricCommandProtocol)
1448
c.callRemote(DontRejectMe, magicWord=u'please',
1449
list=[{'name': 'foo'}]).addCallback(L.append)
1451
response = L.pop().get('response')
1452
self.assertEquals(response, 'foo accepted')
1335
1455
def test_failEarlyOnArgSending(self):
1337
1457
Verify that if we pass an invalid argument list (omitting an argument), an
1705
1825
self.assertFailure(d, error.PeerVerifyError)
1831
class TLSNotAvailableTest(unittest.TestCase):
1833
Tests what happened when ssl is not available in current installation.
1851
def test_callRemoteError(self):
1853
Check that callRemote raises an exception when called with a
1856
cli, svr, p = connectedServerAndClient(
1857
ServerClass=SecurableProto,
1858
ClientClass=SecurableProto)
1861
svr.certFactory = lambda : okc
1863
return self.assertFailure(cli.callRemote(
1864
amp.StartTLS, tls_localCertificate=okc,
1865
tls_verifyAuthorities=[PretendRemoteCertificateAuthority()]),
1869
def test_messageReceivedError(self):
1871
When a client with SSL enabled talks to a server without SSL, it
1872
should return a meaningful error.
1874
svr = SecurableProto()
1876
svr.certFactory = lambda : okc
1878
box['_command'] = 'StartTLS'
1881
svr.sendBox = boxes.append
1882
svr.makeConnection(StringTransport())
1883
svr.ampBoxReceived(box)
1884
self.assertEquals(boxes,
1885
[{'_error_code': 'TLS_ERROR',
1887
'_error_description': 'TLS not available'}])
1709
1891
class InheritedError(Exception):
1908
2090
cert = key.newCertificate(sscrd)
1911
tempcert = tempSelfSigned()
2094
tempcert = tempSelfSigned()
1914
2097
class LiveFireTLSTestCase(LiveFireBase, unittest.TestCase):
1974
2160
return self.cli.callRemote(SecuredPing)
1975
2161
return self.cli.callRemote(amp.StartTLS).addCallback(secured)
1978
2167
class WithServerTLSVerification(LiveFireBase, unittest.TestCase):
1979
2168
clientProto = SimpleSymmetricCommandProtocol
2115
2306
class CommandTestCase(unittest.TestCase):
2117
Tests for L{amp.Command}.
2308
Tests for L{amp.Argument} and L{amp.Command}.
2310
def test_argumentInterface(self):
2312
L{Argument} instances provide L{amp.IArgumentType}.
2314
self.assertTrue(verifyObject(amp.IArgumentType, amp.Argument()))
2120
2317
def test_parseResponse(self):
2222
2419
response.addCallback(gotResponse)
2223
2420
return response
2423
def test_extraArgumentsDisallowed(self):
2425
L{Command.makeArguments} raises L{amp.InvalidSignature} if the objects
2426
dictionary passed to it includes a key which does not correspond to the
2427
Python identifier for a defined argument.
2430
amp.InvalidSignature,
2431
Hello.makeArguments,
2432
dict(hello="hello", bogusArgument=object()), None)
2435
def test_wireSpellingDisallowed(self):
2437
If a command argument conflicts with a Python keyword, the
2438
untransformed argument name is not allowed as a key in the dictionary
2439
passed to L{Command.makeArguments}. If it is supplied,
2440
L{amp.InvalidSignature} is raised.
2442
This may be a pointless implementation restriction which may be lifted.
2443
The current behavior is tested to verify that such arguments are not
2444
silently dropped on the floor (the previous behavior).
2447
amp.InvalidSignature,
2448
Hello.makeArguments,
2449
dict(hello="required", **{"print": "print value"}),
2225
2454
if not interfaces.IReactorSSL.providedBy(reactor):
2226
2455
skipMsg = 'This test case requires SSL support in the reactor'
2227
2456
TLSTest.skip = skipMsg