diff --git a/tracing-attributes/tests/parents.rs b/tracing-attributes/tests/parents.rs index d4559415d..e6db581ff 100644 --- a/tracing-attributes/tests/parents.rs +++ b/tracing-attributes/tests/parents.rs @@ -21,23 +21,16 @@ fn default_parent_test() { .new_span( contextual_parent .clone() - .with_contextual_parent(None) - .with_explicit_parent(None), - ) - .new_span( - child - .clone() - .with_contextual_parent(Some("contextual_parent")) - .with_explicit_parent(None), + .with_ancestry(expect::is_contextual_root()), ) + .new_span(child.clone().with_ancestry(expect::is_contextual_root())) .enter(child.clone()) .exit(child.clone()) .enter(contextual_parent.clone()) .new_span( child .clone() - .with_contextual_parent(Some("contextual_parent")) - .with_explicit_parent(None), + .with_ancestry(expect::has_contextual_parent("contextual_parent")), ) .enter(child.clone()) .exit(child) @@ -68,20 +61,14 @@ fn explicit_parent_test() { .new_span( contextual_parent .clone() - .with_contextual_parent(None) - .with_explicit_parent(None), - ) - .new_span( - explicit_parent - .with_contextual_parent(None) - .with_explicit_parent(None), + .with_ancestry(expect::is_contextual_root()), ) + .new_span(explicit_parent.with_ancestry(expect::is_contextual_root())) .enter(contextual_parent.clone()) .new_span( child .clone() - .with_contextual_parent(Some("contextual_parent")) - .with_explicit_parent(Some("explicit_parent")), + .with_ancestry(expect::has_explicit_parent("explicit_parent")), ) .enter(child.clone()) .exit(child) diff --git a/tracing-futures/tests/std_future.rs b/tracing-futures/tests/std_future.rs index 050d7491c..4e1b597fb 100644 --- a/tracing-futures/tests/std_future.rs +++ b/tracing-futures/tests/std_future.rs @@ -69,13 +69,21 @@ fn span_on_drop() { let subscriber = subscriber::mock() .enter(expect::span().named("foo")) - .event(expect::event().at_level(Level::INFO)) + .event( + expect::event() + .with_ancestry(expect::has_contextual_parent("foo")) + .at_level(Level::INFO), + ) .exit(expect::span().named("foo")) .enter(expect::span().named("foo")) .exit(expect::span().named("foo")) .drop_span(expect::span().named("foo")) .enter(expect::span().named("bar")) - .event(expect::event().at_level(Level::INFO)) + .event( + expect::event() + .with_ancestry(expect::has_contextual_parent("bar")) + .at_level(Level::INFO), + ) .exit(expect::span().named("bar")) .drop_span(expect::span().named("bar")) .only() diff --git a/tracing-mock/src/ancestry.rs b/tracing-mock/src/ancestry.rs new file mode 100644 index 000000000..ee661d45e --- /dev/null +++ b/tracing-mock/src/ancestry.rs @@ -0,0 +1,148 @@ +//! Define the ancestry of an event or span. +//! +//! See the documentation on the [`Ancestry`] enum for further details. + +use tracing_core::{ + span::{self, Attributes}, + Event, +}; + +/// The ancestry of an event or span. +/// +/// An event or span can have an explicitly assigned parent, or be an explicit root. Otherwise, +/// an event or span may have a contextually assigned parent or in the final case will be a +/// contextual root. +#[derive(Debug, Eq, PartialEq)] +pub enum Ancestry { + /// The event or span has an explicitly assigned parent (created with `parent: span_id`) with + /// the specified name. + HasExplicitParent(String), + /// The event or span is an explicitly defined root. It was created with `parent: None` and + /// has no parent. + IsExplicitRoot, + /// The event or span has a contextually assigned parent with the specified name. It has no + /// explicitly assigned parent, nor has it been explicitly defined as a root (it was created + /// without the `parent:` directive). There was a span in context when this event or span was + /// created. + HasContextualParent(String), + /// The event or span is a contextual root. It has no explicitly assigned parent, nor has it + /// been explicitly defined as a root (it was created without the `parent:` directive). + /// Additionally, no span was in context when this event or span was created. + IsContextualRoot, +} + +impl Ancestry { + #[track_caller] + pub(crate) fn check( + &self, + actual_ancestry: &Ancestry, + ctx: impl std::fmt::Display, + collector_name: &str, + ) { + let expected_description = |ancestry: &Ancestry| match ancestry { + Self::IsExplicitRoot => "be an explicit root".to_string(), + Self::HasExplicitParent(name) => format!("have an explicit parent with name='{name}'"), + Self::IsContextualRoot => "be a contextual root".to_string(), + Self::HasContextualParent(name) => { + format!("have a contextual parent with name='{name}'") + } + }; + + let actual_description = |ancestry: &Ancestry| match ancestry { + Self::IsExplicitRoot => "was actually an explicit root".to_string(), + Self::HasExplicitParent(name) => { + format!("actually has an explicit parent with name='{name}'") + } + Self::IsContextualRoot => "was actually a contextual root".to_string(), + Self::HasContextualParent(name) => { + format!("actually has a contextual parent with name='{name}'") + } + }; + + assert_eq!( + self, + actual_ancestry, + "[{collector_name}] expected {ctx} to {expected_description}, but {actual_description}", + expected_description = expected_description(self), + actual_description = actual_description(actual_ancestry) + ); + } +} + +pub(crate) trait HasAncestry { + fn is_contextual(&self) -> bool; + + fn is_root(&self) -> bool; + + fn parent(&self) -> Option<&span::Id>; +} + +impl HasAncestry for &Event<'_> { + fn is_contextual(&self) -> bool { + (self as &Event<'_>).is_contextual() + } + + fn is_root(&self) -> bool { + (self as &Event<'_>).is_root() + } + + fn parent(&self) -> Option<&span::Id> { + (self as &Event<'_>).parent() + } +} + +impl HasAncestry for &Attributes<'_> { + fn is_contextual(&self) -> bool { + (self as &Attributes<'_>).is_contextual() + } + + fn is_root(&self) -> bool { + (self as &Attributes<'_>).is_root() + } + + fn parent(&self) -> Option<&span::Id> { + (self as &Attributes<'_>).parent() + } +} + +/// Determines the ancestry of an actual span or event. +/// +/// The rules for determining the ancestry are as follows: +/// +/// +------------+--------------+-----------------+---------------------+ +/// | Contextual | Current Span | Explicit Parent | Ancestry | +/// +------------+--------------+-----------------+---------------------+ +/// | Yes | Yes | - | HasContextualParent | +/// | Yes | No | - | IsContextualRoot | +/// | No | - | Yes | HasExplicitParent | +/// | No | - | No | IsExplicitRoot | +/// +------------+--------------+-----------------+---------------------+ +pub(crate) fn get_ancestry( + item: impl HasAncestry, + lookup_current: impl FnOnce() -> Option, + span_name: impl FnOnce(&span::Id) -> Option<&str>, +) -> Ancestry { + if item.is_contextual() { + if let Some(parent_id) = lookup_current() { + let contextual_parent_name = span_name(&parent_id).expect( + "tracing-mock: contextual parent cannot \ + be looked up by ID. Was it recorded correctly?", + ); + Ancestry::HasContextualParent(contextual_parent_name.to_string()) + } else { + Ancestry::IsContextualRoot + } + } else if item.is_root() { + Ancestry::IsExplicitRoot + } else { + let parent_id = item.parent().expect( + "tracing-mock: is_contextual=false is_root=false \ + but no explicit parent found. This is a bug!", + ); + let explicit_parent_name = span_name(parent_id).expect( + "tracing-mock: explicit parent cannot be looked \ + up by ID. Is the provided Span ID valid: {parent_id}", + ); + Ancestry::HasExplicitParent(explicit_parent_name.to_string()) + } +} diff --git a/tracing-mock/src/event.rs b/tracing-mock/src/event.rs index 79d99d49d..b122d553e 100644 --- a/tracing-mock/src/event.rs +++ b/tracing-mock/src/event.rs @@ -29,7 +29,7 @@ //! [`subscriber`]: mod@crate::subscriber //! [`expect::event`]: fn@crate::expect::event #![allow(missing_docs)] -use super::{expect, field, metadata::ExpectedMetadata, span, Parent}; +use crate::{ancestry::Ancestry, expect, field, metadata::ExpectedMetadata, span}; use std::fmt; @@ -42,7 +42,7 @@ use std::fmt; #[derive(Default, Eq, PartialEq)] pub struct ExpectedEvent { pub(super) fields: Option, - pub(super) parent: Option, + pub(super) ancestry: Option, pub(super) in_spans: Option>, pub(super) metadata: ExpectedMetadata, } @@ -253,32 +253,30 @@ impl ExpectedEvent { } } - /// Configures this `ExpectedEvent` to expect an explicit parent span - /// when matching events or to be an explicit root. + /// Configures this `ExpectedEvent` to expect the specified [`Ancestry`]. + /// An event's ancestry indicates whether is has a parent or is a root, and + /// whether the parent is explicitly or contextually assigned. /// - /// An _explicit_ parent span is one passed to the `span!` macro in the - /// `parent:` field. + /// An _explicit_ parent span is one passed to the `event!` macro in the + /// `parent:` field. If no `parent:` field is specified, then the event + /// will have a contextually determined parent or be a contextual root if + /// there is no parent. /// - /// 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 an event is recorded with `parent: None`, `None` - /// can be passed to `with_explicit_parent` instead. - /// - /// If an event is recorded without an explicit parent, or if the - /// explicit parent has a different name, this expectation will - /// fail. + /// If the parent is different from the provided one, this expectation + /// will fail. /// /// # Examples /// - /// The explicit parent is matched by name: + /// If `expect::has_explicit_parent("parent_name")` is passed + /// `with_ancestry` then the provided string is the name of the explicit + /// parent span to expect. /// /// ``` /// use tracing::subscriber::with_default; /// use tracing_mock::{subscriber, expect}; /// /// let event = expect::event() - /// .with_explicit_parent(Some("parent_span")); + /// .with_ancestry(expect::has_explicit_parent("parent_span")); /// /// let (subscriber, handle) = subscriber::mock() /// .event(event) @@ -300,29 +298,7 @@ impl ExpectedEvent { /// use tracing_mock::{subscriber, expect}; /// /// let event = expect::event() - /// .with_explicit_parent(None); - /// - /// let (subscriber, handle) = subscriber::mock() - /// .event(event) - /// .run_with_handle(); - /// - /// with_default(subscriber, || { - /// tracing::info!(parent: None, field = &"value"); - /// }); - /// - /// handle.assert_finished(); - /// ``` - /// - /// In the example below, the expectation fails because the - /// event is contextually (rather than explicitly) within the span - /// `parent_span`: - /// - /// ```should_panic - /// use tracing::subscriber::with_default; - /// use tracing_mock::{subscriber, expect}; - /// - /// let event = expect::event() - /// .with_explicit_parent(Some("parent_span")); + /// .with_ancestry(expect::is_explicit_root()); /// /// let (subscriber, handle) = subscriber::mock() /// .enter(expect::span()) @@ -330,48 +306,23 @@ impl ExpectedEvent { /// .run_with_handle(); /// /// with_default(subscriber, || { - /// let parent = tracing::info_span!("parent_span"); - /// let _guard = parent.enter(); - /// tracing::info!(field = &"value"); + /// let _guard = tracing::info_span!("contextual parent").entered(); + /// tracing::info!(parent: None, field = &"value"); /// }); /// /// handle.assert_finished(); /// ``` - pub fn with_explicit_parent(self, parent: Option<&str>) -> ExpectedEvent { - let parent = match parent { - Some(name) => Parent::Explicit(name.into()), - None => Parent::ExplicitRoot, - }; - Self { - parent: Some(parent), - ..self - } - } - - /// Configures this `ExpectedEvent` to match an event with a - /// contextually-determined parent span. - /// - /// 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 an event with an explicit parent span, use - /// [`ExpectedEvent::with_explicit_parent`]. - /// - /// If an event is recorded which is not inside a span, has an explicitly - /// overridden parent span, or with a differently-named span as its - /// parent, this expectation will fail. - /// - /// # Examples /// - /// The contextual parent is matched by name: + /// When `expect::has_contextual_parent("parent_name")` is passed to + /// `with_ancestry` then the provided string is the name of the contextual + /// parent span to expect. /// /// ``` /// use tracing::subscriber::with_default; /// use tracing_mock::{subscriber, expect}; /// /// let event = expect::event() - /// .with_contextual_parent(Some("parent_span")); + /// .with_ancestry(expect::has_contextual_parent("parent_span")); /// /// let (subscriber, handle) = subscriber::mock() /// .enter(expect::span()) @@ -387,14 +338,15 @@ impl ExpectedEvent { /// handle.assert_finished(); /// ``` /// - /// Matching an event recorded outside of a span: + /// Matching an event recorded outside of a span, a contextual + /// root: /// /// ``` /// use tracing::subscriber::with_default; /// use tracing_mock::{subscriber, expect}; /// /// let event = expect::event() - /// .with_contextual_parent(None); + /// .with_ancestry(expect::is_contextual_root()); /// /// let (subscriber, handle) = subscriber::mock() /// .event(event) @@ -407,15 +359,16 @@ impl ExpectedEvent { /// handle.assert_finished(); /// ``` /// - /// In the example below, the expectation fails because the - /// event is recorded with an explicit parent: + /// In the example below, the expectation fails because the event is + /// recorded with an explicit parent, however a contextual parent is + /// expected. /// /// ```should_panic /// use tracing::subscriber::with_default; /// use tracing_mock::{subscriber, expect}; /// /// let event = expect::event() - /// .with_contextual_parent(Some("parent_span")); + /// .with_ancestry(expect::has_contextual_parent("parent_span")); /// /// let (subscriber, handle) = subscriber::mock() /// .enter(expect::span()) @@ -429,13 +382,9 @@ impl ExpectedEvent { /// /// handle.assert_finished(); /// ``` - pub fn with_contextual_parent(self, parent: Option<&str>) -> ExpectedEvent { - let parent = match parent { - Some(name) => Parent::Contextual(name.into()), - None => Parent::ContextualRoot, - }; + pub fn with_ancestry(self, ancenstry: Ancestry) -> ExpectedEvent { Self { - parent: Some(parent), + ancestry: Some(ancenstry), ..self } } @@ -557,7 +506,7 @@ impl ExpectedEvent { pub(crate) fn check( &mut self, event: &tracing::Event<'_>, - get_parent_name: impl FnOnce() -> Option, + get_ancestry: impl FnOnce() -> Ancestry, subscriber_name: &str, ) { let meta = event.metadata(); @@ -577,14 +526,9 @@ impl ExpectedEvent { checker.finish(); } - if let Some(ref expected_parent) = self.parent { - let actual_parent = get_parent_name(); - expected_parent.check_parent_name( - actual_parent.as_deref(), - event.parent().cloned(), - event.metadata().name(), - subscriber_name, - ) + if let Some(ref expected_ancestry) = self.ancestry { + let actual_ancestry = get_ancestry(); + expected_ancestry.check(&actual_ancestry, event.metadata().name(), subscriber_name); } } } @@ -615,7 +559,7 @@ impl fmt::Debug for ExpectedEvent { s.field("fields", fields); } - if let Some(ref parent) = self.parent { + if let Some(ref parent) = self.ancestry { s.field("parent", &format_args!("{:?}", parent)); } diff --git a/tracing-mock/src/expect.rs b/tracing-mock/src/expect.rs index 353bc52f5..95ad3176c 100644 --- a/tracing-mock/src/expect.rs +++ b/tracing-mock/src/expect.rs @@ -1,9 +1,10 @@ use std::fmt; use crate::{ + ancestry::Ancestry, event::ExpectedEvent, field::{ExpectedField, ExpectedFields, ExpectedValue}, - span::{ExpectedSpan, NewSpan}, + span::{ExpectedId, ExpectedSpan, NewSpan}, }; #[derive(Debug, Eq, PartialEq)] @@ -51,6 +52,47 @@ pub fn span() -> ExpectedSpan { } } +/// Returns a new, unset `ExpectedId`. +/// +/// The `ExpectedId` needs to be attached to a [`NewSpan`] or an +/// [`ExpectedSpan`] passed to [`MockCollector::new_span`] to +/// ensure that it gets set. When the a clone of the same +/// `ExpectedSpan` is attached to an [`ExpectedSpan`] and passed to +/// any other method on [`MockCollector`] that accepts it, it will +/// ensure that it is exactly the same span used across those +/// distinct expectations. +/// +/// For more details on how to use this struct, see the documentation +/// on [`ExpectedSpan::with_id`]. +/// +/// [`MockCollector`]: struct@crate::collector::MockCollector +/// [`MockCollector::new_span`]: fn@crate::collector::MockCollector::new_span +pub fn id() -> ExpectedId { + ExpectedId::new_unset() +} + +/// Convenience function that returns [`Ancestry::IsContextualRoot`]. +pub fn is_contextual_root() -> Ancestry { + Ancestry::IsContextualRoot +} + +/// Convenience function that returns [`Ancestry::HasContextualParent`] with +/// provided name. +pub fn has_contextual_parent>(name: S) -> Ancestry { + Ancestry::HasContextualParent(name.into()) +} + +/// Convenience function that returns [`Ancestry::IsExplicitRoot`]. +pub fn is_explicit_root() -> Ancestry { + Ancestry::IsExplicitRoot +} + +/// Convenience function that returns [`Ancestry::HasExplicitParent`] with +/// provided name. +pub fn has_explicit_parent>(name: S) -> Ancestry { + Ancestry::HasExplicitParent(name.into()) +} + impl Expect { pub(crate) fn bad(&self, name: impl AsRef, what: fmt::Arguments<'_>) { let name = name.as_ref(); diff --git a/tracing-mock/src/layer.rs b/tracing-mock/src/layer.rs index 7c36b092b..19882e7f9 100644 --- a/tracing-mock/src/layer.rs +++ b/tracing-mock/src/layer.rs @@ -116,6 +116,7 @@ //! //! [`Layer`]: trait@tracing_subscriber::layer::Layer use crate::{ + ancestry::{get_ancestry, Ancestry, HasAncestry}, event::ExpectedEvent, expect::Expect, span::{ExpectedSpan, NewSpan}, @@ -904,8 +905,7 @@ where match self.expected.lock().unwrap().pop_front() { None => {} Some(Expect::Event(mut expected)) => { - let get_parent_name = || cx.event_span(event).map(|span| span.name().to_string()); - expected.check(event, get_parent_name, &self.name); + expected.check(event, || context_get_ancestry(event, &cx), &self.name); if let Some(expected_scope) = expected.scope_mut() { self.check_event_scope(cx.event_scope(event), expected_scope); @@ -936,13 +936,7 @@ where let was_expected = matches!(expected.front(), Some(Expect::NewSpan(_))); if was_expected { if let Expect::NewSpan(mut expected) = expected.pop_front().unwrap() { - let get_parent_name = || { - span.parent() - .and_then(|id| cx.span(id)) - .or_else(|| cx.lookup_current()) - .map(|span| span.name().to_string()) - }; - expected.check(span, get_parent_name, &self.name); + expected.check(span, || context_get_ancestry(span, &cx), &self.name); } } } @@ -1042,6 +1036,17 @@ where } } +fn context_get_ancestry(item: impl HasAncestry, ctx: &Context<'_, C>) -> Ancestry +where + C: Subscriber + for<'a> LookupSpan<'a>, +{ + get_ancestry( + item, + || ctx.lookup_current().map(|s| s.id()), + |span_id| ctx.span(span_id).map(|span| span.name()), + ) +} + impl fmt::Debug for MockLayer { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut s = f.debug_struct("ExpectSubscriber"); diff --git a/tracing-mock/src/lib.rs b/tracing-mock/src/lib.rs index abf29777a..70f169eec 100644 --- a/tracing-mock/src/lib.rs +++ b/tracing-mock/src/lib.rs @@ -1,4 +1,5 @@ #![doc = include_str!("../README.md")] +pub mod ancestry; pub mod event; pub mod expect; pub mod field; @@ -8,88 +9,3 @@ pub mod subscriber; #[cfg(feature = "tracing-subscriber")] pub mod layer; - -#[derive(Debug, Eq, PartialEq)] -pub enum Parent { - ContextualRoot, - Contextual(String), - ExplicitRoot, - Explicit(String), -} - -impl Parent { - pub fn check_parent_name( - &self, - parent_name: Option<&str>, - provided_parent: Option, - ctx: impl std::fmt::Display, - subscriber_name: &str, - ) { - match self { - Parent::ExplicitRoot => { - assert!( - provided_parent.is_none(), - "[{}] expected {} to be an explicit root, but its parent was actually {:?} (name: {:?})", - subscriber_name, - ctx, - provided_parent, - parent_name, - ); - } - Parent::Explicit(expected_parent) => { - assert!( - provided_parent.is_some(), - "[{}] expected {} to have explicit parent {}, but it has no explicit parent", - subscriber_name, - ctx, - expected_parent, - ); - assert_eq!( - Some(expected_parent.as_ref()), - parent_name, - "[{}] expected {} to have explicit parent {}, but its parent was actually {:?} (name: {:?})", - subscriber_name, - ctx, - expected_parent, - provided_parent, - parent_name, - ); - } - Parent::ContextualRoot => { - assert!( - provided_parent.is_none(), - "[{}] expected {} to be a contextual root, but its parent was actually {:?} (name: {:?})", - subscriber_name, - ctx, - provided_parent, - parent_name, - ); - assert!( - parent_name.is_none(), - "[{}] expected {} to be contextual a root, but we were inside span {:?}", - subscriber_name, - ctx, - parent_name, - ); - } - Parent::Contextual(expected_parent) => { - assert!(provided_parent.is_none(), - "[{}] expected {} to have a contextual parent\nbut it has the explicit parent {:?} (name: {:?})", - subscriber_name, - ctx, - provided_parent, - parent_name, - ); - assert_eq!( - Some(expected_parent.as_ref()), - parent_name, - "[{}] expected {} to have contextual parent {:?}, but got {:?}", - subscriber_name, - ctx, - expected_parent, - parent_name, - ); - } - } - } -} diff --git a/tracing-mock/src/span.rs b/tracing-mock/src/span.rs index 176c33a93..c12655055 100644 --- a/tracing-mock/src/span.rs +++ b/tracing-mock/src/span.rs @@ -41,7 +41,7 @@ //! let new_span = span //! .clone() //! .with_fields(expect::field("field.name").with_value(&"field_value")) -//! .with_explicit_parent(Some("parent_span")); +//! .with_ancestry(expect::has_explicit_parent("parent_span")); //! //! let (subscriber, handle) = subscriber::mock() //! .new_span(expect::span().named("parent_span")) @@ -92,9 +92,16 @@ //! [`expect::span`]: fn@crate::expect::span #![allow(missing_docs)] use crate::{ - expect, field::ExpectedFields, metadata::ExpectedMetadata, subscriber::SpanState, Parent, + ancestry::Ancestry, expect, field::ExpectedFields, metadata::ExpectedMetadata, + subscriber::SpanState, +}; +use std::{ + error, fmt, + sync::{ + atomic::{AtomicU64, Ordering}, + Arc, + }, }; -use std::fmt; /// A mock span. /// @@ -104,6 +111,7 @@ use std::fmt; /// [`subscriber`]: mod@crate::subscriber #[derive(Clone, Default, Eq, PartialEq)] pub struct ExpectedSpan { + pub(crate) id: Option, pub(crate) metadata: ExpectedMetadata, } @@ -127,7 +135,7 @@ pub struct ExpectedSpan { pub struct NewSpan { pub(crate) span: ExpectedSpan, pub(crate) fields: ExpectedFields, - pub(crate) parent: Option, + pub(crate) ancestry: Option, } pub fn named(name: I) -> ExpectedSpan @@ -137,6 +145,24 @@ where expect::span().named(name) } +/// A mock span ID. +/// +/// This ID makes it possible to link together calls to different +/// [`MockSubscriber`] span methods that take an [`ExpectedSpan`] in +/// addition to those that take a [`NewSpan`]. +/// +/// Use [`expect::id`] to construct a new, unset `ExpectedId`. +/// +/// For more details on how to use this struct, see the documentation +/// on [`ExpectedSpan::with_id`]. +/// +/// [`expect::id`]: fn@crate::expect::id +/// [`MockSubscriber`]: struct@crate::subscriber::MockSubscriber +#[derive(Clone, Default)] +pub struct ExpectedId { + inner: Arc, +} + impl ExpectedSpan { /// Sets a name to expect when matching a span. /// @@ -188,6 +214,100 @@ impl ExpectedSpan { name: Some(name.into()), ..self.metadata }, + ..self + } + } + + /// Sets the `ID` to expect when matching a span. + /// + /// The [`ExpectedId`] can be used to differentiate spans that are + /// otherwise identical. An [`ExpectedId`] needs to be attached to + /// an `ExpectedSpan` or [`NewSpan`] which is passed to + /// [`MockSubscriber::new_span`]. The same [`ExpectedId`] can then + /// be used to match the exact same span when passed to + /// [`MockSubscriber::enter`], [`MockSubscriber::exit`], and + /// [`MockSubscriber::drop_span`]. + /// + /// This is especially useful when `tracing-mock` is being used to + /// test the traces being generated within your own crate, in which + /// case you may need to distinguish between spans which have + /// identical metadata but different field values, which can + /// otherwise only be checked in [`MockSubscriber::new_span`]. + /// + /// # Examples + /// + /// Here we expect that the span that is created first is entered + /// second: + /// + /// ``` + /// use tracing_mock::{subscriber, expect}; + /// let id1 = expect::id(); + /// let span1 = expect::span().named("span").with_id(id1.clone()); + /// let id2 = expect::id(); + /// let span2 = expect::span().named("span").with_id(id2.clone()); + /// + /// let (subscriber, handle) = subscriber::mock() + /// .new_span(span1.clone()) + /// .new_span(span2.clone()) + /// .enter(span2) + /// .enter(span1) + /// .run_with_handle(); + /// + /// tracing::subscriber::with_default(subscriber, || { + /// fn create_span() -> tracing::Span { + /// tracing::info_span!("span") + /// } + /// + /// let span1 = create_span(); + /// let span2 = create_span(); + /// + /// let _guard2 = span2.enter(); + /// let _guard1 = span1.enter(); + /// }); + /// + /// handle.assert_finished(); + /// ``` + /// + /// If the order that the spans are entered changes, the test will + /// fail: + /// + /// ```should_panic + /// use tracing_mock::{subscriber, expect}; + /// let id1 = expect::id(); + /// let span1 = expect::span().named("span").with_id(id1.clone()); + /// let id2 = expect::id(); + /// let span2 = expect::span().named("span").with_id(id2.clone()); + /// + /// let (subscriber, handle) = subscriber::mock() + /// .new_span(span1.clone()) + /// .new_span(span2.clone()) + /// .enter(span2) + /// .enter(span1) + /// .run_with_handle(); + /// + /// tracing::subscriber::with_default(subscriber, || { + /// fn create_span() -> tracing::Span { + /// tracing::info_span!("span") + /// } + /// + /// let span1 = create_span(); + /// let span2 = create_span(); + /// + /// let _guard1 = span1.enter(); + /// let _guard2 = span2.enter(); + /// }); + /// + /// handle.assert_finished(); + /// ``` + /// + /// [`MockSubscriber::new_span`]: fn@crate::subscriber::MockSubscriber::new_span + /// [`MockSubscriber::enter`]: fn@crate::subscriber::MockSubscriber::enter + /// [`MockSubscriber::exit`]: fn@crate::subscriber::MockSubscriber::exit + /// [`MockSubscriber::drop_span`]: fn@crate::subscriber::MockSubscriber::drop_span + pub fn with_id(self, id: ExpectedId) -> Self { + Self { + id: Some(id), + ..self } } @@ -241,6 +361,7 @@ impl ExpectedSpan { level: Some(level), ..self.metadata }, + ..self } } @@ -297,11 +418,13 @@ impl ExpectedSpan { target: Some(target.into()), ..self.metadata }, + ..self } } - /// Configures this `ExpectedSpan` to expect an explicit parent - /// span or to be an explicit root. + /// Configures this `ExpectedSpan` to expect the specified [`Ancestry`]. A + /// span's ancestry indicates whether it has a parent or is a root span + /// and whether the parent is explitly or contextually assigned. /// /// **Note**: This method returns a [`NewSpan`] and as such, this /// expectation can only be validated when expecting a new span via @@ -310,27 +433,24 @@ impl ExpectedSpan { /// 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. + /// `parent:` field. If no `parent:` field is specified, then the span + /// will have a contextually determined parent or be a contextual root if + /// there is no parent. /// - /// 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. + /// If the ancestry is different from the provided one, this expectation + /// will fail. /// /// # Examples /// - /// The explicit parent is matched by name: + /// If `expect::has_explicit_parent("parent_name")` is passed + /// `with_ancestry` then the provided string is the name of the explicit + /// parent span to expect. /// /// ``` /// use tracing_mock::{subscriber, expect}; /// /// let span = expect::span() - /// .with_explicit_parent(Some("parent_span")); + /// .with_ancestry(expect::has_explicit_parent("parent_span")); /// /// let (subscriber, handle) = subscriber::mock() /// .new_span(expect::span().named("parent_span")) @@ -351,7 +471,7 @@ impl ExpectedSpan { /// use tracing_mock::{subscriber, expect}; /// /// let span = expect::span() - /// .with_explicit_parent(None); + /// .with_ancestry(expect::is_explicit_root()); /// /// let (subscriber, handle) = subscriber::mock() /// .new_span(span) @@ -373,7 +493,7 @@ impl ExpectedSpan { /// /// let parent_span = expect::span().named("parent_span"); /// let span = expect::span() - /// .with_explicit_parent(Some("parent_span")); + /// .with_ancestry(expect::has_explicit_parent("parent_span")); /// /// let (subscriber, handle) = subscriber::mock() /// .new_span(parent_span.clone()) @@ -390,53 +510,15 @@ impl ExpectedSpan { /// 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()), - None => Parent::ExplicitRoot, - }; - NewSpan { - parent: Some(parent), - span: self, - ..Default::default() - } - } - - /// 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: + /// In the following example, we expect that the matched span is + /// a contextually-determined root: /// /// ``` /// use tracing_mock::{subscriber, expect}; /// /// let parent_span = expect::span().named("parent_span"); /// let span = expect::span() - /// .with_contextual_parent(Some("parent_span")); + /// .with_ancestry(expect::has_contextual_parent("parent_span")); /// /// let (subscriber, handle) = subscriber::mock() /// .new_span(parent_span.clone()) @@ -460,7 +542,7 @@ impl ExpectedSpan { /// use tracing_mock::{subscriber, expect}; /// /// let span = expect::span() - /// .with_contextual_parent(None); + /// .with_ancestry(expect::is_contextual_root()); /// /// let (subscriber, handle) = subscriber::mock() /// .new_span(span) @@ -474,22 +556,26 @@ impl ExpectedSpan { /// ``` /// /// In the example below, the expectation fails because the - /// span is recorded with an explicit parent: + /// 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_contextual_parent(Some("parent_span")); + /// .with_ancestry(expect::has_explicit_parent("parent_span")); /// /// let (subscriber, handle) = subscriber::mock() - /// .new_span(expect::span().named("parent_span")) + /// .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"); - /// tracing::info_span!(parent: parent.id(), "span"); + /// let _guard = parent.enter(); + /// tracing::info_span!("span"); /// }); /// /// handle.assert_finished(); @@ -499,13 +585,9 @@ impl ExpectedSpan { /// [`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()), - None => Parent::ContextualRoot, - }; + pub fn with_ancestry(self, ancestry: Ancestry) -> NewSpan { NewSpan { - parent: Some(parent), + ancestry: Some(ancestry), span: self, ..Default::default() } @@ -598,6 +680,15 @@ impl ExpectedSpan { pub(crate) fn check(&self, actual: &SpanState, subscriber_name: &str) { let meta = actual.metadata(); let name = meta.name(); + + if let Some(expected_id) = &self.id { + expected_id.check( + actual.id(), + format_args!("span `{}`", name), + subscriber_name, + ); + } + self.metadata .check(meta, format_args!("span `{}`", name), subscriber_name); } @@ -643,39 +734,15 @@ 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()), - None => Parent::ExplicitRoot, - }; - NewSpan { - parent: Some(parent), - ..self - } - } - - /// Configures this `NewSpan` to expect a - /// contextually-determined parent span, or to be a contextual - /// root. + /// Configures this `NewSpan` to expect the specified [`Ancestry`]. A + /// span's ancestry indicates whether it has a parent or is a root span + /// and whether the parent is explitly or contextually assigned. /// /// 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()), - None => Parent::ContextualRoot, - }; + /// [`ExpectedSpan::with_ancestry`]. + pub fn with_ancestry(self, ancestry: Ancestry) -> NewSpan { NewSpan { - parent: Some(parent), + ancestry: Some(ancestry), ..self } } @@ -699,7 +766,7 @@ impl NewSpan { pub(crate) fn check( &mut self, span: &tracing_core::span::Attributes<'_>, - get_parent_name: impl FnOnce() -> Option, + get_ancestry: impl FnOnce() -> Ancestry, subscriber_name: &str, ) { let meta = span.metadata(); @@ -711,14 +778,13 @@ impl NewSpan { span.record(&mut checker); checker.finish(); - if let Some(expected_parent) = self.parent.as_ref() { - let actual_parent = get_parent_name(); - expected_parent.check_parent_name( - actual_parent.as_deref(), - span.parent().cloned(), + if let Some(ref expected_ancestry) = self.ancestry { + let actual_ancestry = get_ancestry(); + expected_ancestry.check( + &actual_ancestry, format_args!("span `{}`", name), subscriber_name, - ) + ); } } } @@ -749,7 +815,7 @@ impl fmt::Debug for NewSpan { s.field("target", &target); } - if let Some(ref parent) = self.parent { + if let Some(ref parent) = self.ancestry { s.field("parent", &format_args!("{:?}", parent)); } @@ -760,3 +826,69 @@ impl fmt::Debug for NewSpan { s.finish() } } + +impl PartialEq for ExpectedId { + fn eq(&self, other: &Self) -> bool { + self.inner.load(Ordering::Relaxed) == other.inner.load(Ordering::Relaxed) + } +} + +impl Eq for ExpectedId {} + +impl ExpectedId { + const UNSET: u64 = 0; + + pub(crate) fn new_unset() -> Self { + Self { + inner: Arc::new(AtomicU64::from(Self::UNSET)), + } + } + + pub(crate) fn set(&self, span_id: u64) -> Result<(), SetActualSpanIdError> { + self.inner + .compare_exchange(Self::UNSET, span_id, Ordering::Relaxed, Ordering::Relaxed) + .map_err(|current| SetActualSpanIdError { + previous_span_id: current, + new_span_id: span_id, + })?; + Ok(()) + } + + pub(crate) fn check(&self, actual: u64, ctx: fmt::Arguments<'_>, subscriber_name: &str) { + let id = self.inner.load(Ordering::Relaxed); + + assert!( + id != Self::UNSET, + "\n[{}] expected {} to have expected ID set, but it hasn't been, \ + perhaps this `ExpectedId` wasn't used in a call to `MockSubscriber::new_span()`?", + subscriber_name, + ctx, + ); + + assert_eq!( + id, actual, + "\n[{}] expected {} to have ID `{}`, but it has `{}` instead", + subscriber_name, ctx, id, actual, + ); + } +} + +#[derive(Debug)] +pub(crate) struct SetActualSpanIdError { + previous_span_id: u64, + new_span_id: u64, +} + +impl fmt::Display for SetActualSpanIdError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "Could not set `ExpecedId` to {new}, \ + it had already been set to {previous}", + new = self.new_span_id, + previous = self.previous_span_id + ) + } +} + +impl error::Error for SetActualSpanIdError {} diff --git a/tracing-mock/src/subscriber.rs b/tracing-mock/src/subscriber.rs index 1d5b36bd8..924be2684 100644 --- a/tracing-mock/src/subscriber.rs +++ b/tracing-mock/src/subscriber.rs @@ -138,6 +138,7 @@ //! [`Subscriber`]: trait@tracing::Subscriber //! [`MockSubscriber`]: struct@crate::subscriber::MockSubscriber use crate::{ + ancestry::get_ancestry, event::ExpectedEvent, expect::Expect, field::ExpectedFields, @@ -159,12 +160,17 @@ use tracing::{ }; pub(crate) struct SpanState { + id: u64, name: &'static str, refs: usize, meta: &'static Metadata<'static>, } impl SpanState { + pub(crate) fn id(&self) -> u64 { + self.id + } + pub(crate) fn metadata(&self) -> &'static Metadata<'static> { self.meta } @@ -387,7 +393,7 @@ where /// This function accepts `Into` instead of /// [`ExpectedSpan`] directly, so it can be used to test /// span fields and the span parent. This is because a - /// subscriber only receives the span fields and parent when + /// collector only receives the span fields and parent when /// a span is created, not when it is entered. /// /// The new span doesn't need to be entered for this expectation @@ -1030,20 +1036,24 @@ where { if expected.scope_mut().is_some() { unimplemented!( - "Expected scope for events is not supported with `MockSubscriber`." + "Expected scope for events is not supported with `MockCollector`." ) } } - let get_parent_name = || { - let stack = self.current.lock().unwrap(); - let spans = self.spans.lock().unwrap(); - event - .parent() - .and_then(|id| spans.get(id)) - .or_else(|| stack.last().and_then(|id| spans.get(id))) - .map(|s| s.name.to_string()) + let event_get_ancestry = || { + get_ancestry( + event, + || self.lookup_current(), + |span_id| { + self.spans + .lock() + .unwrap() + .get(span_id) + .map(|span| span.name) + }, + ) }; - expected.check(event, get_parent_name, &self.name); + expected.check(event, event_get_ancestry, &self.name); } Some(ex) => ex.bad(&self.name, format_args!("observed event {:#?}", event)), } @@ -1100,19 +1110,27 @@ where let mut spans = self.spans.lock().unwrap(); if was_expected { if let Expect::NewSpan(mut expected) = expected.pop_front().unwrap() { - let get_parent_name = || { - let stack = self.current.lock().unwrap(); - span.parent() - .and_then(|id| spans.get(id)) - .or_else(|| stack.last().and_then(|id| spans.get(id))) - .map(|s| s.name.to_string()) - }; - expected.check(span, get_parent_name, &self.name); + if let Some(expected_id) = &expected.span.id { + expected_id.set(id.into_u64()).unwrap(); + } + + expected.check( + span, + || { + get_ancestry( + span, + || self.lookup_current(), + |span_id| spans.get(span_id).map(|span| span.name), + ) + }, + &self.name, + ); } } spans.insert( id.clone(), SpanState { + id: id.into_u64(), name: meta.name(), refs: 1, meta, @@ -1256,6 +1274,16 @@ where } } +impl Running +where + F: Fn(&Metadata<'_>) -> bool, +{ + fn lookup_current(&self) -> Option { + let stack = self.current.lock().unwrap(); + stack.last().cloned() + } +} + impl MockHandle { #[cfg(feature = "tracing-subscriber")] pub(crate) fn new(expected: Arc>>, name: String) -> Self { diff --git a/tracing-mock/tests/event_ancestry.rs b/tracing-mock/tests/event_ancestry.rs new file mode 100644 index 000000000..ff659615a --- /dev/null +++ b/tracing-mock/tests/event_ancestry.rs @@ -0,0 +1,346 @@ +//! Tests assertions for the parent made on [`ExpectedEvent`]. +//! +//! The tests in this module completely cover the positive and negative cases +//! when expecting that an event is a contextual or explicit root or expecting +//! that an event has a specific contextual or explicit parent. +//! +//! [`ExpectedEvent`]: crate::event::ExpectedEvent +use tracing::subscriber::with_default; +use tracing_mock::{expect, subscriber}; + +#[test] +fn contextual_parent() { + let event = expect::event().with_ancestry(expect::has_contextual_parent("contextual parent")); + + let (subscriber, handle) = subscriber::mock() + .enter(expect::span()) + .event(event) + .run_with_handle(); + + with_default(subscriber, || { + let _guard = tracing::info_span!("contextual parent").entered(); + tracing::info!(field = &"value"); + }); + + handle.assert_finished(); +} + +#[test] +#[should_panic( + expected = "to have a contextual parent with name='contextual parent', but \ + actually has a contextual parent with name='another parent'" +)] +fn contextual_parent_wrong_name() { + let event = expect::event().with_ancestry(expect::has_contextual_parent("contextual parent")); + + let (subscriber, handle) = subscriber::mock() + .enter(expect::span()) + .event(event) + .run_with_handle(); + + with_default(subscriber, || { + let _guard = tracing::info_span!("another parent").entered(); + tracing::info!(field = &"value"); + }); + + handle.assert_finished(); +} + +#[test] +#[should_panic( + expected = "to have a contextual parent with name='contextual parent', but was actually a \ + contextual root" +)] +fn expect_contextual_parent_actual_contextual_root() { + let event = expect::event().with_ancestry(expect::has_contextual_parent("contextual parent")); + + let (subscriber, handle) = subscriber::mock().event(event).run_with_handle(); + + with_default(subscriber, || { + tracing::info!(field = &"value"); + }); + + handle.assert_finished(); +} + +#[test] +#[should_panic( + expected = "to have a contextual parent with name='contextual parent', but actually has an \ + explicit parent with name='explicit parent'" +)] +fn expect_contextual_parent_actual_explicit_parent() { + let event = expect::event().with_ancestry(expect::has_contextual_parent("contextual parent")); + + let (subscriber, handle) = subscriber::mock().event(event).run_with_handle(); + + with_default(subscriber, || { + let span = tracing::info_span!("explicit parent"); + tracing::info!(parent: span.id(), field = &"value"); + }); + + handle.assert_finished(); +} + +#[test] +#[should_panic( + expected = "to have a contextual parent with name='contextual parent', but was actually an \ + explicit root" +)] +fn expect_contextual_parent_actual_explicit_root() { + let event = expect::event().with_ancestry(expect::has_contextual_parent("contextual parent")); + + let (subscriber, handle) = subscriber::mock() + .enter(expect::span()) + .event(event) + .run_with_handle(); + + with_default(subscriber, || { + let _guard = tracing::info_span!("contextual parent").entered(); + tracing::info!(parent: None, field = &"value"); + }); + + handle.assert_finished(); +} + +#[test] +fn contextual_root() { + let event = expect::event().with_ancestry(expect::is_contextual_root()); + + let (subscriber, handle) = subscriber::mock().event(event).run_with_handle(); + + with_default(subscriber, || { + tracing::info!(field = &"value"); + }); + + handle.assert_finished(); +} + +#[test] +#[should_panic( + expected = "to be a contextual root, but actually has a contextual parent with \ + name='contextual parent'" +)] +fn expect_contextual_root_actual_contextual_parent() { + let event = expect::event().with_ancestry(expect::is_contextual_root()); + + let (subscriber, handle) = subscriber::mock() + .enter(expect::span()) + .event(event) + .run_with_handle(); + + with_default(subscriber, || { + let _guard = tracing::info_span!("contextual parent").entered(); + tracing::info!(field = &"value"); + }); + + handle.assert_finished(); +} + +#[test] +#[should_panic( + expected = "to be a contextual root, but actually has an explicit parent with \ + name='explicit parent'" +)] +fn expect_contextual_root_actual_explicit_parent() { + let event = expect::event().with_ancestry(expect::is_contextual_root()); + + let (subscriber, handle) = subscriber::mock().event(event).run_with_handle(); + + with_default(subscriber, || { + let span = tracing::info_span!("explicit parent"); + tracing::info!(parent: span.id(), field = &"value"); + }); + + handle.assert_finished(); +} + +#[test] +#[should_panic(expected = "to be a contextual root, but was actually an explicit root")] +fn expect_contextual_root_actual_explicit_root() { + let event = expect::event().with_ancestry(expect::is_contextual_root()); + + let (subscriber, handle) = subscriber::mock() + .enter(expect::span()) + .event(event) + .run_with_handle(); + + with_default(subscriber, || { + let _guard = tracing::info_span!("contextual parent").entered(); + tracing::info!(parent: None, field = &"value"); + }); + + handle.assert_finished(); +} + +#[test] +fn explicit_parent() { + let event = expect::event().with_ancestry(expect::has_explicit_parent("explicit parent")); + + let (subscriber, handle) = subscriber::mock().event(event).run_with_handle(); + + with_default(subscriber, || { + let span = tracing::info_span!("explicit parent"); + tracing::info!(parent: span.id(), field = &"value"); + }); + + handle.assert_finished(); +} + +#[test] +#[should_panic( + expected = "to have an explicit parent with name='explicit parent', but actually has an \ + explicit parent with name='another parent'" +)] +fn explicit_parent_wrong_name() { + let event = expect::event().with_ancestry(expect::has_explicit_parent("explicit parent")); + + let (subscriber, handle) = subscriber::mock().event(event).run_with_handle(); + + with_default(subscriber, || { + let span = tracing::info_span!("another parent"); + tracing::info!(parent: span.id(), field = &"value"); + }); + + handle.assert_finished(); +} + +#[test] +#[should_panic( + expected = "to have an explicit parent with name='explicit parent', but actually has a \ + contextual parent with name='contextual parent'" +)] +fn expect_explicit_parent_actual_contextual_parent() { + let event = expect::event().with_ancestry(expect::has_explicit_parent("explicit parent")); + + let (subscriber, handle) = subscriber::mock() + .enter(expect::span()) + .event(event) + .run_with_handle(); + + with_default(subscriber, || { + let _guard = tracing::info_span!("contextual parent").entered(); + tracing::info!(field = &"value"); + }); + + handle.assert_finished(); +} + +#[test] +#[should_panic( + expected = "to have an explicit parent with name='explicit parent', but was actually a \ + contextual root" +)] +fn expect_explicit_parent_actual_contextual_root() { + let event = expect::event().with_ancestry(expect::has_explicit_parent("explicit parent")); + + let (subscriber, handle) = subscriber::mock().event(event).run_with_handle(); + + with_default(subscriber, || { + tracing::info!(field = &"value"); + }); + + handle.assert_finished(); +} + +#[test] +#[should_panic( + expected = "to have an explicit parent with name='explicit parent', but was actually an \ + explicit root" +)] +fn expect_explicit_parent_actual_explicit_root() { + let event = expect::event().with_ancestry(expect::has_explicit_parent("explicit parent")); + + let (subscriber, handle) = subscriber::mock() + .enter(expect::span()) + .event(event) + .run_with_handle(); + + with_default(subscriber, || { + let _guard = tracing::info_span!("contextual parent").entered(); + tracing::info!(parent: None, field = &"value"); + }); + + handle.assert_finished(); +} + +#[test] +fn explicit_root() { + let event = expect::event().with_ancestry(expect::is_explicit_root()); + + let (subscriber, handle) = subscriber::mock() + .enter(expect::span()) + .event(event) + .run_with_handle(); + + with_default(subscriber, || { + let _guard = tracing::info_span!("contextual parent").entered(); + tracing::info!(parent: None, field = &"value"); + }); + + handle.assert_finished(); +} + +#[test] +#[should_panic( + expected = "to be an explicit root, but actually has a contextual parent with \ + name='contextual parent'" +)] +fn expect_explicit_root_actual_contextual_parent() { + let event = expect::event().with_ancestry(expect::is_explicit_root()); + + let (subscriber, handle) = subscriber::mock() + .enter(expect::span()) + .event(event) + .run_with_handle(); + + with_default(subscriber, || { + let _guard = tracing::info_span!("contextual parent").entered(); + tracing::info!(field = &"value"); + }); + + handle.assert_finished(); +} + +#[test] +#[should_panic(expected = "to be an explicit root, but was actually a contextual root")] +fn expect_explicit_root_actual_contextual_root() { + let event = expect::event().with_ancestry(expect::is_explicit_root()); + + let (subscriber, handle) = subscriber::mock().event(event).run_with_handle(); + + with_default(subscriber, || { + tracing::info!(field = &"value"); + }); + + handle.assert_finished(); +} + +#[test] +#[should_panic( + expected = "to be an explicit root, but actually has an explicit parent with name='explicit parent'" +)] +fn expect_explicit_root_actual_explicit_parent() { + let event = expect::event().with_ancestry(expect::is_explicit_root()); + + let (subscriber, handle) = subscriber::mock().event(event).run_with_handle(); + + with_default(subscriber, || { + let span = tracing::info_span!("explicit parent"); + tracing::info!(parent: span.id(), field = &"value"); + }); + + handle.assert_finished(); +} + +#[test] +fn explicit_and_contextual_root_is_explicit() { + let event = expect::event().with_ancestry(expect::is_explicit_root()); + + let (subscriber, handle) = subscriber::mock().event(event).run_with_handle(); + + with_default(subscriber, || { + tracing::info!(parent: None, field = &"value"); + }); + + handle.assert_finished(); +} diff --git a/tracing-mock/tests/span_ancestry.rs b/tracing-mock/tests/span_ancestry.rs new file mode 100644 index 000000000..3c557a301 --- /dev/null +++ b/tracing-mock/tests/span_ancestry.rs @@ -0,0 +1,401 @@ +//! Tests assertions for the parent made on [`ExpectedSpan`]. +//! +//! The tests in this module completely cover the positive and negative cases +//! when expecting that a span is a contextual or explicit root or expecting +//! that a span has a specific contextual or explicit parent. +//! +//! [`ExpectedSpan`]: crate::span::ExpectedSpan +//! +use tracing::subscriber::with_default; +use tracing_mock::{expect, subscriber}; + +#[test] +fn contextual_parent() { + let span = expect::span() + .named("span") + .with_ancestry(expect::has_contextual_parent("contextual parent")); + + let (subscriber, handle) = subscriber::mock() + .enter(expect::span()) + .new_span(span) + .run_with_handle(); + + with_default(subscriber, || { + let _guard = tracing::info_span!("contextual parent").entered(); + tracing::info_span!("span"); + }); + + handle.assert_finished(); +} + +#[test] +#[should_panic( + expected = "to have a contextual parent with name='contextual parent', but \ + actually has a contextual parent with name='another parent'" +)] +fn contextual_parent_wrong_name() { + let span = expect::span() + .named("span") + .with_ancestry(expect::has_contextual_parent("contextual parent")); + + let (subscriber, handle) = subscriber::mock() + .enter(expect::span()) + .new_span(span) + .run_with_handle(); + + with_default(subscriber, || { + let _guard = tracing::info_span!("another parent").entered(); + tracing::info_span!("span"); + }); + + handle.assert_finished(); +} + +#[test] +#[should_panic( + expected = "to have a contextual parent with name='contextual parent', but was actually a \ + contextual root" +)] +fn expect_contextual_parent_actual_contextual_root() { + let span = expect::span() + .named("span") + .with_ancestry(expect::has_contextual_parent("contextual parent")); + + let (subscriber, handle) = subscriber::mock().new_span(span).run_with_handle(); + + with_default(subscriber, || { + tracing::info_span!("span"); + }); + + handle.assert_finished(); +} + +#[test] +#[should_panic( + expected = "to have a contextual parent with name='contextual parent', but actually has an \ + explicit parent with name='explicit parent'" +)] +fn expect_contextual_parent_actual_explicit_parent() { + let span = expect::span() + .named("span") + .with_ancestry(expect::has_contextual_parent("contextual parent")); + + let (subscriber, handle) = subscriber::mock() + .new_span(expect::span()) + .new_span(span) + .run_with_handle(); + + with_default(subscriber, || { + let span = tracing::info_span!("explicit parent"); + tracing::info_span!(parent: span.id(), "span"); + }); + + handle.assert_finished(); +} + +#[test] +#[should_panic( + expected = "to have a contextual parent with name='contextual parent', but was actually an \ + explicit root" +)] +fn expect_contextual_parent_actual_explicit_root() { + let span = expect::span() + .named("span") + .with_ancestry(expect::has_contextual_parent("contextual parent")); + + let (subscriber, handle) = subscriber::mock() + .enter(expect::span()) + .new_span(span) + .run_with_handle(); + + with_default(subscriber, || { + let _guard = tracing::info_span!("contextual parent").entered(); + tracing::info_span!(parent: None, "span"); + }); + + handle.assert_finished(); +} + +#[test] +fn contextual_root() { + let span = expect::span() + .named("span") + .with_ancestry(expect::is_contextual_root()); + + let (subscriber, handle) = subscriber::mock().new_span(span).run_with_handle(); + + with_default(subscriber, || { + tracing::info_span!("span"); + }); + + handle.assert_finished(); +} + +#[test] +#[should_panic( + expected = "to be a contextual root, but actually has a contextual parent with \ + name='contextual parent'" +)] +fn expect_contextual_root_actual_contextual_parent() { + let span = expect::span() + .named("span") + .with_ancestry(expect::is_contextual_root()); + + let (subscriber, handle) = subscriber::mock() + .enter(expect::span()) + .new_span(span) + .run_with_handle(); + + with_default(subscriber, || { + let _guard = tracing::info_span!("contextual parent").entered(); + tracing::info_span!("span"); + }); + + handle.assert_finished(); +} + +#[test] +#[should_panic( + expected = "to be a contextual root, but actually has an explicit parent with \ + name='explicit parent'" +)] +fn expect_contextual_root_actual_explicit_parent() { + let span = expect::span() + .named("span") + .with_ancestry(expect::is_contextual_root()); + + let (subscriber, handle) = subscriber::mock() + .new_span(expect::span()) + .new_span(span) + .run_with_handle(); + + with_default(subscriber, || { + let span = tracing::info_span!("explicit parent"); + tracing::info_span!(parent: span.id(), "span"); + }); + + handle.assert_finished(); +} + +#[test] +#[should_panic(expected = "to be a contextual root, but was actually an explicit root")] +fn expect_contextual_root_actual_explicit_root() { + let span = expect::span() + .named("span") + .with_ancestry(expect::is_contextual_root()); + + let (subscriber, handle) = subscriber::mock() + .enter(expect::span()) + .new_span(span) + .run_with_handle(); + + with_default(subscriber, || { + let _guard = tracing::info_span!("contextual parent").entered(); + tracing::info_span!(parent: None, "span"); + }); + + handle.assert_finished(); +} + +#[test] +fn explicit_parent() { + let span = expect::span() + .named("span") + .with_ancestry(expect::has_explicit_parent("explicit parent")); + + let (subscriber, handle) = subscriber::mock() + .new_span(expect::span()) + .new_span(span) + .run_with_handle(); + + with_default(subscriber, || { + let span = tracing::info_span!("explicit parent"); + tracing::info_span!(parent: span.id(), "span"); + }); + + handle.assert_finished(); +} + +#[test] +#[should_panic( + expected = "to have an explicit parent with name='explicit parent', but actually has an \ + explicit parent with name='another parent'" +)] +fn explicit_parent_wrong_name() { + let span = expect::span() + .named("span") + .with_ancestry(expect::has_explicit_parent("explicit parent")); + + let (subscriber, handle) = subscriber::mock() + .new_span(expect::span()) + .new_span(span) + .run_with_handle(); + + with_default(subscriber, || { + let span = tracing::info_span!("another parent"); + tracing::info_span!(parent: span.id(), "span"); + }); + + handle.assert_finished(); +} + +#[test] +#[should_panic( + expected = "to have an explicit parent with name='explicit parent', but actually has a \ + contextual parent with name='contextual parent'" +)] +fn expect_explicit_parent_actual_contextual_parent() { + let span = expect::span() + .named("span") + .with_ancestry(expect::has_explicit_parent("explicit parent")); + + let (subscriber, handle) = subscriber::mock() + .enter(expect::span()) + .new_span(span) + .run_with_handle(); + + with_default(subscriber, || { + let _guard = tracing::info_span!("contextual parent").entered(); + tracing::info_span!("span"); + }); + + handle.assert_finished(); +} + +#[test] +#[should_panic( + expected = "to have an explicit parent with name='explicit parent', but was actually a \ + contextual root" +)] +fn expect_explicit_parent_actual_contextual_root() { + let span = expect::span() + .named("span") + .with_ancestry(expect::has_explicit_parent("explicit parent")); + + let (subscriber, handle) = subscriber::mock().new_span(span).run_with_handle(); + + with_default(subscriber, || { + tracing::info_span!("span"); + }); + + handle.assert_finished(); +} + +#[test] +#[should_panic( + expected = "to have an explicit parent with name='explicit parent', but was actually an \ + explicit root" +)] +fn expect_explicit_parent_actual_explicit_root() { + let span = expect::span() + .named("span") + .with_ancestry(expect::has_explicit_parent("explicit parent")); + + let (subscriber, handle) = subscriber::mock() + .enter(expect::span()) + .new_span(span) + .run_with_handle(); + + with_default(subscriber, || { + let _guard = tracing::info_span!("contextual parent").entered(); + tracing::info_span!(parent: None, "span"); + }); + + handle.assert_finished(); +} + +#[test] +fn explicit_root() { + let span = expect::span() + .named("span") + .with_ancestry(expect::is_explicit_root()); + + let (subscriber, handle) = subscriber::mock() + .new_span(expect::span()) + .enter(expect::span()) + .new_span(span) + .run_with_handle(); + + with_default(subscriber, || { + let _guard = tracing::info_span!("contextual parent").entered(); + tracing::info_span!(parent: None, "span"); + }); + + handle.assert_finished(); +} + +#[test] +#[should_panic( + expected = "to be an explicit root, but actually has a contextual parent with \ + name='contextual parent'" +)] +fn expect_explicit_root_actual_contextual_parent() { + let span = expect::span() + .named("span") + .with_ancestry(expect::is_explicit_root()); + + let (subscriber, handle) = subscriber::mock() + .enter(expect::span()) + .new_span(span) + .run_with_handle(); + + with_default(subscriber, || { + let _guard = tracing::info_span!("contextual parent").entered(); + tracing::info_span!("span"); + }); + + handle.assert_finished(); +} + +#[test] +#[should_panic(expected = "to be an explicit root, but was actually a contextual root")] +fn expect_explicit_root_actual_contextual_root() { + let span = expect::span() + .named("span") + .with_ancestry(expect::is_explicit_root()); + + let (subscriber, handle) = subscriber::mock().new_span(span).run_with_handle(); + + with_default(subscriber, || { + tracing::info_span!("span"); + }); + + handle.assert_finished(); +} + +#[test] +#[should_panic( + expected = "to be an explicit root, but actually has an explicit parent with name='explicit parent'" +)] +fn expect_explicit_root_actual_explicit_parent() { + let span = expect::span() + .named("span") + .with_ancestry(expect::is_explicit_root()); + + let (subscriber, handle) = subscriber::mock() + .new_span(expect::span()) + .new_span(span) + .run_with_handle(); + + with_default(subscriber, || { + let span = tracing::info_span!("explicit parent"); + tracing::info_span!(parent: span.id(), "span"); + }); + + handle.assert_finished(); +} + +#[test] +fn explicit_and_contextual_root_is_explicit() { + let span = expect::span() + .named("span") + .with_ancestry(expect::is_explicit_root()); + + let (subscriber, handle) = subscriber::mock().new_span(span).run_with_handle(); + + with_default(subscriber, || { + tracing::info_span!(parent: None, "span"); + }); + + handle.assert_finished(); +} diff --git a/tracing/tests/event.rs b/tracing/tests/event.rs index 417b8878b..99e85d558 100644 --- a/tracing/tests/event.rs +++ b/tracing/tests/event.rs @@ -338,7 +338,7 @@ fn both_shorthands() { fn explicit_child() { let (subscriber, handle) = subscriber::mock() .new_span(expect::span().named("foo")) - .event(expect::event().with_explicit_parent(Some("foo"))) + .event(expect::event().with_ancestry(expect::has_explicit_parent("foo"))) .only() .run_with_handle(); @@ -355,11 +355,11 @@ fn explicit_child() { fn explicit_child_at_levels() { let (subscriber, handle) = subscriber::mock() .new_span(expect::span().named("foo")) - .event(expect::event().with_explicit_parent(Some("foo"))) - .event(expect::event().with_explicit_parent(Some("foo"))) - .event(expect::event().with_explicit_parent(Some("foo"))) - .event(expect::event().with_explicit_parent(Some("foo"))) - .event(expect::event().with_explicit_parent(Some("foo"))) + .event(expect::event().with_ancestry(expect::has_explicit_parent("foo"))) + .event(expect::event().with_ancestry(expect::has_explicit_parent("foo"))) + .event(expect::event().with_ancestry(expect::has_explicit_parent("foo"))) + .event(expect::event().with_ancestry(expect::has_explicit_parent("foo"))) + .event(expect::event().with_ancestry(expect::has_explicit_parent("foo"))) .only() .run_with_handle(); diff --git a/tracing/tests/instrument.rs b/tracing/tests/instrument.rs index 9233727d4..eb7e9edbc 100644 --- a/tracing/tests/instrument.rs +++ b/tracing/tests/instrument.rs @@ -35,13 +35,21 @@ fn span_on_drop() { let subscriber = subscriber::mock() .enter(expect::span().named("foo")) - .event(expect::event().at_level(Level::INFO)) + .event( + expect::event() + .with_ancestry(expect::has_contextual_parent("foo")) + .at_level(Level::INFO), + ) .exit(expect::span().named("foo")) .enter(expect::span().named("foo")) .exit(expect::span().named("foo")) .drop_span(expect::span().named("foo")) .enter(expect::span().named("bar")) - .event(expect::event().at_level(Level::INFO)) + .event( + expect::event() + .with_ancestry(expect::has_contextual_parent("bar")) + .at_level(Level::INFO), + ) .exit(expect::span().named("bar")) .drop_span(expect::span().named("bar")) .only() diff --git a/tracing/tests/span.rs b/tracing/tests/span.rs index 6a713b645..bd5bed0d2 100644 --- a/tracing/tests/span.rs +++ b/tracing/tests/span.rs @@ -635,7 +635,11 @@ fn new_span_with_target_and_log_level() { #[test] fn explicit_root_span_is_root() { let (subscriber, handle) = subscriber::mock() - .new_span(expect::span().named("foo").with_explicit_parent(None)) + .new_span( + expect::span() + .named("foo") + .with_ancestry(expect::is_explicit_root()), + ) .only() .run_with_handle(); @@ -652,7 +656,11 @@ fn explicit_root_span_is_root_regardless_of_ctx() { let (subscriber, handle) = subscriber::mock() .new_span(expect::span().named("foo")) .enter(expect::span().named("foo")) - .new_span(expect::span().named("bar").with_explicit_parent(None)) + .new_span( + expect::span() + .named("bar") + .with_ancestry(expect::is_explicit_root()), + ) .exit(expect::span().named("foo")) .only() .run_with_handle(); @@ -674,7 +682,7 @@ fn explicit_child() { .new_span( expect::span() .named("bar") - .with_explicit_parent(Some("foo")), + .with_ancestry(expect::has_explicit_parent("foo")), ) .only() .run_with_handle(); @@ -692,11 +700,31 @@ fn explicit_child() { fn explicit_child_at_levels() { let (subscriber, handle) = subscriber::mock() .new_span(expect::span().named("foo")) - .new_span(expect::span().named("a").with_explicit_parent(Some("foo"))) - .new_span(expect::span().named("b").with_explicit_parent(Some("foo"))) - .new_span(expect::span().named("c").with_explicit_parent(Some("foo"))) - .new_span(expect::span().named("d").with_explicit_parent(Some("foo"))) - .new_span(expect::span().named("e").with_explicit_parent(Some("foo"))) + .new_span( + expect::span() + .named("a") + .with_ancestry(expect::has_explicit_parent("foo")), + ) + .new_span( + expect::span() + .named("b") + .with_ancestry(expect::has_explicit_parent("foo")), + ) + .new_span( + expect::span() + .named("c") + .with_ancestry(expect::has_explicit_parent("foo")), + ) + .new_span( + expect::span() + .named("d") + .with_ancestry(expect::has_explicit_parent("foo")), + ) + .new_span( + expect::span() + .named("e") + .with_ancestry(expect::has_explicit_parent("foo")), + ) .only() .run_with_handle(); @@ -722,7 +750,7 @@ fn explicit_child_regardless_of_ctx() { .new_span( expect::span() .named("baz") - .with_explicit_parent(Some("foo")), + .with_ancestry(expect::has_explicit_parent("foo")), ) .exit(expect::span().named("bar")) .only() @@ -741,7 +769,11 @@ fn explicit_child_regardless_of_ctx() { #[test] fn contextual_root() { let (subscriber, handle) = subscriber::mock() - .new_span(expect::span().named("foo").with_contextual_parent(None)) + .new_span( + expect::span() + .named("foo") + .with_ancestry(expect::is_contextual_root()), + ) .only() .run_with_handle(); @@ -761,7 +793,7 @@ fn contextual_child() { .new_span( expect::span() .named("bar") - .with_contextual_parent(Some("foo")), + .with_ancestry(expect::has_contextual_parent("foo")), ) .exit(expect::span().named("foo")) .only()