From a99885f3449f19b37ee8e90aabc7cfc0382aadde Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Sat, 21 Nov 2020 14:17:31 +0100 Subject: [PATCH] [RFC] code location as a namedtuple --- src/_pytest/compat.py | 25 ++++++++++++++++++++++--- src/_pytest/fixtures.py | 2 +- src/_pytest/python.py | 5 +++-- 3 files changed, 26 insertions(+), 6 deletions(-) diff --git a/src/_pytest/compat.py b/src/_pytest/compat.py index 352211de8..a21e711fc 100644 --- a/src/_pytest/compat.py +++ b/src/_pytest/compat.py @@ -13,6 +13,7 @@ from pathlib import Path from typing import Any from typing import Callable from typing import Generic +from typing import NamedTuple from typing import NoReturn from typing import TYPE_CHECKING from typing import TypeVar @@ -94,17 +95,35 @@ def is_async_function(func: object) -> bool: def getlocation(function, curdir: str | None = None) -> str: +class CodeLocation(NamedTuple): + path: Path + lineno: int + + +def CodeLocation__str__(self: CodeLocation) -> str: + """Python 3.6 hack for NamedTuple __str__""" + return f"{self.path}:{self.lineno}" + + +setattr(CodeLocation, "__str__", CodeLocation__str__) + + +def getlocation(function, curdir: Path | None) -> CodeLocation: function = get_real_func(function) fn = Path(inspect.getfile(function)) lineno = function.__code__.co_firstlineno + + # TODO: this cycle indicates a larger issue + from .pathlib import bestrelpath + if curdir is not None: try: - relfn = fn.relative_to(curdir) + relfn = Path(bestrelpath(curdir, fn)) except ValueError: pass else: - return "%s:%d" % (relfn, lineno + 1) - return "%s:%d" % (fn, lineno + 1) + return CodeLocation(relfn, lineno + 1) + return CodeLocation(fn, lineno + 1) def num_mock_patch_args(function) -> int: diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index 007245b24..83ee0df5d 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -1210,7 +1210,7 @@ class FixtureFunctionMarker: name = self.name or function.__name__ if name == "request": - location = getlocation(function) + location = getlocation(function, None) fail( "'request' is a reserved word for fixtures, use another name:\n {}".format( location diff --git a/src/_pytest/python.py b/src/_pytest/python.py index d04b6fa4d..c1ce9b5ea 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -39,6 +39,7 @@ from _pytest._io import TerminalWriter from _pytest._io.saferepr import saferepr from _pytest.compat import ascii_escaped from _pytest.compat import assert_never +from _pytest.compat import CodeLocation from _pytest.compat import final from _pytest.compat import get_default_arg_names from _pytest.compat import get_real_func @@ -1634,14 +1635,14 @@ def _showfixtures_main(config: Config, session: Session) -> None: fm = session._fixturemanager available = [] - seen: Set[Tuple[str, str]] = set() + seen: Set[Tuple[str, CodeLocation]] = 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, str(curdir)) + loc = getlocation(fixturedef.func, curdir) if (fixturedef.argname, loc) in seen: continue seen.add((fixturedef.argname, loc))