Skip to content

Commit a82d993

Browse files
committed
Add tests for PEP 492 async/await
23 tests covering async def, await, async with, async for, coroutine properties, __await__ protocol, custom awaitables, break/continue/else, nested loops, and combined patterns.
1 parent 09f335b commit a82d993

1 file changed

Lines changed: 318 additions & 0 deletions

File tree

tests/suite/test_async.py

Lines changed: 318 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,318 @@
1+
# Licensed to the .NET Foundation under one or more agreements.
2+
# The .NET Foundation licenses this file to you under the Apache 2.0 License.
3+
# See the LICENSE file in the project root for more information.
4+
5+
"""Tests for PEP 492: async/await support."""
6+
7+
import unittest
8+
9+
from iptest import run_test
10+
11+
12+
def run_coro(coro):
13+
"""Run a coroutine to completion and return its result."""
14+
try:
15+
coro.send(None)
16+
raise AssertionError("Coroutine did not raise StopIteration")
17+
except StopIteration as e:
18+
return e.value
19+
20+
21+
class AsyncIter:
22+
"""Async iterator for testing async for."""
23+
def __init__(self, items):
24+
self.items = list(items)
25+
self.index = 0
26+
27+
def __aiter__(self):
28+
return self
29+
30+
async def __anext__(self):
31+
if self.index >= len(self.items):
32+
raise StopAsyncIteration
33+
val = self.items[self.index]
34+
self.index += 1
35+
return val
36+
37+
38+
class AsyncDefTest(unittest.TestCase):
39+
"""Tests for basic async def and coroutine type."""
40+
41+
def test_basic_return(self):
42+
async def foo():
43+
return 42
44+
self.assertEqual(run_coro(foo()), 42)
45+
46+
def test_coroutine_type(self):
47+
async def foo():
48+
return 1
49+
coro = foo()
50+
self.assertEqual(type(coro).__name__, 'coroutine')
51+
try:
52+
coro.send(None)
53+
except StopIteration:
54+
pass
55+
56+
def test_coroutine_properties(self):
57+
async def named_coro():
58+
return 1
59+
coro = named_coro()
60+
self.assertTrue(hasattr(coro, 'cr_code'))
61+
self.assertTrue(hasattr(coro, 'cr_running'))
62+
self.assertTrue(hasattr(coro, 'cr_frame'))
63+
self.assertTrue(hasattr(coro, '__name__'))
64+
self.assertTrue(hasattr(coro, '__qualname__'))
65+
self.assertEqual(coro.__name__, 'named_coro')
66+
try:
67+
coro.send(None)
68+
except StopIteration:
69+
pass
70+
71+
def test_async_def_no_await(self):
72+
async def foo():
73+
x = 1
74+
y = 2
75+
return x + y
76+
self.assertEqual(run_coro(foo()), 3)
77+
78+
def test_coroutine_close(self):
79+
async def foo():
80+
return 1
81+
coro = foo()
82+
coro.close() # should not raise
83+
84+
def test_coroutine_throw(self):
85+
async def foo():
86+
return 42
87+
coro = foo()
88+
with self.assertRaises(ValueError) as cm:
89+
coro.throw(ValueError('boom'))
90+
self.assertEqual(str(cm.exception), 'boom')
91+
92+
93+
class AwaitTest(unittest.TestCase):
94+
"""Tests for await expression."""
95+
96+
def test_basic_await(self):
97+
async def inner():
98+
return 10
99+
100+
async def outer():
101+
val = await inner()
102+
return val + 5
103+
104+
self.assertEqual(run_coro(outer()), 15)
105+
106+
def test_multiple_awaits(self):
107+
async def val(x):
108+
return x
109+
110+
async def test():
111+
return await val(1) + await val(2) + await val(3)
112+
113+
self.assertEqual(run_coro(test()), 6)
114+
115+
def test_await_protocol(self):
116+
async def foo():
117+
return 99
118+
coro = foo()
119+
wrapper = coro.__await__()
120+
self.assertEqual(type(wrapper).__name__, 'coroutine_wrapper')
121+
try:
122+
wrapper.__next__()
123+
except StopIteration as e:
124+
self.assertEqual(e.value, 99)
125+
126+
def test_custom_awaitable(self):
127+
class MyAwaitable:
128+
def __await__(self):
129+
return iter([])
130+
131+
async def test():
132+
await MyAwaitable()
133+
return 'done'
134+
135+
self.assertEqual(run_coro(test()), 'done')
136+
137+
138+
class AsyncWithTest(unittest.TestCase):
139+
"""Tests for async with statement."""
140+
141+
def test_basic_async_with(self):
142+
class CM:
143+
def __init__(self):
144+
self.entered = False
145+
self.exited = False
146+
async def __aenter__(self):
147+
self.entered = True
148+
return self
149+
async def __aexit__(self, *args):
150+
self.exited = True
151+
152+
async def test():
153+
cm = CM()
154+
async with cm:
155+
self.assertTrue(cm.entered)
156+
self.assertTrue(cm.exited)
157+
return 'ok'
158+
159+
self.assertEqual(run_coro(test()), 'ok')
160+
161+
def test_async_with_as(self):
162+
class CM:
163+
async def __aenter__(self):
164+
return 'value'
165+
async def __aexit__(self, *args):
166+
pass
167+
168+
async def test():
169+
async with CM() as v:
170+
return v
171+
172+
self.assertEqual(run_coro(test()), 'value')
173+
174+
def test_async_with_order(self):
175+
class CM:
176+
def __init__(self):
177+
self.log = []
178+
async def __aenter__(self):
179+
self.log.append('enter')
180+
return self
181+
async def __aexit__(self, *args):
182+
self.log.append('exit')
183+
184+
async def test():
185+
cm = CM()
186+
async with cm:
187+
cm.log.append('body')
188+
return cm.log
189+
190+
self.assertEqual(run_coro(test()), ['enter', 'body', 'exit'])
191+
192+
def test_async_with_no_as(self):
193+
class CM:
194+
async def __aenter__(self):
195+
return 'unused'
196+
async def __aexit__(self, *args):
197+
pass
198+
199+
async def test():
200+
async with CM():
201+
return 'ok'
202+
203+
self.assertEqual(run_coro(test()), 'ok')
204+
205+
206+
class AsyncForTest(unittest.TestCase):
207+
"""Tests for async for statement."""
208+
209+
def test_basic_async_for(self):
210+
async def test():
211+
result = []
212+
async for x in AsyncIter([1, 2, 3]):
213+
result.append(x)
214+
return result
215+
216+
self.assertEqual(run_coro(test()), [1, 2, 3])
217+
218+
def test_async_for_empty(self):
219+
async def test():
220+
result = []
221+
async for x in AsyncIter([]):
222+
result.append(x)
223+
return result
224+
225+
self.assertEqual(run_coro(test()), [])
226+
227+
def test_async_for_else(self):
228+
async def test():
229+
result = []
230+
async for x in AsyncIter([]):
231+
result.append(x)
232+
else:
233+
result.append('else')
234+
return result
235+
236+
self.assertEqual(run_coro(test()), ['else'])
237+
238+
def test_async_for_else_on_completion(self):
239+
async def test():
240+
result = []
241+
async for x in AsyncIter([1, 2]):
242+
result.append(x)
243+
else:
244+
result.append('else')
245+
return result
246+
247+
self.assertEqual(run_coro(test()), [1, 2, 'else'])
248+
249+
def test_async_for_break(self):
250+
async def test():
251+
result = []
252+
async for x in AsyncIter([1, 2, 3, 4, 5]):
253+
if x == 3:
254+
break
255+
result.append(x)
256+
return result
257+
258+
self.assertEqual(run_coro(test()), [1, 2])
259+
260+
def test_async_for_break_skips_else(self):
261+
async def test():
262+
result = []
263+
async for x in AsyncIter([1, 2, 3]):
264+
if x == 2:
265+
break
266+
result.append(x)
267+
else:
268+
result.append('else')
269+
return result
270+
271+
self.assertEqual(run_coro(test()), [1])
272+
273+
def test_async_for_continue(self):
274+
async def test():
275+
result = []
276+
async for x in AsyncIter([1, 2, 3, 4, 5]):
277+
if x % 2 == 0:
278+
continue
279+
result.append(x)
280+
return result
281+
282+
self.assertEqual(run_coro(test()), [1, 3, 5])
283+
284+
def test_nested_async_for(self):
285+
async def test():
286+
result = []
287+
async for x in AsyncIter([1, 2]):
288+
async for y in AsyncIter([10, 20]):
289+
result.append(x * 100 + y)
290+
return result
291+
292+
self.assertEqual(run_coro(test()), [110, 120, 210, 220])
293+
294+
295+
class AsyncCombinedTest(unittest.TestCase):
296+
"""Tests combining async with and async for."""
297+
298+
def test_async_with_and_for(self):
299+
class CM:
300+
def __init__(self):
301+
self.log = []
302+
async def __aenter__(self):
303+
self.log.append('enter')
304+
return self
305+
async def __aexit__(self, *args):
306+
self.log.append('exit')
307+
308+
async def test():
309+
cm = CM()
310+
async with cm:
311+
async for x in AsyncIter([1, 2]):
312+
cm.log.append(x)
313+
return cm.log
314+
315+
self.assertEqual(run_coro(test()), ['enter', 1, 2, 'exit'])
316+
317+
318+
run_test(__name__)

0 commit comments

Comments
 (0)