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

1"""Unit tests for Clippy parser.""" 

2 

3from __future__ import annotations 

4 

5from assertpy import assert_that 

6 

7from lintro.parsers.clippy.clippy_parser import parse_clippy_output 

8 

9 

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") 

27 

28 

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") 

47 

48 

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") 

65 

66 

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") 

79 

80 

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() 

85 

86 

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) 

102 

103 

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) 

116 

117 

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")