Skip to content

Commit 144277b

Browse files
committed
[fix] Fixes a bug that caused internal pytest errors during test collection with doctests.
Pytest-asyncio attaches an event loop of a particular scope to each collector during pytest_collectstart. The implementation did not account for the fact that collectors may have specialized subclasses, such as DoctestModule. This caused a KeyError during a dictionary access, leading to an internal pytest error in the collection phase. This patch changes the implementation of pytest_collectstart to account for subclasses of collectors. Signed-off-by: Michael Seifert <[email protected]>
1 parent 01f5fec commit 144277b

File tree

2 files changed

+32
-5
lines changed

2 files changed

+32
-5
lines changed

pytest_asyncio/plugin.py

+13-5
Original file line numberDiff line numberDiff line change
@@ -544,25 +544,33 @@ def pytest_pycollect_makeitem_convert_async_functions_to_subclass(
544544
_event_loop_fixture_id = StashKey[str]
545545
_fixture_scope_by_collector_type = {
546546
Class: "class",
547-
Module: "module",
547+
# Package is a subclass of module and the dict is used in isinstance checks
548+
# Therefore, the order matters and Package needs to appear before Module
548549
Package: "package",
550+
Module: "module",
549551
Session: "session",
550552
}
551553

552554

553555
@pytest.hookimpl
554556
def pytest_collectstart(collector: pytest.Collector):
557+
try:
558+
collector_scope = next(
559+
scope
560+
for cls, scope in _fixture_scope_by_collector_type.items()
561+
if isinstance(collector, cls)
562+
)
563+
except StopIteration:
564+
return
555565
# Session is not a PyCollector type, so it doesn't have a corresponding
556566
# "obj" attribute to attach a dynamic fixture function to.
557567
# However, there's only one session per pytest run, so there's no need to
558568
# create the fixture dynamically. We can simply define a session-scoped
559569
# event loop fixture once in the plugin code.
560-
if isinstance(collector, Session):
570+
if collector_scope == "session":
561571
event_loop_fixture_id = _session_event_loop.__name__
562572
collector.stash[_event_loop_fixture_id] = event_loop_fixture_id
563573
return
564-
if not isinstance(collector, (Class, Module, Package)):
565-
return
566574
# There seem to be issues when a fixture is shadowed by another fixture
567575
# and both differ in their params.
568576
# https://github.com/pytest-dev/pytest/issues/2043
@@ -574,7 +582,7 @@ def pytest_collectstart(collector: pytest.Collector):
574582
collector.stash[_event_loop_fixture_id] = event_loop_fixture_id
575583

576584
@pytest.fixture(
577-
scope=_fixture_scope_by_collector_type[type(collector)],
585+
scope=collector_scope,
578586
name=event_loop_fixture_id,
579587
)
580588
def scoped_event_loop(

tests/test_doctest.py

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
from textwrap import dedent
2+
3+
from pytest import Pytester
4+
5+
6+
def test_plugin_does_not_interfere_with_doctest_collection(pytester: Pytester):
7+
pytester.makepyfile(
8+
dedent(
9+
'''\
10+
def any_function():
11+
"""
12+
>>> 42
13+
42
14+
"""
15+
'''
16+
),
17+
)
18+
result = pytester.runpytest("--asyncio-mode=strict", "--doctest-modules")
19+
result.assert_outcomes(passed=1)

0 commit comments

Comments
 (0)