Skip to content

Handle match with pytest.raises() #6753

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Feb 22, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions changelog/6752.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
When :py:func:`pytest.raises` is used as a function (as opposed to a context manager),
a `match` keyword argument is now passed through to the tested function. Previously
it was swallowed and ignored (regression in pytest 5.1.0).
13 changes: 8 additions & 5 deletions src/_pytest/python_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -557,18 +557,16 @@ def raises( # noqa: F811
expected_exception: Union["Type[_E]", Tuple["Type[_E]", ...]],
func: Callable,
*args: Any,
match: Optional[str] = ...,
**kwargs: Any
) -> Optional[_pytest._code.ExceptionInfo[_E]]:
) -> _pytest._code.ExceptionInfo[_E]:
... # pragma: no cover


def raises( # noqa: F811
expected_exception: Union["Type[_E]", Tuple["Type[_E]", ...]],
*args: Any,
match: Optional[Union[str, "Pattern"]] = None,
**kwargs: Any
) -> Union["RaisesContext[_E]", Optional[_pytest._code.ExceptionInfo[_E]]]:
) -> Union["RaisesContext[_E]", _pytest._code.ExceptionInfo[_E]]:
r"""
Assert that a code block/function call raises ``expected_exception``
or raise a failure exception otherwise.
Expand All @@ -579,8 +577,12 @@ def raises( # noqa: F811
string that may contain `special characters`__, the pattern can
first be escaped with ``re.escape``.

__ https://docs.python.org/3/library/re.html#regular-expression-syntax
(This is only used when ``pytest.raises`` is used as a context manager,
and passed through to the function otherwise.
When using ``pytest.raises`` as a function, you can use:
``pytest.raises(Exc, func, match="passed on").match("my pattern")``.)

__ https://docs.python.org/3/library/re.html#regular-expression-syntax

.. currentmodule:: _pytest._code

Expand Down Expand Up @@ -693,6 +695,7 @@ def raises( # noqa: F811
message = "DID NOT RAISE {}".format(expected_exception)

if not args:
match = kwargs.pop("match", None)
if kwargs:
msg = "Unexpected keyword arguments passed to pytest.raises: "
msg += ", ".join(sorted(kwargs))
Expand Down
25 changes: 21 additions & 4 deletions testing/python/raises.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import re
import sys

import pytest
Expand Down Expand Up @@ -154,7 +155,7 @@ def test_no_raise_message(self):
else:
assert False, "Expected pytest.raises.Exception"

@pytest.mark.parametrize("method", ["function", "with"])
@pytest.mark.parametrize("method", ["function", "function_match", "with"])
def test_raises_cyclic_reference(self, method):
"""
Ensure pytest.raises does not leave a reference cycle (#1965).
Expand All @@ -175,6 +176,8 @@ def __call__(self):

if method == "function":
pytest.raises(ValueError, t)
elif method == "function_match":
pytest.raises(ValueError, t).match("^$")
else:
with pytest.raises(ValueError):
t()
Expand All @@ -184,7 +187,7 @@ def __call__(self):

assert refcount == len(gc.get_referrers(t))

def test_raises_match(self):
def test_raises_match(self) -> None:
msg = r"with base \d+"
with pytest.raises(ValueError, match=msg):
int("asdf")
Expand All @@ -194,13 +197,27 @@ def test_raises_match(self):
int("asdf")

msg = "with base 16"
expr = r"Pattern '{}' does not match \"invalid literal for int\(\) with base 10: 'asdf'\"".format(
expr = "Pattern {!r} does not match \"invalid literal for int() with base 10: 'asdf'\"".format(
msg
)
with pytest.raises(AssertionError, match=expr):
with pytest.raises(AssertionError, match=re.escape(expr)):
with pytest.raises(ValueError, match=msg):
int("asdf", base=10)

# "match" without context manager.
pytest.raises(ValueError, int, "asdf").match("invalid literal")
with pytest.raises(AssertionError) as excinfo:
pytest.raises(ValueError, int, "asdf").match(msg)
assert str(excinfo.value) == expr

pytest.raises(TypeError, int, match="invalid")

def tfunc(match):
raise ValueError("match={}".format(match))

pytest.raises(ValueError, tfunc, match="asdf").match("match=asdf")
pytest.raises(ValueError, tfunc, match="").match("match=")

def test_match_failure_string_quoting(self):
with pytest.raises(AssertionError) as excinfo:
with pytest.raises(AssertionError, match="'foo"):
Expand Down