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 |