From 603d81ef2fab053b2561c1fbd305ca5c2d77e283 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Thu, 26 Nov 2015 16:27:20 -0200 Subject: [PATCH] deprecated_call now uses monkey patching strategy to capture warnings similar to what we had in 2.7, with a few enhancements Fix #1190 --- CHANGELOG | 8 +++++ _pytest/recwarn.py | 42 +++++++++++++++---------- testing/test_recwarn.py | 69 +++++++++++++++++++++++++++-------------- 3 files changed, 79 insertions(+), 40 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index ee3480e7b..ae766aa0d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,11 @@ +2.8.4.dev +--------- + +- fix #1190: ``deprecated_call()`` now works when the deprecated + function has been already called by another test in the same + module. Thanks Mikhail Chernykh for the report and Bruno Oliveira for the + PR. + 2.8.3 ----- diff --git a/_pytest/recwarn.py b/_pytest/recwarn.py index 129d8250c..c4f9dc322 100644 --- a/_pytest/recwarn.py +++ b/_pytest/recwarn.py @@ -29,29 +29,39 @@ def pytest_namespace(): def deprecated_call(func, *args, **kwargs): - """ assert that calling ``func(*args, **kwargs)`` - triggers a DeprecationWarning. - """ - l = [] - oldwarn_explicit = getattr(warnings, 'warn_explicit') - def warn_explicit(*args, **kwargs): - l.append(args) - oldwarn_explicit(*args, **kwargs) - oldwarn = getattr(warnings, 'warn') - def warn(*args, **kwargs): - l.append(args) - oldwarn(*args, **kwargs) + """ assert that calling ``func(*args, **kwargs)`` triggers a + ``DeprecationWarning`` or ``PendingDeprecationWarning``. + Note: we cannot use WarningsRecorder here because it is still subject + to the mechanism that prevents warnings of the same type from being + triggered twice for the same module. See #1190. + """ + categories = [] + + def warn_explicit(message, category, *args, **kwargs): + categories.append(category) + old_warn_explicit(message, category, *args, **kwargs) + + def warn(message, category=None, **kwargs): + if isinstance(message, Warning): + categories.append(message.__class__) + else: + categories.append(category) + old_warn(message, category, *args, **kwargs) + + old_warn = warnings.warn + old_warn_explicit = warnings.warn_explicit warnings.warn_explicit = warn_explicit warnings.warn = warn try: ret = func(*args, **kwargs) finally: - warnings.warn_explicit = oldwarn_explicit - warnings.warn = oldwarn - if not l: + warnings.warn_explicit = old_warn_explicit + warnings.warn = old_warn + deprecation_categories = (DeprecationWarning, PendingDeprecationWarning) + if not any(issubclass(c, deprecation_categories) for c in categories): __tracebackhide__ = True - raise AssertionError("%r did not produce DeprecationWarning" %(func,)) + raise AssertionError("%r did not produce DeprecationWarning" % (func,)) return ret diff --git a/testing/test_recwarn.py b/testing/test_recwarn.py index 03bbd1eb4..c426ea82e 100644 --- a/testing/test_recwarn.py +++ b/testing/test_recwarn.py @@ -63,32 +63,30 @@ class TestWarningsRecorderChecker(object): with rec: pass # can't enter twice -# -# ============ test pytest.deprecated_call() ============== -# - -def dep(i): - if i == 0: - py.std.warnings.warn("is deprecated", DeprecationWarning) - return 42 - -reg = {} -def dep_explicit(i): - if i == 0: - py.std.warnings.warn_explicit("dep_explicit", category=DeprecationWarning, - filename="hello", lineno=3) class TestDeprecatedCall(object): + """test pytest.deprecated_call()""" + + def dep(self, i): + if i == 0: + py.std.warnings.warn("is deprecated", DeprecationWarning) + 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): - excinfo = pytest.raises(AssertionError, - "pytest.deprecated_call(dep, 3)") + with pytest.raises(AssertionError) as excinfo: + pytest.deprecated_call(self.dep, 3) assert str(excinfo).find("did not produce") != -1 def test_deprecated_call(self): - pytest.deprecated_call(dep, 0) + pytest.deprecated_call(self.dep, 0) def test_deprecated_call_ret(self): - ret = pytest.deprecated_call(dep, 0) + ret = pytest.deprecated_call(self.dep, 0) assert ret == 42 def test_deprecated_call_preserves(self): @@ -104,25 +102,48 @@ class TestDeprecatedCall(object): assert warn_explicit is py.std.warnings.warn_explicit def test_deprecated_explicit_call_raises(self): - pytest.raises(AssertionError, - "pytest.deprecated_call(dep_explicit, 3)") + with pytest.raises(AssertionError): + pytest.deprecated_call(self.dep_explicit, 3) def test_deprecated_explicit_call(self): - pytest.deprecated_call(dep_explicit, 0) - pytest.deprecated_call(dep_explicit, 0) + pytest.deprecated_call(self.dep_explicit, 0) + pytest.deprecated_call(self.dep_explicit, 0) def test_deprecated_call_pending(self): - f = lambda: py.std.warnings.warn(PendingDeprecationWarning("hi")) + def f(): + py.std.warnings.warn(PendingDeprecationWarning("hi")) pytest.deprecated_call(f) def test_deprecated_call_specificity(self): other_warnings = [Warning, UserWarning, SyntaxWarning, RuntimeWarning, FutureWarning, ImportWarning, UnicodeWarning] for warning in other_warnings: - f = lambda: py.std.warnings.warn(warning("hi")) + def f(): + py.std.warnings.warn(warning("hi")) with pytest.raises(AssertionError): 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() + result.stdout.fnmatch_lines('*=== 2 passed in *===') + class TestWarns(object): def test_strings(self):