diff options
author | deshevoy <[email protected]> | 2022-02-10 16:46:57 +0300 |
---|---|---|
committer | Daniil Cherednik <[email protected]> | 2022-02-10 16:46:57 +0300 |
commit | 28148f76dbfcc644d96427d41c92f36cbf2fdc6e (patch) | |
tree | b83306b6e37edeea782e9eed673d89286c4fef35 /contrib/python/pytest/py2/_pytest/mark/structures.py | |
parent | e988f30484abe5fdeedcc7a5d3c226c01a21800c (diff) |
Restoring authorship annotation for <[email protected]>. Commit 2 of 2.
Diffstat (limited to 'contrib/python/pytest/py2/_pytest/mark/structures.py')
-rw-r--r-- | contrib/python/pytest/py2/_pytest/mark/structures.py | 628 |
1 files changed, 314 insertions, 314 deletions
diff --git a/contrib/python/pytest/py2/_pytest/mark/structures.py b/contrib/python/pytest/py2/_pytest/mark/structures.py index b9b0e31634d..aaebe927be7 100644 --- a/contrib/python/pytest/py2/_pytest/mark/structures.py +++ b/contrib/python/pytest/py2/_pytest/mark/structures.py @@ -1,76 +1,76 @@ # -*- coding: utf-8 -*- -import inspect -import warnings -from collections import namedtuple -from operator import attrgetter - -import attr +import inspect +import warnings +from collections import namedtuple +from operator import attrgetter + +import attr import six - + from ..compat import ascii_escaped from ..compat import ATTRS_EQ_FIELD -from ..compat import getfslineno -from ..compat import MappingMixin -from ..compat import NOTSET +from ..compat import getfslineno +from ..compat import MappingMixin +from ..compat import NOTSET from _pytest.deprecated import PYTEST_PARAM_UNKNOWN_KWARGS -from _pytest.outcomes import fail +from _pytest.outcomes import fail from _pytest.warning_types import PytestUnknownMarkWarning - -EMPTY_PARAMETERSET_OPTION = "empty_parameter_set_mark" - - -def alias(name, warning=None): - getter = attrgetter(name) - - def warned(self): - warnings.warn(warning, stacklevel=2) - return getter(self) - - return property(getter if warning is None else warned, doc="alias for " + name) - - -def istestfunc(func): - return ( - hasattr(func, "__call__") - and getattr(func, "__name__", "<lambda>") != "<lambda>" - ) - - -def get_empty_parameterset_mark(config, argnames, func): - from ..nodes import Collector - - requested_mark = config.getini(EMPTY_PARAMETERSET_OPTION) - if requested_mark in ("", None, "skip"): - mark = MARK_GEN.skip - elif requested_mark == "xfail": - mark = MARK_GEN.xfail(run=False) - elif requested_mark == "fail_at_collect": - f_name = func.__name__ - _, lineno = getfslineno(func) - raise Collector.CollectError( + +EMPTY_PARAMETERSET_OPTION = "empty_parameter_set_mark" + + +def alias(name, warning=None): + getter = attrgetter(name) + + def warned(self): + warnings.warn(warning, stacklevel=2) + return getter(self) + + return property(getter if warning is None else warned, doc="alias for " + name) + + +def istestfunc(func): + return ( + hasattr(func, "__call__") + and getattr(func, "__name__", "<lambda>") != "<lambda>" + ) + + +def get_empty_parameterset_mark(config, argnames, func): + from ..nodes import Collector + + requested_mark = config.getini(EMPTY_PARAMETERSET_OPTION) + if requested_mark in ("", None, "skip"): + mark = MARK_GEN.skip + elif requested_mark == "xfail": + mark = MARK_GEN.xfail(run=False) + elif requested_mark == "fail_at_collect": + f_name = func.__name__ + _, lineno = getfslineno(func) + raise Collector.CollectError( "Empty parameter set in '%s' at line %d" % (f_name, lineno + 1) - ) - else: - raise LookupError(requested_mark) - fs, lineno = getfslineno(func) - reason = "got empty parameter set %r, function %s at %s:%d" % ( - argnames, - func.__name__, - fs, - lineno, - ) - return mark(reason=reason) - - -class ParameterSet(namedtuple("ParameterSet", "values, marks, id")): - @classmethod + ) + else: + raise LookupError(requested_mark) + fs, lineno = getfslineno(func) + reason = "got empty parameter set %r, function %s at %s:%d" % ( + argnames, + func.__name__, + fs, + lineno, + ) + return mark(reason=reason) + + +class ParameterSet(namedtuple("ParameterSet", "values, marks, id")): + @classmethod def param(cls, *values, **kwargs): marks = kwargs.pop("marks", ()) - if isinstance(marks, MarkDecorator): - marks = (marks,) - else: - assert isinstance(marks, (tuple, list, set)) - + if isinstance(marks, MarkDecorator): + marks = (marks,) + else: + assert isinstance(marks, (tuple, list, set)) + id_ = kwargs.pop("id", None) if id_ is not None: if not isinstance(id_, six.string_types): @@ -78,63 +78,63 @@ class ParameterSet(namedtuple("ParameterSet", "values, marks, id")): "Expected id to be a string, got {}: {!r}".format(type(id_), id_) ) id_ = ascii_escaped(id_) - + if kwargs: warnings.warn( PYTEST_PARAM_UNKNOWN_KWARGS.format(args=sorted(kwargs)), stacklevel=3 ) - return cls(values, marks, id_) - - @classmethod + return cls(values, marks, id_) + + @classmethod def extract_from(cls, parameterset, force_tuple=False): - """ - :param parameterset: - a legacy style parameterset that may or may not be a tuple, - and may or may not be wrapped into a mess of mark objects - + """ + :param parameterset: + a legacy style parameterset that may or may not be a tuple, + and may or may not be wrapped into a mess of mark objects + :param force_tuple: - enforce tuple wrapping so single argument tuple values - don't get decomposed and break tests - """ - - if isinstance(parameterset, cls): - return parameterset + enforce tuple wrapping so single argument tuple values + don't get decomposed and break tests + """ + + if isinstance(parameterset, cls): + return parameterset if force_tuple: - return cls.param(parameterset) + return cls.param(parameterset) else: return cls(parameterset, marks=[], id=None) - + @staticmethod def _parse_parametrize_args(argnames, argvalues, *args, **kwargs): - if not isinstance(argnames, (tuple, list)): - argnames = [x.strip() for x in argnames.split(",") if x.strip()] - force_tuple = len(argnames) == 1 - else: - force_tuple = False + if not isinstance(argnames, (tuple, list)): + argnames = [x.strip() for x in argnames.split(",") if x.strip()] + force_tuple = len(argnames) == 1 + else: + force_tuple = False return argnames, force_tuple @staticmethod def _parse_parametrize_parameters(argvalues, force_tuple): return [ ParameterSet.extract_from(x, force_tuple=force_tuple) for x in argvalues - ] + ] @classmethod def _for_parametrize(cls, argnames, argvalues, func, config, function_definition): argnames, force_tuple = cls._parse_parametrize_args(argnames, argvalues) parameters = cls._parse_parametrize_parameters(argvalues, force_tuple) - del argvalues - - if parameters: - # check all parameter sets have the correct number of values - for param in parameters: - if len(param.values) != len(argnames): + del argvalues + + if parameters: + # check all parameter sets have the correct number of values + for param in parameters: + if len(param.values) != len(argnames): msg = ( '{nodeid}: in "parametrize" the number of names ({names_len}):\n' " {names}\n" "must be equal to the number of values ({values_len}):\n" " {values}" - ) + ) fail( msg.format( nodeid=function_definition.nodeid, @@ -145,130 +145,130 @@ class ParameterSet(namedtuple("ParameterSet", "values, marks, id")): ), pytrace=False, ) - else: - # empty parameter set (likely computed at runtime): create a single + else: + # empty parameter set (likely computed at runtime): create a single # parameter set with NOTSET values, with the "empty parameter set" mark applied to it - mark = get_empty_parameterset_mark(config, argnames, func) - parameters.append( - ParameterSet(values=(NOTSET,) * len(argnames), marks=[mark], id=None) - ) - return argnames, parameters - - [email protected](frozen=True) -class Mark(object): - #: name of the mark - name = attr.ib(type=str) - #: positional arguments of the mark decorator + mark = get_empty_parameterset_mark(config, argnames, func) + parameters.append( + ParameterSet(values=(NOTSET,) * len(argnames), marks=[mark], id=None) + ) + return argnames, parameters + + [email protected](frozen=True) +class Mark(object): + #: name of the mark + name = attr.ib(type=str) + #: positional arguments of the mark decorator args = attr.ib() # List[object] - #: keyword arguments of the mark decorator + #: keyword arguments of the mark decorator kwargs = attr.ib() # Dict[str, object] - - def combined_with(self, other): - """ - :param other: the mark to combine with - :type other: Mark - :rtype: Mark - + + def combined_with(self, other): + """ + :param other: the mark to combine with + :type other: Mark + :rtype: Mark + combines by appending args and merging the mappings - """ - assert self.name == other.name - return Mark( - self.name, self.args + other.args, dict(self.kwargs, **other.kwargs) - ) - - -class MarkDecorator(object): - """ A decorator for test functions and test classes. When applied - it will create :class:`MarkInfo` objects which may be - :ref:`retrieved by hooks as item keywords <excontrolskip>`. - MarkDecorator instances are often created like this:: - - mark1 = pytest.mark.NAME # simple MarkDecorator - mark2 = pytest.mark.NAME(name1=value) # parametrized MarkDecorator - - and can then be applied as decorators to test functions:: - - @mark2 - def test_function(): - pass - - When a MarkDecorator instance 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 itself to the class so it - gets applied automatically to all test cases found in that class. - 2. If called with a single function as its only positional argument and - no additional keyword arguments, it attaches a MarkInfo object to the - function, containing all the arguments already stored internally in - the MarkDecorator. - 3. When called in any other case, it performs a 'fake construction' call, - i.e. 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 MarkDecorator objects from storing only a - single function or class reference as their positional argument with no - additional keyword or positional arguments. - - """ - - mark = attr.ib(validator=attr.validators.instance_of(Mark)) - - name = alias("mark.name") - args = alias("mark.args") - kwargs = alias("mark.kwargs") - - @property - def markname(self): - return self.name # for backward-compat (2.4.1 had this attr) - - def __eq__(self, other): - return self.mark == other.mark if isinstance(other, MarkDecorator) else False - - def __repr__(self): - return "<MarkDecorator %r>" % (self.mark,) - - def with_args(self, *args, **kwargs): - """ return a MarkDecorator with extra arguments added - - unlike call this can be used even if the sole argument is a callable/class - - :return: MarkDecorator - """ - - mark = Mark(self.name, args, kwargs) - return self.__class__(self.mark.combined_with(mark)) - - def __call__(self, *args, **kwargs): - """ if passed a single callable argument: decorate it with mark info. - otherwise add *args/**kwargs in-place to mark information. """ - if args and not kwargs: - func = args[0] - is_class = inspect.isclass(func) - if len(args) == 1 and (istestfunc(func) or is_class): + """ + assert self.name == other.name + return Mark( + self.name, self.args + other.args, dict(self.kwargs, **other.kwargs) + ) + + +class MarkDecorator(object): + """ A decorator for test functions and test classes. When applied + it will create :class:`MarkInfo` objects which may be + :ref:`retrieved by hooks as item keywords <excontrolskip>`. + MarkDecorator instances are often created like this:: + + mark1 = pytest.mark.NAME # simple MarkDecorator + mark2 = pytest.mark.NAME(name1=value) # parametrized MarkDecorator + + and can then be applied as decorators to test functions:: + + @mark2 + def test_function(): + pass + + When a MarkDecorator instance 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 itself to the class so it + gets applied automatically to all test cases found in that class. + 2. If called with a single function as its only positional argument and + no additional keyword arguments, it attaches a MarkInfo object to the + function, containing all the arguments already stored internally in + the MarkDecorator. + 3. When called in any other case, it performs a 'fake construction' call, + i.e. 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 MarkDecorator objects from storing only a + single function or class reference as their positional argument with no + additional keyword or positional arguments. + + """ + + mark = attr.ib(validator=attr.validators.instance_of(Mark)) + + name = alias("mark.name") + args = alias("mark.args") + kwargs = alias("mark.kwargs") + + @property + def markname(self): + return self.name # for backward-compat (2.4.1 had this attr) + + def __eq__(self, other): + return self.mark == other.mark if isinstance(other, MarkDecorator) else False + + def __repr__(self): + return "<MarkDecorator %r>" % (self.mark,) + + def with_args(self, *args, **kwargs): + """ return a MarkDecorator with extra arguments added + + unlike call this can be used even if the sole argument is a callable/class + + :return: MarkDecorator + """ + + mark = Mark(self.name, args, kwargs) + return self.__class__(self.mark.combined_with(mark)) + + def __call__(self, *args, **kwargs): + """ if passed a single callable argument: decorate it with mark info. + otherwise add *args/**kwargs in-place to mark information. """ + if args and not kwargs: + func = args[0] + is_class = inspect.isclass(func) + if len(args) == 1 and (istestfunc(func) or is_class): store_mark(func, self.mark) - return func - return self.with_args(*args, **kwargs) - - -def get_unpacked_marks(obj): - """ - obtain the unpacked marks that are stored on an object - """ - mark_list = getattr(obj, "pytestmark", []) - if not isinstance(mark_list, list): - mark_list = [mark_list] - return normalize_mark_list(mark_list) - - -def normalize_mark_list(mark_list): - """ - normalizes marker decorating helpers to mark objects - - :type mark_list: List[Union[Mark, Markdecorator]] - :rtype: List[Mark] - """ + return func + return self.with_args(*args, **kwargs) + + +def get_unpacked_marks(obj): + """ + obtain the unpacked marks that are stored on an object + """ + mark_list = getattr(obj, "pytestmark", []) + if not isinstance(mark_list, list): + mark_list = [mark_list] + return normalize_mark_list(mark_list) + + +def normalize_mark_list(mark_list): + """ + normalizes marker decorating helpers to mark objects + + :type mark_list: List[Union[Mark, Markdecorator]] + :rtype: List[Mark] + """ extracted = [ getattr(mark, "mark", mark) for mark in mark_list ] # unpack MarkDecorator @@ -276,38 +276,38 @@ def normalize_mark_list(mark_list): if not isinstance(mark, Mark): raise TypeError("got {!r} instead of Mark".format(mark)) return [x for x in extracted if isinstance(x, Mark)] - - -def store_mark(obj, mark): - """store a Mark on an object - this is used to implement the Mark declarations/decorators correctly - """ - 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] - - -class MarkGenerator(object): - """ Factory for :class:`MarkDecorator` objects - exposed as - a ``pytest.mark`` singleton instance. Example:: - - import pytest - @pytest.mark.slowtest - def test_function(): - pass - - will set a 'slowtest' :class:`MarkInfo` object - on the ``test_function`` object. """ - - _config = None + + +def store_mark(obj, mark): + """store a Mark on an object + this is used to implement the Mark declarations/decorators correctly + """ + 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] + + +class MarkGenerator(object): + """ Factory for :class:`MarkDecorator` objects - exposed as + a ``pytest.mark`` singleton instance. Example:: + + import pytest + @pytest.mark.slowtest + def test_function(): + pass + + will set a 'slowtest' :class:`MarkInfo` object + on the ``test_function`` object. """ + + _config = None _markers = set() - - def __getattr__(self, name): - if name[0] == "_": - raise AttributeError("Marker name must NOT start with underscore") - if self._config is not None: + def __getattr__(self, name): + if name[0] == "_": + raise AttributeError("Marker name must NOT start with underscore") + + if self._config is not None: # We store a set of markers as a performance optimisation - if a mark # name is in the set we definitely know it, but a mark may be known and # not in the set. We therefore start by updating the set! @@ -338,76 +338,76 @@ class MarkGenerator(object): PytestUnknownMarkWarning, ) - return MarkDecorator(Mark(name, (), {})) - - -MARK_GEN = MarkGenerator() - - -class NodeKeywords(MappingMixin): - def __init__(self, node): - self.node = node - self.parent = node.parent - self._markers = {node.name: True} - - def __getitem__(self, key): - try: - return self._markers[key] - except KeyError: - if self.parent is None: - raise - return self.parent.keywords[key] - - def __setitem__(self, key, value): - self._markers[key] = value - - def __delitem__(self, key): - raise ValueError("cannot delete key in keywords dict") - - def __iter__(self): - seen = self._seen() - return iter(seen) - - def _seen(self): - seen = set(self._markers) - if self.parent is not None: - seen.update(self.parent.keywords) - return seen - - def __len__(self): - return len(self._seen()) - - def __repr__(self): - return "<NodeKeywords for node %s>" % (self.node,) - - + return MarkDecorator(Mark(name, (), {})) + + +MARK_GEN = MarkGenerator() + + +class NodeKeywords(MappingMixin): + def __init__(self, node): + self.node = node + self.parent = node.parent + self._markers = {node.name: True} + + def __getitem__(self, key): + try: + return self._markers[key] + except KeyError: + if self.parent is None: + raise + return self.parent.keywords[key] + + def __setitem__(self, key, value): + self._markers[key] = value + + def __delitem__(self, key): + raise ValueError("cannot delete key in keywords dict") + + def __iter__(self): + seen = self._seen() + return iter(seen) + + def _seen(self): + seen = set(self._markers) + if self.parent is not None: + seen.update(self.parent.keywords) + return seen + + def __len__(self): + return len(self._seen()) + + def __repr__(self): + return "<NodeKeywords for node %s>" % (self.node,) + + # mypy cannot find this overload, remove when on attrs>=19.2 @attr.s(hash=False, **{ATTRS_EQ_FIELD: False}) # type: ignore -class NodeMarkers(object): - """ - internal structure for storing marks belonging to a node - - ..warning:: - - unstable api - - """ - - own_markers = attr.ib(default=attr.Factory(list)) - - def update(self, add_markers): - """update the own markers - """ - self.own_markers.extend(add_markers) - - def find(self, name): - """ - find markers in own nodes or parent nodes - needs a better place - """ - for mark in self.own_markers: - if mark.name == name: - yield mark - - def __iter__(self): - return iter(self.own_markers) +class NodeMarkers(object): + """ + internal structure for storing marks belonging to a node + + ..warning:: + + unstable api + + """ + + own_markers = attr.ib(default=attr.Factory(list)) + + def update(self, add_markers): + """update the own markers + """ + self.own_markers.extend(add_markers) + + def find(self, name): + """ + find markers in own nodes or parent nodes + needs a better place + """ + for mark in self.own_markers: + if mark.name == name: + yield mark + + def __iter__(self): + return iter(self.own_markers) |