@@ -946,3 +946,220 @@ test_async_with_mixed_return()
946946
947947[file asyncio/__init__.pyi]
948948def run(x: object) -> object: ...
949+
950+ [case testAsyncTryExceptFinallyAwait]
951+ # Comprehensive test for bug where exceptions are swallowed in async functions
952+ # when the finally block contains an await statement
953+
954+ import asyncio
955+ from testutil import assertRaises
956+
957+ class TestError(Exception):
958+ pass
959+
960+ # Test 0: Simplest case - just try/finally with raise and await
961+ async def simple_try_finally_await() -> None:
962+ try: # type: ignore[mypyc-try-finally-await]
963+ raise ValueError("simple error")
964+ finally:
965+ await asyncio.sleep(0)
966+
967+ # Test 1: Raise inside try, catch in except, don't re-raise
968+ async def async_try_except_no_reraise() -> int:
969+ try: # type: ignore[mypyc-try-finally-await]
970+ raise ValueError("test error")
971+ return 1 # Never reached
972+ except ValueError:
973+ return 2 # Should return this
974+ finally:
975+ await asyncio.sleep(0)
976+ return 3 # Should not reach this
977+
978+ # Test 2: Raise inside try, catch in except, re-raise
979+ async def async_try_except_reraise() -> int:
980+ try: # type: ignore[mypyc-try-finally-await]
981+ raise ValueError("test error")
982+ return 1 # Never reached
983+ except ValueError:
984+ raise # Re-raise the exception
985+ finally:
986+ await asyncio.sleep(0)
987+ return 2 # Should not reach this
988+
989+ # Test 3: Raise inside try, catch in except, raise different error
990+ async def async_try_except_raise_different() -> int:
991+ try: # type: ignore[mypyc-try-finally-await]
992+ raise ValueError("original error")
993+ return 1 # Never reached
994+ except ValueError:
995+ raise RuntimeError("different error")
996+ finally:
997+ await asyncio.sleep(0)
998+ return 2 # Should not reach this
999+
1000+ # Test 4: Another try/except block inside finally
1001+ async def async_try_except_inside_finally() -> int:
1002+ try: # type: ignore[mypyc-try-finally-await]
1003+ raise ValueError("outer error")
1004+ return 1 # Never reached
1005+ finally:
1006+ await asyncio.sleep(0)
1007+ try:
1008+ raise RuntimeError("inner error")
1009+ except RuntimeError:
1010+ pass # Catch inner error
1011+ return 2 # What happens after finally with inner exception handled?
1012+
1013+ # Test 5: Another try/finally block inside finally
1014+ async def async_try_finally_inside_finally() -> int:
1015+ try: # type: ignore[mypyc-try-finally-await]
1016+ raise ValueError("outer error")
1017+ return 1 # Never reached
1018+ finally:
1019+ await asyncio.sleep(0)
1020+ try: # type: ignore[mypyc-try-finally-await]
1021+ raise RuntimeError("inner error")
1022+ finally:
1023+ await asyncio.sleep(0)
1024+ return 2 # Should not reach this
1025+
1026+ # Control case: No await in finally - should work correctly
1027+ async def async_exception_no_await_in_finally() -> None:
1028+ """Control case: This works correctly - exception propagates"""
1029+ try:
1030+ raise TestError("This exception will propagate!")
1031+ finally:
1032+ pass # No await here
1033+
1034+ # Test function with no exception to check normal flow
1035+ async def async_no_exception_with_await_in_finally() -> int:
1036+ try: # type: ignore[mypyc-try-finally-await]
1037+ return 1 # Normal return
1038+ finally:
1039+ await asyncio.sleep(0)
1040+ return 2 # Should not reach this
1041+
1042+ def test_async_try_except_finally_await() -> None:
1043+ # Test 0: Simplest case - just try/finally with exception
1044+ # Expected: ValueError propagates
1045+ with assertRaises(ValueError):
1046+ asyncio.run(simple_try_finally_await())
1047+
1048+ # Test 1: Exception caught, not re-raised
1049+ # Expected: return 2 (from except block)
1050+ result = asyncio.run(async_try_except_no_reraise())
1051+ assert result == 2, f"Expected 2, got {result}"
1052+
1053+ # Test 2: Exception caught and re-raised
1054+ # Expected: ValueError propagates
1055+ with assertRaises(ValueError):
1056+ asyncio.run(async_try_except_reraise())
1057+
1058+ # Test 3: Exception caught, different exception raised
1059+ # Expected: RuntimeError propagates
1060+ with assertRaises(RuntimeError):
1061+ asyncio.run(async_try_except_raise_different())
1062+
1063+ # Test 4: Try/except inside finally
1064+ # Expected: ValueError propagates (outer exception)
1065+ with assertRaises(ValueError):
1066+ asyncio.run(async_try_except_inside_finally())
1067+
1068+ # Test 5: Try/finally inside finally
1069+ # Expected: RuntimeError propagates (inner error)
1070+ with assertRaises(RuntimeError):
1071+ asyncio.run(async_try_finally_inside_finally())
1072+
1073+ # Control case: No await in finally (should work correctly)
1074+ with assertRaises(TestError):
1075+ asyncio.run(async_exception_no_await_in_finally())
1076+
1077+ # Test normal flow (no exception)
1078+ # Expected: return 1
1079+ result = asyncio.run(async_no_exception_with_await_in_finally())
1080+ assert result == 1, f"Expected 1, got {result}"
1081+
1082+ [file asyncio/__init__.pyi]
1083+ async def sleep(t: float) -> None: ...
1084+ def run(x: object) -> object: ...
1085+
1086+ [case testAsyncContextManagerExceptionHandling]
1087+ # Test async context managers with exceptions
1088+ # Async context managers use try/finally internally but seem to work
1089+ # correctly
1090+
1091+ import asyncio
1092+ from testutil import assertRaises
1093+
1094+ # Test 1: Basic async context manager that doesn't suppress exceptions
1095+ class AsyncContextManager:
1096+ async def __aenter__(self) -> 'AsyncContextManager':
1097+ return self
1098+
1099+ async def __aexit__(self, exc_type: type[BaseException] | None,
1100+ exc_val: BaseException | None,
1101+ exc_tb: object) -> None:
1102+ # This await in __aexit__ is like await in finally
1103+ await asyncio.sleep(0)
1104+ # Don't suppress the exception (return None/False)
1105+
1106+ async def func_with_async_context_manager() -> str:
1107+ async with AsyncContextManager():
1108+ raise ValueError("Exception inside async with")
1109+ return "should not reach" # Never reached
1110+ return "should not reach either" # Never reached
1111+
1112+ async def test_basic_exception() -> str:
1113+ try:
1114+ await func_with_async_context_manager()
1115+ return "func_a returned normally - bug!"
1116+ except ValueError:
1117+ return "caught ValueError - correct!"
1118+ except Exception as e:
1119+ return f"caught different exception: {type(e).__name__}"
1120+
1121+ # Test 2: Async context manager that raises a different exception in __aexit__
1122+ class AsyncContextManagerRaisesInExit:
1123+ async def __aenter__(self) -> 'AsyncContextManagerRaisesInExit':
1124+ return self
1125+
1126+ async def __aexit__(self, exc_type: type[BaseException] | None,
1127+ exc_val: BaseException | None,
1128+ exc_tb: object) -> None:
1129+ # This await in __aexit__ is like await in finally
1130+ await asyncio.sleep(0)
1131+ # Raise a different exception - this should replace the original exception
1132+ raise RuntimeError("Exception in __aexit__")
1133+
1134+ async def func_with_raising_context_manager() -> str:
1135+ async with AsyncContextManagerRaisesInExit():
1136+ raise ValueError("Original exception")
1137+ return "should not reach" # Never reached
1138+ return "should not reach either" # Never reached
1139+
1140+ async def test_exception_in_aexit() -> str:
1141+ try:
1142+ await func_with_raising_context_manager()
1143+ return "func returned normally - unexpected!"
1144+ except RuntimeError:
1145+ return "caught RuntimeError - correct!"
1146+ except ValueError:
1147+ return "caught ValueError - original exception not replaced!"
1148+ except Exception as e:
1149+ return f"caught different exception: {type(e).__name__}"
1150+
1151+ def test_async_context_manager_exception_handling() -> None:
1152+ # Test 1: Basic exception propagation
1153+ result = asyncio.run(test_basic_exception())
1154+ # Expected: "caught ValueError - correct!"
1155+ assert result == "caught ValueError - correct!", f"Expected exception to propagate, got: {result}"
1156+
1157+ # Test 2: Exception raised in __aexit__ replaces original exception
1158+ result = asyncio.run(test_exception_in_aexit())
1159+ # Expected: "caught RuntimeError - correct!"
1160+ # (The RuntimeError from __aexit__ should replace the ValueError)
1161+ assert result == "caught RuntimeError - correct!", f"Expected RuntimeError from __aexit__, got: {result}"
1162+
1163+ [file asyncio/__init__.pyi]
1164+ async def sleep(t: float) -> None: ...
1165+ def run(x: object) -> object: ...
0 commit comments