Skip to content

Commit fd3281b

Browse files
committed
Move show-fixtures code from python.py to fixtures.py
It makes more sense, also, we have a long term idea of generalizing fixture support to items defined by other plugins, not just python, in which case `--fixtures` would definitely not be python-plugin specific.
1 parent bdfc5c8 commit fd3281b

File tree

2 files changed

+162
-165
lines changed

2 files changed

+162
-165
lines changed

src/_pytest/fixtures.py

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
from _pytest.compat import safe_getattr
5252
from _pytest.config import _PluggyPlugin
5353
from _pytest.config import Config
54+
from _pytest.config import ExitCode
5455
from _pytest.config.argparsing import Parser
5556
from _pytest.deprecated import check_ispytest
5657
from _pytest.deprecated import MARKED_FIXTURE
@@ -1364,6 +1365,33 @@ def pytest_addoption(parser: Parser) -> None:
13641365
default=[],
13651366
help="List of default fixtures to be used with this project",
13661367
)
1368+
group = parser.getgroup("general")
1369+
group.addoption(
1370+
"--fixtures",
1371+
"--funcargs",
1372+
action="store_true",
1373+
dest="showfixtures",
1374+
default=False,
1375+
help="Show available fixtures, sorted by plugin appearance "
1376+
"(fixtures with leading '_' are only shown with '-v')",
1377+
)
1378+
group.addoption(
1379+
"--fixtures-per-test",
1380+
action="store_true",
1381+
dest="show_fixtures_per_test",
1382+
default=False,
1383+
help="Show fixtures per test",
1384+
)
1385+
1386+
1387+
def pytest_cmdline_main(config: Config) -> Optional[Union[int, ExitCode]]:
1388+
if config.option.showfixtures:
1389+
showfixtures(config)
1390+
return 0
1391+
if config.option.show_fixtures_per_test:
1392+
show_fixtures_per_test(config)
1393+
return 0
1394+
return None
13671395

13681396

13691397
def _get_direct_parametrize_args(node: nodes.Node) -> Set[str]:
@@ -1771,3 +1799,137 @@ def _matchfactories(
17711799
for fixturedef in fixturedefs:
17721800
if fixturedef.baseid in parentnodeids:
17731801
yield fixturedef
1802+
1803+
1804+
def show_fixtures_per_test(config: Config) -> int | ExitCode:
1805+
from _pytest.main import wrap_session
1806+
1807+
return wrap_session(config, _show_fixtures_per_test)
1808+
1809+
1810+
_PYTEST_DIR = Path(_pytest.__file__).parent
1811+
1812+
1813+
def _pretty_fixture_path(invocation_dir: Path, func) -> str:
1814+
loc = Path(getlocation(func, invocation_dir))
1815+
prefix = Path("...", "_pytest")
1816+
try:
1817+
return str(prefix / loc.relative_to(_PYTEST_DIR))
1818+
except ValueError:
1819+
return bestrelpath(invocation_dir, loc)
1820+
1821+
1822+
def _show_fixtures_per_test(config: Config, session: "Session") -> None:
1823+
import _pytest.config
1824+
1825+
session.perform_collect()
1826+
invocation_dir = config.invocation_params.dir
1827+
tw = _pytest.config.create_terminal_writer(config)
1828+
verbose = config.getvalue("verbose")
1829+
1830+
def get_best_relpath(func) -> str:
1831+
loc = getlocation(func, invocation_dir)
1832+
return bestrelpath(invocation_dir, Path(loc))
1833+
1834+
def write_fixture(fixture_def: FixtureDef[object]) -> None:
1835+
argname = fixture_def.argname
1836+
if verbose <= 0 and argname.startswith("_"):
1837+
return
1838+
prettypath = _pretty_fixture_path(invocation_dir, fixture_def.func)
1839+
tw.write(f"{argname}", green=True)
1840+
tw.write(f" -- {prettypath}", yellow=True)
1841+
tw.write("\n")
1842+
fixture_doc = inspect.getdoc(fixture_def.func)
1843+
if fixture_doc:
1844+
write_docstring(
1845+
tw, fixture_doc.split("\n\n")[0] if verbose <= 0 else fixture_doc
1846+
)
1847+
else:
1848+
tw.line(" no docstring available", red=True)
1849+
1850+
def write_item(item: nodes.Item) -> None:
1851+
# Not all items have _fixtureinfo attribute.
1852+
info: Optional[FuncFixtureInfo] = getattr(item, "_fixtureinfo", None)
1853+
if info is None or not info.name2fixturedefs:
1854+
# This test item does not use any fixtures.
1855+
return
1856+
tw.line()
1857+
tw.sep("-", f"fixtures used by {item.name}")
1858+
# TODO: Fix this type ignore.
1859+
tw.sep("-", f"({get_best_relpath(item.function)})") # type: ignore[attr-defined]
1860+
# dict key not used in loop but needed for sorting.
1861+
for _, fixturedefs in sorted(info.name2fixturedefs.items()):
1862+
assert fixturedefs is not None
1863+
if not fixturedefs:
1864+
continue
1865+
# Last item is expected to be the one used by the test item.
1866+
write_fixture(fixturedefs[-1])
1867+
1868+
for session_item in session.items:
1869+
write_item(session_item)
1870+
1871+
1872+
def showfixtures(config: Config) -> Union[int, ExitCode]:
1873+
from _pytest.main import wrap_session
1874+
1875+
return wrap_session(config, _showfixtures_main)
1876+
1877+
1878+
def _showfixtures_main(config: Config, session: "Session") -> None:
1879+
import _pytest.config
1880+
1881+
session.perform_collect()
1882+
invocation_dir = config.invocation_params.dir
1883+
tw = _pytest.config.create_terminal_writer(config)
1884+
verbose = config.getvalue("verbose")
1885+
1886+
fm = session._fixturemanager
1887+
1888+
available = []
1889+
seen: Set[Tuple[str, str]] = set()
1890+
1891+
for argname, fixturedefs in fm._arg2fixturedefs.items():
1892+
assert fixturedefs is not None
1893+
if not fixturedefs:
1894+
continue
1895+
for fixturedef in fixturedefs:
1896+
loc = getlocation(fixturedef.func, invocation_dir)
1897+
if (fixturedef.argname, loc) in seen:
1898+
continue
1899+
seen.add((fixturedef.argname, loc))
1900+
available.append(
1901+
(
1902+
len(fixturedef.baseid),
1903+
fixturedef.func.__module__,
1904+
_pretty_fixture_path(invocation_dir, fixturedef.func),
1905+
fixturedef.argname,
1906+
fixturedef,
1907+
)
1908+
)
1909+
1910+
available.sort()
1911+
currentmodule = None
1912+
for baseid, module, prettypath, argname, fixturedef in available:
1913+
if currentmodule != module:
1914+
if not module.startswith("_pytest."):
1915+
tw.line()
1916+
tw.sep("-", f"fixtures defined from {module}")
1917+
currentmodule = module
1918+
if verbose <= 0 and argname.startswith("_"):
1919+
continue
1920+
tw.write(f"{argname}", green=True)
1921+
if fixturedef.scope != "function":
1922+
tw.write(" [%s scope]" % fixturedef.scope, cyan=True)
1923+
tw.write(f" -- {prettypath}", yellow=True)
1924+
tw.write("\n")
1925+
doc = inspect.getdoc(fixturedef.func)
1926+
if doc:
1927+
write_docstring(tw, doc.split("\n\n")[0] if verbose <= 0 else doc)
1928+
else:
1929+
tw.line(" no docstring available", red=True)
1930+
tw.line()
1931+
1932+
1933+
def write_docstring(tw: TerminalWriter, doc: str, indent: str = " ") -> None:
1934+
for line in doc.split("\n"):
1935+
tw.line(indent + line)

src/_pytest/python.py

Lines changed: 0 additions & 165 deletions
Original file line numberDiff line numberDiff line change
@@ -38,21 +38,18 @@
3838
from _pytest._code.code import ExceptionInfo
3939
from _pytest._code.code import TerminalRepr
4040
from _pytest._code.code import Traceback
41-
from _pytest._io import TerminalWriter
4241
from _pytest._io.saferepr import saferepr
4342
from _pytest.compat import ascii_escaped
4443
from _pytest.compat import get_default_arg_names
4544
from _pytest.compat import get_real_func
4645
from _pytest.compat import getimfunc
47-
from _pytest.compat import getlocation
4846
from _pytest.compat import is_async_function
4947
from _pytest.compat import is_generator
5048
from _pytest.compat import NOTSET
5149
from _pytest.compat import safe_getattr
5250
from _pytest.compat import safe_isclass
5351
from _pytest.compat import STRING_TYPES
5452
from _pytest.config import Config
55-
from _pytest.config import ExitCode
5653
from _pytest.config import hookimpl
5754
from _pytest.config.argparsing import Parser
5855
from _pytest.deprecated import check_ispytest
@@ -69,7 +66,6 @@
6966
from _pytest.mark.structures import normalize_mark_list
7067
from _pytest.outcomes import fail
7168
from _pytest.outcomes import skip
72-
from _pytest.pathlib import bestrelpath
7369
from _pytest.pathlib import fnmatch_ex
7470
from _pytest.pathlib import import_path
7571
from _pytest.pathlib import ImportPathMismatchError
@@ -82,27 +78,7 @@
8278
from _pytest.warning_types import PytestUnhandledCoroutineWarning
8379

8480

85-
_PYTEST_DIR = Path(_pytest.__file__).parent
86-
87-
8881
def pytest_addoption(parser: Parser) -> None:
89-
group = parser.getgroup("general")
90-
group.addoption(
91-
"--fixtures",
92-
"--funcargs",
93-
action="store_true",
94-
dest="showfixtures",
95-
default=False,
96-
help="Show available fixtures, sorted by plugin appearance "
97-
"(fixtures with leading '_' are only shown with '-v')",
98-
)
99-
group.addoption(
100-
"--fixtures-per-test",
101-
action="store_true",
102-
dest="show_fixtures_per_test",
103-
default=False,
104-
help="Show fixtures per test",
105-
)
10682
parser.addini(
10783
"python_files",
10884
type="args",
@@ -131,16 +107,6 @@ def pytest_addoption(parser: Parser) -> None:
131107
)
132108

133109

134-
def pytest_cmdline_main(config: Config) -> Optional[Union[int, ExitCode]]:
135-
if config.option.showfixtures:
136-
showfixtures(config)
137-
return 0
138-
if config.option.show_fixtures_per_test:
139-
show_fixtures_per_test(config)
140-
return 0
141-
return None
142-
143-
144110
def pytest_generate_tests(metafunc: "Metafunc") -> None:
145111
for marker in metafunc.definition.iter_markers(name="parametrize"):
146112
metafunc.parametrize(*marker.args, **marker.kwargs, _param_mark=marker)
@@ -1519,137 +1485,6 @@ def _ascii_escaped_by_config(val: Union[str, bytes], config: Optional[Config]) -
15191485
return val if escape_option else ascii_escaped(val) # type: ignore
15201486

15211487

1522-
def _pretty_fixture_path(invocation_dir: Path, func) -> str:
1523-
loc = Path(getlocation(func, invocation_dir))
1524-
prefix = Path("...", "_pytest")
1525-
try:
1526-
return str(prefix / loc.relative_to(_PYTEST_DIR))
1527-
except ValueError:
1528-
return bestrelpath(invocation_dir, loc)
1529-
1530-
1531-
def show_fixtures_per_test(config):
1532-
from _pytest.main import wrap_session
1533-
1534-
return wrap_session(config, _show_fixtures_per_test)
1535-
1536-
1537-
def _show_fixtures_per_test(config: Config, session: Session) -> None:
1538-
import _pytest.config
1539-
1540-
session.perform_collect()
1541-
invocation_dir = config.invocation_params.dir
1542-
tw = _pytest.config.create_terminal_writer(config)
1543-
verbose = config.getvalue("verbose")
1544-
1545-
def get_best_relpath(func) -> str:
1546-
loc = getlocation(func, invocation_dir)
1547-
return bestrelpath(invocation_dir, Path(loc))
1548-
1549-
def write_fixture(fixture_def: fixtures.FixtureDef[object]) -> None:
1550-
argname = fixture_def.argname
1551-
if verbose <= 0 and argname.startswith("_"):
1552-
return
1553-
prettypath = _pretty_fixture_path(invocation_dir, fixture_def.func)
1554-
tw.write(f"{argname}", green=True)
1555-
tw.write(f" -- {prettypath}", yellow=True)
1556-
tw.write("\n")
1557-
fixture_doc = inspect.getdoc(fixture_def.func)
1558-
if fixture_doc:
1559-
write_docstring(
1560-
tw, fixture_doc.split("\n\n")[0] if verbose <= 0 else fixture_doc
1561-
)
1562-
else:
1563-
tw.line(" no docstring available", red=True)
1564-
1565-
def write_item(item: nodes.Item) -> None:
1566-
# Not all items have _fixtureinfo attribute.
1567-
info: Optional[FuncFixtureInfo] = getattr(item, "_fixtureinfo", None)
1568-
if info is None or not info.name2fixturedefs:
1569-
# This test item does not use any fixtures.
1570-
return
1571-
tw.line()
1572-
tw.sep("-", f"fixtures used by {item.name}")
1573-
# TODO: Fix this type ignore.
1574-
tw.sep("-", f"({get_best_relpath(item.function)})") # type: ignore[attr-defined]
1575-
# dict key not used in loop but needed for sorting.
1576-
for _, fixturedefs in sorted(info.name2fixturedefs.items()):
1577-
assert fixturedefs is not None
1578-
if not fixturedefs:
1579-
continue
1580-
# Last item is expected to be the one used by the test item.
1581-
write_fixture(fixturedefs[-1])
1582-
1583-
for session_item in session.items:
1584-
write_item(session_item)
1585-
1586-
1587-
def showfixtures(config: Config) -> Union[int, ExitCode]:
1588-
from _pytest.main import wrap_session
1589-
1590-
return wrap_session(config, _showfixtures_main)
1591-
1592-
1593-
def _showfixtures_main(config: Config, session: Session) -> None:
1594-
import _pytest.config
1595-
1596-
session.perform_collect()
1597-
invocation_dir = config.invocation_params.dir
1598-
tw = _pytest.config.create_terminal_writer(config)
1599-
verbose = config.getvalue("verbose")
1600-
1601-
fm = session._fixturemanager
1602-
1603-
available = []
1604-
seen: Set[Tuple[str, str]] = set()
1605-
1606-
for argname, fixturedefs in fm._arg2fixturedefs.items():
1607-
assert fixturedefs is not None
1608-
if not fixturedefs:
1609-
continue
1610-
for fixturedef in fixturedefs:
1611-
loc = getlocation(fixturedef.func, invocation_dir)
1612-
if (fixturedef.argname, loc) in seen:
1613-
continue
1614-
seen.add((fixturedef.argname, loc))
1615-
available.append(
1616-
(
1617-
len(fixturedef.baseid),
1618-
fixturedef.func.__module__,
1619-
_pretty_fixture_path(invocation_dir, fixturedef.func),
1620-
fixturedef.argname,
1621-
fixturedef,
1622-
)
1623-
)
1624-
1625-
available.sort()
1626-
currentmodule = None
1627-
for baseid, module, prettypath, argname, fixturedef in available:
1628-
if currentmodule != module:
1629-
if not module.startswith("_pytest."):
1630-
tw.line()
1631-
tw.sep("-", f"fixtures defined from {module}")
1632-
currentmodule = module
1633-
if verbose <= 0 and argname.startswith("_"):
1634-
continue
1635-
tw.write(f"{argname}", green=True)
1636-
if fixturedef.scope != "function":
1637-
tw.write(" [%s scope]" % fixturedef.scope, cyan=True)
1638-
tw.write(f" -- {prettypath}", yellow=True)
1639-
tw.write("\n")
1640-
doc = inspect.getdoc(fixturedef.func)
1641-
if doc:
1642-
write_docstring(tw, doc.split("\n\n")[0] if verbose <= 0 else doc)
1643-
else:
1644-
tw.line(" no docstring available", red=True)
1645-
tw.line()
1646-
1647-
1648-
def write_docstring(tw: TerminalWriter, doc: str, indent: str = " ") -> None:
1649-
for line in doc.split("\n"):
1650-
tw.line(indent + line)
1651-
1652-
16531488
class Function(PyobjMixin, nodes.Item):
16541489
"""Item responsible for setting up and executing a Python test function.
16551490

0 commit comments

Comments
 (0)