Coverage for tests / unit / utils / console / summary / test_execution_summary.py: 100%

77 statements  

« prev     ^ index     » next       coverage.py v7.13.0, created at 2026-04-03 18:53 +0000

1"""Unit tests for ThreadSafeConsoleLogger execution summary methods. 

2 

3This module tests the execution summary functionality of ThreadSafeConsoleLogger, 

4including tests for CHECK and FIX action handling. 

5""" 

6 

7from __future__ import annotations 

8 

9from typing import TYPE_CHECKING 

10from unittest.mock import patch 

11 

12import pytest 

13from assertpy import assert_that 

14 

15from lintro.enums.action import Action 

16from lintro.utils.console.logger import ThreadSafeConsoleLogger 

17 

18if TYPE_CHECKING: 

19 from collections.abc import Callable 

20 

21 from tests.unit.utils.conftest import FakeToolResult 

22 

23 

24# ============================================================================= 

25# Execution Summary Tests - CHECK Action 

26# ============================================================================= 

27 

28 

29def test_execution_summary_check_no_issues( 

30 fake_tool_result_factory: Callable[..., FakeToolResult], 

31) -> None: 

32 """Verify print_execution_summary handles check action with no issues. 

33 

34 When all tools pass with zero issues, the summary should indicate 

35 complete success and call ASCII art with zero total issues. 

36 

37 

38 Args: 

39 fake_tool_result_factory: Factory for creating FakeToolResult instances. 

40 """ 

41 logger = ThreadSafeConsoleLogger() 

42 results = [fake_tool_result_factory(success=True, issues_count=0)] 

43 

44 with ( 

45 patch.object(logger, "console_output"), 

46 patch.object(logger, "_print_summary_table"), 

47 patch.object(logger, "_print_ascii_art") as mock_art, 

48 ): 

49 logger.print_execution_summary(Action.CHECK, results) 

50 mock_art.assert_called_once_with(total_issues=0) 

51 

52 

53def test_execution_summary_check_with_issues( 

54 fake_tool_result_factory: Callable[..., FakeToolResult], 

55) -> None: 

56 """Verify print_execution_summary aggregates issue counts from multiple tools. 

57 

58 When multiple tools report issues, the total should be summed and 

59 passed to both the totals table and ASCII art display. 

60 

61 

62 Args: 

63 fake_tool_result_factory: Factory for creating FakeToolResult instances. 

64 """ 

65 logger = ThreadSafeConsoleLogger() 

66 results = [ 

67 fake_tool_result_factory(success=True, issues_count=5), 

68 fake_tool_result_factory(success=True, issues_count=3), 

69 ] 

70 

71 with ( 

72 patch.object(logger, "console_output"), 

73 patch.object(logger, "_print_summary_table"), 

74 patch.object(logger, "_print_totals_table") as mock_totals, 

75 patch.object(logger, "_print_ascii_art") as mock_art, 

76 ): 

77 logger.print_execution_summary(Action.CHECK, results) 

78 # Should show total of 8 issues 

79 mock_art.assert_called_once_with(total_issues=8) 

80 # Verify totals table was called with correct total 

81 mock_totals.assert_called_once() 

82 call_kwargs = mock_totals.call_args.kwargs 

83 assert_that(call_kwargs["total_issues"]).is_equal_to(8) 

84 

85 

86def test_execution_summary_check_failed_tool_shows_minimum_issues( 

87 fake_tool_result_factory: Callable[..., FakeToolResult], 

88) -> None: 

89 """Verify print_execution_summary shows at least 1 issue when a tool fails. 

90 

91 Failed tools should be treated as having issues even if issues_count is 0, 

92 ensuring the summary reflects the failure state. 

93 

94 

95 Args: 

96 fake_tool_result_factory: Factory for creating FakeToolResult instances. 

97 """ 

98 logger = ThreadSafeConsoleLogger() 

99 results = [fake_tool_result_factory(success=False, issues_count=0)] 

100 

101 with ( 

102 patch.object(logger, "console_output"), 

103 patch.object(logger, "_print_summary_table"), 

104 patch.object(logger, "_print_ascii_art") as mock_art, 

105 ): 

106 logger.print_execution_summary(Action.CHECK, results) 

107 # Should show at least 1 for art when tool failed 

108 mock_art.assert_called_once_with( 

109 total_issues=1, 

110 ) 

111 

112 

113@pytest.mark.parametrize( 

114 ("issue_counts", "expected_total"), 

115 [ 

116 ([0], 0), 

117 ([5], 5), 

118 ([5, 3], 8), 

119 ([1, 2, 3, 4], 10), 

120 ([0, 0, 0], 0), 

121 ], 

122) 

123def test_execution_summary_check_issue_aggregation( 

124 fake_tool_result_factory: Callable[..., FakeToolResult], 

125 issue_counts: list[int], 

126 expected_total: int, 

127) -> None: 

128 """Verify print_execution_summary correctly sums issues from all tools. 

129 

130 Different combinations of issue counts should be properly aggregated 

131 into the correct total. 

132 

133 

134 Args: 

135 fake_tool_result_factory: Factory for creating FakeToolResult instances. 

136 issue_counts: List of issue counts for each tool. 

137 expected_total: Expected total issues after aggregation. 

138 """ 

139 logger = ThreadSafeConsoleLogger() 

140 results = [ 

141 fake_tool_result_factory(success=True, issues_count=count) 

142 for count in issue_counts 

143 ] 

144 

145 with ( 

146 patch.object(logger, "console_output"), 

147 patch.object(logger, "_print_summary_table"), 

148 patch.object(logger, "_print_ascii_art") as mock_art, 

149 ): 

150 logger.print_execution_summary(Action.CHECK, results) 

151 mock_art.assert_called_once_with( 

152 total_issues=expected_total, 

153 ) 

154 

155 

156# ============================================================================= 

157# Execution Summary Tests - FIX Action 

158# ============================================================================= 

159 

160 

161def test_execution_summary_fix_with_standardized_counts( 

162 fake_tool_result_factory: Callable[..., FakeToolResult], 

163) -> None: 

164 """Verify print_execution_summary uses standardized counts for fix action. 

165 

166 When fixed_issues_count and remaining_issues_count are provided, 

167 they should be used instead of parsing from output. 

168 

169 

170 Args: 

171 fake_tool_result_factory: Factory for creating FakeToolResult instances. 

172 """ 

173 logger = ThreadSafeConsoleLogger() 

174 results = [ 

175 fake_tool_result_factory( 

176 success=True, 

177 fixed_issues_count=10, 

178 remaining_issues_count=2, 

179 ), 

180 ] 

181 

182 with ( 

183 patch.object(logger, "console_output"), 

184 patch.object(logger, "_print_summary_table"), 

185 patch.object(logger, "_print_ascii_art") as mock_art, 

186 ): 

187 logger.print_execution_summary(Action.FIX, results) 

188 mock_art.assert_called_once_with(total_issues=2) 

189 

190 

191def test_execution_summary_fix_fallback_to_issues_count( 

192 fake_tool_result_factory: Callable[..., FakeToolResult], 

193) -> None: 

194 """Verify print_execution_summary falls back when fixed_issues_count not provided. 

195 

196 Legacy tools that don't provide fixed_issues_count should still have 

197 their issues_count used for the summary calculation. 

198 

199 

200 Args: 

201 fake_tool_result_factory: Factory for creating FakeToolResult instances. 

202 """ 

203 logger = ThreadSafeConsoleLogger() 

204 results = [ 

205 fake_tool_result_factory( 

206 success=True, 

207 issues_count=5, 

208 fixed_issues_count=None, 

209 ), 

210 ] 

211 

212 with ( 

213 patch.object(logger, "console_output"), 

214 patch.object(logger, "_print_summary_table"), 

215 patch.object(logger, "_print_ascii_art"), 

216 ): 

217 # Should not raise any exception 

218 logger.print_execution_summary(Action.FIX, results) 

219 

220 

221def test_execution_summary_fix_failed_tool_handled( 

222 fake_tool_result_factory: Callable[..., FakeToolResult], 

223) -> None: 

224 """Verify print_execution_summary handles failed tools in fix action gracefully. 

225 

226 Failed tools should not contribute to numeric totals to avoid misleading 

227 success metrics. 

228 

229 

230 Args: 

231 fake_tool_result_factory: Factory for creating FakeToolResult instances. 

232 """ 

233 logger = ThreadSafeConsoleLogger() 

234 results = [ 

235 fake_tool_result_factory( 

236 success=False, 

237 issues_count=0, 

238 remaining_issues_count=None, 

239 ), 

240 ] 

241 

242 with ( 

243 patch.object(logger, "console_output"), 

244 patch.object(logger, "_print_summary_table"), 

245 patch.object(logger, "_print_ascii_art"), 

246 ): 

247 # Should not raise and should handle sentinel values 

248 logger.print_execution_summary(Action.FIX, results) 

249 

250 

251def test_execution_summary_fix_parses_remaining_from_output( 

252 fake_tool_result_factory: Callable[..., FakeToolResult], 

253) -> None: 

254 """Verify print_execution_summary parses remaining issues from output. 

255 

256 When remaining_issues_count is not set, the method should parse 

257 the output string to extract remaining issue counts. 

258 

259 

260 Args: 

261 fake_tool_result_factory: Factory for creating FakeToolResult instances. 

262 """ 

263 logger = ThreadSafeConsoleLogger() 

264 results = [ 

265 fake_tool_result_factory( 

266 success=True, 

267 output="5 remaining issues that cannot be auto-fixed", 

268 remaining_issues_count=None, 

269 ), 

270 ] 

271 

272 with ( 

273 patch.object(logger, "console_output"), 

274 patch.object(logger, "_print_summary_table"), 

275 patch.object(logger, "_print_ascii_art") as mock_art, 

276 ): 

277 logger.print_execution_summary(Action.FIX, results) 

278 mock_art.assert_called_once_with(total_issues=5) 

279 

280 

281def test_execution_summary_fix_parses_cannot_autofix_from_output( 

282 fake_tool_result_factory: Callable[..., FakeToolResult], 

283) -> None: 

284 """Verify print_execution_summary parses 'cannot autofix' count from output. 

285 

286 The 'cannot be auto-fixed' pattern should be recognized and the count 

287 extracted for remaining issues calculation. 

288 

289 

290 Args: 

291 fake_tool_result_factory: Factory for creating FakeToolResult instances. 

292 """ 

293 logger = ThreadSafeConsoleLogger() 

294 # Use the exact format the regex expects 

295 results = [ 

296 fake_tool_result_factory( 

297 success=True, 

298 output="Found 3 issues that cannot be auto-fixed", 

299 remaining_issues_count=None, 

300 ), 

301 ] 

302 

303 with ( 

304 patch.object(logger, "console_output"), 

305 patch.object(logger, "_print_summary_table"), 

306 patch.object(logger, "_print_ascii_art") as mock_art, 

307 ): 

308 logger.print_execution_summary(Action.FIX, results) 

309 mock_art.assert_called_once_with( 

310 total_issues=3, 

311 ) 

312 

313 

314def test_execution_summary_fix_handles_string_sentinel_remaining( 

315 fake_tool_result_factory: Callable[..., FakeToolResult], 

316) -> None: 

317 """Verify print_execution_summary handles string sentinels. 

318 

319 String sentinel values (like 'N/A') should not be added to numeric 

320 totals to prevent type errors in calculations. 

321 

322 

323 Args: 

324 fake_tool_result_factory: Factory for creating FakeToolResult instances. 

325 """ 

326 logger = ThreadSafeConsoleLogger() 

327 result = fake_tool_result_factory(success=True) 

328 # Set a string sentinel using object attribute 

329 result.remaining_issues_count = "N/A" # type: ignore[assignment] 

330 results = [result] 

331 

332 with ( 

333 patch.object(logger, "console_output"), 

334 patch.object(logger, "_print_summary_table"), 

335 patch.object(logger, "_print_ascii_art"), 

336 ): 

337 # Should not raise or add string sentinel to numeric total 

338 logger.print_execution_summary(Action.FIX, results) 

339 

340 

341@pytest.mark.parametrize( 

342 ("fixed", "remaining", "expected_remaining"), 

343 [ 

344 (10, 0, 0), 

345 (5, 3, 3), 

346 (0, 0, 0), 

347 (100, 10, 10), 

348 ], 

349) 

350def test_execution_summary_fix_various_counts( 

351 fake_tool_result_factory: Callable[..., FakeToolResult], 

352 fixed: int, 

353 remaining: int, 

354 expected_remaining: int, 

355) -> None: 

356 """Verify print_execution_summary handles various fixed/remaining combinations. 

357 

358 Different scenarios of fixed and remaining issues should be handled 

359 correctly with proper totals passed to ASCII art. 

360 

361 

362 Args: 

363 fake_tool_result_factory: Factory for creating FakeToolResult instances. 

364 fixed: Number of fixed issues. 

365 remaining: Number of remaining issues. 

366 expected_remaining: Expected remaining issues total. 

367 """ 

368 logger = ThreadSafeConsoleLogger() 

369 results = [ 

370 fake_tool_result_factory( 

371 success=True, 

372 fixed_issues_count=fixed, 

373 remaining_issues_count=remaining, 

374 ), 

375 ] 

376 

377 with ( 

378 patch.object(logger, "console_output"), 

379 patch.object(logger, "_print_summary_table"), 

380 patch.object(logger, "_print_ascii_art") as mock_art, 

381 ): 

382 logger.print_execution_summary(Action.FIX, results) 

383 mock_art.assert_called_once_with(total_issues=expected_remaining)