Skip to content

Commit

Permalink
Create context manager to intercept concrete exceptions from tests wi…
Browse files Browse the repository at this point in the history
…thout huge try... except blocks
  • Loading branch information
elchupanebrej committed Jun 26, 2023
1 parent 8dd1f82 commit ccc6dac
Show file tree
Hide file tree
Showing 4 changed files with 94 additions and 27 deletions.
10 changes: 10 additions & 0 deletions src/pytest_bdd/compatibility/pytest.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from _pytest.runner import CallInfo
from _pytest.terminal import TerminalReporter
from pytest import Module as PytestModule
from pytest import fail as _pytest_fail

from pytest_bdd.packaging import compare_distribution_version

Expand Down Expand Up @@ -131,13 +132,22 @@ def get_config_root_path(config: Config) -> Path:
return Path(getattr(cast(Config, config), "rootpath" if PYTEST61 else "rootdir"))


def fail(reason, pytrace=True):
__tracebackhide__ = True
if PYTEST7:
return _pytest_fail(reason, pytrace=pytrace)
else:
return _pytest_fail(msg=reason, pytrace=pytrace)


__all__ = [
"assert_outcomes",
"Item",
"CallInfo",
"call_fixture_func",
"Config",
"ExitCode",
"fail",
"FixtureDef",
"FixtureLookupError",
"FixtureRequest",
Expand Down
52 changes: 48 additions & 4 deletions src/pytest_bdd/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,33 @@
import base64
import pickle
import re
import sys
from collections import defaultdict
from contextlib import nullcontext, suppress
from contextlib import contextmanager, nullcontext, suppress
from enum import Enum
from functools import reduce
from inspect import getframeinfo, signature
from itertools import tee
from operator import attrgetter, getitem, itemgetter
from sys import _getframe
from typing import TYPE_CHECKING, Any, Callable, Collection, Dict, Mapping, Optional, Sequence, Union, cast

from pytest_bdd.compatibility.pytest import FixtureDef
from typing import (
TYPE_CHECKING,
Any,
Callable,
Collection,
Dict,
Mapping,
Optional,
Pattern,
Sequence,
Type,
Union,
cast,
)

from pytest import raises

from pytest_bdd.compatibility.pytest import FixtureDef, fail
from pytest_bdd.compatibility.typing import Literal, Protocol, runtime_checkable
from pytest_bdd.const import ALPHA_REGEX, PYTHON_REPLACE_REGEX

Expand Down Expand Up @@ -277,3 +293,31 @@ def __next__(self):
self._id_counter += 1

get_next_id = __next__


@contextmanager
def doesnt_raise(
expected_exception: Union[Type[Exception], Sequence[Type[Exception]]],
*,
match: Optional[Union[str, Pattern[str]]] = None,
suppress_not_matched=True,
):
"""
:param expected_exception: Expected exception/s which don't have to be raised; If it raised - test fails
:param match: Message which will be count as failing test. If message is not matched - function passes
:param suppress_not_matched: If specified - all non-matched exceptions will be suppressed
:return:
"""

try:
yield
except expected_exception: # type:ignore[misc]
ex_type, ex_value, ex_traceback = sys.exc_info()
is_matched = True
if match is not None:
is_matched = bool(re.search(match, f"{ex_value}"))
if is_matched:
fail(f"{ex_value}")
elif not suppress_not_matched:
raise
30 changes: 8 additions & 22 deletions tests/struct_bdd/test_deserialization.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from pytest_bdd.model.messages import KeywordType
from pytest_bdd.struct_bdd.model import Alternative, Join, Keyword, Node, Step, Table
from pytest_bdd.struct_bdd.model_builder import GherkinDocumentBuilder
from pytest_bdd.utils import IdGenerator
from pytest_bdd.utils import IdGenerator, doesnt_raise


def test_node_containing_data_load():
Expand Down Expand Up @@ -273,14 +273,12 @@ def test_load_simplest_step_with_keyworded_steps():


def test_load_step_with_single_simplest_steps():
try:
with doesnt_raise(Exception):
Step.parse_obj(dict(Steps=[dict(Step=dict())]))
except Exception as e:
raise AssertionError from e


def test_node_module_load_for_step():
try:
with doesnt_raise(Exception):
doc = dedent(
# language=yaml
"""\
Expand All @@ -300,12 +298,10 @@ def test_node_module_load_for_step():

data = load_yaml(doc, Loader=FullLoader)
Step.parse_obj(data)
except Exception as e:
raise AssertionError from e


def test_data_load():
try:
with doesnt_raise(Exception):
doc = dedent(
# language=yaml
"""\
Expand All @@ -329,12 +325,10 @@ def test_data_load():

data = load_yaml(doc, Loader=FullLoader)
Step.parse_obj(data)
except Exception as e:
raise AssertionError from e


def test_nested_sub_join_load():
try:
with doesnt_raise(Exception):
doc = dedent(
# language=yaml
"""
Expand All @@ -356,12 +350,10 @@ def test_nested_sub_join_load():

data = load_yaml(doc, Loader=FullLoader)
Join.parse_obj(data)
except Exception as e:
raise AssertionError from e


def test_nested_data_load():
try:
with doesnt_raise(Exception):
doc = dedent(
# language=yaml
"""\
Expand Down Expand Up @@ -398,12 +390,10 @@ def test_nested_data_load():

data = load_yaml(doc, Loader=FullLoader)
Step.parse_obj(data)
except Exception as e:
raise AssertionError from e


def test_nested_examples_load():
try:
with doesnt_raise(Exception):
doc = dedent(
# language=yaml
"""\
Expand Down Expand Up @@ -440,8 +430,6 @@ def test_nested_examples_load():

data = load_yaml(doc, Loader=FullLoader)
Step.parse_obj(data)
except Exception as e:
raise AssertionError from e


def test_tags_steps_examples_load():
Expand Down Expand Up @@ -674,7 +662,7 @@ def test_tags_steps_examples_joined_by_value_load():


def test_load_nested_steps():
try:
with doesnt_raise(Exception):
doc = dedent(
# language=yaml
"""\
Expand All @@ -697,5 +685,3 @@ def test_load_nested_steps():

data = load_yaml(doc, Loader=FullLoader)
Step.parse_obj(data)
except Exception as e:
raise AssertionError from e
29 changes: 28 additions & 1 deletion tests/test_utils.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import pytest
from _pytest.outcomes import Failed
from attr import attrib, attrs
from pytest import raises

from pytest_bdd.utils import deepattrgetter, setdefaultattr
from pytest_bdd.utils import deepattrgetter, doesnt_raise, setdefaultattr


def test_get_attribute():
Expand Down Expand Up @@ -207,3 +208,29 @@ class Dumb:

with pytest.raises(ValueError):
setdefaultattr(Dumb(), "a", value=10, value_factory=lambda: 20)


def test_doesnt_raise_fails_test():
with raises(Failed):
with doesnt_raise(RuntimeError):
raise RuntimeError


def test_doesnt_raise_suppress_if_not_match():
try:
with doesnt_raise(RuntimeError, match="cool"):
raise RuntimeError("nice")
except Exception as e: # pragma: no cover
raise AssertionError from e


def test_doesnt_raise_not_suppress_if_not_match_explicitly():
with raises(RuntimeError, match="nice"):
with doesnt_raise(RuntimeError, match="cool", suppress_not_matched=False):
raise RuntimeError("nice")


def test_doesnt_raise_passes_original_exception_if_not_suppressed():
with raises(ValueError):
with doesnt_raise(RuntimeError, suppress_not_matched=False):
raise ValueError("nice")

0 comments on commit ccc6dac

Please sign in to comment.