Merge pull request #8174 from bluetech/py-to-pathlib-5

More py.path -> pathlib conversions
This commit is contained in:
Ran Benita 2020-12-26 15:01:07 +02:00 committed by GitHub
commit d8d2df7e6f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 333 additions and 291 deletions

View File

@ -0,0 +1,6 @@
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``.
- The ``_pytest.python.path_matches_patterns()`` function takes ``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

@ -128,7 +128,7 @@ def filter_traceback_for_conftest_import_failure(
def main( def main(
args: Optional[Union[List[str], py.path.local]] = None, args: Optional[Union[List[str], "os.PathLike[str]"]] = None,
plugins: Optional[Sequence[Union[str, _PluggyPlugin]]] = None, plugins: Optional[Sequence[Union[str, _PluggyPlugin]]] = None,
) -> Union[int, ExitCode]: ) -> Union[int, ExitCode]:
"""Perform an in-process test run. """Perform an in-process test run.
@ -295,13 +295,15 @@ def get_plugin_manager() -> "PytestPluginManager":
def _prepareconfig( def _prepareconfig(
args: Optional[Union[py.path.local, List[str]]] = None, args: Optional[Union[List[str], "os.PathLike[str]"]] = None,
plugins: Optional[Sequence[Union[str, _PluggyPlugin]]] = None, plugins: Optional[Sequence[Union[str, _PluggyPlugin]]] = None,
) -> "Config": ) -> "Config":
if args is None: if args is None:
args = sys.argv[1:] args = sys.argv[1:]
elif isinstance(args, py.path.local): # TODO: Remove type-ignore after next mypy release.
args = [str(args)] # https://github.com/python/typeshed/commit/076983eec45e739c68551cb6119fd7d85fd4afa9
elif isinstance(args, os.PathLike): # type: ignore[misc]
args = [os.fspath(args)]
elif not isinstance(args, list): elif not isinstance(args, list):
msg = "`args` parameter expected to be a list of strings, got: {!r} (type: {})" msg = "`args` parameter expected to be a list of strings, got: {!r} (type: {})"
raise TypeError(msg.format(args, type(args))) raise TypeError(msg.format(args, type(args)))
@ -346,7 +348,7 @@ class PytestPluginManager(PluginManager):
self._conftestpath2mod: Dict[Path, types.ModuleType] = {} self._conftestpath2mod: Dict[Path, types.ModuleType] = {}
self._confcutdir: Optional[Path] = None self._confcutdir: Optional[Path] = None
self._noconftest = False self._noconftest = False
self._duplicatepaths: Set[py.path.local] = set() self._duplicatepaths: Set[Path] = set()
# plugins that were explicitly skipped with pytest.skip # plugins that were explicitly skipped with pytest.skip
# list of (module name, skip reason) # list of (module name, skip reason)

View File

@ -36,6 +36,7 @@ from _pytest.config.argparsing import Parser
from _pytest.fixtures import FixtureRequest from _pytest.fixtures import FixtureRequest
from _pytest.nodes import Collector from _pytest.nodes import Collector
from _pytest.outcomes import OutcomeException from _pytest.outcomes import OutcomeException
from _pytest.pathlib import fnmatch_ex
from _pytest.pathlib import import_path from _pytest.pathlib import import_path
from _pytest.python_api import approx from _pytest.python_api import approx
from _pytest.warning_types import PytestWarning from _pytest.warning_types import PytestWarning
@ -120,32 +121,32 @@ def pytest_unconfigure() -> None:
def pytest_collect_file( def pytest_collect_file(
path: py.path.local, parent: Collector, fspath: Path, path: py.path.local, parent: Collector,
) -> Optional[Union["DoctestModule", "DoctestTextfile"]]: ) -> Optional[Union["DoctestModule", "DoctestTextfile"]]:
config = parent.config config = parent.config
if path.ext == ".py": if fspath.suffix == ".py":
if config.option.doctestmodules and not _is_setup_py(path): if config.option.doctestmodules and not _is_setup_py(fspath):
mod: DoctestModule = DoctestModule.from_parent(parent, fspath=path) mod: DoctestModule = DoctestModule.from_parent(parent, fspath=path)
return mod return mod
elif _is_doctest(config, path, parent): elif _is_doctest(config, fspath, parent):
txt: DoctestTextfile = DoctestTextfile.from_parent(parent, fspath=path) txt: DoctestTextfile = DoctestTextfile.from_parent(parent, fspath=path)
return txt return txt
return None return None
def _is_setup_py(path: py.path.local) -> bool: def _is_setup_py(path: Path) -> bool:
if path.basename != "setup.py": if path.name != "setup.py":
return False return False
contents = path.read_binary() contents = path.read_bytes()
return b"setuptools" in contents or b"distutils" in contents return b"setuptools" in contents or b"distutils" in contents
def _is_doctest(config: Config, path: py.path.local, parent) -> bool: def _is_doctest(config: Config, path: Path, parent: Collector) -> bool:
if path.ext in (".txt", ".rst") and parent.session.isinitpath(path): if path.suffix in (".txt", ".rst") and parent.session.isinitpath(path):
return True return True
globs = config.getoption("doctestglob") or ["test*.txt"] globs = config.getoption("doctestglob") or ["test*.txt"]
for glob in globs: for glob in globs:
if path.check(fnmatch=glob): if fnmatch_ex(glob, path):
return True return True
return False return False

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

@ -37,6 +37,7 @@ from _pytest.fixtures import FixtureManager
from _pytest.outcomes import exit from _pytest.outcomes import exit
from _pytest.pathlib import absolutepath from _pytest.pathlib import absolutepath
from _pytest.pathlib import bestrelpath from _pytest.pathlib import bestrelpath
from _pytest.pathlib import fnmatch_ex
from _pytest.pathlib import visit from _pytest.pathlib import visit
from _pytest.reports import CollectReport from _pytest.reports import CollectReport
from _pytest.reports import TestReport from _pytest.reports import TestReport
@ -353,11 +354,14 @@ def pytest_runtestloop(session: "Session") -> bool:
return True return True
def _in_venv(path: py.path.local) -> bool: def _in_venv(path: Path) -> bool:
"""Attempt to detect if ``path`` is the root of a Virtual Environment by """Attempt to detect if ``path`` is the root of a Virtual Environment by
checking for the existence of the appropriate activate script.""" checking for the existence of the appropriate activate script."""
bindir = path.join("Scripts" if sys.platform.startswith("win") else "bin") bindir = path.joinpath("Scripts" if sys.platform.startswith("win") else "bin")
if not bindir.isdir(): try:
if not bindir.is_dir():
return False
except OSError:
return False return False
activates = ( activates = (
"activate", "activate",
@ -367,33 +371,32 @@ def _in_venv(path: py.path.local) -> bool:
"Activate.bat", "Activate.bat",
"Activate.ps1", "Activate.ps1",
) )
return any([fname.basename in activates for fname in bindir.listdir()]) return any(fname.name in activates for fname in bindir.iterdir())
def pytest_ignore_collect(path: py.path.local, config: Config) -> Optional[bool]: def pytest_ignore_collect(fspath: Path, config: Config) -> Optional[bool]:
path_ = Path(path) ignore_paths = config._getconftest_pathlist("collect_ignore", path=fspath.parent)
ignore_paths = config._getconftest_pathlist("collect_ignore", path=path_.parent)
ignore_paths = ignore_paths or [] ignore_paths = ignore_paths or []
excludeopt = config.getoption("ignore") excludeopt = config.getoption("ignore")
if excludeopt: if excludeopt:
ignore_paths.extend(absolutepath(x) for x in excludeopt) ignore_paths.extend(absolutepath(x) for x in excludeopt)
if path_ in ignore_paths: if fspath in ignore_paths:
return True return True
ignore_globs = config._getconftest_pathlist( ignore_globs = config._getconftest_pathlist(
"collect_ignore_glob", path=path_.parent "collect_ignore_glob", path=fspath.parent
) )
ignore_globs = ignore_globs or [] ignore_globs = ignore_globs or []
excludeglobopt = config.getoption("ignore_glob") excludeglobopt = config.getoption("ignore_glob")
if excludeglobopt: if excludeglobopt:
ignore_globs.extend(absolutepath(x) for x in excludeglobopt) ignore_globs.extend(absolutepath(x) for x in excludeglobopt)
if any(fnmatch.fnmatch(str(path), str(glob)) for glob in ignore_globs): if any(fnmatch.fnmatch(str(fspath), str(glob)) for glob in ignore_globs):
return True return True
allow_in_venv = config.getoption("collect_in_virtualenv") allow_in_venv = config.getoption("collect_in_virtualenv")
if not allow_in_venv and _in_venv(path): if not allow_in_venv and _in_venv(fspath):
return True return True
return None return None
@ -538,21 +541,21 @@ class Session(nodes.FSCollector):
if ihook.pytest_ignore_collect(fspath=fspath, path=path, config=self.config): if ihook.pytest_ignore_collect(fspath=fspath, path=path, config=self.config):
return False return False
norecursepatterns = self.config.getini("norecursedirs") norecursepatterns = self.config.getini("norecursedirs")
if any(path.check(fnmatch=pat) for pat in norecursepatterns): if any(fnmatch_ex(pat, fspath) for pat in norecursepatterns):
return False return False
return True return True
def _collectfile( def _collectfile(
self, path: py.path.local, handle_dupes: bool = True self, fspath: Path, handle_dupes: bool = True
) -> Sequence[nodes.Collector]: ) -> Sequence[nodes.Collector]:
fspath = Path(path) path = py.path.local(fspath)
assert ( assert (
path.isfile() fspath.is_file()
), "{!r} is not a file (isdir={!r}, exists={!r}, islink={!r})".format( ), "{!r} is not a file (isdir={!r}, exists={!r}, islink={!r})".format(
path, path.isdir(), path.exists(), path.islink() fspath, fspath.is_dir(), fspath.exists(), fspath.is_symlink()
) )
ihook = self.gethookproxy(path) ihook = self.gethookproxy(fspath)
if not self.isinitpath(path): if not self.isinitpath(fspath):
if ihook.pytest_ignore_collect( if ihook.pytest_ignore_collect(
fspath=fspath, path=path, config=self.config fspath=fspath, path=path, config=self.config
): ):
@ -562,10 +565,10 @@ class Session(nodes.FSCollector):
keepduplicates = self.config.getoption("keepduplicates") keepduplicates = self.config.getoption("keepduplicates")
if not keepduplicates: if not keepduplicates:
duplicate_paths = self.config.pluginmanager._duplicatepaths duplicate_paths = self.config.pluginmanager._duplicatepaths
if path in duplicate_paths: if fspath in duplicate_paths:
return () return ()
else: else:
duplicate_paths.add(path) duplicate_paths.add(fspath)
return ihook.pytest_collect_file(fspath=fspath, path=path, parent=self) # type: ignore[no-any-return] return ihook.pytest_collect_file(fspath=fspath, path=path, parent=self) # type: ignore[no-any-return]
@ -652,10 +655,8 @@ class Session(nodes.FSCollector):
from _pytest.python import Package from _pytest.python import Package
# Keep track of any collected nodes in here, so we don't duplicate fixtures. # Keep track of any collected nodes in here, so we don't duplicate fixtures.
node_cache1: Dict[py.path.local, Sequence[nodes.Collector]] = {} node_cache1: Dict[Path, Sequence[nodes.Collector]] = {}
node_cache2: Dict[ node_cache2: Dict[Tuple[Type[nodes.Collector], Path], nodes.Collector] = ({})
Tuple[Type[nodes.Collector], py.path.local], nodes.Collector
] = ({})
# Keep track of any collected collectors in matchnodes paths, so they # Keep track of any collected collectors in matchnodes paths, so they
# are not collected more than once. # are not collected more than once.
@ -679,31 +680,31 @@ class Session(nodes.FSCollector):
break break
if parent.is_dir(): if parent.is_dir():
pkginit = py.path.local(parent / "__init__.py") pkginit = parent / "__init__.py"
if pkginit.isfile() and pkginit not in node_cache1: if pkginit.is_file() and pkginit not in node_cache1:
col = self._collectfile(pkginit, handle_dupes=False) col = self._collectfile(pkginit, handle_dupes=False)
if col: if col:
if isinstance(col[0], Package): if isinstance(col[0], Package):
pkg_roots[str(parent)] = col[0] pkg_roots[str(parent)] = col[0]
node_cache1[col[0].fspath] = [col[0]] node_cache1[Path(col[0].fspath)] = [col[0]]
# If it's a directory argument, recurse and look for any Subpackages. # If it's a directory argument, recurse and look for any Subpackages.
# Let the Package collector deal with subnodes, don't collect here. # Let the Package collector deal with subnodes, don't collect here.
if argpath.is_dir(): if argpath.is_dir():
assert not names, "invalid arg {!r}".format((argpath, names)) assert not names, "invalid arg {!r}".format((argpath, names))
seen_dirs: Set[py.path.local] = set() seen_dirs: Set[Path] = set()
for direntry in visit(str(argpath), self._recurse): for direntry in visit(str(argpath), self._recurse):
if not direntry.is_file(): if not direntry.is_file():
continue continue
path = py.path.local(direntry.path) path = Path(direntry.path)
dirpath = path.dirpath() dirpath = path.parent
if dirpath not in seen_dirs: if dirpath not in seen_dirs:
# Collect packages first. # Collect packages first.
seen_dirs.add(dirpath) seen_dirs.add(dirpath)
pkginit = dirpath.join("__init__.py") pkginit = dirpath / "__init__.py"
if pkginit.exists(): if pkginit.exists():
for x in self._collectfile(pkginit): for x in self._collectfile(pkginit):
yield x yield x
@ -714,23 +715,22 @@ class Session(nodes.FSCollector):
continue continue
for x in self._collectfile(path): for x in self._collectfile(path):
key = (type(x), x.fspath) key2 = (type(x), Path(x.fspath))
if key in node_cache2: if key2 in node_cache2:
yield node_cache2[key] yield node_cache2[key2]
else: else:
node_cache2[key] = x node_cache2[key2] = x
yield x yield x
else: else:
assert argpath.is_file() assert argpath.is_file()
argpath_ = py.path.local(argpath) if argpath in node_cache1:
if argpath_ in node_cache1: col = node_cache1[argpath]
col = node_cache1[argpath_]
else: else:
collect_root = pkg_roots.get(argpath_.dirname, self) collect_root = pkg_roots.get(str(argpath.parent), self)
col = collect_root._collectfile(argpath_, handle_dupes=False) col = collect_root._collectfile(argpath, handle_dupes=False)
if col: if col:
node_cache1[argpath_] = col node_cache1[argpath] = col
matching = [] matching = []
work: List[ work: List[
@ -846,7 +846,7 @@ def resolve_collection_argument(
This function ensures the path exists, and returns a tuple: This function ensures the path exists, and returns a tuple:
(py.path.path("/full/path/to/pkg/tests/test_foo.py"), ["TestClass", "test_foo"]) (Path("/full/path/to/pkg/tests/test_foo.py"), ["TestClass", "test_foo"])
When as_pypath is True, expects that the command-line argument actually contains When as_pypath is True, expects that the command-line argument actually contains
module paths instead of file-system paths: module paths instead of file-system paths:

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

@ -387,7 +387,7 @@ def resolve_from_str(input: str, rootpath: Path) -> Path:
return rootpath.joinpath(input) return rootpath.joinpath(input)
def fnmatch_ex(pattern: str, path) -> bool: def fnmatch_ex(pattern: str, path: Union[str, "os.PathLike[str]"]) -> bool:
"""A port of FNMatcher from py.path.common which works with PurePath() instances. """A port of FNMatcher from py.path.common which works with PurePath() instances.
The difference between this algorithm and PurePath.match() is that the The difference between this algorithm and PurePath.match() is that the

View File

@ -66,6 +66,8 @@ 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 import_path from _pytest.pathlib import import_path
from _pytest.pathlib import ImportPathMismatchError from _pytest.pathlib import ImportPathMismatchError
from _pytest.pathlib import parts from _pytest.pathlib import parts
@ -190,11 +192,10 @@ def pytest_pyfunc_call(pyfuncitem: "Function") -> Optional[object]:
def pytest_collect_file( def pytest_collect_file(
fspath: Path, path: py.path.local, parent: nodes.Collector fspath: Path, path: py.path.local, parent: nodes.Collector
) -> Optional["Module"]: ) -> Optional["Module"]:
ext = path.ext if fspath.suffix == ".py":
if ext == ".py":
if not parent.session.isinitpath(fspath): if not parent.session.isinitpath(fspath):
if not path_matches_patterns( if not path_matches_patterns(
path, parent.config.getini("python_files") + ["__init__.py"] fspath, parent.config.getini("python_files") + ["__init__.py"]
): ):
return None return None
ihook = parent.session.gethookproxy(fspath) ihook = parent.session.gethookproxy(fspath)
@ -205,13 +206,13 @@ def pytest_collect_file(
return None return None
def path_matches_patterns(path: py.path.local, patterns: Iterable[str]) -> bool: def path_matches_patterns(path: Path, patterns: Iterable[str]) -> bool:
"""Return whether path matches any of the patterns in the list of globs given.""" """Return whether path matches any of the patterns in the list of globs given."""
return any(path.fnmatch(pattern) for pattern in patterns) return any(fnmatch_ex(pattern, path) for pattern in patterns)
def pytest_pycollect_makemodule(path: py.path.local, parent) -> "Module": def pytest_pycollect_makemodule(fspath: Path, path: py.path.local, parent) -> "Module":
if path.basename == "__init__.py": if fspath.name == "__init__.py":
pkg: Package = Package.from_parent(parent, fspath=path) pkg: Package = Package.from_parent(parent, fspath=path)
return pkg return pkg
mod: Module = Module.from_parent(parent, fspath=path) mod: Module = Module.from_parent(parent, fspath=path)
@ -340,7 +341,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
@ -673,21 +678,21 @@ class Package(Module):
if ihook.pytest_ignore_collect(fspath=fspath, path=path, config=self.config): if ihook.pytest_ignore_collect(fspath=fspath, path=path, config=self.config):
return False return False
norecursepatterns = self.config.getini("norecursedirs") norecursepatterns = self.config.getini("norecursedirs")
if any(path.check(fnmatch=pat) for pat in norecursepatterns): if any(fnmatch_ex(pat, fspath) for pat in norecursepatterns):
return False return False
return True return True
def _collectfile( def _collectfile(
self, path: py.path.local, handle_dupes: bool = True self, fspath: Path, handle_dupes: bool = True
) -> Sequence[nodes.Collector]: ) -> Sequence[nodes.Collector]:
fspath = Path(path) path = py.path.local(fspath)
assert ( assert (
path.isfile() fspath.is_file()
), "{!r} is not a file (isdir={!r}, exists={!r}, islink={!r})".format( ), "{!r} is not a file (isdir={!r}, exists={!r}, islink={!r})".format(
path, path.isdir(), path.exists(), path.islink() path, fspath.is_dir(), fspath.exists(), fspath.is_symlink()
) )
ihook = self.session.gethookproxy(path) ihook = self.session.gethookproxy(fspath)
if not self.session.isinitpath(path): if not self.session.isinitpath(fspath):
if ihook.pytest_ignore_collect( if ihook.pytest_ignore_collect(
fspath=fspath, path=path, config=self.config fspath=fspath, path=path, config=self.config
): ):
@ -697,32 +702,32 @@ class Package(Module):
keepduplicates = self.config.getoption("keepduplicates") keepduplicates = self.config.getoption("keepduplicates")
if not keepduplicates: if not keepduplicates:
duplicate_paths = self.config.pluginmanager._duplicatepaths duplicate_paths = self.config.pluginmanager._duplicatepaths
if path in duplicate_paths: if fspath in duplicate_paths:
return () return ()
else: else:
duplicate_paths.add(path) duplicate_paths.add(fspath)
return ihook.pytest_collect_file(fspath=fspath, path=path, parent=self) # type: ignore[no-any-return] return ihook.pytest_collect_file(fspath=fspath, path=path, parent=self) # type: ignore[no-any-return]
def collect(self) -> Iterable[Union[nodes.Item, nodes.Collector]]: def collect(self) -> Iterable[Union[nodes.Item, nodes.Collector]]:
this_path = self.fspath.dirpath() this_path = Path(self.fspath).parent
init_module = this_path.join("__init__.py") init_module = this_path / "__init__.py"
if init_module.check(file=1) and path_matches_patterns( if init_module.is_file() and path_matches_patterns(
init_module, self.config.getini("python_files") init_module, self.config.getini("python_files")
): ):
yield Module.from_parent(self, fspath=init_module) yield Module.from_parent(self, fspath=py.path.local(init_module))
pkg_prefixes: Set[py.path.local] = set() pkg_prefixes: Set[Path] = set()
for direntry in visit(str(this_path), recurse=self._recurse): for direntry in visit(str(this_path), recurse=self._recurse):
path = py.path.local(direntry.path) path = Path(direntry.path)
# We will visit our own __init__.py file, in which case we skip it. # We will visit our own __init__.py file, in which case we skip it.
if direntry.is_file(): if direntry.is_file():
if direntry.name == "__init__.py" and path.dirpath() == this_path: if direntry.name == "__init__.py" and path.parent == this_path:
continue continue
parts_ = parts(direntry.path) parts_ = parts(direntry.path)
if any( if any(
str(pkg_prefix) in parts_ and pkg_prefix.join("__init__.py") != path str(pkg_prefix) in parts_ and pkg_prefix / "__init__.py" != path
for pkg_prefix in pkg_prefixes for pkg_prefix in pkg_prefixes
): ):
continue continue
@ -732,7 +737,7 @@ class Package(Module):
elif not direntry.is_dir(): elif not direntry.is_dir():
# Broken symlink or invalid/missing file. # Broken symlink or invalid/missing file.
continue continue
elif path.join("__init__.py").check(file=1): elif path.joinpath("__init__.py").is_file():
pkg_prefixes.add(path) pkg_prefixes.add(path)
@ -1412,13 +1417,13 @@ def _show_fixtures_per_test(config: Config, session: Session) -> None:
import _pytest.config import _pytest.config
session.perform_collect() session.perform_collect()
curdir = py.path.local() curdir = Path.cwd()
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): def get_best_relpath(func) -> str:
loc = getlocation(func, str(curdir)) loc = getlocation(func, str(curdir))
return curdir.bestrelpath(py.path.local(loc)) 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
@ -1468,7 +1473,7 @@ def _showfixtures_main(config: Config, session: Session) -> None:
import _pytest.config import _pytest.config
session.perform_collect() session.perform_collect()
curdir = py.path.local() curdir = Path.cwd()
tw = _pytest.config.create_terminal_writer(config) tw = _pytest.config.create_terminal_writer(config)
verbose = config.getvalue("verbose") verbose = config.getvalue("verbose")
@ -1490,7 +1495,7 @@ def _showfixtures_main(config: Config, session: Session) -> None:
( (
len(fixturedef.baseid), len(fixturedef.baseid),
fixturedef.func.__module__, fixturedef.func.__module__,
curdir.bestrelpath(py.path.local(loc)), bestrelpath(curdir, Path(loc)),
fixturedef.argname, fixturedef.argname,
fixturedef, fixturedef,
) )

View File

@ -285,15 +285,13 @@ class WarningReport:
User friendly message about the warning. User friendly message about the warning.
:ivar str|None nodeid: :ivar str|None nodeid:
nodeid that generated the warning (see ``get_location``). nodeid that generated the warning (see ``get_location``).
:ivar tuple|py.path.local fslocation: :ivar tuple fslocation:
File system location of the source of the warning (see ``get_location``). File system location of the source of the warning (see ``get_location``).
""" """
message = attr.ib(type=str) message = attr.ib(type=str)
nodeid = attr.ib(type=Optional[str], default=None) nodeid = attr.ib(type=Optional[str], default=None)
fslocation = attr.ib( fslocation = attr.ib(type=Optional[Tuple[str, int]], default=None)
type=Optional[Union[Tuple[str, int], py.path.local]], default=None
)
count_towards_summary = True count_towards_summary = True
def get_location(self, config: Config) -> Optional[str]: def get_location(self, config: Config) -> Optional[str]:
@ -301,14 +299,9 @@ class WarningReport:
if self.nodeid: if self.nodeid:
return self.nodeid return self.nodeid
if self.fslocation: if self.fslocation:
if isinstance(self.fslocation, tuple) and len(self.fslocation) >= 2: filename, linenum = self.fslocation
filename, linenum = self.fslocation[:2] relpath = bestrelpath(config.invocation_params.dir, absolutepath(filename))
relpath = bestrelpath( return f"{relpath}:{linenum}"
config.invocation_params.dir, absolutepath(filename)
)
return f"{relpath}:{linenum}"
else:
return str(self.fslocation)
return None return None

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)

View File

@ -8,6 +8,7 @@ import pytest
from _pytest.config import ExitCode from _pytest.config import ExitCode
from _pytest.monkeypatch import MonkeyPatch from _pytest.monkeypatch import MonkeyPatch
from _pytest.pytester import Pytester from _pytest.pytester import Pytester
from _pytest.tmpdir import TempPathFactory
pytest_plugins = ("pytester",) pytest_plugins = ("pytester",)
@ -139,9 +140,11 @@ class TestNewAPI:
pytester.runpytest() pytester.runpytest()
assert pytester.path.joinpath(rel_cache_dir).is_dir() assert pytester.path.joinpath(rel_cache_dir).is_dir()
def test_custom_abs_cache_dir(self, pytester: Pytester, tmpdir_factory) -> None: def test_custom_abs_cache_dir(
tmp = str(tmpdir_factory.mktemp("tmp")) self, pytester: Pytester, tmp_path_factory: TempPathFactory
abs_cache_dir = os.path.join(tmp, "custom_cache_dir") ) -> None:
tmp = tmp_path_factory.mktemp("tmp")
abs_cache_dir = tmp / "custom_cache_dir"
pytester.makeini( pytester.makeini(
""" """
[pytest] [pytest]
@ -152,7 +155,7 @@ class TestNewAPI:
) )
pytester.makepyfile(test_errored="def test_error():\n assert False") pytester.makepyfile(test_errored="def test_error():\n assert False")
pytester.runpytest() pytester.runpytest()
assert Path(abs_cache_dir).is_dir() assert abs_cache_dir.is_dir()
def test_custom_cache_dir_with_env_var( def test_custom_cache_dir_with_env_var(
self, pytester: Pytester, monkeypatch: MonkeyPatch self, pytester: Pytester, monkeypatch: MonkeyPatch
@ -185,9 +188,9 @@ def test_cache_reportheader(env, pytester: Pytester, monkeypatch: MonkeyPatch) -
def test_cache_reportheader_external_abspath( def test_cache_reportheader_external_abspath(
pytester: Pytester, tmpdir_factory pytester: Pytester, tmp_path_factory: TempPathFactory
) -> None: ) -> None:
external_cache = tmpdir_factory.mktemp( external_cache = tmp_path_factory.mktemp(
"test_cache_reportheader_external_abspath_abs" "test_cache_reportheader_external_abspath_abs"
) )

View File

@ -212,12 +212,12 @@ class TestCollectFS:
bindir = "Scripts" if sys.platform.startswith("win") else "bin" bindir = "Scripts" if sys.platform.startswith("win") else "bin"
# no bin/activate, not a virtualenv # no bin/activate, not a virtualenv
base_path = pytester.mkdir("venv") base_path = pytester.mkdir("venv")
assert _in_venv(py.path.local(base_path)) is False assert _in_venv(base_path) is False
# with bin/activate, totally a virtualenv # with bin/activate, totally a virtualenv
bin_path = base_path.joinpath(bindir) bin_path = base_path.joinpath(bindir)
bin_path.mkdir() bin_path.mkdir()
bin_path.joinpath(fname).touch() bin_path.joinpath(fname).touch()
assert _in_venv(py.path.local(base_path)) is True assert _in_venv(base_path) is True
def test_custom_norecursedirs(self, pytester: Pytester) -> None: def test_custom_norecursedirs(self, pytester: Pytester) -> None:
pytester.makeini( pytester.makeini(
@ -277,7 +277,7 @@ class TestCollectPluginHookRelay:
wascalled.append(path) wascalled.append(path)
pytester.makefile(".abc", "xyz") pytester.makefile(".abc", "xyz")
pytest.main(py.path.local(pytester.path), plugins=[Plugin()]) pytest.main(pytester.path, plugins=[Plugin()])
assert len(wascalled) == 1 assert len(wascalled) == 1
assert wascalled[0].ext == ".abc" assert wascalled[0].ext == ".abc"

View File

@ -11,7 +11,6 @@ from typing import Type
from typing import Union from typing import Union
import attr import attr
import py.path
import _pytest._code import _pytest._code
import pytest import pytest
@ -28,6 +27,7 @@ from _pytest.config.findpaths import determine_setup
from _pytest.config.findpaths import get_common_ancestor from _pytest.config.findpaths import get_common_ancestor
from _pytest.config.findpaths import locate_config from _pytest.config.findpaths import locate_config
from _pytest.monkeypatch import MonkeyPatch from _pytest.monkeypatch import MonkeyPatch
from _pytest.pathlib import absolutepath
from _pytest.pytester import Pytester from _pytest.pytester import Pytester
@ -854,8 +854,8 @@ class TestConfigFromdictargs:
) )
) )
inifile = "../../foo/bar.ini" inifilename = "../../foo/bar.ini"
option_dict = {"inifilename": inifile, "capture": "no"} option_dict = {"inifilename": inifilename, "capture": "no"}
cwd = tmp_path.joinpath("a/b") cwd = tmp_path.joinpath("a/b")
cwd.mkdir(parents=True) cwd.mkdir(parents=True)
@ -873,14 +873,14 @@ class TestConfigFromdictargs:
with MonkeyPatch.context() as mp: with MonkeyPatch.context() as mp:
mp.chdir(cwd) mp.chdir(cwd)
config = Config.fromdictargs(option_dict, ()) config = Config.fromdictargs(option_dict, ())
inipath = py.path.local(inifile) inipath = absolutepath(inifilename)
assert config.args == [str(cwd)] assert config.args == [str(cwd)]
assert config.option.inifilename == inifile assert config.option.inifilename == inifilename
assert config.option.capture == "no" assert config.option.capture == "no"
# this indicates this is the file used for getting configuration values # this indicates this is the file used for getting configuration values
assert config.inifile == inipath assert config.inipath == inipath
assert config.inicfg.get("name") == "value" assert config.inicfg.get("name") == "value"
assert config.inicfg.get("should_not_be_set") is None assert config.inicfg.get("should_not_be_set") is None

View File

@ -1,10 +1,9 @@
import inspect import inspect
import textwrap import textwrap
from pathlib import Path
from typing import Callable from typing import Callable
from typing import Optional from typing import Optional
import py
import pytest import pytest
from _pytest.doctest import _get_checker from _pytest.doctest import _get_checker
from _pytest.doctest import _is_mocked from _pytest.doctest import _is_mocked
@ -1496,25 +1495,25 @@ def test_warning_on_unwrap_of_broken_object(
assert inspect.unwrap.__module__ == "inspect" assert inspect.unwrap.__module__ == "inspect"
def test_is_setup_py_not_named_setup_py(tmp_path): def test_is_setup_py_not_named_setup_py(tmp_path: Path) -> None:
not_setup_py = tmp_path.joinpath("not_setup.py") not_setup_py = tmp_path.joinpath("not_setup.py")
not_setup_py.write_text('from setuptools import setup; setup(name="foo")') not_setup_py.write_text('from setuptools import setup; setup(name="foo")')
assert not _is_setup_py(py.path.local(str(not_setup_py))) assert not _is_setup_py(not_setup_py)
@pytest.mark.parametrize("mod", ("setuptools", "distutils.core")) @pytest.mark.parametrize("mod", ("setuptools", "distutils.core"))
def test_is_setup_py_is_a_setup_py(tmpdir, mod): def test_is_setup_py_is_a_setup_py(tmp_path: Path, mod: str) -> None:
setup_py = tmpdir.join("setup.py") setup_py = tmp_path.joinpath("setup.py")
setup_py.write(f'from {mod} import setup; setup(name="foo")') setup_py.write_text(f'from {mod} import setup; setup(name="foo")', "utf-8")
assert _is_setup_py(setup_py) assert _is_setup_py(setup_py)
@pytest.mark.parametrize("mod", ("setuptools", "distutils.core")) @pytest.mark.parametrize("mod", ("setuptools", "distutils.core"))
def test_is_setup_py_different_encoding(tmp_path, mod): def test_is_setup_py_different_encoding(tmp_path: Path, mod: str) -> None:
setup_py = tmp_path.joinpath("setup.py") setup_py = tmp_path.joinpath("setup.py")
contents = ( contents = (
"# -*- coding: cp1252 -*-\n" "# -*- coding: cp1252 -*-\n"
'from {} import setup; setup(name="foo", description="")\n'.format(mod) 'from {} import setup; setup(name="foo", description="")\n'.format(mod)
) )
setup_py.write_bytes(contents.encode("cp1252")) setup_py.write_bytes(contents.encode("cp1252"))
assert _is_setup_py(py.path.local(str(setup_py))) assert _is_setup_py(setup_py)

View File

@ -16,7 +16,6 @@ def test_version_less_verbose(pytester: Pytester, pytestconfig, monkeypatch) ->
monkeypatch.delenv("PYTEST_DISABLE_PLUGIN_AUTOLOAD") monkeypatch.delenv("PYTEST_DISABLE_PLUGIN_AUTOLOAD")
result = pytester.runpytest("--version") result = pytester.runpytest("--version")
assert result.ret == 0 assert result.ret == 0
# p = py.path.local(py.__file__).dirpath()
result.stderr.fnmatch_lines([f"pytest {pytest.__version__}"]) result.stderr.fnmatch_lines([f"pytest {pytest.__version__}"])

View File

@ -1,3 +1,4 @@
from pathlib import Path
from typing import cast from typing import cast
from typing import List from typing import List
from typing import Type from typing import Type
@ -69,23 +70,23 @@ def test_node_warning_enforces_warning_types(pytester: Pytester) -> None:
def test__check_initialpaths_for_relpath() -> None: def test__check_initialpaths_for_relpath() -> None:
"""Ensure that it handles dirs, and does not always use dirname.""" """Ensure that it handles dirs, and does not always use dirname."""
cwd = py.path.local() cwd = Path.cwd()
class FakeSession1: class FakeSession1:
_initialpaths = [cwd] _initialpaths = frozenset({cwd})
session = cast(pytest.Session, FakeSession1) session = cast(pytest.Session, FakeSession1)
assert nodes._check_initialpaths_for_relpath(session, cwd) == "" assert nodes._check_initialpaths_for_relpath(session, py.path.local(cwd)) == ""
sub = cwd.join("file") sub = cwd / "file"
class FakeSession2: class FakeSession2:
_initialpaths = [cwd] _initialpaths = frozenset({cwd})
session = cast(pytest.Session, FakeSession2) session = cast(pytest.Session, FakeSession2)
assert nodes._check_initialpaths_for_relpath(session, sub) == "file" assert nodes._check_initialpaths_for_relpath(session, py.path.local(sub)) == "file"
outside = py.path.local("/outside") outside = py.path.local("/outside")
assert nodes._check_initialpaths_for_relpath(session, outside) is None assert nodes._check_initialpaths_for_relpath(session, outside) is None

View File

@ -1,8 +1,11 @@
import os.path import os.path
import pickle
import sys import sys
import unittest.mock import unittest.mock
from pathlib import Path from pathlib import Path
from textwrap import dedent from textwrap import dedent
from types import ModuleType
from typing import Generator
import py import py
@ -20,6 +23,7 @@ from _pytest.pathlib import maybe_delete_a_numbered_dir
from _pytest.pathlib import resolve_package_path from _pytest.pathlib import resolve_package_path
from _pytest.pathlib import symlink_or_skip from _pytest.pathlib import symlink_or_skip
from _pytest.pathlib import visit from _pytest.pathlib import visit
from _pytest.tmpdir import TempPathFactory
class TestFNMatcherPort: class TestFNMatcherPort:
@ -96,38 +100,40 @@ class TestImportPath:
""" """
@pytest.fixture(scope="session") @pytest.fixture(scope="session")
def path1(self, tmpdir_factory): def path1(self, tmp_path_factory: TempPathFactory) -> Generator[Path, None, None]:
path = tmpdir_factory.mktemp("path") path = tmp_path_factory.mktemp("path")
self.setuptestfs(path) self.setuptestfs(path)
yield path yield path
assert path.join("samplefile").check() assert path.joinpath("samplefile").exists()
def setuptestfs(self, path): def setuptestfs(self, path: Path) -> None:
# print "setting up test fs for", repr(path) # print "setting up test fs for", repr(path)
samplefile = path.ensure("samplefile") samplefile = path / "samplefile"
samplefile.write("samplefile\n") samplefile.write_text("samplefile\n")
execfile = path.ensure("execfile") execfile = path / "execfile"
execfile.write("x=42") execfile.write_text("x=42")
execfilepy = path.ensure("execfile.py") execfilepy = path / "execfile.py"
execfilepy.write("x=42") execfilepy.write_text("x=42")
d = {1: 2, "hello": "world", "answer": 42} d = {1: 2, "hello": "world", "answer": 42}
path.ensure("samplepickle").dump(d) path.joinpath("samplepickle").write_bytes(pickle.dumps(d, 1))
sampledir = path.ensure("sampledir", dir=1) sampledir = path / "sampledir"
sampledir.ensure("otherfile") sampledir.mkdir()
sampledir.joinpath("otherfile").touch()
otherdir = path.ensure("otherdir", dir=1) otherdir = path / "otherdir"
otherdir.ensure("__init__.py") otherdir.mkdir()
otherdir.joinpath("__init__.py").touch()
module_a = otherdir.ensure("a.py") module_a = otherdir / "a.py"
module_a.write("from .b import stuff as result\n") module_a.write_text("from .b import stuff as result\n")
module_b = otherdir.ensure("b.py") module_b = otherdir / "b.py"
module_b.write('stuff="got it"\n') module_b.write_text('stuff="got it"\n')
module_c = otherdir.ensure("c.py") module_c = otherdir / "c.py"
module_c.write( module_c.write_text(
dedent( dedent(
""" """
import py; import py;
@ -136,8 +142,8 @@ class TestImportPath:
""" """
) )
) )
module_d = otherdir.ensure("d.py") module_d = otherdir / "d.py"
module_d.write( module_d.write_text(
dedent( dedent(
""" """
import py; import py;
@ -147,122 +153,141 @@ class TestImportPath:
) )
) )
def test_smoke_test(self, path1): def test_smoke_test(self, path1: Path) -> None:
obj = import_path(path1.join("execfile.py")) obj = import_path(path1 / "execfile.py")
assert obj.x == 42 # type: ignore[attr-defined] assert obj.x == 42 # type: ignore[attr-defined]
assert obj.__name__ == "execfile" assert obj.__name__ == "execfile"
def test_renamed_dir_creates_mismatch(self, tmpdir, monkeypatch): def test_renamed_dir_creates_mismatch(
p = tmpdir.ensure("a", "test_x123.py") self, tmp_path: Path, monkeypatch: MonkeyPatch
) -> None:
tmp_path.joinpath("a").mkdir()
p = tmp_path.joinpath("a", "test_x123.py")
p.touch()
import_path(p) import_path(p)
tmpdir.join("a").move(tmpdir.join("b")) tmp_path.joinpath("a").rename(tmp_path.joinpath("b"))
with pytest.raises(ImportPathMismatchError): with pytest.raises(ImportPathMismatchError):
import_path(tmpdir.join("b", "test_x123.py")) import_path(tmp_path.joinpath("b", "test_x123.py"))
# Errors can be ignored. # Errors can be ignored.
monkeypatch.setenv("PY_IGNORE_IMPORTMISMATCH", "1") monkeypatch.setenv("PY_IGNORE_IMPORTMISMATCH", "1")
import_path(tmpdir.join("b", "test_x123.py")) import_path(tmp_path.joinpath("b", "test_x123.py"))
# PY_IGNORE_IMPORTMISMATCH=0 does not ignore error. # PY_IGNORE_IMPORTMISMATCH=0 does not ignore error.
monkeypatch.setenv("PY_IGNORE_IMPORTMISMATCH", "0") monkeypatch.setenv("PY_IGNORE_IMPORTMISMATCH", "0")
with pytest.raises(ImportPathMismatchError): with pytest.raises(ImportPathMismatchError):
import_path(tmpdir.join("b", "test_x123.py")) import_path(tmp_path.joinpath("b", "test_x123.py"))
def test_messy_name(self, tmpdir): def test_messy_name(self, tmp_path: Path) -> None:
# http://bitbucket.org/hpk42/py-trunk/issue/129 # http://bitbucket.org/hpk42/py-trunk/issue/129
path = tmpdir.ensure("foo__init__.py") path = tmp_path / "foo__init__.py"
path.touch()
module = import_path(path) module = import_path(path)
assert module.__name__ == "foo__init__" assert module.__name__ == "foo__init__"
def test_dir(self, tmpdir): def test_dir(self, tmp_path: Path) -> None:
p = tmpdir.join("hello_123") p = tmp_path / "hello_123"
p_init = p.ensure("__init__.py") p.mkdir()
p_init = p / "__init__.py"
p_init.touch()
m = import_path(p) m = import_path(p)
assert m.__name__ == "hello_123" assert m.__name__ == "hello_123"
m = import_path(p_init) m = import_path(p_init)
assert m.__name__ == "hello_123" assert m.__name__ == "hello_123"
def test_a(self, path1): def test_a(self, path1: Path) -> None:
otherdir = path1.join("otherdir") otherdir = path1 / "otherdir"
mod = import_path(otherdir.join("a.py")) mod = import_path(otherdir / "a.py")
assert mod.result == "got it" # type: ignore[attr-defined] assert mod.result == "got it" # type: ignore[attr-defined]
assert mod.__name__ == "otherdir.a" assert mod.__name__ == "otherdir.a"
def test_b(self, path1): def test_b(self, path1: Path) -> None:
otherdir = path1.join("otherdir") otherdir = path1 / "otherdir"
mod = import_path(otherdir.join("b.py")) mod = import_path(otherdir / "b.py")
assert mod.stuff == "got it" # type: ignore[attr-defined] assert mod.stuff == "got it" # type: ignore[attr-defined]
assert mod.__name__ == "otherdir.b" assert mod.__name__ == "otherdir.b"
def test_c(self, path1): def test_c(self, path1: Path) -> None:
otherdir = path1.join("otherdir") otherdir = path1 / "otherdir"
mod = import_path(otherdir.join("c.py")) mod = import_path(otherdir / "c.py")
assert mod.value == "got it" # type: ignore[attr-defined] assert mod.value == "got it" # type: ignore[attr-defined]
def test_d(self, path1): def test_d(self, path1: Path) -> None:
otherdir = path1.join("otherdir") otherdir = path1 / "otherdir"
mod = import_path(otherdir.join("d.py")) mod = import_path(otherdir / "d.py")
assert mod.value2 == "got it" # type: ignore[attr-defined] assert mod.value2 == "got it" # type: ignore[attr-defined]
def test_import_after(self, tmpdir): def test_import_after(self, tmp_path: Path) -> None:
tmpdir.ensure("xxxpackage", "__init__.py") tmp_path.joinpath("xxxpackage").mkdir()
mod1path = tmpdir.ensure("xxxpackage", "module1.py") tmp_path.joinpath("xxxpackage", "__init__.py").touch()
mod1path = tmp_path.joinpath("xxxpackage", "module1.py")
mod1path.touch()
mod1 = import_path(mod1path) mod1 = import_path(mod1path)
assert mod1.__name__ == "xxxpackage.module1" assert mod1.__name__ == "xxxpackage.module1"
from xxxpackage import module1 from xxxpackage import module1
assert module1 is mod1 assert module1 is mod1
def test_check_filepath_consistency(self, monkeypatch, tmpdir): def test_check_filepath_consistency(
self, monkeypatch: MonkeyPatch, tmp_path: Path
) -> None:
name = "pointsback123" name = "pointsback123"
ModuleType = type(os) p = tmp_path.joinpath(name + ".py")
p = tmpdir.ensure(name + ".py") p.touch()
for ending in (".pyc", ".pyo"): for ending in (".pyc", ".pyo"):
mod = ModuleType(name) mod = ModuleType(name)
pseudopath = tmpdir.ensure(name + ending) pseudopath = tmp_path.joinpath(name + ending)
pseudopath.touch()
mod.__file__ = str(pseudopath) mod.__file__ = str(pseudopath)
monkeypatch.setitem(sys.modules, name, mod) monkeypatch.setitem(sys.modules, name, mod)
newmod = import_path(p) newmod = import_path(p)
assert mod == newmod assert mod == newmod
monkeypatch.undo() monkeypatch.undo()
mod = ModuleType(name) mod = ModuleType(name)
pseudopath = tmpdir.ensure(name + "123.py") pseudopath = tmp_path.joinpath(name + "123.py")
pseudopath.touch()
mod.__file__ = str(pseudopath) mod.__file__ = str(pseudopath)
monkeypatch.setitem(sys.modules, name, mod) monkeypatch.setitem(sys.modules, name, mod)
with pytest.raises(ImportPathMismatchError) as excinfo: with pytest.raises(ImportPathMismatchError) as excinfo:
import_path(p) import_path(p)
modname, modfile, orig = excinfo.value.args modname, modfile, orig = excinfo.value.args
assert modname == name assert modname == name
assert modfile == pseudopath assert modfile == str(pseudopath)
assert orig == p assert orig == p
assert issubclass(ImportPathMismatchError, ImportError) assert issubclass(ImportPathMismatchError, ImportError)
def test_issue131_on__init__(self, tmpdir): def test_issue131_on__init__(self, tmp_path: Path) -> None:
# __init__.py files may be namespace packages, and thus the # __init__.py files may be namespace packages, and thus the
# __file__ of an imported module may not be ourselves # __file__ of an imported module may not be ourselves
# see issue # see issue
p1 = tmpdir.ensure("proja", "__init__.py") tmp_path.joinpath("proja").mkdir()
p2 = tmpdir.ensure("sub", "proja", "__init__.py") p1 = tmp_path.joinpath("proja", "__init__.py")
p1.touch()
tmp_path.joinpath("sub", "proja").mkdir(parents=True)
p2 = tmp_path.joinpath("sub", "proja", "__init__.py")
p2.touch()
m1 = import_path(p1) m1 = import_path(p1)
m2 = import_path(p2) m2 = import_path(p2)
assert m1 == m2 assert m1 == m2
def test_ensuresyspath_append(self, tmpdir): def test_ensuresyspath_append(self, tmp_path: Path) -> None:
root1 = tmpdir.mkdir("root1") root1 = tmp_path / "root1"
file1 = root1.ensure("x123.py") root1.mkdir()
file1 = root1 / "x123.py"
file1.touch()
assert str(root1) not in sys.path assert str(root1) not in sys.path
import_path(file1, mode="append") import_path(file1, mode="append")
assert str(root1) == sys.path[-1] assert str(root1) == sys.path[-1]
assert str(root1) not in sys.path[:-1] assert str(root1) not in sys.path[:-1]
def test_invalid_path(self, tmpdir): def test_invalid_path(self, tmp_path: Path) -> None:
with pytest.raises(ImportError): with pytest.raises(ImportError):
import_path(tmpdir.join("invalid.py")) import_path(tmp_path / "invalid.py")
@pytest.fixture @pytest.fixture
def simple_module(self, tmpdir): def simple_module(self, tmp_path: Path) -> Path:
fn = tmpdir.join("mymod.py") fn = tmp_path / "mymod.py"
fn.write( fn.write_text(
dedent( dedent(
""" """
def foo(x): return 40 + x def foo(x): return 40 + x
@ -271,19 +296,21 @@ class TestImportPath:
) )
return fn return fn
def test_importmode_importlib(self, simple_module): def test_importmode_importlib(self, simple_module: Path) -> None:
"""`importlib` mode does not change sys.path.""" """`importlib` mode does not change sys.path."""
module = import_path(simple_module, mode="importlib") module = import_path(simple_module, mode="importlib")
assert module.foo(2) == 42 # type: ignore[attr-defined] assert module.foo(2) == 42 # type: ignore[attr-defined]
assert simple_module.dirname not in sys.path assert str(simple_module.parent) not in sys.path
def test_importmode_twice_is_different_module(self, simple_module): def test_importmode_twice_is_different_module(self, simple_module: Path) -> None:
"""`importlib` mode always returns a new module.""" """`importlib` mode always returns a new module."""
module1 = import_path(simple_module, mode="importlib") module1 = import_path(simple_module, mode="importlib")
module2 = import_path(simple_module, mode="importlib") module2 = import_path(simple_module, mode="importlib")
assert module1 is not module2 assert module1 is not module2
def test_no_meta_path_found(self, simple_module, monkeypatch): def test_no_meta_path_found(
self, simple_module: Path, monkeypatch: MonkeyPatch
) -> None:
"""Even without any meta_path should still import module.""" """Even without any meta_path should still import module."""
monkeypatch.setattr(sys, "meta_path", []) monkeypatch.setattr(sys, "meta_path", [])
module = import_path(simple_module, mode="importlib") module = import_path(simple_module, mode="importlib")
@ -299,7 +326,7 @@ class TestImportPath:
import_path(simple_module, mode="importlib") import_path(simple_module, mode="importlib")
def test_resolve_package_path(tmp_path): def test_resolve_package_path(tmp_path: Path) -> None:
pkg = tmp_path / "pkg1" pkg = tmp_path / "pkg1"
pkg.mkdir() pkg.mkdir()
(pkg / "__init__.py").touch() (pkg / "__init__.py").touch()
@ -309,7 +336,7 @@ def test_resolve_package_path(tmp_path):
assert resolve_package_path(pkg.joinpath("subdir", "__init__.py")) == pkg assert resolve_package_path(pkg.joinpath("subdir", "__init__.py")) == pkg
def test_package_unimportable(tmp_path): def test_package_unimportable(tmp_path: Path) -> None:
pkg = tmp_path / "pkg1-1" pkg = tmp_path / "pkg1-1"
pkg.mkdir() pkg.mkdir()
pkg.joinpath("__init__.py").touch() pkg.joinpath("__init__.py").touch()
@ -323,7 +350,7 @@ def test_package_unimportable(tmp_path):
assert not resolve_package_path(pkg) assert not resolve_package_path(pkg)
def test_access_denied_during_cleanup(tmp_path, monkeypatch): def test_access_denied_during_cleanup(tmp_path: Path, monkeypatch: MonkeyPatch) -> None:
"""Ensure that deleting a numbered dir does not fail because of OSErrors (#4262).""" """Ensure that deleting a numbered dir does not fail because of OSErrors (#4262)."""
path = tmp_path / "temp-1" path = tmp_path / "temp-1"
path.mkdir() path.mkdir()
@ -338,7 +365,7 @@ def test_access_denied_during_cleanup(tmp_path, monkeypatch):
assert not lock_path.is_file() assert not lock_path.is_file()
def test_long_path_during_cleanup(tmp_path): def test_long_path_during_cleanup(tmp_path: Path) -> None:
"""Ensure that deleting long path works (particularly on Windows (#6775)).""" """Ensure that deleting long path works (particularly on Windows (#6775))."""
path = (tmp_path / ("a" * 250)).resolve() path = (tmp_path / ("a" * 250)).resolve()
if sys.platform == "win32": if sys.platform == "win32":
@ -354,14 +381,14 @@ def test_long_path_during_cleanup(tmp_path):
assert not os.path.isdir(extended_path) assert not os.path.isdir(extended_path)
def test_get_extended_length_path_str(): def test_get_extended_length_path_str() -> None:
assert get_extended_length_path_str(r"c:\foo") == r"\\?\c:\foo" assert get_extended_length_path_str(r"c:\foo") == r"\\?\c:\foo"
assert get_extended_length_path_str(r"\\share\foo") == r"\\?\UNC\share\foo" assert get_extended_length_path_str(r"\\share\foo") == r"\\?\UNC\share\foo"
assert get_extended_length_path_str(r"\\?\UNC\share\foo") == r"\\?\UNC\share\foo" assert get_extended_length_path_str(r"\\?\UNC\share\foo") == r"\\?\UNC\share\foo"
assert get_extended_length_path_str(r"\\?\c:\foo") == r"\\?\c:\foo" assert get_extended_length_path_str(r"\\?\c:\foo") == r"\\?\c:\foo"
def test_suppress_error_removing_lock(tmp_path): def test_suppress_error_removing_lock(tmp_path: Path) -> None:
"""ensure_deletable should be resilient if lock file cannot be removed (#5456, #7491)""" """ensure_deletable should be resilient if lock file cannot be removed (#5456, #7491)"""
path = tmp_path / "dir" path = tmp_path / "dir"
path.mkdir() path.mkdir()
@ -406,15 +433,14 @@ def test_commonpath() -> None:
assert commonpath(path, path.parent.parent) == path.parent.parent assert commonpath(path, path.parent.parent) == path.parent.parent
def test_visit_ignores_errors(tmpdir) -> None: def test_visit_ignores_errors(tmp_path: Path) -> None:
symlink_or_skip("recursive", tmpdir.join("recursive")) symlink_or_skip("recursive", tmp_path / "recursive")
tmpdir.join("foo").write_binary(b"") tmp_path.joinpath("foo").write_bytes(b"")
tmpdir.join("bar").write_binary(b"") tmp_path.joinpath("bar").write_bytes(b"")
assert [entry.name for entry in visit(tmpdir, recurse=lambda entry: False)] == [ assert [
"bar", entry.name for entry in visit(str(tmp_path), recurse=lambda entry: False)
"foo", ] == ["bar", "foo"]
]
@pytest.mark.skipif(not sys.platform.startswith("win"), reason="Windows only") @pytest.mark.skipif(not sys.platform.startswith("win"), reason="Windows only")