Skip to content

Commit d4ed88f

Browse files
committed
Recover yield-then-raise coverage via stream-write spy
1 parent 49b2378 commit d4ed88f

1 file changed

Lines changed: 40 additions & 0 deletions

File tree

tests/test_utils.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,11 @@ def _test_gen_func_fails():
289289
yield # unreachable, keeps this a generator function
290290

291291

292+
def _test_gen_func_yields_then_fails():
293+
yield "test"
294+
raise RuntimeError("This is a test.")
295+
296+
292297
def _test_gen_func_echo(file=None):
293298
yield "test"
294299
click.echo("hello", file=file)
@@ -451,6 +456,41 @@ def test_echo_via_pager(monkeypatch, capfd, pager_cmd, test, tmp_path):
451456
)
452457

453458

459+
@pytest.mark.skipif(WIN, reason="Different behavior on windows.")
460+
def test_echo_via_pager_yields_before_exception(monkeypatch, tmp_path):
461+
"""A generator that yields then raises: click writes the partial output to
462+
the pager stream before propagating the exception.
463+
464+
The pager file content is intentionally NOT asserted: pipe-drain timing
465+
between click and the pager subprocess is outside click's control
466+
(#2899, #3470). Spying on ``MaybeStripAnsi.write`` records what click sent
467+
to the pager, which is deterministic regardless of scheduling.
468+
"""
469+
monkeypatch.setitem(os.environ, "PAGER", "cat")
470+
monkeypatch.setattr(click._termui_impl, "isatty", lambda x: True)
471+
472+
writes: list[str] = []
473+
real_write = click._termui_impl.MaybeStripAnsi.write
474+
475+
def spy(self, text):
476+
writes.append(text)
477+
return real_write(self, text)
478+
479+
monkeypatch.setattr(click._termui_impl.MaybeStripAnsi, "write", spy)
480+
481+
pager_out_tmp = tmp_path / "pager_out.txt"
482+
with (
483+
pager_out_tmp.open("w") as f,
484+
patch.object(subprocess, "Popen", partial(subprocess.Popen, stdout=f)),
485+
pytest.raises(RuntimeError, match="This is a test."),
486+
):
487+
click.echo_via_pager(_test_gen_func_yields_then_fails())
488+
489+
assert "".join(writes) == "test", (
490+
f"click should have written the yielded chunk before exception, got {writes!r}"
491+
)
492+
493+
454494
@pytest.mark.stress
455495
@pytest.mark.skipif(WIN, reason="Different behavior on windows.")
456496
@pytest.mark.parametrize("_", range(1000))

0 commit comments

Comments
 (0)