import re import warnings import pytest from _pytest.recwarn import WarningsRecorder from _pytest.warning_types import PytestDeprecationWarning 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_strings(self): # different messages, b/c Python suppresses multiple identical warnings source1 = "warnings.warn('w1', RuntimeWarning)" source2 = "warnings.warn('w2', RuntimeWarning)" source3 = "warnings.warn('w3', RuntimeWarning)" with pytest.warns(PytestDeprecationWarning) as warninfo: # yo dawg pytest.warns(RuntimeWarning, source1) pytest.raises( pytest.fail.Exception, lambda: pytest.warns(UserWarning, source2) ) pytest.warns(RuntimeWarning, source3) assert len(warninfo) == 3 for w in warninfo: assert w.filename == __file__ msg, = w.message.args assert msg.startswith("warns(..., 'code(as_a_string)') is deprecated") 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