pathfinder_common/
test_utils.rs

1//! Various test utils used in other pathfinder related crates
2use fake::{Dummy, Fake, Faker};
3use rand::Rng;
4
5/// In order to provide some basic consistency guarantees some containers just
6/// cannot be empty
7pub fn fake_non_empty_with_rng<C, T>(rng: &mut impl Rng) -> C
8where
9    C: std::iter::FromIterator<T>,
10    T: Dummy<Faker>,
11{
12    let len = rng.gen_range(1..10);
13
14    std::iter::repeat_with(|| Faker.fake_with_rng(rng))
15        .take(len)
16        .collect()
17}
18
19/// Metrics related test aids
20pub mod metrics {
21    use std::borrow::Cow;
22    use std::collections::HashMap;
23    use std::sync::atomic::{AtomicU64, Ordering};
24    use std::sync::{Arc, RwLock};
25
26    use metrics::{
27        Counter,
28        CounterFn,
29        Gauge,
30        Histogram,
31        Key,
32        KeyName,
33        Label,
34        Recorder,
35        SharedString,
36        Unit,
37    };
38
39    /// # Purpose
40    ///
41    /// Unset the global recorder when this guard is dropped, so you don't have
42    /// to remember to call `metrics::clear_recorder()` manually at the end
43    /// of a test.
44    ///
45    /// # Warning
46    ///
47    /// Does __not__ provide any safety wrt. threading/reentrancy/etc.
48    ///
49    /// # Rationale
50    ///
51    /// The [`metrics`] crate relies on the recorder being a [singleton](https://docs.rs/metrics/latest/metrics/#installing-recorders).
52    pub struct ScopedRecorderGuard;
53
54    impl ScopedRecorderGuard {
55        pub fn new<R>(recorder: R) -> Self
56        where
57            R: Recorder + 'static,
58        {
59            metrics::set_boxed_recorder(Box::new(recorder)).unwrap();
60            Self
61        }
62    }
63
64    impl Drop for ScopedRecorderGuard {
65        fn drop(&mut self) {
66            unsafe { metrics::clear_recorder() }
67        }
68    }
69
70    /// Mocks a [recorder](`metrics::Recorder`) only for specified
71    /// [labels](`metrics::Label`) treating the rest of registered metrics
72    /// as _no-op_
73    #[derive(Debug, Default)]
74    pub struct FakeRecorder(FakeRecorderHandle);
75
76    /// Handle to the [`FakeRecorder`], which allows to get the current value of
77    /// counters.
78    #[derive(Clone, Debug, Default)]
79    pub struct FakeRecorderHandle {
80        counters: Arc<RwLock<HashMap<Key, Arc<FakeCounterFn>>>>,
81        methods: Option<&'static [&'static str]>,
82    }
83
84    #[derive(Debug, Default)]
85    struct FakeCounterFn(AtomicU64);
86
87    impl Recorder for FakeRecorder {
88        fn describe_counter(&self, _: KeyName, _: Option<Unit>, _: SharedString) {}
89        fn describe_gauge(&self, _: KeyName, _: Option<Unit>, _: SharedString) {}
90        fn describe_histogram(&self, _: KeyName, _: Option<Unit>, _: SharedString) {}
91
92        /// Registers a counter if the method is on the `self::methods` list and
93        /// returns it.
94        ///
95        /// # Warning
96        ///
97        /// Returns `Counter::noop()` in other cases.
98        fn register_counter(&self, key: &Key) -> Counter {
99            if self.is_key_used(key) {
100                // Check if the counter is already registered
101                let read_guard = self.0.counters.read().unwrap();
102                if let Some(counter) = read_guard.get(key) {
103                    // Do nothing, it's already there
104                    return Counter::from_arc(counter.clone());
105                }
106                drop(read_guard);
107                // We could still be having some contention on write >here<, but let's assume
108                // most of the time the `read()` above does its job
109                let mut write_guard = self.0.counters.write().unwrap();
110                // Put it there
111                // let counter = write_guard.entry(key.clone()).or_default();
112                let counter = write_guard.entry(key.clone()).or_default();
113                Counter::from_arc(counter.clone())
114            } else {
115                // We don't care
116                Counter::noop()
117            }
118        }
119
120        fn register_gauge(&self, _: &Key) -> Gauge {
121            unimplemented!()
122        }
123        fn register_histogram(&self, _: &Key) -> Histogram {
124            // Ignored in tests for now
125            Histogram::noop()
126        }
127    }
128
129    impl FakeRecorder {
130        /// Creates a [`FakeRecorder`] which only holds counter values for
131        /// `methods`.
132        ///
133        /// All other methods use the [no-op counters](`https://docs.rs/metrics/latest/metrics/struct.Counter.html#method.noop`)
134        pub fn new_for(methods: &'static [&'static str]) -> Self {
135            Self(FakeRecorderHandle {
136                counters: Arc::default(),
137                methods: Some(methods),
138            })
139        }
140
141        /// Gets the handle to this recorder
142        pub fn handle(&self) -> FakeRecorderHandle {
143            self.0.clone()
144        }
145
146        fn is_key_used(&self, key: &Key) -> bool {
147            match self.0.methods {
148                Some(methods) => key.labels().any(|label| {
149                    label.key() == "method" && methods.iter().any(|&method| method == label.value())
150                }),
151                None => true,
152            }
153        }
154    }
155
156    impl FakeRecorderHandle {
157        /// Panics in any of the following cases
158        /// - `counter_name` was not registered via
159        ///   [`metrics::register_counter`]
160        /// - `method_name` does not match any [value](https://docs.rs/metrics/latest/metrics/struct.Label.html#method.value)
161        ///   for the `method` [label](https://docs.rs/metrics/latest/metrics/struct.Label.html#)
162        ///   [key](https://docs.rs/metrics/latest/metrics/struct.Label.html#method.key)
163        ///   registered via [`metrics::register_counter`]
164        pub fn get_counter_value(
165            &self,
166            counter_name: &'static str,
167            method_name: impl Into<Cow<'static, str>>,
168        ) -> u64 {
169            let read_guard = self.counters.read().unwrap();
170            read_guard
171                .get(&Key::from_parts(
172                    counter_name,
173                    vec![Label::new("method", method_name.into())],
174                ))
175                .unwrap()
176                .0
177                .load(Ordering::Relaxed)
178        }
179
180        /// Panics in any of the following cases
181        /// - `counter_name` was not registered via
182        ///   [`metrics::register_counter`]
183        /// - `labels` don't match the [label](https://docs.rs/metrics/latest/metrics/struct.Label.html#)-s
184        ///   registered via [`metrics::register_counter`]
185        pub fn get_counter_value_by_label<const N: usize>(
186            &self,
187            counter_name: &'static str,
188            labels: [(&'static str, &'static str); N],
189        ) -> u64 {
190            let read_guard = self.counters.read().unwrap();
191            read_guard
192                .get(&Key::from_parts(
193                    counter_name,
194                    labels
195                        .iter()
196                        .map(|&(key, val)| Label::new(key, val))
197                        .collect::<Vec<_>>(),
198                ))
199                .expect("Unregistered counter name")
200                .0
201                .load(Ordering::Relaxed)
202        }
203    }
204
205    impl CounterFn for FakeCounterFn {
206        fn increment(&self, val: u64) {
207            self.0.fetch_add(val, Ordering::Relaxed);
208        }
209        fn absolute(&self, _: u64) {
210            unimplemented!()
211        }
212    }
213}