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}