junitify 0.1.18

Takes cargo test JSON and transform to JUnit XML
//     junitify - Takes cargo test JSON and transform to JUnit XML
//
//         The MIT License (MIT)
//
//      Copyright (c) KoresFramework (https://gitlab.com/Kores/)
//      Copyright (c) contributors
//
//      Permission is hereby granted, free of charge, to any person obtaining a copy
//      of this software and associated documentation files (the "Software"), to deal
//      in the Software without restriction, including without limitation the rights
//      to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//      copies of the Software, and to permit persons to whom the Software is
//      furnished to do so, subject to the following conditions:
//
//      The above copyright notice and this permission notice shall be included in
//      all copies or substantial portions of the Software.
//
//      THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//      IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//      FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//      AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//      LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//      OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//      THE SOFTWARE.
use crate::junit_parser::{Failure, TestCase, TestSuite};
use crate::test_parser::{ParsedTestSuite, TestStatus};
use chrono::Utc;
use quick_xml::events::{BytesDecl, Event};
use quick_xml::se::Serializer;
use quick_xml::Writer;
use serde::Serialize;
use std::io::ErrorKind;

pub(crate) struct TestProducer {}

#[derive(Debug)]
pub(crate) struct RenderedSuite {
    pub(crate) name: String,
    pub(crate) rendered: String,
}

impl TestProducer {
    pub(crate) fn produce(
        tests: Vec<ParsedTestSuite>,
    ) -> Result<Vec<RenderedSuite>, anyhow::Error> {
        let host = hostname::get();
        let host_string = host.and_then(|s| {
            s.into_string()
                .map_err(|_| std::io::Error::from(ErrorKind::InvalidData))
        });
        let host_string = host_string.unwrap_or("".to_string());
        let host_string = host_string.as_str();

        let mut rendered: Vec<RenderedSuite> = vec![];
        for suite in tests {
            let parsed_suite = TestSuite {
                name: suite.suite_name.clone(),
                tests: suite.test_count,
                skipped: suite.ignored,
                failures: suite.failed,
                errors: suite.errors,
                timestamp: timestamp(),
                hostname: host_string.to_string(),
                time: suite.exec_time,
                test_cases: Some(
                    suite
                        .tests
                        .into_iter()
                        .map(|t| TestCase {
                            name: t.full_name,
                            classname: t.module.unwrap_or(t.name),
                            time: t.exec_time.unwrap_or(0.0f64),
                            failure: match t.status {
                                TestStatus::Ok => None,
                                TestStatus::Failed => Some(Failure {
                                    message: "assertion failed".to_string(),
                                    content: t.std_out,
                                }),
                                TestStatus::Ignored => None,
                            },
                        })
                        .collect(),
                ),
                std_out: None,
                std_err: None,
            };

            let mut contents = Vec::new();
            let mut writer = Writer::new_with_indent(&mut contents, ' ' as u8, 4);
            writer
                .write_event(Event::Decl(BytesDecl::new(b"1.0", Some(b"UTF-8"), None)))
                .unwrap();
            let mut serializer = Serializer::with_root(writer, None);
            parsed_suite.serialize(&mut serializer).unwrap();
            let render = String::from_utf8(contents).unwrap();

            rendered.push(RenderedSuite {
                name: suite.suite_name,
                rendered: render,
            })
        }

        return Ok(rendered);
    }
}

fn timestamp() -> String {
    let now = Utc::now();
    now.format("%Y-%m-%dT%H:%M:%S").to_string()
}