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