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