Skip to content

Commit 66c8fcf

Browse files
committed
Issue #12643: Respect sys.excepthook in code.InteractiveConsole
1 parent 0bb2cce commit 66c8fcf

5 files changed

Lines changed: 91 additions & 5 deletions

File tree

Lib/code.py

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -105,9 +105,10 @@ def showsyntaxerror(self, filename=None):
105105
The output is written by self.write(), below.
106106
107107
"""
108-
type, value, sys.last_traceback = sys.exc_info()
108+
type, value, tb = sys.exc_info()
109109
sys.last_type = type
110110
sys.last_value = value
111+
sys.last_traceback = tb
111112
if filename and type is SyntaxError:
112113
# Work hard to stuff the correct filename in the exception
113114
try:
@@ -119,8 +120,13 @@ def showsyntaxerror(self, filename=None):
119120
# Stuff in the right filename
120121
value = SyntaxError(msg, (filename, lineno, offset, line))
121122
sys.last_value = value
122-
lines = traceback.format_exception_only(type, value)
123-
self.write(''.join(lines))
123+
if sys.excepthook is sys.__excepthook__:
124+
lines = traceback.format_exception_only(type, value)
125+
self.write(''.join(lines))
126+
else:
127+
# If someone has set sys.excepthook, we let that take precedence
128+
# over self.write
129+
sys.excepthook(type, value, tb)
124130

125131
def showtraceback(self):
126132
"""Display the exception that just occurred.
@@ -143,7 +149,12 @@ def showtraceback(self):
143149
lines.extend(traceback.format_exception_only(type, value))
144150
finally:
145151
tblist = tb = None
146-
self.write(''.join(lines))
152+
if sys.excepthook is sys.__excepthook__:
153+
self.write(''.join(lines))
154+
else:
155+
# If someone has set sys.excepthook, we let that take precedence
156+
# over self.write
157+
sys.excepthook(type, value, tb)
147158

148159
def write(self, data):
149160
"""Write a string.

Lib/test/test_code_module.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
"Test InteractiveConsole and InteractiveInterpreter from code module"
2+
import sys
3+
import unittest
4+
from contextlib import ExitStack
5+
from unittest import mock
6+
from test import support
7+
8+
code = support.import_module('code')
9+
10+
11+
class TestInteractiveConsole(unittest.TestCase):
12+
13+
def setUp(self):
14+
self.console = code.InteractiveConsole()
15+
self.mock_sys()
16+
17+
def mock_sys(self):
18+
"Mock system environment for InteractiveConsole"
19+
# use exit stack to match patch context managers to addCleanup
20+
stack = ExitStack()
21+
self.addCleanup(stack.close)
22+
self.infunc = stack.enter_context(mock.patch('code.input',
23+
create=True))
24+
self.stdout = stack.enter_context(mock.patch('code.sys.stdout'))
25+
self.stderr = stack.enter_context(mock.patch('code.sys.stderr'))
26+
prepatch = mock.patch('code.sys', wraps=code.sys, spec=code.sys)
27+
self.sysmod = stack.enter_context(prepatch)
28+
if sys.excepthook is sys.__excepthook__:
29+
self.sysmod.excepthook = self.sysmod.__excepthook__
30+
31+
def test_ps1(self):
32+
self.infunc.side_effect = EOFError('Finished')
33+
self.console.interact()
34+
self.assertEqual(self.sysmod.ps1, '>>> ')
35+
36+
def test_ps2(self):
37+
self.infunc.side_effect = EOFError('Finished')
38+
self.console.interact()
39+
self.assertEqual(self.sysmod.ps2, '... ')
40+
41+
def test_console_stderr(self):
42+
self.infunc.side_effect = ["'antioch'", "", EOFError('Finished')]
43+
self.console.interact()
44+
for call in list(self.stdout.method_calls):
45+
if 'antioch' in ''.join(call[1]):
46+
break
47+
else:
48+
raise AssertionError("no console stdout")
49+
50+
def test_syntax_error(self):
51+
self.infunc.side_effect = ["undefined", EOFError('Finished')]
52+
self.console.interact()
53+
for call in self.stderr.method_calls:
54+
if 'NameError:' in ''.join(call[1]):
55+
break
56+
else:
57+
raise AssertionError("No syntax error from console")
58+
59+
def test_sysexcepthook(self):
60+
self.infunc.side_effect = ["raise ValueError('')",
61+
EOFError('Finished')]
62+
hook = mock.Mock()
63+
self.sysmod.excepthook = hook
64+
self.console.interact()
65+
self.assertTrue(hook.called)
66+
67+
68+
def test_main():
69+
support.run_unittest(TestInteractiveConsole)
70+
71+
if __name__ == "__main__":
72+
unittest.main()

Lib/test/test_sundry.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ def test_at_least_import_untested_modules(self):
99
with support.check_warnings(quiet=True):
1010
import bdb
1111
import cgitb
12-
import code
1312

1413
import distutils.bcppcompiler
1514
import distutils.ccompiler

Misc/ACKS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -487,6 +487,7 @@ Fredrik Håård
487487
Catalin Iacob
488488
Mihai Ibanescu
489489
Ali Ikinci
490+
Aaron Iles
490491
Lars Immisch
491492
Bobby Impollonia
492493
Meador Inge

Misc/NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ Core and Builtins
1919
Library
2020
-------
2121

22+
- Issue #12643: code.InteractiveConsole now respects sys.excepthook when
23+
displaying exceptions (Patch by Aaron Iles)
24+
2225
- Issue #13579: string.Formatter now understands the 'a' conversion specifier.
2326

2427
- Issue #15595: Fix subprocess.Popen(universal_newlines=True)

0 commit comments

Comments
 (0)