This reverts commit dfe54cd82f.
The idea in the commit was to simplify the code by removing the check
and instead letting it TypeError which has the same effect.
However this type error is caught by mypy, and rather than ignoring the
error we think it's better and clearer to go back to the previous
explicit check.
383 lines
13 KiB
Python
383 lines
13 KiB
Python
import re
|
|
import warnings
|
|
|
|
import pytest
|
|
from _pytest.recwarn import WarningsRecorder
|
|
|
|
|
|
def test_recwarn_stacklevel(recwarn):
|
|
warnings.warn("hello")
|
|
warn = recwarn.pop()
|
|
assert warn.filename == __file__
|
|
|
|
|
|
def test_recwarn_functional(testdir):
|
|
testdir.makepyfile(
|
|
"""
|
|
import warnings
|
|
def test_method(recwarn):
|
|
warnings.warn("hello")
|
|
warn = recwarn.pop()
|
|
assert isinstance(warn.message, UserWarning)
|
|
"""
|
|
)
|
|
reprec = testdir.inline_run()
|
|
reprec.assertoutcome(passed=1)
|
|
|
|
|
|
class TestWarningsRecorderChecker:
|
|
def test_recording(self):
|
|
rec = WarningsRecorder()
|
|
with rec:
|
|
assert not rec.list
|
|
warnings.warn_explicit("hello", UserWarning, "xyz", 13)
|
|
assert len(rec.list) == 1
|
|
warnings.warn(DeprecationWarning("hello"))
|
|
assert len(rec.list) == 2
|
|
warn = rec.pop()
|
|
assert str(warn.message) == "hello"
|
|
values = rec.list
|
|
rec.clear()
|
|
assert len(rec.list) == 0
|
|
assert values is rec.list
|
|
pytest.raises(AssertionError, rec.pop)
|
|
|
|
def test_warn_stacklevel(self):
|
|
"""#4243"""
|
|
rec = WarningsRecorder()
|
|
with rec:
|
|
warnings.warn("test", DeprecationWarning, 2)
|
|
|
|
def test_typechecking(self):
|
|
from _pytest.recwarn import WarningsChecker
|
|
|
|
with pytest.raises(TypeError):
|
|
WarningsChecker(5)
|
|
with pytest.raises(TypeError):
|
|
WarningsChecker(("hi", RuntimeWarning))
|
|
with pytest.raises(TypeError):
|
|
WarningsChecker([DeprecationWarning, RuntimeWarning])
|
|
|
|
def test_invalid_enter_exit(self):
|
|
# wrap this test in WarningsRecorder to ensure warning state gets reset
|
|
with WarningsRecorder():
|
|
with pytest.raises(RuntimeError):
|
|
rec = WarningsRecorder()
|
|
rec.__exit__(None, None, None) # can't exit before entering
|
|
|
|
with pytest.raises(RuntimeError):
|
|
rec = WarningsRecorder()
|
|
with rec:
|
|
with rec:
|
|
pass # can't enter twice
|
|
|
|
|
|
class TestDeprecatedCall:
|
|
"""test pytest.deprecated_call()"""
|
|
|
|
def dep(self, i, j=None):
|
|
if i == 0:
|
|
warnings.warn("is deprecated", DeprecationWarning, stacklevel=1)
|
|
return 42
|
|
|
|
def dep_explicit(self, i):
|
|
if i == 0:
|
|
warnings.warn_explicit(
|
|
"dep_explicit", category=DeprecationWarning, filename="hello", lineno=3
|
|
)
|
|
|
|
def test_deprecated_call_raises(self):
|
|
with pytest.raises(pytest.fail.Exception, match="No warnings of type"):
|
|
pytest.deprecated_call(self.dep, 3, 5)
|
|
|
|
def test_deprecated_call(self):
|
|
pytest.deprecated_call(self.dep, 0, 5)
|
|
|
|
def test_deprecated_call_ret(self):
|
|
ret = pytest.deprecated_call(self.dep, 0)
|
|
assert ret == 42
|
|
|
|
def test_deprecated_call_preserves(self):
|
|
onceregistry = warnings.onceregistry.copy()
|
|
filters = warnings.filters[:]
|
|
warn = warnings.warn
|
|
warn_explicit = warnings.warn_explicit
|
|
self.test_deprecated_call_raises()
|
|
self.test_deprecated_call()
|
|
assert onceregistry == warnings.onceregistry
|
|
assert filters == warnings.filters
|
|
assert warn is warnings.warn
|
|
assert warn_explicit is warnings.warn_explicit
|
|
|
|
def test_deprecated_explicit_call_raises(self):
|
|
with pytest.raises(pytest.fail.Exception):
|
|
pytest.deprecated_call(self.dep_explicit, 3)
|
|
|
|
def test_deprecated_explicit_call(self):
|
|
pytest.deprecated_call(self.dep_explicit, 0)
|
|
pytest.deprecated_call(self.dep_explicit, 0)
|
|
|
|
@pytest.mark.parametrize("mode", ["context_manager", "call"])
|
|
def test_deprecated_call_no_warning(self, mode):
|
|
"""Ensure deprecated_call() raises the expected failure when its block/function does
|
|
not raise a deprecation warning.
|
|
"""
|
|
|
|
def f():
|
|
pass
|
|
|
|
msg = "No warnings of type (.*DeprecationWarning.*, .*PendingDeprecationWarning.*)"
|
|
with pytest.raises(pytest.fail.Exception, match=msg):
|
|
if mode == "call":
|
|
pytest.deprecated_call(f)
|
|
else:
|
|
with pytest.deprecated_call():
|
|
f()
|
|
|
|
@pytest.mark.parametrize(
|
|
"warning_type", [PendingDeprecationWarning, DeprecationWarning]
|
|
)
|
|
@pytest.mark.parametrize("mode", ["context_manager", "call"])
|
|
@pytest.mark.parametrize("call_f_first", [True, False])
|
|
@pytest.mark.filterwarnings("ignore")
|
|
def test_deprecated_call_modes(self, warning_type, mode, call_f_first):
|
|
"""Ensure deprecated_call() captures a deprecation warning as expected inside its
|
|
block/function.
|
|
"""
|
|
|
|
def f():
|
|
warnings.warn(warning_type("hi"))
|
|
return 10
|
|
|
|
# ensure deprecated_call() can capture the warning even if it has already been triggered
|
|
if call_f_first:
|
|
assert f() == 10
|
|
if mode == "call":
|
|
assert pytest.deprecated_call(f) == 10
|
|
else:
|
|
with pytest.deprecated_call():
|
|
assert f() == 10
|
|
|
|
@pytest.mark.parametrize("mode", ["context_manager", "call"])
|
|
def test_deprecated_call_exception_is_raised(self, mode):
|
|
"""If the block of the code being tested by deprecated_call() raises an exception,
|
|
it must raise the exception undisturbed.
|
|
"""
|
|
|
|
def f():
|
|
raise ValueError("some exception")
|
|
|
|
with pytest.raises(ValueError, match="some exception"):
|
|
if mode == "call":
|
|
pytest.deprecated_call(f)
|
|
else:
|
|
with pytest.deprecated_call():
|
|
f()
|
|
|
|
def test_deprecated_call_specificity(self):
|
|
other_warnings = [
|
|
Warning,
|
|
UserWarning,
|
|
SyntaxWarning,
|
|
RuntimeWarning,
|
|
FutureWarning,
|
|
ImportWarning,
|
|
UnicodeWarning,
|
|
]
|
|
for warning in other_warnings:
|
|
|
|
def f():
|
|
warnings.warn(warning("hi"))
|
|
|
|
with pytest.raises(pytest.fail.Exception):
|
|
pytest.deprecated_call(f)
|
|
with pytest.raises(pytest.fail.Exception):
|
|
with pytest.deprecated_call():
|
|
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:
|
|
def test_check_callable(self):
|
|
source = "warnings.warn('w1', RuntimeWarning)"
|
|
with pytest.raises(TypeError, match=r".* must be callable"):
|
|
pytest.warns(RuntimeWarning, source)
|
|
|
|
def test_several_messages(self):
|
|
# different messages, b/c Python suppresses multiple identical warnings
|
|
pytest.warns(RuntimeWarning, lambda: warnings.warn("w1", RuntimeWarning))
|
|
with pytest.raises(pytest.fail.Exception):
|
|
pytest.warns(UserWarning, lambda: warnings.warn("w2", RuntimeWarning))
|
|
pytest.warns(RuntimeWarning, lambda: warnings.warn("w3", RuntimeWarning))
|
|
|
|
def test_function(self):
|
|
pytest.warns(
|
|
SyntaxWarning, lambda msg: warnings.warn(msg, SyntaxWarning), "syntax"
|
|
)
|
|
|
|
def test_warning_tuple(self):
|
|
pytest.warns(
|
|
(RuntimeWarning, SyntaxWarning), lambda: warnings.warn("w1", RuntimeWarning)
|
|
)
|
|
pytest.warns(
|
|
(RuntimeWarning, SyntaxWarning), lambda: warnings.warn("w2", SyntaxWarning)
|
|
)
|
|
pytest.raises(
|
|
pytest.fail.Exception,
|
|
lambda: pytest.warns(
|
|
(RuntimeWarning, SyntaxWarning),
|
|
lambda: warnings.warn("w3", UserWarning),
|
|
),
|
|
)
|
|
|
|
def test_as_contextmanager(self):
|
|
with pytest.warns(RuntimeWarning):
|
|
warnings.warn("runtime", RuntimeWarning)
|
|
|
|
with pytest.warns(UserWarning):
|
|
warnings.warn("user", UserWarning)
|
|
|
|
with pytest.raises(pytest.fail.Exception) as excinfo:
|
|
with pytest.warns(RuntimeWarning):
|
|
warnings.warn("user", UserWarning)
|
|
excinfo.match(
|
|
r"DID NOT WARN. No warnings of type \(.+RuntimeWarning.+,\) was emitted. "
|
|
r"The list of emitted warnings is: \[UserWarning\('user',?\)\]."
|
|
)
|
|
|
|
with pytest.raises(pytest.fail.Exception) as excinfo:
|
|
with pytest.warns(UserWarning):
|
|
warnings.warn("runtime", RuntimeWarning)
|
|
excinfo.match(
|
|
r"DID NOT WARN. No warnings of type \(.+UserWarning.+,\) was emitted. "
|
|
r"The list of emitted warnings is: \[RuntimeWarning\('runtime',?\)\]."
|
|
)
|
|
|
|
with pytest.raises(pytest.fail.Exception) as excinfo:
|
|
with pytest.warns(UserWarning):
|
|
pass
|
|
excinfo.match(
|
|
r"DID NOT WARN. No warnings of type \(.+UserWarning.+,\) was emitted. "
|
|
r"The list of emitted warnings is: \[\]."
|
|
)
|
|
|
|
warning_classes = (UserWarning, FutureWarning)
|
|
with pytest.raises(pytest.fail.Exception) as excinfo:
|
|
with pytest.warns(warning_classes) as warninfo:
|
|
warnings.warn("runtime", RuntimeWarning)
|
|
warnings.warn("import", ImportWarning)
|
|
|
|
message_template = (
|
|
"DID NOT WARN. No warnings of type {0} was emitted. "
|
|
"The list of emitted warnings is: {1}."
|
|
)
|
|
excinfo.match(
|
|
re.escape(
|
|
message_template.format(
|
|
warning_classes, [each.message for each in warninfo]
|
|
)
|
|
)
|
|
)
|
|
|
|
def test_record(self):
|
|
with pytest.warns(UserWarning) as record:
|
|
warnings.warn("user", UserWarning)
|
|
|
|
assert len(record) == 1
|
|
assert str(record[0].message) == "user"
|
|
|
|
def test_record_only(self):
|
|
with pytest.warns(None) as record:
|
|
warnings.warn("user", UserWarning)
|
|
warnings.warn("runtime", RuntimeWarning)
|
|
|
|
assert len(record) == 2
|
|
assert str(record[0].message) == "user"
|
|
assert str(record[1].message) == "runtime"
|
|
|
|
def test_record_by_subclass(self):
|
|
with pytest.warns(Warning) as record:
|
|
warnings.warn("user", UserWarning)
|
|
warnings.warn("runtime", RuntimeWarning)
|
|
|
|
assert len(record) == 2
|
|
assert str(record[0].message) == "user"
|
|
assert str(record[1].message) == "runtime"
|
|
|
|
class MyUserWarning(UserWarning):
|
|
pass
|
|
|
|
class MyRuntimeWarning(RuntimeWarning):
|
|
pass
|
|
|
|
with pytest.warns((UserWarning, RuntimeWarning)) as record:
|
|
warnings.warn("user", MyUserWarning)
|
|
warnings.warn("runtime", MyRuntimeWarning)
|
|
|
|
assert len(record) == 2
|
|
assert str(record[0].message) == "user"
|
|
assert str(record[1].message) == "runtime"
|
|
|
|
def test_double_test(self, testdir):
|
|
"""If a test is run again, the warning should still be raised"""
|
|
testdir.makepyfile(
|
|
"""
|
|
import pytest
|
|
import warnings
|
|
|
|
@pytest.mark.parametrize('run', [1, 2])
|
|
def test(run):
|
|
with pytest.warns(RuntimeWarning):
|
|
warnings.warn("runtime", RuntimeWarning)
|
|
"""
|
|
)
|
|
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):
|
|
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)
|
|
|
|
@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
|
|
|
|
def test_warns_context_manager_with_kwargs(self):
|
|
with pytest.raises(TypeError) as excinfo:
|
|
with pytest.warns(UserWarning, foo="bar"):
|
|
pass
|
|
assert "Unexpected keyword arguments" in str(excinfo.value)
|