diff --git a/tobiko/__init__.py b/tobiko/__init__.py index 21d58388f..9ca5ba1f3 100644 --- a/tobiko/__init__.py +++ b/tobiko/__init__.py @@ -124,6 +124,7 @@ MultipleObjectsFound = _select.MultipleObjectsFound SkipException = _skip.SkipException skip_if = _skip.skip_if +skip_on_error = _skip.skip_on_error skip_test = _skip.skip_test skip_unless = _skip.skip_unless skip = _skip.skip diff --git a/tobiko/common/_skip.py b/tobiko/common/_skip.py index 25ac31fec..be9f902f6 100644 --- a/tobiko/common/_skip.py +++ b/tobiko/common/_skip.py @@ -30,6 +30,10 @@ SkipTarget = typing.Union[typing.Callable, SkipDecorator = typing.Callable[[SkipTarget], SkipTarget] +SkipOnErrorType = typing.Union[typing.Type[Exception], + typing.Tuple[typing.Type[Exception], ...]] + + def skip_test(reason: str, cause: Exception = None, bugzilla: int = None) -> typing.NoReturn: @@ -37,7 +41,7 @@ def skip_test(reason: str, if bugzilla is not None: reason += f'\nhttps://bugzilla.redhat.com/show_bug.cgi?id={bugzilla}\n' if cause is not None: - reason += f"\n\n{cause}\n" + reason += f"\n{cause}\n" raise SkipException(reason) from cause @@ -75,12 +79,31 @@ def skip_unless(reason: str, predicate=predicate) +def skip_on_error(reason: str, + predicate: typing.Callable, + *args, + error_type: SkipOnErrorType = None, + bugzilla: int = None, + **kwargs) -> \ + SkipDecorator: + predicate = _get_skip_predicate(predicate, *args, **kwargs) + return _skip_decorator(reason=reason, + error_type=error_type, + bugzilla=bugzilla, + predicate=predicate) + + def _skip_decorator(reason: str, - unless: bool = True, + unless: bool = None, + error_type: SkipOnErrorType = None, bugzilla: int = None, predicate: typing.Callable = None) \ -> SkipDecorator: """Mark test case for being skipped for a given reason unless it matches""" + + if error_type is None: + error_type = tuple() + def decorator(obj: SkipTarget) -> SkipTarget: method = _get_skip_method(obj) @@ -89,11 +112,18 @@ def _skip_decorator(reason: str, _reason = reason cause: typing.Optional[Exception] = None if predicate is not None: - return_value = predicate() - if unless is bool(return_value): - return method(*args, **kwargs) + return_value: typing.Any = None + try: + return_value = predicate() + except error_type as ex: + cause = ex + else: + if unless in [None, bool(return_value)]: + return method(*args, **kwargs) if '{return_value' in reason: _reason = reason.format(return_value=return_value) + if '{cause' in reason: + _reason = reason.format(cause=cause) skip_test(reason=_reason, cause=cause, bugzilla=bugzilla) if obj is method: diff --git a/tobiko/tests/unit/test_skip.py b/tobiko/tests/unit/test_skip.py index e268b491a..eb61edf53 100644 --- a/tobiko/tests/unit/test_skip.py +++ b/tobiko/tests/unit/test_skip.py @@ -14,11 +14,13 @@ # under the License. from __future__ import absolute_import +import typing + import tobiko from tobiko.tests import unit -def condition(value): +def condition(value: typing.Any): return value @@ -224,3 +226,45 @@ class NegativeSkipUnlessConditionCalledWithKwargsTest(NegativeSkipBase): def test_fail(self): self.test_method_called = True + + +def raise_an_error(error: Exception): + raise error + + +def raise_any_error(): + pass + + +@tobiko.skip_on_error('error raised: {cause}', + raise_an_error, + error_type=ValueError, + error=ValueError("It is all right")) +class PositiveSkipOnErrorTest(unit.TobikoUnitTest): + + def test_skip_on_error(self): + self.fail('Not skipped') + + +class PositiveSkipOnErrorMethodTest(unit.TobikoUnitTest): + + @tobiko.skip_on_error('error raised: {cause}', + raise_an_error, + error_type=RuntimeError, + error=RuntimeError("It is all right")) + def test_skip_on_error(self): + self.fail('Not skipped') + + +@tobiko.skip_on_error('error not raised', raise_any_error, + error_type=ValueError) +class NegativeSkipOnErrorTest(NegativeSkipBase): + def test_skip_on_error(self): + self.test_method_called = True + + +class NegativeSkipOnErrorMethodTest(NegativeSkipBase): + @tobiko.skip_on_error('error not raised', raise_any_error, + error_type=ValueError) + def test_skip_on_error(self): + self.test_method_called = True