pipecircuit 0.0.20

Correct. Robust. Fast. Portable. Pick four.
Documentation
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(())
}