@@ -643,3 +643,220 @@ def test_async_def_contains_two_nested_functions() -> None:
643643
644644[file asyncio/__init__.pyi]
645645def run(x: object) -> object: ...
646+
647+ [case testAsyncTryExceptFinallyAwait]
648+ # Comprehensive test for bug where exceptions are swallowed in async functions
649+ # when the finally block contains an await statement
650+
651+ import asyncio
652+ from testutil import assertRaises
653+
654+ class TestError(Exception):
655+ pass
656+
657+ # Test 0: Simplest case - just try/finally with raise and await
658+ async def simple_try_finally_await() -> None:
659+ try: # type: ignore[mypyc-try-finally-await]
660+ raise ValueError("simple error")
661+ finally:
662+ await asyncio.sleep(0)
663+
664+ # Test 1: Raise inside try, catch in except, don't re-raise
665+ async def async_try_except_no_reraise() -> int:
666+ try: # type: ignore[mypyc-try-finally-await]
667+ raise ValueError("test error")
668+ return 1 # Never reached
669+ except ValueError:
670+ return 2 # Should return this
671+ finally:
672+ await asyncio.sleep(0)
673+ return 3 # Should not reach this
674+
675+ # Test 2: Raise inside try, catch in except, re-raise
676+ async def async_try_except_reraise() -> int:
677+ try: # type: ignore[mypyc-try-finally-await]
678+ raise ValueError("test error")
679+ return 1 # Never reached
680+ except ValueError:
681+ raise # Re-raise the exception
682+ finally:
683+ await asyncio.sleep(0)
684+ return 2 # Should not reach this
685+
686+ # Test 3: Raise inside try, catch in except, raise different error
687+ async def async_try_except_raise_different() -> int:
688+ try: # type: ignore[mypyc-try-finally-await]
689+ raise ValueError("original error")
690+ return 1 # Never reached
691+ except ValueError:
692+ raise RuntimeError("different error")
693+ finally:
694+ await asyncio.sleep(0)
695+ return 2 # Should not reach this
696+
697+ # Test 4: Another try/except block inside finally
698+ async def async_try_except_inside_finally() -> int:
699+ try: # type: ignore[mypyc-try-finally-await]
700+ raise ValueError("outer error")
701+ return 1 # Never reached
702+ finally:
703+ await asyncio.sleep(0)
704+ try:
705+ raise RuntimeError("inner error")
706+ except RuntimeError:
707+ pass # Catch inner error
708+ return 2 # What happens after finally with inner exception handled?
709+
710+ # Test 5: Another try/finally block inside finally
711+ async def async_try_finally_inside_finally() -> int:
712+ try: # type: ignore[mypyc-try-finally-await]
713+ raise ValueError("outer error")
714+ return 1 # Never reached
715+ finally:
716+ await asyncio.sleep(0)
717+ try: # type: ignore[mypyc-try-finally-await]
718+ raise RuntimeError("inner error")
719+ finally:
720+ await asyncio.sleep(0)
721+ return 2 # Should not reach this
722+
723+ # Control case: No await in finally - should work correctly
724+ async def async_exception_no_await_in_finally() -> None:
725+ """Control case: This works correctly - exception propagates"""
726+ try:
727+ raise TestError("This exception will propagate!")
728+ finally:
729+ pass # No await here
730+
731+ # Test function with no exception to check normal flow
732+ async def async_no_exception_with_await_in_finally() -> int:
733+ try: # type: ignore[mypyc-try-finally-await]
734+ return 1 # Normal return
735+ finally:
736+ await asyncio.sleep(0)
737+ return 2 # Should not reach this
738+
739+ def test_async_try_except_finally_await() -> None:
740+ # Test 0: Simplest case - just try/finally with exception
741+ # Expected: ValueError propagates
742+ with assertRaises(ValueError):
743+ asyncio.run(simple_try_finally_await())
744+
745+ # Test 1: Exception caught, not re-raised
746+ # Expected: return 2 (from except block)
747+ result = asyncio.run(async_try_except_no_reraise())
748+ assert result == 2, f"Expected 2, got {result}"
749+
750+ # Test 2: Exception caught and re-raised
751+ # Expected: ValueError propagates
752+ with assertRaises(ValueError):
753+ asyncio.run(async_try_except_reraise())
754+
755+ # Test 3: Exception caught, different exception raised
756+ # Expected: RuntimeError propagates
757+ with assertRaises(RuntimeError):
758+ asyncio.run(async_try_except_raise_different())
759+
760+ # Test 4: Try/except inside finally
761+ # Expected: ValueError propagates (outer exception)
762+ with assertRaises(ValueError):
763+ asyncio.run(async_try_except_inside_finally())
764+
765+ # Test 5: Try/finally inside finally
766+ # Expected: RuntimeError propagates (inner error)
767+ with assertRaises(RuntimeError):
768+ asyncio.run(async_try_finally_inside_finally())
769+
770+ # Control case: No await in finally (should work correctly)
771+ with assertRaises(TestError):
772+ asyncio.run(async_exception_no_await_in_finally())
773+
774+ # Test normal flow (no exception)
775+ # Expected: return 1
776+ result = asyncio.run(async_no_exception_with_await_in_finally())
777+ assert result == 1, f"Expected 1, got {result}"
778+
779+ [file asyncio/__init__.pyi]
780+ async def sleep(t: float) -> None: ...
781+ def run(x: object) -> object: ...
782+
783+ [case testAsyncContextManagerExceptionHandling]
784+ # Test async context managers with exceptions
785+ # Async context managers use try/finally internally but seem to work
786+ # correctly
787+
788+ import asyncio
789+ from testutil import assertRaises
790+
791+ # Test 1: Basic async context manager that doesn't suppress exceptions
792+ class AsyncContextManager:
793+ async def __aenter__(self) -> 'AsyncContextManager':
794+ return self
795+
796+ async def __aexit__(self, exc_type: type[BaseException] | None,
797+ exc_val: BaseException | None,
798+ exc_tb: object) -> None:
799+ # This await in __aexit__ is like await in finally
800+ await asyncio.sleep(0)
801+ # Don't suppress the exception (return None/False)
802+
803+ async def func_with_async_context_manager() -> str:
804+ async with AsyncContextManager():
805+ raise ValueError("Exception inside async with")
806+ return "should not reach" # Never reached
807+ return "should not reach either" # Never reached
808+
809+ async def test_basic_exception() -> str:
810+ try:
811+ await func_with_async_context_manager()
812+ return "func_a returned normally - bug!"
813+ except ValueError:
814+ return "caught ValueError - correct!"
815+ except Exception as e:
816+ return f"caught different exception: {type(e).__name__}"
817+
818+ # Test 2: Async context manager that raises a different exception in __aexit__
819+ class AsyncContextManagerRaisesInExit:
820+ async def __aenter__(self) -> 'AsyncContextManagerRaisesInExit':
821+ return self
822+
823+ async def __aexit__(self, exc_type: type[BaseException] | None,
824+ exc_val: BaseException | None,
825+ exc_tb: object) -> None:
826+ # This await in __aexit__ is like await in finally
827+ await asyncio.sleep(0)
828+ # Raise a different exception - this should replace the original exception
829+ raise RuntimeError("Exception in __aexit__")
830+
831+ async def func_with_raising_context_manager() -> str:
832+ async with AsyncContextManagerRaisesInExit():
833+ raise ValueError("Original exception")
834+ return "should not reach" # Never reached
835+ return "should not reach either" # Never reached
836+
837+ async def test_exception_in_aexit() -> str:
838+ try:
839+ await func_with_raising_context_manager()
840+ return "func returned normally - unexpected!"
841+ except RuntimeError:
842+ return "caught RuntimeError - correct!"
843+ except ValueError:
844+ return "caught ValueError - original exception not replaced!"
845+ except Exception as e:
846+ return f"caught different exception: {type(e).__name__}"
847+
848+ def test_async_context_manager_exception_handling() -> None:
849+ # Test 1: Basic exception propagation
850+ result = asyncio.run(test_basic_exception())
851+ # Expected: "caught ValueError - correct!"
852+ assert result == "caught ValueError - correct!", f"Expected exception to propagate, got: {result}"
853+
854+ # Test 2: Exception raised in __aexit__ replaces original exception
855+ result = asyncio.run(test_exception_in_aexit())
856+ # Expected: "caught RuntimeError - correct!"
857+ # (The RuntimeError from __aexit__ should replace the ValueError)
858+ assert result == "caught RuntimeError - correct!", f"Expected RuntimeError from __aexit__, got: {result}"
859+
860+ [file asyncio/__init__.pyi]
861+ async def sleep(t: float) -> None: ...
862+ def run(x: object) -> object: ...
0 commit comments