diff -r edb12dad7bf6 Doc/library/code.rst --- a/Doc/library/code.rst Fri Mar 14 14:20:09 2014 +0000 +++ b/Doc/library/code.rst Sun Mar 16 20:17:37 2014 +0200 @@ -19,6 +19,11 @@ created dictionary with key ``'__name__'`` set to ``'__console__'`` and key ``'__doc__'`` set to ``None``. + .. versionchanged:: 3.5 + + :class:`InteractiveInterpreter` emulates properly the behaviour of the builtin + interpreter when it comes to displaying exception causes. + .. class:: InteractiveConsole(locals=None, filename="") diff -r edb12dad7bf6 Lib/code.py --- a/Lib/code.py Fri Mar 14 14:20:09 2014 +0000 +++ b/Lib/code.py Sun Mar 16 20:17:37 2014 +0200 @@ -136,25 +136,35 @@ 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 try: - type, value, tb = sys.exc_info() - sys.last_type = type - sys.last_value = value - sys.last_traceback = tb - tblist = traceback.extract_tb(tb) - del tblist[:1] - lines = traceback.format_list(tblist) - if lines: - lines.insert(0, "Traceback (most recent call last):\n") - lines.extend(traceback.format_exception_only(type, value)) + lines = [] + for value, tb in traceback._iter_chain(*ei[1:]): + if isinstance(value, str): + lines.append(value) + lines.append('\n') + continue + if tb: + tblist = traceback.extract_tb(tb) + if tb is last_tb: + # The last traceback includes the frame we + # exec'd in + del tblist[:1] + tblines = traceback.format_list(tblist) + if tblines: + lines.append("Traceback (most recent call last):\n") + lines.extend(tblines) + lines.extend(traceback.format_exception_only(type(value), + value)) finally: - tblist = tb = None + tblist = last_tb = ei = None if sys.excepthook is sys.__excepthook__: self.write(''.join(lines)) else: # If someone has set sys.excepthook, we let that take precedence # over self.write - sys.excepthook(type, value, tb) + sys.excepthook(type, value, last_tb) def write(self, data): """Write a string. diff -r edb12dad7bf6 Lib/test/test_code_module.py --- a/Lib/test/test_code_module.py Fri Mar 14 14:20:09 2014 +0000 +++ b/Lib/test/test_code_module.py Sun Mar 16 20:17:37 2014 +0200 @@ -1,6 +1,7 @@ "Test InteractiveConsole and InteractiveInterpreter from code module" import sys import unittest +from textwrap import dedent from contextlib import ExitStack from unittest import mock from test import support @@ -78,6 +79,40 @@ self.console.interact(banner='') self.assertEqual(len(self.stderr.method_calls), 1) + def test_cause_tb(self): + self.infunc.side_effect = ["raise ValueError('') from AttributeError", + EOFError('Finished')] + self.console.interact() + output = ''.join(''.join(call[1]) for call in self.stderr.method_calls) + expected = dedent(""" + AttributeError + + The above exception was the direct cause of the following exception: + + Traceback (most recent call last): + File "", line 1, in + ValueError + """) + self.assertIn(expected, output) + + def test_context_tb(self): + self.infunc.side_effect = ["try: ham\nexcept: eggs\n", + EOFError('Finished')] + self.console.interact() + output = ''.join(''.join(call[1]) for call in self.stderr.method_calls) + expected = dedent(""" + Traceback (most recent call last): + File "", line 1, in + NameError: name 'ham' is not defined + + During handling of the above exception, another exception occurred: + + Traceback (most recent call last): + File "", line 2, in + NameError: name 'eggs' is not defined + """) + self.assertIn(expected, output) + def test_main(): support.run_unittest(TestInteractiveConsole)