Coverage for tests / unit / tools / shellcheck / test_execution.py: 100%

54 statements  

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

1"""Unit tests for shellcheck plugin check execution and output parsing.""" 

2 

3from __future__ import annotations 

4 

5from pathlib import Path 

6from unittest.mock import patch 

7 

8import pytest 

9from assertpy import assert_that 

10 

11from lintro.parsers.shellcheck.shellcheck_parser import parse_shellcheck_output 

12from lintro.tools.definitions.shellcheck import ShellcheckPlugin 

13 

14# Tests for ShellcheckPlugin.check method 

15 

16 

17def test_check_with_mocked_subprocess_success( 

18 shellcheck_plugin: ShellcheckPlugin, 

19 tmp_path: Path, 

20) -> None: 

21 """Check returns success when no issues found. 

22 

23 Args: 

24 shellcheck_plugin: The ShellcheckPlugin instance to test. 

25 tmp_path: Temporary directory path for test files. 

26 """ 

27 # Create a test shell file 

28 test_file = tmp_path / "test_script.sh" 

29 test_file.write_text('#!/bin/bash\necho "Hello World"\n') 

30 

31 with patch.object( 

32 shellcheck_plugin, 

33 "_run_subprocess", 

34 return_value=(True, "[]"), 

35 ): 

36 result = shellcheck_plugin.check([str(test_file)], {}) 

37 

38 assert_that(result.success).is_true() 

39 assert_that(result.issues_count).is_equal_to(0) 

40 

41 

42def test_check_with_mocked_subprocess_issues( 

43 shellcheck_plugin: ShellcheckPlugin, 

44 tmp_path: Path, 

45) -> None: 

46 """Check returns issues when shellcheck finds problems. 

47 

48 Args: 

49 shellcheck_plugin: The ShellcheckPlugin instance to test. 

50 tmp_path: Temporary directory path for test files. 

51 """ 

52 test_file = tmp_path / "test_script.sh" 

53 test_file.write_text("#!/bin/bash\necho $var\n") 

54 

55 shellcheck_output = """[ 

56 { 

57 "file": "test_script.sh", 

58 "line": 2, 

59 "endLine": 2, 

60 "column": 6, 

61 "endColumn": 10, 

62 "level": "warning", 

63 "code": 2086, 

64 "message": "Double quote to prevent globbing and word splitting." 

65 } 

66 ]""" 

67 

68 with patch.object( 

69 shellcheck_plugin, 

70 "_run_subprocess", 

71 return_value=(False, shellcheck_output), 

72 ): 

73 result = shellcheck_plugin.check([str(test_file)], {}) 

74 

75 assert_that(result.success).is_false() 

76 assert_that(result.issues_count).is_greater_than(0) 

77 

78 

79def test_check_with_no_shell_files( 

80 shellcheck_plugin: ShellcheckPlugin, 

81 tmp_path: Path, 

82) -> None: 

83 """Check returns success when no shell files found. 

84 

85 Args: 

86 shellcheck_plugin: The ShellcheckPlugin instance to test. 

87 tmp_path: Temporary directory path for test files. 

88 """ 

89 non_shell_file = tmp_path / "test.txt" 

90 non_shell_file.write_text("Not a shell file") 

91 

92 result = shellcheck_plugin.check([str(non_shell_file)], {}) 

93 

94 assert_that(result.success).is_true() 

95 assert_that(result.output).contains("No") 

96 

97 

98# Tests for ShellcheckPlugin.fix method 

99 

100 

101def test_fix_raises_not_implemented(shellcheck_plugin: ShellcheckPlugin) -> None: 

102 """Fix raises NotImplementedError. 

103 

104 Args: 

105 shellcheck_plugin: The ShellcheckPlugin instance to test. 

106 """ 

107 with pytest.raises(NotImplementedError, match="Shellcheck cannot automatically"): 

108 shellcheck_plugin.fix([], {}) 

109 

110 

111# Tests for output parsing 

112 

113 

114def test_parse_shellcheck_output_single_issue() -> None: 

115 """Parse single issue from shellcheck output.""" 

116 output = """[ 

117 { 

118 "file": "test.sh", 

119 "line": 10, 

120 "endLine": 10, 

121 "column": 5, 

122 "endColumn": 10, 

123 "level": "warning", 

124 "code": 2086, 

125 "message": "Double quote to prevent globbing and word splitting." 

126 } 

127 ]""" 

128 issues = parse_shellcheck_output(output) 

129 

130 assert_that(issues).is_length(1) 

131 assert_that(issues[0].file).is_equal_to("test.sh") 

132 assert_that(issues[0].line).is_equal_to(10) 

133 assert_that(issues[0].code).is_equal_to("SC2086") 

134 assert_that(issues[0].message).contains("Double quote") 

135 

136 

137def test_parse_shellcheck_output_multiple_issues() -> None: 

138 """Parse multiple issues from shellcheck output.""" 

139 output = """[ 

140 { 

141 "file": "test.sh", 

142 "line": 10, 

143 "column": 5, 

144 "level": "warning", 

145 "code": 2086, 

146 "message": "Double quote to prevent globbing." 

147 }, 

148 { 

149 "file": "test.sh", 

150 "line": 20, 

151 "column": 1, 

152 "level": "error", 

153 "code": 1091, 

154 "message": "Not following sourced file." 

155 } 

156 ]""" 

157 issues = parse_shellcheck_output(output) 

158 

159 assert_that(issues).is_length(2) 

160 assert_that(issues[0].code).is_equal_to("SC2086") 

161 assert_that(issues[1].code).is_equal_to("SC1091") 

162 

163 

164def test_parse_shellcheck_output_empty() -> None: 

165 """Parse empty output returns empty list.""" 

166 issues = parse_shellcheck_output("[]") 

167 

168 assert_that(issues).is_empty() 

169 

170 

171def test_parse_shellcheck_output_none() -> None: 

172 """Parse None output returns empty list.""" 

173 issues = parse_shellcheck_output(None) 

174 

175 assert_that(issues).is_empty() 

176 

177 

178def test_parse_shellcheck_output_invalid_json() -> None: 

179 """Parse invalid JSON returns empty list.""" 

180 issues = parse_shellcheck_output("not valid json") 

181 

182 assert_that(issues).is_empty()