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/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/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 = [] 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..365a72cb4e 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<(), ()> { @@ -199,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()) @@ -210,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()) @@ -260,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))) @@ -330,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()) @@ -338,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()) @@ -357,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 bee7aa5f4e..b2c5339c56 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; @@ -159,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/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/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-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-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..4e1b597fb3 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() { @@ -68,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/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/README.md b/tracing-mock/README.md index 299a737534..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(field::msg("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, field}; +use tracing_mock::{expect, subscriber}; #[tracing::instrument] fn yak_shaving(number_of_yaks: u32) { @@ -121,14 +121,14 @@ 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( expect::event().with_fields( expect::field("number_of_yaks") .with_value(&yak_count) - .and(field::msg("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(field::msg("yak shaving completed.")) + .and(expect::msg("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/ancestry.rs b/tracing-mock/src/ancestry.rs new file mode 100644 index 0000000000..817e30fef0 --- /dev/null +++ b/tracing-mock/src/ancestry.rs @@ -0,0 +1,179 @@ +//! Define the ancestry of an event or span. +//! +//! 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 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 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, +} + +pub(crate) enum ActualAncestry { + HasExplicitParent(ActualSpan), + IsExplicitRoot, + HasContextualParent(ActualSpan), + IsContextualRoot, +} + +impl ExpectedAncestry { + #[track_caller] + pub(crate) fn check( + &self, + actual_ancestry: &ActualAncestry, + ctx: impl std::fmt::Display, + collector_name: &str, + ) { + 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, + ); + } + ( + 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, + ); + } + _ => { + // 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}" + ) + ); + } + } + } +} + +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, + actual_span: impl FnOnce(&span::Id) -> Option, +) -> ActualAncestry { + if item.is_contextual() { + if let Some(parent_id) = lookup_current() { + let contextual_parent_span = actual_span(&parent_id).expect( + "tracing-mock: contextual parent cannot \ + be looked up by ID. Was it recorded correctly?", + ); + ActualAncestry::HasContextualParent(contextual_parent_span) + } else { + ActualAncestry::IsContextualRoot + } + } else if item.is_root() { + 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_span = actual_span(parent_id).expect( + "tracing-mock: explicit parent cannot be looked \ + up by ID. Is the provided Span ID valid: {parent_id}", + ); + ActualAncestry::HasExplicitParent(explicit_parent_span) + } +} diff --git a/tracing-mock/src/event.rs b/tracing-mock/src/event.rs index 103d663605..1f2a062f3a 100644 --- a/tracing-mock/src/event.rs +++ b/tracing-mock/src/event.rs @@ -28,11 +28,15 @@ //! //! [`subscriber`]: mod@crate::subscriber //! [`expect::event`]: fn@crate::expect::event -#![allow(missing_docs)] -use super::{expect, field, metadata::ExpectedMetadata, span, Parent}; - use std::fmt; +use crate::{ + ancestry::{ActualAncestry, ExpectedAncestry}, + field, + metadata::ExpectedMetadata, + span, +}; + /// An expected event. /// /// For a detailed description and examples, see the documentation for @@ -42,15 +46,11 @@ 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, } -pub fn msg(message: impl fmt::Display) -> ExpectedEvent { - expect::event().with_fields(field::msg(message)) -} - impl ExpectedEvent { /// Sets a name to expect when matching an event. /// @@ -95,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")); @@ -115,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")); @@ -151,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); @@ -172,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); @@ -205,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"); @@ -225,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"); @@ -253,76 +253,78 @@ impl ExpectedEvent { } } - /// Configures this `ExpectedEvent` to expect an explicit parent span - /// when matching events or to be an explicit root. - /// - /// An _explicit_ parent span is one passed to the `span!` macro in the - /// `parent:` field. + /// 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. /// - /// If `Some("parent_name")` is passed to `with_explicit_parent` then - /// the provided string is the name of the parent span to expect. + /// 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. /// - /// 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: + /// An explicit or contextual can be matched on an `ExpectedSpan`. /// /// ``` /// use tracing::subscriber::with_default; - /// use tracing_mock::{subscriber, expect}; + /// use tracing_mock::{expect, subscriber}; /// + /// let parent = expect::span() + /// .named("parent_span") + /// .with_target("custom-target") + /// .at_level(tracing::Level::INFO); /// let event = expect::event() - /// .with_explicit_parent(Some("parent_span")); + /// .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!("parent_span"); + /// let parent = tracing::info_span!(target: "custom-target", "parent_span"); /// tracing::info!(parent: parent.id(), field = &"value"); /// }); /// /// handle.assert_finished(); /// ``` - /// - /// In the following example, we expect that the matched event is - /// an explicit root: + /// 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; - /// use tracing_mock::{subscriber, expect}; + /// use tracing_mock::{expect, subscriber}; /// /// let event = expect::event() - /// .with_explicit_parent(None); + /// .with_ancestry(expect::has_explicit_parent("parent_span")); /// /// let (subscriber, handle) = subscriber::mock() /// .event(event) /// .run_with_handle(); /// /// with_default(subscriber, || { - /// tracing::info!(parent: None, field = &"value"); + /// let parent = tracing::info_span!("parent_span"); + /// tracing::info!(parent: parent.id(), 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`: + /// In the following example, we expect that the matched event is + /// an explicit root: /// - /// ```should_panic + /// ``` /// use tracing::subscriber::with_default; - /// use tracing_mock::{subscriber, expect}; + /// use tracing_mock::{expect, subscriber}; /// /// 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 +332,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 explicit 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}; + /// use tracing_mock::{expect, subscriber}; /// /// 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 +364,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}; + /// use tracing_mock::{expect, subscriber}; /// /// let event = expect::event() - /// .with_contextual_parent(None); + /// .with_ancestry(expect::is_contextual_root()); /// /// let (subscriber, handle) = subscriber::mock() /// .event(event) @@ -407,15 +385,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}; + /// use tracing_mock::{expect, subscriber}; /// /// 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 +408,11 @@ 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, - }; + /// + /// [`ExpectedId`]: struct@crate::span::ExpectedId + pub fn with_ancestry(self, ancenstry: ExpectedAncestry) -> ExpectedEvent { Self { - parent: Some(parent), + ancestry: Some(ancenstry), ..self } } @@ -557,7 +534,7 @@ impl ExpectedEvent { pub(crate) fn check( &mut self, event: &tracing::Event<'_>, - get_parent_name: impl FnOnce() -> Option, + get_ancestry: impl FnOnce() -> ActualAncestry, subscriber_name: &str, ) { let meta = event.metadata(); @@ -577,14 +554,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 +587,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 044f134580..39e95f73c4 100644 --- a/tracing-mock/src/expect.rs +++ b/tracing-mock/src/expect.rs @@ -1,9 +1,32 @@ +//! 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::{ + ancestry::ExpectedAncestry, event::ExpectedEvent, field::{ExpectedField, ExpectedFields, ExpectedValue}, - span::{ExpectedSpan, NewSpan}, + span::{ExpectedId, ExpectedSpan, NewSpan}, }; #[derive(Debug, Eq, PartialEq)] @@ -22,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, @@ -38,12 +190,100 @@ where } } -pub fn span() -> ExpectedSpan { - ExpectedSpan { - ..Default::default() +/// 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()), } } +/// Returns a new, unset `ExpectedId`. +/// +/// The `ExpectedId` needs to be attached to a [`NewSpan`] or an +/// [`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 [`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`]. +/// +/// [`MockSubscriber`]: struct@crate::subscriber::MockSubscriber +/// [`MockSubscriber::new_span`]: fn@crate::subscriber::MockSubscriber::new_span +pub fn id() -> ExpectedId { + ExpectedId::new_unset() +} + +/// Convenience function that returns [`ExpectedAncestry::IsContextualRoot`]. +pub fn is_contextual_root() -> ExpectedAncestry { + ExpectedAncestry::IsContextualRoot +} + +/// Convenience function that returns [`ExpectedAncestry::HasContextualParent`] with +/// provided name. +pub fn has_contextual_parent>(span: S) -> ExpectedAncestry { + ExpectedAncestry::HasContextualParent(span.into()) +} + +/// Convenience function that returns [`ExpectedAncestry::IsExplicitRoot`]. +pub fn is_explicit_root() -> ExpectedAncestry { + ExpectedAncestry::IsExplicitRoot +} + +/// Convenience function that returns [`ExpectedAncestry::HasExplicitParent`] with +/// provided name. +pub fn has_explicit_parent>(span: S) -> ExpectedAncestry { + ExpectedAncestry::HasExplicitParent(span.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/field.rs b/tracing-mock/src/field.rs index ea5f7a4eb7..908ac4b370 100644 --- a/tracing-mock/src/field.rs +++ b/tracing-mock/src/field.rs @@ -1,3 +1,88 @@ +//! 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::{expect, subscriber}; +//! +//! 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::{expect, subscriber}; +//! +//! 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::{expect, subscriber}; +//! +//! 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 std::{collections::HashMap, fmt}; + use tracing::{ callsite, callsite::Callsite, @@ -5,14 +90,24 @@ use tracing::{ metadata::Kind, }; -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,49 @@ 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 is different, the + /// expectation will fail. + /// + /// # Examples + /// + /// ``` + /// use tracing_mock::{expect, subscriber}; + /// + /// 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::{expect, subscriber}; + /// + /// 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 +200,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::{expect, subscriber}; + /// + /// 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::{expect, subscriber}; + /// + /// 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 +261,50 @@ 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 + /// + /// The following test passes despite the recorded event having + /// fields that were not expected because `only` was not + /// used: + /// + /// ``` + /// use tracing_mock::{expect, subscriber}; + /// + /// let event = expect::event() + /// .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", another_field = 42,); + /// }); + /// + /// handle.assert_finished(); + /// ``` + /// + /// If we include `only` on the `ExpectedField` then the test + /// will fail: + /// + /// ```should_panic + /// use tracing_mock::{expect, subscriber}; + /// + /// 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 +325,139 @@ impl From for ExpectedFields { } impl ExpectedFields { + /// Adds an additional [`ExpectedField`] to be matched. + /// + /// 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 + /// already multiple fields expected. + /// + /// # Examples + /// + /// ``` + /// 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)) + /// .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::{expect, subscriber}; + /// + /// 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. + /// Indicates 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 + /// + /// The following test will pass, even though additional fields are + /// recorded on the event. + /// + /// ``` + /// 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)), + /// ); + /// + /// 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 we include `only` on the `ExpectedFields` then the test + /// will fail: + /// + /// ```should_panic + /// 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(), + /// ); + /// + /// 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 +490,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 +502,7 @@ impl ExpectedFields { } } - pub fn is_empty(&self) -> bool { + pub(crate) fn is_empty(&self) -> bool { self.fields.is_empty() } } @@ -165,7 +521,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, @@ -208,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 ab48171b9d..011c161195 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,12 +7,12 @@ //! 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() //! // Expect a single event with a specified message -//! .event(expect::event().with_fields(field::msg("droids"))) +//! .event(expect::event().with_fields(expect::msg("droids"))) //! .run_with_handle(); //! //! // Use `set_default` to apply the `MockSubscriber` until the end @@ -33,18 +33,18 @@ //! 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() //! .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(field::msg("say hello"))) +//! .event(expect::event().with_fields(expect::msg("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(field::msg("say hello"))) +//! .event(expect::event().with_fields(expect::msg("say hello"))) //! // Exit a matching span -//! .exit(span) +//! .exit(&span) //! // Expect no further messages to be recorded //! .only() //! // Return the subscriber and handle @@ -115,12 +115,12 @@ //! ``` //! //! [`Layer`]: trait@tracing_subscriber::layer::Layer -use crate::{ - event::ExpectedEvent, - expect::Expect, - span::{ExpectedSpan, NewSpan}, - subscriber::MockHandle, +use std::{ + collections::VecDeque, + fmt, + sync::{Arc, Mutex}, }; + use tracing_core::{ span::{Attributes, Id, Record}, Event, Subscriber, @@ -130,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 @@ -145,18 +147,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(field::msg("say hello"))) +/// .event(expect::event().with_fields(expect::msg("say hello"))) /// // Exit a matching span -/// .exit(span) +/// .exit(&span) /// // Expect no further messages to be recorded /// .only() /// // Return the subscriber and handle @@ -206,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}; @@ -257,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, @@ -413,7 +416,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. @@ -431,7 +434,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 +458,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(); @@ -503,8 +506,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(); /// @@ -531,8 +534,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(); /// @@ -551,8 +554,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 } @@ -580,8 +586,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(); /// @@ -607,8 +613,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(); /// @@ -628,8 +634,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 } @@ -771,66 +780,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>, @@ -849,10 +808,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; } @@ -904,8 +863,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 +894,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); } } } @@ -955,7 +907,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 +928,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 +965,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,6 +994,17 @@ where } } +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).into()), + ) +} + 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 720efbe30a..5fa41422ae 100644 --- a/tracing-mock/src/lib.rs +++ b/tracing-mock/src/lib.rs @@ -1,9 +1,40 @@ #![doc = include_str!("../README.md")] -use std::{ - pin::Pin, - task::{Context, Poll}, -}; - +#![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; pub mod field; @@ -13,148 +44,3 @@ pub mod subscriber; #[cfg(feature = "tracing-subscriber")] pub mod layer; - -#[derive(Debug, Eq, PartialEq)] -pub enum Parent { - ContextualRoot, - Contextual(String), - ExplicitRoot, - Explicit(String), -} - -pub struct PollN { - and_return: Option>, - finish_at: usize, - polls: usize, -} - -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, - ); - } - } - } -} - -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-mock/src/metadata.rs b/tracing-mock/src/metadata.rs index 49347434fe..b893731676 100644 --- a/tracing-mock/src/metadata.rs +++ b/tracing-mock/src/metadata.rs @@ -1,5 +1,6 @@ use std::fmt; -use tracing::Metadata; + +use tracing_core::Metadata; #[derive(Clone, Debug, Eq, PartialEq, Default)] pub(crate) struct ExpectedMetadata { @@ -9,48 +10,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 9af084fe69..fffc6a0be9 100644 --- a/tracing-mock/src/span.rs +++ b/tracing-mock/src/span.rs @@ -1,31 +1,296 @@ -#![allow(missing_docs)] -use super::{expect, field::ExpectedFields, metadata::ExpectedMetadata, Parent}; -use std::fmt; +//! 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::{expect, subscriber}; +//! +//! let span = expect::span() +//! .named("interesting_span") +//! .at_level(tracing::Level::INFO); +//! +//! let (subscriber, handle) = subscriber::mock() +//! .enter(&span) +//! .exit(&span) +//! .run_with_handle(); +//! +//! tracing::subscriber::with_default(subscriber, || { +//! let span = tracing::info_span!("interesting_span"); +//! let _guard = span.enter(); +//! }); +//! +//! 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: +//! +//! ``` +//! use tracing_mock::{expect, subscriber}; +//! +//! 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_ancestry(expect::has_explicit_parent("parent_span")); +//! +//! let (subscriber, handle) = subscriber::mock() +//! .new_span("parent_span") +//! .new_span(new_span) +//! .enter(&span) +//! .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::{expect, subscriber}; +//! +//! let span = expect::span() +//! .named("interesting_span") +//! .at_level(tracing::Level::INFO); +//! +//! let (subscriber, handle) = subscriber::mock() +//! .enter(&span) +//! .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 +use std::{ + error, fmt, + sync::{ + atomic::{AtomicU64, Ordering}, + Arc, + }, +}; + +use crate::{ + ancestry::{ActualAncestry, ExpectedAncestry}, + field::ExpectedFields, + metadata::ExpectedMetadata, +}; /// 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) id: Option, 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 +/// 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, pub(crate) fields: ExpectedFields, - pub(crate) parent: Option, + 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>>, +} + +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 +/// [`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. + /// + /// 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::{expect, subscriber}; + /// + /// 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(); + /// ``` + /// + /// 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 [`MockSubscriber`] 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 + /// use tracing_mock::{expect, subscriber}; + /// + /// 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(); + /// ``` + /// + /// [`MockSubscriber`]: struct@crate::subscriber::MockSubscriber pub fn named(self, name: I) -> Self where I: Into, @@ -35,18 +300,232 @@ 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::{expect, subscriber}; + /// 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) + /// .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::{expect, subscriber}; + /// 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, || { + /// 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::{expect, subscriber}; + /// 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) + /// .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 _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 + } + } + + /// 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::{expect, subscriber}; + /// + /// 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::{expect, subscriber}; + /// + /// 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 { level: Some(level), ..self.metadata }, + ..self } } + /// 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::{expect, subscriber}; + /// + /// 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::{expect, subscriber}; + /// + /// 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, @@ -56,53 +535,290 @@ impl ExpectedSpan { target: Some(target.into()), ..self.metadata }, + ..self } } - pub fn with_explicit_parent(self, parent: Option<&str>) -> NewSpan { - let parent = match parent { - Some(name) => Parent::Explicit(name.into()), - None => Parent::ExplicitRoot, - }; + /// 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 + /// [`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 no `parent:` field is specified, then the span + /// will have a contextually determined parent or be a contextual root if + /// there is no parent. + /// + /// If the ancestry is different from the provided one, this expectation + /// will fail. + /// + /// # Examples + /// + /// An explicit or contextual parent can be matched on an `ExpectedSpan`. + /// + /// ``` + /// use tracing_mock::{expect, subscriber}; + /// + /// 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::{expect, subscriber}; + /// + /// let span = expect::span() + /// .with_ancestry(expect::has_explicit_parent("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::{expect, subscriber}; + /// + /// let span = expect::span() + /// .with_ancestry(expect::is_explicit_root()); + /// + /// 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::{expect, subscriber}; + /// + /// let parent_span = expect::span().named("parent_span"); + /// let span = expect::span() + /// .with_ancestry(expect::has_explicit_parent("parent_span")); + /// + /// let (subscriber, handle) = subscriber::mock() + /// .new_span(&parent_span) + /// .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::{expect, subscriber}; + /// + /// 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`: + /// + /// ```should_panic + /// use tracing_mock::{expect, subscriber}; + /// + /// let parent_span = expect::span().named("parent_span"); + /// let span = expect::span() + /// .with_ancestry(expect::has_explicit_parent("parent_span")); + /// + /// let (subscriber, handle) = subscriber::mock() + /// .new_span(&parent_span) + /// .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_ancestry(self, ancestry: ExpectedAncestry) -> NewSpan { NewSpan { - parent: Some(parent), + ancestry: Some(ancestry), span: self, ..Default::default() } } - pub fn with_contextual_parent(self, parent: Option<&str>) -> NewSpan { - let parent = match parent { - Some(name) => Parent::Contextual(name.into()), - None => Parent::ContextualRoot, - }; + /// 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::{expect, subscriber}; + /// + /// 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::{expect, subscriber}; + /// + /// 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 { - parent: Some(parent), span: self, + fields: fields.into(), ..Default::default() } } - pub fn name(&self) -> Option<&str> { + 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) } - 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: &ActualSpan, ctx: impl fmt::Display, subscriber_name: &str) { + if let Some(expected_id) = &self.id { + expected_id.check(&actual.id(), format_args!("{ctx} a span"), 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() + ) + ); + } + } } } } @@ -111,6 +827,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); } @@ -137,39 +857,39 @@ 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() } } } impl NewSpan { - pub fn with_explicit_parent(self, parent: Option<&str>) -> NewSpan { - let parent = match parent { - Some(name) => Parent::Explicit(name.into()), - None => Parent::ExplicitRoot, - }; + /// 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: ExpectedAncestry) -> NewSpan { NewSpan { - parent: Some(parent), + ancestry: Some(ancestry), ..self } } - pub fn with_contextual_parent(self, parent: Option<&str>) -> NewSpan { - let parent = match parent { - Some(name) => Parent::Contextual(name.into()), - None => Parent::ContextualRoot, - }; - NewSpan { - parent: Some(parent), - ..self - } - } - - 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,29 +899,28 @@ impl NewSpan { } } - pub fn check( + pub(crate) fn check( &mut self, span: &tracing_core::span::Attributes<'_>, - get_parent_name: impl FnOnce() -> Option, + 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(); - 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, - ) + ); } } } @@ -232,7 +951,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)); } @@ -243,3 +962,87 @@ 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 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; + + 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: &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!( + 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!( + 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", + ) + ); + } +} + +#[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 ef7a93b148..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(field::msg("droids"))) +//! .event(expect::event().with_fields(expect::msg("droids"))) //! .only() //! .run_with_handle(); //! @@ -32,17 +32,17 @@ //! their respective fields: //! //! ``` -//! 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 a matching span -//! .enter(span.clone()) -//! // Record an event with message "collect parting message" -//! .event(expect::event().with_fields(field::msg("collect parting message"))) +//! .enter(&span) +//! // Record an event with 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) //! // Expect no further messages to be recorded @@ -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); @@ -75,14 +75,14 @@ //! 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.clone()) -//! .event(expect::event().with_fields(field::msg("collect parting message"))) -//! .record(span.clone(), expect::field("parting").with_value(&"goodbye world!")) +//! .enter(&span) +//! .event(expect::event().with_fields(expect::msg("collect parting message"))) +//! .record(&span, expect::field("parting").with_value(&"goodbye world!")) //! .exit(span) //! .only() //! .run_with_handle(); @@ -98,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); @@ -116,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 { @@ -137,12 +137,6 @@ //! //! [`Subscriber`]: trait@tracing::Subscriber //! [`MockSubscriber`]: struct@crate::subscriber::MockSubscriber -use crate::{ - event::ExpectedEvent, - expect::Expect, - field::ExpectedFields, - span::{ExpectedSpan, NewSpan}, -}; use std::{ collections::{HashMap, VecDeque}, sync::{ @@ -158,12 +152,27 @@ use tracing::{ Event, Metadata, Subscriber, }; -struct SpanState { +use crate::{ + ancestry::get_ancestry, + event::ExpectedEvent, + expect::Expect, + field::ExpectedFields, + span::{ActualSpan, ExpectedSpan, NewSpan}, +}; + +pub(crate) struct SpanState { + id: Id, name: &'static str, refs: usize, meta: &'static Metadata<'static>, } +impl From<&SpanState> for ActualSpan { + fn from(span_state: &SpanState) -> Self { + Self::new(span_state.id.clone(), Some(span_state.meta)) + } +} + struct Running) -> bool> { spans: Mutex>, expected: Arc>>, @@ -180,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, @@ -196,6 +206,7 @@ pub struct MockSubscriber) -> bool> { /// module documentation. /// /// [`subscriber`]: mod@crate::subscriber +#[derive(Debug)] pub struct MockHandle(Arc>>, String); /// Create a new [`MockSubscriber`]. @@ -207,17 +218,17 @@ pub struct MockHandle(Arc>>, String); /// /// /// ``` -/// 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 a matching span -/// .enter(span.clone()) -/// // Record an event with message "collect parting message" -/// .event(expect::event().with_fields(field::msg("collect parting message"))) +/// .enter(&span) +/// // Record an event with 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) /// // Expect no further messages to be recorded @@ -235,7 +246,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); @@ -285,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") @@ -340,7 +351,7 @@ where /// # Examples /// /// ``` - /// use tracing_mock::{subscriber, expect}; + /// use tracing_mock::{expect, subscriber}; /// /// let (subscriber, handle) = subscriber::mock() /// .event(expect::event()) @@ -356,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()) @@ -394,12 +405,12 @@ where /// # Examples /// /// ``` - /// use tracing_mock::{subscriber, expect}; + /// use tracing_mock::{expect, subscriber}; /// /// 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(); @@ -415,12 +426,12 @@ 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) /// .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(); @@ -454,14 +465,14 @@ where /// # Examples /// /// ``` - /// use tracing_mock::{subscriber, expect}; + /// use tracing_mock::{expect, subscriber}; /// /// let span = expect::span() /// .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(); /// @@ -477,14 +488,14 @@ 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) /// .named("the span we're testing"); /// let (subscriber, handle) = subscriber::mock() - /// .enter(span.clone()) - /// .exit(span) + /// .enter(&span) + /// .exit(&span) /// .only() /// .run_with_handle(); /// @@ -499,8 +510,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 } @@ -518,14 +532,14 @@ where /// # Examples /// /// ``` - /// use tracing_mock::{subscriber, expect}; + /// use tracing_mock::{expect, subscriber}; /// /// let span = expect::span() /// .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, || { @@ -540,14 +554,14 @@ 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) /// .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, || { @@ -560,8 +574,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 } @@ -577,7 +594,7 @@ where /// # Examples /// /// ``` - /// use tracing_mock::{subscriber, expect}; + /// use tracing_mock::{expect, subscriber}; /// /// let span = expect::span() /// .at_level(tracing::Level::INFO) @@ -598,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) @@ -615,8 +632,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 } @@ -632,8 +652,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 } @@ -654,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"); @@ -678,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"); @@ -698,9 +721,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 } @@ -718,7 +747,7 @@ where /// # Examples /// /// ``` - /// use tracing_mock::{subscriber, expect}; + /// use tracing_mock::{expect, subscriber}; /// /// let span = expect::span() /// .named("my_span"); @@ -742,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"); @@ -763,11 +792,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 } @@ -781,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) @@ -824,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) @@ -860,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()) @@ -878,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()) @@ -935,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() @@ -1028,16 +1059,20 @@ where ) } } - 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.into()) + }, + ) }; - 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)), } @@ -1094,19 +1129,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.into()), + ) + }, + &self.name, + ); } } spans.insert( id.clone(), SpanState { + id: id.clone(), name: meta.name(), refs: 1, meta, @@ -1122,9 +1165,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.into(), "to enter a span", &self.name); } Some(ex) => ex.bad(&self.name, format_args!("entered span {:?}", span.name)), } @@ -1147,9 +1188,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.into(), "to exit a span", &self.name); let curr = self.current.lock().unwrap().pop(); assert_eq!( Some(id), @@ -1165,27 +1204,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 @@ -1254,6 +1300,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 { @@ -1273,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-mock/tests/event_ancestry.rs b/tracing-mock/tests/event_ancestry.rs new file mode 100644 index 0000000000..2b20bc5401 --- /dev/null +++ b/tracing-mock/tests/event_ancestry.rs @@ -0,0 +1,406 @@ +//! 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, Level}; +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 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")); + + 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 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 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")); + + 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 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")); + + 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 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")); + + 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 it actually has a contextual parent span")] +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 it actually has an explicit parent span")] +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 it is 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 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")); + + 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 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")); + + 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 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")); + + 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 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")); + + 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 it actually has a contextual parent span")] +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 it is 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 it actually has an explicit parent span")] +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..0ab6648d81 --- /dev/null +++ b/tracing-mock/tests/span_ancestry.rs @@ -0,0 +1,473 @@ +//! 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, Level}; +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 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() + .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 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 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") + .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 span, but it actually has an \ + explicit parent span")] +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 span, but it is 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 it actually has a contextual parent span")] +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 it actually has an explicit parent span")] +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 it is 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 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() + .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 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") + .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 span, but it is 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 span, but it is 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 it actually has a contextual parent span")] +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 it is 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 it actually has an explicit parent span")] +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-subscriber/tests/env_filter/main.rs b/tracing-subscriber/tests/env_filter/main.rs index 16921814c1..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::*, @@ -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(); @@ -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(); @@ -275,13 +274,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(); @@ -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 229b9ff776..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() { @@ -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(); @@ -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-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-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>, diff --git a/tracing/tests/event.rs b/tracing/tests/event.rs index 0be7c0bc56..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(field::msg(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(field::msg(format_args!("hello from my event"))) + .and(expect::msg(format_args!("hello from my event"))) .only(), ), ) @@ -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/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(); diff --git a/tracing/tests/span.rs b/tracing/tests/span.rs index 09f1be8954..bd5bed0d2f 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)) @@ -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() @@ -781,7 +813,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 +833,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 +853,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 +874,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(),