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(())
}