[RFC] code location as a namedtuple

This commit is contained in:
Ronny Pfannschmidt 2020-11-21 14:17:31 +01:00
parent 431ec6d34e
commit a99885f344
3 changed files with 26 additions and 6 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
@ -94,17 +95,35 @@ def is_async_function(func: object) -> bool:
def getlocation(function, curdir: str | None = None) -> str: 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) 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
# TODO: this cycle indicates a larger issue
from .pathlib import bestrelpath
if curdir is not None: if curdir is not None:
try: try:
relfn = fn.relative_to(curdir) relfn = Path(bestrelpath(curdir, fn))
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

@ -1210,7 +1210,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, None)
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

@ -39,6 +39,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
@ -1634,14 +1635,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, curdir)
if (fixturedef.argname, loc) in seen: if (fixturedef.argname, loc) in seen:
continue continue
seen.add((fixturedef.argname, loc)) seen.add((fixturedef.argname, loc))