1use crate::{
5 DeserializeError, DeserializeErrorKind, NonSuccessKind, PathElement, Property, Report,
6 ReportUuid, TestCase, TestCaseStatus, TestRerun, TestSuite, XmlString,
7};
8use chrono::{DateTime, FixedOffset};
9use indexmap::IndexMap;
10use newtype_uuid::GenericUuid;
11use quick_xml::{
12 escape::{resolve_xml_entity, unescape_with},
13 events::{BytesStart, Event},
14 Reader,
15};
16use std::{io::BufRead, time::Duration};
17
18impl Report {
19 pub fn deserialize<R: BufRead>(reader: R) -> Result<Self, DeserializeError> {
30 let mut xml_reader = Reader::from_reader(reader);
31 xml_reader.config_mut().trim_text(false);
32 deserialize_report(&mut xml_reader)
33 }
34
35 pub fn deserialize_from_str(xml: &str) -> Result<Self, DeserializeError> {
60 Self::deserialize(xml.as_bytes())
61 }
62}
63
64fn deserialize_report<R: BufRead>(reader: &mut Reader<R>) -> Result<Report, DeserializeError> {
66 let mut buf = Vec::new();
67 let mut report: Option<Report> = None;
68 let mut properly_closed = false;
69 let root_path = vec![PathElement::TestSuites];
70
71 loop {
72 match reader.read_event_into(&mut buf) {
73 Ok(Event::Start(e)) if e.name().as_ref() == b"testsuites" => {
74 let mut name = None;
75 let mut uuid = None;
76 let mut timestamp = None;
77 let mut time = None;
78 let mut tests = 0;
79 let mut failures = 0;
80 let mut errors = 0;
81
82 for attr in e.attributes() {
83 let attr = attr.map_err(|e| {
84 DeserializeError::new(DeserializeErrorKind::AttrError(e), root_path.clone())
85 })?;
86 let mut attr_path = root_path.clone();
87 match attr.key.as_ref() {
88 b"name" => {
89 attr_path.push(PathElement::Attribute("name".to_string()));
90 name = Some(parse_xml_string(&attr.value, &attr_path)?);
91 }
92 b"uuid" => {
93 attr_path.push(PathElement::Attribute("uuid".to_string()));
94 uuid = Some(parse_uuid(&attr.value, &attr_path)?);
95 }
96 b"timestamp" => {
97 attr_path.push(PathElement::Attribute("timestamp".to_string()));
98 timestamp = Some(parse_timestamp(&attr.value, &attr_path)?);
99 }
100 b"time" => {
101 attr_path.push(PathElement::Attribute("time".to_string()));
102 time = Some(parse_duration(&attr.value, &attr_path)?);
103 }
104 b"tests" => {
105 attr_path.push(PathElement::Attribute("tests".to_string()));
106 tests = parse_usize(&attr.value, &attr_path)?;
107 }
108 b"failures" => {
109 attr_path.push(PathElement::Attribute("failures".to_string()));
110 failures = parse_usize(&attr.value, &attr_path)?;
111 }
112 b"errors" => {
113 attr_path.push(PathElement::Attribute("errors".to_string()));
114 errors = parse_usize(&attr.value, &attr_path)?;
115 }
116 _ => {} }
118 }
119
120 let name = name.ok_or_else(|| {
121 let mut attr_path = root_path.clone();
122 attr_path.push(PathElement::Attribute("name".to_string()));
123 DeserializeError::new(
124 DeserializeErrorKind::MissingAttribute("name".to_string()),
125 attr_path,
126 )
127 })?;
128
129 let test_suites = Vec::new();
130
131 report = Some(Report {
132 name,
133 uuid,
134 timestamp,
135 time,
136 tests,
137 failures,
138 errors,
139 test_suites,
140 });
141 }
142 Ok(Event::Empty(e)) if e.name().as_ref() == b"testsuites" => {
143 let mut name = None;
144 let mut uuid = None;
145 let mut timestamp = None;
146 let mut time = None;
147 let mut tests = 0;
148 let mut failures = 0;
149 let mut errors = 0;
150
151 for attr in e.attributes() {
152 let attr = attr.map_err(|e| {
153 DeserializeError::new(DeserializeErrorKind::AttrError(e), root_path.clone())
154 })?;
155 let mut attr_path = root_path.clone();
156 match attr.key.as_ref() {
157 b"name" => {
158 attr_path.push(PathElement::Attribute("name".to_string()));
159 name = Some(parse_xml_string(&attr.value, &attr_path)?);
160 }
161 b"uuid" => {
162 attr_path.push(PathElement::Attribute("uuid".to_string()));
163 uuid = Some(parse_uuid(&attr.value, &attr_path)?);
164 }
165 b"timestamp" => {
166 attr_path.push(PathElement::Attribute("timestamp".to_string()));
167 timestamp = Some(parse_timestamp(&attr.value, &attr_path)?);
168 }
169 b"time" => {
170 attr_path.push(PathElement::Attribute("time".to_string()));
171 time = Some(parse_duration(&attr.value, &attr_path)?);
172 }
173 b"tests" => {
174 attr_path.push(PathElement::Attribute("tests".to_string()));
175 tests = parse_usize(&attr.value, &attr_path)?;
176 }
177 b"failures" => {
178 attr_path.push(PathElement::Attribute("failures".to_string()));
179 failures = parse_usize(&attr.value, &attr_path)?;
180 }
181 b"errors" => {
182 attr_path.push(PathElement::Attribute("errors".to_string()));
183 errors = parse_usize(&attr.value, &attr_path)?;
184 }
185 _ => {} }
187 }
188
189 let name = name.ok_or_else(|| {
190 let mut attr_path = root_path.clone();
191 attr_path.push(PathElement::Attribute("name".to_string()));
192 DeserializeError::new(
193 DeserializeErrorKind::MissingAttribute("name".to_string()),
194 attr_path,
195 )
196 })?;
197
198 report = Some(Report {
199 name,
200 uuid,
201 timestamp,
202 time,
203 tests,
204 failures,
205 errors,
206 test_suites: Vec::new(),
207 });
208 properly_closed = true; }
210 Ok(Event::Start(e)) if e.name().as_ref() == b"testsuite" => {
211 if let Some(ref mut report) = report {
212 let suite_index = report.test_suites.len();
213 let test_suite = deserialize_test_suite(reader, &e, &root_path, suite_index)?;
214 report.test_suites.push(test_suite);
215 }
216 }
217 Ok(Event::Empty(e)) if e.name().as_ref() == b"testsuite" => {
218 if let Some(ref mut report) = report {
219 let suite_index = report.test_suites.len();
220 let test_suite = deserialize_test_suite_empty(&e, &root_path, suite_index)?;
221 report.test_suites.push(test_suite);
222 }
223 }
224 Ok(Event::End(e)) if e.name().as_ref() == b"testsuites" => {
225 properly_closed = true;
226 break;
227 }
228 Ok(Event::Eof) => break,
229 Ok(_) => {}
230 Err(e) => {
231 return Err(DeserializeError::new(
232 DeserializeErrorKind::XmlError(e),
233 root_path.clone(),
234 ))
235 }
236 }
237 buf.clear();
238 }
239
240 if !properly_closed && report.is_some() {
241 return Err(DeserializeError::new(
242 DeserializeErrorKind::InvalidStructure(
243 "unexpected EOF, <testsuites> not properly closed".to_string(),
244 ),
245 root_path,
246 ));
247 }
248
249 report.ok_or_else(|| {
250 DeserializeError::new(
251 DeserializeErrorKind::InvalidStructure("missing <testsuites> element".to_string()),
252 Vec::new(),
253 )
254 })
255}
256
257fn deserialize_test_suite<R: BufRead>(
259 reader: &mut Reader<R>,
260 start_element: &BytesStart<'_>,
261 path: &[PathElement],
262 suite_index: usize,
263) -> Result<TestSuite, DeserializeError> {
264 let mut name = None;
265 let mut tests = 0;
266 let mut disabled = 0;
267 let mut errors = 0;
268 let mut failures = 0;
269 let mut timestamp = None;
270 let mut time = None;
271 let mut extra = IndexMap::new();
272
273 for attr in start_element.attributes() {
275 let attr = attr.map_err(|e| {
276 let mut suite_path = path.to_vec();
277 suite_path.push(PathElement::TestSuite(suite_index, None));
278 DeserializeError::new(DeserializeErrorKind::AttrError(e), suite_path)
279 })?;
280 let mut attr_path = path.to_vec();
281 attr_path.push(PathElement::TestSuite(suite_index, None));
282 match attr.key.as_ref() {
283 b"name" => {
284 attr_path.push(PathElement::Attribute("name".to_string()));
285 name = Some(parse_xml_string(&attr.value, &attr_path)?);
286 }
287 b"tests" => {
288 attr_path.push(PathElement::Attribute("tests".to_string()));
289 tests = parse_usize(&attr.value, &attr_path)?;
290 }
291 b"disabled" => {
292 attr_path.push(PathElement::Attribute("disabled".to_string()));
293 disabled = parse_usize(&attr.value, &attr_path)?;
294 }
295 b"errors" => {
296 attr_path.push(PathElement::Attribute("errors".to_string()));
297 errors = parse_usize(&attr.value, &attr_path)?;
298 }
299 b"failures" => {
300 attr_path.push(PathElement::Attribute("failures".to_string()));
301 failures = parse_usize(&attr.value, &attr_path)?;
302 }
303 b"timestamp" => {
304 attr_path.push(PathElement::Attribute("timestamp".to_string()));
305 timestamp = Some(parse_timestamp(&attr.value, &attr_path)?);
306 }
307 b"time" => {
308 attr_path.push(PathElement::Attribute("time".to_string()));
309 time = Some(parse_duration(&attr.value, &attr_path)?);
310 }
311 _ => {
312 let key = parse_xml_string(attr.key.as_ref(), &attr_path)?;
314 let value = parse_xml_string(&attr.value, &attr_path)?;
315 extra.insert(key, value);
316 }
317 }
318 }
319
320 let name_value = name.clone().ok_or_else(|| {
321 let mut attr_path = path.to_vec();
322 attr_path.push(PathElement::TestSuite(suite_index, None));
323 attr_path.push(PathElement::Attribute("name".to_string()));
324 DeserializeError::new(
325 DeserializeErrorKind::MissingAttribute("name".to_string()),
326 attr_path,
327 )
328 })?;
329
330 let mut suite_path = path.to_vec();
332 suite_path.push(PathElement::TestSuite(
333 suite_index,
334 Some(name_value.as_str().to_string()),
335 ));
336
337 let mut test_cases = Vec::new();
338 let mut properties = Vec::new();
339 let mut system_out = None;
340 let mut system_err = None;
341 let mut buf = Vec::new();
342
343 loop {
344 match reader.read_event_into(&mut buf) {
345 Ok(Event::Start(ref e)) => {
346 let element_name = e.name().as_ref().to_vec();
347 if &element_name == b"testcase" {
348 let test_case =
349 deserialize_test_case(reader, e, &suite_path, test_cases.len())?;
350 test_cases.push(test_case);
351 } else if &element_name == b"properties" {
352 properties = deserialize_properties(reader, &suite_path)?;
353 } else if &element_name == b"system-out" {
354 let mut child_path = suite_path.clone();
355 child_path.push(PathElement::SystemOut);
356 system_out = Some(read_text_content(reader, b"system-out", &child_path)?);
357 } else if &element_name == b"system-err" {
358 let mut child_path = suite_path.clone();
359 child_path.push(PathElement::SystemErr);
360 system_err = Some(read_text_content(reader, b"system-err", &child_path)?);
361 } else {
362 let tag_name = e.name().to_owned();
364 reader
365 .read_to_end_into(tag_name, &mut Vec::new())
366 .map_err(|e| {
367 DeserializeError::new(
368 DeserializeErrorKind::XmlError(e),
369 suite_path.clone(),
370 )
371 })?;
372 }
373 }
374 Ok(Event::Empty(ref e)) => {
375 if e.name().as_ref() == b"testcase" {
376 let test_case = deserialize_test_case_empty(e, &suite_path, test_cases.len())?;
377 test_cases.push(test_case);
378 }
379 }
380 Ok(Event::End(ref e)) if e.name().as_ref() == b"testsuite" => break,
381 Ok(Event::Eof) => {
382 return Err(DeserializeError::new(
383 DeserializeErrorKind::InvalidStructure(
384 "unexpected EOF in <testsuite>".to_string(),
385 ),
386 suite_path,
387 ))
388 }
389 Ok(_) => {}
390 Err(e) => {
391 return Err(DeserializeError::new(
392 DeserializeErrorKind::XmlError(e),
393 suite_path,
394 ))
395 }
396 }
397 buf.clear();
398 }
399
400 Ok(TestSuite {
401 name: name_value,
402 tests,
403 disabled,
404 errors,
405 failures,
406 timestamp,
407 time,
408 test_cases,
409 properties,
410 system_out,
411 system_err,
412 extra,
413 })
414}
415
416fn deserialize_test_suite_empty(
418 element: &BytesStart<'_>,
419 path: &[PathElement],
420 suite_index: usize,
421) -> Result<TestSuite, DeserializeError> {
422 let mut name = None;
423 let mut tests = 0;
424 let mut disabled = 0;
425 let mut errors = 0;
426 let mut failures = 0;
427 let mut timestamp = None;
428 let mut time = None;
429 let mut extra = IndexMap::new();
430
431 for attr in element.attributes() {
433 let attr = attr.map_err(|e| {
434 let mut suite_path = path.to_vec();
435 suite_path.push(PathElement::TestSuite(suite_index, None));
436 DeserializeError::new(DeserializeErrorKind::AttrError(e), suite_path)
437 })?;
438 let mut attr_path = path.to_vec();
439 attr_path.push(PathElement::TestSuite(suite_index, None));
440 match attr.key.as_ref() {
441 b"name" => {
442 attr_path.push(PathElement::Attribute("name".to_string()));
443 name = Some(parse_xml_string(&attr.value, &attr_path)?);
444 }
445 b"tests" => {
446 attr_path.push(PathElement::Attribute("tests".to_string()));
447 tests = parse_usize(&attr.value, &attr_path)?;
448 }
449 b"disabled" => {
450 attr_path.push(PathElement::Attribute("disabled".to_string()));
451 disabled = parse_usize(&attr.value, &attr_path)?;
452 }
453 b"errors" => {
454 attr_path.push(PathElement::Attribute("errors".to_string()));
455 errors = parse_usize(&attr.value, &attr_path)?;
456 }
457 b"failures" => {
458 attr_path.push(PathElement::Attribute("failures".to_string()));
459 failures = parse_usize(&attr.value, &attr_path)?;
460 }
461 b"timestamp" => {
462 attr_path.push(PathElement::Attribute("timestamp".to_string()));
463 timestamp = Some(parse_timestamp(&attr.value, &attr_path)?);
464 }
465 b"time" => {
466 attr_path.push(PathElement::Attribute("time".to_string()));
467 time = Some(parse_duration(&attr.value, &attr_path)?);
468 }
469 _ => {
470 let key = parse_xml_string(attr.key.as_ref(), &attr_path)?;
471 let value = parse_xml_string(&attr.value, &attr_path)?;
472 extra.insert(key, value);
473 }
474 }
475 }
476
477 let name = name.ok_or_else(|| {
478 let mut attr_path = path.to_vec();
479 attr_path.push(PathElement::TestSuite(suite_index, None));
480 attr_path.push(PathElement::Attribute("name".to_string()));
481 DeserializeError::new(
482 DeserializeErrorKind::MissingAttribute("name".to_string()),
483 attr_path,
484 )
485 })?;
486
487 Ok(TestSuite {
488 name,
489 tests,
490 disabled,
491 errors,
492 failures,
493 timestamp,
494 time,
495 test_cases: Vec::new(),
496 properties: Vec::new(),
497 system_out: None,
498 system_err: None,
499 extra,
500 })
501}
502
503fn deserialize_test_case<R: BufRead>(
505 reader: &mut Reader<R>,
506 start_element: &BytesStart<'_>,
507 path: &[PathElement],
508 case_index: usize,
509) -> Result<TestCase, DeserializeError> {
510 let mut name = None;
511 let mut classname = None;
512 let mut assertions = None;
513 let mut timestamp = None;
514 let mut time = None;
515 let mut extra = IndexMap::new();
516
517 for attr in start_element.attributes() {
519 let attr = attr.map_err(|e| {
520 let mut case_path = path.to_vec();
521 case_path.push(PathElement::TestCase(case_index, None));
522 DeserializeError::new(DeserializeErrorKind::AttrError(e), case_path)
523 })?;
524 let mut attr_path = path.to_vec();
525 attr_path.push(PathElement::TestCase(case_index, None));
526 match attr.key.as_ref() {
527 b"name" => {
528 attr_path.push(PathElement::Attribute("name".to_string()));
529 name = Some(parse_xml_string(&attr.value, &attr_path)?);
530 }
531 b"classname" => {
532 attr_path.push(PathElement::Attribute("classname".to_string()));
533 classname = Some(parse_xml_string(&attr.value, &attr_path)?);
534 }
535 b"assertions" => {
536 attr_path.push(PathElement::Attribute("assertions".to_string()));
537 assertions = Some(parse_usize(&attr.value, &attr_path)?);
538 }
539 b"timestamp" => {
540 attr_path.push(PathElement::Attribute("timestamp".to_string()));
541 timestamp = Some(parse_timestamp(&attr.value, &attr_path)?);
542 }
543 b"time" => {
544 attr_path.push(PathElement::Attribute("time".to_string()));
545 time = Some(parse_duration(&attr.value, &attr_path)?);
546 }
547 _ => {
548 let key = parse_xml_string(attr.key.as_ref(), &attr_path)?;
549 let value = parse_xml_string(&attr.value, &attr_path)?;
550 extra.insert(key, value);
551 }
552 }
553 }
554
555 let name_value = name.clone().ok_or_else(|| {
556 let mut attr_path = path.to_vec();
557 attr_path.push(PathElement::TestCase(case_index, None));
558 attr_path.push(PathElement::Attribute("name".to_string()));
559 DeserializeError::new(
560 DeserializeErrorKind::MissingAttribute("name".to_string()),
561 attr_path,
562 )
563 })?;
564
565 let mut case_path = path.to_vec();
567 case_path.push(PathElement::TestCase(
568 case_index,
569 Some(name_value.as_str().to_string()),
570 ));
571
572 let mut properties = Vec::new();
573 let mut system_out = None;
574 let mut system_err = None;
575 let mut status_elements = Vec::new();
576 let mut buf = Vec::new();
577
578 loop {
579 match reader.read_event_into(&mut buf) {
580 Ok(Event::Start(ref e)) => {
581 let element_name = e.name().as_ref().to_vec();
582 let is_status_element = matches!(
583 element_name.as_slice(),
584 b"failure"
585 | b"error"
586 | b"skipped"
587 | b"flakyFailure"
588 | b"flakyError"
589 | b"rerunFailure"
590 | b"rerunError"
591 );
592
593 if is_status_element {
594 let status_element = deserialize_status_element(reader, e, false, &case_path)?;
595 status_elements.push(status_element);
596 } else if &element_name == b"properties" {
597 properties = deserialize_properties(reader, &case_path)?;
598 } else if &element_name == b"system-out" {
599 let mut child_path = case_path.clone();
600 child_path.push(PathElement::SystemOut);
601 system_out = Some(read_text_content(reader, b"system-out", &child_path)?);
602 } else if &element_name == b"system-err" {
603 let mut child_path = case_path.clone();
604 child_path.push(PathElement::SystemErr);
605 system_err = Some(read_text_content(reader, b"system-err", &child_path)?);
606 } else {
607 let tag_name = e.name().to_owned();
609 reader
610 .read_to_end_into(tag_name, &mut Vec::new())
611 .map_err(|e| {
612 DeserializeError::new(
613 DeserializeErrorKind::XmlError(e),
614 case_path.clone(),
615 )
616 })?;
617 }
618 }
619 Ok(Event::Empty(ref e)) => {
620 let element_name = e.name().as_ref().to_vec();
621 let is_status_element = matches!(
622 element_name.as_slice(),
623 b"failure"
624 | b"error"
625 | b"skipped"
626 | b"flakyFailure"
627 | b"flakyError"
628 | b"rerunFailure"
629 | b"rerunError"
630 );
631
632 if is_status_element {
633 let status_element = deserialize_status_element(reader, e, true, &case_path)?;
634 status_elements.push(status_element);
635 }
636 }
638 Ok(Event::End(ref e)) if e.name().as_ref() == b"testcase" => break,
639 Ok(Event::Eof) => {
640 return Err(DeserializeError::new(
641 DeserializeErrorKind::InvalidStructure(
642 "unexpected EOF in <testcase>".to_string(),
643 ),
644 case_path,
645 ))
646 }
647 Ok(_) => {}
648 Err(e) => {
649 return Err(DeserializeError::new(
650 DeserializeErrorKind::XmlError(e),
651 case_path,
652 ))
653 }
654 }
655 buf.clear();
656 }
657
658 let status = build_test_case_status(status_elements, &case_path)?;
659
660 Ok(TestCase {
661 name: name_value,
662 classname,
663 assertions,
664 timestamp,
665 time,
666 status,
667 system_out,
668 system_err,
669 extra,
670 properties,
671 })
672}
673
674fn deserialize_test_case_empty(
676 element: &BytesStart<'_>,
677 path: &[PathElement],
678 case_index: usize,
679) -> Result<TestCase, DeserializeError> {
680 let mut name = None;
681 let mut classname = None;
682 let mut assertions = None;
683 let mut timestamp = None;
684 let mut time = None;
685 let mut extra = IndexMap::new();
686
687 for attr in element.attributes() {
688 let attr = attr.map_err(|e| {
689 let mut case_path = path.to_vec();
690 case_path.push(PathElement::TestCase(case_index, None));
691 DeserializeError::new(DeserializeErrorKind::AttrError(e), case_path)
692 })?;
693 let mut attr_path = path.to_vec();
694 attr_path.push(PathElement::TestCase(case_index, None));
695 match attr.key.as_ref() {
696 b"name" => {
697 attr_path.push(PathElement::Attribute("name".to_string()));
698 name = Some(parse_xml_string(&attr.value, &attr_path)?);
699 }
700 b"classname" => {
701 attr_path.push(PathElement::Attribute("classname".to_string()));
702 classname = Some(parse_xml_string(&attr.value, &attr_path)?);
703 }
704 b"assertions" => {
705 attr_path.push(PathElement::Attribute("assertions".to_string()));
706 assertions = Some(parse_usize(&attr.value, &attr_path)?);
707 }
708 b"timestamp" => {
709 attr_path.push(PathElement::Attribute("timestamp".to_string()));
710 timestamp = Some(parse_timestamp(&attr.value, &attr_path)?);
711 }
712 b"time" => {
713 attr_path.push(PathElement::Attribute("time".to_string()));
714 time = Some(parse_duration(&attr.value, &attr_path)?);
715 }
716 _ => {
717 let key = parse_xml_string(attr.key.as_ref(), &attr_path)?;
718 let value = parse_xml_string(&attr.value, &attr_path)?;
719 extra.insert(key, value);
720 }
721 }
722 }
723
724 let name_value = name.ok_or_else(|| {
725 let mut attr_path = path.to_vec();
726 attr_path.push(PathElement::TestCase(case_index, None));
727 attr_path.push(PathElement::Attribute("name".to_string()));
728 DeserializeError::new(
729 DeserializeErrorKind::MissingAttribute("name".to_string()),
730 attr_path,
731 )
732 })?;
733
734 Ok(TestCase {
735 name: name_value,
736 classname,
737 assertions,
738 timestamp,
739 time,
740 status: TestCaseStatus::success(),
741 system_out: None,
742 system_err: None,
743 extra,
744 properties: Vec::new(),
745 })
746}
747
748#[derive(Debug)]
750struct StatusElementData {
752 message: Option<XmlString>,
753 ty: Option<XmlString>,
754 description: Option<XmlString>,
755 stack_trace: Option<XmlString>,
756 system_out: Option<XmlString>,
757 system_err: Option<XmlString>,
758 timestamp: Option<DateTime<FixedOffset>>,
759 time: Option<Duration>,
760}
761
762#[derive(Debug, PartialEq, Eq, Clone, Copy)]
764enum MainStatusKind {
765 Failure,
766 Error,
767 Skipped,
768}
769
770struct MainStatusElement {
772 kind: MainStatusKind,
773 data: StatusElementData,
774}
775
776#[derive(Debug, PartialEq, Eq, Clone, Copy)]
778enum RerunStatusKind {
779 Failure,
780 Error,
781}
782
783struct RerunStatusElement {
785 kind: RerunStatusKind,
786 data: StatusElementData,
787}
788
789enum StatusElement {
791 Main(MainStatusElement),
792 Flaky(RerunStatusElement),
793 Rerun(RerunStatusElement),
794}
795
796enum StatusCategory {
797 Main(MainStatusKind),
798 Flaky(RerunStatusKind),
799 Rerun(RerunStatusKind),
800}
801
802fn deserialize_status_element<R: BufRead>(
804 reader: &mut Reader<R>,
805 element: &BytesStart<'_>,
806 is_empty: bool,
807 path: &[PathElement],
808) -> Result<StatusElement, DeserializeError> {
809 let (category, status_path_elem) = match element.name().as_ref() {
810 b"failure" => (
811 StatusCategory::Main(MainStatusKind::Failure),
812 PathElement::Failure,
813 ),
814 b"error" => (
815 StatusCategory::Main(MainStatusKind::Error),
816 PathElement::Error,
817 ),
818 b"skipped" => (
819 StatusCategory::Main(MainStatusKind::Skipped),
820 PathElement::Skipped,
821 ),
822 b"flakyFailure" => (
823 StatusCategory::Flaky(RerunStatusKind::Failure),
824 PathElement::FlakyFailure,
825 ),
826 b"flakyError" => (
827 StatusCategory::Flaky(RerunStatusKind::Error),
828 PathElement::FlakyError,
829 ),
830 b"rerunFailure" => (
831 StatusCategory::Rerun(RerunStatusKind::Failure),
832 PathElement::RerunFailure,
833 ),
834 b"rerunError" => (
835 StatusCategory::Rerun(RerunStatusKind::Error),
836 PathElement::RerunError,
837 ),
838 _ => {
839 return Err(DeserializeError::new(
840 DeserializeErrorKind::UnexpectedElement(
841 String::from_utf8_lossy(element.name().as_ref()).to_string(),
842 ),
843 path.to_vec(),
844 ))
845 }
846 };
847
848 let mut status_path = path.to_vec();
849 status_path.push(status_path_elem);
850
851 let mut message = None;
852 let mut ty = None;
853 let mut timestamp = None;
854 let mut time = None;
855
856 for attr in element.attributes() {
857 let attr = attr.map_err(|e| {
858 DeserializeError::new(DeserializeErrorKind::AttrError(e), status_path.clone())
859 })?;
860 let mut attr_path = status_path.clone();
861 match attr.key.as_ref() {
862 b"message" => {
863 attr_path.push(PathElement::Attribute("message".to_string()));
864 message = Some(parse_xml_string(&attr.value, &attr_path)?);
865 }
866 b"type" => {
867 attr_path.push(PathElement::Attribute("type".to_string()));
868 ty = Some(parse_xml_string(&attr.value, &attr_path)?);
869 }
870 b"timestamp" => {
871 attr_path.push(PathElement::Attribute("timestamp".to_string()));
872 timestamp = Some(parse_timestamp(&attr.value, &attr_path)?);
873 }
874 b"time" => {
875 attr_path.push(PathElement::Attribute("time".to_string()));
876 time = Some(parse_duration(&attr.value, &attr_path)?);
877 }
878 _ => {} }
880 }
881
882 let mut description_text = String::new();
883 let mut stack_trace = None;
884 let mut system_out = None;
885 let mut system_err = None;
886
887 if !is_empty {
889 let mut buf = Vec::new();
890 loop {
891 match reader.read_event_into(&mut buf) {
892 Ok(Event::Start(ref e)) | Ok(Event::Empty(ref e)) => {
893 let element_name = e.name().as_ref().to_vec();
894 if &element_name == b"stackTrace" {
895 let mut child_path = status_path.clone();
896 child_path.push(PathElement::Attribute("stackTrace".to_string()));
897 stack_trace = Some(read_text_content(reader, b"stackTrace", &child_path)?);
898 } else if &element_name == b"system-out" {
899 let mut child_path = status_path.clone();
900 child_path.push(PathElement::SystemOut);
901 system_out = Some(read_text_content(reader, b"system-out", &child_path)?);
902 } else if &element_name == b"system-err" {
903 let mut child_path = status_path.clone();
904 child_path.push(PathElement::SystemErr);
905 system_err = Some(read_text_content(reader, b"system-err", &child_path)?);
906 } else {
907 let tag_name = e.name().to_owned();
909 reader
910 .read_to_end_into(tag_name, &mut Vec::new())
911 .map_err(|e| {
912 DeserializeError::new(
913 DeserializeErrorKind::XmlError(e),
914 status_path.clone(),
915 )
916 })?;
917 }
918 }
919 Ok(Event::Text(ref e)) => {
920 let text = std::str::from_utf8(e.as_ref()).map_err(|e| {
921 DeserializeError::new(
922 DeserializeErrorKind::Utf8Error(e),
923 status_path.clone(),
924 )
925 })?;
926 let unescaped = unescape_with(text, resolve_xml_entity).map_err(|e| {
928 DeserializeError::new(
929 DeserializeErrorKind::EscapeError(e),
930 status_path.clone(),
931 )
932 })?;
933 description_text.push_str(&unescaped);
934 }
935 Ok(Event::CData(ref e)) => {
936 let text = std::str::from_utf8(e.as_ref()).map_err(|e| {
938 DeserializeError::new(
939 DeserializeErrorKind::Utf8Error(e),
940 status_path.clone(),
941 )
942 })?;
943 description_text.push_str(text);
944 }
945 Ok(Event::GeneralRef(ref e)) => {
946 let entity_name = std::str::from_utf8(e.as_ref()).map_err(|e| {
948 DeserializeError::new(
949 DeserializeErrorKind::Utf8Error(e),
950 status_path.clone(),
951 )
952 })?;
953 let unescaped = resolve_xml_entity(entity_name).ok_or_else(|| {
954 DeserializeError::new(
955 DeserializeErrorKind::InvalidStructure(format!(
956 "unrecognized entity: {entity_name}",
957 )),
958 status_path.clone(),
959 )
960 })?;
961 description_text.push_str(unescaped);
962 }
963 Ok(Event::End(ref e))
964 if matches!(
965 e.name().as_ref(),
966 b"failure"
967 | b"error"
968 | b"skipped"
969 | b"flakyFailure"
970 | b"flakyError"
971 | b"rerunFailure"
972 | b"rerunError"
973 ) =>
974 {
975 break
976 }
977 Ok(Event::Eof) => {
978 return Err(DeserializeError::new(
979 DeserializeErrorKind::InvalidStructure(
980 "unexpected EOF in status element".to_string(),
981 ),
982 status_path,
983 ))
984 }
985 Ok(_) => {}
986 Err(e) => {
987 return Err(DeserializeError::new(
988 DeserializeErrorKind::XmlError(e),
989 status_path,
990 ))
991 }
992 }
993 buf.clear();
994 }
995 }
996
997 let description = if !description_text.trim().is_empty() {
999 Some(XmlString::new(description_text.trim()))
1000 } else {
1001 None
1002 };
1003
1004 let data = StatusElementData {
1005 message,
1006 ty,
1007 description,
1008 stack_trace,
1009 system_out,
1010 system_err,
1011 timestamp,
1012 time,
1013 };
1014
1015 Ok(match category {
1016 StatusCategory::Main(kind) => StatusElement::Main(MainStatusElement { kind, data }),
1017 StatusCategory::Flaky(kind) => StatusElement::Flaky(RerunStatusElement { kind, data }),
1018 StatusCategory::Rerun(kind) => StatusElement::Rerun(RerunStatusElement { kind, data }),
1019 })
1020}
1021
1022fn build_test_case_status(
1024 status_elements: Vec<StatusElement>,
1025 path: &[PathElement],
1026) -> Result<TestCaseStatus, DeserializeError> {
1027 if status_elements.is_empty() {
1028 return Ok(TestCaseStatus::success());
1029 }
1030
1031 let mut main_status: Option<&MainStatusElement> = None;
1033 let mut flaky_runs = Vec::new();
1034 let mut reruns = Vec::new();
1035
1036 for element in &status_elements {
1037 match element {
1038 StatusElement::Main(main) => {
1039 if main_status.is_some() {
1040 return Err(DeserializeError::new(
1041 DeserializeErrorKind::InvalidStructure(
1042 "multiple main status elements (failure/error/skipped) are not allowed"
1043 .to_string(),
1044 ),
1045 path.to_vec(),
1046 ));
1047 }
1048 main_status = Some(main);
1049 }
1050 StatusElement::Flaky(flaky) => {
1051 flaky_runs.push(flaky);
1052 }
1053 StatusElement::Rerun(rerun) => {
1054 reruns.push(rerun);
1055 }
1056 }
1057 }
1058
1059 if let Some(main) = main_status {
1061 match main.kind {
1062 MainStatusKind::Skipped => Ok(TestCaseStatus::Skipped {
1063 message: main.data.message.clone(),
1064 ty: main.data.ty.clone(),
1065 description: main.data.description.clone(),
1066 }),
1067 MainStatusKind::Failure | MainStatusKind::Error => {
1068 let kind = if main.kind == MainStatusKind::Failure {
1069 NonSuccessKind::Failure
1070 } else {
1071 NonSuccessKind::Error
1072 };
1073
1074 let reruns = reruns.into_iter().map(build_test_rerun).collect();
1075
1076 Ok(TestCaseStatus::NonSuccess {
1077 kind,
1078 message: main.data.message.clone(),
1079 ty: main.data.ty.clone(),
1080 description: main.data.description.clone(),
1081 reruns,
1082 })
1083 }
1084 }
1085 } else if !flaky_runs.is_empty() {
1086 let flaky_runs = flaky_runs.into_iter().map(build_test_rerun).collect();
1088
1089 Ok(TestCaseStatus::Success { flaky_runs })
1090 } else {
1091 Err(DeserializeError::new(
1092 DeserializeErrorKind::InvalidStructure("invalid status elements".to_string()),
1093 path.to_vec(),
1094 ))
1095 }
1096}
1097
1098fn build_test_rerun(element: &RerunStatusElement) -> TestRerun {
1102 let kind = match element.kind {
1103 RerunStatusKind::Failure => NonSuccessKind::Failure,
1104 RerunStatusKind::Error => NonSuccessKind::Error,
1105 };
1106
1107 TestRerun {
1108 kind,
1109 timestamp: element.data.timestamp,
1110 time: element.data.time,
1111 message: element.data.message.clone(),
1112 ty: element.data.ty.clone(),
1113 stack_trace: element.data.stack_trace.clone(),
1114 system_out: element.data.system_out.clone(),
1115 system_err: element.data.system_err.clone(),
1116 description: element.data.description.clone(),
1117 }
1118}
1119
1120fn deserialize_properties<R: BufRead>(
1122 reader: &mut Reader<R>,
1123 path: &[PathElement],
1124) -> Result<Vec<Property>, DeserializeError> {
1125 let mut properties = Vec::new();
1126 let mut buf = Vec::new();
1127 let mut prop_path = path.to_vec();
1128 prop_path.push(PathElement::Properties);
1129
1130 loop {
1131 match reader.read_event_into(&mut buf) {
1132 Ok(Event::Empty(e)) if e.name().as_ref() == b"property" => {
1133 let mut elem_path = prop_path.clone();
1134 elem_path.push(PathElement::Property(properties.len()));
1135 let property = deserialize_property(&e, &elem_path)?;
1136 properties.push(property);
1137 }
1138 Ok(Event::End(e)) if e.name().as_ref() == b"properties" => break,
1139 Ok(Event::Eof) => {
1140 return Err(DeserializeError::new(
1141 DeserializeErrorKind::InvalidStructure(
1142 "unexpected EOF in <properties>".to_string(),
1143 ),
1144 prop_path,
1145 ))
1146 }
1147 Ok(_) => {}
1148 Err(e) => {
1149 return Err(DeserializeError::new(
1150 DeserializeErrorKind::XmlError(e),
1151 prop_path,
1152 ))
1153 }
1154 }
1155 buf.clear();
1156 }
1157
1158 Ok(properties)
1159}
1160
1161fn deserialize_property(
1163 element: &BytesStart<'_>,
1164 path: &[PathElement],
1165) -> Result<Property, DeserializeError> {
1166 let mut name = None;
1167 let mut value = None;
1168
1169 for attr in element.attributes() {
1170 let attr = attr.map_err(|e| {
1171 DeserializeError::new(DeserializeErrorKind::AttrError(e), path.to_vec())
1172 })?;
1173 let mut attr_path = path.to_vec();
1174 match attr.key.as_ref() {
1175 b"name" => {
1176 attr_path.push(PathElement::Attribute("name".to_string()));
1177 name = Some(parse_xml_string(&attr.value, &attr_path)?);
1178 }
1179 b"value" => {
1180 attr_path.push(PathElement::Attribute("value".to_string()));
1181 value = Some(parse_xml_string(&attr.value, &attr_path)?);
1182 }
1183 _ => {} }
1185 }
1186
1187 let name = name.ok_or_else(|| {
1188 let mut attr_path = path.to_vec();
1189 attr_path.push(PathElement::Attribute("name".to_string()));
1190 DeserializeError::new(
1191 DeserializeErrorKind::MissingAttribute("name".to_string()),
1192 attr_path,
1193 )
1194 })?;
1195 let value = value.ok_or_else(|| {
1196 let mut attr_path = path.to_vec();
1197 attr_path.push(PathElement::Attribute("value".to_string()));
1198 DeserializeError::new(
1199 DeserializeErrorKind::MissingAttribute("value".to_string()),
1200 attr_path,
1201 )
1202 })?;
1203
1204 Ok(Property { name, value })
1205}
1206
1207fn read_text_content<R: BufRead>(
1209 reader: &mut Reader<R>,
1210 element_name: &[u8],
1211 path: &[PathElement],
1212) -> Result<XmlString, DeserializeError> {
1213 let mut text = String::new();
1214 let mut buf = Vec::new();
1215
1216 loop {
1217 match reader.read_event_into(&mut buf) {
1218 Ok(Event::Text(e)) => {
1219 let s = std::str::from_utf8(e.as_ref()).map_err(|e| {
1220 DeserializeError::new(DeserializeErrorKind::Utf8Error(e), path.to_vec())
1221 })?;
1222 let unescaped = unescape_with(s, resolve_xml_entity).map_err(|e| {
1223 DeserializeError::new(DeserializeErrorKind::EscapeError(e), path.to_vec())
1224 })?;
1225 text.push_str(&unescaped);
1226 }
1227 Ok(Event::CData(e)) => {
1228 let s = std::str::from_utf8(e.as_ref()).map_err(|e| {
1230 DeserializeError::new(DeserializeErrorKind::Utf8Error(e), path.to_vec())
1231 })?;
1232 text.push_str(s);
1233 }
1234 Ok(Event::GeneralRef(e)) => {
1235 let entity_name = std::str::from_utf8(e.as_ref()).map_err(|e| {
1236 DeserializeError::new(DeserializeErrorKind::Utf8Error(e), path.to_vec())
1237 })?;
1238 let unescaped = resolve_xml_entity(entity_name).ok_or_else(|| {
1239 DeserializeError::new(
1240 DeserializeErrorKind::InvalidStructure(format!(
1241 "unrecognized entity: {entity_name}",
1242 )),
1243 path.to_vec(),
1244 )
1245 })?;
1246 text.push_str(unescaped);
1247 }
1248 Ok(Event::End(e)) if e.name().as_ref() == element_name => break,
1249 Ok(Event::Eof) => {
1250 return Err(DeserializeError::new(
1251 DeserializeErrorKind::InvalidStructure(format!(
1252 "unexpected EOF in <{}>",
1253 String::from_utf8_lossy(element_name)
1254 )),
1255 path.to_vec(),
1256 ))
1257 }
1258 Ok(_) => {}
1259 Err(e) => {
1260 return Err(DeserializeError::new(
1261 DeserializeErrorKind::XmlError(e),
1262 path.to_vec(),
1263 ))
1264 }
1265 }
1266 buf.clear();
1267 }
1268
1269 Ok(XmlString::new(text.trim()))
1271}
1272
1273fn parse_xml_string(bytes: &[u8], path: &[PathElement]) -> Result<XmlString, DeserializeError> {
1278 let s = std::str::from_utf8(bytes)
1279 .map_err(|e| DeserializeError::new(DeserializeErrorKind::Utf8Error(e), path.to_vec()))?;
1280 let unescaped = unescape_with(s, resolve_xml_entity)
1281 .map_err(|e| DeserializeError::new(DeserializeErrorKind::EscapeError(e), path.to_vec()))?;
1282 Ok(XmlString::new(unescaped.as_ref()))
1283}
1284
1285fn parse_usize(bytes: &[u8], path: &[PathElement]) -> Result<usize, DeserializeError> {
1286 let s = std::str::from_utf8(bytes)
1287 .map_err(|e| DeserializeError::new(DeserializeErrorKind::Utf8Error(e), path.to_vec()))?;
1288 s.parse()
1289 .map_err(|e| DeserializeError::new(DeserializeErrorKind::ParseIntError(e), path.to_vec()))
1290}
1291
1292fn parse_duration(bytes: &[u8], path: &[PathElement]) -> Result<Duration, DeserializeError> {
1293 let s = std::str::from_utf8(bytes)
1294 .map_err(|e| DeserializeError::new(DeserializeErrorKind::Utf8Error(e), path.to_vec()))?;
1295 let seconds: f64 = s.parse().map_err(|_| {
1296 DeserializeError::new(
1297 DeserializeErrorKind::ParseDurationError(s.to_string()),
1298 path.to_vec(),
1299 )
1300 })?;
1301
1302 Duration::try_from_secs_f64(seconds).map_err(|_| {
1303 DeserializeError::new(
1304 DeserializeErrorKind::ParseDurationError(s.to_string()),
1305 path.to_vec(),
1306 )
1307 })
1308}
1309
1310fn parse_timestamp(
1311 bytes: &[u8],
1312 path: &[PathElement],
1313) -> Result<DateTime<FixedOffset>, DeserializeError> {
1314 let s = std::str::from_utf8(bytes)
1315 .map_err(|e| DeserializeError::new(DeserializeErrorKind::Utf8Error(e), path.to_vec()))?;
1316 DateTime::parse_from_rfc3339(s).map_err(|_| {
1317 DeserializeError::new(
1318 DeserializeErrorKind::ParseTimestampError(s.to_string()),
1319 path.to_vec(),
1320 )
1321 })
1322}
1323
1324fn parse_uuid(bytes: &[u8], path: &[PathElement]) -> Result<ReportUuid, DeserializeError> {
1325 let s = std::str::from_utf8(bytes)
1326 .map_err(|e| DeserializeError::new(DeserializeErrorKind::Utf8Error(e), path.to_vec()))?;
1327 let uuid = s.parse().map_err(|e| {
1328 DeserializeError::new(DeserializeErrorKind::ParseUuidError(e), path.to_vec())
1329 })?;
1330 Ok(ReportUuid::from_untyped_uuid(uuid))
1331}
1332
1333#[cfg(test)]
1334mod tests {
1335 use super::*;
1336
1337 #[test]
1338 fn test_parse_simple_report() {
1339 let xml = r#"<?xml version="1.0" encoding="UTF-8"?>
1340<testsuites name="my-test-run" tests="1" failures="0" errors="0">
1341 <testsuite name="my-test-suite" tests="1" disabled="0" errors="0" failures="0">
1342 <testcase name="success-case"/>
1343 </testsuite>
1344</testsuites>
1345"#;
1346
1347 let report = Report::deserialize_from_str(xml).unwrap();
1348 assert_eq!(report.name.as_str(), "my-test-run");
1349 assert_eq!(report.tests, 1);
1350 assert_eq!(report.failures, 0);
1351 assert_eq!(report.errors, 0);
1352 assert_eq!(report.test_suites.len(), 1);
1353
1354 let suite = &report.test_suites[0];
1355 assert_eq!(suite.name.as_str(), "my-test-suite");
1356 assert_eq!(suite.test_cases.len(), 1);
1357
1358 let case = &suite.test_cases[0];
1359 assert_eq!(case.name.as_str(), "success-case");
1360 assert!(matches!(case.status, TestCaseStatus::Success { .. }));
1361 }
1362
1363 #[test]
1364 fn test_parse_report_with_failure() {
1365 let xml = r#"<?xml version="1.0" encoding="UTF-8"?>
1366<testsuites name="test-run" tests="1" failures="1" errors="0">
1367 <testsuite name="suite" tests="1" disabled="0" errors="0" failures="1">
1368 <testcase name="failing-test">
1369 <failure message="assertion failed">Expected true but got false</failure>
1370 </testcase>
1371 </testsuite>
1372</testsuites>
1373"#;
1374
1375 let report = Report::deserialize_from_str(xml).unwrap();
1376 let case = &report.test_suites[0].test_cases[0];
1377
1378 match &case.status {
1379 TestCaseStatus::NonSuccess {
1380 kind,
1381 message,
1382 description,
1383 ..
1384 } => {
1385 assert_eq!(*kind, NonSuccessKind::Failure);
1386 assert_eq!(message.as_ref().unwrap().as_str(), "assertion failed");
1387 assert_eq!(
1388 description.as_ref().unwrap().as_str(),
1389 "Expected true but got false"
1390 );
1391 }
1392 _ => panic!("Expected NonSuccess status"),
1393 }
1394 }
1395
1396 #[test]
1397 fn test_parse_report_with_properties() {
1398 let xml = r#"<?xml version="1.0" encoding="UTF-8"?>
1399<testsuites name="test-run" tests="1" failures="0" errors="0">
1400 <testsuite name="suite" tests="1" disabled="0" errors="0" failures="0">
1401 <properties>
1402 <property name="env" value="test"/>
1403 <property name="platform" value="linux"/>
1404 </properties>
1405 <testcase name="test"/>
1406 </testsuite>
1407</testsuites>
1408"#;
1409
1410 let report = Report::deserialize_from_str(xml).unwrap();
1411 let suite = &report.test_suites[0];
1412
1413 assert_eq!(suite.properties.len(), 2);
1414 assert_eq!(suite.properties[0].name.as_str(), "env");
1415 assert_eq!(suite.properties[0].value.as_str(), "test");
1416 assert_eq!(suite.properties[1].name.as_str(), "platform");
1417 assert_eq!(suite.properties[1].value.as_str(), "linux");
1418 }
1419}