diff --git a/tracing-mock/README.md b/tracing-mock/README.md index 52703a14d9..d27bb959d0 100644 --- a/tracing-mock/README.md +++ b/tracing-mock/README.md @@ -71,14 +71,14 @@ Below is an example that checks that an event contains a message: ```rust use tracing::collect::with_default; -use tracing_mock::{collector, expect, field}; +use tracing_mock::{collector, expect}; fn yak_shaving() { tracing::info!("preparing to shave yaks"); } let (collector, handle) = collector::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::collect::with_default; -use tracing_mock::{collector, expect, field}; +use tracing_mock::{collector, expect}; #[tracing::instrument] fn yak_shaving(number_of_yaks: u32) { @@ -128,7 +128,7 @@ let (collector, handle) = collector::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 (collector, handle) = collector::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(), ), ) diff --git a/tracing-mock/src/collector.rs b/tracing-mock/src/collector.rs index d1f05a24eb..a91cdd332d 100644 --- a/tracing-mock/src/collector.rs +++ b/tracing-mock/src/collector.rs @@ -12,7 +12,7 @@ //! //! let (collector, handle) = collector::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 (collector, handle) = collector::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-mock/src/event.rs b/tracing-mock/src/event.rs index 2a732dc69c..840867d019 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 044f134580..353bc52f5f 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 fa956c7946..a374461375 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 collector API in the +//! [`collector`] 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::{collector, expect}; +//! +//! let event = expect::event() +//! .with_fields(expect::field("field_name")); +//! +//! let (collector, handle) = collector::mock() +//! .event(event) +//! .run_with_handle(); +//! +//! tracing::collect::with_default(collector, || { +//! 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::{collector, 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 (collector, handle) = collector::mock() +//! .event(event) +//! .run_with_handle(); +//! +//! tracing::collect::with_default(collector, || { +//! 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::{collector, expect}; +//! +//! let event = expect::event() +//! .with_fields(expect::field("field_name").with_value(&"value")); +//! +//! let (collector, handle) = collector::mock() +//! .event(event) +//! .run_with_handle(); +//! +//! tracing::collect::with_default(collector, || { +//! tracing::info!(field_name = "different value"); +//! }); +//! +//! handle.assert_finished(); +//! ``` +//! +//! [`collector`]: mod@crate::collector +//! [`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::{collector, expect}; + /// + /// let event = expect::event() + /// .with_fields(expect::field("field_name").with_value(&"value")); + /// + /// let (collector, handle) = collector::mock() + /// .event(event) + /// .run_with_handle(); + /// + /// tracing::collect::with_default(collector, || { + /// tracing::info!(field_name = "value"); + /// }); + /// + /// handle.assert_finished(); + /// ``` + /// + /// A different value will cause the test to fail: + /// + /// ```should_panic + /// use tracing_mock::{collector, expect}; + /// + /// let event = expect::event() + /// .with_fields(expect::field("field_name").with_value(&"value")); + /// + /// let (collector, handle) = collector::mock() + /// .event(event) + /// .run_with_handle(); + /// + /// tracing::collect::with_default(collector, || { + /// 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::{collector, expect}; + /// + /// let event = expect::event().with_fields( + /// expect::field("field") + /// .with_value(&"value") + /// .and(expect::field("another_field").with_value(&42)), + /// ); + /// + /// let (collector, handle) = collector::mock() + /// .event(event) + /// .run_with_handle(); + /// + /// tracing::collect::with_default(collector, || { + /// 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::{collector, expect}; + /// + /// let event = expect::event().with_fields( + /// expect::field("field") + /// .with_value(&"value") + /// .and(expect::field("another_field").with_value(&42)), + /// ); + /// + /// let (collector, handle) = collector::mock() + /// .event(event) + /// .run_with_handle(); + /// + /// tracing::collect::with_default(collector, || { + /// 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::{collector, expect}; + /// + /// let event = expect::event() + /// .with_fields(expect::field("field").with_value(&"value").only()); + /// + /// let (collector, handle) = collector::mock().event(event).run_with_handle(); + /// + /// tracing::collect::with_default(collector, || { + /// tracing::info!(field = "value"); + /// }); + /// + /// handle.assert_finished(); + /// ``` + /// + /// The following example fails because a second field is recorded. + /// + /// ```should_panic + /// use tracing_mock::{collector, expect}; + /// + /// let event = expect::event() + /// .with_fields(expect::field("field").with_value(&"value").only()); + /// + /// let (collector, handle) = collector::mock().event(event).run_with_handle(); + /// + /// tracing::collect::with_default(collector, || { + /// 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::{collector, 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 (collector, handle) = collector::mock() + /// .event(event) + /// .run_with_handle(); + /// + /// tracing::collect::with_default(collector, || { + /// 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::{collector, 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 (collector, handle) = collector::mock() + /// .event(event) + /// .run_with_handle(); + /// + /// tracing::collect::with_default(collector, || { + /// 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::{collector, expect}; + /// + /// let event = expect::event().with_fields( + /// expect::field("field") + /// .with_value(&"value") + /// .and(expect::field("another_field").with_value(&42)) + /// .only(), + /// ); + /// + /// let (collector, handle) = collector::mock() + /// .event(event) + /// .run_with_handle(); + /// + /// tracing::collect::with_default(collector, || { + /// 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::{collector, expect}; + /// + /// let event = expect::event().with_fields( + /// expect::field("field") + /// .with_value(&"value") + /// .and(expect::field("another_field").with_value(&42)) + /// .only(), + /// ); + /// + /// let (collector, handle) = collector::mock() + /// .event(event) + /// .run_with_handle(); + /// + /// tracing::collect::with_default(collector, || { + /// tracing::info!( + /// field = "value", + /// another_field = 42, + /// a_third_field = true, + /// ); + /// }); + /// + /// handle.assert_finished(); + /// ``` pub fn only(self) -> Self { Self { only: true, ..self } } @@ -132,7 +478,11 @@ impl ExpectedFields { } } - pub fn checker<'a>(&'a mut self, ctx: &'a str, collector_name: &'a str) -> CheckVisitor<'a> { + pub(crate) fn checker<'a>( + &'a mut self, + ctx: &'a str, + collector_name: &'a str, + ) -> CheckVisitor<'a> { CheckVisitor { expect: self, ctx, @@ -140,7 +490,7 @@ impl ExpectedFields { } } - pub fn is_empty(&self) -> bool { + pub(crate) fn is_empty(&self) -> bool { self.fields.is_empty() } } @@ -159,7 +509,7 @@ impl fmt::Display for ExpectedValue { } } -pub struct CheckVisitor<'a> { +pub(crate) struct CheckVisitor<'a> { expect: &'a mut ExpectedFields, ctx: &'a str, collector_name: &'a str, diff --git a/tracing-mock/src/subscriber.rs b/tracing-mock/src/subscriber.rs index b0686f74b8..6a8c536d55 100644 --- a/tracing-mock/src/subscriber.rs +++ b/tracing-mock/src/subscriber.rs @@ -7,12 +7,12 @@ //! validated as the code under test is run. //! //! ``` -//! use tracing_mock::{expect, field, subscriber}; +//! use tracing_mock::{expect, subscriber}; //! use tracing_subscriber::{subscribe::CollectExt, util::SubscriberInitExt, Subscribe}; //! //! 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"))) //! .run_with_handle(); //! //! // Use `set_default` to apply the `MockSubscriber` until the end @@ -33,7 +33,7 @@ //! their respective fields: //! //! ``` -//! use tracing_mock::{expect, field, subscriber}; +//! use tracing_mock::{expect, subscriber}; //! use tracing_subscriber::{subscribe::CollectExt, util::SubscriberInitExt, Subscribe}; //! //! let span = expect::span() @@ -42,7 +42,7 @@ //! // Enter a matching span //! .enter(span.clone()) //! // Record an event with message "collect parting message" -//! .event(expect::event().with_fields(field::msg("say hello"))) +//! .event(expect::event().with_fields(expect::message("say hello"))) //! // Exit a matching span //! .exit(span) //! // Expect no further messages to be recorded @@ -75,7 +75,7 @@ //! span before recording an event, the test will fail: //! //! ```should_panic -//! use tracing_mock::{expect, field, subscriber}; +//! use tracing_mock::{expect, subscriber}; //! use tracing_subscriber::{subscribe::CollectExt, util::SubscriberInitExt, Subscribe}; //! //! let span = expect::span() @@ -84,7 +84,7 @@ //! // Enter a matching span //! .enter(span.clone()) //! // Record an event with message "collect parting message" -//! .event(expect::event().with_fields(field::msg("say hello"))) +//! .event(expect::event().with_fields(expect::message("say hello"))) //! // Exit a matching span //! .exit(span) //! // Expect no further messages to be recorded @@ -145,7 +145,7 @@ use std::{ /// # Examples /// /// ``` -/// use tracing_mock::{expect, field, subscriber}; +/// use tracing_mock::{expect, subscriber}; /// use tracing_subscriber::{subscribe::CollectExt, util::SubscriberInitExt, Subscribe}; /// /// let span = expect::span() @@ -154,7 +154,7 @@ use std::{ /// // Enter a matching span /// .enter(span.clone()) /// // Record an event with message "collect parting message" -/// .event(expect::event().with_fields(field::msg("say hello"))) +/// .event(expect::event().with_fields(expect::message("say hello"))) /// // Exit a matching span /// .exit(span) /// // Expect no further messages to be recorded diff --git a/tracing/tests/event.rs b/tracing/tests/event.rs index 55594ea913..a370b700cd 100644 --- a/tracing/tests/event.rs +++ b/tracing/tests/event.rs @@ -85,7 +85,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 ))) @@ -114,7 +114,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(), ), )