From e37cad2a8b6c32ee0d1597e4d43e778910ccd43a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Robert?= Date: Fri, 4 Jul 2025 12:03:29 +0200 Subject: [PATCH 1/2] BUG/TST: fix a race in `click.testing.StreamMixer`'s finalization, seen in a multi-threaded context --- src/click/testing.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/click/testing.py b/src/click/testing.py index 7c0e8741e2..04d263eeac 100644 --- a/src/click/testing.py +++ b/src/click/testing.py @@ -99,6 +99,18 @@ def __init__(self) -> None: self.stdout: io.BytesIO = BytesIOCopy(copy_to=self.output) self.stderr: io.BytesIO = BytesIOCopy(copy_to=self.output) + def __del__(self) -> None: + """ + Guarantee that embedded file-like objects are closed in a + predictable order, protecting against races between + self.output being closed and other streams being flushed on close + + .. versionadded:: 8.2.2 + """ + self.stderr.close() + self.stdout.close() + self.output.close() + class _NamedTextIOWrapper(io.TextIOWrapper): def __init__( From 4679b1cbc35d05e1c250affaf8a3b7156bad6319 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Robert?= Date: Fri, 4 Jul 2025 12:16:27 +0200 Subject: [PATCH 2/2] TST: adjust tests --- tests/test_testing.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/test_testing.py b/tests/test_testing.py index 0fd6973ae8..11fe29dc5d 100644 --- a/tests/test_testing.py +++ b/tests/test_testing.py @@ -448,8 +448,7 @@ def test_isolation_stderr_errors(): with runner.isolation() as (_, err, _): click.echo("\udce2", err=True, nl=False) - - assert err.getvalue() == b"\\udce2" + assert err.getvalue() == b"\\udce2" def test_isolation_flushes_unflushed_stderr(): @@ -460,8 +459,7 @@ def test_isolation_flushes_unflushed_stderr(): with runner.isolation() as (_, err, _): click.echo("\udce2", err=True, nl=False) - - assert err.getvalue() == b"\\udce2" + assert err.getvalue() == b"\\udce2" @click.command() def cli():