Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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/14287.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Dynamic fixture dependencies via :func:`request.getfixturevalue() <pytest.FixtureRequest.getfixturevalue>` are now shown in ``--setuponly`` output.
9 changes: 7 additions & 2 deletions src/_pytest/fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -377,9 +377,12 @@ def __init__(
# collection. Dynamically requested fixtures (using
# `request.getfixturevalue("foo")`) are added dynamically.
self._arg2fixturedefs: Final = arg2fixturedefs
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewing this inspired me to do a cleanup here: #14290

# The evaluated argnames so far, mapping to the FixtureDef they resolved
# to.
# The argnames evaluated in the current test item, mapping to the FixtureDef
# they resolved to.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"in the current test item" is a good addition, but let's keep the "so far" to make it clear it's a mutable dict that is filled as fixtures are evaluated.

Suggested change
# The argnames evaluated in the current test item, mapping to the FixtureDef
# they resolved to.
# The argnames evaluated in the current test item so far, mapping to the FixtureDef
# they resolved to. Shared by the TopRequest and all of its SubRequests.

self._fixture_defs: Final = fixture_defs
# FixtureDefs requested through this specific `request` object.
# Allows tracking dependencies on fixtures.
self._own_fixture_defs: Final[dict[str, FixtureDef[object]]] = {}
# Notes on the type of `param`:
# -`request.param` is only defined in parametrized fixtures, and will raise
# AttributeError otherwise. Python typing has no notion of "undefined", so
Expand Down Expand Up @@ -555,6 +558,7 @@ def _get_active_fixturedef(self, argname: str) -> FixtureDef[object]:
fixturedef = self._fixture_defs.get(argname)
if fixturedef is not None:
self._check_scope(fixturedef, fixturedef._scope)
self._own_fixture_defs[argname] = fixturedef
return fixturedef

# Find the appropriate fixturedef.
Expand Down Expand Up @@ -616,6 +620,7 @@ def _get_active_fixturedef(self, argname: str) -> FixtureDef[object]:
self, scope, param, param_index, fixturedef, _ispytest=True
)

self._own_fixture_defs[argname] = fixturedef
# Make sure the fixture value is cached, running it if it isn't
fixturedef.execute(request=subrequest)

Expand Down
15 changes: 8 additions & 7 deletions src/_pytest/setuponly.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ def pytest_fixture_setup(
else:
param = request.param
fixturedef.cached_param = param # type: ignore[attr-defined]
_show_fixture_action(fixturedef, request.config, "SETUP")
_show_fixture_action(fixturedef, request, "SETUP")


def pytest_fixture_post_finalizer(
Expand All @@ -56,14 +56,15 @@ def pytest_fixture_post_finalizer(
if fixturedef.cached_result is not None:
config = request.config
if config.option.setupshow:
_show_fixture_action(fixturedef, request.config, "TEARDOWN")
_show_fixture_action(fixturedef, request, "TEARDOWN")
if hasattr(fixturedef, "cached_param"):
del fixturedef.cached_param


def _show_fixture_action(
fixturedef: FixtureDef[object], config: Config, msg: str
fixturedef: FixtureDef[object], request: SubRequest, msg: str
) -> None:
config = request.config
capman = config.pluginmanager.getplugin("capturemanager")
if capman:
capman.suspend_global_capture()
Expand All @@ -77,14 +78,14 @@ def _show_fixture_action(
scopename = fixturedef.scope[0].upper()
tw.write(f"{msg:<8} {scopename} {fixturedef.argname}")

if hasattr(fixturedef, "cached_param"):
tw.write(f"[{saferepr(fixturedef.cached_param, maxsize=42)}]")

if msg == "SETUP":
deps = sorted(arg for arg in fixturedef.argnames if arg != "request")
deps = sorted(request._own_fixture_defs.keys())
if deps:
tw.write(" (fixtures used: {})".format(", ".join(deps)))

if hasattr(fixturedef, "cached_param"):
tw.write(f"[{saferepr(fixturedef.cached_param, maxsize=42)}]")

tw.flush()

if capman:
Expand Down
39 changes: 39 additions & 0 deletions testing/test_setuponly.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,40 @@ def test_arg1(arg_other):
)


def test_show_fixtures_with_parameters_and_deps(pytester: Pytester) -> None:
p = pytester.makepyfile(
"""
import pytest

@pytest.fixture
def static_dep():
pass

@pytest.fixture
def dynamic_dep():
pass

@pytest.fixture
def param_fixture(static_dep, request):
request.getfixturevalue('dynamic_dep')

@pytest.mark.parametrize('param_fixture', [1], indirect=True)
def test_indirect(param_fixture):
pass
"""
)

result = pytester.runpytest("--setup-only", p)
assert result.ret == 0

result.stdout.fnmatch_lines(
[
" SETUP F param_fixture[1] (fixtures used: dynamic_dep, static_dep)",
" TEARDOWN F param_fixture[1]",
]
)


def test_show_fixtures_with_parameter_ids(pytester: Pytester, mode) -> None:
pytester.makeconftest(
'''
Expand Down Expand Up @@ -215,12 +249,15 @@ def test_dynamic_fixture_request(pytester: Pytester) -> None:
p = pytester.makepyfile(
"""
import pytest

@pytest.fixture()
def dynamically_requested_fixture():
pass

@pytest.fixture()
def dependent_fixture(request):
request.getfixturevalue('dynamically_requested_fixture')

def test_dyn(dependent_fixture):
pass
"""
Expand All @@ -232,6 +269,8 @@ def test_dyn(dependent_fixture):
result.stdout.fnmatch_lines(
[
"*SETUP F dynamically_requested_fixture",
"*SETUP F dependent_fixture (fixtures used: dynamically_requested_fixture)",
"*TEARDOWN F dependent_fixture",
"*TEARDOWN F dynamically_requested_fixture",
]
)
Expand Down
Loading