tracing_mock/field.rs
1//! Define expectations to validate fields on events and spans.
2//!
3//! The [`ExpectedField`] struct define expected values for fields in
4//! order to match events and spans via the mock subscriber API in the
5//! [`subscriber`] module.
6//!
7//! Expected fields should be created with [`expect::field`] and a
8//! chain of method calls to specify the field value and additional
9//! fields as necessary.
10//!
11//! # Examples
12//!
13//! The simplest case is to expect that an event has a field with a
14//! specific name, without any expectation about the value:
15//!
16//! ```
17//! use tracing_mock::{expect, subscriber};
18//!
19//! let event = expect::event()
20//! .with_fields(expect::field("field_name"));
21//!
22//! let (subscriber, handle) = subscriber::mock()
23//! .event(event)
24//! .run_with_handle();
25//!
26//! tracing::subscriber::with_default(subscriber, || {
27//! tracing::info!(field_name = "value");
28//! });
29//!
30//! handle.assert_finished();
31//! ```
32//!
33//! It is possible to expect multiple fields and specify the value for
34//! each of them:
35//!
36//! ```
37//! use tracing_mock::{expect, subscriber};
38//!
39//! let event = expect::event().with_fields(
40//! expect::field("string_field")
41//! .with_value(&"field_value")
42//! .and(expect::field("integer_field").with_value(&54_i64))
43//! .and(expect::field("bool_field").with_value(&true)),
44//! );
45//!
46//! let (subscriber, handle) = subscriber::mock()
47//! .event(event)
48//! .run_with_handle();
49//!
50//! tracing::subscriber::with_default(subscriber, || {
51//! tracing::info!(
52//! string_field = "field_value",
53//! integer_field = 54_i64,
54//! bool_field = true,
55//! );
56//! });
57//!
58//! handle.assert_finished();
59//! ```
60//!
61//! If an expected field is not present, or if the value of the field
62//! is different, the test will fail. In this example, the value is
63//! different:
64//!
65//! ```should_panic
66//! use tracing_mock::{expect, subscriber};
67//!
68//! let event = expect::event()
69//! .with_fields(expect::field("field_name").with_value(&"value"));
70//!
71//! let (subscriber, handle) = subscriber::mock()
72//! .event(event)
73//! .run_with_handle();
74//!
75//! tracing::subscriber::with_default(subscriber, || {
76//! tracing::info!(field_name = "different value");
77//! });
78//!
79//! handle.assert_finished();
80//! ```
81//!
82//! [`subscriber`]: mod@crate::subscriber
83//! [`expect::field`]: fn@crate::expect::field
84use std::{collections::HashMap, fmt};
85
86use tracing::{
87 callsite,
88 callsite::Callsite,
89 field::{self, Field, Value, Visit},
90 metadata::Kind,
91};
92
93/// An expectation for multiple fields.
94///
95/// For a detailed description and examples, see the documentation for
96/// the methods and the [`field`] module.
97///
98/// [`field`]: mod@crate::field
99#[derive(Default, Debug, Eq, PartialEq)]
100pub struct ExpectedFields {
101 fields: HashMap<String, ExpectedValue>,
102 only: bool,
103}
104
105/// An expected field.
106///
107/// For a detailed description and examples, see the documentation for
108/// the methods and the [`field`] module.
109///
110/// [`field`]: mod@crate::field
111#[derive(Debug)]
112pub struct ExpectedField {
113 pub(super) name: String,
114 pub(super) value: ExpectedValue,
115}
116
117#[derive(Debug)]
118pub(crate) enum ExpectedValue {
119 F64(f64),
120 I64(i64),
121 U64(u64),
122 Bool(bool),
123 Str(String),
124 Debug(String),
125 Any,
126}
127
128impl Eq for ExpectedValue {}
129
130impl PartialEq for ExpectedValue {
131 fn eq(&self, other: &Self) -> bool {
132 use ExpectedValue::*;
133
134 match (self, other) {
135 (F64(a), F64(b)) => {
136 debug_assert!(!a.is_nan());
137 debug_assert!(!b.is_nan());
138
139 a.eq(b)
140 }
141 (I64(a), I64(b)) => a.eq(b),
142 (U64(a), U64(b)) => a.eq(b),
143 (Bool(a), Bool(b)) => a.eq(b),
144 (Str(a), Str(b)) => a.eq(b),
145 (Debug(a), Debug(b)) => a.eq(b),
146 (Any, _) => true,
147 (_, Any) => true,
148 _ => false,
149 }
150 }
151}
152
153impl ExpectedField {
154 /// Sets the value to expect when matching this field.
155 ///
156 /// If the recorded value for this field is different, the
157 /// expectation will fail.
158 ///
159 /// # Examples
160 ///
161 /// ```
162 /// use tracing_mock::{expect, subscriber};
163 ///
164 /// let event = expect::event()
165 /// .with_fields(expect::field("field_name").with_value(&"value"));
166 ///
167 /// let (subscriber, handle) = subscriber::mock()
168 /// .event(event)
169 /// .run_with_handle();
170 ///
171 /// tracing::subscriber::with_default(subscriber, || {
172 /// tracing::info!(field_name = "value");
173 /// });
174 ///
175 /// handle.assert_finished();
176 /// ```
177 ///
178 /// A different value will cause the test to fail:
179 ///
180 /// ```should_panic
181 /// use tracing_mock::{expect, subscriber};
182 ///
183 /// let event = expect::event()
184 /// .with_fields(expect::field("field_name").with_value(&"value"));
185 ///
186 /// let (subscriber, handle) = subscriber::mock()
187 /// .event(event)
188 /// .run_with_handle();
189 ///
190 /// tracing::subscriber::with_default(subscriber, || {
191 /// tracing::info!(field_name = "different value");
192 /// });
193 ///
194 /// handle.assert_finished();
195 /// ```
196 pub fn with_value(self, value: &dyn Value) -> Self {
197 Self {
198 value: ExpectedValue::from(value),
199 ..self
200 }
201 }
202
203 /// Adds an additional [`ExpectedField`] to be matched.
204 ///
205 /// Any fields introduced by `.and` must also match. If any fields
206 /// are not present, or if the value for any field is different,
207 /// then the expectation will fail.
208 ///
209 /// # Examples
210 ///
211 /// ```
212 /// use tracing_mock::{expect, subscriber};
213 ///
214 /// let event = expect::event().with_fields(
215 /// expect::field("field")
216 /// .with_value(&"value")
217 /// .and(expect::field("another_field").with_value(&42)),
218 /// );
219 ///
220 /// let (subscriber, handle) = subscriber::mock()
221 /// .event(event)
222 /// .run_with_handle();
223 ///
224 /// tracing::subscriber::with_default(subscriber, || {
225 /// tracing::info!(
226 /// field = "value",
227 /// another_field = 42,
228 /// );
229 /// });
230 ///
231 /// handle.assert_finished();
232 /// ```
233 ///
234 /// If the second field is not present, the test will fail:
235 ///
236 /// ```should_panic
237 /// use tracing_mock::{expect, subscriber};
238 ///
239 /// let event = expect::event().with_fields(
240 /// expect::field("field")
241 /// .with_value(&"value")
242 /// .and(expect::field("another_field").with_value(&42)),
243 /// );
244 ///
245 /// let (subscriber, handle) = subscriber::mock()
246 /// .event(event)
247 /// .run_with_handle();
248 ///
249 /// tracing::subscriber::with_default(subscriber, || {
250 /// tracing::info!(field = "value");
251 /// });
252 ///
253 /// handle.assert_finished();
254 /// ```
255 pub fn and(self, other: ExpectedField) -> ExpectedFields {
256 ExpectedFields {
257 fields: HashMap::new(),
258 only: false,
259 }
260 .and(self)
261 .and(other)
262 }
263
264 /// Indicates that no fields other than those specified should be
265 /// expected.
266 ///
267 /// If additional fields are present on the recorded event or span,
268 /// the expectation will fail.
269 ///
270 /// # Examples
271 ///
272 /// The following test passes despite the recorded event having
273 /// fields that were not expected because `only` was not
274 /// used:
275 ///
276 /// ```
277 /// use tracing_mock::{expect, subscriber};
278 ///
279 /// let event = expect::event()
280 /// .with_fields(expect::field("field").with_value(&"value"));
281 ///
282 /// let (subscriber, handle) = subscriber::mock().event(event).run_with_handle();
283 ///
284 /// tracing::subscriber::with_default(subscriber, || {
285 /// tracing::info!(field = "value", another_field = 42,);
286 /// });
287 ///
288 /// handle.assert_finished();
289 /// ```
290 ///
291 /// If we include `only` on the `ExpectedField` then the test
292 /// will fail:
293 ///
294 /// ```should_panic
295 /// use tracing_mock::{expect, subscriber};
296 ///
297 /// let event = expect::event()
298 /// .with_fields(expect::field("field").with_value(&"value").only());
299 ///
300 /// let (subscriber, handle) = subscriber::mock().event(event).run_with_handle();
301 ///
302 /// tracing::subscriber::with_default(subscriber, || {
303 /// tracing::info!(field = "value", another_field = 42,);
304 /// });
305 ///
306 /// handle.assert_finished();
307 /// ```
308 pub fn only(self) -> ExpectedFields {
309 ExpectedFields {
310 fields: HashMap::new(),
311 only: true,
312 }
313 .and(self)
314 }
315}
316
317impl From<ExpectedField> for ExpectedFields {
318 fn from(field: ExpectedField) -> Self {
319 ExpectedFields {
320 fields: HashMap::new(),
321 only: false,
322 }
323 .and(field)
324 }
325}
326
327impl ExpectedFields {
328 /// Adds an additional [`ExpectedField`] to be matched.
329 ///
330 /// All fields must match, if any of them are not present, or if
331 /// the value for any field is different, the expectation will
332 /// fail.
333 ///
334 /// This method performs the same function as
335 /// [`ExpectedField::and`], but applies in the case where there are
336 /// already multiple fields expected.
337 ///
338 /// # Examples
339 ///
340 /// ```
341 /// use tracing_mock::{expect, subscriber};
342 ///
343 /// let event = expect::event().with_fields(
344 /// expect::field("field")
345 /// .with_value(&"value")
346 /// .and(expect::field("another_field").with_value(&42))
347 /// .and(expect::field("a_third_field").with_value(&true)),
348 /// );
349 ///
350 /// let (subscriber, handle) = subscriber::mock()
351 /// .event(event)
352 /// .run_with_handle();
353 ///
354 /// tracing::subscriber::with_default(subscriber, || {
355 /// tracing::info!(
356 /// field = "value",
357 /// another_field = 42,
358 /// a_third_field = true,
359 /// );
360 /// });
361 ///
362 /// handle.assert_finished();
363 /// ```
364 ///
365 /// If any of the expected fields are not present on the recorded
366 /// event, the test will fail:
367 ///
368 /// ```should_panic
369 /// use tracing_mock::{expect, subscriber};
370 ///
371 /// let event = expect::event().with_fields(
372 /// expect::field("field")
373 /// .with_value(&"value")
374 /// .and(expect::field("another_field").with_value(&42))
375 /// .and(expect::field("a_third_field").with_value(&true)),
376 /// );
377 ///
378 /// let (subscriber, handle) = subscriber::mock()
379 /// .event(event)
380 /// .run_with_handle();
381 ///
382 /// tracing::subscriber::with_default(subscriber, || {
383 /// tracing::info!(
384 /// field = "value",
385 /// a_third_field = true,
386 /// );
387 /// });
388 ///
389 /// handle.assert_finished();
390 /// ```
391 ///
392 /// [`ExpectedField::and`]: fn@crate::field::ExpectedField::and
393 pub fn and(mut self, field: ExpectedField) -> Self {
394 self.fields.insert(field.name, field.value);
395 self
396 }
397
398 /// Indicates that no fields other than those specified should be
399 /// expected.
400 ///
401 /// This method performs the same function as
402 /// [`ExpectedField::only`], but applies in the case where there are
403 /// multiple fields expected.
404 ///
405 /// # Examples
406 ///
407 /// The following test will pass, even though additional fields are
408 /// recorded on the event.
409 ///
410 /// ```
411 /// use tracing_mock::{expect, subscriber};
412 ///
413 /// let event = expect::event().with_fields(
414 /// expect::field("field")
415 /// .with_value(&"value")
416 /// .and(expect::field("another_field").with_value(&42)),
417 /// );
418 ///
419 /// let (subscriber, handle) = subscriber::mock()
420 /// .event(event)
421 /// .run_with_handle();
422 ///
423 /// tracing::subscriber::with_default(subscriber, || {
424 /// tracing::info!(
425 /// field = "value",
426 /// another_field = 42,
427 /// a_third_field = true,
428 /// );
429 /// });
430 ///
431 /// handle.assert_finished();
432 /// ```
433 ///
434 /// If we include `only` on the `ExpectedFields` then the test
435 /// will fail:
436 ///
437 /// ```should_panic
438 /// use tracing_mock::{expect, subscriber};
439 ///
440 /// let event = expect::event().with_fields(
441 /// expect::field("field")
442 /// .with_value(&"value")
443 /// .and(expect::field("another_field").with_value(&42))
444 /// .only(),
445 /// );
446 ///
447 /// let (subscriber, handle) = subscriber::mock()
448 /// .event(event)
449 /// .run_with_handle();
450 ///
451 /// tracing::subscriber::with_default(subscriber, || {
452 /// tracing::info!(
453 /// field = "value",
454 /// another_field = 42,
455 /// a_third_field = true,
456 /// );
457 /// });
458 ///
459 /// handle.assert_finished();
460 /// ```
461 pub fn only(self) -> Self {
462 Self { only: true, ..self }
463 }
464
465 fn compare_or_panic(
466 &mut self,
467 name: &str,
468 value: &dyn Value,
469 ctx: &str,
470 subscriber_name: &str,
471 ) {
472 let value = value.into();
473 match self.fields.remove(name) {
474 Some(ExpectedValue::Any) => {}
475 Some(expected) => assert!(
476 expected == value,
477 "\n[{}] expected `{}` to contain:\n\t`{}{}`\nbut got:\n\t`{}{}`",
478 subscriber_name,
479 ctx,
480 name,
481 expected,
482 name,
483 value
484 ),
485 None if self.only => panic!(
486 "[{}]expected `{}` to contain only:\n\t`{}`\nbut got:\n\t`{}{}`",
487 subscriber_name, ctx, self, name, value
488 ),
489 _ => {}
490 }
491 }
492
493 pub(crate) fn checker<'a>(
494 &'a mut self,
495 ctx: &'a str,
496 subscriber_name: &'a str,
497 ) -> CheckVisitor<'a> {
498 CheckVisitor {
499 expect: self,
500 ctx,
501 subscriber_name,
502 }
503 }
504
505 pub(crate) fn is_empty(&self) -> bool {
506 self.fields.is_empty()
507 }
508}
509
510impl fmt::Display for ExpectedValue {
511 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
512 match self {
513 ExpectedValue::F64(v) => write!(f, "f64 = {:?}", v),
514 ExpectedValue::I64(v) => write!(f, "i64 = {:?}", v),
515 ExpectedValue::U64(v) => write!(f, "u64 = {:?}", v),
516 ExpectedValue::Bool(v) => write!(f, "bool = {:?}", v),
517 ExpectedValue::Str(v) => write!(f, "&str = {:?}", v),
518 ExpectedValue::Debug(v) => write!(f, "&fmt::Debug = {:?}", v),
519 ExpectedValue::Any => write!(f, "_ = _"),
520 }
521 }
522}
523
524pub(crate) struct CheckVisitor<'a> {
525 expect: &'a mut ExpectedFields,
526 ctx: &'a str,
527 subscriber_name: &'a str,
528}
529
530impl Visit for CheckVisitor<'_> {
531 fn record_f64(&mut self, field: &Field, value: f64) {
532 self.expect
533 .compare_or_panic(field.name(), &value, self.ctx, self.subscriber_name)
534 }
535
536 fn record_i64(&mut self, field: &Field, value: i64) {
537 self.expect
538 .compare_or_panic(field.name(), &value, self.ctx, self.subscriber_name)
539 }
540
541 fn record_u64(&mut self, field: &Field, value: u64) {
542 self.expect
543 .compare_or_panic(field.name(), &value, self.ctx, self.subscriber_name)
544 }
545
546 fn record_bool(&mut self, field: &Field, value: bool) {
547 self.expect
548 .compare_or_panic(field.name(), &value, self.ctx, self.subscriber_name)
549 }
550
551 fn record_str(&mut self, field: &Field, value: &str) {
552 self.expect
553 .compare_or_panic(field.name(), &value, self.ctx, self.subscriber_name)
554 }
555
556 fn record_debug(&mut self, field: &Field, value: &dyn fmt::Debug) {
557 self.expect.compare_or_panic(
558 field.name(),
559 &field::debug(value),
560 self.ctx,
561 self.subscriber_name,
562 )
563 }
564}
565
566impl CheckVisitor<'_> {
567 pub(crate) fn finish(self) {
568 assert!(
569 self.expect.fields.is_empty(),
570 "[{}] {}missing {}",
571 self.subscriber_name,
572 self.expect,
573 self.ctx
574 );
575 }
576}
577
578impl<'a> From<&'a dyn Value> for ExpectedValue {
579 fn from(value: &'a dyn Value) -> Self {
580 struct MockValueBuilder {
581 value: Option<ExpectedValue>,
582 }
583
584 impl Visit for MockValueBuilder {
585 fn record_f64(&mut self, _: &Field, value: f64) {
586 self.value = Some(ExpectedValue::F64(value));
587 }
588
589 fn record_i64(&mut self, _: &Field, value: i64) {
590 self.value = Some(ExpectedValue::I64(value));
591 }
592
593 fn record_u64(&mut self, _: &Field, value: u64) {
594 self.value = Some(ExpectedValue::U64(value));
595 }
596
597 fn record_bool(&mut self, _: &Field, value: bool) {
598 self.value = Some(ExpectedValue::Bool(value));
599 }
600
601 fn record_str(&mut self, _: &Field, value: &str) {
602 self.value = Some(ExpectedValue::Str(value.to_owned()));
603 }
604
605 fn record_debug(&mut self, _: &Field, value: &dyn fmt::Debug) {
606 self.value = Some(ExpectedValue::Debug(format!("{:?}", value)));
607 }
608 }
609
610 let fake_field = callsite!(name: "fake", kind: Kind::EVENT, fields: fake_field)
611 .metadata()
612 .fields()
613 .field("fake_field")
614 .unwrap();
615 let mut builder = MockValueBuilder { value: None };
616 value.record(&fake_field, &mut builder);
617 builder
618 .value
619 .expect("finish called before a value was recorded")
620 }
621}
622
623impl fmt::Display for ExpectedFields {
624 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
625 write!(f, "fields ")?;
626 let entries = self
627 .fields
628 .iter()
629 .map(|(k, v)| (field::display(k), field::display(v)));
630 f.debug_map().entries(entries).finish()
631 }
632}