blob: c74ed81a36b85ecfba6da1ded7c9027638df87aa [file] [log] [blame]
//! Collects statistics and emits suite/test events as JSON messages, using
//! the same JSON format as libtest's JSON formatter.
//!
//! These messages are then parsed by bootstrap, which replaces them with
//! user-friendly terminal output.
use std::time::Instant;
use serde_json::json;
use crate::executor::{CollectedTest, TestCompletion, TestOutcome};
pub(crate) struct Listener {
suite_start: Option<Instant>,
passed: usize,
failed: usize,
ignored: usize,
filtered_out: usize,
}
impl Listener {
pub(crate) fn new() -> Self {
Self { suite_start: None, passed: 0, failed: 0, ignored: 0, filtered_out: 0 }
}
fn print_message(&self, message: &serde_json::Value) {
println!("{message}");
}
fn now(&self) -> Instant {
Instant::now()
}
pub(crate) fn suite_started(&mut self, test_count: usize, filtered_out: usize) {
self.suite_start = Some(self.now());
self.filtered_out = filtered_out;
let message = json!({ "type": "suite", "event": "started", "test_count": test_count });
self.print_message(&message);
}
pub(crate) fn test_started(&mut self, test: &CollectedTest) {
let name = test.desc.name.as_str();
let message = json!({ "type": "test", "event": "started", "name": name });
self.print_message(&message);
}
pub(crate) fn test_timed_out(&mut self, test: &CollectedTest) {
let name = test.desc.name.as_str();
let message = json!({ "type": "test", "event": "timeout", "name": name });
self.print_message(&message);
}
pub(crate) fn test_finished(&mut self, test: &CollectedTest, completion: &TestCompletion) {
let event;
let name = test.desc.name.as_str();
let mut maybe_message = None;
let maybe_stdout = completion.stdout.as_deref().map(String::from_utf8_lossy);
match completion.outcome {
TestOutcome::Succeeded => {
self.passed += 1;
event = "ok";
}
TestOutcome::Failed { message } => {
self.failed += 1;
maybe_message = message;
event = "failed";
}
TestOutcome::Ignored => {
self.ignored += 1;
maybe_message = test.desc.ignore_message.as_deref();
event = "ignored";
}
};
// This emits optional fields as `null`, instead of omitting them
// completely as libtest does, but bootstrap can parse the result
// either way.
let json = json!({
"type": "test",
"event": event,
"name": name,
"message": maybe_message,
"stdout": maybe_stdout,
});
self.print_message(&json);
}
pub(crate) fn suite_finished(&mut self) -> bool {
let exec_time = self.suite_start.map(|start| (self.now() - start).as_secs_f64());
let suite_passed = self.failed == 0;
let event = if suite_passed { "ok" } else { "failed" };
let message = json!({
"type": "suite",
"event": event,
"passed": self.passed,
"failed": self.failed,
"ignored": self.ignored,
// Compiletest doesn't run any benchmarks, but we still need to set this
// field to 0 so that bootstrap's JSON parser can read our message.
"measured": 0,
"filtered_out": self.filtered_out,
"exec_time": exec_time,
});
self.print_message(&message);
suite_passed
}
}