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