use core::num::NonZeroU64;
use super::{
ElapsedTimerTokens, EmptyElapsedTimerTokens, Timer, TimerIn, TimerOut, TimerToken, WakeAfter,
};
use crate::Pipe;
const fn nonzero(value: u64) -> Result<NonZeroU64, &'static str> {
match NonZeroU64::new(value) {
Some(value) => Ok(value),
None => Err("zero was not positive"),
}
}
#[test]
fn wake_after_preserves_the_declared_precision() -> Result<(), &'static str> {
let seconds = WakeAfter::seconds(nonzero(5)?);
let milliseconds = WakeAfter::milliseconds(nonzero(5_000)?);
assert_eq!(seconds.checked_nanoseconds(), nonzero(5_000_000_000).ok());
assert_eq!(
milliseconds.checked_nanoseconds(),
seconds.checked_nanoseconds()
);
assert_ne!(seconds, milliseconds);
assert_eq!(seconds.ticks(), nonzero(5)?);
assert_eq!(
seconds.precision().nanoseconds_per_tick(),
nonzero(1_000_000_000)?
);
Ok(())
}
#[test]
fn wake_after_reports_nanosecond_overflow() -> Result<(), &'static str> {
let after = WakeAfter::seconds(nonzero(u64::MAX)?);
assert_eq!(after.checked_nanoseconds(), None);
Ok(())
}
#[test]
fn elapsed_batches_are_nonempty() {
assert_eq!(
ElapsedTimerTokens::<u8>::try_new(&[]),
Err(EmptyElapsedTimerTokens)
);
let tokens = [3, 5, 8];
let Ok(elapsed) = ElapsedTimerTokens::try_new(&tokens) else {
return;
};
assert_eq!(elapsed.tokens(), &tokens);
}
#[test]
fn timer_protocol_preserves_arm_cancel_and_elapsed_facts() -> Result<(), &'static str> {
fn accepts_timer_input(_input: <Timer<u64> as Pipe>::In<'_>) {}
fn accepts_timer_output(_output: <Timer<u64> as Pipe>::Out<'_>) {}
let after = WakeAfter::milliseconds(nonzero(25)?);
accepts_timer_input(TimerIn::Arm { token: 7, after });
accepts_timer_input(TimerIn::Cancel { token: 7 });
let tokens = [7, 9];
let elapsed = ElapsedTimerTokens::try_new(&tokens).map_err(|_| "batch was empty")?;
accepts_timer_output(TimerOut::Elapsed { tokens: elapsed });
Ok(())
}
#[test]
fn timer_tokens_preserve_their_nonzero_value() -> Result<(), &'static str> {
let timer_token = TimerToken::from_nonzero(nonzero(17)?);
assert_eq!(timer_token.value(), nonzero(17)?);
assert_eq!(NonZeroU64::from(timer_token), nonzero(17)?);
#[cfg(feature = "storage")]
assert_eq!(
crate::storage::warp_table::IndexKey::index_word(timer_token),
crate::storage::warp_table::integer_index_word(17)
);
Ok(())
}
#[test]
fn wake_precision_conversions_preserve_exact_ticks_and_overflow() -> Result<(), &'static str> {
for (precision, scale) in [
(super::WakePrecision::Nanosecond, 1),
(super::WakePrecision::Microsecond, 1_000),
(super::WakePrecision::Millisecond, 1_000_000),
(super::WakePrecision::Second, 1_000_000_000),
] {
let maximum = u64::MAX.checked_div(scale).ok_or("zero precision")?;
let after = WakeAfter::new(nonzero(maximum)?, precision);
assert_eq!(after.ticks().get(), maximum);
assert_eq!(after.precision(), precision);
assert_eq!(
after.checked_nanoseconds().map(NonZeroU64::get),
maximum.checked_mul(scale)
);
if let Some(outside) = maximum.checked_add(1) {
assert_eq!(
WakeAfter::new(nonzero(outside)?, precision).checked_nanoseconds(),
None
);
}
}
let after = WakeAfter::microseconds(nonzero(37)?);
assert_eq!(after.ticks(), nonzero(37)?);
assert_eq!(after.precision(), super::WakePrecision::Microsecond);
assert_eq!(after.checked_nanoseconds(), Some(nonzero(37_000)?));
Ok(())
}
#[test]
fn timer_conversion_returns_every_inapplicable_output_intact() -> Result<(), &'static str> {
use super::{TimerArmRefusal, TimerCancellationRefusal};
let tokens = [17, 29];
let elapsed = || ElapsedTimerTokens::try_new(&tokens).map_err(|_| "empty batch");
let arm_outputs = || -> Result<_, &'static str> {
Ok([
TimerOut::Elapsed { tokens: elapsed()? },
TimerOut::Cancelled { token: 17 },
TimerOut::CancellationRefused {
token: 29,
reason: TimerCancellationRefusal::NotArmed,
},
])
};
for (output, expected) in arm_outputs()?.into_iter().zip(arm_outputs()?) {
let Err(returned) = output.into_arm_outcome() else {
return Err("non-arm output accepted");
};
assert_eq!(returned, expected);
if let TimerOut::Elapsed { tokens: returned } = returned {
assert!(core::ptr::eq(returned.tokens(), tokens.as_slice()));
}
}
let cancellation_outputs = || -> Result<_, &'static str> {
Ok([
TimerOut::Elapsed { tokens: elapsed()? },
TimerOut::Armed { token: 17 },
TimerOut::ArmRefused {
token: 29,
after: WakeAfter::microseconds(nonzero(3)?),
reason: TimerArmRefusal::AtCapacity {
maximum_deadlines: 7,
},
},
])
};
for (output, expected) in cancellation_outputs()?
.into_iter()
.zip(cancellation_outputs()?)
{
let Err(returned) = output.into_cancellation_outcome() else {
return Err("non-cancellation output accepted");
};
assert_eq!(returned, expected);
if let TimerOut::Elapsed { tokens: returned } = returned {
assert!(core::ptr::eq(returned.tokens(), tokens.as_slice()));
}
}
Ok(())
}