Merge pull request #8144 from bluetech/py-to-pathlib-4
hookspec: add pathlib.Path alternatives to py.path.local parameters in hooks
This commit is contained in:
commit
6c899a0afa
|
@ -0,0 +1,7 @@
|
|||
The following hooks now receive an additional ``pathlib.Path`` argument, equivalent to an existing ``py.path.local`` argument:
|
||||
|
||||
- :func:`pytest_ignore_collect <_pytest.hookspec.pytest_ignore_collect>` - The ``fspath`` parameter (equivalent to existing ``path`` parameter).
|
||||
- :func:`pytest_collect_file <_pytest.hookspec.pytest_collect_file>` - The ``fspath`` parameter (equivalent to existing ``path`` parameter).
|
||||
- :func:`pytest_pycollect_makemodule <_pytest.hookspec.pytest_pycollect_makemodule>` - The ``fspath`` parameter (equivalent to existing ``path`` parameter).
|
||||
- :func:`pytest_report_header <_pytest.hookspec.pytest_report_header>` - The ``startpath`` parameter (equivalent to existing ``startdir`` parameter).
|
||||
- :func:`pytest_report_collectionfinish <_pytest.hookspec.pytest_report_collectionfinish>` - The ``startpath`` parameter (equivalent to existing ``startdir`` parameter).
|
|
@ -27,8 +27,6 @@ from typing import Tuple
|
|||
from typing import TYPE_CHECKING
|
||||
from typing import Union
|
||||
|
||||
import py
|
||||
|
||||
from _pytest._io.saferepr import saferepr
|
||||
from _pytest._version import version
|
||||
from _pytest.assertion import util
|
||||
|
@ -37,6 +35,7 @@ from _pytest.assertion.util import ( # noqa: F401
|
|||
)
|
||||
from _pytest.config import Config
|
||||
from _pytest.main import Session
|
||||
from _pytest.pathlib import absolutepath
|
||||
from _pytest.pathlib import fnmatch_ex
|
||||
from _pytest.store import StoreKey
|
||||
|
||||
|
@ -215,7 +214,7 @@ class AssertionRewritingHook(importlib.abc.MetaPathFinder, importlib.abc.Loader)
|
|||
return True
|
||||
|
||||
if self.session is not None:
|
||||
if self.session.isinitpath(py.path.local(fn)):
|
||||
if self.session.isinitpath(absolutepath(fn)):
|
||||
state.trace(f"matched test file (was specified on cmdline): {fn!r}")
|
||||
return True
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import argparse
|
||||
import os
|
||||
import sys
|
||||
import warnings
|
||||
from gettext import gettext
|
||||
|
@ -14,8 +15,6 @@ from typing import Tuple
|
|||
from typing import TYPE_CHECKING
|
||||
from typing import Union
|
||||
|
||||
import py
|
||||
|
||||
import _pytest._io
|
||||
from _pytest.compat import final
|
||||
from _pytest.config.exceptions import UsageError
|
||||
|
@ -97,14 +96,14 @@ class Parser:
|
|||
|
||||
def parse(
|
||||
self,
|
||||
args: Sequence[Union[str, py.path.local]],
|
||||
args: Sequence[Union[str, "os.PathLike[str]"]],
|
||||
namespace: Optional[argparse.Namespace] = None,
|
||||
) -> argparse.Namespace:
|
||||
from _pytest._argcomplete import try_argcomplete
|
||||
|
||||
self.optparser = self._getparser()
|
||||
try_argcomplete(self.optparser)
|
||||
strargs = [str(x) if isinstance(x, py.path.local) else x for x in args]
|
||||
strargs = [os.fspath(x) for x in args]
|
||||
return self.optparser.parse_args(strargs, namespace=namespace)
|
||||
|
||||
def _getparser(self) -> "MyOptionParser":
|
||||
|
@ -128,7 +127,7 @@ class Parser:
|
|||
|
||||
def parse_setoption(
|
||||
self,
|
||||
args: Sequence[Union[str, py.path.local]],
|
||||
args: Sequence[Union[str, "os.PathLike[str]"]],
|
||||
option: argparse.Namespace,
|
||||
namespace: Optional[argparse.Namespace] = None,
|
||||
) -> List[str]:
|
||||
|
@ -139,7 +138,7 @@ class Parser:
|
|||
|
||||
def parse_known_args(
|
||||
self,
|
||||
args: Sequence[Union[str, py.path.local]],
|
||||
args: Sequence[Union[str, "os.PathLike[str]"]],
|
||||
namespace: Optional[argparse.Namespace] = None,
|
||||
) -> argparse.Namespace:
|
||||
"""Parse and return a namespace object with known arguments at this point."""
|
||||
|
@ -147,13 +146,13 @@ class Parser:
|
|||
|
||||
def parse_known_and_unknown_args(
|
||||
self,
|
||||
args: Sequence[Union[str, py.path.local]],
|
||||
args: Sequence[Union[str, "os.PathLike[str]"]],
|
||||
namespace: Optional[argparse.Namespace] = None,
|
||||
) -> Tuple[argparse.Namespace, List[str]]:
|
||||
"""Parse and return a namespace object with known arguments, and
|
||||
the remaining arguments unknown at this point."""
|
||||
optparser = self._getparser()
|
||||
strargs = [str(x) if isinstance(x, py.path.local) else x for x in args]
|
||||
strargs = [os.fspath(x) for x in args]
|
||||
return optparser.parse_known_args(strargs, namespace=namespace)
|
||||
|
||||
def addini(
|
||||
|
|
|
@ -648,12 +648,13 @@ class FixtureRequest:
|
|||
if has_params:
|
||||
frame = inspect.stack()[3]
|
||||
frameinfo = inspect.getframeinfo(frame[0])
|
||||
source_path = py.path.local(frameinfo.filename)
|
||||
source_path = absolutepath(frameinfo.filename)
|
||||
source_lineno = frameinfo.lineno
|
||||
rel_source_path = source_path.relto(funcitem.config.rootdir)
|
||||
if rel_source_path:
|
||||
source_path_str = rel_source_path
|
||||
else:
|
||||
try:
|
||||
source_path_str = str(
|
||||
source_path.relative_to(funcitem.config.rootpath)
|
||||
)
|
||||
except ValueError:
|
||||
source_path_str = str(source_path)
|
||||
msg = (
|
||||
"The requested fixture has no parameter defined for test:\n"
|
||||
|
@ -876,7 +877,7 @@ class FixtureLookupError(LookupError):
|
|||
class FixtureLookupErrorRepr(TerminalRepr):
|
||||
def __init__(
|
||||
self,
|
||||
filename: Union[str, py.path.local],
|
||||
filename: Union[str, "os.PathLike[str]"],
|
||||
firstlineno: int,
|
||||
tblines: Sequence[str],
|
||||
errorstring: str,
|
||||
|
@ -903,7 +904,7 @@ class FixtureLookupErrorRepr(TerminalRepr):
|
|||
f"{FormattedExcinfo.flow_marker} {line.strip()}", red=True,
|
||||
)
|
||||
tw.line()
|
||||
tw.line("%s:%d" % (self.filename, self.firstlineno + 1))
|
||||
tw.line("%s:%d" % (os.fspath(self.filename), self.firstlineno + 1))
|
||||
|
||||
|
||||
def fail_fixturefunc(fixturefunc, msg: str) -> "NoReturn":
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
"""Hook specifications for pytest plugins which are invoked by pytest itself
|
||||
and by builtin plugins."""
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
from typing import Dict
|
||||
from typing import List
|
||||
|
@ -261,7 +262,9 @@ def pytest_collection_finish(session: "Session") -> None:
|
|||
|
||||
|
||||
@hookspec(firstresult=True)
|
||||
def pytest_ignore_collect(path: py.path.local, config: "Config") -> Optional[bool]:
|
||||
def pytest_ignore_collect(
|
||||
fspath: Path, path: py.path.local, config: "Config"
|
||||
) -> Optional[bool]:
|
||||
"""Return True to prevent considering this path for collection.
|
||||
|
||||
This hook is consulted for all files and directories prior to calling
|
||||
|
@ -269,19 +272,29 @@ def pytest_ignore_collect(path: py.path.local, config: "Config") -> Optional[boo
|
|||
|
||||
Stops at first non-None result, see :ref:`firstresult`.
|
||||
|
||||
:param pathlib.Path fspath: The path to analyze.
|
||||
:param py.path.local path: The path to analyze.
|
||||
:param _pytest.config.Config config: The pytest config object.
|
||||
|
||||
.. versionchanged:: 6.3.0
|
||||
The ``fspath`` parameter was added as a :class:`pathlib.Path`
|
||||
equivalent of the ``path`` parameter.
|
||||
"""
|
||||
|
||||
|
||||
def pytest_collect_file(
|
||||
path: py.path.local, parent: "Collector"
|
||||
fspath: Path, path: py.path.local, parent: "Collector"
|
||||
) -> "Optional[Collector]":
|
||||
"""Create a Collector for the given path, or None if not relevant.
|
||||
|
||||
The new node needs to have the specified ``parent`` as a parent.
|
||||
|
||||
:param pathlib.Path fspath: The path to analyze.
|
||||
:param py.path.local path: The path to collect.
|
||||
|
||||
.. versionchanged:: 6.3.0
|
||||
The ``fspath`` parameter was added as a :class:`pathlib.Path`
|
||||
equivalent of the ``path`` parameter.
|
||||
"""
|
||||
|
||||
|
||||
|
@ -321,7 +334,9 @@ def pytest_make_collect_report(collector: "Collector") -> "Optional[CollectRepor
|
|||
|
||||
|
||||
@hookspec(firstresult=True)
|
||||
def pytest_pycollect_makemodule(path: py.path.local, parent) -> Optional["Module"]:
|
||||
def pytest_pycollect_makemodule(
|
||||
fspath: Path, path: py.path.local, parent
|
||||
) -> Optional["Module"]:
|
||||
"""Return a Module collector or None for the given path.
|
||||
|
||||
This hook will be called for each matching test module path.
|
||||
|
@ -330,7 +345,12 @@ def pytest_pycollect_makemodule(path: py.path.local, parent) -> Optional["Module
|
|||
|
||||
Stops at first non-None result, see :ref:`firstresult`.
|
||||
|
||||
:param py.path.local path: The path of module to collect.
|
||||
:param pathlib.Path fspath: The path of the module to collect.
|
||||
:param py.path.local path: The path of the module to collect.
|
||||
|
||||
.. versionchanged:: 6.3.0
|
||||
The ``fspath`` parameter was added as a :class:`pathlib.Path`
|
||||
equivalent of the ``path`` parameter.
|
||||
"""
|
||||
|
||||
|
||||
|
@ -653,11 +673,12 @@ def pytest_assertion_pass(item: "Item", lineno: int, orig: str, expl: str) -> No
|
|||
|
||||
|
||||
def pytest_report_header(
|
||||
config: "Config", startdir: py.path.local
|
||||
config: "Config", startpath: Path, startdir: py.path.local
|
||||
) -> Union[str, List[str]]:
|
||||
"""Return a string or list of strings to be displayed as header info for terminal reporting.
|
||||
|
||||
:param _pytest.config.Config config: The pytest config object.
|
||||
:param Path startpath: The starting dir.
|
||||
:param py.path.local startdir: The starting dir.
|
||||
|
||||
.. note::
|
||||
|
@ -672,11 +693,15 @@ def pytest_report_header(
|
|||
This function should be implemented only in plugins or ``conftest.py``
|
||||
files situated at the tests root directory due to how pytest
|
||||
:ref:`discovers plugins during startup <pluginorder>`.
|
||||
|
||||
.. versionchanged:: 6.3.0
|
||||
The ``startpath`` parameter was added as a :class:`pathlib.Path`
|
||||
equivalent of the ``startdir`` parameter.
|
||||
"""
|
||||
|
||||
|
||||
def pytest_report_collectionfinish(
|
||||
config: "Config", startdir: py.path.local, items: Sequence["Item"],
|
||||
config: "Config", startpath: Path, startdir: py.path.local, items: Sequence["Item"],
|
||||
) -> Union[str, List[str]]:
|
||||
"""Return a string or list of strings to be displayed after collection
|
||||
has finished successfully.
|
||||
|
@ -686,6 +711,7 @@ def pytest_report_collectionfinish(
|
|||
.. versionadded:: 3.2
|
||||
|
||||
:param _pytest.config.Config config: The pytest config object.
|
||||
:param Path startpath: The starting path.
|
||||
:param py.path.local startdir: The starting dir.
|
||||
:param items: List of pytest items that are going to be executed; this list should not be modified.
|
||||
|
||||
|
@ -695,6 +721,10 @@ def pytest_report_collectionfinish(
|
|||
ran before it.
|
||||
If you want to have your line(s) displayed first, use
|
||||
:ref:`trylast=True <plugin-hookorder>`.
|
||||
|
||||
.. versionchanged:: 6.3.0
|
||||
The ``startpath`` parameter was added as a :class:`pathlib.Path`
|
||||
equivalent of the ``startdir`` parameter.
|
||||
"""
|
||||
|
||||
|
||||
|
|
|
@ -467,7 +467,7 @@ class Session(nodes.FSCollector):
|
|||
self.shouldfail: Union[bool, str] = False
|
||||
self.trace = config.trace.root.get("collection")
|
||||
self.startdir = config.invocation_dir
|
||||
self._initialpaths: FrozenSet[py.path.local] = frozenset()
|
||||
self._initialpaths: FrozenSet[Path] = frozenset()
|
||||
|
||||
self._bestrelpathcache: Dict[Path, str] = _bestrelpath_cache(config.rootpath)
|
||||
|
||||
|
@ -510,8 +510,8 @@ class Session(nodes.FSCollector):
|
|||
|
||||
pytest_collectreport = pytest_runtest_logreport
|
||||
|
||||
def isinitpath(self, path: py.path.local) -> bool:
|
||||
return path in self._initialpaths
|
||||
def isinitpath(self, path: Union[str, "os.PathLike[str]"]) -> bool:
|
||||
return Path(path) in self._initialpaths
|
||||
|
||||
def gethookproxy(self, fspath: "os.PathLike[str]"):
|
||||
# Check if we have the common case of running
|
||||
|
@ -532,9 +532,10 @@ class Session(nodes.FSCollector):
|
|||
def _recurse(self, direntry: "os.DirEntry[str]") -> bool:
|
||||
if direntry.name == "__pycache__":
|
||||
return False
|
||||
path = py.path.local(direntry.path)
|
||||
ihook = self.gethookproxy(path.dirpath())
|
||||
if ihook.pytest_ignore_collect(path=path, config=self.config):
|
||||
fspath = Path(direntry.path)
|
||||
path = py.path.local(fspath)
|
||||
ihook = self.gethookproxy(fspath.parent)
|
||||
if ihook.pytest_ignore_collect(fspath=fspath, path=path, config=self.config):
|
||||
return False
|
||||
norecursepatterns = self.config.getini("norecursedirs")
|
||||
if any(path.check(fnmatch=pat) for pat in norecursepatterns):
|
||||
|
@ -544,6 +545,7 @@ class Session(nodes.FSCollector):
|
|||
def _collectfile(
|
||||
self, path: py.path.local, handle_dupes: bool = True
|
||||
) -> Sequence[nodes.Collector]:
|
||||
fspath = Path(path)
|
||||
assert (
|
||||
path.isfile()
|
||||
), "{!r} is not a file (isdir={!r}, exists={!r}, islink={!r})".format(
|
||||
|
@ -551,7 +553,9 @@ class Session(nodes.FSCollector):
|
|||
)
|
||||
ihook = self.gethookproxy(path)
|
||||
if not self.isinitpath(path):
|
||||
if ihook.pytest_ignore_collect(path=path, config=self.config):
|
||||
if ihook.pytest_ignore_collect(
|
||||
fspath=fspath, path=path, config=self.config
|
||||
):
|
||||
return ()
|
||||
|
||||
if handle_dupes:
|
||||
|
@ -563,7 +567,7 @@ class Session(nodes.FSCollector):
|
|||
else:
|
||||
duplicate_paths.add(path)
|
||||
|
||||
return ihook.pytest_collect_file(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]
|
||||
|
||||
@overload
|
||||
def perform_collect(
|
||||
|
@ -601,14 +605,14 @@ class Session(nodes.FSCollector):
|
|||
self.trace.root.indent += 1
|
||||
|
||||
self._notfound: List[Tuple[str, Sequence[nodes.Collector]]] = []
|
||||
self._initial_parts: List[Tuple[py.path.local, List[str]]] = []
|
||||
self._initial_parts: List[Tuple[Path, List[str]]] = []
|
||||
self.items: List[nodes.Item] = []
|
||||
|
||||
hook = self.config.hook
|
||||
|
||||
items: Sequence[Union[nodes.Item, nodes.Collector]] = self.items
|
||||
try:
|
||||
initialpaths: List[py.path.local] = []
|
||||
initialpaths: List[Path] = []
|
||||
for arg in args:
|
||||
fspath, parts = resolve_collection_argument(
|
||||
self.config.invocation_params.dir,
|
||||
|
@ -669,13 +673,13 @@ class Session(nodes.FSCollector):
|
|||
# No point in finding packages when collecting doctests.
|
||||
if not self.config.getoption("doctestmodules", False):
|
||||
pm = self.config.pluginmanager
|
||||
confcutdir = py.path.local(pm._confcutdir) if pm._confcutdir else None
|
||||
for parent in reversed(argpath.parts()):
|
||||
if confcutdir and confcutdir.relto(parent):
|
||||
confcutdir = pm._confcutdir
|
||||
for parent in (argpath, *argpath.parents):
|
||||
if confcutdir and parent in confcutdir.parents:
|
||||
break
|
||||
|
||||
if parent.isdir():
|
||||
pkginit = parent.join("__init__.py")
|
||||
if parent.is_dir():
|
||||
pkginit = py.path.local(parent / "__init__.py")
|
||||
if pkginit.isfile() and pkginit not in node_cache1:
|
||||
col = self._collectfile(pkginit, handle_dupes=False)
|
||||
if col:
|
||||
|
@ -685,7 +689,7 @@ class Session(nodes.FSCollector):
|
|||
|
||||
# If it's a directory argument, recurse and look for any Subpackages.
|
||||
# Let the Package collector deal with subnodes, don't collect here.
|
||||
if argpath.check(dir=1):
|
||||
if argpath.is_dir():
|
||||
assert not names, "invalid arg {!r}".format((argpath, names))
|
||||
|
||||
seen_dirs: Set[py.path.local] = set()
|
||||
|
@ -717,15 +721,16 @@ class Session(nodes.FSCollector):
|
|||
node_cache2[key] = x
|
||||
yield x
|
||||
else:
|
||||
assert argpath.check(file=1)
|
||||
assert argpath.is_file()
|
||||
|
||||
if argpath in node_cache1:
|
||||
col = node_cache1[argpath]
|
||||
argpath_ = py.path.local(argpath)
|
||||
if argpath_ in node_cache1:
|
||||
col = node_cache1[argpath_]
|
||||
else:
|
||||
collect_root = pkg_roots.get(argpath.dirname, self)
|
||||
col = collect_root._collectfile(argpath, handle_dupes=False)
|
||||
collect_root = pkg_roots.get(argpath_.dirname, self)
|
||||
col = collect_root._collectfile(argpath_, handle_dupes=False)
|
||||
if col:
|
||||
node_cache1[argpath] = col
|
||||
node_cache1[argpath_] = col
|
||||
|
||||
matching = []
|
||||
work: List[
|
||||
|
@ -782,9 +787,7 @@ class Session(nodes.FSCollector):
|
|||
# first yielded item will be the __init__ Module itself, so
|
||||
# just use that. If this special case isn't taken, then all the
|
||||
# files in the package will be yielded.
|
||||
if argpath.basename == "__init__.py" and isinstance(
|
||||
matching[0], Package
|
||||
):
|
||||
if argpath.name == "__init__.py" and isinstance(matching[0], Package):
|
||||
try:
|
||||
yield next(iter(matching[0].collect()))
|
||||
except StopIteration:
|
||||
|
@ -833,7 +836,7 @@ def search_pypath(module_name: str) -> str:
|
|||
|
||||
def resolve_collection_argument(
|
||||
invocation_path: Path, arg: str, *, as_pypath: bool = False
|
||||
) -> Tuple[py.path.local, List[str]]:
|
||||
) -> Tuple[Path, List[str]]:
|
||||
"""Parse path arguments optionally containing selection parts and return (fspath, names).
|
||||
|
||||
Command-line arguments can point to files and/or directories, and optionally contain
|
||||
|
@ -875,4 +878,4 @@ def resolve_collection_argument(
|
|||
else "directory argument cannot contain :: selection parts: {arg}"
|
||||
)
|
||||
raise UsageError(msg.format(arg=arg))
|
||||
return py.path.local(str(fspath)), parts
|
||||
return fspath, parts
|
||||
|
|
|
@ -4,7 +4,6 @@ import re
|
|||
import sys
|
||||
import warnings
|
||||
from contextlib import contextmanager
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
from typing import Generator
|
||||
from typing import List
|
||||
|
@ -325,20 +324,14 @@ class MonkeyPatch:
|
|||
|
||||
invalidate_caches()
|
||||
|
||||
def chdir(self, path) -> None:
|
||||
def chdir(self, path: Union[str, "os.PathLike[str]"]) -> None:
|
||||
"""Change the current working directory to the specified path.
|
||||
|
||||
Path can be a string or a py.path.local object.
|
||||
Path can be a string or a path object.
|
||||
"""
|
||||
if self._cwd is None:
|
||||
self._cwd = os.getcwd()
|
||||
if hasattr(path, "chdir"):
|
||||
path.chdir()
|
||||
elif isinstance(path, Path):
|
||||
# Modern python uses the fspath protocol here LEGACY
|
||||
os.chdir(str(path))
|
||||
else:
|
||||
os.chdir(path)
|
||||
os.chdir(path)
|
||||
|
||||
def undo(self) -> None:
|
||||
"""Undo previous changes.
|
||||
|
|
|
@ -480,10 +480,14 @@ class Collector(Node):
|
|||
excinfo.traceback = ntraceback.filter()
|
||||
|
||||
|
||||
def _check_initialpaths_for_relpath(session, fspath):
|
||||
def _check_initialpaths_for_relpath(
|
||||
session: "Session", fspath: py.path.local
|
||||
) -> Optional[str]:
|
||||
for initial_path in session._initialpaths:
|
||||
if fspath.common(initial_path) == initial_path:
|
||||
return fspath.relto(initial_path)
|
||||
initial_path_ = py.path.local(initial_path)
|
||||
if fspath.common(initial_path_) == initial_path_:
|
||||
return fspath.relto(initial_path_)
|
||||
return None
|
||||
|
||||
|
||||
class FSCollector(Collector):
|
||||
|
|
|
@ -30,8 +30,6 @@ from typing import Set
|
|||
from typing import TypeVar
|
||||
from typing import Union
|
||||
|
||||
import py
|
||||
|
||||
from _pytest.compat import assert_never
|
||||
from _pytest.outcomes import skip
|
||||
from _pytest.warning_types import PytestWarning
|
||||
|
@ -456,7 +454,7 @@ class ImportPathMismatchError(ImportError):
|
|||
|
||||
|
||||
def import_path(
|
||||
p: Union[str, py.path.local, Path],
|
||||
p: Union[str, "os.PathLike[str]"],
|
||||
*,
|
||||
mode: Union[str, ImportMode] = ImportMode.prepend,
|
||||
) -> ModuleType:
|
||||
|
@ -482,7 +480,7 @@ def import_path(
|
|||
"""
|
||||
mode = ImportMode(mode)
|
||||
|
||||
path = Path(str(p))
|
||||
path = Path(p)
|
||||
|
||||
if not path.exists():
|
||||
raise ImportError(path)
|
||||
|
|
|
@ -10,6 +10,7 @@ import warnings
|
|||
from collections import Counter
|
||||
from collections import defaultdict
|
||||
from functools import partial
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
from typing import Callable
|
||||
from typing import Dict
|
||||
|
@ -187,17 +188,19 @@ def pytest_pyfunc_call(pyfuncitem: "Function") -> Optional[object]:
|
|||
|
||||
|
||||
def pytest_collect_file(
|
||||
path: py.path.local, parent: nodes.Collector
|
||||
fspath: Path, path: py.path.local, parent: nodes.Collector
|
||||
) -> Optional["Module"]:
|
||||
ext = path.ext
|
||||
if ext == ".py":
|
||||
if not parent.session.isinitpath(path):
|
||||
if not parent.session.isinitpath(fspath):
|
||||
if not path_matches_patterns(
|
||||
path, parent.config.getini("python_files") + ["__init__.py"]
|
||||
):
|
||||
return None
|
||||
ihook = parent.session.gethookproxy(path)
|
||||
module: Module = ihook.pytest_pycollect_makemodule(path=path, parent=parent)
|
||||
ihook = parent.session.gethookproxy(fspath)
|
||||
module: Module = ihook.pytest_pycollect_makemodule(
|
||||
fspath=fspath, path=path, parent=parent
|
||||
)
|
||||
return module
|
||||
return None
|
||||
|
||||
|
@ -664,9 +667,10 @@ class Package(Module):
|
|||
def _recurse(self, direntry: "os.DirEntry[str]") -> bool:
|
||||
if direntry.name == "__pycache__":
|
||||
return False
|
||||
path = py.path.local(direntry.path)
|
||||
ihook = self.session.gethookproxy(path.dirpath())
|
||||
if ihook.pytest_ignore_collect(path=path, config=self.config):
|
||||
fspath = Path(direntry.path)
|
||||
path = py.path.local(fspath)
|
||||
ihook = self.session.gethookproxy(fspath.parent)
|
||||
if ihook.pytest_ignore_collect(fspath=fspath, path=path, config=self.config):
|
||||
return False
|
||||
norecursepatterns = self.config.getini("norecursedirs")
|
||||
if any(path.check(fnmatch=pat) for pat in norecursepatterns):
|
||||
|
@ -676,6 +680,7 @@ class Package(Module):
|
|||
def _collectfile(
|
||||
self, path: py.path.local, handle_dupes: bool = True
|
||||
) -> Sequence[nodes.Collector]:
|
||||
fspath = Path(path)
|
||||
assert (
|
||||
path.isfile()
|
||||
), "{!r} is not a file (isdir={!r}, exists={!r}, islink={!r})".format(
|
||||
|
@ -683,7 +688,9 @@ class Package(Module):
|
|||
)
|
||||
ihook = self.session.gethookproxy(path)
|
||||
if not self.session.isinitpath(path):
|
||||
if ihook.pytest_ignore_collect(path=path, config=self.config):
|
||||
if ihook.pytest_ignore_collect(
|
||||
fspath=fspath, path=path, config=self.config
|
||||
):
|
||||
return ()
|
||||
|
||||
if handle_dupes:
|
||||
|
@ -695,7 +702,7 @@ class Package(Module):
|
|||
else:
|
||||
duplicate_paths.add(path)
|
||||
|
||||
return ihook.pytest_collect_file(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]]:
|
||||
this_path = self.fspath.dirpath()
|
||||
|
|
|
@ -710,7 +710,7 @@ class TerminalReporter:
|
|||
msg += " -- " + str(sys.executable)
|
||||
self.write_line(msg)
|
||||
lines = self.config.hook.pytest_report_header(
|
||||
config=self.config, startdir=self.startdir
|
||||
config=self.config, startpath=self.startpath, startdir=self.startdir
|
||||
)
|
||||
self._write_report_lines_from_hooks(lines)
|
||||
|
||||
|
@ -745,7 +745,10 @@ class TerminalReporter:
|
|||
self.report_collect(True)
|
||||
|
||||
lines = self.config.hook.pytest_report_collectionfinish(
|
||||
config=self.config, startdir=self.startdir, items=session.items
|
||||
config=self.config,
|
||||
startpath=self.startpath,
|
||||
startdir=self.startdir,
|
||||
items=session.items,
|
||||
)
|
||||
self._write_report_lines_from_hooks(lines)
|
||||
|
||||
|
|
|
@ -17,8 +17,6 @@ from typing import Mapping
|
|||
from typing import Optional
|
||||
from typing import Set
|
||||
|
||||
import py
|
||||
|
||||
import _pytest._code
|
||||
import pytest
|
||||
from _pytest.assertion import util
|
||||
|
@ -1311,7 +1309,7 @@ class TestEarlyRewriteBailout:
|
|||
import importlib.machinery
|
||||
|
||||
self.find_spec_calls: List[str] = []
|
||||
self.initial_paths: Set[py.path.local] = set()
|
||||
self.initial_paths: Set[Path] = set()
|
||||
|
||||
class StubSession:
|
||||
_initialpaths = self.initial_paths
|
||||
|
@ -1346,7 +1344,7 @@ class TestEarlyRewriteBailout:
|
|||
pytester.makepyfile(test_foo="def test_foo(): pass")
|
||||
pytester.makepyfile(bar="def bar(): pass")
|
||||
foobar_path = pytester.makepyfile(foobar="def foobar(): pass")
|
||||
self.initial_paths.add(py.path.local(foobar_path))
|
||||
self.initial_paths.add(foobar_path)
|
||||
|
||||
# conftest files should always be rewritten
|
||||
assert hook.find_spec("conftest") is not None
|
||||
|
|
|
@ -4,13 +4,12 @@ import re
|
|||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
import py.path
|
||||
|
||||
import pytest
|
||||
from _pytest.config import ExitCode
|
||||
from _pytest.config import UsageError
|
||||
from _pytest.main import resolve_collection_argument
|
||||
from _pytest.main import validate_basetemp
|
||||
from _pytest.pytester import Pytester
|
||||
from _pytest.pytester import Testdir
|
||||
|
||||
|
||||
|
@ -109,40 +108,37 @@ def test_validate_basetemp_integration(testdir):
|
|||
|
||||
class TestResolveCollectionArgument:
|
||||
@pytest.fixture
|
||||
def invocation_dir(self, testdir: Testdir) -> py.path.local:
|
||||
testdir.syspathinsert(str(testdir.tmpdir / "src"))
|
||||
testdir.chdir()
|
||||
def invocation_path(self, pytester: Pytester) -> Path:
|
||||
pytester.syspathinsert(pytester.path / "src")
|
||||
pytester.chdir()
|
||||
|
||||
pkg = testdir.tmpdir.join("src/pkg").ensure_dir()
|
||||
pkg.join("__init__.py").ensure()
|
||||
pkg.join("test.py").ensure()
|
||||
return testdir.tmpdir
|
||||
pkg = pytester.path.joinpath("src/pkg")
|
||||
pkg.mkdir(parents=True)
|
||||
pkg.joinpath("__init__.py").touch()
|
||||
pkg.joinpath("test.py").touch()
|
||||
return pytester.path
|
||||
|
||||
@pytest.fixture
|
||||
def invocation_path(self, invocation_dir: py.path.local) -> Path:
|
||||
return Path(str(invocation_dir))
|
||||
|
||||
def test_file(self, invocation_dir: py.path.local, invocation_path: Path) -> None:
|
||||
def test_file(self, invocation_path: Path) -> None:
|
||||
"""File and parts."""
|
||||
assert resolve_collection_argument(invocation_path, "src/pkg/test.py") == (
|
||||
invocation_dir / "src/pkg/test.py",
|
||||
invocation_path / "src/pkg/test.py",
|
||||
[],
|
||||
)
|
||||
assert resolve_collection_argument(invocation_path, "src/pkg/test.py::") == (
|
||||
invocation_dir / "src/pkg/test.py",
|
||||
invocation_path / "src/pkg/test.py",
|
||||
[""],
|
||||
)
|
||||
assert resolve_collection_argument(
|
||||
invocation_path, "src/pkg/test.py::foo::bar"
|
||||
) == (invocation_dir / "src/pkg/test.py", ["foo", "bar"])
|
||||
) == (invocation_path / "src/pkg/test.py", ["foo", "bar"])
|
||||
assert resolve_collection_argument(
|
||||
invocation_path, "src/pkg/test.py::foo::bar::"
|
||||
) == (invocation_dir / "src/pkg/test.py", ["foo", "bar", ""])
|
||||
) == (invocation_path / "src/pkg/test.py", ["foo", "bar", ""])
|
||||
|
||||
def test_dir(self, invocation_dir: py.path.local, invocation_path: Path) -> None:
|
||||
def test_dir(self, invocation_path: Path) -> None:
|
||||
"""Directory and parts."""
|
||||
assert resolve_collection_argument(invocation_path, "src/pkg") == (
|
||||
invocation_dir / "src/pkg",
|
||||
invocation_path / "src/pkg",
|
||||
[],
|
||||
)
|
||||
|
||||
|
@ -156,16 +152,16 @@ class TestResolveCollectionArgument:
|
|||
):
|
||||
resolve_collection_argument(invocation_path, "src/pkg::foo::bar")
|
||||
|
||||
def test_pypath(self, invocation_dir: py.path.local, invocation_path: Path) -> None:
|
||||
def test_pypath(self, invocation_path: Path) -> None:
|
||||
"""Dotted name and parts."""
|
||||
assert resolve_collection_argument(
|
||||
invocation_path, "pkg.test", as_pypath=True
|
||||
) == (invocation_dir / "src/pkg/test.py", [])
|
||||
) == (invocation_path / "src/pkg/test.py", [])
|
||||
assert resolve_collection_argument(
|
||||
invocation_path, "pkg.test::foo::bar", as_pypath=True
|
||||
) == (invocation_dir / "src/pkg/test.py", ["foo", "bar"])
|
||||
) == (invocation_path / "src/pkg/test.py", ["foo", "bar"])
|
||||
assert resolve_collection_argument(invocation_path, "pkg", as_pypath=True) == (
|
||||
invocation_dir / "src/pkg",
|
||||
invocation_path / "src/pkg",
|
||||
[],
|
||||
)
|
||||
|
||||
|
@ -191,13 +187,11 @@ class TestResolveCollectionArgument:
|
|||
):
|
||||
resolve_collection_argument(invocation_path, "foobar", as_pypath=True)
|
||||
|
||||
def test_absolute_paths_are_resolved_correctly(
|
||||
self, invocation_dir: py.path.local, invocation_path: Path
|
||||
) -> None:
|
||||
def test_absolute_paths_are_resolved_correctly(self, invocation_path: Path) -> None:
|
||||
"""Absolute paths resolve back to absolute paths."""
|
||||
full_path = str(invocation_dir / "src")
|
||||
full_path = str(invocation_path / "src")
|
||||
assert resolve_collection_argument(invocation_path, full_path) == (
|
||||
py.path.local(os.path.abspath("src")),
|
||||
Path(os.path.abspath("src")),
|
||||
[],
|
||||
)
|
||||
|
||||
|
@ -206,7 +200,7 @@ class TestResolveCollectionArgument:
|
|||
drive, full_path_without_drive = os.path.splitdrive(full_path)
|
||||
assert resolve_collection_argument(
|
||||
invocation_path, full_path_without_drive
|
||||
) == (py.path.local(os.path.abspath("src")), [])
|
||||
) == (Path(os.path.abspath("src")), [])
|
||||
|
||||
|
||||
def test_module_full_path_without_drive(testdir):
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
from typing import cast
|
||||
from typing import List
|
||||
from typing import Type
|
||||
|
||||
|
@ -73,17 +74,21 @@ def test__check_initialpaths_for_relpath() -> None:
|
|||
class FakeSession1:
|
||||
_initialpaths = [cwd]
|
||||
|
||||
assert nodes._check_initialpaths_for_relpath(FakeSession1, cwd) == ""
|
||||
session = cast(pytest.Session, FakeSession1)
|
||||
|
||||
assert nodes._check_initialpaths_for_relpath(session, cwd) == ""
|
||||
|
||||
sub = cwd.join("file")
|
||||
|
||||
class FakeSession2:
|
||||
_initialpaths = [cwd]
|
||||
|
||||
assert nodes._check_initialpaths_for_relpath(FakeSession2, sub) == "file"
|
||||
session = cast(pytest.Session, FakeSession2)
|
||||
|
||||
assert nodes._check_initialpaths_for_relpath(session, sub) == "file"
|
||||
|
||||
outside = py.path.local("/outside")
|
||||
assert nodes._check_initialpaths_for_relpath(FakeSession2, outside) is None
|
||||
assert nodes._check_initialpaths_for_relpath(session, outside) is None
|
||||
|
||||
|
||||
def test_failure_with_changed_cwd(pytester: Pytester) -> None:
|
||||
|
|
|
@ -1010,7 +1010,7 @@ class TestTerminalFunctional:
|
|||
def test_report_collectionfinish_hook(self, pytester: Pytester, params) -> None:
|
||||
pytester.makeconftest(
|
||||
"""
|
||||
def pytest_report_collectionfinish(config, startdir, items):
|
||||
def pytest_report_collectionfinish(config, startpath, startdir, items):
|
||||
return ['hello from hook: {0} items'.format(len(items))]
|
||||
"""
|
||||
)
|
||||
|
@ -1436,8 +1436,8 @@ class TestGenericReporting:
|
|||
)
|
||||
pytester.mkdir("a").joinpath("conftest.py").write_text(
|
||||
"""
|
||||
def pytest_report_header(config, startdir):
|
||||
return ["line1", str(startdir)]
|
||||
def pytest_report_header(config, startdir, startpath):
|
||||
return ["line1", str(startpath)]
|
||||
"""
|
||||
)
|
||||
result = pytester.runpytest("a")
|
||||
|
|
Loading…
Reference in New Issue