Skip to content

Commit 879e105

Browse files
bluetechflying-sheep
authored andcommitted
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 213155d commit 879e105

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
@@ -1365,6 +1366,33 @@ def pytest_addoption(parser: Parser) -> None:
13651366
default=[],
13661367
help="List of default fixtures to be used with this project",
13671368
)
1369+
group = parser.getgroup("general")
1370+
group.addoption(
1371+
"--fixtures",
1372+
"--funcargs",
1373+
action="store_true",
1374+
dest="showfixtures",
1375+
default=False,
1376+
help="Show available fixtures, sorted by plugin appearance "
1377+
"(fixtures with leading '_' are only shown with '-v')",
1378+
)
1379+
group.addoption(
1380+
"--fixtures-per-test",
1381+
action="store_true",
1382+
dest="show_fixtures_per_test",
1383+
default=False,
1384+
help="Show fixtures per test",
1385+
)
1386+
1387+
1388+
def pytest_cmdline_main(config: Config) -> Optional[Union[int, ExitCode]]:
1389+
if config.option.showfixtures:
1390+
showfixtures(config)
1391+
return 0
1392+
if config.option.show_fixtures_per_test:
1393+
show_fixtures_per_test(config)
1394+
return 0
1395+
return None
13681396

13691397

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

src/_pytest/python.py

Lines changed: 0 additions & 165 deletions
Original file line numberDiff line numberDiff line change
@@ -40,21 +40,18 @@
4040
from _pytest._code.code import ExceptionInfo
4141
from _pytest._code.code import TerminalRepr
4242
from _pytest._code.code import Traceback
43-
from _pytest._io import TerminalWriter
4443
from _pytest._io.saferepr import saferepr
4544
from _pytest.compat import ascii_escaped
4645
from _pytest.compat import get_default_arg_names
4746
from _pytest.compat import get_real_func
4847
from _pytest.compat import getimfunc
49-
from _pytest.compat import getlocation
5048
from _pytest.compat import is_async_function
5149
from _pytest.compat import is_generator
5250
from _pytest.compat import LEGACY_PATH
5351
from _pytest.compat import NOTSET
5452
from _pytest.compat import safe_getattr
5553
from _pytest.compat import safe_isclass
5654
from _pytest.config import Config
57-
from _pytest.config import ExitCode
5855
from _pytest.config import hookimpl
5956
from _pytest.config.argparsing import Parser
6057
from _pytest.deprecated import check_ispytest
@@ -71,7 +68,6 @@
7168
from _pytest.mark.structures import normalize_mark_list
7269
from _pytest.outcomes import fail
7370
from _pytest.outcomes import skip
74-
from _pytest.pathlib import bestrelpath
7571
from _pytest.pathlib import fnmatch_ex
7672
from _pytest.pathlib import import_path
7773
from _pytest.pathlib import ImportPathMismatchError
@@ -88,27 +84,7 @@
8884
from typing import Self
8985

9086

91-
_PYTEST_DIR = Path(_pytest.__file__).parent
92-
93-
9487
def pytest_addoption(parser: Parser) -> None:
95-
group = parser.getgroup("general")
96-
group.addoption(
97-
"--fixtures",
98-
"--funcargs",
99-
action="store_true",
100-
dest="showfixtures",
101-
default=False,
102-
help="Show available fixtures, sorted by plugin appearance "
103-
"(fixtures with leading '_' are only shown with '-v')",
104-
)
105-
group.addoption(
106-
"--fixtures-per-test",
107-
action="store_true",
108-
dest="show_fixtures_per_test",
109-
default=False,
110-
help="Show fixtures per test",
111-
)
11288
parser.addini(
11389
"python_files",
11490
type="args",
@@ -137,16 +113,6 @@ def pytest_addoption(parser: Parser) -> None:
137113
)
138114

139115

140-
def pytest_cmdline_main(config: Config) -> Optional[Union[int, ExitCode]]:
141-
if config.option.showfixtures:
142-
showfixtures(config)
143-
return 0
144-
if config.option.show_fixtures_per_test:
145-
show_fixtures_per_test(config)
146-
return 0
147-
return None
148-
149-
150116
def pytest_generate_tests(metafunc: "Metafunc") -> None:
151117
for marker in metafunc.definition.iter_markers(name="parametrize"):
152118
metafunc.parametrize(*marker.args, **marker.kwargs, _param_mark=marker)
@@ -1525,137 +1491,6 @@ def _ascii_escaped_by_config(val: Union[str, bytes], config: Optional[Config]) -
15251491
return val if escape_option else ascii_escaped(val) # type: ignore
15261492

15271493

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

0 commit comments

Comments
 (0)