Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
15 changes: 13 additions & 2 deletions Lib/code.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ def showsyntaxerror(self, filename=None, **kwargs):
else:
# If someone has set sys.excepthook, we let that take precedence
# over self.write
sys.excepthook(type, value, tb)
self._call_excepthook(type, value, tb)

def showtraceback(self, **kwargs):
"""Display the exception that just occurred.
Expand All @@ -150,10 +150,21 @@ def showtraceback(self, **kwargs):
else:
# If someone has set sys.excepthook, we let that take precedence
# over self.write
sys.excepthook(ei[0], ei[1], last_tb)
self._call_excepthook(ei[0], ei[1], last_tb)
finally:
last_tb = ei = None

def _call_excepthook(self, typ, value, tb):
try:
sys.excepthook(typ, value, tb)
except Exception 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 write(self, data):
"""Write a string.

Expand Down
11 changes: 11 additions & 0 deletions Lib/test/test_code_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,17 @@ def test_sysexcepthook(self):
self.console.interact()
self.assertTrue(hook.called)

def test_sysexcepthook_crashing_doesnt_close_repl(self):
self.infunc.side_effect = ["1/0", "a = 123", "print(a)", EOFError('Finished')]
self.sysmod.excepthook = 1
self.console.interact()
self.assertEqual(['write', ('123', ), {}], self.stdout.method_calls[0])
error = "".join(call.args[0] for call in self.stderr.method_calls if call[0] == 'write')
self.assertIn("Error in sys.excepthook:", error)
self.assertEqual(error.count("'int' object is not callable"), 1)
self.assertIn("Original exception was:", error)
self.assertIn("division by zero", error)

def test_banner(self):
# with banner
self.infunc.side_effect = EOFError('Finished')
Expand Down
24 changes: 24 additions & 0 deletions Lib/test/test_pyrepl/test_pyrepl.py
Original file line number Diff line number Diff line change
Expand Up @@ -1049,6 +1049,30 @@ def test_python_basic_repl(self):
self.assertNotIn("Exception", output)
self.assertNotIn("Traceback", output)

@force_not_colorized
def test_bad_sys_excepthook_doesnt_crash_pyrepl(self):
env = os.environ.copy()
commands = ("import sys\n"
"sys.excepthook = 1\n"
"1/0\n"
"exit()\n")

def check(output, exitcode):
self.assertIn("Error in sys.excepthook:", output)
self.assertEqual(output.count("'int' object is not callable"), 1)
self.assertIn("Original exception was:", output)
self.assertIn("division by zero", output)
self.assertEqual(exitcode, 0)
env.pop("PYTHON_BASIC_REPL", None)
output, exit_code = self.run_repl(commands, env=env)
if "can\'t use pyrepl" in output:
self.skipTest("pyrepl not available")
check(output, exit_code)

env["PYTHON_BASIC_REPL"] = "1"
output, exit_code = self.run_repl(commands, env=env)
check(output, exit_code)

def test_not_wiping_history_file(self):
# skip, if readline module is not available
import_module('readline')
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Don't crash :class:`code.InteractiveInterpreter` when calling
:func:`sys.excepthook` fails.