123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486 |
- # Copyright (c) Twisted Matrix Laboratories.
- # See LICENSE for details.
- """
- Tests for error handling in PB.
- """
- from twisted.internet import reactor, defer
- from twisted.python import log
- from twisted.python.compat import NativeStringIO
- from twisted.python.reflect import qual
- from twisted.spread import pb, flavors, jelly
- from twisted.trial import unittest
- # Test exceptions
- class AsynchronousException(Exception):
- """
- Helper used to test remote methods which return Deferreds which fail with
- exceptions which are not L{pb.Error} subclasses.
- """
- class SynchronousException(Exception):
- """
- Helper used to test remote methods which raise exceptions which are not
- L{pb.Error} subclasses.
- """
- class AsynchronousError(pb.Error):
- """
- Helper used to test remote methods which return Deferreds which fail with
- exceptions which are L{pb.Error} subclasses.
- """
- class SynchronousError(pb.Error):
- """
- Helper used to test remote methods which raise exceptions which are
- L{pb.Error} subclasses.
- """
- class JellyError(flavors.Jellyable, pb.Error, pb.RemoteCopy):
- pass
- class SecurityError(pb.Error, pb.RemoteCopy):
- pass
- pb.setUnjellyableForClass(JellyError, JellyError)
- pb.setUnjellyableForClass(SecurityError, SecurityError)
- pb.globalSecurity.allowInstancesOf(SecurityError)
- # Server-side
- class SimpleRoot(pb.Root):
- def remote_asynchronousException(self):
- """
- Fail asynchronously with a non-pb.Error exception.
- """
- return defer.fail(AsynchronousException("remote asynchronous exception"))
- def remote_synchronousException(self):
- """
- Fail synchronously with a non-pb.Error exception.
- """
- raise SynchronousException("remote synchronous exception")
- def remote_asynchronousError(self):
- """
- Fail asynchronously with a pb.Error exception.
- """
- return defer.fail(AsynchronousError("remote asynchronous error"))
- def remote_synchronousError(self):
- """
- Fail synchronously with a pb.Error exception.
- """
- raise SynchronousError("remote synchronous error")
- def remote_unknownError(self):
- """
- Fail with error that is not known to client.
- """
- class UnknownError(pb.Error):
- pass
- raise UnknownError("I'm not known to client!")
- def remote_jelly(self):
- self.raiseJelly()
- def remote_security(self):
- self.raiseSecurity()
- def remote_deferredJelly(self):
- d = defer.Deferred()
- d.addCallback(self.raiseJelly)
- d.callback(None)
- return d
- def remote_deferredSecurity(self):
- d = defer.Deferred()
- d.addCallback(self.raiseSecurity)
- d.callback(None)
- return d
- def raiseJelly(self, results=None):
- raise JellyError("I'm jellyable!")
- def raiseSecurity(self, results=None):
- raise SecurityError("I'm secure!")
- class SaveProtocolServerFactory(pb.PBServerFactory):
- """
- A L{pb.PBServerFactory} that saves the latest connected client in
- C{protocolInstance}.
- """
- protocolInstance = None
- def clientConnectionMade(self, protocol):
- """
- Keep track of the given protocol.
- """
- self.protocolInstance = protocol
- class PBConnTestCase(unittest.TestCase):
- unsafeTracebacks = 0
- def setUp(self):
- self._setUpServer()
- self._setUpClient()
- def _setUpServer(self):
- self.serverFactory = SaveProtocolServerFactory(SimpleRoot())
- self.serverFactory.unsafeTracebacks = self.unsafeTracebacks
- self.serverPort = reactor.listenTCP(0, self.serverFactory, interface="127.0.0.1")
- def _setUpClient(self):
- portNo = self.serverPort.getHost().port
- self.clientFactory = pb.PBClientFactory()
- self.clientConnector = reactor.connectTCP("127.0.0.1", portNo, self.clientFactory)
- def tearDown(self):
- if self.serverFactory.protocolInstance is not None:
- self.serverFactory.protocolInstance.transport.loseConnection()
- return defer.gatherResults([
- self._tearDownServer(),
- self._tearDownClient()])
- def _tearDownServer(self):
- return defer.maybeDeferred(self.serverPort.stopListening)
- def _tearDownClient(self):
- self.clientConnector.disconnect()
- return defer.succeed(None)
- class PBFailureTests(PBConnTestCase):
- compare = unittest.TestCase.assertEqual
- def _exceptionTest(self, method, exceptionType, flush):
- def eb(err):
- err.trap(exceptionType)
- self.compare(err.traceback, "Traceback unavailable\n")
- if flush:
- errs = self.flushLoggedErrors(exceptionType)
- self.assertEqual(len(errs), 1)
- return (err.type, err.value, err.traceback)
- d = self.clientFactory.getRootObject()
- def gotRootObject(root):
- d = root.callRemote(method)
- d.addErrback(eb)
- return d
- d.addCallback(gotRootObject)
- return d
- def test_asynchronousException(self):
- """
- Test that a Deferred returned by a remote method which already has a
- Failure correctly has that error passed back to the calling side.
- """
- return self._exceptionTest(
- 'asynchronousException', AsynchronousException, True)
- def test_synchronousException(self):
- """
- Like L{test_asynchronousException}, but for a method which raises an
- exception synchronously.
- """
- return self._exceptionTest(
- 'synchronousException', SynchronousException, True)
- def test_asynchronousError(self):
- """
- Like L{test_asynchronousException}, but for a method which returns a
- Deferred failing with an L{pb.Error} subclass.
- """
- return self._exceptionTest(
- 'asynchronousError', AsynchronousError, False)
- def test_synchronousError(self):
- """
- Like L{test_asynchronousError}, but for a method which synchronously
- raises a L{pb.Error} subclass.
- """
- return self._exceptionTest(
- 'synchronousError', SynchronousError, False)
- def _success(self, result, expectedResult):
- self.assertEqual(result, expectedResult)
- return result
- def _addFailingCallbacks(self, remoteCall, expectedResult, eb):
- remoteCall.addCallbacks(self._success, eb,
- callbackArgs=(expectedResult,))
- return remoteCall
- def _testImpl(self, method, expected, eb, exc=None):
- """
- Call the given remote method and attach the given errback to the
- resulting Deferred. If C{exc} is not None, also assert that one
- exception of that type was logged.
- """
- rootDeferred = self.clientFactory.getRootObject()
- def gotRootObj(obj):
- failureDeferred = self._addFailingCallbacks(obj.callRemote(method), expected, eb)
- if exc is not None:
- def gotFailure(err):
- self.assertEqual(len(self.flushLoggedErrors(exc)), 1)
- return err
- failureDeferred.addBoth(gotFailure)
- return failureDeferred
- rootDeferred.addCallback(gotRootObj)
- return rootDeferred
- def test_jellyFailure(self):
- """
- Test that an exception which is a subclass of L{pb.Error} has more
- information passed across the network to the calling side.
- """
- def failureJelly(fail):
- fail.trap(JellyError)
- self.assertNotIsInstance(fail.type, str)
- self.assertIsInstance(fail.value, fail.type)
- return 43
- return self._testImpl('jelly', 43, failureJelly)
- def test_deferredJellyFailure(self):
- """
- Test that a Deferred which fails with a L{pb.Error} is treated in
- the same way as a synchronously raised L{pb.Error}.
- """
- def failureDeferredJelly(fail):
- fail.trap(JellyError)
- self.assertNotIsInstance(fail.type, str)
- self.assertIsInstance(fail.value, fail.type)
- return 430
- return self._testImpl('deferredJelly', 430, failureDeferredJelly)
- def test_unjellyableFailure(self):
- """
- A non-jellyable L{pb.Error} subclass raised by a remote method is
- turned into a Failure with a type set to the FQPN of the exception
- type.
- """
- def failureUnjellyable(fail):
- self.assertEqual(
- fail.type,
- b'twisted.spread.test.test_pbfailure.SynchronousError')
- return 431
- return self._testImpl('synchronousError', 431, failureUnjellyable)
- def test_unknownFailure(self):
- """
- Test that an exception which is a subclass of L{pb.Error} but not
- known on the client side has its type set properly.
- """
- def failureUnknown(fail):
- self.assertEqual(
- fail.type, b'twisted.spread.test.test_pbfailure.UnknownError')
- return 4310
- return self._testImpl('unknownError', 4310, failureUnknown)
- def test_securityFailure(self):
- """
- Test that even if an exception is not explicitly jellyable (by being
- a L{pb.Jellyable} subclass), as long as it is an L{pb.Error}
- subclass it receives the same special treatment.
- """
- def failureSecurity(fail):
- fail.trap(SecurityError)
- self.assertNotIsInstance(fail.type, str)
- self.assertIsInstance(fail.value, fail.type)
- return 4300
- return self._testImpl('security', 4300, failureSecurity)
- def test_deferredSecurity(self):
- """
- Test that a Deferred which fails with a L{pb.Error} which is not
- also a L{pb.Jellyable} is treated in the same way as a synchronously
- raised exception of the same type.
- """
- def failureDeferredSecurity(fail):
- fail.trap(SecurityError)
- self.assertNotIsInstance(fail.type, str)
- self.assertIsInstance(fail.value, fail.type)
- return 43000
- return self._testImpl('deferredSecurity', 43000,
- failureDeferredSecurity)
- def test_noSuchMethodFailure(self):
- """
- Test that attempting to call a method which is not defined correctly
- results in an AttributeError on the calling side.
- """
- def failureNoSuch(fail):
- fail.trap(pb.NoSuchMethod)
- self.compare(fail.traceback, "Traceback unavailable\n")
- return 42000
- return self._testImpl('nosuch', 42000, failureNoSuch, AttributeError)
- def test_copiedFailureLogging(self):
- """
- Test that a copied failure received from a PB call can be logged
- locally.
- Note: this test needs some serious help: all it really tests is that
- log.err(copiedFailure) doesn't raise an exception.
- """
- d = self.clientFactory.getRootObject()
- def connected(rootObj):
- return rootObj.callRemote('synchronousException')
- d.addCallback(connected)
- def exception(failure):
- log.err(failure)
- errs = self.flushLoggedErrors(SynchronousException)
- self.assertEqual(len(errs), 2)
- d.addErrback(exception)
- return d
- def test_throwExceptionIntoGenerator(self):
- """
- L{pb.CopiedFailure.throwExceptionIntoGenerator} will throw a
- L{RemoteError} into the given paused generator at the point where it
- last yielded.
- """
- original = pb.CopyableFailure(AttributeError("foo"))
- copy = jelly.unjelly(jelly.jelly(original, invoker=DummyInvoker()))
- exception = []
- def generatorFunc():
- try:
- yield None
- except pb.RemoteError as exc:
- exception.append(exc)
- else:
- self.fail("RemoteError not raised")
- gen = generatorFunc()
- gen.send(None)
- self.assertRaises(StopIteration, copy.throwExceptionIntoGenerator, gen)
- self.assertEqual(len(exception), 1)
- exc = exception[0]
- self.assertEqual(exc.remoteType, qual(AttributeError).encode("ascii"))
- self.assertEqual(exc.args, ("foo",))
- self.assertEqual(exc.remoteTraceback, 'Traceback unavailable\n')
- class PBFailureUnsafeTests(PBFailureTests):
- compare = unittest.TestCase.failIfEquals
- unsafeTracebacks = 1
- class DummyInvoker(object):
- """
- A behaviorless object to be used as the invoker parameter to
- L{jelly.jelly}.
- """
- serializingPerspective = None
- class FailureJellyingTests(unittest.TestCase):
- """
- Tests for the interaction of jelly and failures.
- """
- def test_unjelliedFailureCheck(self):
- """
- An unjellied L{CopyableFailure} has a check method which behaves the
- same way as the original L{CopyableFailure}'s check method.
- """
- original = pb.CopyableFailure(ZeroDivisionError())
- self.assertIs(
- original.check(ZeroDivisionError), ZeroDivisionError)
- self.assertIs(original.check(ArithmeticError), ArithmeticError)
- copied = jelly.unjelly(jelly.jelly(original, invoker=DummyInvoker()))
- self.assertIs(
- copied.check(ZeroDivisionError), ZeroDivisionError)
- self.assertIs(copied.check(ArithmeticError), ArithmeticError)
- def test_twiceUnjelliedFailureCheck(self):
- """
- The object which results from jellying a L{CopyableFailure}, unjellying
- the result, creating a new L{CopyableFailure} from the result of that,
- jellying it, and finally unjellying the result of that has a check
- method which behaves the same way as the original L{CopyableFailure}'s
- check method.
- """
- original = pb.CopyableFailure(ZeroDivisionError())
- self.assertIs(
- original.check(ZeroDivisionError), ZeroDivisionError)
- self.assertIs(original.check(ArithmeticError), ArithmeticError)
- copiedOnce = jelly.unjelly(
- jelly.jelly(original, invoker=DummyInvoker()))
- derivative = pb.CopyableFailure(copiedOnce)
- copiedTwice = jelly.unjelly(
- jelly.jelly(derivative, invoker=DummyInvoker()))
- self.assertIs(
- copiedTwice.check(ZeroDivisionError), ZeroDivisionError)
- self.assertIs(
- copiedTwice.check(ArithmeticError), ArithmeticError)
- def test_printTracebackIncludesValue(self):
- """
- When L{CopiedFailure.printTraceback} is used to print a copied failure
- which was unjellied from a L{CopyableFailure} with C{unsafeTracebacks}
- set to C{False}, the string representation of the exception value is
- included in the output.
- """
- original = pb.CopyableFailure(Exception("some reason"))
- copied = jelly.unjelly(jelly.jelly(original, invoker=DummyInvoker()))
- output = NativeStringIO()
- copied.printTraceback(output)
- exception = qual(Exception)
- expectedOutput = ("Traceback from remote host -- "
- "{}: some reason\n".format(exception))
- self.assertEqual(expectedOutput, output.getvalue())
|