27
27
import pytest
28
28
from _pytest .mark .structures import get_unpacked_marks
29
29
from pytest import (
30
+ Collector ,
30
31
Config ,
31
32
FixtureRequest ,
32
33
Function ,
@@ -202,11 +203,17 @@ def pytest_report_header(config: Config) -> List[str]:
202
203
203
204
204
205
def _preprocess_async_fixtures (
205
- config : Config ,
206
+ collector : Collector ,
206
207
processed_fixturedefs : Set [FixtureDef ],
207
208
) -> None :
209
+ config = collector .config
208
210
asyncio_mode = _get_asyncio_mode (config )
209
211
fixturemanager = config .pluginmanager .get_plugin ("funcmanage" )
212
+ event_loop_fixture_id = "event_loop"
213
+ for node , mark in collector .iter_markers_with_node ("asyncio_event_loop" ):
214
+ event_loop_fixture_id = node .stash .get (_event_loop_fixture_id , None )
215
+ if event_loop_fixture_id :
216
+ break
210
217
for fixtures in fixturemanager ._arg2fixturedefs .values ():
211
218
for fixturedef in fixtures :
212
219
func = fixturedef .func
@@ -219,46 +226,51 @@ def _preprocess_async_fixtures(
219
226
# This applies to pytest_trio fixtures, for example
220
227
continue
221
228
_make_asyncio_fixture_function (func )
222
- _inject_fixture_argnames (fixturedef )
223
- _synchronize_async_fixture (fixturedef )
229
+ _inject_fixture_argnames (fixturedef , event_loop_fixture_id )
230
+ _synchronize_async_fixture (fixturedef , event_loop_fixture_id )
224
231
assert _is_asyncio_fixture_function (fixturedef .func )
225
232
processed_fixturedefs .add (fixturedef )
226
233
227
234
228
- def _inject_fixture_argnames (fixturedef : FixtureDef ) -> None :
235
+ def _inject_fixture_argnames (
236
+ fixturedef : FixtureDef , event_loop_fixture_id : str
237
+ ) -> None :
229
238
"""
230
239
Ensures that `request` and `event_loop` are arguments of the specified fixture.
231
240
"""
232
241
to_add = []
233
- for name in ("request" , "event_loop" ):
242
+ for name in ("request" , event_loop_fixture_id ):
234
243
if name not in fixturedef .argnames :
235
244
to_add .append (name )
236
245
if to_add :
237
246
fixturedef .argnames += tuple (to_add )
238
247
239
248
240
- def _synchronize_async_fixture (fixturedef : FixtureDef ) -> None :
249
+ def _synchronize_async_fixture (
250
+ fixturedef : FixtureDef , event_loop_fixture_id : str
251
+ ) -> None :
241
252
"""
242
253
Wraps the fixture function of an async fixture in a synchronous function.
243
254
"""
244
255
if inspect .isasyncgenfunction (fixturedef .func ):
245
- _wrap_asyncgen_fixture (fixturedef )
256
+ _wrap_asyncgen_fixture (fixturedef , event_loop_fixture_id )
246
257
elif inspect .iscoroutinefunction (fixturedef .func ):
247
- _wrap_async_fixture (fixturedef )
258
+ _wrap_async_fixture (fixturedef , event_loop_fixture_id )
248
259
249
260
250
261
def _add_kwargs (
251
262
func : Callable [..., Any ],
252
263
kwargs : Dict [str , Any ],
264
+ event_loop_fixture_id : str ,
253
265
event_loop : asyncio .AbstractEventLoop ,
254
266
request : SubRequest ,
255
267
) -> Dict [str , Any ]:
256
268
sig = inspect .signature (func )
257
269
ret = kwargs .copy ()
258
270
if "request" in sig .parameters :
259
271
ret ["request" ] = request
260
- if "event_loop" in sig .parameters :
261
- ret ["event_loop" ] = event_loop
272
+ if event_loop_fixture_id in sig .parameters :
273
+ ret [event_loop_fixture_id ] = event_loop
262
274
return ret
263
275
264
276
@@ -281,17 +293,18 @@ def _perhaps_rebind_fixture_func(
281
293
return func
282
294
283
295
284
- def _wrap_asyncgen_fixture (fixturedef : FixtureDef ) -> None :
296
+ def _wrap_asyncgen_fixture (fixturedef : FixtureDef , event_loop_fixture_id : str ) -> None :
285
297
fixture = fixturedef .func
286
298
287
299
@functools .wraps (fixture )
288
- def _asyncgen_fixture_wrapper (
289
- event_loop : asyncio .AbstractEventLoop , request : SubRequest , ** kwargs : Any
290
- ):
300
+ def _asyncgen_fixture_wrapper (request : SubRequest , ** kwargs : Any ):
291
301
func = _perhaps_rebind_fixture_func (
292
302
fixture , request .instance , fixturedef .unittest
293
303
)
294
- gen_obj = func (** _add_kwargs (func , kwargs , event_loop , request ))
304
+ event_loop = kwargs .pop (event_loop_fixture_id )
305
+ gen_obj = func (
306
+ ** _add_kwargs (func , kwargs , event_loop_fixture_id , event_loop , request )
307
+ )
295
308
296
309
async def setup ():
297
310
res = await gen_obj .__anext__ ()
@@ -319,19 +332,20 @@ async def async_finalizer() -> None:
319
332
fixturedef .func = _asyncgen_fixture_wrapper
320
333
321
334
322
- def _wrap_async_fixture (fixturedef : FixtureDef ) -> None :
335
+ def _wrap_async_fixture (fixturedef : FixtureDef , event_loop_fixture_id : str ) -> None :
323
336
fixture = fixturedef .func
324
337
325
338
@functools .wraps (fixture )
326
- def _async_fixture_wrapper (
327
- event_loop : asyncio .AbstractEventLoop , request : SubRequest , ** kwargs : Any
328
- ):
339
+ def _async_fixture_wrapper (request : SubRequest , ** kwargs : Any ):
329
340
func = _perhaps_rebind_fixture_func (
330
341
fixture , request .instance , fixturedef .unittest
331
342
)
343
+ event_loop = kwargs .pop (event_loop_fixture_id )
332
344
333
345
async def setup ():
334
- res = await func (** _add_kwargs (func , kwargs , event_loop , request ))
346
+ res = await func (
347
+ ** _add_kwargs (func , kwargs , event_loop_fixture_id , event_loop , request )
348
+ )
335
349
return res
336
350
337
351
return event_loop .run_until_complete (setup ())
@@ -351,7 +365,7 @@ def pytest_pycollect_makeitem(
351
365
"""A pytest hook to collect asyncio coroutines."""
352
366
if not collector .funcnamefilter (name ):
353
367
return None
354
- _preprocess_async_fixtures (collector . config , _HOLDER )
368
+ _preprocess_async_fixtures (collector , _HOLDER )
355
369
return None
356
370
357
371
@@ -456,6 +470,12 @@ def pytest_generate_tests(metafunc: Metafunc) -> None:
456
470
_event_loop_fixture_id , None
457
471
)
458
472
if event_loop_fixture_id :
473
+ # This specific fixture name may already be in metafunc.argnames, if this
474
+ # test indirectly depends on the fixture. For example, this is the case
475
+ # when the test depends on an async fixture, both of which share the same
476
+ # asyncio_event_loop mark.
477
+ if event_loop_fixture_id in metafunc .fixturenames :
478
+ continue
459
479
fixturemanager = metafunc .config .pluginmanager .get_plugin ("funcmanage" )
460
480
if "event_loop" in metafunc .fixturenames :
461
481
raise MultipleEventLoopsRequestedError (
0 commit comments