Skip to content

Commit c79694c

Browse files
Kasper JungeRalphify
authored andcommitted
refactor: extract _format_summary from ConsoleEmitter for testability
The run summary formatting logic (counter arithmetic, category filtering, pluralization) was embedded inside the _on_run_stopped rendering method. Extracted it into a pure _format_summary function that can be unit-tested without Rich, and added direct tests for all summary variants. Co-authored-by: Ralphify <noreply@ralphify.co>
1 parent 4a2be8d commit c79694c

File tree

2 files changed

+55
-18
lines changed

2 files changed

+55
-18
lines changed

src/ralphify/_console_emitter.py

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,24 @@ def _plural(count: int, word: str) -> str:
4646
return f"{count} {word}{'s' if count != 1 else ''}"
4747

4848

49+
def _format_summary(
50+
total: int, completed: int, failed: int, timed_out_count: int
51+
) -> str:
52+
"""Build a plain-text run summary string from iteration counters.
53+
54+
``timed_out_count`` is a subset of ``failed`` — non-timeout failures
55+
and timeouts are shown as separate categories for clarity.
56+
"""
57+
non_timeout_failures = failed - timed_out_count
58+
parts = [f"{completed} succeeded"]
59+
if non_timeout_failures:
60+
parts.append(f"{non_timeout_failures} failed")
61+
if timed_out_count:
62+
parts.append(f"{timed_out_count} timed out")
63+
detail = ", ".join(parts)
64+
return f"{_plural(total, 'iteration')} {_ICON_DASH} {detail}"
65+
66+
4967
class _IterationSpinner:
5068
"""Rich renderable that shows a spinner with elapsed time."""
5169

@@ -164,21 +182,8 @@ def _on_run_stopped(self, data: RunStoppedData) -> None:
164182
if data["reason"] != STOP_COMPLETED:
165183
return
166184

167-
total = data["total"]
168-
completed = data["completed"]
169-
failed = data["failed"]
170-
timed_out_count = data["timed_out_count"]
171-
172-
# timed_out_count is a subset of failed — show non-timeout failures
173-
# and timeouts as separate categories for clarity.
174-
non_timeout_failures = failed - timed_out_count
175-
parts = [f"{completed} succeeded"]
176-
if non_timeout_failures:
177-
parts.append(f"{non_timeout_failures} failed")
178-
if timed_out_count:
179-
parts.append(f"{timed_out_count} timed out")
180-
detail = ", ".join(parts)
181-
self._console.print(f"\n[bold {_brand.BLUE}]──────────────────────[/]")
182-
self._console.print(
183-
f"[bold {_brand.GREEN}]Done:[/] {_plural(total, 'iteration')} {_ICON_DASH} {detail}"
185+
summary = _format_summary(
186+
data["total"], data["completed"], data["failed"], data["timed_out_count"]
184187
)
188+
self._console.print(f"\n[bold {_brand.BLUE}]──────────────────────[/]")
189+
self._console.print(f"[bold {_brand.GREEN}]Done:[/] {summary}")

tests/test_console_emitter.py

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import pytest
44
from rich.console import Console
55

6-
from ralphify._console_emitter import ConsoleEmitter, _IterationSpinner
6+
from ralphify._console_emitter import ConsoleEmitter, _IterationSpinner, _format_summary
77
from ralphify._events import Event, EventType
88

99

@@ -473,6 +473,38 @@ def test_completed_plural_iterations(self):
473473
assert "3 iterations" in output
474474

475475

476+
class TestFormatSummary:
477+
def test_all_succeeded(self):
478+
assert _format_summary(3, 3, 0, 0) == "3 iterations — 3 succeeded"
479+
480+
def test_singular_iteration(self):
481+
assert _format_summary(1, 1, 0, 0) == "1 iteration — 1 succeeded"
482+
483+
def test_with_failures(self):
484+
result = _format_summary(5, 4, 1, 0)
485+
assert "4 succeeded" in result
486+
assert "1 failed" in result
487+
assert "timed out" not in result
488+
489+
def test_with_timeouts_only(self):
490+
result = _format_summary(3, 2, 1, 1)
491+
assert "2 succeeded" in result
492+
assert "1 timed out" in result
493+
# timed_out_count is subset of failed — no separate "failed" category
494+
assert "failed" not in result
495+
496+
def test_with_mixed_failures_and_timeouts(self):
497+
result = _format_summary(5, 3, 2, 1)
498+
assert "3 succeeded" in result
499+
assert "1 failed" in result
500+
assert "1 timed out" in result
501+
502+
def test_all_failed(self):
503+
result = _format_summary(3, 0, 3, 0)
504+
assert "0 succeeded" in result
505+
assert "3 failed" in result
506+
507+
476508
class TestIterationSpinner:
477509
def test_renders_elapsed_time(self):
478510
spinner = _IterationSpinner()

0 commit comments

Comments
 (0)