Merge pull request #11910 from bluetech/move-show-fixtures
Move show-fixtures code from python.py to fixtures.py
This commit is contained in:
commit
b63bea9523
|
@ -51,6 +51,7 @@ from _pytest.compat import NotSetType
|
||||||
from _pytest.compat import safe_getattr
|
from _pytest.compat import safe_getattr
|
||||||
from _pytest.config import _PluggyPlugin
|
from _pytest.config import _PluggyPlugin
|
||||||
from _pytest.config import Config
|
from _pytest.config import Config
|
||||||
|
from _pytest.config import ExitCode
|
||||||
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.deprecated import MARKED_FIXTURE
|
from _pytest.deprecated import MARKED_FIXTURE
|
||||||
|
@ -1365,6 +1366,33 @@ def pytest_addoption(parser: Parser) -> None:
|
||||||
default=[],
|
default=[],
|
||||||
help="List of default fixtures to be used with this project",
|
help="List of default fixtures to be used with this project",
|
||||||
)
|
)
|
||||||
|
group = parser.getgroup("general")
|
||||||
|
group.addoption(
|
||||||
|
"--fixtures",
|
||||||
|
"--funcargs",
|
||||||
|
action="store_true",
|
||||||
|
dest="showfixtures",
|
||||||
|
default=False,
|
||||||
|
help="Show available fixtures, sorted by plugin appearance "
|
||||||
|
"(fixtures with leading '_' are only shown with '-v')",
|
||||||
|
)
|
||||||
|
group.addoption(
|
||||||
|
"--fixtures-per-test",
|
||||||
|
action="store_true",
|
||||||
|
dest="show_fixtures_per_test",
|
||||||
|
default=False,
|
||||||
|
help="Show fixtures per test",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def pytest_cmdline_main(config: Config) -> Optional[Union[int, ExitCode]]:
|
||||||
|
if config.option.showfixtures:
|
||||||
|
showfixtures(config)
|
||||||
|
return 0
|
||||||
|
if config.option.show_fixtures_per_test:
|
||||||
|
show_fixtures_per_test(config)
|
||||||
|
return 0
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def _get_direct_parametrize_args(node: nodes.Node) -> Set[str]:
|
def _get_direct_parametrize_args(node: nodes.Node) -> Set[str]:
|
||||||
|
@ -1761,3 +1789,137 @@ class FixtureManager:
|
||||||
for fixturedef in fixturedefs:
|
for fixturedef in fixturedefs:
|
||||||
if fixturedef.baseid in parentnodeids:
|
if fixturedef.baseid in parentnodeids:
|
||||||
yield fixturedef
|
yield fixturedef
|
||||||
|
|
||||||
|
|
||||||
|
def show_fixtures_per_test(config: Config) -> Union[int, ExitCode]:
|
||||||
|
from _pytest.main import wrap_session
|
||||||
|
|
||||||
|
return wrap_session(config, _show_fixtures_per_test)
|
||||||
|
|
||||||
|
|
||||||
|
_PYTEST_DIR = Path(_pytest.__file__).parent
|
||||||
|
|
||||||
|
|
||||||
|
def _pretty_fixture_path(invocation_dir: Path, func) -> str:
|
||||||
|
loc = Path(getlocation(func, invocation_dir))
|
||||||
|
prefix = Path("...", "_pytest")
|
||||||
|
try:
|
||||||
|
return str(prefix / loc.relative_to(_PYTEST_DIR))
|
||||||
|
except ValueError:
|
||||||
|
return bestrelpath(invocation_dir, loc)
|
||||||
|
|
||||||
|
|
||||||
|
def _show_fixtures_per_test(config: Config, session: "Session") -> None:
|
||||||
|
import _pytest.config
|
||||||
|
|
||||||
|
session.perform_collect()
|
||||||
|
invocation_dir = config.invocation_params.dir
|
||||||
|
tw = _pytest.config.create_terminal_writer(config)
|
||||||
|
verbose = config.getvalue("verbose")
|
||||||
|
|
||||||
|
def get_best_relpath(func) -> str:
|
||||||
|
loc = getlocation(func, invocation_dir)
|
||||||
|
return bestrelpath(invocation_dir, Path(loc))
|
||||||
|
|
||||||
|
def write_fixture(fixture_def: FixtureDef[object]) -> None:
|
||||||
|
argname = fixture_def.argname
|
||||||
|
if verbose <= 0 and argname.startswith("_"):
|
||||||
|
return
|
||||||
|
prettypath = _pretty_fixture_path(invocation_dir, fixture_def.func)
|
||||||
|
tw.write(f"{argname}", green=True)
|
||||||
|
tw.write(f" -- {prettypath}", yellow=True)
|
||||||
|
tw.write("\n")
|
||||||
|
fixture_doc = inspect.getdoc(fixture_def.func)
|
||||||
|
if fixture_doc:
|
||||||
|
write_docstring(
|
||||||
|
tw, fixture_doc.split("\n\n")[0] if verbose <= 0 else fixture_doc
|
||||||
|
)
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
|
def showfixtures(config: Config) -> Union[int, ExitCode]:
|
||||||
|
from _pytest.main import wrap_session
|
||||||
|
|
||||||
|
return wrap_session(config, _showfixtures_main)
|
||||||
|
|
||||||
|
|
||||||
|
def _showfixtures_main(config: Config, session: "Session") -> None:
|
||||||
|
import _pytest.config
|
||||||
|
|
||||||
|
session.perform_collect()
|
||||||
|
invocation_dir = config.invocation_params.dir
|
||||||
|
tw = _pytest.config.create_terminal_writer(config)
|
||||||
|
verbose = config.getvalue("verbose")
|
||||||
|
|
||||||
|
fm = session._fixturemanager
|
||||||
|
|
||||||
|
available = []
|
||||||
|
seen: Set[Tuple[str, str]] = set()
|
||||||
|
|
||||||
|
for argname, fixturedefs in fm._arg2fixturedefs.items():
|
||||||
|
assert fixturedefs is not None
|
||||||
|
if not fixturedefs:
|
||||||
|
continue
|
||||||
|
for fixturedef in fixturedefs:
|
||||||
|
loc = getlocation(fixturedef.func, invocation_dir)
|
||||||
|
if (fixturedef.argname, loc) in seen:
|
||||||
|
continue
|
||||||
|
seen.add((fixturedef.argname, loc))
|
||||||
|
available.append(
|
||||||
|
(
|
||||||
|
len(fixturedef.baseid),
|
||||||
|
fixturedef.func.__module__,
|
||||||
|
_pretty_fixture_path(invocation_dir, fixturedef.func),
|
||||||
|
fixturedef.argname,
|
||||||
|
fixturedef,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
available.sort()
|
||||||
|
currentmodule = None
|
||||||
|
for baseid, module, prettypath, argname, fixturedef in available:
|
||||||
|
if currentmodule != module:
|
||||||
|
if not module.startswith("_pytest."):
|
||||||
|
tw.line()
|
||||||
|
tw.sep("-", f"fixtures defined from {module}")
|
||||||
|
currentmodule = module
|
||||||
|
if verbose <= 0 and argname.startswith("_"):
|
||||||
|
continue
|
||||||
|
tw.write(f"{argname}", green=True)
|
||||||
|
if fixturedef.scope != "function":
|
||||||
|
tw.write(" [%s scope]" % fixturedef.scope, cyan=True)
|
||||||
|
tw.write(f" -- {prettypath}", yellow=True)
|
||||||
|
tw.write("\n")
|
||||||
|
doc = inspect.getdoc(fixturedef.func)
|
||||||
|
if doc:
|
||||||
|
write_docstring(tw, doc.split("\n\n")[0] if verbose <= 0 else doc)
|
||||||
|
else:
|
||||||
|
tw.line(" no docstring available", red=True)
|
||||||
|
tw.line()
|
||||||
|
|
||||||
|
|
||||||
|
def write_docstring(tw: TerminalWriter, doc: str, indent: str = " ") -> None:
|
||||||
|
for line in doc.split("\n"):
|
||||||
|
tw.line(indent + line)
|
||||||
|
|
|
@ -40,13 +40,11 @@ from _pytest._code import getfslineno
|
||||||
from _pytest._code.code import ExceptionInfo
|
from _pytest._code.code import ExceptionInfo
|
||||||
from _pytest._code.code import TerminalRepr
|
from _pytest._code.code import TerminalRepr
|
||||||
from _pytest._code.code import Traceback
|
from _pytest._code.code import Traceback
|
||||||
from _pytest._io import TerminalWriter
|
|
||||||
from _pytest._io.saferepr import saferepr
|
from _pytest._io.saferepr import saferepr
|
||||||
from _pytest.compat import ascii_escaped
|
from _pytest.compat import ascii_escaped
|
||||||
from _pytest.compat import get_default_arg_names
|
from _pytest.compat import get_default_arg_names
|
||||||
from _pytest.compat import get_real_func
|
from _pytest.compat import get_real_func
|
||||||
from _pytest.compat import getimfunc
|
from _pytest.compat import getimfunc
|
||||||
from _pytest.compat import getlocation
|
|
||||||
from _pytest.compat import is_async_function
|
from _pytest.compat import is_async_function
|
||||||
from _pytest.compat import is_generator
|
from _pytest.compat import is_generator
|
||||||
from _pytest.compat import LEGACY_PATH
|
from _pytest.compat import LEGACY_PATH
|
||||||
|
@ -54,7 +52,6 @@ from _pytest.compat import NOTSET
|
||||||
from _pytest.compat import safe_getattr
|
from _pytest.compat import safe_getattr
|
||||||
from _pytest.compat import safe_isclass
|
from _pytest.compat import safe_isclass
|
||||||
from _pytest.config import Config
|
from _pytest.config import Config
|
||||||
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
|
||||||
|
@ -71,7 +68,6 @@ from _pytest.mark.structures import MarkDecorator
|
||||||
from _pytest.mark.structures import normalize_mark_list
|
from _pytest.mark.structures import normalize_mark_list
|
||||||
from _pytest.outcomes import fail
|
from _pytest.outcomes import fail
|
||||||
from _pytest.outcomes import skip
|
from _pytest.outcomes import skip
|
||||||
from _pytest.pathlib import bestrelpath
|
|
||||||
from _pytest.pathlib import fnmatch_ex
|
from _pytest.pathlib import fnmatch_ex
|
||||||
from _pytest.pathlib import import_path
|
from _pytest.pathlib import import_path
|
||||||
from _pytest.pathlib import ImportPathMismatchError
|
from _pytest.pathlib import ImportPathMismatchError
|
||||||
|
@ -88,27 +84,7 @@ if TYPE_CHECKING:
|
||||||
from typing import Self
|
from typing import Self
|
||||||
|
|
||||||
|
|
||||||
_PYTEST_DIR = Path(_pytest.__file__).parent
|
|
||||||
|
|
||||||
|
|
||||||
def pytest_addoption(parser: Parser) -> None:
|
def pytest_addoption(parser: Parser) -> None:
|
||||||
group = parser.getgroup("general")
|
|
||||||
group.addoption(
|
|
||||||
"--fixtures",
|
|
||||||
"--funcargs",
|
|
||||||
action="store_true",
|
|
||||||
dest="showfixtures",
|
|
||||||
default=False,
|
|
||||||
help="Show available fixtures, sorted by plugin appearance "
|
|
||||||
"(fixtures with leading '_' are only shown with '-v')",
|
|
||||||
)
|
|
||||||
group.addoption(
|
|
||||||
"--fixtures-per-test",
|
|
||||||
action="store_true",
|
|
||||||
dest="show_fixtures_per_test",
|
|
||||||
default=False,
|
|
||||||
help="Show fixtures per test",
|
|
||||||
)
|
|
||||||
parser.addini(
|
parser.addini(
|
||||||
"python_files",
|
"python_files",
|
||||||
type="args",
|
type="args",
|
||||||
|
@ -137,16 +113,6 @@ def pytest_addoption(parser: Parser) -> None:
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def pytest_cmdline_main(config: Config) -> Optional[Union[int, ExitCode]]:
|
|
||||||
if config.option.showfixtures:
|
|
||||||
showfixtures(config)
|
|
||||||
return 0
|
|
||||||
if config.option.show_fixtures_per_test:
|
|
||||||
show_fixtures_per_test(config)
|
|
||||||
return 0
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def pytest_generate_tests(metafunc: "Metafunc") -> None:
|
def pytest_generate_tests(metafunc: "Metafunc") -> None:
|
||||||
for marker in metafunc.definition.iter_markers(name="parametrize"):
|
for marker in metafunc.definition.iter_markers(name="parametrize"):
|
||||||
metafunc.parametrize(*marker.args, **marker.kwargs, _param_mark=marker)
|
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]) -
|
||||||
return val if escape_option else ascii_escaped(val) # type: ignore
|
return val if escape_option else ascii_escaped(val) # type: ignore
|
||||||
|
|
||||||
|
|
||||||
def _pretty_fixture_path(invocation_dir: Path, func) -> str:
|
|
||||||
loc = Path(getlocation(func, invocation_dir))
|
|
||||||
prefix = Path("...", "_pytest")
|
|
||||||
try:
|
|
||||||
return str(prefix / loc.relative_to(_PYTEST_DIR))
|
|
||||||
except ValueError:
|
|
||||||
return bestrelpath(invocation_dir, loc)
|
|
||||||
|
|
||||||
|
|
||||||
def show_fixtures_per_test(config):
|
|
||||||
from _pytest.main import wrap_session
|
|
||||||
|
|
||||||
return wrap_session(config, _show_fixtures_per_test)
|
|
||||||
|
|
||||||
|
|
||||||
def _show_fixtures_per_test(config: Config, session: Session) -> None:
|
|
||||||
import _pytest.config
|
|
||||||
|
|
||||||
session.perform_collect()
|
|
||||||
invocation_dir = config.invocation_params.dir
|
|
||||||
tw = _pytest.config.create_terminal_writer(config)
|
|
||||||
verbose = config.getvalue("verbose")
|
|
||||||
|
|
||||||
def get_best_relpath(func) -> str:
|
|
||||||
loc = getlocation(func, invocation_dir)
|
|
||||||
return bestrelpath(invocation_dir, Path(loc))
|
|
||||||
|
|
||||||
def write_fixture(fixture_def: fixtures.FixtureDef[object]) -> None:
|
|
||||||
argname = fixture_def.argname
|
|
||||||
if verbose <= 0 and argname.startswith("_"):
|
|
||||||
return
|
|
||||||
prettypath = _pretty_fixture_path(invocation_dir, fixture_def.func)
|
|
||||||
tw.write(f"{argname}", green=True)
|
|
||||||
tw.write(f" -- {prettypath}", yellow=True)
|
|
||||||
tw.write("\n")
|
|
||||||
fixture_doc = inspect.getdoc(fixture_def.func)
|
|
||||||
if fixture_doc:
|
|
||||||
write_docstring(
|
|
||||||
tw, fixture_doc.split("\n\n")[0] if verbose <= 0 else fixture_doc
|
|
||||||
)
|
|
||||||
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)
|
|
||||||
|
|
||||||
|
|
||||||
def showfixtures(config: Config) -> Union[int, ExitCode]:
|
|
||||||
from _pytest.main import wrap_session
|
|
||||||
|
|
||||||
return wrap_session(config, _showfixtures_main)
|
|
||||||
|
|
||||||
|
|
||||||
def _showfixtures_main(config: Config, session: Session) -> None:
|
|
||||||
import _pytest.config
|
|
||||||
|
|
||||||
session.perform_collect()
|
|
||||||
invocation_dir = config.invocation_params.dir
|
|
||||||
tw = _pytest.config.create_terminal_writer(config)
|
|
||||||
verbose = config.getvalue("verbose")
|
|
||||||
|
|
||||||
fm = session._fixturemanager
|
|
||||||
|
|
||||||
available = []
|
|
||||||
seen: Set[Tuple[str, str]] = set()
|
|
||||||
|
|
||||||
for argname, fixturedefs in fm._arg2fixturedefs.items():
|
|
||||||
assert fixturedefs is not None
|
|
||||||
if not fixturedefs:
|
|
||||||
continue
|
|
||||||
for fixturedef in fixturedefs:
|
|
||||||
loc = getlocation(fixturedef.func, invocation_dir)
|
|
||||||
if (fixturedef.argname, loc) in seen:
|
|
||||||
continue
|
|
||||||
seen.add((fixturedef.argname, loc))
|
|
||||||
available.append(
|
|
||||||
(
|
|
||||||
len(fixturedef.baseid),
|
|
||||||
fixturedef.func.__module__,
|
|
||||||
_pretty_fixture_path(invocation_dir, fixturedef.func),
|
|
||||||
fixturedef.argname,
|
|
||||||
fixturedef,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
available.sort()
|
|
||||||
currentmodule = None
|
|
||||||
for baseid, module, prettypath, argname, fixturedef in available:
|
|
||||||
if currentmodule != module:
|
|
||||||
if not module.startswith("_pytest."):
|
|
||||||
tw.line()
|
|
||||||
tw.sep("-", f"fixtures defined from {module}")
|
|
||||||
currentmodule = module
|
|
||||||
if verbose <= 0 and argname.startswith("_"):
|
|
||||||
continue
|
|
||||||
tw.write(f"{argname}", green=True)
|
|
||||||
if fixturedef.scope != "function":
|
|
||||||
tw.write(" [%s scope]" % fixturedef.scope, cyan=True)
|
|
||||||
tw.write(f" -- {prettypath}", yellow=True)
|
|
||||||
tw.write("\n")
|
|
||||||
doc = inspect.getdoc(fixturedef.func)
|
|
||||||
if doc:
|
|
||||||
write_docstring(tw, doc.split("\n\n")[0] if verbose <= 0 else doc)
|
|
||||||
else:
|
|
||||||
tw.line(" no docstring available", red=True)
|
|
||||||
tw.line()
|
|
||||||
|
|
||||||
|
|
||||||
def write_docstring(tw: TerminalWriter, doc: str, indent: str = " ") -> None:
|
|
||||||
for line in doc.split("\n"):
|
|
||||||
tw.line(indent + line)
|
|
||||||
|
|
||||||
|
|
||||||
class Function(PyobjMixin, nodes.Item):
|
class Function(PyobjMixin, nodes.Item):
|
||||||
"""Item responsible for setting up and executing a Python test function.
|
"""Item responsible for setting up and executing a Python test function.
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue