windowed_stats/experimental/
testing.rs

1// Copyright 2024 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use derivative::Derivative;
6use fuchsia_sync::Mutex;
7use std::any::{self, Any};
8use std::collections::HashMap;
9use std::marker::PhantomData;
10use std::sync::Arc;
11
12use crate::experimental::clock::{Timed, Timestamp};
13use crate::experimental::series::interpolation::InterpolationKind;
14use crate::experimental::series::statistic::{FoldError, Metadata, SerialStatistic};
15use crate::experimental::series::{Interpolator, MatrixSampler, SerializedBuffer, TimeMatrix};
16use crate::experimental::serve::{
17    BufferedSampler, InspectSender, InspectedTimeMatrix, ServedTimeMatrix,
18};
19
20type DynamicSample = Box<dyn Any + Send>;
21
22#[derive(Derivative)]
23#[derivative(Debug, PartialEq)]
24pub enum TimeMatrixCall<T> {
25    Fold(Timed<T>),
26    Interpolate(Timestamp),
27}
28
29impl<T> TimeMatrixCall<T> {
30    fn map<U, F>(self, f: F) -> TimeMatrixCall<U>
31    where
32        F: FnOnce(T) -> U,
33    {
34        match self {
35            TimeMatrixCall::Fold(timed) => TimeMatrixCall::Fold(timed.map(f)),
36            TimeMatrixCall::Interpolate(timestamp) => TimeMatrixCall::Interpolate(timestamp),
37        }
38    }
39}
40
41impl<T, E> TimeMatrixCall<Result<T, E>> {
42    fn transpose(self) -> Result<TimeMatrixCall<T>, E> {
43        match self {
44            TimeMatrixCall::Fold(result) => match result.transpose() {
45                Ok(sample) => Ok(TimeMatrixCall::Fold(sample)),
46                Err(error) => Err(error),
47            },
48            TimeMatrixCall::Interpolate(timestamp) => Ok(TimeMatrixCall::Interpolate(timestamp)),
49        }
50    }
51}
52
53#[derive(Debug)]
54pub struct TimeMatrixCallLog {
55    calls: HashMap<String, Vec<TimeMatrixCall<DynamicSample>>>,
56}
57
58impl TimeMatrixCallLog {
59    pub fn drain<T: Any + Send + Clone>(&mut self, name: &str) -> Vec<TimeMatrixCall<T>> {
60        self.calls
61            .remove(name)
62            .unwrap_or_default()
63            .into_iter()
64            .map(|call| call.map(|sample| sample.downcast::<T>().map(|sample| *sample)))
65            .map(TimeMatrixCall::transpose)
66            .collect::<Result<Vec<_>, _>>()
67            .unwrap_or_else(|_| {
68                panic!(
69                    "in time matrix \"{}\": failed to downcast dynamic sample of type `{}`",
70                    name,
71                    any::type_name::<T>()
72                )
73            })
74    }
75
76    pub fn as_hash_map(&self) -> &HashMap<String, Vec<TimeMatrixCall<DynamicSample>>> {
77        &self.calls
78    }
79
80    pub fn is_empty(&self) -> bool {
81        self.calls.is_empty()
82    }
83}
84
85#[derive(Clone)]
86pub struct MockTimeMatrixClient {
87    matrices: Arc<Mutex<Vec<Box<dyn ServedTimeMatrix>>>>,
88    calls: Arc<Mutex<Vec<(String, TimeMatrixCall<DynamicSample>)>>>,
89}
90
91impl MockTimeMatrixClient {
92    pub fn new() -> Self {
93        Self { matrices: Arc::new(Mutex::new(vec![])), calls: Arc::new(Mutex::new(vec![])) }
94    }
95}
96
97impl MockTimeMatrixClient {
98    pub fn fold_buffered_samples(&self) -> TimeMatrixCallLog {
99        for matrix in self.matrices.lock().iter_mut() {
100            matrix.fold_buffered_samples().unwrap();
101        }
102        let mut calls = HashMap::<_, Vec<_>>::new();
103        for (name, call) in self.calls.lock().drain(..) {
104            calls.entry(name).or_default().push(call);
105        }
106        TimeMatrixCallLog { calls }
107    }
108}
109
110impl InspectSender for MockTimeMatrixClient {
111    fn inspect_time_matrix<F, P>(
112        &self,
113        name: impl Into<String>,
114        _matrix: TimeMatrix<F, P>,
115    ) -> InspectedTimeMatrix<F::Sample>
116    where
117        TimeMatrix<F, P>: 'static + MatrixSampler<F::Sample> + Send,
118        Metadata<F>: 'static + Send + Sync,
119        F: SerialStatistic<P>,
120        F::Sample: Send,
121        P: InterpolationKind,
122    {
123        let name = name.into();
124        let (sender, matrix) = BufferedSampler::from_time_matrix(MockTimeMatrix::new(
125            name.clone(),
126            self.calls.clone(),
127        ));
128        self.matrices.lock().push(Box::new(matrix));
129        InspectedTimeMatrix::new(name, sender)
130    }
131
132    fn inspect_time_matrix_with_metadata<F, P>(
133        &self,
134        name: impl Into<String>,
135        matrix: TimeMatrix<F, P>,
136        _metadata: impl Into<Metadata<F>>,
137    ) -> InspectedTimeMatrix<F::Sample>
138    where
139        TimeMatrix<F, P>: 'static + MatrixSampler<F::Sample> + Send,
140        Metadata<F>: 'static + Send + Sync,
141        F: SerialStatistic<P>,
142        F::Sample: Send,
143        P: InterpolationKind,
144    {
145        self.inspect_time_matrix(name, matrix)
146    }
147}
148
149struct MockTimeMatrix<T> {
150    name: String,
151    calls: Arc<Mutex<Vec<(String, TimeMatrixCall<DynamicSample>)>>>,
152    phantom: PhantomData<fn() -> T>,
153}
154
155impl<T> MockTimeMatrix<T> {
156    pub fn new(
157        name: impl Into<String>,
158        calls: Arc<Mutex<Vec<(String, TimeMatrixCall<DynamicSample>)>>>,
159    ) -> Self {
160        MockTimeMatrix { name: name.into(), calls, phantom: PhantomData }
161    }
162}
163
164impl<T> Interpolator for MockTimeMatrix<T> {
165    fn interpolate(&mut self, timestamp: Timestamp) -> Result<(), FoldError> {
166        self.calls.lock().push((self.name.clone(), TimeMatrixCall::Interpolate(timestamp)));
167        Ok(())
168    }
169
170    fn interpolate_and_get_buffers(
171        &mut self,
172        timestamp: Timestamp,
173    ) -> Result<SerializedBuffer, FoldError> {
174        self.interpolate(timestamp)?;
175        Ok(SerializedBuffer { data_semantic: "mock".to_string(), data: vec![] })
176    }
177}
178
179impl<T> MatrixSampler<T> for MockTimeMatrix<T>
180where
181    T: 'static + Send,
182{
183    fn fold(&mut self, sample: Timed<T>) -> Result<(), FoldError> {
184        let sample = sample.map(|v| Box::new(v) as DynamicSample);
185        self.calls.lock().push((self.name.clone(), TimeMatrixCall::Fold(sample)));
186        Ok(())
187    }
188}