Skip to content

[3.12] gh-122478: Remove internal frames from tracebacks in REPL (GH-122528) #122816

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 40 additions & 46 deletions Lib/code.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,29 +105,21 @@ def showsyntaxerror(self, filename=None):
The output is written by self.write(), below.

"""
type, value, tb = sys.exc_info()
sys.last_exc = value
sys.last_type = type
sys.last_value = value
sys.last_traceback = tb
if filename and type is SyntaxError:
# Work hard to stuff the correct filename in the exception
try:
msg, (dummy_filename, lineno, offset, line) = value.args
except ValueError:
# Not the format we expect; leave it alone
pass
else:
# Stuff in the right filename
value = SyntaxError(msg, (filename, lineno, offset, line))
sys.last_exc = sys.last_value = value
if sys.excepthook is sys.__excepthook__:
lines = traceback.format_exception_only(type, value)
self.write(''.join(lines))
else:
# If someone has set sys.excepthook, we let that take precedence
# over self.write
self._call_excepthook(type, value, tb)
try:
typ, value, tb = sys.exc_info()
if filename and typ is SyntaxError:
# Work hard to stuff the correct filename in the exception
try:
msg, (dummy_filename, lineno, offset, line) = value.args
except ValueError:
# Not the format we expect; leave it alone
pass
else:
# Stuff in the right filename
value = SyntaxError(msg, (filename, lineno, offset, line))
self._showtraceback(typ, value, None)
finally:
typ = value = tb = None

def showtraceback(self):
"""Display the exception that just occurred.
Expand All @@ -137,32 +129,34 @@ def showtraceback(self):
The output is written by self.write(), below.

"""
sys.last_type, sys.last_value, last_tb = ei = sys.exc_info()
sys.last_traceback = last_tb
sys.last_exc = ei[1]
try:
if sys.excepthook is sys.__excepthook__:
lines = traceback.format_exception(ei[0], ei[1], last_tb.tb_next)
self.write(''.join(lines))
else:
# If someone has set sys.excepthook, we let that take precedence
# over self.write
self._call_excepthook(ei[0], ei[1], last_tb)
typ, value, tb = sys.exc_info()
self._showtraceback(typ, value, tb.tb_next)
finally:
last_tb = ei = None
typ = value = tb = None

def _call_excepthook(self, typ, value, tb):
try:
sys.excepthook(typ, value, tb)
except SystemExit:
raise
except BaseException as e:
e.__context__ = None
print('Error in sys.excepthook:', file=sys.stderr)
sys.__excepthook__(type(e), e, e.__traceback__.tb_next)
print(file=sys.stderr)
print('Original exception was:', file=sys.stderr)
sys.__excepthook__(typ, value, tb)
def _showtraceback(self, typ, value, tb):
sys.last_type = typ
sys.last_traceback = tb
sys.last_exc = sys.last_value = value = value.with_traceback(tb)
if sys.excepthook is sys.__excepthook__:
lines = traceback.format_exception(typ, value, tb)
self.write(''.join(lines))
else:
# If someone has set sys.excepthook, we let that take precedence
# over self.write
try:
sys.excepthook(typ, value, tb)
except SystemExit:
raise
except BaseException as e:
e.__context__ = None
e = e.with_traceback(e.__traceback__.tb_next)
print('Error in sys.excepthook:', file=sys.stderr)
sys.__excepthook__(type(e), e, e.__traceback__)
print(file=sys.stderr)
print('Original exception was:', file=sys.stderr)
sys.__excepthook__(typ, value, tb)

def write(self, data):
"""Write a string.
Expand Down
125 changes: 117 additions & 8 deletions Lib/test/test_code_module.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"Test InteractiveConsole and InteractiveInterpreter from code module"
import sys
import traceback
import unittest
from textwrap import dedent
from contextlib import ExitStack
Expand All @@ -11,6 +12,7 @@


class TestInteractiveConsole(unittest.TestCase):
maxDiff = None

def setUp(self):
self.console = code.InteractiveConsole()
Expand Down Expand Up @@ -58,21 +60,118 @@ def test_console_stderr(self):
raise AssertionError("no console stdout")

def test_syntax_error(self):
self.infunc.side_effect = ["undefined", EOFError('Finished')]
self.infunc.side_effect = ["def f():",
" x = ?",
"",
EOFError('Finished')]
self.console.interact()
for call in self.stderr.method_calls:
if 'NameError' in ''.join(call[1]):
break
else:
raise AssertionError("No syntax error from console")
output = ''.join(''.join(call[1]) for call in self.stderr.method_calls)
output = output[output.index('(InteractiveConsole)'):]
output = output[:output.index('\nnow exiting')]
self.assertEqual(output.splitlines()[1:], [
' File "<console>", line 2',
' x = ?',
' ^',
'SyntaxError: invalid syntax'])
self.assertIs(self.sysmod.last_type, SyntaxError)
self.assertIs(type(self.sysmod.last_value), SyntaxError)
self.assertIsNone(self.sysmod.last_traceback)
self.assertIsNone(self.sysmod.last_value.__traceback__)
self.assertIs(self.sysmod.last_exc, self.sysmod.last_value)

def test_indentation_error(self):
self.infunc.side_effect = [" 1", EOFError('Finished')]
self.console.interact()
output = ''.join(''.join(call[1]) for call in self.stderr.method_calls)
output = output[output.index('(InteractiveConsole)'):]
output = output[:output.index('\nnow exiting')]
self.assertEqual(output.splitlines()[1:], [
' File "<console>", line 1',
' 1',
'IndentationError: unexpected indent'])
self.assertIs(self.sysmod.last_type, IndentationError)
self.assertIs(type(self.sysmod.last_value), IndentationError)
self.assertIsNone(self.sysmod.last_traceback)
self.assertIsNone(self.sysmod.last_value.__traceback__)
self.assertIs(self.sysmod.last_exc, self.sysmod.last_value)

def test_unicode_error(self):
self.infunc.side_effect = ["'\ud800'", EOFError('Finished')]
self.console.interact()
output = ''.join(''.join(call[1]) for call in self.stderr.method_calls)
output = output[output.index('(InteractiveConsole)'):]
output = output[output.index('\n') + 1:]
self.assertTrue(output.startswith('UnicodeEncodeError: '), output)
self.assertIs(self.sysmod.last_type, UnicodeEncodeError)
self.assertIs(type(self.sysmod.last_value), UnicodeEncodeError)
self.assertIsNone(self.sysmod.last_traceback)
self.assertIsNone(self.sysmod.last_value.__traceback__)
self.assertIs(self.sysmod.last_exc, self.sysmod.last_value)

def test_sysexcepthook(self):
self.infunc.side_effect = ["raise ValueError('')",
self.infunc.side_effect = ["def f():",
" raise ValueError('BOOM!')",
"",
"f()",
EOFError('Finished')]
hook = mock.Mock()
self.sysmod.excepthook = hook
self.console.interact()
self.assertTrue(hook.called)
hook.assert_called()
hook.assert_called_with(self.sysmod.last_type,
self.sysmod.last_value,
self.sysmod.last_traceback)
self.assertIs(self.sysmod.last_type, ValueError)
self.assertIs(type(self.sysmod.last_value), ValueError)
self.assertIs(self.sysmod.last_traceback, self.sysmod.last_value.__traceback__)
self.assertIs(self.sysmod.last_exc, self.sysmod.last_value)
self.assertEqual(traceback.format_exception(self.sysmod.last_exc), [
'Traceback (most recent call last):\n',
' File "<console>", line 1, in <module>\n',
' File "<console>", line 2, in f\n',
'ValueError: BOOM!\n'])

def test_sysexcepthook_syntax_error(self):
self.infunc.side_effect = ["def f():",
" x = ?",
"",
EOFError('Finished')]
hook = mock.Mock()
self.sysmod.excepthook = hook
self.console.interact()
hook.assert_called()
hook.assert_called_with(self.sysmod.last_type,
self.sysmod.last_value,
self.sysmod.last_traceback)
self.assertIs(self.sysmod.last_type, SyntaxError)
self.assertIs(type(self.sysmod.last_value), SyntaxError)
self.assertIsNone(self.sysmod.last_traceback)
self.assertIsNone(self.sysmod.last_value.__traceback__)
self.assertIs(self.sysmod.last_exc, self.sysmod.last_value)
self.assertEqual(traceback.format_exception(self.sysmod.last_exc), [
' File "<console>", line 2\n',
' x = ?\n',
' ^\n',
'SyntaxError: invalid syntax\n'])

def test_sysexcepthook_indentation_error(self):
self.infunc.side_effect = [" 1", EOFError('Finished')]
hook = mock.Mock()
self.sysmod.excepthook = hook
self.console.interact()
hook.assert_called()
hook.assert_called_with(self.sysmod.last_type,
self.sysmod.last_value,
self.sysmod.last_traceback)
self.assertIs(self.sysmod.last_type, IndentationError)
self.assertIs(type(self.sysmod.last_value), IndentationError)
self.assertIsNone(self.sysmod.last_traceback)
self.assertIsNone(self.sysmod.last_value.__traceback__)
self.assertIs(self.sysmod.last_exc, self.sysmod.last_value)
self.assertEqual(traceback.format_exception(self.sysmod.last_exc), [
' File "<console>", line 1\n',
' 1\n',
'IndentationError: unexpected indent\n'])

def test_sysexcepthook_crashing_doesnt_close_repl(self):
self.infunc.side_effect = ["1/0", "a = 123", "print(a)", EOFError('Finished')]
Expand Down Expand Up @@ -164,6 +263,11 @@ def test_cause_tb(self):
ValueError
""")
self.assertIn(expected, output)
self.assertIs(self.sysmod.last_type, ValueError)
self.assertIs(type(self.sysmod.last_value), ValueError)
self.assertIs(self.sysmod.last_traceback, self.sysmod.last_value.__traceback__)
self.assertIsNotNone(self.sysmod.last_traceback)
self.assertIs(self.sysmod.last_exc, self.sysmod.last_value)

def test_context_tb(self):
self.infunc.side_effect = ["try: ham\nexcept: eggs\n",
Expand All @@ -182,6 +286,11 @@ def test_context_tb(self):
NameError: name 'eggs' is not defined
""")
self.assertIn(expected, output)
self.assertIs(self.sysmod.last_type, NameError)
self.assertIs(type(self.sysmod.last_value), NameError)
self.assertIs(self.sysmod.last_traceback, self.sysmod.last_value.__traceback__)
self.assertIsNotNone(self.sysmod.last_traceback)
self.assertIs(self.sysmod.last_exc, self.sysmod.last_value)


if __name__ == "__main__":
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Remove internal frames from tracebacks shown in
:class:`code.InteractiveInterpreter` with non-default :func:`sys.excepthook`.
Save correct tracebacks in :attr:`sys.last_traceback` and update ``__traceback__`` attribute of :attr:`sys.last_value` and :attr:`sys.last_exc`.
Loading