Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog/14533.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fixed a regression where enabling :option:`--doctest-modules` could make fixtures defined in a Python module unavailable to normal tests collected from the same module.
9 changes: 6 additions & 3 deletions src/_pytest/fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -1676,7 +1676,7 @@ def __init__(self, session: Session) -> None:
# TODO: The order of the FixtureDefs list of each arg is significant,
# explain.
self._arg2fixturedefs: Final[dict[str, list[FixtureDef[Any]]]] = {}
self._holderobjseen: Final[set[object]] = set()
self._holderobjseen: Final[set[tuple[object, nodes.Node | str | None]]] = set()
# A mapping from a node to a list of autouse fixture names it defines.
# The Session entry holds global usefixtures from config.
self._node_autousenames: Final[dict[nodes.Node, list[str]]] = {
Expand Down Expand Up @@ -2031,7 +2031,10 @@ def parsefactories(
assert isinstance(node_or_obj, nodes.Node)
holderobj = cast(object, node_or_obj.obj) # type: ignore[attr-defined]
effective_node = node_or_obj
if holderobj in self._holderobjseen:
# The same holder may be parsed with different visibility scopes, e.g.
# a module collected as both DoctestModule and Module.
holderobj_key = (holderobj, effective_node or effective_nodeid)
if holderobj_key in self._holderobjseen:
return

# Avoid accessing `@property` (and other descriptors) when iterating fixtures.
Expand All @@ -2040,7 +2043,7 @@ def parsefactories(
else:
holderobj_tp = holderobj

self._holderobjseen.add(holderobj)
self._holderobjseen.add(holderobj_key)
for name in dir(holderobj):
# The attribute can be an arbitrary descriptor, so the attribute
# access below can raise. safe_getattr() ignores such exceptions.
Expand Down
18 changes: 18 additions & 0 deletions testing/test_doctest.py
Original file line number Diff line number Diff line change
Expand Up @@ -600,6 +600,24 @@ def test_doctestmodule_with_fixtures(self, pytester: Pytester):
reprec = pytester.inline_run(p, "--doctest-modules")
reprec.assertoutcome(passed=1)

def test_doctestmodule_does_not_hide_module_fixtures(
self, pytester: Pytester
) -> None:
pytester.makepyfile(
"""
import pytest

@pytest.fixture
def foo_fixture():
return 1

def test_foo(foo_fixture):
assert foo_fixture
"""
)
result = pytester.runpytest("--doctest-modules")
result.assert_outcomes(passed=1)

def test_doctestmodule_three_tests(self, pytester: Pytester):
p = pytester.makepyfile(
"""
Expand Down
Loading