aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/python/pytest/py3/_pytest/main.py
diff options
context:
space:
mode:
authorarcadia-devtools <arcadia-devtools@yandex-team.ru>2022-02-14 00:49:36 +0300
committerarcadia-devtools <arcadia-devtools@yandex-team.ru>2022-02-14 00:49:36 +0300
commit82cfd1b7cab2d843cdf5467d9737f72597a493bd (patch)
tree1dfdcfe81a1a6b193ceacc2a828c521b657a339b /contrib/python/pytest/py3/_pytest/main.py
parent3df7211d3e3691f8e33b0a1fb1764fe810d59302 (diff)
downloadydb-82cfd1b7cab2d843cdf5467d9737f72597a493bd.tar.gz
intermediate changes
ref:68b1302de4b5da30b6bdf02193f7a2604d8b5cf8
Diffstat (limited to 'contrib/python/pytest/py3/_pytest/main.py')
-rw-r--r--contrib/python/pytest/py3/_pytest/main.py186
1 files changed, 103 insertions, 83 deletions
diff --git a/contrib/python/pytest/py3/_pytest/main.py b/contrib/python/pytest/py3/_pytest/main.py
index 41a33d4494..fea8179ca7 100644
--- a/contrib/python/pytest/py3/_pytest/main.py
+++ b/contrib/python/pytest/py3/_pytest/main.py
@@ -21,7 +21,6 @@ from typing import TYPE_CHECKING
from typing import Union
import attr
-import py
import _pytest._code
from _pytest import nodes
@@ -37,6 +36,7 @@ from _pytest.fixtures import FixtureManager
from _pytest.outcomes import exit
from _pytest.pathlib import absolutepath
from _pytest.pathlib import bestrelpath
+from _pytest.pathlib import fnmatch_ex
from _pytest.pathlib import visit
from _pytest.reports import CollectReport
from _pytest.reports import TestReport
@@ -115,7 +115,9 @@ def pytest_addoption(parser: Parser) -> None:
help="markers not registered in the `markers` section of the configuration file raise errors.",
)
group._addoption(
- "--strict", action="store_true", help="(deprecated) alias to --strict-markers.",
+ "--strict",
+ action="store_true",
+ help="(deprecated) alias to --strict-markers.",
)
group._addoption(
"-c",
@@ -237,10 +239,7 @@ def validate_basetemp(path: str) -> str:
"""Return whether query is an ancestor of base."""
if base == query:
return True
- for parent in base.parents:
- if parent == query:
- return True
- return False
+ return query in base.parents
# check if path is an ancestor of cwd
if is_ancestor(Path.cwd(), Path(path).absolute()):
@@ -290,7 +289,7 @@ def wrap_session(
except exit.Exception as exc:
if exc.returncode is not None:
session.exitstatus = exc.returncode
- sys.stderr.write("{}: {}\n".format(type(exc).__name__, exc))
+ sys.stderr.write(f"{type(exc).__name__}: {exc}\n")
else:
if isinstance(excinfo.value, SystemExit):
sys.stderr.write("mainloop: caught unexpected SystemExit!\n")
@@ -298,7 +297,7 @@ def wrap_session(
finally:
# Explicitly break reference cycle.
excinfo = None # type: ignore
- session.startdir.chdir()
+ os.chdir(session.startpath)
if initstate >= 2:
try:
config.hook.pytest_sessionfinish(
@@ -307,7 +306,7 @@ def wrap_session(
except exit.Exception as exc:
if exc.returncode is not None:
session.exitstatus = exc.returncode
- sys.stderr.write("{}: {}\n".format(type(exc).__name__, exc))
+ sys.stderr.write(f"{type(exc).__name__}: {exc}\n")
config._ensure_unconfigure()
return session.exitstatus
@@ -353,11 +352,14 @@ def pytest_runtestloop(session: "Session") -> bool:
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
checking for the existence of the appropriate activate script."""
- bindir = path.join("Scripts" if sys.platform.startswith("win") else "bin")
- if not bindir.isdir():
+ bindir = path.joinpath("Scripts" if sys.platform.startswith("win") else "bin")
+ try:
+ if not bindir.is_dir():
+ return False
+ except OSError:
return False
activates = (
"activate",
@@ -367,32 +369,34 @@ def _in_venv(path: py.path.local) -> bool:
"Activate.bat",
"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]:
- ignore_paths = config._getconftest_pathlist("collect_ignore", path=path.dirpath())
+def pytest_ignore_collect(collection_path: Path, config: Config) -> Optional[bool]:
+ ignore_paths = config._getconftest_pathlist(
+ "collect_ignore", path=collection_path.parent, rootpath=config.rootpath
+ )
ignore_paths = ignore_paths or []
excludeopt = config.getoption("ignore")
if excludeopt:
- ignore_paths.extend([py.path.local(x) for x in excludeopt])
+ ignore_paths.extend(absolutepath(x) for x in excludeopt)
- if py.path.local(path) in ignore_paths:
+ if collection_path in ignore_paths:
return True
ignore_globs = config._getconftest_pathlist(
- "collect_ignore_glob", path=path.dirpath()
+ "collect_ignore_glob", path=collection_path.parent, rootpath=config.rootpath
)
ignore_globs = ignore_globs or []
excludeglobopt = config.getoption("ignore_glob")
if excludeglobopt:
- ignore_globs.extend([py.path.local(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(collection_path), str(glob)) for glob in ignore_globs):
return True
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(collection_path):
return True
return None
@@ -436,9 +440,9 @@ class Failed(Exception):
"""Signals a stop as failed test run."""
-@attr.s
+@attr.s(slots=True, auto_attribs=True)
class _bestrelpath_cache(Dict[Path, str]):
- path = attr.ib(type=Path)
+ path: Path
def __missing__(self, path: Path) -> str:
r = bestrelpath(self.path, path)
@@ -458,15 +462,19 @@ class Session(nodes.FSCollector):
def __init__(self, config: Config) -> None:
super().__init__(
- config.rootdir, parent=None, config=config, session=self, nodeid=""
+ path=config.rootpath,
+ fspath=None,
+ parent=None,
+ config=config,
+ session=self,
+ nodeid="",
)
self.testsfailed = 0
self.testscollected = 0
self.shouldstop: Union[bool, str] = False
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)
@@ -474,7 +482,7 @@ class Session(nodes.FSCollector):
@classmethod
def from_config(cls, config: Config) -> "Session":
- session: Session = cls._create(config)
+ session: Session = cls._create(config=config)
return session
def __repr__(self) -> str:
@@ -486,6 +494,14 @@ class Session(nodes.FSCollector):
self.testscollected,
)
+ @property
+ def startpath(self) -> Path:
+ """The path from which pytest was invoked.
+
+ .. versionadded:: 7.0.0
+ """
+ return self.config.invocation_params.dir
+
def _node_location_to_relpath(self, node_path: Path) -> str:
# bestrelpath is a quite slow function.
return self._bestrelpathcache[node_path]
@@ -509,20 +525,28 @@ 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:
+ # Optimization: Path(Path(...)) is much slower than isinstance.
+ path_ = path if isinstance(path, Path) else Path(path)
+ return path_ in self._initialpaths
- def gethookproxy(self, fspath: py.path.local):
+ def gethookproxy(self, fspath: "os.PathLike[str]"):
+ # Optimization: Path(Path(...)) is much slower than isinstance.
+ path = fspath if isinstance(fspath, Path) else Path(fspath)
+ pm = self.config.pluginmanager
# Check if we have the common case of running
# hooks with all conftest.py files.
- pm = self.config.pluginmanager
my_conftestmodules = pm._getconftestmodules(
- fspath, self.config.getoption("importmode")
+ path,
+ self.config.getoption("importmode"),
+ rootpath=self.config.rootpath,
)
remove_mods = pm._conftest_plugins.difference(my_conftestmodules)
if remove_mods:
# One or more conftests are not in use at this fspath.
- proxy = FSHookProxy(pm, remove_mods)
+ from .config.compat import PathAwareHookProxy
+
+ proxy = PathAwareHookProxy(FSHookProxy(pm, remove_mods))
else:
# All plugins are active for this fspath.
proxy = self.config.hook
@@ -531,38 +555,38 @@ 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)
+ ihook = self.gethookproxy(fspath.parent)
+ if ihook.pytest_ignore_collect(collection_path=fspath, config=self.config):
return False
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 True
def _collectfile(
- self, path: py.path.local, handle_dupes: bool = True
+ self, fspath: Path, handle_dupes: bool = True
) -> Sequence[nodes.Collector]:
assert (
- path.isfile()
+ fspath.is_file()
), "{!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)
- if not self.isinitpath(path):
- if ihook.pytest_ignore_collect(path=path, config=self.config):
+ ihook = self.gethookproxy(fspath)
+ if not self.isinitpath(fspath):
+ if ihook.pytest_ignore_collect(collection_path=fspath, config=self.config):
return ()
if handle_dupes:
keepduplicates = self.config.getoption("keepduplicates")
if not keepduplicates:
duplicate_paths = self.config.pluginmanager._duplicatepaths
- if path in duplicate_paths:
+ if fspath in duplicate_paths:
return ()
else:
- duplicate_paths.add(path)
+ duplicate_paths.add(fspath)
- return ihook.pytest_collect_file(path=path, parent=self) # type: ignore[no-any-return]
+ return ihook.pytest_collect_file(file_path=fspath, parent=self) # type: ignore[no-any-return]
@overload
def perform_collect(
@@ -581,8 +605,7 @@ class Session(nodes.FSCollector):
) -> Sequence[Union[nodes.Item, nodes.Collector]]:
"""Perform the collection phase for this session.
- This is called by the default
- :func:`pytest_collection <_pytest.hookspec.pytest_collection>` hook
+ This is called by the default :hook:`pytest_collection` hook
implementation; see the documentation of this hook for more details.
For testing purposes, it may also be called directly on a fresh
``Session``.
@@ -600,14 +623,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,
@@ -647,14 +670,12 @@ class Session(nodes.FSCollector):
from _pytest.python import Package
# Keep track of any collected nodes in here, so we don't duplicate fixtures.
- node_cache1: Dict[py.path.local, Sequence[nodes.Collector]] = {}
- node_cache2: Dict[
- Tuple[Type[nodes.Collector], py.path.local], nodes.Collector
- ] = ({})
+ node_cache1: Dict[Path, Sequence[nodes.Collector]] = {}
+ node_cache2: Dict[Tuple[Type[nodes.Collector], Path], nodes.Collector] = {}
# Keep track of any collected collectors in matchnodes paths, so they
# are not collected more than once.
- matchnodes_cache: Dict[Tuple[Type[nodes.Collector], str], CollectReport] = ({})
+ matchnodes_cache: Dict[Tuple[Type[nodes.Collector], str], CollectReport] = {}
# Dirnames of pkgs with dunder-init files.
pkg_roots: Dict[str, Package] = {}
@@ -668,36 +689,37 @@ class Session(nodes.FSCollector):
# No point in finding packages when collecting doctests.
if not self.config.getoption("doctestmodules", False):
pm = self.config.pluginmanager
- for parent in reversed(argpath.parts()):
- if pm._confcutdir and pm._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 pkginit.isfile() and pkginit not in node_cache1:
+ if parent.is_dir():
+ pkginit = parent / "__init__.py"
+ if pkginit.is_file() and pkginit not in node_cache1:
col = self._collectfile(pkginit, handle_dupes=False)
if col:
if isinstance(col[0], Package):
pkg_roots[str(parent)] = col[0]
- node_cache1[col[0].fspath] = [col[0]]
+ node_cache1[col[0].path] = [col[0]]
# 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):
- assert not names, "invalid arg {!r}".format((argpath, names))
+ if argpath.is_dir():
+ assert not names, f"invalid arg {(argpath, names)!r}"
- seen_dirs: Set[py.path.local] = set()
+ seen_dirs: Set[Path] = set()
for direntry in visit(str(argpath), self._recurse):
if not direntry.is_file():
continue
- path = py.path.local(direntry.path)
- dirpath = path.dirpath()
+ path = Path(direntry.path)
+ dirpath = path.parent
if dirpath not in seen_dirs:
# Collect packages first.
seen_dirs.add(dirpath)
- pkginit = dirpath.join("__init__.py")
+ pkginit = dirpath / "__init__.py"
if pkginit.exists():
for x in self._collectfile(pkginit):
yield x
@@ -708,19 +730,19 @@ class Session(nodes.FSCollector):
continue
for x in self._collectfile(path):
- key = (type(x), x.fspath)
- if key in node_cache2:
- yield node_cache2[key]
+ key2 = (type(x), x.path)
+ if key2 in node_cache2:
+ yield node_cache2[key2]
else:
- node_cache2[key] = x
+ node_cache2[key2] = x
yield x
else:
- assert argpath.check(file=1)
+ assert argpath.is_file()
if argpath in node_cache1:
col = node_cache1[argpath]
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)
if col:
node_cache1[argpath] = col
@@ -758,9 +780,6 @@ class Session(nodes.FSCollector):
submatchnodes.append(r)
if submatchnodes:
work.append((submatchnodes, matchnames[1:]))
- # XXX Accept IDs that don't have "()" for class instances.
- elif len(rep.result) == 1 and rep.result[0].name == "()":
- work.append((rep.result, matchnames))
else:
# Report collection failures here to avoid failing to run some test
# specified in the command line because the module could not be
@@ -780,9 +799,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:
@@ -831,7 +848,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
@@ -841,7 +858,7 @@ def resolve_collection_argument(
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
module paths instead of file-system paths:
@@ -854,7 +871,10 @@ def resolve_collection_argument(
If the path doesn't exist, raise UsageError.
If the path is a directory and selection parts are present, raise UsageError.
"""
- strpath, *parts = str(arg).split("::")
+ base, squacket, rest = str(arg).partition("[")
+ strpath, *parts = base.split("::")
+ if parts:
+ parts[-1] = f"{parts[-1]}{squacket}{rest}"
if as_pypath:
strpath = search_pypath(strpath)
fspath = invocation_path / strpath
@@ -873,4 +893,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