Merge ba38c1b8a2
into 381593ccf0
This commit is contained in:
commit
d06123383a
1
AUTHORS
1
AUTHORS
|
@ -423,6 +423,7 @@ Vlad Radziuk
|
||||||
Vladyslav Rachek
|
Vladyslav Rachek
|
||||||
Volodymyr Kochetkov
|
Volodymyr Kochetkov
|
||||||
Volodymyr Piskun
|
Volodymyr Piskun
|
||||||
|
Warren Markham
|
||||||
Wei Lin
|
Wei Lin
|
||||||
Wil Cooley
|
Wil Cooley
|
||||||
William Lee
|
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 import hookimpl
|
||||||
from _pytest.config.argparsing import Parser
|
from _pytest.config.argparsing import Parser
|
||||||
from _pytest.deprecated import check_ispytest
|
from _pytest.deprecated import check_ispytest
|
||||||
|
from _pytest.fixtures import _get_direct_parametrize_args
|
||||||
from _pytest.fixtures import FixtureDef
|
from _pytest.fixtures import FixtureDef
|
||||||
from _pytest.fixtures import FixtureRequest
|
from _pytest.fixtures import FixtureRequest
|
||||||
from _pytest.fixtures import FuncFixtureInfo
|
from _pytest.fixtures import FuncFixtureInfo
|
||||||
|
@ -1482,7 +1483,7 @@ class Metafunc:
|
||||||
|
|
||||||
def _find_parametrized_scope(
|
def _find_parametrized_scope(
|
||||||
argnames: Sequence[str],
|
argnames: Sequence[str],
|
||||||
arg2fixturedefs: Mapping[str, Sequence[fixtures.FixtureDef[object]]],
|
arg2fixturedefs: Mapping[str, Sequence[FixtureDef[object]]],
|
||||||
indirect: Union[bool, Sequence[str]],
|
indirect: Union[bool, Sequence[str]],
|
||||||
) -> Scope:
|
) -> Scope:
|
||||||
"""Find the most appropriate scope for a parametrized call based on its arguments.
|
"""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)
|
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):
|
def show_fixtures_per_test(config):
|
||||||
from _pytest.main import wrap_session
|
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)
|
loc = getlocation(func, invocation_dir)
|
||||||
return bestrelpath(invocation_dir, Path(loc))
|
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
|
argname = fixture_def.argname
|
||||||
if verbose <= 0 and argname.startswith("_"):
|
if verbose <= 0 and argname.startswith("_"):
|
||||||
return
|
return
|
||||||
|
@ -1568,24 +1625,6 @@ def _show_fixtures_per_test(config: Config, session: Session) -> None:
|
||||||
else:
|
else:
|
||||||
tw.line(" no docstring available", red=True)
|
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:
|
for session_item in session.items:
|
||||||
write_item(session_item)
|
write_item(session_item)
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
from _pytest.pytester import Pytester
|
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 = pytester.runpytest("--fixtures-per-test")
|
||||||
result.stdout.no_fnmatch_line("*fixtures used by*")
|
result.stdout.no_fnmatch_line("*fixtures used by*")
|
||||||
assert result.ret == 0
|
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.",
|
" 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