From 2df2db5336e2072e15e49b34fdbcffb2ec731f9e Mon Sep 17 00:00:00 2001 From: Casey Brooks Date: Thu, 25 Dec 2025 19:54:48 +0000 Subject: [PATCH] fix(logging): keep caplog records in sync (#5) --- src/_pytest/assertion/rewrite.py | 6 ++++- src/_pytest/logging.py | 8 +++++- testing/logging/test_fixture.py | 43 ++++++++++++++++++++++++++++++++ 3 files changed, 55 insertions(+), 2 deletions(-) diff --git a/src/_pytest/assertion/rewrite.py b/src/_pytest/assertion/rewrite.py index 81096764e04..609839676c7 100644 --- a/src/_pytest/assertion/rewrite.py +++ b/src/_pytest/assertion/rewrite.py @@ -16,6 +16,7 @@ from pathlib import Path from pathlib import PurePath from typing import Callable +from typing import cast from typing import Dict from typing import IO from typing import Iterable @@ -281,7 +282,10 @@ def get_resource_reader(self, name: str) -> importlib.abc.TraversableResources: else: from importlib.resources.readers import FileReader - return FileReader(types.SimpleNamespace(path=self._rewritten_names[name])) + return cast( + importlib.abc.TraversableResources, + FileReader(types.SimpleNamespace(path=self._rewritten_names[name])), + ) def _write_pyc_fp( diff --git a/src/_pytest/logging.py b/src/_pytest/logging.py index a4f4214b137..cdc84237479 100644 --- a/src/_pytest/logging.py +++ b/src/_pytest/logging.py @@ -440,7 +440,13 @@ def messages(self) -> List[str]: def clear(self) -> None: """Reset the list of log records and the captured log text.""" - self.handler.reset() + self.handler.records.clear() + stream = self.handler.stream + try: + stream.seek(0) + stream.truncate(0) + except (AttributeError, ValueError, OSError): + self.handler.stream = StringIO() def set_level(self, level: Union[int, str], logger: Optional[str] = None) -> None: """Set the level of a logger for the duration of a test. diff --git a/testing/logging/test_fixture.py b/testing/logging/test_fixture.py index bcb20de5805..2b9ebbb472e 100644 --- a/testing/logging/test_fixture.py +++ b/testing/logging/test_fixture.py @@ -151,6 +151,49 @@ def test_clear(caplog): assert not caplog.text +def test_clear_keeps_call_records_in_sync(caplog): + caplog.set_level(logging.INFO) + call_records = caplog.get_records("call") + assert call_records is caplog.records + + logger.info("call before clear") + assert [record.message for record in call_records] == ["call before clear"] + assert [record.message for record in caplog.records] == ["call before clear"] + + caplog.clear() + + assert call_records is caplog.records + assert call_records == [] + assert caplog.get_records("call") is caplog.records + + logger.info("call after clear") + assert [record.message for record in call_records] == ["call after clear"] + assert [record.message for record in caplog.get_records("call")] == [ + "call after clear" + ] + + +def test_clear_preserves_other_phases(caplog, logging_during_setup_and_teardown): + assert [record.message for record in caplog.get_records("setup")] == ["a_setup_log"] + assert caplog.get_records("teardown") == [] + + logger.info("call message before clear") + assert [record.message for record in caplog.get_records("call")] == [ + "call message before clear" + ] + + caplog.clear() + + assert [record.message for record in caplog.get_records("setup")] == ["a_setup_log"] + assert caplog.get_records("call") == [] + assert caplog.get_records("teardown") == [] + + logger.info("call message after clear") + assert [record.message for record in caplog.get_records("call")] == [ + "call message after clear" + ] + + @pytest.fixture def logging_during_setup_and_teardown(caplog): caplog.set_level("INFO")