aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/python/pytest/py3/_pytest/mark/structures.py
diff options
context:
space:
mode:
authorarcadia-devtools <arcadia-devtools@yandex-team.ru>2022-02-14 00:49:36 +0300
committerarcadia-devtools <arcadia-devtools@yandex-team.ru>2022-02-14 00:49:36 +0300
commit82cfd1b7cab2d843cdf5467d9737f72597a493bd (patch)
tree1dfdcfe81a1a6b193ceacc2a828c521b657a339b /contrib/python/pytest/py3/_pytest/mark/structures.py
parent3df7211d3e3691f8e33b0a1fb1764fe810d59302 (diff)
downloadydb-82cfd1b7cab2d843cdf5467d9737f72597a493bd.tar.gz
intermediate changes
ref:68b1302de4b5da30b6bdf02193f7a2604d8b5cf8
Diffstat (limited to 'contrib/python/pytest/py3/_pytest/mark/structures.py')
-rw-r--r--contrib/python/pytest/py3/_pytest/mark/structures.py178
1 files changed, 107 insertions, 71 deletions
diff --git a/contrib/python/pytest/py3/_pytest/mark/structures.py b/contrib/python/pytest/py3/_pytest/mark/structures.py
index f5736a4c1c..92a9ea7512 100644
--- a/contrib/python/pytest/py3/_pytest/mark/structures.py
+++ b/contrib/python/pytest/py3/_pytest/mark/structures.py
@@ -28,6 +28,7 @@ from ..compat import final
from ..compat import NOTSET
from ..compat import NotSetType
from _pytest.config import Config
+from _pytest.deprecated import check_ispytest
from _pytest.outcomes import fail
from _pytest.warning_types import PytestUnknownMarkWarning
@@ -39,10 +40,7 @@ EMPTY_PARAMETERSET_OPTION = "empty_parameter_set_mark"
def istestfunc(func) -> bool:
- return (
- hasattr(func, "__call__")
- and getattr(func, "__name__", "<lambda>") != "<lambda>"
- )
+ return callable(func) and getattr(func, "__name__", "<lambda>") != "<lambda>"
def get_empty_parameterset_mark(
@@ -98,9 +96,7 @@ class ParameterSet(
if id is not None:
if not isinstance(id, str):
- raise TypeError(
- "Expected id to be a string, got {}: {!r}".format(type(id), id)
- )
+ raise TypeError(f"Expected id to be a string, got {type(id)}: {id!r}")
id = ascii_escaped(id)
return cls(values, marks, id)
@@ -200,21 +196,38 @@ class ParameterSet(
@final
-@attr.s(frozen=True)
+@attr.s(frozen=True, init=False, auto_attribs=True)
class Mark:
#: Name of the mark.
- name = attr.ib(type=str)
+ name: str
#: Positional arguments of the mark decorator.
- args = attr.ib(type=Tuple[Any, ...])
+ args: Tuple[Any, ...]
#: Keyword arguments of the mark decorator.
- kwargs = attr.ib(type=Mapping[str, Any])
+ kwargs: Mapping[str, Any]
#: Source Mark for ids with parametrize Marks.
- _param_ids_from = attr.ib(type=Optional["Mark"], default=None, repr=False)
+ _param_ids_from: Optional["Mark"] = attr.ib(default=None, repr=False)
#: Resolved/generated ids with parametrize Marks.
- _param_ids_generated = attr.ib(
- type=Optional[Sequence[str]], default=None, repr=False
- )
+ _param_ids_generated: Optional[Sequence[str]] = attr.ib(default=None, repr=False)
+
+ def __init__(
+ self,
+ name: str,
+ args: Tuple[Any, ...],
+ kwargs: Mapping[str, Any],
+ param_ids_from: Optional["Mark"] = None,
+ param_ids_generated: Optional[Sequence[str]] = None,
+ *,
+ _ispytest: bool = False,
+ ) -> None:
+ """:meta private:"""
+ check_ispytest(_ispytest)
+ # Weirdness to bypass frozen=True.
+ object.__setattr__(self, "name", name)
+ object.__setattr__(self, "args", args)
+ object.__setattr__(self, "kwargs", kwargs)
+ object.__setattr__(self, "_param_ids_from", param_ids_from)
+ object.__setattr__(self, "_param_ids_generated", param_ids_generated)
def _has_param_ids(self) -> bool:
return "ids" in self.kwargs or len(self.args) >= 4
@@ -243,20 +256,21 @@ class Mark:
self.args + other.args,
dict(self.kwargs, **other.kwargs),
param_ids_from=param_ids_from,
+ _ispytest=True,
)
# A generic parameter designating an object to which a Mark may
# be applied -- a test function (callable) or class.
# Note: a lambda is not allowed, but this can't be represented.
-_Markable = TypeVar("_Markable", bound=Union[Callable[..., object], type])
+Markable = TypeVar("Markable", bound=Union[Callable[..., object], type])
-@attr.s
+@attr.s(init=False, auto_attribs=True)
class MarkDecorator:
"""A decorator for applying a mark on test functions and classes.
- MarkDecorators are created with ``pytest.mark``::
+ ``MarkDecorators`` are created with ``pytest.mark``::
mark1 = pytest.mark.NAME # Simple MarkDecorator
mark2 = pytest.mark.NAME(name1=value) # Parametrized MarkDecorator
@@ -267,7 +281,7 @@ class MarkDecorator:
def test_function():
pass
- When a MarkDecorator is called it does the following:
+ When a ``MarkDecorator`` is called, it does the following:
1. If called with a single class as its only positional argument and no
additional keyword arguments, it attaches the mark to the class so it
@@ -276,19 +290,24 @@ class MarkDecorator:
2. If called with a single function as its only positional argument and
no additional keyword arguments, it attaches the mark to the function,
containing all the arguments already stored internally in the
- MarkDecorator.
+ ``MarkDecorator``.
- 3. When called in any other case, it returns a new MarkDecorator instance
- with the original MarkDecorator's content updated with the arguments
- passed to this call.
+ 3. When called in any other case, it returns a new ``MarkDecorator``
+ instance with the original ``MarkDecorator``'s content updated with
+ the arguments passed to this call.
- Note: The rules above prevent MarkDecorators from storing only a single
- function or class reference as their positional argument with no
+ Note: The rules above prevent a ``MarkDecorator`` from storing only a
+ single function or class reference as its positional argument with no
additional keyword or positional arguments. You can work around this by
using `with_args()`.
"""
- mark = attr.ib(type=Mark, validator=attr.validators.instance_of(Mark))
+ mark: Mark
+
+ def __init__(self, mark: Mark, *, _ispytest: bool = False) -> None:
+ """:meta private:"""
+ check_ispytest(_ispytest)
+ self.mark = mark
@property
def name(self) -> str:
@@ -307,27 +326,23 @@ class MarkDecorator:
@property
def markname(self) -> str:
+ """:meta private:"""
return self.name # for backward-compat (2.4.1 had this attr)
- def __repr__(self) -> str:
- return f"<MarkDecorator {self.mark!r}>"
-
def with_args(self, *args: object, **kwargs: object) -> "MarkDecorator":
"""Return a MarkDecorator with extra arguments added.
Unlike calling the MarkDecorator, with_args() can be used even
if the sole argument is a callable/class.
-
- :rtype: MarkDecorator
"""
- mark = Mark(self.name, args, kwargs)
- return self.__class__(self.mark.combined_with(mark))
+ mark = Mark(self.name, args, kwargs, _ispytest=True)
+ return MarkDecorator(self.mark.combined_with(mark), _ispytest=True)
# Type ignored because the overloads overlap with an incompatible
# return type. Not much we can do about that. Thankfully mypy picks
# the first match so it works out even if we break the rules.
@overload
- def __call__(self, arg: _Markable) -> _Markable: # type: ignore[misc]
+ def __call__(self, arg: Markable) -> Markable: # type: ignore[misc]
pass
@overload
@@ -345,7 +360,7 @@ class MarkDecorator:
return self.with_args(*args, **kwargs)
-def get_unpacked_marks(obj) -> List[Mark]:
+def get_unpacked_marks(obj: object) -> Iterable[Mark]:
"""Obtain the unpacked marks that are stored on an object."""
mark_list = getattr(obj, "pytestmark", [])
if not isinstance(mark_list, list):
@@ -353,19 +368,21 @@ def get_unpacked_marks(obj) -> List[Mark]:
return normalize_mark_list(mark_list)
-def normalize_mark_list(mark_list: Iterable[Union[Mark, MarkDecorator]]) -> List[Mark]:
- """Normalize marker decorating helpers to mark objects.
+def normalize_mark_list(
+ mark_list: Iterable[Union[Mark, MarkDecorator]]
+) -> Iterable[Mark]:
+ """
+ Normalize an iterable of Mark or MarkDecorator objects into a list of marks
+ by retrieving the `mark` attribute on MarkDecorator instances.
- :type List[Union[Mark, Markdecorator]] mark_list:
- :rtype: List[Mark]
+ :param mark_list: marks to normalize
+ :returns: A new list of the extracted Mark objects
"""
- extracted = [
- getattr(mark, "mark", mark) for mark in mark_list
- ] # unpack MarkDecorator
- for mark in extracted:
- if not isinstance(mark, Mark):
- raise TypeError(f"got {mark!r} instead of Mark")
- return [x for x in extracted if isinstance(x, Mark)]
+ for mark in mark_list:
+ mark_obj = getattr(mark, "mark", mark)
+ if not isinstance(mark_obj, Mark):
+ raise TypeError(f"got {repr(mark_obj)} instead of Mark")
+ yield mark_obj
def store_mark(obj, mark: Mark) -> None:
@@ -376,17 +393,17 @@ def store_mark(obj, mark: Mark) -> None:
assert isinstance(mark, Mark), mark
# Always reassign name to avoid updating pytestmark in a reference that
# was only borrowed.
- obj.pytestmark = get_unpacked_marks(obj) + [mark]
+ obj.pytestmark = [*get_unpacked_marks(obj), mark]
# Typing for builtin pytest marks. This is cheating; it gives builtin marks
# special privilege, and breaks modularity. But practicality beats purity...
if TYPE_CHECKING:
- from _pytest.fixtures import _Scope
+ from _pytest.scope import _ScopeName
class _SkipMarkDecorator(MarkDecorator):
@overload # type: ignore[override,misc]
- def __call__(self, arg: _Markable) -> _Markable:
+ def __call__(self, arg: Markable) -> Markable:
...
@overload
@@ -404,7 +421,7 @@ if TYPE_CHECKING:
class _XfailMarkDecorator(MarkDecorator):
@overload # type: ignore[override,misc]
- def __call__(self, arg: _Markable) -> _Markable:
+ def __call__(self, arg: Markable) -> Markable:
...
@overload
@@ -432,20 +449,16 @@ if TYPE_CHECKING:
Callable[[Any], Optional[object]],
]
] = ...,
- scope: Optional[_Scope] = ...,
+ scope: Optional[_ScopeName] = ...,
) -> MarkDecorator:
...
class _UsefixturesMarkDecorator(MarkDecorator):
- def __call__( # type: ignore[override]
- self, *fixtures: str
- ) -> MarkDecorator:
+ def __call__(self, *fixtures: str) -> MarkDecorator: # type: ignore[override]
...
class _FilterwarningsMarkDecorator(MarkDecorator):
- def __call__( # type: ignore[override]
- self, *filters: str
- ) -> MarkDecorator:
+ def __call__(self, *filters: str) -> MarkDecorator: # type: ignore[override]
...
@@ -465,9 +478,6 @@ class MarkGenerator:
applies a 'slowtest' :class:`Mark` on ``test_function``.
"""
- _config: Optional[Config] = None
- _markers: Set[str] = set()
-
# See TYPE_CHECKING above.
if TYPE_CHECKING:
skip: _SkipMarkDecorator
@@ -477,7 +487,13 @@ class MarkGenerator:
usefixtures: _UsefixturesMarkDecorator
filterwarnings: _FilterwarningsMarkDecorator
+ def __init__(self, *, _ispytest: bool = False) -> None:
+ check_ispytest(_ispytest)
+ self._config: Optional[Config] = None
+ self._markers: Set[str] = set()
+
def __getattr__(self, name: str) -> MarkDecorator:
+ """Generate a new :class:`MarkDecorator` with the given name."""
if name[0] == "_":
raise AttributeError("Marker name must NOT start with underscore")
@@ -513,19 +529,21 @@ class MarkGenerator:
warnings.warn(
"Unknown pytest.mark.%s - is this a typo? You can register "
"custom marks to avoid this warning - for details, see "
- "https://docs.pytest.org/en/stable/mark.html" % name,
+ "https://docs.pytest.org/en/stable/how-to/mark.html" % name,
PytestUnknownMarkWarning,
2,
)
- return MarkDecorator(Mark(name, (), {}))
+ return MarkDecorator(Mark(name, (), {}, _ispytest=True), _ispytest=True)
-MARK_GEN = MarkGenerator()
+MARK_GEN = MarkGenerator(_ispytest=True)
@final
class NodeKeywords(MutableMapping[str, Any]):
+ __slots__ = ("node", "parent", "_markers")
+
def __init__(self, node: "Node") -> None:
self.node = node
self.parent = node.parent
@@ -542,21 +560,39 @@ class NodeKeywords(MutableMapping[str, Any]):
def __setitem__(self, key: str, value: Any) -> None:
self._markers[key] = value
+ # Note: we could've avoided explicitly implementing some of the methods
+ # below and use the collections.abc fallback, but that would be slow.
+
+ def __contains__(self, key: object) -> bool:
+ return (
+ key in self._markers
+ or self.parent is not None
+ and key in self.parent.keywords
+ )
+
+ def update( # type: ignore[override]
+ self,
+ other: Union[Mapping[str, Any], Iterable[Tuple[str, Any]]] = (),
+ **kwds: Any,
+ ) -> None:
+ self._markers.update(other)
+ self._markers.update(kwds)
+
def __delitem__(self, key: str) -> None:
raise ValueError("cannot delete key in keywords dict")
def __iter__(self) -> Iterator[str]:
- seen = self._seen()
- return iter(seen)
-
- def _seen(self) -> Set[str]:
- seen = set(self._markers)
+ # Doesn't need to be fast.
+ yield from self._markers
if self.parent is not None:
- seen.update(self.parent.keywords)
- return seen
+ for keyword in self.parent.keywords:
+ # self._marks and self.parent.keywords can have duplicates.
+ if keyword not in self._markers:
+ yield keyword
def __len__(self) -> int:
- return len(self._seen())
+ # Doesn't need to be fast.
+ return sum(1 for keyword in self)
def __repr__(self) -> str:
return f"<NodeKeywords for node {self.node}>"