Skip to content
2 changes: 1 addition & 1 deletion tracing-mock/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ publish = false
[dependencies]
tracing = { path = "../tracing", version = "0.2" }
tracing-core = { path = "../tracing-core", version = "0.2", default-features = false }
tracing-subscriber = { path = "../tracing-subscriber", version = "0.3", default-features = false, optional = true }
tracing-subscriber = { path = "../tracing-subscriber", version = "0.3", default-features = false, features = ["registry"], optional = true }
tokio-test = { version = "0.4.2", optional = true }

# Fix minimal-versions; tokio-test fails with otherwise acceptable 0.1.0
Expand Down
8 changes: 8 additions & 0 deletions tracing-mock/src/collector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,14 @@ where
match self.expected.lock().unwrap().pop_front() {
None => {}
Some(Expect::Event(mut expected)) => {
#[cfg(feature = "tracing-subscriber")]
{
if !expected.scope_mut().is_empty() {
unimplemented!(
"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();
Expand Down
287 changes: 266 additions & 21 deletions tracing-mock/src/event.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,42 @@
//! An [`ExpectedEvent`] defines an event to be matched by the mock
//! collector API in the [`collector`] module.
//!
//! The expected event should be created with [`expect::event`] and a
//! chain of method calls to describe the assertions we wish to make
//! about the event.
//!
//! ```
//! use tracing::collect::with_default;
//! use tracing_mock::{collector, expect};
//!
//! let event = expect::event()
//! .at_level(tracing::Level::INFO)
//! .with_fields(expect::field("field.name").with_value(&"field_value"));
//!
//! let (collector, handle) = collector::mock()
//! .event(event)
//! .run_with_handle();
//!
//! with_default(collector, || {
//! tracing::info!(field.name = "field_value");
//! });
//!
//! handle.assert_finished();
//! ```
//!
//! [`collector`]: mod@crate::collector
//! [`expect::event`]: fn@crate::expect::event
#![allow(missing_docs)]
use super::{expect, field, metadata::ExpectedMetadata, span, Parent};

use std::fmt;

/// A mock event.
/// An expected event.
///
/// This is intended for use with the mock subscriber API in the
/// `subscriber` module.
/// For a detailed description and examples see the documentation for
/// the methods and the [`event`] module.
///
/// [`event`]: mod@crate::event
#[derive(Default, Eq, PartialEq)]
pub struct ExpectedEvent {
pub(super) fields: Option<field::ExpectedFields>,
Expand All @@ -20,6 +50,18 @@ pub fn msg(message: impl fmt::Display) -> ExpectedEvent {
}

impl ExpectedEvent {
/// Sets the expected name to match an event.
///
/// By default an event's name takes takes the form:
/// `event <file>:<line>` where `<file>` and `<line>` refer to the
/// location in the source code where the event was generated.
///
/// To overwrite the name of an event, it has to be constructed
/// directly instead of using one of the available macros.
///
/// In general, there are not many use cases for expecting an
/// event, as the value includes the file name and line number,
/// which can make it quite a fragile check.
pub fn named<I>(self, name: I) -> Self
where
I: Into<String>,
Expand All @@ -33,6 +75,30 @@ impl ExpectedEvent {
}
}

/// Sets the expected fields to match an event.
///
/// More information on the available validations is available on
/// the [`ExpectedFields`] docs.
///
/// ```
/// use tracing::collect::with_default;
/// use tracing_mock::{collector, expect};
///
/// let event = expect::event()
/// .with_fields(expect::field("field.name").with_value(&"field_value"));
///
/// let (collector, handle) = collector::mock()
/// .event(event)
/// .run_with_handle();
///
/// with_default(collector, || {
/// tracing::info!(field.name = "field_value");
/// });
///
/// handle.assert_finished();
/// ```
///
/// [`ExpectedFields`]: struct@crate::field::ExpectedFields
pub fn with_fields<I>(self, fields: I) -> Self
where
I: Into<field::ExpectedFields>,
Expand All @@ -43,6 +109,27 @@ impl ExpectedEvent {
}
}

/// Sets the expected level to match an event.
///
/// Only events recorded at `level` will be matched.
///
/// ```
/// use tracing::collect::with_default;
/// use tracing_mock::{collector, expect};
///
/// let event = expect::event()
/// .at_level(tracing::Level::WARN);
///
/// let (collector, handle) = collector::mock()
/// .event(event)
/// .run_with_handle();
///
/// with_default(collector, || {
/// tracing::warn!("this message is bad news");
/// });
///
/// handle.assert_finished();
/// ```
pub fn at_level(self, level: tracing::Level) -> Self {
Self {
metadata: ExpectedMetadata {
Expand All @@ -53,6 +140,25 @@ impl ExpectedEvent {
}
}

/// Sets the expected target to match an event.
///
/// ```
/// use tracing::collect::with_default;
/// use tracing_mock::{collector, expect};
///
/// let event = expect::event()
/// .with_target("some_target");
///
/// let (collector, handle) = collector::mock()
/// .event(event)
/// .run_with_handle();
///
/// with_default(collector, || {
/// tracing::info!(target: "some_target", field = &"value");
/// });
///
/// handle.assert_finished();
/// ```
pub fn with_target<I>(self, target: I) -> Self
where
I: Into<String>,
Expand All @@ -66,18 +172,168 @@ impl ExpectedEvent {
}
}

pub fn with_explicit_parent(self, parent: Option<&str>) -> ExpectedEvent {
let parent = match parent {
Some(name) => Parent::Explicit(name.into()),
None => Parent::ExplicitRoot,
};
/// Sets the expected explicit parent to match an event.
///
/// ```
/// use tracing::collect::with_default;
/// use tracing_mock::{collector, expect};
///
/// let event = expect::event()
/// .with_explicit_parent("parent_span");
///
/// let (collector, handle) = collector::mock()
/// .event(event)
/// .run_with_handle();
///
/// with_default(collector, || {
/// let parent = tracing::info_span!("parent_span");
/// tracing::info!(parent: parent.id(), field = &"value");
/// });
///
/// handle.assert_finished();
/// ```
pub fn with_explicit_parent(self, parent: &str) -> ExpectedEvent {
Self {
parent: Some(Parent::Explicit(parent.into())),
..self
}
}

/// Adds a validation that the event has no explicit parent.
///
/// ```
/// use tracing::collect::with_default;
/// use tracing_mock::{collector, expect};
///
/// let event = expect::event()
/// .without_explicit_parent();
///
/// let (collector, handle) = collector::mock()
/// .event(event)
/// .run_with_handle();
///
/// with_default(collector, || {
/// tracing::info!(field = &"value");
/// });
///
/// handle.assert_finished();
/// ```
pub fn without_explicit_parent(self) -> ExpectedEvent {
Self {
parent: Some(Parent::ExplicitRoot),
..self
}
}

/// Sets the expected contextual parent to match an event.
///
/// ```
/// use tracing::collect::with_default;
/// use tracing_mock::{collector, expect};
///
/// let event = expect::event()
/// .with_contextual_parent("parent_span");
///
/// let (collector, handle) = collector::mock()
/// .enter(expect::span())
/// .event(event)
/// .run_with_handle();
///
/// with_default(collector, || {
/// let parent = tracing::info_span!("parent_span");
/// let _guard = parent.enter();
/// tracing::info!(field = &"value");
/// });
///
/// handle.assert_finished();
/// ```
pub fn with_contextual_parent(self, parent: &str) -> ExpectedEvent {
Self {
parent: Some(Parent::Contextual(parent.into())),
..self
}
}

/// Adds a validation that the event has no contextual parent.
///
/// ```
/// use tracing::collect::with_default;
/// use tracing_mock::{collector, expect};
///
/// let event = expect::event()
/// .with_contextual_parent("parent_span");
///
/// let (collector, handle) = collector::mock()
/// .enter(expect::span())
/// .event(event)
/// .run_with_handle();
///
/// with_default(collector, || {
/// let parent = tracing::info_span!("parent_span");
/// let _guard = parent.enter();
/// tracing::info!(field = &"value");
/// });
///
/// handle.assert_finished();
/// ```
pub fn without_contextual_parent(self) -> ExpectedEvent {
Self {
parent: Some(Parent::ContextualRoot),
..self
}
}

/// Validates that the event is emitted within the scope of the
/// provided `spans`.
///
/// Note: This validation currently only works with a
/// [`MockSubscriber`], it doesn't perform any validation when used
/// with a [`MockCollector`].
///
/// ```
/// use tracing_mock::{collector, expect};
/// use tracing_subscriber::{
/// filter::filter_fn, registry, subscribe::CollectExt, util::SubscriberInitExt, Subscribe,
/// };
///
/// let span1 = expect
/// let event = expect::event()
/// .in_scope([expect::span("parent_span")]);
///
/// let (subscriber, handle) = subscriber::mock()
/// .enter(expect::span())
/// .event(event)
/// .run_with_handle();
///
/// let _collect = registry()
/// .with(subscriber.with_filter(filter_fn(move |_meta| true)))
/// .set_default();
///
/// let parent = tracing::info_span!("parent_span");
/// let _guard = parent.enter();
/// tracing::info!(field = &"value");
///
/// handle.assert_finished();
/// ```
///
/// [`MockSubscriber`]: struct@crate::subscriber::MockSubscriber
/// [`MockCollector`]: struct@crate::collector::MockCollector
#[cfg(feature = "tracing-subscriber")]
pub fn in_scope(self, spans: impl IntoIterator<Item = span::ExpectedSpan>) -> Self {
Self {
parent: Some(parent),
in_spans: spans.into_iter().collect(),
..self
}
}

pub fn check(
/// Provides access to the expected scope (spans) for this expected
/// event.
#[cfg(feature = "tracing-subscriber")]
pub(crate) fn scope_mut(&mut self) -> &mut [span::ExpectedSpan] {
&mut self.in_spans[..]
}

pub(crate) fn check(
&mut self,
event: &tracing::Event<'_>,
get_parent_name: impl FnOnce() -> Option<String>,
Expand Down Expand Up @@ -110,17 +366,6 @@ impl ExpectedEvent {
)
}
}

pub fn in_scope(self, spans: impl IntoIterator<Item = span::ExpectedSpan>) -> Self {
Self {
in_spans: spans.into_iter().collect(),
..self
}
}

pub fn scope_mut(&mut self) -> &mut [span::ExpectedSpan] {
&mut self.in_spans[..]
}
}

impl fmt::Display for ExpectedEvent {
Expand Down
12 changes: 6 additions & 6 deletions tracing/tests/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -337,7 +337,7 @@ fn both_shorthands() {
fn explicit_child() {
let (collector, handle) = collector::mock()
.new_span(expect::span().named("foo"))
.event(expect::event().with_explicit_parent(Some("foo")))
.event(expect::event().with_explicit_parent("foo"))
.only()
.run_with_handle();

Expand All @@ -354,11 +354,11 @@ fn explicit_child() {
fn explicit_child_at_levels() {
let (collector, handle) = collector::mock()
.new_span(expect::span().named("foo"))
.event(expect::event().with_explicit_parent(Some("foo")))
.event(expect::event().with_explicit_parent(Some("foo")))
.event(expect::event().with_explicit_parent(Some("foo")))
.event(expect::event().with_explicit_parent(Some("foo")))
.event(expect::event().with_explicit_parent(Some("foo")))
.event(expect::event().with_explicit_parent("foo"))
.event(expect::event().with_explicit_parent("foo"))
.event(expect::event().with_explicit_parent("foo"))
.event(expect::event().with_explicit_parent("foo"))
.event(expect::event().with_explicit_parent("foo"))
.only()
.run_with_handle();

Expand Down