diff --git a/tracing-mock/README.md b/tracing-mock/README.md index 04b8dc41b..85d677445 100644 --- a/tracing-mock/README.md +++ b/tracing-mock/README.md @@ -78,7 +78,7 @@ fn yak_shaving() { } let (subscriber, handle) = subscriber::mock() - .event(expect::event().with_fields(field::msg("preparing to shave yaks"))) + .event(expect::event().with_fields(expect::message("preparing to shave yaks"))) .only() .run_with_handle(); @@ -102,7 +102,7 @@ Below is a slightly more complex example. `tracing-mock` asserts that, in order: ```rust use tracing::subscriber::with_default; -use tracing_mock::{subscriber, expect, field}; +use tracing_mock::{subscriber, expect}; #[tracing::instrument] fn yak_shaving(number_of_yaks: u32) { @@ -128,7 +128,7 @@ let (subscriber, handle) = subscriber::mock() expect::event().with_fields( expect::field("number_of_yaks") .with_value(&yak_count) - .and(field::msg("preparing to shave yaks")) + .and(expect::message("preparing to shave yaks")) .only(), ), ) @@ -136,7 +136,7 @@ let (subscriber, handle) = subscriber::mock() expect::event().with_fields( expect::field("all_yaks_shaved") .with_value(&true) - .and(field::msg("yak shaving completed.")) + .and(expect::message("yak shaving completed.")) .only(), ), ) @@ -173,4 +173,4 @@ This project is licensed under the [MIT license][mit-url]. Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in Tracing by you, shall be licensed as MIT, without any additional -terms or conditions. \ No newline at end of file +terms or conditions. diff --git a/tracing-mock/src/event.rs b/tracing-mock/src/event.rs index 6c4cbf7e7..79d99d49d 100644 --- a/tracing-mock/src/event.rs +++ b/tracing-mock/src/event.rs @@ -48,7 +48,7 @@ pub struct ExpectedEvent { } pub fn msg(message: impl fmt::Display) -> ExpectedEvent { - expect::event().with_fields(field::msg(message)) + expect::event().with_fields(expect::message(message)) } impl ExpectedEvent { diff --git a/tracing-mock/src/expect.rs b/tracing-mock/src/expect.rs index 044f13458..353bc52f5 100644 --- a/tracing-mock/src/expect.rs +++ b/tracing-mock/src/expect.rs @@ -38,6 +38,13 @@ where } } +pub fn message(message: impl fmt::Display) -> ExpectedField { + ExpectedField { + name: "message".to_string(), + value: ExpectedValue::Debug(message.to_string()), + } +} + pub fn span() -> ExpectedSpan { ExpectedSpan { ..Default::default() diff --git a/tracing-mock/src/field.rs b/tracing-mock/src/field.rs index ea5f7a4eb..6d1e73f98 100644 --- a/tracing-mock/src/field.rs +++ b/tracing-mock/src/field.rs @@ -1,3 +1,86 @@ +//! Define expectations to validate fields on events and spans. +//! +//! The [`ExpectedField`] struct define expected values for fields in +//! order to match events and spans via the mock subscriber API in the +//! [`subscriber`] module. +//! +//! Expected fields should be created with [`expect::field`] and a +//! chain of method calls to specify the field value and additional +//! fields as necessary. +//! +//! # Examples +//! +//! The simplest case is to expect that an event has a field with a +//! specific name, without any expectation about the value: +//! +//! ``` +//! use tracing_mock::{subscriber, expect}; +//! +//! let event = expect::event() +//! .with_fields(expect::field("field_name")); +//! +//! let (subscriber, handle) = subscriber::mock() +//! .event(event) +//! .run_with_handle(); +//! +//! tracing::subscriber::with_default(subscriber, || { +//! tracing::info!(field_name = "value"); +//! }); +//! +//! handle.assert_finished(); +//! ``` +//! +//! It is possible to expect multiple fields and specify the value for +//! each of them: +//! +//! ``` +//! use tracing_mock::{subscriber, expect}; +//! +//! let event = expect::event().with_fields( +//! expect::field("string_field") +//! .with_value(&"field_value") +//! .and(expect::field("integer_field").with_value(&54_i64)) +//! .and(expect::field("bool_field").with_value(&true)), +//! ); +//! +//! let (subscriber, handle) = subscriber::mock() +//! .event(event) +//! .run_with_handle(); +//! +//! tracing::subscriber::with_default(subscriber, || { +//! tracing::info!( +//! string_field = "field_value", +//! integer_field = 54_i64, +//! bool_field = true, +//! ); +//! }); +//! +//! handle.assert_finished(); +//! ``` +//! +//! If an expected field is not present, or if the value of the field +//! is different, the test will fail. In this example, the value is +//! different: +//! +//! ```should_panic +//! use tracing_mock::{subscriber, expect}; +//! +//! let event = expect::event() +//! .with_fields(expect::field("field_name").with_value(&"value")); +//! +//! let (subscriber, handle) = subscriber::mock() +//! .event(event) +//! .run_with_handle(); +//! +//! tracing::subscriber::with_default(subscriber, || { +//! tracing::info!(field_name = "different value"); +//! }); +//! +//! handle.assert_finished(); +//! ``` +//! +//! [`subscriber`]: mod@crate::subscriber +//! [`expect::field`]: fn@crate::expect::field use tracing::{ callsite, callsite::Callsite, @@ -7,12 +90,24 @@ use tracing::{ use std::{collections::HashMap, fmt}; +/// An expectation for multiple fields. +/// +/// For a detailed description and examples, see the documentation for +/// the methods and the [`field`] module. +/// +/// [`field`]: mod@crate::field #[derive(Default, Debug, Eq, PartialEq)] pub struct ExpectedFields { fields: HashMap, only: bool, } +/// An expected field. +/// +/// For a detailed description and examples, see the documentation for +/// the methods and the [`field`] module. +/// +/// [`field`]: mod@crate::field #[derive(Debug)] pub struct ExpectedField { pub(super) name: String, @@ -20,7 +115,7 @@ pub struct ExpectedField { } #[derive(Debug)] -pub enum ExpectedValue { +pub(crate) enum ExpectedValue { F64(f64), I64(i64), U64(u64), @@ -55,15 +150,48 @@ impl PartialEq for ExpectedValue { } } -pub fn msg(message: impl fmt::Display) -> ExpectedField { - ExpectedField { - name: "message".to_string(), - value: ExpectedValue::Debug(message.to_string()), - } -} - impl ExpectedField { - /// Expect a field with the given name and value. + /// Sets the value to expect when matching this field. + /// + /// If the recorded value for this field diffs, the expectation will fail. + /// + /// # Examples + /// + /// ``` + /// use tracing_mock::{subscriber, expect}; + /// + /// let event = expect::event() + /// .with_fields(expect::field("field_name").with_value(&"value")); + /// + /// let (subscriber, handle) = subscriber::mock() + /// .event(event) + /// .run_with_handle(); + /// + /// tracing::subscriber::with_default(subscriber, || { + /// tracing::info!(field_name = "value"); + /// }); + /// + /// handle.assert_finished(); + /// ``` + /// + /// A different value will cause the test to fail: + /// + /// ```should_panic + /// use tracing_mock::{subscriber, expect}; + /// + /// let event = expect::event() + /// .with_fields(expect::field("field_name").with_value(&"value")); + /// + /// let (subscriber, handle) = subscriber::mock() + /// .event(event) + /// .run_with_handle(); + /// + /// tracing::subscriber::with_default(subscriber, || { + /// tracing::info!(field_name = "different value"); + /// }); + /// + /// handle.assert_finished(); + /// ``` pub fn with_value(self, value: &dyn Value) -> Self { Self { value: ExpectedValue::from(value), @@ -71,6 +199,58 @@ impl ExpectedField { } } + /// Adds an additional [`ExpectedField`] to be matched. + /// + /// Any fields introduced by `.and` must also match. If any fields + /// are not present, or if the value for any field is different, + /// then the expectation will fail. + /// + /// # Examples + /// + /// ``` + /// use tracing_mock::{subscriber, expect}; + /// + /// let event = expect::event().with_fields( + /// expect::field("field") + /// .with_value(&"value") + /// .and(expect::field("another_field").with_value(&42)), + /// ); + /// + /// let (subscriber, handle) = subscriber::mock() + /// .event(event) + /// .run_with_handle(); + /// + /// tracing::subscriber::with_default(subscriber, || { + /// tracing::info!( + /// field = "value", + /// another_field = 42, + /// ); + /// }); + /// + /// handle.assert_finished(); + /// ``` + /// + /// If the second field is not present, the test will fail: + /// + /// ```should_panic + /// use tracing_mock::{subscriber, expect}; + /// + /// let event = expect::event().with_fields( + /// expect::field("field") + /// .with_value(&"value") + /// .and(expect::field("another_field").with_value(&42)), + /// ); + /// + /// let (subscriber, handle) = subscriber::mock() + /// .event(event) + /// .run_with_handle(); + /// + /// tracing::subscriber::with_default(subscriber, || { + /// tracing::info!(field = "value"); + /// }); + /// + /// handle.assert_finished(); + /// ``` pub fn and(self, other: ExpectedField) -> ExpectedFields { ExpectedFields { fields: HashMap::new(), @@ -80,6 +260,47 @@ impl ExpectedField { .and(other) } + /// Indicates that no fields other than those specified should be + /// expected. + /// + /// If additional fields are present on the recorded event or span, + /// the expectation will fail. + /// + /// # Examples + /// + /// Check that only a single field is recorded. + /// + /// ``` + /// use tracing_mock::{subscriber, expect}; + /// + /// let event = expect::event() + /// .with_fields(expect::field("field").with_value(&"value").only()); + /// + /// let (subscriber, handle) = subscriber::mock().event(event).run_with_handle(); + /// + /// tracing::subscriber::with_default(subscriber, || { + /// tracing::info!(field = "value"); + /// }); + /// + /// handle.assert_finished(); + /// ``` + /// + /// The following example fails because a second field is recorded. + /// + /// ```should_panic + /// use tracing_mock::{subscriber, expect}; + /// + /// let event = expect::event() + /// .with_fields(expect::field("field").with_value(&"value").only()); + /// + /// let (subscriber, handle) = subscriber::mock().event(event).run_with_handle(); + /// + /// tracing::subscriber::with_default(subscriber, || { + /// tracing::info!(field = "value", another_field = 42,); + /// }); + /// + /// handle.assert_finished(); + /// ``` pub fn only(self) -> ExpectedFields { ExpectedFields { fields: HashMap::new(), @@ -100,12 +321,137 @@ impl From for ExpectedFields { } impl ExpectedFields { + /// Adds an additional [`ExpectedField`] to be matched. + /// + /// _All_ fields must match for the expectation to pass. If any of + /// them are not present, if any of the values differs, the + /// expectation will fail. + /// + /// This method performs the same function as + /// [`ExpectedField::and`], but applies in the case where there are + /// already multiple fields expected. + /// + /// # Examples + /// + /// ``` + /// use tracing_mock::{subscriber, expect}; + /// + /// let event = expect::event().with_fields( + /// expect::field("field") + /// .with_value(&"value") + /// .and(expect::field("another_field").with_value(&42)) + /// .and(expect::field("a_third_field").with_value(&true)), + /// ); + /// + /// let (subscriber, handle) = subscriber::mock() + /// .event(event) + /// .run_with_handle(); + /// + /// tracing::subscriber::with_default(subscriber, || { + /// tracing::info!( + /// field = "value", + /// another_field = 42, + /// a_third_field = true, + /// ); + /// }); + /// + /// handle.assert_finished(); + /// ``` + /// + /// If any of the expected fields are not present on the recorded + /// event, the test will fail: + /// + /// ```should_panic + /// use tracing_mock::{subscriber, expect}; + /// + /// let event = expect::event().with_fields( + /// expect::field("field") + /// .with_value(&"value") + /// .and(expect::field("another_field").with_value(&42)) + /// .and(expect::field("a_third_field").with_value(&true)), + /// ); + /// + /// let (subscriber, handle) = subscriber::mock() + /// .event(event) + /// .run_with_handle(); + /// + /// tracing::subscriber::with_default(subscriber, || { + /// tracing::info!( + /// field = "value", + /// a_third_field = true, + /// ); + /// }); + /// + /// handle.assert_finished(); + /// ``` + /// + /// [`ExpectedField::and`]: fn@crate::field::ExpectedField::and pub fn and(mut self, field: ExpectedField) -> Self { self.fields.insert(field.name, field.value); self } - /// Indicates that no fields other than those specified should be expected. + /// Asserts that no fields other than those specified should be + /// expected. + /// + /// This method performs the same function as + /// [`ExpectedField::only`], but applies in the case where there are + /// multiple fields expected. + /// + /// # Examples + /// + /// Check that only two fields are recorded on the event. + /// + /// ``` + /// use tracing_mock::{subscriber, expect}; + /// + /// let event = expect::event().with_fields( + /// expect::field("field") + /// .with_value(&"value") + /// .and(expect::field("another_field").with_value(&42)) + /// .only(), + /// ); + /// + /// let (subscriber, handle) = subscriber::mock() + /// .event(event) + /// .run_with_handle(); + /// + /// tracing::subscriber::with_default(subscriber, || { + /// tracing::info!( + /// field = "value", + /// another_field = 42, + /// ); + /// }); + /// + /// handle.assert_finished(); + /// ``` + /// + /// The following example fails because a third field is recorded. + /// + /// ```should_panic + /// use tracing_mock::{subscriber, expect}; + /// + /// let event = expect::event().with_fields( + /// expect::field("field") + /// .with_value(&"value") + /// .and(expect::field("another_field").with_value(&42)) + /// .only(), + /// ); + /// + /// let (subscriber, handle) = subscriber::mock() + /// .event(event) + /// .run_with_handle(); + /// + /// tracing::subscriber::with_default(subscriber, || { + /// tracing::info!( + /// field = "value", + /// another_field = 42, + /// a_third_field = true, + /// ); + /// }); + /// + /// handle.assert_finished(); + /// ``` pub fn only(self) -> Self { Self { only: true, ..self } } @@ -138,7 +484,11 @@ impl ExpectedFields { } } - pub fn checker<'a>(&'a mut self, ctx: &'a str, subscriber_name: &'a str) -> CheckVisitor<'a> { + pub(crate) fn checker<'a>( + &'a mut self, + ctx: &'a str, + subscriber_name: &'a str, + ) -> CheckVisitor<'a> { CheckVisitor { expect: self, ctx, @@ -146,7 +496,7 @@ impl ExpectedFields { } } - pub fn is_empty(&self) -> bool { + pub(crate) fn is_empty(&self) -> bool { self.fields.is_empty() } } @@ -165,7 +515,7 @@ impl fmt::Display for ExpectedValue { } } -pub struct CheckVisitor<'a> { +pub(crate) struct CheckVisitor<'a> { expect: &'a mut ExpectedFields, ctx: &'a str, subscriber_name: &'a str, diff --git a/tracing-mock/src/subscriber.rs b/tracing-mock/src/subscriber.rs index 83251a371..1d5b36bd8 100644 --- a/tracing-mock/src/subscriber.rs +++ b/tracing-mock/src/subscriber.rs @@ -12,7 +12,7 @@ //! //! let (subscriber, handle) = subscriber::mock() //! // Expect a single event with a specified message -//! .event(expect::event().with_fields(field::msg("droids"))) +//! .event(expect::event().with_fields(expect::message("droids"))) //! .only() //! .run_with_handle(); //! @@ -40,7 +40,7 @@ //! // Enter a matching span //! .enter(span.clone()) //! // Record an event with message "collect parting message" -//! .event(expect::event().with_fields(field::msg("collect parting message"))) +//! .event(expect::event().with_fields(expect::message("collect parting message"))) //! // Record a value for the field `parting` on a matching span //! .record(span.clone(), expect::field("parting").with_value(&"goodbye world!")) //! // Exit a matching span @@ -81,7 +81,7 @@ //! .named("my_span"); //! let (subscriber, handle) = subscriber::mock() //! .enter(span.clone()) -//! .event(expect::event().with_fields(field::msg("collect parting message"))) +//! .event(expect::event().with_fields(expect::message("collect parting message"))) //! .record(span.clone(), expect::field("parting").with_value(&"goodbye world!")) //! .exit(span) //! .only() @@ -221,7 +221,7 @@ pub struct MockHandle(Arc>>, String); /// // Enter a matching span /// .enter(span.clone()) /// // Record an event with message "collect parting message" -/// .event(expect::event().with_fields(field::msg("collect parting message"))) +/// .event(expect::event().with_fields(expect::message("collect parting message"))) /// // Record a value for the field `parting` on a matching span /// .record(span.clone(), expect::field("parting").with_value(&"goodbye world!")) /// // Exit a matching span diff --git a/tracing/tests/event.rs b/tracing/tests/event.rs index 0be7c0bc5..417b8878b 100644 --- a/tracing/tests/event.rs +++ b/tracing/tests/event.rs @@ -86,7 +86,7 @@ fn message_without_delims() { .and( expect::field("question").with_value(&"life, the universe, and everything"), ) - .and(field::msg(format_args!( + .and(expect::message(format_args!( "hello from my event! tricky? {:?}!", true ))) @@ -115,7 +115,7 @@ fn string_message_without_delims() { .and( expect::field("question").with_value(&"life, the universe, and everything"), ) - .and(field::msg(format_args!("hello from my event"))) + .and(expect::message(format_args!("hello from my event"))) .only(), ), )