Skip to content

Commit e1af98a

Browse files
committed
wip: Add tests for additional edge cases and try to cover them.
1 parent b0349c2 commit e1af98a

File tree

3 files changed

+214
-43
lines changed

3 files changed

+214
-43
lines changed

pytest_asyncio/plugin.py

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -836,14 +836,28 @@ def _get_default_test_loop_scope(config: Config) -> Any:
836836
def _get_loop_facotry(
837837
request: FixtureRequest,
838838
) -> Callable[[], AbstractEventLoop] | None:
839-
if asyncio_mark := request._pyfuncitem.get_closest_marker("asyncio"):
839+
loop_factories = []
840+
asyncio_mark = request._pyfuncitem.get_closest_marker("asyncio")
841+
if asyncio_mark is not None:
840842
# The loop_factory is defined on an asyncio marker
841843
factory = asyncio_mark.kwargs.get("loop_factory", None)
842-
return factory
843-
else:
844-
# The loop_factory is pulled in via a fixture
845-
top_request = list(request._iter_chain())[-1]._parent_request
846-
return top_request._pyfuncitem.__dict__.get("_loop_factory", None) # type: ignore[attr-defined]
844+
loop_factories.append(factory)
845+
# The loop_factory is defined in a transitive fixture
846+
current_request = request
847+
for r in request._iter_chain():
848+
current_request = r
849+
loop_factory = getattr(current_request._fixturedef.func, "_loop_factory", None)
850+
loop_factories.append(loop_factory)
851+
defined_loop_factories = [factory for factory in loop_factories if factory] or [
852+
None
853+
]
854+
print(defined_loop_factories)
855+
if len(defined_loop_factories) > 1:
856+
print(defined_loop_factories)
857+
raise pytest.UsageError(
858+
"Multiple loop factories defined for {request.scope}-scoped loop."
859+
)
860+
return defined_loop_factories[0]
847861

848862

849863
def _create_scoped_runner_fixture(scope: _ScopeName) -> Callable:

tests/async_fixtures/test_async_fixtures_contextvars.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ def test(check_var_fixture):
101101
"""
102102
)
103103
)
104-
result = pytester.runpytest("--asyncio-mode=strict")
104+
result = pytester.runpytest("--asyncio-mode=strict", "-s")
105105
result.assert_outcomes(passed=1)
106106

107107

tests/test_asyncio_mark.py

Lines changed: 193 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -148,15 +148,15 @@ async def test_a():
148148
)
149149

150150

151-
def test_asyncio_marker_fallbacks_to_configured_default_loop_scope_if_not_set(
151+
def test_asyncio_marker_uses_marker_loop_scope_even_if_config_is_set(
152152
pytester: Pytester,
153153
):
154154
pytester.makeini(
155155
dedent(
156156
"""\
157157
[pytest]
158158
asyncio_default_fixture_loop_scope = function
159-
asyncio_default_test_loop_scope = session
159+
asyncio_default_test_loop_scope = module
160160
"""
161161
)
162162
)
@@ -175,6 +175,7 @@ async def session_loop_fixture():
175175
global loop
176176
loop = asyncio.get_running_loop()
177177
178+
@pytest.mark.asyncio(loop_scope="session")
178179
async def test_a(session_loop_fixture):
179180
global loop
180181
assert asyncio.get_running_loop() is loop
@@ -186,56 +187,125 @@ async def test_a(session_loop_fixture):
186187
result.assert_outcomes(passed=1)
187188

188189

189-
def test_asyncio_marker_uses_marker_loop_scope_even_if_config_is_set(
190-
pytester: Pytester,
191-
):
192-
pytester.makeini(
190+
def test_uses_loop_factory_from_test(pytester: Pytester):
191+
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
192+
pytester.makepyfile(
193193
dedent(
194194
"""\
195-
[pytest]
196-
asyncio_default_fixture_loop_scope = function
197-
asyncio_default_test_loop_scope = module
195+
import asyncio
196+
import pytest_asyncio
197+
import pytest
198+
199+
class CustomEventLoop(asyncio.SelectorEventLoop):
200+
pass
201+
202+
@pytest_asyncio.fixture(loop_scope="module")
203+
async def any_fixture():
204+
assert type(asyncio.get_running_loop()) == CustomEventLoop
205+
206+
@pytest.mark.asyncio(loop_scope="module", loop_factory=CustomEventLoop)
207+
async def test_set_loop_factory(any_fixture):
208+
assert type(asyncio.get_running_loop()) == CustomEventLoop
198209
"""
199210
)
200211
)
212+
result = pytester.runpytest("--asyncio-mode=strict")
213+
result.assert_outcomes(passed=1)
214+
201215

216+
def test_uses_loop_factory_from_fixture(pytester: Pytester):
217+
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
202218
pytester.makepyfile(
203219
dedent(
204220
"""\
205221
import asyncio
206222
import pytest_asyncio
207223
import pytest
208224
209-
loop: asyncio.AbstractEventLoop
225+
class CustomEventLoop(asyncio.SelectorEventLoop):
226+
pass
210227
211-
@pytest_asyncio.fixture(loop_scope="session", scope="session")
212-
async def session_loop_fixture():
213-
global loop
214-
loop = asyncio.get_running_loop()
228+
@pytest_asyncio.fixture(loop_scope="module", loop_factory=CustomEventLoop)
229+
async def any_fixture():
230+
assert type(asyncio.get_running_loop()) == CustomEventLoop
215231
216-
@pytest.mark.asyncio(loop_scope="session")
217-
async def test_a(session_loop_fixture):
218-
global loop
219-
assert asyncio.get_running_loop() is loop
232+
@pytest.mark.asyncio(loop_scope="module")
233+
async def test_set_loop_factory(any_fixture):
234+
assert type(asyncio.get_running_loop()) == CustomEventLoop
220235
"""
221236
)
222237
)
238+
result = pytester.runpytest("--asyncio-mode=strict")
239+
result.assert_outcomes(passed=1)
223240

224-
result = pytester.runpytest("--asyncio-mode=auto")
241+
242+
def test_uses_loop_factory_from_transitive_fixture(pytester: Pytester):
243+
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
244+
pytester.makepyfile(
245+
dedent(
246+
"""\
247+
import asyncio
248+
import pytest_asyncio
249+
import pytest
250+
251+
class CustomEventLoop(asyncio.SelectorEventLoop):
252+
pass
253+
254+
@pytest_asyncio.fixture(loop_scope="module", loop_factory=CustomEventLoop)
255+
async def transitive_fixture():
256+
assert type(asyncio.get_running_loop()) == CustomEventLoop
257+
258+
@pytest_asyncio.fixture(loop_scope="module")
259+
async def any_fixture(transitive_fixture):
260+
assert type(asyncio.get_running_loop()) == CustomEventLoop
261+
262+
@pytest.mark.asyncio(loop_scope="module")
263+
async def test_set_loop_factory(any_fixture):
264+
assert type(asyncio.get_running_loop()) == CustomEventLoop
265+
"""
266+
)
267+
)
268+
result = pytester.runpytest("--asyncio-mode=strict")
225269
result.assert_outcomes(passed=1)
226270

227271

228-
def test_asyncio_marker_event_loop_factories(pytester: Pytester):
229-
pytester.makeini(
272+
def test_conflicting_loop_factories_in_tests_raise_error(pytester: Pytester):
273+
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
274+
pytester.makepyfile(
230275
dedent(
231276
"""\
232-
[pytest]
233-
asyncio_default_fixture_loop_scope = function
234-
asyncio_default_test_loop_scope = module
277+
import asyncio
278+
import pytest_asyncio
279+
import pytest
280+
281+
class CustomEventLoop(asyncio.SelectorEventLoop):
282+
pass
283+
284+
class AnotherCustomEventLoop(asyncio.SelectorEventLoop):
285+
pass
286+
287+
@pytest.mark.asyncio(loop_scope="module", loop_factory=CustomEventLoop)
288+
async def test_with_custom_loop_factory():
289+
...
290+
291+
@pytest.mark.asyncio(
292+
loop_scope="module",
293+
loop_factory=AnotherCustomEventLoop
294+
)
295+
async def test_with_a_different_custom_loop_factory():
296+
...
235297
"""
236298
)
237299
)
238300

301+
result = pytester.runpytest("--asyncio-mode=strict", "-s", "--setup-show")
302+
result.assert_outcomes(errors=2)
303+
304+
305+
def test_conflicting_loop_factories_in_tests_and_fixtures_raise_error(
306+
pytester: Pytester,
307+
):
308+
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
239309
pytester.makepyfile(
240310
dedent(
241311
"""\
@@ -246,22 +316,109 @@ def test_asyncio_marker_event_loop_factories(pytester: Pytester):
246316
class CustomEventLoop(asyncio.SelectorEventLoop):
247317
pass
248318
249-
@pytest.mark.asyncio(loop_factory=CustomEventLoop)
250-
async def test_has_different_event_loop():
251-
assert type(asyncio.get_running_loop()).__name__ == "CustomEventLoop"
319+
class AnotherCustomEventLoop(asyncio.SelectorEventLoop):
320+
pass
321+
322+
@pytest_asyncio.fixture(loop_scope="module", loop_factory=CustomEventLoop)
323+
async def fixture_with_custom_loop_factory():
324+
...
325+
326+
@pytest.mark.asyncio(
327+
loop_scope="module",
328+
loop_factory=AnotherCustomEventLoop
329+
)
330+
async def test_trying_to_override_fixtures_loop_factory(
331+
fixture_with_custom_loop_factory
332+
):
333+
# Fails, because it tries to use a different loop factory on the
334+
# same runner as the first test
335+
...
336+
"""
337+
)
338+
)
339+
340+
result = pytester.runpytest("--asyncio-mode=strict")
341+
result.assert_outcomes(passed=1, errors=1)
252342

253-
@pytest_asyncio.fixture(loop_factory=CustomEventLoop)
254-
async def custom_fixture():
255-
yield asyncio.get_running_loop()
256343

257-
async def test_with_fixture(custom_fixture):
258-
# Both of these should be the same...
259-
type(asyncio.get_running_loop()).__name__ == "CustomEventLoop"
260-
type(custom_fixture).__name__ == "CustomEventLoop"
344+
def test_conflicting_loop_factories_in_fixtures_raise_error(pytester: Pytester):
345+
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
346+
pytester.makepyfile(
347+
dedent(
348+
"""\
349+
import asyncio
350+
import pytest_asyncio
351+
import pytest
261352
353+
class CustomEventLoop(asyncio.SelectorEventLoop):
354+
pass
355+
356+
class AnotherCustomEventLoop(asyncio.SelectorEventLoop):
357+
pass
358+
359+
@pytest_asyncio.fixture(loop_scope="module", loop_factory=CustomEventLoop)
360+
async def fixture_with_custom_loop_factory():
361+
...
362+
363+
@pytest_asyncio.fixture(
364+
loop_scope="module",
365+
loop_factory=AnotherCustomEventLoop
366+
)
367+
async def another_fixture_with_custom_loop_factory():
368+
...
369+
370+
@pytest.mark.asyncio(loop_scope="module")
371+
async def test_requesting_two_fixtures_with_different_loop_facoties(
372+
fixture_with_custom_loop_factory,
373+
another_fixture_with_custom_loop_factory,
374+
):
375+
...
262376
"""
263377
)
264378
)
265379

266-
result = pytester.runpytest("--asyncio-mode=auto")
267-
result.assert_outcomes(passed=2)
380+
result = pytester.runpytest("--asyncio-mode=strict", "-s", "--setup-show")
381+
result.assert_outcomes(errors=1)
382+
383+
384+
def test_conflicting_loop_factories_in_transitive_fixtures_raise_error(
385+
pytester: Pytester,
386+
):
387+
pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function")
388+
pytester.makepyfile(
389+
dedent(
390+
"""\
391+
import asyncio
392+
import pytest_asyncio
393+
import pytest
394+
395+
class CustomEventLoop(asyncio.SelectorEventLoop):
396+
pass
397+
398+
class AnotherCustomEventLoop(asyncio.SelectorEventLoop):
399+
pass
400+
401+
@pytest_asyncio.fixture(loop_scope="module", loop_factory=CustomEventLoop)
402+
async def fixture_with_custom_loop_factory():
403+
...
404+
405+
@pytest_asyncio.fixture(
406+
loop_scope="module",
407+
loop_factory=AnotherCustomEventLoop
408+
)
409+
async def another_fixture_with_custom_loop_factory(
410+
fixture_with_custom_loop_factory
411+
):
412+
...
413+
414+
@pytest.mark.asyncio(loop_scope="module")
415+
async def test_requesting_two_fixtures_with_different_loop_facories(
416+
another_fixture_with_custom_loop_factory,
417+
):
418+
...
419+
"""
420+
)
421+
)
422+
423+
result = pytester.runpytest("--asyncio-mode=strict")
424+
result.assert_outcomes(errors=1)

0 commit comments

Comments
 (0)