use super::RevisionStyle;
use super::StatusEmitter;
use super::Summary;
use super::TestStatus;
use crate::test_result::TestResult;
use crate::TestOk;
use std::boxed::Box;
use std::fmt::Debug;
use std::path::{Path, PathBuf};
use bstr::ByteSlice;
fn emit_suite_end(failed: usize, filtered_out: usize, ignored: usize, passed: usize, status: &str) {
println!(
r#"{{ "type": "suite", "event": "{status}", "passed": {passed}, "failed": {failed}, "ignored": {ignored}, "measured": 0, "filtered_out": {filtered_out} }}"#
);
}
fn emit_suite_start() {
println!(r#"{{ "type": "suite", "event": "started" }}"#);
}
fn emit_test_end(name: &String, revision: &String, path: &Path, status: &str, diags: &str) {
let displayed_path = path.display();
let stdout = if diags.is_empty() {
String::new()
} else {
let triaged_diags = serde_json::to_string(diags).unwrap();
format!(r#", "stdout": {triaged_diags}"#)
};
println!(
r#"{{ "type": "test", "event": "{status}", "name": "{name} ({revision}) - {displayed_path}"{stdout} }}"#
);
}
fn emit_test_start(name: &String, revision: &String, path: &Path) {
let displayed_path = path.display();
println!(
r#"{{ "type": "test", "event": "started", "name": "{name} ({revision}) - {displayed_path}" }}"#
);
}
#[derive(Clone)]
pub struct JSON {}
impl JSON {
pub fn new() -> Self {
emit_suite_start();
JSON {}
}
}
impl Default for JSON {
fn default() -> Self {
Self::new()
}
}
impl StatusEmitter for JSON {
fn finalize(
&self,
failed: usize,
succeeded: usize,
ignored: usize,
filtered: usize,
aborted: bool,
) -> Box<dyn Summary> {
let status = if aborted || failed > 0 {
"failed"
} else {
"ok"
};
emit_suite_end(failed, filtered, ignored, succeeded, status);
Box::new(())
}
fn register_test(&self, path: PathBuf) -> Box<dyn TestStatus + 'static> {
let name = path.to_str().unwrap().to_string();
let revision = String::new();
emit_test_start(&name, &revision, &path);
Box::new(JSONStatus {
name,
path,
revision: String::new(),
style: RevisionStyle::Show,
})
}
}
pub struct JSONStatus {
name: String,
path: PathBuf,
revision: String,
style: RevisionStyle,
}
impl TestStatus for JSONStatus {
fn done(&self, result: &TestResult, aborted: bool) {
let status = if aborted {
"timeout"
} else {
match result {
Ok(TestOk::Ignored) => "ignored",
Ok(TestOk::Ok) => "ok",
Err(_) => "failed",
}
};
let diags = if let Err(errored) = result {
let command = errored.command.as_str();
let stdout = errored.stderr.to_str_lossy();
let stderr = errored.stdout.to_str_lossy();
format!(r#"command: <{command}> stdout: <{stdout}> stderr: <{stderr}>"#)
} else {
String::new()
};
emit_test_end(&self.name, &self.revision, self.path(), status, &diags);
}
fn failed_test<'a>(
&'a self,
_cmd: &'a str,
_stderr: &'a [u8],
_stdout: &'a [u8],
) -> Box<dyn Debug + 'a> {
Box::new(())
}
fn for_path(&self, path: &Path) -> Box<dyn TestStatus> {
let status = JSONStatus {
name: self.name.clone(),
path: path.to_path_buf(),
revision: self.revision.clone(),
style: self.style,
};
Box::new(status)
}
fn for_revision(&self, revision: &str, style: RevisionStyle) -> Box<dyn TestStatus> {
let status = JSONStatus {
name: self.name.clone(),
path: self.path.clone(),
revision: revision.to_owned(),
style,
};
Box::new(status)
}
fn path(&self) -> &Path {
&self.path
}
fn revision(&self) -> &str {
&self.revision
}
}