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