blob: 2145710ffc7f13dd012da0426a58a57e1c5eef11 [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 +000013
Benjamin Petersone6528212008-07-15 15:32:09 +000014class SyntaxTracebackCases(unittest.TestCase):
Jeremy Hylton09ccc3a2001-03-21 20:33:04 +000015 # For now, a very minimal set of tests. I want to be sure that
16 # formatting of SyntaxErrors works based on changes for 2.1.
17
18 def get_exception_format(self, func, exc):
19 try:
20 func()
Guido van Rossumb940e112007-01-10 16:19:56 +000021 except exc as value:
Jeremy Hylton09ccc3a2001-03-21 20:33:04 +000022 return traceback.format_exception_only(exc, value)
23 else:
Collin Winter3add4d72007-08-29 23:37:32 +000024 raise ValueError("call did not raise exception")
Tim Peters7e01e282001-04-08 07:44:07 +000025
Jeremy Hylton09ccc3a2001-03-21 20:33:04 +000026 def syntax_error_with_caret(self):
27 compile("def fact(x):\n\treturn x!\n", "?", "exec")
28
Georg Brandl751899a2009-06-04 19:41:00 +000029 def syntax_error_with_caret_2(self):
30 compile("1 +\n", "?", "exec")
31
Jeremy Hylton09ccc3a2001-03-21 20:33:04 +000032 def syntax_error_without_caret(self):
33 # XXX why doesn't compile raise the same traceback?
Barry Warsaw408b6d32002-07-30 23:27:12 +000034 import test.badsyntax_nocaret
Jeremy Hylton09ccc3a2001-03-21 20:33:04 +000035
Thomas Wouters49fd7fa2006-04-21 10:40:58 +000036 def syntax_error_bad_indentation(self):
Georg Brandl88fc6642007-02-09 21:28:07 +000037 compile("def spam():\n print(1)\n print(2)", "?", "exec")
Thomas Wouters49fd7fa2006-04-21 10:40:58 +000038
Jeremy Hylton09ccc3a2001-03-21 20:33:04 +000039 def test_caret(self):
40 err = self.get_exception_format(self.syntax_error_with_caret,
41 SyntaxError)
Guido van Rossume61fd5b2007-07-11 12:20:59 +000042 self.assertEqual(len(err), 4)
Benjamin Petersonc9c0f202009-06-30 23:06:06 +000043 self.assertTrue(err[1].strip() == "return x!")
44 self.assertTrue("^" in err[2]) # third line has caret
Guido van Rossume61fd5b2007-07-11 12:20:59 +000045 self.assertEqual(err[1].find("!"), err[2].find("^")) # in the right place
Tim Peters7e01e282001-04-08 07:44:07 +000046
Georg Brandl751899a2009-06-04 19:41:00 +000047 err = self.get_exception_format(self.syntax_error_with_caret_2,
48 SyntaxError)
Benjamin Petersonc9c0f202009-06-30 23:06:06 +000049 self.assertTrue("^" in err[2]) # third line has caret
50 self.assertTrue(err[2].count('\n') == 1) # and no additional newline
51 self.assertTrue(err[1].find("+") == err[2].find("^")) # in the right place
Georg Brandl751899a2009-06-04 19:41:00 +000052
Jeremy Hylton09ccc3a2001-03-21 20:33:04 +000053 def test_nocaret(self):
Finn Bock57f0f342002-11-06 11:45:15 +000054 if is_jython:
55 # jython adds a caret in this case (why shouldn't it?)
56 return
Jeremy Hylton09ccc3a2001-03-21 20:33:04 +000057 err = self.get_exception_format(self.syntax_error_without_caret,
58 SyntaxError)
Guido van Rossume61fd5b2007-07-11 12:20:59 +000059 self.assertEqual(len(err), 3)
Benjamin Petersonc9c0f202009-06-30 23:06:06 +000060 self.assertTrue(err[1].strip() == "[x for x in x] = x")
Jeremy Hylton09ccc3a2001-03-21 20:33:04 +000061
Thomas Wouters49fd7fa2006-04-21 10:40:58 +000062 def test_bad_indentation(self):
63 err = self.get_exception_format(self.syntax_error_bad_indentation,
64 IndentationError)
Guido van Rossume61fd5b2007-07-11 12:20:59 +000065 self.assertEqual(len(err), 4)
66 self.assertEqual(err[1].strip(), "print(2)")
Benjamin Petersonc9c0f202009-06-30 23:06:06 +000067 self.assertTrue("^" in err[2])
Guido van Rossume61fd5b2007-07-11 12:20:59 +000068 self.assertEqual(err[1].find(")"), err[2].find("^"))
Thomas Wouters49fd7fa2006-04-21 10:40:58 +000069
Thomas Wouters477c8d52006-05-27 19:21:47 +000070 def test_base_exception(self):
71 # Test that exceptions derived from BaseException are formatted right
72 e = KeyboardInterrupt()
73 lst = traceback.format_exception_only(e.__class__, e)
74 self.assertEqual(lst, ['KeyboardInterrupt\n'])
75
Thomas Wouters0e3f5912006-08-11 14:57:12 +000076 def test_format_exception_only_bad__str__(self):
77 class X(Exception):
78 def __str__(self):
79 1/0
80 err = traceback.format_exception_only(X, X())
81 self.assertEqual(len(err), 1)
82 str_value = '<unprintable %s object>' % X.__name__
Georg Brandl1a3284e2007-12-02 09:40:06 +000083 if X.__module__ in ('__main__', 'builtins'):
Brett Cannon44c52612007-02-27 00:12:43 +000084 str_name = X.__name__
85 else:
86 str_name = '.'.join([X.__module__, X.__name__])
87 self.assertEqual(err[0], "%s: %s\n" % (str_name, str_value))
Thomas Wouters0e3f5912006-08-11 14:57:12 +000088
Thomas Wouters89f507f2006-12-13 04:49:30 +000089 def test_without_exception(self):
90 err = traceback.format_exception_only(None, None)
91 self.assertEqual(err, ['None\n'])
92
Amaury Forgeot d'Arccf8016a2008-10-09 23:37:48 +000093 def test_encoded_file(self):
94 # Test that tracebacks are correctly printed for encoded source files:
95 # - correct line number (Issue2384)
96 # - respect file encoding (Issue3975)
97 import tempfile, sys, subprocess, os
98
99 # The spawned subprocess has its stdout redirected to a PIPE, and its
100 # encoding may be different from the current interpreter, on Windows
101 # at least.
102 process = subprocess.Popen([sys.executable, "-c",
103 "import sys; print(sys.stdout.encoding)"],
104 stdout=subprocess.PIPE,
105 stderr=subprocess.STDOUT)
106 stdout, stderr = process.communicate()
107 output_encoding = str(stdout, 'ascii').splitlines()[0]
108
109 def do_test(firstlines, message, charset, lineno):
110 # Raise the message in a subprocess, and catch the output
111 try:
112 output = open(TESTFN, "w", encoding=charset)
113 output.write("""{0}if 1:
114 import traceback;
115 raise RuntimeError('{1}')
116 """.format(firstlines, message))
117 output.close()
118 process = subprocess.Popen([sys.executable, TESTFN],
119 stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
120 stdout, stderr = process.communicate()
121 stdout = stdout.decode(output_encoding).splitlines()
122 finally:
123 unlink(TESTFN)
124
125 # The source lines are encoded with the 'backslashreplace' handler
126 encoded_message = message.encode(output_encoding,
127 'backslashreplace')
128 # and we just decoded them with the output_encoding.
129 message_ascii = encoded_message.decode(output_encoding)
130
131 err_line = "raise RuntimeError('{0}')".format(message_ascii)
132 err_msg = "RuntimeError: {0}".format(message_ascii)
133
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000134 self.assertTrue(("line %s" % lineno) in stdout[1],
Amaury Forgeot d'Arccf8016a2008-10-09 23:37:48 +0000135 "Invalid line number: {0!r} instead of {1}".format(
136 stdout[1], lineno))
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000137 self.assertTrue(stdout[2].endswith(err_line),
Amaury Forgeot d'Arccf8016a2008-10-09 23:37:48 +0000138 "Invalid traceback line: {0!r} instead of {1!r}".format(
139 stdout[2], err_line))
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000140 self.assertTrue(stdout[3] == err_msg,
Amaury Forgeot d'Arccf8016a2008-10-09 23:37:48 +0000141 "Invalid error message: {0!r} instead of {1!r}".format(
142 stdout[3], err_msg))
143
144 do_test("", "foo", "ascii", 3)
145 for charset in ("ascii", "iso-8859-1", "utf-8", "GBK"):
146 if charset == "ascii":
147 text = "foo"
148 elif charset == "GBK":
149 text = "\u4E02\u5100"
150 else:
151 text = "h\xe9 ho"
152 do_test("# coding: {0}\n".format(charset),
153 text, charset, 4)
154 do_test("#!shebang\n# coding: {0}\n".format(charset),
155 text, charset, 5)
156
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000157
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000158class TracebackFormatTests(unittest.TestCase):
159
Georg Brandl236f7972009-04-05 14:28:42 +0000160 def test_traceback_format(self):
161 try:
162 raise KeyError('blah')
163 except KeyError:
164 type_, value, tb = sys.exc_info()
165 traceback_fmt = 'Traceback (most recent call last):\n' + \
166 ''.join(traceback.format_tb(tb))
167 file_ = StringIO()
168 traceback_print(tb, file_)
169 python_fmt = file_.getvalue()
170 else:
171 raise Error("unable to create test traceback string")
172
173 # Make sure that Python and the traceback module format the same thing
174 self.assertEquals(traceback_fmt, python_fmt)
175
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000176 # Make sure that the traceback is properly indented.
Georg Brandl236f7972009-04-05 14:28:42 +0000177 tb_lines = python_fmt.splitlines()
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000178 self.assertEquals(len(tb_lines), 3)
179 banner, location, source_line = tb_lines
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000180 self.assertTrue(banner.startswith('Traceback'))
181 self.assertTrue(location.startswith(' File'))
182 self.assertTrue(source_line.startswith(' raise'))
Benjamin Petersone6528212008-07-15 15:32:09 +0000183
184
185cause_message = (
186 "\nThe above exception was the direct cause "
187 "of the following exception:\n\n")
188
189context_message = (
190 "\nDuring handling of the above exception, "
191 "another exception occurred:\n\n")
192
193boundaries = re.compile(
194 '(%s|%s)' % (re.escape(cause_message), re.escape(context_message)))
195
196
197class BaseExceptionReportingTests:
198
199 def get_exception(self, exception_or_callable):
200 if isinstance(exception_or_callable, Exception):
201 return exception_or_callable
202 try:
203 exception_or_callable()
204 except Exception as e:
205 return e
206
207 def zero_div(self):
208 1/0 # In zero_div
209
210 def check_zero_div(self, msg):
211 lines = msg.splitlines()
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000212 self.assertTrue(lines[-3].startswith(' File'))
213 self.assertTrue('1/0 # In zero_div' in lines[-2], lines[-2])
214 self.assertTrue(lines[-1].startswith('ZeroDivisionError'), lines[-1])
Benjamin Petersone6528212008-07-15 15:32:09 +0000215
216 def test_simple(self):
217 try:
218 1/0 # Marker
219 except ZeroDivisionError as _:
220 e = _
221 lines = self.get_report(e).splitlines()
222 self.assertEquals(len(lines), 4)
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000223 self.assertTrue(lines[0].startswith('Traceback'))
224 self.assertTrue(lines[1].startswith(' File'))
225 self.assertTrue('1/0 # Marker' in lines[2])
226 self.assertTrue(lines[3].startswith('ZeroDivisionError'))
Benjamin Petersone6528212008-07-15 15:32:09 +0000227
228 def test_cause(self):
229 def inner_raise():
230 try:
231 self.zero_div()
232 except ZeroDivisionError as e:
233 raise KeyError from e
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], cause_message)
239 self.check_zero_div(blocks[0])
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000240 self.assertTrue('inner_raise() # Marker' in blocks[2])
Benjamin Petersone6528212008-07-15 15:32:09 +0000241
242 def test_context(self):
243 def inner_raise():
244 try:
245 self.zero_div()
246 except ZeroDivisionError:
247 raise KeyError
248 def outer_raise():
249 inner_raise() # Marker
250 blocks = boundaries.split(self.get_report(outer_raise))
251 self.assertEquals(len(blocks), 3)
252 self.assertEquals(blocks[1], context_message)
253 self.check_zero_div(blocks[0])
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000254 self.assertTrue('inner_raise() # Marker' in blocks[2])
Benjamin Petersone6528212008-07-15 15:32:09 +0000255
256 def test_cause_recursive(self):
257 def inner_raise():
258 try:
259 try:
260 self.zero_div()
261 except ZeroDivisionError as e:
262 z = e
263 raise KeyError from e
264 except KeyError as e:
265 raise z from e
266 def outer_raise():
267 inner_raise() # Marker
268 blocks = boundaries.split(self.get_report(outer_raise))
269 self.assertEquals(len(blocks), 3)
270 self.assertEquals(blocks[1], cause_message)
271 # The first block is the KeyError raised from the ZeroDivisionError
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000272 self.assertTrue('raise KeyError from e' in blocks[0])
273 self.assertTrue('1/0' not in blocks[0])
Benjamin Petersone6528212008-07-15 15:32:09 +0000274 # The second block (apart from the boundary) is the ZeroDivisionError
275 # re-raised from the KeyError
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000276 self.assertTrue('inner_raise() # Marker' in blocks[2])
Benjamin Petersone6528212008-07-15 15:32:09 +0000277 self.check_zero_div(blocks[2])
278
279
280
281class PyExcReportingTests(BaseExceptionReportingTests, unittest.TestCase):
282 #
283 # This checks reporting through the 'traceback' module, with both
284 # format_exception() and print_exception().
285 #
286
287 def get_report(self, e):
288 e = self.get_exception(e)
289 s = ''.join(
290 traceback.format_exception(type(e), e, e.__traceback__))
291 with captured_output("stderr") as sio:
292 traceback.print_exception(type(e), e, e.__traceback__)
293 self.assertEquals(sio.getvalue(), s)
294 return s
295
296
297class CExcReportingTests(BaseExceptionReportingTests, unittest.TestCase):
298 #
299 # This checks built-in reporting by the interpreter.
300 #
301
302 def get_report(self, e):
303 e = self.get_exception(e)
304 with captured_output("stderr") as s:
305 exception_print(e)
306 return s.getvalue()
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000307
308
Fred Drake2e2be372001-09-20 21:33:42 +0000309def test_main():
Benjamin Petersone6528212008-07-15 15:32:09 +0000310 run_unittest(__name__)
Fred Drake2e2be372001-09-20 21:33:42 +0000311
312if __name__ == "__main__":
313 test_main()