mirror of
https://github.com/pre-commit/pre-commit-hooks.git
synced 2026-04-10 21:34:18 +00:00
[pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
This commit is contained in:
parent
72ad6dc953
commit
f4cd1ba0d6
813 changed files with 66015 additions and 58839 deletions
|
|
@ -1,14 +1,15 @@
|
|||
"""Core implementation of the testing process: init, session, runtest loop."""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import dataclasses
|
||||
import fnmatch
|
||||
import functools
|
||||
import importlib
|
||||
import importlib.util
|
||||
import os
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import warnings
|
||||
from pathlib import Path
|
||||
from typing import AbstractSet
|
||||
from typing import Callable
|
||||
from typing import Dict
|
||||
|
|
@ -24,12 +25,10 @@ from typing import Sequence
|
|||
from typing import Tuple
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import Union
|
||||
import warnings
|
||||
|
||||
import pluggy
|
||||
|
||||
from _pytest import nodes
|
||||
import _pytest._code
|
||||
import pluggy
|
||||
from _pytest import nodes
|
||||
from _pytest.config import Config
|
||||
from _pytest.config import directory_arg
|
||||
from _pytest.config import ExitCode
|
||||
|
|
@ -58,195 +57,195 @@ if TYPE_CHECKING:
|
|||
|
||||
def pytest_addoption(parser: Parser) -> None:
|
||||
parser.addini(
|
||||
"norecursedirs",
|
||||
"Directory patterns to avoid for recursion",
|
||||
type="args",
|
||||
'norecursedirs',
|
||||
'Directory patterns to avoid for recursion',
|
||||
type='args',
|
||||
default=[
|
||||
"*.egg",
|
||||
".*",
|
||||
"_darcs",
|
||||
"build",
|
||||
"CVS",
|
||||
"dist",
|
||||
"node_modules",
|
||||
"venv",
|
||||
"{arch}",
|
||||
'*.egg',
|
||||
'.*',
|
||||
'_darcs',
|
||||
'build',
|
||||
'CVS',
|
||||
'dist',
|
||||
'node_modules',
|
||||
'venv',
|
||||
'{arch}',
|
||||
],
|
||||
)
|
||||
parser.addini(
|
||||
"testpaths",
|
||||
"Directories to search for tests when no files or directories are given on the "
|
||||
"command line",
|
||||
type="args",
|
||||
'testpaths',
|
||||
'Directories to search for tests when no files or directories are given on the '
|
||||
'command line',
|
||||
type='args',
|
||||
default=[],
|
||||
)
|
||||
group = parser.getgroup("general", "Running and selection options")
|
||||
group = parser.getgroup('general', 'Running and selection options')
|
||||
group._addoption(
|
||||
"-x",
|
||||
"--exitfirst",
|
||||
action="store_const",
|
||||
dest="maxfail",
|
||||
'-x',
|
||||
'--exitfirst',
|
||||
action='store_const',
|
||||
dest='maxfail',
|
||||
const=1,
|
||||
help="Exit instantly on first error or failed test",
|
||||
help='Exit instantly on first error or failed test',
|
||||
)
|
||||
group = parser.getgroup("pytest-warnings")
|
||||
group = parser.getgroup('pytest-warnings')
|
||||
group.addoption(
|
||||
"-W",
|
||||
"--pythonwarnings",
|
||||
action="append",
|
||||
help="Set which warnings to report, see -W option of Python itself",
|
||||
'-W',
|
||||
'--pythonwarnings',
|
||||
action='append',
|
||||
help='Set which warnings to report, see -W option of Python itself',
|
||||
)
|
||||
parser.addini(
|
||||
"filterwarnings",
|
||||
type="linelist",
|
||||
help="Each line specifies a pattern for "
|
||||
"warnings.filterwarnings. "
|
||||
"Processed after -W/--pythonwarnings.",
|
||||
'filterwarnings',
|
||||
type='linelist',
|
||||
help='Each line specifies a pattern for '
|
||||
'warnings.filterwarnings. '
|
||||
'Processed after -W/--pythonwarnings.',
|
||||
)
|
||||
group._addoption(
|
||||
"--maxfail",
|
||||
metavar="num",
|
||||
action="store",
|
||||
'--maxfail',
|
||||
metavar='num',
|
||||
action='store',
|
||||
type=int,
|
||||
dest="maxfail",
|
||||
dest='maxfail',
|
||||
default=0,
|
||||
help="Exit after first num failures or errors",
|
||||
help='Exit after first num failures or errors',
|
||||
)
|
||||
group._addoption(
|
||||
"--strict-config",
|
||||
action="store_true",
|
||||
help="Any warnings encountered while parsing the `pytest` section of the "
|
||||
"configuration file raise errors",
|
||||
'--strict-config',
|
||||
action='store_true',
|
||||
help='Any warnings encountered while parsing the `pytest` section of the '
|
||||
'configuration file raise errors',
|
||||
)
|
||||
group._addoption(
|
||||
"--strict-markers",
|
||||
action="store_true",
|
||||
help="Markers not registered in the `markers` section of the configuration "
|
||||
"file raise errors",
|
||||
'--strict-markers',
|
||||
action='store_true',
|
||||
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",
|
||||
"--config-file",
|
||||
metavar="FILE",
|
||||
'-c',
|
||||
'--config-file',
|
||||
metavar='FILE',
|
||||
type=str,
|
||||
dest="inifilename",
|
||||
help="Load configuration from `FILE` instead of trying to locate one of the "
|
||||
"implicit configuration files.",
|
||||
dest='inifilename',
|
||||
help='Load configuration from `FILE` instead of trying to locate one of the '
|
||||
'implicit configuration files.',
|
||||
)
|
||||
group._addoption(
|
||||
"--continue-on-collection-errors",
|
||||
action="store_true",
|
||||
'--continue-on-collection-errors',
|
||||
action='store_true',
|
||||
default=False,
|
||||
dest="continue_on_collection_errors",
|
||||
help="Force test execution even if collection errors occur",
|
||||
dest='continue_on_collection_errors',
|
||||
help='Force test execution even if collection errors occur',
|
||||
)
|
||||
group._addoption(
|
||||
"--rootdir",
|
||||
action="store",
|
||||
dest="rootdir",
|
||||
'--rootdir',
|
||||
action='store',
|
||||
dest='rootdir',
|
||||
help="Define root directory for tests. Can be relative path: 'root_dir', './root_dir', "
|
||||
"'root_dir/another_dir/'; absolute path: '/home/user/root_dir'; path with variables: "
|
||||
"'$HOME/root_dir'.",
|
||||
)
|
||||
|
||||
group = parser.getgroup("collect", "collection")
|
||||
group = parser.getgroup('collect', 'collection')
|
||||
group.addoption(
|
||||
"--collectonly",
|
||||
"--collect-only",
|
||||
"--co",
|
||||
action="store_true",
|
||||
'--collectonly',
|
||||
'--collect-only',
|
||||
'--co',
|
||||
action='store_true',
|
||||
help="Only collect tests, don't execute them",
|
||||
)
|
||||
group.addoption(
|
||||
"--pyargs",
|
||||
action="store_true",
|
||||
help="Try to interpret all arguments as Python packages",
|
||||
'--pyargs',
|
||||
action='store_true',
|
||||
help='Try to interpret all arguments as Python packages',
|
||||
)
|
||||
group.addoption(
|
||||
"--ignore",
|
||||
action="append",
|
||||
metavar="path",
|
||||
help="Ignore path during collection (multi-allowed)",
|
||||
'--ignore',
|
||||
action='append',
|
||||
metavar='path',
|
||||
help='Ignore path during collection (multi-allowed)',
|
||||
)
|
||||
group.addoption(
|
||||
"--ignore-glob",
|
||||
action="append",
|
||||
metavar="path",
|
||||
help="Ignore path pattern during collection (multi-allowed)",
|
||||
'--ignore-glob',
|
||||
action='append',
|
||||
metavar='path',
|
||||
help='Ignore path pattern during collection (multi-allowed)',
|
||||
)
|
||||
group.addoption(
|
||||
"--deselect",
|
||||
action="append",
|
||||
metavar="nodeid_prefix",
|
||||
help="Deselect item (via node id prefix) during collection (multi-allowed)",
|
||||
'--deselect',
|
||||
action='append',
|
||||
metavar='nodeid_prefix',
|
||||
help='Deselect item (via node id prefix) during collection (multi-allowed)',
|
||||
)
|
||||
group.addoption(
|
||||
"--confcutdir",
|
||||
dest="confcutdir",
|
||||
'--confcutdir',
|
||||
dest='confcutdir',
|
||||
default=None,
|
||||
metavar="dir",
|
||||
type=functools.partial(directory_arg, optname="--confcutdir"),
|
||||
metavar='dir',
|
||||
type=functools.partial(directory_arg, optname='--confcutdir'),
|
||||
help="Only load conftest.py's relative to specified dir",
|
||||
)
|
||||
group.addoption(
|
||||
"--noconftest",
|
||||
action="store_true",
|
||||
dest="noconftest",
|
||||
'--noconftest',
|
||||
action='store_true',
|
||||
dest='noconftest',
|
||||
default=False,
|
||||
help="Don't load any conftest.py files",
|
||||
)
|
||||
group.addoption(
|
||||
"--keepduplicates",
|
||||
"--keep-duplicates",
|
||||
action="store_true",
|
||||
dest="keepduplicates",
|
||||
'--keepduplicates',
|
||||
'--keep-duplicates',
|
||||
action='store_true',
|
||||
dest='keepduplicates',
|
||||
default=False,
|
||||
help="Keep duplicate tests",
|
||||
help='Keep duplicate tests',
|
||||
)
|
||||
group.addoption(
|
||||
"--collect-in-virtualenv",
|
||||
action="store_true",
|
||||
dest="collect_in_virtualenv",
|
||||
'--collect-in-virtualenv',
|
||||
action='store_true',
|
||||
dest='collect_in_virtualenv',
|
||||
default=False,
|
||||
help="Don't ignore tests in a local virtualenv directory",
|
||||
)
|
||||
group.addoption(
|
||||
"--import-mode",
|
||||
default="prepend",
|
||||
choices=["prepend", "append", "importlib"],
|
||||
dest="importmode",
|
||||
help="Prepend/append to sys.path when importing test modules and conftest "
|
||||
"files. Default: prepend.",
|
||||
'--import-mode',
|
||||
default='prepend',
|
||||
choices=['prepend', 'append', 'importlib'],
|
||||
dest='importmode',
|
||||
help='Prepend/append to sys.path when importing test modules and conftest '
|
||||
'files. Default: prepend.',
|
||||
)
|
||||
parser.addini(
|
||||
"consider_namespace_packages",
|
||||
type="bool",
|
||||
'consider_namespace_packages',
|
||||
type='bool',
|
||||
default=False,
|
||||
help="Consider namespace packages when resolving module names during import",
|
||||
help='Consider namespace packages when resolving module names during import',
|
||||
)
|
||||
|
||||
group = parser.getgroup("debugconfig", "test session debugging and configuration")
|
||||
group = parser.getgroup('debugconfig', 'test session debugging and configuration')
|
||||
group.addoption(
|
||||
"--basetemp",
|
||||
dest="basetemp",
|
||||
'--basetemp',
|
||||
dest='basetemp',
|
||||
default=None,
|
||||
type=validate_basetemp,
|
||||
metavar="dir",
|
||||
metavar='dir',
|
||||
help=(
|
||||
"Base temporary directory for this test run. "
|
||||
"(Warning: this directory is removed if it exists.)"
|
||||
'Base temporary directory for this test run. '
|
||||
'(Warning: this directory is removed if it exists.)'
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def validate_basetemp(path: str) -> str:
|
||||
# GH 7119
|
||||
msg = "basetemp must not be empty, the current working directory or any parent directory of it"
|
||||
msg = 'basetemp must not be empty, the current working directory or any parent directory of it'
|
||||
|
||||
# empty path
|
||||
if not path:
|
||||
|
|
@ -270,8 +269,8 @@ def validate_basetemp(path: str) -> str:
|
|||
|
||||
|
||||
def wrap_session(
|
||||
config: Config, doit: Callable[[Config, "Session"], Optional[Union[int, ExitCode]]]
|
||||
) -> Union[int, ExitCode]:
|
||||
config: Config, doit: Callable[[Config, Session], int | ExitCode | None],
|
||||
) -> int | ExitCode:
|
||||
"""Skeleton command line program."""
|
||||
session = Session.from_config(config)
|
||||
session.exitstatus = ExitCode.OK
|
||||
|
|
@ -290,12 +289,12 @@ def wrap_session(
|
|||
session.exitstatus = ExitCode.TESTS_FAILED
|
||||
except (KeyboardInterrupt, exit.Exception):
|
||||
excinfo = _pytest._code.ExceptionInfo.from_current()
|
||||
exitstatus: Union[int, ExitCode] = ExitCode.INTERRUPTED
|
||||
exitstatus: int | ExitCode = ExitCode.INTERRUPTED
|
||||
if isinstance(excinfo.value, exit.Exception):
|
||||
if excinfo.value.returncode is not None:
|
||||
exitstatus = excinfo.value.returncode
|
||||
if initstate < 2:
|
||||
sys.stderr.write(f"{excinfo.typename}: {excinfo.value.msg}\n")
|
||||
sys.stderr.write(f'{excinfo.typename}: {excinfo.value.msg}\n')
|
||||
config.hook.pytest_keyboard_interrupt(excinfo=excinfo)
|
||||
session.exitstatus = exitstatus
|
||||
except BaseException:
|
||||
|
|
@ -306,10 +305,10 @@ def wrap_session(
|
|||
except exit.Exception as exc:
|
||||
if exc.returncode is not None:
|
||||
session.exitstatus = exc.returncode
|
||||
sys.stderr.write(f"{type(exc).__name__}: {exc}\n")
|
||||
sys.stderr.write(f'{type(exc).__name__}: {exc}\n')
|
||||
else:
|
||||
if isinstance(excinfo.value, SystemExit):
|
||||
sys.stderr.write("mainloop: caught unexpected SystemExit!\n")
|
||||
sys.stderr.write('mainloop: caught unexpected SystemExit!\n')
|
||||
|
||||
finally:
|
||||
# Explicitly break reference cycle.
|
||||
|
|
@ -318,21 +317,21 @@ def wrap_session(
|
|||
if initstate >= 2:
|
||||
try:
|
||||
config.hook.pytest_sessionfinish(
|
||||
session=session, exitstatus=session.exitstatus
|
||||
session=session, exitstatus=session.exitstatus,
|
||||
)
|
||||
except exit.Exception as exc:
|
||||
if exc.returncode is not None:
|
||||
session.exitstatus = exc.returncode
|
||||
sys.stderr.write(f"{type(exc).__name__}: {exc}\n")
|
||||
sys.stderr.write(f'{type(exc).__name__}: {exc}\n')
|
||||
config._ensure_unconfigure()
|
||||
return session.exitstatus
|
||||
|
||||
|
||||
def pytest_cmdline_main(config: Config) -> Union[int, ExitCode]:
|
||||
def pytest_cmdline_main(config: Config) -> int | ExitCode:
|
||||
return wrap_session(config, _main)
|
||||
|
||||
|
||||
def _main(config: Config, session: "Session") -> Optional[Union[int, ExitCode]]:
|
||||
def _main(config: Config, session: Session) -> int | ExitCode | None:
|
||||
"""Default command line protocol for initialization, session,
|
||||
running tests and reporting."""
|
||||
config.hook.pytest_collection(session=session)
|
||||
|
|
@ -345,15 +344,15 @@ def _main(config: Config, session: "Session") -> Optional[Union[int, ExitCode]]:
|
|||
return None
|
||||
|
||||
|
||||
def pytest_collection(session: "Session") -> None:
|
||||
def pytest_collection(session: Session) -> None:
|
||||
session.perform_collect()
|
||||
|
||||
|
||||
def pytest_runtestloop(session: "Session") -> bool:
|
||||
def pytest_runtestloop(session: Session) -> bool:
|
||||
if session.testsfailed and not session.config.option.continue_on_collection_errors:
|
||||
raise session.Interrupted(
|
||||
"%d error%s during collection"
|
||||
% (session.testsfailed, "s" if session.testsfailed != 1 else "")
|
||||
'%d error%s during collection'
|
||||
% (session.testsfailed, 's' if session.testsfailed != 1 else ''),
|
||||
)
|
||||
|
||||
if session.config.option.collectonly:
|
||||
|
|
@ -372,32 +371,32 @@ def pytest_runtestloop(session: "Session") -> 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.joinpath("Scripts" if sys.platform.startswith("win") else "bin")
|
||||
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",
|
||||
"activate.csh",
|
||||
"activate.fish",
|
||||
"Activate",
|
||||
"Activate.bat",
|
||||
"Activate.ps1",
|
||||
'activate',
|
||||
'activate.csh',
|
||||
'activate.fish',
|
||||
'Activate',
|
||||
'Activate.bat',
|
||||
'Activate.ps1',
|
||||
)
|
||||
return any(fname.name in activates for fname in bindir.iterdir())
|
||||
|
||||
|
||||
def pytest_ignore_collect(collection_path: Path, config: Config) -> Optional[bool]:
|
||||
if collection_path.name == "__pycache__":
|
||||
def pytest_ignore_collect(collection_path: Path, config: Config) -> bool | None:
|
||||
if collection_path.name == '__pycache__':
|
||||
return True
|
||||
|
||||
ignore_paths = config._getconftest_pathlist(
|
||||
"collect_ignore", path=collection_path.parent
|
||||
'collect_ignore', path=collection_path.parent,
|
||||
)
|
||||
ignore_paths = ignore_paths or []
|
||||
excludeopt = config.getoption("ignore")
|
||||
excludeopt = config.getoption('ignore')
|
||||
if excludeopt:
|
||||
ignore_paths.extend(absolutepath(x) for x in excludeopt)
|
||||
|
||||
|
|
@ -405,22 +404,22 @@ def pytest_ignore_collect(collection_path: Path, config: Config) -> Optional[boo
|
|||
return True
|
||||
|
||||
ignore_globs = config._getconftest_pathlist(
|
||||
"collect_ignore_glob", path=collection_path.parent
|
||||
'collect_ignore_glob', path=collection_path.parent,
|
||||
)
|
||||
ignore_globs = ignore_globs or []
|
||||
excludeglobopt = config.getoption("ignore_glob")
|
||||
excludeglobopt = config.getoption('ignore_glob')
|
||||
if excludeglobopt:
|
||||
ignore_globs.extend(absolutepath(x) for x in excludeglobopt)
|
||||
|
||||
if any(fnmatch.fnmatch(str(collection_path), str(glob)) for glob in ignore_globs):
|
||||
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(collection_path):
|
||||
return True
|
||||
|
||||
if collection_path.is_dir():
|
||||
norecursepatterns = config.getini("norecursedirs")
|
||||
norecursepatterns = config.getini('norecursedirs')
|
||||
if any(fnmatch_ex(pat, collection_path) for pat in norecursepatterns):
|
||||
return True
|
||||
|
||||
|
|
@ -428,13 +427,13 @@ def pytest_ignore_collect(collection_path: Path, config: Config) -> Optional[boo
|
|||
|
||||
|
||||
def pytest_collect_directory(
|
||||
path: Path, parent: nodes.Collector
|
||||
) -> Optional[nodes.Collector]:
|
||||
path: Path, parent: nodes.Collector,
|
||||
) -> nodes.Collector | None:
|
||||
return Dir.from_parent(parent, path=path)
|
||||
|
||||
|
||||
def pytest_collection_modifyitems(items: List[nodes.Item], config: Config) -> None:
|
||||
deselect_prefixes = tuple(config.getoption("deselect") or [])
|
||||
def pytest_collection_modifyitems(items: list[nodes.Item], config: Config) -> None:
|
||||
deselect_prefixes = tuple(config.getoption('deselect') or [])
|
||||
if not deselect_prefixes:
|
||||
return
|
||||
|
||||
|
|
@ -469,7 +468,7 @@ class FSHookProxy:
|
|||
class Interrupted(KeyboardInterrupt):
|
||||
"""Signals that the test run was interrupted."""
|
||||
|
||||
__module__ = "builtins" # For py3.
|
||||
__module__ = 'builtins' # For py3.
|
||||
|
||||
|
||||
class Failed(Exception):
|
||||
|
|
@ -478,7 +477,7 @@ class Failed(Exception):
|
|||
|
||||
@dataclasses.dataclass
|
||||
class _bestrelpath_cache(Dict[Path, str]):
|
||||
__slots__ = ("path",)
|
||||
__slots__ = ('path',)
|
||||
|
||||
path: Path
|
||||
|
||||
|
|
@ -507,7 +506,7 @@ class Dir(nodes.Directory):
|
|||
parent: nodes.Collector,
|
||||
*,
|
||||
path: Path,
|
||||
) -> "Self":
|
||||
) -> Self:
|
||||
"""The public constructor.
|
||||
|
||||
:param parent: The parent collector of this Dir.
|
||||
|
|
@ -515,9 +514,9 @@ class Dir(nodes.Directory):
|
|||
"""
|
||||
return super().from_parent(parent=parent, path=path)
|
||||
|
||||
def collect(self) -> Iterable[Union[nodes.Item, nodes.Collector]]:
|
||||
def collect(self) -> Iterable[nodes.Item | nodes.Collector]:
|
||||
config = self.config
|
||||
col: Optional[nodes.Collector]
|
||||
col: nodes.Collector | None
|
||||
cols: Sequence[nodes.Collector]
|
||||
ihook = self.ihook
|
||||
for direntry in scandir(self.path):
|
||||
|
|
@ -552,60 +551,60 @@ class Session(nodes.Collector):
|
|||
_setupstate: SetupState
|
||||
# Set on the session by fixtures.pytest_sessionstart.
|
||||
_fixturemanager: FixtureManager
|
||||
exitstatus: Union[int, ExitCode]
|
||||
exitstatus: int | ExitCode
|
||||
|
||||
def __init__(self, config: Config) -> None:
|
||||
super().__init__(
|
||||
name="",
|
||||
name='',
|
||||
path=config.rootpath,
|
||||
fspath=None,
|
||||
parent=None,
|
||||
config=config,
|
||||
session=self,
|
||||
nodeid="",
|
||||
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._initialpaths: FrozenSet[Path] = frozenset()
|
||||
self._initialpaths_with_parents: FrozenSet[Path] = frozenset()
|
||||
self._notfound: List[Tuple[str, Sequence[nodes.Collector]]] = []
|
||||
self._initial_parts: List[CollectionArgument] = []
|
||||
self._collection_cache: Dict[nodes.Collector, CollectReport] = {}
|
||||
self.items: List[nodes.Item] = []
|
||||
self._shouldstop: bool | str = False
|
||||
self._shouldfail: bool | str = False
|
||||
self.trace = config.trace.root.get('collection')
|
||||
self._initialpaths: frozenset[Path] = frozenset()
|
||||
self._initialpaths_with_parents: frozenset[Path] = frozenset()
|
||||
self._notfound: list[tuple[str, Sequence[nodes.Collector]]] = []
|
||||
self._initial_parts: list[CollectionArgument] = []
|
||||
self._collection_cache: dict[nodes.Collector, CollectReport] = {}
|
||||
self.items: list[nodes.Item] = []
|
||||
|
||||
self._bestrelpathcache: Dict[Path, str] = _bestrelpath_cache(config.rootpath)
|
||||
self._bestrelpathcache: dict[Path, str] = _bestrelpath_cache(config.rootpath)
|
||||
|
||||
self.config.pluginmanager.register(self, name="session")
|
||||
self.config.pluginmanager.register(self, name='session')
|
||||
|
||||
@classmethod
|
||||
def from_config(cls, config: Config) -> "Session":
|
||||
def from_config(cls, config: Config) -> Session:
|
||||
session: Session = cls._create(config=config)
|
||||
return session
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return "<%s %s exitstatus=%r testsfailed=%d testscollected=%d>" % (
|
||||
return '<%s %s exitstatus=%r testsfailed=%d testscollected=%d>' % (
|
||||
self.__class__.__name__,
|
||||
self.name,
|
||||
getattr(self, "exitstatus", "<UNSET>"),
|
||||
getattr(self, 'exitstatus', '<UNSET>'),
|
||||
self.testsfailed,
|
||||
self.testscollected,
|
||||
)
|
||||
|
||||
@property
|
||||
def shouldstop(self) -> Union[bool, str]:
|
||||
def shouldstop(self) -> bool | str:
|
||||
return self._shouldstop
|
||||
|
||||
@shouldstop.setter
|
||||
def shouldstop(self, value: Union[bool, str]) -> None:
|
||||
def shouldstop(self, value: bool | str) -> None:
|
||||
# The runner checks shouldfail and assumes that if it is set we are
|
||||
# definitely stopping, so prevent unsetting it.
|
||||
if value is False and self._shouldstop:
|
||||
warnings.warn(
|
||||
PytestWarning(
|
||||
"session.shouldstop cannot be unset after it has been set; ignoring."
|
||||
'session.shouldstop cannot be unset after it has been set; ignoring.',
|
||||
),
|
||||
stacklevel=2,
|
||||
)
|
||||
|
|
@ -613,17 +612,17 @@ class Session(nodes.Collector):
|
|||
self._shouldstop = value
|
||||
|
||||
@property
|
||||
def shouldfail(self) -> Union[bool, str]:
|
||||
def shouldfail(self) -> bool | str:
|
||||
return self._shouldfail
|
||||
|
||||
@shouldfail.setter
|
||||
def shouldfail(self, value: Union[bool, str]) -> None:
|
||||
def shouldfail(self, value: bool | str) -> None:
|
||||
# The runner checks shouldfail and assumes that if it is set we are
|
||||
# definitely stopping, so prevent unsetting it.
|
||||
if value is False and self._shouldfail:
|
||||
warnings.warn(
|
||||
PytestWarning(
|
||||
"session.shouldfail cannot be unset after it has been set; ignoring."
|
||||
'session.shouldfail cannot be unset after it has been set; ignoring.',
|
||||
),
|
||||
stacklevel=2,
|
||||
)
|
||||
|
|
@ -651,19 +650,19 @@ class Session(nodes.Collector):
|
|||
|
||||
@hookimpl(tryfirst=True)
|
||||
def pytest_runtest_logreport(
|
||||
self, report: Union[TestReport, CollectReport]
|
||||
self, report: TestReport | CollectReport,
|
||||
) -> None:
|
||||
if report.failed and not hasattr(report, "wasxfail"):
|
||||
if report.failed and not hasattr(report, 'wasxfail'):
|
||||
self.testsfailed += 1
|
||||
maxfail = self.config.getvalue("maxfail")
|
||||
maxfail = self.config.getvalue('maxfail')
|
||||
if maxfail and self.testsfailed >= maxfail:
|
||||
self.shouldfail = "stopping after %d failures" % (self.testsfailed)
|
||||
self.shouldfail = 'stopping after %d failures' % (self.testsfailed)
|
||||
|
||||
pytest_collectreport = pytest_runtest_logreport
|
||||
|
||||
def isinitpath(
|
||||
self,
|
||||
path: Union[str, "os.PathLike[str]"],
|
||||
path: str | os.PathLike[str],
|
||||
*,
|
||||
with_parents: bool = False,
|
||||
) -> bool:
|
||||
|
|
@ -685,7 +684,7 @@ class Session(nodes.Collector):
|
|||
else:
|
||||
return path_ in self._initialpaths
|
||||
|
||||
def gethookproxy(self, fspath: "os.PathLike[str]") -> pluggy.HookRelay:
|
||||
def gethookproxy(self, fspath: os.PathLike[str]) -> pluggy.HookRelay:
|
||||
# Optimization: Path(Path(...)) is much slower than isinstance.
|
||||
path = fspath if isinstance(fspath, Path) else Path(fspath)
|
||||
pm = self.config.pluginmanager
|
||||
|
|
@ -705,7 +704,7 @@ class Session(nodes.Collector):
|
|||
def _collect_path(
|
||||
self,
|
||||
path: Path,
|
||||
path_cache: Dict[Path, Sequence[nodes.Collector]],
|
||||
path_cache: dict[Path, Sequence[nodes.Collector]],
|
||||
) -> Sequence[nodes.Collector]:
|
||||
"""Create a Collector for the given path.
|
||||
|
||||
|
|
@ -717,8 +716,8 @@ class Session(nodes.Collector):
|
|||
|
||||
if path.is_dir():
|
||||
ihook = self.gethookproxy(path.parent)
|
||||
col: Optional[nodes.Collector] = ihook.pytest_collect_directory(
|
||||
path=path, parent=self
|
||||
col: nodes.Collector | None = ihook.pytest_collect_directory(
|
||||
path=path, parent=self,
|
||||
)
|
||||
cols: Sequence[nodes.Collector] = (col,) if col is not None else ()
|
||||
|
||||
|
|
@ -735,19 +734,19 @@ class Session(nodes.Collector):
|
|||
|
||||
@overload
|
||||
def perform_collect(
|
||||
self, args: Optional[Sequence[str]] = ..., genitems: "Literal[True]" = ...
|
||||
self, args: Sequence[str] | None = ..., genitems: Literal[True] = ...,
|
||||
) -> Sequence[nodes.Item]:
|
||||
...
|
||||
|
||||
@overload
|
||||
def perform_collect(
|
||||
self, args: Optional[Sequence[str]] = ..., genitems: bool = ...
|
||||
) -> Sequence[Union[nodes.Item, nodes.Collector]]:
|
||||
self, args: Sequence[str] | None = ..., genitems: bool = ...,
|
||||
) -> Sequence[nodes.Item | nodes.Collector]:
|
||||
...
|
||||
|
||||
def perform_collect(
|
||||
self, args: Optional[Sequence[str]] = None, genitems: bool = True
|
||||
) -> Sequence[Union[nodes.Item, nodes.Collector]]:
|
||||
self, args: Sequence[str] | None = None, genitems: bool = True,
|
||||
) -> Sequence[nodes.Item | nodes.Collector]:
|
||||
"""Perform the collection phase for this session.
|
||||
|
||||
This is called by the default :hook:`pytest_collection` hook
|
||||
|
|
@ -764,7 +763,7 @@ class Session(nodes.Collector):
|
|||
if args is None:
|
||||
args = self.config.args
|
||||
|
||||
self.trace("perform_collect", self, args)
|
||||
self.trace('perform_collect', self, args)
|
||||
self.trace.root.indent += 1
|
||||
|
||||
hook = self.config.hook
|
||||
|
|
@ -773,10 +772,10 @@ class Session(nodes.Collector):
|
|||
self._initial_parts = []
|
||||
self._collection_cache = {}
|
||||
self.items = []
|
||||
items: Sequence[Union[nodes.Item, nodes.Collector]] = self.items
|
||||
items: Sequence[nodes.Item | nodes.Collector] = self.items
|
||||
try:
|
||||
initialpaths: List[Path] = []
|
||||
initialpaths_with_parents: List[Path] = []
|
||||
initialpaths: list[Path] = []
|
||||
initialpaths_with_parents: list[Path] = []
|
||||
for arg in args:
|
||||
collection_argument = resolve_collection_argument(
|
||||
self.config.invocation_params.dir,
|
||||
|
|
@ -798,10 +797,10 @@ class Session(nodes.Collector):
|
|||
for arg, collectors in self._notfound:
|
||||
if collectors:
|
||||
errors.append(
|
||||
f"not found: {arg}\n(no match in any of {collectors!r})"
|
||||
f'not found: {arg}\n(no match in any of {collectors!r})',
|
||||
)
|
||||
else:
|
||||
errors.append(f"found no collectors for {arg}")
|
||||
errors.append(f'found no collectors for {arg}')
|
||||
|
||||
raise UsageError(*errors)
|
||||
|
||||
|
|
@ -814,7 +813,7 @@ class Session(nodes.Collector):
|
|||
|
||||
self.config.pluginmanager.check_pending()
|
||||
hook.pytest_collection_modifyitems(
|
||||
session=self, config=self.config, items=items
|
||||
session=self, config=self.config, items=items,
|
||||
)
|
||||
finally:
|
||||
self._notfound = []
|
||||
|
|
@ -831,7 +830,7 @@ class Session(nodes.Collector):
|
|||
self,
|
||||
node: nodes.Collector,
|
||||
handle_dupes: bool = True,
|
||||
) -> Tuple[CollectReport, bool]:
|
||||
) -> tuple[CollectReport, bool]:
|
||||
if node in self._collection_cache and handle_dupes:
|
||||
rep = self._collection_cache[node]
|
||||
return rep, True
|
||||
|
|
@ -840,16 +839,16 @@ class Session(nodes.Collector):
|
|||
self._collection_cache[node] = rep
|
||||
return rep, False
|
||||
|
||||
def collect(self) -> Iterator[Union[nodes.Item, nodes.Collector]]:
|
||||
def collect(self) -> Iterator[nodes.Item | nodes.Collector]:
|
||||
# This is a cache for the root directories of the initial paths.
|
||||
# We can't use collection_cache for Session because of its special
|
||||
# role as the bootstrapping collector.
|
||||
path_cache: Dict[Path, Sequence[nodes.Collector]] = {}
|
||||
path_cache: dict[Path, Sequence[nodes.Collector]] = {}
|
||||
|
||||
pm = self.config.pluginmanager
|
||||
|
||||
for collection_argument in self._initial_parts:
|
||||
self.trace("processing argument", collection_argument)
|
||||
self.trace('processing argument', collection_argument)
|
||||
self.trace.root.indent += 1
|
||||
|
||||
argpath = collection_argument.path
|
||||
|
|
@ -858,7 +857,7 @@ class Session(nodes.Collector):
|
|||
|
||||
# resolve_collection_argument() ensures this.
|
||||
if argpath.is_dir():
|
||||
assert not names, f"invalid arg {(argpath, names)!r}"
|
||||
assert not names, f'invalid arg {(argpath, names)!r}'
|
||||
|
||||
paths = [argpath]
|
||||
# Add relevant parents of the path, from the root, e.g.
|
||||
|
|
@ -872,7 +871,7 @@ class Session(nodes.Collector):
|
|||
else:
|
||||
# For --pyargs arguments, only consider paths matching the module
|
||||
# name. Paths beyond the package hierarchy are not included.
|
||||
module_name_parts = module_name.split(".")
|
||||
module_name_parts = module_name.split('.')
|
||||
for i, path in enumerate(argpath.parents, 2):
|
||||
if i > len(module_name_parts) or path.stem != module_name_parts[-i]:
|
||||
break
|
||||
|
|
@ -882,8 +881,8 @@ class Session(nodes.Collector):
|
|||
# and discarding all nodes which don't match the level's part.
|
||||
any_matched_in_initial_part = False
|
||||
notfound_collectors = []
|
||||
work: List[
|
||||
Tuple[Union[nodes.Collector, nodes.Item], List[Union[Path, str]]]
|
||||
work: list[
|
||||
tuple[nodes.Collector | nodes.Item, list[Path | str]]
|
||||
] = [(self, [*paths, *names])]
|
||||
while work:
|
||||
matchnode, matchparts = work.pop()
|
||||
|
|
@ -901,7 +900,7 @@ class Session(nodes.Collector):
|
|||
# Collect this level of matching.
|
||||
# Collecting Session (self) is done directly to avoid endless
|
||||
# recursion to this function.
|
||||
subnodes: Sequence[Union[nodes.Collector, nodes.Item]]
|
||||
subnodes: Sequence[nodes.Collector | nodes.Item]
|
||||
if isinstance(matchnode, Session):
|
||||
assert isinstance(matchparts[0], Path)
|
||||
subnodes = matchnode._collect_path(matchparts[0], path_cache)
|
||||
|
|
@ -909,9 +908,9 @@ class Session(nodes.Collector):
|
|||
# For backward compat, files given directly multiple
|
||||
# times on the command line should not be deduplicated.
|
||||
handle_dupes = not (
|
||||
len(matchparts) == 1
|
||||
and isinstance(matchparts[0], Path)
|
||||
and matchparts[0].is_file()
|
||||
len(matchparts) == 1 and
|
||||
isinstance(matchparts[0], Path) and
|
||||
matchparts[0].is_file()
|
||||
)
|
||||
rep, duplicate = self._collect_one_node(matchnode, handle_dupes)
|
||||
if not duplicate and not rep.passed:
|
||||
|
|
@ -929,15 +928,15 @@ class Session(nodes.Collector):
|
|||
# Path part e.g. `/a/b/` in `/a/b/test_file.py::TestIt::test_it`.
|
||||
if isinstance(matchparts[0], Path):
|
||||
is_match = node.path == matchparts[0]
|
||||
if sys.platform == "win32" and not is_match:
|
||||
if sys.platform == 'win32' and not is_match:
|
||||
# In case the file paths do not match, fallback to samefile() to
|
||||
# account for short-paths on Windows (#11895).
|
||||
same_file = os.path.samefile(node.path, matchparts[0])
|
||||
# We don't want to match links to the current node,
|
||||
# otherwise we would match the same file more than once (#12039).
|
||||
is_match = same_file and (
|
||||
os.path.islink(node.path)
|
||||
== os.path.islink(matchparts[0])
|
||||
os.path.islink(node.path) ==
|
||||
os.path.islink(matchparts[0])
|
||||
)
|
||||
|
||||
# Name part e.g. `TestIt` in `/a/b/test_file.py::TestIt::test_it`.
|
||||
|
|
@ -945,8 +944,8 @@ class Session(nodes.Collector):
|
|||
# TODO: Remove parametrized workaround once collection structure contains
|
||||
# parametrization.
|
||||
is_match = (
|
||||
node.name == matchparts[0]
|
||||
or node.name.split("[")[0] == matchparts[0]
|
||||
node.name == matchparts[0] or
|
||||
node.name.split('[')[0] == matchparts[0]
|
||||
)
|
||||
if is_match:
|
||||
work.append((node, matchparts[1:]))
|
||||
|
|
@ -956,21 +955,21 @@ class Session(nodes.Collector):
|
|||
notfound_collectors.append(matchnode)
|
||||
|
||||
if not any_matched_in_initial_part:
|
||||
report_arg = "::".join((str(argpath), *names))
|
||||
report_arg = '::'.join((str(argpath), *names))
|
||||
self._notfound.append((report_arg, notfound_collectors))
|
||||
|
||||
self.trace.root.indent -= 1
|
||||
|
||||
def genitems(
|
||||
self, node: Union[nodes.Item, nodes.Collector]
|
||||
self, node: nodes.Item | nodes.Collector,
|
||||
) -> Iterator[nodes.Item]:
|
||||
self.trace("genitems", node)
|
||||
self.trace('genitems', node)
|
||||
if isinstance(node, nodes.Item):
|
||||
node.ihook.pytest_itemcollected(item=node)
|
||||
yield node
|
||||
else:
|
||||
assert isinstance(node, nodes.Collector)
|
||||
keepduplicates = self.config.getoption("keepduplicates")
|
||||
keepduplicates = self.config.getoption('keepduplicates')
|
||||
# For backward compat, dedup only applies to files.
|
||||
handle_dupes = not (keepduplicates and isinstance(node, nodes.File))
|
||||
rep, duplicate = self._collect_one_node(node, handle_dupes)
|
||||
|
|
@ -983,7 +982,7 @@ class Session(nodes.Collector):
|
|||
node.ihook.pytest_collectreport(report=rep)
|
||||
|
||||
|
||||
def search_pypath(module_name: str) -> Optional[str]:
|
||||
def search_pypath(module_name: str) -> str | None:
|
||||
"""Search sys.path for the given a dotted module name, and return its file
|
||||
system path if found."""
|
||||
try:
|
||||
|
|
@ -993,7 +992,7 @@ def search_pypath(module_name: str) -> Optional[str]:
|
|||
# ValueError: not a module name
|
||||
except (AttributeError, ImportError, ValueError):
|
||||
return None
|
||||
if spec is None or spec.origin is None or spec.origin == "namespace":
|
||||
if spec is None or spec.origin is None or spec.origin == 'namespace':
|
||||
return None
|
||||
elif spec.submodule_search_locations:
|
||||
return os.path.dirname(spec.origin)
|
||||
|
|
@ -1007,11 +1006,11 @@ class CollectionArgument:
|
|||
|
||||
path: Path
|
||||
parts: Sequence[str]
|
||||
module_name: Optional[str]
|
||||
module_name: str | None
|
||||
|
||||
|
||||
def resolve_collection_argument(
|
||||
invocation_path: Path, arg: str, *, as_pypath: bool = False
|
||||
invocation_path: Path, arg: str, *, as_pypath: bool = False,
|
||||
) -> CollectionArgument:
|
||||
"""Parse path arguments optionally containing selection parts and return (fspath, names).
|
||||
|
||||
|
|
@ -1045,10 +1044,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.
|
||||
"""
|
||||
base, squacket, rest = str(arg).partition("[")
|
||||
strpath, *parts = base.split("::")
|
||||
base, squacket, rest = str(arg).partition('[')
|
||||
strpath, *parts = base.split('::')
|
||||
if parts:
|
||||
parts[-1] = f"{parts[-1]}{squacket}{rest}"
|
||||
parts[-1] = f'{parts[-1]}{squacket}{rest}'
|
||||
module_name = None
|
||||
if as_pypath:
|
||||
pyarg_strpath = search_pypath(strpath)
|
||||
|
|
@ -1059,16 +1058,16 @@ def resolve_collection_argument(
|
|||
fspath = absolutepath(fspath)
|
||||
if not safe_exists(fspath):
|
||||
msg = (
|
||||
"module or package not found: {arg} (missing __init__.py?)"
|
||||
'module or package not found: {arg} (missing __init__.py?)'
|
||||
if as_pypath
|
||||
else "file or directory not found: {arg}"
|
||||
else 'file or directory not found: {arg}'
|
||||
)
|
||||
raise UsageError(msg.format(arg=arg))
|
||||
if parts and fspath.is_dir():
|
||||
msg = (
|
||||
"package argument cannot contain :: selection parts: {arg}"
|
||||
'package argument cannot contain :: selection parts: {arg}'
|
||||
if as_pypath
|
||||
else "directory argument cannot contain :: selection parts: {arg}"
|
||||
else 'directory argument cannot contain :: selection parts: {arg}'
|
||||
)
|
||||
raise UsageError(msg.format(arg=arg))
|
||||
return CollectionArgument(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue