From f1aa9226ac5b1962fdad442652765d5e589c7137 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Fri, 16 Feb 2024 12:22:52 +0200 Subject: [PATCH] [8.0.x] recwarn: let base exceptions propagate through `pytest.warns` again (cherry picked from commit 718cd400152d64e6e6d47dc96f61bf1e6c06011a) --- changelog/11907.bugfix.rst | 1 + src/_pytest/recwarn.py | 12 +++++++ testing/test_recwarn.py | 70 ++++++++++++++++++++++++++++++++++++-- 3 files changed, 81 insertions(+), 2 deletions(-) create mode 100644 changelog/11907.bugfix.rst diff --git a/changelog/11907.bugfix.rst b/changelog/11907.bugfix.rst new file mode 100644 index 00000000000..10aa39aeb0c --- /dev/null +++ b/changelog/11907.bugfix.rst @@ -0,0 +1 @@ +Fix a regression in pytest 8.0.0 whereby calling :func:`pytest.skip` and similar control-flow exceptions within a :func:`pytest.warns()` block would get suppressed instead of propagating. diff --git a/src/_pytest/recwarn.py b/src/_pytest/recwarn.py index ae9df014e50..52b2b11cf0c 100644 --- a/src/_pytest/recwarn.py +++ b/src/_pytest/recwarn.py @@ -21,6 +21,7 @@ from _pytest.deprecated import check_ispytest from _pytest.deprecated import WARNS_NONE_ARG from _pytest.fixtures import fixture +from _pytest.outcomes import Exit from _pytest.outcomes import fail @@ -312,6 +313,17 @@ def __exit__( # nothing to do in this deprecated case, see WARNS_NONE_ARG above return + # BaseExceptions like pytest.{skip,fail,xfail,exit} or Ctrl-C within + # pytest.warns should *not* trigger "DID NOT WARN" and get suppressed + # when the warning doesn't happen. Control-flow exceptions should always + # propagate. + if exc_val is not None and ( + not isinstance(exc_val, Exception) + # Exit is an Exception, not a BaseException, for some reason. + or isinstance(exc_val, Exit) + ): + return + def found_str(): return pformat([record.message for record in self], indent=2) diff --git a/testing/test_recwarn.py b/testing/test_recwarn.py index 3e9e8e9fdba..1db2fe0a12d 100644 --- a/testing/test_recwarn.py +++ b/testing/test_recwarn.py @@ -3,9 +3,10 @@ from typing import Type import warnings -from _pytest.pytester import Pytester -from _pytest.recwarn import WarningsRecorder import pytest +from pytest import ExitCode +from pytest import Pytester +from pytest import WarningsRecorder def test_recwarn_stacklevel(recwarn: WarningsRecorder) -> None: @@ -484,3 +485,68 @@ def test_catch_warning_within_raise(self) -> None: with pytest.raises(ValueError, match="some exception"): warnings.warn("some warning", category=FutureWarning) raise ValueError("some exception") + + def test_skip_within_warns(self, pytester: Pytester) -> None: + """Regression test for #11907.""" + pytester.makepyfile( + """ + import pytest + + def test_it(): + with pytest.warns(Warning): + pytest.skip("this is OK") + """, + ) + + result = pytester.runpytest() + assert result.ret == ExitCode.OK + result.assert_outcomes(skipped=1) + + def test_fail_within_warns(self, pytester: Pytester) -> None: + """Regression test for #11907.""" + pytester.makepyfile( + """ + import pytest + + def test_it(): + with pytest.warns(Warning): + pytest.fail("BOOM") + """, + ) + + result = pytester.runpytest() + assert result.ret == ExitCode.TESTS_FAILED + result.assert_outcomes(failed=1) + assert "DID NOT WARN" not in str(result.stdout) + + def test_exit_within_warns(self, pytester: Pytester) -> None: + """Regression test for #11907.""" + pytester.makepyfile( + """ + import pytest + + def test_it(): + with pytest.warns(Warning): + pytest.exit("BOOM") + """, + ) + + result = pytester.runpytest() + assert result.ret == ExitCode.INTERRUPTED + result.assert_outcomes() + + def test_keyboard_interrupt_within_warns(self, pytester: Pytester) -> None: + """Regression test for #11907.""" + pytester.makepyfile( + """ + import pytest + + def test_it(): + with pytest.warns(Warning): + raise KeyboardInterrupt() + """, + ) + + result = pytester.runpytest_subprocess() + assert result.ret == ExitCode.INTERRUPTED + result.assert_outcomes()