mock: add ExpectedId to link span expectations (#3007)

It currently isn't possible to differentiate spans with the same name,
target, and level when setting expectations on `enter`, `exit`, and
`drop_span`. This is not an issue for `tracing-mock`'s original (and
still primary) use case, which is to test `tracing` itself. However,
when testing the tracing instrumentation in library or application code,
this can be a limitation.

For example, when testing the instrumentation in tokio
(tokio-rs/tokio#6112), it isn't possible to set an expectation on which
task span is entered first, because the name, target, and level of those
spans are always identical - in fact, the spans have the same metadata
and only the field values are different.

To make differentiating different spans possible, `ExpectId` has been
introduced. It is an opaque struct which represents a `span::Id` and can
be used to match spans from a `new_span` expectation (where a `NewSpan`
is accepted and all fields and values can be expected) through to
subsequent `enter`, `exit`, and `drop_span` expectations.

An `ExpectedId` is passed to an `ExpectedSpan` which then needs to be
expected with `MockCollector::new_span`. A clone of the `ExpectedId` (or
a clone of the `ExpectedSpan` with the `ExpectedId` already on it) will
then match the ID assigned to the span to the other span lifecycle
expectations.

The `ExpectedId` uses an `Arc<AtomicU64>` which has the ID for the new
span assigned to it, and then its clones will be matched against that
same ID.

In future changes it will also be possible to use this `ExpectedId` to
match parent spans, currently a parent is only matched by name.
This commit is contained in:
Hayden Stainsby 2024-06-21 11:11:29 +02:00
parent 8b66e704b6
commit 10e722e808
14 changed files with 1353 additions and 356 deletions

View File

@ -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)

View File

@ -69,13 +69,21 @@ fn span_on_drop() {
let subscriber = subscriber::mock()
.enter(expect::span().named("foo"))
.event(expect::event().at_level(Level::INFO))
.event(
expect::event()
.with_ancestry(expect::has_contextual_parent("foo"))
.at_level(Level::INFO),
)
.exit(expect::span().named("foo"))
.enter(expect::span().named("foo"))
.exit(expect::span().named("foo"))
.drop_span(expect::span().named("foo"))
.enter(expect::span().named("bar"))
.event(expect::event().at_level(Level::INFO))
.event(
expect::event()
.with_ancestry(expect::has_contextual_parent("bar"))
.at_level(Level::INFO),
)
.exit(expect::span().named("bar"))
.drop_span(expect::span().named("bar"))
.only()

View File

@ -0,0 +1,148 @@
//! Define the ancestry of an event or span.
//!
//! See the documentation on the [`Ancestry`] enum for further details.
use tracing_core::{
span::{self, Attributes},
Event,
};
/// The ancestry of an event or span.
///
/// An event or span can have an explicitly assigned parent, or be an explicit root. Otherwise,
/// an event or span may have a contextually assigned parent or in the final case will be a
/// contextual root.
#[derive(Debug, Eq, PartialEq)]
pub enum Ancestry {
/// The event or span has an explicitly assigned parent (created with `parent: span_id`) with
/// the specified name.
HasExplicitParent(String),
/// The event or span is an explicitly defined root. It was created with `parent: None` and
/// has no parent.
IsExplicitRoot,
/// The event or span has a contextually assigned parent with the specified name. It has no
/// explicitly assigned parent, nor has it been explicitly defined as a root (it was created
/// without the `parent:` directive). There was a span in context when this event or span was
/// created.
HasContextualParent(String),
/// The event or span is a contextual root. It has no explicitly assigned parent, nor has it
/// been explicitly defined as a root (it was created without the `parent:` directive).
/// Additionally, no span was in context when this event or span was created.
IsContextualRoot,
}
impl Ancestry {
#[track_caller]
pub(crate) fn check(
&self,
actual_ancestry: &Ancestry,
ctx: impl std::fmt::Display,
collector_name: &str,
) {
let expected_description = |ancestry: &Ancestry| match ancestry {
Self::IsExplicitRoot => "be an explicit root".to_string(),
Self::HasExplicitParent(name) => format!("have an explicit parent with name='{name}'"),
Self::IsContextualRoot => "be a contextual root".to_string(),
Self::HasContextualParent(name) => {
format!("have a contextual parent with name='{name}'")
}
};
let actual_description = |ancestry: &Ancestry| match ancestry {
Self::IsExplicitRoot => "was actually an explicit root".to_string(),
Self::HasExplicitParent(name) => {
format!("actually has an explicit parent with name='{name}'")
}
Self::IsContextualRoot => "was actually a contextual root".to_string(),
Self::HasContextualParent(name) => {
format!("actually has a contextual parent with name='{name}'")
}
};
assert_eq!(
self,
actual_ancestry,
"[{collector_name}] expected {ctx} to {expected_description}, but {actual_description}",
expected_description = expected_description(self),
actual_description = actual_description(actual_ancestry)
);
}
}
pub(crate) trait HasAncestry {
fn is_contextual(&self) -> bool;
fn is_root(&self) -> bool;
fn parent(&self) -> Option<&span::Id>;
}
impl HasAncestry for &Event<'_> {
fn is_contextual(&self) -> bool {
(self as &Event<'_>).is_contextual()
}
fn is_root(&self) -> bool {
(self as &Event<'_>).is_root()
}
fn parent(&self) -> Option<&span::Id> {
(self as &Event<'_>).parent()
}
}
impl HasAncestry for &Attributes<'_> {
fn is_contextual(&self) -> bool {
(self as &Attributes<'_>).is_contextual()
}
fn is_root(&self) -> bool {
(self as &Attributes<'_>).is_root()
}
fn parent(&self) -> Option<&span::Id> {
(self as &Attributes<'_>).parent()
}
}
/// Determines the ancestry of an actual span or event.
///
/// The rules for determining the ancestry are as follows:
///
/// +------------+--------------+-----------------+---------------------+
/// | Contextual | Current Span | Explicit Parent | Ancestry |
/// +------------+--------------+-----------------+---------------------+
/// | Yes | Yes | - | HasContextualParent |
/// | Yes | No | - | IsContextualRoot |
/// | No | - | Yes | HasExplicitParent |
/// | No | - | No | IsExplicitRoot |
/// +------------+--------------+-----------------+---------------------+
pub(crate) fn get_ancestry(
item: impl HasAncestry,
lookup_current: impl FnOnce() -> Option<span::Id>,
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())
}
}

View File

@ -29,7 +29,7 @@
//! [`subscriber`]: mod@crate::subscriber
//! [`expect::event`]: fn@crate::expect::event
#![allow(missing_docs)]
use super::{expect, field, metadata::ExpectedMetadata, span, Parent};
use crate::{ancestry::Ancestry, expect, field, metadata::ExpectedMetadata, span};
use std::fmt;
@ -42,7 +42,7 @@ use std::fmt;
#[derive(Default, Eq, PartialEq)]
pub struct ExpectedEvent {
pub(super) fields: Option<field::ExpectedFields>,
pub(super) parent: Option<Parent>,
pub(super) ancestry: Option<Ancestry>,
pub(super) in_spans: Option<Vec<span::ExpectedSpan>>,
pub(super) metadata: ExpectedMetadata,
}
@ -253,32 +253,30 @@ impl ExpectedEvent {
}
}
/// Configures this `ExpectedEvent` to expect an explicit parent span
/// when matching events or to be an explicit root.
/// Configures this `ExpectedEvent` to expect the specified [`Ancestry`].
/// An event's ancestry indicates whether is has a parent or is a root, and
/// whether the parent is explicitly or contextually assigned.
///
/// An _explicit_ parent span is one passed to the `span!` macro in the
/// `parent:` field.
/// An _explicit_ parent span is one passed to the `event!` macro in the
/// `parent:` field. If no `parent:` field is specified, then the event
/// will have a contextually determined parent or be a contextual root if
/// there is no parent.
///
/// If `Some("parent_name")` is passed to `with_explicit_parent` then
/// the provided string is the name of the parent span to expect.
///
/// To expect that an event is recorded with `parent: None`, `None`
/// can be passed to `with_explicit_parent` instead.
///
/// If an event is recorded without an explicit parent, or if the
/// explicit parent has a different name, this expectation will
/// fail.
/// If the parent is different from the provided one, this expectation
/// will fail.
///
/// # Examples
///
/// The explicit parent is matched by name:
/// If `expect::has_explicit_parent("parent_name")` is passed
/// `with_ancestry` then the provided string is the name of the explicit
/// parent span to expect.
///
/// ```
/// use tracing::subscriber::with_default;
/// use tracing_mock::{subscriber, expect};
///
/// let event = expect::event()
/// .with_explicit_parent(Some("parent_span"));
/// .with_ancestry(expect::has_explicit_parent("parent_span"));
///
/// let (subscriber, handle) = subscriber::mock()
/// .event(event)
@ -300,78 +298,31 @@ impl ExpectedEvent {
/// use tracing_mock::{subscriber, expect};
///
/// let event = expect::event()
/// .with_explicit_parent(None);
/// .with_ancestry(expect::is_explicit_root());
///
/// let (subscriber, handle) = subscriber::mock()
/// .enter(expect::span())
/// .event(event)
/// .run_with_handle();
///
/// with_default(subscriber, || {
/// let _guard = tracing::info_span!("contextual parent").entered();
/// tracing::info!(parent: None, field = &"value");
/// });
///
/// handle.assert_finished();
/// ```
///
/// In the example below, the expectation fails because the
/// event is contextually (rather than explicitly) within the span
/// `parent_span`:
///
/// ```should_panic
/// use tracing::subscriber::with_default;
/// use tracing_mock::{subscriber, expect};
///
/// let event = expect::event()
/// .with_explicit_parent(Some("parent_span"));
///
/// let (subscriber, handle) = subscriber::mock()
/// .enter(expect::span())
/// .event(event)
/// .run_with_handle();
///
/// with_default(subscriber, || {
/// let parent = tracing::info_span!("parent_span");
/// let _guard = parent.enter();
/// tracing::info!(field = &"value");
/// });
///
/// handle.assert_finished();
/// ```
pub fn with_explicit_parent(self, parent: Option<&str>) -> ExpectedEvent {
let parent = match parent {
Some(name) => Parent::Explicit(name.into()),
None => Parent::ExplicitRoot,
};
Self {
parent: Some(parent),
..self
}
}
/// Configures this `ExpectedEvent` to match an event with a
/// contextually-determined parent span.
///
/// The provided string is the name of the parent span to expect.
/// To expect that the event is a contextually-determined root, pass
/// `None` instead.
///
/// To expect an event with an explicit parent span, use
/// [`ExpectedEvent::with_explicit_parent`].
///
/// If an event is recorded which is not inside a span, has an explicitly
/// overridden parent span, or with a differently-named span as its
/// parent, this expectation will fail.
///
/// # Examples
///
/// The contextual parent is matched by name:
/// When `expect::has_contextual_parent("parent_name")` is passed to
/// `with_ancestry` then the provided string is the name of the contextual
/// parent span to expect.
///
/// ```
/// use tracing::subscriber::with_default;
/// use tracing_mock::{subscriber, expect};
///
/// let event = expect::event()
/// .with_contextual_parent(Some("parent_span"));
/// .with_ancestry(expect::has_contextual_parent("parent_span"));
///
/// let (subscriber, handle) = subscriber::mock()
/// .enter(expect::span())
@ -387,14 +338,15 @@ impl ExpectedEvent {
/// handle.assert_finished();
/// ```
///
/// Matching an event recorded outside of a span:
/// Matching an event recorded outside of a span, a contextual
/// root:
///
/// ```
/// use tracing::subscriber::with_default;
/// use tracing_mock::{subscriber, expect};
///
/// let event = expect::event()
/// .with_contextual_parent(None);
/// .with_ancestry(expect::is_contextual_root());
///
/// let (subscriber, handle) = subscriber::mock()
/// .event(event)
@ -407,15 +359,16 @@ impl ExpectedEvent {
/// handle.assert_finished();
/// ```
///
/// In the example below, the expectation fails because the
/// event is recorded with an explicit parent:
/// In the example below, the expectation fails because the event is
/// recorded with an explicit parent, however a contextual parent is
/// expected.
///
/// ```should_panic
/// use tracing::subscriber::with_default;
/// use tracing_mock::{subscriber, expect};
///
/// let event = expect::event()
/// .with_contextual_parent(Some("parent_span"));
/// .with_ancestry(expect::has_contextual_parent("parent_span"));
///
/// let (subscriber, handle) = subscriber::mock()
/// .enter(expect::span())
@ -429,13 +382,9 @@ impl ExpectedEvent {
///
/// handle.assert_finished();
/// ```
pub fn with_contextual_parent(self, parent: Option<&str>) -> ExpectedEvent {
let parent = match parent {
Some(name) => Parent::Contextual(name.into()),
None => Parent::ContextualRoot,
};
pub fn with_ancestry(self, ancenstry: Ancestry) -> ExpectedEvent {
Self {
parent: Some(parent),
ancestry: Some(ancenstry),
..self
}
}
@ -557,7 +506,7 @@ impl ExpectedEvent {
pub(crate) fn check(
&mut self,
event: &tracing::Event<'_>,
get_parent_name: impl FnOnce() -> Option<String>,
get_ancestry: impl FnOnce() -> Ancestry,
subscriber_name: &str,
) {
let meta = event.metadata();
@ -577,14 +526,9 @@ impl ExpectedEvent {
checker.finish();
}
if let Some(ref expected_parent) = self.parent {
let actual_parent = get_parent_name();
expected_parent.check_parent_name(
actual_parent.as_deref(),
event.parent().cloned(),
event.metadata().name(),
subscriber_name,
)
if let Some(ref expected_ancestry) = self.ancestry {
let actual_ancestry = get_ancestry();
expected_ancestry.check(&actual_ancestry, event.metadata().name(), subscriber_name);
}
}
}
@ -615,7 +559,7 @@ impl fmt::Debug for ExpectedEvent {
s.field("fields", fields);
}
if let Some(ref parent) = self.parent {
if let Some(ref parent) = self.ancestry {
s.field("parent", &format_args!("{:?}", parent));
}

View File

@ -1,9 +1,10 @@
use std::fmt;
use crate::{
ancestry::Ancestry,
event::ExpectedEvent,
field::{ExpectedField, ExpectedFields, ExpectedValue},
span::{ExpectedSpan, NewSpan},
span::{ExpectedId, ExpectedSpan, NewSpan},
};
#[derive(Debug, Eq, PartialEq)]
@ -51,6 +52,47 @@ pub fn span() -> ExpectedSpan {
}
}
/// Returns a new, unset `ExpectedId`.
///
/// The `ExpectedId` needs to be attached to a [`NewSpan`] or an
/// [`ExpectedSpan`] passed to [`MockCollector::new_span`] to
/// ensure that it gets set. When the a clone of the same
/// `ExpectedSpan` is attached to an [`ExpectedSpan`] and passed to
/// any other method on [`MockCollector`] that accepts it, it will
/// ensure that it is exactly the same span used across those
/// distinct expectations.
///
/// For more details on how to use this struct, see the documentation
/// on [`ExpectedSpan::with_id`].
///
/// [`MockCollector`]: struct@crate::collector::MockCollector
/// [`MockCollector::new_span`]: fn@crate::collector::MockCollector::new_span
pub fn id() -> ExpectedId {
ExpectedId::new_unset()
}
/// Convenience function that returns [`Ancestry::IsContextualRoot`].
pub fn is_contextual_root() -> Ancestry {
Ancestry::IsContextualRoot
}
/// Convenience function that returns [`Ancestry::HasContextualParent`] with
/// provided name.
pub fn has_contextual_parent<S: Into<String>>(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<S: Into<String>>(name: S) -> Ancestry {
Ancestry::HasExplicitParent(name.into())
}
impl Expect {
pub(crate) fn bad(&self, name: impl AsRef<str>, what: fmt::Arguments<'_>) {
let name = name.as_ref();

View File

@ -116,6 +116,7 @@
//!
//! [`Layer`]: trait@tracing_subscriber::layer::Layer
use crate::{
ancestry::{get_ancestry, Ancestry, HasAncestry},
event::ExpectedEvent,
expect::Expect,
span::{ExpectedSpan, NewSpan},
@ -904,8 +905,7 @@ where
match self.expected.lock().unwrap().pop_front() {
None => {}
Some(Expect::Event(mut expected)) => {
let get_parent_name = || cx.event_span(event).map(|span| span.name().to_string());
expected.check(event, get_parent_name, &self.name);
expected.check(event, || context_get_ancestry(event, &cx), &self.name);
if let Some(expected_scope) = expected.scope_mut() {
self.check_event_scope(cx.event_scope(event), expected_scope);
@ -936,13 +936,7 @@ where
let was_expected = matches!(expected.front(), Some(Expect::NewSpan(_)));
if was_expected {
if let Expect::NewSpan(mut expected) = expected.pop_front().unwrap() {
let get_parent_name = || {
span.parent()
.and_then(|id| cx.span(id))
.or_else(|| cx.lookup_current())
.map(|span| span.name().to_string())
};
expected.check(span, get_parent_name, &self.name);
expected.check(span, || context_get_ancestry(span, &cx), &self.name);
}
}
}
@ -1042,6 +1036,17 @@ where
}
}
fn context_get_ancestry<C>(item: impl HasAncestry, ctx: &Context<'_, C>) -> Ancestry
where
C: Subscriber + for<'a> LookupSpan<'a>,
{
get_ancestry(
item,
|| ctx.lookup_current().map(|s| s.id()),
|span_id| ctx.span(span_id).map(|span| span.name()),
)
}
impl fmt::Debug for MockLayer {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut s = f.debug_struct("ExpectSubscriber");

View File

@ -1,4 +1,5 @@
#![doc = include_str!("../README.md")]
pub mod ancestry;
pub mod event;
pub mod expect;
pub mod field;
@ -8,88 +9,3 @@ pub mod subscriber;
#[cfg(feature = "tracing-subscriber")]
pub mod layer;
#[derive(Debug, Eq, PartialEq)]
pub enum Parent {
ContextualRoot,
Contextual(String),
ExplicitRoot,
Explicit(String),
}
impl Parent {
pub fn check_parent_name(
&self,
parent_name: Option<&str>,
provided_parent: Option<tracing_core::span::Id>,
ctx: impl std::fmt::Display,
subscriber_name: &str,
) {
match self {
Parent::ExplicitRoot => {
assert!(
provided_parent.is_none(),
"[{}] expected {} to be an explicit root, but its parent was actually {:?} (name: {:?})",
subscriber_name,
ctx,
provided_parent,
parent_name,
);
}
Parent::Explicit(expected_parent) => {
assert!(
provided_parent.is_some(),
"[{}] expected {} to have explicit parent {}, but it has no explicit parent",
subscriber_name,
ctx,
expected_parent,
);
assert_eq!(
Some(expected_parent.as_ref()),
parent_name,
"[{}] expected {} to have explicit parent {}, but its parent was actually {:?} (name: {:?})",
subscriber_name,
ctx,
expected_parent,
provided_parent,
parent_name,
);
}
Parent::ContextualRoot => {
assert!(
provided_parent.is_none(),
"[{}] expected {} to be a contextual root, but its parent was actually {:?} (name: {:?})",
subscriber_name,
ctx,
provided_parent,
parent_name,
);
assert!(
parent_name.is_none(),
"[{}] expected {} to be contextual a root, but we were inside span {:?}",
subscriber_name,
ctx,
parent_name,
);
}
Parent::Contextual(expected_parent) => {
assert!(provided_parent.is_none(),
"[{}] expected {} to have a contextual parent\nbut it has the explicit parent {:?} (name: {:?})",
subscriber_name,
ctx,
provided_parent,
parent_name,
);
assert_eq!(
Some(expected_parent.as_ref()),
parent_name,
"[{}] expected {} to have contextual parent {:?}, but got {:?}",
subscriber_name,
ctx,
expected_parent,
parent_name,
);
}
}
}
}

View File

@ -41,7 +41,7 @@
//! let new_span = span
//! .clone()
//! .with_fields(expect::field("field.name").with_value(&"field_value"))
//! .with_explicit_parent(Some("parent_span"));
//! .with_ancestry(expect::has_explicit_parent("parent_span"));
//!
//! let (subscriber, handle) = subscriber::mock()
//! .new_span(expect::span().named("parent_span"))
@ -92,9 +92,16 @@
//! [`expect::span`]: fn@crate::expect::span
#![allow(missing_docs)]
use crate::{
expect, field::ExpectedFields, metadata::ExpectedMetadata, subscriber::SpanState, Parent,
ancestry::Ancestry, expect, field::ExpectedFields, metadata::ExpectedMetadata,
subscriber::SpanState,
};
use std::{
error, fmt,
sync::{
atomic::{AtomicU64, Ordering},
Arc,
},
};
use std::fmt;
/// A mock span.
///
@ -104,6 +111,7 @@ use std::fmt;
/// [`subscriber`]: mod@crate::subscriber
#[derive(Clone, Default, Eq, PartialEq)]
pub struct ExpectedSpan {
pub(crate) id: Option<ExpectedId>,
pub(crate) metadata: ExpectedMetadata,
}
@ -127,7 +135,7 @@ pub struct ExpectedSpan {
pub struct NewSpan {
pub(crate) span: ExpectedSpan,
pub(crate) fields: ExpectedFields,
pub(crate) parent: Option<Parent>,
pub(crate) ancestry: Option<Ancestry>,
}
pub fn named<I>(name: I) -> ExpectedSpan
@ -137,6 +145,24 @@ where
expect::span().named(name)
}
/// A mock span ID.
///
/// This ID makes it possible to link together calls to different
/// [`MockSubscriber`] span methods that take an [`ExpectedSpan`] in
/// addition to those that take a [`NewSpan`].
///
/// Use [`expect::id`] to construct a new, unset `ExpectedId`.
///
/// For more details on how to use this struct, see the documentation
/// on [`ExpectedSpan::with_id`].
///
/// [`expect::id`]: fn@crate::expect::id
/// [`MockSubscriber`]: struct@crate::subscriber::MockSubscriber
#[derive(Clone, Default)]
pub struct ExpectedId {
inner: Arc<AtomicU64>,
}
impl ExpectedSpan {
/// Sets a name to expect when matching a span.
///
@ -188,6 +214,100 @@ impl ExpectedSpan {
name: Some(name.into()),
..self.metadata
},
..self
}
}
/// Sets the `ID` to expect when matching a span.
///
/// The [`ExpectedId`] can be used to differentiate spans that are
/// otherwise identical. An [`ExpectedId`] needs to be attached to
/// an `ExpectedSpan` or [`NewSpan`] which is passed to
/// [`MockSubscriber::new_span`]. The same [`ExpectedId`] can then
/// be used to match the exact same span when passed to
/// [`MockSubscriber::enter`], [`MockSubscriber::exit`], and
/// [`MockSubscriber::drop_span`].
///
/// This is especially useful when `tracing-mock` is being used to
/// test the traces being generated within your own crate, in which
/// case you may need to distinguish between spans which have
/// identical metadata but different field values, which can
/// otherwise only be checked in [`MockSubscriber::new_span`].
///
/// # Examples
///
/// Here we expect that the span that is created first is entered
/// second:
///
/// ```
/// use tracing_mock::{subscriber, expect};
/// let id1 = expect::id();
/// let span1 = expect::span().named("span").with_id(id1.clone());
/// let id2 = expect::id();
/// let span2 = expect::span().named("span").with_id(id2.clone());
///
/// let (subscriber, handle) = subscriber::mock()
/// .new_span(span1.clone())
/// .new_span(span2.clone())
/// .enter(span2)
/// .enter(span1)
/// .run_with_handle();
///
/// tracing::subscriber::with_default(subscriber, || {
/// fn create_span() -> tracing::Span {
/// tracing::info_span!("span")
/// }
///
/// let span1 = create_span();
/// let span2 = create_span();
///
/// let _guard2 = span2.enter();
/// let _guard1 = span1.enter();
/// });
///
/// handle.assert_finished();
/// ```
///
/// If the order that the spans are entered changes, the test will
/// fail:
///
/// ```should_panic
/// use tracing_mock::{subscriber, expect};
/// let id1 = expect::id();
/// let span1 = expect::span().named("span").with_id(id1.clone());
/// let id2 = expect::id();
/// let span2 = expect::span().named("span").with_id(id2.clone());
///
/// let (subscriber, handle) = subscriber::mock()
/// .new_span(span1.clone())
/// .new_span(span2.clone())
/// .enter(span2)
/// .enter(span1)
/// .run_with_handle();
///
/// tracing::subscriber::with_default(subscriber, || {
/// fn create_span() -> tracing::Span {
/// tracing::info_span!("span")
/// }
///
/// let span1 = create_span();
/// let span2 = create_span();
///
/// let _guard1 = span1.enter();
/// let _guard2 = span2.enter();
/// });
///
/// handle.assert_finished();
/// ```
///
/// [`MockSubscriber::new_span`]: fn@crate::subscriber::MockSubscriber::new_span
/// [`MockSubscriber::enter`]: fn@crate::subscriber::MockSubscriber::enter
/// [`MockSubscriber::exit`]: fn@crate::subscriber::MockSubscriber::exit
/// [`MockSubscriber::drop_span`]: fn@crate::subscriber::MockSubscriber::drop_span
pub fn with_id(self, id: ExpectedId) -> Self {
Self {
id: Some(id),
..self
}
}
@ -241,6 +361,7 @@ impl ExpectedSpan {
level: Some(level),
..self.metadata
},
..self
}
}
@ -297,11 +418,13 @@ impl ExpectedSpan {
target: Some(target.into()),
..self.metadata
},
..self
}
}
/// Configures this `ExpectedSpan` to expect an explicit parent
/// span or to be an explicit root.
/// Configures this `ExpectedSpan` to expect the specified [`Ancestry`]. A
/// span's ancestry indicates whether it has a parent or is a root span
/// and whether the parent is explitly or contextually assigned.
///
/// **Note**: This method returns a [`NewSpan`] and as such, this
/// expectation can only be validated when expecting a new span via
@ -310,27 +433,24 @@ impl ExpectedSpan {
/// method on [`MockSubscriber`] that takes an `ExpectedSpan`.
///
/// An _explicit_ parent span is one passed to the `span!` macro in the
/// `parent:` field.
/// `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::{subscriber, expect};
///
/// let span = expect::span()
/// .with_explicit_parent(Some("parent_span"));
/// .with_ancestry(expect::has_explicit_parent("parent_span"));
///
/// let (subscriber, handle) = subscriber::mock()
/// .new_span(expect::span().named("parent_span"))
@ -351,7 +471,7 @@ impl ExpectedSpan {
/// use tracing_mock::{subscriber, expect};
///
/// let span = expect::span()
/// .with_explicit_parent(None);
/// .with_ancestry(expect::is_explicit_root());
///
/// let (subscriber, handle) = subscriber::mock()
/// .new_span(span)
@ -373,7 +493,7 @@ impl ExpectedSpan {
///
/// let parent_span = expect::span().named("parent_span");
/// let span = expect::span()
/// .with_explicit_parent(Some("parent_span"));
/// .with_ancestry(expect::has_explicit_parent("parent_span"));
///
/// let (subscriber, handle) = subscriber::mock()
/// .new_span(parent_span.clone())
@ -390,53 +510,15 @@ impl ExpectedSpan {
/// handle.assert_finished();
/// ```
///
/// [`MockSubscriber`]: struct@crate::subscriber::MockSubscriber
/// [`MockSubscriber::enter`]: fn@crate::subscriber::MockSubscriber::enter
/// [`MockSubscriber::exit`]: fn@crate::subscriber::MockSubscriber::exit
/// [`MockSubscriber::new_span`]: fn@crate::subscriber::MockSubscriber::new_span
pub fn with_explicit_parent(self, parent: Option<&str>) -> NewSpan {
let parent = match parent {
Some(name) => Parent::Explicit(name.into()),
None => Parent::ExplicitRoot,
};
NewSpan {
parent: Some(parent),
span: self,
..Default::default()
}
}
/// Configures this `ExpectedSpan` to expect a
/// contextually-determined parent span, or be a contextual
/// root.
///
/// **Note**: This method returns a [`NewSpan`] and as such, this
/// expectation can only be validated when expecting a new span via
/// [`MockSubscriber::new_span`]. It cannot be validated on
/// [`MockSubscriber::enter`], [`MockSubscriber::exit`], or any other
/// method on [`MockSubscriber`] that takes an `ExpectedSpan`.
///
/// The provided string is the name of the parent span to expect.
/// To expect that the event is a contextually-determined root, pass
/// `None` instead.
///
/// To expect a span with an explicit parent span, use
/// [`ExpectedSpan::with_explicit_parent`].
///
/// If a span is recorded which is not inside a span, has an explicitly
/// overridden parent span, or has a differently-named span as its
/// parent, this expectation will fail.
///
/// # Examples
///
/// The contextual parent is matched by name:
/// In the following example, we expect that the matched span is
/// a contextually-determined root:
///
/// ```
/// use tracing_mock::{subscriber, expect};
///
/// let parent_span = expect::span().named("parent_span");
/// let span = expect::span()
/// .with_contextual_parent(Some("parent_span"));
/// .with_ancestry(expect::has_contextual_parent("parent_span"));
///
/// let (subscriber, handle) = subscriber::mock()
/// .new_span(parent_span.clone())
@ -460,7 +542,7 @@ impl ExpectedSpan {
/// use tracing_mock::{subscriber, expect};
///
/// let span = expect::span()
/// .with_contextual_parent(None);
/// .with_ancestry(expect::is_contextual_root());
///
/// let (subscriber, handle) = subscriber::mock()
/// .new_span(span)
@ -474,22 +556,26 @@ impl ExpectedSpan {
/// ```
///
/// In the example below, the expectation fails because the
/// span is recorded with an explicit parent:
/// span is *contextually*—as opposed to explicitly—within the span
/// `parent_span`:
///
/// ```should_panic
/// use tracing_mock::{subscriber, expect};
///
/// let parent_span = expect::span().named("parent_span");
/// let span = expect::span()
/// .with_contextual_parent(Some("parent_span"));
/// .with_ancestry(expect::has_explicit_parent("parent_span"));
///
/// let (subscriber, handle) = subscriber::mock()
/// .new_span(expect::span().named("parent_span"))
/// .new_span(parent_span.clone())
/// .enter(parent_span)
/// .new_span(span)
/// .run_with_handle();
///
/// tracing::subscriber::with_default(subscriber, || {
/// let parent = tracing::info_span!("parent_span");
/// tracing::info_span!(parent: parent.id(), "span");
/// let _guard = parent.enter();
/// tracing::info_span!("span");
/// });
///
/// handle.assert_finished();
@ -499,13 +585,9 @@ impl ExpectedSpan {
/// [`MockSubscriber::enter`]: fn@crate::subscriber::MockSubscriber::enter
/// [`MockSubscriber::exit`]: fn@crate::subscriber::MockSubscriber::exit
/// [`MockSubscriber::new_span`]: fn@crate::subscriber::MockSubscriber::new_span
pub fn with_contextual_parent(self, parent: Option<&str>) -> NewSpan {
let parent = match parent {
Some(name) => Parent::Contextual(name.into()),
None => Parent::ContextualRoot,
};
pub fn with_ancestry(self, ancestry: Ancestry) -> NewSpan {
NewSpan {
parent: Some(parent),
ancestry: Some(ancestry),
span: self,
..Default::default()
}
@ -598,6 +680,15 @@ impl ExpectedSpan {
pub(crate) fn check(&self, actual: &SpanState, subscriber_name: &str) {
let meta = actual.metadata();
let name = meta.name();
if let Some(expected_id) = &self.id {
expected_id.check(
actual.id(),
format_args!("span `{}`", name),
subscriber_name,
);
}
self.metadata
.check(meta, format_args!("span `{}`", name), subscriber_name);
}
@ -643,39 +734,15 @@ impl From<ExpectedSpan> for NewSpan {
}
impl NewSpan {
/// Configures this `ExpectedSpan` to expect an explicit parent
/// span or to be an explicit root.
/// Configures this `NewSpan` to expect the specified [`Ancestry`]. A
/// span's ancestry indicates whether it has a parent or is a root span
/// and whether the parent is explitly or contextually assigned.
///
/// For more information and examples, see the documentation on
/// [`ExpectedSpan::with_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,
};
/// [`ExpectedSpan::with_ancestry`].
pub fn with_ancestry(self, ancestry: Ancestry) -> NewSpan {
NewSpan {
parent: Some(parent),
..self
}
}
/// Configures this `NewSpan` to expect a
/// contextually-determined parent span, or to be a contextual
/// root.
///
/// For more information and examples, see the documentation on
/// [`ExpectedSpan::with_contextual_parent`].
///
/// [`ExpectedSpan::with_contextual_parent`]: fn@crate::span::ExpectedSpan::with_contextual_parent
pub fn with_contextual_parent(self, parent: Option<&str>) -> NewSpan {
let parent = match parent {
Some(name) => Parent::Contextual(name.into()),
None => Parent::ContextualRoot,
};
NewSpan {
parent: Some(parent),
ancestry: Some(ancestry),
..self
}
}
@ -699,7 +766,7 @@ impl NewSpan {
pub(crate) fn check(
&mut self,
span: &tracing_core::span::Attributes<'_>,
get_parent_name: impl FnOnce() -> Option<String>,
get_ancestry: impl FnOnce() -> Ancestry,
subscriber_name: &str,
) {
let meta = span.metadata();
@ -711,14 +778,13 @@ impl NewSpan {
span.record(&mut checker);
checker.finish();
if let Some(expected_parent) = self.parent.as_ref() {
let actual_parent = get_parent_name();
expected_parent.check_parent_name(
actual_parent.as_deref(),
span.parent().cloned(),
if let Some(ref expected_ancestry) = self.ancestry {
let actual_ancestry = get_ancestry();
expected_ancestry.check(
&actual_ancestry,
format_args!("span `{}`", name),
subscriber_name,
)
);
}
}
}
@ -749,7 +815,7 @@ impl fmt::Debug for NewSpan {
s.field("target", &target);
}
if let Some(ref parent) = self.parent {
if let Some(ref parent) = self.ancestry {
s.field("parent", &format_args!("{:?}", parent));
}
@ -760,3 +826,69 @@ impl fmt::Debug for NewSpan {
s.finish()
}
}
impl PartialEq for ExpectedId {
fn eq(&self, other: &Self) -> bool {
self.inner.load(Ordering::Relaxed) == other.inner.load(Ordering::Relaxed)
}
}
impl Eq for ExpectedId {}
impl ExpectedId {
const UNSET: u64 = 0;
pub(crate) fn new_unset() -> Self {
Self {
inner: Arc::new(AtomicU64::from(Self::UNSET)),
}
}
pub(crate) fn set(&self, span_id: u64) -> Result<(), SetActualSpanIdError> {
self.inner
.compare_exchange(Self::UNSET, span_id, Ordering::Relaxed, Ordering::Relaxed)
.map_err(|current| SetActualSpanIdError {
previous_span_id: current,
new_span_id: span_id,
})?;
Ok(())
}
pub(crate) fn check(&self, actual: u64, ctx: fmt::Arguments<'_>, subscriber_name: &str) {
let id = self.inner.load(Ordering::Relaxed);
assert!(
id != Self::UNSET,
"\n[{}] expected {} to have expected ID set, but it hasn't been, \
perhaps this `ExpectedId` wasn't used in a call to `MockSubscriber::new_span()`?",
subscriber_name,
ctx,
);
assert_eq!(
id, actual,
"\n[{}] expected {} to have ID `{}`, but it has `{}` instead",
subscriber_name, ctx, id, actual,
);
}
}
#[derive(Debug)]
pub(crate) struct SetActualSpanIdError {
previous_span_id: u64,
new_span_id: u64,
}
impl fmt::Display for SetActualSpanIdError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"Could not set `ExpecedId` to {new}, \
it had already been set to {previous}",
new = self.new_span_id,
previous = self.previous_span_id
)
}
}
impl error::Error for SetActualSpanIdError {}

View File

@ -138,6 +138,7 @@
//! [`Subscriber`]: trait@tracing::Subscriber
//! [`MockSubscriber`]: struct@crate::subscriber::MockSubscriber
use crate::{
ancestry::get_ancestry,
event::ExpectedEvent,
expect::Expect,
field::ExpectedFields,
@ -159,12 +160,17 @@ use tracing::{
};
pub(crate) struct SpanState {
id: u64,
name: &'static str,
refs: usize,
meta: &'static Metadata<'static>,
}
impl SpanState {
pub(crate) fn id(&self) -> u64 {
self.id
}
pub(crate) fn metadata(&self) -> &'static Metadata<'static> {
self.meta
}
@ -387,7 +393,7 @@ where
/// This function accepts `Into<NewSpan>` instead of
/// [`ExpectedSpan`] directly, so it can be used to test
/// span fields and the span parent. This is because a
/// subscriber only receives the span fields and parent when
/// collector only receives the span fields and parent when
/// a span is created, not when it is entered.
///
/// The new span doesn't need to be entered for this expectation
@ -1030,20 +1036,24 @@ where
{
if expected.scope_mut().is_some() {
unimplemented!(
"Expected scope for events is not supported with `MockSubscriber`."
"Expected scope for events is not supported with `MockCollector`."
)
}
}
let get_parent_name = || {
let stack = self.current.lock().unwrap();
let spans = self.spans.lock().unwrap();
event
.parent()
.and_then(|id| spans.get(id))
.or_else(|| stack.last().and_then(|id| spans.get(id)))
.map(|s| s.name.to_string())
let event_get_ancestry = || {
get_ancestry(
event,
|| self.lookup_current(),
|span_id| {
self.spans
.lock()
.unwrap()
.get(span_id)
.map(|span| span.name)
},
)
};
expected.check(event, get_parent_name, &self.name);
expected.check(event, event_get_ancestry, &self.name);
}
Some(ex) => ex.bad(&self.name, format_args!("observed event {:#?}", event)),
}
@ -1100,19 +1110,27 @@ where
let mut spans = self.spans.lock().unwrap();
if was_expected {
if let Expect::NewSpan(mut expected) = expected.pop_front().unwrap() {
let get_parent_name = || {
let stack = self.current.lock().unwrap();
span.parent()
.and_then(|id| spans.get(id))
.or_else(|| stack.last().and_then(|id| spans.get(id)))
.map(|s| s.name.to_string())
};
expected.check(span, get_parent_name, &self.name);
if let Some(expected_id) = &expected.span.id {
expected_id.set(id.into_u64()).unwrap();
}
expected.check(
span,
|| {
get_ancestry(
span,
|| self.lookup_current(),
|span_id| spans.get(span_id).map(|span| span.name),
)
},
&self.name,
);
}
}
spans.insert(
id.clone(),
SpanState {
id: id.into_u64(),
name: meta.name(),
refs: 1,
meta,
@ -1256,6 +1274,16 @@ where
}
}
impl<F> Running<F>
where
F: Fn(&Metadata<'_>) -> bool,
{
fn lookup_current(&self) -> Option<span::Id> {
let stack = self.current.lock().unwrap();
stack.last().cloned()
}
}
impl MockHandle {
#[cfg(feature = "tracing-subscriber")]
pub(crate) fn new(expected: Arc<Mutex<VecDeque<Expect>>>, name: String) -> Self {

View File

@ -0,0 +1,346 @@
//! Tests assertions for the parent made on [`ExpectedEvent`].
//!
//! The tests in this module completely cover the positive and negative cases
//! when expecting that an event is a contextual or explicit root or expecting
//! that an event has a specific contextual or explicit parent.
//!
//! [`ExpectedEvent`]: crate::event::ExpectedEvent
use tracing::subscriber::with_default;
use tracing_mock::{expect, subscriber};
#[test]
fn contextual_parent() {
let event = expect::event().with_ancestry(expect::has_contextual_parent("contextual parent"));
let (subscriber, handle) = subscriber::mock()
.enter(expect::span())
.event(event)
.run_with_handle();
with_default(subscriber, || {
let _guard = tracing::info_span!("contextual parent").entered();
tracing::info!(field = &"value");
});
handle.assert_finished();
}
#[test]
#[should_panic(
expected = "to have a contextual parent with name='contextual parent', but \
actually has a contextual parent with name='another parent'"
)]
fn contextual_parent_wrong_name() {
let event = expect::event().with_ancestry(expect::has_contextual_parent("contextual parent"));
let (subscriber, handle) = subscriber::mock()
.enter(expect::span())
.event(event)
.run_with_handle();
with_default(subscriber, || {
let _guard = tracing::info_span!("another parent").entered();
tracing::info!(field = &"value");
});
handle.assert_finished();
}
#[test]
#[should_panic(
expected = "to have a contextual parent with name='contextual parent', but was actually a \
contextual root"
)]
fn expect_contextual_parent_actual_contextual_root() {
let event = expect::event().with_ancestry(expect::has_contextual_parent("contextual parent"));
let (subscriber, handle) = subscriber::mock().event(event).run_with_handle();
with_default(subscriber, || {
tracing::info!(field = &"value");
});
handle.assert_finished();
}
#[test]
#[should_panic(
expected = "to have a contextual parent with name='contextual parent', but actually has an \
explicit parent with name='explicit parent'"
)]
fn expect_contextual_parent_actual_explicit_parent() {
let event = expect::event().with_ancestry(expect::has_contextual_parent("contextual parent"));
let (subscriber, handle) = subscriber::mock().event(event).run_with_handle();
with_default(subscriber, || {
let span = tracing::info_span!("explicit parent");
tracing::info!(parent: span.id(), field = &"value");
});
handle.assert_finished();
}
#[test]
#[should_panic(
expected = "to have a contextual parent with name='contextual parent', but was actually an \
explicit root"
)]
fn expect_contextual_parent_actual_explicit_root() {
let event = expect::event().with_ancestry(expect::has_contextual_parent("contextual parent"));
let (subscriber, handle) = subscriber::mock()
.enter(expect::span())
.event(event)
.run_with_handle();
with_default(subscriber, || {
let _guard = tracing::info_span!("contextual parent").entered();
tracing::info!(parent: None, field = &"value");
});
handle.assert_finished();
}
#[test]
fn contextual_root() {
let event = expect::event().with_ancestry(expect::is_contextual_root());
let (subscriber, handle) = subscriber::mock().event(event).run_with_handle();
with_default(subscriber, || {
tracing::info!(field = &"value");
});
handle.assert_finished();
}
#[test]
#[should_panic(
expected = "to be a contextual root, but actually has a contextual parent with \
name='contextual parent'"
)]
fn expect_contextual_root_actual_contextual_parent() {
let event = expect::event().with_ancestry(expect::is_contextual_root());
let (subscriber, handle) = subscriber::mock()
.enter(expect::span())
.event(event)
.run_with_handle();
with_default(subscriber, || {
let _guard = tracing::info_span!("contextual parent").entered();
tracing::info!(field = &"value");
});
handle.assert_finished();
}
#[test]
#[should_panic(
expected = "to be a contextual root, but actually has an explicit parent with \
name='explicit parent'"
)]
fn expect_contextual_root_actual_explicit_parent() {
let event = expect::event().with_ancestry(expect::is_contextual_root());
let (subscriber, handle) = subscriber::mock().event(event).run_with_handle();
with_default(subscriber, || {
let span = tracing::info_span!("explicit parent");
tracing::info!(parent: span.id(), field = &"value");
});
handle.assert_finished();
}
#[test]
#[should_panic(expected = "to be a contextual root, but was actually an explicit root")]
fn expect_contextual_root_actual_explicit_root() {
let event = expect::event().with_ancestry(expect::is_contextual_root());
let (subscriber, handle) = subscriber::mock()
.enter(expect::span())
.event(event)
.run_with_handle();
with_default(subscriber, || {
let _guard = tracing::info_span!("contextual parent").entered();
tracing::info!(parent: None, field = &"value");
});
handle.assert_finished();
}
#[test]
fn explicit_parent() {
let event = expect::event().with_ancestry(expect::has_explicit_parent("explicit parent"));
let (subscriber, handle) = subscriber::mock().event(event).run_with_handle();
with_default(subscriber, || {
let span = tracing::info_span!("explicit parent");
tracing::info!(parent: span.id(), field = &"value");
});
handle.assert_finished();
}
#[test]
#[should_panic(
expected = "to have an explicit parent with name='explicit parent', but actually has an \
explicit parent with name='another parent'"
)]
fn explicit_parent_wrong_name() {
let event = expect::event().with_ancestry(expect::has_explicit_parent("explicit parent"));
let (subscriber, handle) = subscriber::mock().event(event).run_with_handle();
with_default(subscriber, || {
let span = tracing::info_span!("another parent");
tracing::info!(parent: span.id(), field = &"value");
});
handle.assert_finished();
}
#[test]
#[should_panic(
expected = "to have an explicit parent with name='explicit parent', but actually has a \
contextual parent with name='contextual parent'"
)]
fn expect_explicit_parent_actual_contextual_parent() {
let event = expect::event().with_ancestry(expect::has_explicit_parent("explicit parent"));
let (subscriber, handle) = subscriber::mock()
.enter(expect::span())
.event(event)
.run_with_handle();
with_default(subscriber, || {
let _guard = tracing::info_span!("contextual parent").entered();
tracing::info!(field = &"value");
});
handle.assert_finished();
}
#[test]
#[should_panic(
expected = "to have an explicit parent with name='explicit parent', but was actually a \
contextual root"
)]
fn expect_explicit_parent_actual_contextual_root() {
let event = expect::event().with_ancestry(expect::has_explicit_parent("explicit parent"));
let (subscriber, handle) = subscriber::mock().event(event).run_with_handle();
with_default(subscriber, || {
tracing::info!(field = &"value");
});
handle.assert_finished();
}
#[test]
#[should_panic(
expected = "to have an explicit parent with name='explicit parent', but was actually an \
explicit root"
)]
fn expect_explicit_parent_actual_explicit_root() {
let event = expect::event().with_ancestry(expect::has_explicit_parent("explicit parent"));
let (subscriber, handle) = subscriber::mock()
.enter(expect::span())
.event(event)
.run_with_handle();
with_default(subscriber, || {
let _guard = tracing::info_span!("contextual parent").entered();
tracing::info!(parent: None, field = &"value");
});
handle.assert_finished();
}
#[test]
fn explicit_root() {
let event = expect::event().with_ancestry(expect::is_explicit_root());
let (subscriber, handle) = subscriber::mock()
.enter(expect::span())
.event(event)
.run_with_handle();
with_default(subscriber, || {
let _guard = tracing::info_span!("contextual parent").entered();
tracing::info!(parent: None, field = &"value");
});
handle.assert_finished();
}
#[test]
#[should_panic(
expected = "to be an explicit root, but actually has a contextual parent with \
name='contextual parent'"
)]
fn expect_explicit_root_actual_contextual_parent() {
let event = expect::event().with_ancestry(expect::is_explicit_root());
let (subscriber, handle) = subscriber::mock()
.enter(expect::span())
.event(event)
.run_with_handle();
with_default(subscriber, || {
let _guard = tracing::info_span!("contextual parent").entered();
tracing::info!(field = &"value");
});
handle.assert_finished();
}
#[test]
#[should_panic(expected = "to be an explicit root, but was actually a contextual root")]
fn expect_explicit_root_actual_contextual_root() {
let event = expect::event().with_ancestry(expect::is_explicit_root());
let (subscriber, handle) = subscriber::mock().event(event).run_with_handle();
with_default(subscriber, || {
tracing::info!(field = &"value");
});
handle.assert_finished();
}
#[test]
#[should_panic(
expected = "to be an explicit root, but actually has an explicit parent with name='explicit parent'"
)]
fn expect_explicit_root_actual_explicit_parent() {
let event = expect::event().with_ancestry(expect::is_explicit_root());
let (subscriber, handle) = subscriber::mock().event(event).run_with_handle();
with_default(subscriber, || {
let span = tracing::info_span!("explicit parent");
tracing::info!(parent: span.id(), field = &"value");
});
handle.assert_finished();
}
#[test]
fn explicit_and_contextual_root_is_explicit() {
let event = expect::event().with_ancestry(expect::is_explicit_root());
let (subscriber, handle) = subscriber::mock().event(event).run_with_handle();
with_default(subscriber, || {
tracing::info!(parent: None, field = &"value");
});
handle.assert_finished();
}

View File

@ -0,0 +1,401 @@
//! Tests assertions for the parent made on [`ExpectedSpan`].
//!
//! The tests in this module completely cover the positive and negative cases
//! when expecting that a span is a contextual or explicit root or expecting
//! that a span has a specific contextual or explicit parent.
//!
//! [`ExpectedSpan`]: crate::span::ExpectedSpan
//!
use tracing::subscriber::with_default;
use tracing_mock::{expect, subscriber};
#[test]
fn contextual_parent() {
let span = expect::span()
.named("span")
.with_ancestry(expect::has_contextual_parent("contextual parent"));
let (subscriber, handle) = subscriber::mock()
.enter(expect::span())
.new_span(span)
.run_with_handle();
with_default(subscriber, || {
let _guard = tracing::info_span!("contextual parent").entered();
tracing::info_span!("span");
});
handle.assert_finished();
}
#[test]
#[should_panic(
expected = "to have a contextual parent with name='contextual parent', but \
actually has a contextual parent with name='another parent'"
)]
fn contextual_parent_wrong_name() {
let span = expect::span()
.named("span")
.with_ancestry(expect::has_contextual_parent("contextual parent"));
let (subscriber, handle) = subscriber::mock()
.enter(expect::span())
.new_span(span)
.run_with_handle();
with_default(subscriber, || {
let _guard = tracing::info_span!("another parent").entered();
tracing::info_span!("span");
});
handle.assert_finished();
}
#[test]
#[should_panic(
expected = "to have a contextual parent with name='contextual parent', but was actually a \
contextual root"
)]
fn expect_contextual_parent_actual_contextual_root() {
let span = expect::span()
.named("span")
.with_ancestry(expect::has_contextual_parent("contextual parent"));
let (subscriber, handle) = subscriber::mock().new_span(span).run_with_handle();
with_default(subscriber, || {
tracing::info_span!("span");
});
handle.assert_finished();
}
#[test]
#[should_panic(
expected = "to have a contextual parent with name='contextual parent', but actually has an \
explicit parent with name='explicit parent'"
)]
fn expect_contextual_parent_actual_explicit_parent() {
let span = expect::span()
.named("span")
.with_ancestry(expect::has_contextual_parent("contextual parent"));
let (subscriber, handle) = subscriber::mock()
.new_span(expect::span())
.new_span(span)
.run_with_handle();
with_default(subscriber, || {
let span = tracing::info_span!("explicit parent");
tracing::info_span!(parent: span.id(), "span");
});
handle.assert_finished();
}
#[test]
#[should_panic(
expected = "to have a contextual parent with name='contextual parent', but was actually an \
explicit root"
)]
fn expect_contextual_parent_actual_explicit_root() {
let span = expect::span()
.named("span")
.with_ancestry(expect::has_contextual_parent("contextual parent"));
let (subscriber, handle) = subscriber::mock()
.enter(expect::span())
.new_span(span)
.run_with_handle();
with_default(subscriber, || {
let _guard = tracing::info_span!("contextual parent").entered();
tracing::info_span!(parent: None, "span");
});
handle.assert_finished();
}
#[test]
fn contextual_root() {
let span = expect::span()
.named("span")
.with_ancestry(expect::is_contextual_root());
let (subscriber, handle) = subscriber::mock().new_span(span).run_with_handle();
with_default(subscriber, || {
tracing::info_span!("span");
});
handle.assert_finished();
}
#[test]
#[should_panic(
expected = "to be a contextual root, but actually has a contextual parent with \
name='contextual parent'"
)]
fn expect_contextual_root_actual_contextual_parent() {
let span = expect::span()
.named("span")
.with_ancestry(expect::is_contextual_root());
let (subscriber, handle) = subscriber::mock()
.enter(expect::span())
.new_span(span)
.run_with_handle();
with_default(subscriber, || {
let _guard = tracing::info_span!("contextual parent").entered();
tracing::info_span!("span");
});
handle.assert_finished();
}
#[test]
#[should_panic(
expected = "to be a contextual root, but actually has an explicit parent with \
name='explicit parent'"
)]
fn expect_contextual_root_actual_explicit_parent() {
let span = expect::span()
.named("span")
.with_ancestry(expect::is_contextual_root());
let (subscriber, handle) = subscriber::mock()
.new_span(expect::span())
.new_span(span)
.run_with_handle();
with_default(subscriber, || {
let span = tracing::info_span!("explicit parent");
tracing::info_span!(parent: span.id(), "span");
});
handle.assert_finished();
}
#[test]
#[should_panic(expected = "to be a contextual root, but was actually an explicit root")]
fn expect_contextual_root_actual_explicit_root() {
let span = expect::span()
.named("span")
.with_ancestry(expect::is_contextual_root());
let (subscriber, handle) = subscriber::mock()
.enter(expect::span())
.new_span(span)
.run_with_handle();
with_default(subscriber, || {
let _guard = tracing::info_span!("contextual parent").entered();
tracing::info_span!(parent: None, "span");
});
handle.assert_finished();
}
#[test]
fn explicit_parent() {
let span = expect::span()
.named("span")
.with_ancestry(expect::has_explicit_parent("explicit parent"));
let (subscriber, handle) = subscriber::mock()
.new_span(expect::span())
.new_span(span)
.run_with_handle();
with_default(subscriber, || {
let span = tracing::info_span!("explicit parent");
tracing::info_span!(parent: span.id(), "span");
});
handle.assert_finished();
}
#[test]
#[should_panic(
expected = "to have an explicit parent with name='explicit parent', but actually has an \
explicit parent with name='another parent'"
)]
fn explicit_parent_wrong_name() {
let span = expect::span()
.named("span")
.with_ancestry(expect::has_explicit_parent("explicit parent"));
let (subscriber, handle) = subscriber::mock()
.new_span(expect::span())
.new_span(span)
.run_with_handle();
with_default(subscriber, || {
let span = tracing::info_span!("another parent");
tracing::info_span!(parent: span.id(), "span");
});
handle.assert_finished();
}
#[test]
#[should_panic(
expected = "to have an explicit parent with name='explicit parent', but actually has a \
contextual parent with name='contextual parent'"
)]
fn expect_explicit_parent_actual_contextual_parent() {
let span = expect::span()
.named("span")
.with_ancestry(expect::has_explicit_parent("explicit parent"));
let (subscriber, handle) = subscriber::mock()
.enter(expect::span())
.new_span(span)
.run_with_handle();
with_default(subscriber, || {
let _guard = tracing::info_span!("contextual parent").entered();
tracing::info_span!("span");
});
handle.assert_finished();
}
#[test]
#[should_panic(
expected = "to have an explicit parent with name='explicit parent', but was actually a \
contextual root"
)]
fn expect_explicit_parent_actual_contextual_root() {
let span = expect::span()
.named("span")
.with_ancestry(expect::has_explicit_parent("explicit parent"));
let (subscriber, handle) = subscriber::mock().new_span(span).run_with_handle();
with_default(subscriber, || {
tracing::info_span!("span");
});
handle.assert_finished();
}
#[test]
#[should_panic(
expected = "to have an explicit parent with name='explicit parent', but was actually an \
explicit root"
)]
fn expect_explicit_parent_actual_explicit_root() {
let span = expect::span()
.named("span")
.with_ancestry(expect::has_explicit_parent("explicit parent"));
let (subscriber, handle) = subscriber::mock()
.enter(expect::span())
.new_span(span)
.run_with_handle();
with_default(subscriber, || {
let _guard = tracing::info_span!("contextual parent").entered();
tracing::info_span!(parent: None, "span");
});
handle.assert_finished();
}
#[test]
fn explicit_root() {
let span = expect::span()
.named("span")
.with_ancestry(expect::is_explicit_root());
let (subscriber, handle) = subscriber::mock()
.new_span(expect::span())
.enter(expect::span())
.new_span(span)
.run_with_handle();
with_default(subscriber, || {
let _guard = tracing::info_span!("contextual parent").entered();
tracing::info_span!(parent: None, "span");
});
handle.assert_finished();
}
#[test]
#[should_panic(
expected = "to be an explicit root, but actually has a contextual parent with \
name='contextual parent'"
)]
fn expect_explicit_root_actual_contextual_parent() {
let span = expect::span()
.named("span")
.with_ancestry(expect::is_explicit_root());
let (subscriber, handle) = subscriber::mock()
.enter(expect::span())
.new_span(span)
.run_with_handle();
with_default(subscriber, || {
let _guard = tracing::info_span!("contextual parent").entered();
tracing::info_span!("span");
});
handle.assert_finished();
}
#[test]
#[should_panic(expected = "to be an explicit root, but was actually a contextual root")]
fn expect_explicit_root_actual_contextual_root() {
let span = expect::span()
.named("span")
.with_ancestry(expect::is_explicit_root());
let (subscriber, handle) = subscriber::mock().new_span(span).run_with_handle();
with_default(subscriber, || {
tracing::info_span!("span");
});
handle.assert_finished();
}
#[test]
#[should_panic(
expected = "to be an explicit root, but actually has an explicit parent with name='explicit parent'"
)]
fn expect_explicit_root_actual_explicit_parent() {
let span = expect::span()
.named("span")
.with_ancestry(expect::is_explicit_root());
let (subscriber, handle) = subscriber::mock()
.new_span(expect::span())
.new_span(span)
.run_with_handle();
with_default(subscriber, || {
let span = tracing::info_span!("explicit parent");
tracing::info_span!(parent: span.id(), "span");
});
handle.assert_finished();
}
#[test]
fn explicit_and_contextual_root_is_explicit() {
let span = expect::span()
.named("span")
.with_ancestry(expect::is_explicit_root());
let (subscriber, handle) = subscriber::mock().new_span(span).run_with_handle();
with_default(subscriber, || {
tracing::info_span!(parent: None, "span");
});
handle.assert_finished();
}

View File

@ -338,7 +338,7 @@ fn both_shorthands() {
fn explicit_child() {
let (subscriber, handle) = subscriber::mock()
.new_span(expect::span().named("foo"))
.event(expect::event().with_explicit_parent(Some("foo")))
.event(expect::event().with_ancestry(expect::has_explicit_parent("foo")))
.only()
.run_with_handle();
@ -355,11 +355,11 @@ fn explicit_child() {
fn explicit_child_at_levels() {
let (subscriber, handle) = subscriber::mock()
.new_span(expect::span().named("foo"))
.event(expect::event().with_explicit_parent(Some("foo")))
.event(expect::event().with_explicit_parent(Some("foo")))
.event(expect::event().with_explicit_parent(Some("foo")))
.event(expect::event().with_explicit_parent(Some("foo")))
.event(expect::event().with_explicit_parent(Some("foo")))
.event(expect::event().with_ancestry(expect::has_explicit_parent("foo")))
.event(expect::event().with_ancestry(expect::has_explicit_parent("foo")))
.event(expect::event().with_ancestry(expect::has_explicit_parent("foo")))
.event(expect::event().with_ancestry(expect::has_explicit_parent("foo")))
.event(expect::event().with_ancestry(expect::has_explicit_parent("foo")))
.only()
.run_with_handle();

View File

@ -35,13 +35,21 @@ fn span_on_drop() {
let subscriber = subscriber::mock()
.enter(expect::span().named("foo"))
.event(expect::event().at_level(Level::INFO))
.event(
expect::event()
.with_ancestry(expect::has_contextual_parent("foo"))
.at_level(Level::INFO),
)
.exit(expect::span().named("foo"))
.enter(expect::span().named("foo"))
.exit(expect::span().named("foo"))
.drop_span(expect::span().named("foo"))
.enter(expect::span().named("bar"))
.event(expect::event().at_level(Level::INFO))
.event(
expect::event()
.with_ancestry(expect::has_contextual_parent("bar"))
.at_level(Level::INFO),
)
.exit(expect::span().named("bar"))
.drop_span(expect::span().named("bar"))
.only()

View File

@ -635,7 +635,11 @@ fn new_span_with_target_and_log_level() {
#[test]
fn explicit_root_span_is_root() {
let (subscriber, handle) = subscriber::mock()
.new_span(expect::span().named("foo").with_explicit_parent(None))
.new_span(
expect::span()
.named("foo")
.with_ancestry(expect::is_explicit_root()),
)
.only()
.run_with_handle();
@ -652,7 +656,11 @@ fn explicit_root_span_is_root_regardless_of_ctx() {
let (subscriber, handle) = subscriber::mock()
.new_span(expect::span().named("foo"))
.enter(expect::span().named("foo"))
.new_span(expect::span().named("bar").with_explicit_parent(None))
.new_span(
expect::span()
.named("bar")
.with_ancestry(expect::is_explicit_root()),
)
.exit(expect::span().named("foo"))
.only()
.run_with_handle();
@ -674,7 +682,7 @@ fn explicit_child() {
.new_span(
expect::span()
.named("bar")
.with_explicit_parent(Some("foo")),
.with_ancestry(expect::has_explicit_parent("foo")),
)
.only()
.run_with_handle();
@ -692,11 +700,31 @@ fn explicit_child() {
fn explicit_child_at_levels() {
let (subscriber, handle) = subscriber::mock()
.new_span(expect::span().named("foo"))
.new_span(expect::span().named("a").with_explicit_parent(Some("foo")))
.new_span(expect::span().named("b").with_explicit_parent(Some("foo")))
.new_span(expect::span().named("c").with_explicit_parent(Some("foo")))
.new_span(expect::span().named("d").with_explicit_parent(Some("foo")))
.new_span(expect::span().named("e").with_explicit_parent(Some("foo")))
.new_span(
expect::span()
.named("a")
.with_ancestry(expect::has_explicit_parent("foo")),
)
.new_span(
expect::span()
.named("b")
.with_ancestry(expect::has_explicit_parent("foo")),
)
.new_span(
expect::span()
.named("c")
.with_ancestry(expect::has_explicit_parent("foo")),
)
.new_span(
expect::span()
.named("d")
.with_ancestry(expect::has_explicit_parent("foo")),
)
.new_span(
expect::span()
.named("e")
.with_ancestry(expect::has_explicit_parent("foo")),
)
.only()
.run_with_handle();
@ -722,7 +750,7 @@ fn explicit_child_regardless_of_ctx() {
.new_span(
expect::span()
.named("baz")
.with_explicit_parent(Some("foo")),
.with_ancestry(expect::has_explicit_parent("foo")),
)
.exit(expect::span().named("bar"))
.only()
@ -741,7 +769,11 @@ fn explicit_child_regardless_of_ctx() {
#[test]
fn contextual_root() {
let (subscriber, handle) = subscriber::mock()
.new_span(expect::span().named("foo").with_contextual_parent(None))
.new_span(
expect::span()
.named("foo")
.with_ancestry(expect::is_contextual_root()),
)
.only()
.run_with_handle();
@ -761,7 +793,7 @@ fn contextual_child() {
.new_span(
expect::span()
.named("bar")
.with_contextual_parent(Some("foo")),
.with_ancestry(expect::has_contextual_parent("foo")),
)
.exit(expect::span().named("foo"))
.only()