Initial import to Tizen
[profile/ivi/python-twisted.git] / twisted / test / test_pbfailure.py
1 # Copyright (c) Twisted Matrix Laboratories.
2 # See LICENSE for details.
3
4 """
5 Tests for error handling in PB.
6 """
7
8 import sys
9 from StringIO import StringIO
10
11 from twisted.trial import unittest
12
13 from twisted.spread import pb, flavors, jelly
14 from twisted.internet import reactor, defer
15 from twisted.python import log
16
17 ##
18 # test exceptions
19 ##
20 class AsynchronousException(Exception):
21     """
22     Helper used to test remote methods which return Deferreds which fail with
23     exceptions which are not L{pb.Error} subclasses.
24     """
25
26
27 class SynchronousException(Exception):
28     """
29     Helper used to test remote methods which raise exceptions which are not
30     L{pb.Error} subclasses.
31     """
32
33
34 class AsynchronousError(pb.Error):
35     """
36     Helper used to test remote methods which return Deferreds which fail with
37     exceptions which are L{pb.Error} subclasses.
38     """
39
40
41 class SynchronousError(pb.Error):
42     """
43     Helper used to test remote methods which raise exceptions which are
44     L{pb.Error} subclasses.
45     """
46
47
48 #class JellyError(flavors.Jellyable, pb.Error): pass
49 class JellyError(flavors.Jellyable, pb.Error, pb.RemoteCopy):
50     pass
51
52
53 class SecurityError(pb.Error, pb.RemoteCopy):
54     pass
55
56 pb.setUnjellyableForClass(JellyError, JellyError)
57 pb.setUnjellyableForClass(SecurityError, SecurityError)
58 pb.globalSecurity.allowInstancesOf(SecurityError)
59
60
61 ####
62 # server-side
63 ####
64 class SimpleRoot(pb.Root):
65     def remote_asynchronousException(self):
66         """
67         Fail asynchronously with a non-pb.Error exception.
68         """
69         return defer.fail(AsynchronousException("remote asynchronous exception"))
70
71     def remote_synchronousException(self):
72         """
73         Fail synchronously with a non-pb.Error exception.
74         """
75         raise SynchronousException("remote synchronous exception")
76
77     def remote_asynchronousError(self):
78         """
79         Fail asynchronously with a pb.Error exception.
80         """
81         return defer.fail(AsynchronousError("remote asynchronous error"))
82
83     def remote_synchronousError(self):
84         """
85         Fail synchronously with a pb.Error exception.
86         """
87         raise SynchronousError("remote synchronous error")
88
89     def remote_unknownError(self):
90         """
91         Fail with error that is not known to client.
92         """
93         class UnknownError(pb.Error):
94             pass
95         raise UnknownError("I'm not known to client!")
96
97     def remote_jelly(self):
98         self.raiseJelly()
99
100     def remote_security(self):
101         self.raiseSecurity()
102
103     def remote_deferredJelly(self):
104         d = defer.Deferred()
105         d.addCallback(self.raiseJelly)
106         d.callback(None)
107         return d
108
109     def remote_deferredSecurity(self):
110         d = defer.Deferred()
111         d.addCallback(self.raiseSecurity)
112         d.callback(None)
113         return d
114
115     def raiseJelly(self, results=None):
116         raise JellyError("I'm jellyable!")
117
118     def raiseSecurity(self, results=None):
119         raise SecurityError("I'm secure!")
120
121
122
123 class SaveProtocolServerFactory(pb.PBServerFactory):
124     """
125     A L{pb.PBServerFactory} that saves the latest connected client in
126     C{protocolInstance}.
127     """
128     protocolInstance = None
129
130     def clientConnectionMade(self, protocol):
131         """
132         Keep track of the given protocol.
133         """
134         self.protocolInstance = protocol
135
136
137
138 class PBConnTestCase(unittest.TestCase):
139     unsafeTracebacks = 0
140
141     def setUp(self):
142         self._setUpServer()
143         self._setUpClient()
144
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")
149
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)
154
155     def tearDown(self):
156         if self.serverFactory.protocolInstance is not None:
157             self.serverFactory.protocolInstance.transport.loseConnection()
158         return defer.gatherResults([
159             self._tearDownServer(),
160             self._tearDownClient()])
161
162     def _tearDownServer(self):
163         return defer.maybeDeferred(self.serverPort.stopListening)
164
165     def _tearDownClient(self):
166         self.clientConnector.disconnect()
167         return defer.succeed(None)
168
169
170
171 class PBFailureTest(PBConnTestCase):
172     compare = unittest.TestCase.assertEqual
173
174
175     def _exceptionTest(self, method, exceptionType, flush):
176         def eb(err):
177             err.trap(exceptionType)
178             self.compare(err.traceback, "Traceback unavailable\n")
179             if flush:
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)
186             d.addErrback(eb)
187             return d
188         d.addCallback(gotRootObject)
189         return d
190
191
192     def test_asynchronousException(self):
193         """
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.
196         """
197         return self._exceptionTest(
198             'asynchronousException', AsynchronousException, True)
199
200
201     def test_synchronousException(self):
202         """
203         Like L{test_asynchronousException}, but for a method which raises an
204         exception synchronously.
205         """
206         return self._exceptionTest(
207             'synchronousException', SynchronousException, True)
208
209
210     def test_asynchronousError(self):
211         """
212         Like L{test_asynchronousException}, but for a method which returns a
213         Deferred failing with an L{pb.Error} subclass.
214         """
215         return self._exceptionTest(
216             'asynchronousError', AsynchronousError, False)
217
218
219     def test_synchronousError(self):
220         """
221         Like L{test_asynchronousError}, but for a method which synchronously
222         raises a L{pb.Error} subclass.
223         """
224         return self._exceptionTest(
225             'synchronousError', SynchronousError, False)
226
227
228     def _success(self, result, expectedResult):
229         self.assertEqual(result, expectedResult)
230         return result
231
232
233     def _addFailingCallbacks(self, remoteCall, expectedResult, eb):
234         remoteCall.addCallbacks(self._success, eb,
235                                 callbackArgs=(expectedResult,))
236         return remoteCall
237
238
239     def _testImpl(self, method, expected, eb, exc=None):
240         """
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.
244         """
245         rootDeferred = self.clientFactory.getRootObject()
246         def gotRootObj(obj):
247             failureDeferred = self._addFailingCallbacks(obj.callRemote(method), expected, eb)
248             if exc is not None:
249                 def gotFailure(err):
250                     self.assertEqual(len(self.flushLoggedErrors(exc)), 1)
251                     return err
252                 failureDeferred.addBoth(gotFailure)
253             return failureDeferred
254         rootDeferred.addCallback(gotRootObj)
255         return rootDeferred
256
257
258     def test_jellyFailure(self):
259         """
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.
262         """
263         def failureJelly(fail):
264             fail.trap(JellyError)
265             self.failIf(isinstance(fail.type, str))
266             self.failUnless(isinstance(fail.value, fail.type))
267             return 43
268         return self._testImpl('jelly', 43, failureJelly)
269
270
271     def test_deferredJellyFailure(self):
272         """
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}.
275         """
276         def failureDeferredJelly(fail):
277             fail.trap(JellyError)
278             self.failIf(isinstance(fail.type, str))
279             self.failUnless(isinstance(fail.value, fail.type))
280             return 430
281         return self._testImpl('deferredJelly', 430, failureDeferredJelly)
282
283
284     def test_unjellyableFailure(self):
285         """
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
288         type.
289         """
290         def failureUnjellyable(fail):
291             self.assertEqual(
292                 fail.type, 'twisted.test.test_pbfailure.SynchronousError')
293             return 431
294         return self._testImpl('synchronousError', 431, failureUnjellyable)
295
296
297     def test_unknownFailure(self):
298         """
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.
301         """
302         def failureUnknown(fail):
303             self.assertEqual(
304                 fail.type, 'twisted.test.test_pbfailure.UnknownError')
305             return 4310
306         return self._testImpl('unknownError', 4310, failureUnknown)
307
308
309     def test_securityFailure(self):
310         """
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.
314         """
315         def failureSecurity(fail):
316             fail.trap(SecurityError)
317             self.failIf(isinstance(fail.type, str))
318             self.failUnless(isinstance(fail.value, fail.type))
319             return 4300
320         return self._testImpl('security', 4300, failureSecurity)
321
322
323     def test_deferredSecurity(self):
324         """
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.
328         """
329         def failureDeferredSecurity(fail):
330             fail.trap(SecurityError)
331             self.failIf(isinstance(fail.type, str))
332             self.failUnless(isinstance(fail.value, fail.type))
333             return 43000
334         return self._testImpl('deferredSecurity', 43000, failureDeferredSecurity)
335
336
337     def test_noSuchMethodFailure(self):
338         """
339         Test that attempting to call a method which is not defined correctly
340         results in an AttributeError on the calling side.
341         """
342         def failureNoSuch(fail):
343             fail.trap(pb.NoSuchMethod)
344             self.compare(fail.traceback, "Traceback unavailable\n")
345             return 42000
346         return self._testImpl('nosuch', 42000, failureNoSuch, AttributeError)
347
348
349     def test_copiedFailureLogging(self):
350         """
351         Test that a copied failure received from a PB call can be logged
352         locally.
353
354         Note: this test needs some serious help: all it really tests is that
355         log.err(copiedFailure) doesn't raise an exception.
356         """
357         d = self.clientFactory.getRootObject()
358
359         def connected(rootObj):
360             return rootObj.callRemote('synchronousException')
361         d.addCallback(connected)
362
363         def exception(failure):
364             log.err(failure)
365             errs = self.flushLoggedErrors(SynchronousException)
366             self.assertEqual(len(errs), 2)
367         d.addErrback(exception)
368
369         return d
370
371
372     def test_throwExceptionIntoGenerator(self):
373         """
374         L{pb.CopiedFailure.throwExceptionIntoGenerator} will throw a
375         L{RemoteError} into the given paused generator at the point where it
376         last yielded.
377         """
378         original = pb.CopyableFailure(AttributeError("foo"))
379         copy = jelly.unjelly(jelly.jelly(original, invoker=DummyInvoker()))
380         exception = []
381         def generatorFunc():
382             try:
383                 yield None
384             except pb.RemoteError, exc:
385                 exception.append(exc)
386             else:
387                 self.fail("RemoteError not raised")
388         gen = generatorFunc()
389         gen.send(None)
390         self.assertRaises(StopIteration, copy.throwExceptionIntoGenerator, gen)
391         self.assertEqual(len(exception), 1)
392         exc = exception[0]
393         self.assertEqual(exc.remoteType, "exceptions.AttributeError")
394         self.assertEqual(exc.args, ("foo",))
395         self.assertEqual(exc.remoteTraceback, 'Traceback unavailable\n')
396
397     if sys.version_info[:2] < (2, 5):
398         test_throwExceptionIntoGenerator.skip = (
399             "throwExceptionIntoGenerator is not supported in Python < 2.5")
400
401
402
403 class PBFailureTestUnsafe(PBFailureTest):
404     compare = unittest.TestCase.failIfEquals
405     unsafeTracebacks = 1
406
407
408
409 class DummyInvoker(object):
410     """
411     A behaviorless object to be used as the invoker parameter to
412     L{jelly.jelly}.
413     """
414     serializingPerspective = None
415
416
417
418 class FailureJellyingTests(unittest.TestCase):
419     """
420     Tests for the interaction of jelly and failures.
421     """
422     def test_unjelliedFailureCheck(self):
423         """
424         An unjellied L{CopyableFailure} has a check method which behaves the
425         same way as the original L{CopyableFailure}'s check method.
426         """
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)
435
436
437     def test_twiceUnjelliedFailureCheck(self):
438         """
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
443         check method.
444         """
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)
458
459
460     def test_printTracebackIncludesValue(self):
461         """
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.
466         """
467         original = pb.CopyableFailure(Exception("some reason"))
468         copied = jelly.unjelly(jelly.jelly(original, invoker=DummyInvoker()))
469         output = StringIO()
470         copied.printTraceback(output)
471         self.assertEqual(
472             "Traceback from remote host -- Traceback unavailable\n"
473             "exceptions.Exception: some reason\n",
474             output.getvalue())
475