Coverage for src / ocarina / opinionated / plugins / reports / pretty_print_results.py: 0.00%
68 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-06-04 15:56 +0200
« prev ^ index » next coverage.py v7.13.5, created at 2026-06-04 15:56 +0200
1# ruff: noqa: T201
2"""Displays the full test results.
4This module provides a pretty printer to display, in a structured and readable
5way, the results of a complete test suite. It supports hierarchical display
6(campaigns > suites > cases) with optional colorization.
8Architecture:
9 - Recursive traversal of the results structure
10 - Dispatch pattern to map outcomes → textual statuses
11 - Conditional colorization via ANSI codes
12 - Statistics accumulation for final summary
14Output format:
15 Campaign
16 • Suite
17 > Test case 1
18 » PASSED
19 > Test case 2
20 » FAILED
21 → Error message
22 ⫸ At step 3
23 > Test case 3
24 » SKIPPED
26 Test results:
27 1 FAILED | 1 PASSED | 1 SKIPPED
29Features:
30 - ANSI color support (green/red)
31 - Error message display with context (step number)
32 - Smart spacing between groups
34Typical usage:
35 >>> results = run_all_tests()
36 >>> pretty_print_results(results, with_colors=True)
38"""
40from typing import TYPE_CHECKING, Final
42from ocarina.aggregates.tests_layers import (
43 is_test_result_fail,
44 is_test_result_ok,
45 is_test_result_skipped,
46)
47from ocarina.railway.result import Fail, Ok
49if TYPE_CHECKING:
50 from ocarina.custom_types.oc_test_layers import (
51 TestCycleResults,
52 )
54_FAIL: Final[str] = "FAILED"
55_SUCCESS: Final[str] = "PASSED"
56_SKIPPED: Final[str] = "SKIPPED"
57_FINAL_SUMUP_HEADLINE: Final[str] = "Test results:"
59_STATUS_DISPATCH = {
60 Fail: _FAIL,
61 Ok: _SUCCESS,
62 type(None): _SKIPPED,
63}
65_GREEN = "\033[32m"
66_RED = "\033[31m"
67_COLOR_RESET = "\033[0m"
70def _color_text(text: str, color_code: str) -> str:
71 return f"{color_code}{text}{_COLOR_RESET}"
74def _muted_color_text(text: str, color_code: str) -> str: # noqa: ARG001
75 return text
78def _upcase(word: str) -> str:
79 return word.upper()
82def pretty_print_results( # noqa: PLR0912
83 results: TestCycleResults, *, with_colors: bool = False
84) -> None:
85 """Pretty printer."""
86 _color = _color_text if with_colors else _muted_color_text
88 count_ok = 0
89 count_fail = 0
90 count_ignored = 0
92 campaigns_output: list[list[str]] = []
94 for campaign_name, campaigns in results.items():
95 campaign_lines: list[str] = []
97 for suite_name, tests in campaigns.items():
98 if not tests:
99 continue
101 suite_lines: list[str] = [f"• {suite_name}"]
103 for test_name, (outcome, counter, _) in tests.items():
104 status = _STATUS_DISPATCH.get(type(outcome), "???")
105 suite_lines.append(f" › {test_name}") # noqa: RUF001
107 if is_test_result_fail(outcome):
108 count_fail += 1
109 suite_lines.append(_color(f" » {status}", _RED))
110 error_msg = str(outcome.error).rstrip("\n") if outcome.error else ""
111 if error_msg:
112 suite_lines.append(_color(f" → {error_msg}", _RED))
113 suite_lines.append(_color(f" ⫸ At step {counter}", _RED))
114 else:
115 suite_lines.append(_color(f" → At step {counter}", _RED))
116 elif is_test_result_ok(outcome):
117 count_ok += 1
118 suite_lines.append(_color(f" » {status}", _GREEN))
119 elif is_test_result_skipped(outcome):
120 count_ignored += 1
121 suite_lines.append(f" » {status}")
122 else:
123 suite_lines.append(f" » {status}")
125 campaign_lines.extend(suite_lines)
127 if campaign_lines:
128 campaigns_output.append([campaign_name, *campaign_lines])
130 if len(campaigns_output) > 1:
131 print()
133 for i, campaign_lines in enumerate(campaigns_output):
134 for line in campaign_lines:
135 print(line)
136 if i < len(campaigns_output) - 1:
137 print()
139 summary_parts = []
140 if count_fail > 0:
141 summary_parts.append(_color(f"{count_fail} {_upcase(_FAIL)}", _RED))
142 if count_ok > 0:
143 summary_parts.append(_color(f"{count_ok} {_upcase(_SUCCESS)}", _GREEN))
144 if count_ignored > 0:
145 summary_parts.append(f"{count_ignored} {_upcase(_SKIPPED)}")
147 if summary_parts:
148 print()
149 print(_FINAL_SUMUP_HEADLINE)
150 print(" | ".join(summary_parts))