from __future__ import absolute_import, division, print_function import warnings import re import py import sys import pytest from _pytest.recwarn import WarningsRecorder def test_recwarn_functional(testdir): reprec = testdir.inline_runsource(""" import warnings def test_method(recwarn): warnings.warn("hello") warn = recwarn.pop() assert isinstance(warn.message, UserWarning) """) res = reprec.countoutcomes() assert tuple(res) == (1, 0, 0), res class TestWarningsRecorderChecker(object): def test_recording(self): rec = WarningsRecorder() with rec: assert not rec.list py.std.warnings.warn_explicit("hello", UserWarning, "xyz", 13) assert len(rec.list) == 1 py.std.warnings.warn(DeprecationWarning("hello")) assert len(rec.list) == 2 warn = rec.pop() assert str(warn.message) == "hello" l = rec.list rec.clear() assert len(rec.list) == 0 assert l is rec.list pytest.raises(AssertionError, "rec.pop()") 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(object): """test pytest.deprecated_call()""" def dep(self, i, j=None): if i == 0: py.std.warnings.warn("is deprecated", DeprecationWarning, stacklevel=1) return 42 def dep_explicit(self, i): if i == 0: py.std.warnings.warn_explicit("dep_explicit", category=DeprecationWarning, filename="hello", lineno=3) def test_deprecated_call_raises(self): with pytest.raises(AssertionError) as excinfo: pytest.deprecated_call(self.dep, 3, 5) assert 'Did not produce' in str(excinfo) 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 = py.std.warnings.onceregistry.copy() filters = py.std.warnings.filters[:] warn = py.std.warnings.warn warn_explicit = py.std.warnings.warn_explicit self.test_deprecated_call_raises() self.test_deprecated_call() assert onceregistry == py.std.warnings.onceregistry assert filters == py.std.warnings.filters assert warn is py.std.warnings.warn assert warn_explicit is py.std.warnings.warn_explicit def test_deprecated_explicit_call_raises(self): with pytest.raises(AssertionError): 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 = 'Did not produce DeprecationWarning or PendingDeprecationWarning' with pytest.raises(AssertionError, matches=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]) 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(AssertionError): pytest.deprecated_call(f) with pytest.raises(AssertionError): with pytest.deprecated_call(): f() def test_deprecated_function_already_called(self, testdir): """deprecated_call should be able to catch a call to a deprecated function even if that function has already been called in the same module. See #1190. """ testdir.makepyfile(""" import warnings import pytest def deprecated_function(): warnings.warn("deprecated", DeprecationWarning) def test_one(): deprecated_function() def test_two(): pytest.deprecated_call(deprecated_function) """) result = testdir.runpytest() # for some reason in py26 catch_warnings manages to catch the deprecation warning # from deprecated_function(), even with default filters active (which ignore deprecation # warnings) py26 = sys.version_info[:2] == (2, 6) expected = '*=== 2 passed in *===' if not py26 else '*=== 2 passed, 1 warnings in *===' result.stdout.fnmatch_lines(expected) class TestWarns(object): 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)" pytest.warns(RuntimeWarning, source1) pytest.raises(pytest.fail.Exception, lambda: pytest.warns(UserWarning, source2)) pytest.warns(RuntimeWarning, source3) 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*'])