From f10de7df523fb654fded1df730d752fc7ae5eb64 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Wed, 26 Aug 2026 11:07:19 +0200 Subject: [PATCH] Merge pull request #14284 from oaksprout/fix-nested-caplog-filtering Fix nested caplog.filtering early removal (cherry picked from commit c7f9e75c5d3b04bf96e8389a9eac6d98da2be8f4) --- changelog/14189.bugfix.rst | 1 + src/_pytest/logging.py | 12 ++++++++---- testing/logging/test_fixture.py | 34 +++++++++++++++++++++++++++++++++ 3 files changed, 43 insertions(+), 4 deletions(-) create mode 100644 changelog/14189.bugfix.rst diff --git a/changelog/14189.bugfix.rst b/changelog/14189.bugfix.rst new file mode 100644 index 00000000000..cdb0680e35a --- /dev/null +++ b/changelog/14189.bugfix.rst @@ -0,0 +1 @@ +Nested usage of :meth:`caplog.filtering ` no longer removes filters early if they were already present. diff --git a/src/_pytest/logging.py b/src/_pytest/logging.py index 3204d43ee01..5b7092375b0 100644 --- a/src/_pytest/logging.py +++ b/src/_pytest/logging.py @@ -600,11 +600,15 @@ def filtering(self, filter_: logging.Filter) -> Generator[None]: .. versionadded:: 7.5 """ - self.handler.addFilter(filter_) - try: + already_present = filter_ in self.handler.filters + if already_present: yield - finally: - self.handler.removeFilter(filter_) + else: + try: + self.handler.addFilter(filter_) + yield + finally: + self.handler.removeFilter(filter_) @fixture diff --git a/testing/logging/test_fixture.py b/testing/logging/test_fixture.py index c98b7d84258..95c0f44b7f5 100644 --- a/testing/logging/test_fixture.py +++ b/testing/logging/test_fixture.py @@ -206,6 +206,40 @@ def filter(self, record: logging.LogRecord) -> bool: assert unfiltered_tuple == ("test_fixture", 20, "handler call") +class DropAllFilter(logging.Filter): + def filter(self, record: logging.LogRecord) -> bool: + return False + + +def test_with_statement_nested_filtering(caplog: pytest.LogCaptureFixture) -> None: + drop_all = DropAllFilter() + + with caplog.filtering(drop_all): + logger.warning("Will not be captured") + with caplog.filtering(drop_all): + logger.warning("Will also not be captured") + logger.warning("Should not be captured either") + + assert caplog.records == [] + + +def test_with_statement_filtering_already_present( + caplog: pytest.LogCaptureFixture, +) -> None: + drop_all = DropAllFilter() + + caplog.handler.addFilter(drop_all) + try: + with caplog.filtering(drop_all): + logger.warning("Should not be captured") + + # After context manager, filter should STILL be present because it was already there + logger.warning("Should still not be captured") + assert caplog.records == [] + finally: + caplog.handler.removeFilter(drop_all) + + @pytest.mark.parametrize( "level_str,expected_disable_level", [