Skip to content

[MRG][feature] Change warns signature to mimic the raises call #2711

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
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
43 changes: 38 additions & 5 deletions _pytest/recwarn.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
import sys
import warnings

import re

from _pytest.fixtures import yield_fixture
from _pytest.outcomes import fail

Expand Down Expand Up @@ -98,23 +100,41 @@ def warns(expected_warning, *args, **kwargs):

>>> with warns(RuntimeWarning):
... warnings.warn("my warning", RuntimeWarning)

In the context manager form you may use the keyword argument ``match`` to assert
that the exception matches a text or regex::

>>> with warns(UserWarning, match='must be 0 or None'):
... warnings.warn("value must be 0 or None", UserWarning)

>>> with warns(UserWarning, match=r'must be \d+$'):
... warnings.warn("value must be 42", UserWarning)

>>> with warns(UserWarning, match=r'must be \d+$'):
... warnings.warn("this is not here", UserWarning)
Traceback (most recent call last):
...
Failed: DID NOT WARN. No warnings of type ...UserWarning... was emitted...

"""
wcheck = WarningsChecker(expected_warning)
match_expr = None
if not args:
return wcheck
if "match" in kwargs:
match_expr = kwargs.pop("match")
return WarningsChecker(expected_warning, match_expr=match_expr)
elif isinstance(args[0], str):
code, = args
assert isinstance(code, str)
frame = sys._getframe(1)
loc = frame.f_locals.copy()
loc.update(kwargs)

with wcheck:
with WarningsChecker(expected_warning, match_expr=match_expr):
code = _pytest._code.Source(code).compile()
py.builtin.exec_(code, frame.f_globals, loc)
else:
func = args[0]
with wcheck:
with WarningsChecker(expected_warning, match_expr=match_expr):
return func(*args[1:], **kwargs)


Expand Down Expand Up @@ -174,7 +194,7 @@ def __exit__(self, *exc_info):


class WarningsChecker(WarningsRecorder):
def __init__(self, expected_warning=None):
def __init__(self, expected_warning=None, match_expr=None):
super(WarningsChecker, self).__init__()

msg = ("exceptions must be old-style classes or "
Expand All @@ -189,6 +209,7 @@ def __init__(self, expected_warning=None):
raise TypeError(msg % type(expected_warning))

self.expected_warning = expected_warning
self.match_expr = match_expr

def __exit__(self, *exc_info):
super(WarningsChecker, self).__exit__(*exc_info)
Expand All @@ -203,3 +224,15 @@ def __exit__(self, *exc_info):
"The list of emitted warnings is: {1}.".format(
self.expected_warning,
[each.message for each in self]))
elif self.match_expr is not None:
for r in self:
if issubclass(r.category, self.expected_warning):
if re.compile(self.match_expr).search(str(r.message)):
break
else:
fail("DID NOT WARN. No warnings of type {0} matching"
" ('{1}') was emitted. The list of emitted warnings"
" is: {2}.".format(
self.expected_warning,
self.match_expr,
[each.message for each in self]))
1 change: 1 addition & 0 deletions changelog/2708.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Match ``warns`` signature to ``raises`` by adding ``match`` keyworkd.
15 changes: 14 additions & 1 deletion doc/en/warnings.rst
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,20 @@ which works in a similar manner to :ref:`raises <assertraises>`::
with pytest.warns(UserWarning):
warnings.warn("my warning", UserWarning)

The test will fail if the warning in question is not raised.
The test will fail if the warning in question is not raised. The keyword
argument ``match`` to assert that the exception matches a text or regex::

>>> with warns(UserWarning, match='must be 0 or None'):
... warnings.warn("value must be 0 or None", UserWarning)

>>> with warns(UserWarning, match=r'must be \d+$'):
... warnings.warn("value must be 42", UserWarning)

>>> with warns(UserWarning, match=r'must be \d+$'):
... warnings.warn("this is not here", UserWarning)
Traceback (most recent call last):
...
Failed: DID NOT WARN. No warnings of type ...UserWarning... was emitted...

You can also call ``pytest.warns`` on a function or code string::

Expand Down
24 changes: 24 additions & 0 deletions testing/test_recwarn.py
Original file line number Diff line number Diff line change
Expand Up @@ -284,3 +284,27 @@ def test(run):
''')
result = testdir.runpytest()
result.stdout.fnmatch_lines(['*2 passed in*'])

def test_match_regex(self):
with pytest.warns(UserWarning, match=r'must be \d+$'):
warnings.warn("value must be 42", UserWarning)

with pytest.raises(pytest.fail.Exception):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please include a case which matches with match but the warning raised is wrong:

with pytest.raises(pytest.fail.Exception):
   with pytest.warns(UserWarning, match=r'must be \d+$'):
       warnings.warn("value must be 42", RuntimeWarning)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

with pytest.warns(UserWarning, match=r'must be \d+$'):
warnings.warn("this is not here", UserWarning)

with pytest.raises(pytest.fail.Exception):
with pytest.warns(FutureWarning, match=r'must be \d+$'):
warnings.warn("value must be 42", UserWarning)

def test_one_from_multiple_warns(self):
with pytest.warns(UserWarning, match=r'aaa'):
warnings.warn("cccccccccc", UserWarning)
warnings.warn("bbbbbbbbbb", UserWarning)
warnings.warn("aaaaaaaaaa", UserWarning)

def test_none_of_multiple_warns(self):
with pytest.raises(pytest.fail.Exception):
with pytest.warns(UserWarning, match=r'aaa'):
warnings.warn("bbbbbbbbbb", UserWarning)
warnings.warn("cccccccccc", UserWarning)