Coverage for tests / unit / parsers / test_clippy_parser.py: 100%
50 statements
« prev ^ index » next coverage.py v7.13.0, created at 2026-04-03 18:53 +0000
« prev ^ index » next coverage.py v7.13.0, created at 2026-04-03 18:53 +0000
1"""Unit tests for Clippy parser."""
3from __future__ import annotations
5from assertpy import assert_that
7from lintro.parsers.clippy.clippy_parser import parse_clippy_output
10def test_parse_clippy_output_single_issue() -> None:
11 """Parse a single Clippy warning from JSON Lines."""
12 output = (
13 '{"reason":"compiler-message","message":{"code":{"code":"clippy::needless_return"},'
14 '"level":"warning","message":"unneeded `return` statement",'
15 '"spans":[{"file_name":"src/lib.rs","line_start":42,"line_end":42,'
16 '"column_start":5,"column_end":15}],'
17 '"rendered":"warning: unneeded `return` statement..."}}'
18 )
19 issues = parse_clippy_output(output)
20 assert_that(issues).is_length(1)
21 assert_that(issues[0].file).is_equal_to("src/lib.rs")
22 assert_that(issues[0].line).is_equal_to(42)
23 assert_that(issues[0].column).is_equal_to(5)
24 assert_that(issues[0].code).is_equal_to("clippy::needless_return")
25 assert_that(issues[0].message).contains("unneeded")
26 assert_that(issues[0].level).is_equal_to("warning")
29def test_parse_clippy_output_multiple_issues() -> None:
30 """Parse multiple Clippy warnings from JSON Lines."""
31 output = (
32 '{"reason":"compiler-message","message":{"code":{"code":"clippy::needless_return"},'
33 '"level":"warning","message":"unneeded return",'
34 '"spans":[{"file_name":"src/lib.rs",'
35 '"line_start":1,"line_end":1,"column_start":1,"column_end":10}]}}\n'
36 '{"reason":"compiler-message","message":{"code":{"code":"clippy::unused_variable"},'
37 '"level":"warning","message":"unused variable",'
38 '"spans":[{"file_name":"src/main.rs",'
39 '"line_start":5,"line_end":5,"column_start":3,"column_end":8}]}}'
40 )
41 issues = parse_clippy_output(output)
42 assert_that(issues).is_length(2)
43 assert_that(issues[0].file).is_equal_to("src/lib.rs")
44 assert_that(issues[1].file).is_equal_to("src/main.rs")
45 assert_that(issues[0].code).is_equal_to("clippy::needless_return")
46 assert_that(issues[1].code).is_equal_to("clippy::unused_variable")
49def test_parse_clippy_output_ignores_non_clippy() -> None:
50 """Ignore non-Clippy compiler messages."""
51 output = (
52 '{"reason":"compiler-message","message":{"code":{"code":"E0425"},'
53 '"level":"error","message":"cannot find value",'
54 '"spans":[{"file_name":"src/lib.rs",'
55 '"line_start":1,"line_end":1,"column_start":1,"column_end":5}]}}\n'
56 '{"reason":"compiler-message","message":{"code":{"code":"clippy::needless_return"},'
57 '"level":"warning","message":"unneeded return",'
58 '"spans":[{"file_name":"src/lib.rs",'
59 '"line_start":2,"line_end":2,"column_start":1,"column_end":10}]}}'
60 )
61 issues = parse_clippy_output(output)
62 # Should only parse the clippy issue, not the compiler error
63 assert_that(issues).is_length(1)
64 assert_that(issues[0].code).is_equal_to("clippy::needless_return")
67def test_parse_clippy_output_ignores_non_compiler_messages() -> None:
68 """Ignore non-compiler-message reasons."""
69 output = (
70 '{"reason":"build-finished","success":true}\n'
71 '{"reason":"compiler-message","message":{"code":{"code":"clippy::needless_return"},'
72 '"level":"warning","message":"unneeded return",'
73 '"spans":[{"file_name":"src/lib.rs",'
74 '"line_start":1,"line_end":1,"column_start":1,"column_end":10}]}}'
75 )
76 issues = parse_clippy_output(output)
77 assert_that(issues).is_length(1)
78 assert_that(issues[0].code).is_equal_to("clippy::needless_return")
81def test_parse_clippy_output_empty() -> None:
82 """Handle empty output."""
83 assert_that(parse_clippy_output("")).is_empty()
84 assert_that(parse_clippy_output("\n\n")).is_empty()
87def test_parse_clippy_output_invalid_json() -> None:
88 """Skip invalid JSON lines."""
89 output = (
90 '{"reason":"compiler-message","message":{"code":{"code":"clippy::needless_return"},'
91 '"level":"warning","message":"unneeded return",'
92 '"spans":[{"file_name":"src/lib.rs",'
93 '"line_start":1,"line_end":1,"column_start":1,"column_end":10}]}}\n'
94 "not valid json\n"
95 '{"reason":"compiler-message","message":{"code":{"code":"clippy::unused_variable"},'
96 '"level":"warning","message":"unused variable",'
97 '"spans":[{"file_name":"src/main.rs",'
98 '"line_start":5,"line_end":5,"column_start":3,"column_end":8}]}}'
99 )
100 issues = parse_clippy_output(output)
101 assert_that(issues).is_length(2)
104def test_parse_clippy_output_multi_line_span() -> None:
105 """Handle multi-line spans correctly."""
106 output = (
107 '{"reason":"compiler-message","message":{"code":{"code":"clippy::too_many_lines"},'
108 '"level":"warning","message":"function too long",'
109 '"spans":[{"file_name":"src/lib.rs",'
110 '"line_start":10,"line_end":15,"column_start":1,"column_end":5}]}}'
111 )
112 issues = parse_clippy_output(output)
113 assert_that(issues).is_length(1)
114 assert_that(issues[0].line).is_equal_to(10)
115 assert_that(issues[0].end_line).is_equal_to(15)
118def test_parse_clippy_output_ansi_codes_stripped() -> None:
119 """Strip ANSI escape codes from output for consistent CI/local parsing."""
120 # Output with ANSI color codes (common in CI environments)
121 output = (
122 '\x1b[33m{"reason":"compiler-message","message":{"code":{"code":"clippy::needless_return"},'
123 '"level":"warning","message":"unneeded `return` statement",'
124 '"spans":[{"file_name":"src/lib.rs","line_start":42,"line_end":42,'
125 '"column_start":5,"column_end":15}],'
126 '"rendered":"warning: unneeded `return` statement..."}}\x1b[0m'
127 )
128 issues = parse_clippy_output(output)
129 assert_that(issues).is_length(1)
130 assert_that(issues[0].file).is_equal_to("src/lib.rs")
131 assert_that(issues[0].code).is_equal_to("clippy::needless_return")