Skip to content

Commit 62547dc

Browse files
committed
One way to write a test for async cm signal safety
1 parent 9b8891a commit 62547dc

1 file changed

Lines changed: 22 additions & 13 deletions

File tree

‎Lib/test/test_with_signal_safety.py‎

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -85,15 +85,7 @@ def traced_function():
8585
self.fail(f"Exception wasn't raised @{target_offset}")
8686

8787

88-
def _test_asynchronous_cm(self):
89-
# NOTE: this can't work, since asyncio is written in Python, and hence
90-
# will always process pending calls at some point during the evaluation
91-
# of __aenter__ and __aexit__
92-
#
93-
# So to handle that case, we need to some way to tell the event loop
94-
# to convert pending call processing into calls to
95-
# asyncio.get_event_loop().call_soon() instead of processing them
96-
# immediately
88+
def test_asynchronous_cm(self):
9789
class AsyncTrackingCM():
9890
def __init__(self):
9991
self.enter_without_exit = None
@@ -106,17 +98,34 @@ async def traced_coroutine():
10698
async with tracking_cm:
10799
1 + 1
108100
return
101+
async def cushion():
102+
await traced_coroutine()
103+
# In case the last injected call spills over, we want the
104+
# exception to be raised here instead of deep in the bowels of
105+
# asyncio (which will probably lock up or something).
106+
while True:
107+
pass
109108
target_offset = -1
110109
max_offset = len(traced_coroutine.__code__.co_code) - 2
111110
loop = asyncio.get_event_loop()
112111
while target_offset < max_offset:
113112
target_offset += 1
114113
raise_after_offset(traced_coroutine, target_offset)
115114
try:
116-
loop.run_until_complete(traced_coroutine())
117-
except InjectedException:
118-
# key invariant: if we entered the CM, we exited it
119-
self.assertFalse(tracking_cm.enter_without_exit)
115+
loop.run_until_complete(cushion())
116+
except InjectedException as exc:
117+
# key invariant: if we entered the CM, we exited it. Meaning:
118+
# either __aexit__ ran fully, or else the exception was raised
119+
# *inside* __aexit__.
120+
tb = exc.__traceback__
121+
while tb is not None:
122+
if tb.tb_frame.f_code is AsyncTrackingCM.__aexit__.__code__:
123+
# This was raised inside __aexit__
124+
break
125+
tb = tb.tb_next
126+
else:
127+
# It wasn't raised inside __aexit__
128+
self.assertFalse(tracking_cm.enter_without_exit)
120129
else:
121130
self.fail(f"Exception wasn't raised @{target_offset}")
122131

0 commit comments

Comments
 (0)