new file mode 100644
@@ -0,0 +1,173 @@
+"Fix pager test flackiness and add stress tests for echo_via_pager"
+
+From 7eb57cff7cd292fbd526eaf861c8f945ddeb07a3 Mon Sep 17 00:00:00 2001
+From: Kevin Deldycke <kevin@deldycke.com>
+Date: Fri, 22 May 2026 11:13:12 +0200
+Subject: [PATCH] Fix pager test race by raising before yield
+
+Upstream-Status: Backport [https://github.com/pallets/click/commit/ffcc7494c991c9197902fdf4995e11b2da3e9762]
+
+Refs: #3470 #2899
+---
+ pyproject.toml | 3 +-
+ src/click/_compat.py | 1 -
+ tests/test_stream_lifecycle.py | 4 +-
+ tests/test_utils.py | 79 +++++++++++++++++++++++++++++++---
+ 4 files changed, 78 insertions(+), 9 deletions(-)
+
+diff --git a/pyproject.toml b/pyproject.toml
+index e39ef21055..ed91e5d741 100644
+--- a/pyproject.toml
++++ b/pyproject.toml
+@@ -185,12 +185,11 @@ commands = [[
+ ]]
+
+ [tool.tox.env.stress]
+-description = "stress tests for stream lifecycle race conditions"
++description = "high-iteration stress tests for race conditions"
+ commands = [[
+ "pytest", "-v", "--tb=short", "-x", "-m", "stress",
+ "--basetemp={env_tmp_dir}",
+ "--override-ini=addopts=",
+- "tests/test_stream_lifecycle.py",
+ {replace = "posargs", default = [], extend = true},
+ ]]
+
+diff --git a/src/click/_compat.py b/src/click/_compat.py
+index 36c0e53975..134c4f3893 100644
+--- a/src/click/_compat.py
++++ b/src/click/_compat.py
+@@ -12,7 +12,6 @@
+
+ CYGWIN = sys.platform.startswith("cygwin")
+ WIN = sys.platform.startswith("win")
+-MAC = sys.platform == "darwin"
+ auto_wrap_for_ansi: t.Callable[[t.TextIO], t.TextIO] | None = None
+ _ansi_re = re.compile(r"\033\[[;?0-9]*[a-zA-Z]")
+
+diff --git a/tests/test_stream_lifecycle.py b/tests/test_stream_lifecycle.py
+index 42e26acc34..9f4aa20868 100644
+--- a/tests/test_stream_lifecycle.py
++++ b/tests/test_stream_lifecycle.py
+@@ -484,7 +484,9 @@ def cli():
+ # Category 6: Stress tests - high-iteration reproducers for race conditions
+ #
+ # These are marked with ``pytest.mark.stress`` so they can be included or
+-# excluded independently. The CI workflow runs them in a separate job.
++# excluded independently. The ``tox -e stress`` env collects every
++# ``pytest.mark.stress`` test across the suite (not just this file), so
++# stress regressions for other components live alongside their unit tests.
+ # ---------------------------------------------------------------------------
+
+
+diff --git a/tests/test_utils.py b/tests/test_utils.py
+index 139cd7324f..1aebf3cc5a 100644
+--- a/tests/test_utils.py
++++ b/tests/test_utils.py
+@@ -16,7 +16,6 @@
+
+ import click._termui_impl
+ import click.utils
+-from click._compat import MAC
+ from click._compat import WIN
+ from click._utils import UNSET
+
+@@ -286,6 +285,11 @@ def _test_gen_func():
+
+
+ def _test_gen_func_fails():
++ raise RuntimeError("This is a test.")
++ yield # unreachable, keeps this a generator function
++
++
++def _test_gen_func_yields_then_fails():
+ yield "test"
+ raise RuntimeError("This is a test.")
+
+@@ -315,10 +319,6 @@ def _test_simulate_keyboard_interrupt(file=None):
+
+
+ @pytest.mark.skipif(WIN, reason="Different behavior on windows.")
+-@pytest.mark.skipif(
+- MAC and sys.version_info >= (3, 13) and not sys._is_gil_enabled(),
+- reason="Generator exception tests are flaky in Python 3.14t on macOS.",
+-)
+ @pytest.mark.parametrize(
+ "pager_cmd", ["cat", "cat ", " cat ", "less", " less", " less "]
+ )
+@@ -456,6 +456,75 @@ def test_echo_via_pager(monkeypatch, capfd, pager_cmd, test, tmp_path):
+ )
+
+
++@pytest.mark.skipif(WIN, reason="Different behavior on windows.")
++def test_echo_via_pager_yields_before_exception(monkeypatch, tmp_path):
++ """A generator that yields then raises: click writes the partial output to
++ the pager stream before propagating the exception.
++
++ The pager file content is intentionally NOT asserted: pipe-drain timing
++ between click and the pager subprocess is outside click's control
++ (#2899, #3470). Spying on ``MaybeStripAnsi.write`` records what click sent
++ to the pager, which is deterministic regardless of scheduling.
++ """
++ monkeypatch.setitem(os.environ, "PAGER", "cat")
++ monkeypatch.setattr(click._termui_impl, "isatty", lambda x: True)
++
++ writes: list[str] = []
++ real_write = click._termui_impl.MaybeStripAnsi.write
++
++ def spy(self, text):
++ writes.append(text)
++ return real_write(self, text)
++
++ monkeypatch.setattr(click._termui_impl.MaybeStripAnsi, "write", spy)
++
++ pager_out_tmp = tmp_path / "pager_out.txt"
++ with (
++ pager_out_tmp.open("w") as f,
++ patch.object(subprocess, "Popen", partial(subprocess.Popen, stdout=f)),
++ pytest.raises(RuntimeError, match="This is a test."),
++ ):
++ click.echo_via_pager(_test_gen_func_yields_then_fails())
++
++ assert "".join(writes) == "test", (
++ f"click should have written the yielded chunk before exception, got {writes!r}"
++ )
++
++
++@pytest.mark.stress
++@pytest.mark.skipif(WIN, reason="Different behavior on windows.")
++@pytest.mark.parametrize("_", range(1000))
++def test_stress_echo_via_pager_exception_cleanup(_, monkeypatch, tmp_path):
++ """Repeated exceptions during ``echo_via_pager`` must not leak subprocesses.
++
++ Regression coverage for the cleanup path in ``_pipepager``'s exception
++ handler (issue #2899, PR #3470). Each iteration spawns a real pager
++ subprocess, raises before any data is written and check there is no leak.
++ """
++ monkeypatch.setitem(os.environ, "PAGER", "cat")
++ monkeypatch.setattr(click._termui_impl, "isatty", lambda x: True)
++
++ spawned: list[subprocess.Popen] = []
++ real_popen = subprocess.Popen
++
++ def tracking_popen(*args, **kwargs):
++ p = real_popen(*args, **kwargs)
++ spawned.append(p)
++ return p
++
++ pager_out_tmp = tmp_path / "pager_out.txt"
++ with (
++ pager_out_tmp.open("w") as f,
++ patch.object(subprocess, "Popen", partial(tracking_popen, stdout=f)),
++ pytest.raises(RuntimeError),
++ ):
++ click.echo_via_pager(_test_gen_func_fails())
++
++ assert spawned, "pager subprocess was never started"
++ for p in spawned:
++ assert p.returncode is not None, "pager subprocess not reaped"
++
++
+ def test_echo_color_flag(monkeypatch, capfd):
+ isatty = True
+ monkeypatch.setattr(click._compat, "isatty", lambda x: isatty)
new file mode 100644
@@ -0,0 +1,29 @@
+Fix an error with newer versions of pytest (9.1.0) by converting the generator into a tuple:
+
+E pytest.PytestRemovedIn10Warning: Passing a non-Collection iterable to parametrize is deprecated.
+E Test: tests/test_basic.py::test_boolean_conversion, argvalues type: chain
+E Please convert to a list or tuple.
+E See https://docs.pytest.org/en/stable/deprecations.html#parametrize-iterators
+ERROR: tests/test_basic.py:tests/test_basic.py
+
+Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
+
+Upstream-Status: Submitted [https://github.com/pallets/click/pull/3597]
+
+Index: click-8.3.3/tests/test_basic.py
+===================================================================
+--- click-8.3.3.orig/tests/test_basic.py
++++ click-8.3.3/tests/test_basic.py
+@@ -258,10 +258,10 @@ def test_boolean_flag(runner, default, a
+
+ @pytest.mark.parametrize(
+ ("value", "expect"),
+- chain(
++ tuple(chain(
+ ((x, "True") for x in ("1", "true", "t", "yes", "y", "on")),
+ ((x, "False") for x in ("0", "false", "f", "no", "n", "off")),
+- ),
++ )),
+ )
+ def test_boolean_conversion(runner, value, expect):
+ @click.command()
@@ -8,6 +8,8 @@ HOMEPAGE = "http://click.pocoo.org/"
LICENSE = "BSD-3-Clause"
LIC_FILES_CHKSUM = "file://LICENSE.txt;md5=1fa98232fd645608937a0fdc82e999b8"
+SRC_URI += "file://pytest-fix.patch"
+SRC_URI += "file://ffcc7494c991c9197902fdf4995e11b2da3e9762.patch"
SRC_URI[sha256sum] = "918b5633eddf6b41c32d4f454bf0de810065c74e3f7dbf8ee5452f8be88d3e96"
inherit pypi python_flit_core ptest-python-pytest
Submit a patch upstream to allow it to work with the new version of pytest. Also backport a "flaky test" fix which may help issues we're seeing on the autobuilder. Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org> --- ...7494c991c9197902fdf4995e11b2da3e9762.patch | 173 ++++++++++++++++++ .../python/python3-click/pytest-fix.patch | 29 +++ .../python/python3-click_8.4.1.bb | 2 + 3 files changed, 204 insertions(+) create mode 100644 meta/recipes-devtools/python/python3-click/ffcc7494c991c9197902fdf4995e11b2da3e9762.patch create mode 100644 meta/recipes-devtools/python/python3-click/pytest-fix.patch