[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 e73e7a7abd)
This commit is contained in:
Serhiy Storchaka 2024-08-23 09:27:03 +03:00 committed by GitHub
parent 6e6855950a
commit 8fe586d27a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 160 additions and 54 deletions

View file

@ -105,12 +105,9 @@ class InteractiveInterpreter:
The output is written by self.write(), below. The output is written by self.write(), below.
""" """
type, value, tb = sys.exc_info() try:
sys.last_exc = value typ, value, tb = sys.exc_info()
sys.last_type = type if filename and typ is SyntaxError:
sys.last_value = value
sys.last_traceback = tb
if filename and type is SyntaxError:
# Work hard to stuff the correct filename in the exception # Work hard to stuff the correct filename in the exception
try: try:
msg, (dummy_filename, lineno, offset, line) = value.args msg, (dummy_filename, lineno, offset, line) = value.args
@ -120,14 +117,9 @@ class InteractiveInterpreter:
else: else:
# Stuff in the right filename # Stuff in the right filename
value = SyntaxError(msg, (filename, lineno, offset, line)) value = SyntaxError(msg, (filename, lineno, offset, line))
sys.last_exc = sys.last_value = value self._showtraceback(typ, value, None)
if sys.excepthook is sys.__excepthook__: finally:
lines = traceback.format_exception_only(type, value) typ = value = tb = None
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)
def showtraceback(self): def showtraceback(self):
"""Display the exception that just occurred. """Display the exception that just occurred.
@ -137,29 +129,31 @@ class InteractiveInterpreter:
The output is written by self.write(), below. 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: try:
typ, value, tb = sys.exc_info()
self._showtraceback(typ, value, tb.tb_next)
finally:
typ = value = tb = None
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__: if sys.excepthook is sys.__excepthook__:
lines = traceback.format_exception(ei[0], ei[1], last_tb.tb_next) lines = traceback.format_exception(typ, value, tb)
self.write(''.join(lines)) self.write(''.join(lines))
else: else:
# If someone has set sys.excepthook, we let that take precedence # If someone has set sys.excepthook, we let that take precedence
# over self.write # over self.write
self._call_excepthook(ei[0], ei[1], last_tb)
finally:
last_tb = ei = None
def _call_excepthook(self, typ, value, tb):
try: try:
sys.excepthook(typ, value, tb) sys.excepthook(typ, value, tb)
except SystemExit: except SystemExit:
raise raise
except BaseException as e: except BaseException as e:
e.__context__ = None e.__context__ = None
e = e.with_traceback(e.__traceback__.tb_next)
print('Error in sys.excepthook:', file=sys.stderr) print('Error in sys.excepthook:', file=sys.stderr)
sys.__excepthook__(type(e), e, e.__traceback__.tb_next) sys.__excepthook__(type(e), e, e.__traceback__)
print(file=sys.stderr) print(file=sys.stderr)
print('Original exception was:', file=sys.stderr) print('Original exception was:', file=sys.stderr)
sys.__excepthook__(typ, value, tb) sys.__excepthook__(typ, value, tb)

View file

@ -1,5 +1,6 @@
"Test InteractiveConsole and InteractiveInterpreter from code module" "Test InteractiveConsole and InteractiveInterpreter from code module"
import sys import sys
import traceback
import unittest import unittest
from textwrap import dedent from textwrap import dedent
from contextlib import ExitStack from contextlib import ExitStack
@ -11,6 +12,7 @@ code = import_helper.import_module('code')
class TestInteractiveConsole(unittest.TestCase): class TestInteractiveConsole(unittest.TestCase):
maxDiff = None
def setUp(self): def setUp(self):
self.console = code.InteractiveConsole() self.console = code.InteractiveConsole()
@ -58,21 +60,118 @@ class TestInteractiveConsole(unittest.TestCase):
raise AssertionError("no console stdout") raise AssertionError("no console stdout")
def test_syntax_error(self): def test_syntax_error(self):
self.infunc.side_effect = ["undefined", EOFError('Finished')] self.infunc.side_effect = ["def f():",
" x = ?",
"",
EOFError('Finished')]
self.console.interact() self.console.interact()
for call in self.stderr.method_calls: output = ''.join(''.join(call[1]) for call in self.stderr.method_calls)
if 'NameError' in ''.join(call[1]): output = output[output.index('(InteractiveConsole)'):]
break output = output[:output.index('\nnow exiting')]
else: self.assertEqual(output.splitlines()[1:], [
raise AssertionError("No syntax error from console") ' 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): def test_sysexcepthook(self):
self.infunc.side_effect = ["raise ValueError('')", self.infunc.side_effect = ["def f():",
" raise ValueError('BOOM!')",
"",
"f()",
EOFError('Finished')] EOFError('Finished')]
hook = mock.Mock() hook = mock.Mock()
self.sysmod.excepthook = hook self.sysmod.excepthook = hook
self.console.interact() 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): def test_sysexcepthook_crashing_doesnt_close_repl(self):
self.infunc.side_effect = ["1/0", "a = 123", "print(a)", EOFError('Finished')] self.infunc.side_effect = ["1/0", "a = 123", "print(a)", EOFError('Finished')]
@ -164,6 +263,11 @@ class TestInteractiveConsole(unittest.TestCase):
ValueError ValueError
""") """)
self.assertIn(expected, output) 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): def test_context_tb(self):
self.infunc.side_effect = ["try: ham\nexcept: eggs\n", self.infunc.side_effect = ["try: ham\nexcept: eggs\n",
@ -182,6 +286,11 @@ class TestInteractiveConsole(unittest.TestCase):
NameError: name 'eggs' is not defined NameError: name 'eggs' is not defined
""") """)
self.assertIn(expected, output) 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__": if __name__ == "__main__":

View file

@ -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`.