From 01ee553b22e477a7738f01aa0e15e419f23786b5 Mon Sep 17 00:00:00 2001 From: Irit Katriel Date: Thu, 5 May 2022 16:30:39 +0100 Subject: [PATCH 1/3] gh-92118: Add test for traceback when exception is modified by ExitStack.__exit__ --- Lib/test/test_contextlib.py | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/Lib/test/test_contextlib.py b/Lib/test/test_contextlib.py index bfe81173ddc0b4..fdadbe6b1cba9e 100644 --- a/Lib/test/test_contextlib.py +++ b/Lib/test/test_contextlib.py @@ -748,6 +748,39 @@ def test_exit_suppress(self): stack.push(lambda *exc: True) 1/0 + def test_exit_exception_traceback(self): + # This test captures the current behavior of ExitStack so that we know + # if we ever unintendedly change it. It is not a statement of what the + # desired behavior is (for instance, we may want to remove some of the + # internal contextlib frames). + + def raise_exc(exc): + raise exc + + try: + with self.exit_stack() as stack: + stack.callback(raise_exc, ValueError) + 1/0 + except ValueError as e: + exc = e + + self.assertIsInstance(exc, ValueError) + ve_frames = traceback.extract_tb(exc.__traceback__) + self.assertEqual(len(ve_frames), 5) + self.assertEqual( + [(f.name, f.line) for f in ve_frames], + [('test_exit_exception_traceback', 'with self.exit_stack() as stack:'), + ('__exit__', 'raise exc_details[1]'), + ('__exit__', 'if cb(*exc_details):'), + ('_exit_wrapper', 'callback(*args, **kwds)'), + ('raise_exc', 'raise exc')]) + + self.assertIsInstance(exc.__context__, ZeroDivisionError) + zde_frames = traceback.extract_tb(exc.__context__.__traceback__) + self.assertEqual(len(zde_frames), 1) + self.assertEqual([(f.name, f.line) for f in zde_frames], + [('test_exit_exception_traceback', '1/0')]) + def test_exit_exception_chaining_reference(self): # Sanity check to make sure that ExitStack chaining matches # actual nested with statements From f77f1cd9996de73905c17dfbe1e48fa3641fbdc1 Mon Sep 17 00:00:00 2001 From: Irit Katriel Date: Thu, 5 May 2022 16:55:27 +0100 Subject: [PATCH 2/3] remove redundant assertions --- Lib/test/test_contextlib.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/Lib/test/test_contextlib.py b/Lib/test/test_contextlib.py index fdadbe6b1cba9e..967b74a7c3ed63 100644 --- a/Lib/test/test_contextlib.py +++ b/Lib/test/test_contextlib.py @@ -766,7 +766,6 @@ def raise_exc(exc): self.assertIsInstance(exc, ValueError) ve_frames = traceback.extract_tb(exc.__traceback__) - self.assertEqual(len(ve_frames), 5) self.assertEqual( [(f.name, f.line) for f in ve_frames], [('test_exit_exception_traceback', 'with self.exit_stack() as stack:'), @@ -777,7 +776,6 @@ def raise_exc(exc): self.assertIsInstance(exc.__context__, ZeroDivisionError) zde_frames = traceback.extract_tb(exc.__context__.__traceback__) - self.assertEqual(len(zde_frames), 1) self.assertEqual([(f.name, f.line) for f in zde_frames], [('test_exit_exception_traceback', '1/0')]) From 235044a884fdc508d6102c0286d85181d85976ba Mon Sep 17 00:00:00 2001 From: Irit Katriel Date: Thu, 5 May 2022 17:13:26 +0100 Subject: [PATCH 3/3] fix test_contextlib_async --- Lib/test/test_contextlib.py | 17 +++++++++++------ Lib/test/test_contextlib_async.py | 7 +++++++ 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/Lib/test/test_contextlib.py b/Lib/test/test_contextlib.py index 967b74a7c3ed63..31f5c74572b630 100644 --- a/Lib/test/test_contextlib.py +++ b/Lib/test/test_contextlib.py @@ -766,13 +766,14 @@ def raise_exc(exc): self.assertIsInstance(exc, ValueError) ve_frames = traceback.extract_tb(exc.__traceback__) + expected = \ + [('test_exit_exception_traceback', 'with self.exit_stack() as stack:')] + \ + self.callback_error_internal_frames + \ + [('_exit_wrapper', 'callback(*args, **kwds)'), + ('raise_exc', 'raise exc')] + self.assertEqual( - [(f.name, f.line) for f in ve_frames], - [('test_exit_exception_traceback', 'with self.exit_stack() as stack:'), - ('__exit__', 'raise exc_details[1]'), - ('__exit__', 'if cb(*exc_details):'), - ('_exit_wrapper', 'callback(*args, **kwds)'), - ('raise_exc', 'raise exc')]) + [(f.name, f.line) for f in ve_frames], expected) self.assertIsInstance(exc.__context__, ZeroDivisionError) zde_frames = traceback.extract_tb(exc.__context__.__traceback__) @@ -1048,6 +1049,10 @@ def first(): class TestExitStack(TestBaseExitStack, unittest.TestCase): exit_stack = ExitStack + callback_error_internal_frames = [ + ('__exit__', 'raise exc_details[1]'), + ('__exit__', 'if cb(*exc_details):'), + ] class TestRedirectStream: diff --git a/Lib/test/test_contextlib_async.py b/Lib/test/test_contextlib_async.py index 462e05cc79aec8..76bd81c7d02ccd 100644 --- a/Lib/test/test_contextlib_async.py +++ b/Lib/test/test_contextlib_async.py @@ -487,6 +487,13 @@ def __exit__(self, *exc_details): return self.run_coroutine(self.__aexit__(*exc_details)) exit_stack = SyncAsyncExitStack + callback_error_internal_frames = [ + ('__exit__', 'return self.run_coroutine(self.__aexit__(*exc_details))'), + ('run_coroutine', 'raise exc'), + ('run_coroutine', 'raise exc'), + ('__aexit__', 'raise exc_details[1]'), + ('__aexit__', 'cb_suppress = cb(*exc_details)'), + ] def setUp(self): self.loop = asyncio.new_event_loop()