1#![cfg_attr(
33 feature = "document-features",
34 cfg_attr(doc, doc = ::document_features::document_features!())
35)]
36#![forbid(unsafe_code)]
37#![deny(missing_docs)]
38#![cfg_attr(docsrs, feature(doc_auto_cfg))]
41
42mod errors;
44
45pub use errors::Error;
46use quick_xml::escape::unescape;
47use quick_xml::events::BytesStart as XMLBytesStart;
48use quick_xml::events::Event as XMLEvent;
49use quick_xml::name::QName;
50use quick_xml::Reader as XMLReader;
51use std::borrow::Cow;
52#[cfg(feature = "properties_as_hashmap")]
53use std::collections::HashMap;
54use std::io::prelude::*;
55use std::str;
56use std::vec::Vec;
57
58#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
59#[derive(Debug, Clone, Default)]
60pub struct Properties {
62 #[cfg(feature = "properties_as_hashmap")]
64 pub hashmap: HashMap<String, String>,
65 #[cfg(feature = "properties_as_vector")]
67 pub vec: Vec<(String, String)>,
68}
69
70fn parse_property<B: BufRead>(
72 e: &XMLBytesStart,
73 r: Option<&mut XMLReader<B>>,
74) -> Result<(String, String), Error> {
75 let mut k: Option<String> = None;
76 let mut v: Option<String> = None;
77 for a in e.attributes() {
78 let a = a?;
79 match a.key {
80 QName(b"name") => k = Some(try_from_attribute_value_string(a.value)?),
81 QName(b"value") => v = Some(try_from_attribute_value_string(a.value)?),
82 _ => {}
83 };
84 }
85 if let Some(r) = r {
86 loop {
87 let mut buf = Vec::new();
88 match r.read_event_into(&mut buf) {
89 Ok(XMLEvent::End(ref e)) if e.name() == QName(b"property") => break,
90 Ok(XMLEvent::Eof) => {
91 return Err(Error::UnexpectedEndOfFile("property".to_string()));
92 }
93 Ok(XMLEvent::Text(e)) => {
94 if v.is_none() {
95 v = Some(e.unescape()?.trim().to_string());
96 } else {
97 v.as_mut().unwrap().push('\n');
98 v.as_mut().unwrap().push_str(e.unescape()?.trim());
99 }
100 }
101 Ok(XMLEvent::CData(e)) => {
102 if v.is_none() {
103 v = Some(str::from_utf8(&e)?.to_string());
104 } else {
105 v.as_mut().unwrap().push('\n');
106 v.as_mut().unwrap().push_str(str::from_utf8(&e)?);
107 }
108 }
109 Ok(XMLEvent::Start(ref e)) => {
110 r.read_to_end_into(e.name(), &mut Vec::new())?;
111 }
112 Err(err) => return Err(err.into()),
113 _ => (),
114 }
115 buf.clear();
116 }
117 }
118 match (k, v) {
119 (Some(k), Some(v)) => Ok((k, v)),
120 (Some(k), None) => Ok((k, "".to_string())),
121 _ => Err(Error::MissingPropertyName),
122 }
123}
124
125impl Properties {
126 fn from_reader<B: BufRead>(r: &mut XMLReader<B>) -> Result<Self, Error> {
128 let mut p = Self::default();
129 loop {
130 let mut buf = Vec::new();
131 match r.read_event_into(&mut buf) {
132 Ok(XMLEvent::End(ref e)) if e.name() == QName(b"properties") => break,
133
134 Ok(XMLEvent::Empty(ref e)) if e.name() == QName(b"property") => {
135 let (k, v) = parse_property::<B>(e, None)?;
136 p.add_property(k, v);
137 }
138 Ok(XMLEvent::Start(ref e)) if e.name() == QName(b"property") => {
139 let (k, v) = parse_property(e, Some(r))?;
140 p.add_property(k, v);
141 }
142 Ok(XMLEvent::Start(ref e)) => {
143 r.read_to_end_into(e.name(), &mut Vec::new())?;
144 }
145 Ok(XMLEvent::Eof) => {
146 return Err(Error::UnexpectedEndOfFile("properties".to_string()));
147 }
148 Err(err) => return Err(err.into()),
149 _ => (),
150 }
151 buf.clear();
152 }
153 Ok(p)
154 }
155
156 #[cfg_attr(
158 all(
159 not(feature = "properties_as_hashmap"),
160 not(feature = "properties_as_vector")
161 ),
162 allow(unused_variables)
163 )]
164 fn add_property(&mut self, key: String, value: String) {
166 #[cfg(feature = "properties_as_hashmap")]
167 self.hashmap.insert(key.clone(), value.clone());
168 #[cfg(feature = "properties_as_vector")]
169 self.vec.push((key, value));
170 }
171}
172
173#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
174#[derive(Debug, Clone, Copy, Default)]
175pub enum RerunOrFlakyKind {
177 #[default]
179 FlakyFailure,
180 FlakyError,
182 RerunFailure,
184 RerunError,
186}
187
188#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
189#[derive(Debug, Clone, Default)]
190pub struct RerunOrFlaky {
192 pub timestamp: Option<String>,
194 pub time: f64,
196 pub rerun_type: String,
198 pub message: String,
200 pub text: String,
202 pub system_out: Option<String>,
204 pub system_err: Option<String>,
206 pub stack_trace: Option<String>,
208 pub kind: RerunOrFlakyKind,
210}
211
212impl RerunOrFlaky {
213 fn parse_attributes(&mut self, e: &XMLBytesStart) -> Result<(), Error> {
215 for a in e.attributes() {
216 let a = a?;
217 match a.key {
218 QName(b"type") => self.rerun_type = try_from_attribute_value_string(a.value)?,
220 QName(b"time") => self.time = try_from_attribute_value_f64(a.value)?,
221 QName(b"timestamp") => {
222 self.timestamp = Some(try_from_attribute_value_string(a.value)?)
223 }
224 QName(b"message") => self.message = try_from_attribute_value_string(a.value)?,
225 _ => {}
226 };
227 }
228 Ok(())
229 }
230
231 fn new_empty(e: &XMLBytesStart, kind: RerunOrFlakyKind) -> Result<Self, Error> {
233 let mut rt = Self {
234 kind,
235 ..Default::default()
236 };
237 rt.parse_attributes(e)?;
238 Ok(rt)
239 }
240
241 fn from_reader<B: BufRead>(
243 e: &XMLBytesStart,
244 r: &mut XMLReader<B>,
245 kind: RerunOrFlakyKind,
246 ) -> Result<Self, Error> {
247 let mut rt = Self {
248 kind,
249 system_out: None,
250 system_err: None,
251 stack_trace: None,
252 ..Default::default()
253 };
254 rt.parse_attributes(e)?;
255
256 let end_tag_name = e.name();
257
258 loop {
259 let mut buf = Vec::new();
260 match r.read_event_into(&mut buf) {
261 Ok(XMLEvent::End(ref end_event)) if end_event.name() == end_tag_name => break,
262 Ok(XMLEvent::Text(e)) => {
263 if rt.text.is_empty() {
264 rt.text = e.unescape()?.trim().to_string();
265 } else {
266 rt.text.push('\n');
267 rt.text.push_str(e.unescape()?.trim());
268 }
269 }
270 Ok(XMLEvent::CData(e)) => {
271 if rt.text.is_empty() {
272 rt.text = str::from_utf8(&e)?.to_string();
273 } else {
274 rt.text.push('\n');
275 rt.text.push_str(str::from_utf8(&e)?);
276 }
277 }
278 Ok(XMLEvent::Start(ref start_event)) => match start_event.name() {
279 QName(b"system-out") => {
280 if let Some(parsed_content) = parse_system(start_event, r)? {
281 let current_out = rt.system_out.get_or_insert_with(String::new);
282 if !current_out.is_empty() {
283 current_out.push('\n');
284 }
285 current_out.push_str(&parsed_content);
286 }
287 }
288 QName(b"system-err") => {
289 if let Some(parsed_content) = parse_system(start_event, r)? {
290 let current_err = rt.system_err.get_or_insert_with(String::new);
291 if !current_err.is_empty() {
292 current_err.push('\n');
293 }
294 current_err.push_str(&parsed_content);
295 }
296 }
297 QName(b"stackTrace") => {
298 rt.stack_trace = parse_system(start_event, r)?;
300 }
301 _ => {
302 r.read_to_end_into(start_event.name(), &mut Vec::new())?;
303 }
304 },
305 Ok(XMLEvent::Empty(ref empty_event)) => match empty_event.name() {
306 QName(b"system-out") => {}
307 QName(b"system-err") => {}
308 QName(b"stackTrace") => {}
309 _ => {}
310 },
311 Ok(XMLEvent::Eof) => {
312 let tag_name = String::from_utf8_lossy(end_tag_name.as_ref()).to_string();
313 return Err(Error::UnexpectedEndOfFile(tag_name));
314 }
315 Err(err) => return Err(err.into()),
316 _ => (),
317 }
318 buf.clear();
319 }
320 Ok(rt)
321 }
322}
323
324#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
325#[derive(Debug, Clone, Default)]
326pub struct TestFailure {
328 pub message: String,
330 pub text: String,
332 pub failure_type: String,
334}
335impl TestFailure {
336 fn parse_attributes(&mut self, e: &XMLBytesStart) -> Result<(), Error> {
338 for a in e.attributes() {
339 let a = a?;
340 match a.key {
341 QName(b"type") => self.failure_type = try_from_attribute_value_string(a.value)?,
342 QName(b"message") => self.message = try_from_attribute_value_string(a.value)?,
343 _ => {}
344 };
345 }
346 Ok(())
347 }
348
349 fn new_empty(e: &XMLBytesStart) -> Result<Self, Error> {
351 let mut tf = Self::default();
352 tf.parse_attributes(e)?;
353 Ok(tf)
354 }
355
356 fn from_reader<B: BufRead>(e: &XMLBytesStart, r: &mut XMLReader<B>) -> Result<Self, Error> {
358 let mut tf = Self::default();
359 tf.parse_attributes(e)?;
360 loop {
361 let mut buf = Vec::new();
362 match r.read_event_into(&mut buf) {
363 Ok(XMLEvent::End(ref e)) if e.name() == QName(b"failure") => break,
364 Ok(XMLEvent::Text(e)) => {
365 if tf.text.is_empty() {
366 tf.text = e.unescape()?.trim().to_string();
367 } else {
368 tf.text.push('\n');
369 tf.text.push_str(e.unescape()?.trim());
370 }
371 }
372 Ok(XMLEvent::Eof) => {
373 return Err(Error::UnexpectedEndOfFile("failure".to_string()));
374 }
375 Ok(XMLEvent::CData(e)) => {
376 if tf.text.is_empty() {
377 tf.text = str::from_utf8(&e)?.to_string();
378 } else {
379 tf.text.push('\n');
380 tf.text.push_str(str::from_utf8(&e)?);
381 }
382 }
383 Ok(XMLEvent::Start(ref e)) => {
384 r.read_to_end_into(e.name(), &mut Vec::new())?;
385 }
386 Err(err) => return Err(err.into()),
387 _ => (),
388 }
389 buf.clear();
390 }
391 Ok(tf)
392 }
393}
394
395#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
396#[derive(Debug, Clone, Default)]
397pub struct TestError {
399 pub message: String,
401 pub text: String,
403 pub error_type: String,
405}
406impl TestError {
407 fn parse_attributes(&mut self, e: &XMLBytesStart) -> Result<(), Error> {
409 for a in e.attributes() {
410 let a = a?;
411 match a.key {
412 QName(b"type") => self.error_type = try_from_attribute_value_string(a.value)?,
413 QName(b"message") => self.message = try_from_attribute_value_string(a.value)?,
414 _ => {}
415 };
416 }
417 Ok(())
418 }
419
420 fn new_empty(e: &XMLBytesStart) -> Result<Self, Error> {
422 let mut te = Self::default();
423 te.parse_attributes(e)?;
424 Ok(te)
425 }
426
427 fn from_reader<B: BufRead>(e: &XMLBytesStart, r: &mut XMLReader<B>) -> Result<Self, Error> {
429 let mut te = Self::default();
430 te.parse_attributes(e)?;
431 loop {
432 let mut buf = Vec::new();
433 match r.read_event_into(&mut buf) {
434 Ok(XMLEvent::End(ref e)) if e.name() == QName(b"error") => break,
435 Ok(XMLEvent::Text(e)) => {
436 if te.text.is_empty() {
437 te.text = e.unescape()?.trim().to_string();
438 } else {
439 te.text.push('\n');
440 te.text.push_str(e.unescape()?.trim());
441 }
442 }
443 Ok(XMLEvent::Eof) => {
444 return Err(Error::UnexpectedEndOfFile("error".to_string()));
445 }
446 Ok(XMLEvent::CData(e)) => {
447 if te.text.is_empty() {
448 te.text = str::from_utf8(&e)?.to_string();
449 } else {
450 te.text.push('\n');
451 te.text.push_str(str::from_utf8(&e)?);
452 }
453 }
454 Ok(XMLEvent::Start(ref e)) => {
455 r.read_to_end_into(e.name(), &mut Vec::new())?;
456 }
457 Err(err) => return Err(err.into()),
458 _ => (),
459 }
460 buf.clear();
461 }
462 Ok(te)
463 }
464}
465
466#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
467#[derive(Debug, Clone, Default)]
468pub struct TestSkipped {
470 pub message: String,
472 pub text: String,
474 pub skipped_type: String,
476}
477impl TestSkipped {
478 fn parse_attributes(&mut self, e: &XMLBytesStart) -> Result<(), Error> {
480 for a in e.attributes() {
481 let a = a?;
482 match a.key {
483 QName(b"type") => self.skipped_type = try_from_attribute_value_string(a.value)?,
484 QName(b"message") => self.message = try_from_attribute_value_string(a.value)?,
485 _ => {}
486 };
487 }
488 Ok(())
489 }
490
491 fn new_empty(e: &XMLBytesStart) -> Result<Self, Error> {
493 let mut ts = Self::default();
494 ts.parse_attributes(e)?;
495 Ok(ts)
496 }
497
498 fn from_reader<B: BufRead>(e: &XMLBytesStart, r: &mut XMLReader<B>) -> Result<Self, Error> {
500 let mut ts = Self::default();
501 ts.parse_attributes(e)?;
502 loop {
503 let mut buf = Vec::new();
504 match r.read_event_into(&mut buf) {
505 Ok(XMLEvent::End(ref e)) if e.name() == QName(b"skipped") => break,
506 Ok(XMLEvent::Text(e)) => {
507 if ts.text.is_empty() {
508 ts.text = e.unescape()?.trim().to_string();
509 } else {
510 ts.text.push('\n');
511 ts.text.push_str(e.unescape()?.trim());
512 }
513 }
514 Ok(XMLEvent::Eof) => {
515 return Err(Error::UnexpectedEndOfFile("skipped".to_string()));
516 }
517 Ok(XMLEvent::CData(e)) => {
518 if ts.text.is_empty() {
519 ts.text = str::from_utf8(&e)?.to_string();
520 } else {
521 ts.text.push('\n');
522 ts.text.push_str(str::from_utf8(&e)?);
523 }
524 }
525 Ok(XMLEvent::Start(ref e)) => {
526 r.read_to_end_into(e.name(), &mut Vec::new())?;
527 }
528 Err(err) => return Err(err.into()),
529 _ => (),
530 }
531 buf.clear();
532 }
533 Ok(ts)
534 }
535}
536
537#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
538#[derive(Debug, Clone, Default)]
539pub enum TestStatus {
541 #[default]
543 Success,
544 Error(TestError),
546 Failure(TestFailure),
548 Skipped(TestSkipped),
550}
551impl TestStatus {
552 pub fn is_success(&self) -> bool {
554 matches!(self, TestStatus::Success)
555 }
556 pub fn is_error(&self) -> bool {
558 matches!(self, TestStatus::Error(_))
559 }
560 pub fn error_as_ref(&self) -> &TestError {
566 if let TestStatus::Error(e) = self {
567 return e;
568 }
569 panic!("called `TestStatus::error()` on a value that is not TestStatus::Error(_)");
570 }
571
572 pub fn is_failure(&self) -> bool {
574 matches!(self, TestStatus::Failure(_))
575 }
576
577 pub fn failure_as_ref(&self) -> &TestFailure {
583 if let TestStatus::Failure(e) = self {
584 return e;
585 }
586 panic!("called `TestStatus::failure()` on a value that is not TestStatus::Failure(_)");
587 }
588
589 pub fn is_skipped(&self) -> bool {
591 matches!(self, TestStatus::Skipped(_))
592 }
593
594 pub fn skipped_as_ref(&self) -> &TestSkipped {
600 if let TestStatus::Skipped(e) = self {
601 return e;
602 }
603 panic!("called `TestStatus::skipped()` on a value that is not TestStatus::Skipped(_)");
604 }
605}
606
607#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
608#[derive(Debug, Clone, Default)]
609pub struct TestCase {
611 pub time: f64,
613 pub name: String,
618 pub status: TestStatus,
620 pub original_name: String,
622 pub classname: Option<String>,
624 pub group: Option<String>,
626 pub file: Option<String>,
628 pub line: Option<u64>,
630 pub system_out: Option<String>,
632 pub system_err: Option<String>,
634 pub properties: Properties,
636 pub reruns: Vec<RerunOrFlaky>,
638}
639impl TestCase {
640 fn parse_attributes(&mut self, e: &XMLBytesStart) -> Result<(), Error> {
642 for a in e.attributes() {
643 let a = a?;
644 match a.key {
645 QName(b"time") => self.time = try_from_attribute_value_f64(a.value)?,
646 QName(b"name") => self.original_name = try_from_attribute_value_string(a.value)?,
647 QName(b"classname") => {
648 self.classname = Some(try_from_attribute_value_string(a.value)?)
649 }
650 QName(b"group") => self.group = Some(try_from_attribute_value_string(a.value)?),
651 QName(b"file") => self.file = Some(try_from_attribute_value_string(a.value)?),
652 QName(b"line") => self.line = Some(try_from_attribute_value_u64(a.value)?),
653 _ => {}
654 };
655 }
656 if let Some(cn) = self.classname.as_ref() {
657 self.name = format!("{}::{}", cn, self.original_name);
658 } else if let Some(gn) = self.group.as_ref() {
659 self.name = format!("{}::{}", gn, self.original_name);
660 } else {
661 self.name.clone_from(&self.original_name);
662 }
663 Ok(())
664 }
665
666 fn new_empty(e: &XMLBytesStart) -> Result<Self, Error> {
668 let mut tc = Self::default();
669 tc.parse_attributes(e)?;
670 Ok(tc)
671 }
672
673 fn from_reader<B: BufRead>(e: &XMLBytesStart, r: &mut XMLReader<B>) -> Result<Self, Error> {
675 let mut tc = Self {
676 system_out: None,
677 system_err: None,
678 ..Default::default()
679 };
680 tc.parse_attributes(e)?;
681 loop {
682 let mut buf = Vec::new();
683 match r.read_event_into(&mut buf) {
684 Ok(XMLEvent::End(ref e)) if e.name() == QName(b"testcase") => break,
685
686 Ok(XMLEvent::Empty(ref empty_event)) => match empty_event.name() {
687 QName(b"system-out") => {
688 if tc.system_out.is_none() {
689 tc.system_out = Some(String::new());
690 }
691 }
692 QName(b"system-err") => {
693 if tc.system_err.is_none() {
694 tc.system_err = Some(String::new());
695 }
696 }
697 QName(b"flakyFailure") => {
698 tc.reruns.push(RerunOrFlaky::new_empty(
699 empty_event,
700 RerunOrFlakyKind::FlakyFailure,
701 )?);
702 }
703 QName(b"flakyError") => {
704 tc.reruns.push(RerunOrFlaky::new_empty(
705 empty_event,
706 RerunOrFlakyKind::FlakyError,
707 )?);
708 }
709 QName(b"rerunFailure") => {
710 tc.reruns.push(RerunOrFlaky::new_empty(
711 empty_event,
712 RerunOrFlakyKind::RerunFailure,
713 )?);
714 }
715 QName(b"rerunError") => {
716 tc.reruns.push(RerunOrFlaky::new_empty(
717 empty_event,
718 RerunOrFlakyKind::RerunError,
719 )?);
720 }
721 QName(b"skipped") => {
722 tc.status = TestStatus::Skipped(TestSkipped::new_empty(empty_event)?);
723 }
724 QName(b"failure") => {
725 tc.status = TestStatus::Failure(TestFailure::new_empty(empty_event)?);
726 }
727 QName(b"error") => {
728 tc.status = TestStatus::Error(TestError::new_empty(empty_event)?);
729 }
730 _ => {}
731 },
732
733 Ok(XMLEvent::Start(ref start_event)) => match start_event.name() {
734 QName(b"skipped") => {
735 tc.status = TestStatus::Skipped(TestSkipped::from_reader(start_event, r)?);
736 }
737 QName(b"failure") => {
738 tc.status = TestStatus::Failure(TestFailure::from_reader(start_event, r)?);
739 }
740 QName(b"error") => {
741 tc.status = TestStatus::Error(TestError::from_reader(start_event, r)?);
742 }
743 QName(b"flakyFailure") => {
744 tc.reruns.push(RerunOrFlaky::from_reader(
745 start_event,
746 r,
747 RerunOrFlakyKind::FlakyFailure,
748 )?);
749 }
750 QName(b"flakyError") => {
751 tc.reruns.push(RerunOrFlaky::from_reader(
752 start_event,
753 r,
754 RerunOrFlakyKind::FlakyError,
755 )?);
756 }
757 QName(b"rerunFailure") => {
758 tc.reruns.push(RerunOrFlaky::from_reader(
759 start_event,
760 r,
761 RerunOrFlakyKind::RerunFailure,
762 )?);
763 }
764 QName(b"rerunError") => {
765 tc.reruns.push(RerunOrFlaky::from_reader(
766 start_event,
767 r,
768 RerunOrFlakyKind::RerunError,
769 )?);
770 }
771 QName(b"system-out") => {
772 if let Some(parsed_content) = parse_system(start_event, r)? {
773 let current_out = tc.system_out.get_or_insert_with(String::new);
774 if !current_out.is_empty() {
775 current_out.push('\n');
776 }
777 current_out.push_str(&parsed_content);
778 }
779 }
780 QName(b"system-err") => {
781 if let Some(parsed_content) = parse_system(start_event, r)? {
782 let current_err = tc.system_err.get_or_insert_with(String::new);
783 if !current_err.is_empty() {
784 current_err.push('\n');
785 }
786 current_err.push_str(&parsed_content);
787 }
788 }
789 QName(b"properties") => {
790 tc.properties = Properties::from_reader(r)?;
791 }
792 _ => {
793 r.read_to_end_into(start_event.name(), &mut Vec::new())?;
794 }
795 },
796 Ok(XMLEvent::Eof) => {
797 return Err(Error::UnexpectedEndOfFile("testcase".to_string()))
798 }
799 Err(err) => return Err(err.into()),
800 _ => (),
801 }
802 buf.clear();
803 }
804 Ok(tc)
805 }
806}
807
808#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
809#[derive(Debug, Clone, Default)]
810pub struct TestSuite {
812 pub cases: Vec<TestCase>,
814 pub suites: Vec<TestSuite>,
816 pub time: f64,
818 pub tests: u64,
820 pub errors: u64,
822 pub failures: u64,
824 pub skipped: u64,
826 pub assertions: Option<u64>,
828 pub name: String,
830 pub timestamp: Option<String>,
832 pub hostname: Option<String>,
834 pub id: Option<String>,
836 pub package: Option<String>,
838 pub file: Option<String>,
840 pub log: Option<String>,
842 pub url: Option<String>,
844 pub version: Option<String>,
846 pub system_out: Option<String>,
848 pub system_err: Option<String>,
850 pub properties: Properties,
852}
853impl TestSuite {
854 fn parse_attributes(&mut self, e: &XMLBytesStart) -> Result<(), Error> {
856 for a in e.attributes() {
857 let a = a?;
858 match a.key {
859 QName(b"time") => self.time = try_from_attribute_value_f64(a.value)?,
860 QName(b"tests") => self.tests = try_from_attribute_value_u64(a.value)?,
861 QName(b"errors") => self.errors = try_from_attribute_value_u64(a.value)?,
862 QName(b"failures") => self.failures = try_from_attribute_value_u64(a.value)?,
863 QName(b"skipped") => self.skipped = try_from_attribute_value_u64(a.value)?,
864 QName(b"assertions") => {
865 self.assertions = Some(try_from_attribute_value_u64(a.value)?)
866 }
867 QName(b"name") => self.name = try_from_attribute_value_string(a.value)?,
868 QName(b"timestamp") => {
869 self.timestamp = Some(try_from_attribute_value_string(a.value)?)
870 }
871 QName(b"hostname") => {
872 self.hostname = Some(try_from_attribute_value_string(a.value)?)
873 }
874 QName(b"id") => self.id = Some(try_from_attribute_value_string(a.value)?),
875 QName(b"package") => self.package = Some(try_from_attribute_value_string(a.value)?),
876 QName(b"file") => self.file = Some(try_from_attribute_value_string(a.value)?),
877 QName(b"log") => self.log = Some(try_from_attribute_value_string(a.value)?),
878 QName(b"url") => self.url = Some(try_from_attribute_value_string(a.value)?),
879 QName(b"version") => self.version = Some(try_from_attribute_value_string(a.value)?),
880 _ => {}
881 };
882 }
883 Ok(())
884 }
885
886 fn new_empty(e: &XMLBytesStart) -> Result<Self, Error> {
888 let mut ts = Self::default();
889 ts.parse_attributes(e)?;
890 Ok(ts)
891 }
892
893 fn from_reader<B: BufRead>(e: &XMLBytesStart, r: &mut XMLReader<B>) -> Result<Self, Error> {
895 let mut ts = Self::default();
896 ts.parse_attributes(e)?;
897 loop {
898 let mut buf = Vec::new();
899 match r.read_event_into(&mut buf) {
900 Ok(XMLEvent::End(ref e)) if e.name() == QName(b"testsuite") => break,
901 Ok(XMLEvent::Start(ref e)) if e.name() == QName(b"testsuite") => {
902 ts.suites.push(TestSuite::from_reader(e, r)?);
903 }
904 Ok(XMLEvent::Start(ref e)) if e.name() == QName(b"testcase") => {
905 ts.cases.push(TestCase::from_reader(e, r)?);
906 }
907 Ok(XMLEvent::Empty(ref e)) if e.name() == QName(b"testcase") => {
908 ts.cases.push(TestCase::new_empty(e)?);
909 }
910 Ok(XMLEvent::Empty(ref e)) if e.name() == QName(b"system-out") => {}
911 Ok(XMLEvent::Start(ref e)) if e.name() == QName(b"system-out") => {
912 ts.system_out = parse_system(e, r)?;
913 }
914 Ok(XMLEvent::Empty(ref e)) if e.name() == QName(b"system-err") => {}
915 Ok(XMLEvent::Start(ref e)) if e.name() == QName(b"system-err") => {
916 ts.system_err = parse_system(e, r)?;
917 }
918 Ok(XMLEvent::Start(ref e)) if e.name() == QName(b"properties") => {
919 ts.properties = Properties::from_reader(r)?;
920 }
921 Ok(XMLEvent::Start(ref e)) => {
922 r.read_to_end_into(e.name(), &mut Vec::new())?;
923 }
924 Ok(XMLEvent::Eof) => {
925 return Err(Error::UnexpectedEndOfFile("testsuite".to_string()));
926 }
927 Err(err) => return Err(err.into()),
928 _ => (),
929 }
930 buf.clear();
931 }
932 Ok(ts)
933 }
934}
935
936#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
937#[derive(Debug, Clone, Default)]
938pub struct TestSuites {
940 pub suites: Vec<TestSuite>,
942 pub time: f64,
944 pub tests: u64,
946 pub errors: u64,
948 pub failures: u64,
950 pub skipped: u64,
952 pub name: String,
954}
955impl TestSuites {
956 fn parse_attributes(&mut self, e: &XMLBytesStart) -> Result<(), Error> {
958 for a in e.attributes() {
959 let a = a?;
960 match a.key {
961 QName(b"time") => self.time = try_from_attribute_value_f64(a.value)?,
962 QName(b"tests") => self.tests = try_from_attribute_value_u64(a.value)?,
963 QName(b"errors") => self.errors = try_from_attribute_value_u64(a.value)?,
964 QName(b"failures") => self.failures = try_from_attribute_value_u64(a.value)?,
965 QName(b"skipped") => self.skipped = try_from_attribute_value_u64(a.value)?,
966 QName(b"name") => self.name = try_from_attribute_value_string(a.value)?,
967 _ => {}
968 };
969 }
970 Ok(())
971 }
972
973 fn new_empty(e: &XMLBytesStart) -> Result<Self, Error> {
975 let mut ts = Self::default();
976 ts.parse_attributes(e)?;
977 Ok(ts)
978 }
979
980 fn from_reader<B: BufRead>(e: &XMLBytesStart, r: &mut XMLReader<B>) -> Result<Self, Error> {
982 let mut ts = Self::default();
983 ts.parse_attributes(e)?;
984 loop {
985 let mut buf = Vec::new();
986 match r.read_event_into(&mut buf) {
987 Ok(XMLEvent::End(ref e)) if e.name() == QName(b"testsuites") => break,
988 Ok(XMLEvent::End(ref e)) if e.name() == QName(b"testrun") => break,
989 Ok(XMLEvent::Start(ref e)) if e.name() == QName(b"testsuite") => {
990 ts.suites.push(TestSuite::from_reader(e, r)?);
991 }
992 Ok(XMLEvent::Empty(ref e)) if e.name() == QName(b"testsuite") => {
993 ts.suites.push(TestSuite::new_empty(e)?);
994 }
995 Ok(XMLEvent::Eof) => {
996 return Err(Error::UnexpectedEndOfFile("testsuites".to_string()));
997 }
998 Ok(XMLEvent::Start(ref e)) => {
999 r.read_to_end_into(e.name(), &mut Vec::new())?;
1000 }
1001 Err(err) => return Err(err.into()),
1002 _ => (),
1003 }
1004 buf.clear();
1005 }
1006 Ok(ts)
1007 }
1008}
1009
1010fn try_from_attribute_value_f64(value: Cow<[u8]>) -> Result<f64, Error> {
1012 match str::from_utf8(&value)? {
1013 "" => Ok(f64::default()),
1014 s => Ok(s.parse::<f64>()?),
1015 }
1016}
1017
1018fn try_from_attribute_value_u64(value: Cow<[u8]>) -> Result<u64, Error> {
1020 match str::from_utf8(&value)? {
1021 "" => Ok(u64::default()),
1022 s => Ok(s.parse::<u64>()?),
1023 }
1024}
1025
1026fn try_from_attribute_value_string(value: Cow<[u8]>) -> Result<String, Error> {
1028 let s = str::from_utf8(&value)?;
1029 let u = unescape(s)?;
1030 Ok(u.to_string())
1031}
1032
1033fn parse_system<B: BufRead>(
1035 orig: &XMLBytesStart,
1036 r: &mut XMLReader<B>,
1037) -> Result<Option<String>, Error> {
1038 let mut res: Option<String> = Some(String::new());
1039 loop {
1040 let mut buf = Vec::new();
1041 match r.read_event_into(&mut buf) {
1042 Ok(XMLEvent::End(ref e)) if e.name() == orig.name() => break,
1043 Ok(XMLEvent::Text(e)) => {
1044 res.get_or_insert(String::new()).push_str(&e.unescape()?);
1045 }
1046 Ok(XMLEvent::CData(e)) => {
1047 res.get_or_insert(String::new())
1048 .push_str(str::from_utf8(&e)?);
1049 }
1050 Ok(XMLEvent::Eof) => {
1051 return Err(Error::UnexpectedEndOfFile(format!("{:?}", orig.name())));
1052 }
1053 Ok(XMLEvent::Start(ref e)) => {
1054 r.read_to_end_into(e.name(), &mut Vec::new())?;
1055 }
1056 Err(err) => return Err(err.into()),
1057 _ => (),
1058 }
1059 buf.clear();
1060 }
1061 Ok(res)
1062}
1063
1064pub fn from_reader<B: BufRead>(reader: B) -> Result<TestSuites, Error> {
1083 let mut r = XMLReader::from_reader(reader);
1084 loop {
1085 let mut buf = Vec::new();
1086 match r.read_event_into(&mut buf) {
1087 Ok(XMLEvent::Empty(ref e)) if e.name() == QName(b"testsuites") => {
1088 return TestSuites::new_empty(e);
1089 }
1090 Ok(XMLEvent::Empty(ref e)) if e.name() == QName(b"testrun") => {
1091 return TestSuites::new_empty(e);
1092 }
1093 Ok(XMLEvent::Start(ref e)) if e.name() == QName(b"testsuites") => {
1094 return TestSuites::from_reader(e, &mut r);
1095 }
1096 Ok(XMLEvent::Start(ref e)) if e.name() == QName(b"testrun") => {
1097 return TestSuites::from_reader(e, &mut r);
1098 }
1099 Ok(XMLEvent::Empty(ref e)) if e.name() == QName(b"testsuite") => {
1100 let ts = TestSuite::new_empty(e)?;
1101 let mut suites = TestSuites::default();
1102 suites.suites.push(ts);
1103 return Ok(suites);
1104 }
1105 Ok(XMLEvent::Start(ref e)) if e.name() == QName(b"testsuite") => {
1106 let ts = TestSuite::from_reader(e, &mut r)?;
1107 let mut suites = TestSuites::default();
1108 suites.suites.push(ts);
1109 return Ok(suites);
1110 }
1111 Ok(XMLEvent::Start(ref e)) => {
1112 r.read_to_end_into(e.name(), &mut Vec::new())?;
1113 }
1114 Ok(XMLEvent::Eof) => {
1115 return Err(Error::UnexpectedEndOfFile("testsuites".to_string()));
1116 }
1117 Err(err) => return Err(err.into()),
1118 _ => (),
1119 }
1120 buf.clear();
1121 }
1122}