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