Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

support libtest json output #252

Merged
merged 15 commits into from Oct 13, 2023
2 changes: 2 additions & 0 deletions src/lib.rs
Expand Up @@ -99,6 +99,7 @@ pub use dependency::DependencyBuilder;
pub use dependency::{Dependency, DependencyKind};
use diagnostic::Diagnostic;
pub use errors::{Error, Result};
pub use libtest::TestMessage;
#[allow(deprecated)]
pub use messages::parse_messages;
pub use messages::{
Expand All @@ -115,6 +116,7 @@ use serde::{Deserialize, Deserializer, Serialize};
mod dependency;
pub mod diagnostic;
mod errors;
pub mod libtest;
bend-n marked this conversation as resolved.
Show resolved Hide resolved
mod messages;

/// An "opaque" identifier for a package.
Expand Down
173 changes: 173 additions & 0 deletions src/libtest.rs
@@ -0,0 +1,173 @@
//! parses output of <https://github.com/rust-lang/rust/blob/master/library/test/src/formatters/json.rs>.
use serde::{Deserialize, Serialize};

/// Suite related event
#[derive(Debug, PartialEq, Deserialize, Serialize)]
#[serde(tag = "event")]
#[serde(rename_all = "lowercase")]
/// Suite event
pub enum SuiteEvent {
/// emitted on the start of a test run, and the start of the doctests
Started {
/// number of tests in this suite
test_count: usize,
},
/// the suite has finished
Ok {
/// the number of tests that passed
passed: usize,
/// the number of tests that failed
failed: usize,
/// number of tests that were ignored
ignored: usize,
/// number of benchmarks run
measured: usize,
/// i think this is based on what you specify in the cargo test argument
filtered_out: usize,
/// how long the suite took to run
exec_time: f32,
},
/// the suite has at least one failing test
Failed {
/// the number of tests that passed
passed: usize,
/// the number of tests that failed
failed: usize,
/// number of tests that were ignored
ignored: usize,
/// i think its something to do with benchmarks?
measured: usize,
/// i think this is based on what you specify in the cargo test argument
filtered_out: usize,
/// how long the suite took to run
exec_time: f32,
},
}

#[derive(Debug, PartialEq, Deserialize, Serialize)]
#[serde(tag = "event")]
#[serde(rename_all = "lowercase")]
/// Test event
pub enum TestEvent {
/// a new test starts
Started {
/// the name of this test
name: String,
},
/// the test has finished
Ok {
/// which one
name: String,
/// in how long
exec_time: f32,
/// what did it say?
stdout: Option<String>,
},
/// the test has failed
Failed {
/// which one
name: String,
/// in how long
exec_time: f32,
/// why?
stdout: Option<String>,
/// it timed out?
reason: Option<String>,
/// what message
message: Option<String>,
},
/// the test has been ignored
Ignored {
/// which one
name: String,
},
/// the test has timed out
Timeout {
/// which one
name: String,
},
}

impl TestEvent {
/// Get the name of this test
pub fn name(&self) -> &str {
let (Self::Started { name }
| Self::Ok { name, .. }
| Self::Ignored { name }
| Self::Failed { name, .. }
| Self::Timeout { name }) = self;
name
}

/// Get the stdout of this test, if available.
pub fn stdout(&self) -> Option<&str> {
match self {
Self::Ok { stdout, .. } | Self::Failed { stdout, .. } => stdout.as_deref(),
_ => None,
}
}

/// If this test is in progress.
pub fn in_progress(&self) -> bool {
matches!(self, Self::Started { .. })
}
bend-n marked this conversation as resolved.
Show resolved Hide resolved

/// If this test has finished
pub fn is_finished(&self) -> bool {
!self.in_progress()
}
}

#[derive(Debug, PartialEq, Deserialize, Serialize)]
/// Represents the output of `cargo test -- -Zunstable-options --report-time --show-output --format json`.
///
/// requires --report-time
///
/// # Stability
///
/// As this struct is for interfacing with the unstable libtest json output, this struct may change at any time, without semver guarantees.
#[serde(tag = "type")]
#[serde(rename_all = "lowercase")]
pub enum TestMessage {
/// suite related message
Suite(SuiteEvent),
/// test related message
Test(TestEvent),
/// bench related message
Bench {
/// name of benchmark
name: String,
/// distribution
median: f32,
/// deviation
deviation: f32,
/// thruput in MiB per second
mib_per_second: Option<f32>,
},
}

#[test]
fn deser() {
macro_rules! run {
($($input:literal parses to $output:expr),+) => {
$(assert_eq!(dbg!(serde_json::from_str::<TestMessage>($input)).unwrap(), $output);)+
};
}
run![
r#"{ "type": "suite", "event": "started", "test_count": 2 }"# parses to TestMessage::Suite(SuiteEvent::Started { test_count: 2 }),
r#"{ "type": "test", "event": "started", "name": "fail" }"# parses to TestMessage::Test(TestEvent::Started { name: "fail".into() }),
r#"{ "type": "test", "name": "fail", "event": "ok", "exec_time": 0.000003428, "stdout": "hello world" }"# parses to TestMessage::Test(TestEvent::Ok { name: "fail".into(), exec_time: 0.000003428, stdout: Some("hello world".into()) }),
r#"{ "type": "test", "event": "started", "name": "nope" }"# parses to TestMessage::Test(TestEvent::Started { name: "nope".into() }),
r#"{ "type": "test", "name": "nope", "event": "ignored" }"# parses to TestMessage::Test(TestEvent::Ignored { name: "nope".into() }),
r#"{ "type": "suite", "event": "ok", "passed": 1, "failed": 0, "ignored": 1, "measured": 0, "filtered_out": 0, "exec_time": 0.000684028 }"# parses to TestMessage::Suite(SuiteEvent::Ok { passed: 1, failed: 0, ignored: 1, measured: 0, filtered_out: 0, exec_time: 0.000684028 })
];

run![
r#"{ "type": "suite", "event": "started", "test_count": 2 }"# parses to TestMessage::Suite(SuiteEvent::Started { test_count: 2 }),
r#"{ "type": "test", "event": "started", "name": "fail" }"# parses to TestMessage::Test(TestEvent::Started { name: "fail".into() }),
r#"{ "type": "test", "event": "started", "name": "benc" }"# parses to TestMessage::Test(TestEvent::Started { name: "benc".into() }),
r#"{ "type": "bench", "name": "benc", "median": 0, "deviation": 0 }"# parses to TestMessage::Bench { name: "benc".into(), median: 0., deviation: 0., mib_per_second: None },
r#"{ "type": "test", "name": "fail", "event": "failed", "exec_time": 0.000081092, "stdout": "thread 'fail' panicked" }"# parses to TestMessage::Test(TestEvent::Failed { name: "fail".into(), exec_time: 0.000081092, stdout: Some("thread 'fail' panicked".into()), reason: None, message: None} ),
r#"{ "type": "suite", "event": "failed", "passed": 0, "failed": 1, "ignored": 0, "measured": 1, "filtered_out": 0, "exec_time": 0.000731068 }"# parses to TestMessage::Suite(SuiteEvent::Failed { passed: 0, failed: 1, ignored: 0, measured: 1, filtered_out: 0, exec_time: 0.000731068 })
];
}