Skip to content

Commit e0c8a7c

Browse files
cstructseifertm
authored andcommitted
Preprocess async fixtures for each event loop
Caching of fixture preprocessing is now also keyed by event loop id, sync fixtures can be processed if they are wrapping a async fixture, each async fixture has a mapping from root scope names to fixture id that is now used to dynamically get the event loop fixture. This fixes #862.
1 parent f45aa18 commit e0c8a7c

File tree

2 files changed

+96
-31
lines changed

2 files changed

+96
-31
lines changed

pytest_asyncio/plugin.py

+61-31
Original file line numberDiff line numberDiff line change
@@ -238,8 +238,8 @@ def _preprocess_async_fixtures(
238238
for fixtures in fixturemanager._arg2fixturedefs.values():
239239
for fixturedef in fixtures:
240240
func = fixturedef.func
241-
if fixturedef in processed_fixturedefs or not _is_coroutine_or_asyncgen(
242-
func
241+
if not _is_coroutine_or_asyncgen(func) and not getattr(
242+
func, "_async_fixture", False
243243
):
244244
continue
245245
if not _is_asyncio_fixture_function(func) and asyncio_mode == Mode.STRICT:
@@ -252,14 +252,21 @@ def _preprocess_async_fixtures(
252252
or fixturedef.scope
253253
)
254254
if scope == "function":
255+
event_loop_fixture_name = "function"
255256
event_loop_fixture_id: Optional[str] = "event_loop"
256257
else:
257-
event_loop_node = _retrieve_scope_root(collector, scope)
258+
try:
259+
event_loop_node = _retrieve_scope_root(collector, scope)
260+
except Exception:
261+
continue
262+
event_loop_fixture_name = event_loop_node.name
258263
event_loop_fixture_id = event_loop_node.stash.get(
259264
# Type ignored because of non-optimal mypy inference.
260265
_event_loop_fixture_id, # type: ignore[arg-type]
261266
None,
262267
)
268+
if (fixturedef, event_loop_fixture_id) in processed_fixturedefs:
269+
continue
263270
_make_asyncio_fixture_function(func, scope)
264271
function_signature = inspect.signature(func)
265272
if "event_loop" in function_signature.parameters:
@@ -272,42 +279,33 @@ def _preprocess_async_fixtures(
272279
)
273280
)
274281
assert event_loop_fixture_id
275-
_inject_fixture_argnames(
276-
fixturedef,
277-
event_loop_fixture_id,
278-
)
282+
if "request" not in fixturedef.argnames:
283+
fixturedef.argnames += ("request",)
279284
_synchronize_async_fixture(
280285
fixturedef,
286+
event_loop_fixture_name,
281287
event_loop_fixture_id,
282288
)
283289
assert _is_asyncio_fixture_function(fixturedef.func)
284-
processed_fixturedefs.add(fixturedef)
285-
286-
287-
def _inject_fixture_argnames(
288-
fixturedef: FixtureDef, event_loop_fixture_id: str
289-
) -> None:
290-
"""
291-
Ensures that `request` and `event_loop` are arguments of the specified fixture.
292-
"""
293-
to_add = []
294-
for name in ("request", event_loop_fixture_id):
295-
if name not in fixturedef.argnames:
296-
to_add.append(name)
297-
if to_add:
298-
fixturedef.argnames += tuple(to_add)
290+
processed_fixturedefs.add((fixturedef, event_loop_fixture_id))
299291

300292

301293
def _synchronize_async_fixture(
302-
fixturedef: FixtureDef, event_loop_fixture_id: str
294+
fixturedef: FixtureDef, event_loop_fixture_name: str, event_loop_fixture_id: str
303295
) -> None:
304296
"""
305297
Wraps the fixture function of an async fixture in a synchronous function.
306298
"""
307-
if inspect.isasyncgenfunction(fixturedef.func):
308-
_wrap_asyncgen_fixture(fixturedef, event_loop_fixture_id)
309-
elif inspect.iscoroutinefunction(fixturedef.func):
310-
_wrap_async_fixture(fixturedef, event_loop_fixture_id)
299+
if inspect.isasyncgenfunction(fixturedef.func) or getattr(
300+
fixturedef.func, "_async_fixture", False
301+
):
302+
_wrap_asyncgen_fixture(
303+
fixturedef, event_loop_fixture_name, event_loop_fixture_id
304+
)
305+
elif inspect.iscoroutinefunction(fixturedef.func) or getattr(
306+
fixturedef.func, "_async_fixture", False
307+
):
308+
_wrap_async_fixture(fixturedef, event_loop_fixture_name, event_loop_fixture_id)
311309

312310

313311
def _add_kwargs(
@@ -345,14 +343,27 @@ def _perhaps_rebind_fixture_func(
345343
return func
346344

347345

348-
def _wrap_asyncgen_fixture(fixturedef: FixtureDef, event_loop_fixture_id: str) -> None:
346+
def _wrap_asyncgen_fixture(
347+
fixturedef: FixtureDef, event_loop_fixture_name: str, event_loop_fixture_id: str
348+
) -> None:
349349
fixture = fixturedef.func
350350

351+
event_loop_id_mapping = getattr(fixture, "_event_loop_id_mapping", {})
352+
event_loop_id_mapping[event_loop_fixture_name] = event_loop_fixture_id
353+
event_loop_id_mapping["function"] = event_loop_fixture_id
354+
355+
if getattr(fixture, "_async_fixture", False):
356+
return fixture
357+
351358
@functools.wraps(fixture)
352359
def _asyncgen_fixture_wrapper(request: FixtureRequest, **kwargs: Any):
353360
unittest = fixturedef.unittest if hasattr(fixturedef, "unittest") else False
354361
func = _perhaps_rebind_fixture_func(fixture, request.instance, unittest)
355-
event_loop = kwargs.pop(event_loop_fixture_id)
362+
event_loop_fixture_id = event_loop_id_mapping.get(
363+
request.node.name, event_loop_id_mapping["function"]
364+
)
365+
event_loop = request.getfixturevalue(event_loop_fixture_id)
366+
kwargs.pop(event_loop_fixture_id, None)
356367
gen_obj = func(
357368
**_add_kwargs(func, kwargs, event_loop_fixture_id, event_loop, request)
358369
)
@@ -380,17 +391,33 @@ async def async_finalizer() -> None:
380391
request.addfinalizer(finalizer)
381392
return result
382393

394+
setattr(_asyncgen_fixture_wrapper, "_event_loop_id_mapping", event_loop_id_mapping)
395+
setattr(_asyncgen_fixture_wrapper, "_async_fixture", True)
396+
383397
fixturedef.func = _asyncgen_fixture_wrapper
384398

385399

386-
def _wrap_async_fixture(fixturedef: FixtureDef, event_loop_fixture_id: str) -> None:
400+
def _wrap_async_fixture(
401+
fixturedef: FixtureDef, event_loop_fixture_name: str, event_loop_fixture_id: str
402+
) -> None:
387403
fixture = fixturedef.func
388404

405+
event_loop_id_mapping = getattr(fixture, "_event_loop_id_mapping", {})
406+
event_loop_id_mapping[event_loop_fixture_name] = event_loop_fixture_id
407+
event_loop_id_mapping["function"] = event_loop_fixture_id
408+
409+
if getattr(fixture, "_async_fixture", False):
410+
return fixture
411+
389412
@functools.wraps(fixture)
390413
def _async_fixture_wrapper(request: FixtureRequest, **kwargs: Any):
391414
unittest = False if pytest.version_tuple >= (8, 2) else fixturedef.unittest
392415
func = _perhaps_rebind_fixture_func(fixture, request.instance, unittest)
393-
event_loop = kwargs.pop(event_loop_fixture_id)
416+
event_loop_fixture_id = event_loop_id_mapping.get(
417+
request.node.name, event_loop_id_mapping["function"]
418+
)
419+
event_loop = request.getfixturevalue(event_loop_fixture_id)
420+
kwargs.pop(event_loop_fixture_id, None)
394421

395422
async def setup():
396423
res = await func(
@@ -400,6 +427,9 @@ async def setup():
400427

401428
return event_loop.run_until_complete(setup())
402429

430+
setattr(_async_fixture_wrapper, "_event_loop_id_mapping", event_loop_id_mapping)
431+
setattr(_async_fixture_wrapper, "_async_fixture", True)
432+
403433
fixturedef.func = _async_fixture_wrapper
404434

405435

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
from textwrap import dedent
2+
3+
from pytest import Pytester
4+
5+
6+
def test_asyncio_mark_provides_package_scoped_loop_strict_mode(pytester: Pytester):
7+
pytester.makepyfile(
8+
__init__="",
9+
conftest=dedent(
10+
"""\
11+
import pytest_asyncio
12+
@pytest_asyncio.fixture(scope="module")
13+
async def async_shared_module_fixture():
14+
return True
15+
"""
16+
),
17+
test_module_one=dedent(
18+
"""\
19+
import pytest
20+
@pytest.mark.asyncio
21+
async def test_shared_module_fixture_use_a(async_shared_module_fixture):
22+
assert async_shared_module_fixture is True
23+
"""
24+
),
25+
test_module_two=dedent(
26+
"""\
27+
import pytest
28+
@pytest.mark.asyncio
29+
async def test_shared_module_fixture_use_b(async_shared_module_fixture):
30+
assert async_shared_module_fixture is True
31+
"""
32+
),
33+
)
34+
result = pytester.runpytest("--asyncio-mode=strict")
35+
result.assert_outcomes(passed=2)

0 commit comments

Comments
 (0)