| OLD | NEW |
| (Empty) |
| 1 # Copyright (c) 2001-2008 Twisted Matrix Laboratories. | |
| 2 # See LICENSE for details. | |
| 3 | |
| 4 """ | |
| 5 Tests for error handling in PB. | |
| 6 """ | |
| 7 | |
| 8 from twisted.trial import unittest | |
| 9 | |
| 10 from twisted.spread import pb, flavors, jelly | |
| 11 from twisted.internet import reactor, defer | |
| 12 from twisted.python import log | |
| 13 | |
| 14 ## | |
| 15 # test exceptions | |
| 16 ## | |
| 17 class AsynchronousException(Exception): | |
| 18 """ | |
| 19 Helper used to test remote methods which return Deferreds which fail with | |
| 20 exceptions which are not L{pb.Error} subclasses. | |
| 21 """ | |
| 22 | |
| 23 | |
| 24 class SynchronousException(Exception): | |
| 25 """ | |
| 26 Helper used to test remote methods which raise exceptions which are not | |
| 27 L{pb.Error} subclasses. | |
| 28 """ | |
| 29 | |
| 30 | |
| 31 class AsynchronousError(pb.Error): | |
| 32 """ | |
| 33 Helper used to test remote methods which return Deferreds which fail with | |
| 34 exceptions which are L{pb.Error} subclasses. | |
| 35 """ | |
| 36 | |
| 37 | |
| 38 class SynchronousError(pb.Error): | |
| 39 """ | |
| 40 Helper used to test remote methods which raise exceptions which are | |
| 41 L{pb.Error} subclasses. | |
| 42 """ | |
| 43 | |
| 44 | |
| 45 #class JellyError(flavors.Jellyable, pb.Error): pass | |
| 46 class JellyError(flavors.Jellyable, pb.Error, pb.RemoteCopy): | |
| 47 pass | |
| 48 | |
| 49 | |
| 50 class SecurityError(pb.Error, pb.RemoteCopy): | |
| 51 pass | |
| 52 | |
| 53 pb.setUnjellyableForClass(JellyError, JellyError) | |
| 54 pb.setUnjellyableForClass(SecurityError, SecurityError) | |
| 55 pb.globalSecurity.allowInstancesOf(SecurityError) | |
| 56 | |
| 57 | |
| 58 #### | |
| 59 # server-side | |
| 60 #### | |
| 61 class SimpleRoot(pb.Root): | |
| 62 def remote_asynchronousException(self): | |
| 63 """ | |
| 64 Fail asynchronously with a non-pb.Error exception. | |
| 65 """ | |
| 66 return defer.fail(AsynchronousException("remote asynchronous exception")
) | |
| 67 | |
| 68 def remote_synchronousException(self): | |
| 69 """ | |
| 70 Fail synchronously with a non-pb.Error exception. | |
| 71 """ | |
| 72 raise SynchronousException("remote synchronous exception") | |
| 73 | |
| 74 def remote_asynchronousError(self): | |
| 75 """ | |
| 76 Fail asynchronously with a pb.Error exception. | |
| 77 """ | |
| 78 return defer.fail(AsynchronousError("remote asynchronous error")) | |
| 79 | |
| 80 def remote_synchronousError(self): | |
| 81 """ | |
| 82 Fail synchronously with a pb.Error exception. | |
| 83 """ | |
| 84 raise SynchronousError("remote synchronous error") | |
| 85 | |
| 86 def remote_unknownError(self): | |
| 87 """ | |
| 88 Fail with error that is not known to client. | |
| 89 """ | |
| 90 class UnknownError(pb.Error): | |
| 91 pass | |
| 92 raise UnknownError("I'm not known to client!") | |
| 93 | |
| 94 def remote_jelly(self): | |
| 95 self.raiseJelly() | |
| 96 | |
| 97 def remote_security(self): | |
| 98 self.raiseSecurity() | |
| 99 | |
| 100 def remote_deferredJelly(self): | |
| 101 d = defer.Deferred() | |
| 102 d.addCallback(self.raiseJelly) | |
| 103 d.callback(None) | |
| 104 return d | |
| 105 | |
| 106 def remote_deferredSecurity(self): | |
| 107 d = defer.Deferred() | |
| 108 d.addCallback(self.raiseSecurity) | |
| 109 d.callback(None) | |
| 110 return d | |
| 111 | |
| 112 def raiseJelly(self, results=None): | |
| 113 raise JellyError("I'm jellyable!") | |
| 114 | |
| 115 def raiseSecurity(self, results=None): | |
| 116 raise SecurityError("I'm secure!") | |
| 117 | |
| 118 | |
| 119 | |
| 120 class PBConnTestCase(unittest.TestCase): | |
| 121 unsafeTracebacks = 0 | |
| 122 | |
| 123 def setUp(self): | |
| 124 self._setUpServer() | |
| 125 self._setUpClient() | |
| 126 | |
| 127 def _setUpServer(self): | |
| 128 self.serverFactory = pb.PBServerFactory(SimpleRoot()) | |
| 129 self.serverFactory.unsafeTracebacks = self.unsafeTracebacks | |
| 130 self.serverPort = reactor.listenTCP(0, self.serverFactory, interface="12
7.0.0.1") | |
| 131 | |
| 132 def _setUpClient(self): | |
| 133 portNo = self.serverPort.getHost().port | |
| 134 self.clientFactory = pb.PBClientFactory() | |
| 135 self.clientConnector = reactor.connectTCP("127.0.0.1", portNo, self.clie
ntFactory) | |
| 136 | |
| 137 def tearDown(self): | |
| 138 return defer.gatherResults([ | |
| 139 self._tearDownServer(), | |
| 140 self._tearDownClient()]) | |
| 141 | |
| 142 def _tearDownServer(self): | |
| 143 return defer.maybeDeferred(self.serverPort.stopListening) | |
| 144 | |
| 145 def _tearDownClient(self): | |
| 146 self.clientConnector.disconnect() | |
| 147 return defer.succeed(None) | |
| 148 | |
| 149 | |
| 150 | |
| 151 class PBFailureTest(PBConnTestCase): | |
| 152 compare = unittest.TestCase.assertEquals | |
| 153 | |
| 154 | |
| 155 def _exceptionTest(self, method, exceptionType, flush): | |
| 156 def eb(err): | |
| 157 err.trap(exceptionType) | |
| 158 self.compare(err.traceback, "Traceback unavailable\n") | |
| 159 if flush: | |
| 160 errs = self.flushLoggedErrors(exceptionType) | |
| 161 self.assertEqual(len(errs), 1) | |
| 162 return (err.type, err.value, err.traceback) | |
| 163 d = self.clientFactory.getRootObject() | |
| 164 def gotRootObject(root): | |
| 165 d = root.callRemote(method) | |
| 166 d.addErrback(eb) | |
| 167 return d | |
| 168 d.addCallback(gotRootObject) | |
| 169 return d | |
| 170 | |
| 171 | |
| 172 def test_asynchronousException(self): | |
| 173 """ | |
| 174 Test that a Deferred returned by a remote method which already has a | |
| 175 Failure correctly has that error passed back to the calling side. | |
| 176 """ | |
| 177 return self._exceptionTest( | |
| 178 'asynchronousException', AsynchronousException, True) | |
| 179 | |
| 180 | |
| 181 def test_synchronousException(self): | |
| 182 """ | |
| 183 Like L{test_asynchronousException}, but for a method which raises an | |
| 184 exception synchronously. | |
| 185 """ | |
| 186 return self._exceptionTest( | |
| 187 'synchronousException', SynchronousException, True) | |
| 188 | |
| 189 | |
| 190 def test_asynchronousError(self): | |
| 191 """ | |
| 192 Like L{test_asynchronousException}, but for a method which returns a | |
| 193 Deferred failing with an L{pb.Error} subclass. | |
| 194 """ | |
| 195 return self._exceptionTest( | |
| 196 'asynchronousError', AsynchronousError, False) | |
| 197 | |
| 198 | |
| 199 def test_synchronousError(self): | |
| 200 """ | |
| 201 Like L{test_asynchronousError}, but for a method which synchronously | |
| 202 raises a L{pb.Error} subclass. | |
| 203 """ | |
| 204 return self._exceptionTest( | |
| 205 'synchronousError', SynchronousError, False) | |
| 206 | |
| 207 | |
| 208 def _success(self, result, expectedResult): | |
| 209 self.assertEquals(result, expectedResult) | |
| 210 return result | |
| 211 | |
| 212 | |
| 213 def _addFailingCallbacks(self, remoteCall, expectedResult, eb): | |
| 214 remoteCall.addCallbacks(self._success, eb, | |
| 215 callbackArgs=(expectedResult,)) | |
| 216 return remoteCall | |
| 217 | |
| 218 | |
| 219 def _testImpl(self, method, expected, eb, exc=None): | |
| 220 """ | |
| 221 Call the given remote method and attach the given errback to the | |
| 222 resulting Deferred. If C{exc} is not None, also assert that one | |
| 223 exception of that type was logged. | |
| 224 """ | |
| 225 rootDeferred = self.clientFactory.getRootObject() | |
| 226 def gotRootObj(obj): | |
| 227 failureDeferred = self._addFailingCallbacks(obj.callRemote(method),
expected, eb) | |
| 228 if exc is not None: | |
| 229 def gotFailure(err): | |
| 230 self.assertEquals(len(self.flushLoggedErrors(exc)), 1) | |
| 231 return err | |
| 232 failureDeferred.addBoth(gotFailure) | |
| 233 return failureDeferred | |
| 234 rootDeferred.addCallback(gotRootObj) | |
| 235 return rootDeferred | |
| 236 | |
| 237 | |
| 238 def test_jellyFailure(self): | |
| 239 """ | |
| 240 Test that an exception which is a subclass of L{pb.Error} has more | |
| 241 information passed across the network to the calling side. | |
| 242 """ | |
| 243 def failureJelly(fail): | |
| 244 fail.trap(JellyError) | |
| 245 self.failIf(isinstance(fail.type, str)) | |
| 246 self.failUnless(isinstance(fail.value, fail.type)) | |
| 247 return 43 | |
| 248 return self._testImpl('jelly', 43, failureJelly) | |
| 249 | |
| 250 | |
| 251 def test_deferredJellyFailure(self): | |
| 252 """ | |
| 253 Test that a Deferred which fails with a L{pb.Error} is treated in | |
| 254 the same way as a synchronously raised L{pb.Error}. | |
| 255 """ | |
| 256 def failureDeferredJelly(fail): | |
| 257 fail.trap(JellyError) | |
| 258 self.failIf(isinstance(fail.type, str)) | |
| 259 self.failUnless(isinstance(fail.value, fail.type)) | |
| 260 return 430 | |
| 261 return self._testImpl('deferredJelly', 430, failureDeferredJelly) | |
| 262 | |
| 263 | |
| 264 def test_unjellyableFailure(self): | |
| 265 """ | |
| 266 An non-jellyable L{pb.Error} subclass raised by a remote method is | |
| 267 turned into a Failure with a type set to the FQPN of the exception | |
| 268 type. | |
| 269 """ | |
| 270 def failureUnjellyable(fail): | |
| 271 self.assertEqual( | |
| 272 fail.type, 'twisted.test.test_pbfailure.SynchronousError') | |
| 273 return 431 | |
| 274 return self._testImpl('synchronousError', 431, failureUnjellyable) | |
| 275 | |
| 276 | |
| 277 def test_unknownFailure(self): | |
| 278 """ | |
| 279 Test that an exception which is a subclass of L{pb.Error} but not | |
| 280 known on the client side has its type set properly. | |
| 281 """ | |
| 282 def failureUnknown(fail): | |
| 283 self.assertEqual( | |
| 284 fail.type, 'twisted.test.test_pbfailure.UnknownError') | |
| 285 return 4310 | |
| 286 return self._testImpl('unknownError', 4310, failureUnknown) | |
| 287 | |
| 288 | |
| 289 def test_securityFailure(self): | |
| 290 """ | |
| 291 Test that even if an exception is not explicitly jellyable (by being | |
| 292 a L{pb.Jellyable} subclass), as long as it is an L{pb.Error} | |
| 293 subclass it receives the same special treatment. | |
| 294 """ | |
| 295 def failureSecurity(fail): | |
| 296 fail.trap(SecurityError) | |
| 297 self.failIf(isinstance(fail.type, str)) | |
| 298 self.failUnless(isinstance(fail.value, fail.type)) | |
| 299 return 4300 | |
| 300 return self._testImpl('security', 4300, failureSecurity) | |
| 301 | |
| 302 | |
| 303 def test_deferredSecurity(self): | |
| 304 """ | |
| 305 Test that a Deferred which fails with a L{pb.Error} which is not | |
| 306 also a L{pb.Jellyable} is treated in the same way as a synchronously | |
| 307 raised exception of the same type. | |
| 308 """ | |
| 309 def failureDeferredSecurity(fail): | |
| 310 fail.trap(SecurityError) | |
| 311 self.failIf(isinstance(fail.type, str)) | |
| 312 self.failUnless(isinstance(fail.value, fail.type)) | |
| 313 return 43000 | |
| 314 return self._testImpl('deferredSecurity', 43000, failureDeferredSecurity
) | |
| 315 | |
| 316 | |
| 317 def test_noSuchMethodFailure(self): | |
| 318 """ | |
| 319 Test that attempting to call a method which is not defined correctly | |
| 320 results in an AttributeError on the calling side. | |
| 321 """ | |
| 322 def failureNoSuch(fail): | |
| 323 fail.trap(pb.NoSuchMethod) | |
| 324 self.compare(fail.traceback, "Traceback unavailable\n") | |
| 325 return 42000 | |
| 326 return self._testImpl('nosuch', 42000, failureNoSuch, AttributeError) | |
| 327 | |
| 328 | |
| 329 def test_copiedFailureLogging(self): | |
| 330 """ | |
| 331 Test that a copied failure received from a PB call can be logged | |
| 332 locally. | |
| 333 | |
| 334 Note: this test needs some serious help: all it really tests is that | |
| 335 log.err(copiedFailure) doesn't raise an exception. | |
| 336 """ | |
| 337 d = self.clientFactory.getRootObject() | |
| 338 | |
| 339 def connected(rootObj): | |
| 340 return rootObj.callRemote('synchronousException') | |
| 341 d.addCallback(connected) | |
| 342 | |
| 343 def exception(failure): | |
| 344 log.err(failure) | |
| 345 errs = self.flushLoggedErrors(SynchronousException) | |
| 346 self.assertEquals(len(errs), 2) | |
| 347 d.addErrback(exception) | |
| 348 | |
| 349 return d | |
| 350 | |
| 351 | |
| 352 | |
| 353 class PBFailureTestUnsafe(PBFailureTest): | |
| 354 compare = unittest.TestCase.failIfEquals | |
| 355 unsafeTracebacks = 1 | |
| 356 | |
| 357 | |
| 358 | |
| 359 class DummyInvoker(object): | |
| 360 """ | |
| 361 A behaviorless object to be used as the invoker parameter to | |
| 362 L{jelly.jelly}. | |
| 363 """ | |
| 364 serializingPerspective = None | |
| 365 | |
| 366 | |
| 367 | |
| 368 class FailureJellyingTests(unittest.TestCase): | |
| 369 """ | |
| 370 Tests for the interaction of jelly and failures. | |
| 371 """ | |
| 372 def test_unjelliedFailureCheck(self): | |
| 373 """ | |
| 374 An unjellied L{CopyableFailure} has a check method which behaves the | |
| 375 same way as the original L{CopyableFailure}'s check method. | |
| 376 """ | |
| 377 original = pb.CopyableFailure(ZeroDivisionError()) | |
| 378 self.assertIdentical( | |
| 379 original.check(ZeroDivisionError), ZeroDivisionError) | |
| 380 self.assertIdentical(original.check(ArithmeticError), ArithmeticError) | |
| 381 copied = jelly.unjelly(jelly.jelly(original, invoker=DummyInvoker())) | |
| 382 self.assertIdentical( | |
| 383 copied.check(ZeroDivisionError), ZeroDivisionError) | |
| 384 self.assertIdentical(copied.check(ArithmeticError), ArithmeticError) | |
| 385 | |
| 386 | |
| 387 def test_twiceUnjelliedFailureCheck(self): | |
| 388 """ | |
| 389 The object which results from jellying a L{CopyableFailure}, unjellying | |
| 390 the result, creating a new L{CopyableFailure} from the result of that, | |
| 391 jellying it, and finally unjellying the result of that has a check | |
| 392 method which behaves the same way as the original L{CopyableFailure}'s | |
| 393 check method. | |
| 394 """ | |
| 395 original = pb.CopyableFailure(ZeroDivisionError()) | |
| 396 self.assertIdentical( | |
| 397 original.check(ZeroDivisionError), ZeroDivisionError) | |
| 398 self.assertIdentical(original.check(ArithmeticError), ArithmeticError) | |
| 399 copiedOnce = jelly.unjelly( | |
| 400 jelly.jelly(original, invoker=DummyInvoker())) | |
| 401 derivative = pb.CopyableFailure(copiedOnce) | |
| 402 copiedTwice = jelly.unjelly( | |
| 403 jelly.jelly(derivative, invoker=DummyInvoker())) | |
| 404 self.assertIdentical( | |
| 405 copiedTwice.check(ZeroDivisionError), ZeroDivisionError) | |
| 406 self.assertIdentical( | |
| 407 copiedTwice.check(ArithmeticError), ArithmeticError) | |
| OLD | NEW |