diff --git a/tracing-attributes/tests/async_fn.rs b/tracing-attributes/tests/async_fn.rs index 6acbd0e2a0..365a72cb4e 100644 --- a/tracing-attributes/tests/async_fn.rs +++ b/tracing-attributes/tests/async_fn.rs @@ -200,8 +200,8 @@ fn async_fn_with_async_trait() { let (subscriber, handle) = subscriber::mock() .new_span( span.clone() - .with_field(expect::field("self")) - .with_field(expect::field("v")), + .with_fields(expect::field("self")) + .with_fields(expect::field("v")), ) .enter(span.clone()) .new_span(span3.clone()) @@ -211,7 +211,7 @@ fn async_fn_with_async_trait() { .enter(span3.clone()) .exit(span3.clone()) .drop_span(span3) - .new_span(span2.clone().with_field(expect::field("self"))) + .new_span(span2.clone().with_fields(expect::field("self"))) .enter(span2.clone()) .event(expect::event().with_fields(expect::field("val").with_value(&5u64))) .exit(span2.clone()) @@ -261,7 +261,7 @@ fn async_fn_with_async_trait_and_fields_expressions() { let span = expect::span().named("call"); let (subscriber, handle) = subscriber::mock() .new_span( - span.clone().with_field( + span.clone().with_fields( expect::field("_v") .with_value(&5usize) .and(expect::field("test").with_value(&tracing::field::debug(10))) @@ -331,7 +331,7 @@ fn async_fn_with_async_trait_and_fields_expressions_with_generic_parameter() { let span4 = expect::span().named("sync_fun"); let (subscriber, handle) = subscriber::mock() /*.new_span(span.clone() - .with_field( + .with_fields( expect::field("Self").with_value(&"TestImpler"))) .enter(span.clone()) .exit(span.clone()) @@ -339,13 +339,13 @@ fn async_fn_with_async_trait_and_fields_expressions_with_generic_parameter() { .new_span( span2 .clone() - .with_field(expect::field("Self").with_value(&std::any::type_name::())), + .with_fields(expect::field("Self").with_value(&std::any::type_name::())), ) .enter(span2.clone()) .new_span( span4 .clone() - .with_field(expect::field("Self").with_value(&std::any::type_name::())), + .with_fields(expect::field("Self").with_value(&std::any::type_name::())), ) .enter(span4.clone()) .exit(span4.clone()) @@ -358,7 +358,7 @@ fn async_fn_with_async_trait_and_fields_expressions_with_generic_parameter() { .new_span( span3 .clone() - .with_field(expect::field("Self").with_value(&std::any::type_name::())), + .with_fields(expect::field("Self").with_value(&std::any::type_name::())), ) .enter(span3.clone()) .exit(span3.clone()) diff --git a/tracing-attributes/tests/destructuring.rs b/tracing-attributes/tests/destructuring.rs index cc4fecf3f2..b0e87376ce 100644 --- a/tracing-attributes/tests/destructuring.rs +++ b/tracing-attributes/tests/destructuring.rs @@ -11,7 +11,7 @@ fn destructure_tuples() { let (subscriber, handle) = subscriber::mock() .new_span( - span.clone().with_field( + span.clone().with_fields( expect::field("arg1") .with_value(&format_args!("1")) .and(expect::field("arg2").with_value(&format_args!("2"))) @@ -40,7 +40,7 @@ fn destructure_nested_tuples() { let (subscriber, handle) = subscriber::mock() .new_span( - span.clone().with_field( + span.clone().with_fields( expect::field("arg1") .with_value(&format_args!("1")) .and(expect::field("arg2").with_value(&format_args!("2"))) @@ -72,7 +72,7 @@ fn destructure_refs() { let (subscriber, handle) = subscriber::mock() .new_span( span.clone() - .with_field(expect::field("arg1").with_value(&1usize).only()), + .with_fields(expect::field("arg1").with_value(&1usize).only()), ) .enter(span.clone()) .exit(span.clone()) @@ -98,7 +98,7 @@ fn destructure_tuple_structs() { let (subscriber, handle) = subscriber::mock() .new_span( - span.clone().with_field( + span.clone().with_fields( expect::field("arg1") .with_value(&format_args!("1")) .and(expect::field("arg2").with_value(&format_args!("2"))) @@ -139,7 +139,7 @@ fn destructure_structs() { let (subscriber, handle) = subscriber::mock() .new_span( - span.clone().with_field( + span.clone().with_fields( expect::field("arg1") .with_value(&format_args!("1")) .and(expect::field("arg2").with_value(&format_args!("2"))) @@ -184,7 +184,7 @@ fn destructure_everything() { let (subscriber, handle) = subscriber::mock() .new_span( - span.clone().with_field( + span.clone().with_fields( expect::field("arg1") .with_value(&format_args!("1")) .and(expect::field("arg2").with_value(&format_args!("2"))) diff --git a/tracing-attributes/tests/err.rs b/tracing-attributes/tests/err.rs index 9d82487ea9..b2c5339c56 100644 --- a/tracing-attributes/tests/err.rs +++ b/tracing-attributes/tests/err.rs @@ -160,7 +160,7 @@ fn impl_trait_return_type() { let (subscriber, handle) = subscriber::mock() .new_span( span.clone() - .with_field(expect::field("x").with_value(&10usize).only()), + .with_fields(expect::field("x").with_value(&10usize).only()), ) .enter(span.clone()) .exit(span.clone()) diff --git a/tracing-attributes/tests/fields.rs b/tracing-attributes/tests/fields.rs index a3b23d7ac2..35b69967ec 100644 --- a/tracing-attributes/tests/fields.rs +++ b/tracing-attributes/tests/fields.rs @@ -46,7 +46,7 @@ impl HasField { #[test] fn fields() { - let span = expect::span().with_field( + let span = expect::span().with_fields( expect::field("foo") .with_value(&"bar") .and(expect::field("dsa").with_value(&true)) @@ -60,7 +60,7 @@ fn fields() { #[test] fn expr_field() { - let span = expect::span().with_field( + let span = expect::span().with_fields( expect::field("s") .with_value(&"hello world") .and(expect::field("len").with_value(&"hello world".len())) @@ -73,7 +73,7 @@ fn expr_field() { #[test] fn two_expr_fields() { - let span = expect::span().with_field( + let span = expect::span().with_fields( expect::field("s") .with_value(&"hello world") .and(expect::field("s.len").with_value(&"hello world".len())) @@ -87,7 +87,7 @@ fn two_expr_fields() { #[test] fn clashy_expr_field() { - let span = expect::span().with_field( + let span = expect::span().with_fields( // Overriding the `s` field should record `s` as a `Display` value, // rather than as a `Debug` value. expect::field("s") @@ -99,7 +99,7 @@ fn clashy_expr_field() { fn_clashy_expr_field("hello world"); }); - let span = expect::span().with_field(expect::field("s").with_value(&"s").only()); + let span = expect::span().with_fields(expect::field("s").with_value(&"s").only()); run_test(span, || { fn_clashy_expr_field2("hello world"); }); @@ -108,7 +108,7 @@ fn clashy_expr_field() { #[test] fn self_expr_field() { let span = - expect::span().with_field(expect::field("my_field").with_value(&"hello world").only()); + expect::span().with_fields(expect::field("my_field").with_value(&"hello world").only()); run_test(span, || { let has_field = HasField { my_field: "hello world", @@ -119,7 +119,7 @@ fn self_expr_field() { #[test] fn parameters_with_fields() { - let span = expect::span().with_field( + let span = expect::span().with_fields( expect::field("foo") .with_value(&"bar") .and(expect::field("param").with_value(&1u32)) @@ -132,7 +132,7 @@ fn parameters_with_fields() { #[test] fn empty_field() { - let span = expect::span().with_field(expect::field("foo").with_value(&"bar").only()); + let span = expect::span().with_fields(expect::field("foo").with_value(&"bar").only()); run_test(span, || { fn_empty_field(); }); @@ -140,7 +140,7 @@ fn empty_field() { #[test] fn string_field() { - let span = expect::span().with_field(expect::field("s").with_value(&"hello world").only()); + let span = expect::span().with_fields(expect::field("s").with_value(&"hello world").only()); run_test(span, || { fn_string(String::from("hello world")); }); diff --git a/tracing-attributes/tests/instrument.rs b/tracing-attributes/tests/instrument.rs index c5e816045b..d01df0c313 100644 --- a/tracing-attributes/tests/instrument.rs +++ b/tracing-attributes/tests/instrument.rs @@ -64,7 +64,7 @@ fn fields() { .with_target("my_target"); let (subscriber, handle) = subscriber::mock() .new_span( - span.clone().with_field( + span.clone().with_fields( expect::field("arg1") .with_value(&2usize) .and(expect::field("arg2").with_value(&false)) @@ -76,7 +76,7 @@ fn fields() { .exit(span.clone()) .drop_span(span) .new_span( - span2.clone().with_field( + span2.clone().with_fields( expect::field("arg1") .with_value(&3usize) .and(expect::field("arg2").with_value(&true)) @@ -126,7 +126,7 @@ fn skip() { let (subscriber, handle) = subscriber::mock() .new_span( span.clone() - .with_field(expect::field("arg1").with_value(&2usize).only()), + .with_fields(expect::field("arg1").with_value(&2usize).only()), ) .enter(span.clone()) .exit(span.clone()) @@ -134,7 +134,7 @@ fn skip() { .new_span( span2 .clone() - .with_field(expect::field("arg1").with_value(&3usize).only()), + .with_fields(expect::field("arg1").with_value(&3usize).only()), ) .enter(span2.clone()) .exit(span2.clone()) @@ -171,7 +171,7 @@ fn generics() { let (subscriber, handle) = subscriber::mock() .new_span( - span.clone().with_field( + span.clone().with_fields( expect::field("arg1") .with_value(&format_args!("Foo")) .and(expect::field("arg2").with_value(&format_args!("false"))), @@ -204,7 +204,7 @@ fn methods() { let (subscriber, handle) = subscriber::mock() .new_span( - span.clone().with_field( + span.clone().with_fields( expect::field("self") .with_value(&format_args!("Foo")) .and(expect::field("arg1").with_value(&42usize)), @@ -236,7 +236,7 @@ fn impl_trait_return_type() { let (subscriber, handle) = subscriber::mock() .new_span( span.clone() - .with_field(expect::field("x").with_value(&10usize).only()), + .with_fields(expect::field("x").with_value(&10usize).only()), ) .enter(span.clone()) .exit(span.clone()) diff --git a/tracing-mock/README.md b/tracing-mock/README.md index 299a737534..04b8dc41b2 100644 --- a/tracing-mock/README.md +++ b/tracing-mock/README.md @@ -121,7 +121,7 @@ let span = expect::span().named("yak_shaving"); let (subscriber, handle) = subscriber::mock() .new_span( span.clone() - .with_field(expect::field("number_of_yaks").with_value(&yak_count).only()), + .with_fields(expect::field("number_of_yaks").with_value(&yak_count).only()), ) .enter(span.clone()) .event( diff --git a/tracing-mock/src/event.rs b/tracing-mock/src/event.rs index 103d663605..6c4cbf7e71 100644 --- a/tracing-mock/src/event.rs +++ b/tracing-mock/src/event.rs @@ -364,7 +364,7 @@ impl ExpectedEvent { /// /// # Examples /// - /// The explicit parent is matched by name: + /// The contextual parent is matched by name: /// /// ``` /// use tracing::subscriber::with_default; diff --git a/tracing-mock/src/layer.rs b/tracing-mock/src/layer.rs index ab48171b9d..fa3c8ab28b 100644 --- a/tracing-mock/src/layer.rs +++ b/tracing-mock/src/layer.rs @@ -431,7 +431,7 @@ impl MockLayerBuilder { /// let span = expect::span() /// .at_level(tracing::Level::INFO) /// .named("the span we're testing") - /// .with_field(expect::field("testing").with_value(&"yes")); + /// .with_fields(expect::field("testing").with_value(&"yes")); /// let (layer, handle) = layer::mock() /// .new_span(span) /// .run_with_handle(); @@ -455,7 +455,7 @@ impl MockLayerBuilder { /// let span = expect::span() /// .at_level(tracing::Level::INFO) /// .named("the span we're testing") - /// .with_field(expect::field("testing").with_value(&"yes")); + /// .with_fields(expect::field("testing").with_value(&"yes")); /// let (layer, handle) = layer::mock() /// .new_span(span) /// .run_with_handle(); diff --git a/tracing-mock/src/span.rs b/tracing-mock/src/span.rs index 9af084fe69..176c33a938 100644 --- a/tracing-mock/src/span.rs +++ b/tracing-mock/src/span.rs @@ -1,16 +1,128 @@ +//! Define expectations to match and validate spans. +//! +//! The [`ExpectedSpan`] and [`NewSpan`] structs define expectations +//! for spans to be matched by the mock subscriber API in the +//! [`subscriber`] module. +//! +//! Expected spans should be created with [`expect::span`] and a +//! chain of method calls describing the assertions made about the +//! span. Expectations about the lifecycle of the span can be set on the [`MockSubscriber`]. +//! +//! # Examples +//! +//! ``` +//! use tracing_mock::{subscriber, expect}; +//! +//! let span = expect::span() +//! .named("interesting_span") +//! .at_level(tracing::Level::INFO); +//! +//! let (subscriber, handle) = subscriber::mock() +//! .enter(span.clone()) +//! .exit(span) +//! .run_with_handle(); +//! +//! tracing::subscriber::with_default(subscriber, || { +//! let span = tracing::info_span!("interesting_span"); +//! let _guard = span.enter(); +//! }); +//! +//! handle.assert_finished(); +//! ``` +//! +//! The following example asserts the name, level, parent, and fields of the span: +//! +//! ``` +//! use tracing_mock::{subscriber, expect}; +//! +//! let span = expect::span() +//! .named("interesting_span") +//! .at_level(tracing::Level::INFO); +//! let new_span = span +//! .clone() +//! .with_fields(expect::field("field.name").with_value(&"field_value")) +//! .with_explicit_parent(Some("parent_span")); +//! +//! let (subscriber, handle) = subscriber::mock() +//! .new_span(expect::span().named("parent_span")) +//! .new_span(new_span) +//! .enter(span.clone()) +//! .exit(span) +//! .run_with_handle(); +//! +//! tracing::subscriber::with_default(subscriber, || { +//! let parent = tracing::info_span!("parent_span"); +//! +//! let span = tracing::info_span!( +//! parent: parent.id(), +//! "interesting_span", +//! field.name = "field_value", +//! ); +//! let _guard = span.enter(); +//! }); +//! +//! handle.assert_finished(); +//! ``` +//! +//! All expectations must be met for the test to pass. For example, +//! the following test will fail due to a mismatch in the spans' names: +//! +//! ```should_panic +//! use tracing_mock::{subscriber, expect}; +//! +//! let span = expect::span() +//! .named("interesting_span") +//! .at_level(tracing::Level::INFO); +//! +//! let (subscriber, handle) = subscriber::mock() +//! .enter(span.clone()) +//! .exit(span) +//! .run_with_handle(); +//! +//! tracing::subscriber::with_default(subscriber, || { +//! let span = tracing::info_span!("another_span"); +//! let _guard = span.enter(); +//! }); +//! +//! handle.assert_finished(); +//! ``` +//! +//! [`MockSubscriber`]: struct@crate::subscriber::MockSubscriber +//! [`subscriber`]: mod@crate::subscriber +//! [`expect::span`]: fn@crate::expect::span #![allow(missing_docs)] -use super::{expect, field::ExpectedFields, metadata::ExpectedMetadata, Parent}; +use crate::{ + expect, field::ExpectedFields, metadata::ExpectedMetadata, subscriber::SpanState, Parent, +}; use std::fmt; /// A mock span. /// /// This is intended for use with the mock subscriber API in the -/// `subscriber` module. +/// [`subscriber`] module. +/// +/// [`subscriber`]: mod@crate::subscriber #[derive(Clone, Default, Eq, PartialEq)] pub struct ExpectedSpan { pub(crate) metadata: ExpectedMetadata, } +/// A mock new span. +/// +/// **Note**: This struct contains expectations that can only be asserted +/// on when expecting a new span via [`MockSubscriber::new_span`]. They +/// cannot be validated on [`MockSubscriber::enter`], +/// [`MockSubscriber::exit`], or any other method on [`MockSubscriber`] +/// that takes an `ExpectedSpan`. +/// +/// For more details on how to use this struct, see the documentation +/// on the [`subscriber`] module. +/// +/// [`subscriber`]: mod@crate::subscriber +/// [`MockSubscriber`]: struct@crate::subscriber::MockSubscriber +/// [`MockSubscriber::enter`]: fn@crate::subscriber::MockSubscriber::enter +/// [`MockSubscriber::exit`]: fn@crate::subscriber::MockSubscriber::exit +/// [`MockSubscriber::new_span`]: fn@crate::subscriber::MockSubscriber::new_span #[derive(Default, Eq, PartialEq)] pub struct NewSpan { pub(crate) span: ExpectedSpan, @@ -26,6 +138,47 @@ where } impl ExpectedSpan { + /// Sets a name to expect when matching a span. + /// + /// If an event is recorded with a name that differs from the one provided to this method, the expectation will fail. + /// + /// # Examples + /// + /// ``` + /// use tracing_mock::{subscriber, expect}; + /// + /// let span = expect::span().named("span name"); + /// + /// let (subscriber, handle) = subscriber::mock() + /// .enter(span) + /// .run_with_handle(); + /// + /// tracing::subscriber::with_default(subscriber, || { + /// let span = tracing::info_span!("span name"); + /// let _guard = span.enter(); + /// }); + /// + /// handle.assert_finished(); + /// ``` + /// + /// When the span name is different, the assertion will fail: + /// + /// ```should_panic + /// use tracing_mock::{subscriber, expect}; + /// + /// let span = expect::span().named("span name"); + /// + /// let (subscriber, handle) = subscriber::mock() + /// .enter(span) + /// .run_with_handle(); + /// + /// tracing::subscriber::with_default(subscriber, || { + /// let span = tracing::info_span!("a different span name"); + /// let _guard = span.enter(); + /// }); + /// + /// handle.assert_finished(); + /// ``` pub fn named(self, name: I) -> Self where I: Into, @@ -38,6 +191,50 @@ impl ExpectedSpan { } } + /// Sets the [`Level`](tracing::Level) to expect when matching a span. + /// + /// If an span is record with a level that differs from the one provided to this method, the expectation will fail. + /// + /// # Examples + /// + /// ``` + /// use tracing_mock::{subscriber, expect}; + /// + /// let span = expect::span() + /// .at_level(tracing::Level::INFO); + /// + /// let (subscriber, handle) = subscriber::mock() + /// .enter(span) + /// .run_with_handle(); + /// + /// tracing::subscriber::with_default(subscriber, || { + /// let span = tracing::info_span!("span"); + /// let _guard = span.enter(); + /// }); + /// + /// handle.assert_finished(); + /// ``` + /// + /// Expecting a span at `INFO` level will fail if the event is + /// recorded at any other level: + /// + /// ```should_panic + /// use tracing_mock::{subscriber, expect}; + /// + /// let span = expect::span() + /// .at_level(tracing::Level::INFO); + /// + /// let (subscriber, handle) = subscriber::mock() + /// .enter(span) + /// .run_with_handle(); + /// + /// tracing::subscriber::with_default(subscriber, || { + /// let span = tracing::warn_span!("a serious span"); + /// let _guard = span.enter(); + /// }); + /// + /// handle.assert_finished(); + /// ``` pub fn at_level(self, level: tracing::Level) -> Self { Self { metadata: ExpectedMetadata { @@ -47,6 +244,50 @@ impl ExpectedSpan { } } + /// Sets the target to expect when matching a span. + /// + /// If an event is recorded with a target that doesn't match the + /// provided target, this expectation will fail. + /// + /// # Examples + /// + /// ``` + /// use tracing_mock::{subscriber, expect}; + /// + /// let span = expect::span() + /// .with_target("some_target"); + /// + /// let (subscriber, handle) = subscriber::mock() + /// .enter(span) + /// .run_with_handle(); + /// + /// tracing::subscriber::with_default(subscriber, || { + /// let span = tracing::info_span!(target: "some_target", "span"); + /// let _guard = span.enter(); + /// }); + /// + /// handle.assert_finished(); + /// ``` + /// + /// The test will fail if the target is different: + /// + /// ```should_panic + /// use tracing_mock::{subscriber, expect}; + /// + /// let span = expect::span() + /// .with_target("some_target"); + /// + /// let (subscriber, handle) = subscriber::mock() + /// .enter(span) + /// .run_with_handle(); + /// + /// tracing::subscriber::with_default(subscriber, || { + /// let span = tracing::info_span!(target: "a_different_target", "span"); + /// let _guard = span.enter(); + /// }); + /// + /// handle.assert_finished(); + /// ``` pub fn with_target(self, target: I) -> Self where I: Into, @@ -59,6 +300,100 @@ impl ExpectedSpan { } } + /// Configures this `ExpectedSpan` to expect an explicit parent + /// span or to be an explicit root. + /// + /// **Note**: This method returns a [`NewSpan`] and as such, this + /// expectation can only be validated when expecting a new span via + /// [`MockSubscriber::new_span`]. It cannot be validated on + /// [`MockSubscriber::enter`], [`MockSubscriber::exit`], or any other + /// method on [`MockSubscriber`] that takes an `ExpectedSpan`. + /// + /// An _explicit_ parent span is one passed to the `span!` macro in the + /// `parent:` field. + /// + /// If `Some("parent_name")` is passed to `with_explicit_parent` then, + /// the provided string is the name of the parent span to expect. + /// + /// To expect that a span is recorded with no parent, `None` + /// can be passed to `with_explicit_parent` instead. + /// + /// If a span is recorded without an explicit parent, or if the + /// explicit parent has a different name, this expectation will + /// fail. + /// + /// # Examples + /// + /// The explicit parent is matched by name: + /// + /// ``` + /// use tracing_mock::{subscriber, expect}; + /// + /// let span = expect::span() + /// .with_explicit_parent(Some("parent_span")); + /// + /// let (subscriber, handle) = subscriber::mock() + /// .new_span(expect::span().named("parent_span")) + /// .new_span(span) + /// .run_with_handle(); + /// + /// tracing::subscriber::with_default(subscriber, || { + /// let parent = tracing::info_span!("parent_span"); + /// tracing::info_span!(parent: parent.id(), "span"); + /// }); + /// + /// handle.assert_finished(); + /// ``` + /// + /// In the following example, the expected span is an explicit root: + /// + /// ``` + /// use tracing_mock::{subscriber, expect}; + /// + /// let span = expect::span() + /// .with_explicit_parent(None); + /// + /// let (subscriber, handle) = subscriber::mock() + /// .new_span(span) + /// .run_with_handle(); + /// + /// tracing::subscriber::with_default(subscriber, || { + /// tracing::info_span!(parent: None, "span"); + /// }); + /// + /// handle.assert_finished(); + /// ``` + /// + /// In the example below, the expectation fails because the + /// span is *contextually*—as opposed to explicitly—within the span + /// `parent_span`: + /// + /// ```should_panic + /// use tracing_mock::{subscriber, expect}; + /// + /// let parent_span = expect::span().named("parent_span"); + /// let span = expect::span() + /// .with_explicit_parent(Some("parent_span")); + /// + /// let (subscriber, handle) = subscriber::mock() + /// .new_span(parent_span.clone()) + /// .enter(parent_span) + /// .new_span(span) + /// .run_with_handle(); + /// + /// tracing::subscriber::with_default(subscriber, || { + /// let parent = tracing::info_span!("parent_span"); + /// let _guard = parent.enter(); + /// tracing::info_span!("span"); + /// }); + /// + /// handle.assert_finished(); + /// ``` + /// + /// [`MockSubscriber`]: struct@crate::subscriber::MockSubscriber + /// [`MockSubscriber::enter`]: fn@crate::subscriber::MockSubscriber::enter + /// [`MockSubscriber::exit`]: fn@crate::subscriber::MockSubscriber::exit + /// [`MockSubscriber::new_span`]: fn@crate::subscriber::MockSubscriber::new_span pub fn with_explicit_parent(self, parent: Option<&str>) -> NewSpan { let parent = match parent { Some(name) => Parent::Explicit(name.into()), @@ -71,6 +406,99 @@ impl ExpectedSpan { } } + /// Configures this `ExpectedSpan` to expect a + /// contextually-determined parent span, or be a contextual + /// root. + /// + /// **Note**: This method returns a [`NewSpan`] and as such, this + /// expectation can only be validated when expecting a new span via + /// [`MockSubscriber::new_span`]. It cannot be validated on + /// [`MockSubscriber::enter`], [`MockSubscriber::exit`], or any other + /// method on [`MockSubscriber`] that takes an `ExpectedSpan`. + /// + /// The provided string is the name of the parent span to expect. + /// To expect that the event is a contextually-determined root, pass + /// `None` instead. + /// + /// To expect a span with an explicit parent span, use + /// [`ExpectedSpan::with_explicit_parent`]. + /// + /// If a span is recorded which is not inside a span, has an explicitly + /// overridden parent span, or has a differently-named span as its + /// parent, this expectation will fail. + /// + /// # Examples + /// + /// The contextual parent is matched by name: + /// + /// ``` + /// use tracing_mock::{subscriber, expect}; + /// + /// let parent_span = expect::span().named("parent_span"); + /// let span = expect::span() + /// .with_contextual_parent(Some("parent_span")); + /// + /// let (subscriber, handle) = subscriber::mock() + /// .new_span(parent_span.clone()) + /// .enter(parent_span) + /// .new_span(span) + /// .run_with_handle(); + /// + /// tracing::subscriber::with_default(subscriber, || { + /// let parent = tracing::info_span!("parent_span"); + /// let _guard = parent.enter(); + /// tracing::info_span!("span"); + /// }); + /// + /// handle.assert_finished(); + /// ``` + /// + /// In the following example, we expect that the matched span is + /// a contextually-determined root: + /// + /// ``` + /// use tracing_mock::{subscriber, expect}; + /// + /// let span = expect::span() + /// .with_contextual_parent(None); + /// + /// let (subscriber, handle) = subscriber::mock() + /// .new_span(span) + /// .run_with_handle(); + /// + /// tracing::subscriber::with_default(subscriber, || { + /// tracing::info_span!("span"); + /// }); + /// + /// handle.assert_finished(); + /// ``` + /// + /// In the example below, the expectation fails because the + /// span is recorded with an explicit parent: + /// + /// ```should_panic + /// use tracing_mock::{subscriber, expect}; + /// + /// let span = expect::span() + /// .with_contextual_parent(Some("parent_span")); + /// + /// let (subscriber, handle) = subscriber::mock() + /// .new_span(expect::span().named("parent_span")) + /// .new_span(span) + /// .run_with_handle(); + /// + /// tracing::subscriber::with_default(subscriber, || { + /// let parent = tracing::info_span!("parent_span"); + /// tracing::info_span!(parent: parent.id(), "span"); + /// }); + /// + /// handle.assert_finished(); + /// ``` + /// + /// [`MockSubscriber`]: struct@crate::subscriber::MockSubscriber + /// [`MockSubscriber::enter`]: fn@crate::subscriber::MockSubscriber::enter + /// [`MockSubscriber::exit`]: fn@crate::subscriber::MockSubscriber::exit + /// [`MockSubscriber::new_span`]: fn@crate::subscriber::MockSubscriber::new_span pub fn with_contextual_parent(self, parent: Option<&str>) -> NewSpan { let parent = match parent { Some(name) => Parent::Contextual(name.into()), @@ -83,27 +511,95 @@ impl ExpectedSpan { } } - pub fn name(&self) -> Option<&str> { + /// Adds fields to expect when matching a span. + /// + /// **Note**: This method returns a [`NewSpan`] and as such, this + /// expectation can only be validated when expecting a new span via + /// [`MockSubscriber::new_span`]. It cannot be validated on + /// [`MockSubscriber::enter`], [`MockSubscriber::exit`], or any other + /// method on [`MockSubscriber`] that takes an `ExpectedSpan`. + /// + /// If a span is recorded with fields that do not match the provided + /// [`ExpectedFields`], this expectation will fail. + /// + /// If the provided field is not present on the recorded span or + /// if the value for that field diffs, then the expectation + /// will fail. + /// + /// More information on the available validations is available in + /// the [`ExpectedFields`] documentation. + /// + /// # Examples + /// + /// ``` + /// use tracing_mock::{subscriber, expect}; + /// + /// let span = expect::span() + /// .with_fields(expect::field("field.name").with_value(&"field_value")); + /// + /// let (subscriber, handle) = subscriber::mock() + /// .new_span(span) + /// .run_with_handle(); + /// + /// tracing::subscriber::with_default(subscriber, || { + /// tracing::info_span!("span", field.name = "field_value"); + /// }); + /// + /// handle.assert_finished(); + /// ``` + /// + /// A different field value will cause the expectation to fail: + /// + /// ```should_panic + /// use tracing_mock::{subscriber, expect}; + /// + /// let span = expect::span() + /// .with_fields(expect::field("field.name").with_value(&"field_value")); + /// + /// let (subscriber, handle) = subscriber::mock() + /// .new_span(span) + /// .run_with_handle(); + /// + /// tracing::subscriber::with_default(subscriber, || { + /// tracing::info_span!("span", field.name = "different_field_value"); + /// }); + /// + /// handle.assert_finished(); + /// ``` + /// + /// [`ExpectedFields`]: struct@crate::field::ExpectedFields + /// [`MockSubscriber`]: struct@crate::subscriber::MockSubscriber + /// [`MockSubscriber::enter`]: fn@crate::subscriber::MockSubscriber::enter + /// [`MockSubscriber::exit`]: fn@crate::subscriber::MockSubscriber::exit + /// [`MockSubscriber::new_span`]: fn@crate::subscriber::MockSubscriber::new_span + pub fn with_fields(self, fields: I) -> NewSpan + where + I: Into, + { + NewSpan { + span: self, + fields: fields.into(), + ..Default::default() + } + } + + pub(crate) fn name(&self) -> Option<&str> { self.metadata.name.as_ref().map(String::as_ref) } - pub fn level(&self) -> Option { + pub(crate) fn level(&self) -> Option { self.metadata.level } - pub fn target(&self) -> Option<&str> { + pub(crate) fn target(&self) -> Option<&str> { self.metadata.target.as_deref() } - pub fn with_field(self, fields: I) -> NewSpan - where - I: Into, - { - NewSpan { - span: self, - fields: fields.into(), - ..Default::default() - } + pub(crate) fn check(&self, actual: &SpanState, subscriber_name: &str) { + let meta = actual.metadata(); + let name = meta.name(); + self.metadata + .check(meta, format_args!("span `{}`", name), subscriber_name); } } @@ -147,6 +643,13 @@ impl From for NewSpan { } impl NewSpan { + /// Configures this `ExpectedSpan` to expect an explicit parent + /// span or to be an explicit root. + /// + /// For more information and examples, see the documentation on + /// [`ExpectedSpan::with_explicit_parent`]. + /// + /// [`ExpectedSpan::with_explicit_parent`]: fn@crate::span::ExpectedSpan::with_explicit_parent pub fn with_explicit_parent(self, parent: Option<&str>) -> NewSpan { let parent = match parent { Some(name) => Parent::Explicit(name.into()), @@ -158,6 +661,14 @@ impl NewSpan { } } + /// Configures this `NewSpan` to expect a + /// contextually-determined parent span, or to be a contextual + /// root. + /// + /// For more information and examples, see the documentation on + /// [`ExpectedSpan::with_contextual_parent`]. + /// + /// [`ExpectedSpan::with_contextual_parent`]: fn@crate::span::ExpectedSpan::with_contextual_parent pub fn with_contextual_parent(self, parent: Option<&str>) -> NewSpan { let parent = match parent { Some(name) => Parent::Contextual(name.into()), @@ -169,7 +680,13 @@ impl NewSpan { } } - pub fn with_field(self, fields: I) -> NewSpan + /// Adds fields to expect when matching a span. + /// + /// For more information and examples, see the documentation on + /// [`ExpectedSpan::with_fields`]. + /// + /// [`ExpectedSpan::with_fields`]: fn@crate::span::ExpectedSpan::with_fields + pub fn with_fields(self, fields: I) -> NewSpan where I: Into, { @@ -179,7 +696,7 @@ impl NewSpan { } } - pub fn check( + pub(crate) fn check( &mut self, span: &tracing_core::span::Attributes<'_>, get_parent_name: impl FnOnce() -> Option, diff --git a/tracing-mock/src/subscriber.rs b/tracing-mock/src/subscriber.rs index ef7a93b148..83251a3715 100644 --- a/tracing-mock/src/subscriber.rs +++ b/tracing-mock/src/subscriber.rs @@ -158,12 +158,18 @@ use tracing::{ Event, Metadata, Subscriber, }; -struct SpanState { +pub(crate) struct SpanState { name: &'static str, refs: usize, meta: &'static Metadata<'static>, } +impl SpanState { + pub(crate) fn metadata(&self) -> &'static Metadata<'static> { + self.meta + } +} + struct Running) -> bool> { spans: Mutex>, expected: Arc>>, @@ -399,7 +405,7 @@ where /// let span = expect::span() /// .at_level(tracing::Level::INFO) /// .named("the span we're testing") - /// .with_field(expect::field("testing").with_value(&"yes")); + /// .with_fields(expect::field("testing").with_value(&"yes")); /// let (subscriber, handle) = subscriber::mock() /// .new_span(span) /// .run_with_handle(); @@ -420,7 +426,7 @@ where /// let span = expect::span() /// .at_level(tracing::Level::INFO) /// .named("the span we're testing") - /// .with_field(expect::field("testing").with_value(&"yes")); + /// .with_fields(expect::field("testing").with_value(&"yes")); /// let (subscriber, handle) = subscriber::mock() /// .new_span(span) /// .run_with_handle(); @@ -1122,9 +1128,7 @@ where match self.expected.lock().unwrap().pop_front() { None => {} Some(Expect::Enter(ref expected_span)) => { - if let Some(name) = expected_span.name() { - assert_eq!(name, span.name); - } + expected_span.check(span, &self.name); } Some(ex) => ex.bad(&self.name, format_args!("entered span {:?}", span.name)), } @@ -1147,9 +1151,7 @@ where match self.expected.lock().unwrap().pop_front() { None => {} Some(Expect::Exit(ref expected_span)) => { - if let Some(name) = expected_span.name() { - assert_eq!(name, span.name); - } + expected_span.check(span, &self.name); let curr = self.current.lock().unwrap().pop(); assert_eq!( Some(id), diff --git a/tracing-subscriber/tests/env_filter/main.rs b/tracing-subscriber/tests/env_filter/main.rs index 16921814c1..c541197b10 100644 --- a/tracing-subscriber/tests/env_filter/main.rs +++ b/tracing-subscriber/tests/env_filter/main.rs @@ -42,13 +42,13 @@ fn same_name_spans() { expect::span() .named("foo") .at_level(Level::TRACE) - .with_field(expect::field("bar")), + .with_fields(expect::field("bar")), ) .new_span( expect::span() .named("foo") .at_level(Level::TRACE) - .with_field(expect::field("baz")), + .with_fields(expect::field("baz")), ) .only() .run_with_handle(); @@ -275,13 +275,13 @@ mod per_layer_filter { expect::span() .named("foo") .at_level(Level::TRACE) - .with_field(expect::field("bar")), + .with_fields(expect::field("bar")), ) .new_span( expect::span() .named("foo") .at_level(Level::TRACE) - .with_field(expect::field("baz")), + .with_fields(expect::field("baz")), ) .only() .run_with_handle(); diff --git a/tracing-subscriber/tests/env_filter/per_layer.rs b/tracing-subscriber/tests/env_filter/per_layer.rs index 229b9ff776..fe6031f263 100644 --- a/tracing-subscriber/tests/env_filter/per_layer.rs +++ b/tracing-subscriber/tests/env_filter/per_layer.rs @@ -37,13 +37,13 @@ fn same_name_spans() { expect::span() .named("foo") .at_level(Level::TRACE) - .with_field(expect::field("bar")), + .with_fields(expect::field("bar")), ) .new_span( expect::span() .named("foo") .at_level(Level::TRACE) - .with_field(expect::field("baz")), + .with_fields(expect::field("baz")), ) .only() .run_with_handle(); diff --git a/tracing-subscriber/tests/same_len_filters.rs b/tracing-subscriber/tests/same_len_filters.rs index ac0b908d28..7827f5043e 100644 --- a/tracing-subscriber/tests/same_len_filters.rs +++ b/tracing-subscriber/tests/same_len_filters.rs @@ -61,13 +61,13 @@ fn same_num_fields_and_name_len() { expect::span() .named("foo") .at_level(Level::TRACE) - .with_field(expect::field("bar")), + .with_fields(expect::field("bar")), ) .new_span( expect::span() .named("baz") .at_level(Level::TRACE) - .with_field(expect::field("boz")), + .with_fields(expect::field("boz")), ) .only() .run_with_handle(); diff --git a/tracing/tests/span.rs b/tracing/tests/span.rs index 09f1be8954..6a713b6453 100644 --- a/tracing/tests/span.rs +++ b/tracing/tests/span.rs @@ -342,7 +342,7 @@ fn entered_api() { fn moved_field() { let (subscriber, handle) = subscriber::mock() .new_span( - expect::span().named("foo").with_field( + expect::span().named("foo").with_fields( expect::field("bar") .with_value(&display("hello from my span")) .only(), @@ -373,7 +373,7 @@ fn dotted_field_name() { .new_span( expect::span() .named("foo") - .with_field(expect::field("fields.bar").with_value(&true).only()), + .with_fields(expect::field("fields.bar").with_value(&true).only()), ) .only() .run_with_handle(); @@ -389,7 +389,7 @@ fn dotted_field_name() { fn borrowed_field() { let (subscriber, handle) = subscriber::mock() .new_span( - expect::span().named("foo").with_field( + expect::span().named("foo").with_fields( expect::field("bar") .with_value(&display("hello from my span")) .only(), @@ -432,7 +432,7 @@ fn move_field_out_of_struct() { }; let (subscriber, handle) = subscriber::mock() .new_span( - expect::span().named("foo").with_field( + expect::span().named("foo").with_fields( expect::field("x") .with_value(&debug(3.234)) .and(expect::field("y").with_value(&debug(-1.223))) @@ -442,7 +442,7 @@ fn move_field_out_of_struct() { .new_span( expect::span() .named("bar") - .with_field(expect::field("position").with_value(&debug(&pos)).only()), + .with_fields(expect::field("position").with_value(&debug(&pos)).only()), ) .run_with_handle(); @@ -465,7 +465,7 @@ fn move_field_out_of_struct() { fn float_values() { let (subscriber, handle) = subscriber::mock() .new_span( - expect::span().named("foo").with_field( + expect::span().named("foo").with_fields( expect::field("x") .with_value(&3.234) .and(expect::field("y").with_value(&-1.223)) @@ -492,7 +492,7 @@ fn add_field_after_new_span() { .new_span( expect::span() .named("foo") - .with_field(expect::field("bar").with_value(&5) + .with_fields(expect::field("bar").with_value(&5) .and(expect::field("baz").with_value).only()), ) .record( @@ -549,7 +549,7 @@ fn add_fields_only_after_new_span() { fn record_new_value_for_field() { let (subscriber, handle) = subscriber::mock() .new_span( - expect::span().named("foo").with_field( + expect::span().named("foo").with_fields( expect::field("bar") .with_value(&5) .and(expect::field("baz").with_value(&false)) @@ -580,7 +580,7 @@ fn record_new_value_for_field() { fn record_new_values_for_fields() { let (subscriber, handle) = subscriber::mock() .new_span( - expect::span().named("foo").with_field( + expect::span().named("foo").with_fields( expect::field("bar") .with_value(&4) .and(expect::field("baz").with_value(&false)) @@ -781,7 +781,7 @@ fn contextual_child() { fn display_shorthand() { let (subscriber, handle) = subscriber::mock() .new_span( - expect::span().named("my_span").with_field( + expect::span().named("my_span").with_fields( expect::field("my_field") .with_value(&display("hello world")) .only(), @@ -801,7 +801,7 @@ fn display_shorthand() { fn debug_shorthand() { let (subscriber, handle) = subscriber::mock() .new_span( - expect::span().named("my_span").with_field( + expect::span().named("my_span").with_fields( expect::field("my_field") .with_value(&debug("hello world")) .only(), @@ -821,7 +821,7 @@ fn debug_shorthand() { fn both_shorthands() { let (subscriber, handle) = subscriber::mock() .new_span( - expect::span().named("my_span").with_field( + expect::span().named("my_span").with_fields( expect::field("display_field") .with_value(&display("hello world")) .and(expect::field("debug_field").with_value(&debug("hello world"))) @@ -842,7 +842,7 @@ fn both_shorthands() { fn constant_field_name() { let (subscriber, handle) = subscriber::mock() .new_span( - expect::span().named("my_span").with_field( + expect::span().named("my_span").with_fields( expect::field("foo") .with_value(&"bar") .and(expect::field("constant string").with_value(&"also works")) diff --git a/tracing/tests/subscriber.rs b/tracing/tests/subscriber.rs index f676efeee8..1b9862879f 100644 --- a/tracing/tests/subscriber.rs +++ b/tracing/tests/subscriber.rs @@ -60,7 +60,7 @@ fn event_macros_dont_infinite_loop() { fn boxed_subscriber() { let (subscriber, handle) = subscriber::mock() .new_span( - expect::span().named("foo").with_field( + expect::span().named("foo").with_fields( expect::field("bar") .with_value(&display("hello from my span")) .only(), @@ -93,7 +93,7 @@ fn arced_subscriber() { let (subscriber, handle) = subscriber::mock() .new_span( - expect::span().named("foo").with_field( + expect::span().named("foo").with_fields( expect::field("bar") .with_value(&display("hello from my span")) .only(),