mirror of
https://github.com/tokio-rs/tracing.git
synced 2025-10-02 07:20:35 +00:00

There has been interest around publishing tracing-mock to crates.io for some time. In order to make this possible, documentation and some code clean up is needed. The `expect` module, which contains constructor functions for many of the other `tracing-mock` modules needs documentation and examples. This change adds documentation to the `expect` module and all the public APIs within it. This includes doctests on all the methods which serve as examples. The lint for `missing_docs` has been enabled for the entire `tracing-mock` crate! This has been done together with all the other lints that are enabled on the other crates in this project. The `event::msg("message")` constructor was removed, in favor of requiring an explicit construction via `expect::event().with_fields(expect::msg("message"))`. This is appropriate to reduce the API surface that would need to be supported in the future and also because the `event::msg` constructor could be overridden by a subsequent usage of `with_fields`. The shorthand `expect::message()` was renamed to `expect::msg` to make this change less burdensome. The `span::named("name")` constructor was removed, in favor of requiring an explicit construction via `expect::span.with_name("name")`. The latter isn't much longer and since #3097, a string with the name can be passed directly everywhere that an `ExpectedSpan` is required. This change also sets the `missing_docs` lint to warn for the entire `tracing-mock` crate, making it ready to publish (once backported). Refs: #539
633 lines
18 KiB
Rust
633 lines
18 KiB
Rust
//! 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,
|
|
field::{self, Field, Value, Visit},
|
|
metadata::Kind,
|
|
};
|
|
|
|
/// 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<String, ExpectedValue>,
|
|
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,
|
|
pub(super) value: ExpectedValue,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub(crate) enum ExpectedValue {
|
|
F64(f64),
|
|
I64(i64),
|
|
U64(u64),
|
|
Bool(bool),
|
|
Str(String),
|
|
Debug(String),
|
|
Any,
|
|
}
|
|
|
|
impl Eq for ExpectedValue {}
|
|
|
|
impl PartialEq for ExpectedValue {
|
|
fn eq(&self, other: &Self) -> bool {
|
|
use ExpectedValue::*;
|
|
|
|
match (self, other) {
|
|
(F64(a), F64(b)) => {
|
|
debug_assert!(!a.is_nan());
|
|
debug_assert!(!b.is_nan());
|
|
|
|
a.eq(b)
|
|
}
|
|
(I64(a), I64(b)) => a.eq(b),
|
|
(U64(a), U64(b)) => a.eq(b),
|
|
(Bool(a), Bool(b)) => a.eq(b),
|
|
(Str(a), Str(b)) => a.eq(b),
|
|
(Debug(a), Debug(b)) => a.eq(b),
|
|
(Any, _) => true,
|
|
(_, Any) => true,
|
|
_ => false,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl ExpectedField {
|
|
/// 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),
|
|
..self
|
|
}
|
|
}
|
|
|
|
/// 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(),
|
|
only: false,
|
|
}
|
|
.and(self)
|
|
.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(),
|
|
only: true,
|
|
}
|
|
.and(self)
|
|
}
|
|
}
|
|
|
|
impl From<ExpectedField> for ExpectedFields {
|
|
fn from(field: ExpectedField) -> Self {
|
|
ExpectedFields {
|
|
fields: HashMap::new(),
|
|
only: false,
|
|
}
|
|
.and(field)
|
|
}
|
|
}
|
|
|
|
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.
|
|
///
|
|
/// 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 }
|
|
}
|
|
|
|
fn compare_or_panic(
|
|
&mut self,
|
|
name: &str,
|
|
value: &dyn Value,
|
|
ctx: &str,
|
|
subscriber_name: &str,
|
|
) {
|
|
let value = value.into();
|
|
match self.fields.remove(name) {
|
|
Some(ExpectedValue::Any) => {}
|
|
Some(expected) => assert!(
|
|
expected == value,
|
|
"\n[{}] expected `{}` to contain:\n\t`{}{}`\nbut got:\n\t`{}{}`",
|
|
subscriber_name,
|
|
ctx,
|
|
name,
|
|
expected,
|
|
name,
|
|
value
|
|
),
|
|
None if self.only => panic!(
|
|
"[{}]expected `{}` to contain only:\n\t`{}`\nbut got:\n\t`{}{}`",
|
|
subscriber_name, ctx, self, name, value
|
|
),
|
|
_ => {}
|
|
}
|
|
}
|
|
|
|
pub(crate) fn checker<'a>(
|
|
&'a mut self,
|
|
ctx: &'a str,
|
|
subscriber_name: &'a str,
|
|
) -> CheckVisitor<'a> {
|
|
CheckVisitor {
|
|
expect: self,
|
|
ctx,
|
|
subscriber_name,
|
|
}
|
|
}
|
|
|
|
pub(crate) fn is_empty(&self) -> bool {
|
|
self.fields.is_empty()
|
|
}
|
|
}
|
|
|
|
impl fmt::Display for ExpectedValue {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
match self {
|
|
ExpectedValue::F64(v) => write!(f, "f64 = {:?}", v),
|
|
ExpectedValue::I64(v) => write!(f, "i64 = {:?}", v),
|
|
ExpectedValue::U64(v) => write!(f, "u64 = {:?}", v),
|
|
ExpectedValue::Bool(v) => write!(f, "bool = {:?}", v),
|
|
ExpectedValue::Str(v) => write!(f, "&str = {:?}", v),
|
|
ExpectedValue::Debug(v) => write!(f, "&fmt::Debug = {:?}", v),
|
|
ExpectedValue::Any => write!(f, "_ = _"),
|
|
}
|
|
}
|
|
}
|
|
|
|
pub(crate) struct CheckVisitor<'a> {
|
|
expect: &'a mut ExpectedFields,
|
|
ctx: &'a str,
|
|
subscriber_name: &'a str,
|
|
}
|
|
|
|
impl<'a> Visit for CheckVisitor<'a> {
|
|
fn record_f64(&mut self, field: &Field, value: f64) {
|
|
self.expect
|
|
.compare_or_panic(field.name(), &value, self.ctx, self.subscriber_name)
|
|
}
|
|
|
|
fn record_i64(&mut self, field: &Field, value: i64) {
|
|
self.expect
|
|
.compare_or_panic(field.name(), &value, self.ctx, self.subscriber_name)
|
|
}
|
|
|
|
fn record_u64(&mut self, field: &Field, value: u64) {
|
|
self.expect
|
|
.compare_or_panic(field.name(), &value, self.ctx, self.subscriber_name)
|
|
}
|
|
|
|
fn record_bool(&mut self, field: &Field, value: bool) {
|
|
self.expect
|
|
.compare_or_panic(field.name(), &value, self.ctx, self.subscriber_name)
|
|
}
|
|
|
|
fn record_str(&mut self, field: &Field, value: &str) {
|
|
self.expect
|
|
.compare_or_panic(field.name(), &value, self.ctx, self.subscriber_name)
|
|
}
|
|
|
|
fn record_debug(&mut self, field: &Field, value: &dyn fmt::Debug) {
|
|
self.expect.compare_or_panic(
|
|
field.name(),
|
|
&field::debug(value),
|
|
self.ctx,
|
|
self.subscriber_name,
|
|
)
|
|
}
|
|
}
|
|
|
|
impl<'a> CheckVisitor<'a> {
|
|
pub(crate) fn finish(self) {
|
|
assert!(
|
|
self.expect.fields.is_empty(),
|
|
"[{}] {}missing {}",
|
|
self.subscriber_name,
|
|
self.expect,
|
|
self.ctx
|
|
);
|
|
}
|
|
}
|
|
|
|
impl<'a> From<&'a dyn Value> for ExpectedValue {
|
|
fn from(value: &'a dyn Value) -> Self {
|
|
struct MockValueBuilder {
|
|
value: Option<ExpectedValue>,
|
|
}
|
|
|
|
impl Visit for MockValueBuilder {
|
|
fn record_f64(&mut self, _: &Field, value: f64) {
|
|
self.value = Some(ExpectedValue::F64(value));
|
|
}
|
|
|
|
fn record_i64(&mut self, _: &Field, value: i64) {
|
|
self.value = Some(ExpectedValue::I64(value));
|
|
}
|
|
|
|
fn record_u64(&mut self, _: &Field, value: u64) {
|
|
self.value = Some(ExpectedValue::U64(value));
|
|
}
|
|
|
|
fn record_bool(&mut self, _: &Field, value: bool) {
|
|
self.value = Some(ExpectedValue::Bool(value));
|
|
}
|
|
|
|
fn record_str(&mut self, _: &Field, value: &str) {
|
|
self.value = Some(ExpectedValue::Str(value.to_owned()));
|
|
}
|
|
|
|
fn record_debug(&mut self, _: &Field, value: &dyn fmt::Debug) {
|
|
self.value = Some(ExpectedValue::Debug(format!("{:?}", value)));
|
|
}
|
|
}
|
|
|
|
let fake_field = callsite!(name: "fake", kind: Kind::EVENT, fields: fake_field)
|
|
.metadata()
|
|
.fields()
|
|
.field("fake_field")
|
|
.unwrap();
|
|
let mut builder = MockValueBuilder { value: None };
|
|
value.record(&fake_field, &mut builder);
|
|
builder
|
|
.value
|
|
.expect("finish called before a value was recorded")
|
|
}
|
|
}
|
|
|
|
impl fmt::Display for ExpectedFields {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
write!(f, "fields ")?;
|
|
let entries = self
|
|
.fields
|
|
.iter()
|
|
.map(|(k, v)| (field::display(k), field::display(v)));
|
|
f.debug_map().entries(entries).finish()
|
|
}
|
|
}
|