[3.13] gh-82378 fix sys.tracebacklimit in pyrepl, approach 2 (GH-123062) (#123252)

Make sure that pyrepl uses the same logic for sys.tracebacklimit as both
the basic repl and the standard sys.excepthook
(cherry picked from commit 63603bca35)
This commit is contained in:
CF Bolz-Tereick 2024-08-23 13:59:08 +02:00 committed by GitHub
parent 95b4f9c9ad
commit 0955db1bd8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 53 additions and 12 deletions

View File

@ -164,8 +164,13 @@ class InteractiveColoredConsole(code.InteractiveConsole):
def showsyntaxerror(self, filename=None, **kwargs): def showsyntaxerror(self, filename=None, **kwargs):
super().showsyntaxerror(filename=filename, **kwargs) super().showsyntaxerror(filename=filename, **kwargs)
def showtraceback(self): def _excepthook(self, typ, value, tb):
super().showtraceback(colorize=self.can_colorize) import traceback
lines = traceback.format_exception(
typ, value, tb,
colorize=self.can_colorize,
limit=traceback.BUILTIN_EXCEPTION_LIMIT)
self.write(''.join(lines))
def runsource(self, source, filename="<input>", symbol="single"): def runsource(self, source, filename="<input>", symbol="single"):
try: try:

View File

@ -107,17 +107,16 @@ class InteractiveInterpreter:
The output is written by self.write(), below. The output is written by self.write(), below.
""" """
colorize = kwargs.pop('colorize', False)
try: try:
typ, value, tb = sys.exc_info() typ, value, tb = sys.exc_info()
if filename and issubclass(typ, SyntaxError): if filename and issubclass(typ, SyntaxError):
value.filename = filename value.filename = filename
source = kwargs.pop('source', "") source = kwargs.pop('source', "")
self._showtraceback(typ, value, None, colorize, source) self._showtraceback(typ, value, None, source)
finally: finally:
typ = value = tb = None typ = value = tb = None
def showtraceback(self, **kwargs): def showtraceback(self):
"""Display the exception that just occurred. """Display the exception that just occurred.
We remove the first stack item because it is our own code. We remove the first stack item because it is our own code.
@ -125,14 +124,13 @@ class InteractiveInterpreter:
The output is written by self.write(), below. The output is written by self.write(), below.
""" """
colorize = kwargs.pop('colorize', False)
try: try:
typ, value, tb = sys.exc_info() typ, value, tb = sys.exc_info()
self._showtraceback(typ, value, tb.tb_next, colorize, '') self._showtraceback(typ, value, tb.tb_next, '')
finally: finally:
typ = value = tb = None typ = value = tb = None
def _showtraceback(self, typ, value, tb, colorize, source): def _showtraceback(self, typ, value, tb, source):
sys.last_type = typ sys.last_type = typ
sys.last_traceback = tb sys.last_traceback = tb
value = value.with_traceback(tb) value = value.with_traceback(tb)
@ -143,9 +141,7 @@ class InteractiveInterpreter:
value.text = lines[value.lineno - 1] value.text = lines[value.lineno - 1]
sys.last_exc = sys.last_value = value = value.with_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(typ, value, tb, self._excepthook(typ, value, tb)
colorize=colorize)
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
@ -162,6 +158,12 @@ class InteractiveInterpreter:
print('Original exception was:', file=sys.stderr) print('Original exception was:', file=sys.stderr)
sys.__excepthook__(typ, value, tb) sys.__excepthook__(typ, value, tb)
def _excepthook(self, typ, value, tb):
# This method is being overwritten in
# _pyrepl.console.InteractiveColoredConsole
lines = traceback.format_exception(typ, value, tb)
self.write(''.join(lines))
def write(self, data): def write(self, data):
"""Write a string. """Write a string.

View File

@ -1020,7 +1020,7 @@ class TestMain(TestCase):
env.update({"TERM": "dumb"}) env.update({"TERM": "dumb"})
output, exit_code = self.run_repl("exit()\n", env=env) output, exit_code = self.run_repl("exit()\n", env=env)
self.assertEqual(exit_code, 0) self.assertEqual(exit_code, 0)
self.assertIn("warning: can\'t use pyrepl", output) self.assertIn("warning: can't use pyrepl", output)
self.assertNotIn("Exception", output) self.assertNotIn("Exception", output)
self.assertNotIn("Traceback", output) self.assertNotIn("Traceback", output)
@ -1114,6 +1114,38 @@ class TestMain(TestCase):
self.assertIn("IndentationError: unexpected indent", output) self.assertIn("IndentationError: unexpected indent", output)
self.assertIn("<python-input-0>", output) self.assertIn("<python-input-0>", output)
@force_not_colorized
def test_proper_tracebacklimit(self):
env = os.environ.copy()
for set_tracebacklimit in [True, False]:
commands = ("import sys\n" +
("sys.tracebacklimit = 1\n" if set_tracebacklimit else "") +
"def x1(): 1/0\n\n"
"def x2(): x1()\n\n"
"def x3(): x2()\n\n"
"x3()\n"
"exit()\n")
for basic_repl in [True, False]:
if basic_repl:
env["PYTHON_BASIC_REPL"] = "1"
else:
env.pop("PYTHON_BASIC_REPL", None)
with self.subTest(set_tracebacklimit=set_tracebacklimit,
basic_repl=basic_repl):
output, exit_code = self.run_repl(commands, env=env)
if "can't use pyrepl" in output:
self.skipTest("pyrepl not available")
self.assertIn("in x1", output)
if set_tracebacklimit:
self.assertNotIn("in x2", output)
self.assertNotIn("in x3", output)
self.assertNotIn("in <module>", output)
else:
self.assertIn("in x2", output)
self.assertIn("in x3", output)
self.assertIn("in <module>", output)
def run_repl( def run_repl(
self, self,
repl_input: str | list[str], repl_input: str | list[str],

View File

@ -0,0 +1,2 @@
Make sure that the new :term:`REPL` interprets :data:`sys.tracebacklimit` in
the same way that the classic REPL did.