Coverage for /home/crpier/Projects/snektest/snektest/presenter/summary.py: 53%

83 statements  

« prev     ^ index     » next       coverage.py v7.14.1, created at 2026-06-07 00:30 +0300

1from __future__ import annotations 

2 

3from dataclasses import dataclass 

4 

5from rich.console import Console 

6from rich.text import Text 

7 

8from snektest.models import ( 

9 ErrorResult, 

10 FailedResult, 

11 PassedResult, 

12 TeardownFailure, 

13 TestResult, 

14) 

15 

16 

17@dataclass(frozen=True) 

18class RunCounts: 

19 passed: int 

20 failed: int 

21 errors: int 

22 fixture_teardown_failed: int 

23 session_teardown_failed: int 

24 

25 

26def _print_summary_entry( 

27 console: Console, 

28 *, 

29 label: str, 

30 style: str, 

31 details: str, 

32) -> None: 

33 line = Text.assemble((label, style), " ", details) 

34 console.print(line, markup=False, no_wrap=True, overflow="ellipsis") 

35 

36 

37def _summarize_exception(exc_value: BaseException) -> str: 

38 """Render exception values as single-line summary details. 

39 

40 Full multi-line exception messages are already shown in the failure details. 

41 The summary keeps only the first line so repeated diagnostics do not swamp 

42 the final count line. 

43 """ 

44 first_line = str(exc_value).splitlines()[0] if str(exc_value) else "" 

45 if not first_line: 

46 return "" 

47 return f"{type(exc_value).__name__}: {first_line}" 

48 

49 

50def _print_warnings(console: Console, test_results: list[TestResult]) -> None: 

51 """Print warnings from test results.""" 

52 all_warnings = [w for result in test_results for w in result.warnings] 

53 if all_warnings: 

54 console.print() 

55 console.rule("WARNINGS", style="bold yellow") 

56 for warning in all_warnings: 

57 console.print(warning, markup=False, style="yellow") 

58 console.print() 

59 

60 

61def _print_test_failures(console: Console, test_results: list[TestResult]) -> None: 

62 """Print test failures.""" 

63 for result in test_results: 

64 if (failed_result := result.result) and isinstance(failed_result, FailedResult): 

65 error_msg = _summarize_exception(failed_result.exc_value) 

66 details = f"{result.name} - {error_msg}" if error_msg else f"{result.name}" 

67 _print_summary_entry( 

68 console, 

69 label="FAILED", 

70 style="red", 

71 details=details, 

72 ) 

73 

74 

75def _print_test_errors(console: Console, test_results: list[TestResult]) -> None: 

76 """Print test errors (unexpected exceptions).""" 

77 for result in test_results: 

78 if (error_result := result.result) and isinstance(error_result, ErrorResult): 

79 error_msg = _summarize_exception(error_result.exc_value) 

80 details = f"{result.name} - {error_msg}" if error_msg else f"{result.name}" 

81 _print_summary_entry( 

82 console, 

83 label="ERROR", 

84 style="dark_orange", 

85 details=details, 

86 ) 

87 

88 

89def _print_fixture_teardown_failures( 

90 console: Console, test_results: list[TestResult] 

91) -> None: 

92 """Print fixture teardown failures.""" 

93 for result in test_results: 

94 for teardown_failure in result.fixture_teardown_failures: 

95 error_msg = _summarize_exception(teardown_failure.exc_value) 

96 _print_summary_entry( 

97 console, 

98 label="FIXTURE TEARDOWN FAILED", 

99 style="red", 

100 details=f"{result.name} - {teardown_failure.fixture_name}: {error_msg}", 

101 ) 

102 

103 

104def _print_session_teardown_failures( 

105 console: Console, session_teardown_failures: list[TeardownFailure] 

106) -> None: 

107 """Print session teardown failures.""" 

108 for teardown_failure in session_teardown_failures: 

109 error_msg = _summarize_exception(teardown_failure.exc_value) 

110 _print_summary_entry( 

111 console, 

112 label="SESSION FIXTURE TEARDOWN FAILED", 

113 style="red", 

114 details=f"{teardown_failure.fixture_name}: {error_msg}", 

115 ) 

116 

117 

118def _has_failures(counts: RunCounts) -> bool: 

119 return ( 

120 counts.failed > 0 

121 or counts.errors > 0 

122 or counts.fixture_teardown_failed > 0 

123 or counts.session_teardown_failed > 0 

124 ) 

125 

126 

127def _build_status_text(*, counts: RunCounts, total_duration: float) -> tuple[str, str]: 

128 """Build status text and set its color.""" 

129 status_color = "red" if _has_failures(counts) else "green" 

130 status_text = "" 

131 if counts.failed > 0: 

132 status_text += f"{counts.failed} failed, " 

133 if counts.errors > 0: 

134 status_text += f"{counts.errors} error, " 

135 if counts.fixture_teardown_failed > 0: 

136 status_text += f"{counts.fixture_teardown_failed} fixture teardown failed, " 

137 if counts.session_teardown_failed > 0: 

138 status_text += ( 

139 f"{counts.session_teardown_failed} session fixture teardown failed, " 

140 ) 

141 status_text += f"{counts.passed} passed in {total_duration:.2f}s" 

142 return status_text, f"bold {status_color}" 

143 

144 

145def print_summary( 

146 console: Console, 

147 test_results: list[TestResult], 

148 total_duration: float, 

149 session_teardown_failures: list[TeardownFailure] | None = None, 

150) -> None: 

151 """Print test summary with counts and status.""" 

152 if session_teardown_failures is None: 

153 session_teardown_failures = [] 

154 

155 passed = sum( 

156 1 for result in test_results if isinstance(result.result, PassedResult) 

157 ) 

158 failed = sum( 

159 1 for result in test_results if isinstance(result.result, FailedResult) 

160 ) 

161 errors = sum(1 for result in test_results if isinstance(result.result, ErrorResult)) 

162 fixture_teardown_failed = sum( 

163 len(result.fixture_teardown_failures) for result in test_results 

164 ) 

165 session_teardown_failed = len(session_teardown_failures) 

166 

167 counts = RunCounts( 

168 passed=passed, 

169 failed=failed, 

170 errors=errors, 

171 fixture_teardown_failed=fixture_teardown_failed, 

172 session_teardown_failed=session_teardown_failed, 

173 ) 

174 

175 _print_warnings(console, test_results) 

176 

177 if _has_failures(counts): 

178 console.rule("SUMMARY", style="wheat1") 

179 _print_test_failures(console, test_results) 

180 _print_test_errors(console, test_results) 

181 _print_fixture_teardown_failures(console, test_results) 

182 _print_session_teardown_failures(console, session_teardown_failures) 

183 console.print() 

184 

185 status_text, status_style = _build_status_text( 

186 counts=counts, 

187 total_duration=total_duration, 

188 ) 

189 console.rule(status_text, style=status_style)