From deba52c67973ac369eec3871139de1d3f335bc2b Mon Sep 17 00:00:00 2001 From: bendn Date: Mon, 9 Oct 2023 07:41:14 +0700 Subject: [PATCH 01/15] implement TestEvent --- src/lib.rs | 2 + src/libtest/event.rs | 256 ++++++++++++++++++++++++++++++++++++++++++ src/libtest/mod.rs | 4 + src/libtest/status.rs | 13 +++ src/libtest/type.rs | 15 +++ 5 files changed, 290 insertions(+) create mode 100644 src/libtest/event.rs create mode 100644 src/libtest/mod.rs create mode 100644 src/libtest/status.rs create mode 100644 src/libtest/type.rs diff --git a/src/lib.rs b/src/lib.rs index d7c3413c..2b427c68 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -99,6 +99,7 @@ pub use dependency::DependencyBuilder; pub use dependency::{Dependency, DependencyKind}; use diagnostic::Diagnostic; pub use errors::{Error, Result}; +pub use libtest::event::TestEvent; #[allow(deprecated)] pub use messages::parse_messages; pub use messages::{ @@ -115,6 +116,7 @@ use serde::{Deserialize, Deserializer, Serialize}; mod dependency; pub mod diagnostic; mod errors; +mod libtest; mod messages; /// An "opaque" identifier for a package. diff --git a/src/libtest/event.rs b/src/libtest/event.rs new file mode 100644 index 00000000..d93c22a1 --- /dev/null +++ b/src/libtest/event.rs @@ -0,0 +1,256 @@ +use serde::{ + de::{Deserializer, Error, MapAccess, Unexpected, Visitor}, + Deserialize, +}; + +use super::{r#type::Type, status::Status}; + +#[derive(Debug, PartialEq)] +/// Represents the output of `cargo test -- -Zunstable-options --report-time --show-output`. +/// +/// requires --report-time and --show-output +pub enum TestEvent { + /// emitted on the start of a test run, and the start of the doctests + SuiteStart { + /// number of tests in this suite + test_count: usize, + }, + /// the suite has finished + SuiteOk { + /// 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, + }, + /// the suite has at least one failing test + SuiteFail { + /// 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, + }, + /// a new test starts + TestStart { + /// the name of this test + name: String, + }, + /// the test has finished + TestOk { + /// which one + name: String, + /// in how long + exec_time: f32, + /// what did it say? + stdout: Option, + }, + /// the test has failed + TestFail { + /// which one + name: String, + /// in how long + exec_time: f32, + /// why? + stdout: Option, + }, + /// the test has timed out + TestTimeout { + /// which one + name: String, + /// how long did it run + exec_time: f32, + /// did it say anything + stdout: Option, + }, + /// the test has failed, with a message, i am not sure how this works. + TestFailMessage { + /// which one + name: String, + /// in how long + exec_time: f32, + /// stdout? + stdout: Option, + /// what message + message: String, + }, + /// the test has been ignored + TestIgnore { + /// which one + name: String, + }, +} + +impl<'de> Deserialize<'de> for TestEvent { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + return deserializer.deserialize_map(Visit); + struct Visit; + // look ma, no intermediary data structures + impl<'de> Visitor<'de> for Visit { + type Value = TestEvent; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("a test result") + } + + fn visit_map(self, mut map: A) -> Result + where + A: MapAccess<'de>, + { + macro_rules! take { + ($name:ident as $what:ty) => {{ + let Some((key, value)) = map.next_entry::<&str, $what>()? else { + return Err(Error::missing_field(stringify!($name))); + }; + if key != stringify!($name) { + return Err(Error::missing_field(stringify!($name))); + } + value + }}; + ($str:ident { $($name:ident: $what:ty),+ $(,)? }) => {{ + TestEvent::$str { + $($name: take!($name as $what),)+ + } + }} + } + let kind = take!(type as Type); + let mut statuz = None; + let mut name = None; + match map + .next_key::<&str>()? + .ok_or(Error::missing_field("event | name"))? + { + "event" => statuz = Some(map.next_value::()?), + "name" => name = Some(map.next_value::()?), + f => return Err(Error::unknown_field(f, &["event", "name"])), + } + + let status = if let Some(status) = statuz { + status + } else { + take!(event as Status) + }; + Ok(match (kind, status) { + (Type::Suite, Status::Started) => TestEvent::SuiteStart { + test_count: take!(test_count as usize), + }, + (Type::Suite, Status::Ok) => take!(SuiteOk { + passed: usize, + failed: usize, + ignored: usize, + measured: usize, + filtered_out: usize, + exec_time: f32 + }), + (Type::Suite, Status::Failed) => take!(SuiteFail { + passed: usize, + failed: usize, + ignored: usize, + measured: usize, + filtered_out: usize, + exec_time: f32, + }), + (Type::Suite, Status::Ignored) => { + return Err(Error::custom("test suite's cannot be ignored")); + } + (Type::Test, Status::Started) => take!(TestStart { name: String }), + (Type::Test, Status::Ok) => TestEvent::TestOk { + name: name.ok_or(Error::missing_field("name"))?, + exec_time: take!(exec_time as f32), + stdout: match map.next_key::<&str>()? { + Some("stdout") => Some(map.next_value::()?), + Some(k) => return Err(Error::unknown_field(k, &["stdout"])), + None => None, + }, + }, + (Type::Test, Status::Ignored) => TestEvent::TestIgnore { + name: name.ok_or(Error::missing_field("name"))?, + }, + (Type::Test, Status::Failed) => { + let exec_time = take!(exec_time as f32); + let name = name.ok_or(Error::missing_field("name"))?; + let stdout = match map.next_key::<&str>()? { + Some("stdout") => Some(map.next_value::()?), + Some(k) => return Err(Error::unknown_field(k, &["stdout"])), + None => None, + }; + match map.next_key::<&str>()? { + Some("reason") => { + let reason = map.next_value::<&str>()?; + if reason != "time limit exceeded" { + return Err(Error::invalid_value( + Unexpected::Str(reason), + &"time limit exceeded", + )); + } + TestEvent::TestTimeout { + name, + exec_time, + stdout, + } + } + Some("message") => { + let message = map.next_value::()?; + TestEvent::TestFailMessage { + name, + exec_time, + stdout, + message, + } + } + _ => TestEvent::TestFail { + name, + exec_time, + stdout, + }, + } + } + (Type::Bench, _) => { + todo!() + } + }) + } + } + } +} + +#[test] +fn deser() { + macro_rules! run { + ($($input:literal parses to $output:expr),+) => { + $(assert_eq!(dbg!(serde_json::from_str::($input)).unwrap(), $output);)+ + }; + } + run![ + r#"{ "type": "suite", "event": "started", "test_count": 2 }"# parses to TestEvent::SuiteStart { test_count: 2 }, + r#"{ "type": "test", "event": "started", "name": "fail" }"# parses to TestEvent::TestStart { name: "fail".into() }, + r#"{ "type": "test", "name": "fail", "event": "ok", "exec_time": 0.000003428, "stdout": "hello world" }"# parses to TestEvent::TestOk { name: "fail".into(), exec_time: 0.000003428, stdout: Some("hello world".into()) } , + r#"{ "type": "test", "event": "started", "name": "nope" }"# parses to TestEvent::TestStart { name: "nope".into() }, + r#"{ "type": "test", "name": "nope", "event": "ignored" }"# parses to TestEvent::TestIgnore { 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 TestEvent::SuiteOk { passed: 1, failed: 0, ignored: 1, measured: 0, filtered_out: 0, exec_time: 0.000684028 } + ]; + + run![ + r#"{ "type": "suite", "event": "started", "test_count": 1 }"# parses to TestEvent::SuiteStart { test_count: 1 }, + r#"{ "type": "test", "event": "started", "name": "fail" }"# parses to TestEvent::TestStart { name: "fail".into() }, + r#"{ "type": "test", "name": "fail", "event": "failed", "exec_time": 0.000081092, "stdout": "thread 'fail' panicked" }"# parses to TestEvent::TestFail { name: "fail".into(), exec_time: 0.000081092, stdout: Some("thread 'fail' panicked".into()) }, + r#"{ "type": "suite", "event": "failed", "passed": 0, "failed": 1, "ignored": 0, "measured": 0, "filtered_out": 0, "exec_time": 0.000731068 }"# parses to TestEvent::SuiteFail { passed: 0, failed: 1, ignored: 0, measured: 0, filtered_out: 0, exec_time: 0.000731068 } + ]; +} diff --git a/src/libtest/mod.rs b/src/libtest/mod.rs new file mode 100644 index 00000000..7f0d8aa9 --- /dev/null +++ b/src/libtest/mod.rs @@ -0,0 +1,4 @@ +//! parses output of . +pub mod event; +pub mod status; +pub mod r#type; diff --git a/src/libtest/status.rs b/src/libtest/status.rs new file mode 100644 index 00000000..f2ee0a0a --- /dev/null +++ b/src/libtest/status.rs @@ -0,0 +1,13 @@ +use serde::Deserialize; +#[derive(Deserialize, Debug)] +#[serde(rename_all = "lowercase")] +pub enum Status { + /// Test is ok (corresponds to `TestResult::TrOk` in libtest) + Ok, + /// Test started + Started, + /// Test failed (corresponds to {`TestResult::TrFailed`, `TestResult::TrTimedFail`, `TestResult::TrFailedMsg` in libtest}) + Failed, + /// Test ignored (corresponds to `TestResult::TrIgnored` in libtest) + Ignored, +} diff --git a/src/libtest/type.rs b/src/libtest/type.rs new file mode 100644 index 00000000..6ed2b062 --- /dev/null +++ b/src/libtest/type.rs @@ -0,0 +1,15 @@ +use serde::Deserialize; + +#[derive(Deserialize, Debug)] +#[serde(rename_all = "lowercase")] +pub enum Type { + Test, + /// Occurs usually 4 times in a `cargo test` lifetime: + /// - once at the start, how many normal tests + /// - once at the end of the normal tests, how did it go + /// - once at the start of the doc tests + /// - once at the end of the doc tests + Suite, + /// `#[bench]` type tests. + Bench, +} From fa283d4bf471fe3a913d7871e787914d417b7a3e Mon Sep 17 00:00:00 2001 From: bendn Date: Mon, 9 Oct 2023 07:49:18 +0700 Subject: [PATCH 02/15] add test_name helper method --- src/libtest/event.rs | 33 ++++++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/src/libtest/event.rs b/src/libtest/event.rs index d93c22a1..43d8ccfc 100644 --- a/src/libtest/event.rs +++ b/src/libtest/event.rs @@ -68,15 +68,6 @@ pub enum TestEvent { /// why? stdout: Option, }, - /// the test has timed out - TestTimeout { - /// which one - name: String, - /// how long did it run - exec_time: f32, - /// did it say anything - stdout: Option, - }, /// the test has failed, with a message, i am not sure how this works. TestFailMessage { /// which one @@ -93,6 +84,30 @@ pub enum TestEvent { /// which one name: String, }, + /// the test has timed out + TestTimeout { + /// which one + name: String, + /// how long did it run + exec_time: f32, + /// did it say anything + stdout: Option, + }, +} + +impl TestEvent { + /// Get the name of this test, if this is a test. + pub fn test_name(&self) -> Option<&str> { + match self { + Self::TestStart { name } + | Self::TestOk { name, .. } + | Self::TestFail { name, .. } + | Self::TestFailMessage { name, .. } + | Self::TestIgnore { name } + | Self::TestTimeout { name, .. } => Some(&name), + _ => None, + } + } } impl<'de> Deserialize<'de> for TestEvent { From 9a8e59c35b42f2ef9df575c61e8d4030705dab12 Mon Sep 17 00:00:00 2001 From: bendn Date: Mon, 9 Oct 2023 08:15:53 +0700 Subject: [PATCH 03/15] use #[serde(tag)] why did nobody tell me about #[serde(tag)] --- src/lib.rs | 2 +- src/libtest/event.rs | 271 ------------------------------------------ src/libtest/mod.rs | 134 ++++++++++++++++++++- src/libtest/status.rs | 13 -- src/libtest/type.rs | 15 --- 5 files changed, 132 insertions(+), 303 deletions(-) delete mode 100644 src/libtest/event.rs delete mode 100644 src/libtest/status.rs delete mode 100644 src/libtest/type.rs diff --git a/src/lib.rs b/src/lib.rs index 2b427c68..924a07dc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -99,7 +99,7 @@ pub use dependency::DependencyBuilder; pub use dependency::{Dependency, DependencyKind}; use diagnostic::Diagnostic; pub use errors::{Error, Result}; -pub use libtest::event::TestEvent; +pub use libtest::{SuiteEvent, TestEvent, TestMessage}; #[allow(deprecated)] pub use messages::parse_messages; pub use messages::{ diff --git a/src/libtest/event.rs b/src/libtest/event.rs deleted file mode 100644 index 43d8ccfc..00000000 --- a/src/libtest/event.rs +++ /dev/null @@ -1,271 +0,0 @@ -use serde::{ - de::{Deserializer, Error, MapAccess, Unexpected, Visitor}, - Deserialize, -}; - -use super::{r#type::Type, status::Status}; - -#[derive(Debug, PartialEq)] -/// Represents the output of `cargo test -- -Zunstable-options --report-time --show-output`. -/// -/// requires --report-time and --show-output -pub enum TestEvent { - /// emitted on the start of a test run, and the start of the doctests - SuiteStart { - /// number of tests in this suite - test_count: usize, - }, - /// the suite has finished - SuiteOk { - /// 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, - }, - /// the suite has at least one failing test - SuiteFail { - /// 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, - }, - /// a new test starts - TestStart { - /// the name of this test - name: String, - }, - /// the test has finished - TestOk { - /// which one - name: String, - /// in how long - exec_time: f32, - /// what did it say? - stdout: Option, - }, - /// the test has failed - TestFail { - /// which one - name: String, - /// in how long - exec_time: f32, - /// why? - stdout: Option, - }, - /// the test has failed, with a message, i am not sure how this works. - TestFailMessage { - /// which one - name: String, - /// in how long - exec_time: f32, - /// stdout? - stdout: Option, - /// what message - message: String, - }, - /// the test has been ignored - TestIgnore { - /// which one - name: String, - }, - /// the test has timed out - TestTimeout { - /// which one - name: String, - /// how long did it run - exec_time: f32, - /// did it say anything - stdout: Option, - }, -} - -impl TestEvent { - /// Get the name of this test, if this is a test. - pub fn test_name(&self) -> Option<&str> { - match self { - Self::TestStart { name } - | Self::TestOk { name, .. } - | Self::TestFail { name, .. } - | Self::TestFailMessage { name, .. } - | Self::TestIgnore { name } - | Self::TestTimeout { name, .. } => Some(&name), - _ => None, - } - } -} - -impl<'de> Deserialize<'de> for TestEvent { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - return deserializer.deserialize_map(Visit); - struct Visit; - // look ma, no intermediary data structures - impl<'de> Visitor<'de> for Visit { - type Value = TestEvent; - - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("a test result") - } - - fn visit_map(self, mut map: A) -> Result - where - A: MapAccess<'de>, - { - macro_rules! take { - ($name:ident as $what:ty) => {{ - let Some((key, value)) = map.next_entry::<&str, $what>()? else { - return Err(Error::missing_field(stringify!($name))); - }; - if key != stringify!($name) { - return Err(Error::missing_field(stringify!($name))); - } - value - }}; - ($str:ident { $($name:ident: $what:ty),+ $(,)? }) => {{ - TestEvent::$str { - $($name: take!($name as $what),)+ - } - }} - } - let kind = take!(type as Type); - let mut statuz = None; - let mut name = None; - match map - .next_key::<&str>()? - .ok_or(Error::missing_field("event | name"))? - { - "event" => statuz = Some(map.next_value::()?), - "name" => name = Some(map.next_value::()?), - f => return Err(Error::unknown_field(f, &["event", "name"])), - } - - let status = if let Some(status) = statuz { - status - } else { - take!(event as Status) - }; - Ok(match (kind, status) { - (Type::Suite, Status::Started) => TestEvent::SuiteStart { - test_count: take!(test_count as usize), - }, - (Type::Suite, Status::Ok) => take!(SuiteOk { - passed: usize, - failed: usize, - ignored: usize, - measured: usize, - filtered_out: usize, - exec_time: f32 - }), - (Type::Suite, Status::Failed) => take!(SuiteFail { - passed: usize, - failed: usize, - ignored: usize, - measured: usize, - filtered_out: usize, - exec_time: f32, - }), - (Type::Suite, Status::Ignored) => { - return Err(Error::custom("test suite's cannot be ignored")); - } - (Type::Test, Status::Started) => take!(TestStart { name: String }), - (Type::Test, Status::Ok) => TestEvent::TestOk { - name: name.ok_or(Error::missing_field("name"))?, - exec_time: take!(exec_time as f32), - stdout: match map.next_key::<&str>()? { - Some("stdout") => Some(map.next_value::()?), - Some(k) => return Err(Error::unknown_field(k, &["stdout"])), - None => None, - }, - }, - (Type::Test, Status::Ignored) => TestEvent::TestIgnore { - name: name.ok_or(Error::missing_field("name"))?, - }, - (Type::Test, Status::Failed) => { - let exec_time = take!(exec_time as f32); - let name = name.ok_or(Error::missing_field("name"))?; - let stdout = match map.next_key::<&str>()? { - Some("stdout") => Some(map.next_value::()?), - Some(k) => return Err(Error::unknown_field(k, &["stdout"])), - None => None, - }; - match map.next_key::<&str>()? { - Some("reason") => { - let reason = map.next_value::<&str>()?; - if reason != "time limit exceeded" { - return Err(Error::invalid_value( - Unexpected::Str(reason), - &"time limit exceeded", - )); - } - TestEvent::TestTimeout { - name, - exec_time, - stdout, - } - } - Some("message") => { - let message = map.next_value::()?; - TestEvent::TestFailMessage { - name, - exec_time, - stdout, - message, - } - } - _ => TestEvent::TestFail { - name, - exec_time, - stdout, - }, - } - } - (Type::Bench, _) => { - todo!() - } - }) - } - } - } -} - -#[test] -fn deser() { - macro_rules! run { - ($($input:literal parses to $output:expr),+) => { - $(assert_eq!(dbg!(serde_json::from_str::($input)).unwrap(), $output);)+ - }; - } - run![ - r#"{ "type": "suite", "event": "started", "test_count": 2 }"# parses to TestEvent::SuiteStart { test_count: 2 }, - r#"{ "type": "test", "event": "started", "name": "fail" }"# parses to TestEvent::TestStart { name: "fail".into() }, - r#"{ "type": "test", "name": "fail", "event": "ok", "exec_time": 0.000003428, "stdout": "hello world" }"# parses to TestEvent::TestOk { name: "fail".into(), exec_time: 0.000003428, stdout: Some("hello world".into()) } , - r#"{ "type": "test", "event": "started", "name": "nope" }"# parses to TestEvent::TestStart { name: "nope".into() }, - r#"{ "type": "test", "name": "nope", "event": "ignored" }"# parses to TestEvent::TestIgnore { 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 TestEvent::SuiteOk { passed: 1, failed: 0, ignored: 1, measured: 0, filtered_out: 0, exec_time: 0.000684028 } - ]; - - run![ - r#"{ "type": "suite", "event": "started", "test_count": 1 }"# parses to TestEvent::SuiteStart { test_count: 1 }, - r#"{ "type": "test", "event": "started", "name": "fail" }"# parses to TestEvent::TestStart { name: "fail".into() }, - r#"{ "type": "test", "name": "fail", "event": "failed", "exec_time": 0.000081092, "stdout": "thread 'fail' panicked" }"# parses to TestEvent::TestFail { name: "fail".into(), exec_time: 0.000081092, stdout: Some("thread 'fail' panicked".into()) }, - r#"{ "type": "suite", "event": "failed", "passed": 0, "failed": 1, "ignored": 0, "measured": 0, "filtered_out": 0, "exec_time": 0.000731068 }"# parses to TestEvent::SuiteFail { passed: 0, failed: 1, ignored: 0, measured: 0, filtered_out: 0, exec_time: 0.000731068 } - ]; -} diff --git a/src/libtest/mod.rs b/src/libtest/mod.rs index 7f0d8aa9..10f249a2 100644 --- a/src/libtest/mod.rs +++ b/src/libtest/mod.rs @@ -1,4 +1,132 @@ //! parses output of . -pub mod event; -pub mod status; -pub mod r#type; +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, + /// 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, + }, + /// 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, + }, + /// the test has failed + Failed { + /// which one + name: String, + /// in how long + exec_time: f32, + /// why? + stdout: Option, + /// it timed out? + reason: Option, + /// what message + message: Option, + }, + /// the test has been ignored + Ignored { + /// 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; + name + } +} + +#[derive(Debug, PartialEq, Deserialize, Serialize)] +/// Represents the output of `cargo test -- -Zunstable-options --report-time --show-output --format json`. +/// +/// requires --report-time +#[serde(tag = "type")] +#[serde(rename_all = "lowercase")] +pub enum TestMessage { + /// suite related message + Suite(SuiteEvent), + /// test related message + Test(TestEvent), +} + +#[test] +fn deser() { + macro_rules! run { + ($($input:literal parses to $output:expr),+) => { + $(assert_eq!(dbg!(serde_json::from_str::($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": 1 }"# parses to TestMessage::Suite(SuiteEvent::Started { test_count: 1 }), + r#"{ "type": "test", "event": "started", "name": "fail" }"# parses to TestMessage::Test(TestEvent::Started { name: "fail".into() }), + 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": 0, "filtered_out": 0, "exec_time": 0.000731068 }"# parses to TestMessage::Suite(SuiteEvent::Failed { passed: 0, failed: 1, ignored: 0, measured: 0, filtered_out: 0, exec_time: 0.000731068 }) + ]; +} diff --git a/src/libtest/status.rs b/src/libtest/status.rs deleted file mode 100644 index f2ee0a0a..00000000 --- a/src/libtest/status.rs +++ /dev/null @@ -1,13 +0,0 @@ -use serde::Deserialize; -#[derive(Deserialize, Debug)] -#[serde(rename_all = "lowercase")] -pub enum Status { - /// Test is ok (corresponds to `TestResult::TrOk` in libtest) - Ok, - /// Test started - Started, - /// Test failed (corresponds to {`TestResult::TrFailed`, `TestResult::TrTimedFail`, `TestResult::TrFailedMsg` in libtest}) - Failed, - /// Test ignored (corresponds to `TestResult::TrIgnored` in libtest) - Ignored, -} diff --git a/src/libtest/type.rs b/src/libtest/type.rs deleted file mode 100644 index 6ed2b062..00000000 --- a/src/libtest/type.rs +++ /dev/null @@ -1,15 +0,0 @@ -use serde::Deserialize; - -#[derive(Deserialize, Debug)] -#[serde(rename_all = "lowercase")] -pub enum Type { - Test, - /// Occurs usually 4 times in a `cargo test` lifetime: - /// - once at the start, how many normal tests - /// - once at the end of the normal tests, how did it go - /// - once at the start of the doc tests - /// - once at the end of the doc tests - Suite, - /// `#[bench]` type tests. - Bench, -} From b22b586c70ea9a7c241fc340d175d35f5cef6ec9 Mon Sep 17 00:00:00 2001 From: bendn Date: Mon, 9 Oct 2023 08:22:20 +0700 Subject: [PATCH 04/15] add missing features --- src/libtest/mod.rs | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/libtest/mod.rs b/src/libtest/mod.rs index 10f249a2..7211b216 100644 --- a/src/libtest/mod.rs +++ b/src/libtest/mod.rs @@ -81,6 +81,11 @@ pub enum TestEvent { /// which one name: String, }, + /// the test has timed out + Timeout { + /// which one + name: String, + }, } impl TestEvent { @@ -89,7 +94,8 @@ impl TestEvent { let (Self::Started { name } | Self::Ok { name, .. } | Self::Ignored { name } - | Self::Failed { name, .. }) = self; + | Self::Failed { name, .. } + | Self::Timeout { name }) = self; name } } @@ -105,6 +111,17 @@ pub enum TestMessage { 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, + }, } #[test] From d4f3825aa2cbce4091135294ee2e0aa553cf6ec1 Mon Sep 17 00:00:00 2001 From: bendn Date: Mon, 9 Oct 2023 08:35:03 +0700 Subject: [PATCH 05/15] add stability warning --- src/libtest/mod.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/libtest/mod.rs b/src/libtest/mod.rs index 7211b216..ba42fc2b 100644 --- a/src/libtest/mod.rs +++ b/src/libtest/mod.rs @@ -104,6 +104,10 @@ impl TestEvent { /// 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 { From 50b413ccbf5f9174138a39a5becd65994e03f3fb Mon Sep 17 00:00:00 2001 From: bendn Date: Mon, 9 Oct 2023 19:35:28 +0700 Subject: [PATCH 06/15] remove module dir --- src/{libtest/mod.rs => libtest.rs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/{libtest/mod.rs => libtest.rs} (100%) diff --git a/src/libtest/mod.rs b/src/libtest.rs similarity index 100% rename from src/libtest/mod.rs rename to src/libtest.rs From 60f783d11721a467bfc5be7c8827351edd3622fe Mon Sep 17 00:00:00 2001 From: bendn Date: Mon, 9 Oct 2023 19:40:26 +0700 Subject: [PATCH 07/15] add bench test --- src/libtest.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/libtest.rs b/src/libtest.rs index ba42fc2b..fbf3135f 100644 --- a/src/libtest.rs +++ b/src/libtest.rs @@ -20,7 +20,7 @@ pub enum SuiteEvent { failed: usize, /// number of tests that were ignored ignored: usize, - /// i think its something to do with benchmarks? + /// number of benchmarks run measured: usize, /// i think this is based on what you specify in the cargo test argument filtered_out: usize, @@ -145,9 +145,11 @@ fn deser() { ]; run![ - r#"{ "type": "suite", "event": "started", "test_count": 1 }"# parses to TestMessage::Suite(SuiteEvent::Started { test_count: 1 }), + 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": 0, "filtered_out": 0, "exec_time": 0.000731068 }"# parses to TestMessage::Suite(SuiteEvent::Failed { passed: 0, failed: 1, ignored: 0, measured: 0, filtered_out: 0, exec_time: 0.000731068 }) + 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 }) ]; } From 162826a243762771002bb8a59d6454f85ff4a99c Mon Sep 17 00:00:00 2001 From: bendn Date: Thu, 12 Oct 2023 05:30:55 +0700 Subject: [PATCH 08/15] make mod pub --- src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 924a07dc..2242875a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -99,7 +99,7 @@ pub use dependency::DependencyBuilder; pub use dependency::{Dependency, DependencyKind}; use diagnostic::Diagnostic; pub use errors::{Error, Result}; -pub use libtest::{SuiteEvent, TestEvent, TestMessage}; +pub use libtest::TestMessage; #[allow(deprecated)] pub use messages::parse_messages; pub use messages::{ @@ -116,7 +116,7 @@ use serde::{Deserialize, Deserializer, Serialize}; mod dependency; pub mod diagnostic; mod errors; -mod libtest; +pub mod libtest; mod messages; /// An "opaque" identifier for a package. From 9259190c574378546bb82712367cb3f131e5a6db Mon Sep 17 00:00:00 2001 From: bendn Date: Thu, 12 Oct 2023 06:16:26 +0700 Subject: [PATCH 09/15] add stdout helper method --- src/libtest.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/libtest.rs b/src/libtest.rs index fbf3135f..3d1f4a1b 100644 --- a/src/libtest.rs +++ b/src/libtest.rs @@ -98,6 +98,14 @@ impl TestEvent { | 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)] From db32e7e4f2e45fa4046d55ed328546ea6e32e5ef Mon Sep 17 00:00:00 2001 From: bendn Date: Thu, 12 Oct 2023 06:18:48 +0700 Subject: [PATCH 10/15] add helper methods for health --- src/libtest.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/libtest.rs b/src/libtest.rs index 3d1f4a1b..a8d350eb 100644 --- a/src/libtest.rs +++ b/src/libtest.rs @@ -99,13 +99,23 @@ impl TestEvent { name } - /// Get the stdout of this test, if available. + /// 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 { .. }) + } + + /// If this test has finished + pub fn is_finished(&self) -> bool { + !self.in_progress() + } } #[derive(Debug, PartialEq, Deserialize, Serialize)] From d7d326f8458bf7dbc7af380c454e922a5661898d Mon Sep 17 00:00:00 2001 From: bendn Date: Thu, 12 Oct 2023 17:02:43 +0700 Subject: [PATCH 11/15] add "unstable" feature --- Cargo.toml | 1 + src/lib.rs | 2 ++ src/libtest.rs | 14 +++----------- 3 files changed, 6 insertions(+), 11 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 722d7b73..59313abb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,6 +21,7 @@ thiserror = "1.0.31" [features] default = [] builder = ["derive_builder"] +unstable = [] [package.metadata.cargo_metadata_test] some_field = true diff --git a/src/lib.rs b/src/lib.rs index 2242875a..061fd8bc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -99,6 +99,7 @@ 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; @@ -116,6 +117,7 @@ use serde::{Deserialize, Deserializer, Serialize}; mod dependency; pub mod diagnostic; mod errors; +#[cfg(feature = "unstable")] pub mod libtest; mod messages; diff --git a/src/libtest.rs b/src/libtest.rs index a8d350eb..f4ff791c 100644 --- a/src/libtest.rs +++ b/src/libtest.rs @@ -1,4 +1,6 @@ -//! parses output of . +//! Parses output of [libtest](https://github.com/rust-lang/rust/blob/master/library/test/src/formatters/json.rs). +//! +//! As this module parses output of a unstable format, all structs in this module are volatile. use serde::{Deserialize, Serialize}; /// Suite related event @@ -106,16 +108,6 @@ impl TestEvent { _ => None, } } - - /// If this test is in progress. - pub fn in_progress(&self) -> bool { - matches!(self, Self::Started { .. }) - } - - /// If this test has finished - pub fn is_finished(&self) -> bool { - !self.in_progress() - } } #[derive(Debug, PartialEq, Deserialize, Serialize)] From 023b857c0b7a28028cf7cf5280240c07ff096ff2 Mon Sep 17 00:00:00 2001 From: bendn Date: Thu, 12 Oct 2023 20:15:14 +0700 Subject: [PATCH 12/15] patch bump --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 59313abb..8138918a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cargo_metadata" -version = "0.18.0" +version = "0.18.1" authors = ["Oliver Schneider "] repository = "https://github.com/oli-obk/cargo_metadata" description = "structured access to the output of `cargo metadata`" From 04d1899f0a09c21478297c047fe1efc3d335399c Mon Sep 17 00:00:00 2001 From: bendn Date: Fri, 13 Oct 2023 04:26:09 +0700 Subject: [PATCH 13/15] update changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0759858a..cac128c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,3 +38,7 @@ - Updated `derive_builder` to the latest version. - Made use of `matches!` macros where possible. - Fixed some tests + +### Added + +- Added `TestMessage`, `TestEvent`, `SuiteEvent` for parsing the `cargo test -- --format json` output. From bd379af5cd4503bd4233bf5a7792e2a9c20a4d42 Mon Sep 17 00:00:00 2001 From: bendn Date: Fri, 13 Oct 2023 04:36:46 +0700 Subject: [PATCH 14/15] clarify libtest module level doc --- src/libtest.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libtest.rs b/src/libtest.rs index f4ff791c..4de86e7d 100644 --- a/src/libtest.rs +++ b/src/libtest.rs @@ -1,6 +1,6 @@ //! Parses output of [libtest](https://github.com/rust-lang/rust/blob/master/library/test/src/formatters/json.rs). //! -//! As this module parses output of a unstable format, all structs in this module are volatile. +//! 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 From 87adb467d8637b21f546cd106a398db0ecc67676 Mon Sep 17 00:00:00 2001 From: Oli Scherer Date: Fri, 13 Oct 2023 13:25:25 +0200 Subject: [PATCH 15/15] Update CHANGELOG.md --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cac128c6..c045c7d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,8 @@ - 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.