]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-95882: fix regression in the traceback of exceptions propagated from inside a...
authorMiss Islington (bot) <31488909+miss-islington@users.noreply.github.com>
Tue, 3 Jan 2023 16:18:45 +0000 (08:18 -0800)
committerGitHub <noreply@github.com>
Tue, 3 Jan 2023 16:18:45 +0000 (08:18 -0800)
(cherry picked from commit b3722ca058f6a6d6505cf2ea9ffabaf7fb6b6e19)

Co-authored-by: Thomas Grainger <tagrain@gmail.com>
Lib/contextlib.py
Lib/test/test_contextlib.py
Lib/test/test_contextlib_async.py
Misc/ACKS
Misc/NEWS.d/next/Library/2022-08-11-10-02-19.gh-issue-95882.FsUr72.rst [new file with mode: 0644]

index 625bb33b12d5fd17638934215fbe8b7cd0bffe2d..58e9a498878d01b2eaaf18fd5d497248720e43d9 100644 (file)
@@ -173,7 +173,7 @@ class _GeneratorContextManager(
                     isinstance(value, StopIteration)
                     and exc.__cause__ is value
                 ):
-                    exc.__traceback__ = traceback
+                    value.__traceback__ = traceback
                     return False
                 raise
             except BaseException as exc:
@@ -228,6 +228,7 @@ class _AsyncGeneratorContextManager(
             except RuntimeError as exc:
                 # Don't re-raise the passed in exception. (issue27122)
                 if exc is value:
+                    exc.__traceback__ = traceback
                     return False
                 # Avoid suppressing if a Stop(Async)Iteration exception
                 # was passed to athrow() and later wrapped into a RuntimeError
@@ -239,6 +240,7 @@ class _AsyncGeneratorContextManager(
                     isinstance(value, (StopIteration, StopAsyncIteration))
                     and exc.__cause__ is value
                 ):
+                    value.__traceback__ = traceback
                     return False
                 raise
             except BaseException as exc:
@@ -250,6 +252,7 @@ class _AsyncGeneratorContextManager(
                 # and the __exit__() protocol.
                 if exc is not value:
                     raise
+                exc.__traceback__ = traceback
                 return False
             raise RuntimeError("generator didn't stop after athrow()")
 
index 31f5c74572b630cb9111d84fa866d91260f92800..ec06785b5667a6c1d3e9cc5a7fa7f5a8e4726b88 100644 (file)
@@ -104,15 +104,39 @@ class ContextManagerTestCase(unittest.TestCase):
         self.assertEqual(frames[0].line, '1/0')
 
         # Repeat with RuntimeError (which goes through a different code path)
+        class RuntimeErrorSubclass(RuntimeError):
+            pass
+
         try:
             with f():
-                raise NotImplementedError(42)
-        except NotImplementedError as e:
+                raise RuntimeErrorSubclass(42)
+        except RuntimeErrorSubclass as e:
             frames = traceback.extract_tb(e.__traceback__)
 
         self.assertEqual(len(frames), 1)
         self.assertEqual(frames[0].name, 'test_contextmanager_traceback')
-        self.assertEqual(frames[0].line, 'raise NotImplementedError(42)')
+        self.assertEqual(frames[0].line, 'raise RuntimeErrorSubclass(42)')
+
+        class StopIterationSubclass(StopIteration):
+            pass
+
+        for stop_exc in (
+            StopIteration('spam'),
+            StopIterationSubclass('spam'),
+        ):
+            with self.subTest(type=type(stop_exc)):
+                try:
+                    with f():
+                        raise stop_exc
+                except type(stop_exc) as e:
+                    self.assertIs(e, stop_exc)
+                    frames = traceback.extract_tb(e.__traceback__)
+                else:
+                    self.fail(f'{stop_exc} was suppressed')
+
+                self.assertEqual(len(frames), 1)
+                self.assertEqual(frames[0].name, 'test_contextmanager_traceback')
+                self.assertEqual(frames[0].line, 'raise stop_exc')
 
     def test_contextmanager_no_reraise(self):
         @contextmanager
index b64673d2c31e05672f566699fc0546c7afb58b77..3d43ed0fcab1686764695443185509a3686d3738 100644 (file)
@@ -5,6 +5,7 @@ from contextlib import (
 import functools
 from test import support
 import unittest
+import traceback
 
 from test.test_contextlib import TestBaseExitStack
 
@@ -125,6 +126,62 @@ class AsyncContextManagerTestCase(unittest.TestCase):
                 raise ZeroDivisionError()
         self.assertEqual(state, [1, 42, 999])
 
+    @_async_test
+    async def test_contextmanager_traceback(self):
+        @asynccontextmanager
+        async def f():
+            yield
+
+        try:
+            async with f():
+                1/0
+        except ZeroDivisionError as e:
+            frames = traceback.extract_tb(e.__traceback__)
+
+        self.assertEqual(len(frames), 1)
+        self.assertEqual(frames[0].name, 'test_contextmanager_traceback')
+        self.assertEqual(frames[0].line, '1/0')
+
+        # Repeat with RuntimeError (which goes through a different code path)
+        class RuntimeErrorSubclass(RuntimeError):
+            pass
+
+        try:
+            async with f():
+                raise RuntimeErrorSubclass(42)
+        except RuntimeErrorSubclass as e:
+            frames = traceback.extract_tb(e.__traceback__)
+
+        self.assertEqual(len(frames), 1)
+        self.assertEqual(frames[0].name, 'test_contextmanager_traceback')
+        self.assertEqual(frames[0].line, 'raise RuntimeErrorSubclass(42)')
+
+        class StopIterationSubclass(StopIteration):
+            pass
+
+        class StopAsyncIterationSubclass(StopAsyncIteration):
+            pass
+
+        for stop_exc in (
+            StopIteration('spam'),
+            StopAsyncIteration('ham'),
+            StopIterationSubclass('spam'),
+            StopAsyncIterationSubclass('spam')
+        ):
+            with self.subTest(type=type(stop_exc)):
+                try:
+                    async with f():
+                        raise stop_exc
+                except type(stop_exc) as e:
+                    self.assertIs(e, stop_exc)
+                    frames = traceback.extract_tb(e.__traceback__)
+                else:
+                    self.fail(f'{stop_exc} was suppressed')
+
+                self.assertEqual(len(frames), 1)
+                self.assertEqual(frames[0].name, 'test_contextmanager_traceback')
+                self.assertEqual(frames[0].line, 'raise stop_exc')
+
     @_async_test
     async def test_contextmanager_no_reraise(self):
         @asynccontextmanager
index 58b29c19400e873b81b51e98c7b933665ad08a99..dc47e894b7647f005a1cbdbbb9e24e7257da01a0 100644 (file)
--- a/Misc/ACKS
+++ b/Misc/ACKS
@@ -641,6 +641,7 @@ Hans de Graaff
 Tim Graham
 Kim Gräsman
 Alex Grönholm
+Thomas Grainger
 Nathaniel Gray
 Eddy De Greef
 Duane Griffin
diff --git a/Misc/NEWS.d/next/Library/2022-08-11-10-02-19.gh-issue-95882.FsUr72.rst b/Misc/NEWS.d/next/Library/2022-08-11-10-02-19.gh-issue-95882.FsUr72.rst
new file mode 100644 (file)
index 0000000..9cdb237
--- /dev/null
@@ -0,0 +1 @@
+Fix a 3.11 regression in :func:`~contextlib.asynccontextmanager`, which caused it to propagate exceptions with incorrect tracebacks and fix a 3.11 regression in  :func:`~contextlib.contextmanager`, which caused it to propagate exceptions with incorrect tracebacks for :exc:`StopIteration`.