tracing_fluent_assertions/
assertion.rs

1//! Core assertion types and utilities.
2use std::{marker::PhantomData, sync::Arc};
3
4use crate::{
5    matcher::SpanMatcher,
6    state::{EntryState, State},
7};
8
9enum AssertionCriterion {
10    WasCreated,
11    WasEntered,
12    WasExited,
13    WasClosed,
14    WasNotCreated,
15    WasNotEntered,
16    WasNotExited,
17    WasNotClosed,
18    CreatedExactly(usize),
19    EnteredExactly(usize),
20    ExitedExactly(usize),
21    ClosedExactly(usize),
22    CreatedAtLeast(usize),
23    EnteredAtLeast(usize),
24    ExitedAtLeast(usize),
25    ClosedAtLeast(usize),
26}
27
28impl AssertionCriterion {
29    pub fn assert(&self, state: &Arc<EntryState>) {
30        match self {
31            AssertionCriterion::WasCreated => assert!(state.num_created() != 0),
32            AssertionCriterion::WasEntered => assert!(state.num_entered() != 0),
33            AssertionCriterion::WasExited => assert!(state.num_exited() != 0),
34            AssertionCriterion::WasClosed => assert!(state.num_closed() != 0),
35            AssertionCriterion::WasNotCreated => assert_eq!(0, state.num_created()),
36            AssertionCriterion::WasNotEntered => assert_eq!(0, state.num_entered()),
37            AssertionCriterion::WasNotExited => assert_eq!(0, state.num_exited()),
38            AssertionCriterion::WasNotClosed => assert_eq!(0, state.num_closed()),
39            AssertionCriterion::CreatedExactly(times) => assert_eq!(state.num_created(), *times),
40            AssertionCriterion::EnteredExactly(times) => assert_eq!(state.num_entered(), *times),
41            AssertionCriterion::ExitedExactly(times) => assert_eq!(state.num_exited(), *times),
42            AssertionCriterion::ClosedExactly(times) => assert_eq!(state.num_closed(), *times),
43            AssertionCriterion::CreatedAtLeast(times) => assert!(state.num_created() >= *times),
44            AssertionCriterion::EnteredAtLeast(times) => assert!(state.num_entered() >= *times),
45            AssertionCriterion::ExitedAtLeast(times) => assert!(state.num_exited() >= *times),
46            AssertionCriterion::ClosedAtLeast(times) => assert!(state.num_closed() >= *times),
47        }
48    }
49
50    pub fn try_assert(&self, state: &Arc<EntryState>) -> bool {
51        match self {
52            AssertionCriterion::WasCreated => state.num_created() != 0,
53            AssertionCriterion::WasEntered => state.num_entered() != 0,
54            AssertionCriterion::WasExited => state.num_exited() != 0,
55            AssertionCriterion::WasClosed => state.num_closed() != 0,
56            AssertionCriterion::WasNotCreated => state.num_created() == 0,
57            AssertionCriterion::WasNotEntered => state.num_entered() == 0,
58            AssertionCriterion::WasNotExited => state.num_exited() == 0,
59            AssertionCriterion::WasNotClosed => state.num_closed() == 0,
60            AssertionCriterion::CreatedExactly(times) => state.num_created() == *times,
61            AssertionCriterion::EnteredExactly(times) => state.num_entered() == *times,
62            AssertionCriterion::ExitedExactly(times) => state.num_exited() == *times,
63            AssertionCriterion::ClosedExactly(times) => state.num_closed() == *times,
64            AssertionCriterion::CreatedAtLeast(times) => state.num_created() >= *times,
65            AssertionCriterion::EnteredAtLeast(times) => state.num_entered() >= *times,
66            AssertionCriterion::ExitedAtLeast(times) => state.num_exited() >= *times,
67            AssertionCriterion::ClosedAtLeast(times) => state.num_closed() >= *times,
68        }
69    }
70}
71
72/// A specific set of criteria to enforce on matching spans.
73///
74/// Assertions represent both a span "matcher" -- which controls which spans the criteria are
75/// applied to -- and the criteria themselves, which define the behavior to assert against the
76/// matching spans.
77///
78/// ## Matching behavior
79///
80/// As an `Assertion` can match multiple spans, care must be taken when building the `Assertion` to
81/// constrain the matcher correctly.  For example, if you want to focus on a specific span, you
82/// would want to use match on the span name at a minimum, and potentially match on the span target
83/// if there were other spans with the same name in different modules.  However, if you simply
84/// wanted to check if any spans under a specific module path were created -- perhaps to assert that
85/// a particular codeflow is being exercised, but not assert _how_ it's being exercised -- then only
86/// specifying the span target might suffice.
87pub struct Assertion {
88    state: Arc<State>,
89    entry_state: Arc<EntryState>,
90    matcher: SpanMatcher,
91    criteria: Vec<AssertionCriterion>,
92}
93
94impl Assertion {
95    /// Asserts that all criteria have been met.
96    ///
97    /// Uses the "assert" macros from the standard library, so criterion which have not been met
98    /// will cause a panic, similar to using the "assert" macros directly.
99    ///
100    /// For a fallible assertion that can be called over and over without panicking, [`try_assert`]
101    /// can be used instead.
102    pub fn assert(&self) {
103        for criterion in &self.criteria {
104            criterion.assert(&self.entry_state);
105        }
106    }
107
108    /// Attempts to assert that all criteria have been met.
109    ///
110    /// If any of the criteria have not yet been met, `false` will be returned.  Otherwise, `true`
111    /// will be returned.
112    ///
113    /// If assertions should end your test immediately, [`assert`] can be used instead.
114    pub fn try_assert(&self) -> bool {
115        for criterion in &self.criteria {
116            if !criterion.try_assert(&self.entry_state) {
117                return false;
118            }
119        }
120
121        true
122    }
123}
124
125impl Drop for Assertion {
126    fn drop(&mut self) {
127        self.state.remove_entry(&self.matcher);
128    }
129}
130
131/// An [`AssertionBuilder`] which does not yet have a span matcher.
132///
133/// A matcher consists of either a span name, or the target of a span itself, or potentially both.
134/// A span target refers to the `tracing` parlance, where "target" refers to the module path that a
135/// span is defined in.
136///
137/// Additionally, a span matcher can include specific fields that must be present on a span in order
138/// to match.
139pub struct NoMatcher {
140    _p: PhantomData<()>,
141}
142
143/// An [`AssertionBuilder`] which has a valid span matcher but does not yet have any assertion
144/// criteria.
145///
146/// Assertion criteria are the actual behavioral matchers, such as "this span must have been entered
147/// at least once" or "this span must have been created at least N times".
148pub struct NoCriteria {
149    _p: PhantomData<()>,
150}
151
152/// An [`AssertionBuilder`] which has a valid span matcher and at least one assertion criterion.
153pub struct Constrained {
154    _p: PhantomData<()>,
155}
156
157/// Configures and constructs an [`Assertion`].
158///
159/// This builder uses a state pattern to ensure that the necessary fields are configured before a
160/// valid `Assertion` can be constructed.  You may notice that some methods are only available once
161/// other methods have been called.
162///
163/// You must first define a span matcher, which defines how this assertion is matched to a given
164/// span, and then you must specify the assertion criteria itself, which defines the behavior of the
165/// span to assert for.
166///
167/// Once these are defined, an `Assertion` can be constructed by calling [`finalize`].
168pub struct AssertionBuilder<S> {
169    state: Arc<State>,
170    matcher: Option<SpanMatcher>,
171    criteria: Vec<AssertionCriterion>,
172    _builder_state: PhantomData<fn(S)>,
173}
174
175impl AssertionBuilder<NoMatcher> {
176    /// Sets the name of the span to match.
177    ///
178    /// All span matchers, which includes [`with_name`], [`with_target`], [`with_parent_name`], and
179    /// [`with_span_field`], are additive, which means a span must match all of them to match the
180    /// assertion overall.
181    pub fn with_name<S>(mut self, name: S) -> AssertionBuilder<NoCriteria>
182    where
183        S: Into<String>,
184    {
185        let matcher = self.matcher.get_or_insert_with(SpanMatcher::default);
186        matcher.set_name(name.into());
187
188        AssertionBuilder {
189            state: self.state,
190            matcher: self.matcher,
191            criteria: self.criteria,
192            _builder_state: PhantomData,
193        }
194    }
195
196    /// Sets the target of the span to match.
197    ///
198    /// All span matchers, which includes [`with_name`], [`with_target`], [`with_parent_name`], and
199    /// [`with_span_field`], are additive, which means a span must match all of them to match the
200    /// assertion overall.
201    pub fn with_target<S>(mut self, target: S) -> AssertionBuilder<NoCriteria>
202    where
203        S: Into<String>,
204    {
205        let matcher = self.matcher.get_or_insert_with(SpanMatcher::default);
206        matcher.set_target(target.into());
207
208        AssertionBuilder {
209            state: self.state,
210            matcher: self.matcher,
211            criteria: self.criteria,
212            _builder_state: PhantomData,
213        }
214    }
215}
216
217impl AssertionBuilder<NoCriteria> {
218    /// Sets the name of the span to match.
219    ///
220    /// All span matchers, which includes [`with_name`], [`with_target`], [`with_parent_name`], and
221    /// [`with_span_field`], are additive, which means a span must match all of them to match the
222    /// assertion overall.
223    pub fn with_name<S>(mut self, name: S) -> AssertionBuilder<NoCriteria>
224    where
225        S: Into<String>,
226    {
227        let matcher = self.matcher.get_or_insert_with(SpanMatcher::default);
228        matcher.set_name(name.into());
229
230        AssertionBuilder {
231            state: self.state,
232            matcher: self.matcher,
233            criteria: self.criteria,
234            _builder_state: PhantomData,
235        }
236    }
237
238    /// Sets the target of the span to match.
239    ///
240    /// All span matchers, which includes [`with_name`], [`with_target`], [`with_parent_name`], and
241    /// [`with_span_field`], are additive, which means a span must match all of them to match the
242    /// assertion overall.
243    pub fn with_target<S>(mut self, target: S) -> AssertionBuilder<NoCriteria>
244    where
245        S: Into<String>,
246    {
247        let matcher = self.matcher.get_or_insert_with(SpanMatcher::default);
248        matcher.set_target(target.into());
249
250        AssertionBuilder {
251            state: self.state,
252            matcher: self.matcher,
253            criteria: self.criteria,
254            _builder_state: PhantomData,
255        }
256    }
257
258    /// Sets the name of a parent span to match.
259    ///
260    /// The span must have at least one parent span within its entire lineage that matches the given
261    /// name.
262    ///
263    /// All span matchers, which includes [`with_name`], [`with_target`], [`with_parent_name`], and
264    /// [`with_span_field`], are additive, which means a span must match all of them to match the
265    /// assertion overall.
266    pub fn with_parent_name<S>(mut self, name: S) -> AssertionBuilder<NoCriteria>
267    where
268        S: Into<String>,
269    {
270        let matcher = self.matcher.get_or_insert_with(SpanMatcher::default);
271        matcher.set_parent_name(name.into());
272
273        AssertionBuilder {
274            state: self.state,
275            matcher: self.matcher,
276            criteria: self.criteria,
277            _builder_state: PhantomData,
278        }
279    }
280
281    /// Adds a field which the span must contain to match.
282    ///
283    /// The field is matched by name.
284    ///
285    /// All span matchers, which includes [`with_name`], [`with_target`], and [`with_span_field`],
286    /// are additive, which means a span must match all of them to match the assertion overall.
287    pub fn with_span_field<S>(mut self, field: S) -> AssertionBuilder<NoCriteria>
288    where
289        S: Into<String>,
290    {
291        if let Some(matcher) = self.matcher.as_mut() {
292            matcher.add_field_exists(field.into());
293        }
294
295        AssertionBuilder {
296            state: self.state,
297            matcher: self.matcher,
298            criteria: self.criteria,
299            _builder_state: PhantomData,
300        }
301    }
302
303    /// Asserts that a matching span was created at least once.
304    pub fn was_created(mut self) -> AssertionBuilder<Constrained> {
305        self.criteria.push(AssertionCriterion::WasCreated);
306
307        AssertionBuilder {
308            state: self.state,
309            matcher: self.matcher,
310            criteria: self.criteria,
311            _builder_state: PhantomData,
312        }
313    }
314
315    /// Asserts that a matching span was entered at least once.
316    pub fn was_entered(mut self) -> AssertionBuilder<Constrained> {
317        self.criteria.push(AssertionCriterion::WasEntered);
318
319        AssertionBuilder {
320            state: self.state,
321            matcher: self.matcher,
322            criteria: self.criteria,
323            _builder_state: PhantomData,
324        }
325    }
326
327    /// Asserts that a matching span was exited at least once.
328    pub fn was_exited(mut self) -> AssertionBuilder<Constrained> {
329        self.criteria.push(AssertionCriterion::WasExited);
330
331        AssertionBuilder {
332            state: self.state,
333            matcher: self.matcher,
334            criteria: self.criteria,
335            _builder_state: PhantomData,
336        }
337    }
338
339    /// Asserts that a matching span was closed at least once.
340    pub fn was_closed(mut self) -> AssertionBuilder<Constrained> {
341        self.criteria.push(AssertionCriterion::WasClosed);
342
343        AssertionBuilder {
344            state: self.state,
345            matcher: self.matcher,
346            criteria: self.criteria,
347            _builder_state: PhantomData,
348        }
349    }
350
351    /// Asserts that a matching span was not created.
352    pub fn was_not_created(mut self) -> AssertionBuilder<Constrained> {
353        self.criteria.push(AssertionCriterion::WasNotCreated);
354
355        AssertionBuilder {
356            state: self.state,
357            matcher: self.matcher,
358            criteria: self.criteria,
359            _builder_state: PhantomData,
360        }
361    }
362
363    /// Asserts that a matching span was not entered.
364    pub fn was_not_entered(mut self) -> AssertionBuilder<Constrained> {
365        self.criteria.push(AssertionCriterion::WasNotEntered);
366
367        AssertionBuilder {
368            state: self.state,
369            matcher: self.matcher,
370            criteria: self.criteria,
371            _builder_state: PhantomData,
372        }
373    }
374
375    /// Asserts that a matching span was not exited.
376    pub fn was_not_exited(mut self) -> AssertionBuilder<Constrained> {
377        self.criteria.push(AssertionCriterion::WasNotExited);
378
379        AssertionBuilder {
380            state: self.state,
381            matcher: self.matcher,
382            criteria: self.criteria,
383            _builder_state: PhantomData,
384        }
385    }
386
387    /// Asserts that a matching span was not closed.
388    pub fn was_not_closed(mut self) -> AssertionBuilder<Constrained> {
389        self.criteria.push(AssertionCriterion::WasNotClosed);
390
391        AssertionBuilder {
392            state: self.state,
393            matcher: self.matcher,
394            criteria: self.criteria,
395            _builder_state: PhantomData,
396        }
397    }
398
399    /// Asserts that a matching span was created exactly `n` times.
400    pub fn was_created_exactly(mut self, n: usize) -> AssertionBuilder<Constrained> {
401        self.criteria.push(AssertionCriterion::CreatedExactly(n));
402
403        AssertionBuilder {
404            state: self.state,
405            matcher: self.matcher,
406            criteria: self.criteria,
407            _builder_state: PhantomData,
408        }
409    }
410
411    /// Asserts that a matching span was entered exactly `n` times.
412    pub fn was_entered_exactly(mut self, n: usize) -> AssertionBuilder<Constrained> {
413        self.criteria.push(AssertionCriterion::EnteredExactly(n));
414
415        AssertionBuilder {
416            state: self.state,
417            matcher: self.matcher,
418            criteria: self.criteria,
419            _builder_state: PhantomData,
420        }
421    }
422
423    /// Asserts that a matching span was exited exactly `n` times.
424    pub fn was_exited_exactly(mut self, n: usize) -> AssertionBuilder<Constrained> {
425        self.criteria.push(AssertionCriterion::ExitedExactly(n));
426
427        AssertionBuilder {
428            state: self.state,
429            matcher: self.matcher,
430            criteria: self.criteria,
431            _builder_state: PhantomData,
432        }
433    }
434
435    /// Asserts that a matching span was closed exactly `n` times.
436    pub fn was_closed_exactly(mut self, n: usize) -> AssertionBuilder<Constrained> {
437        self.criteria.push(AssertionCriterion::ClosedExactly(n));
438
439        AssertionBuilder {
440            state: self.state,
441            matcher: self.matcher,
442            criteria: self.criteria,
443            _builder_state: PhantomData,
444        }
445    }
446
447    /// Asserts that a matching span was created at least `n` times.
448    pub fn was_created_at_least(mut self, n: usize) -> AssertionBuilder<Constrained> {
449        self.criteria.push(AssertionCriterion::CreatedAtLeast(n));
450
451        AssertionBuilder {
452            state: self.state,
453            matcher: self.matcher,
454            criteria: self.criteria,
455            _builder_state: PhantomData,
456        }
457    }
458
459    /// Asserts that a matching span was entered at least `n` times.
460    pub fn was_entered_at_least(mut self, n: usize) -> AssertionBuilder<Constrained> {
461        self.criteria.push(AssertionCriterion::EnteredAtLeast(n));
462
463        AssertionBuilder {
464            state: self.state,
465            matcher: self.matcher,
466            criteria: self.criteria,
467            _builder_state: PhantomData,
468        }
469    }
470
471    /// Asserts that a matching span was exited at least `n` times.
472    pub fn was_exited_at_least(mut self, n: usize) -> AssertionBuilder<Constrained> {
473        self.criteria.push(AssertionCriterion::ExitedAtLeast(n));
474
475        AssertionBuilder {
476            state: self.state,
477            matcher: self.matcher,
478            criteria: self.criteria,
479            _builder_state: PhantomData,
480        }
481    }
482
483    /// Asserts that a matching span was closed at least `n` times.
484    pub fn was_closed_at_least(mut self, n: usize) -> AssertionBuilder<Constrained> {
485        self.criteria.push(AssertionCriterion::ClosedAtLeast(n));
486
487        AssertionBuilder {
488            state: self.state,
489            matcher: self.matcher,
490            criteria: self.criteria,
491            _builder_state: PhantomData,
492        }
493    }
494}
495
496impl AssertionBuilder<Constrained> {
497    /// Asserts that a matching span was created at least once.
498    pub fn was_created(mut self) -> Self {
499        self.criteria.push(AssertionCriterion::WasCreated);
500        self
501    }
502
503    /// Asserts that a matching span was entered at least once.
504    pub fn was_entered(mut self) -> Self {
505        self.criteria.push(AssertionCriterion::WasEntered);
506        self
507    }
508
509    /// Asserts that a matching span was exited at least once.
510    pub fn was_exited(mut self) -> Self {
511        self.criteria.push(AssertionCriterion::WasExited);
512        self
513    }
514
515    /// Asserts that a matching span was closed at least once.
516    pub fn was_closed(mut self) -> Self {
517        self.criteria.push(AssertionCriterion::WasClosed);
518        self
519    }
520
521    /// Asserts that a matching span was not created.
522    pub fn was_not_created(mut self) -> Self {
523        self.criteria.push(AssertionCriterion::WasNotCreated);
524        self
525    }
526
527    /// Asserts that a matching span was not entered.
528    pub fn was_not_entered(mut self) -> Self {
529        self.criteria.push(AssertionCriterion::WasNotEntered);
530        self
531    }
532
533    /// Asserts that a matching span was not exited.
534    pub fn was_not_exited(mut self) -> Self {
535        self.criteria.push(AssertionCriterion::WasNotExited);
536        self
537    }
538
539    /// Asserts that a matching span was not closed.
540    pub fn was_not_closed(mut self) -> Self {
541        self.criteria.push(AssertionCriterion::WasNotClosed);
542        self
543    }
544
545    /// Asserts that a matching span was created exactly `n` times.
546    pub fn was_created_exactly(mut self, n: usize) -> Self {
547        self.criteria.push(AssertionCriterion::CreatedExactly(n));
548        self
549    }
550
551    /// Asserts that a matching span was entered exactly `n` times.
552    pub fn was_entered_exactly(mut self, n: usize) -> Self {
553        self.criteria.push(AssertionCriterion::EnteredExactly(n));
554        self
555    }
556
557    /// Asserts that a matching span was exited exactly `n` times.
558    pub fn was_exited_exactly(mut self, n: usize) -> Self {
559        self.criteria.push(AssertionCriterion::ExitedExactly(n));
560        self
561    }
562
563    /// Asserts that a matching span was closed exactly `n` times.
564    pub fn was_closed_exactly(mut self, n: usize) -> Self {
565        self.criteria.push(AssertionCriterion::ClosedExactly(n));
566        self
567    }
568
569    /// Asserts that a matching span was created at least `n` times.
570    pub fn was_created_at_least(mut self, n: usize) -> Self {
571        self.criteria.push(AssertionCriterion::CreatedAtLeast(n));
572        self
573    }
574
575    /// Asserts that a matching span was entered at least `n` times.
576    pub fn was_entered_at_least(mut self, n: usize) -> Self {
577        self.criteria.push(AssertionCriterion::EnteredAtLeast(n));
578        self
579    }
580
581    /// Asserts that a matching span was exited at least `n` times.
582    pub fn was_exited_at_least(mut self, n: usize) -> Self {
583        self.criteria.push(AssertionCriterion::ExitedAtLeast(n));
584        self
585    }
586
587    /// Asserts that a matching span was closed at least `n` times.
588    pub fn was_closed_at_least(mut self, n: usize) -> Self {
589        self.criteria.push(AssertionCriterion::ClosedAtLeast(n));
590        self
591    }
592
593    /// Creates the finalized `Assertion`.
594    ///
595    /// Once finalized, the assertion is live and its state will be updated going forward.
596    pub fn finalize(mut self) -> Assertion {
597        let matcher = self
598            .matcher
599            .take()
600            .expect("matcher must be present at this point");
601        let entry_state = self.state.create_entry(matcher.clone());
602        Assertion {
603            state: Arc::clone(&self.state),
604            entry_state,
605            matcher,
606            criteria: self.criteria,
607        }
608    }
609}
610
611/// Creates and stores all constructed [`Assertion`]s.
612#[derive(Clone, Default)]
613pub struct AssertionRegistry {
614    state: Arc<State>,
615}
616
617impl AssertionRegistry {
618    pub(crate) fn state(&self) -> &Arc<State> {
619        &self.state
620    }
621
622    /// Creates an [`AssertionBuilder`] for constructing a new [`Assertion`].
623    pub fn build(&self) -> AssertionBuilder<NoMatcher> {
624        AssertionBuilder {
625            state: Arc::clone(&self.state),
626            matcher: None,
627            criteria: Vec::new(),
628            _builder_state: PhantomData,
629        }
630    }
631}