[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,29 +105,21 @@ 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 # Work hard to stuff the correct filename in the exception
sys.last_traceback = tb try:
if filename and type is SyntaxError: msg, (dummy_filename, lineno, offset, line) = value.args
# Work hard to stuff the correct filename in the exception except ValueError:
try: # Not the format we expect; leave it alone
msg, (dummy_filename, lineno, offset, line) = value.args pass
except ValueError: else:
# Not the format we expect; leave it alone # Stuff in the right filename
pass value = SyntaxError(msg, (filename, lineno, offset, line))
else: self._showtraceback(typ, value, None)
# Stuff in the right filename finally:
value = SyntaxError(msg, (filename, lineno, offset, line)) typ = value = tb = None
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)
def showtraceback(self): def showtraceback(self):
"""Display the exception that just occurred. """Display the exception that just occurred.
@ -137,32 +129,34 @@ 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:
if sys.excepthook is sys.__excepthook__: typ, value, tb = sys.exc_info()
lines = traceback.format_exception(ei[0], ei[1], last_tb.tb_next) self._showtraceback(typ, value, 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)
finally: finally:
last_tb = ei = None typ = value = tb = None
def _call_excepthook(self, typ, value, tb): def _showtraceback(self, typ, value, tb):
try: sys.last_type = typ
sys.excepthook(typ, value, tb) sys.last_traceback = tb
except SystemExit: sys.last_exc = sys.last_value = value = value.with_traceback(tb)
raise if sys.excepthook is sys.__excepthook__:
except BaseException as e: lines = traceback.format_exception(typ, value, tb)
e.__context__ = None self.write(''.join(lines))
print('Error in sys.excepthook:', file=sys.stderr) else:
sys.__excepthook__(type(e), e, e.__traceback__.tb_next) # If someone has set sys.excepthook, we let that take precedence
print(file=sys.stderr) # over self.write
print('Original exception was:', file=sys.stderr) try:
sys.__excepthook__(typ, value, tb) 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): def write(self, data):
"""Write a string. """Write a string.

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