Skip to content

Commit

Permalink
Deprecate the 'message' parameter of pytest.raises
Browse files Browse the repository at this point in the history
  • Loading branch information
nicoddemus committed Dec 13, 2018
1 parent 110fe24 commit 5b83417
Show file tree
Hide file tree
Showing 7 changed files with 77 additions and 37 deletions.
8 changes: 8 additions & 0 deletions changelog/3974.deprecation.rst
@@ -0,0 +1,8 @@
Passing the ``message`` parameter of ``pytest.raises`` now issues a ``DeprecationWarning``.

It is a common mistake to think this parameter will match the exception message, while in fact
it only serves to provide a custom message in case the ``pytest.raises`` check fails. To avoid this
mistake and because it is believed to be little used, pytest is deprecating it without providing
an alternative for the moment.

If you have concerns about this, please comment on `issue #3974 <https://github.com/pytest-dev/pytest/issues/3974>`__.
13 changes: 13 additions & 0 deletions doc/en/deprecations.rst
Expand Up @@ -14,6 +14,19 @@ Below is a complete list of all pytest features which are considered deprecated.
:class:`_pytest.warning_types.PytestWarning` or subclasses, which can be filtered using
:ref:`standard warning filters <warnings>`.

``"message"`` parameter of ``pytest.raises``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

.. deprecated:: 4.1

It is a common mistake to think this parameter will match the exception message, while in fact
it only serves to provide a custom message in case the ``pytest.raises`` check fails. To avoid this
mistake and because it is believed to be little used, pytest is deprecating it without providing
an alternative for the moment.

If you have concerns about this, please comment on `issue #3974 <https://github.com/pytest-dev/pytest/issues/3974>`__.


``pytest.config`` global
~~~~~~~~~~~~~~~~~~~~~~~~

Expand Down
7 changes: 7 additions & 0 deletions src/_pytest/deprecated.py
Expand Up @@ -51,6 +51,13 @@
"getfuncargvalue is deprecated, use getfixturevalue"
)

RAISES_MESSAGE_PARAMETER = PytestDeprecationWarning(
"The 'message' parameter is deprecated.\n"
"(did you mean to use `match='some regex'` to check the exception message?)\n"
"Please comment on https://github.com/pytest-dev/pytest/issues/3974 "
"if you have concerns about removal of this parameter."
)

RESULT_LOG = PytestDeprecationWarning(
"--result-log is deprecated and scheduled for removal in pytest 5.0.\n"
"See https://docs.pytest.org/en/latest/deprecations.html#result-log-result-log for more information."
Expand Down
73 changes: 39 additions & 34 deletions src/_pytest/python_api.py
Expand Up @@ -13,11 +13,11 @@
from six.moves import zip

import _pytest._code
from _pytest import deprecated
from _pytest.compat import isclass
from _pytest.compat import Mapping
from _pytest.compat import Sequence
from _pytest.compat import STRING_TYPES
from _pytest.deprecated import RAISES_EXEC
from _pytest.outcomes import fail

BASE_TYPE = (type, STRING_TYPES)
Expand Down Expand Up @@ -551,29 +551,47 @@ def _is_numpy_array(obj):
def raises(expected_exception, *args, **kwargs):
r"""
Assert that a code block/function call raises ``expected_exception``
and raise a failure exception otherwise.
or raise a failure exception otherwise.
:arg message: if specified, provides a custom failure message if the
exception is not raised
:arg match: if specified, asserts that the exception matches a text or regex
:kwparam match: if specified, asserts that the exception matches a text or regex
This helper produces a ``ExceptionInfo()`` object (see below).
:kwparam message: **(deprecated since 4.1)** if specified, provides a custom failure message
if the exception is not raised
You may use this function as a context manager::
.. currentmodule:: _pytest._code
Use ``pytest.raises`` as a context manager, which will capture the exception of the given
type::
>>> with raises(ZeroDivisionError):
... 1/0
.. versionchanged:: 2.10
If the code block does not raise the expected exception (``ZeroDivisionError`` in the example
above), or no exception at all, the check will fail instead.
You can also use the keyword argument ``match`` to assert that the
exception matches a text or regex::
>>> with raises(ValueError, match='must be 0 or None'):
... raise ValueError("value must be 0 or None")
>>> with raises(ValueError, match=r'must be \d+$'):
... raise ValueError("value must be 42")
In the context manager form you may use the keyword argument
``message`` to specify a custom failure message::
The context manager produces an :class:`ExceptionInfo` object which can be used to inspect the
details of the captured exception::
>>> with raises(ZeroDivisionError, message="Expecting ZeroDivisionError"):
... pass
Traceback (most recent call last):
...
Failed: Expecting ZeroDivisionError
>>> with raises(ValueError) as exc_info:
... raise ValueError("value must be 42")
>>> assert exc_info.type is ValueError
>>> assert exc_info.value.args[0] == "value must be 42"
.. deprecated:: 4.1
In the context manager form you may use the keyword argument
``message`` to specify a custom failure message that will be displayed
in case the ``pytest.raises`` check fails. This has been deprecated as it
is considered error prone as users often mean to use ``match`` instead.
.. note::
Expand All @@ -587,7 +605,7 @@ def raises(expected_exception, *args, **kwargs):
>>> with raises(ValueError) as exc_info:
... if value > 10:
... raise ValueError("value must be <= 10")
... assert exc_info.type == ValueError # this will not execute
... assert exc_info.type is ValueError # this will not execute
Instead, the following approach must be taken (note the difference in
scope)::
Expand All @@ -596,23 +614,10 @@ def raises(expected_exception, *args, **kwargs):
... if value > 10:
... raise ValueError("value must be <= 10")
...
>>> assert exc_info.type == ValueError
Since version ``3.1`` you can use the keyword argument ``match`` to assert that the
exception matches a text or regex::
>>> with raises(ValueError, match='must be 0 or None'):
... raise ValueError("value must be 0 or None")
>>> with raises(ValueError, match=r'must be \d+$'):
... raise ValueError("value must be 42")
>>> assert exc_info.type is ValueError
**Legacy form**
The form below is fully supported but discouraged for new code because the
context manager form is regarded as more readable and less error-prone.
It is possible to specify a callable by passing a to-be-called lambda::
>>> raises(ZeroDivisionError, lambda: 1/0)
Expand All @@ -627,9 +632,8 @@ def raises(expected_exception, *args, **kwargs):
>>> raises(ZeroDivisionError, f, x=0)
<ExceptionInfo ...>
.. currentmodule:: _pytest._code
Consult the API of ``excinfo`` objects: :class:`ExceptionInfo`.
The form above is fully supported but discouraged for new code because the
context manager form is regarded as more readable and less error-prone.
.. note::
Similar to caught exception objects in Python, explicitly clearing
Expand Down Expand Up @@ -660,6 +664,7 @@ def raises(expected_exception, *args, **kwargs):
if not args:
if "message" in kwargs:
message = kwargs.pop("message")
warnings.warn(deprecated.RAISES_MESSAGE_PARAMETER, stacklevel=2)
if "match" in kwargs:
match_expr = kwargs.pop("match")
if kwargs:
Expand All @@ -668,7 +673,7 @@ def raises(expected_exception, *args, **kwargs):
raise TypeError(msg)
return RaisesContext(expected_exception, message, match_expr)
elif isinstance(args[0], str):
warnings.warn(RAISES_EXEC, stacklevel=2)
warnings.warn(deprecated.RAISES_EXEC, stacklevel=2)
code, = args
assert isinstance(code, str)
frame = sys._getframe(1)
Expand Down
6 changes: 6 additions & 0 deletions testing/deprecated_test.py
Expand Up @@ -136,6 +136,12 @@ def test_func(pytestconfig):
)


def test_raises_message_argument_deprecated():
with pytest.warns(pytest.PytestDeprecationWarning):
with pytest.raises(RuntimeError, message="foobar"):
raise RuntimeError


def test_pytest_plugins_in_non_top_level_conftest_deprecated(testdir):
from _pytest.deprecated import PYTEST_PLUGINS_FROM_NON_TOP_LEVEL_CONFTEST

Expand Down
5 changes: 3 additions & 2 deletions testing/python/raises.py
Expand Up @@ -121,8 +121,9 @@ def test_no_raise_message(self):
def test_custom_raise_message(self):
message = "TEST_MESSAGE"
try:
with pytest.raises(ValueError, message=message):
pass
with pytest.warns(PytestDeprecationWarning):
with pytest.raises(ValueError, message=message):
pass
except pytest.raises.Exception as e:
assert e.msg == message
else:
Expand Down
2 changes: 1 addition & 1 deletion testing/test_pytester.py
Expand Up @@ -280,7 +280,7 @@ def test_assert_outcomes_after_pytest_error(testdir):
testdir.makepyfile("def test_foo(): assert True")

result = testdir.runpytest("--unexpected-argument")
with pytest.raises(ValueError, message="Pytest terminal report not found"):
with pytest.raises(ValueError, match="Pytest terminal report not found"):
result.assert_outcomes(passed=0)


Expand Down

0 comments on commit 5b83417

Please sign in to comment.