1 # Copyright (c) Twisted Matrix Laboratories.
2 # See LICENSE for details.
5 Tests for error handling in PB.
9 from StringIO import StringIO
11 from twisted.trial import unittest
13 from twisted.spread import pb, flavors, jelly
14 from twisted.internet import reactor, defer
15 from twisted.python import log
20 class AsynchronousException(Exception):
22 Helper used to test remote methods which return Deferreds which fail with
23 exceptions which are not L{pb.Error} subclasses.
27 class SynchronousException(Exception):
29 Helper used to test remote methods which raise exceptions which are not
30 L{pb.Error} subclasses.
34 class AsynchronousError(pb.Error):
36 Helper used to test remote methods which return Deferreds which fail with
37 exceptions which are L{pb.Error} subclasses.
41 class SynchronousError(pb.Error):
43 Helper used to test remote methods which raise exceptions which are
44 L{pb.Error} subclasses.
48 #class JellyError(flavors.Jellyable, pb.Error): pass
49 class JellyError(flavors.Jellyable, pb.Error, pb.RemoteCopy):
53 class SecurityError(pb.Error, pb.RemoteCopy):
56 pb.setUnjellyableForClass(JellyError, JellyError)
57 pb.setUnjellyableForClass(SecurityError, SecurityError)
58 pb.globalSecurity.allowInstancesOf(SecurityError)
64 class SimpleRoot(pb.Root):
65 def remote_asynchronousException(self):
67 Fail asynchronously with a non-pb.Error exception.
69 return defer.fail(AsynchronousException("remote asynchronous exception"))
71 def remote_synchronousException(self):
73 Fail synchronously with a non-pb.Error exception.
75 raise SynchronousException("remote synchronous exception")
77 def remote_asynchronousError(self):
79 Fail asynchronously with a pb.Error exception.
81 return defer.fail(AsynchronousError("remote asynchronous error"))
83 def remote_synchronousError(self):
85 Fail synchronously with a pb.Error exception.
87 raise SynchronousError("remote synchronous error")
89 def remote_unknownError(self):
91 Fail with error that is not known to client.
93 class UnknownError(pb.Error):
95 raise UnknownError("I'm not known to client!")
97 def remote_jelly(self):
100 def remote_security(self):
103 def remote_deferredJelly(self):
105 d.addCallback(self.raiseJelly)
109 def remote_deferredSecurity(self):
111 d.addCallback(self.raiseSecurity)
115 def raiseJelly(self, results=None):
116 raise JellyError("I'm jellyable!")
118 def raiseSecurity(self, results=None):
119 raise SecurityError("I'm secure!")
123 class SaveProtocolServerFactory(pb.PBServerFactory):
125 A L{pb.PBServerFactory} that saves the latest connected client in
128 protocolInstance = None
130 def clientConnectionMade(self, protocol):
132 Keep track of the given protocol.
134 self.protocolInstance = protocol
138 class PBConnTestCase(unittest.TestCase):
145 def _setUpServer(self):
146 self.serverFactory = SaveProtocolServerFactory(SimpleRoot())
147 self.serverFactory.unsafeTracebacks = self.unsafeTracebacks
148 self.serverPort = reactor.listenTCP(0, self.serverFactory, interface="127.0.0.1")
150 def _setUpClient(self):
151 portNo = self.serverPort.getHost().port
152 self.clientFactory = pb.PBClientFactory()
153 self.clientConnector = reactor.connectTCP("127.0.0.1", portNo, self.clientFactory)
156 if self.serverFactory.protocolInstance is not None:
157 self.serverFactory.protocolInstance.transport.loseConnection()
158 return defer.gatherResults([
159 self._tearDownServer(),
160 self._tearDownClient()])
162 def _tearDownServer(self):
163 return defer.maybeDeferred(self.serverPort.stopListening)
165 def _tearDownClient(self):
166 self.clientConnector.disconnect()
167 return defer.succeed(None)
171 class PBFailureTest(PBConnTestCase):
172 compare = unittest.TestCase.assertEqual
175 def _exceptionTest(self, method, exceptionType, flush):
177 err.trap(exceptionType)
178 self.compare(err.traceback, "Traceback unavailable\n")
180 errs = self.flushLoggedErrors(exceptionType)
181 self.assertEqual(len(errs), 1)
182 return (err.type, err.value, err.traceback)
183 d = self.clientFactory.getRootObject()
184 def gotRootObject(root):
185 d = root.callRemote(method)
188 d.addCallback(gotRootObject)
192 def test_asynchronousException(self):
194 Test that a Deferred returned by a remote method which already has a
195 Failure correctly has that error passed back to the calling side.
197 return self._exceptionTest(
198 'asynchronousException', AsynchronousException, True)
201 def test_synchronousException(self):
203 Like L{test_asynchronousException}, but for a method which raises an
204 exception synchronously.
206 return self._exceptionTest(
207 'synchronousException', SynchronousException, True)
210 def test_asynchronousError(self):
212 Like L{test_asynchronousException}, but for a method which returns a
213 Deferred failing with an L{pb.Error} subclass.
215 return self._exceptionTest(
216 'asynchronousError', AsynchronousError, False)
219 def test_synchronousError(self):
221 Like L{test_asynchronousError}, but for a method which synchronously
222 raises a L{pb.Error} subclass.
224 return self._exceptionTest(
225 'synchronousError', SynchronousError, False)
228 def _success(self, result, expectedResult):
229 self.assertEqual(result, expectedResult)
233 def _addFailingCallbacks(self, remoteCall, expectedResult, eb):
234 remoteCall.addCallbacks(self._success, eb,
235 callbackArgs=(expectedResult,))
239 def _testImpl(self, method, expected, eb, exc=None):
241 Call the given remote method and attach the given errback to the
242 resulting Deferred. If C{exc} is not None, also assert that one
243 exception of that type was logged.
245 rootDeferred = self.clientFactory.getRootObject()
247 failureDeferred = self._addFailingCallbacks(obj.callRemote(method), expected, eb)
250 self.assertEqual(len(self.flushLoggedErrors(exc)), 1)
252 failureDeferred.addBoth(gotFailure)
253 return failureDeferred
254 rootDeferred.addCallback(gotRootObj)
258 def test_jellyFailure(self):
260 Test that an exception which is a subclass of L{pb.Error} has more
261 information passed across the network to the calling side.
263 def failureJelly(fail):
264 fail.trap(JellyError)
265 self.failIf(isinstance(fail.type, str))
266 self.failUnless(isinstance(fail.value, fail.type))
268 return self._testImpl('jelly', 43, failureJelly)
271 def test_deferredJellyFailure(self):
273 Test that a Deferred which fails with a L{pb.Error} is treated in
274 the same way as a synchronously raised L{pb.Error}.
276 def failureDeferredJelly(fail):
277 fail.trap(JellyError)
278 self.failIf(isinstance(fail.type, str))
279 self.failUnless(isinstance(fail.value, fail.type))
281 return self._testImpl('deferredJelly', 430, failureDeferredJelly)
284 def test_unjellyableFailure(self):
286 An non-jellyable L{pb.Error} subclass raised by a remote method is
287 turned into a Failure with a type set to the FQPN of the exception
290 def failureUnjellyable(fail):
292 fail.type, 'twisted.test.test_pbfailure.SynchronousError')
294 return self._testImpl('synchronousError', 431, failureUnjellyable)
297 def test_unknownFailure(self):
299 Test that an exception which is a subclass of L{pb.Error} but not
300 known on the client side has its type set properly.
302 def failureUnknown(fail):
304 fail.type, 'twisted.test.test_pbfailure.UnknownError')
306 return self._testImpl('unknownError', 4310, failureUnknown)
309 def test_securityFailure(self):
311 Test that even if an exception is not explicitly jellyable (by being
312 a L{pb.Jellyable} subclass), as long as it is an L{pb.Error}
313 subclass it receives the same special treatment.
315 def failureSecurity(fail):
316 fail.trap(SecurityError)
317 self.failIf(isinstance(fail.type, str))
318 self.failUnless(isinstance(fail.value, fail.type))
320 return self._testImpl('security', 4300, failureSecurity)
323 def test_deferredSecurity(self):
325 Test that a Deferred which fails with a L{pb.Error} which is not
326 also a L{pb.Jellyable} is treated in the same way as a synchronously
327 raised exception of the same type.
329 def failureDeferredSecurity(fail):
330 fail.trap(SecurityError)
331 self.failIf(isinstance(fail.type, str))
332 self.failUnless(isinstance(fail.value, fail.type))
334 return self._testImpl('deferredSecurity', 43000, failureDeferredSecurity)
337 def test_noSuchMethodFailure(self):
339 Test that attempting to call a method which is not defined correctly
340 results in an AttributeError on the calling side.
342 def failureNoSuch(fail):
343 fail.trap(pb.NoSuchMethod)
344 self.compare(fail.traceback, "Traceback unavailable\n")
346 return self._testImpl('nosuch', 42000, failureNoSuch, AttributeError)
349 def test_copiedFailureLogging(self):
351 Test that a copied failure received from a PB call can be logged
354 Note: this test needs some serious help: all it really tests is that
355 log.err(copiedFailure) doesn't raise an exception.
357 d = self.clientFactory.getRootObject()
359 def connected(rootObj):
360 return rootObj.callRemote('synchronousException')
361 d.addCallback(connected)
363 def exception(failure):
365 errs = self.flushLoggedErrors(SynchronousException)
366 self.assertEqual(len(errs), 2)
367 d.addErrback(exception)
372 def test_throwExceptionIntoGenerator(self):
374 L{pb.CopiedFailure.throwExceptionIntoGenerator} will throw a
375 L{RemoteError} into the given paused generator at the point where it
378 original = pb.CopyableFailure(AttributeError("foo"))
379 copy = jelly.unjelly(jelly.jelly(original, invoker=DummyInvoker()))
384 except pb.RemoteError, exc:
385 exception.append(exc)
387 self.fail("RemoteError not raised")
388 gen = generatorFunc()
390 self.assertRaises(StopIteration, copy.throwExceptionIntoGenerator, gen)
391 self.assertEqual(len(exception), 1)
393 self.assertEqual(exc.remoteType, "exceptions.AttributeError")
394 self.assertEqual(exc.args, ("foo",))
395 self.assertEqual(exc.remoteTraceback, 'Traceback unavailable\n')
397 if sys.version_info[:2] < (2, 5):
398 test_throwExceptionIntoGenerator.skip = (
399 "throwExceptionIntoGenerator is not supported in Python < 2.5")
403 class PBFailureTestUnsafe(PBFailureTest):
404 compare = unittest.TestCase.failIfEquals
409 class DummyInvoker(object):
411 A behaviorless object to be used as the invoker parameter to
414 serializingPerspective = None
418 class FailureJellyingTests(unittest.TestCase):
420 Tests for the interaction of jelly and failures.
422 def test_unjelliedFailureCheck(self):
424 An unjellied L{CopyableFailure} has a check method which behaves the
425 same way as the original L{CopyableFailure}'s check method.
427 original = pb.CopyableFailure(ZeroDivisionError())
428 self.assertIdentical(
429 original.check(ZeroDivisionError), ZeroDivisionError)
430 self.assertIdentical(original.check(ArithmeticError), ArithmeticError)
431 copied = jelly.unjelly(jelly.jelly(original, invoker=DummyInvoker()))
432 self.assertIdentical(
433 copied.check(ZeroDivisionError), ZeroDivisionError)
434 self.assertIdentical(copied.check(ArithmeticError), ArithmeticError)
437 def test_twiceUnjelliedFailureCheck(self):
439 The object which results from jellying a L{CopyableFailure}, unjellying
440 the result, creating a new L{CopyableFailure} from the result of that,
441 jellying it, and finally unjellying the result of that has a check
442 method which behaves the same way as the original L{CopyableFailure}'s
445 original = pb.CopyableFailure(ZeroDivisionError())
446 self.assertIdentical(
447 original.check(ZeroDivisionError), ZeroDivisionError)
448 self.assertIdentical(original.check(ArithmeticError), ArithmeticError)
449 copiedOnce = jelly.unjelly(
450 jelly.jelly(original, invoker=DummyInvoker()))
451 derivative = pb.CopyableFailure(copiedOnce)
452 copiedTwice = jelly.unjelly(
453 jelly.jelly(derivative, invoker=DummyInvoker()))
454 self.assertIdentical(
455 copiedTwice.check(ZeroDivisionError), ZeroDivisionError)
456 self.assertIdentical(
457 copiedTwice.check(ArithmeticError), ArithmeticError)
460 def test_printTracebackIncludesValue(self):
462 When L{CopiedFailure.printTraceback} is used to print a copied failure
463 which was unjellied from a L{CopyableFailure} with C{unsafeTracebacks}
464 set to C{False}, the string representation of the exception value is
465 included in the output.
467 original = pb.CopyableFailure(Exception("some reason"))
468 copied = jelly.unjelly(jelly.jelly(original, invoker=DummyInvoker()))
470 copied.printTraceback(output)
472 "Traceback from remote host -- Traceback unavailable\n"
473 "exceptions.Exception: some reason\n",