aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorarcadia-devtools <arcadia-devtools@yandex-team.ru>2022-03-14 14:36:14 +0300
committerarcadia-devtools <arcadia-devtools@yandex-team.ru>2022-03-14 14:36:14 +0300
commite55fb55efda71cea0cd9c5fdafa41af406aef5bf (patch)
tree664dd8ed9a31584f9373593983273c9de2f13e7b
parent95e3624686fdca2887aa10594ee976cfddd32e38 (diff)
downloadydb-e55fb55efda71cea0cd9c5fdafa41af406aef5bf.tar.gz
intermediate changes
ref:8379e897e1f4fa0d71bb778a7c8bc68cb5e2f5ea
-rw-r--r--contrib/python/pytest/py3/.dist-info/METADATA7
-rw-r--r--contrib/python/pytest/py3/.dist-info/entry_points.txt1
-rw-r--r--contrib/python/pytest/py3/AUTHORS1
-rw-r--r--contrib/python/pytest/py3/README.rst2
-rw-r--r--contrib/python/pytest/py3/_pytest/_argcomplete.py1
-rw-r--r--contrib/python/pytest/py3/_pytest/_version.py4
-rw-r--r--contrib/python/pytest/py3/_pytest/assertion/rewrite.py21
-rw-r--r--contrib/python/pytest/py3/_pytest/assertion/util.py41
-rw-r--r--contrib/python/pytest/py3/_pytest/capture.py8
-rw-r--r--contrib/python/pytest/py3/_pytest/compat.py12
-rw-r--r--contrib/python/pytest/py3/_pytest/config/__init__.py68
-rw-r--r--contrib/python/pytest/py3/_pytest/config/findpaths.py2
-rw-r--r--contrib/python/pytest/py3/_pytest/deprecated.py32
-rw-r--r--contrib/python/pytest/py3/_pytest/doctest.py2
-rw-r--r--contrib/python/pytest/py3/_pytest/fixtures.py143
-rw-r--r--contrib/python/pytest/py3/_pytest/hookspec.py40
-rw-r--r--contrib/python/pytest/py3/_pytest/junitxml.py2
-rw-r--r--contrib/python/pytest/py3/_pytest/logging.py33
-rw-r--r--contrib/python/pytest/py3/_pytest/main.py3
-rw-r--r--contrib/python/pytest/py3/_pytest/mark/__init__.py18
-rw-r--r--contrib/python/pytest/py3/_pytest/mark/structures.py15
-rw-r--r--contrib/python/pytest/py3/_pytest/monkeypatch.py2
-rw-r--r--contrib/python/pytest/py3/_pytest/pathlib.py23
-rw-r--r--contrib/python/pytest/py3/_pytest/pytester.py12
-rw-r--r--contrib/python/pytest/py3/_pytest/python.py435
-rw-r--r--contrib/python/pytest/py3/_pytest/python_api.py41
-rw-r--r--contrib/python/pytest/py3/_pytest/reports.py3
-rw-r--r--contrib/python/pytest/py3/_pytest/runner.py7
-rw-r--r--contrib/python/pytest/py3/_pytest/terminal.py26
-rw-r--r--contrib/python/pytest/py3/_pytest/unittest.py4
-rw-r--r--contrib/python/pytest/py3/_pytest/warning_types.py7
-rw-r--r--contrib/python/pytest/py3/_pytest/warnings.py27
-rw-r--r--contrib/python/pytest/py3/patches/03-limit-id.patch51
-rw-r--r--contrib/python/pytest/py3/patches/04-support-cyrillic-id.patch2
-rw-r--r--contrib/python/pytest/py3/patches/06-support-ya-markers.patch2
-rw-r--r--contrib/python/pytest/py3/patches/07-disable-translate-non-printable.patch2
-rw-r--r--contrib/python/pytest/py3/pytest/__init__.py6
-rw-r--r--contrib/python/pytest/py3/pytest/collect.py38
-rw-r--r--contrib/python/pytest/py3/ya.make7
39 files changed, 511 insertions, 640 deletions
diff --git a/contrib/python/pytest/py3/.dist-info/METADATA b/contrib/python/pytest/py3/.dist-info/METADATA
index b2e35a1a37..68b4160d36 100644
--- a/contrib/python/pytest/py3/.dist-info/METADATA
+++ b/contrib/python/pytest/py3/.dist-info/METADATA
@@ -1,6 +1,6 @@
Metadata-Version: 2.1
Name: pytest
-Version: 7.0.1
+Version: 7.1.0
Summary: pytest: simple powerful testing with Python
Home-page: https://docs.pytest.org/en/latest/
Author: Holger Krekel, Bruno Oliveira, Ronny Pfannschmidt, Floris Bruynooghe, Brianna Laugher, Florian Bruhin and others
@@ -23,7 +23,6 @@ Classifier: Operating System :: Microsoft :: Windows
Classifier: Operating System :: POSIX
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
-Classifier: Programming Language :: Python :: 3.6
Classifier: Programming Language :: Python :: 3.7
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
@@ -31,7 +30,7 @@ Classifier: Programming Language :: Python :: 3.10
Classifier: Topic :: Software Development :: Libraries
Classifier: Topic :: Software Development :: Testing
Classifier: Topic :: Utilities
-Requires-Python: >=3.6
+Requires-Python: >=3.7
Description-Content-Type: text/x-rst
License-File: LICENSE
Requires-Dist: attrs (>=19.2.0)
@@ -154,7 +153,7 @@ Features
- Can run `unittest <https://docs.pytest.org/en/stable/how-to/unittest.html>`_ (or trial),
`nose <https://docs.pytest.org/en/stable/how-to/nose.html>`_ test suites out of the box
-- Python 3.6+ and PyPy3
+- Python 3.7+ or PyPy3
- Rich plugin architecture, with over 850+ `external plugins <https://docs.pytest.org/en/latest/reference/plugin_list.html>`_ and thriving community
diff --git a/contrib/python/pytest/py3/.dist-info/entry_points.txt b/contrib/python/pytest/py3/.dist-info/entry_points.txt
index 0267c75b77..192205dfa5 100644
--- a/contrib/python/pytest/py3/.dist-info/entry_points.txt
+++ b/contrib/python/pytest/py3/.dist-info/entry_points.txt
@@ -1,4 +1,3 @@
[console_scripts]
py.test = pytest:console_main
pytest = pytest:console_main
-
diff --git a/contrib/python/pytest/py3/AUTHORS b/contrib/python/pytest/py3/AUTHORS
index 9413f9c2e7..2b6ca66de7 100644
--- a/contrib/python/pytest/py3/AUTHORS
+++ b/contrib/python/pytest/py3/AUTHORS
@@ -187,6 +187,7 @@ Kevin Cox
Kevin J. Foley
Kian-Meng Ang
Kodi B. Arfer
+Kojo Idrissa
Kostis Anagnostopoulos
Kristoffer Nordström
Kyle Altendorf
diff --git a/contrib/python/pytest/py3/README.rst b/contrib/python/pytest/py3/README.rst
index 1473376517..f0fe356321 100644
--- a/contrib/python/pytest/py3/README.rst
+++ b/contrib/python/pytest/py3/README.rst
@@ -100,7 +100,7 @@ Features
- Can run `unittest <https://docs.pytest.org/en/stable/how-to/unittest.html>`_ (or trial),
`nose <https://docs.pytest.org/en/stable/how-to/nose.html>`_ test suites out of the box
-- Python 3.6+ and PyPy3
+- Python 3.7+ or PyPy3
- Rich plugin architecture, with over 850+ `external plugins <https://docs.pytest.org/en/latest/reference/plugin_list.html>`_ and thriving community
diff --git a/contrib/python/pytest/py3/_pytest/_argcomplete.py b/contrib/python/pytest/py3/_pytest/_argcomplete.py
index 41d9d9407c..120f09ff68 100644
--- a/contrib/python/pytest/py3/_pytest/_argcomplete.py
+++ b/contrib/python/pytest/py3/_pytest/_argcomplete.py
@@ -108,7 +108,6 @@ if os.environ.get("_ARGCOMPLETE"):
def try_argcomplete(parser: argparse.ArgumentParser) -> None:
argcomplete.autocomplete(parser, always_complete_options=False)
-
else:
def try_argcomplete(parser: argparse.ArgumentParser) -> None:
diff --git a/contrib/python/pytest/py3/_pytest/_version.py b/contrib/python/pytest/py3/_pytest/_version.py
index 5515abadad..8a09b04a9d 100644
--- a/contrib/python/pytest/py3/_pytest/_version.py
+++ b/contrib/python/pytest/py3/_pytest/_version.py
@@ -1,5 +1,5 @@
# coding: utf-8
# file generated by setuptools_scm
# don't change, don't track in version control
-version = '7.0.1'
-version_tuple = (7, 0, 1)
+version = '7.1.0'
+version_tuple = (7, 1, 0)
diff --git a/contrib/python/pytest/py3/_pytest/assertion/rewrite.py b/contrib/python/pytest/py3/_pytest/assertion/rewrite.py
index 88ac6cab36..81096764e0 100644
--- a/contrib/python/pytest/py3/_pytest/assertion/rewrite.py
+++ b/contrib/python/pytest/py3/_pytest/assertion/rewrite.py
@@ -100,9 +100,6 @@ class AssertionRewritingHook(importlib.abc.MetaPathFinder, importlib.abc.Loader)
spec is None
# this is a namespace package (without `__init__.py`)
# there's nothing to rewrite there
- # python3.6: `namespace`
- # python3.7+: `None`
- or spec.origin == "namespace"
or spec.origin is None
# we can only rewrite source files
or not isinstance(spec.loader, importlib.machinery.SourceFileLoader)
@@ -295,9 +292,8 @@ def _write_pyc_fp(
# import. However, there's little reason to deviate.
fp.write(importlib.util.MAGIC_NUMBER)
# https://www.python.org/dev/peps/pep-0552/
- if sys.version_info >= (3, 7):
- flags = b"\x00\x00\x00\x00"
- fp.write(flags)
+ flags = b"\x00\x00\x00\x00"
+ fp.write(flags)
# as of now, bytecode header expects 32-bit numbers for size and mtime (#4903)
mtime = int(source_stat.st_mtime) & 0xFFFFFFFF
size = source_stat.st_size & 0xFFFFFFFF
@@ -326,7 +322,6 @@ if sys.platform == "win32":
return False
return True
-
else:
def _write_pyc(
@@ -379,31 +374,29 @@ def _read_pyc(
except OSError:
return None
with fp:
- # https://www.python.org/dev/peps/pep-0552/
- has_flags = sys.version_info >= (3, 7)
try:
stat_result = os.stat(source)
mtime = int(stat_result.st_mtime)
size = stat_result.st_size
- data = fp.read(16 if has_flags else 12)
+ data = fp.read(16)
except OSError as e:
trace(f"_read_pyc({source}): OSError {e}")
return None
# Check for invalid or out of date pyc file.
- if len(data) != (16 if has_flags else 12):
+ if len(data) != (16):
trace("_read_pyc(%s): invalid pyc (too short)" % source)
return None
if data[:4] != importlib.util.MAGIC_NUMBER:
trace("_read_pyc(%s): invalid pyc (bad magic number)" % source)
return None
- if has_flags and data[4:8] != b"\x00\x00\x00\x00":
+ if data[4:8] != b"\x00\x00\x00\x00":
trace("_read_pyc(%s): invalid pyc (unsupported flags)" % source)
return None
- mtime_data = data[8 if has_flags else 4 : 12 if has_flags else 8]
+ mtime_data = data[8:12]
if int.from_bytes(mtime_data, "little") != mtime & 0xFFFFFFFF:
trace("_read_pyc(%s): out of date" % source)
return None
- size_data = data[12 if has_flags else 8 : 16 if has_flags else 12]
+ size_data = data[12:16]
if int.from_bytes(size_data, "little") != size & 0xFFFFFFFF:
trace("_read_pyc(%s): invalid pyc (incorrect size)" % source)
return None
diff --git a/contrib/python/pytest/py3/_pytest/assertion/util.py b/contrib/python/pytest/py3/_pytest/assertion/util.py
index 19f1089c20..03167ddd47 100644
--- a/contrib/python/pytest/py3/_pytest/assertion/util.py
+++ b/contrib/python/pytest/py3/_pytest/assertion/util.py
@@ -135,6 +135,27 @@ def isiterable(obj: Any) -> bool:
return False
+def has_default_eq(
+ obj: object,
+) -> bool:
+ """Check if an instance of an object contains the default eq
+
+ First, we check if the object's __eq__ attribute has __code__,
+ if so, we check the equally of the method code filename (__code__.co_filename)
+ to the default one generated by the dataclass and attr module
+ for dataclasses the default co_filename is <string>, for attrs class, the __eq__ should contain "attrs eq generated"
+ """
+ # inspired from https://github.com/willmcgugan/rich/blob/07d51ffc1aee6f16bd2e5a25b4e82850fb9ed778/rich/pretty.py#L68
+ if hasattr(obj.__eq__, "__code__") and hasattr(obj.__eq__.__code__, "co_filename"):
+ code_filename = obj.__eq__.__code__.co_filename
+
+ if isattrs(obj):
+ return "attrs generated eq" in code_filename
+
+ return code_filename == "<string>" # data class
+ return True
+
+
def assertrepr_compare(config, op: str, left: Any, right: Any) -> Optional[List[str]]:
"""Return specialised explanations for some operators/operands."""
verbose = config.getoption("verbose")
@@ -202,8 +223,6 @@ def _compare_eq_any(left: Any, right: Any, verbose: int = 0) -> List[str]:
explanation = _compare_eq_set(left, right, verbose)
elif isdict(left) and isdict(right):
explanation = _compare_eq_dict(left, right, verbose)
- elif verbose > 0:
- explanation = _compare_eq_verbose(left, right)
if isiterable(left) and isiterable(right):
expl = _compare_eq_iterable(left, right, verbose)
@@ -260,18 +279,6 @@ def _diff_text(left: str, right: str, verbose: int = 0) -> List[str]:
return explanation
-def _compare_eq_verbose(left: Any, right: Any) -> List[str]:
- keepends = True
- left_lines = repr(left).splitlines(keepends)
- right_lines = repr(right).splitlines(keepends)
-
- explanation: List[str] = []
- explanation += ["+" + line for line in left_lines]
- explanation += ["-" + line for line in right_lines]
-
- return explanation
-
-
def _surrounding_parens_on_own_lines(lines: List[str]) -> None:
"""Move opening/closing parenthesis/bracket to own lines."""
opening = lines[0][:1]
@@ -287,8 +294,8 @@ def _surrounding_parens_on_own_lines(lines: List[str]) -> None:
def _compare_eq_iterable(
left: Iterable[Any], right: Iterable[Any], verbose: int = 0
) -> List[str]:
- if not verbose and not running_on_ci():
- return ["Use -v to get the full diff"]
+ if verbose <= 0 and not running_on_ci():
+ return ["Use -v to get more diff"]
# dynamic import to speedup pytest
import difflib
@@ -427,6 +434,8 @@ def _compare_eq_dict(
def _compare_eq_cls(left: Any, right: Any, verbose: int) -> List[str]:
+ if not has_default_eq(left):
+ return []
if isdatacls(left):
all_fields = left.__dataclass_fields__
fields_to_check = [field for field, info in all_fields.items() if info.compare]
diff --git a/contrib/python/pytest/py3/_pytest/capture.py b/contrib/python/pytest/py3/_pytest/capture.py
index 884f035e29..ee9de37332 100644
--- a/contrib/python/pytest/py3/_pytest/capture.py
+++ b/contrib/python/pytest/py3/_pytest/capture.py
@@ -68,8 +68,8 @@ def _colorama_workaround() -> None:
pass
-def _py36_windowsconsoleio_workaround(stream: TextIO) -> None:
- """Workaround for Windows Unicode console handling on Python>=3.6.
+def _windowsconsoleio_workaround(stream: TextIO) -> None:
+ """Workaround for Windows Unicode console handling.
Python 3.6 implemented Unicode console handling for Windows. This works
by reading/writing to the raw console handle using
@@ -112,7 +112,7 @@ def _py36_windowsconsoleio_workaround(stream: TextIO) -> None:
buffering = -1
return io.TextIOWrapper(
- open(os.dup(f.fileno()), mode, buffering), # type: ignore[arg-type]
+ open(os.dup(f.fileno()), mode, buffering),
f.encoding,
f.errors,
f.newlines,
@@ -128,7 +128,7 @@ def _py36_windowsconsoleio_workaround(stream: TextIO) -> None:
def pytest_load_initial_conftests(early_config: Config):
ns = early_config.known_args_namespace
if ns.capture == "fd":
- _py36_windowsconsoleio_workaround(sys.stdout)
+ _windowsconsoleio_workaround(sys.stdout)
_colorama_workaround()
pluginmanager = early_config.pluginmanager
capman = CaptureManager(ns.capture)
diff --git a/contrib/python/pytest/py3/_pytest/compat.py b/contrib/python/pytest/py3/_pytest/compat.py
index 25894d344d..71c2518d77 100644
--- a/contrib/python/pytest/py3/_pytest/compat.py
+++ b/contrib/python/pytest/py3/_pytest/compat.py
@@ -4,7 +4,6 @@ import functools
import inspect
import os
import sys
-from contextlib import contextmanager
from inspect import Parameter
from inspect import signature
from pathlib import Path
@@ -186,17 +185,6 @@ def getfuncargnames(
return arg_names
-if sys.version_info < (3, 7):
-
- @contextmanager
- def nullcontext():
- yield
-
-
-else:
- from contextlib import nullcontext as nullcontext # noqa: F401
-
-
def get_default_arg_names(function: Callable[..., Any]) -> Tuple[str, ...]:
# Note: this code intentionally mirrors the code at the beginning of
# getfuncargnames, to get the arguments which were excluded from its result
diff --git a/contrib/python/pytest/py3/_pytest/config/__init__.py b/contrib/python/pytest/py3/_pytest/config/__init__.py
index ebf6e1b950..f4818c861c 100644
--- a/contrib/python/pytest/py3/_pytest/config/__init__.py
+++ b/contrib/python/pytest/py3/_pytest/config/__init__.py
@@ -1,7 +1,6 @@
"""Command line options, ini-file and conftest.py processing."""
import argparse
import collections.abc
-import contextlib
import copy
import enum
import inspect
@@ -353,13 +352,17 @@ class PytestPluginManager(PluginManager):
import _pytest.assertion
super().__init__("pytest")
- # The objects are module objects, only used generically.
- self._conftest_plugins: Set[types.ModuleType] = set()
- # State related to local conftest plugins.
+ # -- State related to local conftest plugins.
+ # All loaded conftest modules.
+ self._conftest_plugins: Set[types.ModuleType] = set()
+ # All conftest modules applicable for a directory.
+ # This includes the directory's own conftest modules as well
+ # as those of its parent directories.
self._dirpath2confmods: Dict[Path, List[types.ModuleType]] = {}
- self._conftestpath2mod: Dict[Path, types.ModuleType] = {}
+ # Cutoff directory above which conftests are no longer discovered.
self._confcutdir: Optional[Path] = None
+ # If set, conftest loading is skipped.
self._noconftest = False
# _getconftestmodules()'s call to _get_directory() causes a stat
@@ -528,6 +531,19 @@ class PytestPluginManager(PluginManager):
if not foundanchor:
self._try_load_conftest(current, namespace.importmode, rootpath)
+ def _is_in_confcutdir(self, path: Path) -> bool:
+ """Whether a path is within the confcutdir.
+
+ When false, should not load conftest.
+ """
+ if self._confcutdir is None:
+ return True
+ try:
+ path.relative_to(self._confcutdir)
+ except ValueError:
+ return False
+ return True
+
def _try_load_conftest(
self, anchor: Path, importmode: Union[str, ImportMode], rootpath: Path
) -> None:
@@ -540,7 +556,7 @@ class PytestPluginManager(PluginManager):
def _getconftestmodules(
self, path: Path, importmode: Union[str, ImportMode], rootpath: Path
- ) -> List[types.ModuleType]:
+ ) -> Sequence[types.ModuleType]:
if self._noconftest:
return []
@@ -556,14 +572,12 @@ class PytestPluginManager(PluginManager):
# and allow users to opt into looking into the rootdir parent
# directories instead of requiring to specify confcutdir.
clist = []
- confcutdir_parents = self._confcutdir.parents if self._confcutdir else []
for parent in reversed((directory, *directory.parents)):
- if parent in confcutdir_parents:
- continue
- conftestpath = parent / "conftest.py"
- if conftestpath.is_file():
- mod = self._importconftest(conftestpath, importmode, rootpath)
- clist.append(mod)
+ if self._is_in_confcutdir(parent):
+ conftestpath = parent / "conftest.py"
+ if conftestpath.is_file():
+ mod = self._importconftest(conftestpath, importmode, rootpath)
+ clist.append(mod)
self._dirpath2confmods[directory] = clist
return clist
@@ -585,15 +599,9 @@ class PytestPluginManager(PluginManager):
def _importconftest(
self, conftestpath: Path, importmode: Union[str, ImportMode], rootpath: Path
) -> types.ModuleType:
- # Use a resolved Path object as key to avoid loading the same conftest
- # twice with build systems that create build directories containing
- # symlinks to actual files.
- # Using Path().resolve() is better than py.path.realpath because
- # it resolves to the correct path/drive in case-insensitive file systems (#5792)
- key = conftestpath.resolve()
-
- with contextlib.suppress(KeyError):
- return self._conftestpath2mod[key]
+ existing = self.get_plugin(str(conftestpath))
+ if existing is not None:
+ return cast(types.ModuleType, existing)
pkgpath = resolve_package_path(conftestpath)
if pkgpath is None:
@@ -609,11 +617,10 @@ class PytestPluginManager(PluginManager):
self._check_non_top_pytest_plugins(mod, conftestpath)
self._conftest_plugins.add(mod)
- self._conftestpath2mod[key] = mod
dirpath = conftestpath.parent
if dirpath in self._dirpath2confmods:
for path, mods in self._dirpath2confmods.items():
- if path and dirpath in path.parents or path == dirpath:
+ if dirpath in path.parents or path == dirpath:
assert mod not in mods
mods.append(mod)
self.trace(f"loading conftestmodule {mod!r}")
@@ -1341,14 +1348,6 @@ class Config:
if records:
frame = sys._getframe(stacklevel - 1)
location = frame.f_code.co_filename, frame.f_lineno, frame.f_code.co_name
- self.hook.pytest_warning_captured.call_historic(
- kwargs=dict(
- warning_message=records[0],
- when="config",
- item=None,
- location=location,
- )
- )
self.hook.pytest_warning_recorded.call_historic(
kwargs=dict(
warning_message=records[0],
@@ -1448,6 +1447,7 @@ class Config:
)
except KeyError:
return None
+ assert mod.__file__ is not None
modpath = Path(mod.__file__).parent
values: List[Path] = []
for relroot in relroots:
@@ -1593,7 +1593,7 @@ def _strtobool(val: str) -> bool:
@lru_cache(maxsize=50)
def parse_warning_filter(
arg: str, *, escape: bool
-) -> Tuple[str, str, Type[Warning], str, int]:
+) -> Tuple["warnings._ActionKind", str, Type[Warning], str, int]:
"""Parse a warnings filter string.
This is copied from warnings._setoption with the following changes:
@@ -1635,7 +1635,7 @@ def parse_warning_filter(
parts.append("")
action_, message, category_, module, lineno_ = (s.strip() for s in parts)
try:
- action: str = warnings._getaction(action_) # type: ignore[attr-defined]
+ action: "warnings._ActionKind" = warnings._getaction(action_) # type: ignore[attr-defined]
except warnings._OptionError as e:
raise UsageError(error_template.format(error=str(e)))
try:
diff --git a/contrib/python/pytest/py3/_pytest/config/findpaths.py b/contrib/python/pytest/py3/_pytest/config/findpaths.py
index 89ade5f23b..c082e652d9 100644
--- a/contrib/python/pytest/py3/_pytest/config/findpaths.py
+++ b/contrib/python/pytest/py3/_pytest/config/findpaths.py
@@ -70,7 +70,7 @@ def load_config_dict_from_file(
try:
config = tomli.loads(toml_text)
except tomli.TOMLDecodeError as exc:
- raise UsageError(str(exc)) from exc
+ raise UsageError(f"{filepath}: {exc}") from exc
result = config.get("tool", {}).get("pytest", {}).get("ini_options", None)
if result is not None:
diff --git a/contrib/python/pytest/py3/_pytest/deprecated.py b/contrib/python/pytest/py3/_pytest/deprecated.py
index 5248927113..f2d79760ae 100644
--- a/contrib/python/pytest/py3/_pytest/deprecated.py
+++ b/contrib/python/pytest/py3/_pytest/deprecated.py
@@ -11,7 +11,6 @@ in case of warnings which need to format their messages.
from warnings import warn
from _pytest.warning_types import PytestDeprecationWarning
-from _pytest.warning_types import PytestRemovedIn7Warning
from _pytest.warning_types import PytestRemovedIn8Warning
from _pytest.warning_types import UnformattedWarning
@@ -24,18 +23,6 @@ DEPRECATED_EXTERNAL_PLUGINS = {
}
-FILLFUNCARGS = UnformattedWarning(
- PytestRemovedIn7Warning,
- "{name} is deprecated, use "
- "function._request._fillfixtures() instead if you cannot avoid reaching into internals.",
-)
-
-PYTEST_COLLECT_MODULE = UnformattedWarning(
- PytestRemovedIn7Warning,
- "pytest.collect.{name} was moved to pytest.{name}\n"
- "Please update to the new name.",
-)
-
# This can be* removed pytest 8, but it's harmless and common, so no rush to remove.
# * If you're in the future: "could have been".
YIELD_FIXTURE = PytestDeprecationWarning(
@@ -43,20 +30,6 @@ YIELD_FIXTURE = PytestDeprecationWarning(
"Use @pytest.fixture instead; they are the same."
)
-MINUS_K_DASH = PytestRemovedIn7Warning(
- "The `-k '-expr'` syntax to -k is deprecated.\nUse `-k 'not expr'` instead."
-)
-
-MINUS_K_COLON = PytestRemovedIn7Warning(
- "The `-k 'expr:'` syntax to -k is deprecated.\n"
- "Please open an issue if you use this and want a replacement."
-)
-
-WARNING_CAPTURED_HOOK = PytestRemovedIn7Warning(
- "The pytest_warning_captured is deprecated and will be removed in a future release.\n"
- "Please use pytest_warning_recorded instead."
-)
-
WARNING_CMDLINE_PREPARSE_HOOK = PytestRemovedIn8Warning(
"The pytest_cmdline_preparse hook is deprecated and will be removed in a future release. \n"
"Please use pytest_load_initial_conftests hook instead."
@@ -74,11 +47,6 @@ STRICT_OPTION = PytestRemovedIn8Warning(
# This deprecation is never really meant to be removed.
PRIVATE = PytestDeprecationWarning("A private pytest class or function was used.")
-UNITTEST_SKIP_DURING_COLLECTION = PytestRemovedIn8Warning(
- "Raising unittest.SkipTest to skip tests during collection is deprecated. "
- "Use pytest.skip() instead."
-)
-
ARGUMENT_PERCENT_DEFAULT = PytestRemovedIn8Warning(
'pytest now uses argparse. "%default" should be changed to "%(default)s"',
)
diff --git a/contrib/python/pytest/py3/_pytest/doctest.py b/contrib/python/pytest/py3/_pytest/doctest.py
index 0784f431b8..7d37be2acc 100644
--- a/contrib/python/pytest/py3/_pytest/doctest.py
+++ b/contrib/python/pytest/py3/_pytest/doctest.py
@@ -656,7 +656,7 @@ def _init_checker_class() -> Type["doctest.OutputChecker"]:
precision = 0 if fraction is None else len(fraction)
if exponent is not None:
precision -= int(exponent)
- if float(w.group()) == approx(float(g.group()), abs=10 ** -precision):
+ if float(w.group()) == approx(float(g.group()), abs=10**-precision):
# They're close enough. Replace the text we actually
# got with the text we want, so that it will match when we
# check the string literally.
diff --git a/contrib/python/pytest/py3/_pytest/fixtures.py b/contrib/python/pytest/py3/_pytest/fixtures.py
index fddff931c5..ee3e93f190 100644
--- a/contrib/python/pytest/py3/_pytest/fixtures.py
+++ b/contrib/python/pytest/py3/_pytest/fixtures.py
@@ -52,7 +52,6 @@ from _pytest.config import _PluggyPlugin
from _pytest.config import Config
from _pytest.config.argparsing import Parser
from _pytest.deprecated import check_ispytest
-from _pytest.deprecated import FILLFUNCARGS
from _pytest.deprecated import YIELD_FIXTURE
from _pytest.mark import Mark
from _pytest.mark import ParameterSet
@@ -73,7 +72,6 @@ if TYPE_CHECKING:
from _pytest.scope import _ScopeName
from _pytest.main import Session
from _pytest.python import CallSpec2
- from _pytest.python import Function
from _pytest.python import Metafunc
@@ -352,41 +350,6 @@ def reorder_items_atscope(
return items_done
-def _fillfuncargs(function: "Function") -> None:
- """Fill missing fixtures for a test function, old public API (deprecated)."""
- warnings.warn(FILLFUNCARGS.format(name="pytest._fillfuncargs()"), stacklevel=2)
- _fill_fixtures_impl(function)
-
-
-def fillfixtures(function: "Function") -> None:
- """Fill missing fixtures for a test function (deprecated)."""
- warnings.warn(
- FILLFUNCARGS.format(name="_pytest.fixtures.fillfixtures()"), stacklevel=2
- )
- _fill_fixtures_impl(function)
-
-
-def _fill_fixtures_impl(function: "Function") -> None:
- """Internal implementation to fill fixtures on the given function object."""
- try:
- request = function._request
- except AttributeError:
- # XXX this special code path is only expected to execute
- # with the oejskit plugin. It uses classes with funcargs
- # and we thus have to work a bit to allow this.
- fm = function.session._fixturemanager
- assert function.parent is not None
- fi = fm.getfixtureinfo(function.parent, function.obj, None)
- function._fixtureinfo = fi
- request = function._request = FixtureRequest(function, _ispytest=True)
- fm.session._setupstate.setup(function)
- request._fillfixtures()
- # Prune out funcargs for jstests.
- function.funcargs = {name: function.funcargs[name] for name in fi.argnames}
- else:
- request._fillfixtures()
-
-
def get_direct_param_fixture_func(request):
return request.param
@@ -634,8 +597,17 @@ class FixtureRequest:
funcitem = self._pyfuncitem
scope = fixturedef._scope
try:
- param = funcitem.callspec.getparam(argname)
- except (AttributeError, ValueError):
+ callspec = funcitem.callspec
+ except AttributeError:
+ callspec = None
+ if callspec is not None and argname in callspec.params:
+ param = callspec.params[argname]
+ param_index = callspec.indices[argname]
+ # If a parametrize invocation set a scope it will override
+ # the static scope defined with the fixture function.
+ with suppress(KeyError):
+ scope = callspec._arg2scope[argname]
+ else:
param = NOTSET
param_index = 0
has_params = fixturedef.params is not None
@@ -675,12 +647,6 @@ class FixtureRequest:
)
)
fail(msg, pytrace=False)
- else:
- param_index = funcitem.callspec.indices[argname]
- # If a parametrize invocation set a scope it will override
- # the static scope defined with the fixture function.
- with suppress(KeyError):
- scope = funcitem.callspec._arg2scope[argname]
subrequest = SubRequest(
self, scope, param, param_index, fixturedef, _ispytest=True
@@ -964,7 +930,7 @@ def _eval_scope_callable(
@final
class FixtureDef(Generic[FixtureValue]):
- """A container for a factory definition."""
+ """A container for a fixture definition."""
def __init__(
self,
@@ -976,33 +942,56 @@ class FixtureDef(Generic[FixtureValue]):
params: Optional[Sequence[object]],
unittest: bool = False,
ids: Optional[
- Union[
- Tuple[Union[None, str, float, int, bool], ...],
- Callable[[Any], Optional[object]],
- ]
+ Union[Tuple[Optional[object], ...], Callable[[Any], Optional[object]]]
] = None,
) -> None:
self._fixturemanager = fixturemanager
+ # The "base" node ID for the fixture.
+ #
+ # This is a node ID prefix. A fixture is only available to a node (e.g.
+ # a `Function` item) if the fixture's baseid is a parent of the node's
+ # nodeid (see the `iterparentnodeids` function for what constitutes a
+ # "parent" and a "prefix" in this context).
+ #
+ # For a fixture found in a Collector's object (e.g. a `Module`s module,
+ # a `Class`'s class), the baseid is the Collector's nodeid.
+ #
+ # For a fixture found in a conftest plugin, the baseid is the conftest's
+ # directory path relative to the rootdir.
+ #
+ # For other plugins, the baseid is the empty string (always matches).
self.baseid = baseid or ""
+ # Whether the fixture was found from a node or a conftest in the
+ # collection tree. Will be false for fixtures defined in non-conftest
+ # plugins.
self.has_location = baseid is not None
+ # The fixture factory function.
self.func = func
+ # The name by which the fixture may be requested.
self.argname = argname
if scope is None:
scope = Scope.Function
elif callable(scope):
scope = _eval_scope_callable(scope, argname, fixturemanager.config)
-
if isinstance(scope, str):
scope = Scope.from_user(
scope, descr=f"Fixture '{func.__name__}'", where=baseid
)
self._scope = scope
+ # If the fixture is directly parametrized, the parameter values.
self.params: Optional[Sequence[object]] = params
- self.argnames: Tuple[str, ...] = getfuncargnames(
- func, name=argname, is_method=unittest
- )
- self.unittest = unittest
+ # If the fixture is directly parametrized, a tuple of explicit IDs to
+ # assign to the parameter values, or a callable to generate an ID given
+ # a parameter value.
self.ids = ids
+ # The names requested by the fixtures.
+ self.argnames = getfuncargnames(func, name=argname, is_method=unittest)
+ # Whether the fixture was collected from a unittest TestCase class.
+ # Note that it really only makes sense to define autouse fixtures in
+ # unittest TestCases.
+ self.unittest = unittest
+ # If the fixture was executed, the current value of the fixture.
+ # Can change if the fixture is executed with different parameters.
self.cached_result: Optional[_FixtureCachedResult[FixtureValue]] = None
self._finalizers: List[Callable[[], object]] = []
@@ -1029,8 +1018,8 @@ class FixtureDef(Generic[FixtureValue]):
if exc:
raise exc
finally:
- hook = self._fixturemanager.session.gethookproxy(request.node.path)
- hook.pytest_fixture_post_finalizer(fixturedef=self, request=request)
+ ihook = request.node.ihook
+ ihook.pytest_fixture_post_finalizer(fixturedef=self, request=request)
# Even if finalization fails, we invalidate the cached fixture
# value and remove all finalizers because they may be bound methods
# which will keep instances alive.
@@ -1064,8 +1053,8 @@ class FixtureDef(Generic[FixtureValue]):
self.finish(request)
assert self.cached_result is None
- hook = self._fixturemanager.session.gethookproxy(request.node.path)
- result = hook.pytest_fixture_setup(fixturedef=self, request=request)
+ ihook = request.node.ihook
+ result = ihook.pytest_fixture_setup(fixturedef=self, request=request)
return result
def cache_key(self, request: SubRequest) -> object:
@@ -1130,18 +1119,8 @@ def pytest_fixture_setup(
def _ensure_immutable_ids(
- ids: Optional[
- Union[
- Iterable[Union[None, str, float, int, bool]],
- Callable[[Any], Optional[object]],
- ]
- ],
-) -> Optional[
- Union[
- Tuple[Union[None, str, float, int, bool], ...],
- Callable[[Any], Optional[object]],
- ]
-]:
+ ids: Optional[Union[Sequence[Optional[object]], Callable[[Any], Optional[object]]]]
+) -> Optional[Union[Tuple[Optional[object], ...], Callable[[Any], Optional[object]]]]:
if ids is None:
return None
if callable(ids):
@@ -1185,9 +1164,8 @@ class FixtureFunctionMarker:
scope: "Union[_ScopeName, Callable[[str, Config], _ScopeName]]"
params: Optional[Tuple[object, ...]] = attr.ib(converter=_params_converter)
autouse: bool = False
- ids: Union[
- Tuple[Union[None, str, float, int, bool], ...],
- Callable[[Any], Optional[object]],
+ ids: Optional[
+ Union[Tuple[Optional[object], ...], Callable[[Any], Optional[object]]]
] = attr.ib(
default=None,
converter=_ensure_immutable_ids,
@@ -1228,10 +1206,7 @@ def fixture(
params: Optional[Iterable[object]] = ...,
autouse: bool = ...,
ids: Optional[
- Union[
- Iterable[Union[None, str, float, int, bool]],
- Callable[[Any], Optional[object]],
- ]
+ Union[Sequence[Optional[object]], Callable[[Any], Optional[object]]]
] = ...,
name: Optional[str] = ...,
) -> FixtureFunction:
@@ -1246,10 +1221,7 @@ def fixture(
params: Optional[Iterable[object]] = ...,
autouse: bool = ...,
ids: Optional[
- Union[
- Iterable[Union[None, str, float, int, bool]],
- Callable[[Any], Optional[object]],
- ]
+ Union[Sequence[Optional[object]], Callable[[Any], Optional[object]]]
] = ...,
name: Optional[str] = None,
) -> FixtureFunctionMarker:
@@ -1263,10 +1235,7 @@ def fixture(
params: Optional[Iterable[object]] = None,
autouse: bool = False,
ids: Optional[
- Union[
- Iterable[Union[None, str, float, int, bool]],
- Callable[[Any], Optional[object]],
- ]
+ Union[Sequence[Optional[object]], Callable[[Any], Optional[object]]]
] = None,
name: Optional[str] = None,
) -> Union[FixtureFunctionMarker, FixtureFunction]:
@@ -1308,7 +1277,7 @@ def fixture(
the fixture.
:param ids:
- List of string ids each corresponding to the params so that they are
+ Sequence of ids each corresponding to the params so that they are
part of the test id. If no ids are provided they will be generated
automatically from the params.
diff --git a/contrib/python/pytest/py3/_pytest/hookspec.py b/contrib/python/pytest/py3/_pytest/hookspec.py
index 79251315d8..a03c0e9ab7 100644
--- a/contrib/python/pytest/py3/_pytest/hookspec.py
+++ b/contrib/python/pytest/py3/_pytest/hookspec.py
@@ -13,7 +13,6 @@ from typing import Union
from pluggy import HookspecMarker
-from _pytest.deprecated import WARNING_CAPTURED_HOOK
from _pytest.deprecated import WARNING_CMDLINE_PREPARSE_HOOK
if TYPE_CHECKING:
@@ -34,10 +33,10 @@ if TYPE_CHECKING:
from _pytest.nodes import Collector
from _pytest.nodes import Item
from _pytest.outcomes import Exit
+ from _pytest.python import Class
from _pytest.python import Function
from _pytest.python import Metafunc
from _pytest.python import Module
- from _pytest.python import PyCollector
from _pytest.reports import CollectReport
from _pytest.reports import TestReport
from _pytest.runner import CallInfo
@@ -360,7 +359,7 @@ def pytest_pycollect_makemodule(
@hookspec(firstresult=True)
def pytest_pycollect_makeitem(
- collector: "PyCollector", name: str, obj: object
+ collector: Union["Module", "Class"], name: str, obj: object
) -> Union[None, "Item", "Collector", List[Union["Item", "Collector"]]]:
"""Return a custom item/collector for a Python object in a module, or None.
@@ -777,41 +776,6 @@ def pytest_terminal_summary(
"""
-@hookspec(historic=True, warn_on_impl=WARNING_CAPTURED_HOOK)
-def pytest_warning_captured(
- warning_message: "warnings.WarningMessage",
- when: "Literal['config', 'collect', 'runtest']",
- item: Optional["Item"],
- location: Optional[Tuple[str, int, str]],
-) -> None:
- """(**Deprecated**) Process a warning captured by the internal pytest warnings plugin.
-
- .. deprecated:: 6.0
-
- This hook is considered deprecated and will be removed in a future pytest version.
- Use :func:`pytest_warning_recorded` instead.
-
- :param warnings.WarningMessage warning_message:
- The captured warning. This is the same object produced by :py:func:`warnings.catch_warnings`, and contains
- the same attributes as the parameters of :py:func:`warnings.showwarning`.
-
- :param str when:
- Indicates when the warning was captured. Possible values:
-
- * ``"config"``: during pytest configuration/initialization stage.
- * ``"collect"``: during test collection.
- * ``"runtest"``: during test execution.
-
- :param pytest.Item|None item:
- The item being executed if ``when`` is ``"runtest"``, otherwise ``None``.
-
- :param tuple location:
- When available, holds information about the execution context of the captured
- warning (filename, linenumber, function). ``function`` evaluates to <module>
- when the execution context is at the module level.
- """
-
-
@hookspec(historic=True)
def pytest_warning_recorded(
warning_message: "warnings.WarningMessage",
diff --git a/contrib/python/pytest/py3/_pytest/junitxml.py b/contrib/python/pytest/py3/_pytest/junitxml.py
index 4af5fbab0c..1b9e3bfeca 100644
--- a/contrib/python/pytest/py3/_pytest/junitxml.py
+++ b/contrib/python/pytest/py3/_pytest/junitxml.py
@@ -92,7 +92,7 @@ class _NodeReporter:
self.xml = xml
self.add_stats = self.xml.add_stats
self.family = self.xml.family
- self.duration = 0
+ self.duration = 0.0
self.properties: List[Tuple[str, str]] = []
self.nodes: List[ET.Element] = []
self.attrs: Dict[str, str] = {}
diff --git a/contrib/python/pytest/py3/_pytest/logging.py b/contrib/python/pytest/py3/_pytest/logging.py
index 31ad830107..0163554bae 100644
--- a/contrib/python/pytest/py3/_pytest/logging.py
+++ b/contrib/python/pytest/py3/_pytest/logging.py
@@ -1,9 +1,10 @@
"""Access and control log capturing."""
+import io
import logging
import os
import re
-import sys
from contextlib import contextmanager
+from contextlib import nullcontext
from io import StringIO
from pathlib import Path
from typing import AbstractSet
@@ -13,6 +14,7 @@ from typing import List
from typing import Mapping
from typing import Optional
from typing import Tuple
+from typing import TYPE_CHECKING
from typing import TypeVar
from typing import Union
@@ -20,7 +22,6 @@ from _pytest import nodes
from _pytest._io import TerminalWriter
from _pytest.capture import CaptureManager
from _pytest.compat import final
-from _pytest.compat import nullcontext
from _pytest.config import _strtobool
from _pytest.config import Config
from _pytest.config import create_terminal_writer
@@ -34,6 +35,11 @@ from _pytest.main import Session
from _pytest.stash import StashKey
from _pytest.terminal import TerminalReporter
+if TYPE_CHECKING:
+ logging_StreamHandler = logging.StreamHandler[StringIO]
+else:
+ logging_StreamHandler = logging.StreamHandler
+
DEFAULT_LOG_FORMAT = "%(levelname)-8s %(name)s:%(filename)s:%(lineno)d %(message)s"
DEFAULT_LOG_DATE_FORMAT = "%H:%M:%S"
@@ -322,11 +328,9 @@ class catching_logs:
root_logger.removeHandler(self.handler)
-class LogCaptureHandler(logging.StreamHandler):
+class LogCaptureHandler(logging_StreamHandler):
"""A logging handler that stores log records and the log text."""
- stream: StringIO
-
def __init__(self) -> None:
"""Create a new log handler."""
super().__init__(StringIO())
@@ -621,20 +625,11 @@ class LoggingPlugin:
if not fpath.parent.exists():
fpath.parent.mkdir(exist_ok=True, parents=True)
- stream = fpath.open(mode="w", encoding="UTF-8")
- if sys.version_info >= (3, 7):
- old_stream = self.log_file_handler.setStream(stream)
- else:
- old_stream = self.log_file_handler.stream
- self.log_file_handler.acquire()
- try:
- self.log_file_handler.flush()
- self.log_file_handler.stream = stream
- finally:
- self.log_file_handler.release()
+ # https://github.com/python/mypy/issues/11193
+ stream: io.TextIOWrapper = fpath.open(mode="w", encoding="UTF-8") # type: ignore[assignment]
+ old_stream = self.log_file_handler.setStream(stream)
if old_stream:
- # https://github.com/python/typeshed/pull/5663
- old_stream.close() # type:ignore[attr-defined]
+ old_stream.close()
def _log_cli_enabled(self):
"""Return whether live logging is enabled."""
@@ -758,7 +753,7 @@ class _FileHandler(logging.FileHandler):
pass
-class _LiveLoggingStreamHandler(logging.StreamHandler):
+class _LiveLoggingStreamHandler(logging_StreamHandler):
"""A logging StreamHandler used by the live logging feature: it will
write a newline before the first log message in each test.
diff --git a/contrib/python/pytest/py3/_pytest/main.py b/contrib/python/pytest/py3/_pytest/main.py
index fea8179ca7..8f590754ae 100644
--- a/contrib/python/pytest/py3/_pytest/main.py
+++ b/contrib/python/pytest/py3/_pytest/main.py
@@ -689,9 +689,8 @@ class Session(nodes.FSCollector):
# No point in finding packages when collecting doctests.
if not self.config.getoption("doctestmodules", False):
pm = self.config.pluginmanager
- confcutdir = pm._confcutdir
for parent in (argpath, *argpath.parents):
- if confcutdir and parent in confcutdir.parents:
+ if not pm._is_in_confcutdir(argpath):
break
if parent.is_dir():
diff --git a/contrib/python/pytest/py3/_pytest/mark/__init__.py b/contrib/python/pytest/py3/_pytest/mark/__init__.py
index 7e082f2e6e..11e6e34d73 100644
--- a/contrib/python/pytest/py3/_pytest/mark/__init__.py
+++ b/contrib/python/pytest/py3/_pytest/mark/__init__.py
@@ -1,5 +1,4 @@
"""Generic mechanism for marking and selecting python functions."""
-import warnings
from typing import AbstractSet
from typing import Collection
from typing import List
@@ -23,8 +22,6 @@ from _pytest.config import ExitCode
from _pytest.config import hookimpl
from _pytest.config import UsageError
from _pytest.config.argparsing import Parser
-from _pytest.deprecated import MINUS_K_COLON
-from _pytest.deprecated import MINUS_K_DASH
from _pytest.stash import StashKey
if TYPE_CHECKING:
@@ -189,27 +186,14 @@ def deselect_by_keyword(items: "List[Item]", config: Config) -> None:
if not keywordexpr:
return
- if keywordexpr.startswith("-"):
- # To be removed in pytest 8.0.0.
- warnings.warn(MINUS_K_DASH, stacklevel=2)
- keywordexpr = "not " + keywordexpr[1:]
- selectuntil = False
- if keywordexpr[-1:] == ":":
- # To be removed in pytest 8.0.0.
- warnings.warn(MINUS_K_COLON, stacklevel=2)
- selectuntil = True
- keywordexpr = keywordexpr[:-1]
-
expr = _parse_expression(keywordexpr, "Wrong expression passed to '-k'")
remaining = []
deselected = []
for colitem in items:
- if keywordexpr and not expr.evaluate(KeywordMatcher.from_item(colitem)):
+ if not expr.evaluate(KeywordMatcher.from_item(colitem)):
deselected.append(colitem)
else:
- if selectuntil:
- keywordexpr = None
remaining.append(colitem)
if deselected:
diff --git a/contrib/python/pytest/py3/_pytest/mark/structures.py b/contrib/python/pytest/py3/_pytest/mark/structures.py
index 92a9ea7512..93d6778c4e 100644
--- a/contrib/python/pytest/py3/_pytest/mark/structures.py
+++ b/contrib/python/pytest/py3/_pytest/mark/structures.py
@@ -72,16 +72,11 @@ def get_empty_parameterset_mark(
return mark
-class ParameterSet(
- NamedTuple(
- "ParameterSet",
- [
- ("values", Sequence[Union[object, NotSetType]]),
- ("marks", Collection[Union["MarkDecorator", "Mark"]]),
- ("id", Optional[str]),
- ],
- )
-):
+class ParameterSet(NamedTuple):
+ values: Sequence[Union[object, NotSetType]]
+ marks: Collection[Union["MarkDecorator", "Mark"]]
+ id: Optional[str]
+
@classmethod
def param(
cls,
diff --git a/contrib/python/pytest/py3/_pytest/monkeypatch.py b/contrib/python/pytest/py3/_pytest/monkeypatch.py
index 31f95a95ab..91d590fb3d 100644
--- a/contrib/python/pytest/py3/_pytest/monkeypatch.py
+++ b/contrib/python/pytest/py3/_pytest/monkeypatch.py
@@ -55,7 +55,7 @@ def resolve(name: str) -> object:
parts = name.split(".")
used = parts.pop(0)
- found = __import__(used)
+ found: object = __import__(used)
for part in parts:
used += "." + part
try:
diff --git a/contrib/python/pytest/py3/_pytest/pathlib.py b/contrib/python/pytest/py3/_pytest/pathlib.py
index b44753e1a4..c5a411b596 100644
--- a/contrib/python/pytest/py3/_pytest/pathlib.py
+++ b/contrib/python/pytest/py3/_pytest/pathlib.py
@@ -539,6 +539,9 @@ def import_path(
ignore = os.environ.get("PY_IGNORE_IMPORTMISMATCH", "")
if ignore != "1":
module_file = mod.__file__
+ if module_file is None:
+ raise ImportPathMismatchError(module_name, module_file, path)
+
if module_file.endswith((".pyc", ".pyo")):
module_file = module_file[:-1]
if module_file.endswith(os.path.sep + "__init__.py"):
@@ -562,7 +565,6 @@ if sys.platform.startswith("win"):
def _is_same(f1: str, f2: str) -> bool:
return Path(f1) == Path(f2) or os.path.samefile(f1, f2)
-
else:
def _is_same(f1: str, f2: str) -> bool:
@@ -601,11 +603,20 @@ def insert_missing_modules(modules: Dict[str, ModuleType], module_name: str) ->
module_parts = module_name.split(".")
while module_name:
if module_name not in modules:
- module = ModuleType(
- module_name,
- doc="Empty module created by pytest's importmode=importlib.",
- )
- modules[module_name] = module
+ try:
+ # If sys.meta_path is empty, calling import_module will issue
+ # a warning and raise ModuleNotFoundError. To avoid the
+ # warning, we check sys.meta_path explicitly and raise the error
+ # ourselves to fall back to creating a dummy module.
+ if not sys.meta_path:
+ raise ModuleNotFoundError
+ importlib.import_module(module_name)
+ except ModuleNotFoundError:
+ module = ModuleType(
+ module_name,
+ doc="Empty module created by pytest's importmode=importlib.",
+ )
+ modules[module_name] = module
module_parts.pop(-1)
module_name = ".".join(module_parts)
diff --git a/contrib/python/pytest/py3/_pytest/pytester.py b/contrib/python/pytest/py3/_pytest/pytester.py
index 363a372744..8368f94412 100644
--- a/contrib/python/pytest/py3/_pytest/pytester.py
+++ b/contrib/python/pytest/py3/_pytest/pytester.py
@@ -128,7 +128,7 @@ class LsofFdLeakChecker:
stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL,
check=True,
- universal_newlines=True,
+ text=True,
).stdout
def isopen(line: str) -> bool:
@@ -477,7 +477,9 @@ def LineMatcher_fixture(request: FixtureRequest) -> Type["LineMatcher"]:
@fixture
-def pytester(request: FixtureRequest, tmp_path_factory: TempPathFactory) -> "Pytester":
+def pytester(
+ request: FixtureRequest, tmp_path_factory: TempPathFactory, monkeypatch: MonkeyPatch
+) -> "Pytester":
"""
Facilities to write tests/configuration files, execute pytest in isolation, and match
against expected output, perfect for black-box testing of pytest plugins.
@@ -488,7 +490,7 @@ def pytester(request: FixtureRequest, tmp_path_factory: TempPathFactory) -> "Pyt
It is particularly useful for testing plugins. It is similar to the :fixture:`tmp_path`
fixture but provides methods which aid in testing pytest itself.
"""
- return Pytester(request, tmp_path_factory, _ispytest=True)
+ return Pytester(request, tmp_path_factory, monkeypatch, _ispytest=True)
@fixture
@@ -683,6 +685,7 @@ class Pytester:
self,
request: FixtureRequest,
tmp_path_factory: TempPathFactory,
+ monkeypatch: MonkeyPatch,
*,
_ispytest: bool = False,
) -> None:
@@ -706,7 +709,7 @@ class Pytester:
self._method = self._request.config.getoption("--runpytest")
self._test_tmproot = tmp_path_factory.mktemp(f"tmp-{name}", numbered=True)
- self._monkeypatch = mp = MonkeyPatch()
+ self._monkeypatch = mp = monkeypatch
mp.setenv("PYTEST_DEBUG_TEMPROOT", str(self._test_tmproot))
# Ensure no unexpected caching via tox.
mp.delenv("TOX_ENV_DIR", raising=False)
@@ -738,7 +741,6 @@ class Pytester:
self._sys_modules_snapshot.restore()
self._sys_path_snapshot.restore()
self._cwd_snapshot.restore()
- self._monkeypatch.undo()
def __take_sys_modules_snapshot(self) -> SysModulesSnapshot:
# Some zope modules used by twisted-related tests keep internal state
diff --git a/contrib/python/pytest/py3/_pytest/python.py b/contrib/python/pytest/py3/_pytest/python.py
index eed95b65cc..2855880a4e 100644
--- a/contrib/python/pytest/py3/_pytest/python.py
+++ b/contrib/python/pytest/py3/_pytest/python.py
@@ -224,11 +224,15 @@ def pytest_pycollect_makemodule(module_path: Path, parent) -> "Module":
@hookimpl(trylast=True)
-def pytest_pycollect_makeitem(collector: "PyCollector", name: str, obj: object):
+def pytest_pycollect_makeitem(
+ collector: Union["Module", "Class"], name: str, obj: object
+) -> Union[None, nodes.Item, nodes.Collector, List[Union[nodes.Item, nodes.Collector]]]:
+ assert isinstance(collector, (Class, Module)), type(collector)
# Nothing was collected elsewhere, let's do it here.
if safe_isclass(obj):
if collector.istestclass(obj, name):
- return Class.from_parent(collector, name=name, obj=obj)
+ klass: Class = Class.from_parent(collector, name=name, obj=obj)
+ return klass
elif collector.istestfunction(obj, name):
# mock seems to store unbound methods (issue473), normalize it.
obj = getattr(obj, "__func__", obj)
@@ -247,15 +251,16 @@ def pytest_pycollect_makeitem(collector: "PyCollector", name: str, obj: object):
)
elif getattr(obj, "__test__", True):
if is_generator(obj):
- res = Function.from_parent(collector, name=name)
+ res: Function = Function.from_parent(collector, name=name)
reason = "yield tests were removed in pytest 4.0 - {name} will be ignored".format(
name=name
)
res.add_marker(MARK_GEN.xfail(run=False, reason=reason))
res.warn(PytestCollectionWarning(reason))
+ return res
else:
- res = list(collector._genfunctions(name, obj))
- return res
+ return list(collector._genfunctions(name, obj))
+ return None
class PyobjMixin(nodes.Node):
@@ -298,6 +303,9 @@ class PyobjMixin(nodes.Node):
# used to avoid Function marker duplication
if self._ALLOW_MARKERS:
self.own_markers.extend(get_unpacked_marks(self.obj))
+ # This assumes that `obj` is called before there is a chance
+ # to add custom keys to `self.keywords`, so no fear of overriding.
+ self.keywords.update((mark.name, mark) for mark in self.own_markers)
return obj
@obj.setter
@@ -335,6 +343,7 @@ class PyobjMixin(nodes.Node):
if isinstance(compat_co_firstlineno, int):
# nose compatibility
file_path = sys.modules[obj.__module__].__file__
+ assert file_path is not None
if file_path.endswith(".pyc"):
file_path = file_path[:-1]
path: Union["os.PathLike[str]", str] = file_path
@@ -419,7 +428,7 @@ class PyCollector(PyobjMixin, nodes.Collector):
for basecls in self.obj.__mro__:
dicts.append(basecls.__dict__)
- # In each class, nodes should be definition ordered. Since Python 3.6,
+ # In each class, nodes should be definition ordered.
# __dict__ is definition ordered.
seen: Set[str] = set()
dict_values: List[List[Union[nodes.Item, nodes.Collector]]] = []
@@ -896,11 +905,7 @@ class InstanceDummy:
only to ignore it; this dummy class keeps them working. This will be removed
in pytest 8."""
- pass
-
-# Note: module __getattr__ only works on Python>=3.7. Unfortunately
-# we can't provide this deprecation warning on Python 3.6.
def __getattr__(name: str) -> object:
if name == "Instance":
warnings.warn(INSTANCE_COLLECTOR, 2)
@@ -923,6 +928,172 @@ def hasnew(obj: object) -> bool:
@final
+@attr.s(frozen=True, auto_attribs=True, slots=True)
+class IdMaker:
+ """Make IDs for a parametrization."""
+
+ # The argnames of the parametrization.
+ argnames: Sequence[str]
+ # The ParameterSets of the parametrization.
+ parametersets: Sequence[ParameterSet]
+ # Optionally, a user-provided callable to make IDs for parameters in a
+ # ParameterSet.
+ idfn: Optional[Callable[[Any], Optional[object]]]
+ # Optionally, explicit IDs for ParameterSets by index.
+ ids: Optional[Sequence[Optional[object]]]
+ # Optionally, the pytest config.
+ # Used for controlling ASCII escaping, and for calling the
+ # :hook:`pytest_make_parametrize_id` hook.
+ config: Optional[Config]
+ # Optionally, the ID of the node being parametrized.
+ # Used only for clearer error messages.
+ nodeid: Optional[str]
+ # Optionally, the ID of the function being parametrized.
+ # Used only for clearer error messages.
+ func_name: Optional[str]
+
+ def make_unique_parameterset_ids(self) -> List[str]:
+ """Make a unique identifier for each ParameterSet, that may be used to
+ identify the parametrization in a node ID.
+
+ Format is <prm_1_token>-...-<prm_n_token>[counter], where prm_x_token is
+ - user-provided id, if given
+ - else an id derived from the value, applicable for certain types
+ - else <argname><parameterset index>
+ The counter suffix is appended only in case a string wouldn't be unique
+ otherwise.
+ """
+ resolved_ids = list(self._limit_ids(self._resolve_ids(), limit=500))
+ # All IDs must be unique!
+ if len(resolved_ids) != len(set(resolved_ids)):
+ # Record the number of occurrences of each ID.
+ id_counts = Counter(resolved_ids)
+ # Map the ID to its next suffix.
+ id_suffixes: Dict[str, int] = defaultdict(int)
+ # Suffix non-unique IDs to make them unique.
+ for index, id in enumerate(resolved_ids):
+ if id_counts[id] > 1:
+ resolved_ids[index] = f"{id}{id_suffixes[id]}"
+ id_suffixes[id] += 1
+ return resolved_ids
+
+ def _limit_ids(self, ids, limit=500):
+ prefix_count = {}
+ limit -= 6
+ assert limit > 0
+
+ for idval in ids:
+ if len(idval) > limit:
+ prefix = idval[:limit]
+ idx = prefix_count.get(prefix, -1) + 1
+ prefix_count[prefix] = idx
+ idval = "{}-{}".format(prefix, idx)
+ yield idval
+
+ def _resolve_ids(self) -> Iterable[str]:
+ """Resolve IDs for all ParameterSets (may contain duplicates)."""
+ for idx, parameterset in enumerate(self.parametersets):
+ if parameterset.id is not None:
+ # ID provided directly - pytest.param(..., id="...")
+ yield parameterset.id
+ elif self.ids and idx < len(self.ids) and self.ids[idx] is not None:
+ # ID provided in the IDs list - parametrize(..., ids=[...]).
+ yield self._idval_from_value_required(self.ids[idx], idx)
+ else:
+ # ID not provided - generate it.
+ yield "-".join(
+ self._idval(val, argname, idx)
+ for val, argname in zip(parameterset.values, self.argnames)
+ )
+
+ def _idval(self, val: object, argname: str, idx: int) -> str:
+ """Make an ID for a parameter in a ParameterSet."""
+ idval = self._idval_from_function(val, argname, idx)
+ if idval is not None:
+ return idval
+ idval = self._idval_from_hook(val, argname)
+ if idval is not None:
+ return idval
+ idval = self._idval_from_value(val)
+ if idval is not None:
+ return idval
+ return self._idval_from_argname(argname, idx)
+
+ def _idval_from_function(
+ self, val: object, argname: str, idx: int
+ ) -> Optional[str]:
+ """Try to make an ID for a parameter in a ParameterSet using the
+ user-provided id callable, if given."""
+ if self.idfn is None:
+ return None
+ try:
+ id = self.idfn(val)
+ except Exception as e:
+ prefix = f"{self.nodeid}: " if self.nodeid is not None else ""
+ msg = "error raised while trying to determine id of parameter '{}' at position {}"
+ msg = prefix + msg.format(argname, idx)
+ raise ValueError(msg) from e
+ if id is None:
+ return None
+ return self._idval_from_value(id)
+
+ def _idval_from_hook(self, val: object, argname: str) -> Optional[str]:
+ """Try to make an ID for a parameter in a ParameterSet by calling the
+ :hook:`pytest_make_parametrize_id` hook."""
+ if self.config:
+ id: Optional[str] = self.config.hook.pytest_make_parametrize_id(
+ config=self.config, val=val, argname=argname
+ )
+ return id
+ return None
+
+ def _idval_from_value(self, val: object) -> Optional[str]:
+ """Try to make an ID for a parameter in a ParameterSet from its value,
+ if the value type is supported."""
+ if isinstance(val, STRING_TYPES):
+ return _ascii_escaped_by_config(val, self.config)
+ elif val is None or isinstance(val, (float, int, bool, complex)):
+ return str(val)
+ elif isinstance(val, Pattern):
+ return ascii_escaped(val.pattern)
+ elif val is NOTSET:
+ # Fallback to default. Note that NOTSET is an enum.Enum.
+ pass
+ elif isinstance(val, enum.Enum):
+ return str(val)
+ elif isinstance(getattr(val, "__name__", None), str):
+ # Name of a class, function, module, etc.
+ name: str = getattr(val, "__name__")
+ return name
+ return None
+
+ def _idval_from_value_required(self, val: object, idx: int) -> str:
+ """Like _idval_from_value(), but fails if the type is not supported."""
+ id = self._idval_from_value(val)
+ if id is not None:
+ return id
+
+ # Fail.
+ if self.func_name is not None:
+ prefix = f"In {self.func_name}: "
+ elif self.nodeid is not None:
+ prefix = f"In {self.nodeid}: "
+ else:
+ prefix = ""
+ msg = (
+ f"{prefix}ids contains unsupported value {saferepr(val)} (type: {type(val)!r}) at index {idx}. "
+ "Supported types are: str, bytes, int, float, complex, bool, enum, regex or anything with a __name__."
+ )
+ fail(msg, pytrace=False)
+
+ @staticmethod
+ def _idval_from_argname(argname: str, idx: int) -> str:
+ """Make an ID for a parameter in a ParameterSet from the argument name
+ and the index of the ParameterSet."""
+ return str(argname) + str(idx)
+
+
+@final
@attr.s(frozen=True, slots=True, auto_attribs=True)
class CallSpec2:
"""A planned parameterized invocation of a test function.
@@ -1044,10 +1215,7 @@ class Metafunc:
argvalues: Iterable[Union[ParameterSet, Sequence[object], object]],
indirect: Union[bool, Sequence[str]] = False,
ids: Optional[
- Union[
- Iterable[Union[None, str, float, int, bool]],
- Callable[[Any], Optional[object]],
- ]
+ Union[Iterable[Optional[object]], Callable[[Any], Optional[object]]]
] = None,
scope: "Optional[_ScopeName]" = None,
*,
@@ -1114,7 +1282,7 @@ class Metafunc:
It will also override any fixture-function defined scope, allowing
to set a dynamic scope using test context or configuration.
"""
- argnames, parameters = ParameterSet._for_parametrize(
+ argnames, parametersets = ParameterSet._for_parametrize(
argnames,
argvalues,
self.function,
@@ -1146,8 +1314,8 @@ class Metafunc:
if generated_ids is not None:
ids = generated_ids
- ids = self._resolve_arg_ids(
- argnames, ids, parameters, nodeid=self.definition.nodeid
+ ids = self._resolve_parameter_set_ids(
+ argnames, ids, parametersets, nodeid=self.definition.nodeid
)
# Store used (possibly generated) ids with parametrize Marks.
@@ -1159,7 +1327,9 @@ class Metafunc:
# of all calls.
newcalls = []
for callspec in self._calls or [CallSpec2()]:
- for param_index, (param_id, param_set) in enumerate(zip(ids, parameters)):
+ for param_index, (param_id, param_set) in enumerate(
+ zip(ids, parametersets)
+ ):
newcallspec = callspec.setmulti(
valtypes=arg_values_types,
argnames=argnames,
@@ -1172,27 +1342,29 @@ class Metafunc:
newcalls.append(newcallspec)
self._calls = newcalls
- def _resolve_arg_ids(
+ def _resolve_parameter_set_ids(
self,
argnames: Sequence[str],
ids: Optional[
- Union[
- Iterable[Union[None, str, float, int, bool]],
- Callable[[Any], Optional[object]],
- ]
+ Union[Iterable[Optional[object]], Callable[[Any], Optional[object]]]
],
- parameters: Sequence[ParameterSet],
+ parametersets: Sequence[ParameterSet],
nodeid: str,
) -> List[str]:
- """Resolve the actual ids for the given argnames, based on the ``ids`` parameter given
- to ``parametrize``.
+ """Resolve the actual ids for the given parameter sets.
- :param List[str] argnames: List of argument names passed to ``parametrize()``.
- :param ids: The ids parameter of the parametrized call (see docs).
- :param List[ParameterSet] parameters: The list of parameter values, same size as ``argnames``.
- :param str str: The nodeid of the item that generated this parametrized call.
- :rtype: List[str]
- :returns: The list of ids for each argname given.
+ :param argnames:
+ Argument names passed to ``parametrize()``.
+ :param ids:
+ The `ids` parameter of the ``parametrize()`` call (see docs).
+ :param parametersets:
+ The parameter sets, each containing a set of values corresponding
+ to ``argnames``.
+ :param nodeid str:
+ The nodeid of the definition item that generated this
+ parametrization.
+ :returns:
+ List with ids for each parameter set given.
"""
if ids is None:
idfn = None
@@ -1202,15 +1374,24 @@ class Metafunc:
ids_ = None
else:
idfn = None
- ids_ = self._validate_ids(ids, parameters, self.function.__name__)
- return idmaker(argnames, parameters, idfn, ids_, self.config, nodeid=nodeid)
+ ids_ = self._validate_ids(ids, parametersets, self.function.__name__)
+ id_maker = IdMaker(
+ argnames,
+ parametersets,
+ idfn,
+ ids_,
+ self.config,
+ nodeid=nodeid,
+ func_name=self.function.__name__,
+ )
+ return id_maker.make_unique_parameterset_ids()
def _validate_ids(
self,
- ids: Iterable[Union[None, str, float, int, bool]],
- parameters: Sequence[ParameterSet],
+ ids: Iterable[Optional[object]],
+ parametersets: Sequence[ParameterSet],
func_name: str,
- ) -> List[Union[None, str]]:
+ ) -> List[Optional[object]]:
try:
num_ids = len(ids) # type: ignore[arg-type]
except TypeError:
@@ -1218,29 +1399,14 @@ class Metafunc:
iter(ids)
except TypeError as e:
raise TypeError("ids must be a callable or an iterable") from e
- num_ids = len(parameters)
+ num_ids = len(parametersets)
# num_ids == 0 is a special case: https://github.com/pytest-dev/pytest/issues/1849
- if num_ids != len(parameters) and num_ids != 0:
+ if num_ids != len(parametersets) and num_ids != 0:
msg = "In {}: {} parameter sets specified, with different number of ids: {}"
- fail(msg.format(func_name, len(parameters), num_ids), pytrace=False)
-
- new_ids = []
- for idx, id_value in enumerate(itertools.islice(ids, num_ids)):
- if id_value is None or isinstance(id_value, str):
- new_ids.append(id_value)
- elif isinstance(id_value, (float, int, bool)):
- new_ids.append(str(id_value))
- else:
- msg = ( # type: ignore[unreachable]
- "In {}: ids must be list of string/float/int/bool, "
- "found: {} (type: {!r}) at index {}"
- )
- fail(
- msg.format(func_name, saferepr(id_value), type(id_value), idx),
- pytrace=False,
- )
- return new_ids
+ fail(msg.format(func_name, len(parametersets), num_ids), pytrace=False)
+
+ return list(itertools.islice(ids, num_ids))
def _resolve_arg_value_types(
self,
@@ -1360,132 +1526,6 @@ def _ascii_escaped_by_config(val: Union[str, bytes], config: Optional[Config]) -
return val if escape_option else ascii_escaped(val) # type: ignore
-def _idval(
- val: object,
- argname: str,
- idx: int,
- idfn: Optional[Callable[[Any], Optional[object]]],
- nodeid: Optional[str],
- config: Optional[Config],
-) -> str:
- if idfn:
- try:
- generated_id = idfn(val)
- if generated_id is not None:
- val = generated_id
- except Exception as e:
- prefix = f"{nodeid}: " if nodeid is not None else ""
- msg = "error raised while trying to determine id of parameter '{}' at position {}"
- msg = prefix + msg.format(argname, idx)
- raise ValueError(msg) from e
- elif config:
- hook_id: Optional[str] = config.hook.pytest_make_parametrize_id(
- config=config, val=val, argname=argname
- )
- if hook_id:
- return hook_id
-
- if isinstance(val, STRING_TYPES):
- return _ascii_escaped_by_config(val, config)
- elif val is None or isinstance(val, (float, int, bool, complex)):
- return str(val)
- elif isinstance(val, Pattern):
- return ascii_escaped(val.pattern)
- elif val is NOTSET:
- # Fallback to default. Note that NOTSET is an enum.Enum.
- pass
- elif isinstance(val, enum.Enum):
- return str(val)
- elif isinstance(getattr(val, "__name__", None), str):
- # Name of a class, function, module, etc.
- name: str = getattr(val, "__name__")
- return name
- return str(argname) + str(idx)
-
-
-def limit_idval(limit):
- import functools
-
- names = {}
- limit -= 6
- assert limit > 0
-
- def decorator(func):
- @functools.wraps(func)
- def wrapper(*args, **kw):
- idval = func(*args, **kw)
- if len(idval) > limit:
- prefix = idval[:limit]
- # There might be same prefix for the different test cases - take item into account
- name = "{}-{}".format(kw.get('item', ''), prefix)
- idx = names.setdefault(name, -1) + 1
- names[name] = idx
- idval = "{}-{}".format(prefix, idx)
- return idval
-
- return wrapper
-
- return decorator
-
-
-# XXX limit testnames in the name of sanity and readability
-@limit_idval(limit=500)
-def _idvalset(
- idx: int,
- parameterset: ParameterSet,
- argnames: Iterable[str],
- idfn: Optional[Callable[[Any], Optional[object]]],
- ids: Optional[List[Union[None, str]]],
- nodeid: Optional[str],
- config: Optional[Config],
-) -> str:
- if parameterset.id is not None:
- return parameterset.id
- id = None if ids is None or idx >= len(ids) else ids[idx]
- if id is None:
- this_id = [
- _idval(val, argname, idx, idfn, nodeid=nodeid, config=config)
- for val, argname in zip(parameterset.values, argnames)
- ]
- return "-".join(this_id)
- else:
- return _ascii_escaped_by_config(id, config)
-
-
-def idmaker(
- argnames: Iterable[str],
- parametersets: Iterable[ParameterSet],
- idfn: Optional[Callable[[Any], Optional[object]]] = None,
- ids: Optional[List[Union[None, str]]] = None,
- config: Optional[Config] = None,
- nodeid: Optional[str] = None,
-) -> List[str]:
- resolved_ids = [
- _idvalset(
- valindex, parameterset, argnames, idfn, ids, config=config, nodeid=nodeid
- )
- for valindex, parameterset in enumerate(parametersets)
- ]
-
- # All IDs must be unique!
- unique_ids = set(resolved_ids)
- if len(unique_ids) != len(resolved_ids):
-
- # Record the number of occurrences of each test ID.
- test_id_counts = Counter(resolved_ids)
-
- # Map the test ID to its next suffix.
- test_id_suffixes: Dict[str, int] = defaultdict(int)
-
- # Suffix non-unique IDs to make them unique.
- for index, test_id in enumerate(resolved_ids):
- if test_id_counts[test_id] > 1:
- resolved_ids[index] = f"{test_id}{test_id_suffixes[test_id]}"
- test_id_suffixes[test_id] += 1
-
- return resolved_ids
-
-
def _pretty_fixture_path(func) -> str:
cwd = Path.cwd()
loc = Path(getlocation(func, str(cwd)))
@@ -1657,7 +1697,7 @@ class Function(PyobjMixin, nodes.Item):
config: Optional[Config] = None,
callspec: Optional[CallSpec2] = None,
callobj=NOTSET,
- keywords=None,
+ keywords: Optional[Mapping[str, Any]] = None,
session: Optional[Session] = None,
fixtureinfo: Optional[FuncFixtureInfo] = None,
originalname: Optional[str] = None,
@@ -1678,31 +1718,20 @@ class Function(PyobjMixin, nodes.Item):
# Note: when FunctionDefinition is introduced, we should change ``originalname``
# to a readonly property that returns FunctionDefinition.name.
- self.keywords.update(self.obj.__dict__)
self.own_markers.extend(get_unpacked_marks(self.obj))
if callspec:
self.callspec = callspec
- # this is total hostile and a mess
- # keywords are broken by design by now
- # this will be redeemed later
- for mark in callspec.marks:
- # feel free to cry, this was broken for years before
- # and keywords can't fix it per design
- self.keywords[mark.name] = mark
- self.own_markers.extend(normalize_mark_list(callspec.marks))
- if keywords:
- self.keywords.update(keywords)
+ self.own_markers.extend(callspec.marks)
# todo: this is a hell of a hack
# https://github.com/pytest-dev/pytest/issues/4569
-
- self.keywords.update(
- {
- mark.name: True
- for mark in self.iter_markers()
- if mark.name not in self.keywords
- }
- )
+ # Note: the order of the updates is important here; indicates what
+ # takes priority (ctor argument over function attributes over markers).
+ # Take own_markers only; NodeKeywords handles parent traversal on its own.
+ self.keywords.update((mark.name, mark) for mark in self.own_markers)
+ self.keywords.update(self.obj.__dict__)
+ if keywords:
+ self.keywords.update(keywords)
if fixtureinfo is None:
fixtureinfo = self.session._fixturemanager.getfixtureinfo(
diff --git a/contrib/python/pytest/py3/_pytest/python_api.py b/contrib/python/pytest/py3/_pytest/python_api.py
index cb72fde1e1..ea646811dd 100644
--- a/contrib/python/pytest/py3/_pytest/python_api.py
+++ b/contrib/python/pytest/py3/_pytest/python_api.py
@@ -1,5 +1,6 @@
import math
import pprint
+from collections.abc import Collection
from collections.abc import Sized
from decimal import Decimal
from numbers import Complex
@@ -8,7 +9,6 @@ from typing import Any
from typing import Callable
from typing import cast
from typing import Generic
-from typing import Iterable
from typing import List
from typing import Mapping
from typing import Optional
@@ -131,7 +131,6 @@ class ApproxBase:
# a numeric type. For this reason, the default is to do nothing. The
# classes that deal with sequences should reimplement this method to
# raise if there are any non-numeric elements in the sequence.
- pass
def _recursive_list_map(f, x):
@@ -307,12 +306,12 @@ class ApproxMapping(ApproxBase):
raise TypeError(msg.format(key, value, pprint.pformat(self.expected)))
-class ApproxSequencelike(ApproxBase):
+class ApproxSequenceLike(ApproxBase):
"""Perform approximate comparisons where the expected value is a sequence of numbers."""
def __repr__(self) -> str:
seq_type = type(self.expected)
- if seq_type not in (tuple, list, set):
+ if seq_type not in (tuple, list):
seq_type = list
return "approx({!r})".format(
seq_type(self._approx_scalar(x) for x in self.expected)
@@ -516,7 +515,7 @@ class ApproxDecimal(ApproxScalar):
def approx(expected, rel=None, abs=None, nan_ok: bool = False) -> ApproxBase:
- """Assert that two numbers (or two sets of numbers) are equal to each other
+ """Assert that two numbers (or two ordered sequences of numbers) are equal to each other
within some tolerance.
Due to the :std:doc:`tutorial/floatingpoint`, numbers that we
@@ -548,16 +547,11 @@ def approx(expected, rel=None, abs=None, nan_ok: bool = False) -> ApproxBase:
>>> 0.1 + 0.2 == approx(0.3)
True
- The same syntax also works for sequences of numbers::
+ The same syntax also works for ordered sequences of numbers::
>>> (0.1 + 0.2, 0.2 + 0.4) == approx((0.3, 0.6))
True
- Dictionary *values*::
-
- >>> {'a': 0.1 + 0.2, 'b': 0.2 + 0.4} == approx({'a': 0.3, 'b': 0.6})
- True
-
``numpy`` arrays::
>>> import numpy as np # doctest: +SKIP
@@ -570,6 +564,20 @@ def approx(expected, rel=None, abs=None, nan_ok: bool = False) -> ApproxBase:
>>> np.array([0.1, 0.2]) + np.array([0.2, 0.1]) == approx(0.3) # doctest: +SKIP
True
+ Only ordered sequences are supported, because ``approx`` needs
+ to infer the relative position of the sequences without ambiguity. This means
+ ``sets`` and other unordered sequences are not supported.
+
+ Finally, dictionary *values* can also be compared::
+
+ >>> {'a': 0.1 + 0.2, 'b': 0.2 + 0.4} == approx({'a': 0.3, 'b': 0.6})
+ True
+
+ The comparision will be true if both mappings have the same keys and their
+ respective values match the expected tolerances.
+
+ **Tolerances**
+
By default, ``approx`` considers numbers within a relative tolerance of
``1e-6`` (i.e. one part in a million) of its expected value to be equal.
This treatment would lead to surprising results if the expected value was
@@ -709,12 +717,19 @@ def approx(expected, rel=None, abs=None, nan_ok: bool = False) -> ApproxBase:
expected = _as_numpy_array(expected)
cls = ApproxNumpy
elif (
- isinstance(expected, Iterable)
+ hasattr(expected, "__getitem__")
and isinstance(expected, Sized)
# Type ignored because the error is wrong -- not unreachable.
and not isinstance(expected, STRING_TYPES) # type: ignore[unreachable]
):
- cls = ApproxSequencelike
+ cls = ApproxSequenceLike
+ elif (
+ isinstance(expected, Collection)
+ # Type ignored because the error is wrong -- not unreachable.
+ and not isinstance(expected, STRING_TYPES) # type: ignore[unreachable]
+ ):
+ msg = f"pytest.approx() only supports ordered sequences, but got: {repr(expected)}"
+ raise TypeError(msg)
else:
cls = ApproxScalar
diff --git a/contrib/python/pytest/py3/_pytest/reports.py b/contrib/python/pytest/py3/_pytest/reports.py
index a68e68bc52..725fdf6173 100644
--- a/contrib/python/pytest/py3/_pytest/reports.py
+++ b/contrib/python/pytest/py3/_pytest/reports.py
@@ -7,6 +7,7 @@ from typing import Dict
from typing import Iterable
from typing import Iterator
from typing import List
+from typing import Mapping
from typing import Optional
from typing import Tuple
from typing import Type
@@ -254,7 +255,7 @@ class TestReport(BaseReport):
self,
nodeid: str,
location: Tuple[str, Optional[int], str],
- keywords,
+ keywords: Mapping[str, Any],
outcome: "Literal['passed', 'failed', 'skipped']",
longrepr: Union[
None, ExceptionInfo[BaseException], Tuple[str, int, str], str, TerminalRepr
diff --git a/contrib/python/pytest/py3/_pytest/runner.py b/contrib/python/pytest/py3/_pytest/runner.py
index e43dd2dc81..df6eecdb12 100644
--- a/contrib/python/pytest/py3/_pytest/runner.py
+++ b/contrib/python/pytest/py3/_pytest/runner.py
@@ -2,7 +2,6 @@
import bdb
import os
import sys
-import warnings
from typing import Callable
from typing import cast
from typing import Dict
@@ -28,7 +27,6 @@ from _pytest._code.code import TerminalRepr
from _pytest.compat import final
from _pytest.config.argparsing import Parser
from _pytest.deprecated import check_ispytest
-from _pytest.deprecated import UNITTEST_SKIP_DURING_COLLECTION
from _pytest.nodes import Collector
from _pytest.nodes import Item
from _pytest.nodes import Node
@@ -379,11 +377,6 @@ def pytest_make_collect_report(collector: Collector) -> CollectReport:
# Type ignored because unittest is loaded dynamically.
skip_exceptions.append(unittest.SkipTest) # type: ignore
if isinstance(call.excinfo.value, tuple(skip_exceptions)):
- if unittest is not None and isinstance(
- call.excinfo.value, unittest.SkipTest # type: ignore[attr-defined]
- ):
- warnings.warn(UNITTEST_SKIP_DURING_COLLECTION, stacklevel=2)
-
outcome = "skipped"
r_ = collector._repr_failure_py(call.excinfo, "line")
assert isinstance(r_, ExceptionChainRepr), repr(r_)
diff --git a/contrib/python/pytest/py3/_pytest/terminal.py b/contrib/python/pytest/py3/_pytest/terminal.py
index ccbd84d7d7..b4848c48ab 100644
--- a/contrib/python/pytest/py3/_pytest/terminal.py
+++ b/contrib/python/pytest/py3/_pytest/terminal.py
@@ -542,15 +542,21 @@ class TerminalReporter:
if not running_xdist:
self.write_ensure_prefix(line, word, **markup)
if rep.skipped or hasattr(report, "wasxfail"):
- available_width = (
- (self._tw.fullwidth - self._tw.width_of_current_line)
- - len(" [100%]")
- - 1
- )
reason = _get_raw_skip_reason(rep)
- reason_ = _format_trimmed(" ({})", reason, available_width)
- if reason and reason_ is not None:
- self._tw.write(reason_)
+ if self.config.option.verbose < 2:
+ available_width = (
+ (self._tw.fullwidth - self._tw.width_of_current_line)
+ - len(" [100%]")
+ - 1
+ )
+ formatted_reason = _format_trimmed(
+ " ({})", reason, available_width
+ )
+ else:
+ formatted_reason = f" ({reason})"
+
+ if reason and formatted_reason is not None:
+ self._tw.write(formatted_reason)
if self._show_progress_info:
self._write_progress_information_filling_space()
else:
@@ -657,7 +663,7 @@ class TerminalReporter:
errors = len(self.stats.get("error", []))
skipped = len(self.stats.get("skipped", []))
deselected = len(self.stats.get("deselected", []))
- selected = self._numcollected - errors - skipped - deselected
+ selected = self._numcollected - deselected
line = "collected " if final else "collecting "
line += (
str(self._numcollected) + " item" + ("" if self._numcollected == 1 else "s")
@@ -668,7 +674,7 @@ class TerminalReporter:
line += " / %d deselected" % deselected
if skipped:
line += " / %d skipped" % skipped
- if self._numcollected > selected > 0:
+ if self._numcollected > selected:
line += " / %d selected" % selected
if self.isatty:
self.rewrite(line, bold=True, erase=True)
diff --git a/contrib/python/pytest/py3/_pytest/unittest.py b/contrib/python/pytest/py3/_pytest/unittest.py
index 0315168b04..851e4943b2 100644
--- a/contrib/python/pytest/py3/_pytest/unittest.py
+++ b/contrib/python/pytest/py3/_pytest/unittest.py
@@ -27,7 +27,7 @@ from _pytest.outcomes import skip
from _pytest.outcomes import xfail
from _pytest.python import Class
from _pytest.python import Function
-from _pytest.python import PyCollector
+from _pytest.python import Module
from _pytest.runner import CallInfo
from _pytest.scope import Scope
@@ -42,7 +42,7 @@ if TYPE_CHECKING:
def pytest_pycollect_makeitem(
- collector: PyCollector, name: str, obj: object
+ collector: Union[Module, Class], name: str, obj: object
) -> Optional["UnitTestCase"]:
# Has unittest been imported and is obj a subclass of its TestCase?
try:
diff --git a/contrib/python/pytest/py3/_pytest/warning_types.py b/contrib/python/pytest/py3/_pytest/warning_types.py
index 2a97a31978..ac79bb53ac 100644
--- a/contrib/python/pytest/py3/_pytest/warning_types.py
+++ b/contrib/python/pytest/py3/_pytest/warning_types.py
@@ -49,13 +49,6 @@ class PytestDeprecationWarning(PytestWarning, DeprecationWarning):
@final
-class PytestRemovedIn7Warning(PytestDeprecationWarning):
- """Warning class for features that will be removed in pytest 7."""
-
- __module__ = "pytest"
-
-
-@final
class PytestRemovedIn8Warning(PytestDeprecationWarning):
"""Warning class for features that will be removed in pytest 8."""
diff --git a/contrib/python/pytest/py3/_pytest/warnings.py b/contrib/python/pytest/py3/_pytest/warnings.py
index c0c946cbde..4aaa944529 100644
--- a/contrib/python/pytest/py3/_pytest/warnings.py
+++ b/contrib/python/pytest/py3/_pytest/warnings.py
@@ -49,8 +49,6 @@ def catch_warnings_for_item(
warnings.filterwarnings("always", category=DeprecationWarning)
warnings.filterwarnings("always", category=PendingDeprecationWarning)
- warnings.filterwarnings("error", category=pytest.PytestRemovedIn7Warning)
-
apply_warning_filters(config_filters, cmdline_filters)
# apply filters from "filterwarnings" marks
@@ -63,14 +61,6 @@ def catch_warnings_for_item(
yield
for warning_message in log:
- ihook.pytest_warning_captured.call_historic(
- kwargs=dict(
- warning_message=warning_message,
- when=when,
- item=item,
- location=None,
- )
- )
ihook.pytest_warning_recorded.call_historic(
kwargs=dict(
warning_message=warning_message,
@@ -91,6 +81,23 @@ def warning_record_to_str(warning_message: warnings.WarningMessage) -> str:
warning_message.lineno,
warning_message.line,
)
+ if warning_message.source is not None:
+ try:
+ import tracemalloc
+ except ImportError:
+ pass
+ else:
+ tb = tracemalloc.get_object_traceback(warning_message.source)
+ if tb is not None:
+ formatted_tb = "\n".join(tb.format())
+ # Use a leading new line to better separate the (large) output
+ # from the traceback to the previous warning text.
+ msg += f"\nObject allocated at:\n{formatted_tb}"
+ else:
+ # No need for a leading new line.
+ url = "https://docs.pytest.org/en/stable/how-to/capture-warnings.html#resource-warnings"
+ msg += "Enable tracemalloc to get traceback where the object was allocated.\n"
+ msg += f"See {url} for more info."
return msg
diff --git a/contrib/python/pytest/py3/patches/03-limit-id.patch b/contrib/python/pytest/py3/patches/03-limit-id.patch
index 16abfefa35..e9dccc7e12 100644
--- a/contrib/python/pytest/py3/patches/03-limit-id.patch
+++ b/contrib/python/pytest/py3/patches/03-limit-id.patch
@@ -1,36 +1,31 @@
--- contrib/python/pytest/py3/_pytest/python.py (index)
+++ contrib/python/pytest/py3/_pytest/python.py (working tree)
-@@ -1403,6 +1403,33 @@ def _idval(val, argname, idx, idfn, item, config):
- return str(argname) + str(idx)
+@@ -963,7 +963,7 @@ class IdMaker:
+ The counter suffix is appended only in case a string wouldn't be unique
+ otherwise.
+ """
+- resolved_ids = list(self._resolve_ids())
++ resolved_ids = list(self._limit_ids(self._resolve_ids(), limit=500))
+ # All IDs must be unique!
+ if len(resolved_ids) != len(set(resolved_ids)):
+ # Record the number of occurrences of each ID.
+@@ -977,6 +977,19 @@ class IdMaker:
+ id_suffixes[id] += 1
+ return resolved_ids
-
-+def limit_idval(limit):
-+ import functools
-+
-+ names = {}
-+ limit -= 6
-+ assert limit > 0
++ def _limit_ids(self, ids, limit=500):
++ prefix_count = {}
++ limit -= 6
++ assert limit > 0
+
-+ def decorator(func):
-+ @functools.wraps(func)
-+ def wrapper(*args, **kw):
-+ idval = func(*args, **kw)
++ for idval in ids:
+ if len(idval) > limit:
+ prefix = idval[:limit]
-+ # There might be same prefix for the different test cases - take item into account
-+ name = "{}-{}".format(kw.get('item', ''), prefix)
-+ idx = names.setdefault(name, -1) + 1
-+ names[name] = idx
++ idx = prefix_count.get(prefix, -1) + 1
++ prefix_count[prefix] = idx
+ idval = "{}-{}".format(prefix, idx)
-+ return idval
-+
-+ return wrapper
-+
-+ return decorator
-+
++ yield idval
+
-+# XXX limit testnames in the name of sanity and readability
-+@limit_idval(limit=500)
- def _idvalset(
- idx: int,
- parameterset: ParameterSet,
+ def _resolve_ids(self) -> Iterable[str]:
+ """Resolve IDs for all ParameterSets (may contain duplicates)."""
+ for idx, parameterset in enumerate(self.parametersets):
diff --git a/contrib/python/pytest/py3/patches/04-support-cyrillic-id.patch b/contrib/python/pytest/py3/patches/04-support-cyrillic-id.patch
index 5cb6a61d61..dccd656020 100644
--- a/contrib/python/pytest/py3/patches/04-support-cyrillic-id.patch
+++ b/contrib/python/pytest/py3/patches/04-support-cyrillic-id.patch
@@ -1,6 +1,6 @@
--- contrib/python/pytest/py3/_pytest/compat.py (index)
+++ contrib/python/pytest/py3/_pytest/compat.py (working tree)
-@@ -248,7 +248,7 @@ if _PY3:
+@@ -236,7 +236,7 @@ if _PY3:
if isinstance(val, bytes):
ret = _bytes_to_ascii(val)
else:
diff --git a/contrib/python/pytest/py3/patches/06-support-ya-markers.patch b/contrib/python/pytest/py3/patches/06-support-ya-markers.patch
index c71f914537..c5ba0d7a90 100644
--- a/contrib/python/pytest/py3/patches/06-support-ya-markers.patch
+++ b/contrib/python/pytest/py3/patches/06-support-ya-markers.patch
@@ -1,6 +1,6 @@
--- contrib/python/pytest/py3/_pytest/mark/structures.py (index)
+++ contrib/python/pytest/py3/_pytest/mark/structures.py (working tree)
-@@ -506,7 +506,10 @@ class MarkGenerator(object):
+@@ -501,7 +501,10 @@ class MarkGenerator(object):
# example lines: "skipif(condition): skip the given test if..."
# or "hypothesis: tests which use Hypothesis", so to get the
# marker name we split on both `:` and `(`.
diff --git a/contrib/python/pytest/py3/patches/07-disable-translate-non-printable.patch b/contrib/python/pytest/py3/patches/07-disable-translate-non-printable.patch
index 72fe70220b..b6f78704f5 100644
--- a/contrib/python/pytest/py3/patches/07-disable-translate-non-printable.patch
+++ b/contrib/python/pytest/py3/patches/07-disable-translate-non-printable.patch
@@ -1,6 +1,6 @@
--- contrib/python/pytest/py3/_pytest/compat.py (index)
+++ contrib/python/pytest/py3/_pytest/compat.py (working tree)
-@@ -249,7 +249,7 @@ if _PY3:
+@@ -237,7 +237,7 @@ if _PY3:
ret = _bytes_to_ascii(val)
else:
ret = val
diff --git a/contrib/python/pytest/py3/pytest/__init__.py b/contrib/python/pytest/py3/pytest/__init__.py
index 6050fd1124..777d377406 100644
--- a/contrib/python/pytest/py3/pytest/__init__.py
+++ b/contrib/python/pytest/py3/pytest/__init__.py
@@ -1,6 +1,5 @@
# PYTHON_ARGCOMPLETE_OK
"""pytest: unit and functional testing with Python."""
-from . import collect
from _pytest import __version__
from _pytest import version_tuple
from _pytest._code import ExceptionInfo
@@ -19,7 +18,6 @@ from _pytest.config import UsageError
from _pytest.config.argparsing import OptionGroup
from _pytest.config.argparsing import Parser
from _pytest.debugging import pytestPDB as __pytestPDB
-from _pytest.fixtures import _fillfuncargs
from _pytest.fixtures import fixture
from _pytest.fixtures import FixtureLookupError
from _pytest.fixtures import FixtureRequest
@@ -70,7 +68,6 @@ from _pytest.warning_types import PytestCollectionWarning
from _pytest.warning_types import PytestConfigWarning
from _pytest.warning_types import PytestDeprecationWarning
from _pytest.warning_types import PytestExperimentalApiWarning
-from _pytest.warning_types import PytestRemovedIn7Warning
from _pytest.warning_types import PytestRemovedIn8Warning
from _pytest.warning_types import PytestUnhandledCoroutineWarning
from _pytest.warning_types import PytestUnhandledThreadExceptionWarning
@@ -83,14 +80,12 @@ set_trace = __pytestPDB.set_trace
__all__ = [
"__version__",
- "_fillfuncargs",
"approx",
"Cache",
"CallInfo",
"CaptureFixture",
"Class",
"cmdline",
- "collect",
"Collector",
"CollectReport",
"Config",
@@ -131,7 +126,6 @@ __all__ = [
"PytestConfigWarning",
"PytestDeprecationWarning",
"PytestExperimentalApiWarning",
- "PytestRemovedIn7Warning",
"PytestRemovedIn8Warning",
"Pytester",
"PytestPluginManager",
diff --git a/contrib/python/pytest/py3/pytest/collect.py b/contrib/python/pytest/py3/pytest/collect.py
deleted file mode 100644
index 4b2b581806..0000000000
--- a/contrib/python/pytest/py3/pytest/collect.py
+++ /dev/null
@@ -1,38 +0,0 @@
-import sys
-import warnings
-from types import ModuleType
-from typing import Any
-from typing import List
-
-import pytest
-from _pytest.deprecated import PYTEST_COLLECT_MODULE
-
-COLLECT_FAKEMODULE_ATTRIBUTES = [
- "Collector",
- "Module",
- "Function",
- "Session",
- "Item",
- "Class",
- "File",
- "_fillfuncargs",
-]
-
-
-class FakeCollectModule(ModuleType):
- def __init__(self) -> None:
- super().__init__("pytest.collect")
- self.__all__ = list(COLLECT_FAKEMODULE_ATTRIBUTES)
- self.__pytest = pytest
-
- def __dir__(self) -> List[str]:
- return dir(super()) + self.__all__
-
- def __getattr__(self, name: str) -> Any:
- if name not in self.__all__:
- raise AttributeError(name)
- warnings.warn(PYTEST_COLLECT_MODULE.format(name=name), stacklevel=2)
- return getattr(pytest, name)
-
-
-sys.modules["pytest.collect"] = FakeCollectModule()
diff --git a/contrib/python/pytest/py3/ya.make b/contrib/python/pytest/py3/ya.make
index 7382ac8c22..143adf67e0 100644
--- a/contrib/python/pytest/py3/ya.make
+++ b/contrib/python/pytest/py3/ya.make
@@ -1,8 +1,10 @@
+# Generated by devtools/yamaker (pypi).
+
PY3_LIBRARY()
OWNER(dmitko g:python-contrib)
-VERSION(7.0.1)
+VERSION(7.1.0)
LICENSE(MIT)
@@ -24,7 +26,7 @@ ENDIF()
NO_LINT()
NO_CHECK_IMPORTS(
- __tests__.* # all test modules get imported when tests are run
+ __tests__.*
_pytest.*
)
@@ -97,7 +99,6 @@ PY_SRCS(
_pytest/warnings.py
pytest/__init__.py
pytest/__main__.py
- pytest/collect.py
)
RESOURCE_FILES(