Coverage for tests / unit / parsers / test_hadolint_parser.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"""Unit tests for hadolint parser."""
3from __future__ import annotations
5import pytest
6from assertpy import assert_that
8from lintro.parsers.hadolint.hadolint_parser import parse_hadolint_output
11@pytest.mark.parametrize(
12 "output",
13 [
14 "",
15 " \n \n ",
16 ],
17 ids=["empty", "whitespace_only"],
18)
19def test_parse_hadolint_output_returns_empty_for_no_content(output: str) -> None:
20 """Parse empty or whitespace-only output returns empty list.
22 Args:
23 output: The hadolint output to parse.
24 """
25 result = parse_hadolint_output(output)
26 assert_that(result).is_empty()
29@pytest.mark.parametrize(
30 "level,code,output_line",
31 [
32 ("error", "DL3006", "Dockerfile:1 DL3006 error: Always tag the version"),
33 ("warning", "DL3009", "Dockerfile:3 DL3009 warning: Delete apt-get lists"),
34 ("info", "DL3015", "Dockerfile:5 DL3015 info: Avoid additional packages"),
35 ("style", "DL3000", "Dockerfile:10 DL3000 style: Use absolute WORKDIR"),
36 ],
37)
38def test_parse_hadolint_output_severity_levels(
39 level: str,
40 code: str,
41 output_line: str,
42) -> None:
43 """Parse issues with different severity levels.
45 Args:
46 level: The expected severity level.
47 code: The expected error code.
48 output_line: The hadolint output line to parse.
49 """
50 result = parse_hadolint_output(output_line)
51 assert_that(result).is_length(1)
52 assert_that(result[0].level).is_equal_to(level)
53 assert_that(result[0].code).is_equal_to(code)
56def test_parse_hadolint_output_extracts_all_fields() -> None:
57 """Parse error-level issue extracts all fields correctly."""
58 output = "Dockerfile:1 DL3006 error: Always tag the version of an image explicitly"
59 result = parse_hadolint_output(output)
60 assert_that(result).is_length(1)
61 assert_that(result[0].file).is_equal_to("Dockerfile")
62 assert_that(result[0].line).is_equal_to(1)
63 assert_that(result[0].code).is_equal_to("DL3006")
64 assert_that(result[0].level).is_equal_to("error")
65 assert_that(result[0].message).is_equal_to(
66 "Always tag the version of an image explicitly",
67 )
70def test_parse_hadolint_output_multiple_issues() -> None:
71 """Parse multiple issues."""
72 output = """Dockerfile:1 DL3006 error: Always tag the version
73Dockerfile:3 DL3009 warning: Delete apt-get lists
74Dockerfile:5 DL3015 info: Avoid additional packages"""
75 result = parse_hadolint_output(output)
76 assert_that(result).is_length(3)
77 assert_that(result[0].line).is_equal_to(1)
78 assert_that(result[1].line).is_equal_to(3)
79 assert_that(result[2].line).is_equal_to(5)
82def test_parse_hadolint_output_non_matching_lines_ignored() -> None:
83 """Non-matching lines are ignored."""
84 output = """Some header text
85Dockerfile:1 DL3006 error: Tag the version
86Other random output"""
87 result = parse_hadolint_output(output)
88 assert_that(result).is_length(1)
91def test_parse_hadolint_output_column_is_zero() -> None:
92 """Column is always 0 (hadolint doesn't provide it)."""
93 output = "Dockerfile:1 DL3006 error: Some error"
94 result = parse_hadolint_output(output)
95 assert_that(result[0].column).is_equal_to(0)
98def test_parse_hadolint_output_file_with_path() -> None:
99 """Parse file with directory path."""
100 output = "docker/prod/Dockerfile:10 DL3006 error: Tag the version"
101 result = parse_hadolint_output(output)
102 assert_that(result[0].file).is_equal_to("docker/prod/Dockerfile")
105def test_parse_hadolint_output_blank_lines_between_issues() -> None:
106 """Handle blank lines between issues."""
107 output = """Dockerfile:1 DL3006 error: Error one
109Dockerfile:5 DL3009 warning: Warning one"""
110 result = parse_hadolint_output(output)
111 assert_that(result).is_length(2)
114# =============================================================================
115# Edge case tests
116# =============================================================================
119def test_parse_hadolint_output_unicode_in_message() -> None:
120 """Handle Unicode characters in error messages."""
121 output = "Dockerfile:1 DL3006 error: Não use versões implícitas"
122 result = parse_hadolint_output(output)
123 assert_that(result).is_length(1)
124 assert_that(result[0].message).contains("Não")
127def test_parse_hadolint_output_file_path_with_spaces() -> None:
128 """Handle file paths with spaces (if quoted by tool)."""
129 output = "my project/Dockerfile:1 DL3006 error: Tag version"
130 result = parse_hadolint_output(output)
131 assert_that(result).is_length(1)
132 assert_that(result[0].file).contains("my project")
135def test_parse_hadolint_output_very_long_message() -> None:
136 """Handle extremely long error messages."""
137 long_text = "x" * 5000
138 output = f"Dockerfile:1 DL3006 error: {long_text}"
139 result = parse_hadolint_output(output)
140 assert_that(result).is_length(1)
141 assert_that(len(result[0].message)).is_equal_to(5000)
144def test_parse_hadolint_output_very_large_line_number() -> None:
145 """Handle very large line numbers."""
146 output = "Dockerfile:999999 DL3006 error: Error on large line"
147 result = parse_hadolint_output(output)
148 assert_that(result).is_length(1)
149 assert_that(result[0].line).is_equal_to(999999)
152def test_parse_hadolint_output_special_chars_in_message() -> None:
153 """Handle special characters in error messages."""
154 output = 'Dockerfile:1 DL3006 error: Use "quotes" and <brackets>'
155 result = parse_hadolint_output(output)
156 assert_that(result).is_length(1)
157 assert_that(result[0].message).contains("quotes")
158 assert_that(result[0].message).contains("<brackets>")
161def test_parse_hadolint_output_colon_in_message() -> None:
162 """Handle colons in error messages (common in Docker contexts)."""
163 output = "Dockerfile:1 DL3006 error: FROM should use image:tag format"
164 result = parse_hadolint_output(output)
165 assert_that(result).is_length(1)
166 assert_that(result[0].message).contains("image:tag")
169def test_parse_hadolint_output_deeply_nested_path() -> None:
170 """Handle deeply nested file paths."""
171 deep_path = "a/b/c/d/e/f/g/h/i/j/Dockerfile"
172 output = f"{deep_path}:1 DL3006 error: Tag version"
173 result = parse_hadolint_output(output)
174 assert_that(result).is_length(1)
175 assert_that(result[0].file).is_equal_to(deep_path)
178def test_parse_hadolint_output_ansi_codes_stripped() -> None:
179 """Strip ANSI escape codes from output for consistent CI/local parsing."""
180 # Output with ANSI color codes (common in CI environments)
181 output = "\x1b[31mDockerfile:1 DL3006 error: Always tag the version\x1b[0m"
182 result = parse_hadolint_output(output)
183 assert_that(result).is_length(1)
184 assert_that(result[0].file).is_equal_to("Dockerfile")
185 assert_that(result[0].code).is_equal_to("DL3006")