@@ -204,13 +204,6 @@ def _preprocess_async_fixtures(
204
204
config = collector .config
205
205
asyncio_mode = _get_asyncio_mode (config )
206
206
fixturemanager = config .pluginmanager .get_plugin ("funcmanage" )
207
- marker = collector .get_closest_marker ("asyncio" )
208
- scope = marker .kwargs .get ("scope" , "function" ) if marker else "function"
209
- if scope == "function" :
210
- event_loop_fixture_id = "event_loop"
211
- else :
212
- event_loop_node = _retrieve_scope_root (collector , scope )
213
- event_loop_fixture_id = event_loop_node .stash .get (_event_loop_fixture_id , None )
214
207
for fixtures in fixturemanager ._arg2fixturedefs .values ():
215
208
for fixturedef in fixtures :
216
209
func = fixturedef .func
@@ -222,6 +215,14 @@ def _preprocess_async_fixtures(
222
215
# Ignore async fixtures without explicit asyncio mark in strict mode
223
216
# This applies to pytest_trio fixtures, for example
224
217
continue
218
+ scope = fixturedef .scope
219
+ if scope == "function" :
220
+ event_loop_fixture_id = "event_loop"
221
+ else :
222
+ event_loop_node = _retrieve_scope_root (collector , scope )
223
+ event_loop_fixture_id = event_loop_node .stash .get (
224
+ _event_loop_fixture_id , None
225
+ )
225
226
_make_asyncio_fixture_function (func )
226
227
function_signature = inspect .signature (func )
227
228
if "event_loop" in function_signature .parameters :
@@ -589,6 +590,12 @@ def scoped_event_loop(
589
590
yield loop
590
591
loop .close ()
591
592
asyncio .set_event_loop_policy (old_loop_policy )
593
+ # When a test uses both a scoped event loop and the event_loop fixture,
594
+ # the "_provide_clean_event_loop" finalizer of the event_loop fixture
595
+ # will already have installed a fresh event loop, in order to shield
596
+ # subsequent tests from side-effects. We close this loop before restoring
597
+ # the old loop to avoid ResourceWarnings.
598
+ asyncio .get_event_loop ().close ()
592
599
asyncio .set_event_loop (old_loop )
593
600
594
601
# @pytest.fixture does not register the fixture anywhere, so pytest doesn't
@@ -680,7 +687,9 @@ def pytest_generate_tests(metafunc: Metafunc) -> None:
680
687
)
681
688
# Add the scoped event loop fixture to Metafunc's list of fixture names and
682
689
# fixturedefs and leave the actual parametrization to pytest
683
- metafunc .fixturenames .insert (0 , event_loop_fixture_id )
690
+ # The fixture needs to be appended to avoid messing up the fixture evaluation
691
+ # order
692
+ metafunc .fixturenames .append (event_loop_fixture_id )
684
693
metafunc ._arg2fixturedefs [
685
694
event_loop_fixture_id
686
695
] = fixturemanager ._arg2fixturedefs [event_loop_fixture_id ]
@@ -885,8 +894,13 @@ def pytest_runtest_setup(item: pytest.Item) -> None:
885
894
fixturenames = item .fixturenames # type: ignore[attr-defined]
886
895
# inject an event loop fixture for all async tests
887
896
if "event_loop" in fixturenames :
897
+ # Move the "event_loop" fixture to the beginning of the fixture evaluation
898
+ # closure for backwards compatibility
888
899
fixturenames .remove ("event_loop" )
889
- fixturenames .insert (0 , event_loop_fixture_id )
900
+ fixturenames .insert (0 , "event_loop" )
901
+ else :
902
+ if event_loop_fixture_id not in fixturenames :
903
+ fixturenames .append (event_loop_fixture_id )
890
904
obj = getattr (item , "obj" , None )
891
905
if not getattr (obj , "hypothesis" , False ) and getattr (
892
906
obj , "is_hypothesis_test" , False
@@ -944,6 +958,12 @@ def _session_event_loop(
944
958
yield loop
945
959
loop .close ()
946
960
asyncio .set_event_loop_policy (old_loop_policy )
961
+ # When a test uses both a scoped event loop and the event_loop fixture,
962
+ # the "_provide_clean_event_loop" finalizer of the event_loop fixture
963
+ # will already have installed a fresh event loop, in order to shield
964
+ # subsequent tests from side-effects. We close this loop before restoring
965
+ # the old loop to avoid ResourceWarnings.
966
+ asyncio .get_event_loop ().close ()
947
967
asyncio .set_event_loop (old_loop )
948
968
949
969
0 commit comments