Merge pull request #2711 from massich/mimic_raises_signature_in_warns
[MRG][feature] Change warns signature to mimic the raises call
This commit is contained in:
commit
6967f3070e
|
@ -8,6 +8,8 @@ import py
|
||||||
import sys
|
import sys
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
from _pytest.fixtures import yield_fixture
|
from _pytest.fixtures import yield_fixture
|
||||||
from _pytest.outcomes import fail
|
from _pytest.outcomes import fail
|
||||||
|
|
||||||
|
@ -98,10 +100,28 @@ def warns(expected_warning, *args, **kwargs):
|
||||||
|
|
||||||
>>> with warns(RuntimeWarning):
|
>>> with warns(RuntimeWarning):
|
||||||
... warnings.warn("my warning", 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:
|
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):
|
elif isinstance(args[0], str):
|
||||||
code, = args
|
code, = args
|
||||||
assert isinstance(code, str)
|
assert isinstance(code, str)
|
||||||
|
@ -109,12 +129,12 @@ def warns(expected_warning, *args, **kwargs):
|
||||||
loc = frame.f_locals.copy()
|
loc = frame.f_locals.copy()
|
||||||
loc.update(kwargs)
|
loc.update(kwargs)
|
||||||
|
|
||||||
with wcheck:
|
with WarningsChecker(expected_warning, match_expr=match_expr):
|
||||||
code = _pytest._code.Source(code).compile()
|
code = _pytest._code.Source(code).compile()
|
||||||
py.builtin.exec_(code, frame.f_globals, loc)
|
py.builtin.exec_(code, frame.f_globals, loc)
|
||||||
else:
|
else:
|
||||||
func = args[0]
|
func = args[0]
|
||||||
with wcheck:
|
with WarningsChecker(expected_warning, match_expr=match_expr):
|
||||||
return func(*args[1:], **kwargs)
|
return func(*args[1:], **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
@ -174,7 +194,7 @@ class WarningsRecorder(warnings.catch_warnings):
|
||||||
|
|
||||||
|
|
||||||
class WarningsChecker(WarningsRecorder):
|
class WarningsChecker(WarningsRecorder):
|
||||||
def __init__(self, expected_warning=None):
|
def __init__(self, expected_warning=None, match_expr=None):
|
||||||
super(WarningsChecker, self).__init__()
|
super(WarningsChecker, self).__init__()
|
||||||
|
|
||||||
msg = ("exceptions must be old-style classes or "
|
msg = ("exceptions must be old-style classes or "
|
||||||
|
@ -189,6 +209,7 @@ class WarningsChecker(WarningsRecorder):
|
||||||
raise TypeError(msg % type(expected_warning))
|
raise TypeError(msg % type(expected_warning))
|
||||||
|
|
||||||
self.expected_warning = expected_warning
|
self.expected_warning = expected_warning
|
||||||
|
self.match_expr = match_expr
|
||||||
|
|
||||||
def __exit__(self, *exc_info):
|
def __exit__(self, *exc_info):
|
||||||
super(WarningsChecker, self).__exit__(*exc_info)
|
super(WarningsChecker, self).__exit__(*exc_info)
|
||||||
|
@ -203,3 +224,15 @@ class WarningsChecker(WarningsRecorder):
|
||||||
"The list of emitted warnings is: {1}.".format(
|
"The list of emitted warnings is: {1}.".format(
|
||||||
self.expected_warning,
|
self.expected_warning,
|
||||||
[each.message for each in self]))
|
[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]))
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
Match ``warns`` signature to ``raises`` by adding ``match`` keyworkd.
|
|
@ -168,7 +168,20 @@ which works in a similar manner to :ref:`raises <assertraises>`::
|
||||||
with pytest.warns(UserWarning):
|
with pytest.warns(UserWarning):
|
||||||
warnings.warn("my warning", 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::
|
You can also call ``pytest.warns`` on a function or code string::
|
||||||
|
|
||||||
|
|
|
@ -284,3 +284,27 @@ class TestWarns(object):
|
||||||
''')
|
''')
|
||||||
result = testdir.runpytest()
|
result = testdir.runpytest()
|
||||||
result.stdout.fnmatch_lines(['*2 passed in*'])
|
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):
|
||||||
|
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)
|
||||||
|
|
Loading…
Reference in New Issue