This commit is contained in:
Ronny Pfannschmidt 2023-06-28 09:34:29 +03:00 committed by GitHub
commit c73a1004b2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 40 additions and 14 deletions

View File

@ -13,6 +13,7 @@ from pathlib import Path
from typing import Any from typing import Any
from typing import Callable from typing import Callable
from typing import Generic from typing import Generic
from typing import NamedTuple
from typing import NoReturn from typing import NoReturn
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from typing import TypeVar from typing import TypeVar
@ -93,18 +94,36 @@ def is_async_function(func: object) -> bool:
return iscoroutinefunction(func) or inspect.isasyncgenfunction(func) return iscoroutinefunction(func) or inspect.isasyncgenfunction(func)
def getlocation(function, curdir: str | None = None) -> str: class CodeLocation(NamedTuple):
path: Path
lineno: int
def __str__(self: CodeLocation) -> str:
"""Python 3.6.0 hack for NamedTuple __str__"""
return f"{self.path}:{self.lineno}"
def getlocation(
function, *, relative_to: Path | None, allow_escape: bool
) -> CodeLocation:
function = get_real_func(function) function = get_real_func(function)
fn = Path(inspect.getfile(function)) fn = Path(inspect.getfile(function))
lineno = function.__code__.co_firstlineno lineno = function.__code__.co_firstlineno
if curdir is not None:
# TODO: this cycle indicates a larger issue
from .pathlib import bestrelpath
if relative_to is not None:
try: try:
relfn = fn.relative_to(curdir) if allow_escape:
relfn = Path(bestrelpath(relative_to, fn))
else:
relfn = relative_to.relative_to(relative_to)
except ValueError: except ValueError:
pass pass
else: else:
return "%s:%d" % (relfn, lineno + 1) return CodeLocation(relfn, lineno + 1)
return "%s:%d" % (fn, lineno + 1) return CodeLocation(fn, lineno + 1)
def num_mock_patch_args(function) -> int: def num_mock_patch_args(function) -> int:

View File

@ -676,7 +676,11 @@ class FixtureRequest:
"\n\nRequested here:\n{}:{}".format( "\n\nRequested here:\n{}:{}".format(
funcitem.nodeid, funcitem.nodeid,
fixturedef.argname, fixturedef.argname,
getlocation(fixturedef.func, funcitem.config.rootpath), getlocation(
fixturedef.func,
relative_to=funcitem.config.rootpath,
allow_escape=True,
),
source_path_str, source_path_str,
source_lineno, source_lineno,
) )
@ -1207,7 +1211,7 @@ class FixtureFunctionMarker:
name = self.name or function.__name__ name = self.name or function.__name__
if name == "request": if name == "request":
location = getlocation(function) location = getlocation(function, relative_to=None, allow_escape=True)
fail( fail(
"'request' is a reserved word for fixtures, use another name:\n {}".format( "'request' is a reserved word for fixtures, use another name:\n {}".format(
location location

View File

@ -40,6 +40,7 @@ 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 assert_never from _pytest.compat import assert_never
from _pytest.compat import CodeLocation
from _pytest.compat import final from _pytest.compat import final
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
@ -1544,7 +1545,7 @@ def _ascii_escaped_by_config(val: Union[str, bytes], config: Optional[Config]) -
def _pretty_fixture_path(func) -> str: def _pretty_fixture_path(func) -> str:
cwd = Path.cwd() cwd = Path.cwd()
loc = Path(getlocation(func, str(cwd))) loc = Path(str(getlocation(func, relative_to=cwd, allow_escape=False)))
prefix = Path("...", "_pytest") prefix = Path("...", "_pytest")
try: try:
return str(prefix / loc.relative_to(_PYTEST_DIR)) return str(prefix / loc.relative_to(_PYTEST_DIR))
@ -1566,9 +1567,8 @@ def _show_fixtures_per_test(config: Config, session: Session) -> None:
tw = _pytest.config.create_terminal_writer(config) tw = _pytest.config.create_terminal_writer(config)
verbose = config.getvalue("verbose") verbose = config.getvalue("verbose")
def get_best_relpath(func) -> str: def get_best_relpath(func):
loc = getlocation(func, str(curdir)) return getlocation(func, relative_to=curdir, allow_escape=False)
return bestrelpath(curdir, Path(loc))
def write_fixture(fixture_def: fixtures.FixtureDef[object]) -> None: def write_fixture(fixture_def: fixtures.FixtureDef[object]) -> None:
argname = fixture_def.argname argname = fixture_def.argname
@ -1589,9 +1589,12 @@ def _show_fixtures_per_test(config: Config, session: Session) -> None:
def write_item(item: nodes.Item) -> None: def write_item(item: nodes.Item) -> None:
# Not all items have _fixtureinfo attribute. # Not all items have _fixtureinfo attribute.
info: Optional[FuncFixtureInfo] = getattr(item, "_fixtureinfo", None) info: Optional[FuncFixtureInfo] = getattr(item, "_fixtureinfo", None)
function: Optional[Callable[..., Any]] = getattr(item, "function", None)
if info is None or not info.name2fixturedefs: if info is None or not info.name2fixturedefs:
# This test item does not use any fixtures. # This test item does not use any fixtures.
return return
if function is None:
return
tw.line() tw.line()
tw.sep("-", f"fixtures used by {item.name}") tw.sep("-", f"fixtures used by {item.name}")
# TODO: Fix this type ignore. # TODO: Fix this type ignore.
@ -1625,14 +1628,14 @@ def _showfixtures_main(config: Config, session: Session) -> None:
fm = session._fixturemanager fm = session._fixturemanager
available = [] available = []
seen: Set[Tuple[str, str]] = set() seen: Set[Tuple[str, CodeLocation]] = set()
for argname, fixturedefs in fm._arg2fixturedefs.items(): for argname, fixturedefs in fm._arg2fixturedefs.items():
assert fixturedefs is not None assert fixturedefs is not None
if not fixturedefs: if not fixturedefs:
continue continue
for fixturedef in fixturedefs: for fixturedef in fixturedefs:
loc = getlocation(fixturedef.func, str(curdir)) loc = getlocation(fixturedef.func, relative_to=curdir, allow_escape=True)
if (fixturedef.argname, loc) in seen: if (fixturedef.argname, loc) in seen:
continue continue
seen.add((fixturedef.argname, loc)) seen.add((fixturedef.argname, loc))
@ -1658,7 +1661,7 @@ def _showfixtures_main(config: Config, session: Session) -> None:
continue continue
tw.write(f"{argname}", green=True) tw.write(f"{argname}", green=True)
if fixturedef.scope != "function": if fixturedef.scope != "function":
tw.write(" [%s scope]" % fixturedef.scope, cyan=True) tw.write(f" [{fixturedef.scope} scope]", cyan=True)
tw.write(f" -- {prettypath}", yellow=True) tw.write(f" -- {prettypath}", yellow=True)
tw.write("\n") tw.write("\n")
doc = inspect.getdoc(fixturedef.func) doc = inspect.getdoc(fixturedef.func)