/tools/third_party/pytest/testing/test_recwarn.py
Python | 386 lines | 378 code | 5 blank | 3 comment | 0 complexity | 867c0070236b2a4fd9bcd086ab81074d MD5 | raw file
- import re
- import warnings
- from typing import Optional
- import pytest
- from _pytest.recwarn import WarningsRecorder
- def test_recwarn_stacklevel(recwarn: WarningsRecorder) -> None:
- warnings.warn("hello")
- warn = recwarn.pop()
- assert warn.filename == __file__
- def test_recwarn_functional(testdir) -> None:
- 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) -> None:
- 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) -> None:
- """#4243"""
- rec = WarningsRecorder()
- with rec:
- warnings.warn("test", DeprecationWarning, 2)
- def test_typechecking(self) -> None:
- from _pytest.recwarn import WarningsChecker
- with pytest.raises(TypeError):
- WarningsChecker(5) # type: ignore
- with pytest.raises(TypeError):
- WarningsChecker(("hi", RuntimeWarning)) # type: ignore
- with pytest.raises(TypeError):
- WarningsChecker([DeprecationWarning, RuntimeWarning]) # type: ignore
- def test_invalid_enter_exit(self) -> None:
- # 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: int, j: Optional[int] = None) -> int:
- if i == 0:
- warnings.warn("is deprecated", DeprecationWarning, stacklevel=1)
- return 42
- def dep_explicit(self, i: int) -> None:
- if i == 0:
- warnings.warn_explicit(
- "dep_explicit", category=DeprecationWarning, filename="hello", lineno=3
- )
- def test_deprecated_call_raises(self) -> None:
- with pytest.raises(pytest.fail.Exception, match="No warnings of type"):
- pytest.deprecated_call(self.dep, 3, 5)
- def test_deprecated_call(self) -> None:
- pytest.deprecated_call(self.dep, 0, 5)
- def test_deprecated_call_ret(self) -> None:
- ret = pytest.deprecated_call(self.dep, 0)
- assert ret == 42
- def test_deprecated_call_preserves(self) -> None:
- # Type ignored because `onceregistry` and `filters` are not
- # documented API.
- onceregistry = warnings.onceregistry.copy() # type: ignore
- filters = warnings.filters[:] # type: ignore
- warn = warnings.warn
- warn_explicit = warnings.warn_explicit
- self.test_deprecated_call_raises()
- self.test_deprecated_call()
- assert onceregistry == warnings.onceregistry # type: ignore
- assert filters == warnings.filters # type: ignore
- assert warn is warnings.warn
- assert warn_explicit is warnings.warn_explicit
- def test_deprecated_explicit_call_raises(self) -> None:
- with pytest.raises(pytest.fail.Exception):
- pytest.deprecated_call(self.dep_explicit, 3)
- def test_deprecated_explicit_call(self) -> None:
- 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) -> None:
- """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) -> None:
- """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) -> None:
- """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) -> None:
- 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) -> None:
- 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) -> None:
- source = "warnings.warn('w1', RuntimeWarning)"
- with pytest.raises(TypeError, match=r".* must be callable"):
- pytest.warns(RuntimeWarning, source) # type: ignore
- def test_several_messages(self) -> None:
- # 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) -> None:
- pytest.warns(
- SyntaxWarning, lambda msg: warnings.warn(msg, SyntaxWarning), "syntax"
- )
- def test_warning_tuple(self) -> None:
- 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) -> None:
- 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) -> None:
- 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) -> None:
- 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) -> None:
- 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) -> None:
- """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) -> None:
- 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) -> None:
- 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) -> None:
- 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) -> None:
- def f() -> int:
- warnings.warn(UserWarning("ohai"))
- return 10
- assert f() == 10
- assert pytest.warns(UserWarning, f) == 10
- assert pytest.warns(UserWarning, f) == 10
- assert pytest.warns(UserWarning, f) != "10" # type: ignore[comparison-overlap]
- def test_warns_context_manager_with_kwargs(self) -> None:
- with pytest.raises(TypeError) as excinfo:
- with pytest.warns(UserWarning, foo="bar"): # type: ignore
- pass
- assert "Unexpected keyword arguments" in str(excinfo.value)