Skip to content

Commit 8fe586d

Browse files
[3.12] gh-122478: Remove internal frames from tracebacks in REPL (GH-122528) (GH-122816)
Frames of methods in code and codeop modules was show with non-default sys.excepthook. Save correct tracebacks in sys.last_traceback and update __traceback__ attribute of sys.last_value and sys.last_exc. (cherry picked from commit e73e7a7)
1 parent 6e68559 commit 8fe586d

File tree

3 files changed

+160
-54
lines changed

3 files changed

+160
-54
lines changed

Lib/code.py

Lines changed: 40 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -105,29 +105,21 @@ def showsyntaxerror(self, filename=None):
105105
The output is written by self.write(), below.
106106
107107
"""
108-
type, value, tb = sys.exc_info()
109-
sys.last_exc = value
110-
sys.last_type = type
111-
sys.last_value = value
112-
sys.last_traceback = tb
113-
if filename and type is SyntaxError:
114-
# Work hard to stuff the correct filename in the exception
115-
try:
116-
msg, (dummy_filename, lineno, offset, line) = value.args
117-
except ValueError:
118-
# Not the format we expect; leave it alone
119-
pass
120-
else:
121-
# Stuff in the right filename
122-
value = SyntaxError(msg, (filename, lineno, offset, line))
123-
sys.last_exc = sys.last_value = value
124-
if sys.excepthook is sys.__excepthook__:
125-
lines = traceback.format_exception_only(type, value)
126-
self.write(''.join(lines))
127-
else:
128-
# If someone has set sys.excepthook, we let that take precedence
129-
# over self.write
130-
self._call_excepthook(type, value, tb)
108+
try:
109+
typ, value, tb = sys.exc_info()
110+
if filename and typ is SyntaxError:
111+
# Work hard to stuff the correct filename in the exception
112+
try:
113+
msg, (dummy_filename, lineno, offset, line) = value.args
114+
except ValueError:
115+
# Not the format we expect; leave it alone
116+
pass
117+
else:
118+
# Stuff in the right filename
119+
value = SyntaxError(msg, (filename, lineno, offset, line))
120+
self._showtraceback(typ, value, None)
121+
finally:
122+
typ = value = tb = None
131123

132124
def showtraceback(self):
133125
"""Display the exception that just occurred.
@@ -137,32 +129,34 @@ def showtraceback(self):
137129
The output is written by self.write(), below.
138130
139131
"""
140-
sys.last_type, sys.last_value, last_tb = ei = sys.exc_info()
141-
sys.last_traceback = last_tb
142-
sys.last_exc = ei[1]
143132
try:
144-
if sys.excepthook is sys.__excepthook__:
145-
lines = traceback.format_exception(ei[0], ei[1], last_tb.tb_next)
146-
self.write(''.join(lines))
147-
else:
148-
# If someone has set sys.excepthook, we let that take precedence
149-
# over self.write
150-
self._call_excepthook(ei[0], ei[1], last_tb)
133+
typ, value, tb = sys.exc_info()
134+
self._showtraceback(typ, value, tb.tb_next)
151135
finally:
152-
last_tb = ei = None
136+
typ = value = tb = None
153137

154-
def _call_excepthook(self, typ, value, tb):
155-
try:
156-
sys.excepthook(typ, value, tb)
157-
except SystemExit:
158-
raise
159-
except BaseException as e:
160-
e.__context__ = None
161-
print('Error in sys.excepthook:', file=sys.stderr)
162-
sys.__excepthook__(type(e), e, e.__traceback__.tb_next)
163-
print(file=sys.stderr)
164-
print('Original exception was:', file=sys.stderr)
165-
sys.__excepthook__(typ, value, tb)
138+
def _showtraceback(self, typ, value, tb):
139+
sys.last_type = typ
140+
sys.last_traceback = tb
141+
sys.last_exc = sys.last_value = value = value.with_traceback(tb)
142+
if sys.excepthook is sys.__excepthook__:
143+
lines = traceback.format_exception(typ, value, tb)
144+
self.write(''.join(lines))
145+
else:
146+
# If someone has set sys.excepthook, we let that take precedence
147+
# over self.write
148+
try:
149+
sys.excepthook(typ, value, tb)
150+
except SystemExit:
151+
raise
152+
except BaseException as e:
153+
e.__context__ = None
154+
e = e.with_traceback(e.__traceback__.tb_next)
155+
print('Error in sys.excepthook:', file=sys.stderr)
156+
sys.__excepthook__(type(e), e, e.__traceback__)
157+
print(file=sys.stderr)
158+
print('Original exception was:', file=sys.stderr)
159+
sys.__excepthook__(typ, value, tb)
166160

167161
def write(self, data):
168162
"""Write a string.

Lib/test/test_code_module.py

Lines changed: 117 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"Test InteractiveConsole and InteractiveInterpreter from code module"
22
import sys
3+
import traceback
34
import unittest
45
from textwrap import dedent
56
from contextlib import ExitStack
@@ -11,6 +12,7 @@
1112

1213

1314
class TestInteractiveConsole(unittest.TestCase):
15+
maxDiff = None
1416

1517
def setUp(self):
1618
self.console = code.InteractiveConsole()
@@ -58,21 +60,118 @@ def test_console_stderr(self):
5860
raise AssertionError("no console stdout")
5961

6062
def test_syntax_error(self):
61-
self.infunc.side_effect = ["undefined", EOFError('Finished')]
63+
self.infunc.side_effect = ["def f():",
64+
" x = ?",
65+
"",
66+
EOFError('Finished')]
6267
self.console.interact()
63-
for call in self.stderr.method_calls:
64-
if 'NameError' in ''.join(call[1]):
65-
break
66-
else:
67-
raise AssertionError("No syntax error from console")
68+
output = ''.join(''.join(call[1]) for call in self.stderr.method_calls)
69+
output = output[output.index('(InteractiveConsole)'):]
70+
output = output[:output.index('\nnow exiting')]
71+
self.assertEqual(output.splitlines()[1:], [
72+
' File "<console>", line 2',
73+
' x = ?',
74+
' ^',
75+
'SyntaxError: invalid syntax'])
76+
self.assertIs(self.sysmod.last_type, SyntaxError)
77+
self.assertIs(type(self.sysmod.last_value), SyntaxError)
78+
self.assertIsNone(self.sysmod.last_traceback)
79+
self.assertIsNone(self.sysmod.last_value.__traceback__)
80+
self.assertIs(self.sysmod.last_exc, self.sysmod.last_value)
81+
82+
def test_indentation_error(self):
83+
self.infunc.side_effect = [" 1", EOFError('Finished')]
84+
self.console.interact()
85+
output = ''.join(''.join(call[1]) for call in self.stderr.method_calls)
86+
output = output[output.index('(InteractiveConsole)'):]
87+
output = output[:output.index('\nnow exiting')]
88+
self.assertEqual(output.splitlines()[1:], [
89+
' File "<console>", line 1',
90+
' 1',
91+
'IndentationError: unexpected indent'])
92+
self.assertIs(self.sysmod.last_type, IndentationError)
93+
self.assertIs(type(self.sysmod.last_value), IndentationError)
94+
self.assertIsNone(self.sysmod.last_traceback)
95+
self.assertIsNone(self.sysmod.last_value.__traceback__)
96+
self.assertIs(self.sysmod.last_exc, self.sysmod.last_value)
97+
98+
def test_unicode_error(self):
99+
self.infunc.side_effect = ["'\ud800'", EOFError('Finished')]
100+
self.console.interact()
101+
output = ''.join(''.join(call[1]) for call in self.stderr.method_calls)
102+
output = output[output.index('(InteractiveConsole)'):]
103+
output = output[output.index('\n') + 1:]
104+
self.assertTrue(output.startswith('UnicodeEncodeError: '), output)
105+
self.assertIs(self.sysmod.last_type, UnicodeEncodeError)
106+
self.assertIs(type(self.sysmod.last_value), UnicodeEncodeError)
107+
self.assertIsNone(self.sysmod.last_traceback)
108+
self.assertIsNone(self.sysmod.last_value.__traceback__)
109+
self.assertIs(self.sysmod.last_exc, self.sysmod.last_value)
68110

69111
def test_sysexcepthook(self):
70-
self.infunc.side_effect = ["raise ValueError('')",
112+
self.infunc.side_effect = ["def f():",
113+
" raise ValueError('BOOM!')",
114+
"",
115+
"f()",
71116
EOFError('Finished')]
72117
hook = mock.Mock()
73118
self.sysmod.excepthook = hook
74119
self.console.interact()
75-
self.assertTrue(hook.called)
120+
hook.assert_called()
121+
hook.assert_called_with(self.sysmod.last_type,
122+
self.sysmod.last_value,
123+
self.sysmod.last_traceback)
124+
self.assertIs(self.sysmod.last_type, ValueError)
125+
self.assertIs(type(self.sysmod.last_value), ValueError)
126+
self.assertIs(self.sysmod.last_traceback, self.sysmod.last_value.__traceback__)
127+
self.assertIs(self.sysmod.last_exc, self.sysmod.last_value)
128+
self.assertEqual(traceback.format_exception(self.sysmod.last_exc), [
129+
'Traceback (most recent call last):\n',
130+
' File "<console>", line 1, in <module>\n',
131+
' File "<console>", line 2, in f\n',
132+
'ValueError: BOOM!\n'])
133+
134+
def test_sysexcepthook_syntax_error(self):
135+
self.infunc.side_effect = ["def f():",
136+
" x = ?",
137+
"",
138+
EOFError('Finished')]
139+
hook = mock.Mock()
140+
self.sysmod.excepthook = hook
141+
self.console.interact()
142+
hook.assert_called()
143+
hook.assert_called_with(self.sysmod.last_type,
144+
self.sysmod.last_value,
145+
self.sysmod.last_traceback)
146+
self.assertIs(self.sysmod.last_type, SyntaxError)
147+
self.assertIs(type(self.sysmod.last_value), SyntaxError)
148+
self.assertIsNone(self.sysmod.last_traceback)
149+
self.assertIsNone(self.sysmod.last_value.__traceback__)
150+
self.assertIs(self.sysmod.last_exc, self.sysmod.last_value)
151+
self.assertEqual(traceback.format_exception(self.sysmod.last_exc), [
152+
' File "<console>", line 2\n',
153+
' x = ?\n',
154+
' ^\n',
155+
'SyntaxError: invalid syntax\n'])
156+
157+
def test_sysexcepthook_indentation_error(self):
158+
self.infunc.side_effect = [" 1", EOFError('Finished')]
159+
hook = mock.Mock()
160+
self.sysmod.excepthook = hook
161+
self.console.interact()
162+
hook.assert_called()
163+
hook.assert_called_with(self.sysmod.last_type,
164+
self.sysmod.last_value,
165+
self.sysmod.last_traceback)
166+
self.assertIs(self.sysmod.last_type, IndentationError)
167+
self.assertIs(type(self.sysmod.last_value), IndentationError)
168+
self.assertIsNone(self.sysmod.last_traceback)
169+
self.assertIsNone(self.sysmod.last_value.__traceback__)
170+
self.assertIs(self.sysmod.last_exc, self.sysmod.last_value)
171+
self.assertEqual(traceback.format_exception(self.sysmod.last_exc), [
172+
' File "<console>", line 1\n',
173+
' 1\n',
174+
'IndentationError: unexpected indent\n'])
76175

77176
def test_sysexcepthook_crashing_doesnt_close_repl(self):
78177
self.infunc.side_effect = ["1/0", "a = 123", "print(a)", EOFError('Finished')]
@@ -164,6 +263,11 @@ def test_cause_tb(self):
164263
ValueError
165264
""")
166265
self.assertIn(expected, output)
266+
self.assertIs(self.sysmod.last_type, ValueError)
267+
self.assertIs(type(self.sysmod.last_value), ValueError)
268+
self.assertIs(self.sysmod.last_traceback, self.sysmod.last_value.__traceback__)
269+
self.assertIsNotNone(self.sysmod.last_traceback)
270+
self.assertIs(self.sysmod.last_exc, self.sysmod.last_value)
167271

168272
def test_context_tb(self):
169273
self.infunc.side_effect = ["try: ham\nexcept: eggs\n",
@@ -182,6 +286,11 @@ def test_context_tb(self):
182286
NameError: name 'eggs' is not defined
183287
""")
184288
self.assertIn(expected, output)
289+
self.assertIs(self.sysmod.last_type, NameError)
290+
self.assertIs(type(self.sysmod.last_value), NameError)
291+
self.assertIs(self.sysmod.last_traceback, self.sysmod.last_value.__traceback__)
292+
self.assertIsNotNone(self.sysmod.last_traceback)
293+
self.assertIs(self.sysmod.last_exc, self.sysmod.last_value)
185294

186295

187296
if __name__ == "__main__":
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Remove internal frames from tracebacks shown in
2+
:class:`code.InteractiveInterpreter` with non-default :func:`sys.excepthook`.
3+
Save correct tracebacks in :attr:`sys.last_traceback` and update ``__traceback__`` attribute of :attr:`sys.last_value` and :attr:`sys.last_exc`.

0 commit comments

Comments
 (0)