pipecircuit 0.0.22

Correct. Robust. Fast. Portable. Pick four.
Documentation
use super::*;
use alloc::boxed::Box;
use core::num::{NonZeroU32, NonZeroU64};

#[test]
fn impossible_column_capacity_preserves_the_creation_failure() -> Result<(), &'static str> {
    let capacity = Capacity::new(NonZeroU32::MIN.saturating_add(2_048));
    let Err(error) = DelayLedger::<[u8; (isize::MAX as usize) / 1_024]>::try_new(capacity) else {
        return Err("unrepresentable columns were allocated");
    };
    assert_eq!(
        error.failure(),
        DelayLedgerCreationFailure::StorageUnavailable
    );
    assert!(matches!(
        error._source,
        WarpTableCreationError::ColumnsUnavailable { .. }
    ));
    Ok(())
}

#[test]
fn exhausted_identifier_outcome_preserves_message_and_wake_precision() -> Result<(), &'static str> {
    let ledger = DelayLedger::try_new(Capacity::new(NonZeroU32::MIN)).map_err(|_| "ledger")?;
    let message = Box::<[u8]>::from([3, 7, 9]);
    let bytes = message.as_ptr();
    let after = WakeAfter::microseconds(NonZeroU64::MIN.saturating_add(12));
    let outcome = ledger.admission_outcome(
        WarpTableInsertOutcome::IdentifiersExhausted {
            rejected: PendingDelayedMessage { message },
        },
        after,
    );
    let DelayAdmissionOutcome::Refused { reason, rejected } = outcome else {
        return Err("exhaustion did not return the delayed message");
    };
    assert_eq!(reason, DelayUnavailable::IdentifiersExhausted);
    let (message, returned_after) = rejected.into_parts();
    assert_eq!(message.as_ref(), &[3, 7, 9]);
    assert_eq!(message.as_ptr(), bytes);
    assert_eq!(returned_after, after);
    assert!(ledger.is_empty());
    Ok(())
}

#[test]
fn late_withdrawal_and_refusal_preserve_admission_after_release() -> Result<(), &'static str> {
    let mut ledger = DelayLedger::try_new(Capacity::new(NonZeroU32::MIN)).map_err(|_| "ledger")?;
    let after = WakeAfter::microseconds(NonZeroU64::MIN.saturating_add(7));
    let message = Box::<[u8]>::from([1, 2, 3]);
    let bytes = message.as_ptr();
    let DelayAdmissionOutcome::Admitted { admission } = ledger.admit(Delayed::new(message, after))
    else {
        return Err("admission");
    };
    let input = admission.timer_input();
    let token = admission.token;
    let DelayReleaseOutcome::Released { message } = ledger.release(token) else {
        return Err("release");
    };
    assert_eq!(message.as_ref(), &[1, 2, 3]);
    assert_eq!(message.as_ptr(), bytes);
    let DelayWithdrawalOutcome::Absent { admission } = ledger.withdraw(admission) else {
        return Err("released message withdrawn twice");
    };
    assert_eq!(admission.timer_input(), input);
    let reason = TimerArmRefusal::AtCapacity {
        maximum_deadlines: 9,
    };
    let output = crate::TimerOut::ArmRefused {
        token,
        after,
        reason,
    }
    .into_arm_outcome()
    .map_err(|_| "arm output")?;
    let DelayArmOutcome::Refused { refusal } = admission
        .settle_timer_arm(output)
        .map_err(|_| "arm mismatch")?
    else {
        return Err("refusal");
    };
    let DelayRefusalOutcome::Stale { refusal } = ledger.refuse(refusal) else {
        return Err("released message refused twice");
    };
    assert_eq!(refusal.token, token);
    assert_eq!(refusal.wake_after, after);
    assert_eq!(refusal.reason(), reason);
    assert!(ledger.is_empty());
    assert_eq!(ledger.len(), 0);
    Ok(())
}

#[test]
fn pending_count_tracks_multiple_admissions_and_out_of_order_release() -> Result<(), &'static str> {
    let capacity = Capacity::new(NonZeroU32::MIN.saturating_add(2));
    let mut ledger = DelayLedger::try_new(capacity).map_err(|_| "ledger")?;
    assert_eq!(ledger.capacity(), capacity);
    assert_eq!(ledger.len(), 0);
    let after = WakeAfter::microseconds(NonZeroU64::MIN);
    let mut tokens = alloc::vec::Vec::new();
    for message in [11, 23, 37] {
        let DelayAdmissionOutcome::Admitted { admission } =
            ledger.admit(Delayed::new(message, after))
        else {
            return Err("admission");
        };
        tokens.push(admission.token);
        assert_eq!(ledger.len(), tokens.len());
        assert!(!ledger.is_empty());
    }
    for (token, expected, remaining) in [
        (*tokens.get(1).ok_or("middle")?, 23, 2),
        (*tokens.first().ok_or("first")?, 11, 1),
        (*tokens.get(2).ok_or("last")?, 37, 0),
    ] {
        let DelayReleaseOutcome::Released { message } = ledger.release(token) else {
            return Err("release");
        };
        assert_eq!(message, expected);
        assert_eq!(ledger.len(), remaining);
        assert_eq!(ledger.is_empty(), remaining == 0);
    }
    Ok(())
}

#[test]
fn arm_refusal_must_match_both_token_and_original_precision() -> Result<(), &'static str> {
    let capacity = Capacity::new(NonZeroU32::MIN);
    let after = WakeAfter::microseconds(NonZeroU64::MIN);
    for wrong_token in [false, true] {
        let mut ledger = DelayLedger::try_new(capacity).map_err(|_| "ledger")?;
        let DelayAdmissionOutcome::Admitted { admission } =
            ledger.admit(Delayed::new(37_u8, after))
        else {
            return Err("admission");
        };
        let original = admission.timer_input();
        let token = admission.token;
        let reported_token = if wrong_token {
            TimerToken::from_nonzero(token.value().saturating_add(1))
        } else {
            token
        };
        let reported_after = if wrong_token {
            after
        } else {
            WakeAfter::nanoseconds(NonZeroU64::MIN.saturating_add(999))
        };
        let reason = TimerArmRefusal::WakeTickOutsideRange;
        let output = crate::TimerOut::ArmRefused {
            token: reported_token,
            after: reported_after,
            reason,
        }
        .into_arm_outcome()
        .map_err(|_| "arm output")?;
        let Err(mismatch) = admission.settle_timer_arm(output) else {
            return Err("partially matching refusal accepted");
        };
        let (admission, decision) = mismatch.into_parts();
        assert_eq!(admission.timer_input(), original);
        assert_eq!(
            decision,
            TimerArmDecision::Refused {
                token: reported_token,
                after: reported_after,
                reason
            }
        );
        assert_eq!(ledger.len(), 1);
        let DelayWithdrawalOutcome::Withdrawn { rejected } = ledger.withdraw(admission) else {
            return Err("mismatch lost ownership");
        };
        assert_eq!(rejected.into_parts(), (37, after));
        assert_eq!(ledger.len(), 0);
    }
    Ok(())
}