Skip to content

Commit

Permalink
Merge pull request #252 from bend-n/libtest
Browse files Browse the repository at this point in the history
support libtest json output
  • Loading branch information
oli-obk committed Oct 13, 2023
2 parents 8fa38ab + 87adb46 commit 5b794f8
Show file tree
Hide file tree
Showing 4 changed files with 177 additions and 1 deletion.
6 changes: 6 additions & 0 deletions CHANGELOG.md
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
@@ -1,6 +1,6 @@
[package]
name = "cargo_metadata"
version = "0.18.0"
version = "0.18.1"
authors = ["Oliver Schneider <git-spam-no-reply9815368754983@oli-obk.de>"]
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
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;
mod messages;

/// An "opaque" identifier for a package.
Expand Down
165 changes: 165 additions & 0 deletions src/libtest.rs
@@ -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 })
];
}

0 comments on commit 5b794f8

Please sign in to comment.