diff --git a/src/_pytest/capture.py b/src/_pytest/capture.py index 6d98676be5f..48b2f153de7 100644 --- a/src/_pytest/capture.py +++ b/src/_pytest/capture.py @@ -449,6 +449,8 @@ class SysCapture(SysCaptureBase[str]): def snap(self) -> str: self._assert_state("snap", ("started", "suspended")) + if getattr(self.tmpfile, "closed", False): + return self.EMPTY_BUFFER assert isinstance(self.tmpfile, CaptureIO) res = self.tmpfile.getvalue() self.tmpfile.seek(0) @@ -566,6 +568,8 @@ class FDCaptureBinary(FDCaptureBase[bytes]): def snap(self) -> bytes: self._assert_state("snap", ("started", "suspended")) + if getattr(self.tmpfile, "closed", False): + return self.EMPTY_BUFFER self.tmpfile.seek(0) res = self.tmpfile.buffer.read() self.tmpfile.seek(0) @@ -588,6 +592,8 @@ class FDCapture(FDCaptureBase[str]): def snap(self) -> str: self._assert_state("snap", ("started", "suspended")) + if getattr(self.tmpfile, "closed", False): + return self.EMPTY_BUFFER self.tmpfile.seek(0) res = self.tmpfile.read() self.tmpfile.seek(0) diff --git a/testing/test_capture.py b/testing/test_capture.py index 7aaba99fe43..dc1edafd5d5 100644 --- a/testing/test_capture.py +++ b/testing/test_capture.py @@ -1750,3 +1750,30 @@ def pytest_terminal_summary(config): match = re.search(r"^value: '(.*)'\r?$", rest, re.MULTILINE) assert match is not None assert match.group(1) == "hi" + + +def test_snap_on_closed_tmpfile() -> None: + """Regression test for #14528: snap() should not crash when tmpfile is closed.""" + # FDCapture + cap = capture.FDCapture(1) + cap.start() + cap.done() + assert cap.snap() == "" + + # FDCaptureBinary + capb = capture.FDCaptureBinary(1) + capb.start() + capb.done() + assert capb.snap() == b"" + + # SysCapture + caps = capture.SysCapture(1) + caps.start() + caps.done() + assert caps.snap() == "" + + # SysCaptureBinary + capsb = capture.SysCaptureBinary(1) + capsb.start() + capsb.done() + assert capsb.snap() == b""