diff --git a/changelog/4617.bugfix.rst b/changelog/4617.bugfix.rst new file mode 100644 index 00000000000..166378efd63 --- /dev/null +++ b/changelog/4617.bugfix.rst @@ -0,0 +1 @@ +Fixed ``pytest.warns`` bug when context manager is reused (e.g. multiple parametrization). diff --git a/src/_pytest/recwarn.py b/src/_pytest/recwarn.py index f39f7aee789..a58c75d3a72 100644 --- a/src/_pytest/recwarn.py +++ b/src/_pytest/recwarn.py @@ -192,6 +192,10 @@ def __exit__(self, *exc_info): warnings.warn = self._saved_warn super(WarningsRecorder, self).__exit__(*exc_info) + # Built-in catch_warnings does not reset entered state so we do it + # manually here for this context manager to become reusable. + self._entered = False + class WarningsChecker(WarningsRecorder): def __init__(self, expected_warning=None, match_expr=None): diff --git a/testing/test_warnings.py b/testing/test_warnings.py index 3bac9a545d0..45e19614925 100644 --- a/testing/test_warnings.py +++ b/testing/test_warnings.py @@ -683,3 +683,27 @@ def test_false_function_no_warn(self, testdir): self.create_file(testdir, False) result = testdir.runpytest() result.stdout.fnmatch_lines(["*1 failed in*"]) + + +def test_warningschecker_twice(testdir): + """Issue #4617""" + + testdir.makepyfile( + """ + import pytest + import warnings + + @pytest.mark.parametrize("other", [1, 2]) + @pytest.mark.parametrize("expectation", [ + pytest.warns(DeprecationWarning, + match="Message A"), + pytest.warns(DeprecationWarning, + match="Message A"), + ]) + def test_parametrized_warnings(other, expectation): + with expectation: + warnings.warn("Message A", DeprecationWarning) + """ + ) + result = testdir.runpytest() + result.stdout.fnmatch_lines(["* 4 passed in *"])