Coverage for tests / unit / compatibility / test_compatibility_ruff_black.py: 99%
78 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"""Compatibility tests ensuring Ruff/Black policy interactions."""
3from __future__ import annotations
5from collections.abc import Callable
6from dataclasses import dataclass, field
7from typing import TYPE_CHECKING, Any
9import pytest
10from assertpy import assert_that
12if TYPE_CHECKING:
13 pass
15from lintro.enums.tool_name import ToolName
16from lintro.models.core.tool_result import ToolResult
17from lintro.tools import tool_manager
18from lintro.utils.execution.tool_configuration import ToolsToRunResult
19from lintro.utils.output import OutputManager
20from lintro.utils.tool_executor import run_lint_tools_simple
23@dataclass
24class FakeToolDefinition:
25 """Fake ToolDefinition for testing."""
27 name: str
28 can_fix: bool = False
29 description: str = ""
30 file_patterns: list[str] = field(default_factory=list)
31 native_configs: list[str] = field(default_factory=list)
34class FakeTool:
35 """Simple stub representing a tool with check/fix capability."""
37 def __init__(self, name: ToolName, can_fix: bool) -> None:
38 """Initialize stub tool.
40 Args:
41 name: Tool name.
42 can_fix: Whether the tool can apply fixes.
43 """
44 self.name = name
45 self._definition = FakeToolDefinition(name=str(name), can_fix=can_fix)
46 self.options: dict[str, Any] = {}
48 @property
49 def definition(self) -> FakeToolDefinition:
50 """Return the tool definition.
52 Returns:
53 FakeToolDefinition containing tool metadata.
54 """
55 return self._definition
57 @property
58 def can_fix(self) -> bool:
59 """Return whether the tool can fix issues.
61 Returns:
62 True if the tool can fix issues.
63 """
64 return self._definition.can_fix
66 def set_options(self, **kwargs: Any) -> None:
67 """Record provided options for later assertions.
69 Args:
70 **kwargs: Arbitrary option key-value pairs forwarded by the runner.
71 """
72 self.options.update(kwargs)
74 def reset_options(self) -> None:
75 """Reset options to defaults (no-op for stub)."""
76 self.options.clear()
78 def check(
79 self,
80 paths: list[str],
81 options: dict[str, Any] | None = None,
82 ) -> ToolResult:
83 """Return a successful empty result for lint checks.
85 Args:
86 paths: Target file or directory paths to check.
87 options: Optional tool options.
89 Returns:
90 ToolResult indicating success with zero issues.
91 """
92 return ToolResult(name=self.name, success=True, output="", issues_count=0)
94 def fix(
95 self,
96 paths: list[str],
97 options: dict[str, Any] | None = None,
98 ) -> ToolResult:
99 """Return a successful empty result for fixes.
101 Args:
102 paths: Target file or directory paths to fix.
103 options: Optional tool options.
105 Returns:
106 ToolResult indicating success with zero issues.
107 """
108 return ToolResult(name=self.name, success=True, output="", issues_count=0)
111def _stub_logger(monkeypatch: pytest.MonkeyPatch) -> None:
112 """Silence console logger for deterministic tests.
114 Args:
115 monkeypatch: Pytest monkeypatch fixture for patching objects.
116 """
117 import lintro.utils.console as cl
119 class SilentLogger:
120 def __getattr__(self, name: str) -> Callable[..., None]:
121 def _(*a: Any, **k: Any) -> None:
122 return None
124 return _
126 monkeypatch.setattr(cl, "create_logger", lambda *_a, **_k: SilentLogger())
129def _setup_tools(monkeypatch: pytest.MonkeyPatch) -> tuple[FakeTool, FakeTool]:
130 """Prepare stubbed tool manager and output manager plumbing.
132 Args:
133 monkeypatch: Pytest monkeypatch fixture for patching objects.
135 Returns:
136 Tuple of stubbed Ruff and Black tool instances.
137 """
138 import lintro.utils.tool_executor as te
140 ruff = FakeTool(ToolName.RUFF, can_fix=True)
141 black = FakeTool(ToolName.BLACK, can_fix=True)
142 tool_map = {ToolName.RUFF: ruff, ToolName.BLACK: black}
144 def fake_get_tools(
145 _tools: str | None,
146 _action: str,
147 *,
148 ignore_conflicts: bool = False, # noqa: ARG001 — must match caller kwarg name
149 ) -> ToolsToRunResult:
150 """Return tool names for ruff and black in order.
152 Args:
153 _tools: Optional tool selection string (ignored in tests).
154 _action: Runner action being executed (ignored in tests).
155 ignore_conflicts: Whether to ignore tool conflicts (ignored in tests).
157 Returns:
158 ToolsToRunResult with ruff and black tools.
159 """
160 return ToolsToRunResult(to_run=[ToolName.RUFF, ToolName.BLACK])
162 # Patch get_tools_to_run in the tool_executor module where it's imported
163 monkeypatch.setattr(te, "get_tools_to_run", fake_get_tools)
164 monkeypatch.setattr(
165 tool_manager,
166 "get_tool",
167 lambda name: tool_map[ToolName(name.lower())],
168 )
170 def noop_write_reports_from_results(
171 self: object,
172 results: list[ToolResult],
173 ) -> None:
174 """No-op writer used to avoid filesystem interaction.
176 Args:
177 self: Output manager instance under test.
178 results: Aggregated tool results to write.
180 Returns:
181 None.
182 """
183 return None
185 monkeypatch.setattr(
186 OutputManager,
187 "write_reports_from_results",
188 noop_write_reports_from_results,
189 )
191 return ruff, black
194def test_ruff_formatting_disabled_when_black_present(
195 monkeypatch: pytest.MonkeyPatch,
196) -> None:
197 """Black present: Ruff formatting should be disabled by default.
199 Args:
200 monkeypatch: Pytest monkeypatch fixture for patching objects.
201 """
202 _stub_logger(monkeypatch)
203 ruff, black = _setup_tools(monkeypatch)
205 code = run_lint_tools_simple(
206 action="fmt",
207 paths=["."],
208 tools="all",
209 tool_options=None,
210 exclude=None,
211 include_venv=False,
212 group_by="auto",
213 output_format="grid",
214 verbose=False,
215 raw_output=False,
216 )
218 assert_that(code).is_equal_to(0)
219 assert_that(ruff.options.get("format")).is_false()
222def test_ruff_formatting_respects_cli_override(
223 monkeypatch: pytest.MonkeyPatch,
224) -> None:
225 """CLI options should re-enable Ruff format and format_check.
227 Args:
228 monkeypatch: Pytest monkeypatch fixture for patching objects.
229 """
230 _stub_logger(monkeypatch)
231 ruff, black = _setup_tools(monkeypatch)
233 code = run_lint_tools_simple(
234 action="fmt",
235 paths=["."],
236 tools="all",
237 tool_options="ruff:format=True,ruff:format_check=True",
238 exclude=None,
239 include_venv=False,
240 group_by="auto",
241 output_format="grid",
242 verbose=False,
243 raw_output=False,
244 )
246 assert_that(code).is_equal_to(0)
247 assert_that(ruff.options.get("format")).is_true()
248 assert_that(ruff.options.get("format_check")).is_true()
251def test_ruff_format_check_disabled_in_check_when_black_present(
252 monkeypatch: pytest.MonkeyPatch,
253) -> None:
254 """Black present: Ruff format_check should be disabled in check.
256 Args:
257 monkeypatch: Pytest monkeypatch fixture for patching objects.
258 """
259 _stub_logger(monkeypatch)
260 ruff, black = _setup_tools(monkeypatch)
262 code = run_lint_tools_simple(
263 action="check",
264 paths=["."],
265 tools="all",
266 tool_options=None,
267 exclude=None,
268 include_venv=False,
269 group_by="auto",
270 output_format="grid",
271 verbose=False,
272 raw_output=False,
273 )
275 assert_that(code).is_equal_to(0)
276 assert_that(ruff.options.get("format_check")).is_false()