From 00809ea08a5ddb5a4fd7ab2bed906661b0e6ffff Mon Sep 17 00:00:00 2001 From: Hayden Stainsby Date: Wed, 30 Oct 2024 13:59:25 +0100 Subject: [PATCH 1/9] ci: pin Rust to 1.81 for `wasm32-unknown-unknown` tests (#3125) There is an incompatibility with the version of Node available on our test runners and wasm32 in Rust 1.82 (#3123). To unblock the CI, this change pins Rust to 1.81 for the tests using the `wasm32-unknown-unknown` target. This is the same strategy used in Tokio to mitigate tokio-rs/tokio#6910 until a more permanent fix can be put in place. This change also bumps the MSRV on the `tracing-examples` crate from 1.63.0 to 1.64.0 to avoid triggering a lint about the MSRV after a change in Tokio 1.41.0 which bumps the required Rust version for the `try_join!` macro. The Tokio MSRV is 1.70 now, so needing this bump for the examples seems reasonable. --- .github/workflows/CI.yml | 24 +++++++++++++----------- examples/Cargo.toml | 2 +- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index fc79d0965a..dc14a813d9 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -35,7 +35,7 @@ jobs: name: cargo check runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - name: Check run: cargo check --all --tests --benches @@ -46,7 +46,7 @@ jobs: needs: check runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable with: components: rustfmt @@ -58,7 +58,7 @@ jobs: runs-on: ubuntu-latest needs: check steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable with: components: clippy @@ -88,7 +88,7 @@ jobs: - tracing - tracing-subscriber steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - name: install cargo-hack uses: taiki-e/install-action@cargo-hack @@ -146,7 +146,7 @@ jobs: - 1.63.0 - stable steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: install Rust nightly uses: dtolnay/rust-toolchain@nightly - name: "install Rust ${{ matrix.toolchain }}" @@ -210,7 +210,7 @@ jobs: fail-fast: false runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: "install Rust ${{ matrix.rust }}" uses: dtolnay/rust-toolchain@master with: @@ -252,7 +252,7 @@ jobs: - tracing-tower fail-fast: false steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable with: target: wasm32-unknown-unknown @@ -268,9 +268,11 @@ jobs: subcrate: - tracing steps: - - uses: actions/checkout@v3 - - uses: dtolnay/rust-toolchain@stable + - uses: actions/checkout@v4 + - name: Install Rust 1.81 + uses: dtolnay/rust-toolchain@stable with: + toolchain: 1.81 target: wasm32-unknown-unknown - name: install test runner for wasm uses: taiki-e/install-action@wasm-pack @@ -283,7 +285,7 @@ jobs: needs: check runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - name: "Test log support" run: cargo test @@ -315,4 +317,4 @@ jobs: - test-wasm - test-features-stable steps: - - run: exit 0 \ No newline at end of file + - run: exit 0 diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 9c07e33415..1443644503 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -3,7 +3,7 @@ name = "tracing-examples" version = "0.0.0" publish = false edition = "2018" -rust-version = "1.63.0" +rust-version = "1.64.0" [features] default = [] From a87faa38f8344afc6f15cfc1c275878874d4d903 Mon Sep 17 00:00:00 2001 From: Hayden Stainsby Date: Tue, 7 Nov 2023 11:36:18 +0100 Subject: [PATCH 2/9] test: add `tracing-test` crate for non-publishable test utils (#2466) There has been interest around publishing tracing-mock to crates.io for some time. In order to make this possible, it needs to be cleaned up. There are some test utils in the `tracing-mock` crate which wouldn't make sense to publish. They provide test futures that are needed in multiple `tracing-*` crates, but would likely not be needed outside that context. This change moves that functionality into a separate `tracing-test` crate, which should never be published to crates.io. Refs: #539 Co-authored-by: David Barsky --- Cargo.toml | 1 + tracing-attributes/Cargo.toml | 17 ++++- tracing-attributes/tests/async_fn.rs | 5 +- tracing-attributes/tests/err.rs | 1 + tracing-attributes/tests/follows_from.rs | 3 +- tracing-attributes/tests/ret.rs | 3 +- tracing-futures/Cargo.toml | 3 +- tracing-futures/tests/std_future.rs | 3 +- tracing-mock/Cargo.toml | 1 - tracing-mock/src/lib.rs | 65 ------------------- tracing-test/Cargo.toml | 26 ++++++++ tracing-test/LICENSE | 25 +++++++ tracing-test/README.md | 58 +++++++++++++++++ tracing-test/src/lib.rs | 65 +++++++++++++++++++ .../test_static_max_level_features/Cargo.toml | 2 +- .../tests/test.rs | 3 +- 16 files changed, 204 insertions(+), 77 deletions(-) create mode 100644 tracing-test/Cargo.toml create mode 100644 tracing-test/LICENSE create mode 100644 tracing-test/README.md create mode 100644 tracing-test/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index afd6d10d83..1624740e6c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ members = [ "tracing-mock", "tracing-subscriber", "tracing-serde", + "tracing-test", "tracing-appender", "tracing-journald", "examples" diff --git a/tracing-attributes/Cargo.toml b/tracing-attributes/Cargo.toml index 6dff74311d..a5d1939f7d 100644 --- a/tracing-attributes/Cargo.toml +++ b/tracing-attributes/Cargo.toml @@ -40,14 +40,25 @@ async-await = [] [dependencies] proc-macro2 = "1.0.60" -syn = { version = "2.0", default-features = false, features = ["full", "parsing", "printing", "visit-mut", "clone-impls", "extra-traits", "proc-macro"] } +syn = { version = "2.0", default-features = false, features = [ + "full", + "parsing", + "printing", + "visit-mut", + "clone-impls", + "extra-traits", + "proc-macro", +] } quote = "1.0.20" [dev-dependencies] tracing = { path = "../tracing", version = "0.1.35" } -tracing-mock = { path = "../tracing-mock", features = ["tokio-test"] } -tracing-subscriber = { path = "../tracing-subscriber", version = "0.3.0", features = ["env-filter"] } +tracing-mock = { path = "../tracing-mock" } tokio-test = "0.4.2" +tracing-subscriber = { path = "../tracing-subscriber", version = "0.3.0", features = [ + "env-filter", +] } +tracing-test = { path = "../tracing-test" } async-trait = "0.1.67" trybuild = "1.0.64" rustversion = "1.0.9" diff --git a/tracing-attributes/tests/async_fn.rs b/tracing-attributes/tests/async_fn.rs index affa89ea35..6acbd0e2a0 100644 --- a/tracing-attributes/tests/async_fn.rs +++ b/tracing-attributes/tests/async_fn.rs @@ -1,9 +1,10 @@ -use tracing_mock::*; - use std::convert::Infallible; use std::{future::Future, pin::Pin, sync::Arc}; + use tracing::subscriber::with_default; use tracing_attributes::instrument; +use tracing_mock::{expect, subscriber}; +use tracing_test::{block_on_future, PollN}; #[instrument] async fn test_async_fn(polls: usize) -> Result<(), ()> { diff --git a/tracing-attributes/tests/err.rs b/tracing-attributes/tests/err.rs index bee7aa5f4e..9d82487ea9 100644 --- a/tracing-attributes/tests/err.rs +++ b/tracing-attributes/tests/err.rs @@ -4,6 +4,7 @@ use tracing_attributes::instrument; use tracing_mock::*; use tracing_subscriber::filter::EnvFilter; use tracing_subscriber::layer::SubscriberExt; +use tracing_test::{block_on_future, PollN}; use std::convert::TryFrom; use std::num::TryFromIntError; diff --git a/tracing-attributes/tests/follows_from.rs b/tracing-attributes/tests/follows_from.rs index 6b5526b82e..a81c6c7813 100644 --- a/tracing-attributes/tests/follows_from.rs +++ b/tracing-attributes/tests/follows_from.rs @@ -1,6 +1,7 @@ use tracing::{subscriber::with_default, Id, Level, Span}; use tracing_attributes::instrument; -use tracing_mock::*; +use tracing_mock::{expect, subscriber}; +use tracing_test::block_on_future; #[instrument(follows_from = causes, skip(causes))] fn with_follows_from_sync(causes: impl IntoIterator>>) {} diff --git a/tracing-attributes/tests/ret.rs b/tracing-attributes/tests/ret.rs index 90bd9e185d..0ba2e10c8b 100644 --- a/tracing-attributes/tests/ret.rs +++ b/tracing-attributes/tests/ret.rs @@ -1,11 +1,12 @@ use std::convert::TryFrom; use std::num::TryFromIntError; -use tracing_mock::*; use tracing::{subscriber::with_default, Level}; use tracing_attributes::instrument; +use tracing_mock::{expect, subscriber}; use tracing_subscriber::layer::SubscriberExt; use tracing_subscriber::EnvFilter; +use tracing_test::block_on_future; #[instrument(ret)] fn ret() -> i32 { diff --git a/tracing-futures/Cargo.toml b/tracing-futures/Cargo.toml index c03c222d85..0dac3d4c78 100644 --- a/tracing-futures/Cargo.toml +++ b/tracing-futures/Cargo.toml @@ -43,7 +43,8 @@ mio = { version = "0.6.23", optional = true } futures = "0.3.21" tokio-test = "0.4.2" tracing-core = { path = "../tracing-core", version = "0.1.28" } -tracing-mock = { path = "../tracing-mock", features = ["tokio-test"] } +tracing-mock = { path = "../tracing-mock" } +tracing-test = { path = "../tracing-test" } [badges] maintenance = { status = "actively-developed" } diff --git a/tracing-futures/tests/std_future.rs b/tracing-futures/tests/std_future.rs index ba35de6f8f..050d7491c3 100644 --- a/tracing-futures/tests/std_future.rs +++ b/tracing-futures/tests/std_future.rs @@ -3,7 +3,8 @@ use std::{future::Future, pin::Pin, task}; use futures::FutureExt as _; use tracing::Instrument; use tracing::{subscriber::with_default, Level}; -use tracing_mock::*; +use tracing_mock::{expect, subscriber}; +use tracing_test::{block_on_future, PollN}; #[test] fn enter_exit_is_reasonable() { diff --git a/tracing-mock/Cargo.toml b/tracing-mock/Cargo.toml index ca2bebb1eb..b183aba29e 100644 --- a/tracing-mock/Cargo.toml +++ b/tracing-mock/Cargo.toml @@ -21,7 +21,6 @@ publish = false tracing = { path = "../tracing", version = "0.1.35", features = ["std", "attributes"], default-features = false } tracing-core = { path = "../tracing-core", version = "0.1.28", default-features = false } 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 tokio-stream = { version = "0.1.9", optional = true } diff --git a/tracing-mock/src/lib.rs b/tracing-mock/src/lib.rs index 720efbe30a..abf29777a4 100644 --- a/tracing-mock/src/lib.rs +++ b/tracing-mock/src/lib.rs @@ -1,9 +1,4 @@ #![doc = include_str!("../README.md")] -use std::{ - pin::Pin, - task::{Context, Poll}, -}; - pub mod event; pub mod expect; pub mod field; @@ -22,12 +17,6 @@ pub enum Parent { Explicit(String), } -pub struct PollN { - and_return: Option>, - finish_at: usize, - polls: usize, -} - impl Parent { pub fn check_parent_name( &self, @@ -104,57 +93,3 @@ impl Parent { } } } - -impl std::future::Future for PollN -where - T: Unpin, - E: Unpin, -{ - type Output = Result; - fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { - let this = self.get_mut(); - - this.polls += 1; - if this.polls == this.finish_at { - let value = this.and_return.take().expect("polled after ready"); - - Poll::Ready(value) - } else { - cx.waker().wake_by_ref(); - Poll::Pending - } - } -} - -impl PollN<(), ()> { - pub fn new_ok(finish_at: usize) -> Self { - Self { - and_return: Some(Ok(())), - finish_at, - polls: 0, - } - } - - pub fn new_err(finish_at: usize) -> Self { - Self { - and_return: Some(Err(())), - finish_at, - polls: 0, - } - } -} - -#[cfg(feature = "tokio-test")] -pub fn block_on_future(future: F) -> F::Output -where - F: std::future::Future, -{ - use tokio_test::task; - - let mut task = task::spawn(future); - loop { - if let Poll::Ready(v) = task.poll() { - break v; - } - } -} diff --git a/tracing-test/Cargo.toml b/tracing-test/Cargo.toml new file mode 100644 index 0000000000..40fd7c789c --- /dev/null +++ b/tracing-test/Cargo.toml @@ -0,0 +1,26 @@ +## BIG SCARY NOTE +# This crate is internal and to be used for testing only. It should not +# be published to crates.io ever. If the functionality is needed outside +# the tracing project, it should be moved back to tracing-mock. + +[package] +name = "tracing-test" +version = "0.1.0" +authors = [ + "Eliza Weisman ", + "Tokio Contributors ", +] +license = "MIT" +readme = "README.md" +repository = "https://github.com/tokio-rs/tracing" +homepage = "https://tokio.rs" +edition = "2018" +rust-version = "1.49.0" +publish = false + +[dependencies] +tokio-test = "0.4.2" + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs"] diff --git a/tracing-test/LICENSE b/tracing-test/LICENSE new file mode 100644 index 0000000000..cdb28b4b56 --- /dev/null +++ b/tracing-test/LICENSE @@ -0,0 +1,25 @@ +Copyright (c) 2019 Tokio Contributors + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/tracing-test/README.md b/tracing-test/README.md new file mode 100644 index 0000000000..6cef11e90b --- /dev/null +++ b/tracing-test/README.md @@ -0,0 +1,58 @@ +![Tracing — Structured, application-level diagnostics][splash] + +[splash]: https://raw.githubusercontent.com/tokio-rs/tracing/master/assets/splash.svg + +# tracing-test + +Utilities for testing [`tracing`][tracing] and crates that uses it. + +[![Documentation (master)][docs-master-badge]][docs-master-url] +[![MIT licensed][mit-badge]][mit-url] +[![Build Status][actions-badge]][actions-url] +[![Discord chat][discord-badge]][discord-url] + +[Documentation][docs-master-url] | [Chat][discord-url] + +[docs-master-badge]: https://img.shields.io/badge/docs-master-blue +[docs-master-url]: https://tracing-rs.netlify.com/tracing_mock +[mit-badge]: https://img.shields.io/badge/license-MIT-blue.svg +[mit-url]: https://github.com/tokio-rs/tracing/blob/master/tracing-test/LICENSE +[actions-badge]: https://github.com/tokio-rs/tracing/workflows/CI/badge.svg +[actions-url]:https://github.com/tokio-rs/tracing/actions?query=workflow%3ACI +[discord-badge]: https://img.shields.io/discord/500028886025895936?logo=discord&label=discord&logoColor=white +[discord-url]: https://discord.gg/EeF3cQw + +## Overview + +[`tracing`] is a framework for instrumenting Rust programs to collect +structured, event-based diagnostic information. `tracing-test` provides +some reusable tools to aid in testing, but that are only intended for +internal use. For mocks and expectations, see [`tracing-mock`]. + +*Compiler support: [requires `rustc` 1.56+][msrv]* + +[msrv]: #supported-rust-versions + +## Supported Rust Versions + +Tracing is built against the latest stable release. The minimum supported +version is 1.56. The current Tracing version is not guaranteed to build on Rust +versions earlier than the minimum supported version. + +Tracing follows the same compiler support policies as the rest of the Tokio +project. The current stable Rust compiler and the three most recent minor +versions before it will always be supported. For example, if the current stable +compiler version is 1.45, the minimum supported version will not be increased +past 1.42, three minor versions prior. Increasing the minimum supported compiler +version is not considered a semver breaking change as long as doing so complies +with this policy. + +## License + +This project is licensed under the [MIT license][mit-url]. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in Tracing by you, shall be licensed as MIT, without any additional +terms or conditions. \ No newline at end of file diff --git a/tracing-test/src/lib.rs b/tracing-test/src/lib.rs new file mode 100644 index 0000000000..d89186a066 --- /dev/null +++ b/tracing-test/src/lib.rs @@ -0,0 +1,65 @@ +use std::{ + pin::Pin, + task::{Context, Poll}, +}; + +#[allow(missing_docs)] + +pub struct PollN { + and_return: Option>, + finish_at: usize, + polls: usize, +} + +impl std::future::Future for PollN +where + T: Unpin, + E: Unpin, +{ + type Output = Result; + fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { + let this = self.get_mut(); + + this.polls += 1; + if this.polls == this.finish_at { + let value = this.and_return.take().expect("polled after ready"); + + Poll::Ready(value) + } else { + cx.waker().wake_by_ref(); + Poll::Pending + } + } +} + +impl PollN<(), ()> { + pub fn new_ok(finish_at: usize) -> Self { + Self { + and_return: Some(Ok(())), + finish_at, + polls: 0, + } + } + + pub fn new_err(finish_at: usize) -> Self { + Self { + and_return: Some(Err(())), + finish_at, + polls: 0, + } + } +} + +pub fn block_on_future(future: F) -> F::Output +where + F: std::future::Future, +{ + use tokio_test::task; + + let mut task = task::spawn(future); + loop { + if let Poll::Ready(v) = task.poll() { + break v; + } + } +} diff --git a/tracing/test_static_max_level_features/Cargo.toml b/tracing/test_static_max_level_features/Cargo.toml index 89edfd506b..44271a8974 100644 --- a/tracing/test_static_max_level_features/Cargo.toml +++ b/tracing/test_static_max_level_features/Cargo.toml @@ -19,4 +19,4 @@ features = ["max_level_debug", "release_max_level_info"] [dev-dependencies] tokio-test = "0.2.0" -tracing-mock = { path = "../../tracing-mock", features = ["tokio-test"] } +tracing-test = { path = "../../tracing-test" } diff --git a/tracing/test_static_max_level_features/tests/test.rs b/tracing/test_static_max_level_features/tests/test.rs index 6e964624bd..71692fd9e9 100644 --- a/tracing/test_static_max_level_features/tests/test.rs +++ b/tracing/test_static_max_level_features/tests/test.rs @@ -5,7 +5,8 @@ use tracing::span::{Attributes, Record}; use tracing::{ debug, error, info, instrument, span, trace, warn, Subscriber, Event, Id, Level, Metadata, }; -use tracing_mock::*; +use tracing_core::span::Current; +use tracing_test::block_on_future; struct State { last_level: Mutex>, From c9bd136937a15e9c1d06ceb01c48b9580d8580b7 Mon Sep 17 00:00:00 2001 From: Hayden Stainsby Date: Tue, 14 Nov 2023 10:57:14 +0100 Subject: [PATCH 3/9] 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 --- 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::())), + .with_fields(expect::field("Self").with_value(&std::any::type_name::())), ) .enter(span2.clone()) .new_span( span4 .clone() - .with_field(expect::field("Self").with_value(&std::any::type_name::())), + .with_fields(expect::field("Self").with_value(&std::any::type_name::())), ) .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::())), + .with_fields(expect::field("Self").with_value(&std::any::type_name::())), ) .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(self, name: I) -> Self where I: Into, @@ -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(self, target: I) -> Self where I: Into, @@ -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(self, fields: I) -> NewSpan + where + I: Into, + { + 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 { + pub(crate) fn level(&self) -> Option { 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(self, fields: I) -> NewSpan - where - I: Into, - { - 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 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(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(self, fields: I) -> NewSpan where I: Into, { @@ -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, 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) -> bool> { spans: Mutex>, expected: Arc>>, @@ -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(), From 631b575c3fc28b6c9ca6d5c102c9b25032ca2c34 Mon Sep 17 00:00:00 2001 From: Hayden Stainsby Date: Tue, 14 Nov 2023 12:29:28 +0100 Subject: [PATCH 4/9] mock: document public APIs in the `field` module (#2443) This change adds documentation to the tracing-mock `field` module and all the public APIs within it. This includes doctests on all the methods which serve as examples. Additionally, the `field::msg` function (which constructs a field with name "message" and the provided value) was moved to `expect::message`. This is part of a unification of all expectation constructors inside the `expect` module. Refs: #539 Co-authored-by: David Barsky --- tracing-mock/README.md | 10 +- tracing-mock/src/event.rs | 2 +- tracing-mock/src/expect.rs | 7 + tracing-mock/src/field.rs | 376 +++++++++++++++++++++++++++++++-- tracing-mock/src/layer.rs | 8 +- tracing-mock/src/subscriber.rs | 8 +- tracing/tests/event.rs | 4 +- 7 files changed, 386 insertions(+), 29 deletions(-) diff --git a/tracing-mock/README.md b/tracing-mock/README.md index 04b8dc41b2..85d6774457 100644 --- a/tracing-mock/README.md +++ b/tracing-mock/README.md @@ -78,7 +78,7 @@ fn yak_shaving() { } let (subscriber, handle) = subscriber::mock() - .event(expect::event().with_fields(field::msg("preparing to shave yaks"))) + .event(expect::event().with_fields(expect::message("preparing to shave yaks"))) .only() .run_with_handle(); @@ -102,7 +102,7 @@ Below is a slightly more complex example. `tracing-mock` asserts that, in order: ```rust use tracing::subscriber::with_default; -use tracing_mock::{subscriber, expect, field}; +use tracing_mock::{subscriber, expect}; #[tracing::instrument] fn yak_shaving(number_of_yaks: u32) { @@ -128,7 +128,7 @@ let (subscriber, handle) = subscriber::mock() expect::event().with_fields( expect::field("number_of_yaks") .with_value(&yak_count) - .and(field::msg("preparing to shave yaks")) + .and(expect::message("preparing to shave yaks")) .only(), ), ) @@ -136,7 +136,7 @@ let (subscriber, handle) = subscriber::mock() expect::event().with_fields( expect::field("all_yaks_shaved") .with_value(&true) - .and(field::msg("yak shaving completed.")) + .and(expect::message("yak shaving completed.")) .only(), ), ) @@ -173,4 +173,4 @@ This project is licensed under the [MIT license][mit-url]. Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in Tracing by you, shall be licensed as MIT, without any additional -terms or conditions. \ No newline at end of file +terms or conditions. diff --git a/tracing-mock/src/event.rs b/tracing-mock/src/event.rs index 6c4cbf7e71..79d99d49dc 100644 --- a/tracing-mock/src/event.rs +++ b/tracing-mock/src/event.rs @@ -48,7 +48,7 @@ pub struct ExpectedEvent { } pub fn msg(message: impl fmt::Display) -> ExpectedEvent { - expect::event().with_fields(field::msg(message)) + expect::event().with_fields(expect::message(message)) } impl ExpectedEvent { diff --git a/tracing-mock/src/expect.rs b/tracing-mock/src/expect.rs index 044f134580..353bc52f5f 100644 --- a/tracing-mock/src/expect.rs +++ b/tracing-mock/src/expect.rs @@ -38,6 +38,13 @@ where } } +pub fn message(message: impl fmt::Display) -> ExpectedField { + ExpectedField { + name: "message".to_string(), + value: ExpectedValue::Debug(message.to_string()), + } +} + pub fn span() -> ExpectedSpan { ExpectedSpan { ..Default::default() diff --git a/tracing-mock/src/field.rs b/tracing-mock/src/field.rs index ea5f7a4eb7..6d1e73f98a 100644 --- a/tracing-mock/src/field.rs +++ b/tracing-mock/src/field.rs @@ -1,3 +1,86 @@ +//! Define expectations to validate fields on events and spans. +//! +//! The [`ExpectedField`] struct define expected values for fields in +//! order to match events and spans via the mock subscriber API in the +//! [`subscriber`] module. +//! +//! Expected fields should be created with [`expect::field`] and a +//! chain of method calls to specify the field value and additional +//! fields as necessary. +//! +//! # Examples +//! +//! The simplest case is to expect that an event has a field with a +//! specific name, without any expectation about the value: +//! +//! ``` +//! use tracing_mock::{subscriber, expect}; +//! +//! let event = expect::event() +//! .with_fields(expect::field("field_name")); +//! +//! let (subscriber, handle) = subscriber::mock() +//! .event(event) +//! .run_with_handle(); +//! +//! tracing::subscriber::with_default(subscriber, || { +//! tracing::info!(field_name = "value"); +//! }); +//! +//! handle.assert_finished(); +//! ``` +//! +//! It is possible to expect multiple fields and specify the value for +//! each of them: +//! +//! ``` +//! use tracing_mock::{subscriber, expect}; +//! +//! let event = expect::event().with_fields( +//! expect::field("string_field") +//! .with_value(&"field_value") +//! .and(expect::field("integer_field").with_value(&54_i64)) +//! .and(expect::field("bool_field").with_value(&true)), +//! ); +//! +//! let (subscriber, handle) = subscriber::mock() +//! .event(event) +//! .run_with_handle(); +//! +//! tracing::subscriber::with_default(subscriber, || { +//! tracing::info!( +//! string_field = "field_value", +//! integer_field = 54_i64, +//! bool_field = true, +//! ); +//! }); +//! +//! handle.assert_finished(); +//! ``` +//! +//! If an expected field is not present, or if the value of the field +//! is different, the test will fail. In this example, the value is +//! different: +//! +//! ```should_panic +//! use tracing_mock::{subscriber, expect}; +//! +//! let event = expect::event() +//! .with_fields(expect::field("field_name").with_value(&"value")); +//! +//! let (subscriber, handle) = subscriber::mock() +//! .event(event) +//! .run_with_handle(); +//! +//! tracing::subscriber::with_default(subscriber, || { +//! tracing::info!(field_name = "different value"); +//! }); +//! +//! handle.assert_finished(); +//! ``` +//! +//! [`subscriber`]: mod@crate::subscriber +//! [`expect::field`]: fn@crate::expect::field use tracing::{ callsite, callsite::Callsite, @@ -7,12 +90,24 @@ use tracing::{ use std::{collections::HashMap, fmt}; +/// An expectation for multiple fields. +/// +/// For a detailed description and examples, see the documentation for +/// the methods and the [`field`] module. +/// +/// [`field`]: mod@crate::field #[derive(Default, Debug, Eq, PartialEq)] pub struct ExpectedFields { fields: HashMap, only: bool, } +/// An expected field. +/// +/// For a detailed description and examples, see the documentation for +/// the methods and the [`field`] module. +/// +/// [`field`]: mod@crate::field #[derive(Debug)] pub struct ExpectedField { pub(super) name: String, @@ -20,7 +115,7 @@ pub struct ExpectedField { } #[derive(Debug)] -pub enum ExpectedValue { +pub(crate) enum ExpectedValue { F64(f64), I64(i64), U64(u64), @@ -55,15 +150,48 @@ impl PartialEq for ExpectedValue { } } -pub fn msg(message: impl fmt::Display) -> ExpectedField { - ExpectedField { - name: "message".to_string(), - value: ExpectedValue::Debug(message.to_string()), - } -} - impl ExpectedField { - /// Expect a field with the given name and value. + /// Sets the value to expect when matching this field. + /// + /// If the recorded value for this field diffs, the expectation will fail. + /// + /// # Examples + /// + /// ``` + /// use tracing_mock::{subscriber, expect}; + /// + /// let event = expect::event() + /// .with_fields(expect::field("field_name").with_value(&"value")); + /// + /// let (subscriber, handle) = subscriber::mock() + /// .event(event) + /// .run_with_handle(); + /// + /// tracing::subscriber::with_default(subscriber, || { + /// tracing::info!(field_name = "value"); + /// }); + /// + /// handle.assert_finished(); + /// ``` + /// + /// A different value will cause the test to fail: + /// + /// ```should_panic + /// use tracing_mock::{subscriber, expect}; + /// + /// let event = expect::event() + /// .with_fields(expect::field("field_name").with_value(&"value")); + /// + /// let (subscriber, handle) = subscriber::mock() + /// .event(event) + /// .run_with_handle(); + /// + /// tracing::subscriber::with_default(subscriber, || { + /// tracing::info!(field_name = "different value"); + /// }); + /// + /// handle.assert_finished(); + /// ``` pub fn with_value(self, value: &dyn Value) -> Self { Self { value: ExpectedValue::from(value), @@ -71,6 +199,58 @@ impl ExpectedField { } } + /// Adds an additional [`ExpectedField`] to be matched. + /// + /// Any fields introduced by `.and` must also match. If any fields + /// are not present, or if the value for any field is different, + /// then the expectation will fail. + /// + /// # Examples + /// + /// ``` + /// use tracing_mock::{subscriber, expect}; + /// + /// let event = expect::event().with_fields( + /// expect::field("field") + /// .with_value(&"value") + /// .and(expect::field("another_field").with_value(&42)), + /// ); + /// + /// let (subscriber, handle) = subscriber::mock() + /// .event(event) + /// .run_with_handle(); + /// + /// tracing::subscriber::with_default(subscriber, || { + /// tracing::info!( + /// field = "value", + /// another_field = 42, + /// ); + /// }); + /// + /// handle.assert_finished(); + /// ``` + /// + /// If the second field is not present, the test will fail: + /// + /// ```should_panic + /// use tracing_mock::{subscriber, expect}; + /// + /// let event = expect::event().with_fields( + /// expect::field("field") + /// .with_value(&"value") + /// .and(expect::field("another_field").with_value(&42)), + /// ); + /// + /// let (subscriber, handle) = subscriber::mock() + /// .event(event) + /// .run_with_handle(); + /// + /// tracing::subscriber::with_default(subscriber, || { + /// tracing::info!(field = "value"); + /// }); + /// + /// handle.assert_finished(); + /// ``` pub fn and(self, other: ExpectedField) -> ExpectedFields { ExpectedFields { fields: HashMap::new(), @@ -80,6 +260,47 @@ impl ExpectedField { .and(other) } + /// Indicates that no fields other than those specified should be + /// expected. + /// + /// If additional fields are present on the recorded event or span, + /// the expectation will fail. + /// + /// # Examples + /// + /// Check that only a single field is recorded. + /// + /// ``` + /// use tracing_mock::{subscriber, expect}; + /// + /// let event = expect::event() + /// .with_fields(expect::field("field").with_value(&"value").only()); + /// + /// let (subscriber, handle) = subscriber::mock().event(event).run_with_handle(); + /// + /// tracing::subscriber::with_default(subscriber, || { + /// tracing::info!(field = "value"); + /// }); + /// + /// handle.assert_finished(); + /// ``` + /// + /// The following example fails because a second field is recorded. + /// + /// ```should_panic + /// use tracing_mock::{subscriber, expect}; + /// + /// let event = expect::event() + /// .with_fields(expect::field("field").with_value(&"value").only()); + /// + /// let (subscriber, handle) = subscriber::mock().event(event).run_with_handle(); + /// + /// tracing::subscriber::with_default(subscriber, || { + /// tracing::info!(field = "value", another_field = 42,); + /// }); + /// + /// handle.assert_finished(); + /// ``` pub fn only(self) -> ExpectedFields { ExpectedFields { fields: HashMap::new(), @@ -100,12 +321,137 @@ impl From for ExpectedFields { } impl ExpectedFields { + /// Adds an additional [`ExpectedField`] to be matched. + /// + /// _All_ fields must match for the expectation to pass. If any of + /// them are not present, if any of the values differs, the + /// expectation will fail. + /// + /// This method performs the same function as + /// [`ExpectedField::and`], but applies in the case where there are + /// already multiple fields expected. + /// + /// # Examples + /// + /// ``` + /// use tracing_mock::{subscriber, expect}; + /// + /// let event = expect::event().with_fields( + /// expect::field("field") + /// .with_value(&"value") + /// .and(expect::field("another_field").with_value(&42)) + /// .and(expect::field("a_third_field").with_value(&true)), + /// ); + /// + /// let (subscriber, handle) = subscriber::mock() + /// .event(event) + /// .run_with_handle(); + /// + /// tracing::subscriber::with_default(subscriber, || { + /// tracing::info!( + /// field = "value", + /// another_field = 42, + /// a_third_field = true, + /// ); + /// }); + /// + /// handle.assert_finished(); + /// ``` + /// + /// If any of the expected fields are not present on the recorded + /// event, the test will fail: + /// + /// ```should_panic + /// use tracing_mock::{subscriber, expect}; + /// + /// let event = expect::event().with_fields( + /// expect::field("field") + /// .with_value(&"value") + /// .and(expect::field("another_field").with_value(&42)) + /// .and(expect::field("a_third_field").with_value(&true)), + /// ); + /// + /// let (subscriber, handle) = subscriber::mock() + /// .event(event) + /// .run_with_handle(); + /// + /// tracing::subscriber::with_default(subscriber, || { + /// tracing::info!( + /// field = "value", + /// a_third_field = true, + /// ); + /// }); + /// + /// handle.assert_finished(); + /// ``` + /// + /// [`ExpectedField::and`]: fn@crate::field::ExpectedField::and pub fn and(mut self, field: ExpectedField) -> Self { self.fields.insert(field.name, field.value); self } - /// Indicates that no fields other than those specified should be expected. + /// Asserts that no fields other than those specified should be + /// expected. + /// + /// This method performs the same function as + /// [`ExpectedField::only`], but applies in the case where there are + /// multiple fields expected. + /// + /// # Examples + /// + /// Check that only two fields are recorded on the event. + /// + /// ``` + /// use tracing_mock::{subscriber, expect}; + /// + /// let event = expect::event().with_fields( + /// expect::field("field") + /// .with_value(&"value") + /// .and(expect::field("another_field").with_value(&42)) + /// .only(), + /// ); + /// + /// let (subscriber, handle) = subscriber::mock() + /// .event(event) + /// .run_with_handle(); + /// + /// tracing::subscriber::with_default(subscriber, || { + /// tracing::info!( + /// field = "value", + /// another_field = 42, + /// ); + /// }); + /// + /// handle.assert_finished(); + /// ``` + /// + /// The following example fails because a third field is recorded. + /// + /// ```should_panic + /// use tracing_mock::{subscriber, expect}; + /// + /// let event = expect::event().with_fields( + /// expect::field("field") + /// .with_value(&"value") + /// .and(expect::field("another_field").with_value(&42)) + /// .only(), + /// ); + /// + /// let (subscriber, handle) = subscriber::mock() + /// .event(event) + /// .run_with_handle(); + /// + /// tracing::subscriber::with_default(subscriber, || { + /// tracing::info!( + /// field = "value", + /// another_field = 42, + /// a_third_field = true, + /// ); + /// }); + /// + /// handle.assert_finished(); + /// ``` pub fn only(self) -> Self { Self { only: true, ..self } } @@ -138,7 +484,11 @@ impl ExpectedFields { } } - pub fn checker<'a>(&'a mut self, ctx: &'a str, subscriber_name: &'a str) -> CheckVisitor<'a> { + pub(crate) fn checker<'a>( + &'a mut self, + ctx: &'a str, + subscriber_name: &'a str, + ) -> CheckVisitor<'a> { CheckVisitor { expect: self, ctx, @@ -146,7 +496,7 @@ impl ExpectedFields { } } - pub fn is_empty(&self) -> bool { + pub(crate) fn is_empty(&self) -> bool { self.fields.is_empty() } } @@ -165,7 +515,7 @@ impl fmt::Display for ExpectedValue { } } -pub struct CheckVisitor<'a> { +pub(crate) struct CheckVisitor<'a> { expect: &'a mut ExpectedFields, ctx: &'a str, subscriber_name: &'a str, diff --git a/tracing-mock/src/layer.rs b/tracing-mock/src/layer.rs index fa3c8ab28b..7c36b092be 100644 --- a/tracing-mock/src/layer.rs +++ b/tracing-mock/src/layer.rs @@ -12,7 +12,7 @@ //! //! let (layer, handle) = layer::mock() //! // Expect a single event with a specified message -//! .event(expect::event().with_fields(field::msg("droids"))) +//! .event(expect::event().with_fields(expect::message("droids"))) //! .run_with_handle(); //! //! // Use `set_default` to apply the `MockSubscriber` until the end @@ -42,7 +42,7 @@ //! // Enter a matching span //! .enter(span.clone()) //! // Record an event with message "collect parting message" -//! .event(expect::event().with_fields(field::msg("say hello"))) +//! .event(expect::event().with_fields(expect::message("say hello"))) //! // Exit a matching span //! .exit(span) //! // Expect no further messages to be recorded @@ -84,7 +84,7 @@ //! // Enter a matching span //! .enter(span.clone()) //! // Record an event with message "collect parting message" -//! .event(expect::event().with_fields(field::msg("say hello"))) +//! .event(expect::event().with_fields(expect::message("say hello"))) //! // Exit a matching span //! .exit(span) //! // Expect no further messages to be recorded @@ -154,7 +154,7 @@ use std::{ /// // Enter a matching span /// .enter(span.clone()) /// // Record an event with message "collect parting message" -/// .event(expect::event().with_fields(field::msg("say hello"))) +/// .event(expect::event().with_fields(expect::message("say hello"))) /// // Exit a matching span /// .exit(span) /// // Expect no further messages to be recorded diff --git a/tracing-mock/src/subscriber.rs b/tracing-mock/src/subscriber.rs index 83251a3715..1d5b36bd80 100644 --- a/tracing-mock/src/subscriber.rs +++ b/tracing-mock/src/subscriber.rs @@ -12,7 +12,7 @@ //! //! let (subscriber, handle) = subscriber::mock() //! // Expect a single event with a specified message -//! .event(expect::event().with_fields(field::msg("droids"))) +//! .event(expect::event().with_fields(expect::message("droids"))) //! .only() //! .run_with_handle(); //! @@ -40,7 +40,7 @@ //! // Enter a matching span //! .enter(span.clone()) //! // Record an event with message "collect parting message" -//! .event(expect::event().with_fields(field::msg("collect parting message"))) +//! .event(expect::event().with_fields(expect::message("collect parting message"))) //! // Record a value for the field `parting` on a matching span //! .record(span.clone(), expect::field("parting").with_value(&"goodbye world!")) //! // Exit a matching span @@ -81,7 +81,7 @@ //! .named("my_span"); //! let (subscriber, handle) = subscriber::mock() //! .enter(span.clone()) -//! .event(expect::event().with_fields(field::msg("collect parting message"))) +//! .event(expect::event().with_fields(expect::message("collect parting message"))) //! .record(span.clone(), expect::field("parting").with_value(&"goodbye world!")) //! .exit(span) //! .only() @@ -221,7 +221,7 @@ pub struct MockHandle(Arc>>, String); /// // Enter a matching span /// .enter(span.clone()) /// // Record an event with message "collect parting message" -/// .event(expect::event().with_fields(field::msg("collect parting message"))) +/// .event(expect::event().with_fields(expect::message("collect parting message"))) /// // Record a value for the field `parting` on a matching span /// .record(span.clone(), expect::field("parting").with_value(&"goodbye world!")) /// // Exit a matching span diff --git a/tracing/tests/event.rs b/tracing/tests/event.rs index 0be7c0bc56..417b8878b1 100644 --- a/tracing/tests/event.rs +++ b/tracing/tests/event.rs @@ -86,7 +86,7 @@ fn message_without_delims() { .and( expect::field("question").with_value(&"life, the universe, and everything"), ) - .and(field::msg(format_args!( + .and(expect::message(format_args!( "hello from my event! tricky? {:?}!", true ))) @@ -115,7 +115,7 @@ fn string_message_without_delims() { .and( expect::field("question").with_value(&"life, the universe, and everything"), ) - .and(field::msg(format_args!("hello from my event"))) + .and(expect::message(format_args!("hello from my event"))) .only(), ), ) From 8aecce82913bd8551e9055101905b22a825c2bd3 Mon Sep 17 00:00:00 2001 From: Hayden Stainsby Date: Fri, 21 Jun 2024 11:11:29 +0200 Subject: [PATCH 5/9] mock: add `ExpectedId` to link span expectations (#3007) It currently isn't possible to differentiate spans with the same name, target, and level when setting expectations on `enter`, `exit`, and `drop_span`. This is not an issue for `tracing-mock`'s original (and still primary) use case, which is to test `tracing` itself. However, when testing the tracing instrumentation in library or application code, this can be a limitation. For example, when testing the instrumentation in tokio (tokio-rs/tokio#6112), it isn't possible to set an expectation on which task span is entered first, because the name, target, and level of those spans are always identical - in fact, the spans have the same metadata and only the field values are different. To make differentiating different spans possible, `ExpectId` has been introduced. It is an opaque struct which represents a `span::Id` and can be used to match spans from a `new_span` expectation (where a `NewSpan` is accepted and all fields and values can be expected) through to subsequent `enter`, `exit`, and `drop_span` expectations. An `ExpectedId` is passed to an `ExpectedSpan` which then needs to be expected with `MockCollector::new_span`. A clone of the `ExpectedId` (or a clone of the `ExpectedSpan` with the `ExpectedId` already on it) will then match the ID assigned to the span to the other span lifecycle expectations. The `ExpectedId` uses an `Arc` which has the ID for the new span assigned to it, and then its clones will be matched against that same ID. In future changes it will also be possible to use this `ExpectedId` to match parent spans, currently a parent is only matched by name. --- tracing-attributes/tests/parents.rs | 25 +- tracing-futures/tests/std_future.rs | 12 +- tracing-mock/src/ancestry.rs | 148 ++++++++++ tracing-mock/src/event.rs | 128 +++------ tracing-mock/src/expect.rs | 44 ++- tracing-mock/src/layer.rs | 23 +- tracing-mock/src/lib.rs | 86 +----- tracing-mock/src/span.rs | 352 +++++++++++++++-------- tracing-mock/src/subscriber.rs | 66 +++-- tracing-mock/tests/event_ancestry.rs | 346 +++++++++++++++++++++++ tracing-mock/tests/span_ancestry.rs | 401 +++++++++++++++++++++++++++ tracing/tests/event.rs | 12 +- tracing/tests/instrument.rs | 12 +- tracing/tests/span.rs | 54 +++- 14 files changed, 1353 insertions(+), 356 deletions(-) create mode 100644 tracing-mock/src/ancestry.rs create mode 100644 tracing-mock/tests/event_ancestry.rs create mode 100644 tracing-mock/tests/span_ancestry.rs diff --git a/tracing-attributes/tests/parents.rs b/tracing-attributes/tests/parents.rs index d4559415d9..e6db581ff5 100644 --- a/tracing-attributes/tests/parents.rs +++ b/tracing-attributes/tests/parents.rs @@ -21,23 +21,16 @@ fn default_parent_test() { .new_span( contextual_parent .clone() - .with_contextual_parent(None) - .with_explicit_parent(None), - ) - .new_span( - child - .clone() - .with_contextual_parent(Some("contextual_parent")) - .with_explicit_parent(None), + .with_ancestry(expect::is_contextual_root()), ) + .new_span(child.clone().with_ancestry(expect::is_contextual_root())) .enter(child.clone()) .exit(child.clone()) .enter(contextual_parent.clone()) .new_span( child .clone() - .with_contextual_parent(Some("contextual_parent")) - .with_explicit_parent(None), + .with_ancestry(expect::has_contextual_parent("contextual_parent")), ) .enter(child.clone()) .exit(child) @@ -68,20 +61,14 @@ fn explicit_parent_test() { .new_span( contextual_parent .clone() - .with_contextual_parent(None) - .with_explicit_parent(None), - ) - .new_span( - explicit_parent - .with_contextual_parent(None) - .with_explicit_parent(None), + .with_ancestry(expect::is_contextual_root()), ) + .new_span(explicit_parent.with_ancestry(expect::is_contextual_root())) .enter(contextual_parent.clone()) .new_span( child .clone() - .with_contextual_parent(Some("contextual_parent")) - .with_explicit_parent(Some("explicit_parent")), + .with_ancestry(expect::has_explicit_parent("explicit_parent")), ) .enter(child.clone()) .exit(child) diff --git a/tracing-futures/tests/std_future.rs b/tracing-futures/tests/std_future.rs index 050d7491c3..4e1b597fb3 100644 --- a/tracing-futures/tests/std_future.rs +++ b/tracing-futures/tests/std_future.rs @@ -69,13 +69,21 @@ fn span_on_drop() { let subscriber = subscriber::mock() .enter(expect::span().named("foo")) - .event(expect::event().at_level(Level::INFO)) + .event( + expect::event() + .with_ancestry(expect::has_contextual_parent("foo")) + .at_level(Level::INFO), + ) .exit(expect::span().named("foo")) .enter(expect::span().named("foo")) .exit(expect::span().named("foo")) .drop_span(expect::span().named("foo")) .enter(expect::span().named("bar")) - .event(expect::event().at_level(Level::INFO)) + .event( + expect::event() + .with_ancestry(expect::has_contextual_parent("bar")) + .at_level(Level::INFO), + ) .exit(expect::span().named("bar")) .drop_span(expect::span().named("bar")) .only() diff --git a/tracing-mock/src/ancestry.rs b/tracing-mock/src/ancestry.rs new file mode 100644 index 0000000000..ee661d45e8 --- /dev/null +++ b/tracing-mock/src/ancestry.rs @@ -0,0 +1,148 @@ +//! Define the ancestry of an event or span. +//! +//! See the documentation on the [`Ancestry`] enum for further details. + +use tracing_core::{ + span::{self, Attributes}, + Event, +}; + +/// The ancestry of an event or span. +/// +/// An event or span can have an explicitly assigned parent, or be an explicit root. Otherwise, +/// an event or span may have a contextually assigned parent or in the final case will be a +/// contextual root. +#[derive(Debug, Eq, PartialEq)] +pub enum Ancestry { + /// The event or span has an explicitly assigned parent (created with `parent: span_id`) with + /// the specified name. + HasExplicitParent(String), + /// The event or span is an explicitly defined root. It was created with `parent: None` and + /// has no parent. + IsExplicitRoot, + /// The event or span has a contextually assigned parent with the specified name. It has no + /// explicitly assigned parent, nor has it been explicitly defined as a root (it was created + /// without the `parent:` directive). There was a span in context when this event or span was + /// created. + HasContextualParent(String), + /// The event or span is a contextual root. It has no explicitly assigned parent, nor has it + /// been explicitly defined as a root (it was created without the `parent:` directive). + /// Additionally, no span was in context when this event or span was created. + IsContextualRoot, +} + +impl Ancestry { + #[track_caller] + pub(crate) fn check( + &self, + actual_ancestry: &Ancestry, + ctx: impl std::fmt::Display, + collector_name: &str, + ) { + let expected_description = |ancestry: &Ancestry| match ancestry { + Self::IsExplicitRoot => "be an explicit root".to_string(), + Self::HasExplicitParent(name) => format!("have an explicit parent with name='{name}'"), + Self::IsContextualRoot => "be a contextual root".to_string(), + Self::HasContextualParent(name) => { + format!("have a contextual parent with name='{name}'") + } + }; + + let actual_description = |ancestry: &Ancestry| match ancestry { + Self::IsExplicitRoot => "was actually an explicit root".to_string(), + Self::HasExplicitParent(name) => { + format!("actually has an explicit parent with name='{name}'") + } + Self::IsContextualRoot => "was actually a contextual root".to_string(), + Self::HasContextualParent(name) => { + format!("actually has a contextual parent with name='{name}'") + } + }; + + assert_eq!( + self, + actual_ancestry, + "[{collector_name}] expected {ctx} to {expected_description}, but {actual_description}", + expected_description = expected_description(self), + actual_description = actual_description(actual_ancestry) + ); + } +} + +pub(crate) trait HasAncestry { + fn is_contextual(&self) -> bool; + + fn is_root(&self) -> bool; + + fn parent(&self) -> Option<&span::Id>; +} + +impl HasAncestry for &Event<'_> { + fn is_contextual(&self) -> bool { + (self as &Event<'_>).is_contextual() + } + + fn is_root(&self) -> bool { + (self as &Event<'_>).is_root() + } + + fn parent(&self) -> Option<&span::Id> { + (self as &Event<'_>).parent() + } +} + +impl HasAncestry for &Attributes<'_> { + fn is_contextual(&self) -> bool { + (self as &Attributes<'_>).is_contextual() + } + + fn is_root(&self) -> bool { + (self as &Attributes<'_>).is_root() + } + + fn parent(&self) -> Option<&span::Id> { + (self as &Attributes<'_>).parent() + } +} + +/// Determines the ancestry of an actual span or event. +/// +/// The rules for determining the ancestry are as follows: +/// +/// +------------+--------------+-----------------+---------------------+ +/// | Contextual | Current Span | Explicit Parent | Ancestry | +/// +------------+--------------+-----------------+---------------------+ +/// | Yes | Yes | - | HasContextualParent | +/// | Yes | No | - | IsContextualRoot | +/// | No | - | Yes | HasExplicitParent | +/// | No | - | No | IsExplicitRoot | +/// +------------+--------------+-----------------+---------------------+ +pub(crate) fn get_ancestry( + item: impl HasAncestry, + lookup_current: impl FnOnce() -> Option, + span_name: impl FnOnce(&span::Id) -> Option<&str>, +) -> Ancestry { + if item.is_contextual() { + if let Some(parent_id) = lookup_current() { + let contextual_parent_name = span_name(&parent_id).expect( + "tracing-mock: contextual parent cannot \ + be looked up by ID. Was it recorded correctly?", + ); + Ancestry::HasContextualParent(contextual_parent_name.to_string()) + } else { + Ancestry::IsContextualRoot + } + } else if item.is_root() { + Ancestry::IsExplicitRoot + } else { + let parent_id = item.parent().expect( + "tracing-mock: is_contextual=false is_root=false \ + but no explicit parent found. This is a bug!", + ); + let explicit_parent_name = span_name(parent_id).expect( + "tracing-mock: explicit parent cannot be looked \ + up by ID. Is the provided Span ID valid: {parent_id}", + ); + Ancestry::HasExplicitParent(explicit_parent_name.to_string()) + } +} diff --git a/tracing-mock/src/event.rs b/tracing-mock/src/event.rs index 79d99d49dc..b122d553e7 100644 --- a/tracing-mock/src/event.rs +++ b/tracing-mock/src/event.rs @@ -29,7 +29,7 @@ //! [`subscriber`]: mod@crate::subscriber //! [`expect::event`]: fn@crate::expect::event #![allow(missing_docs)] -use super::{expect, field, metadata::ExpectedMetadata, span, Parent}; +use crate::{ancestry::Ancestry, expect, field, metadata::ExpectedMetadata, span}; use std::fmt; @@ -42,7 +42,7 @@ use std::fmt; #[derive(Default, Eq, PartialEq)] pub struct ExpectedEvent { pub(super) fields: Option, - pub(super) parent: Option, + pub(super) ancestry: Option, pub(super) in_spans: Option>, pub(super) metadata: ExpectedMetadata, } @@ -253,32 +253,30 @@ impl ExpectedEvent { } } - /// Configures this `ExpectedEvent` to expect an explicit parent span - /// when matching events or to be an explicit root. + /// Configures this `ExpectedEvent` to expect the specified [`Ancestry`]. + /// An event's ancestry indicates whether is has a parent or is a root, and + /// whether the parent is explicitly or contextually assigned. /// - /// An _explicit_ parent span is one passed to the `span!` macro in the - /// `parent:` field. + /// An _explicit_ parent span is one passed to the `event!` macro in the + /// `parent:` field. If no `parent:` field is specified, then the event + /// will have a contextually determined parent or be a contextual root if + /// there is no parent. /// - /// If `Some("parent_name")` is passed to `with_explicit_parent` then - /// the provided string is the name of the parent span to expect. - /// - /// To expect that an event is recorded with `parent: None`, `None` - /// can be passed to `with_explicit_parent` instead. - /// - /// If an event is recorded without an explicit parent, or if the - /// explicit parent has a different name, this expectation will - /// fail. + /// If the parent is different from the provided one, this expectation + /// will fail. /// /// # Examples /// - /// The explicit parent is matched by name: + /// If `expect::has_explicit_parent("parent_name")` is passed + /// `with_ancestry` then the provided string is the name of the explicit + /// parent span to expect. /// /// ``` /// use tracing::subscriber::with_default; /// use tracing_mock::{subscriber, expect}; /// /// let event = expect::event() - /// .with_explicit_parent(Some("parent_span")); + /// .with_ancestry(expect::has_explicit_parent("parent_span")); /// /// let (subscriber, handle) = subscriber::mock() /// .event(event) @@ -300,29 +298,7 @@ impl ExpectedEvent { /// use tracing_mock::{subscriber, expect}; /// /// let event = expect::event() - /// .with_explicit_parent(None); - /// - /// let (subscriber, handle) = subscriber::mock() - /// .event(event) - /// .run_with_handle(); - /// - /// with_default(subscriber, || { - /// tracing::info!(parent: None, field = &"value"); - /// }); - /// - /// handle.assert_finished(); - /// ``` - /// - /// In the example below, the expectation fails because the - /// event is contextually (rather than explicitly) within the span - /// `parent_span`: - /// - /// ```should_panic - /// use tracing::subscriber::with_default; - /// use tracing_mock::{subscriber, expect}; - /// - /// let event = expect::event() - /// .with_explicit_parent(Some("parent_span")); + /// .with_ancestry(expect::is_explicit_root()); /// /// let (subscriber, handle) = subscriber::mock() /// .enter(expect::span()) @@ -330,48 +306,23 @@ impl ExpectedEvent { /// .run_with_handle(); /// /// with_default(subscriber, || { - /// let parent = tracing::info_span!("parent_span"); - /// let _guard = parent.enter(); - /// tracing::info!(field = &"value"); + /// let _guard = tracing::info_span!("contextual parent").entered(); + /// tracing::info!(parent: None, field = &"value"); /// }); /// /// handle.assert_finished(); /// ``` - pub fn with_explicit_parent(self, parent: Option<&str>) -> ExpectedEvent { - let parent = match parent { - Some(name) => Parent::Explicit(name.into()), - None => Parent::ExplicitRoot, - }; - Self { - parent: Some(parent), - ..self - } - } - - /// Configures this `ExpectedEvent` to match an event with a - /// contextually-determined parent span. - /// - /// The provided string is the name of the parent span to expect. - /// To expect that the event is a contextually-determined root, pass - /// `None` instead. - /// - /// To expect an event with an explicit parent span, use - /// [`ExpectedEvent::with_explicit_parent`]. - /// - /// If an event is recorded which is not inside a span, has an explicitly - /// overridden parent span, or with a differently-named span as its - /// parent, this expectation will fail. - /// - /// # Examples /// - /// The contextual parent is matched by name: + /// When `expect::has_contextual_parent("parent_name")` is passed to + /// `with_ancestry` then the provided string is the name of the contextual + /// parent span to expect. /// /// ``` /// use tracing::subscriber::with_default; /// use tracing_mock::{subscriber, expect}; /// /// let event = expect::event() - /// .with_contextual_parent(Some("parent_span")); + /// .with_ancestry(expect::has_contextual_parent("parent_span")); /// /// let (subscriber, handle) = subscriber::mock() /// .enter(expect::span()) @@ -387,14 +338,15 @@ impl ExpectedEvent { /// handle.assert_finished(); /// ``` /// - /// Matching an event recorded outside of a span: + /// Matching an event recorded outside of a span, a contextual + /// root: /// /// ``` /// use tracing::subscriber::with_default; /// use tracing_mock::{subscriber, expect}; /// /// let event = expect::event() - /// .with_contextual_parent(None); + /// .with_ancestry(expect::is_contextual_root()); /// /// let (subscriber, handle) = subscriber::mock() /// .event(event) @@ -407,15 +359,16 @@ impl ExpectedEvent { /// handle.assert_finished(); /// ``` /// - /// In the example below, the expectation fails because the - /// event is recorded with an explicit parent: + /// In the example below, the expectation fails because the event is + /// recorded with an explicit parent, however a contextual parent is + /// expected. /// /// ```should_panic /// use tracing::subscriber::with_default; /// use tracing_mock::{subscriber, expect}; /// /// let event = expect::event() - /// .with_contextual_parent(Some("parent_span")); + /// .with_ancestry(expect::has_contextual_parent("parent_span")); /// /// let (subscriber, handle) = subscriber::mock() /// .enter(expect::span()) @@ -429,13 +382,9 @@ impl ExpectedEvent { /// /// handle.assert_finished(); /// ``` - pub fn with_contextual_parent(self, parent: Option<&str>) -> ExpectedEvent { - let parent = match parent { - Some(name) => Parent::Contextual(name.into()), - None => Parent::ContextualRoot, - }; + pub fn with_ancestry(self, ancenstry: Ancestry) -> ExpectedEvent { Self { - parent: Some(parent), + ancestry: Some(ancenstry), ..self } } @@ -557,7 +506,7 @@ impl ExpectedEvent { pub(crate) fn check( &mut self, event: &tracing::Event<'_>, - get_parent_name: impl FnOnce() -> Option, + get_ancestry: impl FnOnce() -> Ancestry, subscriber_name: &str, ) { let meta = event.metadata(); @@ -577,14 +526,9 @@ impl ExpectedEvent { checker.finish(); } - if let Some(ref expected_parent) = self.parent { - let actual_parent = get_parent_name(); - expected_parent.check_parent_name( - actual_parent.as_deref(), - event.parent().cloned(), - event.metadata().name(), - subscriber_name, - ) + if let Some(ref expected_ancestry) = self.ancestry { + let actual_ancestry = get_ancestry(); + expected_ancestry.check(&actual_ancestry, event.metadata().name(), subscriber_name); } } } @@ -615,7 +559,7 @@ impl fmt::Debug for ExpectedEvent { s.field("fields", fields); } - if let Some(ref parent) = self.parent { + if let Some(ref parent) = self.ancestry { s.field("parent", &format_args!("{:?}", parent)); } diff --git a/tracing-mock/src/expect.rs b/tracing-mock/src/expect.rs index 353bc52f5f..95ad3176ca 100644 --- a/tracing-mock/src/expect.rs +++ b/tracing-mock/src/expect.rs @@ -1,9 +1,10 @@ use std::fmt; use crate::{ + ancestry::Ancestry, event::ExpectedEvent, field::{ExpectedField, ExpectedFields, ExpectedValue}, - span::{ExpectedSpan, NewSpan}, + span::{ExpectedId, ExpectedSpan, NewSpan}, }; #[derive(Debug, Eq, PartialEq)] @@ -51,6 +52,47 @@ pub fn span() -> ExpectedSpan { } } +/// Returns a new, unset `ExpectedId`. +/// +/// The `ExpectedId` needs to be attached to a [`NewSpan`] or an +/// [`ExpectedSpan`] passed to [`MockCollector::new_span`] to +/// ensure that it gets set. When the a clone of the same +/// `ExpectedSpan` is attached to an [`ExpectedSpan`] and passed to +/// any other method on [`MockCollector`] that accepts it, it will +/// ensure that it is exactly the same span used across those +/// distinct expectations. +/// +/// For more details on how to use this struct, see the documentation +/// on [`ExpectedSpan::with_id`]. +/// +/// [`MockCollector`]: struct@crate::collector::MockCollector +/// [`MockCollector::new_span`]: fn@crate::collector::MockCollector::new_span +pub fn id() -> ExpectedId { + ExpectedId::new_unset() +} + +/// Convenience function that returns [`Ancestry::IsContextualRoot`]. +pub fn is_contextual_root() -> Ancestry { + Ancestry::IsContextualRoot +} + +/// Convenience function that returns [`Ancestry::HasContextualParent`] with +/// provided name. +pub fn has_contextual_parent>(name: S) -> Ancestry { + Ancestry::HasContextualParent(name.into()) +} + +/// Convenience function that returns [`Ancestry::IsExplicitRoot`]. +pub fn is_explicit_root() -> Ancestry { + Ancestry::IsExplicitRoot +} + +/// Convenience function that returns [`Ancestry::HasExplicitParent`] with +/// provided name. +pub fn has_explicit_parent>(name: S) -> Ancestry { + Ancestry::HasExplicitParent(name.into()) +} + impl Expect { pub(crate) fn bad(&self, name: impl AsRef, what: fmt::Arguments<'_>) { let name = name.as_ref(); diff --git a/tracing-mock/src/layer.rs b/tracing-mock/src/layer.rs index 7c36b092be..19882e7f99 100644 --- a/tracing-mock/src/layer.rs +++ b/tracing-mock/src/layer.rs @@ -116,6 +116,7 @@ //! //! [`Layer`]: trait@tracing_subscriber::layer::Layer use crate::{ + ancestry::{get_ancestry, Ancestry, HasAncestry}, event::ExpectedEvent, expect::Expect, span::{ExpectedSpan, NewSpan}, @@ -904,8 +905,7 @@ where match self.expected.lock().unwrap().pop_front() { None => {} Some(Expect::Event(mut expected)) => { - let get_parent_name = || cx.event_span(event).map(|span| span.name().to_string()); - expected.check(event, get_parent_name, &self.name); + expected.check(event, || context_get_ancestry(event, &cx), &self.name); if let Some(expected_scope) = expected.scope_mut() { self.check_event_scope(cx.event_scope(event), expected_scope); @@ -936,13 +936,7 @@ where let was_expected = matches!(expected.front(), Some(Expect::NewSpan(_))); if was_expected { if let Expect::NewSpan(mut expected) = expected.pop_front().unwrap() { - let get_parent_name = || { - span.parent() - .and_then(|id| cx.span(id)) - .or_else(|| cx.lookup_current()) - .map(|span| span.name().to_string()) - }; - expected.check(span, get_parent_name, &self.name); + expected.check(span, || context_get_ancestry(span, &cx), &self.name); } } } @@ -1042,6 +1036,17 @@ where } } +fn context_get_ancestry(item: impl HasAncestry, ctx: &Context<'_, C>) -> Ancestry +where + C: Subscriber + for<'a> LookupSpan<'a>, +{ + get_ancestry( + item, + || ctx.lookup_current().map(|s| s.id()), + |span_id| ctx.span(span_id).map(|span| span.name()), + ) +} + impl fmt::Debug for MockLayer { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut s = f.debug_struct("ExpectSubscriber"); diff --git a/tracing-mock/src/lib.rs b/tracing-mock/src/lib.rs index abf29777a4..70f169eecf 100644 --- a/tracing-mock/src/lib.rs +++ b/tracing-mock/src/lib.rs @@ -1,4 +1,5 @@ #![doc = include_str!("../README.md")] +pub mod ancestry; pub mod event; pub mod expect; pub mod field; @@ -8,88 +9,3 @@ pub mod subscriber; #[cfg(feature = "tracing-subscriber")] pub mod layer; - -#[derive(Debug, Eq, PartialEq)] -pub enum Parent { - ContextualRoot, - Contextual(String), - ExplicitRoot, - Explicit(String), -} - -impl Parent { - pub fn check_parent_name( - &self, - parent_name: Option<&str>, - provided_parent: Option, - ctx: impl std::fmt::Display, - subscriber_name: &str, - ) { - match self { - Parent::ExplicitRoot => { - assert!( - provided_parent.is_none(), - "[{}] expected {} to be an explicit root, but its parent was actually {:?} (name: {:?})", - subscriber_name, - ctx, - provided_parent, - parent_name, - ); - } - Parent::Explicit(expected_parent) => { - assert!( - provided_parent.is_some(), - "[{}] expected {} to have explicit parent {}, but it has no explicit parent", - subscriber_name, - ctx, - expected_parent, - ); - assert_eq!( - Some(expected_parent.as_ref()), - parent_name, - "[{}] expected {} to have explicit parent {}, but its parent was actually {:?} (name: {:?})", - subscriber_name, - ctx, - expected_parent, - provided_parent, - parent_name, - ); - } - Parent::ContextualRoot => { - assert!( - provided_parent.is_none(), - "[{}] expected {} to be a contextual root, but its parent was actually {:?} (name: {:?})", - subscriber_name, - ctx, - provided_parent, - parent_name, - ); - assert!( - parent_name.is_none(), - "[{}] expected {} to be contextual a root, but we were inside span {:?}", - subscriber_name, - ctx, - parent_name, - ); - } - Parent::Contextual(expected_parent) => { - assert!(provided_parent.is_none(), - "[{}] expected {} to have a contextual parent\nbut it has the explicit parent {:?} (name: {:?})", - subscriber_name, - ctx, - provided_parent, - parent_name, - ); - assert_eq!( - Some(expected_parent.as_ref()), - parent_name, - "[{}] expected {} to have contextual parent {:?}, but got {:?}", - subscriber_name, - ctx, - expected_parent, - parent_name, - ); - } - } - } -} diff --git a/tracing-mock/src/span.rs b/tracing-mock/src/span.rs index 176c33a938..c12655055a 100644 --- a/tracing-mock/src/span.rs +++ b/tracing-mock/src/span.rs @@ -41,7 +41,7 @@ //! let new_span = span //! .clone() //! .with_fields(expect::field("field.name").with_value(&"field_value")) -//! .with_explicit_parent(Some("parent_span")); +//! .with_ancestry(expect::has_explicit_parent("parent_span")); //! //! let (subscriber, handle) = subscriber::mock() //! .new_span(expect::span().named("parent_span")) @@ -92,9 +92,16 @@ //! [`expect::span`]: fn@crate::expect::span #![allow(missing_docs)] use crate::{ - expect, field::ExpectedFields, metadata::ExpectedMetadata, subscriber::SpanState, Parent, + ancestry::Ancestry, expect, field::ExpectedFields, metadata::ExpectedMetadata, + subscriber::SpanState, +}; +use std::{ + error, fmt, + sync::{ + atomic::{AtomicU64, Ordering}, + Arc, + }, }; -use std::fmt; /// A mock span. /// @@ -104,6 +111,7 @@ use std::fmt; /// [`subscriber`]: mod@crate::subscriber #[derive(Clone, Default, Eq, PartialEq)] pub struct ExpectedSpan { + pub(crate) id: Option, pub(crate) metadata: ExpectedMetadata, } @@ -127,7 +135,7 @@ pub struct ExpectedSpan { pub struct NewSpan { pub(crate) span: ExpectedSpan, pub(crate) fields: ExpectedFields, - pub(crate) parent: Option, + pub(crate) ancestry: Option, } pub fn named(name: I) -> ExpectedSpan @@ -137,6 +145,24 @@ where expect::span().named(name) } +/// A mock span ID. +/// +/// This ID makes it possible to link together calls to different +/// [`MockSubscriber`] span methods that take an [`ExpectedSpan`] in +/// addition to those that take a [`NewSpan`]. +/// +/// Use [`expect::id`] to construct a new, unset `ExpectedId`. +/// +/// For more details on how to use this struct, see the documentation +/// on [`ExpectedSpan::with_id`]. +/// +/// [`expect::id`]: fn@crate::expect::id +/// [`MockSubscriber`]: struct@crate::subscriber::MockSubscriber +#[derive(Clone, Default)] +pub struct ExpectedId { + inner: Arc, +} + impl ExpectedSpan { /// Sets a name to expect when matching a span. /// @@ -188,6 +214,100 @@ impl ExpectedSpan { name: Some(name.into()), ..self.metadata }, + ..self + } + } + + /// Sets the `ID` to expect when matching a span. + /// + /// The [`ExpectedId`] can be used to differentiate spans that are + /// otherwise identical. An [`ExpectedId`] needs to be attached to + /// an `ExpectedSpan` or [`NewSpan`] which is passed to + /// [`MockSubscriber::new_span`]. The same [`ExpectedId`] can then + /// be used to match the exact same span when passed to + /// [`MockSubscriber::enter`], [`MockSubscriber::exit`], and + /// [`MockSubscriber::drop_span`]. + /// + /// This is especially useful when `tracing-mock` is being used to + /// test the traces being generated within your own crate, in which + /// case you may need to distinguish between spans which have + /// identical metadata but different field values, which can + /// otherwise only be checked in [`MockSubscriber::new_span`]. + /// + /// # Examples + /// + /// Here we expect that the span that is created first is entered + /// second: + /// + /// ``` + /// use tracing_mock::{subscriber, expect}; + /// let id1 = expect::id(); + /// let span1 = expect::span().named("span").with_id(id1.clone()); + /// let id2 = expect::id(); + /// let span2 = expect::span().named("span").with_id(id2.clone()); + /// + /// let (subscriber, handle) = subscriber::mock() + /// .new_span(span1.clone()) + /// .new_span(span2.clone()) + /// .enter(span2) + /// .enter(span1) + /// .run_with_handle(); + /// + /// tracing::subscriber::with_default(subscriber, || { + /// fn create_span() -> tracing::Span { + /// tracing::info_span!("span") + /// } + /// + /// let span1 = create_span(); + /// let span2 = create_span(); + /// + /// let _guard2 = span2.enter(); + /// let _guard1 = span1.enter(); + /// }); + /// + /// handle.assert_finished(); + /// ``` + /// + /// If the order that the spans are entered changes, the test will + /// fail: + /// + /// ```should_panic + /// use tracing_mock::{subscriber, expect}; + /// let id1 = expect::id(); + /// let span1 = expect::span().named("span").with_id(id1.clone()); + /// let id2 = expect::id(); + /// let span2 = expect::span().named("span").with_id(id2.clone()); + /// + /// let (subscriber, handle) = subscriber::mock() + /// .new_span(span1.clone()) + /// .new_span(span2.clone()) + /// .enter(span2) + /// .enter(span1) + /// .run_with_handle(); + /// + /// tracing::subscriber::with_default(subscriber, || { + /// fn create_span() -> tracing::Span { + /// tracing::info_span!("span") + /// } + /// + /// let span1 = create_span(); + /// let span2 = create_span(); + /// + /// let _guard1 = span1.enter(); + /// let _guard2 = span2.enter(); + /// }); + /// + /// handle.assert_finished(); + /// ``` + /// + /// [`MockSubscriber::new_span`]: fn@crate::subscriber::MockSubscriber::new_span + /// [`MockSubscriber::enter`]: fn@crate::subscriber::MockSubscriber::enter + /// [`MockSubscriber::exit`]: fn@crate::subscriber::MockSubscriber::exit + /// [`MockSubscriber::drop_span`]: fn@crate::subscriber::MockSubscriber::drop_span + pub fn with_id(self, id: ExpectedId) -> Self { + Self { + id: Some(id), + ..self } } @@ -241,6 +361,7 @@ impl ExpectedSpan { level: Some(level), ..self.metadata }, + ..self } } @@ -297,11 +418,13 @@ impl ExpectedSpan { target: Some(target.into()), ..self.metadata }, + ..self } } - /// Configures this `ExpectedSpan` to expect an explicit parent - /// span or to be an explicit root. + /// Configures this `ExpectedSpan` to expect the specified [`Ancestry`]. A + /// span's ancestry indicates whether it has a parent or is a root span + /// and whether the parent is explitly or contextually assigned. /// /// **Note**: This method returns a [`NewSpan`] and as such, this /// expectation can only be validated when expecting a new span via @@ -310,27 +433,24 @@ impl ExpectedSpan { /// method on [`MockSubscriber`] that takes an `ExpectedSpan`. /// /// An _explicit_ parent span is one passed to the `span!` macro in the - /// `parent:` field. - /// - /// If `Some("parent_name")` is passed to `with_explicit_parent` then, - /// the provided string is the name of the parent span to expect. + /// `parent:` field. If no `parent:` field is specified, then the span + /// will have a contextually determined parent or be a contextual root if + /// there is no parent. /// - /// To expect that a span is recorded with no parent, `None` - /// can be passed to `with_explicit_parent` instead. - /// - /// If a span is recorded without an explicit parent, or if the - /// explicit parent has a different name, this expectation will - /// fail. + /// If the ancestry is different from the provided one, this expectation + /// will fail. /// /// # Examples /// - /// The explicit parent is matched by name: + /// If `expect::has_explicit_parent("parent_name")` is passed + /// `with_ancestry` then the provided string is the name of the explicit + /// parent span to expect. /// /// ``` /// use tracing_mock::{subscriber, expect}; /// /// let span = expect::span() - /// .with_explicit_parent(Some("parent_span")); + /// .with_ancestry(expect::has_explicit_parent("parent_span")); /// /// let (subscriber, handle) = subscriber::mock() /// .new_span(expect::span().named("parent_span")) @@ -351,7 +471,7 @@ impl ExpectedSpan { /// use tracing_mock::{subscriber, expect}; /// /// let span = expect::span() - /// .with_explicit_parent(None); + /// .with_ancestry(expect::is_explicit_root()); /// /// let (subscriber, handle) = subscriber::mock() /// .new_span(span) @@ -373,7 +493,7 @@ impl ExpectedSpan { /// /// let parent_span = expect::span().named("parent_span"); /// let span = expect::span() - /// .with_explicit_parent(Some("parent_span")); + /// .with_ancestry(expect::has_explicit_parent("parent_span")); /// /// let (subscriber, handle) = subscriber::mock() /// .new_span(parent_span.clone()) @@ -390,53 +510,15 @@ impl ExpectedSpan { /// handle.assert_finished(); /// ``` /// - /// [`MockSubscriber`]: struct@crate::subscriber::MockSubscriber - /// [`MockSubscriber::enter`]: fn@crate::subscriber::MockSubscriber::enter - /// [`MockSubscriber::exit`]: fn@crate::subscriber::MockSubscriber::exit - /// [`MockSubscriber::new_span`]: fn@crate::subscriber::MockSubscriber::new_span - pub fn with_explicit_parent(self, parent: Option<&str>) -> NewSpan { - let parent = match parent { - Some(name) => Parent::Explicit(name.into()), - None => Parent::ExplicitRoot, - }; - NewSpan { - parent: Some(parent), - span: self, - ..Default::default() - } - } - - /// Configures this `ExpectedSpan` to expect a - /// contextually-determined parent span, or be a contextual - /// root. - /// - /// **Note**: This method returns a [`NewSpan`] and as such, this - /// expectation can only be validated when expecting a new span via - /// [`MockSubscriber::new_span`]. It cannot be validated on - /// [`MockSubscriber::enter`], [`MockSubscriber::exit`], or any other - /// method on [`MockSubscriber`] that takes an `ExpectedSpan`. - /// - /// The provided string is the name of the parent span to expect. - /// To expect that the event is a contextually-determined root, pass - /// `None` instead. - /// - /// To expect a span with an explicit parent span, use - /// [`ExpectedSpan::with_explicit_parent`]. - /// - /// If a span is recorded which is not inside a span, has an explicitly - /// overridden parent span, or has a differently-named span as its - /// parent, this expectation will fail. - /// - /// # Examples - /// - /// The contextual parent is matched by name: + /// In the following example, we expect that the matched span is + /// a contextually-determined root: /// /// ``` /// use tracing_mock::{subscriber, expect}; /// /// let parent_span = expect::span().named("parent_span"); /// let span = expect::span() - /// .with_contextual_parent(Some("parent_span")); + /// .with_ancestry(expect::has_contextual_parent("parent_span")); /// /// let (subscriber, handle) = subscriber::mock() /// .new_span(parent_span.clone()) @@ -460,7 +542,7 @@ impl ExpectedSpan { /// use tracing_mock::{subscriber, expect}; /// /// let span = expect::span() - /// .with_contextual_parent(None); + /// .with_ancestry(expect::is_contextual_root()); /// /// let (subscriber, handle) = subscriber::mock() /// .new_span(span) @@ -474,22 +556,26 @@ impl ExpectedSpan { /// ``` /// /// In the example below, the expectation fails because the - /// span is recorded with an explicit parent: + /// span is *contextually*—as opposed to explicitly—within the span + /// `parent_span`: /// /// ```should_panic /// use tracing_mock::{subscriber, expect}; /// + /// let parent_span = expect::span().named("parent_span"); /// let span = expect::span() - /// .with_contextual_parent(Some("parent_span")); + /// .with_ancestry(expect::has_explicit_parent("parent_span")); /// /// let (subscriber, handle) = subscriber::mock() - /// .new_span(expect::span().named("parent_span")) + /// .new_span(parent_span.clone()) + /// .enter(parent_span) /// .new_span(span) /// .run_with_handle(); /// /// tracing::subscriber::with_default(subscriber, || { /// let parent = tracing::info_span!("parent_span"); - /// tracing::info_span!(parent: parent.id(), "span"); + /// let _guard = parent.enter(); + /// tracing::info_span!("span"); /// }); /// /// handle.assert_finished(); @@ -499,13 +585,9 @@ impl ExpectedSpan { /// [`MockSubscriber::enter`]: fn@crate::subscriber::MockSubscriber::enter /// [`MockSubscriber::exit`]: fn@crate::subscriber::MockSubscriber::exit /// [`MockSubscriber::new_span`]: fn@crate::subscriber::MockSubscriber::new_span - pub fn with_contextual_parent(self, parent: Option<&str>) -> NewSpan { - let parent = match parent { - Some(name) => Parent::Contextual(name.into()), - None => Parent::ContextualRoot, - }; + pub fn with_ancestry(self, ancestry: Ancestry) -> NewSpan { NewSpan { - parent: Some(parent), + ancestry: Some(ancestry), span: self, ..Default::default() } @@ -598,6 +680,15 @@ impl ExpectedSpan { pub(crate) fn check(&self, actual: &SpanState, subscriber_name: &str) { let meta = actual.metadata(); let name = meta.name(); + + if let Some(expected_id) = &self.id { + expected_id.check( + actual.id(), + format_args!("span `{}`", name), + subscriber_name, + ); + } + self.metadata .check(meta, format_args!("span `{}`", name), subscriber_name); } @@ -643,39 +734,15 @@ impl From 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()), - None => Parent::ExplicitRoot, - }; - NewSpan { - parent: Some(parent), - ..self - } - } - - /// Configures this `NewSpan` to expect a - /// contextually-determined parent span, or to be a contextual - /// root. + /// Configures this `NewSpan` to expect the specified [`Ancestry`]. A + /// span's ancestry indicates whether it has a parent or is a root span + /// and whether the parent is explitly or contextually assigned. /// /// For more information and examples, see the documentation on - /// [`ExpectedSpan::with_contextual_parent`]. - /// - /// [`ExpectedSpan::with_contextual_parent`]: fn@crate::span::ExpectedSpan::with_contextual_parent - pub fn with_contextual_parent(self, parent: Option<&str>) -> NewSpan { - let parent = match parent { - Some(name) => Parent::Contextual(name.into()), - None => Parent::ContextualRoot, - }; + /// [`ExpectedSpan::with_ancestry`]. + pub fn with_ancestry(self, ancestry: Ancestry) -> NewSpan { NewSpan { - parent: Some(parent), + ancestry: Some(ancestry), ..self } } @@ -699,7 +766,7 @@ impl NewSpan { pub(crate) fn check( &mut self, span: &tracing_core::span::Attributes<'_>, - get_parent_name: impl FnOnce() -> Option, + get_ancestry: impl FnOnce() -> Ancestry, subscriber_name: &str, ) { let meta = span.metadata(); @@ -711,14 +778,13 @@ impl NewSpan { span.record(&mut checker); checker.finish(); - if let Some(expected_parent) = self.parent.as_ref() { - let actual_parent = get_parent_name(); - expected_parent.check_parent_name( - actual_parent.as_deref(), - span.parent().cloned(), + if let Some(ref expected_ancestry) = self.ancestry { + let actual_ancestry = get_ancestry(); + expected_ancestry.check( + &actual_ancestry, format_args!("span `{}`", name), subscriber_name, - ) + ); } } } @@ -749,7 +815,7 @@ impl fmt::Debug for NewSpan { s.field("target", &target); } - if let Some(ref parent) = self.parent { + if let Some(ref parent) = self.ancestry { s.field("parent", &format_args!("{:?}", parent)); } @@ -760,3 +826,69 @@ impl fmt::Debug for NewSpan { s.finish() } } + +impl PartialEq for ExpectedId { + fn eq(&self, other: &Self) -> bool { + self.inner.load(Ordering::Relaxed) == other.inner.load(Ordering::Relaxed) + } +} + +impl Eq for ExpectedId {} + +impl ExpectedId { + const UNSET: u64 = 0; + + pub(crate) fn new_unset() -> Self { + Self { + inner: Arc::new(AtomicU64::from(Self::UNSET)), + } + } + + pub(crate) fn set(&self, span_id: u64) -> Result<(), SetActualSpanIdError> { + self.inner + .compare_exchange(Self::UNSET, span_id, Ordering::Relaxed, Ordering::Relaxed) + .map_err(|current| SetActualSpanIdError { + previous_span_id: current, + new_span_id: span_id, + })?; + Ok(()) + } + + pub(crate) fn check(&self, actual: u64, ctx: fmt::Arguments<'_>, subscriber_name: &str) { + let id = self.inner.load(Ordering::Relaxed); + + assert!( + id != Self::UNSET, + "\n[{}] expected {} to have expected ID set, but it hasn't been, \ + perhaps this `ExpectedId` wasn't used in a call to `MockSubscriber::new_span()`?", + subscriber_name, + ctx, + ); + + assert_eq!( + id, actual, + "\n[{}] expected {} to have ID `{}`, but it has `{}` instead", + subscriber_name, ctx, id, actual, + ); + } +} + +#[derive(Debug)] +pub(crate) struct SetActualSpanIdError { + previous_span_id: u64, + new_span_id: u64, +} + +impl fmt::Display for SetActualSpanIdError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "Could not set `ExpecedId` to {new}, \ + it had already been set to {previous}", + new = self.new_span_id, + previous = self.previous_span_id + ) + } +} + +impl error::Error for SetActualSpanIdError {} diff --git a/tracing-mock/src/subscriber.rs b/tracing-mock/src/subscriber.rs index 1d5b36bd80..924be26840 100644 --- a/tracing-mock/src/subscriber.rs +++ b/tracing-mock/src/subscriber.rs @@ -138,6 +138,7 @@ //! [`Subscriber`]: trait@tracing::Subscriber //! [`MockSubscriber`]: struct@crate::subscriber::MockSubscriber use crate::{ + ancestry::get_ancestry, event::ExpectedEvent, expect::Expect, field::ExpectedFields, @@ -159,12 +160,17 @@ use tracing::{ }; pub(crate) struct SpanState { + id: u64, name: &'static str, refs: usize, meta: &'static Metadata<'static>, } impl SpanState { + pub(crate) fn id(&self) -> u64 { + self.id + } + pub(crate) fn metadata(&self) -> &'static Metadata<'static> { self.meta } @@ -387,7 +393,7 @@ where /// This function accepts `Into` instead of /// [`ExpectedSpan`] directly, so it can be used to test /// span fields and the span parent. This is because a - /// subscriber only receives the span fields and parent when + /// collector only receives the span fields and parent when /// a span is created, not when it is entered. /// /// The new span doesn't need to be entered for this expectation @@ -1030,20 +1036,24 @@ where { if expected.scope_mut().is_some() { unimplemented!( - "Expected scope for events is not supported with `MockSubscriber`." + "Expected scope for events is not supported with `MockCollector`." ) } } - let get_parent_name = || { - let stack = self.current.lock().unwrap(); - let spans = self.spans.lock().unwrap(); - event - .parent() - .and_then(|id| spans.get(id)) - .or_else(|| stack.last().and_then(|id| spans.get(id))) - .map(|s| s.name.to_string()) + let event_get_ancestry = || { + get_ancestry( + event, + || self.lookup_current(), + |span_id| { + self.spans + .lock() + .unwrap() + .get(span_id) + .map(|span| span.name) + }, + ) }; - expected.check(event, get_parent_name, &self.name); + expected.check(event, event_get_ancestry, &self.name); } Some(ex) => ex.bad(&self.name, format_args!("observed event {:#?}", event)), } @@ -1100,19 +1110,27 @@ where let mut spans = self.spans.lock().unwrap(); if was_expected { if let Expect::NewSpan(mut expected) = expected.pop_front().unwrap() { - let get_parent_name = || { - let stack = self.current.lock().unwrap(); - span.parent() - .and_then(|id| spans.get(id)) - .or_else(|| stack.last().and_then(|id| spans.get(id))) - .map(|s| s.name.to_string()) - }; - expected.check(span, get_parent_name, &self.name); + if let Some(expected_id) = &expected.span.id { + expected_id.set(id.into_u64()).unwrap(); + } + + expected.check( + span, + || { + get_ancestry( + span, + || self.lookup_current(), + |span_id| spans.get(span_id).map(|span| span.name), + ) + }, + &self.name, + ); } } spans.insert( id.clone(), SpanState { + id: id.into_u64(), name: meta.name(), refs: 1, meta, @@ -1256,6 +1274,16 @@ where } } +impl Running +where + F: Fn(&Metadata<'_>) -> bool, +{ + fn lookup_current(&self) -> Option { + let stack = self.current.lock().unwrap(); + stack.last().cloned() + } +} + impl MockHandle { #[cfg(feature = "tracing-subscriber")] pub(crate) fn new(expected: Arc>>, name: String) -> Self { diff --git a/tracing-mock/tests/event_ancestry.rs b/tracing-mock/tests/event_ancestry.rs new file mode 100644 index 0000000000..ff659615a6 --- /dev/null +++ b/tracing-mock/tests/event_ancestry.rs @@ -0,0 +1,346 @@ +//! Tests assertions for the parent made on [`ExpectedEvent`]. +//! +//! The tests in this module completely cover the positive and negative cases +//! when expecting that an event is a contextual or explicit root or expecting +//! that an event has a specific contextual or explicit parent. +//! +//! [`ExpectedEvent`]: crate::event::ExpectedEvent +use tracing::subscriber::with_default; +use tracing_mock::{expect, subscriber}; + +#[test] +fn contextual_parent() { + let event = expect::event().with_ancestry(expect::has_contextual_parent("contextual parent")); + + let (subscriber, handle) = subscriber::mock() + .enter(expect::span()) + .event(event) + .run_with_handle(); + + with_default(subscriber, || { + let _guard = tracing::info_span!("contextual parent").entered(); + tracing::info!(field = &"value"); + }); + + handle.assert_finished(); +} + +#[test] +#[should_panic( + expected = "to have a contextual parent with name='contextual parent', but \ + actually has a contextual parent with name='another parent'" +)] +fn contextual_parent_wrong_name() { + let event = expect::event().with_ancestry(expect::has_contextual_parent("contextual parent")); + + let (subscriber, handle) = subscriber::mock() + .enter(expect::span()) + .event(event) + .run_with_handle(); + + with_default(subscriber, || { + let _guard = tracing::info_span!("another parent").entered(); + tracing::info!(field = &"value"); + }); + + handle.assert_finished(); +} + +#[test] +#[should_panic( + expected = "to have a contextual parent with name='contextual parent', but was actually a \ + contextual root" +)] +fn expect_contextual_parent_actual_contextual_root() { + let event = expect::event().with_ancestry(expect::has_contextual_parent("contextual parent")); + + let (subscriber, handle) = subscriber::mock().event(event).run_with_handle(); + + with_default(subscriber, || { + tracing::info!(field = &"value"); + }); + + handle.assert_finished(); +} + +#[test] +#[should_panic( + expected = "to have a contextual parent with name='contextual parent', but actually has an \ + explicit parent with name='explicit parent'" +)] +fn expect_contextual_parent_actual_explicit_parent() { + let event = expect::event().with_ancestry(expect::has_contextual_parent("contextual parent")); + + let (subscriber, handle) = subscriber::mock().event(event).run_with_handle(); + + with_default(subscriber, || { + let span = tracing::info_span!("explicit parent"); + tracing::info!(parent: span.id(), field = &"value"); + }); + + handle.assert_finished(); +} + +#[test] +#[should_panic( + expected = "to have a contextual parent with name='contextual parent', but was actually an \ + explicit root" +)] +fn expect_contextual_parent_actual_explicit_root() { + let event = expect::event().with_ancestry(expect::has_contextual_parent("contextual parent")); + + let (subscriber, handle) = subscriber::mock() + .enter(expect::span()) + .event(event) + .run_with_handle(); + + with_default(subscriber, || { + let _guard = tracing::info_span!("contextual parent").entered(); + tracing::info!(parent: None, field = &"value"); + }); + + handle.assert_finished(); +} + +#[test] +fn contextual_root() { + let event = expect::event().with_ancestry(expect::is_contextual_root()); + + let (subscriber, handle) = subscriber::mock().event(event).run_with_handle(); + + with_default(subscriber, || { + tracing::info!(field = &"value"); + }); + + handle.assert_finished(); +} + +#[test] +#[should_panic( + expected = "to be a contextual root, but actually has a contextual parent with \ + name='contextual parent'" +)] +fn expect_contextual_root_actual_contextual_parent() { + let event = expect::event().with_ancestry(expect::is_contextual_root()); + + let (subscriber, handle) = subscriber::mock() + .enter(expect::span()) + .event(event) + .run_with_handle(); + + with_default(subscriber, || { + let _guard = tracing::info_span!("contextual parent").entered(); + tracing::info!(field = &"value"); + }); + + handle.assert_finished(); +} + +#[test] +#[should_panic( + expected = "to be a contextual root, but actually has an explicit parent with \ + name='explicit parent'" +)] +fn expect_contextual_root_actual_explicit_parent() { + let event = expect::event().with_ancestry(expect::is_contextual_root()); + + let (subscriber, handle) = subscriber::mock().event(event).run_with_handle(); + + with_default(subscriber, || { + let span = tracing::info_span!("explicit parent"); + tracing::info!(parent: span.id(), field = &"value"); + }); + + handle.assert_finished(); +} + +#[test] +#[should_panic(expected = "to be a contextual root, but was actually an explicit root")] +fn expect_contextual_root_actual_explicit_root() { + let event = expect::event().with_ancestry(expect::is_contextual_root()); + + let (subscriber, handle) = subscriber::mock() + .enter(expect::span()) + .event(event) + .run_with_handle(); + + with_default(subscriber, || { + let _guard = tracing::info_span!("contextual parent").entered(); + tracing::info!(parent: None, field = &"value"); + }); + + handle.assert_finished(); +} + +#[test] +fn explicit_parent() { + let event = expect::event().with_ancestry(expect::has_explicit_parent("explicit parent")); + + let (subscriber, handle) = subscriber::mock().event(event).run_with_handle(); + + with_default(subscriber, || { + let span = tracing::info_span!("explicit parent"); + tracing::info!(parent: span.id(), field = &"value"); + }); + + handle.assert_finished(); +} + +#[test] +#[should_panic( + expected = "to have an explicit parent with name='explicit parent', but actually has an \ + explicit parent with name='another parent'" +)] +fn explicit_parent_wrong_name() { + let event = expect::event().with_ancestry(expect::has_explicit_parent("explicit parent")); + + let (subscriber, handle) = subscriber::mock().event(event).run_with_handle(); + + with_default(subscriber, || { + let span = tracing::info_span!("another parent"); + tracing::info!(parent: span.id(), field = &"value"); + }); + + handle.assert_finished(); +} + +#[test] +#[should_panic( + expected = "to have an explicit parent with name='explicit parent', but actually has a \ + contextual parent with name='contextual parent'" +)] +fn expect_explicit_parent_actual_contextual_parent() { + let event = expect::event().with_ancestry(expect::has_explicit_parent("explicit parent")); + + let (subscriber, handle) = subscriber::mock() + .enter(expect::span()) + .event(event) + .run_with_handle(); + + with_default(subscriber, || { + let _guard = tracing::info_span!("contextual parent").entered(); + tracing::info!(field = &"value"); + }); + + handle.assert_finished(); +} + +#[test] +#[should_panic( + expected = "to have an explicit parent with name='explicit parent', but was actually a \ + contextual root" +)] +fn expect_explicit_parent_actual_contextual_root() { + let event = expect::event().with_ancestry(expect::has_explicit_parent("explicit parent")); + + let (subscriber, handle) = subscriber::mock().event(event).run_with_handle(); + + with_default(subscriber, || { + tracing::info!(field = &"value"); + }); + + handle.assert_finished(); +} + +#[test] +#[should_panic( + expected = "to have an explicit parent with name='explicit parent', but was actually an \ + explicit root" +)] +fn expect_explicit_parent_actual_explicit_root() { + let event = expect::event().with_ancestry(expect::has_explicit_parent("explicit parent")); + + let (subscriber, handle) = subscriber::mock() + .enter(expect::span()) + .event(event) + .run_with_handle(); + + with_default(subscriber, || { + let _guard = tracing::info_span!("contextual parent").entered(); + tracing::info!(parent: None, field = &"value"); + }); + + handle.assert_finished(); +} + +#[test] +fn explicit_root() { + let event = expect::event().with_ancestry(expect::is_explicit_root()); + + let (subscriber, handle) = subscriber::mock() + .enter(expect::span()) + .event(event) + .run_with_handle(); + + with_default(subscriber, || { + let _guard = tracing::info_span!("contextual parent").entered(); + tracing::info!(parent: None, field = &"value"); + }); + + handle.assert_finished(); +} + +#[test] +#[should_panic( + expected = "to be an explicit root, but actually has a contextual parent with \ + name='contextual parent'" +)] +fn expect_explicit_root_actual_contextual_parent() { + let event = expect::event().with_ancestry(expect::is_explicit_root()); + + let (subscriber, handle) = subscriber::mock() + .enter(expect::span()) + .event(event) + .run_with_handle(); + + with_default(subscriber, || { + let _guard = tracing::info_span!("contextual parent").entered(); + tracing::info!(field = &"value"); + }); + + handle.assert_finished(); +} + +#[test] +#[should_panic(expected = "to be an explicit root, but was actually a contextual root")] +fn expect_explicit_root_actual_contextual_root() { + let event = expect::event().with_ancestry(expect::is_explicit_root()); + + let (subscriber, handle) = subscriber::mock().event(event).run_with_handle(); + + with_default(subscriber, || { + tracing::info!(field = &"value"); + }); + + handle.assert_finished(); +} + +#[test] +#[should_panic( + expected = "to be an explicit root, but actually has an explicit parent with name='explicit parent'" +)] +fn expect_explicit_root_actual_explicit_parent() { + let event = expect::event().with_ancestry(expect::is_explicit_root()); + + let (subscriber, handle) = subscriber::mock().event(event).run_with_handle(); + + with_default(subscriber, || { + let span = tracing::info_span!("explicit parent"); + tracing::info!(parent: span.id(), field = &"value"); + }); + + handle.assert_finished(); +} + +#[test] +fn explicit_and_contextual_root_is_explicit() { + let event = expect::event().with_ancestry(expect::is_explicit_root()); + + let (subscriber, handle) = subscriber::mock().event(event).run_with_handle(); + + with_default(subscriber, || { + tracing::info!(parent: None, field = &"value"); + }); + + handle.assert_finished(); +} diff --git a/tracing-mock/tests/span_ancestry.rs b/tracing-mock/tests/span_ancestry.rs new file mode 100644 index 0000000000..3c557a3017 --- /dev/null +++ b/tracing-mock/tests/span_ancestry.rs @@ -0,0 +1,401 @@ +//! Tests assertions for the parent made on [`ExpectedSpan`]. +//! +//! The tests in this module completely cover the positive and negative cases +//! when expecting that a span is a contextual or explicit root or expecting +//! that a span has a specific contextual or explicit parent. +//! +//! [`ExpectedSpan`]: crate::span::ExpectedSpan +//! +use tracing::subscriber::with_default; +use tracing_mock::{expect, subscriber}; + +#[test] +fn contextual_parent() { + let span = expect::span() + .named("span") + .with_ancestry(expect::has_contextual_parent("contextual parent")); + + let (subscriber, handle) = subscriber::mock() + .enter(expect::span()) + .new_span(span) + .run_with_handle(); + + with_default(subscriber, || { + let _guard = tracing::info_span!("contextual parent").entered(); + tracing::info_span!("span"); + }); + + handle.assert_finished(); +} + +#[test] +#[should_panic( + expected = "to have a contextual parent with name='contextual parent', but \ + actually has a contextual parent with name='another parent'" +)] +fn contextual_parent_wrong_name() { + let span = expect::span() + .named("span") + .with_ancestry(expect::has_contextual_parent("contextual parent")); + + let (subscriber, handle) = subscriber::mock() + .enter(expect::span()) + .new_span(span) + .run_with_handle(); + + with_default(subscriber, || { + let _guard = tracing::info_span!("another parent").entered(); + tracing::info_span!("span"); + }); + + handle.assert_finished(); +} + +#[test] +#[should_panic( + expected = "to have a contextual parent with name='contextual parent', but was actually a \ + contextual root" +)] +fn expect_contextual_parent_actual_contextual_root() { + let span = expect::span() + .named("span") + .with_ancestry(expect::has_contextual_parent("contextual parent")); + + let (subscriber, handle) = subscriber::mock().new_span(span).run_with_handle(); + + with_default(subscriber, || { + tracing::info_span!("span"); + }); + + handle.assert_finished(); +} + +#[test] +#[should_panic( + expected = "to have a contextual parent with name='contextual parent', but actually has an \ + explicit parent with name='explicit parent'" +)] +fn expect_contextual_parent_actual_explicit_parent() { + let span = expect::span() + .named("span") + .with_ancestry(expect::has_contextual_parent("contextual parent")); + + let (subscriber, handle) = subscriber::mock() + .new_span(expect::span()) + .new_span(span) + .run_with_handle(); + + with_default(subscriber, || { + let span = tracing::info_span!("explicit parent"); + tracing::info_span!(parent: span.id(), "span"); + }); + + handle.assert_finished(); +} + +#[test] +#[should_panic( + expected = "to have a contextual parent with name='contextual parent', but was actually an \ + explicit root" +)] +fn expect_contextual_parent_actual_explicit_root() { + let span = expect::span() + .named("span") + .with_ancestry(expect::has_contextual_parent("contextual parent")); + + let (subscriber, handle) = subscriber::mock() + .enter(expect::span()) + .new_span(span) + .run_with_handle(); + + with_default(subscriber, || { + let _guard = tracing::info_span!("contextual parent").entered(); + tracing::info_span!(parent: None, "span"); + }); + + handle.assert_finished(); +} + +#[test] +fn contextual_root() { + let span = expect::span() + .named("span") + .with_ancestry(expect::is_contextual_root()); + + let (subscriber, handle) = subscriber::mock().new_span(span).run_with_handle(); + + with_default(subscriber, || { + tracing::info_span!("span"); + }); + + handle.assert_finished(); +} + +#[test] +#[should_panic( + expected = "to be a contextual root, but actually has a contextual parent with \ + name='contextual parent'" +)] +fn expect_contextual_root_actual_contextual_parent() { + let span = expect::span() + .named("span") + .with_ancestry(expect::is_contextual_root()); + + let (subscriber, handle) = subscriber::mock() + .enter(expect::span()) + .new_span(span) + .run_with_handle(); + + with_default(subscriber, || { + let _guard = tracing::info_span!("contextual parent").entered(); + tracing::info_span!("span"); + }); + + handle.assert_finished(); +} + +#[test] +#[should_panic( + expected = "to be a contextual root, but actually has an explicit parent with \ + name='explicit parent'" +)] +fn expect_contextual_root_actual_explicit_parent() { + let span = expect::span() + .named("span") + .with_ancestry(expect::is_contextual_root()); + + let (subscriber, handle) = subscriber::mock() + .new_span(expect::span()) + .new_span(span) + .run_with_handle(); + + with_default(subscriber, || { + let span = tracing::info_span!("explicit parent"); + tracing::info_span!(parent: span.id(), "span"); + }); + + handle.assert_finished(); +} + +#[test] +#[should_panic(expected = "to be a contextual root, but was actually an explicit root")] +fn expect_contextual_root_actual_explicit_root() { + let span = expect::span() + .named("span") + .with_ancestry(expect::is_contextual_root()); + + let (subscriber, handle) = subscriber::mock() + .enter(expect::span()) + .new_span(span) + .run_with_handle(); + + with_default(subscriber, || { + let _guard = tracing::info_span!("contextual parent").entered(); + tracing::info_span!(parent: None, "span"); + }); + + handle.assert_finished(); +} + +#[test] +fn explicit_parent() { + let span = expect::span() + .named("span") + .with_ancestry(expect::has_explicit_parent("explicit parent")); + + let (subscriber, handle) = subscriber::mock() + .new_span(expect::span()) + .new_span(span) + .run_with_handle(); + + with_default(subscriber, || { + let span = tracing::info_span!("explicit parent"); + tracing::info_span!(parent: span.id(), "span"); + }); + + handle.assert_finished(); +} + +#[test] +#[should_panic( + expected = "to have an explicit parent with name='explicit parent', but actually has an \ + explicit parent with name='another parent'" +)] +fn explicit_parent_wrong_name() { + let span = expect::span() + .named("span") + .with_ancestry(expect::has_explicit_parent("explicit parent")); + + let (subscriber, handle) = subscriber::mock() + .new_span(expect::span()) + .new_span(span) + .run_with_handle(); + + with_default(subscriber, || { + let span = tracing::info_span!("another parent"); + tracing::info_span!(parent: span.id(), "span"); + }); + + handle.assert_finished(); +} + +#[test] +#[should_panic( + expected = "to have an explicit parent with name='explicit parent', but actually has a \ + contextual parent with name='contextual parent'" +)] +fn expect_explicit_parent_actual_contextual_parent() { + let span = expect::span() + .named("span") + .with_ancestry(expect::has_explicit_parent("explicit parent")); + + let (subscriber, handle) = subscriber::mock() + .enter(expect::span()) + .new_span(span) + .run_with_handle(); + + with_default(subscriber, || { + let _guard = tracing::info_span!("contextual parent").entered(); + tracing::info_span!("span"); + }); + + handle.assert_finished(); +} + +#[test] +#[should_panic( + expected = "to have an explicit parent with name='explicit parent', but was actually a \ + contextual root" +)] +fn expect_explicit_parent_actual_contextual_root() { + let span = expect::span() + .named("span") + .with_ancestry(expect::has_explicit_parent("explicit parent")); + + let (subscriber, handle) = subscriber::mock().new_span(span).run_with_handle(); + + with_default(subscriber, || { + tracing::info_span!("span"); + }); + + handle.assert_finished(); +} + +#[test] +#[should_panic( + expected = "to have an explicit parent with name='explicit parent', but was actually an \ + explicit root" +)] +fn expect_explicit_parent_actual_explicit_root() { + let span = expect::span() + .named("span") + .with_ancestry(expect::has_explicit_parent("explicit parent")); + + let (subscriber, handle) = subscriber::mock() + .enter(expect::span()) + .new_span(span) + .run_with_handle(); + + with_default(subscriber, || { + let _guard = tracing::info_span!("contextual parent").entered(); + tracing::info_span!(parent: None, "span"); + }); + + handle.assert_finished(); +} + +#[test] +fn explicit_root() { + let span = expect::span() + .named("span") + .with_ancestry(expect::is_explicit_root()); + + let (subscriber, handle) = subscriber::mock() + .new_span(expect::span()) + .enter(expect::span()) + .new_span(span) + .run_with_handle(); + + with_default(subscriber, || { + let _guard = tracing::info_span!("contextual parent").entered(); + tracing::info_span!(parent: None, "span"); + }); + + handle.assert_finished(); +} + +#[test] +#[should_panic( + expected = "to be an explicit root, but actually has a contextual parent with \ + name='contextual parent'" +)] +fn expect_explicit_root_actual_contextual_parent() { + let span = expect::span() + .named("span") + .with_ancestry(expect::is_explicit_root()); + + let (subscriber, handle) = subscriber::mock() + .enter(expect::span()) + .new_span(span) + .run_with_handle(); + + with_default(subscriber, || { + let _guard = tracing::info_span!("contextual parent").entered(); + tracing::info_span!("span"); + }); + + handle.assert_finished(); +} + +#[test] +#[should_panic(expected = "to be an explicit root, but was actually a contextual root")] +fn expect_explicit_root_actual_contextual_root() { + let span = expect::span() + .named("span") + .with_ancestry(expect::is_explicit_root()); + + let (subscriber, handle) = subscriber::mock().new_span(span).run_with_handle(); + + with_default(subscriber, || { + tracing::info_span!("span"); + }); + + handle.assert_finished(); +} + +#[test] +#[should_panic( + expected = "to be an explicit root, but actually has an explicit parent with name='explicit parent'" +)] +fn expect_explicit_root_actual_explicit_parent() { + let span = expect::span() + .named("span") + .with_ancestry(expect::is_explicit_root()); + + let (subscriber, handle) = subscriber::mock() + .new_span(expect::span()) + .new_span(span) + .run_with_handle(); + + with_default(subscriber, || { + let span = tracing::info_span!("explicit parent"); + tracing::info_span!(parent: span.id(), "span"); + }); + + handle.assert_finished(); +} + +#[test] +fn explicit_and_contextual_root_is_explicit() { + let span = expect::span() + .named("span") + .with_ancestry(expect::is_explicit_root()); + + let (subscriber, handle) = subscriber::mock().new_span(span).run_with_handle(); + + with_default(subscriber, || { + tracing::info_span!(parent: None, "span"); + }); + + handle.assert_finished(); +} diff --git a/tracing/tests/event.rs b/tracing/tests/event.rs index 417b8878b1..99e85d5582 100644 --- a/tracing/tests/event.rs +++ b/tracing/tests/event.rs @@ -338,7 +338,7 @@ fn both_shorthands() { fn explicit_child() { let (subscriber, handle) = subscriber::mock() .new_span(expect::span().named("foo")) - .event(expect::event().with_explicit_parent(Some("foo"))) + .event(expect::event().with_ancestry(expect::has_explicit_parent("foo"))) .only() .run_with_handle(); @@ -355,11 +355,11 @@ fn explicit_child() { fn explicit_child_at_levels() { let (subscriber, handle) = subscriber::mock() .new_span(expect::span().named("foo")) - .event(expect::event().with_explicit_parent(Some("foo"))) - .event(expect::event().with_explicit_parent(Some("foo"))) - .event(expect::event().with_explicit_parent(Some("foo"))) - .event(expect::event().with_explicit_parent(Some("foo"))) - .event(expect::event().with_explicit_parent(Some("foo"))) + .event(expect::event().with_ancestry(expect::has_explicit_parent("foo"))) + .event(expect::event().with_ancestry(expect::has_explicit_parent("foo"))) + .event(expect::event().with_ancestry(expect::has_explicit_parent("foo"))) + .event(expect::event().with_ancestry(expect::has_explicit_parent("foo"))) + .event(expect::event().with_ancestry(expect::has_explicit_parent("foo"))) .only() .run_with_handle(); diff --git a/tracing/tests/instrument.rs b/tracing/tests/instrument.rs index 9233727d44..eb7e9edbc9 100644 --- a/tracing/tests/instrument.rs +++ b/tracing/tests/instrument.rs @@ -35,13 +35,21 @@ fn span_on_drop() { let subscriber = subscriber::mock() .enter(expect::span().named("foo")) - .event(expect::event().at_level(Level::INFO)) + .event( + expect::event() + .with_ancestry(expect::has_contextual_parent("foo")) + .at_level(Level::INFO), + ) .exit(expect::span().named("foo")) .enter(expect::span().named("foo")) .exit(expect::span().named("foo")) .drop_span(expect::span().named("foo")) .enter(expect::span().named("bar")) - .event(expect::event().at_level(Level::INFO)) + .event( + expect::event() + .with_ancestry(expect::has_contextual_parent("bar")) + .at_level(Level::INFO), + ) .exit(expect::span().named("bar")) .drop_span(expect::span().named("bar")) .only() diff --git a/tracing/tests/span.rs b/tracing/tests/span.rs index 6a713b6453..bd5bed0d2f 100644 --- a/tracing/tests/span.rs +++ b/tracing/tests/span.rs @@ -635,7 +635,11 @@ fn new_span_with_target_and_log_level() { #[test] fn explicit_root_span_is_root() { let (subscriber, handle) = subscriber::mock() - .new_span(expect::span().named("foo").with_explicit_parent(None)) + .new_span( + expect::span() + .named("foo") + .with_ancestry(expect::is_explicit_root()), + ) .only() .run_with_handle(); @@ -652,7 +656,11 @@ fn explicit_root_span_is_root_regardless_of_ctx() { let (subscriber, handle) = subscriber::mock() .new_span(expect::span().named("foo")) .enter(expect::span().named("foo")) - .new_span(expect::span().named("bar").with_explicit_parent(None)) + .new_span( + expect::span() + .named("bar") + .with_ancestry(expect::is_explicit_root()), + ) .exit(expect::span().named("foo")) .only() .run_with_handle(); @@ -674,7 +682,7 @@ fn explicit_child() { .new_span( expect::span() .named("bar") - .with_explicit_parent(Some("foo")), + .with_ancestry(expect::has_explicit_parent("foo")), ) .only() .run_with_handle(); @@ -692,11 +700,31 @@ fn explicit_child() { fn explicit_child_at_levels() { let (subscriber, handle) = subscriber::mock() .new_span(expect::span().named("foo")) - .new_span(expect::span().named("a").with_explicit_parent(Some("foo"))) - .new_span(expect::span().named("b").with_explicit_parent(Some("foo"))) - .new_span(expect::span().named("c").with_explicit_parent(Some("foo"))) - .new_span(expect::span().named("d").with_explicit_parent(Some("foo"))) - .new_span(expect::span().named("e").with_explicit_parent(Some("foo"))) + .new_span( + expect::span() + .named("a") + .with_ancestry(expect::has_explicit_parent("foo")), + ) + .new_span( + expect::span() + .named("b") + .with_ancestry(expect::has_explicit_parent("foo")), + ) + .new_span( + expect::span() + .named("c") + .with_ancestry(expect::has_explicit_parent("foo")), + ) + .new_span( + expect::span() + .named("d") + .with_ancestry(expect::has_explicit_parent("foo")), + ) + .new_span( + expect::span() + .named("e") + .with_ancestry(expect::has_explicit_parent("foo")), + ) .only() .run_with_handle(); @@ -722,7 +750,7 @@ fn explicit_child_regardless_of_ctx() { .new_span( expect::span() .named("baz") - .with_explicit_parent(Some("foo")), + .with_ancestry(expect::has_explicit_parent("foo")), ) .exit(expect::span().named("bar")) .only() @@ -741,7 +769,11 @@ fn explicit_child_regardless_of_ctx() { #[test] fn contextual_root() { let (subscriber, handle) = subscriber::mock() - .new_span(expect::span().named("foo").with_contextual_parent(None)) + .new_span( + expect::span() + .named("foo") + .with_ancestry(expect::is_contextual_root()), + ) .only() .run_with_handle(); @@ -761,7 +793,7 @@ fn contextual_child() { .new_span( expect::span() .named("bar") - .with_contextual_parent(Some("foo")), + .with_ancestry(expect::has_contextual_parent("foo")), ) .exit(expect::span().named("foo")) .only() From d0fd7a246d66712186fc74cc743bd6c479a3824b Mon Sep 17 00:00:00 2001 From: Hayden Stainsby Date: Mon, 5 Aug 2024 19:03:29 +0200 Subject: [PATCH 6/9] mock: correct contextual/explicit parent assertions (#3004) When recording the parent of an event or span, the `MockCollector` treats an explicit parent of `None` (i.e. an event or span that is an explicit root) in the same way as if there is no explicit root. This leads to it picking up the contextual parent or treating the event or span as a contextual root. This change refactors the recording of the parent to use `is_contextual` to distinguish whether or not an explicit parent has been specified. The actual parent is also written into an `Ancestry` enum so that the expected and actual values can be compared in a more explicit way. Additionally, the `Ancestry` struct has been moved into its own module and the check behavior has been fixed. The error message has also been unified across all cases. Another problem with the previous API is that the two methods `with_contextual_parent` and `with_explicit_parent` are actually mutually exclusive, a span or event cannot be both of them. It is also a (small) mental leap for the user to go from `with_*_parent(None)` to understanding that this means that a span or event is a root (either contextual or explicit). As such, the API has been reworked into a single method `with_ancestry`, which takes an enum with the following four variants: * `HasExplicitParent(String)` (parent span name) * `IsExplicitRoot` * `HasContextualParent(String)` (parent span name) * `IsContextualRoot` To make the interface as useable as possible, helper functions have been defined in the `expect` module which can be used to create the enum variants. Specifically, these take `Into` parameter for the span name. Given the number of different cases involved in checking ancestry, separate integration tests have been added to `tracing-mock` specifically for testing all the positive and negative cases when asserting on the ancestry of events and spans. There were two tests in `tracing-attributes` which specified both an explicit and a contextual parent. This behavior was never intended to work as all events and spans are either contextual or not. The tests have been corrected to only expect one of the two. Fixes: #2440 --- tracing-mock/src/span.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/tracing-mock/src/span.rs b/tracing-mock/src/span.rs index c12655055a..1614f29b1a 100644 --- a/tracing-mock/src/span.rs +++ b/tracing-mock/src/span.rs @@ -516,19 +516,14 @@ impl ExpectedSpan { /// ``` /// use tracing_mock::{subscriber, expect}; /// - /// let parent_span = expect::span().named("parent_span"); /// let span = expect::span() - /// .with_ancestry(expect::has_contextual_parent("parent_span")); + /// .with_ancestry(expect::is_contextual_root()); /// /// 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"); /// }); /// From 0350aeb1bd476354aba259d602df080034b1a608 Mon Sep 17 00:00:00 2001 From: Hayden Stainsby Date: Tue, 29 Oct 2024 15:38:35 +0100 Subject: [PATCH 7/9] mock: improve ergonomics when an `ExpectedSpan` is needed (#3097) Many of the methods on `MockCollector` take an `ExpectedSpan`. This often requires significant boilerplate. For example, to expect that a span with a specific name enters and then exits, the following code is needed: ```rust let span = expect::span().named("span name"); let (collector, handle) = collector::mock() .enter(span.clone()) .exit(span) .run_with_handle(); ``` In order to make using `tracing-mock` more ergonomic and also more compact, the `MockCollector` and `MockSubscriber` methods that previous took an `ExpectedSpan`, are now generic over `Into`. There are currently 3 implementations of `From` for `ExpectedSpan` which allow the following shorthand uses: `T: Into` - an `ExpectedSpan` will be created that expects to have a name specified by `T`. ```rust let (collector, handle) = collector::mock() .enter("span name") .exit("span name") .run_with_handle(); ``` `&ExpectedId` - an `ExpectedSpan` will be created that expects to have the expected Id. A reference is taken and cloned internally because the caller always needs to use an `ExpectedId` in at least 2 calls to the mock collector/subscriber. ```rust let id = expect::id(); let (collector, handle) = collector::mock() .new_span(&id) .enter(&id) .run_with_handle(); ``` `&ExpectedSpan` - The expected span is taken by reference and cloned. ```rust let span = expect::span().named("span name"); let (collector, handle) = collector::mock() .enter(&span) .exit(&span) .run_with_handle(); ``` In Rust, taking a reference to an object and immediately cloning it is an anti-pattern. It is considered better to force the user to clone outside the API to make the cloning explict. However, in the case of a testing framework, it seems reasonable to prefer a more concise API, rather than having it more explicit. To reduce the size of this PR and to avoid unnecessary churn in other crates, the tests within the tracing repo which use `tracing-mock` will not be updated to use the new `Into` capabilities. The new API is backwards compatible and those tests can remain as they are. --- tracing-mock/src/layer.rs | 52 ++++++----- tracing-mock/src/span.rs | 162 ++++++++++++++++++++++++--------- tracing-mock/src/subscriber.rs | 75 +++++++++------ 3 files changed, 196 insertions(+), 93 deletions(-) diff --git a/tracing-mock/src/layer.rs b/tracing-mock/src/layer.rs index 19882e7f99..9ecd71d8e9 100644 --- a/tracing-mock/src/layer.rs +++ b/tracing-mock/src/layer.rs @@ -1,5 +1,5 @@ //! An implementation of the [`Layer`] trait which validates that -//! the `tracing` data it recieves matches the expected output for a test. +//! the `tracing` data it receives matches the expected output for a test. //! //! //! The [`MockLayer`] is the central component in these tools. The @@ -7,7 +7,7 @@ //! validated as the code under test is run. //! //! ``` -//! use tracing_mock::{expect, field, layer}; +//! use tracing_mock::{expect, layer}; //! use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, Layer}; //! //! let (layer, handle) = layer::mock() @@ -40,11 +40,11 @@ //! .named("my_span"); //! let (layer, handle) = layer::mock() //! // Enter a matching span -//! .enter(span.clone()) +//! .enter(&span) //! // Record an event with message "collect parting message" //! .event(expect::event().with_fields(expect::message("say hello"))) //! // Exit a matching span -//! .exit(span) +//! .exit(&span) //! // Expect no further messages to be recorded //! .only() //! // Return the layer and handle @@ -75,18 +75,18 @@ //! span before recording an event, the test will fail: //! //! ```should_panic -//! use tracing_mock::{expect, field, layer}; +//! use tracing_mock::{expect, 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()) +//! .enter(&span) //! // Record an event with message "collect parting message" //! .event(expect::event().with_fields(expect::message("say hello"))) //! // Exit a matching span -//! .exit(span) +//! .exit(&span) //! // Expect no further messages to be recorded //! .only() //! // Return the subscriber and handle @@ -146,18 +146,18 @@ use std::{ /// # Examples /// /// ``` -/// use tracing_mock::{expect, field, layer}; +/// use tracing_mock::{expect, 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()) +/// .enter(&span) /// // Record an event with message "collect parting message" /// .event(expect::event().with_fields(expect::message("say hello"))) /// // Exit a matching span -/// .exit(span) +/// .exit(&span) /// // Expect no further messages to be recorded /// .only() /// // Return the subscriber and handle @@ -414,7 +414,7 @@ impl MockLayerBuilder { /// /// This function accepts `Into` instead of /// [`ExpectedSpan`] directly. [`NewSpan`] can be used to test - /// span fields and the span parent. + /// span fields and the span ancestry. /// /// The new span doesn't need to be entered for this expectation /// to succeed. @@ -504,8 +504,8 @@ impl MockLayerBuilder { /// .at_level(tracing::Level::INFO) /// .named("the span we're testing"); /// let (layer, handle) = layer::mock() - /// .enter(span.clone()) - /// .exit(span) + /// .enter(&span) + /// .exit(&span) /// .only() /// .run_with_handle(); /// @@ -532,8 +532,8 @@ impl MockLayerBuilder { /// .at_level(tracing::Level::INFO) /// .named("the span we're testing"); /// let (layer, handle) = layer::mock() - /// .enter(span.clone()) - /// .exit(span) + /// .enter(&span) + /// .exit(&span) /// .only() /// .run_with_handle(); /// @@ -552,8 +552,11 @@ impl MockLayerBuilder { /// /// [`exit`]: fn@Self::exit /// [`only`]: fn@Self::only - pub fn enter(mut self, span: ExpectedSpan) -> Self { - self.expected.push_back(Expect::Enter(span)); + pub fn enter(mut self, span: S) -> Self + where + S: Into, + { + self.expected.push_back(Expect::Enter(span.into())); self } @@ -581,8 +584,8 @@ impl MockLayerBuilder { /// .at_level(tracing::Level::INFO) /// .named("the span we're testing"); /// let (layer, handle) = layer::mock() - /// .enter(span.clone()) - /// .exit(span) + /// .enter(&span) + /// .exit(&span) /// .only() /// .run_with_handle(); /// @@ -608,8 +611,8 @@ impl MockLayerBuilder { /// .at_level(tracing::Level::INFO) /// .named("the span we're testing"); /// let (layer, handle) = layer::mock() - /// .enter(span.clone()) - /// .exit(span) + /// .enter(&span) + /// .exit(&span) /// .only() /// .run_with_handle(); /// @@ -629,8 +632,11 @@ impl MockLayerBuilder { /// [`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)); + pub fn exit(mut self, span: S) -> Self + where + S: Into, + { + self.expected.push_back(Expect::Exit(span.into())); self } diff --git a/tracing-mock/src/span.rs b/tracing-mock/src/span.rs index 1614f29b1a..d00690331f 100644 --- a/tracing-mock/src/span.rs +++ b/tracing-mock/src/span.rs @@ -18,8 +18,8 @@ //! .at_level(tracing::Level::INFO); //! //! let (subscriber, handle) = subscriber::mock() -//! .enter(span.clone()) -//! .exit(span) +//! .enter(&span) +//! .exit(&span) //! .run_with_handle(); //! //! tracing::subscriber::with_default(subscriber, || { @@ -30,6 +30,25 @@ //! handle.assert_finished(); //! ``` //! +//! Instead of passing an `ExpectedSpan`, the subscriber methods will also accept +//! anything that implements `Into` which is shorthand for +//! `expect::span().named(name)`. +//! +//! ``` +//! use tracing_mock::subscriber; +//! +//! let (subscriber, handle) = subscriber::mock() +//! .enter("interesting_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: //! //! ``` @@ -44,10 +63,10 @@ //! .with_ancestry(expect::has_explicit_parent("parent_span")); //! //! let (subscriber, handle) = subscriber::mock() -//! .new_span(expect::span().named("parent_span")) +//! .new_span("parent_span") //! .new_span(new_span) -//! .enter(span.clone()) -//! .exit(span) +//! .enter(&span) +//! .exit(&span) //! .run_with_handle(); //! //! tracing::subscriber::with_default(subscriber, || { @@ -75,8 +94,8 @@ //! .at_level(tracing::Level::INFO); //! //! let (subscriber, handle) = subscriber::mock() -//! .enter(span.clone()) -//! .exit(span) +//! .enter(&span) +//! .exit(&span) //! .run_with_handle(); //! //! tracing::subscriber::with_default(subscriber, || { @@ -115,6 +134,27 @@ pub struct ExpectedSpan { pub(crate) metadata: ExpectedMetadata, } +impl From for ExpectedSpan +where + I: Into, +{ + fn from(name: I) -> Self { + ExpectedSpan::default().named(name) + } +} + +impl From<&ExpectedId> for ExpectedSpan { + fn from(id: &ExpectedId) -> Self { + ExpectedSpan::default().with_id(id.clone()) + } +} + +impl From<&ExpectedSpan> for ExpectedSpan { + fn from(span: &ExpectedSpan) -> Self { + span.clone() + } +} + /// A mock new span. /// /// **Note**: This struct contains expectations that can only be asserted @@ -166,7 +206,8 @@ pub struct ExpectedId { 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. + /// If an event is recorded with a name that differs from the one provided to this method, the + /// expectation will fail. /// /// # Examples /// @@ -187,6 +228,25 @@ impl ExpectedSpan { /// handle.assert_finished(); /// ``` /// + /// If only the name of the span needs to be validated, then + /// instead of using the `named` method, a string can be passed + /// to the [`MockCollector`] functions directly. + /// + /// ``` + /// use tracing_mock::subscriber; + /// + /// let (subscriber, handle) = subscriber::mock() + /// .enter("span name") + /// .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 @@ -205,6 +265,8 @@ impl ExpectedSpan { /// /// handle.assert_finished(); /// ``` + /// + /// [`MockCollector`]: struct@crate::subscriber::MockCollector pub fn named(self, name: I) -> Self where I: Into, @@ -247,10 +309,41 @@ impl ExpectedSpan { /// let span2 = expect::span().named("span").with_id(id2.clone()); /// /// let (subscriber, handle) = subscriber::mock() - /// .new_span(span1.clone()) - /// .new_span(span2.clone()) - /// .enter(span2) - /// .enter(span1) + /// .new_span(&span1) + /// .new_span(&span2) + /// .enter(&span2) + /// .enter(&span1) + /// .run_with_handle(); + /// + /// tracing::subscriber::with_default(subscriber, || { + /// fn create_span() -> tracing::Span { + /// tracing::info_span!("span") + /// } + /// + /// let span1 = create_span(); + /// let span2 = create_span(); + /// + /// let _guard2 = span2.enter(); + /// let _guard1 = span1.enter(); + /// }); + /// + /// handle.assert_finished(); + /// ``` + /// + /// Since `ExpectedId` implements `Into`, in cases where + /// only checking on Id is desired, a shorthand version of the previous + /// example can be used. + /// + /// ``` + /// use tracing_mock::{subscriber, expect}; + /// let id1 = expect::id(); + /// let id2 = expect::id(); + /// + /// let (subscriber, handle) = subscriber::mock() + /// .new_span(&id1) + /// .new_span(&id2) + /// .enter(&id2) + /// .enter(&id1) /// .run_with_handle(); /// /// tracing::subscriber::with_default(subscriber, || { @@ -279,10 +372,10 @@ impl ExpectedSpan { /// let span2 = expect::span().named("span").with_id(id2.clone()); /// /// let (subscriber, handle) = subscriber::mock() - /// .new_span(span1.clone()) - /// .new_span(span2.clone()) - /// .enter(span2) - /// .enter(span1) + /// .new_span(&span1) + /// .new_span(&span2) + /// .enter(&span2) + /// .enter(&span1) /// .run_with_handle(); /// /// tracing::subscriber::with_default(subscriber, || { @@ -496,8 +589,8 @@ impl ExpectedSpan { /// .with_ancestry(expect::has_explicit_parent("parent_span")); /// /// let (subscriber, handle) = subscriber::mock() - /// .new_span(parent_span.clone()) - /// .enter(parent_span) + /// .new_span(&parent_span) + /// .enter(&parent_span) /// .new_span(span) /// .run_with_handle(); /// @@ -530,26 +623,6 @@ impl ExpectedSpan { /// 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_ancestry(expect::is_contextual_root()); - /// - /// 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 *contextually*—as opposed to explicitly—within the span /// `parent_span`: @@ -562,8 +635,8 @@ impl ExpectedSpan { /// .with_ancestry(expect::has_explicit_parent("parent_span")); /// /// let (subscriber, handle) = subscriber::mock() - /// .new_span(parent_span.clone()) - /// .enter(parent_span) + /// .new_span(&parent_span) + /// .enter(&parent_span) /// .new_span(span) /// .run_with_handle(); /// @@ -719,10 +792,13 @@ impl fmt::Display for ExpectedSpan { } } -impl From for NewSpan { - fn from(span: ExpectedSpan) -> Self { +impl From for NewSpan +where + S: Into, +{ + fn from(span: S) -> Self { Self { - span, + span: span.into(), ..Default::default() } } diff --git a/tracing-mock/src/subscriber.rs b/tracing-mock/src/subscriber.rs index 924be26840..8eca275a47 100644 --- a/tracing-mock/src/subscriber.rs +++ b/tracing-mock/src/subscriber.rs @@ -38,13 +38,13 @@ //! .named("my_span"); //! let (subscriber, handle) = subscriber::mock() //! // Enter a matching span -//! .enter(span.clone()) +//! .enter(&span) //! // Record an event with message "collect parting message" //! .event(expect::event().with_fields(expect::message("collect parting message"))) //! // Record a value for the field `parting` on a matching span //! .record(span.clone(), expect::field("parting").with_value(&"goodbye world!")) //! // Exit a matching span -//! .exit(span) +//! .exit(&span) //! // Expect no further messages to be recorded //! .only() //! // Return the subscriber and handle @@ -80,9 +80,10 @@ //! let span = expect::span() //! .named("my_span"); //! let (subscriber, handle) = subscriber::mock() -//! .enter(span.clone()) +//! // Enter a matching span +//! .enter(&span) //! .event(expect::event().with_fields(expect::message("collect parting message"))) -//! .record(span.clone(), expect::field("parting").with_value(&"goodbye world!")) +//! .record(&span, expect::field("parting").with_value(&"goodbye world!")) //! .exit(span) //! .only() //! .run_with_handle(); @@ -225,13 +226,13 @@ pub struct MockHandle(Arc>>, String); /// .named("my_span"); /// let (subscriber, handle) = subscriber::mock() /// // Enter a matching span -/// .enter(span.clone()) +/// .enter(&span) /// // Record an event with message "collect parting message" /// .event(expect::event().with_fields(expect::message("collect parting message"))) /// // Record a value for the field `parting` on a matching span /// .record(span.clone(), expect::field("parting").with_value(&"goodbye world!")) /// // Exit a matching span -/// .exit(span) +/// .exit(&span) /// // Expect no further messages to be recorded /// .only() /// // Return the subscriber and handle @@ -472,8 +473,8 @@ where /// .at_level(tracing::Level::INFO) /// .named("the span we're testing"); /// let (subscriber, handle) = subscriber::mock() - /// .enter(span.clone()) - /// .exit(span) + /// .enter(&span) + /// .exit(&span) /// .only() /// .run_with_handle(); /// @@ -495,8 +496,8 @@ where /// .at_level(tracing::Level::INFO) /// .named("the span we're testing"); /// let (subscriber, handle) = subscriber::mock() - /// .enter(span.clone()) - /// .exit(span) + /// .enter(&span) + /// .exit(&span) /// .only() /// .run_with_handle(); /// @@ -511,8 +512,11 @@ where /// /// [`exit`]: fn@Self::exit /// [`only`]: fn@Self::only - pub fn enter(mut self, span: ExpectedSpan) -> Self { - self.expected.push_back(Expect::Enter(span)); + pub fn enter(mut self, span: S) -> Self + where + S: Into, + { + self.expected.push_back(Expect::Enter(span.into())); self } @@ -536,8 +540,8 @@ where /// .at_level(tracing::Level::INFO) /// .named("the span we're testing"); /// let (subscriber, handle) = subscriber::mock() - /// .enter(span.clone()) - /// .exit(span) + /// .enter(&span) + /// .exit(&span) /// .run_with_handle(); /// /// tracing::subscriber::with_default(subscriber, || { @@ -558,8 +562,8 @@ where /// .at_level(tracing::Level::INFO) /// .named("the span we're testing"); /// let (subscriber, handle) = subscriber::mock() - /// .enter(span.clone()) - /// .exit(span) + /// .enter(&span) + /// .exit(&span) /// .run_with_handle(); /// /// tracing::subscriber::with_default(subscriber, || { @@ -572,8 +576,11 @@ where /// ``` /// /// [`enter`]: fn@Self::enter - pub fn exit(mut self, span: ExpectedSpan) -> Self { - self.expected.push_back(Expect::Exit(span)); + pub fn exit(mut self, span: S) -> Self + where + S: Into, + { + self.expected.push_back(Expect::Exit(span.into())); self } @@ -627,8 +634,11 @@ where /// /// handle.assert_finished(); /// ``` - pub fn clone_span(mut self, span: ExpectedSpan) -> Self { - self.expected.push_back(Expect::CloneSpan(span)); + pub fn clone_span(mut self, span: S) -> Self + where + S: Into, + { + self.expected.push_back(Expect::CloneSpan(span.into())); self } @@ -644,8 +654,11 @@ where /// /// [`Subscriber::drop_span`]: fn@tracing::Subscriber::drop_span #[allow(deprecated)] - pub fn drop_span(mut self, span: ExpectedSpan) -> Self { - self.expected.push_back(Expect::DropSpan(span)); + pub fn drop_span(mut self, span: S) -> Self + where + S: Into, + { + self.expected.push_back(Expect::DropSpan(span.into())); self } @@ -710,9 +723,15 @@ where /// ``` /// /// [`Span::follows_from`]: fn@tracing::Span::follows_from - pub fn follows_from(mut self, consequence: ExpectedSpan, cause: ExpectedSpan) -> Self { - self.expected - .push_back(Expect::FollowsFrom { consequence, cause }); + pub fn follows_from(mut self, consequence: S1, cause: S2) -> Self + where + S1: Into, + S2: Into, + { + self.expected.push_back(Expect::FollowsFrom { + consequence: consequence.into(), + cause: cause.into(), + }); self } @@ -775,11 +794,13 @@ where /// ``` /// /// [`field`]: mod@crate::field - pub fn record(mut self, span: ExpectedSpan, fields: I) -> Self + pub fn record(mut self, span: S, fields: I) -> Self where + S: Into, I: Into, { - self.expected.push_back(Expect::Visit(span, fields.into())); + self.expected + .push_back(Expect::Visit(span.into(), fields.into())); self } From de4ecd52201a06a84f9811fd660e190c7a7fb591 Mon Sep 17 00:00:00 2001 From: Hayden Stainsby Date: Wed, 30 Oct 2024 15:31:46 +0100 Subject: [PATCH 8/9] mock: match parent span on `ExpectedSpan` (#3098) The `with_ancestry` methods on `NewSpan` and `ExpectedEvent` provide a way to match whether the span or event is a contextual or explicit root or if it has a contextual or explicit parent span. However, in the case of matching on a contextual or explicit parent span, only the span name could be used for matching. This is sufficiently precise when testing tracing instrumentation in other libraries or applications as opposed to testing tracing itself. It is likely that a user would like to test that some span or event has a specific span as a parent, and not just any span with a specific name, in many cases, all the possible parent spans may have the same name. This is the case when testing tracing instrumentation in Tokio. To solve this problem, the `Ancestry` struct was renamed to `ExpectedAncestry` and in the case of expecting an explicit or conextual parent, an `ExpectedSpan` object can be passed in. This provides the maximum possible flexibility. The convenience functions in the `expect` module now take `Into` so that existing tests that pass a string type object for the parent will see the same behaviour as previously and shorthand use for expected Ids is also available. Additionally, the span checking code has been unified between the `MockCollector` and `MockSubscriber` cases and the assertion descriptions have been improved to make them more readable. --- tracing-mock/src/ancestry.rs | 119 ++++++++++++------- tracing-mock/src/event.rs | 53 +++++++-- tracing-mock/src/expect.rs | 26 ++--- tracing-mock/src/layer.rs | 86 +++----------- tracing-mock/src/metadata.rs | 67 +++++++---- tracing-mock/src/span.rs | 167 +++++++++++++++++++++------ tracing-mock/src/subscriber.rs | 92 +++++++-------- tracing-mock/tests/event_ancestry.rs | 148 +++++++++++++++++------- tracing-mock/tests/span_ancestry.rs | 160 ++++++++++++++++++------- 9 files changed, 589 insertions(+), 329 deletions(-) diff --git a/tracing-mock/src/ancestry.rs b/tracing-mock/src/ancestry.rs index ee661d45e8..817e30fef0 100644 --- a/tracing-mock/src/ancestry.rs +++ b/tracing-mock/src/ancestry.rs @@ -1,71 +1,102 @@ //! Define the ancestry of an event or span. //! -//! See the documentation on the [`Ancestry`] enum for further details. +//! See the documentation on the [`ExpectedAncestry`] enum for further details. use tracing_core::{ span::{self, Attributes}, Event, }; +use crate::span::{ActualSpan, ExpectedSpan}; + /// The ancestry of an event or span. /// /// An event or span can have an explicitly assigned parent, or be an explicit root. Otherwise, /// an event or span may have a contextually assigned parent or in the final case will be a /// contextual root. #[derive(Debug, Eq, PartialEq)] -pub enum Ancestry { - /// The event or span has an explicitly assigned parent (created with `parent: span_id`) with - /// the specified name. - HasExplicitParent(String), +pub enum ExpectedAncestry { + /// The event or span has an explicitly assigned parent (created with `parent: span_id`) span. + HasExplicitParent(ExpectedSpan), /// The event or span is an explicitly defined root. It was created with `parent: None` and /// has no parent. IsExplicitRoot, - /// The event or span has a contextually assigned parent with the specified name. It has no - /// explicitly assigned parent, nor has it been explicitly defined as a root (it was created - /// without the `parent:` directive). There was a span in context when this event or span was - /// created. - HasContextualParent(String), + /// The event or span has a contextually assigned parent span. It has no explicitly assigned + /// parent span, nor has it been explicitly defined as a root (it was created without the + /// `parent:` directive). There was a span in context when this event or span was created. + HasContextualParent(ExpectedSpan), /// The event or span is a contextual root. It has no explicitly assigned parent, nor has it /// been explicitly defined as a root (it was created without the `parent:` directive). /// Additionally, no span was in context when this event or span was created. IsContextualRoot, } -impl Ancestry { +pub(crate) enum ActualAncestry { + HasExplicitParent(ActualSpan), + IsExplicitRoot, + HasContextualParent(ActualSpan), + IsContextualRoot, +} + +impl ExpectedAncestry { #[track_caller] pub(crate) fn check( &self, - actual_ancestry: &Ancestry, + actual_ancestry: &ActualAncestry, ctx: impl std::fmt::Display, collector_name: &str, ) { - let expected_description = |ancestry: &Ancestry| match ancestry { - Self::IsExplicitRoot => "be an explicit root".to_string(), - Self::HasExplicitParent(name) => format!("have an explicit parent with name='{name}'"), - Self::IsContextualRoot => "be a contextual root".to_string(), - Self::HasContextualParent(name) => { - format!("have a contextual parent with name='{name}'") + match (self, actual_ancestry) { + (Self::IsExplicitRoot, ActualAncestry::IsExplicitRoot) => {} + (Self::IsContextualRoot, ActualAncestry::IsContextualRoot) => {} + ( + Self::HasExplicitParent(expected_parent), + ActualAncestry::HasExplicitParent(actual_parent), + ) => { + expected_parent.check( + actual_parent, + format_args!("{ctx} to have an explicit parent span"), + collector_name, + ); } - }; - - let actual_description = |ancestry: &Ancestry| match ancestry { - Self::IsExplicitRoot => "was actually an explicit root".to_string(), - Self::HasExplicitParent(name) => { - format!("actually has an explicit parent with name='{name}'") + ( + Self::HasContextualParent(expected_parent), + ActualAncestry::HasContextualParent(actual_parent), + ) => { + println!("----> [{collector_name}] check {expected_parent:?} against actual parent with Id={id:?}", id = actual_parent.id()); + expected_parent.check( + actual_parent, + format_args!("{ctx} to have a contextual parent span"), + collector_name, + ); } - Self::IsContextualRoot => "was actually a contextual root".to_string(), - Self::HasContextualParent(name) => { - format!("actually has a contextual parent with name='{name}'") + _ => { + // Ancestry types don't match at all. + let expected_description = match self { + Self::IsExplicitRoot => "be an explicit root", + Self::HasExplicitParent(_) => "have an explicit parent span", + Self::IsContextualRoot => "be a contextual root", + Self::HasContextualParent(_) => "have a contextual parent span", + }; + + let actual_description = match actual_ancestry { + ActualAncestry::IsExplicitRoot => "is actually an explicit root", + ActualAncestry::HasExplicitParent(_) => "actually has an explicit parent span", + ActualAncestry::IsContextualRoot => "is actually a contextual root", + ActualAncestry::HasContextualParent(_) => { + "actually has a contextual parent span" + } + }; + + panic!( + "{}", + format!( + "[{collector_name}] expected {ctx} to {expected_description}, \ + but it {actual_description}" + ) + ); } - }; - - assert_eq!( - self, - actual_ancestry, - "[{collector_name}] expected {ctx} to {expected_description}, but {actual_description}", - expected_description = expected_description(self), - actual_description = actual_description(actual_ancestry) - ); + } } } @@ -120,29 +151,29 @@ impl HasAncestry for &Attributes<'_> { pub(crate) fn get_ancestry( item: impl HasAncestry, lookup_current: impl FnOnce() -> Option, - span_name: impl FnOnce(&span::Id) -> Option<&str>, -) -> Ancestry { + actual_span: impl FnOnce(&span::Id) -> Option, +) -> ActualAncestry { if item.is_contextual() { if let Some(parent_id) = lookup_current() { - let contextual_parent_name = span_name(&parent_id).expect( + let contextual_parent_span = actual_span(&parent_id).expect( "tracing-mock: contextual parent cannot \ be looked up by ID. Was it recorded correctly?", ); - Ancestry::HasContextualParent(contextual_parent_name.to_string()) + ActualAncestry::HasContextualParent(contextual_parent_span) } else { - Ancestry::IsContextualRoot + ActualAncestry::IsContextualRoot } } else if item.is_root() { - Ancestry::IsExplicitRoot + ActualAncestry::IsExplicitRoot } else { let parent_id = item.parent().expect( "tracing-mock: is_contextual=false is_root=false \ but no explicit parent found. This is a bug!", ); - let explicit_parent_name = span_name(parent_id).expect( + let explicit_parent_span = actual_span(parent_id).expect( "tracing-mock: explicit parent cannot be looked \ up by ID. Is the provided Span ID valid: {parent_id}", ); - Ancestry::HasExplicitParent(explicit_parent_name.to_string()) + ActualAncestry::HasExplicitParent(explicit_parent_span) } } diff --git a/tracing-mock/src/event.rs b/tracing-mock/src/event.rs index b122d553e7..eab9955430 100644 --- a/tracing-mock/src/event.rs +++ b/tracing-mock/src/event.rs @@ -29,7 +29,12 @@ //! [`subscriber`]: mod@crate::subscriber //! [`expect::event`]: fn@crate::expect::event #![allow(missing_docs)] -use crate::{ancestry::Ancestry, expect, field, metadata::ExpectedMetadata, span}; +use crate::{ + ancestry::{ActualAncestry, ExpectedAncestry}, + expect, field, + metadata::ExpectedMetadata, + span, +}; use std::fmt; @@ -42,7 +47,7 @@ use std::fmt; #[derive(Default, Eq, PartialEq)] pub struct ExpectedEvent { pub(super) fields: Option, - pub(super) ancestry: Option, + pub(super) ancestry: Option, pub(super) in_spans: Option>, pub(super) metadata: ExpectedMetadata, } @@ -253,9 +258,10 @@ impl ExpectedEvent { } } - /// Configures this `ExpectedEvent` to expect the specified [`Ancestry`]. - /// An event's ancestry indicates whether is has a parent or is a root, and - /// whether the parent is explicitly or contextually assigned. + /// Configures this `ExpectedEvent` to expect the specified + /// [`ExpectedAncestry`]. An event's ancestry indicates whether is has a + /// parent or is a root, and whether the parent is explicitly or + /// contextually assigned. /// /// An _explicit_ parent span is one passed to the `event!` macro in the /// `parent:` field. If no `parent:` field is specified, then the event @@ -267,9 +273,34 @@ impl ExpectedEvent { /// /// # Examples /// - /// If `expect::has_explicit_parent("parent_name")` is passed - /// `with_ancestry` then the provided string is the name of the explicit - /// parent span to expect. + /// An explicit or contextual can be matched on an `ExpectedSpan`. + /// + /// ``` + /// use tracing::subscriber::with_default; + /// use tracing_mock::{subscriber, expect}; + /// + /// let parent = expect::span() + /// .named("parent_span") + /// .with_target("custom-target") + /// .at_level(tracing::Level::INFO); + /// let event = expect::event() + /// .with_ancestry(expect::has_explicit_parent(parent)); + /// + /// let (subscriber, handle) = subscriber::mock() + /// .event(event) + /// .run_with_handle(); + /// + /// with_default(subscriber, || { + /// let parent = tracing::info_span!(target: "custom-target", "parent_span"); + /// tracing::info!(parent: parent.id(), field = &"value"); + /// }); + /// + /// handle.assert_finished(); + /// ``` + /// The functions `expect::has_explicit_parent` and + /// `expect::has_contextual_parent` take `Into`, so a string + /// passed directly will match on a span with that name, or an + /// [`ExpectedId`] can be passed to match a span with that Id. /// /// ``` /// use tracing::subscriber::with_default; @@ -382,7 +413,9 @@ impl ExpectedEvent { /// /// handle.assert_finished(); /// ``` - pub fn with_ancestry(self, ancenstry: Ancestry) -> ExpectedEvent { + /// + /// [`ExpectedId`]: struct@crate::span::ExpectedId + pub fn with_ancestry(self, ancenstry: ExpectedAncestry) -> ExpectedEvent { Self { ancestry: Some(ancenstry), ..self @@ -506,7 +539,7 @@ impl ExpectedEvent { pub(crate) fn check( &mut self, event: &tracing::Event<'_>, - get_ancestry: impl FnOnce() -> Ancestry, + get_ancestry: impl FnOnce() -> ActualAncestry, subscriber_name: &str, ) { let meta = event.metadata(); diff --git a/tracing-mock/src/expect.rs b/tracing-mock/src/expect.rs index 95ad3176ca..aebcc9eecd 100644 --- a/tracing-mock/src/expect.rs +++ b/tracing-mock/src/expect.rs @@ -1,7 +1,7 @@ use std::fmt; use crate::{ - ancestry::Ancestry, + ancestry::ExpectedAncestry, event::ExpectedEvent, field::{ExpectedField, ExpectedFields, ExpectedValue}, span::{ExpectedId, ExpectedSpan, NewSpan}, @@ -71,26 +71,26 @@ pub fn id() -> ExpectedId { ExpectedId::new_unset() } -/// Convenience function that returns [`Ancestry::IsContextualRoot`]. -pub fn is_contextual_root() -> Ancestry { - Ancestry::IsContextualRoot +/// Convenience function that returns [`ExpectedAncestry::IsContextualRoot`]. +pub fn is_contextual_root() -> ExpectedAncestry { + ExpectedAncestry::IsContextualRoot } -/// Convenience function that returns [`Ancestry::HasContextualParent`] with +/// Convenience function that returns [`ExpectedAncestry::HasContextualParent`] with /// provided name. -pub fn has_contextual_parent>(name: S) -> Ancestry { - Ancestry::HasContextualParent(name.into()) +pub fn has_contextual_parent>(span: S) -> ExpectedAncestry { + ExpectedAncestry::HasContextualParent(span.into()) } -/// Convenience function that returns [`Ancestry::IsExplicitRoot`]. -pub fn is_explicit_root() -> Ancestry { - Ancestry::IsExplicitRoot +/// Convenience function that returns [`ExpectedAncestry::IsExplicitRoot`]. +pub fn is_explicit_root() -> ExpectedAncestry { + ExpectedAncestry::IsExplicitRoot } -/// Convenience function that returns [`Ancestry::HasExplicitParent`] with +/// Convenience function that returns [`ExpectedAncestry::HasExplicitParent`] with /// provided name. -pub fn has_explicit_parent>(name: S) -> Ancestry { - Ancestry::HasExplicitParent(name.into()) +pub fn has_explicit_parent>(span: S) -> ExpectedAncestry { + ExpectedAncestry::HasExplicitParent(span.into()) } impl Expect { diff --git a/tracing-mock/src/layer.rs b/tracing-mock/src/layer.rs index 9ecd71d8e9..84478ade9d 100644 --- a/tracing-mock/src/layer.rs +++ b/tracing-mock/src/layer.rs @@ -116,10 +116,10 @@ //! //! [`Layer`]: trait@tracing_subscriber::layer::Layer use crate::{ - ancestry::{get_ancestry, Ancestry, HasAncestry}, + ancestry::{get_ancestry, ActualAncestry, HasAncestry}, event::ExpectedEvent, expect::Expect, - span::{ExpectedSpan, NewSpan}, + span::{ActualSpan, ExpectedSpan, NewSpan}, subscriber::MockHandle, }; use tracing_core::{ @@ -778,66 +778,16 @@ impl MockLayerBuilder { } } -impl MockLayer { - fn check_span_ref<'spans, S>( - &self, - expected: &ExpectedSpan, - actual: &SpanRef<'spans, S>, - what_happened: impl fmt::Display, - ) where - S: LookupSpan<'spans>, - { - if let Some(exp_name) = expected.name() { - assert_eq!( - actual.name(), - exp_name, - "\n[{}] expected {} a span named {:?}\n\ - [{}] but it was named {:?} instead (span {} {:?})", - self.name, - what_happened, - exp_name, - self.name, - actual.name(), - actual.name(), - actual.id() - ); - } - - if let Some(exp_level) = expected.level() { - let actual_level = actual.metadata().level(); - assert_eq!( - actual_level, - &exp_level, - "\n[{}] expected {} a span at {:?}\n\ - [{}] but it was at {:?} instead (span {} {:?})", - self.name, - what_happened, - exp_level, - self.name, - actual_level, - actual.name(), - actual.id(), - ); - } - - if let Some(exp_target) = expected.target() { - let actual_target = actual.metadata().target(); - assert_eq!( - actual_target, - exp_target, - "\n[{}] expected {} a span with target {:?}\n\ - [{}] but it had the target {:?} instead (span {} {:?})", - self.name, - what_happened, - exp_target, - self.name, - actual_target, - actual.name(), - actual.id(), - ); - } +impl<'a, S> From<&SpanRef<'a, S>> for ActualSpan +where + S: LookupSpan<'a>, +{ + fn from(span_ref: &SpanRef<'a, S>) -> Self { + Self::new(span_ref.id(), Some(span_ref.metadata())) } +} +impl MockLayer { fn check_event_scope( &self, current_scope: Option>, @@ -856,10 +806,10 @@ impl MockLayer { actual.id(), expected ); - self.check_span_ref( - expected, - &actual, + expected.check( + &(&actual).into(), format_args!("the {}th span in the event's scope to be", i), + &self.name, ); i += 1; } @@ -955,7 +905,7 @@ where match self.expected.lock().unwrap().pop_front() { None => {} Some(Expect::Enter(ref expected_span)) => { - self.check_span_ref(expected_span, &span, "to enter"); + expected_span.check(&(&span).into(), "to enter", &self.name); } Some(ex) => ex.bad(&self.name, format_args!("entered span {:?}", span.name())), } @@ -976,7 +926,7 @@ where match self.expected.lock().unwrap().pop_front() { None => {} Some(Expect::Exit(ref expected_span)) => { - self.check_span_ref(expected_span, &span, "to exit"); + expected_span.check(&(&span).into(), "to exit", &self.name); let curr = self.current.lock().unwrap().pop(); assert_eq!( Some(id), @@ -1013,7 +963,7 @@ where // as failing the assertion can cause a double panic. if !::std::thread::panicking() { if let Some(ref span) = span { - self.check_span_ref(expected_span, span, "to close"); + expected_span.check(&span.into(), "to close a span", &self.name); } } true @@ -1042,14 +992,14 @@ where } } -fn context_get_ancestry(item: impl HasAncestry, ctx: &Context<'_, C>) -> Ancestry +fn context_get_ancestry(item: impl HasAncestry, ctx: &Context<'_, C>) -> ActualAncestry where C: Subscriber + for<'a> LookupSpan<'a>, { get_ancestry( item, || ctx.lookup_current().map(|s| s.id()), - |span_id| ctx.span(span_id).map(|span| span.name()), + |span_id| ctx.span(span_id).map(|span| (&span).into()), ) } diff --git a/tracing-mock/src/metadata.rs b/tracing-mock/src/metadata.rs index 49347434fe..a9d9ae3e23 100644 --- a/tracing-mock/src/metadata.rs +++ b/tracing-mock/src/metadata.rs @@ -1,5 +1,5 @@ use std::fmt; -use tracing::Metadata; +use tracing_core::Metadata; #[derive(Clone, Debug, Eq, PartialEq, Default)] pub(crate) struct ExpectedMetadata { @@ -9,48 +9,69 @@ pub(crate) struct ExpectedMetadata { } impl ExpectedMetadata { + /// Checks the given metadata against this expected metadata and panics if + /// there is a mismatch. + /// + /// The context `ctx` should fit into the followint sentence: + /// + /// > expected {ctx} named `expected_name`, but got one named `actual_name` + /// + /// Examples could be: + /// * a new span + /// * to enter a span + /// * an event + /// + /// # Panics + /// + /// This method will panic if any of the expectations that have been + /// specified are noto met. + /// pub(crate) fn check( &self, actual: &Metadata<'_>, - ctx: fmt::Arguments<'_>, + ctx: impl fmt::Display, subscriber_name: &str, ) { if let Some(ref expected_name) = self.name { - let name = actual.name(); + let actual_name = actual.name(); assert!( - expected_name == name, - "\n[{}] expected {} to be named `{}`, but got one named `{}`", - subscriber_name, - ctx, - expected_name, - name + expected_name == actual_name, + "{}", + format_args!( + "\n[{subscriber_name}] expected {ctx} named `{expected_name}`,\n\ + [{subscriber_name}] but got one named `{actual_name}` instead." + ), ) } if let Some(ref expected_level) = self.level { - let level = actual.level(); + let actual_level = actual.level(); assert!( - expected_level == level, - "\n[{}] expected {} to be at level `{:?}`, but it was at level `{:?}` instead", - subscriber_name, - ctx, - expected_level, - level, + expected_level == actual_level, + "{}", + format_args!( + "\n[{subscriber_name}] expected {ctx} at level `{expected_level:?}`,\n\ + [{subscriber_name}] but got one at level `{actual_level:?}` instead." + ), ) } if let Some(ref expected_target) = self.target { - let target = actual.target(); + let actual_target = actual.target(); assert!( - expected_target == target, - "\n[{}] expected {} to have target `{}`, but it had target `{}` instead", - subscriber_name, - ctx, - expected_target, - target, + expected_target == actual_target, + "{}", + format_args!( + "\n[{subscriber_name}] expected {ctx} with target `{expected_target}`,\n\ + [{subscriber_name}] but got one with target `{actual_target}` instead." + ), ) } } + + pub(crate) fn has_expectations(&self) -> bool { + self.name.is_some() || self.level.is_some() || self.target.is_some() + } } impl fmt::Display for ExpectedMetadata { diff --git a/tracing-mock/src/span.rs b/tracing-mock/src/span.rs index d00690331f..7e7b4303de 100644 --- a/tracing-mock/src/span.rs +++ b/tracing-mock/src/span.rs @@ -111,8 +111,10 @@ //! [`expect::span`]: fn@crate::expect::span #![allow(missing_docs)] use crate::{ - ancestry::Ancestry, expect, field::ExpectedFields, metadata::ExpectedMetadata, - subscriber::SpanState, + ancestry::{ActualAncestry, ExpectedAncestry}, + expect, + field::ExpectedFields, + metadata::ExpectedMetadata, }; use std::{ error, fmt, @@ -175,7 +177,7 @@ impl From<&ExpectedSpan> for ExpectedSpan { pub struct NewSpan { pub(crate) span: ExpectedSpan, pub(crate) fields: ExpectedFields, - pub(crate) ancestry: Option, + pub(crate) ancestry: Option, } pub fn named(name: I) -> ExpectedSpan @@ -185,6 +187,36 @@ where expect::span().named(name) } +pub(crate) struct ActualSpan { + id: tracing_core::span::Id, + metadata: Option<&'static tracing_core::Metadata<'static>>, +} + +impl ActualSpan { + pub(crate) fn new( + id: tracing_core::span::Id, + metadata: Option<&'static tracing_core::Metadata<'static>>, + ) -> Self { + Self { id, metadata } + } + + /// The Id of the actual span. + pub(crate) fn id(&self) -> tracing_core::span::Id { + self.id.clone() + } + + /// The metadata for the actual span if it is available. + pub(crate) fn metadata(&self) -> Option<&'static tracing_core::Metadata<'static>> { + self.metadata + } +} + +impl From<&tracing_core::span::Id> for ActualSpan { + fn from(id: &tracing_core::span::Id) -> Self { + Self::new(id.clone(), None) + } +} + /// A mock span ID. /// /// This ID makes it possible to link together calls to different @@ -230,7 +262,7 @@ impl ExpectedSpan { /// /// If only the name of the span needs to be validated, then /// instead of using the `named` method, a string can be passed - /// to the [`MockCollector`] functions directly. + /// to the [`MockSubscriber`] functions directly. /// /// ``` /// use tracing_mock::subscriber; @@ -266,7 +298,7 @@ impl ExpectedSpan { /// handle.assert_finished(); /// ``` /// - /// [`MockCollector`]: struct@crate::subscriber::MockCollector + /// [`MockSubscriber`]: struct@crate::subscriber::MockSubscriber pub fn named(self, name: I) -> Self where I: Into, @@ -515,9 +547,10 @@ impl ExpectedSpan { } } - /// Configures this `ExpectedSpan` to expect the specified [`Ancestry`]. A - /// span's ancestry indicates whether it has a parent or is a root span - /// and whether the parent is explitly or contextually assigned. + /// Configures this `ExpectedSpan` to expect the specified + /// [`ExpectedAncestry`]. A span's ancestry indicates whether it has a + /// parent or is a root span and whether the parent is explitly or + /// contextually assigned. /// /// **Note**: This method returns a [`NewSpan`] and as such, this /// expectation can only be validated when expecting a new span via @@ -535,9 +568,35 @@ impl ExpectedSpan { /// /// # Examples /// - /// If `expect::has_explicit_parent("parent_name")` is passed - /// `with_ancestry` then the provided string is the name of the explicit - /// parent span to expect. + /// An explicit or contextual parent can be matched on an `ExpectedSpan`. + /// + /// ``` + /// use tracing_mock::{subscriber, expect}; + /// + /// let parent = expect::span() + /// .named("parent_span") + /// .with_target("custom-target") + /// .at_level(tracing::Level::INFO); + /// let span = expect::span() + /// .with_ancestry(expect::has_explicit_parent(&parent)); + /// + /// let (subscriber, handle) = subscriber::mock() + /// .new_span(&parent) + /// .new_span(span) + /// .run_with_handle(); + /// + /// tracing::subscriber::with_default(subscriber, || { + /// let parent = tracing::info_span!(target: "custom-target", "parent_span"); + /// tracing::info_span!(parent: parent.id(), "span"); + /// }); + /// + /// handle.assert_finished(); + /// ``` + /// + /// The functions `expect::has_explicit_parent` and + /// `expect::has_contextual_parent` take `Into`, so a string + /// passed directly will match on a span with that name, or an + /// [`ExpectedId`] can be passed to match a span with that Id. /// /// ``` /// use tracing_mock::{subscriber, expect}; @@ -653,7 +712,7 @@ impl ExpectedSpan { /// [`MockSubscriber::enter`]: fn@crate::subscriber::MockSubscriber::enter /// [`MockSubscriber::exit`]: fn@crate::subscriber::MockSubscriber::exit /// [`MockSubscriber::new_span`]: fn@crate::subscriber::MockSubscriber::new_span - pub fn with_ancestry(self, ancestry: Ancestry) -> NewSpan { + pub fn with_ancestry(self, ancestry: ExpectedAncestry) -> NewSpan { NewSpan { ancestry: Some(ancestry), span: self, @@ -733,6 +792,10 @@ impl ExpectedSpan { } } + pub(crate) fn id(&self) -> Option<&ExpectedId> { + self.id.as_ref() + } + pub(crate) fn name(&self) -> Option<&str> { self.metadata.name.as_ref().map(String::as_ref) } @@ -745,20 +808,26 @@ impl ExpectedSpan { self.metadata.target.as_deref() } - pub(crate) fn check(&self, actual: &SpanState, subscriber_name: &str) { - let meta = actual.metadata(); - let name = meta.name(); - + pub(crate) fn check(&self, actual: &ActualSpan, ctx: impl fmt::Display, subscriber_name: &str) { if let Some(expected_id) = &self.id { - expected_id.check( - actual.id(), - format_args!("span `{}`", name), - subscriber_name, - ); + expected_id.check(&actual.id(), format_args!("{ctx} a span"), subscriber_name); } - self.metadata - .check(meta, format_args!("span `{}`", name), subscriber_name); + match actual.metadata() { + Some(actual_metadata) => self.metadata.check(actual_metadata, ctx, subscriber_name), + None => { + if self.metadata.has_expectations() { + panic!( + "{}", + format_args!( + "[{subscriber_name}] expected {ctx} a span with valid metadata, \ + but got one with unknown Id={actual_id}", + actual_id = actual.id().into_u64() + ) + ); + } + } + } } } @@ -766,6 +835,10 @@ impl fmt::Debug for ExpectedSpan { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut s = f.debug_struct("MockSpan"); + if let Some(id) = self.id() { + s.field("id", &id); + } + if let Some(name) = self.name() { s.field("name", &name); } @@ -805,13 +878,13 @@ where } impl NewSpan { - /// Configures this `NewSpan` to expect the specified [`Ancestry`]. A - /// span's ancestry indicates whether it has a parent or is a root span + /// Configures this `NewSpan` to expect the specified [`ExpectedAncestry`]. + /// A span's ancestry indicates whether it has a parent or is a root span /// and whether the parent is explitly or contextually assigned. /// /// For more information and examples, see the documentation on /// [`ExpectedSpan::with_ancestry`]. - pub fn with_ancestry(self, ancestry: Ancestry) -> NewSpan { + pub fn with_ancestry(self, ancestry: ExpectedAncestry) -> NewSpan { NewSpan { ancestry: Some(ancestry), ..self @@ -837,14 +910,14 @@ impl NewSpan { pub(crate) fn check( &mut self, span: &tracing_core::span::Attributes<'_>, - get_ancestry: impl FnOnce() -> Ancestry, + get_ancestry: impl FnOnce() -> ActualAncestry, subscriber_name: &str, ) { let meta = span.metadata(); let name = meta.name(); self.span .metadata - .check(meta, format_args!("span `{}`", name), subscriber_name); + .check(meta, "a new span", subscriber_name); let mut checker = self.fields.checker(name, subscriber_name); span.record(&mut checker); checker.finish(); @@ -906,6 +979,12 @@ impl PartialEq for ExpectedId { impl Eq for ExpectedId {} +impl fmt::Debug for ExpectedId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple("ExpectedId").field(&self.inner).finish() + } +} + impl ExpectedId { const UNSET: u64 = 0; @@ -925,21 +1004,33 @@ impl ExpectedId { Ok(()) } - pub(crate) fn check(&self, actual: u64, ctx: fmt::Arguments<'_>, subscriber_name: &str) { - let id = self.inner.load(Ordering::Relaxed); + pub(crate) fn check( + &self, + actual: &tracing_core::span::Id, + ctx: fmt::Arguments<'_>, + subscriber_name: &str, + ) { + let expected_id = self.inner.load(Ordering::Relaxed); + let actual_id = actual.into_u64(); assert!( - id != Self::UNSET, - "\n[{}] expected {} to have expected ID set, but it hasn't been, \ - perhaps this `ExpectedId` wasn't used in a call to `MockSubscriber::new_span()`?", - subscriber_name, - ctx, + expected_id != Self::UNSET, + "{}", + format!( + "\n[{subscriber_name}] expected {ctx} with an expected Id set,\n\ + [{subscriber_name}] but it hasn't been, perhaps this `ExpectedId` \ + wasn't used in a call to `new_span()`?" + ) ); assert_eq!( - id, actual, - "\n[{}] expected {} to have ID `{}`, but it has `{}` instead", - subscriber_name, ctx, id, actual, + expected_id, + actual_id, + "{}", + format_args!( + "\n[{subscriber_name}] expected {ctx} with Id `{expected_id}`,\n\ + [{subscriber_name}] but got one with Id `{actual_id}` instead", + ) ); } } diff --git a/tracing-mock/src/subscriber.rs b/tracing-mock/src/subscriber.rs index 8eca275a47..688e8aa64c 100644 --- a/tracing-mock/src/subscriber.rs +++ b/tracing-mock/src/subscriber.rs @@ -39,8 +39,8 @@ //! let (subscriber, handle) = subscriber::mock() //! // Enter a matching span //! .enter(&span) -//! // Record an event with message "collect parting message" -//! .event(expect::event().with_fields(expect::message("collect parting message"))) +//! // Record an event with message "subscriber parting message" +//! .event(expect::event().with_fields(expect::message("subscriber parting message"))) //! // Record a value for the field `parting` on a matching span //! .record(span.clone(), expect::field("parting").with_value(&"goodbye world!")) //! // Exit a matching span @@ -60,7 +60,7 @@ //! ); //! //! let _guard = span.enter(); -//! tracing::info!("collect parting message"); +//! tracing::info!("subscriber parting message"); //! let parting = "goodbye world!"; //! //! span.record("parting", &parting); @@ -80,9 +80,8 @@ //! let span = expect::span() //! .named("my_span"); //! let (subscriber, handle) = subscriber::mock() -//! // Enter a matching span //! .enter(&span) -//! .event(expect::event().with_fields(expect::message("collect parting message"))) +//! .event(expect::event().with_fields(expect::message("subscriber parting message"))) //! .record(&span, expect::field("parting").with_value(&"goodbye world!")) //! .exit(span) //! .only() @@ -99,7 +98,7 @@ //! //! // Don't enter the span. //! // let _guard = span.enter(); -//! tracing::info!("collect parting message"); +//! tracing::info!("subscriber parting message"); //! let parting = "goodbye world!"; //! //! span.record("parting", &parting); @@ -117,7 +116,7 @@ //! [main] expected to enter a span named `my_span` //! [main] but instead observed event Event { //! fields: ValueSet { -//! message: collect parting message, +//! message: subscriber parting message, //! callsite: Identifier(0x10eda3278), //! }, //! metadata: Metadata { @@ -143,7 +142,7 @@ use crate::{ event::ExpectedEvent, expect::Expect, field::ExpectedFields, - span::{ExpectedSpan, NewSpan}, + span::{ActualSpan, ExpectedSpan, NewSpan}, }; use std::{ collections::{HashMap, VecDeque}, @@ -161,19 +160,15 @@ use tracing::{ }; pub(crate) struct SpanState { - id: u64, + id: Id, name: &'static str, refs: usize, meta: &'static Metadata<'static>, } -impl SpanState { - pub(crate) fn id(&self) -> u64 { - self.id - } - - pub(crate) fn metadata(&self) -> &'static Metadata<'static> { - self.meta +impl From<&SpanState> for ActualSpan { + fn from(span_state: &SpanState) -> Self { + Self::new(span_state.id.clone(), Some(span_state.meta)) } } @@ -227,8 +222,8 @@ pub struct MockHandle(Arc>>, String); /// let (subscriber, handle) = subscriber::mock() /// // Enter a matching span /// .enter(&span) -/// // Record an event with message "collect parting message" -/// .event(expect::event().with_fields(expect::message("collect parting message"))) +/// // Record an event with message "subscriber parting message" +/// .event(expect::event().with_fields(expect::message("subscriber parting message"))) /// // Record a value for the field `parting` on a matching span /// .record(span.clone(), expect::field("parting").with_value(&"goodbye world!")) /// // Exit a matching span @@ -248,7 +243,7 @@ pub struct MockHandle(Arc>>, String); /// ); /// /// let _guard = span.enter(); -/// tracing::info!("collect parting message"); +/// tracing::info!("subscriber parting message"); /// let parting = "goodbye world!"; /// /// span.record("parting", &parting); @@ -394,7 +389,7 @@ where /// This function accepts `Into` instead of /// [`ExpectedSpan`] directly, so it can be used to test /// span fields and the span parent. This is because a - /// collector only receives the span fields and parent when + /// subscriber only receives the span fields and parent when /// a span is created, not when it is entered. /// /// The new span doesn't need to be entered for this expectation @@ -1057,7 +1052,7 @@ where { if expected.scope_mut().is_some() { unimplemented!( - "Expected scope for events is not supported with `MockCollector`." + "Expected scope for events is not supported with `MockSubscriber`." ) } } @@ -1070,7 +1065,7 @@ where .lock() .unwrap() .get(span_id) - .map(|span| span.name) + .map(|span| span.into()) }, ) }; @@ -1141,7 +1136,7 @@ where get_ancestry( span, || self.lookup_current(), - |span_id| spans.get(span_id).map(|span| span.name), + |span_id| spans.get(span_id).map(|span| span.into()), ) }, &self.name, @@ -1151,7 +1146,7 @@ where spans.insert( id.clone(), SpanState { - id: id.into_u64(), + id: id.clone(), name: meta.name(), refs: 1, meta, @@ -1167,7 +1162,7 @@ where match self.expected.lock().unwrap().pop_front() { None => {} Some(Expect::Enter(ref expected_span)) => { - expected_span.check(span, &self.name); + expected_span.check(&span.into(), "to enter a span", &self.name); } Some(ex) => ex.bad(&self.name, format_args!("entered span {:?}", span.name)), } @@ -1190,7 +1185,7 @@ where match self.expected.lock().unwrap().pop_front() { None => {} Some(Expect::Exit(ref expected_span)) => { - expected_span.check(span, &self.name); + expected_span.check(&span.into(), "to exit a span", &self.name); let curr = self.current.lock().unwrap().pop(); assert_eq!( Some(id), @@ -1206,27 +1201,34 @@ where } fn clone_span(&self, id: &Id) -> Id { - let name = self.spans.lock().unwrap().get_mut(id).map(|span| { - let name = span.name; - println!( - "[{}] clone_span: {}; id={:?}; refs={:?};", - self.name, name, id, span.refs - ); - span.refs += 1; - name - }); - if name.is_none() { - println!("[{}] clone_span: id={:?};", self.name, id); + let mut spans = self.spans.lock().unwrap(); + let mut span = spans.get_mut(id); + match span.as_deref_mut() { + Some(span) => { + println!( + "[{}] clone_span: {}; id={:?}; refs={:?};", + self.name, span.name, id, span.refs, + ); + span.refs += 1; + } + None => { + println!( + "[{}] clone_span: id={:?} (not found in span list);", + self.name, id + ); + } } + let mut expected = self.expected.lock().unwrap(); - let was_expected = if let Some(Expect::CloneSpan(ref span)) = expected.front() { - assert_eq!( - name, - span.name(), - "[{}] expected to clone a span named {:?}", - self.name, - span.name() - ); + let was_expected = if let Some(Expect::CloneSpan(ref expected_span)) = expected.front() { + match span { + Some(actual_span) => { + let actual_span: &_ = actual_span; + expected_span.check(&actual_span.into(), "to clone a span", &self.name); + } + // Check only by Id + None => expected_span.check(&id.into(), "to clone a span", &self.name), + } true } else { false diff --git a/tracing-mock/tests/event_ancestry.rs b/tracing-mock/tests/event_ancestry.rs index ff659615a6..2b20bc5401 100644 --- a/tracing-mock/tests/event_ancestry.rs +++ b/tracing-mock/tests/event_ancestry.rs @@ -5,7 +5,7 @@ //! that an event has a specific contextual or explicit parent. //! //! [`ExpectedEvent`]: crate::event::ExpectedEvent -use tracing::subscriber::with_default; +use tracing::{subscriber::with_default, Level}; use tracing_mock::{expect, subscriber}; #[test] @@ -27,8 +27,8 @@ fn contextual_parent() { #[test] #[should_panic( - expected = "to have a contextual parent with name='contextual parent', but \ - actually has a contextual parent with name='another parent'" + expected = "to have a contextual parent span named `contextual parent`,\n\ + [contextual_parent_wrong_name] but got one named `another parent` instead." )] fn contextual_parent_wrong_name() { let event = expect::event().with_ancestry(expect::has_contextual_parent("contextual parent")); @@ -46,11 +46,53 @@ fn contextual_parent_wrong_name() { handle.assert_finished(); } +#[test] +#[should_panic(expected = "to have a contextual parent span a span with Id `1`,\n\ + [contextual_parent_wrong_id] but got one with Id `2` instead")] +fn contextual_parent_wrong_id() { + let id = expect::id(); + let event = expect::event().with_ancestry(expect::has_contextual_parent(&id)); + + let (subscriber, handle) = subscriber::mock() + .new_span(&id) + .enter(expect::span()) + .event(event) + .run_with_handle(); + + with_default(subscriber, || { + let _span = tracing::info_span!("contextual parent"); + let _guard = tracing::info_span!("another parent").entered(); + tracing::info!(field = &"value"); + }); + + handle.assert_finished(); +} + #[test] #[should_panic( - expected = "to have a contextual parent with name='contextual parent', but was actually a \ - contextual root" + expected = "to have a contextual parent span at level `Level(Info)`,\n\ + [contextual_parent_wrong_level] but got one at level `Level(Debug)` instead." )] +fn contextual_parent_wrong_level() { + let parent = expect::span().at_level(Level::INFO); + let event = expect::event().with_ancestry(expect::has_contextual_parent(parent)); + + let (subscriber, handle) = subscriber::mock() + .enter(expect::span()) + .event(event) + .run_with_handle(); + + with_default(subscriber, || { + let _guard = tracing::debug_span!("contextual parent").entered(); + tracing::info!(field = &"value"); + }); + + handle.assert_finished(); +} + +#[test] +#[should_panic(expected = "to have a contextual parent span, but it is actually a \ + contextual root")] fn expect_contextual_parent_actual_contextual_root() { let event = expect::event().with_ancestry(expect::has_contextual_parent("contextual parent")); @@ -64,10 +106,8 @@ fn expect_contextual_parent_actual_contextual_root() { } #[test] -#[should_panic( - expected = "to have a contextual parent with name='contextual parent', but actually has an \ - explicit parent with name='explicit parent'" -)] +#[should_panic(expected = "to have a contextual parent span, but it actually has an \ + explicit parent span")] fn expect_contextual_parent_actual_explicit_parent() { let event = expect::event().with_ancestry(expect::has_contextual_parent("contextual parent")); @@ -82,10 +122,8 @@ fn expect_contextual_parent_actual_explicit_parent() { } #[test] -#[should_panic( - expected = "to have a contextual parent with name='contextual parent', but was actually an \ - explicit root" -)] +#[should_panic(expected = "to have a contextual parent span, but it is actually an \ + explicit root")] fn expect_contextual_parent_actual_explicit_root() { let event = expect::event().with_ancestry(expect::has_contextual_parent("contextual parent")); @@ -116,10 +154,7 @@ fn contextual_root() { } #[test] -#[should_panic( - expected = "to be a contextual root, but actually has a contextual parent with \ - name='contextual parent'" -)] +#[should_panic(expected = "to be a contextual root, but it actually has a contextual parent span")] fn expect_contextual_root_actual_contextual_parent() { let event = expect::event().with_ancestry(expect::is_contextual_root()); @@ -137,10 +172,7 @@ fn expect_contextual_root_actual_contextual_parent() { } #[test] -#[should_panic( - expected = "to be a contextual root, but actually has an explicit parent with \ - name='explicit parent'" -)] +#[should_panic(expected = "to be a contextual root, but it actually has an explicit parent span")] fn expect_contextual_root_actual_explicit_parent() { let event = expect::event().with_ancestry(expect::is_contextual_root()); @@ -155,7 +187,7 @@ fn expect_contextual_root_actual_explicit_parent() { } #[test] -#[should_panic(expected = "to be a contextual root, but was actually an explicit root")] +#[should_panic(expected = "to be a contextual root, but it is actually an explicit root")] fn expect_contextual_root_actual_explicit_root() { let event = expect::event().with_ancestry(expect::is_contextual_root()); @@ -188,8 +220,8 @@ fn explicit_parent() { #[test] #[should_panic( - expected = "to have an explicit parent with name='explicit parent', but actually has an \ - explicit parent with name='another parent'" + expected = "to have an explicit parent span named `explicit parent`,\n\ + [explicit_parent_wrong_name] but got one named `another parent` instead." )] fn explicit_parent_wrong_name() { let event = expect::event().with_ancestry(expect::has_explicit_parent("explicit parent")); @@ -205,10 +237,47 @@ fn explicit_parent_wrong_name() { } #[test] -#[should_panic( - expected = "to have an explicit parent with name='explicit parent', but actually has a \ - contextual parent with name='contextual parent'" -)] +#[should_panic(expected = "to have an explicit parent span a span with Id `1`,\n\ + [explicit_parent_wrong_id] but got one with Id `2` instead")] +fn explicit_parent_wrong_id() { + let id = expect::id(); + let event = expect::event().with_ancestry(expect::has_explicit_parent(&id)); + + let (subscriber, handle) = subscriber::mock() + .new_span(&id) + .new_span(expect::span()) + .event(event) + .run_with_handle(); + + with_default(subscriber, || { + let _span = tracing::info_span!("explicit parent"); + let another_span = tracing::info_span!("another parent"); + tracing::info!(parent: another_span.id(), field = &"value"); + }); + + handle.assert_finished(); +} + +#[test] +#[should_panic(expected = "to have an explicit parent span at level `Level(Info)`,\n\ + [explicit_parent_wrong_level] but got one at level `Level(Debug)` instead.")] +fn explicit_parent_wrong_level() { + let parent = expect::span().at_level(Level::INFO); + let event = expect::event().with_ancestry(expect::has_explicit_parent(parent)); + + let (subscriber, handle) = subscriber::mock().event(event).run_with_handle(); + + with_default(subscriber, || { + let span = tracing::debug_span!("explicit parent"); + tracing::info!(parent: span.id(), field = &"value"); + }); + + handle.assert_finished(); +} + +#[test] +#[should_panic(expected = "to have an explicit parent span, but it actually has a \ + contextual parent span")] fn expect_explicit_parent_actual_contextual_parent() { let event = expect::event().with_ancestry(expect::has_explicit_parent("explicit parent")); @@ -226,10 +295,8 @@ fn expect_explicit_parent_actual_contextual_parent() { } #[test] -#[should_panic( - expected = "to have an explicit parent with name='explicit parent', but was actually a \ - contextual root" -)] +#[should_panic(expected = "to have an explicit parent span, but it is actually a \ + contextual root")] fn expect_explicit_parent_actual_contextual_root() { let event = expect::event().with_ancestry(expect::has_explicit_parent("explicit parent")); @@ -243,10 +310,8 @@ fn expect_explicit_parent_actual_contextual_root() { } #[test] -#[should_panic( - expected = "to have an explicit parent with name='explicit parent', but was actually an \ - explicit root" -)] +#[should_panic(expected = "to have an explicit parent span, but it is actually an \ + explicit root")] fn expect_explicit_parent_actual_explicit_root() { let event = expect::event().with_ancestry(expect::has_explicit_parent("explicit parent")); @@ -281,10 +346,7 @@ fn explicit_root() { } #[test] -#[should_panic( - expected = "to be an explicit root, but actually has a contextual parent with \ - name='contextual parent'" -)] +#[should_panic(expected = "to be an explicit root, but it actually has a contextual parent span")] fn expect_explicit_root_actual_contextual_parent() { let event = expect::event().with_ancestry(expect::is_explicit_root()); @@ -302,7 +364,7 @@ fn expect_explicit_root_actual_contextual_parent() { } #[test] -#[should_panic(expected = "to be an explicit root, but was actually a contextual root")] +#[should_panic(expected = "to be an explicit root, but it is actually a contextual root")] fn expect_explicit_root_actual_contextual_root() { let event = expect::event().with_ancestry(expect::is_explicit_root()); @@ -316,9 +378,7 @@ fn expect_explicit_root_actual_contextual_root() { } #[test] -#[should_panic( - expected = "to be an explicit root, but actually has an explicit parent with name='explicit parent'" -)] +#[should_panic(expected = "to be an explicit root, but it actually has an explicit parent span")] fn expect_explicit_root_actual_explicit_parent() { let event = expect::event().with_ancestry(expect::is_explicit_root()); diff --git a/tracing-mock/tests/span_ancestry.rs b/tracing-mock/tests/span_ancestry.rs index 3c557a3017..0ab6648d81 100644 --- a/tracing-mock/tests/span_ancestry.rs +++ b/tracing-mock/tests/span_ancestry.rs @@ -6,7 +6,7 @@ //! //! [`ExpectedSpan`]: crate::span::ExpectedSpan //! -use tracing::subscriber::with_default; +use tracing::{subscriber::with_default, Level}; use tracing_mock::{expect, subscriber}; #[test] @@ -30,8 +30,8 @@ fn contextual_parent() { #[test] #[should_panic( - expected = "to have a contextual parent with name='contextual parent', but \ - actually has a contextual parent with name='another parent'" + expected = "to have a contextual parent span named `contextual parent`,\n\ + [contextual_parent_wrong_name] but got one named `another parent` instead." )] fn contextual_parent_wrong_name() { let span = expect::span() @@ -51,11 +51,58 @@ fn contextual_parent_wrong_name() { handle.assert_finished(); } +#[test] +#[should_panic(expected = "to have a contextual parent span a span with Id `1`,\n\ + [contextual_parent_wrong_id] but got one with Id `2` instead")] +fn contextual_parent_wrong_id() { + let id = expect::id(); + let span = expect::span() + .named("span") + .with_ancestry(expect::has_contextual_parent(&id)); + + let (subscriber, handle) = subscriber::mock() + .new_span(&id) + .new_span(expect::span()) + .enter(expect::span()) + .new_span(span) + .run_with_handle(); + + with_default(subscriber, || { + let _span = tracing::info_span!("contextual parent"); + let _guard = tracing::info_span!("another parent").entered(); + tracing::info_span!("span"); + }); + + handle.assert_finished(); +} + #[test] #[should_panic( - expected = "to have a contextual parent with name='contextual parent', but was actually a \ - contextual root" + expected = "to have a contextual parent span at level `Level(Info)`,\n\ + [contextual_parent_wrong_level] but got one at level `Level(Debug)` instead." )] +fn contextual_parent_wrong_level() { + let parent = expect::span().at_level(Level::INFO); + let span = expect::span() + .named("span") + .with_ancestry(expect::has_contextual_parent(parent)); + + let (subscriber, handle) = subscriber::mock() + .enter(expect::span()) + .new_span(span) + .run_with_handle(); + + with_default(subscriber, || { + let _guard = tracing::debug_span!("contextual parent").entered(); + tracing::info_span!("span"); + }); + + handle.assert_finished(); +} + +#[test] +#[should_panic(expected = "to have a contextual parent span, but it is actually a \ + contextual root")] fn expect_contextual_parent_actual_contextual_root() { let span = expect::span() .named("span") @@ -71,10 +118,8 @@ fn expect_contextual_parent_actual_contextual_root() { } #[test] -#[should_panic( - expected = "to have a contextual parent with name='contextual parent', but actually has an \ - explicit parent with name='explicit parent'" -)] +#[should_panic(expected = "to have a contextual parent span, but it actually has an \ + explicit parent span")] fn expect_contextual_parent_actual_explicit_parent() { let span = expect::span() .named("span") @@ -94,10 +139,8 @@ fn expect_contextual_parent_actual_explicit_parent() { } #[test] -#[should_panic( - expected = "to have a contextual parent with name='contextual parent', but was actually an \ - explicit root" -)] +#[should_panic(expected = "to have a contextual parent span, but it is actually an \ + explicit root")] fn expect_contextual_parent_actual_explicit_root() { let span = expect::span() .named("span") @@ -132,10 +175,7 @@ fn contextual_root() { } #[test] -#[should_panic( - expected = "to be a contextual root, but actually has a contextual parent with \ - name='contextual parent'" -)] +#[should_panic(expected = "to be a contextual root, but it actually has a contextual parent span")] fn expect_contextual_root_actual_contextual_parent() { let span = expect::span() .named("span") @@ -155,10 +195,7 @@ fn expect_contextual_root_actual_contextual_parent() { } #[test] -#[should_panic( - expected = "to be a contextual root, but actually has an explicit parent with \ - name='explicit parent'" -)] +#[should_panic(expected = "to be a contextual root, but it actually has an explicit parent span")] fn expect_contextual_root_actual_explicit_parent() { let span = expect::span() .named("span") @@ -178,7 +215,7 @@ fn expect_contextual_root_actual_explicit_parent() { } #[test] -#[should_panic(expected = "to be a contextual root, but was actually an explicit root")] +#[should_panic(expected = "to be a contextual root, but it is actually an explicit root")] fn expect_contextual_root_actual_explicit_root() { let span = expect::span() .named("span") @@ -218,8 +255,8 @@ fn explicit_parent() { #[test] #[should_panic( - expected = "to have an explicit parent with name='explicit parent', but actually has an \ - explicit parent with name='another parent'" + expected = "to have an explicit parent span named `explicit parent`,\n\ + [explicit_parent_wrong_name] but got one named `another parent` instead." )] fn explicit_parent_wrong_name() { let span = expect::span() @@ -240,10 +277,54 @@ fn explicit_parent_wrong_name() { } #[test] -#[should_panic( - expected = "to have an explicit parent with name='explicit parent', but actually has a \ - contextual parent with name='contextual parent'" -)] +#[should_panic(expected = "to have an explicit parent span a span with Id `1`,\n\ + [explicit_parent_wrong_id] but got one with Id `2` instead")] +fn explicit_parent_wrong_id() { + let id = expect::id(); + let span = expect::span() + .named("span") + .with_ancestry(expect::has_explicit_parent(&id)); + + let (subscriber, handle) = subscriber::mock() + .new_span(&id) + .new_span(expect::span()) + .new_span(span) + .run_with_handle(); + + with_default(subscriber, || { + let _span = tracing::info_span!("explicit parent"); + let another_span = tracing::info_span!("another parent"); + tracing::info_span!(parent: another_span.id(), "span"); + }); + + handle.assert_finished(); +} + +#[test] +#[should_panic(expected = "to have an explicit parent span at level `Level(Info)`,\n\ + [explicit_parent_wrong_level] but got one at level `Level(Debug)` instead.")] +fn explicit_parent_wrong_level() { + let parent = expect::span().at_level(Level::INFO); + let span = expect::span() + .named("span") + .with_ancestry(expect::has_explicit_parent(parent)); + + let (subscriber, handle) = subscriber::mock() + .new_span(expect::span()) + .new_span(span) + .run_with_handle(); + + with_default(subscriber, || { + let span = tracing::debug_span!("explicit parent"); + tracing::info_span!(parent: span.id(), "span"); + }); + + handle.assert_finished(); +} + +#[test] +#[should_panic(expected = "to have an explicit parent span, but it actually has a \ + contextual parent span")] fn expect_explicit_parent_actual_contextual_parent() { let span = expect::span() .named("span") @@ -263,10 +344,8 @@ fn expect_explicit_parent_actual_contextual_parent() { } #[test] -#[should_panic( - expected = "to have an explicit parent with name='explicit parent', but was actually a \ - contextual root" -)] +#[should_panic(expected = "to have an explicit parent span, but it is actually a \ + contextual root")] fn expect_explicit_parent_actual_contextual_root() { let span = expect::span() .named("span") @@ -282,10 +361,8 @@ fn expect_explicit_parent_actual_contextual_root() { } #[test] -#[should_panic( - expected = "to have an explicit parent with name='explicit parent', but was actually an \ - explicit root" -)] +#[should_panic(expected = "to have an explicit parent span, but it is actually an \ + explicit root")] fn expect_explicit_parent_actual_explicit_root() { let span = expect::span() .named("span") @@ -325,10 +402,7 @@ fn explicit_root() { } #[test] -#[should_panic( - expected = "to be an explicit root, but actually has a contextual parent with \ - name='contextual parent'" -)] +#[should_panic(expected = "to be an explicit root, but it actually has a contextual parent span")] fn expect_explicit_root_actual_contextual_parent() { let span = expect::span() .named("span") @@ -348,7 +422,7 @@ fn expect_explicit_root_actual_contextual_parent() { } #[test] -#[should_panic(expected = "to be an explicit root, but was actually a contextual root")] +#[should_panic(expected = "to be an explicit root, but it is actually a contextual root")] fn expect_explicit_root_actual_contextual_root() { let span = expect::span() .named("span") @@ -364,9 +438,7 @@ fn expect_explicit_root_actual_contextual_root() { } #[test] -#[should_panic( - expected = "to be an explicit root, but actually has an explicit parent with name='explicit parent'" -)] +#[should_panic(expected = "to be an explicit root, but it actually has an explicit parent span")] fn expect_explicit_root_actual_explicit_parent() { let span = expect::span() .named("span") From 33e48c431ad4d73e3a52220e044220a31a2622ac Mon Sep 17 00:00:00 2001 From: Hayden Stainsby Date: Sat, 2 Nov 2024 12:25:10 +0100 Subject: [PATCH 9/9] mock: complete API documentation including expect module (#2494) 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 `expect` module, which contains constructor functions for many of the other `tracing-mock` modules needs documentation and examples. This change adds documentation to the `expect` module and all the public APIs within it. This includes doctests on all the methods which serve as examples. The lint for `missing_docs` has been enabled for the entire `tracing-mock` crate! This has been done together with all the other lints that are enabled on the other crates in this project. The `event::msg("message")` constructor was removed, in favor of requiring an explicit construction via `expect::event().with_fields(expect::msg("message"))`. This is appropriate to reduce the API surface that would need to be supported in the future and also because the `event::msg` constructor could be overridden by a subsequent usage of `with_fields`. The shorthand `expect::message()` was renamed to `expect::msg` to make this change less burdensome. The `span::named("name")` constructor was removed, in favor of requiring an explicit construction via `expect::span.with_name("name")`. The latter isn't much longer and since #3097, a string with the name can be passed directly everywhere that an `ExpectedSpan` is required. This change also sets the `missing_docs` lint to warn for the entire `tracing-mock` crate, making it ready to publish (once backported). Refs: #539 --- tracing-mock/README.md | 12 +- tracing-mock/src/event.rs | 35 ++- tracing-mock/src/expect.rs | 213 +++++++++++++++++- tracing-mock/src/field.rs | 64 +++--- tracing-mock/src/layer.rs | 34 +-- tracing-mock/src/lib.rs | 35 +++ tracing-mock/src/metadata.rs | 1 + tracing-mock/src/span.rs | 60 +++-- tracing-mock/src/subscriber.rs | 83 +++---- tracing-subscriber/tests/env_filter/main.rs | 21 +- .../tests/env_filter/per_layer.rs | 12 +- .../tests/layer_filters/filter_scopes.rs | 71 ++++-- .../tests/layer_filters/main.rs | 10 +- .../tests/layer_filters/targets.rs | 3 +- .../tests/layer_filters/trees.rs | 36 ++- tracing/tests/event.rs | 4 +- tracing/tests/scoped_clobbers_default.rs | 8 +- 17 files changed, 480 insertions(+), 222 deletions(-) diff --git a/tracing-mock/README.md b/tracing-mock/README.md index 85d6774457..b9892efc76 100644 --- a/tracing-mock/README.md +++ b/tracing-mock/README.md @@ -4,7 +4,7 @@ # tracing-mock -Utilities for testing [`tracing`][tracing] and crates that uses it. +Utilities for testing [`tracing`] and crates that uses it. [![Documentation (master)][docs-master-badge]][docs-master-url] [![MIT licensed][mit-badge]][mit-url] @@ -71,14 +71,14 @@ Below is an example that checks that an event contains a message: ```rust use tracing::subscriber::with_default; -use tracing_mock::{subscriber, expect, field}; +use tracing_mock::{expect, subscriber}; fn yak_shaving() { tracing::info!("preparing to shave yaks"); } let (subscriber, handle) = subscriber::mock() - .event(expect::event().with_fields(expect::message("preparing to shave yaks"))) + .event(expect::event().with_fields(expect::msg("preparing to shave yaks"))) .only() .run_with_handle(); @@ -102,7 +102,7 @@ Below is a slightly more complex example. `tracing-mock` asserts that, in order: ```rust use tracing::subscriber::with_default; -use tracing_mock::{subscriber, expect}; +use tracing_mock::{expect, subscriber}; #[tracing::instrument] fn yak_shaving(number_of_yaks: u32) { @@ -128,7 +128,7 @@ let (subscriber, handle) = subscriber::mock() expect::event().with_fields( expect::field("number_of_yaks") .with_value(&yak_count) - .and(expect::message("preparing to shave yaks")) + .and(expect::msg("preparing to shave yaks")) .only(), ), ) @@ -136,7 +136,7 @@ let (subscriber, handle) = subscriber::mock() expect::event().with_fields( expect::field("all_yaks_shaved") .with_value(&true) - .and(expect::message("yak shaving completed.")) + .and(expect::msg("yak shaving completed.")) .only(), ), ) diff --git a/tracing-mock/src/event.rs b/tracing-mock/src/event.rs index eab9955430..1f2a062f3a 100644 --- a/tracing-mock/src/event.rs +++ b/tracing-mock/src/event.rs @@ -28,16 +28,15 @@ //! //! [`subscriber`]: mod@crate::subscriber //! [`expect::event`]: fn@crate::expect::event -#![allow(missing_docs)] +use std::fmt; + use crate::{ ancestry::{ActualAncestry, ExpectedAncestry}, - expect, field, + field, metadata::ExpectedMetadata, span, }; -use std::fmt; - /// An expected event. /// /// For a detailed description and examples, see the documentation for @@ -52,10 +51,6 @@ pub struct ExpectedEvent { pub(super) metadata: ExpectedMetadata, } -pub fn msg(message: impl fmt::Display) -> ExpectedEvent { - expect::event().with_fields(expect::message(message)) -} - impl ExpectedEvent { /// Sets a name to expect when matching an event. /// @@ -100,7 +95,7 @@ impl ExpectedEvent { /// /// ``` /// use tracing::subscriber::with_default; - /// use tracing_mock::{subscriber, expect}; + /// use tracing_mock::{expect, subscriber}; /// /// let event = expect::event() /// .with_fields(expect::field("field.name").with_value(&"field_value")); @@ -120,7 +115,7 @@ impl ExpectedEvent { /// /// ```should_panic /// use tracing::subscriber::with_default; - /// use tracing_mock::{subscriber, expect}; + /// use tracing_mock::{expect, subscriber}; /// /// let event = expect::event() /// .with_fields(expect::field("field.name").with_value(&"field_value")); @@ -156,7 +151,7 @@ impl ExpectedEvent { /// /// ``` /// use tracing::subscriber::with_default; - /// use tracing_mock::{subscriber, expect}; + /// use tracing_mock::{expect, subscriber}; /// /// let event = expect::event() /// .at_level(tracing::Level::WARN); @@ -177,7 +172,7 @@ impl ExpectedEvent { /// /// ```should_panic /// use tracing::subscriber::with_default; - /// use tracing_mock::{subscriber, expect}; + /// use tracing_mock::{expect, subscriber}; /// /// let event = expect::event() /// .at_level(tracing::Level::INFO); @@ -210,7 +205,7 @@ impl ExpectedEvent { /// /// ``` /// use tracing::subscriber::with_default; - /// use tracing_mock::{subscriber, expect}; + /// use tracing_mock::{expect, subscriber}; /// /// let event = expect::event() /// .with_target("some_target"); @@ -230,7 +225,7 @@ impl ExpectedEvent { /// /// ```should_panic /// use tracing::subscriber::with_default; - /// use tracing_mock::{subscriber, expect}; + /// use tracing_mock::{expect, subscriber}; /// /// let event = expect::event() /// .with_target("some_target"); @@ -277,7 +272,7 @@ impl ExpectedEvent { /// /// ``` /// use tracing::subscriber::with_default; - /// use tracing_mock::{subscriber, expect}; + /// use tracing_mock::{expect, subscriber}; /// /// let parent = expect::span() /// .named("parent_span") @@ -304,7 +299,7 @@ impl ExpectedEvent { /// /// ``` /// use tracing::subscriber::with_default; - /// use tracing_mock::{subscriber, expect}; + /// use tracing_mock::{expect, subscriber}; /// /// let event = expect::event() /// .with_ancestry(expect::has_explicit_parent("parent_span")); @@ -326,7 +321,7 @@ impl ExpectedEvent { /// /// ``` /// use tracing::subscriber::with_default; - /// use tracing_mock::{subscriber, expect}; + /// use tracing_mock::{expect, subscriber}; /// /// let event = expect::event() /// .with_ancestry(expect::is_explicit_root()); @@ -350,7 +345,7 @@ impl ExpectedEvent { /// /// ``` /// use tracing::subscriber::with_default; - /// use tracing_mock::{subscriber, expect}; + /// use tracing_mock::{expect, subscriber}; /// /// let event = expect::event() /// .with_ancestry(expect::has_contextual_parent("parent_span")); @@ -374,7 +369,7 @@ impl ExpectedEvent { /// /// ``` /// use tracing::subscriber::with_default; - /// use tracing_mock::{subscriber, expect}; + /// use tracing_mock::{expect, subscriber}; /// /// let event = expect::event() /// .with_ancestry(expect::is_contextual_root()); @@ -396,7 +391,7 @@ impl ExpectedEvent { /// /// ```should_panic /// use tracing::subscriber::with_default; - /// use tracing_mock::{subscriber, expect}; + /// use tracing_mock::{expect, subscriber}; /// /// let event = expect::event() /// .with_ancestry(expect::has_contextual_parent("parent_span")); diff --git a/tracing-mock/src/expect.rs b/tracing-mock/src/expect.rs index aebcc9eecd..39e95f73c4 100644 --- a/tracing-mock/src/expect.rs +++ b/tracing-mock/src/expect.rs @@ -1,3 +1,25 @@ +//! Construct expectations for traces which should be received +//! +//! This module contains constructors for expectations defined +//! in the [`event`], [`span`], and [`field`] modules. +//! +//! # Examples +//! +//! ``` +//! use tracing_mock::{expect, subscriber}; +//! +//! let (subscriber, handle) = subscriber::mock() +//! // Expect an event with message +//! .event(expect::event().with_fields(expect::msg("message"))) +//! .only() +//! .run_with_handle(); +//! +//! tracing::subscriber::with_default(subscriber, || { +//! tracing::info!("message"); +//! }); +//! +//! handle.assert_finished(); +//! ``` use std::fmt; use crate::{ @@ -23,12 +45,141 @@ pub(crate) enum Expect { Nothing, } +/// Create a new [`ExpectedEvent`]. +/// +/// For details on how to add additional assertions to the expected +/// event, see the [`event`] module and the [`ExpectedEvent`] struct. +/// +/// # Examples +/// +/// ``` +/// use tracing_mock::{expect, subscriber}; +/// +/// let (subscriber, handle) = subscriber::mock() +/// .event(expect::event()) +/// .run_with_handle(); +/// +/// tracing::subscriber::with_default(subscriber, || { +/// tracing::info!(field.name = "field_value"); +/// }); +/// +/// handle.assert_finished(); +/// ``` +/// +/// If we expect an event and instead record something else, the test +/// will fail: +/// +/// ```should_panic +/// use tracing_mock::{expect, subscriber}; +/// +/// let (subscriber, handle) = subscriber::mock() +/// .event(expect::event()) +/// .run_with_handle(); +/// +/// tracing::subscriber::with_default(subscriber, || { +/// let span = tracing::info_span!("span"); +/// let _guard = span.enter(); +/// }); +/// +/// handle.assert_finished(); +/// ``` pub fn event() -> ExpectedEvent { ExpectedEvent { ..Default::default() } } +/// Construct a new [`ExpectedSpan`]. +/// +/// For details on how to add additional assertions to the expected +/// span, see the [`span`] module and the [`ExpectedSpan`] and +/// [`NewSpan`] structs. +/// +/// # Examples +/// +/// ``` +/// use tracing_mock::{expect, subscriber}; +/// +/// let (subscriber, handle) = subscriber::mock() +/// .new_span(expect::span()) +/// .enter(expect::span()) +/// .run_with_handle(); +/// +/// tracing::subscriber::with_default(subscriber, || { +/// let span = tracing::info_span!("span"); +/// let _guard = span.enter(); +/// }); +/// +/// handle.assert_finished(); +/// ``` +/// +/// If we expect to enter a span and instead record something else, the test +/// will fail: +/// +/// ```should_panic +/// use tracing_mock::{expect, subscriber}; +/// +/// let (subscriber, handle) = subscriber::mock() +/// .enter(expect::span()) +/// .run_with_handle(); +/// +/// tracing::subscriber::with_default(subscriber, || { +/// tracing::info!(field.name = "field_value"); +/// }); +/// +/// handle.assert_finished(); +/// ``` +pub fn span() -> ExpectedSpan { + ExpectedSpan { + ..Default::default() + } +} + +/// Construct a new [`ExpectedField`]. +/// +/// For details on how to set the value of the expected field and +/// how to expect multiple fields, see the [`field`] module and the +/// [`ExpectedField`] and [`ExpectedFields`] structs. +/// span, see the [`span`] module and the [`ExpectedSpan`] and +/// [`NewSpan`] structs. +/// +/// # Examples +/// +/// ``` +/// use tracing_mock::{expect, subscriber}; +/// +/// let event = expect::event() +/// .with_fields(expect::field("field.name").with_value(&"field_value")); +/// +/// let (subscriber, handle) = subscriber::mock() +/// .event(event) +/// .run_with_handle(); +/// +/// tracing::subscriber::with_default(subscriber, || { +/// tracing::info!(field.name = "field_value"); +/// }); +/// +/// handle.assert_finished(); +/// ``` +/// +/// A different field value will cause the test to fail: +/// +/// ```should_panic +/// use tracing_mock::{expect, subscriber}; +/// +/// let event = expect::event() +/// .with_fields(expect::field("field.name").with_value(&"field_value")); +/// +/// let (subscriber, handle) = subscriber::mock() +/// .event(event) +/// .run_with_handle(); +/// +/// tracing::subscriber::with_default(subscriber, || { +/// tracing::info!(field.name = "different_field_value"); +/// }); +/// +/// handle.assert_finished(); +/// ``` pub fn field(name: K) -> ExpectedField where String: From, @@ -39,34 +190,74 @@ where } } -pub fn message(message: impl fmt::Display) -> ExpectedField { +/// Construct a new message [`ExpectedField`]. +/// +/// For details on how to set the value of the message field and +/// how to expect multiple fields, see the [`field`] module and the +/// [`ExpectedField`] and [`ExpectedFields`] structs. +/// +/// This is equivalent to +/// `expect::field("message").with_value(message)`. +/// +/// # Examples +/// +/// ``` +/// use tracing_mock::{expect, subscriber}; +/// +/// let event = expect::event().with_fields( +/// expect::msg("message")); +/// +/// let (subscriber, handle) = subscriber::mock() +/// .event(event) +/// .run_with_handle(); +/// +/// tracing::subscriber::with_default(subscriber, || { +/// tracing::info!("message"); +/// }); +/// +/// handle.assert_finished(); +/// ``` +/// +/// A different message value will cause the test to fail: +/// +/// ```should_panic +/// use tracing_mock::{expect, subscriber}; +/// +/// let event = expect::event().with_fields( +/// expect::msg("message")); +/// +/// let (subscriber, handle) = subscriber::mock() +/// .event(event) +/// .run_with_handle(); +/// +/// tracing::subscriber::with_default(subscriber, || { +/// tracing::info!("different message"); +/// }); +/// +/// handle.assert_finished(); +/// ``` +pub fn msg(message: impl fmt::Display) -> ExpectedField { ExpectedField { name: "message".to_string(), value: ExpectedValue::Debug(message.to_string()), } } -pub fn span() -> ExpectedSpan { - ExpectedSpan { - ..Default::default() - } -} - /// Returns a new, unset `ExpectedId`. /// /// The `ExpectedId` needs to be attached to a [`NewSpan`] or an -/// [`ExpectedSpan`] passed to [`MockCollector::new_span`] to +/// [`ExpectedSpan`] passed to [`MockSubscriber::new_span`] to /// ensure that it gets set. When the a clone of the same /// `ExpectedSpan` is attached to an [`ExpectedSpan`] and passed to -/// any other method on [`MockCollector`] that accepts it, it will +/// any other method on [`MockSubscriber`] that accepts it, it will /// ensure that it is exactly the same span used across those /// distinct expectations. /// /// For more details on how to use this struct, see the documentation /// on [`ExpectedSpan::with_id`]. /// -/// [`MockCollector`]: struct@crate::collector::MockCollector -/// [`MockCollector::new_span`]: fn@crate::collector::MockCollector::new_span +/// [`MockSubscriber`]: struct@crate::subscriber::MockSubscriber +/// [`MockSubscriber::new_span`]: fn@crate::subscriber::MockSubscriber::new_span pub fn id() -> ExpectedId { ExpectedId::new_unset() } diff --git a/tracing-mock/src/field.rs b/tracing-mock/src/field.rs index 6d1e73f98a..908ac4b370 100644 --- a/tracing-mock/src/field.rs +++ b/tracing-mock/src/field.rs @@ -14,7 +14,7 @@ //! specific name, without any expectation about the value: //! //! ``` -//! use tracing_mock::{subscriber, expect}; +//! use tracing_mock::{expect, subscriber}; //! //! let event = expect::event() //! .with_fields(expect::field("field_name")); @@ -34,7 +34,7 @@ //! each of them: //! //! ``` -//! use tracing_mock::{subscriber, expect}; +//! use tracing_mock::{expect, subscriber}; //! //! let event = expect::event().with_fields( //! expect::field("string_field") @@ -63,7 +63,7 @@ //! different: //! //! ```should_panic -//! use tracing_mock::{subscriber, expect}; +//! use tracing_mock::{expect, subscriber}; //! //! let event = expect::event() //! .with_fields(expect::field("field_name").with_value(&"value")); @@ -81,6 +81,8 @@ //! //! [`subscriber`]: mod@crate::subscriber //! [`expect::field`]: fn@crate::expect::field +use std::{collections::HashMap, fmt}; + use tracing::{ callsite, callsite::Callsite, @@ -88,8 +90,6 @@ use tracing::{ metadata::Kind, }; -use std::{collections::HashMap, fmt}; - /// An expectation for multiple fields. /// /// For a detailed description and examples, see the documentation for @@ -153,12 +153,13 @@ impl PartialEq for ExpectedValue { impl ExpectedField { /// Sets the value to expect when matching this field. /// - /// If the recorded value for this field diffs, the expectation will fail. + /// If the recorded value for this field is different, the + /// expectation will fail. /// /// # Examples /// /// ``` - /// use tracing_mock::{subscriber, expect}; + /// use tracing_mock::{expect, subscriber}; /// /// let event = expect::event() /// .with_fields(expect::field("field_name").with_value(&"value")); @@ -177,7 +178,7 @@ impl ExpectedField { /// A different value will cause the test to fail: /// /// ```should_panic - /// use tracing_mock::{subscriber, expect}; + /// use tracing_mock::{expect, subscriber}; /// /// let event = expect::event() /// .with_fields(expect::field("field_name").with_value(&"value")); @@ -208,7 +209,7 @@ impl ExpectedField { /// # Examples /// /// ``` - /// use tracing_mock::{subscriber, expect}; + /// use tracing_mock::{expect, subscriber}; /// /// let event = expect::event().with_fields( /// expect::field("field") @@ -233,7 +234,7 @@ impl ExpectedField { /// If the second field is not present, the test will fail: /// /// ```should_panic - /// use tracing_mock::{subscriber, expect}; + /// use tracing_mock::{expect, subscriber}; /// /// let event = expect::event().with_fields( /// expect::field("field") @@ -268,27 +269,30 @@ impl ExpectedField { /// /// # Examples /// - /// Check that only a single field is recorded. + /// The following test passes despite the recorded event having + /// fields that were not expected because `only` was not + /// used: /// /// ``` - /// use tracing_mock::{subscriber, expect}; + /// use tracing_mock::{expect, subscriber}; /// /// let event = expect::event() - /// .with_fields(expect::field("field").with_value(&"value").only()); + /// .with_fields(expect::field("field").with_value(&"value")); /// /// let (subscriber, handle) = subscriber::mock().event(event).run_with_handle(); /// /// tracing::subscriber::with_default(subscriber, || { - /// tracing::info!(field = "value"); + /// tracing::info!(field = "value", another_field = 42,); /// }); /// /// handle.assert_finished(); /// ``` /// - /// The following example fails because a second field is recorded. + /// If we include `only` on the `ExpectedField` then the test + /// will fail: /// /// ```should_panic - /// use tracing_mock::{subscriber, expect}; + /// use tracing_mock::{expect, subscriber}; /// /// let event = expect::event() /// .with_fields(expect::field("field").with_value(&"value").only()); @@ -323,9 +327,9 @@ impl From for ExpectedFields { impl ExpectedFields { /// Adds an additional [`ExpectedField`] to be matched. /// - /// _All_ fields must match for the expectation to pass. If any of - /// them are not present, if any of the values differs, the - /// expectation will fail. + /// All fields must match, if any of them are not present, or if + /// the value for any field is different, the expectation will + /// fail. /// /// This method performs the same function as /// [`ExpectedField::and`], but applies in the case where there are @@ -334,7 +338,7 @@ impl ExpectedFields { /// # Examples /// /// ``` - /// use tracing_mock::{subscriber, expect}; + /// use tracing_mock::{expect, subscriber}; /// /// let event = expect::event().with_fields( /// expect::field("field") @@ -362,7 +366,7 @@ impl ExpectedFields { /// event, the test will fail: /// /// ```should_panic - /// use tracing_mock::{subscriber, expect}; + /// use tracing_mock::{expect, subscriber}; /// /// let event = expect::event().with_fields( /// expect::field("field") @@ -391,7 +395,7 @@ impl ExpectedFields { self } - /// Asserts that no fields other than those specified should be + /// Indicates that no fields other than those specified should be /// expected. /// /// This method performs the same function as @@ -400,16 +404,16 @@ impl ExpectedFields { /// /// # Examples /// - /// Check that only two fields are recorded on the event. + /// The following test will pass, even though additional fields are + /// recorded on the event. /// /// ``` - /// use tracing_mock::{subscriber, expect}; + /// use tracing_mock::{expect, subscriber}; /// /// let event = expect::event().with_fields( /// expect::field("field") /// .with_value(&"value") - /// .and(expect::field("another_field").with_value(&42)) - /// .only(), + /// .and(expect::field("another_field").with_value(&42)), /// ); /// /// let (subscriber, handle) = subscriber::mock() @@ -420,16 +424,18 @@ impl ExpectedFields { /// tracing::info!( /// field = "value", /// another_field = 42, + /// a_third_field = true, /// ); /// }); /// /// handle.assert_finished(); /// ``` /// - /// The following example fails because a third field is recorded. + /// If we include `only` on the `ExpectedFields` then the test + /// will fail: /// /// ```should_panic - /// use tracing_mock::{subscriber, expect}; + /// use tracing_mock::{expect, subscriber}; /// /// let event = expect::event().with_fields( /// expect::field("field") @@ -558,7 +564,7 @@ impl<'a> Visit for CheckVisitor<'a> { } impl<'a> CheckVisitor<'a> { - pub fn finish(self) { + pub(crate) fn finish(self) { assert!( self.expect.fields.is_empty(), "[{}] {}missing {}", diff --git a/tracing-mock/src/layer.rs b/tracing-mock/src/layer.rs index 84478ade9d..011c161195 100644 --- a/tracing-mock/src/layer.rs +++ b/tracing-mock/src/layer.rs @@ -12,7 +12,7 @@ //! //! let (layer, handle) = layer::mock() //! // Expect a single event with a specified message -//! .event(expect::event().with_fields(expect::message("droids"))) +//! .event(expect::event().with_fields(expect::msg("droids"))) //! .run_with_handle(); //! //! // Use `set_default` to apply the `MockSubscriber` until the end @@ -33,7 +33,7 @@ //! their respective fields: //! //! ``` -//! use tracing_mock::{expect, field, layer}; +//! use tracing_mock::{expect, layer}; //! use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, Layer}; //! //! let span = expect::span() @@ -42,7 +42,7 @@ //! // Enter a matching span //! .enter(&span) //! // Record an event with message "collect parting message" -//! .event(expect::event().with_fields(expect::message("say hello"))) +//! .event(expect::event().with_fields(expect::msg("say hello"))) //! // Exit a matching span //! .exit(&span) //! // Expect no further messages to be recorded @@ -84,7 +84,7 @@ //! // Enter a matching span //! .enter(&span) //! // Record an event with message "collect parting message" -//! .event(expect::event().with_fields(expect::message("say hello"))) +//! .event(expect::event().with_fields(expect::msg("say hello"))) //! // Exit a matching span //! .exit(&span) //! // Expect no further messages to be recorded @@ -115,13 +115,12 @@ //! ``` //! //! [`Layer`]: trait@tracing_subscriber::layer::Layer -use crate::{ - ancestry::{get_ancestry, ActualAncestry, HasAncestry}, - event::ExpectedEvent, - expect::Expect, - span::{ActualSpan, ExpectedSpan, NewSpan}, - subscriber::MockHandle, +use std::{ + collections::VecDeque, + fmt, + sync::{Arc, Mutex}, }; + use tracing_core::{ span::{Attributes, Id, Record}, Event, Subscriber, @@ -131,10 +130,12 @@ use tracing_subscriber::{ registry::{LookupSpan, SpanRef}, }; -use std::{ - collections::VecDeque, - fmt, - sync::{Arc, Mutex}, +use crate::{ + ancestry::{get_ancestry, ActualAncestry, HasAncestry}, + event::ExpectedEvent, + expect::Expect, + span::{ActualSpan, ExpectedSpan, NewSpan}, + subscriber::MockHandle, }; /// Create a [`MockLayerBuilder`] used to construct a @@ -155,7 +156,7 @@ use std::{ /// // Enter a matching span /// .enter(&span) /// // Record an event with message "collect parting message" -/// .event(expect::event().with_fields(expect::message("say hello"))) +/// .event(expect::event().with_fields(expect::msg("say hello"))) /// // Exit a matching span /// .exit(&span) /// // Expect no further messages to be recorded @@ -207,7 +208,7 @@ pub fn mock() -> MockLayerBuilder { /// /// # Examples /// -/// The example from [`named`] could be rewritten as: +/// The example from [`MockLayerBuilder::named`] could be rewritten as: /// /// ```should_panic /// use tracing_mock::{expect, layer}; @@ -258,6 +259,7 @@ pub fn named(name: impl std::fmt::Display) -> MockLayerBuilder { /// /// [`layer`]: mod@crate::layer +#[derive(Debug)] pub struct MockLayerBuilder { expected: VecDeque, name: String, diff --git a/tracing-mock/src/lib.rs b/tracing-mock/src/lib.rs index 70f169eecf..5fa41422ae 100644 --- a/tracing-mock/src/lib.rs +++ b/tracing-mock/src/lib.rs @@ -1,4 +1,39 @@ #![doc = include_str!("../README.md")] +#![cfg_attr( + docsrs, + // Allows displaying cfgs/feature flags in the documentation. + feature(doc_cfg), + // Fail the docs build if any intra-docs links are broken + deny(rustdoc::broken_intra_doc_links), +)] +#![doc( + html_logo_url = "https://raw.githubusercontent.com/tokio-rs/tracing/master/assets/logo-type.png", + html_favicon_url = "https://raw.githubusercontent.com/tokio-rs/tracing/master/assets/favicon.ico", + issue_tracker_base_url = "https://github.com/tokio-rs/tracing/issues/" +)] +#![warn( + missing_debug_implementations, + missing_docs, + rust_2018_idioms, + unreachable_pub, + bad_style, + dead_code, + improper_ctypes, + non_shorthand_field_patterns, + no_mangle_generic_items, + overflowing_literals, + path_statements, + patterns_in_fns_without_body, + private_interfaces, + private_bounds, + unconditional_recursion, + unused, + unused_allocation, + unused_comparisons, + unused_parens, + while_true +)] + pub mod ancestry; pub mod event; pub mod expect; diff --git a/tracing-mock/src/metadata.rs b/tracing-mock/src/metadata.rs index a9d9ae3e23..b893731676 100644 --- a/tracing-mock/src/metadata.rs +++ b/tracing-mock/src/metadata.rs @@ -1,4 +1,5 @@ use std::fmt; + use tracing_core::Metadata; #[derive(Clone, Debug, Eq, PartialEq, Default)] diff --git a/tracing-mock/src/span.rs b/tracing-mock/src/span.rs index 7e7b4303de..fffc6a0be9 100644 --- a/tracing-mock/src/span.rs +++ b/tracing-mock/src/span.rs @@ -11,7 +11,7 @@ //! # Examples //! //! ``` -//! use tracing_mock::{subscriber, expect}; +//! use tracing_mock::{expect, subscriber}; //! //! let span = expect::span() //! .named("interesting_span") @@ -52,7 +52,7 @@ //! The following example asserts the name, level, parent, and fields of the span: //! //! ``` -//! use tracing_mock::{subscriber, expect}; +//! use tracing_mock::{expect, subscriber}; //! //! let span = expect::span() //! .named("interesting_span") @@ -87,7 +87,7 @@ //! the following test will fail due to a mismatch in the spans' names: //! //! ```should_panic -//! use tracing_mock::{subscriber, expect}; +//! use tracing_mock::{expect, subscriber}; //! //! let span = expect::span() //! .named("interesting_span") @@ -109,13 +109,6 @@ //! [`MockSubscriber`]: struct@crate::subscriber::MockSubscriber //! [`subscriber`]: mod@crate::subscriber //! [`expect::span`]: fn@crate::expect::span -#![allow(missing_docs)] -use crate::{ - ancestry::{ActualAncestry, ExpectedAncestry}, - expect, - field::ExpectedFields, - metadata::ExpectedMetadata, -}; use std::{ error, fmt, sync::{ @@ -124,6 +117,12 @@ use std::{ }, }; +use crate::{ + ancestry::{ActualAncestry, ExpectedAncestry}, + field::ExpectedFields, + metadata::ExpectedMetadata, +}; + /// A mock span. /// /// This is intended for use with the mock subscriber API in the @@ -180,13 +179,6 @@ pub struct NewSpan { pub(crate) ancestry: Option, } -pub fn named(name: I) -> ExpectedSpan -where - I: Into, -{ - expect::span().named(name) -} - pub(crate) struct ActualSpan { id: tracing_core::span::Id, metadata: Option<&'static tracing_core::Metadata<'static>>, @@ -244,7 +236,7 @@ impl ExpectedSpan { /// # Examples /// /// ``` - /// use tracing_mock::{subscriber, expect}; + /// use tracing_mock::{expect, subscriber}; /// /// let span = expect::span().named("span name"); /// @@ -282,7 +274,7 @@ impl ExpectedSpan { /// When the span name is different, the assertion will fail: /// /// ```should_panic - /// use tracing_mock::{subscriber, expect}; + /// use tracing_mock::{expect, subscriber}; /// /// let span = expect::span().named("span name"); /// @@ -334,7 +326,7 @@ impl ExpectedSpan { /// second: /// /// ``` - /// use tracing_mock::{subscriber, expect}; + /// use tracing_mock::{expect, subscriber}; /// let id1 = expect::id(); /// let span1 = expect::span().named("span").with_id(id1.clone()); /// let id2 = expect::id(); @@ -367,7 +359,7 @@ impl ExpectedSpan { /// example can be used. /// /// ``` - /// use tracing_mock::{subscriber, expect}; + /// use tracing_mock::{expect, subscriber}; /// let id1 = expect::id(); /// let id2 = expect::id(); /// @@ -397,7 +389,7 @@ impl ExpectedSpan { /// fail: /// /// ```should_panic - /// use tracing_mock::{subscriber, expect}; + /// use tracing_mock::{expect, subscriber}; /// let id1 = expect::id(); /// let span1 = expect::span().named("span").with_id(id1.clone()); /// let id2 = expect::id(); @@ -443,7 +435,7 @@ impl ExpectedSpan { /// # Examples /// /// ``` - /// use tracing_mock::{subscriber, expect}; + /// use tracing_mock::{expect, subscriber}; /// /// let span = expect::span() /// .at_level(tracing::Level::INFO); @@ -464,7 +456,7 @@ impl ExpectedSpan { /// recorded at any other level: /// /// ```should_panic - /// use tracing_mock::{subscriber, expect}; + /// use tracing_mock::{expect, subscriber}; /// /// let span = expect::span() /// .at_level(tracing::Level::INFO); @@ -498,7 +490,7 @@ impl ExpectedSpan { /// # Examples /// /// ``` - /// use tracing_mock::{subscriber, expect}; + /// use tracing_mock::{expect, subscriber}; /// /// let span = expect::span() /// .with_target("some_target"); @@ -518,7 +510,7 @@ impl ExpectedSpan { /// The test will fail if the target is different: /// /// ```should_panic - /// use tracing_mock::{subscriber, expect}; + /// use tracing_mock::{expect, subscriber}; /// /// let span = expect::span() /// .with_target("some_target"); @@ -571,7 +563,7 @@ impl ExpectedSpan { /// An explicit or contextual parent can be matched on an `ExpectedSpan`. /// /// ``` - /// use tracing_mock::{subscriber, expect}; + /// use tracing_mock::{expect, subscriber}; /// /// let parent = expect::span() /// .named("parent_span") @@ -599,7 +591,7 @@ impl ExpectedSpan { /// [`ExpectedId`] can be passed to match a span with that Id. /// /// ``` - /// use tracing_mock::{subscriber, expect}; + /// use tracing_mock::{expect, subscriber}; /// /// let span = expect::span() /// .with_ancestry(expect::has_explicit_parent("parent_span")); @@ -620,7 +612,7 @@ impl ExpectedSpan { /// In the following example, the expected span is an explicit root: /// /// ``` - /// use tracing_mock::{subscriber, expect}; + /// use tracing_mock::{expect, subscriber}; /// /// let span = expect::span() /// .with_ancestry(expect::is_explicit_root()); @@ -641,7 +633,7 @@ impl ExpectedSpan { /// `parent_span`: /// /// ```should_panic - /// use tracing_mock::{subscriber, expect}; + /// use tracing_mock::{expect, subscriber}; /// /// let parent_span = expect::span().named("parent_span"); /// let span = expect::span() @@ -666,7 +658,7 @@ impl ExpectedSpan { /// a contextually-determined root: /// /// ``` - /// use tracing_mock::{subscriber, expect}; + /// use tracing_mock::{expect, subscriber}; /// /// let span = expect::span() /// .with_ancestry(expect::is_contextual_root()); @@ -687,7 +679,7 @@ impl ExpectedSpan { /// `parent_span`: /// /// ```should_panic - /// use tracing_mock::{subscriber, expect}; + /// use tracing_mock::{expect, subscriber}; /// /// let parent_span = expect::span().named("parent_span"); /// let span = expect::span() @@ -741,7 +733,7 @@ impl ExpectedSpan { /// # Examples /// /// ``` - /// use tracing_mock::{subscriber, expect}; + /// use tracing_mock::{expect, subscriber}; /// /// let span = expect::span() /// .with_fields(expect::field("field.name").with_value(&"field_value")); @@ -760,7 +752,7 @@ impl ExpectedSpan { /// A different field value will cause the expectation to fail: /// /// ```should_panic - /// use tracing_mock::{subscriber, expect}; + /// use tracing_mock::{expect, subscriber}; /// /// let span = expect::span() /// .with_fields(expect::field("field.name").with_value(&"field_value")); diff --git a/tracing-mock/src/subscriber.rs b/tracing-mock/src/subscriber.rs index 688e8aa64c..3e2f57b33b 100644 --- a/tracing-mock/src/subscriber.rs +++ b/tracing-mock/src/subscriber.rs @@ -8,11 +8,11 @@ //! # Examples //! //! ``` -//! use tracing_mock::{subscriber, expect, field}; +//! use tracing_mock::{expect, subscriber, field}; //! //! let (subscriber, handle) = subscriber::mock() //! // Expect a single event with a specified message -//! .event(expect::event().with_fields(expect::message("droids"))) +//! .event(expect::event().with_fields(expect::msg("droids"))) //! .only() //! .run_with_handle(); //! @@ -32,7 +32,7 @@ //! their respective fields: //! //! ``` -//! use tracing_mock::{subscriber, expect, field}; +//! use tracing_mock::{expect, subscriber, field}; //! //! let span = expect::span() //! .named("my_span"); @@ -40,11 +40,11 @@ //! // Enter a matching span //! .enter(&span) //! // Record an event with message "subscriber parting message" -//! .event(expect::event().with_fields(expect::message("subscriber parting message"))) +//! .event(expect::event().with_fields(expect::msg("subscriber parting message"))) //! // Record a value for the field `parting` on a matching span -//! .record(span.clone(), expect::field("parting").with_value(&"goodbye world!")) +//! .record(&span, expect::field("parting").with_value(&"goodbye world!")) //! // Exit a matching span -//! .exit(&span) +//! .exit(span) //! // Expect no further messages to be recorded //! .only() //! // Return the subscriber and handle @@ -75,13 +75,13 @@ //! span before recording an event, the test will fail: //! //! ```should_panic -//! use tracing_mock::{subscriber, expect, field}; +//! use tracing_mock::{expect, subscriber, field}; //! //! let span = expect::span() //! .named("my_span"); //! let (subscriber, handle) = subscriber::mock() //! .enter(&span) -//! .event(expect::event().with_fields(expect::message("subscriber parting message"))) +//! .event(expect::event().with_fields(expect::msg("collect parting message"))) //! .record(&span, expect::field("parting").with_value(&"goodbye world!")) //! .exit(span) //! .only() @@ -137,13 +137,6 @@ //! //! [`Subscriber`]: trait@tracing::Subscriber //! [`MockSubscriber`]: struct@crate::subscriber::MockSubscriber -use crate::{ - ancestry::get_ancestry, - event::ExpectedEvent, - expect::Expect, - field::ExpectedFields, - span::{ActualSpan, ExpectedSpan, NewSpan}, -}; use std::{ collections::{HashMap, VecDeque}, sync::{ @@ -159,6 +152,14 @@ use tracing::{ Event, Metadata, Subscriber, }; +use crate::{ + ancestry::get_ancestry, + event::ExpectedEvent, + expect::Expect, + field::ExpectedFields, + span::{ActualSpan, ExpectedSpan, NewSpan}, +}; + pub(crate) struct SpanState { id: Id, name: &'static str, @@ -188,6 +189,7 @@ struct Running) -> bool> { /// for the methods and the [`subscriber`] module. /// /// [`subscriber`]: mod@crate::subscriber +#[derive(Debug)] pub struct MockSubscriber) -> bool> { expected: VecDeque, max_level: Option, @@ -204,6 +206,7 @@ pub struct MockSubscriber) -> bool> { /// module documentation. /// /// [`subscriber`]: mod@crate::subscriber +#[derive(Debug)] pub struct MockHandle(Arc>>, String); /// Create a new [`MockSubscriber`]. @@ -215,7 +218,7 @@ pub struct MockHandle(Arc>>, String); /// /// /// ``` -/// use tracing_mock::{subscriber, expect, field}; +/// use tracing_mock::{expect, subscriber, field}; /// /// let span = expect::span() /// .named("my_span"); @@ -223,11 +226,11 @@ pub struct MockHandle(Arc>>, String); /// // Enter a matching span /// .enter(&span) /// // Record an event with message "subscriber parting message" -/// .event(expect::event().with_fields(expect::message("subscriber parting message"))) +/// .event(expect::event().with_fields(expect::msg("subscriber parting message"))) /// // Record a value for the field `parting` on a matching span -/// .record(span.clone(), expect::field("parting").with_value(&"goodbye world!")) +/// .record(&span, expect::field("parting").with_value(&"goodbye world!")) /// // Exit a matching span -/// .exit(&span) +/// .exit(span) /// // Expect no further messages to be recorded /// .only() /// // Return the subscriber and handle @@ -293,7 +296,7 @@ where /// event, the test will fail: /// /// ```should_panic - /// use tracing_mock::{subscriber, expect}; + /// use tracing_mock::{expect, subscriber}; /// /// let (subscriber_1, handle_1) = subscriber::mock() /// .named("subscriber-1") @@ -348,7 +351,7 @@ where /// # Examples /// /// ``` - /// use tracing_mock::{subscriber, expect}; + /// use tracing_mock::{expect, subscriber}; /// /// let (subscriber, handle) = subscriber::mock() /// .event(expect::event()) @@ -364,7 +367,7 @@ where /// A span is entered before the event, causing the test to fail: /// /// ```should_panic - /// use tracing_mock::{subscriber, expect}; + /// use tracing_mock::{expect, subscriber}; /// /// let (subscriber, handle) = subscriber::mock() /// .event(expect::event()) @@ -402,7 +405,7 @@ where /// # Examples /// /// ``` - /// use tracing_mock::{subscriber, expect}; + /// use tracing_mock::{expect, subscriber}; /// /// let span = expect::span() /// .at_level(tracing::Level::INFO) @@ -423,7 +426,7 @@ where /// test to fail: /// /// ```should_panic - /// use tracing_mock::{subscriber, expect}; + /// use tracing_mock::{expect, subscriber}; /// /// let span = expect::span() /// .at_level(tracing::Level::INFO) @@ -462,7 +465,7 @@ where /// # Examples /// /// ``` - /// use tracing_mock::{subscriber, expect}; + /// use tracing_mock::{expect, subscriber}; /// /// let span = expect::span() /// .at_level(tracing::Level::INFO) @@ -485,7 +488,7 @@ where /// test to fail: /// /// ```should_panic - /// use tracing_mock::{subscriber, expect}; + /// use tracing_mock::{expect, subscriber}; /// /// let span = expect::span() /// .at_level(tracing::Level::INFO) @@ -529,7 +532,7 @@ where /// # Examples /// /// ``` - /// use tracing_mock::{subscriber, expect}; + /// use tracing_mock::{expect, subscriber}; /// /// let span = expect::span() /// .at_level(tracing::Level::INFO) @@ -551,7 +554,7 @@ where /// test to fail: /// /// ```should_panic - /// use tracing_mock::{subscriber, expect}; + /// use tracing_mock::{expect, subscriber}; /// /// let span = expect::span() /// .at_level(tracing::Level::INFO) @@ -591,7 +594,7 @@ where /// # Examples /// /// ``` - /// use tracing_mock::{subscriber, expect}; + /// use tracing_mock::{expect, subscriber}; /// /// let span = expect::span() /// .at_level(tracing::Level::INFO) @@ -612,7 +615,7 @@ where /// test to fail: /// /// ```should_panic - /// use tracing_mock::{subscriber, expect}; + /// use tracing_mock::{expect, subscriber}; /// /// let span = expect::span() /// .at_level(tracing::Level::INFO) @@ -674,7 +677,7 @@ where /// # Examples /// /// ``` - /// use tracing_mock::{subscriber, expect}; + /// use tracing_mock::{expect, subscriber}; /// /// let cause = expect::span().named("cause"); /// let consequence = expect::span().named("consequence"); @@ -698,7 +701,7 @@ where /// this test to fail: /// /// ```should_panic - /// use tracing_mock::{subscriber, expect}; + /// use tracing_mock::{expect, subscriber}; /// /// let cause = expect::span().named("cause"); /// let consequence = expect::span().named("consequence"); @@ -744,7 +747,7 @@ where /// # Examples /// /// ``` - /// use tracing_mock::{subscriber, expect}; + /// use tracing_mock::{expect, subscriber}; /// /// let span = expect::span() /// .named("my_span"); @@ -768,7 +771,7 @@ where /// causing the test to fail: /// /// ```should_panic - /// use tracing_mock::{subscriber, expect}; + /// use tracing_mock::{expect, subscriber}; /// /// let span = expect::span() /// .named("my_span"); @@ -809,7 +812,7 @@ where /// # Examples /// /// ``` - /// use tracing_mock::{subscriber, expect}; + /// use tracing_mock::{expect, subscriber}; /// /// let (subscriber, handle) = subscriber::mock() /// .with_filter(|meta| meta.level() <= &tracing::Level::WARN) @@ -852,7 +855,7 @@ where /// # Examples /// /// ``` - /// use tracing_mock::{subscriber, expect}; + /// use tracing_mock::{expect, subscriber}; /// /// let (subscriber, handle) = subscriber::mock() /// .with_max_level_hint(tracing::Level::INFO) @@ -888,7 +891,7 @@ where /// expect a single event, but receive three: /// /// ``` - /// use tracing_mock::{subscriber, expect}; + /// use tracing_mock::{expect, subscriber}; /// /// let (subscriber, handle) = subscriber::mock() /// .event(expect::event()) @@ -906,7 +909,7 @@ where /// After including `only`, the test will fail: /// /// ```should_panic - /// use tracing_mock::{subscriber, expect}; + /// use tracing_mock::{expect, subscriber}; /// /// let (subscriber, handle) = subscriber::mock() /// .event(expect::event()) @@ -963,7 +966,7 @@ where /// # Examples /// /// ``` - /// use tracing_mock::{subscriber, expect}; + /// use tracing_mock::{expect, subscriber}; /// /// // subscriber and handle are returned from `run_with_handle()` /// let (subscriber, handle) = subscriber::mock() @@ -1326,7 +1329,7 @@ impl MockHandle { /// # Examples /// /// ``` - /// use tracing_mock::{subscriber, expect}; + /// use tracing_mock::{expect, subscriber}; /// /// let (subscriber, handle) = subscriber::mock() /// .event(expect::event()) diff --git a/tracing-subscriber/tests/env_filter/main.rs b/tracing-subscriber/tests/env_filter/main.rs index c541197b10..35919004a1 100644 --- a/tracing-subscriber/tests/env_filter/main.rs +++ b/tracing-subscriber/tests/env_filter/main.rs @@ -3,7 +3,7 @@ mod per_layer; use tracing::{self, subscriber::with_default, Level}; -use tracing_mock::{expect, layer, span, subscriber}; +use tracing_mock::{expect, layer, subscriber}; use tracing_subscriber::{ filter::{EnvFilter, LevelFilter}, prelude::*, @@ -94,18 +94,17 @@ fn level_filter_event_with_target_and_span_global() { .parse() .expect("filter should parse"); - let cool_span = span::named("cool_span"); - let uncool_span = span::named("uncool_span"); + let cool_span = expect::span().named("cool_span"); let (layer, handle) = layer::mock() - .enter(cool_span.clone()) + .enter(&cool_span) .event( expect::event() .at_level(Level::DEBUG) .in_scope(vec![cool_span.clone()]), ) .exit(cool_span) - .enter(uncool_span.clone()) - .exit(uncool_span) + .enter("uncool_span") + .exit("uncool_span") .only() .run_with_handle(); @@ -330,7 +329,7 @@ mod per_layer_filter { .parse() .expect("filter should parse"); - let cool_span = span::named("cool_span"); + let cool_span = expect::span().named("cool_span"); let (layer, handle) = layer::mock() .enter(cool_span.clone()) .event( @@ -422,8 +421,8 @@ mod per_layer_filter { let filter: EnvFilter = "info,[cool_span]=debug" .parse() .expect("filter should parse"); - let cool_span = span::named("cool_span"); - let uncool_span = span::named("uncool_span"); + let cool_span = expect::span().named("cool_span"); + let uncool_span = expect::span().named("uncool_span"); let (layer, finished) = layer::mock() .event(expect::event().at_level(Level::INFO)) .enter(cool_span.clone()) @@ -493,7 +492,7 @@ mod per_layer_filter { // Test that multiple dynamic (span) filters only apply to the layers // they're attached to. let (layer1, handle1) = { - let span = span::named("span1"); + let span = expect::span().named("span1"); let filter: EnvFilter = "[span1]=debug".parse().expect("filter 1 should parse"); let (layer, handle) = layer::named("layer1") .enter(span.clone()) @@ -509,7 +508,7 @@ mod per_layer_filter { }; let (layer2, handle2) = { - let span = span::named("span2"); + let span = expect::span().named("span2"); let filter: EnvFilter = "[span2]=info".parse().expect("filter 2 should parse"); let (layer, handle) = layer::named("layer2") .enter(span.clone()) diff --git a/tracing-subscriber/tests/env_filter/per_layer.rs b/tracing-subscriber/tests/env_filter/per_layer.rs index fe6031f263..d606b1c701 100644 --- a/tracing-subscriber/tests/env_filter/per_layer.rs +++ b/tracing-subscriber/tests/env_filter/per_layer.rs @@ -2,7 +2,7 @@ //! `Layer` filter). #![cfg(feature = "registry")] use super::*; -use tracing_mock::{layer, span}; +use tracing_mock::{expect, layer}; #[test] fn level_filter_event() { @@ -92,7 +92,7 @@ fn level_filter_event_with_target_and_span() { .parse() .expect("filter should parse"); - let cool_span = span::named("cool_span"); + let cool_span = expect::span().named("cool_span"); let (layer, handle) = layer::mock() .enter(cool_span.clone()) .event( @@ -184,8 +184,8 @@ fn span_name_filter_is_dynamic() { let filter: EnvFilter = "info,[cool_span]=debug" .parse() .expect("filter should parse"); - let cool_span = span::named("cool_span"); - let uncool_span = span::named("uncool_span"); + let cool_span = expect::span().named("cool_span"); + let uncool_span = expect::span().named("uncool_span"); let (layer, finished) = layer::mock() .event(expect::event().at_level(Level::INFO)) .enter(cool_span.clone()) @@ -255,7 +255,7 @@ fn multiple_dynamic_filters() { // Test that multiple dynamic (span) filters only apply to the layers // they're attached to. let (layer1, handle1) = { - let span = span::named("span1"); + let span = expect::span().named("span1"); let filter: EnvFilter = "[span1]=debug".parse().expect("filter 1 should parse"); let (layer, handle) = layer::named("layer1") .enter(span.clone()) @@ -271,7 +271,7 @@ fn multiple_dynamic_filters() { }; let (layer2, handle2) = { - let span = span::named("span2"); + let span = expect::span().named("span2"); let filter: EnvFilter = "[span2]=info".parse().expect("filter 2 should parse"); let (layer, handle) = layer::named("layer2") .enter(span.clone()) diff --git a/tracing-subscriber/tests/layer_filters/filter_scopes.rs b/tracing-subscriber/tests/layer_filters/filter_scopes.rs index 02d000748f..5ff4eecf02 100644 --- a/tracing-subscriber/tests/layer_filters/filter_scopes.rs +++ b/tracing-subscriber/tests/layer_filters/filter_scopes.rs @@ -1,5 +1,5 @@ use super::*; -use tracing_mock::{event, expect, layer::MockLayer}; +use tracing_mock::{expect, layer::MockLayer}; #[test] fn filters_span_scopes() { @@ -8,12 +8,16 @@ fn filters_span_scopes() { .enter(expect::span().at_level(Level::INFO)) .enter(expect::span().at_level(Level::WARN)) .enter(expect::span().at_level(Level::ERROR)) - .event(event::msg("hello world").in_scope(vec![ - expect::span().at_level(Level::ERROR), - expect::span().at_level(Level::WARN), - expect::span().at_level(Level::INFO), - expect::span().at_level(Level::DEBUG), - ])) + .event( + expect::event() + .with_fields(expect::msg("hello world")) + .in_scope(vec![ + expect::span().at_level(Level::ERROR), + expect::span().at_level(Level::WARN), + expect::span().at_level(Level::INFO), + expect::span().at_level(Level::DEBUG), + ]), + ) .exit(expect::span().at_level(Level::ERROR)) .exit(expect::span().at_level(Level::WARN)) .exit(expect::span().at_level(Level::INFO)) @@ -24,11 +28,15 @@ fn filters_span_scopes() { .enter(expect::span().at_level(Level::INFO)) .enter(expect::span().at_level(Level::WARN)) .enter(expect::span().at_level(Level::ERROR)) - .event(event::msg("hello world").in_scope(vec![ - expect::span().at_level(Level::ERROR), - expect::span().at_level(Level::WARN), - expect::span().at_level(Level::INFO), - ])) + .event( + expect::event() + .with_fields(expect::msg("hello world")) + .in_scope(vec![ + expect::span().at_level(Level::ERROR), + expect::span().at_level(Level::WARN), + expect::span().at_level(Level::INFO), + ]), + ) .exit(expect::span().at_level(Level::ERROR)) .exit(expect::span().at_level(Level::WARN)) .exit(expect::span().at_level(Level::INFO)) @@ -37,10 +45,14 @@ fn filters_span_scopes() { let (warn_layer, warn_handle) = layer::named("warn") .enter(expect::span().at_level(Level::WARN)) .enter(expect::span().at_level(Level::ERROR)) - .event(event::msg("hello world").in_scope(vec![ - expect::span().at_level(Level::ERROR), - expect::span().at_level(Level::WARN), - ])) + .event( + expect::event() + .with_fields(expect::msg("hello world")) + .in_scope(vec![ + expect::span().at_level(Level::ERROR), + expect::span().at_level(Level::WARN), + ]), + ) .exit(expect::span().at_level(Level::ERROR)) .exit(expect::span().at_level(Level::WARN)) .only() @@ -72,12 +84,17 @@ fn filters_interleaved_span_scopes() { layer::named(format!("target_{}", target)) .enter(expect::span().with_target(target)) .enter(expect::span().with_target(target)) - .event(event::msg("hello world").in_scope(vec![ - expect::span().with_target(target), - expect::span().with_target(target), - ])) .event( - event::msg("hello to my target") + expect::event() + .with_fields(expect::msg("hello world")) + .in_scope(vec![ + expect::span().with_target(target), + expect::span().with_target(target), + ]), + ) + .event( + expect::event() + .with_fields(expect::msg("hello to my target")) .in_scope(vec![ expect::span().with_target(target), expect::span().with_target(target), @@ -95,10 +112,14 @@ fn filters_interleaved_span_scopes() { let (all_layer, all_handle) = layer::named("all") .enter(expect::span().with_target("b")) .enter(expect::span().with_target("a")) - .event(event::msg("hello world").in_scope(vec![ - expect::span().with_target("a"), - expect::span().with_target("b"), - ])) + .event( + expect::event() + .with_fields(expect::msg("hello world")) + .in_scope(vec![ + expect::span().with_target("a"), + expect::span().with_target("b"), + ]), + ) .exit(expect::span().with_target("a")) .exit(expect::span().with_target("b")) .only() diff --git a/tracing-subscriber/tests/layer_filters/main.rs b/tracing-subscriber/tests/layer_filters/main.rs index 80b929cbd6..90e2f0eedf 100644 --- a/tracing-subscriber/tests/layer_filters/main.rs +++ b/tracing-subscriber/tests/layer_filters/main.rs @@ -9,7 +9,7 @@ mod trees; mod vec; use tracing::{level_filters::LevelFilter, Level}; -use tracing_mock::{event, expect, layer, subscriber}; +use tracing_mock::{expect, layer, subscriber}; use tracing_subscriber::{filter, prelude::*, Layer}; #[test] @@ -159,18 +159,18 @@ fn global_filters_affect_subscriber_filters() { #[test] fn filter_fn() { let (all, all_handle) = layer::named("all_targets") - .event(event::msg("hello foo")) - .event(event::msg("hello bar")) + .event(expect::event().with_fields(expect::msg("hello foo"))) + .event(expect::event().with_fields(expect::msg("hello bar"))) .only() .run_with_handle(); let (foo, foo_handle) = layer::named("foo_target") - .event(event::msg("hello foo")) + .event(expect::event().with_fields(expect::msg("hello foo"))) .only() .run_with_handle(); let (bar, bar_handle) = layer::named("bar_target") - .event(event::msg("hello bar")) + .event(expect::event().with_fields(expect::msg("hello bar"))) .only() .run_with_handle(); diff --git a/tracing-subscriber/tests/layer_filters/targets.rs b/tracing-subscriber/tests/layer_filters/targets.rs index 19bcff1489..d71306ff8d 100644 --- a/tracing-subscriber/tests/layer_filters/targets.rs +++ b/tracing-subscriber/tests/layer_filters/targets.rs @@ -1,5 +1,4 @@ use super::*; -use tracing_mock::event; use tracing_subscriber::{ filter::{filter_fn, Targets}, prelude::*, @@ -39,7 +38,7 @@ fn inner_layer_short_circuits() { // evaluation, we aren't left with a "dirty" per-layer filter state. let (layer, handle) = layer::mock() - .event(event::msg("hello world")) + .event(expect::event().with_fields(expect::msg("hello world"))) .only() .run_with_handle(); diff --git a/tracing-subscriber/tests/layer_filters/trees.rs b/tracing-subscriber/tests/layer_filters/trees.rs index a0dbbbe907..4cab020c9f 100644 --- a/tracing-subscriber/tests/layer_filters/trees.rs +++ b/tracing-subscriber/tests/layer_filters/trees.rs @@ -1,5 +1,5 @@ use super::*; -use tracing_mock::{event, expect, layer::MockLayer}; +use tracing_mock::{expect, layer::MockLayer}; #[test] fn basic_trees() { @@ -70,9 +70,13 @@ fn filter_span_scopes() { fn target_layer(target: &'static str) -> (MockLayer, subscriber::MockHandle) { layer::named(format!("target_{}", target)) .enter(expect::span().with_target(target).at_level(Level::INFO)) - .event(event::msg("hello world").in_scope(vec![ - expect::span().with_target(target).at_level(Level::INFO), - ])) + .event( + expect::event() + .with_fields(expect::msg("hello world")) + .in_scope(vec![expect::span() + .with_target(target) + .at_level(Level::INFO)]), + ) .exit(expect::span().with_target(target).at_level(Level::INFO)) .only() .run_with_handle() @@ -83,10 +87,14 @@ fn filter_span_scopes() { let (info_layer, info_handle) = layer::named("info") .enter(expect::span().with_target("b").at_level(Level::INFO)) .enter(expect::span().with_target("a").at_level(Level::INFO)) - .event(event::msg("hello world").in_scope(vec![ - expect::span().with_target("a").at_level(Level::INFO), - expect::span().with_target("b").at_level(Level::INFO), - ])) + .event( + expect::event() + .with_fields(expect::msg("hello world")) + .in_scope(vec![ + expect::span().with_target("a").at_level(Level::INFO), + expect::span().with_target("b").at_level(Level::INFO), + ]), + ) .exit(expect::span().with_target("a").at_level(Level::INFO)) .exit(expect::span().with_target("b").at_level(Level::INFO)) .only() @@ -103,14 +111,20 @@ fn filter_span_scopes() { .enter(expect::span().with_target("b").at_level(Level::INFO)) .enter(expect::span().with_target("a").at_level(Level::INFO)) .enter(expect::span().with_target("b").at_level(Level::TRACE)) - .event(event::msg("hello world").in_scope(full_scope.clone())) .event( - event::msg("hello to my target") + expect::event() + .with_fields(expect::msg("hello world")) + .in_scope(full_scope.clone()), + ) + .event( + expect::event() + .with_fields(expect::msg("hello to my target")) .with_target("a") .in_scope(full_scope.clone()), ) .event( - event::msg("hello to my target") + expect::event() + .with_fields(expect::msg("hello to my target")) .with_target("b") .in_scope(full_scope), ) diff --git a/tracing/tests/event.rs b/tracing/tests/event.rs index 99e85d5582..175b21bedf 100644 --- a/tracing/tests/event.rs +++ b/tracing/tests/event.rs @@ -86,7 +86,7 @@ fn message_without_delims() { .and( expect::field("question").with_value(&"life, the universe, and everything"), ) - .and(expect::message(format_args!( + .and(expect::msg(format_args!( "hello from my event! tricky? {:?}!", true ))) @@ -115,7 +115,7 @@ fn string_message_without_delims() { .and( expect::field("question").with_value(&"life, the universe, and everything"), ) - .and(expect::message(format_args!("hello from my event"))) + .and(expect::msg(format_args!("hello from my event"))) .only(), ), ) diff --git a/tracing/tests/scoped_clobbers_default.rs b/tracing/tests/scoped_clobbers_default.rs index dfd6fc9de2..41bb138eda 100644 --- a/tracing/tests/scoped_clobbers_default.rs +++ b/tracing/tests/scoped_clobbers_default.rs @@ -1,18 +1,18 @@ #![cfg(feature = "std")] -use tracing_mock::*; +use tracing_mock::{expect, subscriber}; #[test] fn scoped_clobbers_global() { // Reproduces https://github.com/tokio-rs/tracing/issues/2050 let (scoped, scoped_handle) = subscriber::mock() - .event(event::msg("before global")) - .event(event::msg("before drop")) + .event(expect::event().with_fields(expect::msg("before global"))) + .event(expect::event().with_fields(expect::msg("before drop"))) .only() .run_with_handle(); let (global, global_handle) = subscriber::mock() - .event(event::msg("after drop")) + .event(expect::event().with_fields(expect::msg("after drop"))) .only() .run_with_handle();