Skip to content

Commit dd129e9

Browse files
committed
[fix] Fixes a bug that prevented tests from being collected from package when any module inside the package used a pytest.skip statement.
Signed-off-by: Michael Seifert <[email protected]>
1 parent 463ce98 commit dd129e9

File tree

2 files changed

+47
-27
lines changed

2 files changed

+47
-27
lines changed

pytest_asyncio/plugin.py

Lines changed: 17 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -558,6 +558,10 @@ def pytest_pycollect_makeitem_convert_async_functions_to_subclass(
558558
Session: "session",
559559
}
560560

561+
# A stack used to push package-scoped loops during collection of a package
562+
# and pop those loops during collection of a Module
563+
__package_loop_stack: List[Union[FixtureFunctionMarker, FixtureFunction]] = []
564+
561565

562566
@pytest.hookimpl
563567
def pytest_collectstart(collector: pytest.Collector):
@@ -609,31 +613,11 @@ def scoped_event_loop(
609613
# collected Python object, where it will be picked up by pytest.Class.collect()
610614
# or pytest.Module.collect(), respectively
611615
if type(collector) is Package:
612-
613-
def _patched_collect():
614-
# When collector is a Package, collector.obj is the package's
615-
# __init__.py. Accessing the __init__.py to attach the fixture function
616-
# may trigger additional module imports or change the order of imports,
617-
# which leads to a number of problems.
618-
# see https://github.com/pytest-dev/pytest-asyncio/issues/729
619-
# Moreover, Package.obj has been removed in pytest 8.
620-
# Therefore, pytest-asyncio attaches the packages-scoped event loop
621-
# fixture to the first collected module in that package.
622-
package_scoped_loop_added = False
623-
for subcollector in collector.__original_collect():
624-
if (
625-
not package_scoped_loop_added
626-
and isinstance(subcollector, Module)
627-
and getattr(subcollector, "obj", None)
628-
):
629-
subcollector.obj.__pytest_asyncio_package_scoped_event_loop = (
630-
scoped_event_loop
631-
)
632-
package_scoped_loop_added = True
633-
yield subcollector
634-
635-
collector.__original_collect = collector.collect
636-
collector.collect = _patched_collect
616+
# Packages do not have a corresponding Python object. Therefore, the fixture
617+
# for the package-scoped event loop is added to a stack. When a module inside
618+
# the package is collected, the module will attach the fixture to its
619+
# Python object.
620+
__package_loop_stack.append(scoped_event_loop)
637621
elif isinstance(collector, Module):
638622
# Accessing Module.obj triggers a module import executing module-level
639623
# statements. A module-level pytest.skip statement raises the "Skipped"
@@ -644,8 +628,14 @@ def _patched_collect():
644628
# module before it runs the actual collection.
645629
def _patched_collect():
646630
# If the collected module is a DoctestTextfile, collector.obj is None
647-
if collector.obj is not None:
648-
collector.obj.__pytest_asyncio_scoped_event_loop = scoped_event_loop
631+
module = collector.obj
632+
if module is not None:
633+
module.__pytest_asyncio_scoped_event_loop = scoped_event_loop
634+
try:
635+
package_loop = __package_loop_stack.pop()
636+
module.__pytest_asyncio_package_scoped_event_loop = package_loop
637+
except IndexError:
638+
pass
649639
return collector.__original_collect()
650640

651641
collector.__original_collect = collector.collect

tests/test_skips.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,3 +105,33 @@ async def test_is_skipped():
105105
)
106106
result = pytester.runpytest("--asyncio-mode=auto")
107107
result.assert_outcomes(skipped=1)
108+
109+
110+
def test_skip_in_module_does_not_skip_package(pytester: Pytester):
111+
pytester.makepyfile(
112+
__init__="",
113+
test_skip=dedent(
114+
"""\
115+
import pytest
116+
117+
pytest.skip("Skip all tests", allow_module_level=True)
118+
119+
def test_a():
120+
pass
121+
122+
def test_b():
123+
pass
124+
"""
125+
),
126+
test_something=dedent(
127+
"""\
128+
import pytest
129+
130+
@pytest.mark.asyncio
131+
async def test_something():
132+
pass
133+
"""
134+
),
135+
)
136+
result = pytester.runpytest("--asyncio-mode=strict")
137+
result.assert_outcomes(passed=1, skipped=1)

0 commit comments

Comments
 (0)