code: convert from py.path to pathlib

This commit is contained in:
Ran Benita 2020-12-19 14:11:00 +02:00
parent 7aa2240832
commit 92ba96b061
7 changed files with 90 additions and 74 deletions

View File

@ -0,0 +1,5 @@
The following changes have been made to internal pytest types/functions:
- The ``path`` property of ``_pytest.code.Code`` returns ``Path`` instead of ``py.path.local``.
- The ``path`` property of ``_pytest.code.TracebackEntry`` returns ``Path`` instead of ``py.path.local``.
- The ``_pytest.code.getfslineno()`` function returns ``Path`` instead of ``py.path.local``.

View File

@ -43,6 +43,8 @@ from _pytest._io.saferepr import safeformat
from _pytest._io.saferepr import saferepr from _pytest._io.saferepr import saferepr
from _pytest.compat import final from _pytest.compat import final
from _pytest.compat import get_real_func from _pytest.compat import get_real_func
from _pytest.pathlib import absolutepath
from _pytest.pathlib import bestrelpath
if TYPE_CHECKING: if TYPE_CHECKING:
from typing_extensions import Literal from typing_extensions import Literal
@ -78,16 +80,16 @@ class Code:
return self.raw.co_name return self.raw.co_name
@property @property
def path(self) -> Union[py.path.local, str]: def path(self) -> Union[Path, str]:
"""Return a path object pointing to source code, or an ``str`` in """Return a path object pointing to source code, or an ``str`` in
case of ``OSError`` / non-existing file.""" case of ``OSError`` / non-existing file."""
if not self.raw.co_filename: if not self.raw.co_filename:
return "" return ""
try: try:
p = py.path.local(self.raw.co_filename) p = absolutepath(self.raw.co_filename)
# maybe don't try this checking # maybe don't try this checking
if not p.check(): if not p.exists():
raise OSError("py.path check failed.") raise OSError("path check failed.")
return p return p
except OSError: except OSError:
# XXX maybe try harder like the weird logic # XXX maybe try harder like the weird logic
@ -223,7 +225,7 @@ class TracebackEntry:
return source.getstatement(self.lineno) return source.getstatement(self.lineno)
@property @property
def path(self) -> Union[py.path.local, str]: def path(self) -> Union[Path, str]:
"""Path to the source code.""" """Path to the source code."""
return self.frame.code.path return self.frame.code.path
@ -336,10 +338,10 @@ class Traceback(List[TracebackEntry]):
def cut( def cut(
self, self,
path=None, path: Optional[Union[Path, str]] = None,
lineno: Optional[int] = None, lineno: Optional[int] = None,
firstlineno: Optional[int] = None, firstlineno: Optional[int] = None,
excludepath: Optional[py.path.local] = None, excludepath: Optional[Path] = None,
) -> "Traceback": ) -> "Traceback":
"""Return a Traceback instance wrapping part of this Traceback. """Return a Traceback instance wrapping part of this Traceback.
@ -353,17 +355,19 @@ class Traceback(List[TracebackEntry]):
for x in self: for x in self:
code = x.frame.code code = x.frame.code
codepath = code.path codepath = code.path
if path is not None and codepath != path:
continue
if ( if (
(path is None or codepath == path) excludepath is not None
and ( and isinstance(codepath, Path)
excludepath is None and excludepath in codepath.parents
or not isinstance(codepath, py.path.local)
or not codepath.relto(excludepath)
)
and (lineno is None or x.lineno == lineno)
and (firstlineno is None or x.frame.code.firstlineno == firstlineno)
): ):
return Traceback(x._rawentry, self._excinfo) continue
if lineno is not None and x.lineno != lineno:
continue
if firstlineno is not None and x.frame.code.firstlineno != firstlineno:
continue
return Traceback(x._rawentry, self._excinfo)
return self return self
@overload @overload
@ -801,7 +805,8 @@ class FormattedExcinfo:
message = "in %s" % (entry.name) message = "in %s" % (entry.name)
else: else:
message = excinfo and excinfo.typename or "" message = excinfo and excinfo.typename or ""
path = self._makepath(entry.path) entry_path = entry.path
path = self._makepath(entry_path)
reprfileloc = ReprFileLocation(path, entry.lineno + 1, message) reprfileloc = ReprFileLocation(path, entry.lineno + 1, message)
localsrepr = self.repr_locals(entry.locals) localsrepr = self.repr_locals(entry.locals)
return ReprEntry(lines, reprargs, localsrepr, reprfileloc, style) return ReprEntry(lines, reprargs, localsrepr, reprfileloc, style)
@ -814,15 +819,15 @@ class FormattedExcinfo:
lines.extend(self.get_exconly(excinfo, indent=4)) lines.extend(self.get_exconly(excinfo, indent=4))
return ReprEntry(lines, None, None, None, style) return ReprEntry(lines, None, None, None, style)
def _makepath(self, path): def _makepath(self, path: Union[Path, str]) -> str:
if not self.abspath: if not self.abspath and isinstance(path, Path):
try: try:
np = py.path.local().bestrelpath(path) np = bestrelpath(Path.cwd(), path)
except OSError: except OSError:
return path return str(path)
if len(np) < len(str(path)): if len(np) < len(str(path)):
path = np return np
return path return str(path)
def repr_traceback(self, excinfo: ExceptionInfo[BaseException]) -> "ReprTraceback": def repr_traceback(self, excinfo: ExceptionInfo[BaseException]) -> "ReprTraceback":
traceback = excinfo.traceback traceback = excinfo.traceback
@ -1181,7 +1186,7 @@ class ReprFuncArgs(TerminalRepr):
tw.line("") tw.line("")
def getfslineno(obj: object) -> Tuple[Union[str, py.path.local], int]: def getfslineno(obj: object) -> Tuple[Union[str, Path], int]:
"""Return source location (path, lineno) for the given object. """Return source location (path, lineno) for the given object.
If the source cannot be determined return ("", -1). If the source cannot be determined return ("", -1).
@ -1203,7 +1208,7 @@ def getfslineno(obj: object) -> Tuple[Union[str, py.path.local], int]:
except TypeError: except TypeError:
return "", -1 return "", -1
fspath = fn and py.path.local(fn) or "" fspath = fn and absolutepath(fn) or ""
lineno = -1 lineno = -1
if fspath: if fspath:
try: try:

View File

@ -5,6 +5,7 @@ import sys
import warnings import warnings
from collections import defaultdict from collections import defaultdict
from collections import deque from collections import deque
from pathlib import Path
from types import TracebackType from types import TracebackType
from typing import Any from typing import Any
from typing import Callable from typing import Callable
@ -58,6 +59,7 @@ from _pytest.mark.structures import MarkDecorator
from _pytest.outcomes import fail from _pytest.outcomes import fail
from _pytest.outcomes import TEST_OUTCOME from _pytest.outcomes import TEST_OUTCOME
from _pytest.pathlib import absolutepath from _pytest.pathlib import absolutepath
from _pytest.pathlib import bestrelpath
from _pytest.store import StoreKey from _pytest.store import StoreKey
if TYPE_CHECKING: if TYPE_CHECKING:
@ -718,7 +720,11 @@ class FixtureRequest:
for fixturedef in self._get_fixturestack(): for fixturedef in self._get_fixturestack():
factory = fixturedef.func factory = fixturedef.func
fs, lineno = getfslineno(factory) fs, lineno = getfslineno(factory)
p = self._pyfuncitem.session.fspath.bestrelpath(fs) if isinstance(fs, Path):
session: Session = self._pyfuncitem.session
p = bestrelpath(Path(session.fspath), fs)
else:
p = fs
args = _format_args(factory) args = _format_args(factory)
lines.append("%s:%d: def %s%s" % (p, lineno + 1, factory.__name__, args)) lines.append("%s:%d: def %s%s" % (p, lineno + 1, factory.__name__, args))
return lines return lines

View File

@ -39,7 +39,7 @@ if TYPE_CHECKING:
SEP = "/" SEP = "/"
tracebackcutdir = py.path.local(_pytest.__file__).dirpath() tracebackcutdir = Path(_pytest.__file__).parent
def iterparentnodeids(nodeid: str) -> Iterator[str]: def iterparentnodeids(nodeid: str) -> Iterator[str]:
@ -416,9 +416,7 @@ class Node(metaclass=NodeMeta):
return self._repr_failure_py(excinfo, style) return self._repr_failure_py(excinfo, style)
def get_fslocation_from_item( def get_fslocation_from_item(node: "Node") -> Tuple[Union[str, Path], Optional[int]]:
node: "Node",
) -> Tuple[Union[str, py.path.local], Optional[int]]:
"""Try to extract the actual location from a node, depending on available attributes: """Try to extract the actual location from a node, depending on available attributes:
* "location": a pair (path, lineno) * "location": a pair (path, lineno)
@ -474,7 +472,7 @@ class Collector(Node):
def _prunetraceback(self, excinfo: ExceptionInfo[BaseException]) -> None: def _prunetraceback(self, excinfo: ExceptionInfo[BaseException]) -> None:
if hasattr(self, "fspath"): if hasattr(self, "fspath"):
traceback = excinfo.traceback traceback = excinfo.traceback
ntraceback = traceback.cut(path=self.fspath) ntraceback = traceback.cut(path=Path(self.fspath))
if ntraceback == traceback: if ntraceback == traceback:
ntraceback = ntraceback.cut(excludepath=tracebackcutdir) ntraceback = ntraceback.cut(excludepath=tracebackcutdir)
excinfo.traceback = ntraceback.filter() excinfo.traceback = ntraceback.filter()

View File

@ -340,7 +340,11 @@ class PyobjMixin:
fspath: Union[py.path.local, str] = file_path fspath: Union[py.path.local, str] = file_path
lineno = compat_co_firstlineno lineno = compat_co_firstlineno
else: else:
fspath, lineno = getfslineno(obj) path, lineno = getfslineno(obj)
if isinstance(path, Path):
fspath = py.path.local(path)
else:
fspath = path
modpath = self.getmodpath() modpath = self.getmodpath()
assert isinstance(lineno, int) assert isinstance(lineno, int)
return fspath, lineno, modpath return fspath, lineno, modpath

View File

@ -1,7 +1,6 @@
import importlib import importlib
import io import io
import operator import operator
import os
import queue import queue
import sys import sys
import textwrap import textwrap
@ -12,14 +11,14 @@ from typing import Tuple
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from typing import Union from typing import Union
import py
import _pytest import _pytest
import pytest import pytest
from _pytest._code.code import ExceptionChainRepr from _pytest._code.code import ExceptionChainRepr
from _pytest._code.code import ExceptionInfo from _pytest._code.code import ExceptionInfo
from _pytest._code.code import FormattedExcinfo from _pytest._code.code import FormattedExcinfo
from _pytest._io import TerminalWriter from _pytest._io import TerminalWriter
from _pytest.monkeypatch import MonkeyPatch
from _pytest.pathlib import bestrelpath
from _pytest.pathlib import import_path from _pytest.pathlib import import_path
from _pytest.pytester import LineMatcher from _pytest.pytester import LineMatcher
from _pytest.pytester import Pytester from _pytest.pytester import Pytester
@ -150,9 +149,10 @@ class TestTraceback_f_g_h:
" except somenoname: # type: ignore[name-defined] # noqa: F821", " except somenoname: # type: ignore[name-defined] # noqa: F821",
] ]
def test_traceback_cut(self): def test_traceback_cut(self) -> None:
co = _pytest._code.Code.from_function(f) co = _pytest._code.Code.from_function(f)
path, firstlineno = co.path, co.firstlineno path, firstlineno = co.path, co.firstlineno
assert isinstance(path, Path)
traceback = self.excinfo.traceback traceback = self.excinfo.traceback
newtraceback = traceback.cut(path=path, firstlineno=firstlineno) newtraceback = traceback.cut(path=path, firstlineno=firstlineno)
assert len(newtraceback) == 1 assert len(newtraceback) == 1
@ -163,11 +163,11 @@ class TestTraceback_f_g_h:
p = pytester.makepyfile("def f(): raise ValueError") p = pytester.makepyfile("def f(): raise ValueError")
with pytest.raises(ValueError) as excinfo: with pytest.raises(ValueError) as excinfo:
import_path(p).f() # type: ignore[attr-defined] import_path(p).f() # type: ignore[attr-defined]
basedir = py.path.local(pytest.__file__).dirpath() basedir = Path(pytest.__file__).parent
newtraceback = excinfo.traceback.cut(excludepath=basedir) newtraceback = excinfo.traceback.cut(excludepath=basedir)
for x in newtraceback: for x in newtraceback:
if hasattr(x, "path"): assert isinstance(x.path, Path)
assert not py.path.local(x.path).relto(basedir) assert basedir not in x.path.parents
assert newtraceback[-1].frame.code.path == p assert newtraceback[-1].frame.code.path == p
def test_traceback_filter(self): def test_traceback_filter(self):
@ -376,7 +376,7 @@ def test_excinfo_no_python_sourcecode(tmpdir):
for item in excinfo.traceback: for item in excinfo.traceback:
print(item) # XXX: for some reason jinja.Template.render is printed in full print(item) # XXX: for some reason jinja.Template.render is printed in full
item.source # shouldn't fail item.source # shouldn't fail
if isinstance(item.path, py.path.local) and item.path.basename == "test.txt": if isinstance(item.path, Path) and item.path.name == "test.txt":
assert str(item.source) == "{{ h()}}:" assert str(item.source) == "{{ h()}}:"
@ -392,16 +392,16 @@ def test_entrysource_Queue_example():
assert s.startswith("def get") assert s.startswith("def get")
def test_codepath_Queue_example(): def test_codepath_Queue_example() -> None:
try: try:
queue.Queue().get(timeout=0.001) queue.Queue().get(timeout=0.001)
except queue.Empty: except queue.Empty:
excinfo = _pytest._code.ExceptionInfo.from_current() excinfo = _pytest._code.ExceptionInfo.from_current()
entry = excinfo.traceback[-1] entry = excinfo.traceback[-1]
path = entry.path path = entry.path
assert isinstance(path, py.path.local) assert isinstance(path, Path)
assert path.basename.lower() == "queue.py" assert path.name.lower() == "queue.py"
assert path.check() assert path.exists()
def test_match_succeeds(): def test_match_succeeds():
@ -805,21 +805,21 @@ raise ValueError()
raised = 0 raised = 0
orig_getcwd = os.getcwd orig_path_cwd = Path.cwd
def raiseos(): def raiseos():
nonlocal raised nonlocal raised
upframe = sys._getframe().f_back upframe = sys._getframe().f_back
assert upframe is not None assert upframe is not None
if upframe.f_code.co_name == "checked_call": if upframe.f_code.co_name == "_makepath":
# Only raise with expected calls, but not via e.g. inspect for # Only raise with expected calls, but not via e.g. inspect for
# py38-windows. # py38-windows.
raised += 1 raised += 1
raise OSError(2, "custom_oserror") raise OSError(2, "custom_oserror")
return orig_getcwd() return orig_path_cwd()
monkeypatch.setattr(os, "getcwd", raiseos) monkeypatch.setattr(Path, "cwd", raiseos)
assert p._makepath(__file__) == __file__ assert p._makepath(Path(__file__)) == __file__
assert raised == 1 assert raised == 1
repr_tb = p.repr_traceback(excinfo) repr_tb = p.repr_traceback(excinfo)
@ -1015,7 +1015,9 @@ raise ValueError()
assert line.endswith("mod.py") assert line.endswith("mod.py")
assert tw_mock.lines[10] == ":3: ValueError" assert tw_mock.lines[10] == ":3: ValueError"
def test_toterminal_long_filenames(self, importasmod, tw_mock): def test_toterminal_long_filenames(
self, importasmod, tw_mock, monkeypatch: MonkeyPatch
) -> None:
mod = importasmod( mod = importasmod(
""" """
def f(): def f():
@ -1023,25 +1025,22 @@ raise ValueError()
""" """
) )
excinfo = pytest.raises(ValueError, mod.f) excinfo = pytest.raises(ValueError, mod.f)
path = py.path.local(mod.__file__) path = Path(mod.__file__)
old = path.dirpath().chdir() monkeypatch.chdir(path.parent)
try: repr = excinfo.getrepr(abspath=False)
repr = excinfo.getrepr(abspath=False) repr.toterminal(tw_mock)
repr.toterminal(tw_mock) x = bestrelpath(Path.cwd(), path)
x = py.path.local().bestrelpath(path) if len(x) < len(str(path)):
if len(x) < len(str(path)):
msg = tw_mock.get_write_msg(-2)
assert msg == "mod.py"
assert tw_mock.lines[-1] == ":3: ValueError"
repr = excinfo.getrepr(abspath=True)
repr.toterminal(tw_mock)
msg = tw_mock.get_write_msg(-2) msg = tw_mock.get_write_msg(-2)
assert msg == path assert msg == "mod.py"
line = tw_mock.lines[-1] assert tw_mock.lines[-1] == ":3: ValueError"
assert line == ":3: ValueError"
finally: repr = excinfo.getrepr(abspath=True)
old.chdir() repr.toterminal(tw_mock)
msg = tw_mock.get_write_msg(-2)
assert msg == str(path)
line = tw_mock.lines[-1]
assert line == ":3: ValueError"
@pytest.mark.parametrize( @pytest.mark.parametrize(
"reproptions", "reproptions",

View File

@ -6,13 +6,12 @@ import inspect
import linecache import linecache
import sys import sys
import textwrap import textwrap
from pathlib import Path
from types import CodeType from types import CodeType
from typing import Any from typing import Any
from typing import Dict from typing import Dict
from typing import Optional from typing import Optional
import py.path
import pytest import pytest
from _pytest._code import Code from _pytest._code import Code
from _pytest._code import Frame from _pytest._code import Frame
@ -352,8 +351,8 @@ def test_getfslineno() -> None:
fspath, lineno = getfslineno(f) fspath, lineno = getfslineno(f)
assert isinstance(fspath, py.path.local) assert isinstance(fspath, Path)
assert fspath.basename == "test_source.py" assert fspath.name == "test_source.py"
assert lineno == f.__code__.co_firstlineno - 1 # see findsource assert lineno == f.__code__.co_firstlineno - 1 # see findsource
class A: class A:
@ -362,8 +361,8 @@ def test_getfslineno() -> None:
fspath, lineno = getfslineno(A) fspath, lineno = getfslineno(A)
_, A_lineno = inspect.findsource(A) _, A_lineno = inspect.findsource(A)
assert isinstance(fspath, py.path.local) assert isinstance(fspath, Path)
assert fspath.basename == "test_source.py" assert fspath.name == "test_source.py"
assert lineno == A_lineno assert lineno == A_lineno
assert getfslineno(3) == ("", -1) assert getfslineno(3) == ("", -1)