@@ -643,3 +643,220 @@ def test_async_def_contains_two_nested_functions() -> None:
643
643
644
644
[file asyncio/__init__.pyi]
645
645
def 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