Merge ba38c1b8a2
into 381593ccf0
This commit is contained in:
commit
d06123383a
1
AUTHORS
1
AUTHORS
|
@ -423,6 +423,7 @@ Vlad Radziuk
|
|||
Vladyslav Rachek
|
||||
Volodymyr Kochetkov
|
||||
Volodymyr Piskun
|
||||
Warren Markham
|
||||
Wei Lin
|
||||
Wil Cooley
|
||||
William Lee
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
Improved output of --fixtures-per-test by excluding internal, pseudo fixtures.
|
|
@ -58,6 +58,7 @@ from _pytest.config import ExitCode
|
|||
from _pytest.config import hookimpl
|
||||
from _pytest.config.argparsing import Parser
|
||||
from _pytest.deprecated import check_ispytest
|
||||
from _pytest.fixtures import _get_direct_parametrize_args
|
||||
from _pytest.fixtures import FixtureDef
|
||||
from _pytest.fixtures import FixtureRequest
|
||||
from _pytest.fixtures import FuncFixtureInfo
|
||||
|
@ -1482,7 +1483,7 @@ class Metafunc:
|
|||
|
||||
def _find_parametrized_scope(
|
||||
argnames: Sequence[str],
|
||||
arg2fixturedefs: Mapping[str, Sequence[fixtures.FixtureDef[object]]],
|
||||
arg2fixturedefs: Mapping[str, Sequence[FixtureDef[object]]],
|
||||
indirect: Union[bool, Sequence[str]],
|
||||
) -> Scope:
|
||||
"""Find the most appropriate scope for a parametrized call based on its arguments.
|
||||
|
@ -1534,6 +1535,48 @@ def _pretty_fixture_path(invocation_dir: Path, func) -> str:
|
|||
return bestrelpath(invocation_dir, loc)
|
||||
|
||||
|
||||
def _get_fixtures_per_test(test: nodes.Item) -> Optional[List[FixtureDef[object]]]:
|
||||
"""Returns all fixtures used by the test item except for a) those
|
||||
created by direct parametrization with ``@pytest.mark.parametrize`` and
|
||||
b) those accessed dynamically with ``request.getfixturevalue``.
|
||||
|
||||
The justification for excluding fixtures created by direct
|
||||
parametrization is that their appearance in a report would surprise
|
||||
users currently learning about fixtures, as they do not conform to the
|
||||
documented characteristics of fixtures (reusable, providing
|
||||
setup/teardown features, and created via the ``@pytest.fixture``
|
||||
decorator).
|
||||
|
||||
In other words, an internal detail that leverages the fixture system
|
||||
to batch execute tests should not be exposed in a report intended to
|
||||
summarise the user's fixture choices.
|
||||
"""
|
||||
# Contains information on the fixtures the test item requests
|
||||
# statically, if any.
|
||||
fixture_info: Optional[FuncFixtureInfo] = getattr(test, "_fixtureinfo", None)
|
||||
if fixture_info is None:
|
||||
# The given test item does not statically request any fixtures.
|
||||
return []
|
||||
|
||||
# In the transitive closure of fixture names required by the item
|
||||
# through autouse, function parameter or @userfixture mechanisms,
|
||||
# multiple overrides may have occured; for this reason, the fixture
|
||||
# names are matched to a sequence of FixtureDefs.
|
||||
name2fixturedefs = fixture_info.name2fixturedefs
|
||||
fixturedefs = [
|
||||
# The final override, which is the one the test item will utilise,
|
||||
# is stored in the final position of the sequence; therefore, we
|
||||
# take the FixtureDef of the final override and add it to the list.
|
||||
#
|
||||
# If there wasn't an override, the final item will simply be the
|
||||
# first item, as required.
|
||||
fixturedefs[-1]
|
||||
for argname, fixturedefs in sorted(name2fixturedefs.items())
|
||||
if argname not in _get_direct_parametrize_args(test)
|
||||
]
|
||||
return fixturedefs
|
||||
|
||||
|
||||
def show_fixtures_per_test(config):
|
||||
from _pytest.main import wrap_session
|
||||
|
||||
|
@ -1552,7 +1595,21 @@ def _show_fixtures_per_test(config: Config, session: Session) -> None:
|
|||
loc = getlocation(func, invocation_dir)
|
||||
return bestrelpath(invocation_dir, Path(loc))
|
||||
|
||||
def write_fixture(fixture_def: fixtures.FixtureDef[object]) -> None:
|
||||
def write_item(item: nodes.Item) -> None:
|
||||
fixturedefs = _get_fixtures_per_test(item)
|
||||
if not fixturedefs:
|
||||
# This test item does not use any fixtures.
|
||||
# Do not write anything.
|
||||
return
|
||||
|
||||
tw.line()
|
||||
tw.sep("-", f"fixtures used by {item.name}")
|
||||
tw.sep("-", f"({get_best_relpath(item.function)})") # type: ignore[attr-defined]
|
||||
|
||||
for fixturedef in fixturedefs:
|
||||
write_fixture(fixturedef)
|
||||
|
||||
def write_fixture(fixture_def: FixtureDef[object]) -> None:
|
||||
argname = fixture_def.argname
|
||||
if verbose <= 0 and argname.startswith("_"):
|
||||
return
|
||||
|
@ -1568,24 +1625,6 @@ def _show_fixtures_per_test(config: Config, session: Session) -> None:
|
|||
else:
|
||||
tw.line(" no docstring available", red=True)
|
||||
|
||||
def write_item(item: nodes.Item) -> None:
|
||||
# Not all items have _fixtureinfo attribute.
|
||||
info: Optional[FuncFixtureInfo] = getattr(item, "_fixtureinfo", None)
|
||||
if info is None or not info.name2fixturedefs:
|
||||
# This test item does not use any fixtures.
|
||||
return
|
||||
tw.line()
|
||||
tw.sep("-", f"fixtures used by {item.name}")
|
||||
# TODO: Fix this type ignore.
|
||||
tw.sep("-", f"({get_best_relpath(item.function)})") # type: ignore[attr-defined]
|
||||
# dict key not used in loop but needed for sorting.
|
||||
for _, fixturedefs in sorted(info.name2fixturedefs.items()):
|
||||
assert fixturedefs is not None
|
||||
if not fixturedefs:
|
||||
continue
|
||||
# Last item is expected to be the one used by the test item.
|
||||
write_fixture(fixturedefs[-1])
|
||||
|
||||
for session_item in session.items:
|
||||
write_item(session_item)
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
from _pytest.pytester import Pytester
|
||||
|
||||
|
||||
def test_no_items_should_not_show_output(pytester: Pytester) -> None:
|
||||
def test_should_show_no_ouput_when_zero_items(pytester: Pytester) -> None:
|
||||
result = pytester.runpytest("--fixtures-per-test")
|
||||
result.stdout.no_fnmatch_line("*fixtures used by*")
|
||||
assert result.ret == 0
|
||||
|
@ -252,3 +252,74 @@ def test_verbose_include_multiline_docstring(pytester: Pytester) -> None:
|
|||
" Docstring content that extends into a third paragraph.",
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def test_should_not_show_pseudo_fixtures(pytester: Pytester) -> None:
|
||||
"""A fixture is considered pseudo if it was directly created using the
|
||||
``@pytest.mark.parametrize`` decorator as part of internal pytest
|
||||
mechanisms (such as to manage batch execution). These fixtures should not
|
||||
be included in the output because they don't satisfy user expectations for
|
||||
how fixtures are created and used."""
|
||||
p = pytester.makepyfile(
|
||||
"""
|
||||
import pytest
|
||||
|
||||
@pytest.mark.parametrize("x", [1])
|
||||
def test_pseudo_fixture(x):
|
||||
pass
|
||||
"""
|
||||
)
|
||||
result = pytester.runpytest("--fixtures-per-test", p)
|
||||
result.stdout.no_fnmatch_line("*fixtures used by*")
|
||||
assert result.ret == 0
|
||||
|
||||
|
||||
def test_should_show_parametrized_fixtures_used_by_test(pytester: Pytester) -> None:
|
||||
"""A fixture with parameters should be included if it was created using
|
||||
the @pytest.fixture decorator, including those that are indirectly
|
||||
parametrized."""
|
||||
p = pytester.makepyfile(
|
||||
'''
|
||||
import pytest
|
||||
|
||||
@pytest.fixture(params=['a', 'b'])
|
||||
def directly(request):
|
||||
"""parametrized fixture"""
|
||||
return request.param
|
||||
|
||||
@pytest.fixture
|
||||
def indirectly(request):
|
||||
"""indirectly parametrized fixture"""
|
||||
return request.param
|
||||
|
||||
def test_directly_parametrized_fixture(directly):
|
||||
pass
|
||||
|
||||
@pytest.mark.parametrize("indirectly", ["a", "b"], indirect=True)
|
||||
def test_indirectly_parametrized_fixture(indirectly):
|
||||
pass
|
||||
'''
|
||||
)
|
||||
result = pytester.runpytest("--fixtures-per-test", p)
|
||||
assert result.ret == 0
|
||||
|
||||
result.stdout.fnmatch_lines(
|
||||
[
|
||||
"*fixtures used by test_directly_parametrized_fixture*",
|
||||
"*(test_should_show_parametrized_fixtures_used_by_test.py:14)*",
|
||||
"directly -- test_should_show_parametrized_fixtures_used_by_test.py:4",
|
||||
" parametrized fixture",
|
||||
"*fixtures used by test_directly_parametrized_fixture*",
|
||||
"*(test_should_show_parametrized_fixtures_used_by_test.py:14)*",
|
||||
"directly -- test_should_show_parametrized_fixtures_used_by_test.py:4",
|
||||
" parametrized fixture",
|
||||
"*fixtures used by test_indirectly_parametrized_fixture*",
|
||||
"*(test_should_show_parametrized_fixtures_used_by_test.py:17)*",
|
||||
"indirectly -- test_should_show_parametrized_fixtures_used_by_test.py:9",
|
||||
" indirectly parametrized fixture",
|
||||
"*fixtures used by test_indirectly_parametrized_fixture*",
|
||||
"*(test_should_show_parametrized_fixtures_used_by_test.py:17)*",
|
||||
"indirectly -- test_should_show_parametrized_fixtures_used_by_test.py:9",
|
||||
" indirectly parametrized fixture",
|
||||
]
|
||||
)
|
||||
|
|
Loading…
Reference in New Issue