use quick_xml::Writer;
use quick_xml::events::{BytesDecl, BytesEnd, BytesStart, BytesText, Event};
use std::fs::File;
use std::path::Path;
use crate::submitter::StepResult;
use anyhow::Error;
use std::fs::create_dir_all;
pub fn create_junit(
results: &[StepResult],
file_path: &Path,
hostname: Option<&str>,
) -> Result<(), Error> {
if let Some(parent) = file_path.parent() {
create_dir_all(parent)?;
}
let file = File::create(file_path)?;
let mut writer = Writer::new_with_indent(file, b' ', 4);
writer.write_event(Event::Decl(BytesDecl::new("1.0", Some("UTF-8"), None)))?;
let test_num = results.len();
let skip_num = results
.iter()
.filter(|step| {
if let Some(ref output) = step.error {
return output == "Dependency Not Met";
}
false
})
.count();
let failure_num = results.iter().filter(|step| !step.pass).count() - skip_num;
let time = results
.iter()
.fold(0f32, |sum, step| sum + (step.duration / 1000f32));
let hostname = match hostname {
Some(hostname) => String::from(hostname),
None => hostname::get()
.map(|name| name.to_string_lossy().to_string())
.unwrap_or_else(|_| String::from("")),
};
let mut testsuite = BytesStart::new("testsuite");
testsuite.push_attribute(("name", "lorikeet"));
testsuite.push_attribute(("hostname", &*hostname));
testsuite.push_attribute(("tests", &*test_num.to_string()));
testsuite.push_attribute(("failures", &*failure_num.to_string()));
testsuite.push_attribute(("skipped", &*skip_num.to_string()));
testsuite.push_attribute(("time", &*time.to_string()));
writer.write_event(Event::Start(testsuite))?;
for result in results.iter() {
let mut testcase = BytesStart::new("testcase");
testcase.push_attribute(("name", &*result.name));
if let Some(ref desc) = result.description {
testcase.push_attribute(("classname", desc as &str));
} else {
testcase.push_attribute(("classname", ""));
}
testcase.push_attribute(("time", &*(result.duration / 1000f32).to_string()));
writer.write_event(Event::Start(testcase))?;
writer.write_event(Event::Start(BytesStart::from_content(
"system-out",
"system-out".len(),
)))?;
writer.write_event(Event::Text(BytesText::new(&filter_invalid_chars(
&result.output,
))))?;
writer.write_event(Event::End(BytesEnd::new("system-out")))?;
if !result.pass {
let error_text = result.error.as_deref().unwrap_or("");
if error_text == "Dependency Not Met" {
let mut skipped = BytesStart::new("skipped");
skipped.push_attribute(("message", "Dependency Not Met"));
writer.write_event(Event::Start(skipped))?;
writer.write_event(Event::End(BytesEnd::new("skipped")))?;
} else {
let mut failure = BytesStart::new("failure");
failure.push_attribute(("message", "Step failed to finish"));
writer.write_event(Event::Start(failure))?;
writer.write_event(Event::Text(BytesText::new(&filter_invalid_chars(
error_text,
))))?;
writer.write_event(Event::End(BytesEnd::new("failure")))?;
}
}
writer.write_event(Event::End(BytesEnd::new("testcase")))?;
}
writer.write_event(Event::End(BytesEnd::new("testsuite")))?;
Ok(())
}
fn filter_invalid_chars(input: &str) -> String {
let mut output = String::new();
for ch in input.chars() {
if ('\u{0020}'..='\u{D7FF}').contains(&ch)
|| ('\u{E000}'..='\u{FFFD}').contains(&ch)
|| ch == '\u{0009}'
|| ch == '\u{0A}'
|| ch == '\u{0D}'
{
output.push(ch);
}
}
output
}