Coverage for tests / unit / parsers / pytest / test_pytest_parser.py: 100%
168 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 pytest parser."""
3from assertpy import assert_that
5from lintro.parsers.pytest.pytest_parser import (
6 parse_pytest_json_output,
7 parse_pytest_junit_xml,
8 parse_pytest_output,
9 parse_pytest_text_output,
10)
13def test_parse_pytest_json_output_empty() -> None:
14 """Test parsing empty JSON output."""
15 result = parse_pytest_json_output("")
16 assert_that(result).is_empty()
18 result = parse_pytest_json_output("{}")
19 assert_that(result).is_empty()
21 result = parse_pytest_json_output("[]")
22 assert_that(result).is_empty()
25def test_parse_pytest_json_output_valid() -> None:
26 """Test parsing valid JSON output."""
27 json_output = """{
28 "tests": [
29 {
30 "file": "test_example.py",
31 "lineno": 10,
32 "name": "test_failure",
33 "outcome": "failed",
34 "call": {
35 "longrepr": "AssertionError: Expected 1 but got 2"
36 },
37 "duration": 0.001,
38 "nodeid": "test_example.py::test_failure"
39 },
40 {
41 "file": "test_example.py",
42 "lineno": 15,
43 "name": "test_error",
44 "outcome": "error",
45 "longrepr": "ZeroDivisionError: division by zero",
46 "duration": 0.002,
47 "nodeid": "test_example.py::test_error"
48 }
49 ]
50 }"""
52 result = parse_pytest_json_output(json_output)
53 assert_that(result).is_length(2)
55 assert_that(result[0].file).is_equal_to("test_example.py")
56 assert_that(result[0].line).is_equal_to(10)
57 assert_that(result[0].test_name).is_equal_to("test_failure")
58 assert_that(result[0].test_status).is_equal_to("FAILED")
59 assert_that(result[0].message).is_equal_to("AssertionError: Expected 1 but got 2")
60 assert_that(result[0].duration).is_equal_to(0.001)
61 assert_that(result[0].node_id).is_equal_to("test_example.py::test_failure")
63 assert_that(result[1].file).is_equal_to("test_example.py")
64 assert_that(result[1].line).is_equal_to(15)
65 assert_that(result[1].test_name).is_equal_to("test_error")
66 assert_that(result[1].test_status).is_equal_to("ERROR")
67 assert_that(result[1].message).is_equal_to("ZeroDivisionError: division by zero")
68 assert_that(result[1].duration).is_equal_to(0.002)
69 assert_that(result[1].node_id).is_equal_to("test_example.py::test_error")
72def test_parse_pytest_text_output_empty() -> None:
73 """Test parsing empty text output."""
74 result = parse_pytest_text_output("")
75 assert_that(result).is_empty()
78def test_parse_pytest_text_output_failures() -> None:
79 """Test parsing text output with failures."""
80 text_output = (
81 "FAILED test_example.py::test_failure - "
82 "AssertionError: Expected 1 but got 2\n"
83 "ERROR test_example.py::test_error - "
84 "ZeroDivisionError: division by zero\n"
85 "FAILED test_example.py::test_another_failure - "
86 "ValueError: invalid value\n"
87 )
89 result = parse_pytest_text_output(text_output)
90 assert_that(result).is_length(3)
92 assert_that(result[0].file).is_equal_to("test_example.py")
93 assert_that(result[0].test_name).is_equal_to("test_failure")
94 assert_that(result[0].test_status).is_equal_to("FAILED")
95 assert_that(result[0].message).is_equal_to("AssertionError: Expected 1 but got 2")
97 assert_that(result[1].file).is_equal_to("test_example.py")
98 assert_that(result[1].test_name).is_equal_to("test_error")
99 assert_that(result[1].test_status).is_equal_to("ERROR")
100 assert_that(result[1].message).is_equal_to("ZeroDivisionError: division by zero")
102 assert_that(result[2].file).is_equal_to("test_example.py")
103 assert_that(result[2].test_name).is_equal_to("test_another_failure")
104 assert_that(result[2].test_status).is_equal_to("FAILED")
105 assert_that(result[2].message).is_equal_to("ValueError: invalid value")
108def test_parse_pytest_text_output_line_format() -> None:
109 """Test parsing text output with line number format."""
110 text_output = (
111 "test_example.py:10: FAILED - AssertionError: Expected 1 but got 2\n"
112 "test_example.py:15: ERROR - ZeroDivisionError: division by zero\n"
113 )
115 result = parse_pytest_text_output(text_output)
116 assert_that(result).is_length(2)
118 assert_that(result[0].file).is_equal_to("test_example.py")
119 assert_that(result[0].line).is_equal_to(10)
120 assert_that(result[0].test_status).is_equal_to("FAILED")
121 assert_that(result[0].message).is_equal_to("AssertionError: Expected 1 but got 2")
123 assert_that(result[1].file).is_equal_to("test_example.py")
124 assert_that(result[1].line).is_equal_to(15)
125 assert_that(result[1].test_status).is_equal_to("ERROR")
126 assert_that(result[1].message).is_equal_to("ZeroDivisionError: division by zero")
129def test_parse_pytest_junit_xml_empty() -> None:
130 """Test parsing empty JUnit XML output."""
131 result = parse_pytest_junit_xml("")
132 assert_that(result).is_empty()
135def test_parse_pytest_junit_xml_valid() -> None:
136 """Test parsing valid JUnit XML output."""
137 xml_output = (
138 '<?xml version="1.0" encoding="utf-8"?>\n'
139 '<testsuite name="pytest" tests="2" failures="1" errors="1" time="0.003">\n'
140 ' <testcase name="test_failure" file="test_example.py" line="10" '
141 'time="0.001" classname="TestExample">\n'
142 ' <failure message="AssertionError: Expected 1 but got 2">'
143 "Traceback (most recent call last):\n"
144 ' File "test_example.py", line 10, in test_failure\n'
145 " assert_that(1).is_equal_to(2)\n"
146 "AssertionError: Expected 1 but got 2</failure>\n"
147 " </testcase>\n"
148 ' <testcase name="test_error" file="test_example.py" line="15" '
149 'time="0.002" classname="TestExample">\n'
150 ' <error message="ZeroDivisionError: division by zero">'
151 "Traceback (most recent call last):\n"
152 ' File "test_example.py", line 15, in test_error\n'
153 " 1 / 0\n"
154 "ZeroDivisionError: division by zero</error>\n"
155 " </testcase>\n"
156 "</testsuite>"
157 )
159 result = parse_pytest_junit_xml(xml_output)
160 assert_that(result).is_length(2)
162 assert_that(result[0].file).is_equal_to("test_example.py")
163 assert_that(result[0].line).is_equal_to(10)
164 assert_that(result[0].test_name).is_equal_to("test_failure")
165 assert_that(result[0].test_status).is_equal_to("FAILED")
166 assert_that(result[0].message).contains("AssertionError: Expected 1 but got 2")
167 assert_that(result[0].duration).is_equal_to(0.001)
168 assert_that(result[0].node_id).is_equal_to("TestExample::test_failure")
170 assert_that(result[1].file).is_equal_to("test_example.py")
171 assert_that(result[1].line).is_equal_to(15)
172 assert_that(result[1].test_name).is_equal_to("test_error")
173 assert_that(result[1].test_status).is_equal_to("ERROR")
174 assert_that(result[1].message).contains("ZeroDivisionError: division by zero")
175 assert_that(result[1].duration).is_equal_to(0.002)
176 assert_that(result[1].node_id).is_equal_to("TestExample::test_error")
179def test_parse_pytest_output_format_dispatch() -> None:
180 """Test that parse_pytest_output dispatches to correct parser."""
181 # Test JSON format
182 json_output = '{"tests": []}'
183 result = parse_pytest_output(json_output, format="json")
184 assert_that(result).is_instance_of(list)
186 # Test text format
187 text_output = "FAILED test.py::test - AssertionError"
188 result = parse_pytest_output(text_output, format="text")
189 assert_that(result).is_instance_of(list)
191 # Test junit format
192 xml_output = '<?xml version="1.0"?><testsuite></testsuite>'
193 result = parse_pytest_output(xml_output, format="junit")
194 assert_that(result).is_instance_of(list)
196 # Test default format (text)
197 result = parse_pytest_output(text_output)
198 assert_that(result).is_instance_of(list)
201def test_parse_pytest_json_output_malformed() -> None:
202 """Test parsing malformed JSON output."""
203 malformed_json = '{"tests": [{"incomplete": "object"'
204 result = parse_pytest_json_output(malformed_json)
205 assert_that(result).is_empty()
208def test_parse_pytest_junit_xml_malformed() -> None:
209 """Test parsing malformed JUnit XML output."""
210 malformed_xml = "<testsuite><testcase><incomplete>"
211 result = parse_pytest_junit_xml(malformed_xml)
212 assert_that(result).is_empty()
215def test_parse_pytest_text_output_ansi_codes() -> None:
216 """Test parsing text output with ANSI color codes."""
217 text_with_ansi = (
218 "\x1b[31mFAILED\x1b[0m test_example.py::test_failure - AssertionError"
219 )
220 result = parse_pytest_text_output(text_with_ansi)
221 assert_that(result).is_length(1)
222 assert_that(result[0].test_status).is_equal_to("FAILED")
223 assert_that(result[0].message).is_equal_to("AssertionError")
226def test_parse_pytest_json_output_missing_optional_fields() -> None:
227 """Test parsing JSON with missing optional fields."""
228 json_output = """{
229 "tests": [
230 {
231 "file": "test_example.py",
232 "name": "test_failure",
233 "outcome": "failed"
234 }
235 ]
236 }"""
237 result = parse_pytest_json_output(json_output)
238 assert_that(result).is_length(1)
239 assert_that(result[0].file).is_equal_to("test_example.py")
240 assert_that(result[0].line).is_equal_to(0)
241 assert_that(result[0].duration).is_equal_to(0.0)
244def test_parse_pytest_json_output_alternative_list_format() -> None:
245 """Test parsing JSON alternative list format."""
246 json_output = """[
247 {
248 "file": "test_example.py",
249 "name": "test_failure",
250 "outcome": "failed",
251 "longrepr": "AssertionError"
252 }
253 ]"""
254 result = parse_pytest_json_output(json_output)
255 assert_that(result).is_length(1)
256 assert_that(result[0].file).is_equal_to("test_example.py")
259def test_parse_pytest_json_output_with_call_message() -> None:
260 """Test parsing JSON with message in call field."""
261 json_output = """{
262 "tests": [
263 {
264 "file": "test_example.py",
265 "name": "test_failure",
266 "outcome": "failed",
267 "call": {
268 "longrepr": "Error in call"
269 },
270 "longrepr": "Error in test"
271 }
272 ]
273 }"""
274 result = parse_pytest_json_output(json_output)
275 assert_that(result).is_length(1)
276 # Should prefer call.longrepr
277 assert_that(result[0].message).is_equal_to("Error in call")
280def test_parse_pytest_json_output_passed_test_ignored() -> None:
281 """Test that passed tests are ignored in JSON parsing."""
282 json_output = """{
283 "tests": [
284 {
285 "file": "test_example.py",
286 "name": "test_success",
287 "outcome": "passed"
288 },
289 {
290 "file": "test_example.py",
291 "name": "test_failure",
292 "outcome": "failed",
293 "longrepr": "Error"
294 }
295 ]
296 }"""
297 result = parse_pytest_json_output(json_output)
298 assert_that(result).is_length(1)
299 assert_that(result[0].test_name).is_equal_to("test_failure")
302def test_parse_pytest_text_output_alternative_failure_format() -> None:
303 """Test parsing text output with alternative failure format."""
304 text_output = "FAILED test_example.py::test_failure Some error message"
305 result = parse_pytest_text_output(text_output)
306 # Should parse using alternative pattern
307 assert_that(result).is_length(1)
308 assert_that(result[0].file).is_equal_to("test_example.py")
309 assert_that(result[0].test_name).is_equal_to("test_failure")
310 assert_that(result[0].test_status).is_equal_to("FAILED")
311 assert_that(result[0].message).contains("Some error message")
314def test_parse_pytest_text_output_multiple_failures() -> None:
315 """Test parsing text output with multiple failure types."""
316 text_output = (
317 "FAILED test_a.py::test_1 - Error 1\n"
318 "ERROR test_b.py::test_2 - Error 2\n"
319 "FAILED test_c.py::test_3 - Error 3\n"
320 )
321 result = parse_pytest_text_output(text_output)
322 assert_that(result).is_length(3)
325def test_parse_pytest_junit_xml_missing_attributes() -> None:
326 """Test parsing JUnit XML with missing attributes."""
327 xml_output = (
328 '<?xml version="1.0" encoding="utf-8"?>\n'
329 "<testsuite>\n"
330 ' <testcase name="test_failure">\n'
331 ' <failure message="Error">Traceback</failure>\n'
332 " </testcase>\n"
333 "</testsuite>"
334 )
335 result = parse_pytest_junit_xml(xml_output)
336 assert_that(result).is_length(1)
337 assert_that(result[0].test_name).is_equal_to("test_failure")
340def test_parse_pytest_junit_xml_without_message_attribute() -> None:
341 """Test parsing JUnit XML failure without message attribute."""
342 xml_output = (
343 '<?xml version="1.0" encoding="utf-8"?>\n'
344 "<testsuite>\n"
345 ' <testcase name="test_failure" file="test.py">\n'
346 " <failure>Error text content</failure>\n"
347 " </testcase>\n"
348 "</testsuite>"
349 )
350 result = parse_pytest_junit_xml(xml_output)
351 assert_that(result).is_length(1)
352 assert_that(result[0].message).contains("Error text content")
355def test_parse_pytest_junit_xml_no_failure_or_error() -> None:
356 """Test parsing JUnit XML with passed testcase."""
357 xml_output = (
358 '<?xml version="1.0" encoding="utf-8"?>\n'
359 "<testsuite>\n"
360 ' <testcase name="test_success" file="test.py">\n'
361 " </testcase>\n"
362 "</testsuite>"
363 )
364 result = parse_pytest_junit_xml(xml_output)
365 # Passed tests should be ignored
366 assert_that(result).is_length(0)
369def test_parse_pytest_text_output_file_and_line_format() -> None:
370 """Test text output with file::test format followed by line format."""
371 text_output = (
372 "test_example.py::test_function\ntest_example.py:10: FAILED - AssertionError\n"
373 )
374 result = parse_pytest_text_output(text_output)
375 assert_that(result).is_length(1)
376 assert_that(result[0].file).is_equal_to("test_example.py")
379def test_parse_pytest_output_with_empty_format() -> None:
380 """Test parse_pytest_output with default empty text."""
381 result = parse_pytest_output("", format="text")
382 assert_that(result).is_empty()
385def test_parse_pytest_output_dispatches_correctly() -> None:
386 """Test that parse_pytest_output dispatches to correct parser."""
387 json_result = parse_pytest_output("{}", format="json")
388 assert_that(json_result).is_instance_of(list)
390 text_result = parse_pytest_output("test", format="text")
391 assert_that(text_result).is_instance_of(list)
393 xml_result = parse_pytest_output("<xml/>", format="junit")
394 assert_that(xml_result).is_instance_of(list)