#![cfg(feature = "std")]
use core::time::Duration;
use std::sync::{Arc, Mutex};
use std::time::Instant;
use pipe_io::sink::VecSink;
use pipe_io::source::IterSource;
use pipe_io::{Clock, Pipeline, Window, WindowPolicy};
#[derive(Clone)]
struct ScriptedClock {
times: Arc<Mutex<std::vec::IntoIter<Instant>>>,
fallback: Arc<Mutex<Instant>>,
}
impl ScriptedClock {
fn new(times: Vec<Instant>) -> Self {
let last = *times.last().expect("at least one time");
Self {
times: Arc::new(Mutex::new(times.into_iter())),
fallback: Arc::new(Mutex::new(last)),
}
}
}
impl Clock for ScriptedClock {
fn now(&self) -> Instant {
let mut iter = self.times.lock().unwrap();
if let Some(t) = iter.next() {
*self.fallback.lock().unwrap() = t;
t
} else {
*self.fallback.lock().unwrap()
}
}
}
#[test]
fn tumbling_window_rolls_up_metric() {
let t0 = Instant::now();
let clock = ScriptedClock::new(vec![
t0, t0 + Duration::from_secs(2), t0 + Duration::from_secs(9), t0 + Duration::from_secs(11), t0 + Duration::from_secs(15), t0 + Duration::from_secs(20), ]);
let sink = VecSink::<Vec<u32>>::new();
let handle = sink.handle();
Pipeline::from_source(IterSource::new(vec![1u32, 2, 3, 4, 5]))
.window_with(
WindowPolicy::Tumbling {
size: Duration::from_secs(10),
},
clock,
)
.map(|w: Window<u32>| w.into_inner())
.sink(sink)
.run()
.expect("run");
let groups = handle.take();
assert_eq!(groups.len(), 2);
assert_eq!(groups[0], vec![1, 2, 3]);
assert_eq!(groups[1], vec![4, 5]);
}
#[test]
fn session_window_boundary() {
let t0 = Instant::now();
let clock = ScriptedClock::new(vec![
t0, t0 + Duration::from_secs(1), t0 + Duration::from_secs(2), t0 + Duration::from_secs(30), t0 + Duration::from_secs(31), t0 + Duration::from_secs(32), ]);
let sink = VecSink::<Vec<u32>>::new();
let handle = sink.handle();
Pipeline::from_source(IterSource::new(vec![1u32, 2, 3, 4, 5]))
.window_with(
WindowPolicy::Session {
idle: Duration::from_secs(5),
},
clock,
)
.map(|w: Window<u32>| w.into_inner())
.sink(sink)
.run()
.expect("run");
let groups = handle.take();
assert_eq!(groups.len(), 2);
assert_eq!(groups[0], vec![1, 2, 3]);
assert_eq!(groups[1], vec![4, 5]);
}
#[test]
fn sliding_window_overlaps() {
let t0 = Instant::now();
let clock = ScriptedClock::new(vec![
t0, t0 + Duration::from_secs(3), t0 + Duration::from_secs(7), t0 + Duration::from_secs(11), t0 + Duration::from_secs(20), ]);
let sink = VecSink::<Vec<u32>>::new();
let handle = sink.handle();
Pipeline::from_source(IterSource::new(vec![1u32, 2, 3, 4]))
.window_with(
WindowPolicy::Sliding {
size: Duration::from_secs(10),
slide: Duration::from_secs(5),
},
clock,
)
.map(|w: Window<u32>| w.into_inner())
.sink(sink)
.run()
.expect("run");
let groups = handle.take();
assert_eq!(groups[0], vec![1, 2, 3]);
assert!(groups.iter().skip(1).any(|g| *g == vec![3, 4]));
assert!(groups.iter().skip(1).any(|g| *g == vec![4]));
}
#[test]
fn tumbling_with_no_items_emits_nothing() {
let t0 = Instant::now();
let clock = ScriptedClock::new(vec![t0]);
let sink = VecSink::<Vec<u32>>::new();
let handle = sink.handle();
Pipeline::from_source(IterSource::new(std::iter::empty::<u32>()))
.window_with(
WindowPolicy::Tumbling {
size: Duration::from_secs(10),
},
clock,
)
.map(|w: Window<u32>| w.into_inner())
.sink(sink)
.run()
.expect("run");
assert!(handle.take().is_empty());
}