From c6983d516743a8869932c4fd7240021b5abe391c Mon Sep 17 00:00:00 2001
From: Hayden Stainsby <hds@caffeineconcepts.com>
Date: Tue, 14 Nov 2023 10:57:14 +0100
Subject: [PATCH] mock: document public APIs in `span` module (#2442)

This change adds documentation to the tracing-mock span module and all
the public APIs within it. This includes doctests on all the methods
which serve as examples.

Additionally, the validation on `ExpectedSpan` was improved so that it
validates the level and target during `enter` and `exit` as well as on
`new_span`.

The method `ExpectedSpan::with_field` was renamed to `with_fields`
(plural) to match the same method on `ExpectedEvent` (and because
multiple fields can be passed to it).

A copy-paste typo was also fixed in the documentation for
`ExpectedEvent::with_contextual_parent`.

Refs: #539

Co-authored-by: David Barsky <me@davidbarsky.com>
---
 tracing-attributes/tests/async_fn.rs          |  16 +-
 tracing-attributes/tests/destructuring.rs     |  12 +-
 tracing-attributes/tests/err.rs               |   2 +-
 tracing-attributes/tests/fields.rs            |  18 +-
 tracing-attributes/tests/instrument.rs        |  14 +-
 tracing-mock/README.md                        |   2 +-
 tracing-mock/src/event.rs                     |   2 +-
 tracing-mock/src/layer.rs                     |   4 +-
 tracing-mock/src/span.rs                      | 549 +++++++++++++++++-
 tracing-mock/src/subscriber.rs                |  20 +-
 tracing-subscriber/tests/env_filter/main.rs   |   8 +-
 .../tests/env_filter/per_layer.rs             |   4 +-
 tracing-subscriber/tests/same_len_filters.rs  |   4 +-
 tracing/tests/span.rs                         |  26 +-
 tracing/tests/subscriber.rs                   |   4 +-
 15 files changed, 602 insertions(+), 83 deletions(-)

diff --git a/tracing-attributes/tests/async_fn.rs b/tracing-attributes/tests/async_fn.rs
index 6acbd0e2a0..365a72cb4e 100644
--- a/tracing-attributes/tests/async_fn.rs
+++ b/tracing-attributes/tests/async_fn.rs
@@ -200,8 +200,8 @@ fn async_fn_with_async_trait() {
     let (subscriber, handle) = subscriber::mock()
         .new_span(
             span.clone()
-                .with_field(expect::field("self"))
-                .with_field(expect::field("v")),
+                .with_fields(expect::field("self"))
+                .with_fields(expect::field("v")),
         )
         .enter(span.clone())
         .new_span(span3.clone())
@@ -211,7 +211,7 @@ fn async_fn_with_async_trait() {
         .enter(span3.clone())
         .exit(span3.clone())
         .drop_span(span3)
-        .new_span(span2.clone().with_field(expect::field("self")))
+        .new_span(span2.clone().with_fields(expect::field("self")))
         .enter(span2.clone())
         .event(expect::event().with_fields(expect::field("val").with_value(&5u64)))
         .exit(span2.clone())
@@ -261,7 +261,7 @@ fn async_fn_with_async_trait_and_fields_expressions() {
     let span = expect::span().named("call");
     let (subscriber, handle) = subscriber::mock()
         .new_span(
-            span.clone().with_field(
+            span.clone().with_fields(
                 expect::field("_v")
                     .with_value(&5usize)
                     .and(expect::field("test").with_value(&tracing::field::debug(10)))
@@ -331,7 +331,7 @@ fn async_fn_with_async_trait_and_fields_expressions_with_generic_parameter() {
     let span4 = expect::span().named("sync_fun");
     let (subscriber, handle) = subscriber::mock()
         /*.new_span(span.clone()
-            .with_field(
+            .with_fields(
                 expect::field("Self").with_value(&"TestImpler")))
         .enter(span.clone())
         .exit(span.clone())
@@ -339,13 +339,13 @@ fn async_fn_with_async_trait_and_fields_expressions_with_generic_parameter() {
         .new_span(
             span2
                 .clone()
-                .with_field(expect::field("Self").with_value(&std::any::type_name::<TestImpl>())),
+                .with_fields(expect::field("Self").with_value(&std::any::type_name::<TestImpl>())),
         )
         .enter(span2.clone())
         .new_span(
             span4
                 .clone()
-                .with_field(expect::field("Self").with_value(&std::any::type_name::<TestImpl>())),
+                .with_fields(expect::field("Self").with_value(&std::any::type_name::<TestImpl>())),
         )
         .enter(span4.clone())
         .exit(span4.clone())
@@ -358,7 +358,7 @@ fn async_fn_with_async_trait_and_fields_expressions_with_generic_parameter() {
         .new_span(
             span3
                 .clone()
-                .with_field(expect::field("Self").with_value(&std::any::type_name::<TestImpl>())),
+                .with_fields(expect::field("Self").with_value(&std::any::type_name::<TestImpl>())),
         )
         .enter(span3.clone())
         .exit(span3.clone())
diff --git a/tracing-attributes/tests/destructuring.rs b/tracing-attributes/tests/destructuring.rs
index cc4fecf3f2..b0e87376ce 100644
--- a/tracing-attributes/tests/destructuring.rs
+++ b/tracing-attributes/tests/destructuring.rs
@@ -11,7 +11,7 @@ fn destructure_tuples() {
 
     let (subscriber, handle) = subscriber::mock()
         .new_span(
-            span.clone().with_field(
+            span.clone().with_fields(
                 expect::field("arg1")
                     .with_value(&format_args!("1"))
                     .and(expect::field("arg2").with_value(&format_args!("2")))
@@ -40,7 +40,7 @@ fn destructure_nested_tuples() {
 
     let (subscriber, handle) = subscriber::mock()
         .new_span(
-            span.clone().with_field(
+            span.clone().with_fields(
                 expect::field("arg1")
                     .with_value(&format_args!("1"))
                     .and(expect::field("arg2").with_value(&format_args!("2")))
@@ -72,7 +72,7 @@ fn destructure_refs() {
     let (subscriber, handle) = subscriber::mock()
         .new_span(
             span.clone()
-                .with_field(expect::field("arg1").with_value(&1usize).only()),
+                .with_fields(expect::field("arg1").with_value(&1usize).only()),
         )
         .enter(span.clone())
         .exit(span.clone())
@@ -98,7 +98,7 @@ fn destructure_tuple_structs() {
 
     let (subscriber, handle) = subscriber::mock()
         .new_span(
-            span.clone().with_field(
+            span.clone().with_fields(
                 expect::field("arg1")
                     .with_value(&format_args!("1"))
                     .and(expect::field("arg2").with_value(&format_args!("2")))
@@ -139,7 +139,7 @@ fn destructure_structs() {
 
     let (subscriber, handle) = subscriber::mock()
         .new_span(
-            span.clone().with_field(
+            span.clone().with_fields(
                 expect::field("arg1")
                     .with_value(&format_args!("1"))
                     .and(expect::field("arg2").with_value(&format_args!("2")))
@@ -184,7 +184,7 @@ fn destructure_everything() {
 
     let (subscriber, handle) = subscriber::mock()
         .new_span(
-            span.clone().with_field(
+            span.clone().with_fields(
                 expect::field("arg1")
                     .with_value(&format_args!("1"))
                     .and(expect::field("arg2").with_value(&format_args!("2")))
diff --git a/tracing-attributes/tests/err.rs b/tracing-attributes/tests/err.rs
index 9d82487ea9..b2c5339c56 100644
--- a/tracing-attributes/tests/err.rs
+++ b/tracing-attributes/tests/err.rs
@@ -160,7 +160,7 @@ fn impl_trait_return_type() {
     let (subscriber, handle) = subscriber::mock()
         .new_span(
             span.clone()
-                .with_field(expect::field("x").with_value(&10usize).only()),
+                .with_fields(expect::field("x").with_value(&10usize).only()),
         )
         .enter(span.clone())
         .exit(span.clone())
diff --git a/tracing-attributes/tests/fields.rs b/tracing-attributes/tests/fields.rs
index a3b23d7ac2..35b69967ec 100644
--- a/tracing-attributes/tests/fields.rs
+++ b/tracing-attributes/tests/fields.rs
@@ -46,7 +46,7 @@ impl HasField {
 
 #[test]
 fn fields() {
-    let span = expect::span().with_field(
+    let span = expect::span().with_fields(
         expect::field("foo")
             .with_value(&"bar")
             .and(expect::field("dsa").with_value(&true))
@@ -60,7 +60,7 @@ fn fields() {
 
 #[test]
 fn expr_field() {
-    let span = expect::span().with_field(
+    let span = expect::span().with_fields(
         expect::field("s")
             .with_value(&"hello world")
             .and(expect::field("len").with_value(&"hello world".len()))
@@ -73,7 +73,7 @@ fn expr_field() {
 
 #[test]
 fn two_expr_fields() {
-    let span = expect::span().with_field(
+    let span = expect::span().with_fields(
         expect::field("s")
             .with_value(&"hello world")
             .and(expect::field("s.len").with_value(&"hello world".len()))
@@ -87,7 +87,7 @@ fn two_expr_fields() {
 
 #[test]
 fn clashy_expr_field() {
-    let span = expect::span().with_field(
+    let span = expect::span().with_fields(
         // Overriding the `s` field should record `s` as a `Display` value,
         // rather than as a `Debug` value.
         expect::field("s")
@@ -99,7 +99,7 @@ fn clashy_expr_field() {
         fn_clashy_expr_field("hello world");
     });
 
-    let span = expect::span().with_field(expect::field("s").with_value(&"s").only());
+    let span = expect::span().with_fields(expect::field("s").with_value(&"s").only());
     run_test(span, || {
         fn_clashy_expr_field2("hello world");
     });
@@ -108,7 +108,7 @@ fn clashy_expr_field() {
 #[test]
 fn self_expr_field() {
     let span =
-        expect::span().with_field(expect::field("my_field").with_value(&"hello world").only());
+        expect::span().with_fields(expect::field("my_field").with_value(&"hello world").only());
     run_test(span, || {
         let has_field = HasField {
             my_field: "hello world",
@@ -119,7 +119,7 @@ fn self_expr_field() {
 
 #[test]
 fn parameters_with_fields() {
-    let span = expect::span().with_field(
+    let span = expect::span().with_fields(
         expect::field("foo")
             .with_value(&"bar")
             .and(expect::field("param").with_value(&1u32))
@@ -132,7 +132,7 @@ fn parameters_with_fields() {
 
 #[test]
 fn empty_field() {
-    let span = expect::span().with_field(expect::field("foo").with_value(&"bar").only());
+    let span = expect::span().with_fields(expect::field("foo").with_value(&"bar").only());
     run_test(span, || {
         fn_empty_field();
     });
@@ -140,7 +140,7 @@ fn empty_field() {
 
 #[test]
 fn string_field() {
-    let span = expect::span().with_field(expect::field("s").with_value(&"hello world").only());
+    let span = expect::span().with_fields(expect::field("s").with_value(&"hello world").only());
     run_test(span, || {
         fn_string(String::from("hello world"));
     });
diff --git a/tracing-attributes/tests/instrument.rs b/tracing-attributes/tests/instrument.rs
index c5e816045b..d01df0c313 100644
--- a/tracing-attributes/tests/instrument.rs
+++ b/tracing-attributes/tests/instrument.rs
@@ -64,7 +64,7 @@ fn fields() {
         .with_target("my_target");
     let (subscriber, handle) = subscriber::mock()
         .new_span(
-            span.clone().with_field(
+            span.clone().with_fields(
                 expect::field("arg1")
                     .with_value(&2usize)
                     .and(expect::field("arg2").with_value(&false))
@@ -76,7 +76,7 @@ fn fields() {
         .exit(span.clone())
         .drop_span(span)
         .new_span(
-            span2.clone().with_field(
+            span2.clone().with_fields(
                 expect::field("arg1")
                     .with_value(&3usize)
                     .and(expect::field("arg2").with_value(&true))
@@ -126,7 +126,7 @@ fn skip() {
     let (subscriber, handle) = subscriber::mock()
         .new_span(
             span.clone()
-                .with_field(expect::field("arg1").with_value(&2usize).only()),
+                .with_fields(expect::field("arg1").with_value(&2usize).only()),
         )
         .enter(span.clone())
         .exit(span.clone())
@@ -134,7 +134,7 @@ fn skip() {
         .new_span(
             span2
                 .clone()
-                .with_field(expect::field("arg1").with_value(&3usize).only()),
+                .with_fields(expect::field("arg1").with_value(&3usize).only()),
         )
         .enter(span2.clone())
         .exit(span2.clone())
@@ -171,7 +171,7 @@ fn generics() {
 
     let (subscriber, handle) = subscriber::mock()
         .new_span(
-            span.clone().with_field(
+            span.clone().with_fields(
                 expect::field("arg1")
                     .with_value(&format_args!("Foo"))
                     .and(expect::field("arg2").with_value(&format_args!("false"))),
@@ -204,7 +204,7 @@ fn methods() {
 
     let (subscriber, handle) = subscriber::mock()
         .new_span(
-            span.clone().with_field(
+            span.clone().with_fields(
                 expect::field("self")
                     .with_value(&format_args!("Foo"))
                     .and(expect::field("arg1").with_value(&42usize)),
@@ -236,7 +236,7 @@ fn impl_trait_return_type() {
     let (subscriber, handle) = subscriber::mock()
         .new_span(
             span.clone()
-                .with_field(expect::field("x").with_value(&10usize).only()),
+                .with_fields(expect::field("x").with_value(&10usize).only()),
         )
         .enter(span.clone())
         .exit(span.clone())
diff --git a/tracing-mock/README.md b/tracing-mock/README.md
index 299a737534..04b8dc41b2 100644
--- a/tracing-mock/README.md
+++ b/tracing-mock/README.md
@@ -121,7 +121,7 @@ let span = expect::span().named("yak_shaving");
 let (subscriber, handle) = subscriber::mock()
     .new_span(
         span.clone()
-            .with_field(expect::field("number_of_yaks").with_value(&yak_count).only()),
+            .with_fields(expect::field("number_of_yaks").with_value(&yak_count).only()),
     )
     .enter(span.clone())
     .event(
diff --git a/tracing-mock/src/event.rs b/tracing-mock/src/event.rs
index 103d663605..6c4cbf7e71 100644
--- a/tracing-mock/src/event.rs
+++ b/tracing-mock/src/event.rs
@@ -364,7 +364,7 @@ impl ExpectedEvent {
     ///
     /// # Examples
     ///
-    /// The explicit parent is matched by name:
+    /// The contextual parent is matched by name:
     ///
     /// ```
     /// use tracing::subscriber::with_default;
diff --git a/tracing-mock/src/layer.rs b/tracing-mock/src/layer.rs
index ab48171b9d..fa3c8ab28b 100644
--- a/tracing-mock/src/layer.rs
+++ b/tracing-mock/src/layer.rs
@@ -431,7 +431,7 @@ impl MockLayerBuilder {
     /// let span = expect::span()
     ///     .at_level(tracing::Level::INFO)
     ///     .named("the span we're testing")
-    ///     .with_field(expect::field("testing").with_value(&"yes"));
+    ///     .with_fields(expect::field("testing").with_value(&"yes"));
     /// let (layer, handle) = layer::mock()
     ///     .new_span(span)
     ///     .run_with_handle();
@@ -455,7 +455,7 @@ impl MockLayerBuilder {
     /// let span = expect::span()
     ///     .at_level(tracing::Level::INFO)
     ///     .named("the span we're testing")
-    ///     .with_field(expect::field("testing").with_value(&"yes"));
+    ///     .with_fields(expect::field("testing").with_value(&"yes"));
     /// let (layer, handle) = layer::mock()
     ///     .new_span(span)
     ///     .run_with_handle();
diff --git a/tracing-mock/src/span.rs b/tracing-mock/src/span.rs
index 9af084fe69..176c33a938 100644
--- a/tracing-mock/src/span.rs
+++ b/tracing-mock/src/span.rs
@@ -1,16 +1,128 @@
+//! Define expectations to match and validate spans.
+//!
+//! The [`ExpectedSpan`] and [`NewSpan`] structs define expectations
+//! for spans to be matched by the mock subscriber API in the
+//! [`subscriber`] module.
+//!
+//! Expected spans should be created with [`expect::span`] and a
+//! chain of method calls describing the assertions made about the
+//! span. Expectations about the lifecycle of the span can be set on the [`MockSubscriber`].
+//!
+//! # Examples
+//!
+//! ```
+//! use tracing_mock::{subscriber, expect};
+//!
+//! let span = expect::span()
+//!     .named("interesting_span")
+//!     .at_level(tracing::Level::INFO);
+//!
+//! let (subscriber, handle) = subscriber::mock()
+//!     .enter(span.clone())
+//!     .exit(span)
+//!     .run_with_handle();
+//!
+//! tracing::subscriber::with_default(subscriber, || {
+//!    let span = tracing::info_span!("interesting_span");
+//!     let _guard = span.enter();
+//! });
+//!
+//! handle.assert_finished();
+//! ```
+//!
+//! The following example asserts the name, level, parent, and fields of the span:
+//!
+//! ```
+//! use tracing_mock::{subscriber, expect};
+//!
+//! let span = expect::span()
+//!     .named("interesting_span")
+//!     .at_level(tracing::Level::INFO);
+//! let new_span = span
+//!     .clone()
+//!     .with_fields(expect::field("field.name").with_value(&"field_value"))
+//!     .with_explicit_parent(Some("parent_span"));
+//!
+//! let (subscriber, handle) = subscriber::mock()
+//!     .new_span(expect::span().named("parent_span"))
+//!     .new_span(new_span)
+//!     .enter(span.clone())
+//!     .exit(span)
+//!     .run_with_handle();
+//!
+//! tracing::subscriber::with_default(subscriber, || {
+//!     let parent = tracing::info_span!("parent_span");
+//!
+//!     let span = tracing::info_span!(
+//!         parent: parent.id(),
+//!         "interesting_span",
+//!         field.name = "field_value",
+//!     );
+//!     let _guard = span.enter();
+//! });
+//!
+//! handle.assert_finished();
+//! ```
+//!
+//! All expectations must be met for the test to pass. For example,
+//! the following test will fail due to a mismatch in the spans' names:
+//!
+//! ```should_panic
+//! use tracing_mock::{subscriber, expect};
+//!
+//! let span = expect::span()
+//!     .named("interesting_span")
+//!     .at_level(tracing::Level::INFO);
+//!
+//! let (subscriber, handle) = subscriber::mock()
+//!     .enter(span.clone())
+//!     .exit(span)
+//!     .run_with_handle();
+//!
+//! tracing::subscriber::with_default(subscriber, || {
+//!    let span = tracing::info_span!("another_span");
+//!    let _guard = span.enter();
+//! });
+//!
+//! handle.assert_finished();
+//! ```
+//!
+//! [`MockSubscriber`]: struct@crate::subscriber::MockSubscriber
+//! [`subscriber`]: mod@crate::subscriber
+//! [`expect::span`]: fn@crate::expect::span
 #![allow(missing_docs)]
-use super::{expect, field::ExpectedFields, metadata::ExpectedMetadata, Parent};
+use crate::{
+    expect, field::ExpectedFields, metadata::ExpectedMetadata, subscriber::SpanState, Parent,
+};
 use std::fmt;
 
 /// A mock span.
 ///
 /// This is intended for use with the mock subscriber API in the
-/// `subscriber` module.
+/// [`subscriber`] module.
+///
+/// [`subscriber`]: mod@crate::subscriber
 #[derive(Clone, Default, Eq, PartialEq)]
 pub struct ExpectedSpan {
     pub(crate) metadata: ExpectedMetadata,
 }
 
+/// A mock new span.
+///
+/// **Note**: This struct contains expectations that can only be asserted
+/// on when expecting a new span via [`MockSubscriber::new_span`]. They
+/// cannot be validated on [`MockSubscriber::enter`],
+/// [`MockSubscriber::exit`], or any other method on [`MockSubscriber`]
+/// that takes an `ExpectedSpan`.
+///
+/// For more details on how to use this struct, see the documentation
+/// on the [`subscriber`] module.
+///
+/// [`subscriber`]: mod@crate::subscriber
+/// [`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
 #[derive(Default, Eq, PartialEq)]
 pub struct NewSpan {
     pub(crate) span: ExpectedSpan,
@@ -26,6 +138,47 @@ where
 }
 
 impl ExpectedSpan {
+    /// Sets a name to expect when matching a span.
+    ///
+    /// If an event is recorded with a name that differs from the one provided to this method, the expectation will fail.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// use tracing_mock::{subscriber, expect};
+    ///
+    /// let span = expect::span().named("span name");
+    ///
+    /// let (subscriber, handle) = subscriber::mock()
+    ///     .enter(span)
+    ///     .run_with_handle();
+    ///
+    /// tracing::subscriber::with_default(subscriber, || {
+    ///     let span = tracing::info_span!("span name");
+    ///     let _guard = span.enter();
+    /// });
+    ///
+    /// handle.assert_finished();
+    /// ```
+    ///
+    /// When the span name is different, the assertion will fail:
+    ///
+    /// ```should_panic
+    /// use tracing_mock::{subscriber, expect};
+    ///
+    /// let span = expect::span().named("span name");
+    ///
+    /// let (subscriber, handle) = subscriber::mock()
+    ///     .enter(span)
+    ///     .run_with_handle();
+    ///
+    /// tracing::subscriber::with_default(subscriber, || {
+    ///     let span = tracing::info_span!("a different span name");
+    ///     let _guard = span.enter();
+    /// });
+    ///
+    /// handle.assert_finished();
+    /// ```
     pub fn named<I>(self, name: I) -> Self
     where
         I: Into<String>,
@@ -38,6 +191,50 @@ impl ExpectedSpan {
         }
     }
 
+    /// Sets the [`Level`](tracing::Level) to expect when matching a span.
+    ///
+    /// If an span is record with a level that differs from the one provided to this method, the expectation will fail.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// use tracing_mock::{subscriber, expect};
+    ///
+    /// let span = expect::span()
+    ///     .at_level(tracing::Level::INFO);
+    ///
+    /// let (subscriber, handle) = subscriber::mock()
+    ///     .enter(span)
+    ///     .run_with_handle();
+    ///
+    /// tracing::subscriber::with_default(subscriber, || {
+    ///     let span = tracing::info_span!("span");
+    ///     let _guard = span.enter();
+    /// });
+    ///
+    /// handle.assert_finished();
+    /// ```
+    ///
+    /// Expecting a span at `INFO` level will fail if the event is
+    /// recorded at any other level:
+    ///
+    /// ```should_panic
+    /// use tracing_mock::{subscriber, expect};
+    ///
+    /// let span = expect::span()
+    ///     .at_level(tracing::Level::INFO);
+    ///
+    /// let (subscriber, handle) = subscriber::mock()
+    ///     .enter(span)
+    ///     .run_with_handle();
+    ///
+    /// tracing::subscriber::with_default(subscriber, || {
+    ///     let span = tracing::warn_span!("a serious span");
+    ///     let _guard = span.enter();
+    /// });
+    ///
+    /// handle.assert_finished();
+    /// ```
     pub fn at_level(self, level: tracing::Level) -> Self {
         Self {
             metadata: ExpectedMetadata {
@@ -47,6 +244,50 @@ impl ExpectedSpan {
         }
     }
 
+    /// Sets the target to expect when matching a span.
+    ///
+    /// If an event is recorded with a target that doesn't match the
+    /// provided target, this expectation will fail.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// use tracing_mock::{subscriber, expect};
+    ///
+    /// let span = expect::span()
+    ///     .with_target("some_target");
+    ///
+    /// let (subscriber, handle) = subscriber::mock()
+    ///     .enter(span)
+    ///     .run_with_handle();
+    ///
+    /// tracing::subscriber::with_default(subscriber, || {
+    ///     let span = tracing::info_span!(target: "some_target", "span");
+    ///     let _guard = span.enter();
+    /// });
+    ///
+    /// handle.assert_finished();
+    /// ```
+    ///
+    /// The test will fail if the target is different:
+    ///
+    /// ```should_panic
+    /// use tracing_mock::{subscriber, expect};
+    ///
+    /// let span = expect::span()
+    ///     .with_target("some_target");
+    ///
+    /// let (subscriber, handle) = subscriber::mock()
+    ///     .enter(span)
+    ///     .run_with_handle();
+    ///
+    /// tracing::subscriber::with_default(subscriber, || {
+    ///     let span = tracing::info_span!(target: "a_different_target", "span");
+    ///     let _guard = span.enter();
+    /// });
+    ///
+    /// handle.assert_finished();
+    /// ```
     pub fn with_target<I>(self, target: I) -> Self
     where
         I: Into<String>,
@@ -59,6 +300,100 @@ impl ExpectedSpan {
         }
     }
 
+    /// Configures this `ExpectedSpan` to expect an explicit parent
+    /// span or to be an explicit 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`.
+    ///
+    /// An _explicit_ parent span is one passed to the `span!` macro in the
+    /// `parent:` field.
+    ///
+    /// If `Some("parent_name")` is passed to `with_explicit_parent` then,
+    /// the provided string is the name of the parent span to expect.
+    ///
+    /// 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.
+    ///
+    /// # Examples
+    ///
+    /// The explicit parent is matched by name:
+    ///
+    /// ```
+    /// use tracing_mock::{subscriber, expect};
+    ///
+    /// let span = expect::span()
+    ///     .with_explicit_parent(Some("parent_span"));
+    ///
+    /// let (subscriber, handle) = subscriber::mock()
+    ///     .new_span(expect::span().named("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");
+    /// });
+    ///
+    /// handle.assert_finished();
+    /// ```
+    ///
+    /// In the following example, the expected span is an explicit root:
+    ///
+    /// ```
+    /// use tracing_mock::{subscriber, expect};
+    ///
+    /// let span = expect::span()
+    ///     .with_explicit_parent(None);
+    ///
+    /// let (subscriber, handle) = subscriber::mock()
+    ///     .new_span(span)
+    ///     .run_with_handle();
+    ///
+    /// tracing::subscriber::with_default(subscriber, || {
+    ///     tracing::info_span!(parent: None, "span");
+    /// });
+    ///
+    /// handle.assert_finished();
+    /// ```
+    ///
+    /// In the example below, the expectation fails because the
+    /// span is *contextually*—as opposed to explicitly—within the span
+    /// `parent_span`:
+    ///
+    /// ```should_panic
+    /// use tracing_mock::{subscriber, expect};
+    ///
+    /// let parent_span = expect::span().named("parent_span");
+    /// let span = expect::span()
+    ///     .with_explicit_parent(Some("parent_span"));
+    ///
+    /// let (subscriber, handle) = subscriber::mock()
+    ///     .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");
+    ///     let _guard = parent.enter();
+    ///     tracing::info_span!("span");
+    /// });
+    ///
+    /// 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()),
@@ -71,6 +406,99 @@ impl ExpectedSpan {
         }
     }
 
+    /// 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:
+    ///
+    /// ```
+    /// use tracing_mock::{subscriber, expect};
+    ///
+    /// let parent_span = expect::span().named("parent_span");
+    /// let span = expect::span()
+    ///     .with_contextual_parent(Some("parent_span"));
+    ///
+    /// let (subscriber, handle) = subscriber::mock()
+    ///     .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");
+    ///     let _guard = parent.enter();
+    ///     tracing::info_span!("span");
+    /// });
+    ///
+    /// handle.assert_finished();
+    /// ```
+    ///
+    /// In the following example, we expect that the matched span is
+    /// a contextually-determined root:
+    ///
+    /// ```
+    /// use tracing_mock::{subscriber, expect};
+    ///
+    /// let span = expect::span()
+    ///     .with_contextual_parent(None);
+    ///
+    /// let (subscriber, handle) = subscriber::mock()
+    ///     .new_span(span)
+    ///     .run_with_handle();
+    ///
+    /// tracing::subscriber::with_default(subscriber, || {
+    ///     tracing::info_span!("span");
+    /// });
+    ///
+    /// handle.assert_finished();
+    /// ```
+    ///
+    /// In the example below, the expectation fails because the
+    /// span is recorded with an explicit parent:
+    ///
+    /// ```should_panic
+    /// use tracing_mock::{subscriber, expect};
+    ///
+    /// let span = expect::span()
+    ///     .with_contextual_parent(Some("parent_span"));
+    ///
+    /// let (subscriber, handle) = subscriber::mock()
+    ///     .new_span(expect::span().named("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");
+    /// });
+    ///
+    /// 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_contextual_parent(self, parent: Option<&str>) -> NewSpan {
         let parent = match parent {
             Some(name) => Parent::Contextual(name.into()),
@@ -83,27 +511,95 @@ impl ExpectedSpan {
         }
     }
 
-    pub fn name(&self) -> Option<&str> {
+    /// Adds fields to expect when matching a span.
+    ///
+    /// **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`.
+    ///
+    /// If a span is recorded with fields that do not match the provided
+    /// [`ExpectedFields`], this expectation will fail.
+    ///
+    /// If the provided field is not present on the recorded span or
+    /// if the value for that field diffs, then the expectation
+    /// will fail.
+    ///
+    /// More information on the available validations is available in
+    /// the [`ExpectedFields`] documentation.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// use tracing_mock::{subscriber, expect};
+    ///
+    /// let span = expect::span()
+    ///     .with_fields(expect::field("field.name").with_value(&"field_value"));
+    ///
+    /// let (subscriber, handle) = subscriber::mock()
+    ///     .new_span(span)
+    ///     .run_with_handle();
+    ///
+    /// tracing::subscriber::with_default(subscriber, || {
+    ///     tracing::info_span!("span", field.name = "field_value");
+    /// });
+    ///
+    /// handle.assert_finished();
+    /// ```
+    ///
+    /// A different field value will cause the expectation to fail:
+    ///
+    /// ```should_panic
+    /// use tracing_mock::{subscriber, expect};
+    ///
+    /// let span = expect::span()
+    ///     .with_fields(expect::field("field.name").with_value(&"field_value"));
+    ///
+    /// let (subscriber, handle) = subscriber::mock()
+    ///     .new_span(span)
+    ///     .run_with_handle();
+    ///
+    /// tracing::subscriber::with_default(subscriber, || {
+    ///     tracing::info_span!("span", field.name = "different_field_value");
+    /// });
+    ///
+    /// handle.assert_finished();
+    /// ```
+    ///
+    /// [`ExpectedFields`]: struct@crate::field::ExpectedFields
+    /// [`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_fields<I>(self, fields: I) -> NewSpan
+    where
+        I: Into<ExpectedFields>,
+    {
+        NewSpan {
+            span: self,
+            fields: fields.into(),
+            ..Default::default()
+        }
+    }
+
+    pub(crate) fn name(&self) -> Option<&str> {
         self.metadata.name.as_ref().map(String::as_ref)
     }
 
-    pub fn level(&self) -> Option<tracing::Level> {
+    pub(crate) fn level(&self) -> Option<tracing::Level> {
         self.metadata.level
     }
 
-    pub fn target(&self) -> Option<&str> {
+    pub(crate) fn target(&self) -> Option<&str> {
         self.metadata.target.as_deref()
     }
 
-    pub fn with_field<I>(self, fields: I) -> NewSpan
-    where
-        I: Into<ExpectedFields>,
-    {
-        NewSpan {
-            span: self,
-            fields: fields.into(),
-            ..Default::default()
-        }
+    pub(crate) fn check(&self, actual: &SpanState, subscriber_name: &str) {
+        let meta = actual.metadata();
+        let name = meta.name();
+        self.metadata
+            .check(meta, format_args!("span `{}`", name), subscriber_name);
     }
 }
 
@@ -147,6 +643,13 @@ impl From<ExpectedSpan> for NewSpan {
 }
 
 impl NewSpan {
+    /// Configures this `ExpectedSpan` to expect an explicit parent
+    /// span or to be an explicit root.
+    ///
+    /// For more information and examples, see the documentation on
+    /// [`ExpectedSpan::with_explicit_parent`].
+    ///
+    /// [`ExpectedSpan::with_explicit_parent`]: fn@crate::span::ExpectedSpan::with_explicit_parent
     pub fn with_explicit_parent(self, parent: Option<&str>) -> NewSpan {
         let parent = match parent {
             Some(name) => Parent::Explicit(name.into()),
@@ -158,6 +661,14 @@ impl NewSpan {
         }
     }
 
+    /// 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()),
@@ -169,7 +680,13 @@ impl NewSpan {
         }
     }
 
-    pub fn with_field<I>(self, fields: I) -> NewSpan
+    /// Adds fields to expect when matching a span.
+    ///
+    /// For more information and examples, see the documentation on
+    /// [`ExpectedSpan::with_fields`].
+    ///
+    /// [`ExpectedSpan::with_fields`]: fn@crate::span::ExpectedSpan::with_fields
+    pub fn with_fields<I>(self, fields: I) -> NewSpan
     where
         I: Into<ExpectedFields>,
     {
@@ -179,7 +696,7 @@ impl NewSpan {
         }
     }
 
-    pub fn check(
+    pub(crate) fn check(
         &mut self,
         span: &tracing_core::span::Attributes<'_>,
         get_parent_name: impl FnOnce() -> Option<String>,
diff --git a/tracing-mock/src/subscriber.rs b/tracing-mock/src/subscriber.rs
index ef7a93b148..83251a3715 100644
--- a/tracing-mock/src/subscriber.rs
+++ b/tracing-mock/src/subscriber.rs
@@ -158,12 +158,18 @@ use tracing::{
     Event, Metadata, Subscriber,
 };
 
-struct SpanState {
+pub(crate) struct SpanState {
     name: &'static str,
     refs: usize,
     meta: &'static Metadata<'static>,
 }
 
+impl SpanState {
+    pub(crate) fn metadata(&self) -> &'static Metadata<'static> {
+        self.meta
+    }
+}
+
 struct Running<F: Fn(&Metadata<'_>) -> bool> {
     spans: Mutex<HashMap<Id, SpanState>>,
     expected: Arc<Mutex<VecDeque<Expect>>>,
@@ -399,7 +405,7 @@ where
     /// let span = expect::span()
     ///     .at_level(tracing::Level::INFO)
     ///     .named("the span we're testing")
-    ///     .with_field(expect::field("testing").with_value(&"yes"));
+    ///     .with_fields(expect::field("testing").with_value(&"yes"));
     /// let (subscriber, handle) = subscriber::mock()
     ///     .new_span(span)
     ///     .run_with_handle();
@@ -420,7 +426,7 @@ where
     /// let span = expect::span()
     ///     .at_level(tracing::Level::INFO)
     ///     .named("the span we're testing")
-    ///     .with_field(expect::field("testing").with_value(&"yes"));
+    ///     .with_fields(expect::field("testing").with_value(&"yes"));
     /// let (subscriber, handle) = subscriber::mock()
     ///     .new_span(span)
     ///     .run_with_handle();
@@ -1122,9 +1128,7 @@ where
             match self.expected.lock().unwrap().pop_front() {
                 None => {}
                 Some(Expect::Enter(ref expected_span)) => {
-                    if let Some(name) = expected_span.name() {
-                        assert_eq!(name, span.name);
-                    }
+                    expected_span.check(span, &self.name);
                 }
                 Some(ex) => ex.bad(&self.name, format_args!("entered span {:?}", span.name)),
             }
@@ -1147,9 +1151,7 @@ where
         match self.expected.lock().unwrap().pop_front() {
             None => {}
             Some(Expect::Exit(ref expected_span)) => {
-                if let Some(name) = expected_span.name() {
-                    assert_eq!(name, span.name);
-                }
+                expected_span.check(span, &self.name);
                 let curr = self.current.lock().unwrap().pop();
                 assert_eq!(
                     Some(id),
diff --git a/tracing-subscriber/tests/env_filter/main.rs b/tracing-subscriber/tests/env_filter/main.rs
index 16921814c1..c541197b10 100644
--- a/tracing-subscriber/tests/env_filter/main.rs
+++ b/tracing-subscriber/tests/env_filter/main.rs
@@ -42,13 +42,13 @@ fn same_name_spans() {
             expect::span()
                 .named("foo")
                 .at_level(Level::TRACE)
-                .with_field(expect::field("bar")),
+                .with_fields(expect::field("bar")),
         )
         .new_span(
             expect::span()
                 .named("foo")
                 .at_level(Level::TRACE)
-                .with_field(expect::field("baz")),
+                .with_fields(expect::field("baz")),
         )
         .only()
         .run_with_handle();
@@ -275,13 +275,13 @@ mod per_layer_filter {
                 expect::span()
                     .named("foo")
                     .at_level(Level::TRACE)
-                    .with_field(expect::field("bar")),
+                    .with_fields(expect::field("bar")),
             )
             .new_span(
                 expect::span()
                     .named("foo")
                     .at_level(Level::TRACE)
-                    .with_field(expect::field("baz")),
+                    .with_fields(expect::field("baz")),
             )
             .only()
             .run_with_handle();
diff --git a/tracing-subscriber/tests/env_filter/per_layer.rs b/tracing-subscriber/tests/env_filter/per_layer.rs
index 229b9ff776..fe6031f263 100644
--- a/tracing-subscriber/tests/env_filter/per_layer.rs
+++ b/tracing-subscriber/tests/env_filter/per_layer.rs
@@ -37,13 +37,13 @@ fn same_name_spans() {
             expect::span()
                 .named("foo")
                 .at_level(Level::TRACE)
-                .with_field(expect::field("bar")),
+                .with_fields(expect::field("bar")),
         )
         .new_span(
             expect::span()
                 .named("foo")
                 .at_level(Level::TRACE)
-                .with_field(expect::field("baz")),
+                .with_fields(expect::field("baz")),
         )
         .only()
         .run_with_handle();
diff --git a/tracing-subscriber/tests/same_len_filters.rs b/tracing-subscriber/tests/same_len_filters.rs
index ac0b908d28..7827f5043e 100644
--- a/tracing-subscriber/tests/same_len_filters.rs
+++ b/tracing-subscriber/tests/same_len_filters.rs
@@ -61,13 +61,13 @@ fn same_num_fields_and_name_len() {
             expect::span()
                 .named("foo")
                 .at_level(Level::TRACE)
-                .with_field(expect::field("bar")),
+                .with_fields(expect::field("bar")),
         )
         .new_span(
             expect::span()
                 .named("baz")
                 .at_level(Level::TRACE)
-                .with_field(expect::field("boz")),
+                .with_fields(expect::field("boz")),
         )
         .only()
         .run_with_handle();
diff --git a/tracing/tests/span.rs b/tracing/tests/span.rs
index 09f1be8954..6a713b6453 100644
--- a/tracing/tests/span.rs
+++ b/tracing/tests/span.rs
@@ -342,7 +342,7 @@ fn entered_api() {
 fn moved_field() {
     let (subscriber, handle) = subscriber::mock()
         .new_span(
-            expect::span().named("foo").with_field(
+            expect::span().named("foo").with_fields(
                 expect::field("bar")
                     .with_value(&display("hello from my span"))
                     .only(),
@@ -373,7 +373,7 @@ fn dotted_field_name() {
         .new_span(
             expect::span()
                 .named("foo")
-                .with_field(expect::field("fields.bar").with_value(&true).only()),
+                .with_fields(expect::field("fields.bar").with_value(&true).only()),
         )
         .only()
         .run_with_handle();
@@ -389,7 +389,7 @@ fn dotted_field_name() {
 fn borrowed_field() {
     let (subscriber, handle) = subscriber::mock()
         .new_span(
-            expect::span().named("foo").with_field(
+            expect::span().named("foo").with_fields(
                 expect::field("bar")
                     .with_value(&display("hello from my span"))
                     .only(),
@@ -432,7 +432,7 @@ fn move_field_out_of_struct() {
     };
     let (subscriber, handle) = subscriber::mock()
         .new_span(
-            expect::span().named("foo").with_field(
+            expect::span().named("foo").with_fields(
                 expect::field("x")
                     .with_value(&debug(3.234))
                     .and(expect::field("y").with_value(&debug(-1.223)))
@@ -442,7 +442,7 @@ fn move_field_out_of_struct() {
         .new_span(
             expect::span()
                 .named("bar")
-                .with_field(expect::field("position").with_value(&debug(&pos)).only()),
+                .with_fields(expect::field("position").with_value(&debug(&pos)).only()),
         )
         .run_with_handle();
 
@@ -465,7 +465,7 @@ fn move_field_out_of_struct() {
 fn float_values() {
     let (subscriber, handle) = subscriber::mock()
         .new_span(
-            expect::span().named("foo").with_field(
+            expect::span().named("foo").with_fields(
                 expect::field("x")
                     .with_value(&3.234)
                     .and(expect::field("y").with_value(&-1.223))
@@ -492,7 +492,7 @@ fn add_field_after_new_span() {
         .new_span(
             expect::span()
                 .named("foo")
-                .with_field(expect::field("bar").with_value(&5)
+                .with_fields(expect::field("bar").with_value(&5)
                 .and(expect::field("baz").with_value).only()),
         )
         .record(
@@ -549,7 +549,7 @@ fn add_fields_only_after_new_span() {
 fn record_new_value_for_field() {
     let (subscriber, handle) = subscriber::mock()
         .new_span(
-            expect::span().named("foo").with_field(
+            expect::span().named("foo").with_fields(
                 expect::field("bar")
                     .with_value(&5)
                     .and(expect::field("baz").with_value(&false))
@@ -580,7 +580,7 @@ fn record_new_value_for_field() {
 fn record_new_values_for_fields() {
     let (subscriber, handle) = subscriber::mock()
         .new_span(
-            expect::span().named("foo").with_field(
+            expect::span().named("foo").with_fields(
                 expect::field("bar")
                     .with_value(&4)
                     .and(expect::field("baz").with_value(&false))
@@ -781,7 +781,7 @@ fn contextual_child() {
 fn display_shorthand() {
     let (subscriber, handle) = subscriber::mock()
         .new_span(
-            expect::span().named("my_span").with_field(
+            expect::span().named("my_span").with_fields(
                 expect::field("my_field")
                     .with_value(&display("hello world"))
                     .only(),
@@ -801,7 +801,7 @@ fn display_shorthand() {
 fn debug_shorthand() {
     let (subscriber, handle) = subscriber::mock()
         .new_span(
-            expect::span().named("my_span").with_field(
+            expect::span().named("my_span").with_fields(
                 expect::field("my_field")
                     .with_value(&debug("hello world"))
                     .only(),
@@ -821,7 +821,7 @@ fn debug_shorthand() {
 fn both_shorthands() {
     let (subscriber, handle) = subscriber::mock()
         .new_span(
-            expect::span().named("my_span").with_field(
+            expect::span().named("my_span").with_fields(
                 expect::field("display_field")
                     .with_value(&display("hello world"))
                     .and(expect::field("debug_field").with_value(&debug("hello world")))
@@ -842,7 +842,7 @@ fn both_shorthands() {
 fn constant_field_name() {
     let (subscriber, handle) = subscriber::mock()
         .new_span(
-            expect::span().named("my_span").with_field(
+            expect::span().named("my_span").with_fields(
                 expect::field("foo")
                     .with_value(&"bar")
                     .and(expect::field("constant string").with_value(&"also works"))
diff --git a/tracing/tests/subscriber.rs b/tracing/tests/subscriber.rs
index f676efeee8..1b9862879f 100644
--- a/tracing/tests/subscriber.rs
+++ b/tracing/tests/subscriber.rs
@@ -60,7 +60,7 @@ fn event_macros_dont_infinite_loop() {
 fn boxed_subscriber() {
     let (subscriber, handle) = subscriber::mock()
         .new_span(
-            expect::span().named("foo").with_field(
+            expect::span().named("foo").with_fields(
                 expect::field("bar")
                     .with_value(&display("hello from my span"))
                     .only(),
@@ -93,7 +93,7 @@ fn arced_subscriber() {
 
     let (subscriber, handle) = subscriber::mock()
         .new_span(
-            expect::span().named("foo").with_field(
+            expect::span().named("foo").with_fields(
                 expect::field("bar")
                     .with_value(&display("hello from my span"))
                     .only(),