use beamr::term::Term;
use frame_core::runtime::{RuntimeError, mailbox_integer};
struct Lcg(u64);
impl Lcg {
fn next(&mut self) -> u64 {
self.0 = self
.0
.wrapping_mul(6_364_136_223_846_793_005)
.wrapping_add(1_442_695_040_888_963_407);
self.0
}
}
fn in_range(value: i64, reserved_below: i64) -> bool {
value >= reserved_below && value <= Term::SMALL_INT_MAX
}
#[test]
fn refuses_negative_reserved_bounds() {
for reserved_below in [-1_i64, -42, i64::MIN] {
let refused = mailbox_integer(b"id", reserved_below);
assert!(
matches!(
refused,
Err(RuntimeError::MailboxReservedRangeInvalid { .. })
),
"reserved_below={reserved_below} must be refused"
);
}
}
#[test]
fn refuses_bounds_beyond_the_mailbox_integer_domain() {
for reserved_below in [Term::SMALL_INT_MAX + 1, i64::MAX] {
let refused = mailbox_integer(b"id", reserved_below);
assert!(
matches!(
refused,
Err(RuntimeError::MailboxReservedRangeInvalid { .. })
),
"reserved_below={reserved_below} must be refused"
);
}
}
#[test]
fn maximal_reserved_bound_pins_the_single_remaining_value() -> Result<(), Box<dyn std::error::Error>>
{
for bytes in [b"".as_slice(), b"a", b"any identifier at all"] {
assert_eq!(
mailbox_integer(bytes, Term::SMALL_INT_MAX)?,
Term::SMALL_INT_MAX
);
}
Ok(())
}
#[test]
fn empty_and_short_identifiers_still_fold_into_range() -> Result<(), Box<dyn std::error::Error>> {
for bytes in [b"".as_slice(), b"\x00", b"\xff", b"abc"] {
for reserved_below in [0_i64, 1, 3, Term::SMALL_INT_MAX - 1] {
let value = mailbox_integer(bytes, reserved_below)?;
assert!(
in_range(value, reserved_below),
"bytes={bytes:?} reserved_below={reserved_below} produced {value}"
);
}
}
Ok(())
}
#[test]
fn fold_is_deterministic() -> Result<(), Box<dyn std::error::Error>> {
let bytes = b"0192a4c6-2b7e-4d5f-8a90-1234567890ab";
let first = mailbox_integer(bytes, 3)?;
let second = mailbox_integer(bytes, 3)?;
assert_eq!(first, second);
Ok(())
}
#[test]
fn fold_consumes_bytes_beyond_the_first_eight() -> Result<(), Box<dyn std::error::Error>> {
let base = *b"0123456789abcdef";
let mut ninth_differs = base;
ninth_differs[8] ^= 0xff;
let mut last_differs = base;
last_differs[15] ^= 0xff;
let folded_base = mailbox_integer(&base, 3)?;
assert_ne!(folded_base, mailbox_integer(&ninth_differs, 3)?);
assert_ne!(folded_base, mailbox_integer(&last_differs, 3)?);
Ok(())
}
#[test]
fn property_sweep_folds_any_bytes_above_any_valid_reserved_bound()
-> Result<(), Box<dyn std::error::Error>> {
let boundary_bounds = [
0_i64,
1,
2,
3,
Term::SMALL_INT_MAX - 2,
Term::SMALL_INT_MAX - 1,
Term::SMALL_INT_MAX,
];
let mut lcg = Lcg(0x5eed_5eed_5eed_5eed);
let mut cases = 0_u32;
for round in 0..2_000_u64 {
let reserved_below = if round % 2 == 0 {
boundary_bounds[usize::try_from(round / 2)? % boundary_bounds.len()]
} else {
i64::try_from(lcg.next() % u64::try_from(Term::SMALL_INT_MAX)?)?
};
let len = usize::try_from(lcg.next() % 65)?;
let mut bytes = Vec::with_capacity(len);
for _ in 0..len {
bytes.push(u8::try_from(lcg.next() % 256)?);
}
let value = mailbox_integer(&bytes, reserved_below)?;
assert!(
in_range(value, reserved_below),
"len={len} reserved_below={reserved_below} produced out-of-range {value}"
);
assert_eq!(
value,
mailbox_integer(&bytes, reserved_below)?,
"fold must be deterministic"
);
cases += 1;
}
assert_eq!(cases, 2_000);
Ok(())
}