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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,9 @@
- Updated `derive_builder` to the latest version.
- Made use of `matches!` macros where possible.
- Fixed some tests

## [0.15.1] - 2022-10-13

### Added

- Added `TestMessage`, `TestEvent`, `SuiteEvent` for parsing the `cargo test -- --format json` output.
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "cargo_metadata"
version = "0.18.0"
version = "0.18.1"
authors = ["Oliver Schneider <[email protected]>"]
repository = "https://github.com/oli-obk/cargo_metadata"
description = "structured access to the output of `cargo metadata`"
Expand All @@ -21,6 +21,7 @@ thiserror = "1.0.31"
[features]
default = []
builder = ["derive_builder"]
unstable = []

[package.metadata.cargo_metadata_test]
some_field = true
Expand Down
4 changes: 4 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@ pub use dependency::DependencyBuilder;
pub use dependency::{Dependency, DependencyKind};
use diagnostic::Diagnostic;
pub use errors::{Error, Result};
#[cfg(feature = "unstable")]
pub use libtest::TestMessage;
#[allow(deprecated)]
pub use messages::parse_messages;
pub use messages::{
Expand All @@ -115,6 +117,8 @@ use serde::{Deserialize, Deserializer, Serialize};
mod dependency;
pub mod diagnostic;
mod errors;
#[cfg(feature = "unstable")]
pub mod libtest;
bend-n marked this conversation as resolved.
Show resolved Hide resolved
mod messages;

/// An "opaque" identifier for a package.
Expand Down
165 changes: 165 additions & 0 deletions src/libtest.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
//! Parses output of [libtest](https://github.com/rust-lang/rust/blob/master/library/test/src/formatters/json.rs).
//!
//! Since this module parses output in an unstable format, all structs in this module may change at any time, and are exempt from semver guarantees.
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,
}
}
}

#[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 })
];
}
Loading