Coverage for tests / unit / tools / rustfmt / test_error_handling.py: 100%
86 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"""Unit tests for rustfmt plugin error handling."""
3from __future__ import annotations
5import subprocess
6from pathlib import Path
7from unittest.mock import patch
9from assertpy import assert_that
11from lintro.parsers.rustfmt.rustfmt_parser import parse_rustfmt_output
12from lintro.tools.definitions.rustfmt import RustfmtPlugin
14# =============================================================================
15# Tests for timeout handling
16# =============================================================================
19def test_check_with_timeout(
20 rustfmt_plugin: RustfmtPlugin,
21 tmp_path: Path,
22) -> None:
23 """Check handles timeout correctly.
25 Args:
26 rustfmt_plugin: The RustfmtPlugin instance to test.
27 tmp_path: Temporary directory path for test files.
28 """
29 cargo_toml = tmp_path / "Cargo.toml"
30 cargo_toml.write_text('[package]\nname = "test"\nversion = "0.1.0"')
32 test_file = tmp_path / "src" / "main.rs"
33 test_file.parent.mkdir(parents=True, exist_ok=True)
34 test_file.write_text("fn main() {}")
36 with patch(
37 "lintro.plugins.execution_preparation.verify_tool_version",
38 return_value=None,
39 ):
40 with patch.object(
41 rustfmt_plugin,
42 "_run_subprocess",
43 side_effect=subprocess.TimeoutExpired(cmd=["cargo", "fmt"], timeout=60),
44 ):
45 result = rustfmt_plugin.check([str(test_file)], {})
47 assert_that(result.success).is_false()
48 assert_that(result.output).contains("timed out")
49 assert_that(result.issues_count).is_equal_to(1)
52def test_fix_with_timeout_on_initial_check(
53 rustfmt_plugin: RustfmtPlugin,
54 tmp_path: Path,
55) -> None:
56 """Fix handles timeout on initial check correctly.
58 Args:
59 rustfmt_plugin: The RustfmtPlugin instance to test.
60 tmp_path: Temporary directory path for test files.
61 """
62 cargo_toml = tmp_path / "Cargo.toml"
63 cargo_toml.write_text('[package]\nname = "test"\nversion = "0.1.0"')
65 test_file = tmp_path / "src" / "main.rs"
66 test_file.parent.mkdir(parents=True, exist_ok=True)
67 test_file.write_text("fn main() {}")
69 with patch(
70 "lintro.plugins.execution_preparation.verify_tool_version",
71 return_value=None,
72 ):
73 with patch.object(
74 rustfmt_plugin,
75 "_run_subprocess",
76 side_effect=subprocess.TimeoutExpired(cmd=["cargo", "fmt"], timeout=60),
77 ):
78 result = rustfmt_plugin.fix([str(test_file)], {})
80 assert_that(result.success).is_false()
81 assert_that(result.output).contains("timed out")
82 # Timeout is counted as an execution failure (consistent with clippy)
83 assert_that(result.issues_count).is_equal_to(1)
86def test_fix_with_timeout_on_fix_command(
87 rustfmt_plugin: RustfmtPlugin,
88 tmp_path: Path,
89) -> None:
90 """Fix handles timeout on fix command correctly.
92 Args:
93 rustfmt_plugin: The RustfmtPlugin instance to test.
94 tmp_path: Temporary directory path for test files.
95 """
96 cargo_toml = tmp_path / "Cargo.toml"
97 cargo_toml.write_text('[package]\nname = "test"\nversion = "0.1.0"')
99 test_file = tmp_path / "src" / "main.rs"
100 test_file.parent.mkdir(parents=True, exist_ok=True)
101 test_file.write_text("fn main(){}")
103 call_count = 0
105 def mock_run(
106 cmd: list[str],
107 timeout: int,
108 cwd: str | None = None,
109 ) -> tuple[bool, str]:
110 """Mock subprocess that times out on fix command.
112 Args:
113 cmd: Command list.
114 timeout: Timeout in seconds.
115 cwd: Working directory.
117 Returns:
118 tuple[bool, str]: Tuple of (success, output).
120 Raises:
121 subprocess.TimeoutExpired: On second call (fix command).
122 """
123 nonlocal call_count
124 call_count += 1
125 if call_count == 1:
126 # First check - issues found
127 return (False, "Diff in src/main.rs:1:")
128 # Fix command times out
129 raise subprocess.TimeoutExpired(cmd=cmd, timeout=timeout)
131 with patch(
132 "lintro.plugins.execution_preparation.verify_tool_version",
133 return_value=None,
134 ):
135 with patch.object(rustfmt_plugin, "_run_subprocess", side_effect=mock_run):
136 result = rustfmt_plugin.fix([str(test_file)], {})
138 assert_that(result.success).is_false()
139 assert_that(result.output).contains("timed out")
140 assert_that(result.initial_issues_count).is_equal_to(1)
141 assert_that(result.fixed_issues_count).is_equal_to(0)
144def test_fix_with_timeout_on_verification(
145 rustfmt_plugin: RustfmtPlugin,
146 tmp_path: Path,
147) -> None:
148 """Fix handles timeout on verification check correctly.
150 Args:
151 rustfmt_plugin: The RustfmtPlugin instance to test.
152 tmp_path: Temporary directory path for test files.
153 """
154 cargo_toml = tmp_path / "Cargo.toml"
155 cargo_toml.write_text('[package]\nname = "test"\nversion = "0.1.0"')
157 test_file = tmp_path / "src" / "main.rs"
158 test_file.parent.mkdir(parents=True, exist_ok=True)
159 test_file.write_text("fn main(){}")
161 call_count = 0
163 def mock_run(
164 cmd: list[str],
165 timeout: int,
166 cwd: str | None = None,
167 ) -> tuple[bool, str]:
168 """Mock subprocess that times out on verification.
170 Args:
171 cmd: Command list.
172 timeout: Timeout in seconds.
173 cwd: Working directory.
175 Returns:
176 tuple[bool, str]: Tuple of (success, output).
178 Raises:
179 subprocess.TimeoutExpired: On third call (verification).
180 """
181 nonlocal call_count
182 call_count += 1
183 if call_count == 1:
184 # First check - issues found
185 return (False, "Diff in src/main.rs:1:")
186 elif call_count == 2:
187 # Fix command succeeds
188 return (True, "")
189 # Verification times out
190 raise subprocess.TimeoutExpired(cmd=cmd, timeout=timeout)
192 with patch(
193 "lintro.plugins.execution_preparation.verify_tool_version",
194 return_value=None,
195 ):
196 with patch.object(rustfmt_plugin, "_run_subprocess", side_effect=mock_run):
197 result = rustfmt_plugin.fix([str(test_file)], {})
199 assert_that(result.success).is_false()
200 assert_that(result.output).contains("timed out")
201 assert_that(result.initial_issues_count).is_equal_to(1)
202 assert_that(result.fixed_issues_count).is_equal_to(0)
205# =============================================================================
206# Tests for output parsing
207# =============================================================================
210def test_parse_rustfmt_output_with_diff() -> None:
211 """Parse diff output from rustfmt."""
212 output = """Diff in src/main.rs:1:
213-fn main(){let x=1;}
214+fn main() {
215+ let x = 1;
216+}"""
217 issues = parse_rustfmt_output(output)
219 assert_that(issues).is_length(1)
220 assert_that(issues[0].file).contains("main.rs")
223def test_parse_rustfmt_output_multiple_files() -> None:
224 """Parse output with multiple file diffs."""
225 output = """Diff in src/main.rs:1:
226-fn main(){}
227+fn main() {}
228Diff in src/lib.rs:5:
229-fn foo(){}
230+fn foo() {}"""
231 issues = parse_rustfmt_output(output)
233 assert_that(issues).is_length(2)
236def test_parse_rustfmt_output_empty() -> None:
237 """Parse empty output returns empty list."""
238 issues = parse_rustfmt_output("")
240 assert_that(issues).is_empty()
243def test_parse_rustfmt_output_none() -> None:
244 """Parse None output returns empty list."""
245 issues = parse_rustfmt_output(None)
247 assert_that(issues).is_empty()