Merge pull request #4104 from asottile/deprecated_call_match
Implement pytest.deprecated_call with pytest.warns
This commit is contained in:
commit
8ecdd4e9ff
|
@ -0,0 +1 @@
|
||||||
|
``pytest.warn`` will capture previously-warned warnings in Python 2. Previously they were never raised.
|
|
@ -0,0 +1,4 @@
|
||||||
|
Reimplement ``pytest.deprecated_call`` using ``pytest.warns`` so it supports the ``match='...'`` keyword argument.
|
||||||
|
|
||||||
|
This has the side effect that ``pytest.deprecated_call`` now raises ``pytest.fail.Exception`` instead
|
||||||
|
of ``AssertionError``.
|
|
@ -43,45 +43,10 @@ def deprecated_call(func=None, *args, **kwargs):
|
||||||
in which case it will ensure calling ``func(*args, **kwargs)`` produces one of the warnings
|
in which case it will ensure calling ``func(*args, **kwargs)`` produces one of the warnings
|
||||||
types above.
|
types above.
|
||||||
"""
|
"""
|
||||||
if not func:
|
__tracebackhide__ = True
|
||||||
return _DeprecatedCallContext()
|
if func is not None:
|
||||||
else:
|
args = (func,) + args
|
||||||
__tracebackhide__ = True
|
return warns((DeprecationWarning, PendingDeprecationWarning), *args, **kwargs)
|
||||||
with _DeprecatedCallContext():
|
|
||||||
return func(*args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class _DeprecatedCallContext(object):
|
|
||||||
"""Implements the logic to capture deprecation warnings as a context manager."""
|
|
||||||
|
|
||||||
def __enter__(self):
|
|
||||||
self._captured_categories = []
|
|
||||||
self._old_warn = warnings.warn
|
|
||||||
self._old_warn_explicit = warnings.warn_explicit
|
|
||||||
warnings.warn_explicit = self._warn_explicit
|
|
||||||
warnings.warn = self._warn
|
|
||||||
|
|
||||||
def _warn_explicit(self, message, category, *args, **kwargs):
|
|
||||||
self._captured_categories.append(category)
|
|
||||||
|
|
||||||
def _warn(self, message, category=None, *args, **kwargs):
|
|
||||||
if isinstance(message, Warning):
|
|
||||||
self._captured_categories.append(message.__class__)
|
|
||||||
else:
|
|
||||||
self._captured_categories.append(category)
|
|
||||||
|
|
||||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
||||||
warnings.warn_explicit = self._old_warn_explicit
|
|
||||||
warnings.warn = self._old_warn
|
|
||||||
|
|
||||||
if exc_type is None:
|
|
||||||
deprecation_categories = (DeprecationWarning, PendingDeprecationWarning)
|
|
||||||
if not any(
|
|
||||||
issubclass(c, deprecation_categories) for c in self._captured_categories
|
|
||||||
):
|
|
||||||
__tracebackhide__ = True
|
|
||||||
msg = "Did not produce DeprecationWarning or PendingDeprecationWarning"
|
|
||||||
raise AssertionError(msg)
|
|
||||||
|
|
||||||
|
|
||||||
def warns(expected_warning, *args, **kwargs):
|
def warns(expected_warning, *args, **kwargs):
|
||||||
|
@ -116,6 +81,7 @@ def warns(expected_warning, *args, **kwargs):
|
||||||
Failed: DID NOT WARN. No warnings of type ...UserWarning... was emitted...
|
Failed: DID NOT WARN. No warnings of type ...UserWarning... was emitted...
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
__tracebackhide__ = True
|
||||||
match_expr = None
|
match_expr = None
|
||||||
if not args:
|
if not args:
|
||||||
if "match" in kwargs:
|
if "match" in kwargs:
|
||||||
|
@ -183,12 +149,25 @@ class WarningsRecorder(warnings.catch_warnings):
|
||||||
raise RuntimeError("Cannot enter %r twice" % self)
|
raise RuntimeError("Cannot enter %r twice" % self)
|
||||||
self._list = super(WarningsRecorder, self).__enter__()
|
self._list = super(WarningsRecorder, self).__enter__()
|
||||||
warnings.simplefilter("always")
|
warnings.simplefilter("always")
|
||||||
|
# python3 keeps track of a "filter version", when the filters are
|
||||||
|
# updated previously seen warnings can be re-warned. python2 has no
|
||||||
|
# concept of this so we must reset the warnings registry manually.
|
||||||
|
# trivial patching of `warnings.warn` seems to be enough somehow?
|
||||||
|
if six.PY2:
|
||||||
|
|
||||||
|
def warn(*args, **kwargs):
|
||||||
|
return self._saved_warn(*args, **kwargs)
|
||||||
|
|
||||||
|
warnings.warn, self._saved_warn = warn, warnings.warn
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def __exit__(self, *exc_info):
|
def __exit__(self, *exc_info):
|
||||||
if not self._entered:
|
if not self._entered:
|
||||||
__tracebackhide__ = True
|
__tracebackhide__ = True
|
||||||
raise RuntimeError("Cannot exit %r without entering first" % self)
|
raise RuntimeError("Cannot exit %r without entering first" % self)
|
||||||
|
# see above where `self._saved_warn` is assigned
|
||||||
|
if six.PY2:
|
||||||
|
warnings.warn = self._saved_warn
|
||||||
super(WarningsRecorder, self).__exit__(*exc_info)
|
super(WarningsRecorder, self).__exit__(*exc_info)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -76,9 +76,8 @@ class TestDeprecatedCall(object):
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_deprecated_call_raises(self):
|
def test_deprecated_call_raises(self):
|
||||||
with pytest.raises(AssertionError) as excinfo:
|
with pytest.raises(pytest.fail.Exception, match="No warnings of type"):
|
||||||
pytest.deprecated_call(self.dep, 3, 5)
|
pytest.deprecated_call(self.dep, 3, 5)
|
||||||
assert "Did not produce" in str(excinfo)
|
|
||||||
|
|
||||||
def test_deprecated_call(self):
|
def test_deprecated_call(self):
|
||||||
pytest.deprecated_call(self.dep, 0, 5)
|
pytest.deprecated_call(self.dep, 0, 5)
|
||||||
|
@ -100,7 +99,7 @@ class TestDeprecatedCall(object):
|
||||||
assert warn_explicit is warnings.warn_explicit
|
assert warn_explicit is warnings.warn_explicit
|
||||||
|
|
||||||
def test_deprecated_explicit_call_raises(self):
|
def test_deprecated_explicit_call_raises(self):
|
||||||
with pytest.raises(AssertionError):
|
with pytest.raises(pytest.fail.Exception):
|
||||||
pytest.deprecated_call(self.dep_explicit, 3)
|
pytest.deprecated_call(self.dep_explicit, 3)
|
||||||
|
|
||||||
def test_deprecated_explicit_call(self):
|
def test_deprecated_explicit_call(self):
|
||||||
|
@ -116,8 +115,8 @@ class TestDeprecatedCall(object):
|
||||||
def f():
|
def f():
|
||||||
pass
|
pass
|
||||||
|
|
||||||
msg = "Did not produce DeprecationWarning or PendingDeprecationWarning"
|
msg = "No warnings of type (.*DeprecationWarning.*, .*PendingDeprecationWarning.*)"
|
||||||
with pytest.raises(AssertionError, match=msg):
|
with pytest.raises(pytest.fail.Exception, match=msg):
|
||||||
if mode == "call":
|
if mode == "call":
|
||||||
pytest.deprecated_call(f)
|
pytest.deprecated_call(f)
|
||||||
else:
|
else:
|
||||||
|
@ -179,12 +178,20 @@ class TestDeprecatedCall(object):
|
||||||
def f():
|
def f():
|
||||||
warnings.warn(warning("hi"))
|
warnings.warn(warning("hi"))
|
||||||
|
|
||||||
with pytest.raises(AssertionError):
|
with pytest.raises(pytest.fail.Exception):
|
||||||
pytest.deprecated_call(f)
|
pytest.deprecated_call(f)
|
||||||
with pytest.raises(AssertionError):
|
with pytest.raises(pytest.fail.Exception):
|
||||||
with pytest.deprecated_call():
|
with pytest.deprecated_call():
|
||||||
f()
|
f()
|
||||||
|
|
||||||
|
def test_deprecated_call_supports_match(self):
|
||||||
|
with pytest.deprecated_call(match=r"must be \d+$"):
|
||||||
|
warnings.warn("value must be 42", DeprecationWarning)
|
||||||
|
|
||||||
|
with pytest.raises(pytest.fail.Exception):
|
||||||
|
with pytest.deprecated_call(match=r"must be \d+$"):
|
||||||
|
warnings.warn("this is not here", DeprecationWarning)
|
||||||
|
|
||||||
|
|
||||||
class TestWarns(object):
|
class TestWarns(object):
|
||||||
def test_strings(self):
|
def test_strings(self):
|
||||||
|
@ -343,3 +350,13 @@ class TestWarns(object):
|
||||||
with pytest.warns(UserWarning, match=r"aaa"):
|
with pytest.warns(UserWarning, match=r"aaa"):
|
||||||
warnings.warn("bbbbbbbbbb", UserWarning)
|
warnings.warn("bbbbbbbbbb", UserWarning)
|
||||||
warnings.warn("cccccccccc", UserWarning)
|
warnings.warn("cccccccccc", UserWarning)
|
||||||
|
|
||||||
|
@pytest.mark.filterwarnings("ignore")
|
||||||
|
def test_can_capture_previously_warned(self):
|
||||||
|
def f():
|
||||||
|
warnings.warn(UserWarning("ohai"))
|
||||||
|
return 10
|
||||||
|
|
||||||
|
assert f() == 10
|
||||||
|
assert pytest.warns(UserWarning, f) == 10
|
||||||
|
assert pytest.warns(UserWarning, f) == 10
|
||||||
|
|
Loading…
Reference in New Issue