#![allow(clippy::unwrap_used, clippy::expect_used)]
use core::future::Future;
use proxima_clock::ticks::Ticks;
use proxima_core::markers::DropSafe;
use proxima_primitives::block_on;
use proxima_primitives::pipe::fan_in::{Exhausted, FanIn, Select};
use proxima_primitives::pipe::primitives::UnpinPipe;
struct RedundantSource {
reading: Ticks,
remaining_answers: core::cell::Cell<u32>,
}
impl UnpinPipe for RedundantSource {
type In = ();
type Out = Ticks;
type Err = Exhausted;
fn call(&self, (): ()) -> impl Future<Output = Result<Ticks, Exhausted>> + Unpin {
let remaining = self.remaining_answers.get();
let outcome = if remaining == 0 {
Err(Exhausted)
} else {
self.remaining_answers.set(remaining - 1);
Ok(self.reading)
};
core::future::ready(outcome)
}
}
impl DropSafe for RedundantSource {}
#[test]
fn fan_in_arbitrates_across_two_redundant_tick_sources_with_no_bespoke_machinery() {
let gps_pps = RedundantSource {
reading: Ticks::from_raw(1_000),
remaining_answers: core::cell::Cell::new(2),
};
let local_oscillator = RedundantSource {
reading: Ticks::from_raw(2_000),
remaining_answers: core::cell::Cell::new(2),
};
let fan = FanIn::new([gps_pps, local_oscillator], Select::RoundRobin);
let first = block_on(fan.call(())).expect("gps still live");
assert_eq!(first, Ticks::from_raw(1_000));
let second = block_on(fan.call(())).expect("local oscillator still live");
assert_eq!(second, Ticks::from_raw(2_000));
let third = block_on(fan.call(())).expect("gps still live");
assert_eq!(third, Ticks::from_raw(1_000));
let fourth = block_on(fan.call(())).expect("local oscillator still live");
assert_eq!(fourth, Ticks::from_raw(2_000));
assert_eq!(
fan.live_count(),
2,
"both sources still marked live before their final call"
);
let fifth = block_on(fan.call(()));
assert_eq!(
fifth,
Err(Exhausted),
"both redundant sources answered their fixed count; the merge itself exhausts"
);
assert_eq!(fan.live_count(), 0);
}
#[test]
fn fan_in_keeps_arbitrating_when_one_redundant_source_goes_stale_first() {
let gps_pps = RedundantSource {
reading: Ticks::from_raw(1_000),
remaining_answers: core::cell::Cell::new(1),
};
let local_oscillator = RedundantSource {
reading: Ticks::from_raw(2_000),
remaining_answers: core::cell::Cell::new(3),
};
let fan = FanIn::new([gps_pps, local_oscillator], Select::RoundRobin);
let readings: Vec<Ticks> = (0..4)
.map(|_| block_on(fan.call(())).expect("at least one source still live"))
.collect();
assert_eq!(
readings,
vec![
Ticks::from_raw(1_000),
Ticks::from_raw(2_000),
Ticks::from_raw(2_000),
Ticks::from_raw(2_000),
],
"GPS answers once then exhausts; the merge falls through to the local oscillator"
);
assert_eq!(
fan.live_count(),
1,
"local oscillator's 4th answer used its last reading but is not yet KNOWN exhausted \
until asked again"
);
let fifth = block_on(fan.call(()));
assert_eq!(
fifth,
Err(Exhausted),
"both redundant sources have now been asked past their fixed answer count"
);
assert_eq!(fan.live_count(), 0);
}