blob: c44e2b1d110ae890e67145a66711fba21a50fe4f [file] [log] [blame]
Jeremy Hylton09ccc3a2001-03-21 20:33:04 +00001"""Test cases for traceback module"""
2
Benjamin Petersone6528212008-07-15 15:32:09 +00003from _testcapi import traceback_print, exception_print
Christian Heimes81ee3ef2008-05-04 22:42:01 +00004from io import StringIO
5import sys
Jeremy Hylton09ccc3a2001-03-21 20:33:04 +00006import unittest
Benjamin Petersone6528212008-07-15 15:32:09 +00007import re
8from test.support import run_unittest, is_jython, Error, captured_output
Amaury Forgeot d'Arccf8016a2008-10-09 23:37:48 +00009from test.support import TESTFN, unlink
Jeremy Hylton09ccc3a2001-03-21 20:33:04 +000010
11import traceback
12
Christian Heimes81ee3ef2008-05-04 22:42:01 +000013try:
14 raise KeyError
15except KeyError:
16 type_, value, tb = sys.exc_info()
17 file_ = StringIO()
18 traceback_print(tb, file_)
19 example_traceback = file_.getvalue()
20else:
21 raise Error("unable to create test traceback string")
22
23
Benjamin Petersone6528212008-07-15 15:32:09 +000024class SyntaxTracebackCases(unittest.TestCase):
Jeremy Hylton09ccc3a2001-03-21 20:33:04 +000025 # For now, a very minimal set of tests. I want to be sure that
26 # formatting of SyntaxErrors works based on changes for 2.1.
27
28 def get_exception_format(self, func, exc):
29 try:
30 func()
Guido van Rossumb940e112007-01-10 16:19:56 +000031 except exc as value:
Jeremy Hylton09ccc3a2001-03-21 20:33:04 +000032 return traceback.format_exception_only(exc, value)
33 else:
Collin Winter3add4d72007-08-29 23:37:32 +000034 raise ValueError("call did not raise exception")
Tim Peters7e01e282001-04-08 07:44:07 +000035
Jeremy Hylton09ccc3a2001-03-21 20:33:04 +000036 def syntax_error_with_caret(self):
37 compile("def fact(x):\n\treturn x!\n", "?", "exec")
38
39 def syntax_error_without_caret(self):
40 # XXX why doesn't compile raise the same traceback?
Barry Warsaw408b6d32002-07-30 23:27:12 +000041 import test.badsyntax_nocaret
Jeremy Hylton09ccc3a2001-03-21 20:33:04 +000042
Thomas Wouters49fd7fa2006-04-21 10:40:58 +000043 def syntax_error_bad_indentation(self):
Georg Brandl88fc6642007-02-09 21:28:07 +000044 compile("def spam():\n print(1)\n print(2)", "?", "exec")
Thomas Wouters49fd7fa2006-04-21 10:40:58 +000045
Jeremy Hylton09ccc3a2001-03-21 20:33:04 +000046 def test_caret(self):
47 err = self.get_exception_format(self.syntax_error_with_caret,
48 SyntaxError)
Guido van Rossume61fd5b2007-07-11 12:20:59 +000049 self.assertEqual(len(err), 4)
Jeremy Hylton09ccc3a2001-03-21 20:33:04 +000050 self.assert_(err[1].strip() == "return x!")
Thomas Wouters0e3f5912006-08-11 14:57:12 +000051 self.assert_("^" in err[2]) # third line has caret
Guido van Rossume61fd5b2007-07-11 12:20:59 +000052 self.assertEqual(err[1].find("!"), err[2].find("^")) # in the right place
Tim Peters7e01e282001-04-08 07:44:07 +000053
Jeremy Hylton09ccc3a2001-03-21 20:33:04 +000054 def test_nocaret(self):
Finn Bock57f0f342002-11-06 11:45:15 +000055 if is_jython:
56 # jython adds a caret in this case (why shouldn't it?)
57 return
Jeremy Hylton09ccc3a2001-03-21 20:33:04 +000058 err = self.get_exception_format(self.syntax_error_without_caret,
59 SyntaxError)
Guido van Rossume61fd5b2007-07-11 12:20:59 +000060 self.assertEqual(len(err), 3)
Jeremy Hylton09ccc3a2001-03-21 20:33:04 +000061 self.assert_(err[1].strip() == "[x for x in x] = x")
62
Thomas Wouters49fd7fa2006-04-21 10:40:58 +000063 def test_bad_indentation(self):
64 err = self.get_exception_format(self.syntax_error_bad_indentation,
65 IndentationError)
Guido van Rossume61fd5b2007-07-11 12:20:59 +000066 self.assertEqual(len(err), 4)
67 self.assertEqual(err[1].strip(), "print(2)")
Thomas Wouters0e3f5912006-08-11 14:57:12 +000068 self.assert_("^" in err[2])
Guido van Rossume61fd5b2007-07-11 12:20:59 +000069 self.assertEqual(err[1].find(")"), err[2].find("^"))
Thomas Wouters49fd7fa2006-04-21 10:40:58 +000070
Thomas Wouters477c8d52006-05-27 19:21:47 +000071 def test_base_exception(self):
72 # Test that exceptions derived from BaseException are formatted right
73 e = KeyboardInterrupt()
74 lst = traceback.format_exception_only(e.__class__, e)
75 self.assertEqual(lst, ['KeyboardInterrupt\n'])
76
Thomas Wouters0e3f5912006-08-11 14:57:12 +000077 def test_format_exception_only_bad__str__(self):
78 class X(Exception):
79 def __str__(self):
80 1/0
81 err = traceback.format_exception_only(X, X())
82 self.assertEqual(len(err), 1)
83 str_value = '<unprintable %s object>' % X.__name__
Georg Brandl1a3284e2007-12-02 09:40:06 +000084 if X.__module__ in ('__main__', 'builtins'):
Brett Cannon44c52612007-02-27 00:12:43 +000085 str_name = X.__name__
86 else:
87 str_name = '.'.join([X.__module__, X.__name__])
88 self.assertEqual(err[0], "%s: %s\n" % (str_name, str_value))
Thomas Wouters0e3f5912006-08-11 14:57:12 +000089
Thomas Wouters89f507f2006-12-13 04:49:30 +000090 def test_without_exception(self):
91 err = traceback.format_exception_only(None, None)
92 self.assertEqual(err, ['None\n'])
93
Amaury Forgeot d'Arccf8016a2008-10-09 23:37:48 +000094 def test_encoded_file(self):
95 # Test that tracebacks are correctly printed for encoded source files:
96 # - correct line number (Issue2384)
97 # - respect file encoding (Issue3975)
98 import tempfile, sys, subprocess, os
99
100 # The spawned subprocess has its stdout redirected to a PIPE, and its
101 # encoding may be different from the current interpreter, on Windows
102 # at least.
103 process = subprocess.Popen([sys.executable, "-c",
104 "import sys; print(sys.stdout.encoding)"],
105 stdout=subprocess.PIPE,
106 stderr=subprocess.STDOUT)
107 stdout, stderr = process.communicate()
108 output_encoding = str(stdout, 'ascii').splitlines()[0]
109
110 def do_test(firstlines, message, charset, lineno):
111 # Raise the message in a subprocess, and catch the output
112 try:
113 output = open(TESTFN, "w", encoding=charset)
114 output.write("""{0}if 1:
115 import traceback;
116 raise RuntimeError('{1}')
117 """.format(firstlines, message))
118 output.close()
119 process = subprocess.Popen([sys.executable, TESTFN],
120 stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
121 stdout, stderr = process.communicate()
122 stdout = stdout.decode(output_encoding).splitlines()
123 finally:
124 unlink(TESTFN)
125
126 # The source lines are encoded with the 'backslashreplace' handler
127 encoded_message = message.encode(output_encoding,
128 'backslashreplace')
129 # and we just decoded them with the output_encoding.
130 message_ascii = encoded_message.decode(output_encoding)
131
132 err_line = "raise RuntimeError('{0}')".format(message_ascii)
133 err_msg = "RuntimeError: {0}".format(message_ascii)
134
135 self.assert_(("line %s" % lineno) in stdout[1],
136 "Invalid line number: {0!r} instead of {1}".format(
137 stdout[1], lineno))
138 self.assert_(stdout[2].endswith(err_line),
139 "Invalid traceback line: {0!r} instead of {1!r}".format(
140 stdout[2], err_line))
141 self.assert_(stdout[3] == err_msg,
142 "Invalid error message: {0!r} instead of {1!r}".format(
143 stdout[3], err_msg))
144
145 do_test("", "foo", "ascii", 3)
146 for charset in ("ascii", "iso-8859-1", "utf-8", "GBK"):
147 if charset == "ascii":
148 text = "foo"
149 elif charset == "GBK":
150 text = "\u4E02\u5100"
151 else:
152 text = "h\xe9 ho"
153 do_test("# coding: {0}\n".format(charset),
154 text, charset, 4)
155 do_test("#!shebang\n# coding: {0}\n".format(charset),
156 text, charset, 5)
157
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000158
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000159class TracebackFormatTests(unittest.TestCase):
160
161 def test_traceback_indentation(self):
162 # Make sure that the traceback is properly indented.
163 tb_lines = example_traceback.splitlines()
164 self.assertEquals(len(tb_lines), 3)
165 banner, location, source_line = tb_lines
166 self.assert_(banner.startswith('Traceback'))
167 self.assert_(location.startswith(' File'))
Benjamin Petersone6528212008-07-15 15:32:09 +0000168 self.assert_(source_line.startswith(' raise'))
169
170
171cause_message = (
172 "\nThe above exception was the direct cause "
173 "of the following exception:\n\n")
174
175context_message = (
176 "\nDuring handling of the above exception, "
177 "another exception occurred:\n\n")
178
179boundaries = re.compile(
180 '(%s|%s)' % (re.escape(cause_message), re.escape(context_message)))
181
182
183class BaseExceptionReportingTests:
184
185 def get_exception(self, exception_or_callable):
186 if isinstance(exception_or_callable, Exception):
187 return exception_or_callable
188 try:
189 exception_or_callable()
190 except Exception as e:
191 return e
192
193 def zero_div(self):
194 1/0 # In zero_div
195
196 def check_zero_div(self, msg):
197 lines = msg.splitlines()
198 self.assert_(lines[-3].startswith(' File'))
199 self.assert_('1/0 # In zero_div' in lines[-2], lines[-2])
200 self.assert_(lines[-1].startswith('ZeroDivisionError'), lines[-1])
201
202 def test_simple(self):
203 try:
204 1/0 # Marker
205 except ZeroDivisionError as _:
206 e = _
207 lines = self.get_report(e).splitlines()
208 self.assertEquals(len(lines), 4)
209 self.assert_(lines[0].startswith('Traceback'))
210 self.assert_(lines[1].startswith(' File'))
211 self.assert_('1/0 # Marker' in lines[2])
212 self.assert_(lines[3].startswith('ZeroDivisionError'))
213
214 def test_cause(self):
215 def inner_raise():
216 try:
217 self.zero_div()
218 except ZeroDivisionError as e:
219 raise KeyError from e
220 def outer_raise():
221 inner_raise() # Marker
222 blocks = boundaries.split(self.get_report(outer_raise))
223 self.assertEquals(len(blocks), 3)
224 self.assertEquals(blocks[1], cause_message)
225 self.check_zero_div(blocks[0])
226 self.assert_('inner_raise() # Marker' in blocks[2])
227
228 def test_context(self):
229 def inner_raise():
230 try:
231 self.zero_div()
232 except ZeroDivisionError:
233 raise KeyError
234 def outer_raise():
235 inner_raise() # Marker
236 blocks = boundaries.split(self.get_report(outer_raise))
237 self.assertEquals(len(blocks), 3)
238 self.assertEquals(blocks[1], context_message)
239 self.check_zero_div(blocks[0])
240 self.assert_('inner_raise() # Marker' in blocks[2])
241
242 def test_cause_recursive(self):
243 def inner_raise():
244 try:
245 try:
246 self.zero_div()
247 except ZeroDivisionError as e:
248 z = e
249 raise KeyError from e
250 except KeyError as e:
251 raise z from e
252 def outer_raise():
253 inner_raise() # Marker
254 blocks = boundaries.split(self.get_report(outer_raise))
255 self.assertEquals(len(blocks), 3)
256 self.assertEquals(blocks[1], cause_message)
257 # The first block is the KeyError raised from the ZeroDivisionError
258 self.assert_('raise KeyError from e' in blocks[0])
259 self.assert_('1/0' not in blocks[0])
260 # The second block (apart from the boundary) is the ZeroDivisionError
261 # re-raised from the KeyError
262 self.assert_('inner_raise() # Marker' in blocks[2])
263 self.check_zero_div(blocks[2])
264
265
266
267class PyExcReportingTests(BaseExceptionReportingTests, unittest.TestCase):
268 #
269 # This checks reporting through the 'traceback' module, with both
270 # format_exception() and print_exception().
271 #
272
273 def get_report(self, e):
274 e = self.get_exception(e)
275 s = ''.join(
276 traceback.format_exception(type(e), e, e.__traceback__))
277 with captured_output("stderr") as sio:
278 traceback.print_exception(type(e), e, e.__traceback__)
279 self.assertEquals(sio.getvalue(), s)
280 return s
281
282
283class CExcReportingTests(BaseExceptionReportingTests, unittest.TestCase):
284 #
285 # This checks built-in reporting by the interpreter.
286 #
287
288 def get_report(self, e):
289 e = self.get_exception(e)
290 with captured_output("stderr") as s:
291 exception_print(e)
292 return s.getvalue()
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000293
294
Fred Drake2e2be372001-09-20 21:33:42 +0000295def test_main():
Benjamin Petersone6528212008-07-15 15:32:09 +0000296 run_unittest(__name__)
Fred Drake2e2be372001-09-20 21:33:42 +0000297
298if __name__ == "__main__":
299 test_main()