diff options
author | Nick Coghlan <ncoghlan@gmail.com> | 2014-01-22 13:04:37 (GMT) |
---|---|---|
committer | Nick Coghlan <ncoghlan@gmail.com> | 2014-01-22 13:04:37 (GMT) |
commit | d58831e6886ce457626448c6c2efd1f4cd05dd3a (patch) | |
tree | 2571c8d98d76f8ed944de9a517c546c53a25cf61 /Lib | |
parent | 4a2dbeb0d3067aefab00ba3f43ee1939608323be (diff) | |
parent | 09761e7c9cf984b8164c172fcf9f1a5994402495 (diff) | |
download | cpython-d58831e6886ce457626448c6c2efd1f4cd05dd3a.zip cpython-d58831e6886ce457626448c6c2efd1f4cd05dd3a.tar.gz cpython-d58831e6886ce457626448c6c2efd1f4cd05dd3a.tar.bz2 |
Merge #20317 from 3.3
Diffstat (limited to 'Lib')
-rw-r--r-- | Lib/contextlib.py | 10 | ||||
-rw-r--r-- | Lib/test/test_contextlib.py | 23 |
2 files changed, 32 insertions, 1 deletions
diff --git a/Lib/contextlib.py b/Lib/contextlib.py index d3219f6..ca7a79d 100644 --- a/Lib/contextlib.py +++ b/Lib/contextlib.py @@ -298,11 +298,19 @@ class ExitStack(object): # we were actually nesting multiple with statements frame_exc = sys.exc_info()[1] def _fix_exception_context(new_exc, old_exc): + # Context isn't what we want, so find the end of the chain while 1: exc_context = new_exc.__context__ - if exc_context in (None, frame_exc): + if exc_context is old_exc: + # Context is already set correctly (see issue 20317) + return + if exc_context is None or exc_context is frame_exc: break + details = id(new_exc), id(old_exc), id(exc_context) + raise Exception(str(details)) new_exc = exc_context + # Change the end of the chain to point to the exception + # we expect it to reference new_exc.__context__ = old_exc # Callbacks are invoked in LIFO order to match the behaviour of diff --git a/Lib/test/test_contextlib.py b/Lib/test/test_contextlib.py index b8770c8..f947232 100644 --- a/Lib/test/test_contextlib.py +++ b/Lib/test/test_contextlib.py @@ -626,6 +626,29 @@ class TestExitStack(unittest.TestCase): else: self.fail("Expected KeyError, but no exception was raised") + def test_exit_exception_with_correct_context(self): + # http://bugs.python.org/issue20317 + @contextmanager + def gets_the_context_right(): + try: + yield 6 + finally: + 1 / 0 + + # The contextmanager already fixes the context, so prior to the + # fix, ExitStack would try to fix it *again* and get into an + # infinite self-referential loop + try: + with ExitStack() as stack: + stack.enter_context(gets_the_context_right()) + stack.enter_context(gets_the_context_right()) + stack.enter_context(gets_the_context_right()) + except ZeroDivisionError as exc: + self.assertIsInstance(exc.__context__, ZeroDivisionError) + self.assertIsInstance(exc.__context__.__context__, ZeroDivisionError) + self.assertIsNone(exc.__context__.__context__.__context__) + + def test_body_exception_suppress(self): def suppress_exc(*exc_details): return True |