Skip to content

hookspec: add pathlib.Path alternatives to py.path.local parameters in hooks #8144

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Dec 15, 2020
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions changelog/8144.feature.rst
Original file line number Diff line number Diff line change
@@ -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).
5 changes: 2 additions & 3 deletions src/_pytest/assertion/rewrite.py
Original file line number Diff line number Diff line change
@@ -27,8 +27,6 @@
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.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 @@ def _should_rewrite(self, name: str, fn: str, state: "AssertionState") -> bool:
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

15 changes: 7 additions & 8 deletions src/_pytest/config/argparsing.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import argparse
import os
import sys
import warnings
from gettext import gettext
@@ -14,8 +15,6 @@
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 @@ def addoption(self, *opts: str, **attrs: Any) -> None:

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 @@ def _getparser(self) -> "MyOptionParser":

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,21 +138,21 @@ def parse_setoption(

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."""
return self.parse_known_and_unknown_args(args, namespace=namespace)[0]

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(
15 changes: 8 additions & 7 deletions src/_pytest/fixtures.py
Original file line number Diff line number Diff line change
@@ -648,12 +648,13 @@ def _compute_fixture_value(self, fixturedef: "FixtureDef[object]") -> None:
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 @@ def formatrepr(self) -> "FixtureLookupErrorRepr":
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 @@ def toterminal(self, tw: TerminalWriter) -> None:
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":
42 changes: 36 additions & 6 deletions src/_pytest/hookspec.py
Original file line number Diff line number Diff line change
@@ -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,27 +262,39 @@ 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
more specific hooks.
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.
"""


57 changes: 30 additions & 27 deletions src/_pytest/main.py
Original file line number Diff line number Diff line change
@@ -467,7 +467,7 @@ def __init__(self, config: Config) -> None:
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 @@ def pytest_runtest_logreport(

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 @@ def gethookproxy(self, fspath: "os.PathLike[str]"):
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,14 +545,17 @@ def _recurse(self, direntry: "os.DirEntry[str]") -> bool:
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(
path, path.isdir(), path.exists(), path.islink()
)
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 @@ def _collectfile(
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 @@ def perform_collect(
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 @@ def collect(self) -> Iterator[Union[nodes.Item, nodes.Collector]]:
# 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 @@ def collect(self) -> Iterator[Union[nodes.Item, nodes.Collector]]:

# 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 @@ def collect(self) -> Iterator[Union[nodes.Item, nodes.Collector]]:
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 @@ def collect(self) -> Iterator[Union[nodes.Item, nodes.Collector]]:
# 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
13 changes: 3 additions & 10 deletions src/_pytest/monkeypatch.py
Original file line number Diff line number Diff line change
@@ -4,7 +4,6 @@
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 @@ def syspath_prepend(self, path) -> None:

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.
10 changes: 7 additions & 3 deletions src/_pytest/nodes.py
Original file line number Diff line number Diff line change
@@ -480,10 +480,14 @@ def _prunetraceback(self, excinfo: ExceptionInfo[BaseException]) -> None:
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):
6 changes: 2 additions & 4 deletions src/_pytest/pathlib.py
Original file line number Diff line number Diff line change
@@ -30,8 +30,6 @@
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)
25 changes: 16 additions & 9 deletions src/_pytest/python.py
Original file line number Diff line number Diff line change
@@ -10,6 +10,7 @@
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 @@ def isinitpath(self, path: py.path.local) -> bool:
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,14 +680,17 @@ def _recurse(self, direntry: "os.DirEntry[str]") -> bool:
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(
path, path.isdir(), path.exists(), path.islink()
)
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 @@ def _collectfile(
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()
7 changes: 5 additions & 2 deletions src/_pytest/terminal.py
Original file line number Diff line number Diff line change
@@ -710,7 +710,7 @@ def pytest_sessionstart(self, session: "Session") -> None:
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 @@ def pytest_collection_finish(self, session: "Session") -> None:
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)

6 changes: 2 additions & 4 deletions testing/test_assertrewrite.py
Original file line number Diff line number Diff line change
@@ -17,8 +17,6 @@
from typing import Optional
from typing import Set

import py

import _pytest._code
import pytest
from _pytest.assertion import util
@@ -1311,7 +1309,7 @@ def hook(
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 @@ def fix(): return 1
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
54 changes: 24 additions & 30 deletions testing/test_main.py
Original file line number Diff line number Diff line change
@@ -4,13 +4,12 @@
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()

pkg = testdir.tmpdir.join("src/pkg").ensure_dir()
pkg.join("__init__.py").ensure()
pkg.join("test.py").ensure()
return testdir.tmpdir
def invocation_path(self, pytester: Pytester) -> Path:
pytester.syspathinsert(pytester.path / "src")
pytester.chdir()

@pytest.fixture
def invocation_path(self, invocation_dir: py.path.local) -> Path:
return Path(str(invocation_dir))
pkg = pytester.path.joinpath("src/pkg")
pkg.mkdir(parents=True)
pkg.joinpath("__init__.py").touch()
pkg.joinpath("test.py").touch()
return pytester.path

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 @@ def test_dir(self, invocation_dir: py.path.local, invocation_path: Path) -> None
):
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 @@ def test_does_not_exist(self, invocation_path: Path) -> None:
):
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 @@ def test_absolute_paths_are_resolved_correctly(
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):
11 changes: 8 additions & 3 deletions testing/test_nodes.py
Original file line number Diff line number Diff line change
@@ -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:
6 changes: 3 additions & 3 deletions testing/test_terminal.py
Original file line number Diff line number Diff line change
@@ -1010,7 +1010,7 @@ def test_more_quiet_reporting(self, pytester: Pytester) -> None:
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 @@ def pytest_report_header(config):
)
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")