Coverage for tests / integration / tools / test_yamllint_integration.py: 100%

81 statements  

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

1"""Integration tests for Yamllint tool definition. 

2 

3These tests require yamllint to be installed and available in PATH. 

4They verify the YamllintPlugin definition, check command, and output preservation. 

5""" 

6 

7from __future__ import annotations 

8 

9import shutil 

10from collections.abc import Callable 

11from pathlib import Path 

12from typing import TYPE_CHECKING 

13 

14import pytest 

15from assertpy import assert_that 

16 

17if TYPE_CHECKING: 

18 from lintro.plugins.base import BaseToolPlugin 

19 

20# Skip all tests if yamllint is not installed 

21pytestmark = pytest.mark.skipif( 

22 shutil.which("yamllint") is None, 

23 reason="yamllint not installed", 

24) 

25 

26 

27@pytest.fixture 

28def temp_yaml_file_valid(tmp_path: Path) -> str: 

29 """Create a temporary valid YAML file. 

30 

31 Args: 

32 tmp_path: Pytest fixture providing a temporary directory. 

33 

34 Returns: 

35 Path to the created file as a string. 

36 """ 

37 file_path = tmp_path / "valid.yaml" 

38 file_path.write_text( 

39 """\ 

40--- 

41name: test 

42version: 1.0.0 

43description: A valid YAML file 

44items: 

45 - one 

46 - two 

47 - three 

48""", 

49 ) 

50 return str(file_path) 

51 

52 

53@pytest.fixture 

54def temp_yaml_file_with_issues(tmp_path: Path) -> str: 

55 """Create a temporary YAML file with linting issues. 

56 

57 Creates a file with trailing spaces and indentation issues 

58 that yamllint should detect. 

59 

60 Args: 

61 tmp_path: Pytest fixture providing a temporary directory. 

62 

63 Returns: 

64 Path to the created file as a string. 

65 """ 

66 file_path = tmp_path / "issues.yaml" 

67 # Note: trailing spaces after "test" and inconsistent indentation 

68 file_path.write_text( 

69 "name: test \n" # trailing spaces 

70 "items:\n" 

71 " - one\n" 

72 " - two\n" # wrong indentation (3 spaces instead of 2) 

73 " - three\n", 

74 ) 

75 return str(file_path) 

76 

77 

78@pytest.fixture 

79def temp_yaml_file_syntax_error(tmp_path: Path) -> str: 

80 """Create a temporary YAML file with syntax errors. 

81 

82 Creates a file with invalid YAML syntax that yamllint will report 

83 but may not produce parseable issue lines. 

84 

85 Args: 

86 tmp_path: Pytest fixture providing a temporary directory. 

87 

88 Returns: 

89 Path to the created file as a string. 

90 """ 

91 file_path = tmp_path / "syntax_error.yaml" 

92 # Invalid YAML: duplicate keys and malformed structure 

93 file_path.write_text( 

94 """\ 

95name: test 

96name: duplicate 

97items: 

98 - one 

99 - : invalid 

100""", 

101 ) 

102 return str(file_path) 

103 

104 

105# --- Tests for YamllintPlugin definition --- 

106 

107 

108@pytest.mark.parametrize( 

109 ("attr", "expected"), 

110 [ 

111 ("name", "yamllint"), 

112 ("can_fix", False), 

113 ], 

114 ids=["name", "can_fix"], 

115) 

116def test_definition_attributes( 

117 get_plugin: Callable[[str], BaseToolPlugin], 

118 attr: str, 

119 expected: object, 

120) -> None: 

121 """Verify YamllintPlugin definition has correct attribute values. 

122 

123 Args: 

124 get_plugin: Fixture factory to get plugin instances. 

125 attr: The attribute name to check on the definition. 

126 expected: The expected value of the attribute. 

127 """ 

128 yamllint_plugin = get_plugin("yamllint") 

129 assert_that(getattr(yamllint_plugin.definition, attr)).is_equal_to(expected) 

130 

131 

132def test_definition_file_patterns( 

133 get_plugin: Callable[[str], BaseToolPlugin], 

134) -> None: 

135 """Verify YamllintPlugin definition includes YAML file patterns. 

136 

137 Args: 

138 get_plugin: Fixture factory to get plugin instances. 

139 """ 

140 yamllint_plugin = get_plugin("yamllint") 

141 assert_that(yamllint_plugin.definition.file_patterns).contains("*.yml") 

142 assert_that(yamllint_plugin.definition.file_patterns).contains("*.yaml") 

143 

144 

145# --- Integration tests for yamllint check command --- 

146 

147 

148def test_check_valid_yaml_file( 

149 get_plugin: Callable[[str], BaseToolPlugin], 

150 temp_yaml_file_valid: str, 

151) -> None: 

152 """Verify yamllint check passes on valid YAML files. 

153 

154 Args: 

155 get_plugin: Fixture factory to get plugin instances. 

156 temp_yaml_file_valid: Path to valid YAML file. 

157 """ 

158 yamllint_plugin = get_plugin("yamllint") 

159 result = yamllint_plugin.check([temp_yaml_file_valid], {}) 

160 

161 assert_that(result).is_not_none() 

162 assert_that(result.name).is_equal_to("yamllint") 

163 assert_that(result.success).is_true() 

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

165 

166 

167def test_check_yaml_file_with_issues( 

168 get_plugin: Callable[[str], BaseToolPlugin], 

169 temp_yaml_file_with_issues: str, 

170) -> None: 

171 """Verify yamllint check detects issues in problematic YAML files. 

172 

173 Args: 

174 get_plugin: Fixture factory to get plugin instances. 

175 temp_yaml_file_with_issues: Path to file with YAML issues. 

176 """ 

177 yamllint_plugin = get_plugin("yamllint") 

178 result = yamllint_plugin.check([temp_yaml_file_with_issues], {}) 

179 

180 assert_that(result).is_not_none() 

181 assert_that(result.name).is_equal_to("yamllint") 

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

183 # Verify raw output is preserved when there are issues 

184 assert_that(result.output).is_not_none() 

185 

186 

187def test_check_yaml_file_with_syntax_error( 

188 get_plugin: Callable[[str], BaseToolPlugin], 

189 temp_yaml_file_syntax_error: str, 

190) -> None: 

191 """Verify yamllint check handles syntax errors and preserves output. 

192 

193 This tests the bug fix where raw output was being discarded when 

194 yamllint failed but parsing produced no issues. 

195 

196 Args: 

197 get_plugin: Fixture factory to get plugin instances. 

198 temp_yaml_file_syntax_error: Path to file with YAML syntax errors. 

199 """ 

200 yamllint_plugin = get_plugin("yamllint") 

201 result = yamllint_plugin.check([temp_yaml_file_syntax_error], {}) 

202 

203 assert_that(result).is_not_none() 

204 assert_that(result.name).is_equal_to("yamllint") 

205 # Yamllint must report failure or issues for syntax errors - never silently pass 

206 assert (not result.success) or (result.issues_count > 0) 

207 # The key assertion: output should always be preserved even if parsing fails 

208 # to extract structured issues 

209 assert_that(result.output).is_not_none() 

210 

211 

212def test_check_empty_directory( 

213 get_plugin: Callable[[str], BaseToolPlugin], 

214 tmp_path: Path, 

215) -> None: 

216 """Verify yamllint check handles empty directories gracefully. 

217 

218 Args: 

219 get_plugin: Fixture factory to get plugin instances. 

220 tmp_path: Pytest fixture providing a temporary directory. 

221 """ 

222 yamllint_plugin = get_plugin("yamllint") 

223 result = yamllint_plugin.check([str(tmp_path)], {}) 

224 

225 assert_that(result).is_not_none() 

226 assert_that(result.success).is_true() 

227 

228 

229def test_check_nonexistent_file( 

230 get_plugin: Callable[[str], BaseToolPlugin], 

231 tmp_path: Path, 

232) -> None: 

233 """Verify yamllint check raises FileNotFoundError for nonexistent files. 

234 

235 Args: 

236 get_plugin: Fixture factory to get plugin instances. 

237 tmp_path: Pytest fixture providing a temporary directory. 

238 """ 

239 nonexistent = tmp_path / "nonexistent.yaml" 

240 yamllint_plugin = get_plugin("yamllint") 

241 

242 with pytest.raises(FileNotFoundError): 

243 yamllint_plugin.check([str(nonexistent)], {}) 

244 

245 

246def test_check_preserves_output_on_failure( 

247 get_plugin: Callable[[str], BaseToolPlugin], 

248 tmp_path: Path, 

249) -> None: 

250 """Verify raw output is preserved when yamllint fails. 

251 

252 This is a regression test for the bug where error messages were 

253 being discarded when the tool failed but parsing produced no 

254 structured issues. 

255 

256 Args: 

257 get_plugin: Fixture factory to get plugin instances. 

258 tmp_path: Pytest fixture providing a temporary directory. 

259 """ 

260 # Create a file that will cause yamllint to fail with unparseable output 

261 # by using completely invalid YAML 

262 invalid_file = tmp_path / "broken.yaml" 

263 invalid_file.write_text( 

264 """\ 

265{{{invalid: yaml: content::: 

266 - this: [is: broken 

267""", 

268 ) 

269 

270 yamllint_plugin = get_plugin("yamllint") 

271 result = yamllint_plugin.check([str(invalid_file)], {}) 

272 

273 assert_that(result).is_not_none() 

274 assert_that(result.name).is_equal_to("yamllint") 

275 # The critical assertion: when yamllint fails, output must be preserved 

276 # so users can see what went wrong 

277 if not result.success: 

278 assert_that(result.output).is_not_none() 

279 assert_that(result.output).is_not_empty() 

280 

281 

282# --- Tests for YamllintPlugin.set_options method --- 

283 

284 

285@pytest.mark.parametrize( 

286 ("option_name", "option_value"), 

287 [ 

288 ("strict", True), 

289 ("relaxed", True), 

290 ("no_warnings", True), 

291 ], 

292 ids=["strict", "relaxed", "no_warnings"], 

293) 

294def test_set_options( 

295 get_plugin: Callable[[str], BaseToolPlugin], 

296 option_name: str, 

297 option_value: object, 

298) -> None: 

299 """Verify YamllintPlugin.set_options correctly sets various options. 

300 

301 Args: 

302 get_plugin: Fixture factory to get plugin instances. 

303 option_name: Name of the option to set. 

304 option_value: Value to set for the option. 

305 """ 

306 yamllint_plugin = get_plugin("yamllint") 

307 yamllint_plugin.set_options(**{option_name: option_value}) 

308 assert_that(yamllint_plugin.options.get(option_name)).is_equal_to(option_value) 

309 

310 

311def test_set_options_format( 

312 get_plugin: Callable[[str], BaseToolPlugin], 

313) -> None: 

314 """Verify format option is normalized correctly. 

315 

316 Args: 

317 get_plugin: Fixture factory to get plugin instances. 

318 """ 

319 yamllint_plugin = get_plugin("yamllint") 

320 yamllint_plugin.set_options(format="standard") 

321 assert_that(yamllint_plugin.options.get("format")).is_equal_to("standard")