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

1"""Unit tests for pytest parser.""" 

2 

3from assertpy import assert_that 

4 

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) 

11 

12 

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() 

17 

18 result = parse_pytest_json_output("{}") 

19 assert_that(result).is_empty() 

20 

21 result = parse_pytest_json_output("[]") 

22 assert_that(result).is_empty() 

23 

24 

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 }""" 

51 

52 result = parse_pytest_json_output(json_output) 

53 assert_that(result).is_length(2) 

54 

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") 

62 

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") 

70 

71 

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() 

76 

77 

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 ) 

88 

89 result = parse_pytest_text_output(text_output) 

90 assert_that(result).is_length(3) 

91 

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") 

96 

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") 

101 

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") 

106 

107 

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 ) 

114 

115 result = parse_pytest_text_output(text_output) 

116 assert_that(result).is_length(2) 

117 

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") 

122 

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") 

127 

128 

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() 

133 

134 

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 ) 

158 

159 result = parse_pytest_junit_xml(xml_output) 

160 assert_that(result).is_length(2) 

161 

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") 

169 

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") 

177 

178 

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) 

185 

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) 

190 

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) 

195 

196 # Test default format (text) 

197 result = parse_pytest_output(text_output) 

198 assert_that(result).is_instance_of(list) 

199 

200 

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() 

206 

207 

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() 

213 

214 

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") 

224 

225 

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) 

242 

243 

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") 

257 

258 

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") 

278 

279 

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") 

300 

301 

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") 

312 

313 

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) 

323 

324 

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") 

338 

339 

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") 

353 

354 

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) 

367 

368 

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") 

377 

378 

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() 

383 

384 

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) 

389 

390 text_result = parse_pytest_output("test", format="text") 

391 assert_that(text_result).is_instance_of(list) 

392 

393 xml_result = parse_pytest_output("<xml/>", format="junit") 

394 assert_that(xml_result).is_instance_of(list)