Coverage for tests / unit / tools / shfmt / 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 shfmt plugin execution.""" 

2 

3from __future__ import annotations 

4 

5from pathlib import Path 

6from typing import TYPE_CHECKING 

7from unittest.mock import patch 

8 

9from assertpy import assert_that 

10 

11from lintro.tools.definitions.shfmt import ShfmtPlugin 

12 

13if TYPE_CHECKING: 

14 pass 

15 

16 

17# ============================================================================= 

18# Tests for ShfmtPlugin.check method 

19# ============================================================================= 

20 

21 

22def test_check_with_mocked_subprocess_success( 

23 shfmt_plugin: ShfmtPlugin, 

24 tmp_path: Path, 

25) -> None: 

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

27 

28 Args: 

29 shfmt_plugin: The ShfmtPlugin instance to test. 

30 tmp_path: Temporary directory path for test files. 

31 """ 

32 test_file = tmp_path / "test_script.sh" 

33 test_file.write_text('#!/bin/bash\necho "hello"\n') 

34 

35 with patch( 

36 "lintro.plugins.execution_preparation.verify_tool_version", 

37 return_value=None, 

38 ): 

39 with patch.object( 

40 shfmt_plugin, 

41 "_run_subprocess", 

42 return_value=(True, ""), 

43 ): 

44 result = shfmt_plugin.check([str(test_file)], {}) 

45 

46 assert_that(result.success).is_true() 

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

48 

49 

50def test_check_with_mocked_subprocess_issues( 

51 shfmt_plugin: ShfmtPlugin, 

52 tmp_path: Path, 

53) -> None: 

54 """Check returns issues when shfmt finds formatting problems. 

55 

56 Args: 

57 shfmt_plugin: The ShfmtPlugin instance to test. 

58 tmp_path: Temporary directory path for test files. 

59 """ 

60 test_file = tmp_path / "test_script.sh" 

61 test_file.write_text( 

62 '#!/bin/bash\nif [ "$foo" = "bar" ]; then\necho "match"\nfi\n', 

63 ) 

64 

65 shfmt_diff_output = f"""--- {test_file} 

66+++ {test_file} 

67@@ -1,4 +1,4 @@ 

68 #!/bin/bash 

69-if [ "$foo" = "bar" ]; then 

70+if [ "$foo" = "bar" ]; then 

71 echo "match" 

72 fi""" 

73 

74 with patch( 

75 "lintro.plugins.execution_preparation.verify_tool_version", 

76 return_value=None, 

77 ): 

78 with patch.object( 

79 shfmt_plugin, 

80 "_run_subprocess", 

81 return_value=(False, shfmt_diff_output), 

82 ): 

83 result = shfmt_plugin.check([str(test_file)], {}) 

84 

85 assert_that(result.success).is_false() 

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

87 

88 

89def test_check_with_no_shell_files( 

90 shfmt_plugin: ShfmtPlugin, 

91 tmp_path: Path, 

92) -> None: 

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

94 

95 Args: 

96 shfmt_plugin: The ShfmtPlugin instance to test. 

97 tmp_path: Temporary directory path for test files. 

98 """ 

99 non_sh_file = tmp_path / "test.txt" 

100 non_sh_file.write_text("Not a shell file") 

101 

102 with patch.object(shfmt_plugin, "_verify_tool_version", return_value=None): 

103 result = shfmt_plugin.check([str(non_sh_file)], {}) 

104 

105 assert_that(result.success).is_true() 

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

107 

108 

109# ============================================================================= 

110# Tests for ShfmtPlugin.fix method 

111# ============================================================================= 

112 

113 

114def test_fix_with_mocked_subprocess_success( 

115 shfmt_plugin: ShfmtPlugin, 

116 tmp_path: Path, 

117) -> None: 

118 """Fix returns success when fixes are applied. 

119 

120 Args: 

121 shfmt_plugin: The ShfmtPlugin instance to test. 

122 tmp_path: Temporary directory path for test files. 

123 """ 

124 test_file = tmp_path / "test_script.sh" 

125 test_file.write_text( 

126 '#!/bin/bash\nif [ "$foo" = "bar" ]; then\necho "match"\nfi\n', 

127 ) 

128 

129 shfmt_diff_output = f"""--- {test_file} 

130+++ {test_file} 

131@@ -1,4 +1,4 @@ 

132 #!/bin/bash 

133-if [ "$foo" = "bar" ]; then 

134+if [ "$foo" = "bar" ]; then 

135 echo "match" 

136 fi""" 

137 

138 call_count = 0 

139 

140 def mock_run_subprocess( 

141 cmd: list[str], 

142 timeout: int, 

143 cwd: str | None = None, 

144 ) -> tuple[bool, str]: 

145 """Mock subprocess that returns diff on check, success on fix. 

146 

147 Args: 

148 cmd: Command list. 

149 timeout: Timeout in seconds. 

150 cwd: Working directory. 

151 

152 Returns: 

153 Tuple of (success, output). 

154 """ 

155 nonlocal call_count 

156 call_count += 1 

157 # First call is check with -d flag 

158 if "-d" in cmd: 

159 return (False, shfmt_diff_output) 

160 # Second call is fix with -w flag 

161 return (True, "") 

162 

163 with patch( 

164 "lintro.plugins.execution_preparation.verify_tool_version", 

165 return_value=None, 

166 ): 

167 with patch.object( 

168 shfmt_plugin, 

169 "_run_subprocess", 

170 side_effect=mock_run_subprocess, 

171 ): 

172 result = shfmt_plugin.fix([str(test_file)], {}) 

173 

174 assert_that(result.success).is_true() 

175 assert_that(result.fixed_issues_count).is_greater_than(0) 

176 # Verify the mock was called expected number of times (check + fix) 

177 assert_that(call_count).is_equal_to(2) 

178 

179 

180def test_fix_with_nothing_to_fix( 

181 shfmt_plugin: ShfmtPlugin, 

182 tmp_path: Path, 

183) -> None: 

184 """Fix returns success when no fixes needed. 

185 

186 Args: 

187 shfmt_plugin: The ShfmtPlugin instance to test. 

188 tmp_path: Temporary directory path for test files. 

189 """ 

190 test_file = tmp_path / "test_script.sh" 

191 test_file.write_text('#!/bin/bash\necho "hello"\n') 

192 

193 with patch( 

194 "lintro.plugins.execution_preparation.verify_tool_version", 

195 return_value=None, 

196 ): 

197 with patch.object( 

198 shfmt_plugin, 

199 "_run_subprocess", 

200 return_value=(True, ""), 

201 ): 

202 result = shfmt_plugin.fix([str(test_file)], {}) 

203 

204 assert_that(result.success).is_true() 

205 assert_that(result.output).contains("No fixes needed")