mirror of https://github.com/python/cpython.git
gh-95882: fix regression in the traceback of exceptions propagated from inside a contextlib context manager (GH-95883)
(cherry picked from commit b3722ca058
)
Co-authored-by: Thomas Grainger <tagrain@gmail.com>
This commit is contained in:
parent
b99ac1dbc0
commit
861cdefc1b
|
@ -173,7 +173,7 @@ def __exit__(self, typ, value, traceback):
|
||||||
isinstance(value, StopIteration)
|
isinstance(value, StopIteration)
|
||||||
and exc.__cause__ is value
|
and exc.__cause__ is value
|
||||||
):
|
):
|
||||||
exc.__traceback__ = traceback
|
value.__traceback__ = traceback
|
||||||
return False
|
return False
|
||||||
raise
|
raise
|
||||||
except BaseException as exc:
|
except BaseException as exc:
|
||||||
|
@ -228,6 +228,7 @@ async def __aexit__(self, typ, value, traceback):
|
||||||
except RuntimeError as exc:
|
except RuntimeError as exc:
|
||||||
# Don't re-raise the passed in exception. (issue27122)
|
# Don't re-raise the passed in exception. (issue27122)
|
||||||
if exc is value:
|
if exc is value:
|
||||||
|
exc.__traceback__ = traceback
|
||||||
return False
|
return False
|
||||||
# Avoid suppressing if a Stop(Async)Iteration exception
|
# Avoid suppressing if a Stop(Async)Iteration exception
|
||||||
# was passed to athrow() and later wrapped into a RuntimeError
|
# was passed to athrow() and later wrapped into a RuntimeError
|
||||||
|
@ -239,6 +240,7 @@ async def __aexit__(self, typ, value, traceback):
|
||||||
isinstance(value, (StopIteration, StopAsyncIteration))
|
isinstance(value, (StopIteration, StopAsyncIteration))
|
||||||
and exc.__cause__ is value
|
and exc.__cause__ is value
|
||||||
):
|
):
|
||||||
|
value.__traceback__ = traceback
|
||||||
return False
|
return False
|
||||||
raise
|
raise
|
||||||
except BaseException as exc:
|
except BaseException as exc:
|
||||||
|
@ -250,6 +252,7 @@ async def __aexit__(self, typ, value, traceback):
|
||||||
# and the __exit__() protocol.
|
# and the __exit__() protocol.
|
||||||
if exc is not value:
|
if exc is not value:
|
||||||
raise
|
raise
|
||||||
|
exc.__traceback__ = traceback
|
||||||
return False
|
return False
|
||||||
raise RuntimeError("generator didn't stop after athrow()")
|
raise RuntimeError("generator didn't stop after athrow()")
|
||||||
|
|
||||||
|
|
|
@ -104,15 +104,39 @@ def f():
|
||||||
self.assertEqual(frames[0].line, '1/0')
|
self.assertEqual(frames[0].line, '1/0')
|
||||||
|
|
||||||
# Repeat with RuntimeError (which goes through a different code path)
|
# Repeat with RuntimeError (which goes through a different code path)
|
||||||
|
class RuntimeErrorSubclass(RuntimeError):
|
||||||
|
pass
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with f():
|
with f():
|
||||||
raise NotImplementedError(42)
|
raise RuntimeErrorSubclass(42)
|
||||||
except NotImplementedError as e:
|
except RuntimeErrorSubclass as e:
|
||||||
frames = traceback.extract_tb(e.__traceback__)
|
frames = traceback.extract_tb(e.__traceback__)
|
||||||
|
|
||||||
self.assertEqual(len(frames), 1)
|
self.assertEqual(len(frames), 1)
|
||||||
self.assertEqual(frames[0].name, 'test_contextmanager_traceback')
|
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):
|
def test_contextmanager_no_reraise(self):
|
||||||
@contextmanager
|
@contextmanager
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
import functools
|
import functools
|
||||||
from test import support
|
from test import support
|
||||||
import unittest
|
import unittest
|
||||||
|
import traceback
|
||||||
|
|
||||||
from test.test_contextlib import TestBaseExitStack
|
from test.test_contextlib import TestBaseExitStack
|
||||||
|
|
||||||
|
@ -125,6 +126,62 @@ async def woohoo():
|
||||||
raise ZeroDivisionError()
|
raise ZeroDivisionError()
|
||||||
self.assertEqual(state, [1, 42, 999])
|
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_test
|
||||||
async def test_contextmanager_no_reraise(self):
|
async def test_contextmanager_no_reraise(self):
|
||||||
@asynccontextmanager
|
@asynccontextmanager
|
||||||
|
|
|
@ -641,6 +641,7 @@ Hans de Graaff
|
||||||
Tim Graham
|
Tim Graham
|
||||||
Kim Gräsman
|
Kim Gräsman
|
||||||
Alex Grönholm
|
Alex Grönholm
|
||||||
|
Thomas Grainger
|
||||||
Nathaniel Gray
|
Nathaniel Gray
|
||||||
Eddy De Greef
|
Eddy De Greef
|
||||||
Duane Griffin
|
Duane Griffin
|
||||||
|
|
|
@ -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`.
|
Loading…
Reference in New Issue