Coverage for tests / unit / parsers / test_shfmt_parser.py: 100%
101 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 shfmt parser."""
3from __future__ import annotations
5import pytest
6from assertpy import assert_that
8from lintro.parsers.shfmt.shfmt_parser import parse_shfmt_output
11@pytest.mark.parametrize(
12 "output",
13 [
14 None,
15 "",
16 " \n \n ",
17 ],
18 ids=["none", "empty", "whitespace_only"],
19)
20def test_parse_shfmt_output_returns_empty_for_no_content(
21 output: str | None,
22) -> None:
23 """Parse empty, None, or whitespace-only output returns empty list.
25 Args:
26 output: The shfmt output to parse.
27 """
28 result = parse_shfmt_output(output)
29 assert_that(result).is_empty()
32def test_parse_shfmt_output_single_file_diff() -> None:
33 """Parse a single file diff output into one issue."""
34 output = """--- script.sh.orig
35+++ script.sh
36@@ -1,3 +1,3 @@
37-if [ "$foo" = "bar" ]; then
38+if [ "$foo" = "bar" ]; then
39 echo "match"
40 fi"""
41 result = parse_shfmt_output(output)
42 assert_that(result).is_length(1)
43 assert_that(result[0].file).is_equal_to("script.sh")
44 assert_that(result[0].line).is_equal_to(1)
45 assert_that(result[0].message).is_equal_to("Needs formatting")
46 assert_that(result[0].fixable).is_true()
47 assert_that(result[0].diff_content).contains("if [ ")
48 assert_that(result[0].diff_content).contains("if [ ")
51def test_parse_shfmt_output_multiple_files() -> None:
52 """Parse diff output with multiple files."""
53 output = """--- script1.sh.orig
54+++ script1.sh
55@@ -1,2 +1,2 @@
56-echo "hello"
57+echo "hello"
58--- script2.sh.orig
59+++ script2.sh
60@@ -5,2 +5,2 @@
61-if[ "$x" ]; then
62+if [ "$x" ]; then"""
63 result = parse_shfmt_output(output)
64 assert_that(result).is_length(2)
65 assert_that(result[0].file).is_equal_to("script1.sh")
66 assert_that(result[0].line).is_equal_to(1)
67 assert_that(result[1].file).is_equal_to("script2.sh")
68 assert_that(result[1].line).is_equal_to(5)
71def test_parse_shfmt_output_file_with_path() -> None:
72 """Parse diff with directory path in filename."""
73 output = """--- scripts/deploy/setup.sh.orig
74+++ scripts/deploy/setup.sh
75@@ -10,2 +10,2 @@
76-echo "deploying"
77+echo "deploying"
78"""
79 result = parse_shfmt_output(output)
80 assert_that(result).is_length(1)
81 assert_that(result[0].file).is_equal_to("scripts/deploy/setup.sh")
82 assert_that(result[0].line).is_equal_to(10)
85def test_parse_shfmt_output_extracts_diff_content() -> None:
86 """Verify diff content is captured correctly."""
87 output = """--- test.sh.orig
88+++ test.sh
89@@ -1,5 +1,5 @@
90 #!/bin/bash
91-x=1+2
92+x=1 + 2
93 echo $x
94"""
95 result = parse_shfmt_output(output)
96 assert_that(result).is_length(1)
97 assert_that(result[0].diff_content).contains("--- test.sh.orig")
98 assert_that(result[0].diff_content).contains("+++ test.sh")
99 assert_that(result[0].diff_content).contains("-x=1+2")
100 assert_that(result[0].diff_content).contains("+x=1 + 2")
103def test_parse_shfmt_output_multiple_hunks_uses_first_line() -> None:
104 """When multiple hunks exist, use the first hunk's line number."""
105 output = """--- multi.sh.orig
106+++ multi.sh
107@@ -3,2 +3,2 @@
108-echo "first"
109+echo "first"
110@@ -10,2 +10,2 @@
111-echo "second"
112+echo "second"
113"""
114 result = parse_shfmt_output(output)
115 assert_that(result).is_length(1)
116 # Should use line 3 from first hunk
117 assert_that(result[0].line).is_equal_to(3)
120def test_parse_shfmt_output_column_is_zero() -> None:
121 """Column is always 0 (shfmt doesn't provide column info)."""
122 output = """--- test.sh.orig
123+++ test.sh
124@@ -1 +1 @@
125-echo "test"
126+echo "test"
127"""
128 result = parse_shfmt_output(output)
129 assert_that(result[0].column).is_equal_to(0)
132def test_parse_shfmt_output_fixable_is_true() -> None:
133 """All shfmt issues are fixable."""
134 output = """--- test.sh.orig
135+++ test.sh
136@@ -1 +1 @@
137-echo "test"
138+echo "test"
139"""
140 result = parse_shfmt_output(output)
141 assert_that(result[0].fixable).is_true()
144def test_parse_shfmt_output_no_orig_suffix() -> None:
145 """Handle diff output without .orig suffix on --- line."""
146 output = """--- test.sh
147+++ test.sh
148@@ -1 +1 @@
149-echo "test"
150+echo "test"
151"""
152 result = parse_shfmt_output(output)
153 assert_that(result).is_length(1)
154 assert_that(result[0].file).is_equal_to("test.sh")
157# =============================================================================
158# Edge case tests
159# =============================================================================
162def test_parse_shfmt_output_unicode_in_content() -> None:
163 """Handle Unicode characters in diff content."""
164 output = """--- unicode.sh.orig
165+++ unicode.sh
166@@ -1 +1 @@
167-echo "Olá mundo"
168+echo "Olá mundo"
169"""
170 result = parse_shfmt_output(output)
171 assert_that(result).is_length(1)
172 assert_that(result[0].diff_content).contains("Olá")
175def test_parse_shfmt_output_file_path_with_spaces() -> None:
176 """Handle file paths with spaces."""
177 output = """--- my scripts/test.sh.orig
178+++ my scripts/test.sh
179@@ -1 +1 @@
180-echo "test"
181+echo "test"
182"""
183 result = parse_shfmt_output(output)
184 assert_that(result).is_length(1)
185 assert_that(result[0].file).is_equal_to("my scripts/test.sh")
188def test_parse_shfmt_output_very_large_line_number() -> None:
189 """Handle very large line numbers."""
190 output = """--- large.sh.orig
191+++ large.sh
192@@ -999999 +999999 @@
193-echo "test"
194+echo "test"
195"""
196 result = parse_shfmt_output(output)
197 assert_that(result).is_length(1)
198 assert_that(result[0].line).is_equal_to(999999)
201def test_parse_shfmt_output_deeply_nested_path() -> None:
202 """Handle deeply nested file paths."""
203 deep_path = "a/b/c/d/e/f/g/h/i/j/script.sh"
204 output = f"""--- {deep_path}.orig
205+++ {deep_path}
206@@ -1 +1 @@
207-echo "test"
208+echo "test"
209"""
210 result = parse_shfmt_output(output)
211 assert_that(result).is_length(1)
212 assert_that(result[0].file).is_equal_to(deep_path)
215def test_parse_shfmt_output_special_chars_in_content() -> None:
216 """Handle special characters in diff content."""
217 output = """--- special.sh.orig
218+++ special.sh
219@@ -1 +1 @@
220-echo '$var' && echo "quote: \"nested\""
221+echo '$var' && echo "quote: \"nested\""
222"""
223 result = parse_shfmt_output(output)
224 assert_that(result).is_length(1)
225 assert_that(result[0].diff_content).contains("$var")
226 assert_that(result[0].diff_content).contains("nested")
229def test_parse_shfmt_output_empty_hunk() -> None:
230 """Handle diff with context-only hunk (no changes)."""
231 # This is an edge case - shfmt wouldn't normally output this,
232 # but parser should handle it gracefully
233 output = """--- test.sh.orig
234+++ test.sh
235@@ -1,3 +1,3 @@
236 #!/bin/bash
237 echo "unchanged"
238 exit 0
239"""
240 result = parse_shfmt_output(output)
241 assert_that(result).is_length(1)
242 assert_that(result[0].file).is_equal_to("test.sh")
243 # Line defaults to 1 from the hunk header (fallback behavior)
244 assert_that(result[0].line).is_equal_to(1)
247def test_parse_shfmt_output_bash_extension() -> None:
248 """Parse file with .bash extension."""
249 output = """--- script.bash.orig
250+++ script.bash
251@@ -1 +1 @@
252-echo "test"
253+echo "test"
254"""
255 result = parse_shfmt_output(output)
256 assert_that(result).is_length(1)
257 assert_that(result[0].file).is_equal_to("script.bash")
260def test_parse_shfmt_output_ksh_extension() -> None:
261 """Parse file with .ksh extension."""
262 output = """--- script.ksh.orig
263+++ script.ksh
264@@ -1 +1 @@
265-echo "test"
266+echo "test"
267"""
268 result = parse_shfmt_output(output)
269 assert_that(result).is_length(1)
270 assert_that(result[0].file).is_equal_to("script.ksh")