Coverage for tests / integration / tools / test_shfmt_integration.py: 100%
90 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 shfmt tool definition.
3These tests require shfmt to be installed and available in PATH.
4They verify the ShfmtPlugin 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 shfmt is not installed
21pytestmark = pytest.mark.skipif(
22 shutil.which("shfmt") is None,
23 reason="shfmt not installed",
24)
27@pytest.fixture
28def temp_shell_file_with_issues(tmp_path: Path) -> str:
29 """Create a temporary shell script with formatting issues.
31 Creates a file containing shell code with formatting issues that shfmt
32 should detect, including:
33 - Inconsistent indentation
34 - Missing spaces 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_script.sh"
43 file_path.write_text(
44 """\
45#!/bin/bash
46if [ "$1" = "test" ];then
47echo "hello"
48 echo "world"
49fi
50""",
51 )
52 return str(file_path)
55@pytest.fixture
56def temp_shell_file_clean(tmp_path: Path) -> str:
57 """Create a temporary shell script with no formatting issues.
59 Creates a file containing properly formatted shell code that should pass
60 shfmt checking 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_script.sh"
69 file_path.write_text(
70 """\
71#!/bin/bash
73# A clean shell script
74say_hello() {
75 echo "Hello, World!"
76}
78if [ -n "$1" ]; then
79 say_hello
80fi
81""",
82 )
83 return str(file_path)
86@pytest.fixture
87def temp_shell_file_complex_issues(tmp_path: Path) -> str:
88 """Create a temporary shell script with multiple formatting issues.
90 Creates a file containing code with various formatting issues that shfmt
91 should fix, including:
92 - No space after semicolon in for loop
93 - Inconsistent indentation
94 - Binary operator at end of line instead of start
96 Args:
97 tmp_path: Pytest fixture providing a temporary directory.
99 Returns:
100 Path to the created file as a string.
101 """
102 file_path = tmp_path / "complex_script.sh"
103 file_path.write_text(
104 """\
105#!/bin/bash
106for i in 1 2 3;do
107echo $i
108done
109if [ "$1" = "a" ] ||
110[ "$1" = "b" ]; then
111 echo "match"
112fi
113""",
114 )
115 return str(file_path)
118# --- Tests for ShfmtPlugin definition ---
121@pytest.mark.parametrize(
122 ("attr", "expected"),
123 [
124 ("name", "shfmt"),
125 ("can_fix", True),
126 ],
127 ids=["name", "can_fix"],
128)
129def test_definition_attributes(
130 get_plugin: Callable[[str], BaseToolPlugin],
131 attr: str,
132 expected: object,
133) -> None:
134 """Verify ShfmtPlugin definition has correct attribute values.
136 Tests that the plugin definition exposes the expected values for
137 name and can_fix attributes.
139 Args:
140 get_plugin: Fixture factory to get plugin instances.
141 attr: The attribute name to check on the definition.
142 expected: The expected value of the attribute.
143 """
144 shfmt_plugin = get_plugin("shfmt")
145 assert_that(getattr(shfmt_plugin.definition, attr)).is_equal_to(expected)
148def test_definition_file_patterns(
149 get_plugin: Callable[[str], BaseToolPlugin],
150) -> None:
151 """Verify ShfmtPlugin definition includes shell file patterns.
153 Tests that the plugin is configured to handle shell files (*.sh, *.bash, *.ksh).
155 Args:
156 get_plugin: Fixture factory to get plugin instances.
157 """
158 shfmt_plugin = get_plugin("shfmt")
159 assert_that(shfmt_plugin.definition.file_patterns).contains("*.sh")
160 assert_that(shfmt_plugin.definition.file_patterns).contains("*.bash")
163def test_definition_has_version_command(
164 get_plugin: Callable[[str], BaseToolPlugin],
165) -> None:
166 """Verify ShfmtPlugin definition has a version command.
168 Tests that the plugin exposes a version command for checking
169 the installed shfmt version.
171 Args:
172 get_plugin: Fixture factory to get plugin instances.
173 """
174 shfmt_plugin = get_plugin("shfmt")
175 assert_that(shfmt_plugin.definition.version_command).is_not_none()
178# --- Integration tests for shfmt check command ---
181def test_check_file_with_issues(
182 get_plugin: Callable[[str], BaseToolPlugin],
183 temp_shell_file_with_issues: str,
184) -> None:
185 """Verify shfmt check detects formatting issues in problematic files.
187 Runs shfmt on a file containing formatting issues and verifies that
188 issues are found.
190 Args:
191 get_plugin: Fixture factory to get plugin instances.
192 temp_shell_file_with_issues: Path to file with formatting issues.
193 """
194 shfmt_plugin = get_plugin("shfmt")
195 result = shfmt_plugin.check([temp_shell_file_with_issues], {})
197 assert_that(result).is_not_none()
198 assert_that(result.name).is_equal_to("shfmt")
199 assert_that(result.issues_count).is_greater_than(0)
202def test_check_clean_file(
203 get_plugin: Callable[[str], BaseToolPlugin],
204 temp_shell_file_clean: str,
205) -> None:
206 """Verify shfmt check passes on clean files.
208 Runs shfmt on a clean file and verifies no issues are found.
210 Args:
211 get_plugin: Fixture factory to get plugin instances.
212 temp_shell_file_clean: Path to clean file.
213 """
214 shfmt_plugin = get_plugin("shfmt")
215 result = shfmt_plugin.check([temp_shell_file_clean], {})
217 assert_that(result).is_not_none()
218 assert_that(result.name).is_equal_to("shfmt")
219 assert_that(result.success).is_true()
222def test_check_empty_directory(
223 get_plugin: Callable[[str], BaseToolPlugin],
224 tmp_path: Path,
225) -> None:
226 """Verify shfmt check handles empty directories gracefully.
228 Runs shfmt on an empty directory and verifies a result is returned
229 with zero issues.
231 Args:
232 get_plugin: Fixture factory to get plugin instances.
233 tmp_path: Pytest fixture providing a temporary directory.
234 """
235 shfmt_plugin = get_plugin("shfmt")
236 result = shfmt_plugin.check([str(tmp_path)], {})
238 assert_that(result).is_not_none()
239 assert_that(result.issues_count).is_equal_to(0)
242# --- Integration tests for shfmt fix command ---
245def test_fix_formats_file(
246 get_plugin: Callable[[str], BaseToolPlugin],
247 temp_shell_file_with_issues: str,
248) -> None:
249 """Verify shfmt fix reformats files with formatting issues.
251 Runs shfmt fix on a file with formatting issues and verifies
252 the file content changes.
254 Args:
255 get_plugin: Fixture factory to get plugin instances.
256 temp_shell_file_with_issues: Path to file with formatting issues.
257 """
258 shfmt_plugin = get_plugin("shfmt")
259 original = Path(temp_shell_file_with_issues).read_text()
261 result = shfmt_plugin.fix([temp_shell_file_with_issues], {})
263 assert_that(result).is_not_none()
264 assert_that(result.name).is_equal_to("shfmt")
266 new_content = Path(temp_shell_file_with_issues).read_text()
267 assert_that(new_content).is_not_equal_to(original)
270def test_fix_complex_file(
271 get_plugin: Callable[[str], BaseToolPlugin],
272 temp_shell_file_complex_issues: str,
273) -> None:
274 """Verify shfmt fix handles complex formatting issues.
276 Runs shfmt fix on a file with multiple formatting issues and verifies
277 fixes are applied.
279 Args:
280 get_plugin: Fixture factory to get plugin instances.
281 temp_shell_file_complex_issues: Path to file with complex issues.
282 """
283 shfmt_plugin = get_plugin("shfmt")
284 original = Path(temp_shell_file_complex_issues).read_text()
286 result = shfmt_plugin.fix([temp_shell_file_complex_issues], {})
288 assert_that(result).is_not_none()
290 new_content = Path(temp_shell_file_complex_issues).read_text()
291 assert_that(new_content).is_not_equal_to(original)
294def test_fix_clean_file_unchanged(
295 get_plugin: Callable[[str], BaseToolPlugin],
296 temp_shell_file_clean: str,
297) -> None:
298 """Verify shfmt fix doesn't change already formatted files.
300 Runs shfmt fix on a clean file and verifies the content stays the same.
302 Args:
303 get_plugin: Fixture factory to get plugin instances.
304 temp_shell_file_clean: Path to clean file.
305 """
306 shfmt_plugin = get_plugin("shfmt")
307 original = Path(temp_shell_file_clean).read_text()
309 result = shfmt_plugin.fix([temp_shell_file_clean], {})
311 assert_that(result).is_not_none()
312 assert_that(result.success).is_true()
314 new_content = Path(temp_shell_file_clean).read_text()
315 assert_that(new_content).is_equal_to(original)
318# --- Integration tests for shfmt check with various options ---
321@pytest.mark.parametrize(
322 ("option_name", "option_value"),
323 [
324 ("indent", 4),
325 ("binary_next_line", True),
326 ("switch_case_indent", True),
327 ("space_redirects", True),
328 ("language_dialect", "bash"),
329 ("simplify", True),
330 ],
331 ids=[
332 "indent",
333 "binary_next_line",
334 "switch_case_indent",
335 "space_redirects",
336 "language_dialect",
337 "simplify",
338 ],
339)
340def test_check_with_options(
341 get_plugin: Callable[[str], BaseToolPlugin],
342 temp_shell_file_with_issues: str,
343 option_name: str,
344 option_value: object,
345) -> None:
346 """Verify shfmt check works with various configuration options.
348 Runs shfmt with different options configured and verifies the
349 check completes successfully.
351 Args:
352 get_plugin: Fixture factory to get plugin instances.
353 temp_shell_file_with_issues: Path to file with formatting issues.
354 option_name: Name of the option to set.
355 option_value: Value to set for the option.
356 """
357 shfmt_plugin = get_plugin("shfmt")
358 shfmt_plugin.set_options(**{option_name: option_value})
359 result = shfmt_plugin.check([temp_shell_file_with_issues], {})
361 assert_that(result).is_not_none()
362 assert_that(result.name).is_equal_to("shfmt")
365# --- Tests for ShfmtPlugin.set_options method ---
368@pytest.mark.parametrize(
369 ("option_name", "option_value", "expected"),
370 [
371 ("indent", 4, 4),
372 ("indent", 0, 0),
373 ("binary_next_line", True, True),
374 ("switch_case_indent", True, True),
375 ("space_redirects", True, True),
376 ("language_dialect", "bash", "bash"),
377 ("language_dialect", "posix", "posix"),
378 ("simplify", True, True),
379 ],
380 ids=[
381 "indent_spaces",
382 "indent_tabs",
383 "binary_next_line",
384 "switch_case_indent",
385 "space_redirects",
386 "dialect_bash",
387 "dialect_posix",
388 "simplify",
389 ],
390)
391def test_set_options(
392 get_plugin: Callable[[str], BaseToolPlugin],
393 option_name: str,
394 option_value: object,
395 expected: object,
396) -> None:
397 """Verify ShfmtPlugin.set_options correctly sets various options.
399 Tests that plugin options can be set and retrieved correctly.
401 Args:
402 get_plugin: Fixture factory to get plugin instances.
403 option_name: Name of the option to set.
404 option_value: Value to set for the option.
405 expected: Expected value when retrieving the option.
406 """
407 shfmt_plugin = get_plugin("shfmt")
408 shfmt_plugin.set_options(**{option_name: option_value})
409 assert_that(shfmt_plugin.options.get(option_name)).is_equal_to(expected)
412def test_invalid_language_dialect(
413 get_plugin: Callable[[str], BaseToolPlugin],
414) -> None:
415 """Verify ShfmtPlugin.set_options rejects invalid language_dialect values.
417 Tests that invalid language_dialect values raise ValueError.
419 Args:
420 get_plugin: Fixture factory to get plugin instances.
421 """
422 shfmt_plugin = get_plugin("shfmt")
423 with pytest.raises(ValueError, match="Invalid language_dialect"):
424 shfmt_plugin.set_options(language_dialect="invalid")