diff --git a/changelog/6752.bugfix.rst b/changelog/6752.bugfix.rst new file mode 100644 index 00000000000..510ea8d7dc9 --- /dev/null +++ b/changelog/6752.bugfix.rst @@ -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). diff --git a/src/_pytest/python_api.py b/src/_pytest/python_api.py index 24145016ce6..d3692412def 100644 --- a/src/_pytest/python_api.py +++ b/src/_pytest/python_api.py @@ -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. @@ -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 @@ -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)) diff --git a/testing/python/raises.py b/testing/python/raises.py index a53b2137a5f..6c607464d54 100644 --- a/testing/python/raises.py +++ b/testing/python/raises.py @@ -1,3 +1,4 @@ +import re import sys import pytest @@ -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). @@ -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() @@ -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") @@ -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"):