diff --git a/tracing-mock/src/ancestry.rs b/tracing-mock/src/ancestry.rs index ee661d45e..817e30fef 100644 --- a/tracing-mock/src/ancestry.rs +++ b/tracing-mock/src/ancestry.rs @@ -1,71 +1,102 @@ //! Define the ancestry of an event or span. //! -//! See the documentation on the [`Ancestry`] enum for further details. +//! See the documentation on the [`ExpectedAncestry`] enum for further details. use tracing_core::{ span::{self, Attributes}, Event, }; +use crate::span::{ActualSpan, ExpectedSpan}; + /// 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), +pub enum ExpectedAncestry { + /// The event or span has an explicitly assigned parent (created with `parent: span_id`) span. + HasExplicitParent(ExpectedSpan), /// 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 has a contextually assigned parent span. It has no explicitly assigned + /// parent span, 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(ExpectedSpan), /// 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 { +pub(crate) enum ActualAncestry { + HasExplicitParent(ActualSpan), + IsExplicitRoot, + HasContextualParent(ActualSpan), + IsContextualRoot, +} + +impl ExpectedAncestry { #[track_caller] pub(crate) fn check( &self, - actual_ancestry: &Ancestry, + actual_ancestry: &ActualAncestry, 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}'") + match (self, actual_ancestry) { + (Self::IsExplicitRoot, ActualAncestry::IsExplicitRoot) => {} + (Self::IsContextualRoot, ActualAncestry::IsContextualRoot) => {} + ( + Self::HasExplicitParent(expected_parent), + ActualAncestry::HasExplicitParent(actual_parent), + ) => { + expected_parent.check( + actual_parent, + format_args!("{ctx} to have an explicit parent span"), + collector_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::HasContextualParent(expected_parent), + ActualAncestry::HasContextualParent(actual_parent), + ) => { + println!("----> [{collector_name}] check {expected_parent:?} against actual parent with Id={id:?}", id = actual_parent.id()); + expected_parent.check( + actual_parent, + format_args!("{ctx} to have a contextual parent span"), + collector_name, + ); } - Self::IsContextualRoot => "was actually a contextual root".to_string(), - Self::HasContextualParent(name) => { - format!("actually has a contextual parent with name='{name}'") + _ => { + // Ancestry types don't match at all. + let expected_description = match self { + Self::IsExplicitRoot => "be an explicit root", + Self::HasExplicitParent(_) => "have an explicit parent span", + Self::IsContextualRoot => "be a contextual root", + Self::HasContextualParent(_) => "have a contextual parent span", + }; + + let actual_description = match actual_ancestry { + ActualAncestry::IsExplicitRoot => "is actually an explicit root", + ActualAncestry::HasExplicitParent(_) => "actually has an explicit parent span", + ActualAncestry::IsContextualRoot => "is actually a contextual root", + ActualAncestry::HasContextualParent(_) => { + "actually has a contextual parent span" + } + }; + + panic!( + "{}", + format!( + "[{collector_name}] expected {ctx} to {expected_description}, \ + but it {actual_description}" + ) + ); } - }; - - 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) - ); + } } } @@ -120,29 +151,29 @@ impl HasAncestry for &Attributes<'_> { pub(crate) fn get_ancestry( item: impl HasAncestry, lookup_current: impl FnOnce() -> Option, - span_name: impl FnOnce(&span::Id) -> Option<&str>, -) -> Ancestry { + actual_span: impl FnOnce(&span::Id) -> Option, +) -> ActualAncestry { if item.is_contextual() { if let Some(parent_id) = lookup_current() { - let contextual_parent_name = span_name(&parent_id).expect( + let contextual_parent_span = actual_span(&parent_id).expect( "tracing-mock: contextual parent cannot \ be looked up by ID. Was it recorded correctly?", ); - Ancestry::HasContextualParent(contextual_parent_name.to_string()) + ActualAncestry::HasContextualParent(contextual_parent_span) } else { - Ancestry::IsContextualRoot + ActualAncestry::IsContextualRoot } } else if item.is_root() { - Ancestry::IsExplicitRoot + ActualAncestry::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( + let explicit_parent_span = actual_span(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()) + ActualAncestry::HasExplicitParent(explicit_parent_span) } } diff --git a/tracing-mock/src/collector.rs b/tracing-mock/src/collector.rs index 4789b7ce6..9f27e1078 100644 --- a/tracing-mock/src/collector.rs +++ b/tracing-mock/src/collector.rs @@ -142,7 +142,7 @@ use crate::{ event::ExpectedEvent, expect::Expect, field::ExpectedFields, - span::{ExpectedSpan, NewSpan}, + span::{ActualSpan, ExpectedSpan, NewSpan}, }; use std::{ collections::{HashMap, VecDeque}, @@ -160,19 +160,15 @@ use tracing::{ }; pub(crate) struct SpanState { - id: u64, + id: Id, 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 +impl From<&SpanState> for ActualSpan { + fn from(span_state: &SpanState) -> Self { + Self::new(span_state.id.clone(), Some(span_state.meta)) } } @@ -1069,7 +1065,7 @@ where .lock() .unwrap() .get(span_id) - .map(|span| span.name) + .map(|span| span.into()) }, ) }; @@ -1140,7 +1136,7 @@ where get_ancestry( span, || self.lookup_current(), - |span_id| spans.get(span_id).map(|span| span.name), + |span_id| spans.get(span_id).map(|span| span.into()), ) }, &self.name, @@ -1150,7 +1146,7 @@ where spans.insert( id.clone(), SpanState { - id: id.into_u64(), + id: id.clone(), name: meta.name(), refs: 1, meta, @@ -1166,7 +1162,7 @@ where match self.expected.lock().unwrap().pop_front() { None => {} Some(Expect::Enter(ref expected_span)) => { - expected_span.check(span, &self.name); + expected_span.check(&span.into(), "to enter a span", &self.name); } Some(ex) => ex.bad(&self.name, format_args!("entered span {:?}", span.name)), } @@ -1189,7 +1185,7 @@ where match self.expected.lock().unwrap().pop_front() { None => {} Some(Expect::Exit(ref expected_span)) => { - expected_span.check(span, &self.name); + expected_span.check(&span.into(), "to exit a span", &self.name); let curr = self.current.lock().unwrap().pop(); assert_eq!( Some(id), @@ -1205,27 +1201,34 @@ where } fn clone_span(&self, id: &Id) -> Id { - let name = self.spans.lock().unwrap().get_mut(id).map(|span| { - let name = span.name; - println!( - "[{}] clone_span: {}; id={:?}; refs={:?};", - self.name, name, id, span.refs - ); - span.refs += 1; - name - }); - if name.is_none() { - println!("[{}] clone_span: id={:?};", self.name, id); + let mut spans = self.spans.lock().unwrap(); + let mut span = spans.get_mut(id); + match span.as_deref_mut() { + Some(span) => { + println!( + "[{}] clone_span: {}; id={:?}; refs={:?};", + self.name, span.name, id, span.refs, + ); + span.refs += 1; + } + None => { + println!( + "[{}] clone_span: id={:?} (not found in span list);", + self.name, id + ); + } } + let mut expected = self.expected.lock().unwrap(); - let was_expected = if let Some(Expect::CloneSpan(ref span)) = expected.front() { - assert_eq!( - name, - span.name(), - "[{}] expected to clone a span named {:?}", - self.name, - span.name() - ); + let was_expected = if let Some(Expect::CloneSpan(ref expected_span)) = expected.front() { + match span { + Some(actual_span) => { + let actual_span: &_ = actual_span; + expected_span.check(&actual_span.into(), "to clone a span", &self.name); + } + // Check only by Id + None => expected_span.check(&id.into(), "to clone a span", &self.name), + } true } else { false diff --git a/tracing-mock/src/event.rs b/tracing-mock/src/event.rs index 630f005a8..c9445a107 100644 --- a/tracing-mock/src/event.rs +++ b/tracing-mock/src/event.rs @@ -29,7 +29,12 @@ //! [`collector`]: mod@crate::collector //! [`expect::event`]: fn@crate::expect::event #![allow(missing_docs)] -use crate::{ancestry::Ancestry, expect, field, metadata::ExpectedMetadata, span}; +use crate::{ + ancestry::{ActualAncestry, ExpectedAncestry}, + expect, field, + metadata::ExpectedMetadata, + span, +}; use std::fmt; @@ -42,7 +47,7 @@ use std::fmt; #[derive(Default, Eq, PartialEq)] pub struct ExpectedEvent { pub(super) fields: Option, - pub(super) ancestry: Option, + pub(super) ancestry: Option, pub(super) in_spans: Option>, pub(super) metadata: ExpectedMetadata, } @@ -253,9 +258,10 @@ impl ExpectedEvent { } } - /// 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. + /// Configures this `ExpectedEvent` to expect the specified + /// [`ExpectedAncestry`]. 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 `event!` macro in the /// `parent:` field. If no `parent:` field is specified, then the event @@ -267,9 +273,34 @@ impl ExpectedEvent { /// /// # Examples /// - /// 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. + /// An explicit or contextual can be matched on an `ExpectedSpan`. + /// + /// ``` + /// use tracing::collect::with_default; + /// use tracing_mock::{collector, expect}; + /// + /// let parent = expect::span() + /// .named("parent_span") + /// .with_target("custom-target") + /// .at_level(tracing::Level::INFO); + /// let event = expect::event() + /// .with_ancestry(expect::has_explicit_parent(parent)); + /// + /// let (collector, handle) = collector::mock() + /// .event(event) + /// .run_with_handle(); + /// + /// with_default(collector, || { + /// let parent = tracing::info_span!(target: "custom-target", "parent_span"); + /// tracing::info!(parent: parent.id(), field = &"value"); + /// }); + /// + /// handle.assert_finished(); + /// ``` + /// The functions `expect::has_explicit_parent` and + /// `expect::has_contextual_parent` take `Into`, so a string + /// passed directly will match on a span with that name, or an + /// [`ExpectedId`] can be passed to match a span with that Id. /// /// ``` /// use tracing::collect::with_default; @@ -382,7 +413,9 @@ impl ExpectedEvent { /// /// handle.assert_finished(); /// ``` - pub fn with_ancestry(self, ancenstry: Ancestry) -> ExpectedEvent { + /// + /// [`ExpectedId`]: struct@crate::span::ExpectedId + pub fn with_ancestry(self, ancenstry: ExpectedAncestry) -> ExpectedEvent { Self { ancestry: Some(ancenstry), ..self @@ -506,7 +539,7 @@ impl ExpectedEvent { pub(crate) fn check( &mut self, event: &tracing::Event<'_>, - get_ancestry: impl FnOnce() -> Ancestry, + get_ancestry: impl FnOnce() -> ActualAncestry, collector_name: &str, ) { let meta = event.metadata(); diff --git a/tracing-mock/src/expect.rs b/tracing-mock/src/expect.rs index 95ad3176c..aebcc9eec 100644 --- a/tracing-mock/src/expect.rs +++ b/tracing-mock/src/expect.rs @@ -1,7 +1,7 @@ use std::fmt; use crate::{ - ancestry::Ancestry, + ancestry::ExpectedAncestry, event::ExpectedEvent, field::{ExpectedField, ExpectedFields, ExpectedValue}, span::{ExpectedId, ExpectedSpan, NewSpan}, @@ -71,26 +71,26 @@ 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 [`ExpectedAncestry::IsContextualRoot`]. +pub fn is_contextual_root() -> ExpectedAncestry { + ExpectedAncestry::IsContextualRoot } -/// Convenience function that returns [`Ancestry::HasContextualParent`] with +/// Convenience function that returns [`ExpectedAncestry::HasContextualParent`] with /// provided name. -pub fn has_contextual_parent>(name: S) -> Ancestry { - Ancestry::HasContextualParent(name.into()) +pub fn has_contextual_parent>(span: S) -> ExpectedAncestry { + ExpectedAncestry::HasContextualParent(span.into()) } -/// Convenience function that returns [`Ancestry::IsExplicitRoot`]. -pub fn is_explicit_root() -> Ancestry { - Ancestry::IsExplicitRoot +/// Convenience function that returns [`ExpectedAncestry::IsExplicitRoot`]. +pub fn is_explicit_root() -> ExpectedAncestry { + ExpectedAncestry::IsExplicitRoot } -/// Convenience function that returns [`Ancestry::HasExplicitParent`] with +/// Convenience function that returns [`ExpectedAncestry::HasExplicitParent`] with /// provided name. -pub fn has_explicit_parent>(name: S) -> Ancestry { - Ancestry::HasExplicitParent(name.into()) +pub fn has_explicit_parent>(span: S) -> ExpectedAncestry { + ExpectedAncestry::HasExplicitParent(span.into()) } impl Expect { diff --git a/tracing-mock/src/metadata.rs b/tracing-mock/src/metadata.rs index 9d2d34114..d33ca209c 100644 --- a/tracing-mock/src/metadata.rs +++ b/tracing-mock/src/metadata.rs @@ -9,48 +9,69 @@ pub(crate) struct ExpectedMetadata { } impl ExpectedMetadata { + /// Checks the given metadata against this expected metadata and panics if + /// there is a mismatch. + /// + /// The context `ctx` should fit into the followint sentence: + /// + /// > expected {ctx} named `expected_name`, but got one named `actual_name` + /// + /// Examples could be: + /// * a new span + /// * to enter a span + /// * an event + /// + /// # Panics + /// + /// This method will panic if any of the expectations that have been + /// specified are noto met. + /// pub(crate) fn check( &self, actual: &Metadata<'_>, - ctx: fmt::Arguments<'_>, + ctx: impl fmt::Display, collector_name: &str, ) { if let Some(ref expected_name) = self.name { - let name = actual.name(); + let actual_name = actual.name(); assert!( - expected_name == name, - "\n[{}] expected {} to be named `{}`, but got one named `{}`", - collector_name, - ctx, - expected_name, - name + expected_name == actual_name, + "{}", + format_args!( + "\n[{collector_name}] expected {ctx} named `{expected_name}`,\n\ + [{collector_name}] but got one named `{actual_name}` instead." + ), ) } if let Some(ref expected_level) = self.level { - let level = actual.level(); + let actual_level = actual.level(); assert!( - expected_level == level, - "\n[{}] expected {} to be at level `{:?}`, but it was at level `{:?}` instead", - collector_name, - ctx, - expected_level, - level, + expected_level == actual_level, + "{}", + format_args!( + "\n[{collector_name}] expected {ctx} at level `{expected_level:?}`,\n\ + [{collector_name}] but got one at level `{actual_level:?}` instead." + ), ) } if let Some(ref expected_target) = self.target { - let target = actual.target(); + let actual_target = actual.target(); assert!( - expected_target == target, - "\n[{}] expected {} to have target `{}`, but it had target `{}` instead", - collector_name, - ctx, - expected_target, - target, + expected_target == actual_target, + "{}", + format_args!( + "\n[{collector_name}] expected {ctx} with target `{expected_target}`,\n\ + [{collector_name}] but got one with target `{actual_target}` instead." + ), ) } } + + pub(crate) fn has_expectations(&self) -> bool { + self.name.is_some() || self.level.is_some() || self.target.is_some() + } } impl fmt::Display for ExpectedMetadata { diff --git a/tracing-mock/src/span.rs b/tracing-mock/src/span.rs index 34568551b..56406ed42 100644 --- a/tracing-mock/src/span.rs +++ b/tracing-mock/src/span.rs @@ -111,7 +111,9 @@ //! [`expect::span`]: fn@crate::expect::span #![allow(missing_docs)] use crate::{ - ancestry::Ancestry, collector::SpanState, expect, field::ExpectedFields, + ancestry::{ActualAncestry, ExpectedAncestry}, + expect, + field::ExpectedFields, metadata::ExpectedMetadata, }; use std::{ @@ -175,7 +177,7 @@ impl From<&ExpectedSpan> for ExpectedSpan { pub struct NewSpan { pub(crate) span: ExpectedSpan, pub(crate) fields: ExpectedFields, - pub(crate) ancestry: Option, + pub(crate) ancestry: Option, } pub fn named(name: I) -> ExpectedSpan @@ -185,6 +187,36 @@ where expect::span().named(name) } +pub(crate) struct ActualSpan { + id: tracing_core::span::Id, + metadata: Option<&'static tracing_core::Metadata<'static>>, +} + +impl ActualSpan { + pub(crate) fn new( + id: tracing_core::span::Id, + metadata: Option<&'static tracing_core::Metadata<'static>>, + ) -> Self { + Self { id, metadata } + } + + /// The Id of the actual span. + pub(crate) fn id(&self) -> tracing_core::span::Id { + self.id.clone() + } + + /// The metadata for the actual span if it is available. + pub(crate) fn metadata(&self) -> Option<&'static tracing_core::Metadata<'static>> { + self.metadata + } +} + +impl From<&tracing_core::span::Id> for ActualSpan { + fn from(id: &tracing_core::span::Id) -> Self { + Self::new(id.clone(), None) + } +} + /// A mock span ID. /// /// This ID makes it possible to link together calls to different @@ -515,9 +547,10 @@ impl ExpectedSpan { } } - /// 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. + /// Configures this `ExpectedSpan` to expect the specified + /// [`ExpectedAncestry`]. 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 @@ -535,9 +568,35 @@ impl ExpectedSpan { /// /// # Examples /// - /// 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. + /// An explicit or contextual parent can be matched on an `ExpectedSpan`. + /// + /// ``` + /// use tracing_mock::{collector, expect}; + /// + /// let parent = expect::span() + /// .named("parent_span") + /// .with_target("custom-target") + /// .at_level(tracing::Level::INFO); + /// let span = expect::span() + /// .with_ancestry(expect::has_explicit_parent(&parent)); + /// + /// let (collector, handle) = collector::mock() + /// .new_span(&parent) + /// .new_span(span) + /// .run_with_handle(); + /// + /// tracing::collect::with_default(collector, || { + /// let parent = tracing::info_span!(target: "custom-target", "parent_span"); + /// tracing::info_span!(parent: parent.id(), "span"); + /// }); + /// + /// handle.assert_finished(); + /// ``` + /// + /// The functions `expect::has_explicit_parent` and + /// `expect::has_contextual_parent` take `Into`, so a string + /// passed directly will match on a span with that name, or an + /// [`ExpectedId`] can be passed to match a span with that Id. /// /// ``` /// use tracing_mock::{collector, expect}; @@ -653,7 +712,7 @@ 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_ancestry(self, ancestry: Ancestry) -> NewSpan { + pub fn with_ancestry(self, ancestry: ExpectedAncestry) -> NewSpan { NewSpan { ancestry: Some(ancestry), span: self, @@ -733,6 +792,10 @@ impl ExpectedSpan { } } + pub(crate) fn id(&self) -> Option<&ExpectedId> { + self.id.as_ref() + } + pub(crate) fn name(&self) -> Option<&str> { self.metadata.name.as_ref().map(String::as_ref) } @@ -745,16 +808,26 @@ impl ExpectedSpan { self.metadata.target.as_deref() } - pub(crate) fn check(&self, actual: &SpanState, collector_name: &str) { - let meta = actual.metadata(); - let name = meta.name(); - + pub(crate) fn check(&self, actual: &ActualSpan, ctx: impl fmt::Display, collector_name: &str) { if let Some(expected_id) = &self.id { - expected_id.check(actual.id(), format_args!("span `{}`", name), collector_name); + expected_id.check(&actual.id(), format_args!("{ctx} a span"), collector_name); } - self.metadata - .check(meta, format_args!("span `{}`", name), collector_name); + match actual.metadata() { + Some(actual_metadata) => self.metadata.check(actual_metadata, ctx, collector_name), + None => { + if self.metadata.has_expectations() { + panic!( + "{}", + format_args!( + "[{collector_name}] expected {ctx} a span with valid metadata, \ + but got one with unknown Id={actual_id}", + actual_id = actual.id().into_u64() + ) + ); + } + } + } } } @@ -762,6 +835,10 @@ impl fmt::Debug for ExpectedSpan { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut s = f.debug_struct("MockSpan"); + if let Some(id) = self.id() { + s.field("id", &id); + } + if let Some(name) = self.name() { s.field("name", &name); } @@ -801,13 +878,13 @@ where } impl NewSpan { - /// Configures this `NewSpan` to expect the specified [`Ancestry`]. A - /// span's ancestry indicates whether it has a parent or is a root span + /// Configures this `NewSpan` to expect the specified [`ExpectedAncestry`]. + /// 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_ancestry`]. - pub fn with_ancestry(self, ancestry: Ancestry) -> NewSpan { + pub fn with_ancestry(self, ancestry: ExpectedAncestry) -> NewSpan { NewSpan { ancestry: Some(ancestry), ..self @@ -833,14 +910,12 @@ impl NewSpan { pub(crate) fn check( &mut self, span: &tracing_core::span::Attributes<'_>, - get_ancestry: impl FnOnce() -> Ancestry, + get_ancestry: impl FnOnce() -> ActualAncestry, collector_name: &str, ) { let meta = span.metadata(); let name = meta.name(); - self.span - .metadata - .check(meta, format_args!("span `{}`", name), collector_name); + self.span.metadata.check(meta, "a new span", collector_name); let mut checker = self.fields.checker(name, collector_name); span.record(&mut checker); checker.finish(); @@ -902,6 +977,12 @@ impl PartialEq for ExpectedId { impl Eq for ExpectedId {} +impl fmt::Debug for ExpectedId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple("ExpectedId").field(&self.inner).finish() + } +} + impl ExpectedId { const UNSET: u64 = 0; @@ -921,21 +1002,33 @@ impl ExpectedId { Ok(()) } - pub(crate) fn check(&self, actual: u64, ctx: fmt::Arguments<'_>, collector_name: &str) { - let id = self.inner.load(Ordering::Relaxed); + pub(crate) fn check( + &self, + actual: &tracing_core::span::Id, + ctx: fmt::Arguments<'_>, + collector_name: &str, + ) { + let expected_id = self.inner.load(Ordering::Relaxed); + let actual_id = actual.into_u64(); 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 `MockCollector::new_span()`?", - collector_name, - ctx, + expected_id != Self::UNSET, + "{}", + format!( + "\n[{collector_name}] expected {ctx} with an expected Id set,\n\ + [{collector_name}] but it hasn't been, perhaps this `ExpectedId` \ + wasn't used in a call to `new_span()`?" + ) ); assert_eq!( - id, actual, - "\n[{}] expected {} to have ID `{}`, but it has `{}` instead", - collector_name, ctx, id, actual, + expected_id, + actual_id, + "{}", + format_args!( + "\n[{collector_name}] expected {ctx} with Id `{expected_id}`,\n\ + [{collector_name}] but got one with Id `{actual_id}` instead", + ) ); } } diff --git a/tracing-mock/src/subscriber.rs b/tracing-mock/src/subscriber.rs index 16ed3a59f..33cab2bf2 100644 --- a/tracing-mock/src/subscriber.rs +++ b/tracing-mock/src/subscriber.rs @@ -116,11 +116,11 @@ //! //! [`Subscribe`]: trait@tracing_subscriber::subscribe::Subscribe use crate::{ - ancestry::{get_ancestry, Ancestry, HasAncestry}, + ancestry::{get_ancestry, ActualAncestry, HasAncestry}, collector::MockHandle, event::ExpectedEvent, expect::Expect, - span::{ExpectedSpan, NewSpan}, + span::{ActualSpan, ExpectedSpan, NewSpan}, }; use tracing_core::{ span::{Attributes, Id, Record}, @@ -779,66 +779,16 @@ impl MockSubscriberBuilder { } } -impl MockSubscriber { - fn check_span_ref<'spans, S>( - &self, - expected: &ExpectedSpan, - actual: &SpanRef<'spans, S>, - what_happened: impl fmt::Display, - ) where - S: LookupSpan<'spans>, - { - if let Some(exp_name) = expected.name() { - assert_eq!( - actual.name(), - exp_name, - "\n[{}] expected {} a span named {:?}\n\ - [{}] but it was named {:?} instead (span {} {:?})", - self.name, - what_happened, - exp_name, - self.name, - actual.name(), - actual.name(), - actual.id() - ); - } - - if let Some(exp_level) = expected.level() { - let actual_level = actual.metadata().level(); - assert_eq!( - actual_level, - &exp_level, - "\n[{}] expected {} a span at {:?}\n\ - [{}] but it was at {:?} instead (span {} {:?})", - self.name, - what_happened, - exp_level, - self.name, - actual_level, - actual.name(), - actual.id(), - ); - } - - if let Some(exp_target) = expected.target() { - let actual_target = actual.metadata().target(); - assert_eq!( - actual_target, - exp_target, - "\n[{}] expected {} a span with target {:?}\n\ - [{}] but it had the target {:?} instead (span {} {:?})", - self.name, - what_happened, - exp_target, - self.name, - actual_target, - actual.name(), - actual.id(), - ); - } +impl<'a, S> From<&SpanRef<'a, S>> for ActualSpan +where + S: LookupSpan<'a>, +{ + fn from(span_ref: &SpanRef<'a, S>) -> Self { + Self::new(span_ref.id(), Some(span_ref.metadata())) } +} +impl MockSubscriber { fn check_event_scope( &self, current_scope: Option>, @@ -857,10 +807,10 @@ impl MockSubscriber { actual.id(), expected ); - self.check_span_ref( - expected, - &actual, + expected.check( + &(&actual).into(), format_args!("the {}th span in the event's scope to be", i), + &self.name, ); i += 1; } @@ -956,7 +906,7 @@ where match self.expected.lock().unwrap().pop_front() { None => {} Some(Expect::Enter(ref expected_span)) => { - self.check_span_ref(expected_span, &span, "to enter"); + expected_span.check(&(&span).into(), "to enter", &self.name); } Some(ex) => ex.bad(&self.name, format_args!("entered span {:?}", span.name())), } @@ -977,7 +927,7 @@ where match self.expected.lock().unwrap().pop_front() { None => {} Some(Expect::Exit(ref expected_span)) => { - self.check_span_ref(expected_span, &span, "to exit"); + expected_span.check(&(&span).into(), "to exit", &self.name); let curr = self.current.lock().unwrap().pop(); assert_eq!( Some(id), @@ -1014,7 +964,7 @@ where // as failing the assertion can cause a double panic. if !::std::thread::panicking() { if let Some(ref span) = span { - self.check_span_ref(expected_span, span, "to close"); + expected_span.check(&span.into(), "to close a span", &self.name); } } true @@ -1043,14 +993,14 @@ where } } -fn context_get_ancestry(item: impl HasAncestry, ctx: &Context<'_, C>) -> Ancestry +fn context_get_ancestry(item: impl HasAncestry, ctx: &Context<'_, C>) -> ActualAncestry 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()), + |span_id| ctx.span(span_id).map(|span| (&span).into()), ) } diff --git a/tracing-mock/tests/event_ancestry.rs b/tracing-mock/tests/event_ancestry.rs index 6bd253d01..0d9db8758 100644 --- a/tracing-mock/tests/event_ancestry.rs +++ b/tracing-mock/tests/event_ancestry.rs @@ -5,7 +5,7 @@ //! that an event has a specific contextual or explicit parent. //! //! [`ExpectedEvent`]: crate::event::ExpectedEvent -use tracing::collect::with_default; +use tracing::{collect::with_default, Level}; use tracing_mock::{collector, expect}; #[test] @@ -27,8 +27,8 @@ fn contextual_parent() { #[test] #[should_panic( - expected = "to have a contextual parent with name='contextual parent', but \ - actually has a contextual parent with name='another parent'" + expected = "to have a contextual parent span named `contextual parent`,\n\ + [contextual_parent_wrong_name] but got one named `another parent` instead." )] fn contextual_parent_wrong_name() { let event = expect::event().with_ancestry(expect::has_contextual_parent("contextual parent")); @@ -46,11 +46,53 @@ fn contextual_parent_wrong_name() { handle.assert_finished(); } +#[test] +#[should_panic(expected = "to have a contextual parent span a span with Id `1`,\n\ + [contextual_parent_wrong_id] but got one with Id `2` instead")] +fn contextual_parent_wrong_id() { + let id = expect::id(); + let event = expect::event().with_ancestry(expect::has_contextual_parent(&id)); + + let (collector, handle) = collector::mock() + .new_span(&id) + .enter(expect::span()) + .event(event) + .run_with_handle(); + + with_default(collector, || { + let _span = tracing::info_span!("contextual parent"); + 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" + expected = "to have a contextual parent span at level `Level(Info)`,\n\ + [contextual_parent_wrong_level] but got one at level `Level(Debug)` instead." )] +fn contextual_parent_wrong_level() { + let parent = expect::span().at_level(Level::INFO); + let event = expect::event().with_ancestry(expect::has_contextual_parent(parent)); + + let (collector, handle) = collector::mock() + .enter(expect::span()) + .event(event) + .run_with_handle(); + + with_default(collector, || { + let _guard = tracing::debug_span!("contextual parent").entered(); + tracing::info!(field = &"value"); + }); + + handle.assert_finished(); +} + +#[test] +#[should_panic(expected = "to have a contextual parent span, but it is actually a \ + contextual root")] fn expect_contextual_parent_actual_contextual_root() { let event = expect::event().with_ancestry(expect::has_contextual_parent("contextual parent")); @@ -64,10 +106,8 @@ fn expect_contextual_parent_actual_contextual_root() { } #[test] -#[should_panic( - expected = "to have a contextual parent with name='contextual parent', but actually has an \ - explicit parent with name='explicit parent'" -)] +#[should_panic(expected = "to have a contextual parent span, but it actually has an \ + explicit parent span")] fn expect_contextual_parent_actual_explicit_parent() { let event = expect::event().with_ancestry(expect::has_contextual_parent("contextual parent")); @@ -82,10 +122,8 @@ fn expect_contextual_parent_actual_explicit_parent() { } #[test] -#[should_panic( - expected = "to have a contextual parent with name='contextual parent', but was actually an \ - explicit root" -)] +#[should_panic(expected = "to have a contextual parent span, but it is actually an \ + explicit root")] fn expect_contextual_parent_actual_explicit_root() { let event = expect::event().with_ancestry(expect::has_contextual_parent("contextual parent")); @@ -116,10 +154,7 @@ fn contextual_root() { } #[test] -#[should_panic( - expected = "to be a contextual root, but actually has a contextual parent with \ - name='contextual parent'" -)] +#[should_panic(expected = "to be a contextual root, but it actually has a contextual parent span")] fn expect_contextual_root_actual_contextual_parent() { let event = expect::event().with_ancestry(expect::is_contextual_root()); @@ -137,10 +172,7 @@ fn expect_contextual_root_actual_contextual_parent() { } #[test] -#[should_panic( - expected = "to be a contextual root, but actually has an explicit parent with \ - name='explicit parent'" -)] +#[should_panic(expected = "to be a contextual root, but it actually has an explicit parent span")] fn expect_contextual_root_actual_explicit_parent() { let event = expect::event().with_ancestry(expect::is_contextual_root()); @@ -155,7 +187,7 @@ fn expect_contextual_root_actual_explicit_parent() { } #[test] -#[should_panic(expected = "to be a contextual root, but was actually an explicit root")] +#[should_panic(expected = "to be a contextual root, but it is actually an explicit root")] fn expect_contextual_root_actual_explicit_root() { let event = expect::event().with_ancestry(expect::is_contextual_root()); @@ -188,8 +220,8 @@ fn explicit_parent() { #[test] #[should_panic( - expected = "to have an explicit parent with name='explicit parent', but actually has an \ - explicit parent with name='another parent'" + expected = "to have an explicit parent span named `explicit parent`,\n\ + [explicit_parent_wrong_name] but got one named `another parent` instead." )] fn explicit_parent_wrong_name() { let event = expect::event().with_ancestry(expect::has_explicit_parent("explicit parent")); @@ -205,10 +237,47 @@ fn explicit_parent_wrong_name() { } #[test] -#[should_panic( - expected = "to have an explicit parent with name='explicit parent', but actually has a \ - contextual parent with name='contextual parent'" -)] +#[should_panic(expected = "to have an explicit parent span a span with Id `1`,\n\ + [explicit_parent_wrong_id] but got one with Id `2` instead")] +fn explicit_parent_wrong_id() { + let id = expect::id(); + let event = expect::event().with_ancestry(expect::has_explicit_parent(&id)); + + let (collector, handle) = collector::mock() + .new_span(&id) + .new_span(expect::span()) + .event(event) + .run_with_handle(); + + with_default(collector, || { + let _span = tracing::info_span!("explicit parent"); + let another_span = tracing::info_span!("another parent"); + tracing::info!(parent: another_span.id(), field = &"value"); + }); + + handle.assert_finished(); +} + +#[test] +#[should_panic(expected = "to have an explicit parent span at level `Level(Info)`,\n\ + [explicit_parent_wrong_level] but got one at level `Level(Debug)` instead.")] +fn explicit_parent_wrong_level() { + let parent = expect::span().at_level(Level::INFO); + let event = expect::event().with_ancestry(expect::has_explicit_parent(parent)); + + let (collector, handle) = collector::mock().event(event).run_with_handle(); + + with_default(collector, || { + let span = tracing::debug_span!("explicit parent"); + tracing::info!(parent: span.id(), field = &"value"); + }); + + handle.assert_finished(); +} + +#[test] +#[should_panic(expected = "to have an explicit parent span, but it actually has a \ + contextual parent span")] fn expect_explicit_parent_actual_contextual_parent() { let event = expect::event().with_ancestry(expect::has_explicit_parent("explicit parent")); @@ -226,10 +295,8 @@ fn expect_explicit_parent_actual_contextual_parent() { } #[test] -#[should_panic( - expected = "to have an explicit parent with name='explicit parent', but was actually a \ - contextual root" -)] +#[should_panic(expected = "to have an explicit parent span, but it is actually a \ + contextual root")] fn expect_explicit_parent_actual_contextual_root() { let event = expect::event().with_ancestry(expect::has_explicit_parent("explicit parent")); @@ -243,10 +310,8 @@ fn expect_explicit_parent_actual_contextual_root() { } #[test] -#[should_panic( - expected = "to have an explicit parent with name='explicit parent', but was actually an \ - explicit root" -)] +#[should_panic(expected = "to have an explicit parent span, but it is actually an \ + explicit root")] fn expect_explicit_parent_actual_explicit_root() { let event = expect::event().with_ancestry(expect::has_explicit_parent("explicit parent")); @@ -281,10 +346,7 @@ fn explicit_root() { } #[test] -#[should_panic( - expected = "to be an explicit root, but actually has a contextual parent with \ - name='contextual parent'" -)] +#[should_panic(expected = "to be an explicit root, but it actually has a contextual parent span")] fn expect_explicit_root_actual_contextual_parent() { let event = expect::event().with_ancestry(expect::is_explicit_root()); @@ -302,7 +364,7 @@ fn expect_explicit_root_actual_contextual_parent() { } #[test] -#[should_panic(expected = "to be an explicit root, but was actually a contextual root")] +#[should_panic(expected = "to be an explicit root, but it is actually a contextual root")] fn expect_explicit_root_actual_contextual_root() { let event = expect::event().with_ancestry(expect::is_explicit_root()); @@ -316,9 +378,7 @@ fn expect_explicit_root_actual_contextual_root() { } #[test] -#[should_panic( - expected = "to be an explicit root, but actually has an explicit parent with name='explicit parent'" -)] +#[should_panic(expected = "to be an explicit root, but it actually has an explicit parent span")] fn expect_explicit_root_actual_explicit_parent() { let event = expect::event().with_ancestry(expect::is_explicit_root()); diff --git a/tracing-mock/tests/span_ancestry.rs b/tracing-mock/tests/span_ancestry.rs index 603c0a6b3..0136e7cd1 100644 --- a/tracing-mock/tests/span_ancestry.rs +++ b/tracing-mock/tests/span_ancestry.rs @@ -6,7 +6,7 @@ //! //! [`ExpectedSpan`]: crate::span::ExpectedSpan //! -use tracing::collect::with_default; +use tracing::{collect::with_default, Level}; use tracing_mock::{collector, expect}; #[test] @@ -30,8 +30,8 @@ fn contextual_parent() { #[test] #[should_panic( - expected = "to have a contextual parent with name='contextual parent', but \ - actually has a contextual parent with name='another parent'" + expected = "to have a contextual parent span named `contextual parent`,\n\ + [contextual_parent_wrong_name] but got one named `another parent` instead." )] fn contextual_parent_wrong_name() { let span = expect::span() @@ -51,11 +51,58 @@ fn contextual_parent_wrong_name() { handle.assert_finished(); } +#[test] +#[should_panic(expected = "to have a contextual parent span a span with Id `1`,\n\ + [contextual_parent_wrong_id] but got one with Id `2` instead")] +fn contextual_parent_wrong_id() { + let id = expect::id(); + let span = expect::span() + .named("span") + .with_ancestry(expect::has_contextual_parent(&id)); + + let (collector, handle) = collector::mock() + .new_span(&id) + .new_span(expect::span()) + .enter(expect::span()) + .new_span(span) + .run_with_handle(); + + with_default(collector, || { + let _span = tracing::info_span!("contextual parent"); + 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" + expected = "to have a contextual parent span at level `Level(Info)`,\n\ + [contextual_parent_wrong_level] but got one at level `Level(Debug)` instead." )] +fn contextual_parent_wrong_level() { + let parent = expect::span().at_level(Level::INFO); + let span = expect::span() + .named("span") + .with_ancestry(expect::has_contextual_parent(parent)); + + let (collector, handle) = collector::mock() + .enter(expect::span()) + .new_span(span) + .run_with_handle(); + + with_default(collector, || { + let _guard = tracing::debug_span!("contextual parent").entered(); + tracing::info_span!("span"); + }); + + handle.assert_finished(); +} + +#[test] +#[should_panic(expected = "to have a contextual parent span, but it is actually a \ + contextual root")] fn expect_contextual_parent_actual_contextual_root() { let span = expect::span() .named("span") @@ -71,10 +118,8 @@ fn expect_contextual_parent_actual_contextual_root() { } #[test] -#[should_panic( - expected = "to have a contextual parent with name='contextual parent', but actually has an \ - explicit parent with name='explicit parent'" -)] +#[should_panic(expected = "to have a contextual parent span, but it actually has an \ + explicit parent span")] fn expect_contextual_parent_actual_explicit_parent() { let span = expect::span() .named("span") @@ -94,10 +139,8 @@ fn expect_contextual_parent_actual_explicit_parent() { } #[test] -#[should_panic( - expected = "to have a contextual parent with name='contextual parent', but was actually an \ - explicit root" -)] +#[should_panic(expected = "to have a contextual parent span, but it is actually an \ + explicit root")] fn expect_contextual_parent_actual_explicit_root() { let span = expect::span() .named("span") @@ -132,10 +175,7 @@ fn contextual_root() { } #[test] -#[should_panic( - expected = "to be a contextual root, but actually has a contextual parent with \ - name='contextual parent'" -)] +#[should_panic(expected = "to be a contextual root, but it actually has a contextual parent span")] fn expect_contextual_root_actual_contextual_parent() { let span = expect::span() .named("span") @@ -155,10 +195,7 @@ fn expect_contextual_root_actual_contextual_parent() { } #[test] -#[should_panic( - expected = "to be a contextual root, but actually has an explicit parent with \ - name='explicit parent'" -)] +#[should_panic(expected = "to be a contextual root, but it actually has an explicit parent span")] fn expect_contextual_root_actual_explicit_parent() { let span = expect::span() .named("span") @@ -178,7 +215,7 @@ fn expect_contextual_root_actual_explicit_parent() { } #[test] -#[should_panic(expected = "to be a contextual root, but was actually an explicit root")] +#[should_panic(expected = "to be a contextual root, but it is actually an explicit root")] fn expect_contextual_root_actual_explicit_root() { let span = expect::span() .named("span") @@ -218,8 +255,8 @@ fn explicit_parent() { #[test] #[should_panic( - expected = "to have an explicit parent with name='explicit parent', but actually has an \ - explicit parent with name='another parent'" + expected = "to have an explicit parent span named `explicit parent`,\n\ + [explicit_parent_wrong_name] but got one named `another parent` instead." )] fn explicit_parent_wrong_name() { let span = expect::span() @@ -240,10 +277,54 @@ fn explicit_parent_wrong_name() { } #[test] -#[should_panic( - expected = "to have an explicit parent with name='explicit parent', but actually has a \ - contextual parent with name='contextual parent'" -)] +#[should_panic(expected = "to have an explicit parent span a span with Id `1`,\n\ + [explicit_parent_wrong_id] but got one with Id `2` instead")] +fn explicit_parent_wrong_id() { + let id = expect::id(); + let span = expect::span() + .named("span") + .with_ancestry(expect::has_explicit_parent(&id)); + + let (collector, handle) = collector::mock() + .new_span(&id) + .new_span(expect::span()) + .new_span(span) + .run_with_handle(); + + with_default(collector, || { + let _span = tracing::info_span!("explicit parent"); + let another_span = tracing::info_span!("another parent"); + tracing::info_span!(parent: another_span.id(), "span"); + }); + + handle.assert_finished(); +} + +#[test] +#[should_panic(expected = "to have an explicit parent span at level `Level(Info)`,\n\ + [explicit_parent_wrong_level] but got one at level `Level(Debug)` instead.")] +fn explicit_parent_wrong_level() { + let parent = expect::span().at_level(Level::INFO); + let span = expect::span() + .named("span") + .with_ancestry(expect::has_explicit_parent(parent)); + + let (collector, handle) = collector::mock() + .new_span(expect::span()) + .new_span(span) + .run_with_handle(); + + with_default(collector, || { + let span = tracing::debug_span!("explicit parent"); + tracing::info_span!(parent: span.id(), "span"); + }); + + handle.assert_finished(); +} + +#[test] +#[should_panic(expected = "to have an explicit parent span, but it actually has a \ + contextual parent span")] fn expect_explicit_parent_actual_contextual_parent() { let span = expect::span() .named("span") @@ -263,10 +344,8 @@ fn expect_explicit_parent_actual_contextual_parent() { } #[test] -#[should_panic( - expected = "to have an explicit parent with name='explicit parent', but was actually a \ - contextual root" -)] +#[should_panic(expected = "to have an explicit parent span, but it is actually a \ + contextual root")] fn expect_explicit_parent_actual_contextual_root() { let span = expect::span() .named("span") @@ -282,10 +361,8 @@ fn expect_explicit_parent_actual_contextual_root() { } #[test] -#[should_panic( - expected = "to have an explicit parent with name='explicit parent', but was actually an \ - explicit root" -)] +#[should_panic(expected = "to have an explicit parent span, but it is actually an \ + explicit root")] fn expect_explicit_parent_actual_explicit_root() { let span = expect::span() .named("span") @@ -325,10 +402,7 @@ fn explicit_root() { } #[test] -#[should_panic( - expected = "to be an explicit root, but actually has a contextual parent with \ - name='contextual parent'" -)] +#[should_panic(expected = "to be an explicit root, but it actually has a contextual parent span")] fn expect_explicit_root_actual_contextual_parent() { let span = expect::span() .named("span") @@ -348,7 +422,7 @@ fn expect_explicit_root_actual_contextual_parent() { } #[test] -#[should_panic(expected = "to be an explicit root, but was actually a contextual root")] +#[should_panic(expected = "to be an explicit root, but it is actually a contextual root")] fn expect_explicit_root_actual_contextual_root() { let span = expect::span() .named("span") @@ -364,9 +438,7 @@ fn expect_explicit_root_actual_contextual_root() { } #[test] -#[should_panic( - expected = "to be an explicit root, but actually has an explicit parent with name='explicit parent'" -)] +#[should_panic(expected = "to be an explicit root, but it actually has an explicit parent span")] fn expect_explicit_root_actual_explicit_parent() { let span = expect::span() .named("span")