diff --git a/CHANGES.rst b/CHANGES.rst index 493cf2d88..cfc786ad8 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -15,6 +15,8 @@ Unreleased empty. :issue:`3019` :pr:`3021` - When ``Sentinel.UNSET`` is found during parsing, it will skip calls to ``type_cast_value``. :issue:`3069` :pr:`3090` +- Fix regression related to ``click.testing.StreamMixer`` finalization in ``CliRunner`` + that introduced a race condition. :issue:`3110` :pr:`3140` Version 8.3.0 -------------- diff --git a/src/click/testing.py b/src/click/testing.py index f6f60b809..e734ddd08 100644 --- a/src/click/testing.py +++ b/src/click/testing.py @@ -72,16 +72,20 @@ class BytesIOCopy(io.BytesIO): .. versionadded:: 8.2 """ + copy_to: io.BytesIO + def __init__(self, copy_to: io.BytesIO) -> None: super().__init__() self.copy_to = copy_to def flush(self) -> None: + if not self.copy_to.closed: + self.copy_to.flush() super().flush() - self.copy_to.flush() def write(self, b: ReadableBuffer) -> int: - self.copy_to.write(b) + if not self.copy_to.closed: + self.copy_to.write(b) return super().write(b) @@ -100,15 +104,15 @@ def __init__(self) -> None: def __del__(self) -> None: """ - Guarantee that embedded file-like objects are closed in a + Guarantee that embedded file-like objects are deleted in a predictable order, protecting against races between - self.output being closed and other streams being flushed on close + self.output being deleted and other streams being flushed on deletion. .. versionadded:: 8.2.2 """ - self.stderr.close() - self.stdout.close() - self.output.close() + del self.stdout + del self.stderr + del self.output class _NamedTextIOWrapper(io.TextIOWrapper):