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

129 statements  

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

1"""Unit tests for print_summary_table in summary_tables module. 

2 

3Tests cover: 

4- print_summary_table for CHECK, FIX, and TEST actions 

5- Multiple tools display 

6- Edge cases including empty results and unknown tools 

7- Module constants and their usage 

8""" 

9 

10from __future__ import annotations 

11 

12from typing import TYPE_CHECKING 

13 

14from assertpy import assert_that 

15 

16from lintro.enums.action import Action 

17from lintro.utils.summary_tables import ( 

18 DEFAULT_REMAINING_COUNT, 

19 print_summary_table, 

20) 

21 

22if TYPE_CHECKING: 

23 from collections.abc import Callable 

24 

25 from tests.unit.utils.conftest import FakeToolResult 

26 

27 

28# ============================================================================= 

29# Tests for print_summary_table with CHECK action 

30# ============================================================================= 

31 

32 

33def test_check_success_no_issues( 

34 console_capture: tuple[Callable[[str], None], list[str]], 

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

36) -> None: 

37 """Display passing check with no issues shows PASS status and tool name. 

38 

39 Args: 

40 console_capture: Mock console output capture. 

41 fake_tool_result_factory: Factory for creating fake tool results. 

42 """ 

43 capture, output = console_capture 

44 result = fake_tool_result_factory(name="ruff", success=True, issues_count=0) 

45 

46 print_summary_table(capture, Action.CHECK, [result]) 

47 

48 combined = "".join(output) 

49 assert_that(combined).contains("ruff") 

50 assert_that(combined).contains("PASS") 

51 

52 

53def test_check_with_issues( 

54 console_capture: tuple[Callable[[str], None], list[str]], 

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

56) -> None: 

57 """Display failing check with issues shows FAIL status and issue count. 

58 

59 Args: 

60 console_capture: Mock console output capture. 

61 fake_tool_result_factory: Factory for creating fake tool results. 

62 """ 

63 capture, output = console_capture 

64 result = fake_tool_result_factory(name="ruff", success=True, issues_count=5) 

65 

66 print_summary_table(capture, Action.CHECK, [result]) 

67 

68 combined = "".join(output) 

69 assert_that(combined).contains("ruff") 

70 assert_that(combined).contains("FAIL") 

71 assert_that(combined).contains("5") 

72 

73 

74def test_check_execution_failure( 

75 console_capture: tuple[Callable[[str], None], list[str]], 

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

77) -> None: 

78 """Display check with execution failure shows FAIL status. 

79 

80 Args: 

81 console_capture: Mock console output capture. 

82 fake_tool_result_factory: Factory for creating fake tool results. 

83 """ 

84 capture, output = console_capture 

85 result = fake_tool_result_factory( 

86 name="ruff", 

87 success=False, 

88 issues_count=0, 

89 output="timeout occurred", 

90 ) 

91 

92 print_summary_table(capture, Action.CHECK, [result]) 

93 

94 combined = "".join(output) 

95 assert_that(combined).contains("FAIL") 

96 

97 

98def test_check_skipped_tool( 

99 console_capture: tuple[Callable[[str], None], list[str]], 

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

101) -> None: 

102 """Display skipped status for version check failures. 

103 

104 Args: 

105 console_capture: Mock console output capture. 

106 fake_tool_result_factory: Factory for creating fake tool results. 

107 """ 

108 capture, output = console_capture 

109 result = fake_tool_result_factory( 

110 name="ruff", 

111 success=False, 

112 issues_count=0, 

113 output="Skipping ruff: version check failed", 

114 ) 

115 

116 print_summary_table(capture, Action.CHECK, [result]) 

117 

118 combined = "".join(output) 

119 assert_that(combined).contains("SKIPPED") 

120 

121 

122# ============================================================================= 

123# Tests for print_summary_table with FIX action 

124# ============================================================================= 

125 

126 

127def test_fix_with_fixed_count( 

128 console_capture: tuple[Callable[[str], None], list[str]], 

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

130) -> None: 

131 """Display fixed count and remaining for fix action with columns present. 

132 

133 Args: 

134 console_capture: Mock console output capture. 

135 fake_tool_result_factory: Factory for creating fake tool results. 

136 """ 

137 capture, output = console_capture 

138 result = fake_tool_result_factory( 

139 name="black", 

140 success=True, 

141 issues_count=0, 

142 fixed_issues_count=3, 

143 remaining_issues_count=0, 

144 ) 

145 

146 print_summary_table(capture, Action.FIX, [result]) 

147 

148 combined = "".join(output) 

149 assert_that(combined).contains("black") 

150 assert_that(combined).contains("PASS") 

151 assert_that(combined).contains("Fixed") 

152 assert_that(combined).contains("AI-Applied") 

153 assert_that(combined).contains("AI-Resolved") 

154 assert_that(combined).contains("Remaining") 

155 

156 

157def test_fix_with_remaining_issues( 

158 console_capture: tuple[Callable[[str], None], list[str]], 

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

160) -> None: 

161 """Display remaining issues for fix action when some issues cannot be fixed. 

162 

163 Args: 

164 console_capture: Mock console output capture. 

165 fake_tool_result_factory: Factory for creating fake tool results. 

166 """ 

167 capture, output = console_capture 

168 result = fake_tool_result_factory( 

169 name="black", 

170 success=True, 

171 issues_count=0, 

172 fixed_issues_count=5, 

173 remaining_issues_count=2, 

174 ) 

175 

176 print_summary_table(capture, Action.FIX, [result]) 

177 

178 combined = "".join(output) 

179 assert_that(combined).contains("black") 

180 

181 

182def test_fix_no_files_shows_zero( 

183 console_capture: tuple[Callable[[str], None], list[str]], 

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

185) -> None: 

186 """Display PASS with 0 fixed/remaining when no files to format. 

187 

188 Note: "No files to format" means the tool ran successfully but found no 

189 files - this is PASS with 0 issues, not SKIPPED (consistent with check mode). 

190 

191 Args: 

192 console_capture: Mock console output capture. 

193 fake_tool_result_factory: Factory for creating fake tool results. 

194 """ 

195 capture, output = console_capture 

196 result = fake_tool_result_factory( 

197 name="black", 

198 success=True, 

199 issues_count=0, 

200 output="No files to format", 

201 ) 

202 

203 print_summary_table(capture, Action.FIX, [result]) 

204 

205 combined = "".join(output) 

206 assert_that(combined).contains("PASS") 

207 # Should show 0 for both Fixed and Remaining, not SKIPPED 

208 assert_that(combined).does_not_contain("SKIPPED") 

209 

210 

211def test_fix_parsing_remaining_from_output( 

212 console_capture: tuple[Callable[[str], None], list[str]], 

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

214) -> None: 

215 """Parse remaining issues from output when not explicitly provided. 

216 

217 Args: 

218 console_capture: Mock console output capture. 

219 fake_tool_result_factory: Factory for creating fake tool results. 

220 """ 

221 capture, output = console_capture 

222 result = fake_tool_result_factory( 

223 name="black", 

224 success=True, 

225 issues_count=3, 

226 output="Found 2 issue(s) that cannot be auto-fixed", 

227 ) 

228 

229 print_summary_table(capture, Action.FIX, [result]) 

230 

231 combined = "".join(output) 

232 assert_that(combined).contains("black") 

233 

234 

235def test_fix_shows_ai_fixed_count_from_metadata( 

236 console_capture: tuple[Callable[[str], None], list[str]], 

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

238) -> None: 

239 """Display AI columns when AI metadata provides per-tool counts. 

240 

241 Args: 

242 console_capture: Mock console output capture. 

243 fake_tool_result_factory: Factory for creating fake tool results. 

244 """ 

245 capture, output = console_capture 

246 result = fake_tool_result_factory( 

247 name="ruff", 

248 success=False, 

249 fixed_issues_count=6, 

250 remaining_issues_count=5, 

251 ) 

252 result.ai_metadata = { 

253 "applied_count": 4, 

254 "verified_count": 3, 

255 "unverified_count": 1, 

256 } 

257 

258 print_summary_table(capture, Action.FIX, [result]) 

259 

260 combined = "".join(output) 

261 assert_that(combined).contains("AI-Applied") 

262 assert_that(combined).contains("AI-Resolved") 

263 assert_that(combined).contains("4") 

264 assert_that(combined).contains("3") 

265 assert_that(combined).contains("1 unresolved") 

266 

267 

268# ============================================================================= 

269# Tests for print_summary_table with TEST action 

270# ============================================================================= 

271 

272 

273def test_pytest_with_summary( 

274 console_capture: tuple[Callable[[str], None], list[str]], 

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

276) -> None: 

277 """Display pytest summary with detailed metrics including all columns. 

278 

279 Args: 

280 console_capture: Mock console output capture. 

281 fake_tool_result_factory: Factory for creating fake tool results. 

282 """ 

283 capture, output = console_capture 

284 result = fake_tool_result_factory( 

285 name="pytest", 

286 success=True, 

287 issues_count=0, 

288 pytest_summary={ 

289 "passed": 10, 

290 "failed": 2, 

291 "skipped": 1, 

292 "duration": 1.5, 

293 "total": 13, 

294 }, 

295 ) 

296 

297 print_summary_table(capture, Action.TEST, [result]) 

298 

299 combined = "".join(output) 

300 assert_that(combined).contains("pytest") 

301 assert_that(combined).contains("Passed") 

302 assert_that(combined).contains("Failed") 

303 assert_that(combined).contains("Skipped") 

304 assert_that(combined).contains("Duration") 

305 

306 

307def test_non_pytest_test_tool( 

308 console_capture: tuple[Callable[[str], None], list[str]], 

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

310) -> None: 

311 """Display basic pass/fail for non-pytest tools in test action. 

312 

313 Tool names with underscores are displayed with hyphens for consistency. 

314 

315 Args: 

316 console_capture: Mock console output capture. 

317 fake_tool_result_factory: Factory for creating fake tool results. 

318 """ 

319 capture, output = console_capture 

320 result = fake_tool_result_factory( 

321 name="other_test_runner", 

322 success=True, 

323 issues_count=0, 

324 ) 

325 

326 print_summary_table(capture, Action.TEST, [result]) 

327 

328 combined = "".join(output) 

329 # Underscores are converted to hyphens for display 

330 assert_that(combined).contains("other-test-runner") 

331 assert_that(combined).does_not_contain( 

332 "other_test_runner", 

333 ) # original with underscore 

334 assert_that(combined).contains("PASS") 

335 

336 

337# ============================================================================= 

338# Tests for print_summary_table with multiple tools 

339# ============================================================================= 

340 

341 

342def test_multiple_tools_displayed( 

343 console_capture: tuple[Callable[[str], None], list[str]], 

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

345) -> None: 

346 """Display all tools in the summary table when multiple tools are run. 

347 

348 Args: 

349 console_capture: Mock console output capture. 

350 fake_tool_result_factory: Factory for creating fake tool results. 

351 """ 

352 capture, output = console_capture 

353 results = [ 

354 fake_tool_result_factory(name="ruff", success=True, issues_count=0), 

355 fake_tool_result_factory(name="black", success=True, issues_count=2), 

356 fake_tool_result_factory(name="mypy", success=False, issues_count=5), 

357 ] 

358 

359 print_summary_table(capture, Action.CHECK, results) 

360 

361 combined = "".join(output) 

362 assert_that(combined).contains("ruff") 

363 assert_that(combined).contains("black") 

364 assert_that(combined).contains("mypy") 

365 

366 

367def test_tools_sorted_alphabetically( 

368 console_capture: tuple[Callable[[str], None], list[str]], 

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

370) -> None: 

371 """Display tools in alphabetical order regardless of input order. 

372 

373 Args: 

374 console_capture: Mock console output capture. 

375 fake_tool_result_factory: Factory for creating fake tool results. 

376 """ 

377 capture, output = console_capture 

378 # Input in non-alphabetical order: ruff, bandit, clippy 

379 results = [ 

380 fake_tool_result_factory(name="ruff", success=True, issues_count=0), 

381 fake_tool_result_factory(name="bandit", success=True, issues_count=0), 

382 fake_tool_result_factory(name="clippy", success=True, issues_count=0), 

383 ] 

384 

385 print_summary_table(capture, Action.CHECK, results) 

386 

387 combined = "".join(output) 

388 # Verify alphabetical order: bandit < clippy < ruff 

389 bandit_pos = combined.find("bandit") 

390 clippy_pos = combined.find("clippy") 

391 ruff_pos = combined.find("ruff") 

392 

393 assert_that(bandit_pos).is_less_than(clippy_pos) 

394 assert_that(clippy_pos).is_less_than(ruff_pos) 

395 

396 

397# ============================================================================= 

398# Tests for edge cases in print_summary_table 

399# ============================================================================= 

400 

401 

402def test_empty_results_list( 

403 console_capture: tuple[Callable[[str], None], list[str]], 

404) -> None: 

405 """Handle empty results list gracefully by still producing output. 

406 

407 Args: 

408 console_capture: Mock console output capture. 

409 """ 

410 capture, output = console_capture 

411 

412 print_summary_table(capture, Action.CHECK, []) 

413 

414 # Should output something (table headers even if empty) 

415 assert_that(output).is_not_empty() 

416 

417 

418def test_unknown_tool_name( 

419 console_capture: tuple[Callable[[str], None], list[str]], 

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

421) -> None: 

422 """Handle unknown tool name gracefully with underscore-to-hyphen conversion. 

423 

424 Tool names with underscores are displayed with hyphens for consistency 

425 with actual CLI tool naming conventions. 

426 

427 Args: 

428 console_capture: Mock console output capture. 

429 fake_tool_result_factory: Factory for creating fake tool results. 

430 """ 

431 capture, output = console_capture 

432 result = fake_tool_result_factory( 

433 name="unknown_tool_xyz", 

434 success=True, 

435 issues_count=0, 

436 ) 

437 

438 print_summary_table(capture, Action.CHECK, [result]) 

439 

440 combined = "".join(output) 

441 # Underscores are converted to hyphens for display 

442 assert_that(combined).contains("unknown-tool-xyz") 

443 assert_that(combined).does_not_contain( 

444 "unknown_tool_xyz", 

445 ) # original with underscore 

446 

447 

448# ============================================================================= 

449# Tests for module constants 

450# ============================================================================= 

451 

452 

453def test_default_remaining_count_is_question_mark() -> None: 

454 """Verify DEFAULT_REMAINING_COUNT is the expected sentinel value.""" 

455 assert_that(DEFAULT_REMAINING_COUNT).is_equal_to("?") 

456 assert_that(DEFAULT_REMAINING_COUNT).is_instance_of(str) 

457 

458 

459def test_default_remaining_count_used_in_fix_output( 

460 console_capture: tuple[Callable[[str], None], list[str]], 

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

462) -> None: 

463 """Verify DEFAULT_REMAINING_COUNT is used when remaining count is unknown. 

464 

465 When a tool fails but the remaining issue count cannot be determined, 

466 the constant should appear in the output as a fallback indicator. 

467 

468 Args: 

469 console_capture: Mock console output capture. 

470 fake_tool_result_factory: Factory for creating fake tool results. 

471 """ 

472 capture, output = console_capture 

473 result = fake_tool_result_factory( 

474 name="ruff", 

475 success=False, 

476 issues_count=0, 

477 output="some remaining issues exist", 

478 remaining_issues_count=None, 

479 fixed_issues_count=None, 

480 ) 

481 

482 print_summary_table(capture, Action.FIX, [result]) 

483 

484 combined = "".join(output) 

485 # The "?" should appear in the remaining column when count is unknown 

486 assert_that(combined).contains(DEFAULT_REMAINING_COUNT)