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
« prev ^ index » next coverage.py v7.13.0, created at 2026-04-03 18:53 +0000
1"""Integration tests for Yamllint tool definition.
3These tests require yamllint to be installed and available in PATH.
4They verify the YamllintPlugin definition, check command, and output preservation.
5"""
7from __future__ import annotations
9import shutil
10from collections.abc import Callable
11from pathlib import Path
12from typing import TYPE_CHECKING
14import pytest
15from assertpy import assert_that
17if TYPE_CHECKING:
18 from lintro.plugins.base import BaseToolPlugin
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)
27@pytest.fixture
28def temp_yaml_file_valid(tmp_path: Path) -> str:
29 """Create a temporary valid YAML file.
31 Args:
32 tmp_path: Pytest fixture providing a temporary directory.
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)
53@pytest.fixture
54def temp_yaml_file_with_issues(tmp_path: Path) -> str:
55 """Create a temporary YAML file with linting issues.
57 Creates a file with trailing spaces and indentation issues
58 that yamllint should detect.
60 Args:
61 tmp_path: Pytest fixture providing a temporary directory.
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)
78@pytest.fixture
79def temp_yaml_file_syntax_error(tmp_path: Path) -> str:
80 """Create a temporary YAML file with syntax errors.
82 Creates a file with invalid YAML syntax that yamllint will report
83 but may not produce parseable issue lines.
85 Args:
86 tmp_path: Pytest fixture providing a temporary directory.
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)
105# --- Tests for YamllintPlugin definition ---
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.
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)
132def test_definition_file_patterns(
133 get_plugin: Callable[[str], BaseToolPlugin],
134) -> None:
135 """Verify YamllintPlugin definition includes YAML file patterns.
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")
145# --- Integration tests for yamllint check command ---
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.
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], {})
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)
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.
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], {})
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()
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.
193 This tests the bug fix where raw output was being discarded when
194 yamllint failed but parsing produced no issues.
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], {})
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()
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.
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)], {})
225 assert_that(result).is_not_none()
226 assert_that(result.success).is_true()
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.
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")
242 with pytest.raises(FileNotFoundError):
243 yamllint_plugin.check([str(nonexistent)], {})
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.
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.
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 )
270 yamllint_plugin = get_plugin("yamllint")
271 result = yamllint_plugin.check([str(invalid_file)], {})
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()
282# --- Tests for YamllintPlugin.set_options method ---
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.
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)
311def test_set_options_format(
312 get_plugin: Callable[[str], BaseToolPlugin],
313) -> None:
314 """Verify format option is normalized correctly.
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")