diff --git a/src/_pytest/mark/__init__.py b/src/_pytest/mark/__init__.py index 0dc3718e8..0cefe47c3 100644 --- a/src/_pytest/mark/__init__.py +++ b/src/_pytest/mark/__init__.py @@ -200,17 +200,12 @@ def deselect_by_keyword(items: "List[Item]", config: Config) -> None: selectuntil = True keywordexpr = keywordexpr[:-1] - try: - expression = Expression.compile(keywordexpr) - except ParseError as e: - raise UsageError( - f"Wrong expression passed to '-k': {keywordexpr}: {e}" - ) from None + expr = _parse_expression(keywordexpr, "Wrong expression passed to '-k'") remaining = [] deselected = [] for colitem in items: - if keywordexpr and not expression.evaluate(KeywordMatcher.from_item(colitem)): + if keywordexpr and not expr.evaluate(KeywordMatcher.from_item(colitem)): deselected.append(colitem) else: if selectuntil: @@ -245,24 +240,26 @@ def deselect_by_mark(items: "List[Item]", config: Config) -> None: if not matchexpr: return - try: - expression = Expression.compile(matchexpr) - except ParseError as e: - raise UsageError(f"Wrong expression passed to '-m': {matchexpr}: {e}") from None - - remaining = [] - deselected = [] + expr = _parse_expression(matchexpr, "Wrong expression passed to '-m'") + remaining: List[Item] = [] + deselected: List[Item] = [] for item in items: - if expression.evaluate(MarkMatcher.from_item(item)): + if expr.evaluate(MarkMatcher.from_item(item)): remaining.append(item) else: deselected.append(item) - if deselected: config.hook.pytest_deselected(items=deselected) items[:] = remaining +def _parse_expression(expr: str, exc_message: str) -> Expression: + try: + return Expression.compile(expr) + except ParseError as e: + raise UsageError(f"{exc_message}: {expr}: {e}") from None + + def pytest_collection_modifyitems(items: "List[Item]", config: Config) -> None: deselect_by_keyword(items, config) deselect_by_mark(items, config) diff --git a/src/_pytest/mark/structures.py b/src/_pytest/mark/structures.py index d7f0ffec5..e40a605bd 100644 --- a/src/_pytest/mark/structures.py +++ b/src/_pytest/mark/structures.py @@ -40,10 +40,7 @@ EMPTY_PARAMETERSET_OPTION = "empty_parameter_set_mark" def istestfunc(func) -> bool: - return ( - hasattr(func, "__call__") - and getattr(func, "__name__", "") != "" - ) + return callable(func) and getattr(func, "__name__", "") != "" def get_empty_parameterset_mark( @@ -332,9 +329,6 @@ class MarkDecorator: """:meta private:""" return self.name # for backward-compat (2.4.1 had this attr) - def __repr__(self) -> str: - return f"" - def with_args(self, *args: object, **kwargs: object) -> "MarkDecorator": """Return a MarkDecorator with extra arguments added. @@ -375,18 +369,21 @@ def get_unpacked_marks(obj) -> List[Mark]: def normalize_mark_list(mark_list: Iterable[Union[Mark, MarkDecorator]]) -> List[Mark]: - """Normalize marker decorating helpers to mark objects. - - :type List[Union[Mark, Markdecorator]] mark_list: - :rtype: List[Mark] """ - 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)] + Normalize an iterable of Mark or MarkDecorator objects into a list of marks + by retrieving the `mark` attribute on MarkDecorator instances. + + :param mark_list: marks to normalize + :returns: A new list of the extracted Mark objects + """ + + def parse_mark(mark: Union[Mark, MarkDecorator]) -> Mark: + mark_obj = getattr(mark, "mark", mark) + if not isinstance(mark_obj, Mark): + raise TypeError(f"got {repr(mark_obj)} instead of Mark") + return mark_obj + + return [parse_mark(x) for x in mark_list] def store_mark(obj, mark: Mark) -> None: