diff --git a/pytest_asyncio/plugin.py b/pytest_asyncio/plugin.py index dfbd9958..6e7a3e67 100644 --- a/pytest_asyncio/plugin.py +++ b/pytest_asyncio/plugin.py @@ -544,25 +544,33 @@ def pytest_pycollect_makeitem_convert_async_functions_to_subclass( _event_loop_fixture_id = StashKey[str] _fixture_scope_by_collector_type = { Class: "class", - Module: "module", + # Package is a subclass of module and the dict is used in isinstance checks + # Therefore, the order matters and Package needs to appear before Module Package: "package", + Module: "module", Session: "session", } @pytest.hookimpl def pytest_collectstart(collector: pytest.Collector): + try: + collector_scope = next( + scope + for cls, scope in _fixture_scope_by_collector_type.items() + if isinstance(collector, cls) + ) + except StopIteration: + return # Session is not a PyCollector type, so it doesn't have a corresponding # "obj" attribute to attach a dynamic fixture function to. # However, there's only one session per pytest run, so there's no need to # create the fixture dynamically. We can simply define a session-scoped # event loop fixture once in the plugin code. - if isinstance(collector, Session): + if collector_scope == "session": event_loop_fixture_id = _session_event_loop.__name__ collector.stash[_event_loop_fixture_id] = event_loop_fixture_id return - if not isinstance(collector, (Class, Module, Package)): - return # There seem to be issues when a fixture is shadowed by another fixture # and both differ in their params. # https://github.com/pytest-dev/pytest/issues/2043 @@ -574,7 +582,7 @@ def pytest_collectstart(collector: pytest.Collector): collector.stash[_event_loop_fixture_id] = event_loop_fixture_id @pytest.fixture( - scope=_fixture_scope_by_collector_type[type(collector)], + scope=collector_scope, name=event_loop_fixture_id, ) def scoped_event_loop( diff --git a/tests/test_doctest.py b/tests/test_doctest.py new file mode 100644 index 00000000..6c26e645 --- /dev/null +++ b/tests/test_doctest.py @@ -0,0 +1,19 @@ +from textwrap import dedent + +from pytest import Pytester + + +def test_plugin_does_not_interfere_with_doctest_collection(pytester: Pytester): + pytester.makepyfile( + dedent( + '''\ + def any_function(): + """ + >>> 42 + 42 + """ + ''' + ), + ) + result = pytester.runpytest("--asyncio-mode=strict", "--doctest-modules") + result.assert_outcomes(passed=1)