@@ -946,3 +946,220 @@ test_async_with_mixed_return()
946
946
947
947
[file asyncio/__init__.pyi]
948
948
def 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