mock: document public APIs in subscriber module (#2446)

There has been interest around publishing `tracing-mock` to crates.io
for some time. In order to make this possible, documentation and some
code clean up is needed.

The `subscriber` module needs documentation and examples.

This change adds documentation to the `subscriber` module and all the public
APIs within it. This includes doctests on all the methods which serve as
examples.

The `MockSubscriberBuilder::record` method was removed as its
functionality is not implemented.

Previously, the `MockSubscriber` would verify the scope of an
`ExpectedEvent`, even if `in_scope` hadn't been called. In this case,
that would check that an event was not in a span if `in_scope` had not
been called. `tracing-subscriber` all adhere to this pattern. However it
is different to the behavior of all other expectation methods, where an
explicit call is needed to expect something, otherwise nothing is
checked. As such, the behavior has been modified to align with the rest
of the crate. The previous behavior can be achieved by calling
`in_scope(None)` to verify that an event has no scope. The documentation
for `in_scope` has been updated with an example for this case.

The tests in `tracing-subscriber` which previously verified *implicitly*
that an event had no scope (by not calling `in_scope` at all) have *not*
been modified. It is my opinion that this implicit behavior was never
required.

Refs: #539
This commit is contained in:
Hayden Stainsby 2023-01-27 00:50:53 +01:00 committed by Eliza Weisman
parent 2cb1d63eca
commit 8c74eb8c91
3 changed files with 782 additions and 89 deletions

View File

@ -9,7 +9,7 @@
//!
//! ```
//! use tracing::subscriber::with_default;
//! use tracing_mock::{subscriber, expect};
//! use tracing_mock::{expect, subscriber};
//!
//! let event = expect::event()
//! .at_level(tracing::Level::INFO)
@ -43,7 +43,7 @@ use std::fmt;
pub struct ExpectedEvent {
pub(super) fields: Option<field::ExpectedFields>,
pub(super) parent: Option<Parent>,
pub(super) in_spans: Vec<span::ExpectedSpan>,
pub(super) in_spans: Option<Vec<span::ExpectedSpan>>,
pub(super) metadata: ExpectedMetadata,
}
@ -451,30 +451,28 @@ impl ExpectedEvent {
/// recorded event, the expectation will fail.
///
/// **Note**: This validation currently only works with a
/// [`MockSubscriber`]. If used with a [`MockCollector`], the
/// [`MockLayer`]. If used with a [`MockSubscriber`], the
/// expectation will fail directly as it is unimplemented.
///
/// # Examples
///
/// ```
/// use tracing_mock::{expect, subscriber};
/// use tracing_subscriber::{
/// filter::filter_fn, registry, subscribe::CollectExt, util::SubscriberInitExt, Subscribe,
/// };
/// use tracing_mock::{expect, layer};
/// use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, Layer};
///
/// let event = expect::event().in_scope([
/// expect::span().named("parent_span"),
/// expect::span().named("grandparent_span")
/// ]);
///
/// let (subscriber, handle) = subscriber::mock()
/// let (layer, handle) = layer::mock()
/// .enter(expect::span())
/// .enter(expect::span())
/// .event(event)
/// .run_with_handle();
///
/// let _subscriber = registry()
/// .with(subscriber.with_filter(filter_fn(move |_meta| true)))
/// let _subscriber = tracing_subscriber::registry()
/// .with(layer.with_filter(tracing_subscriber::filter::filter_fn(move |_meta| true)))
/// .set_default();
///
/// let grandparent = tracing::info_span!("grandparent_span");
@ -489,23 +487,21 @@ impl ExpectedEvent {
/// The scope must match exactly, otherwise the expectation will fail:
///
/// ```should_panic
/// use tracing_mock::{expect, subscriber};
/// use tracing_subscriber::{
/// filter::filter_fn, registry, subscribe::CollectExt, util::SubscriberInitExt, Subscribe,
/// };
/// use tracing_mock::{expect, layer};
/// use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, Layer};
///
/// let event = expect::event().in_scope([
/// expect::span().named("parent_span"),
/// expect::span().named("grandparent_span")
/// ]);
///
/// let (subscriber, handle) = subscriber::mock()
/// let (layer, handle) = layer::mock()
/// .enter(expect::span())
/// .event(event)
/// .run_with_handle();
///
/// let _subscriber = registry()
/// .with(subscriber.with_filter(filter_fn(move |_meta| true)))
/// let _subscriber = tracing_subscriber::registry()
/// .with(layer.with_filter(tracing_subscriber::filter::filter_fn(move |_meta| true)))
/// .set_default();
///
/// let parent = tracing::info_span!("parent_span");
@ -514,12 +510,39 @@ impl ExpectedEvent {
///
/// handle.assert_finished();
/// ```
///
/// It is also possible to test that an event has no parent spans
/// by passing `None` to `in_scope`. If the event is within a
/// span, the test will fail:
///
/// ```should_panic
/// use tracing_mock::{expect, layer};
/// use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, Layer};
///
/// let event = expect::event().in_scope(None);
///
/// let (layer, handle) = layer::mock()
/// .enter(expect::span())
/// .event(event)
/// .run_with_handle();
///
/// let _subscriber = tracing_subscriber::registry()
/// .with(layer.with_filter(tracing_subscriber::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();
/// ```
///
/// [`MockLayer`]: struct@crate::layer::MockLayer
/// [`MockSubscriber`]: struct@crate::subscriber::MockSubscriber
/// [`MockCollector`]: struct@crate::subscriber::MockCollector
#[cfg(feature = "tracing-subscriber")]
pub fn in_scope(self, spans: impl IntoIterator<Item = span::ExpectedSpan>) -> Self {
Self {
in_spans: spans.into_iter().collect(),
in_spans: Some(spans.into_iter().collect()),
..self
}
}
@ -527,8 +550,8 @@ impl ExpectedEvent {
/// 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 scope_mut(&mut self) -> Option<&mut [span::ExpectedSpan]> {
self.in_spans.as_mut().map(|s| &mut s[..])
}
pub(crate) fn check(
@ -596,8 +619,8 @@ impl fmt::Debug for ExpectedEvent {
s.field("parent", &format_args!("{:?}", parent));
}
if !self.in_spans.is_empty() {
s.field("in_spans", &self.in_spans);
if let Some(in_spans) = &self.in_spans {
s.field("in_spans", in_spans);
}
s.finish()

View File

@ -1,8 +1,123 @@
#![allow(missing_docs, dead_code)]
//! An implementation of the [`Layer`] trait which validates that
//! the `tracing` data it recieves matches the expected output for a test.
//!
//!
//! The [`MockLayer`] is the central component in these tools. The
//! `MockLayer` has expectations set on it which are later
//! validated as the code under test is run.
//!
//! ```
//! use tracing_mock::{expect, field, layer};
//! use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, Layer};
//!
//! let (layer, handle) = layer::mock()
//! // Expect a single event with a specified message
//! .event(expect::event().with_fields(field::msg("droids")))
//! .run_with_handle();
//!
//! // Use `set_default` to apply the `MockSubscriber` until the end
//! // of the current scope (when the guard `_subscriber` is dropped).
//! let _subscriber = tracing_subscriber::registry()
//! .with(layer.with_filter(tracing_subscriber::filter::filter_fn(move |_meta| true)))
//! .set_default();
//!
//! // These *are* the droids we are looking for
//! tracing::info!("droids");
//!
//! // Use the handle to check the assertions. This line will panic if an
//! // assertion is not met.
//! handle.assert_finished();
//! ```
//!
//! A more complex example may consider multiple spans and events with
//! their respective fields:
//!
//! ```
//! use tracing_mock::{expect, field, layer};
//! use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, Layer};
//!
//! let span = expect::span()
//! .named("my_span");
//! let (layer, handle) = layer::mock()
//! // Enter a matching span
//! .enter(span.clone())
//! // Record an event with message "collect parting message"
//! .event(expect::event().with_fields(field::msg("say hello")))
//! // Exit a matching span
//! .exit(span)
//! // Expect no further messages to be recorded
//! .only()
//! // Return the layer and handle
//! .run_with_handle();
//!
//! // Use `set_default` to apply the `MockLayers` until the end
//! // of the current scope (when the guard `_subscriber` is dropped).
//! let _subscriber = tracing_subscriber::registry()
//! .with(layer.with_filter(tracing_subscriber::filter::filter_fn(move |_meta| true)))
//! .set_default();
//!
//! {
//! let span = tracing::trace_span!(
//! "my_span",
//! greeting = "hello world",
//! );
//!
//! let _guard = span.enter();
//! tracing::info!("say hello");
//! }
//!
//! // Use the handle to check the assertions. This line will panic if an
//! // assertion is not met.
//! handle.assert_finished();
//! ```
//!
//! If we modify the previous example so that we **don't** enter the
//! span before recording an event, the test will fail:
//!
//! ```should_panic
//! use tracing_mock::{expect, field, layer};
//! use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, Layer};
//!
//! let span = expect::span()
//! .named("my_span");
//! let (layer, handle) = layer::mock()
//! // Enter a matching span
//! .enter(span.clone())
//! // Record an event with message "collect parting message"
//! .event(expect::event().with_fields(field::msg("say hello")))
//! // Exit a matching span
//! .exit(span)
//! // Expect no further messages to be recorded
//! .only()
//! // Return the subscriber and handle
//! .run_with_handle();
//!
//! // Use `set_default` to apply the `MockSubscriber` until the end
//! // of the current scope (when the guard `_subscriber` is dropped).
//! let _subscriber = tracing_subscriber::registry()
//! .with(layer.with_filter(tracing_subscriber::filter::filter_fn(move |_meta| true)))
//! .set_default();
//!
//! {
//! let span = tracing::trace_span!(
//! "my_span",
//! greeting = "hello world",
//! );
//!
//! // Don't enter the span.
//! // let _guard = span.enter();
//! tracing::info!("say hello");
//! }
//!
//! // Use the handle to check the assertions. This line will panic if an
//! // assertion is not met.
//! handle.assert_finished();
//! ```
//!
//! [`Layer`]: trait@tracing_subscriber::layer::Layer
use crate::{
event::ExpectedEvent,
expect::Expect,
field::ExpectedFields,
span::{ExpectedSpan, NewSpan},
subscriber::MockHandle,
};
@ -21,6 +136,55 @@ use std::{
sync::{Arc, Mutex},
};
/// Create a [`MockLayerBuilder`] used to construct a
/// [`MockLayer`].
///
/// For additional information and examples, see the [`layer`]
/// module and [`MockLayerBuilder`] documentation.
///
/// # Examples
///
/// ```
/// use tracing_mock::{expect, field, layer};
/// use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, Layer};
///
/// let span = expect::span()
/// .named("my_span");
/// let (layer, handle) = layer::mock()
/// // Enter a matching span
/// .enter(span.clone())
/// // Record an event with message "collect parting message"
/// .event(expect::event().with_fields(field::msg("say hello")))
/// // Exit a matching span
/// .exit(span)
/// // Expect no further messages to be recorded
/// .only()
/// // Return the subscriber and handle
/// .run_with_handle();
///
/// // Use `set_default` to apply the `MockSubscriber` until the end
/// // of the current scope (when the guard `_subscriber` is dropped).
/// let _subscriber = tracing_subscriber::registry()
/// .with(layer.with_filter(tracing_subscriber::filter::filter_fn(move |_meta| true)))
/// .set_default();
///
/// {
/// let span = tracing::trace_span!(
/// "my_span",
/// greeting = "hello world",
/// );
///
/// let _guard = span.enter();
/// tracing::info!("say hello");
/// }
///
/// // Use the handle to check the assertions. This line will panic if an
/// // assertion is not met.
/// handle.assert_finished();
/// ```
///
/// [`layer`]: mod@crate::layer
#[must_use]
pub fn mock() -> MockLayerBuilder {
MockLayerBuilder {
expected: Default::default(),
@ -31,15 +195,81 @@ pub fn mock() -> MockLayerBuilder {
}
}
/// Create a [`MockLayerBuilder`] with a name already set.
///
/// This constructor is equivalent to calling
/// [`MockLayerBuilder::named`] in the following way"
/// `layer::mock().named(name)`.
///
/// For additional information and examples, see the [`layer`]
/// module and [`MockLayerBuilder`] documentation.
///
/// # Examples
///
/// The example from [`named`] could be rewritten as:
///
/// ```should_panic
/// use tracing_mock::{expect, layer};
/// use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, Layer};
///
/// let (layer_1, handle_1) = layer::named("subscriber-1")
/// .event(expect::event())
/// .run_with_handle();
///
/// let (layer_2, handle_2) = layer::named("subscriber-2")
/// .event(expect::event())
/// .run_with_handle();
///
/// let _subscriber = tracing_subscriber::registry()
/// .with(
/// layer_2.with_filter(tracing_subscriber::filter::filter_fn(move |_meta| true))
/// )
/// .set_default();
/// {
/// let _subscriber = tracing_subscriber::registry()
/// .with(
/// layer_1
/// .with_filter(tracing_subscriber::filter::filter_fn(move |_meta| true))
/// )
/// .set_default();
///
/// tracing::info!("a");
/// }
///
/// handle_1.assert_finished();
/// handle_2.assert_finished();
/// ```
///
/// [`MockLayerBuilder::named`]: fn@crate::layer::MockLayerBuilder::named
/// [`layer`]: mod@crate::layer
#[must_use]
pub fn named(name: impl std::fmt::Display) -> MockLayerBuilder {
mock().named(name)
}
/// A builder for constructing [`MockLayer`]s.
///
/// The methods on this builder set expectations which are then
/// validated by the constructed [`MockLayer`].
///
/// For a detailed description and examples see the documentation
/// for the methods and the [`layer`] module.
///
/// [`layer`]: mod@crate::layer
pub struct MockLayerBuilder {
expected: VecDeque<Expect>,
name: String,
}
/// A layer which validates the traces it receives.
///
/// A `MockLayer` is constructed with a
/// [`MockLayerBuilder`]. For a detailed description and examples,
/// see the documentation for that struct and for the [`layer`]
/// module.
///
/// [`layer`]: mod@crate::layer
pub struct MockLayer {
expected: Arc<Mutex<VecDeque<Expect>>>,
current: Mutex<Vec<Id>>,
@ -58,8 +288,61 @@ impl MockLayerBuilder {
/// When a test has only one mock layer, this is sufficient. However,
/// some tests may include multiple layers, in order to test
/// interactions between multiple layers. In that case, it can be
/// helpful to give each layer a separate name to distinguish where the
/// helpful to give each layers a separate name to distinguish where the
/// debugging output comes from.
///
/// # Examples
///
/// In the following example, we create two layers, both
/// expecting to receive an event. As we only record a single
/// event, the test will fail:
///
/// ```should_panic
/// use tracing_mock::{layer, expect};
/// use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, Layer};
///
/// let (layer_1, handle_1) = layer::mock()
/// .named("layer-1")
/// .event(expect::event())
/// .run_with_handle();
///
/// let (layer_2, handle_2) = layer::mock()
/// .named("layer-2")
/// .event(expect::event())
/// .run_with_handle();
///
/// let _subscriber = tracing_subscriber::registry()
/// .with(
/// layer_2.with_filter(tracing_subscriber::filter::filter_fn(move |_meta| true))
/// )
/// .set_default();
/// {
/// let _subscriber = tracing_subscriber::registry()
/// .with(
/// layer_1
/// .with_filter(tracing_subscriber::filter::filter_fn(move |_meta| true))
/// )
/// .set_default();
///
/// tracing::info!("a");
/// }
///
/// handle_1.assert_finished();
/// handle_2.assert_finished();
/// ```
///
/// In the test output, we see that the layer which didn't
/// received the event was the one named `layer-2`, which is
/// correct as the layer named `layer-1` was the default
/// when the event was recorded:
///
/// ```text
/// [main::layer-2] more notifications expected: [
/// Event(
/// MockEvent,
/// ),
/// ]', tracing-mock/src/subscriber.rs:472:13
/// ```
pub fn named(mut self, name: impl fmt::Display) -> Self {
use std::fmt::Write;
if !self.name.is_empty() {
@ -70,34 +353,125 @@ impl MockLayerBuilder {
self
}
pub fn enter(mut self, span: ExpectedSpan) -> Self {
self.expected.push_back(Expect::Enter(span));
self
}
/// Adds an expectation that an event matching the [`ExpectedEvent`]
/// will be recorded next.
///
/// The `event` can be a default mock which will match any event
/// (`expect::event()`) or can include additional expectations.
/// See the [`ExpectedEvent`] documentation for more details.
///
/// If an event is recorded that doesn't match the `ExpectedEvent`,
/// or if something else (such as entering a span) is recorded
/// first, then the expectation will fail.
///
/// # Examples
///
/// ```
/// use tracing_mock::{expect, layer};
/// use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, Layer};
///
/// let (layer, handle) = layer::mock()
/// .event(expect::event())
/// .run_with_handle();
///
/// let _subscriber = tracing_subscriber::registry()
/// .with(layer.with_filter(tracing_subscriber::filter::filter_fn(move |_meta| true)))
/// .set_default();
///
/// tracing::info!("event");
///
/// handle.assert_finished();
/// ```
///
/// A span is entered before the event, causing the test to fail:
///
/// ```should_panic
/// use tracing_mock::{expect, layer};
/// use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, Layer};
///
/// let (layer, handle) = layer::mock()
/// .event(expect::event())
/// .run_with_handle();
///
/// let _subscriber = tracing_subscriber::registry()
/// .with(layer.with_filter(tracing_subscriber::filter::filter_fn(move |_meta| true)))
/// .set_default();
///
/// let span = tracing::info_span!("span");
/// let _guard = span.enter();
/// tracing::info!("event");
///
/// handle.assert_finished();
/// ```
pub fn event(mut self, event: ExpectedEvent) -> Self {
self.expected.push_back(Expect::Event(event));
self
}
pub fn exit(mut self, span: ExpectedSpan) -> Self {
self.expected.push_back(Expect::Exit(span));
self
}
pub fn only(mut self) -> Self {
self.expected.push_back(Expect::Nothing);
self
}
pub fn record<I>(mut self, span: ExpectedSpan, fields: I) -> Self
where
I: Into<ExpectedFields>,
{
self.expected.push_back(Expect::Visit(span, fields.into()));
self
}
/// Adds an expectation that the creation of a span will be
/// recorded next.
///
/// This function accepts `Into<NewSpan>` instead of
/// [`ExpectedSpan`] directly. [`NewSpan`] can be used to test
/// span fields and the span parent.
///
/// The new span doesn't need to be entered for this expectation
/// to succeed.
///
/// If a span is recorded that doesn't match the `ExpectedSpan`,
/// or if something else (such as an event) is recorded first,
/// then the expectation will fail.
///
/// # Examples
///
/// ```
/// use tracing_mock::{expect, layer};
/// use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, Layer};
///
/// let span = expect::span()
/// .at_level(tracing::Level::INFO)
/// .named("the span we're testing")
/// .with_field(expect::field("testing").with_value(&"yes"));
/// let (layer, handle) = layer::mock()
/// .new_span(span)
/// .run_with_handle();
///
/// let _subscriber = tracing_subscriber::registry()
/// .with(layer.with_filter(tracing_subscriber::filter::filter_fn(move |_meta| true)))
/// .set_default();
///
/// _ = tracing::info_span!("the span we're testing", testing = "yes");
///
/// handle.assert_finished();
/// ```
///
/// An event is recorded before the span is created, causing the
/// test to fail:
///
/// ```should_panic
/// use tracing_mock::{expect, layer};
/// use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, Layer};
///
/// let span = expect::span()
/// .at_level(tracing::Level::INFO)
/// .named("the span we're testing")
/// .with_field(expect::field("testing").with_value(&"yes"));
/// let (layer, handle) = layer::mock()
/// .new_span(span)
/// .run_with_handle();
///
/// let _subscriber = tracing_subscriber::registry()
/// .with(layer.with_filter(tracing_subscriber::filter::filter_fn(move |_meta| true)))
/// .set_default();
///
/// tracing::info!("an event");
/// _ = tracing::info_span!("the span we're testing", testing = "yes");
///
/// handle.assert_finished();
/// ```
///
/// [`ExpectedSpan`]: struct@crate::span::ExpectedSpan
/// [`NewSpan`]: struct@crate::span::NewSpan
pub fn new_span<I>(mut self, new_span: I) -> Self
where
I: Into<NewSpan>,
@ -106,6 +480,252 @@ impl MockLayerBuilder {
self
}
/// Adds an expectation that entering a span matching the
/// [`ExpectedSpan`] will be recorded next.
///
/// This expectation is generally accompanied by a call to
/// [`exit`], since an entered span will typically be exited. If used
/// together with [`only`], this is likely necessary, because the span
/// will be dropped before the test completes (except in rare cases,
/// such as if [`std::mem::forget`] is used).
///
/// If the span that is entered doesn't match the [`ExpectedSpan`],
/// or if something else (such as an event) is recorded first,
/// then the expectation will fail.
///
/// # Examples
///
/// ```
/// use tracing_mock::{expect, layer};
/// use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, Layer};
///
/// let span = expect::span()
/// .at_level(tracing::Level::INFO)
/// .named("the span we're testing");
/// let (layer, handle) = layer::mock()
/// .enter(span.clone())
/// .exit(span)
/// .only()
/// .run_with_handle();
///
/// let _subscriber = tracing_subscriber::registry()
/// .with(layer.with_filter(tracing_subscriber::filter::filter_fn(move |_meta| true)))
/// .set_default();
///
/// {
/// let span = tracing::info_span!("the span we're testing");
/// let _entered = span.enter();
/// }
///
/// handle.assert_finished();
/// ```
///
/// An event is recorded before the span is entered, causing the
/// test to fail:
///
/// ```should_panic
/// use tracing_mock::{expect, layer};
/// use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, Layer};
///
/// let span = expect::span()
/// .at_level(tracing::Level::INFO)
/// .named("the span we're testing");
/// let (layer, handle) = layer::mock()
/// .enter(span.clone())
/// .exit(span)
/// .only()
/// .run_with_handle();
///
/// let _subscriber = tracing_subscriber::registry()
/// .with(layer.with_filter(tracing_subscriber::filter::filter_fn(move |_meta| true)))
/// .set_default();
///
/// {
/// tracing::info!("an event");
/// let span = tracing::info_span!("the span we're testing");
/// let _entered = span.enter();
/// }
///
/// handle.assert_finished();
/// ```
///
/// [`exit`]: fn@Self::exit
/// [`only`]: fn@Self::only
pub fn enter(mut self, span: ExpectedSpan) -> Self {
self.expected.push_back(Expect::Enter(span));
self
}
/// Adds an expectation that exiting a span matching the
/// [`ExpectedSpan`] will be recorded next.
///
/// As a span may be entered and exited multiple times,
/// this is different from the span being closed. In
/// general [`enter`] and `exit` should be paired.
///
/// If the span that is exited doesn't match the [`ExpectedSpan`],
/// or if something else (such as an event) is recorded first,
/// then the expectation will fail.
///
/// **Note**: Ensure that the guard returned by [`Span::enter`]
/// is dropped before calling [`MockHandle::assert_finished`].
///
/// # Examples
///
/// ```
/// use tracing_mock::{expect, layer};
/// use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, Layer};
///
/// let span = expect::span()
/// .at_level(tracing::Level::INFO)
/// .named("the span we're testing");
/// let (layer, handle) = layer::mock()
/// .enter(span.clone())
/// .exit(span)
/// .only()
/// .run_with_handle();
///
/// let _subscriber = tracing_subscriber::registry()
/// .with(layer.with_filter(tracing_subscriber::filter::filter_fn(move |_meta| true)))
/// .set_default();
/// {
/// let span = tracing::info_span!("the span we're testing");
/// let _entered = span.enter();
/// }
///
/// handle.assert_finished();
/// ```
///
/// An event is recorded before the span is exited, causing the
/// test to fail:
///
/// ```should_panic
/// use tracing_mock::{expect, layer};
/// use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, Layer};
///
/// let span = expect::span()
/// .at_level(tracing::Level::INFO)
/// .named("the span we're testing");
/// let (layer, handle) = layer::mock()
/// .enter(span.clone())
/// .exit(span)
/// .only()
/// .run_with_handle();
///
/// let _subscriber = tracing_subscriber::registry()
/// .with(layer.with_filter(tracing_subscriber::filter::filter_fn(move |_meta| true)))
/// .set_default();
///
/// {
/// let span = tracing::info_span!("the span we're testing");
/// let _entered = span.enter();
/// tracing::info!("an event");
/// }
///
/// handle.assert_finished();
/// ```
///
/// [`enter`]: fn@Self::enter
/// [`MockHandle::assert_finished`]: fn@crate::subscriber::MockHandle::assert_finished
/// [`Span::enter`]: fn@tracing::Span::enter
pub fn exit(mut self, span: ExpectedSpan) -> Self {
self.expected.push_back(Expect::Exit(span));
self
}
/// Expects that no further traces are received.
///
/// The call to `only` should appear immediately before the final
/// call to [`run`] or [`run_with_handle`], as any expectations which
/// are added after `only` will not be considered.
///
/// # Examples
///
/// Consider this simple test. It passes even though we only
/// expect a single event, but receive three:
///
/// ```
/// use tracing_mock::{expect, layer};
/// use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, Layer};
///
/// let (layer, handle) = layer::mock()
/// .event(expect::event())
/// .run_with_handle();
///
/// let _subscriber = tracing_subscriber::registry()
/// .with(layer.with_filter(tracing_subscriber::filter::filter_fn(move |_meta| true)))
/// .set_default();
///
/// tracing::info!("a");
/// tracing::info!("b");
/// tracing::info!("c");
///
/// handle.assert_finished();
/// ```
///
/// After including `only`, the test will fail:
///
/// ```should_panic
/// use tracing_mock::{expect, layer};
/// use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, Layer};
///
/// let (layer, handle) = layer::mock()
/// .event(expect::event())
/// .only()
/// .run_with_handle();
///
/// let _subscriber = tracing_subscriber::registry()
/// .with(layer.with_filter(tracing_subscriber::filter::filter_fn(move |_meta| true)))
/// .set_default();
///
/// tracing::info!("a");
/// tracing::info!("b");
/// tracing::info!("c");
///
/// handle.assert_finished();
/// ```
///
/// [`run`]: fn@Self::run
/// [`run_with_handle`]: fn@Self::run_with_handle
pub fn only(mut self) -> Self {
self.expected.push_back(Expect::Nothing);
self
}
/// Consume this builder and return a [`MockLayer`] which can
/// be set as the default subscriber.
///
/// This function is similar to [`run_with_handle`], but it doesn't
/// return a [`MockHandle`]. This is useful if the desired
/// assertions can be checked externally to the subscriber.
///
/// # Examples
///
/// The following test is used within the `tracing-subscriber`
/// codebase:
///
/// ```
/// use tracing::Subscriber;
/// use tracing_mock::layer;
/// use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, Layer};
///
/// let unfiltered = layer::named("unfiltered").run().boxed();
/// let info = layer::named("info")
/// .run()
/// .with_filter(tracing_core::LevelFilter::INFO)
/// .boxed();
/// let debug = layer::named("debug")
/// .run()
/// .with_filter(tracing_core::LevelFilter::DEBUG)
/// .boxed();
///
/// let subscriber = tracing_subscriber::registry().with(vec![unfiltered, info, debug]);
///
/// assert_eq!(subscriber.max_level_hint(), None);
/// ```
///
/// [`MockHandle`]: struct@crate::subscriber::MockHandle
/// [`run_with_handle`]: fn@Self::run_with_handle
pub fn run(self) -> MockLayer {
MockLayer {
expected: Arc::new(Mutex::new(self.expected)),
@ -114,15 +734,40 @@ impl MockLayerBuilder {
}
}
/// Consume this builder and return a [`MockLayer`] which can
/// be set as the default subscriber and a [`MockHandle`] which can
/// be used to validate the provided expectations.
///
/// # Examples
///
/// ```
/// use tracing_mock::{expect, layer};
/// use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, Layer};
///
/// let (layer, handle) = layer::mock()
/// .event(expect::event())
/// .run_with_handle();
///
/// let _subscriber = tracing_subscriber::registry()
/// .with(layer.with_filter(tracing_subscriber::filter::filter_fn(move |_meta| true)))
/// .set_default();
///
/// tracing::info!("event");
///
/// handle.assert_finished();
/// ```
///
/// [`MockHandle`]: struct@crate::subscriber::MockHandle
/// [`MockLayer`]: struct@crate::layer::MockLayer
pub fn run_with_handle(self) -> (MockLayer, MockHandle) {
let expected = Arc::new(Mutex::new(self.expected));
let handle = MockHandle::new(expected.clone(), self.name.clone());
let layer = MockLayer {
let subscriber = MockLayer {
expected,
name: self.name,
current: Mutex::new(Vec::new()),
};
(layer, handle)
(subscriber, handle)
}
}
@ -185,6 +830,46 @@ impl MockLayer {
);
}
}
fn check_event_scope<C>(
&self,
current_scope: Option<tracing_subscriber::registry::Scope<'_, C>>,
expected_scope: &mut [ExpectedSpan],
) where
C: for<'lookup> tracing_subscriber::registry::LookupSpan<'lookup>,
{
let mut current_scope = current_scope.into_iter().flatten();
let mut i = 0;
for (expected, actual) in expected_scope.iter_mut().zip(&mut current_scope) {
println!(
"[{}] event_scope[{}] actual={} ({:?}); expected={}",
self.name,
i,
actual.name(),
actual.id(),
expected
);
self.check_span_ref(
expected,
&actual,
format_args!("the {}th span in the event's scope to be", i),
);
i += 1;
}
let remaining_expected = &expected_scope[i..];
assert!(
remaining_expected.is_empty(),
"\n[{}] did not observe all expected spans in event scope!\n[{}] missing: {:#?}",
self.name,
self.name,
remaining_expected,
);
assert!(
current_scope.next().is_none(),
"\n[{}] did not expect all spans in the actual event scope!",
self.name,
);
}
}
impl<C> Layer<C> for MockLayer
@ -221,45 +906,21 @@ where
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);
let mut current_scope = cx.event_scope(event).into_iter().flatten();
let expected_scope = expected.scope_mut();
let mut i = 0;
for (expected, actual) in expected_scope.iter_mut().zip(&mut current_scope) {
println!(
"[{}] event_scope[{}] actual={} ({:?}); expected={}",
self.name,
i,
actual.name(),
actual.id(),
expected
);
self.check_span_ref(
expected,
&actual,
format_args!("the {}th span in the event's scope to be", i),
);
i += 1;
if let Some(expected_scope) = expected.scope_mut() {
self.check_event_scope(cx.event_scope(event), expected_scope);
}
let remaining_expected = &expected_scope[i..];
assert!(
remaining_expected.is_empty(),
"\n[{}] did not observe all expected spans in event scope!\n[{}] missing: {:#?}",
self.name,
self.name,
remaining_expected,
);
assert!(
current_scope.next().is_none(),
"\n[{}] did not expect all spans in the actual event scope!",
self.name,
);
}
Some(ex) => ex.bad(&self.name, format_args!("observed event {:#?}", event)),
}
}
fn on_follows_from(&self, _span: &Id, _follows: &Id, _: Context<'_, C>) {
// TODO: it should be possible to expect spans to follow from other spans
unimplemented!(
"so far, we don't have any tests that need an `on_follows_from` \
implementation.\nif you just wrote one that does, feel free to \
implement it!"
);
}
fn on_new_span(&self, span: &Attributes<'_>, id: &Id, cx: Context<'_, C>) {
@ -377,13 +1038,13 @@ where
}
fn on_id_change(&self, _old: &Id, _new: &Id, _ctx: Context<'_, C>) {
panic!("well-behaved Layers should never do this to us, lol");
panic!("well-behaved subscribers should never do this to us, lol");
}
}
impl fmt::Debug for MockLayer {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut s = f.debug_struct("Expectlayer");
let mut s = f.debug_struct("ExpectSubscriber");
s.field("name", &self.name);
if let Ok(expected) = self.expected.try_lock() {

View File

@ -155,14 +155,14 @@ where
}
pub fn run(self) -> impl Subscriber {
let (subscriber, _) = self.run_with_handle();
subscriber
let (collector, _) = self.run_with_handle();
collector
}
pub fn run_with_handle(self) -> (impl Subscriber, MockHandle) {
let expected = Arc::new(Mutex::new(self.expected));
let handle = MockHandle(expected.clone(), self.name.clone());
let subscriber = Running {
let collector = Running {
spans: Mutex::new(HashMap::new()),
expected,
current: Mutex::new(Vec::new()),
@ -171,7 +171,7 @@ where
max_level: self.max_level,
name: self.name,
};
(subscriber, handle)
(collector, handle)
}
}
@ -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_some() {
unimplemented!(
"Expected scope for events is not supported with `MockSubscriber`."
)
}
}
let get_parent_name = || {
let stack = self.current.lock().unwrap();
let spans = self.spans.lock().unwrap();
@ -454,6 +462,7 @@ where
}
impl MockHandle {
#[cfg(feature = "tracing-subscriber")]
pub(crate) fn new(expected: Arc<Mutex<VecDeque<Expect>>>, name: String) -> Self {
Self(expected, name)
}