From dd9da727685d78c04f46d79e60894c7634f0cbc9 Mon Sep 17 00:00:00 2001 From: Hayden Stainsby Date: Tue, 7 Nov 2023 13:22:18 +0100 Subject: [PATCH] mock: correct contextual/explicit parent assertions When recording the parent of an event or span, the `MockCollector` treats an explicit parent of `None` (i.e. an event or span that is an explicit root) in the same way as if there is no explicit root. This leads to it picking up the contextual parent or treating the event or span as a contextual root. This change refactors the recording of the parent to use `is_contextual` to distinguish whether or not an explicit parent has been specified. The actual parent is also written into an `Ancestry` enum so that the expected and actual values can be compared in a more explicit way. Additionally, the `Ancestry` struct has been moved into its own module and the check behavior has been fixed. The error message has also been unified across all cases. Another problem with the previous API is that the two methods `with_contextual_parent` and `with_explicit_parent` are actually mutually exclusive, a span or event cannot be both of them. It is also a (small) mental leap for the user to go from `with_*_parent(None)` to understanding that this means that a span or event is a root (either contextual or explicit). As such, the API has been reworked into a single method `with_ancestry`, which takes an enum with the following four variants: * `HasExplicitParent(String)` (parent span name) * `IsExplicitRoot` * `HasContextualParent(String)` (parent span name) * `IsContextualRoot` To make the interface as useable as possible, helper functions have been defined in the `expect` module which can be used to create the enum variants. Specifically, these take `Into` parameter for the span name. Given the number of different cases involved in checking ancestry, separate integration tests have been added to `tracing-mock` specifically for testing all the positive and negative cases when asserting on the ancestry of events and spans. There were two tests in `tracing-attributes` which specified both an explicit and a contextual parent. This behavior was never intended to work as all events and spans are either contextual or not. The tests have been corrected to only expect one of the two. Fixes: #2440 --- tracing-attributes/tests/parents.rs | 25 +- tracing-futures/tests/std_future.rs | 4 +- tracing-mock/src/ancestry.rs | 145 ++++++++++ tracing-mock/src/collector.rs | 52 ++-- tracing-mock/src/event.rs | 134 +++------ tracing-mock/src/expect.rs | 23 ++ tracing-mock/src/lib.rs | 86 +----- tracing-mock/src/span.rs | 177 +++--------- tracing-mock/src/subscriber.rs | 25 +- tracing-mock/tests/event_ancestry.rs | 346 +++++++++++++++++++++++ tracing-mock/tests/span_ancestry.rs | 401 +++++++++++++++++++++++++++ tracing/tests/event.rs | 12 +- tracing/tests/instrument.rs | 4 +- tracing/tests/span.rs | 54 +++- 14 files changed, 1110 insertions(+), 378 deletions(-) create mode 100644 tracing-mock/src/ancestry.rs create mode 100644 tracing-mock/tests/event_ancestry.rs create mode 100644 tracing-mock/tests/span_ancestry.rs diff --git a/tracing-attributes/tests/parents.rs b/tracing-attributes/tests/parents.rs index c33e44a74f..2111121b7d 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 d8ec669175..fb2a31c130 100644 --- a/tracing-futures/tests/std_future.rs +++ b/tracing-futures/tests/std_future.rs @@ -71,7 +71,7 @@ fn span_on_drop() { .enter(expect::span().named("foo")) .event( expect::event() - .with_contextual_parent(Some("foo")) + .with_ancestry(expect::has_contextual_parent("foo")) .at_level(Level::INFO), ) .exit(expect::span().named("foo")) @@ -81,7 +81,7 @@ fn span_on_drop() { .enter(expect::span().named("bar")) .event( expect::event() - .with_contextual_parent(Some("bar")) + .with_ancestry(expect::has_contextual_parent("bar")) .at_level(Level::INFO), ) .exit(expect::span().named("bar")) diff --git a/tracing-mock/src/ancestry.rs b/tracing-mock/src/ancestry.rs new file mode 100644 index 0000000000..f3c082c072 --- /dev/null +++ b/tracing-mock/src/ancestry.rs @@ -0,0 +1,145 @@ +//! 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. Additionally, + /// it has no explicitly assigned parent. + HasContextualParent(String), + /// The event or span is a contextual root. It has no contextual parent and also has no + /// explicitly assigned parent. + 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/collector.rs b/tracing-mock/src/collector.rs index a91cdd332d..ab804fdfeb 100644 --- a/tracing-mock/src/collector.rs +++ b/tracing-mock/src/collector.rs @@ -138,6 +138,7 @@ //! [`Collect`]: trait@tracing::Collect //! [`MockCollector`]: struct@crate::collector::MockCollector use crate::{ + ancestry::get_ancestry, event::ExpectedEvent, expect::Expect, field::ExpectedFields, @@ -1034,16 +1035,20 @@ where ) } } - 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,14 +1105,17 @@ 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); + expected.check( + span, + || { + get_ancestry( + span, + || self.lookup_current(), + |span_id| spans.get(span_id).map(|span| span.name), + ) + }, + &self.name, + ); } } spans.insert( @@ -1256,6 +1264,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/src/event.rs b/tracing-mock/src/event.rs index 840867d019..5385baa083 100644 --- a/tracing-mock/src/event.rs +++ b/tracing-mock/src/event.rs @@ -29,7 +29,9 @@ //! [`collector`]: mod@crate::collector //! [`expect::event`]: fn@crate::expect::event #![allow(missing_docs)] -use super::{expect, field, metadata::ExpectedMetadata, span, Parent}; +use crate::ancestry::Ancestry; + +use super::{expect, field, metadata::ExpectedMetadata, span}; use std::fmt; @@ -42,7 +44,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 +255,30 @@ impl ExpectedEvent { } } - /// Configures this `ExpectedEvent` to expect an explicit parent span - /// when matching events or to be an explicit root. - /// - /// 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. + /// 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. /// - /// To expect that an event is recorded with `parent: None`, `None` - /// can be passed to `with_explicit_parent` instead. + /// 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 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 `Parent::Explicit(Some("parent_name"))` is passed to `with_parent` + /// then the provided string is the name of the explicit parent span to + /// expect. /// /// ``` /// use tracing::collect::with_default; /// use tracing_mock::{collector, expect}; /// /// let event = expect::event() - /// .with_explicit_parent(Some("parent_span")); + /// .with_ancestry(expect::has_explicit_parent("parent_span")); /// /// let (collector, handle) = collector::mock() /// .event(event) @@ -300,29 +300,7 @@ impl ExpectedEvent { /// use tracing_mock::{collector, expect}; /// /// let event = expect::event() - /// .with_explicit_parent(None); - /// - /// let (collector, handle) = collector::mock() - /// .event(event) - /// .run_with_handle(); - /// - /// with_default(collector, || { - /// 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::collect::with_default; - /// use tracing_mock::{collector, expect}; - /// - /// let event = expect::event() - /// .with_explicit_parent(Some("parent_span")); + /// .with_ancestry(expect::is_explicit_root()); /// /// let (collector, handle) = collector::mock() /// .enter(expect::span()) @@ -330,48 +308,23 @@ impl ExpectedEvent { /// .run_with_handle(); /// /// with_default(collector, || { - /// 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 `Parent::Contextual(Some("parent_name"))` is passed to + /// `with_parent` then the provided string is the name of the explicit + /// parent span to expect. /// /// ``` /// use tracing::collect::with_default; /// use tracing_mock::{collector, expect}; /// /// let event = expect::event() - /// .with_contextual_parent(Some("parent_span")); + /// .with_ancestry(expect::has_contextual_parent("parent_span")); /// /// let (collector, handle) = collector::mock() /// .enter(expect::span()) @@ -387,14 +340,19 @@ impl ExpectedEvent { /// handle.assert_finished(); /// ``` /// - /// Matching an event recorded outside of a span: + /// In the example below, the expectation fails because the + /// event is contextually (rather than explicitly) within the span + /// `parent_span`: + /// + /// Matching an event recorded outside of a span, a contextual + /// root: /// /// ``` /// use tracing::collect::with_default; /// use tracing_mock::{collector, expect}; /// /// let event = expect::event() - /// .with_contextual_parent(None); + /// .with_ancestry(expect::is_contextual_root()); /// /// let (collector, handle) = collector::mock() /// .event(event) @@ -407,15 +365,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::collect::with_default; /// use tracing_mock::{collector, expect}; /// /// let event = expect::event() - /// .with_contextual_parent(Some("parent_span")); + /// .with_ancestry(expect::has_contextual_parent("parent_span")); /// /// let (collector, handle) = collector::mock() /// .enter(expect::span()) @@ -429,13 +388,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 +512,7 @@ impl ExpectedEvent { pub(crate) fn check( &mut self, event: &tracing::Event<'_>, - get_parent_name: impl FnOnce() -> Option, + get_ancestry: impl FnOnce() -> Ancestry, collector_name: &str, ) { let meta = event.metadata(); @@ -577,14 +532,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(), - collector_name, - ) + if let Some(ref expected_ancestry) = self.ancestry { + let actual_ancestry = get_ancestry(); + expected_ancestry.check(&actual_ancestry, event.metadata().name(), collector_name); } } } @@ -615,7 +565,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 353bc52f5f..eb70f993ae 100644 --- a/tracing-mock/src/expect.rs +++ b/tracing-mock/src/expect.rs @@ -1,6 +1,7 @@ use std::fmt; use crate::{ + ancestry::Ancestry, event::ExpectedEvent, field::{ExpectedField, ExpectedFields, ExpectedValue}, span::{ExpectedSpan, NewSpan}, @@ -51,6 +52,28 @@ pub fn span() -> ExpectedSpan { } } +/// 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/lib.rs b/tracing-mock/src/lib.rs index 9fdeab5866..1f6d2e4907 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 collector; pub mod event; pub mod expect; @@ -8,88 +9,3 @@ pub mod span; #[cfg(feature = "tracing-subscriber")] pub mod subscriber; - -#[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, - collector_name: &str, - ) { - match self { - Parent::ExplicitRoot => { - assert!( - provided_parent.is_none(), - "[{}] expected {} to be an explicit root, but its parent was actually {:?} (name: {:?})", - collector_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", - collector_name, - ctx, - expected_parent, - ); - assert_eq!( - Some(expected_parent.as_ref()), - parent_name, - "[{}] expected {} to have explicit parent {}, but its parent was actually {:?} (name: {:?})", - collector_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: {:?})", - collector_name, - ctx, - provided_parent, - parent_name, - ); - assert!( - parent_name.is_none(), - "[{}] expected {} to be contextual a root, but we were inside span {:?}", - collector_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: {:?})", - collector_name, - ctx, - provided_parent, - parent_name, - ); - assert_eq!( - Some(expected_parent.as_ref()), - parent_name, - "[{}] expected {} to have contextual parent {:?}, but got {:?}", - collector_name, - ctx, - expected_parent, - parent_name, - ); - } - } - } -} diff --git a/tracing-mock/src/span.rs b/tracing-mock/src/span.rs index 42ed3a603e..7a35c5b58f 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_parent(expect::has_explicit_parent("parent_span")); //! //! let (collector, handle) = collector::mock() //! .new_span(expect::span().named("parent_span")) @@ -92,7 +92,8 @@ //! [`expect::span`]: fn@crate::expect::span #![allow(missing_docs)] use crate::{ - collector::SpanState, expect, field::ExpectedFields, metadata::ExpectedMetadata, Parent, + ancestry::Ancestry, collector::SpanState, expect, field::ExpectedFields, + metadata::ExpectedMetadata, }; use std::fmt; @@ -127,7 +128,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 @@ -300,8 +301,9 @@ impl ExpectedSpan { } } - /// 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 +312,24 @@ impl ExpectedSpan { /// method on [`MockCollector`] that takes an `ExpectedSpan`. /// /// An _explicit_ parent span is one passed to the `span!` macro in the - /// `parent:` field. + /// `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. /// - /// 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. + /// 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::{collector, expect}; /// /// let span = expect::span() - /// .with_explicit_parent(Some("parent_span")); + /// .with_ancestry(expect::has_explicit_parent("parent_span")); /// /// let (collector, handle) = collector::mock() /// .new_span(expect::span().named("parent_span")) @@ -351,7 +350,7 @@ impl ExpectedSpan { /// use tracing_mock::{collector, expect}; /// /// let span = expect::span() - /// .with_explicit_parent(None); + /// .with_ancestry(expect::is_explicit_root()); /// /// let (collector, handle) = collector::mock() /// .new_span(span) @@ -364,79 +363,16 @@ impl ExpectedSpan { /// 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::{collector, expect}; - /// - /// let parent_span = expect::span().named("parent_span"); - /// let span = expect::span() - /// .with_explicit_parent(Some("parent_span")); - /// - /// let (collector, handle) = collector::mock() - /// .new_span(parent_span.clone()) - /// .enter(parent_span) - /// .new_span(span) - /// .run_with_handle(); - /// - /// tracing::collect::with_default(collector, || { - /// let parent = tracing::info_span!("parent_span"); - /// let _guard = parent.enter(); - /// tracing::info_span!("span"); - /// }); - /// - /// handle.assert_finished(); - /// ``` - /// - /// [`MockCollector`]: struct@crate::collector::MockCollector - /// [`MockCollector::enter`]: fn@crate::collector::MockCollector::enter - /// [`MockCollector::exit`]: fn@crate::collector::MockCollector::exit - /// [`MockCollector::new_span`]: fn@crate::collector::MockCollector::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 - /// [`MockCollector::new_span`]. It cannot be validated on - /// [`MockCollector::enter`], [`MockCollector::exit`], or any other - /// method on [`MockCollector`] 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: + /// When `expect::has_contextual_parent("parent_name")` is passed to + /// `with_ancestry` then the provided string is the name of the explicit + /// parent span to expect. /// /// ``` /// use tracing_mock::{collector, 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 (collector, handle) = collector::mock() /// .new_span(parent_span.clone()) @@ -460,7 +396,7 @@ impl ExpectedSpan { /// use tracing_mock::{collector, expect}; /// /// let span = expect::span() - /// .with_contextual_parent(None); + /// .with_ancestry(expect::is_contextual_root()); /// /// let (collector, handle) = collector::mock() /// .new_span(span) @@ -474,22 +410,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::{collector, 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 (collector, handle) = collector::mock() - /// .new_span(expect::span().named("parent_span")) + /// .new_span(parent_span.clone()) + /// .enter(parent_span) /// .new_span(span) /// .run_with_handle(); /// /// tracing::collect::with_default(collector, || { /// 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 +439,9 @@ impl ExpectedSpan { /// [`MockCollector::enter`]: fn@crate::collector::MockCollector::enter /// [`MockCollector::exit`]: fn@crate::collector::MockCollector::exit /// [`MockCollector::new_span`]: fn@crate::collector::MockCollector::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() } @@ -643,39 +579,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()), - 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 a parent span, or to be a 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()), - None => Parent::ContextualRoot, - }; + /// [`ExpectedSpan::with_parent`]. + pub fn with_parent(self, ancestry: Ancestry) -> NewSpan { NewSpan { - parent: Some(parent), + ancestry: Some(ancestry), ..self } } @@ -699,7 +609,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, collector_name: &str, ) { let meta = span.metadata(); @@ -711,14 +621,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), collector_name, - ) + ); } } } @@ -749,7 +658,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)); } diff --git a/tracing-mock/src/subscriber.rs b/tracing-mock/src/subscriber.rs index f9560065ff..07d8bb39a0 100644 --- a/tracing-mock/src/subscriber.rs +++ b/tracing-mock/src/subscriber.rs @@ -116,6 +116,7 @@ //! //! [`Subscribe`]: trait@tracing_subscriber::subscribe::Subscribe use crate::{ + ancestry::{get_ancestry, Ancestry, HasAncestry}, collector::MockHandle, event::ExpectedEvent, expect::Expect, @@ -414,7 +415,7 @@ impl MockSubscriberBuilder { /// /// This function accepts `Into` instead of /// [`ExpectedSpan`] directly. [`NewSpan`] can be used to test - /// span fields and the span parent. + /// span fields and the span ancestry. /// /// The new span doesn't need to be entered for this expectation /// to succeed. @@ -905,8 +906,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); @@ -937,13 +937,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); } } } @@ -1043,6 +1037,17 @@ where } } +fn context_get_ancestry(item: impl HasAncestry, ctx: &Context<'_, C>) -> Ancestry +where + C: Collect + 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 MockSubscriber { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut s = f.debug_struct("ExpectSubscriber"); diff --git a/tracing-mock/tests/event_ancestry.rs b/tracing-mock/tests/event_ancestry.rs new file mode 100644 index 0000000000..6bd253d016 --- /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::collect::with_default; +use tracing_mock::{collector, expect}; + +#[test] +fn contextual_parent() { + let event = expect::event().with_ancestry(expect::has_contextual_parent("contextual parent")); + + let (collector, handle) = collector::mock() + .enter(expect::span()) + .event(event) + .run_with_handle(); + + with_default(collector, || { + 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 (collector, handle) = collector::mock() + .enter(expect::span()) + .event(event) + .run_with_handle(); + + with_default(collector, || { + 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 (collector, handle) = collector::mock().event(event).run_with_handle(); + + with_default(collector, || { + 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 (collector, handle) = collector::mock().event(event).run_with_handle(); + + with_default(collector, || { + 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 (collector, handle) = collector::mock() + .enter(expect::span()) + .event(event) + .run_with_handle(); + + with_default(collector, || { + 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 (collector, handle) = collector::mock().event(event).run_with_handle(); + + with_default(collector, || { + 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 (collector, handle) = collector::mock() + .enter(expect::span()) + .event(event) + .run_with_handle(); + + with_default(collector, || { + 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 (collector, handle) = collector::mock().event(event).run_with_handle(); + + with_default(collector, || { + 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 (collector, handle) = collector::mock() + .enter(expect::span()) + .event(event) + .run_with_handle(); + + with_default(collector, || { + 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 (collector, handle) = collector::mock().event(event).run_with_handle(); + + with_default(collector, || { + 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 (collector, handle) = collector::mock().event(event).run_with_handle(); + + with_default(collector, || { + 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 (collector, handle) = collector::mock() + .enter(expect::span()) + .event(event) + .run_with_handle(); + + with_default(collector, || { + 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 (collector, handle) = collector::mock().event(event).run_with_handle(); + + with_default(collector, || { + 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 (collector, handle) = collector::mock() + .enter(expect::span()) + .event(event) + .run_with_handle(); + + with_default(collector, || { + 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 (collector, handle) = collector::mock() + .enter(expect::span()) + .event(event) + .run_with_handle(); + + with_default(collector, || { + 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 (collector, handle) = collector::mock() + .enter(expect::span()) + .event(event) + .run_with_handle(); + + with_default(collector, || { + 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 (collector, handle) = collector::mock().event(event).run_with_handle(); + + with_default(collector, || { + 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 (collector, handle) = collector::mock().event(event).run_with_handle(); + + with_default(collector, || { + 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 (collector, handle) = collector::mock().event(event).run_with_handle(); + + with_default(collector, || { + 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 0000000000..603c0a6b3c --- /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::collect::with_default; +use tracing_mock::{collector, expect}; + +#[test] +fn contextual_parent() { + let span = expect::span() + .named("span") + .with_ancestry(expect::has_contextual_parent("contextual parent")); + + let (collector, handle) = collector::mock() + .enter(expect::span()) + .new_span(span) + .run_with_handle(); + + with_default(collector, || { + 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 (collector, handle) = collector::mock() + .enter(expect::span()) + .new_span(span) + .run_with_handle(); + + with_default(collector, || { + 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 (collector, handle) = collector::mock().new_span(span).run_with_handle(); + + with_default(collector, || { + 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 (collector, handle) = collector::mock() + .new_span(expect::span()) + .new_span(span) + .run_with_handle(); + + with_default(collector, || { + 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 (collector, handle) = collector::mock() + .enter(expect::span()) + .new_span(span) + .run_with_handle(); + + with_default(collector, || { + 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 (collector, handle) = collector::mock().new_span(span).run_with_handle(); + + with_default(collector, || { + 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 (collector, handle) = collector::mock() + .enter(expect::span()) + .new_span(span) + .run_with_handle(); + + with_default(collector, || { + 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 (collector, handle) = collector::mock() + .new_span(expect::span()) + .new_span(span) + .run_with_handle(); + + with_default(collector, || { + 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 (collector, handle) = collector::mock() + .enter(expect::span()) + .new_span(span) + .run_with_handle(); + + with_default(collector, || { + 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 (collector, handle) = collector::mock() + .new_span(expect::span()) + .new_span(span) + .run_with_handle(); + + with_default(collector, || { + 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 (collector, handle) = collector::mock() + .new_span(expect::span()) + .new_span(span) + .run_with_handle(); + + with_default(collector, || { + 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 (collector, handle) = collector::mock() + .enter(expect::span()) + .new_span(span) + .run_with_handle(); + + with_default(collector, || { + 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 (collector, handle) = collector::mock().new_span(span).run_with_handle(); + + with_default(collector, || { + 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 (collector, handle) = collector::mock() + .enter(expect::span()) + .new_span(span) + .run_with_handle(); + + with_default(collector, || { + 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 (collector, handle) = collector::mock() + .new_span(expect::span()) + .enter(expect::span()) + .new_span(span) + .run_with_handle(); + + with_default(collector, || { + 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 (collector, handle) = collector::mock() + .enter(expect::span()) + .new_span(span) + .run_with_handle(); + + with_default(collector, || { + 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 (collector, handle) = collector::mock().new_span(span).run_with_handle(); + + with_default(collector, || { + 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 (collector, handle) = collector::mock() + .new_span(expect::span()) + .new_span(span) + .run_with_handle(); + + with_default(collector, || { + 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 (collector, handle) = collector::mock().new_span(span).run_with_handle(); + + with_default(collector, || { + tracing::info_span!(parent: None, "span"); + }); + + handle.assert_finished(); +} diff --git a/tracing/tests/event.rs b/tracing/tests/event.rs index 0af0602e96..3680917dc8 100644 --- a/tracing/tests/event.rs +++ b/tracing/tests/event.rs @@ -337,7 +337,7 @@ fn both_shorthands() { fn explicit_child() { let (collector, handle) = collector::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(); @@ -354,11 +354,11 @@ fn explicit_child() { fn explicit_child_at_levels() { let (collector, handle) = collector::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 6e48de6bfc..36b7394883 100644 --- a/tracing/tests/instrument.rs +++ b/tracing/tests/instrument.rs @@ -37,7 +37,7 @@ fn span_on_drop() { .enter(expect::span().named("foo")) .event( expect::event() - .with_contextual_parent(Some("foo")) + .with_ancestry(expect::has_contextual_parent("foo")) .at_level(Level::INFO), ) .exit(expect::span().named("foo")) @@ -47,7 +47,7 @@ fn span_on_drop() { .enter(expect::span().named("bar")) .event( expect::event() - .with_contextual_parent(Some("bar")) + .with_ancestry(expect::has_contextual_parent("bar")) .at_level(Level::INFO), ) .exit(expect::span().named("bar")) diff --git a/tracing/tests/span.rs b/tracing/tests/span.rs index e2c4c92396..f861f44b3b 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 (collector, handle) = collector::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 (collector, handle) = collector::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 (collector, handle) = collector::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 (collector, handle) = collector::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()