Coverage for tests / unit / parsers / test_taplo_parser.py: 100%
144 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 taplo parser."""
3from __future__ import annotations
5import pytest
6from assertpy import assert_that
8from lintro.parsers.taplo.taplo_parser import parse_taplo_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_taplo_output_returns_empty_for_no_content(output: str | None) -> None:
21 """Parse empty, None, or whitespace-only output returns empty list.
23 Args:
24 output: The taplo output to parse.
25 """
26 result = parse_taplo_output(output)
27 assert_that(result).is_empty()
30def test_parse_taplo_output_extracts_all_fields() -> None:
31 """Parse error-level issue extracts all fields correctly."""
32 output = """error[invalid_value]: invalid value
33 --> pyproject.toml:5:10
34 |
35 5 | version =
36 | ^ expected a value"""
37 result = parse_taplo_output(output)
38 assert_that(result).is_length(1)
39 assert_that(result[0].file).is_equal_to("pyproject.toml")
40 assert_that(result[0].line).is_equal_to(5)
41 assert_that(result[0].column).is_equal_to(10)
42 assert_that(result[0].level).is_equal_to("error")
43 assert_that(result[0].code).is_equal_to("invalid_value")
44 assert_that(result[0].message).is_equal_to("invalid value")
47@pytest.mark.parametrize(
48 ("level", "code", "output"),
49 [
50 (
51 "error",
52 "invalid_value",
53 "error[invalid_value]: missing value\n --> file.toml:1:5",
54 ),
55 (
56 "warning",
57 "deprecated_syntax",
58 "warning[deprecated_syntax]: use new syntax\n --> file.toml:2:1",
59 ),
60 (
61 "error",
62 "expected_table_array",
63 "error[expected_table_array]: expected table array\n --> config.toml:10:1",
64 ),
65 ],
66 ids=["error_invalid_value", "warning_deprecated", "error_expected_table_array"],
67)
68def test_parse_taplo_output_severity_levels(
69 level: str,
70 code: str,
71 output: str,
72) -> None:
73 """Parse issues with different severity levels.
75 Args:
76 level: The expected severity level.
77 code: The expected error code.
78 output: The taplo output to parse.
79 """
80 result = parse_taplo_output(output)
81 assert_that(result).is_length(1)
82 assert_that(result[0].level).is_equal_to(level)
83 assert_that(result[0].code).is_equal_to(code)
86def test_parse_taplo_output_multiple_issues() -> None:
87 """Parse multiple issues from output."""
88 output = """error[invalid_value]: first error
89 --> file1.toml:1:5
90 |
91 1 | key =
92 | ^ expected value
94error[syntax_error]: second error
95 --> file2.toml:10:1
96 |
9710 | [invalid
98 | ^ unclosed bracket
100warning[deprecated]: third issue
101 --> file3.toml:5:3"""
102 result = parse_taplo_output(output)
103 assert_that(result).is_length(3)
104 assert_that(result[0].line).is_equal_to(1)
105 assert_that(result[0].file).is_equal_to("file1.toml")
106 assert_that(result[1].line).is_equal_to(10)
107 assert_that(result[1].file).is_equal_to("file2.toml")
108 assert_that(result[2].line).is_equal_to(5)
109 assert_that(result[2].file).is_equal_to("file3.toml")
112def test_parse_taplo_output_non_matching_lines_ignored() -> None:
113 """Non-matching lines are ignored."""
114 output = """Some header text
115Taplo version 0.9.0
117error[invalid_value]: actual error
118 --> pyproject.toml:5:10
120Other random output
121Done."""
122 result = parse_taplo_output(output)
123 assert_that(result).is_length(1)
124 assert_that(result[0].code).is_equal_to("invalid_value")
127def test_parse_taplo_output_file_with_path() -> None:
128 """Parse file with directory path."""
129 output = """error[invalid_key]: invalid key name
130 --> config/settings/app.toml:10:1"""
131 result = parse_taplo_output(output)
132 assert_that(result[0].file).is_equal_to("config/settings/app.toml")
135def test_parse_taplo_output_blank_lines_between_issues() -> None:
136 """Handle blank lines between issues."""
137 output = """error[error_one]: first
138 --> file.toml:1:1
141error[error_two]: second
142 --> file.toml:5:1"""
143 result = parse_taplo_output(output)
144 assert_that(result).is_length(2)
147# =============================================================================
148# Edge case tests
149# =============================================================================
152def test_parse_taplo_output_unicode_in_message() -> None:
153 """Handle Unicode characters in error messages."""
154 output = """error[invalid_value]: caractere invalide
155 --> file.toml:1:1"""
156 result = parse_taplo_output(output)
157 assert_that(result).is_length(1)
158 assert_that(result[0].message).contains("invalide")
161def test_parse_taplo_output_very_long_message() -> None:
162 """Handle extremely long error messages."""
163 long_text = "x" * 5000
164 output = f"""error[long_error]: {long_text}
165 --> file.toml:1:1"""
166 result = parse_taplo_output(output)
167 assert_that(result).is_length(1)
168 assert_that(len(result[0].message)).is_equal_to(5000)
171def test_parse_taplo_output_very_large_line_number() -> None:
172 """Handle very large line numbers."""
173 output = """error[error]: error on large line
174 --> file.toml:999999:1"""
175 result = parse_taplo_output(output)
176 assert_that(result).is_length(1)
177 assert_that(result[0].line).is_equal_to(999999)
180def test_parse_taplo_output_special_chars_in_message() -> None:
181 """Handle special characters in error messages."""
182 output = """error[special]: Use "quotes" and <brackets> and [arrays]
183 --> file.toml:1:1"""
184 result = parse_taplo_output(output)
185 assert_that(result).is_length(1)
186 assert_that(result[0].message).contains("quotes")
187 assert_that(result[0].message).contains("<brackets>")
190def test_parse_taplo_output_colon_in_message() -> None:
191 """Handle colons in error messages."""
192 output = """error[invalid]: expected format: key = value
193 --> file.toml:1:1"""
194 result = parse_taplo_output(output)
195 assert_that(result).is_length(1)
196 assert_that(result[0].message).contains("key = value")
199def test_parse_taplo_output_deeply_nested_path() -> None:
200 """Handle deeply nested file paths."""
201 deep_path = "a/b/c/d/e/f/g/h/i/j/config.toml"
202 output = f"""error[nested]: deep path error
203 --> {deep_path}:1:1"""
204 result = parse_taplo_output(output)
205 assert_that(result).is_length(1)
206 assert_that(result[0].file).is_equal_to(deep_path)
209def test_parse_taplo_output_code_with_underscores() -> None:
210 """Handle error codes with underscores."""
211 output = """error[expected_table_array_header]: expected table array
212 --> file.toml:1:1"""
213 result = parse_taplo_output(output)
214 assert_that(result).is_length(1)
215 assert_that(result[0].code).is_equal_to("expected_table_array_header")
218def test_parse_taplo_output_column_position() -> None:
219 """Verify column position is correctly extracted."""
220 output = """error[syntax]: error at column
221 --> file.toml:5:42"""
222 result = parse_taplo_output(output)
223 assert_that(result).is_length(1)
224 assert_that(result[0].column).is_equal_to(42)
227def test_parse_taplo_output_windows_path() -> None:
228 """Handle Windows-style paths if present."""
229 # Note: Taplo typically uses forward slashes even on Windows,
230 # but we test path handling nonetheless
231 output = """error[error]: windows path
232 --> C:/Users/test/config.toml:1:1"""
233 result = parse_taplo_output(output)
234 assert_that(result).is_length(1)
235 # The path includes drive letter
236 assert_that(result[0].file).contains("config.toml")
239def test_parse_taplo_output_location_with_extra_spaces() -> None:
240 """Handle location line with varying whitespace."""
241 output = """error[invalid]: error message
242 --> pyproject.toml:5:10"""
243 result = parse_taplo_output(output)
244 assert_that(result).is_length(1)
245 assert_that(result[0].file).is_equal_to("pyproject.toml")
248def test_parse_taplo_output_issue_dataclass_fields() -> None:
249 """Verify TaploIssue has correct field defaults."""
250 output = """error[test_code]: test message
251 --> test.toml:1:1"""
252 result = parse_taplo_output(output)
253 assert_that(result).is_length(1)
254 issue = result[0]
256 # Verify all fields are populated
257 assert_that(issue.file).is_equal_to("test.toml")
258 assert_that(issue.line).is_equal_to(1)
259 assert_that(issue.column).is_equal_to(1)
260 assert_that(issue.level).is_equal_to("error")
261 assert_that(issue.code).is_equal_to("test_code")
262 assert_that(issue.message).is_equal_to("test message")
265# =============================================================================
266# Format check output tests (taplo fmt --check)
267# =============================================================================
270def test_parse_taplo_output_fmt_check_format() -> None:
271 """Parse taplo fmt --check output format.
273 Taplo fmt --check outputs:
274 ERROR taplo:format_files: the file is not properly formatted path="file.toml"
275 """
276 output = 'ERROR taplo:format_files: the file is not properly formatted path="/tmp/test.toml"'
277 result = parse_taplo_output(output)
279 assert_that(result).is_length(1)
280 assert_that(result[0].file).is_equal_to("/tmp/test.toml")
281 assert_that(result[0].level).is_equal_to("error")
282 assert_that(result[0].code).is_equal_to("format")
283 assert_that(result[0].message).is_equal_to("the file is not properly formatted")
286def test_parse_taplo_output_fmt_check_multiple_files() -> None:
287 """Parse taplo fmt --check output with multiple files."""
288 output = """ERROR taplo:format_files: the file is not properly formatted path="config.toml"
289ERROR taplo:format_files: the file is not properly formatted path="pyproject.toml"
290ERROR operation failed error=some files were not properly formatted"""
291 result = parse_taplo_output(output)
293 assert_that(result).is_length(2)
294 assert_that(result[0].file).is_equal_to("config.toml")
295 assert_that(result[1].file).is_equal_to("pyproject.toml")
298def test_parse_taplo_output_fmt_check_with_info_lines() -> None:
299 """Parse taplo fmt --check output with INFO lines mixed in."""
300 output = """ INFO taplo:format_files:collect_files: found files total=1 excluded=0
301ERROR taplo:format_files: the file is not properly formatted path="test.toml"
302ERROR operation failed error=some files were not properly formatted"""
303 result = parse_taplo_output(output)
305 assert_that(result).is_length(1)
306 assert_that(result[0].file).is_equal_to("test.toml")
309def test_parse_taplo_output_fmt_check_rust_log_error_format() -> None:
310 """Parse taplo fmt --check output when RUST_LOG=error is set.
312 When RUST_LOG=error is set, taplo outputs a simplified format without
313 the taplo:format_files: prefix.
314 """
315 output = """ERROR the file is not properly formatted path="/tmp/test.toml"
316ERROR operation failed error=some files were not properly formatted"""
317 result = parse_taplo_output(output)
319 assert_that(result).is_length(1)
320 assert_that(result[0].file).is_equal_to("/tmp/test.toml")
321 assert_that(result[0].level).is_equal_to("error")
322 assert_that(result[0].code).is_equal_to("format")
323 assert_that(result[0].message).is_equal_to("the file is not properly formatted")
326def test_parse_taplo_output_ansi_codes_stripped() -> None:
327 """Strip ANSI escape codes from output for consistent CI/local parsing."""
328 # Output with ANSI color codes (common in CI environments)
329 output = """\x1b[31merror[invalid_value]: invalid value\x1b[0m
330 --> pyproject.toml:5:10"""
331 result = parse_taplo_output(output)
332 assert_that(result).is_length(1)
333 assert_that(result[0].file).is_equal_to("pyproject.toml")
334 assert_that(result[0].code).is_equal_to("invalid_value")