Coverage for tests / integration / tools / test_ruff_integration.py: 100%
104 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 Ruff tool definition.
3These tests require ruff to be installed and available in PATH.
4They verify the RuffPlugin definition, check command, fix command, and set_options method.
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 ruff is not installed
21pytestmark = pytest.mark.skipif(
22 shutil.which("ruff") is None,
23 reason="ruff not installed",
24)
27@pytest.fixture
28def temp_python_file_with_issues(tmp_path: Path) -> str:
29 """Create a temporary Python file with lint issues.
31 Creates a file containing code with lint issues that Ruff
32 should detect, including:
33 - Unused imports
34 - Missing whitespace around operators
36 Args:
37 tmp_path: Pytest fixture providing a temporary directory.
39 Returns:
40 Path to the created file as a string.
41 """
42 file_path = tmp_path / "test_file.py"
43 file_path.write_text(
44 """\
45import os
46import sys # unused import
47x=1 # missing whitespace around operator
48def foo():
49 pass
50""",
51 )
52 return str(file_path)
55@pytest.fixture
56def temp_python_file_clean(tmp_path: Path) -> str:
57 """Create a temporary Python file with no lint issues.
59 Creates a file containing clean Python code that should pass
60 Ruff linting without issues.
62 Args:
63 tmp_path: Pytest fixture providing a temporary directory.
65 Returns:
66 Path to the created file as a string.
67 """
68 file_path = tmp_path / "clean_file.py"
69 file_path.write_text(
70 """\
71\"\"\"A clean module.\"\"\"
74def hello() -> str:
75 \"\"\"Return a greeting.\"\"\"
76 return "Hello, World!"
77""",
78 )
79 return str(file_path)
82@pytest.fixture
83def temp_python_file_formatting_issues(tmp_path: Path) -> str:
84 """Create a temporary Python file with formatting issues.
86 Creates a file containing code with formatting issues that Ruff format
87 should fix, including:
88 - Missing spaces around operators
89 - Missing blank lines between functions
90 - Long line that should be wrapped
92 Args:
93 tmp_path: Pytest fixture providing a temporary directory.
95 Returns:
96 Path to the created file as a string.
97 """
98 file_path = tmp_path / "format_file.py"
99 file_path.write_text(
100 """\
101def foo(a,b,c):
102 x=a+b+c
103 return x
104def bar(x,y):
105 z=x*y
106 return z
107very_long_variable_name={"key1":"value1","key2":"value2","key3":"value3","key4":"value4"}
108""",
109 )
110 return str(file_path)
113# --- Tests for RuffPlugin definition ---
116@pytest.mark.parametrize(
117 ("attr", "expected"),
118 [
119 ("name", "ruff"),
120 ("can_fix", True),
121 ],
122 ids=["name", "can_fix"],
123)
124def test_definition_attributes(
125 get_plugin: Callable[[str], BaseToolPlugin],
126 attr: str,
127 expected: object,
128) -> None:
129 """Verify RuffPlugin definition has correct attribute values.
131 Tests that the plugin definition exposes the expected values for
132 name and can_fix attributes.
134 Args:
135 get_plugin: Fixture factory to get plugin instances.
136 attr: The attribute name to check on the definition.
137 expected: The expected value of the attribute.
138 """
139 ruff_plugin = get_plugin("ruff")
140 assert_that(getattr(ruff_plugin.definition, attr)).is_equal_to(expected)
143def test_definition_file_patterns(get_plugin: Callable[[str], BaseToolPlugin]) -> None:
144 """Verify RuffPlugin definition includes Python file patterns.
146 Tests that the plugin is configured to handle Python files (*.py).
148 Args:
149 get_plugin: Fixture factory to get plugin instances.
150 """
151 ruff_plugin = get_plugin("ruff")
152 assert_that(ruff_plugin.definition.file_patterns).contains("*.py")
155def test_definition_has_version_command(
156 get_plugin: Callable[[str], BaseToolPlugin],
157) -> None:
158 """Verify RuffPlugin definition has a version command.
160 Tests that the plugin exposes a version command for checking
161 the installed Ruff version.
163 Args:
164 get_plugin: Fixture factory to get plugin instances.
165 """
166 ruff_plugin = get_plugin("ruff")
167 assert_that(ruff_plugin.definition.version_command).is_not_none()
170# --- Integration tests for ruff check command ---
173def test_check_file_with_issues(
174 get_plugin: Callable[[str], BaseToolPlugin],
175 temp_python_file_with_issues: str,
176) -> None:
177 """Verify Ruff check detects lint issues in problematic files.
179 Runs Ruff on a file containing lint issues and verifies that
180 issues are found.
182 Args:
183 get_plugin: Fixture factory to get plugin instances.
184 temp_python_file_with_issues: Path to file with lint issues.
185 """
186 ruff_plugin = get_plugin("ruff")
187 result = ruff_plugin.check([temp_python_file_with_issues], {})
189 assert_that(result).is_not_none()
190 assert_that(result.name).is_equal_to("ruff")
191 assert_that(result.issues_count).is_greater_than(0)
194def test_check_clean_file(
195 get_plugin: Callable[[str], BaseToolPlugin],
196 temp_python_file_clean: str,
197) -> None:
198 """Verify Ruff check passes on clean files.
200 Runs Ruff on a clean file and verifies no issues are found.
202 Args:
203 get_plugin: Fixture factory to get plugin instances.
204 temp_python_file_clean: Path to clean file.
205 """
206 ruff_plugin = get_plugin("ruff")
207 result = ruff_plugin.check([temp_python_file_clean], {})
209 assert_that(result).is_not_none()
210 assert_that(result.name).is_equal_to("ruff")
211 assert_that(result.success).is_true()
214def test_check_nonexistent_file(
215 get_plugin: Callable[[str], BaseToolPlugin],
216 tmp_path: Path,
217) -> None:
218 """Verify Ruff check raises error for nonexistent files.
220 Attempts to run Ruff on a nonexistent file and verifies that
221 a FileNotFoundError is raised.
223 Args:
224 get_plugin: Fixture factory to get plugin instances.
225 tmp_path: Pytest fixture providing a temporary directory.
226 """
227 ruff_plugin = get_plugin("ruff")
228 nonexistent = str(tmp_path / "nonexistent.py")
229 with pytest.raises(FileNotFoundError):
230 ruff_plugin.check([nonexistent], {})
233def test_check_empty_directory(
234 get_plugin: Callable[[str], BaseToolPlugin],
235 tmp_path: Path,
236) -> None:
237 """Verify Ruff check handles empty directories gracefully.
239 Runs Ruff on an empty directory and verifies a result is returned
240 with zero issues.
242 Args:
243 get_plugin: Fixture factory to get plugin instances.
244 tmp_path: Pytest fixture providing a temporary directory.
245 """
246 ruff_plugin = get_plugin("ruff")
247 result = ruff_plugin.check([str(tmp_path)], {})
249 assert_that(result).is_not_none()
250 assert_that(result.issues_count).is_equal_to(0)
253# --- Integration tests for ruff fix command ---
256def test_fix_formats_file(
257 get_plugin: Callable[[str], BaseToolPlugin],
258 temp_python_file_formatting_issues: str,
259) -> None:
260 """Verify Ruff fix reformats files with formatting issues.
262 Runs Ruff fix on a file with formatting issues and verifies
263 the file content changes.
265 Args:
266 get_plugin: Fixture factory to get plugin instances.
267 temp_python_file_formatting_issues: Path to file with formatting issues.
268 """
269 ruff_plugin = get_plugin("ruff")
270 original = Path(temp_python_file_formatting_issues).read_text()
272 result = ruff_plugin.fix([temp_python_file_formatting_issues], {})
274 assert_that(result).is_not_none()
275 assert_that(result.name).is_equal_to("ruff")
277 new_content = Path(temp_python_file_formatting_issues).read_text()
278 assert_that(new_content).is_not_equal_to(original)
281def test_fix_removes_unused_imports(
282 get_plugin: Callable[[str], BaseToolPlugin],
283 tmp_path: Path,
284) -> None:
285 """Verify Ruff fix removes unused imports when configured.
287 Runs Ruff fix with F401 rule selected on a file with unused imports
288 and verifies fixes are applied.
290 Args:
291 get_plugin: Fixture factory to get plugin instances.
292 tmp_path: Pytest fixture providing a temporary directory.
293 """
294 ruff_plugin = get_plugin("ruff")
295 file_path = tmp_path / "unused_import.py"
296 file_path.write_text("import os\nimport sys\n\nx = 1\n")
298 ruff_plugin.set_options(select=["F401"])
300 result = ruff_plugin.fix([str(file_path)], {})
302 assert_that(result).is_not_none()
305# --- Integration tests for ruff check with various options ---
308@pytest.mark.parametrize(
309 ("option_name", "option_value"),
310 [
311 ("select", ["F401"]),
312 ("ignore", ["E501", "F401"]),
313 ("line_length", 120),
314 ],
315 ids=["select_rules", "ignore_rules", "line_length"],
316)
317def test_check_with_options(
318 get_plugin: Callable[[str], BaseToolPlugin],
319 temp_python_file_with_issues: str,
320 option_name: str,
321 option_value: object,
322) -> None:
323 """Verify Ruff check works with various configuration options.
325 Runs Ruff with different options configured and verifies the
326 check completes successfully.
328 Args:
329 get_plugin: Fixture factory to get plugin instances.
330 temp_python_file_with_issues: Path to file with lint issues.
331 option_name: Name of the option to set.
332 option_value: Value to set for the option.
333 """
334 ruff_plugin = get_plugin("ruff")
335 ruff_plugin.set_options(**{option_name: option_value})
336 result = ruff_plugin.check([temp_python_file_with_issues], {})
338 assert_that(result).is_not_none()
339 assert_that(result.name).is_equal_to("ruff")
342# --- Integration tests for ruff fix with various options ---
345def test_fix_with_unsafe_fixes(
346 get_plugin: Callable[[str], BaseToolPlugin],
347 tmp_path: Path,
348) -> None:
349 """Verify Ruff fix works with unsafe fixes enabled.
351 Runs Ruff fix with unsafe_fixes option enabled and verifies
352 the fix completes.
354 Args:
355 get_plugin: Fixture factory to get plugin instances.
356 tmp_path: Pytest fixture providing a temporary directory.
357 """
358 ruff_plugin = get_plugin("ruff")
359 file_path = tmp_path / "unsafe_fix.py"
360 file_path.write_text("import os\nimport sys\n\nx = 1\n")
362 ruff_plugin.set_options(unsafe_fixes=True, select=["F401"])
363 result = ruff_plugin.fix([str(file_path)], {})
365 assert_that(result).is_not_none()
368def test_fix_with_format_disabled(
369 get_plugin: Callable[[str], BaseToolPlugin],
370 tmp_path: Path,
371) -> None:
372 """Verify Ruff fix works with formatting disabled.
374 Runs Ruff fix with format option disabled and verifies
375 the fix completes.
377 Args:
378 get_plugin: Fixture factory to get plugin instances.
379 tmp_path: Pytest fixture providing a temporary directory.
380 """
381 ruff_plugin = get_plugin("ruff")
382 file_path = tmp_path / "no_format.py"
383 file_path.write_text("x=1\ny=2\n")
385 ruff_plugin.set_options(format=False)
386 result = ruff_plugin.fix([str(file_path)], {})
388 assert_that(result).is_not_none()
391# --- Tests for RuffPlugin.set_options method ---
394@pytest.mark.parametrize(
395 ("option_name", "option_value", "expected"),
396 [
397 ("line_length", 100, 100),
398 ("select", ["E", "F"], ["E", "F"]),
399 ("ignore", ["E501"], ["E501"]),
400 ("unsafe_fixes", True, True),
401 ],
402 ids=["line_length", "select_rules", "ignore_rules", "unsafe_fixes"],
403)
404def test_set_options(
405 get_plugin: Callable[[str], BaseToolPlugin],
406 option_name: str,
407 option_value: object,
408 expected: object,
409) -> None:
410 """Verify RuffPlugin.set_options correctly sets various options.
412 Tests that plugin options can be set and retrieved correctly.
414 Args:
415 get_plugin: Fixture factory to get plugin instances.
416 option_name: Name of the option to set.
417 option_value: Value to set for the option.
418 expected: Expected value when retrieving the option.
419 """
420 ruff_plugin = get_plugin("ruff")
421 ruff_plugin.set_options(**{option_name: option_value})
422 assert_that(ruff_plugin.options.get(option_name)).is_equal_to(expected)
425@pytest.mark.parametrize(
426 ("option_value", "error_match"),
427 [
428 ("not an int", "must be an integer"),
429 (-1, None),
430 ],
431 ids=["invalid_type", "negative_value"],
432)
433def test_invalid_line_length(
434 get_plugin: Callable[[str], BaseToolPlugin],
435 option_value: object,
436 error_match: str | None,
437) -> None:
438 """Verify RuffPlugin.set_options rejects invalid line_length values.
440 Tests that invalid line_length values raise ValueError.
442 Args:
443 get_plugin: Fixture factory to get plugin instances.
444 option_value: Invalid value to set for line_length.
445 error_match: Expected error message pattern, or None for any ValueError.
446 """
447 ruff_plugin = get_plugin("ruff")
448 if error_match:
449 with pytest.raises(ValueError, match=error_match):
450 ruff_plugin.set_options(line_length=option_value)
451 else:
452 with pytest.raises(ValueError):
453 ruff_plugin.set_options(line_length=option_value)