Imported Upstream version 12.1.0
[contrib/python-twisted.git] / twisted / test / test_failure.py
1 # Copyright (c) Twisted Matrix Laboratories.
2 # See LICENSE for details.
3
4 """
5 Test cases for failure module.
6 """
7
8 import re
9 import sys
10 import StringIO
11 import traceback
12 import pdb
13
14 from twisted.trial import unittest, util
15
16 from twisted.python import failure
17
18 try:
19     from twisted.test import raiser
20 except ImportError:
21     raiser = None
22
23
24 def getDivisionFailure(*args, **kwargs):
25     """
26     Make a C{Failure} of a divide-by-zero error.
27
28     @param args: Any C{*args} are passed to Failure's constructor.
29     @param kwargs: Any C{**kwargs} are passed to Failure's constructor.
30     """
31     try:
32         1/0
33     except:
34         f = failure.Failure(*args, **kwargs)
35     return f
36
37
38 class FailureTestCase(unittest.TestCase):
39
40     def testFailAndTrap(self):
41         """Trapping a failure."""
42         try:
43             raise NotImplementedError('test')
44         except:
45             f = failure.Failure()
46         error = f.trap(SystemExit, RuntimeError)
47         self.assertEqual(error, RuntimeError)
48         self.assertEqual(f.type, NotImplementedError)
49
50
51     def test_notTrapped(self):
52         """Making sure trap doesn't trap what it shouldn't."""
53         try:
54             raise ValueError()
55         except:
56             f = failure.Failure()
57         self.assertRaises(failure.Failure, f.trap, OverflowError)
58
59
60     def assertStartsWith(self, s, prefix):
61         """
62         Assert that s starts with a particular prefix.
63         """
64         self.assertTrue(s.startswith(prefix),
65                         '%r is not the start of %r' % (prefix, s))
66
67
68     def test_printingSmokeTest(self):
69         """
70         None of the print* methods fail when called.
71         """
72         f = getDivisionFailure()
73         out = StringIO.StringIO()
74         f.printDetailedTraceback(out)
75         self.assertStartsWith(out.getvalue(), '*--- Failure')
76         out = StringIO.StringIO()
77         f.printBriefTraceback(out)
78         self.assertStartsWith(out.getvalue(), 'Traceback')
79         out = StringIO.StringIO()
80         f.printTraceback(out)
81         self.assertStartsWith(out.getvalue(), 'Traceback')
82
83
84     def test_printingCapturedVarsSmokeTest(self):
85         """
86         None of the print* methods fail when called on a L{Failure} constructed
87         with C{captureVars=True}.
88
89         Local variables on the stack can be seen in the detailed traceback.
90         """
91         exampleLocalVar = 'xyzzy'
92         f = getDivisionFailure(captureVars=True)
93         out = StringIO.StringIO()
94         f.printDetailedTraceback(out)
95         self.assertStartsWith(out.getvalue(), '*--- Failure')
96         self.assertNotEqual(None, re.search('exampleLocalVar.*xyzzy',
97                                             out.getvalue()))
98         out = StringIO.StringIO()
99         f.printBriefTraceback(out)
100         self.assertStartsWith(out.getvalue(), 'Traceback')
101         out = StringIO.StringIO()
102         f.printTraceback(out)
103         self.assertStartsWith(out.getvalue(), 'Traceback')
104
105
106     def test_printingCapturedVarsCleanedSmokeTest(self):
107         """
108         C{printDetailedTraceback} includes information about local variables on
109         the stack after C{cleanFailure} has been called.
110         """
111         exampleLocalVar = 'xyzzy'
112         f = getDivisionFailure(captureVars=True)
113         f.cleanFailure()
114         out = StringIO.StringIO()
115         f.printDetailedTraceback(out)
116         self.assertNotEqual(None, re.search('exampleLocalVar.*xyzzy',
117                                             out.getvalue()))
118
119
120     def test_printingNoVars(self):
121         """
122         Calling C{Failure()} with no arguments does not capture any locals or
123         globals, so L{printDetailedTraceback} cannot show them in its output.
124         """
125         out = StringIO.StringIO()
126         f = getDivisionFailure()
127         f.printDetailedTraceback(out)
128         # There should be no variables in the detailed output.  Variables are
129         # printed on lines with 2 leading spaces.
130         linesWithVars = [line for line in out.getvalue().splitlines()
131                          if line.startswith('  ')]
132         self.assertEqual([], linesWithVars)
133         self.assertSubstring(
134             'Capture of Locals and Globals disabled', out.getvalue())
135
136
137     def test_printingCaptureVars(self):
138         """
139         Calling C{Failure(captureVars=True)} captures the locals and globals
140         for its stack frames, so L{printDetailedTraceback} will show them in
141         its output.
142         """
143         out = StringIO.StringIO()
144         f = getDivisionFailure(captureVars=True)
145         f.printDetailedTraceback(out)
146         # Variables are printed on lines with 2 leading spaces.
147         linesWithVars = [line for line in out.getvalue().splitlines()
148                          if line.startswith('  ')]
149         self.assertNotEqual([], linesWithVars)
150
151
152     def testExplictPass(self):
153         e = RuntimeError()
154         f = failure.Failure(e)
155         f.trap(RuntimeError)
156         self.assertEqual(f.value, e)
157
158
159     def _getInnermostFrameLine(self, f):
160         try:
161             f.raiseException()
162         except ZeroDivisionError:
163             tb = traceback.extract_tb(sys.exc_info()[2])
164             return tb[-1][-1]
165         else:
166             raise Exception(
167                 "f.raiseException() didn't raise ZeroDivisionError!?")
168
169
170     def testRaiseExceptionWithTB(self):
171         f = getDivisionFailure()
172         innerline = self._getInnermostFrameLine(f)
173         self.assertEqual(innerline, '1/0')
174
175
176     def testLackOfTB(self):
177         f = getDivisionFailure()
178         f.cleanFailure()
179         innerline = self._getInnermostFrameLine(f)
180         self.assertEqual(innerline, '1/0')
181
182     testLackOfTB.todo = "the traceback is not preserved, exarkun said he'll try to fix this! god knows how"
183
184
185     _stringException = "bugger off"
186     def _getStringFailure(self):
187         try:
188             raise self._stringException
189         except:
190             f = failure.Failure()
191         return f
192
193
194     def test_raiseStringExceptions(self):
195         # String exceptions used to totally bugged f.raiseException
196         f = self._getStringFailure()
197         try:
198             f.raiseException()
199         except:
200             self.assertEqual(sys.exc_info()[0], self._stringException)
201         else:
202             raise AssertionError("Should have raised")
203     test_raiseStringExceptions.suppress = [
204         util.suppress(message='raising a string exception is deprecated')]
205
206
207     def test_printStringExceptions(self):
208         """
209         L{Failure.printTraceback} should write out stack and exception
210         information, even for string exceptions.
211         """
212         failure = self._getStringFailure()
213         output = StringIO.StringIO()
214         failure.printTraceback(file=output)
215         lines = output.getvalue().splitlines()
216         # The last line should be the value of the raised string
217         self.assertEqual(lines[-1], self._stringException)
218
219     test_printStringExceptions.suppress = [
220         util.suppress(message='raising a string exception is deprecated')]
221
222     if sys.version_info[:2] >= (2, 6):
223         skipMsg = ("String exceptions aren't supported anymore starting "
224                    "Python 2.6")
225         test_raiseStringExceptions.skip = skipMsg
226         test_printStringExceptions.skip = skipMsg
227
228
229     def testConstructionFails(self):
230         """
231         Creating a Failure with no arguments causes it to try to discover the
232         current interpreter exception state.  If no such state exists, creating
233         the Failure should raise a synchronous exception.
234         """
235         self.assertRaises(failure.NoCurrentExceptionError, failure.Failure)
236
237
238     def test_getTracebackObject(self):
239         """
240         If the C{Failure} has not been cleaned, then C{getTracebackObject}
241         returns the traceback object that captured in its constructor.
242         """
243         f = getDivisionFailure()
244         self.assertEqual(f.getTracebackObject(), f.tb)
245
246
247     def test_getTracebackObjectFromCaptureVars(self):
248         """
249         C{captureVars=True} has no effect on the result of
250         C{getTracebackObject}.
251         """
252         try:
253             1/0
254         except ZeroDivisionError:
255             noVarsFailure = failure.Failure()
256             varsFailure = failure.Failure(captureVars=True)
257         self.assertEqual(noVarsFailure.getTracebackObject(), varsFailure.tb)
258
259
260     def test_getTracebackObjectFromClean(self):
261         """
262         If the Failure has been cleaned, then C{getTracebackObject} returns an
263         object that looks the same to L{traceback.extract_tb}.
264         """
265         f = getDivisionFailure()
266         expected = traceback.extract_tb(f.getTracebackObject())
267         f.cleanFailure()
268         observed = traceback.extract_tb(f.getTracebackObject())
269         self.assertNotEqual(None, expected)
270         self.assertEqual(expected, observed)
271
272
273     def test_getTracebackObjectFromCaptureVarsAndClean(self):
274         """
275         If the Failure was created with captureVars, then C{getTracebackObject}
276         returns an object that looks the same to L{traceback.extract_tb}.
277         """
278         f = getDivisionFailure(captureVars=True)
279         expected = traceback.extract_tb(f.getTracebackObject())
280         f.cleanFailure()
281         observed = traceback.extract_tb(f.getTracebackObject())
282         self.assertEqual(expected, observed)
283
284
285     def test_getTracebackObjectWithoutTraceback(self):
286         """
287         L{failure.Failure}s need not be constructed with traceback objects. If
288         a C{Failure} has no traceback information at all, C{getTracebackObject}
289         just returns None.
290
291         None is a good value, because traceback.extract_tb(None) -> [].
292         """
293         f = failure.Failure(Exception("some error"))
294         self.assertEqual(f.getTracebackObject(), None)
295
296
297
298 class BrokenStr(Exception):
299     """
300     An exception class the instances of which cannot be presented as strings via
301     C{str}.
302     """
303     def __str__(self):
304         # Could raise something else, but there's no point as yet.
305         raise self
306
307
308
309 class BrokenExceptionMetaclass(type):
310     """
311     A metaclass for an exception type which cannot be presented as a string via
312     C{str}.
313     """
314     def __str__(self):
315         raise ValueError("You cannot make a string out of me.")
316
317
318
319 class BrokenExceptionType(Exception, object):
320     """
321     The aforementioned exception type which cnanot be presented as a string via
322     C{str}.
323     """
324     __metaclass__ = BrokenExceptionMetaclass
325
326
327
328 class GetTracebackTests(unittest.TestCase):
329     """
330     Tests for L{Failure.getTraceback}.
331     """
332     def _brokenValueTest(self, detail):
333         """
334         Construct a L{Failure} with an exception that raises an exception from
335         its C{__str__} method and then call C{getTraceback} with the specified
336         detail and verify that it returns a string.
337         """
338         x = BrokenStr()
339         f = failure.Failure(x)
340         traceback = f.getTraceback(detail=detail)
341         self.assertIsInstance(traceback, str)
342
343
344     def test_brokenValueBriefDetail(self):
345         """
346         A L{Failure} might wrap an exception with a C{__str__} method which
347         raises an exception.  In this case, calling C{getTraceback} on the
348         failure with the C{"brief"} detail does not raise an exception.
349         """
350         self._brokenValueTest("brief")
351
352
353     def test_brokenValueDefaultDetail(self):
354         """
355         Like test_brokenValueBriefDetail, but for the C{"default"} detail case.
356         """
357         self._brokenValueTest("default")
358
359
360     def test_brokenValueVerboseDetail(self):
361         """
362         Like test_brokenValueBriefDetail, but for the C{"default"} detail case.
363         """
364         self._brokenValueTest("verbose")
365
366
367     def _brokenTypeTest(self, detail):
368         """
369         Construct a L{Failure} with an exception type that raises an exception
370         from its C{__str__} method and then call C{getTraceback} with the
371         specified detail and verify that it returns a string.
372         """
373         f = failure.Failure(BrokenExceptionType())
374         traceback = f.getTraceback(detail=detail)
375         self.assertIsInstance(traceback, str)
376
377
378     def test_brokenTypeBriefDetail(self):
379         """
380         A L{Failure} might wrap an exception the type object of which has a
381         C{__str__} method which raises an exception.  In this case, calling
382         C{getTraceback} on the failure with the C{"brief"} detail does not raise
383         an exception.
384         """
385         self._brokenTypeTest("brief")
386
387
388     def test_brokenTypeDefaultDetail(self):
389         """
390         Like test_brokenTypeBriefDetail, but for the C{"default"} detail case.
391         """
392         self._brokenTypeTest("default")
393
394
395     def test_brokenTypeVerboseDetail(self):
396         """
397         Like test_brokenTypeBriefDetail, but for the C{"verbose"} detail case.
398         """
399         self._brokenTypeTest("verbose")
400
401
402
403 class FindFailureTests(unittest.TestCase):
404     """
405     Tests for functionality related to L{Failure._findFailure}.
406     """
407
408     def test_findNoFailureInExceptionHandler(self):
409         """
410         Within an exception handler, _findFailure should return
411         C{None} in case no Failure is associated with the current
412         exception.
413         """
414         try:
415             1/0
416         except:
417             self.assertEqual(failure.Failure._findFailure(), None)
418         else:
419             self.fail("No exception raised from 1/0!?")
420
421
422     def test_findNoFailure(self):
423         """
424         Outside of an exception handler, _findFailure should return None.
425         """
426         self.assertEqual(sys.exc_info()[-1], None) #environment sanity check
427         self.assertEqual(failure.Failure._findFailure(), None)
428
429
430     def test_findFailure(self):
431         """
432         Within an exception handler, it should be possible to find the
433         original Failure that caused the current exception (if it was
434         caused by raiseException).
435         """
436         f = getDivisionFailure()
437         f.cleanFailure()
438         try:
439             f.raiseException()
440         except:
441             self.assertEqual(failure.Failure._findFailure(), f)
442         else:
443             self.fail("No exception raised from raiseException!?")
444
445
446     def test_failureConstructionFindsOriginalFailure(self):
447         """
448         When a Failure is constructed in the context of an exception
449         handler that is handling an exception raised by
450         raiseException, the new Failure should be chained to that
451         original Failure.
452         """
453         f = getDivisionFailure()
454         f.cleanFailure()
455         try:
456             f.raiseException()
457         except:
458             newF = failure.Failure()
459             self.assertEqual(f.getTraceback(), newF.getTraceback())
460         else:
461             self.fail("No exception raised from raiseException!?")
462
463
464     def test_failureConstructionWithMungedStackSucceeds(self):
465         """
466         Pyrex and Cython are known to insert fake stack frames so as to give
467         more Python-like tracebacks. These stack frames with empty code objects
468         should not break extraction of the exception.
469         """
470         try:
471             raiser.raiseException()
472         except raiser.RaiserException:
473             f = failure.Failure()
474             self.assertTrue(f.check(raiser.RaiserException))
475         else:
476             self.fail("No exception raised from extension?!")
477
478
479     if raiser is None:
480         skipMsg = "raiser extension not available"
481         test_failureConstructionWithMungedStackSucceeds.skip = skipMsg
482
483
484
485 class TestFormattableTraceback(unittest.TestCase):
486     """
487     Whitebox tests that show that L{failure._Traceback} constructs objects that
488     can be used by L{traceback.extract_tb}.
489
490     If the objects can be used by L{traceback.extract_tb}, then they can be
491     formatted using L{traceback.format_tb} and friends.
492     """
493
494     def test_singleFrame(self):
495         """
496         A C{_Traceback} object constructed with a single frame should be able
497         to be passed to L{traceback.extract_tb}, and we should get a singleton
498         list containing a (filename, lineno, methodname, line) tuple.
499         """
500         tb = failure._Traceback([['method', 'filename.py', 123, {}, {}]])
501         # Note that we don't need to test that extract_tb correctly extracts
502         # the line's contents. In this case, since filename.py doesn't exist,
503         # it will just use None.
504         self.assertEqual(traceback.extract_tb(tb),
505                          [('filename.py', 123, 'method', None)])
506
507
508     def test_manyFrames(self):
509         """
510         A C{_Traceback} object constructed with multiple frames should be able
511         to be passed to L{traceback.extract_tb}, and we should get a list
512         containing a tuple for each frame.
513         """
514         tb = failure._Traceback([
515             ['method1', 'filename.py', 123, {}, {}],
516             ['method2', 'filename.py', 235, {}, {}]])
517         self.assertEqual(traceback.extract_tb(tb),
518                          [('filename.py', 123, 'method1', None),
519                           ('filename.py', 235, 'method2', None)])
520
521
522
523 class TestFrameAttributes(unittest.TestCase):
524     """
525     _Frame objects should possess some basic attributes that qualify them as
526     fake python Frame objects.
527     """
528
529     def test_fakeFrameAttributes(self):
530         """
531         L{_Frame} instances have the C{f_globals} and C{f_locals} attributes
532         bound to C{dict} instance.  They also have the C{f_code} attribute
533         bound to something like a code object.
534         """
535         frame = failure._Frame("dummyname", "dummyfilename")
536         self.assertIsInstance(frame.f_globals, dict)
537         self.assertIsInstance(frame.f_locals, dict)
538         self.assertIsInstance(frame.f_code, failure._Code)
539
540
541
542 class TestDebugMode(unittest.TestCase):
543     """
544     Failure's debug mode should allow jumping into the debugger.
545     """
546
547     def setUp(self):
548         """
549         Override pdb.post_mortem so we can make sure it's called.
550         """
551         # Make sure any changes we make are reversed:
552         post_mortem = pdb.post_mortem
553         origInit = failure.Failure.__dict__['__init__']
554         def restore():
555             pdb.post_mortem = post_mortem
556             failure.Failure.__dict__['__init__'] = origInit
557         self.addCleanup(restore)
558
559         self.result = []
560         pdb.post_mortem = self.result.append
561         failure.startDebugMode()
562
563
564     def test_regularFailure(self):
565         """
566         If startDebugMode() is called, calling Failure() will first call
567         pdb.post_mortem with the traceback.
568         """
569         try:
570             1/0
571         except:
572             typ, exc, tb = sys.exc_info()
573             f = failure.Failure()
574         self.assertEqual(self.result, [tb])
575         self.assertEqual(f.captureVars, False)
576
577
578     def test_captureVars(self):
579         """
580         If startDebugMode() is called, passing captureVars to Failure() will
581         not blow up.
582         """
583         try:
584             1/0
585         except:
586             typ, exc, tb = sys.exc_info()
587             f = failure.Failure(captureVars=True)
588         self.assertEqual(self.result, [tb])
589         self.assertEqual(f.captureVars, True)
590
591
592
593 if sys.version_info[:2] >= (2, 5):
594     from twisted.test.generator_failure_tests import TwoPointFiveFailureTests