use super::*;
use bolero::check;
use rand::{seq::SliceRandom, Rng, SeedableRng};
use std::collections::{binary_heap::PeekMut, BinaryHeap, HashSet};
#[test]
fn check() {
check!().with_type::<Vec<KeyId>>().for_each(|ops| {
let mut oracle = std::collections::HashSet::new();
let subject = State::with_shared(Shared::new());
let id = Id::from([0; 16]);
for op in ops {
let expected = oracle.insert(*op);
let actual = subject
.post_authentication(&Credentials { id, key_id: *op })
.is_ok();
if !expected {
assert!(!actual);
}
}
});
}
#[test]
fn check_ordered() {
check!().with_type::<Vec<KeyId>>().for_each(|ops| {
let mut ops = ops.clone();
ops.sort();
let mut oracle = std::collections::HashSet::new();
let subject = State::with_shared(Shared::new());
let id = Id::from([0; 16]);
for op in ops {
let expected = oracle.insert(op);
let actual = subject
.post_authentication(&Credentials { id, key_id: op })
.is_ok();
assert_eq!(actual, expected);
}
});
}
#[test]
fn check_u16() {
check!().with_type::<Vec<u16>>().for_each(|ops| {
let mut oracle = std::collections::HashSet::new();
let subject = State::with_shared(Shared::new());
for op in ops {
let op = KeyId::new(*op as u64).unwrap();
let expected = oracle.insert(op);
let id = Id::from([0; 16]);
let actual = subject
.post_authentication(&Credentials { id, key_id: op })
.is_ok();
if !expected {
assert!(!actual);
}
}
});
}
#[test]
fn check_ordered_u16() {
check!().with_type::<Vec<u16>>().for_each(|ops| {
let mut ops = ops.clone();
ops.sort();
let mut oracle = std::collections::HashSet::new();
let subject = State::with_shared(Shared::new());
let id = Id::from([0; 16]);
for op in ops {
let op = KeyId::new(op as u64).unwrap();
let expected = oracle.insert(op);
let actual = subject
.post_authentication(&Credentials { id, key_id: op })
.is_ok();
assert_eq!(actual, expected);
}
});
}
#[test]
fn shared() {
let subject = Shared::new();
let id1 = Id::from([0; 16]);
let mut id2 = Id::from([0; 16]);
id2[10] = 1;
let key1 = KeyId::new(0).unwrap();
let key2 = KeyId::new(1).unwrap();
subject.insert(&Credentials {
id: id1,
key_id: key1,
});
assert_eq!(
subject.remove(&Credentials {
id: id1,
key_id: key1,
}),
Ok(())
);
assert_eq!(
subject.remove(&Credentials {
id: id1,
key_id: key1,
}),
Err(Error::AlreadyExists)
);
subject.insert(&Credentials {
id: id2,
key_id: key1,
});
assert_eq!(
subject.remove(&Credentials {
id: id1,
key_id: key1,
}),
Err(Error::Unknown)
);
assert_eq!(
subject.remove(&Credentials {
id: id1,
key_id: key2,
}),
Err(Error::Unknown)
);
assert_eq!(
subject.remove(&Credentials {
id: id2,
key_id: key1,
}),
Ok(())
);
}
#[test]
fn check_shuffled_chunks() {
check!()
.with_type::<(u64, u8)>()
.for_each(|&(seed, chunk_size)| {
check_shuffled_chunks_inner(seed, chunk_size);
});
}
#[test]
fn check_shuffled_chunks_specific() {
check_shuffled_chunks_inner(0xf323243, 10);
check_shuffled_chunks_inner(0xf323243, 63);
check_shuffled_chunks_inner(0xf323243, 129);
}
fn check_shuffled_chunks_inner(seed: u64, chunk_size: u8) {
eprintln!("======== starting test run ({seed} {chunk_size}) ==========");
if chunk_size == 0 || chunk_size >= 129 {
return;
}
let mut model = Model::default();
let mut rng = rand::rngs::SmallRng::seed_from_u64(seed);
let mut deltas = (-(chunk_size as i32 / 2)..(chunk_size as i32 / 2)).collect::<Vec<_>>();
for initial in (128u32..100_000u32).step_by(chunk_size as usize) {
deltas.shuffle(&mut rng);
for delta in deltas.iter() {
model.insert(initial.checked_add_signed(*delta).unwrap() as u64);
}
}
}
#[test]
fn check_delayed() {
check!()
.with_type::<(u64, u16)>()
.for_each(|&(seed, delay)| {
check_delayed_inner(seed, delay);
});
}
#[test]
fn check_delayed_specific() {
check_delayed_inner(0xf323243, 10);
check_delayed_inner(0xf323243, 63);
check_delayed_inner(0xf323243, 129);
}
fn check_delayed_inner(seed: u64, delay: u16) {
assert!((delay as usize) < SHARED_ENTRIES);
let delay = delay as u64;
eprintln!("======== starting test run ({seed} {delay}) ==========");
let mut model = Model::default();
let mut rng = rand::rngs::SmallRng::seed_from_u64(seed);
let mut buffered: BinaryHeap<(std::cmp::Reverse<u64>, u64)> = BinaryHeap::new();
for id in 0..(SHARED_ENTRIES as u64 * 3) {
while let Some(peeked) = buffered.peek_mut() {
if (peeked.0).0 == id {
model.insert(peeked.1);
PeekMut::pop(peeked);
} else {
break;
}
}
if id % 128 != 0 {
let insert_before = rng.gen_range(id + 1 + delay..id + SHARED_ENTRIES as u64);
buffered.push((std::cmp::Reverse(insert_before), id));
} else {
model.insert(id);
}
}
}
struct Model {
insert_order: Vec<u64>,
oracle: HashSet<u64>,
subject: State,
}
impl Default for Model {
fn default() -> Self {
Self {
oracle: Default::default(),
insert_order: Vec::new(),
subject: State::with_shared(Shared::new()),
}
}
}
impl Model {
fn insert(&mut self, op: u64) {
let pid = Id::from([0; 16]);
let id = KeyId::new(op).unwrap();
let expected = self.oracle.insert(op);
if expected {
self.insert_order.push(op);
}
let actual = self.subject.post_authentication(&Credentials {
id: pid,
key_id: id,
});
if actual.is_ok() != expected {
let mut oracle = self.oracle.iter().collect::<Vec<_>>();
oracle.sort_unstable();
panic!(
"Inserting {:?} failed, in oracle: {}, in subject: {:?}, inserted: {:?}",
op, expected, actual, self.insert_order
);
}
}
}
#[test]
fn shared_no_collisions() {
let mut seen = HashSet::new();
let shared = Shared::new();
for key_id in 0..SHARED_ENTRIES as u64 {
let index = shared.index(&Credentials {
id: Id::from([0; 16]),
key_id: KeyId::new(key_id).unwrap(),
});
assert!(seen.insert(index));
}
let index = shared.index(&Credentials {
id: Id::from([0; 16]),
key_id: KeyId::new(SHARED_ENTRIES as u64 + 1).unwrap(),
});
assert!(!seen.insert(index));
}
#[test]
fn shared_id_pair_no_collisions() {
let shared = Shared::new();
let id1 = Id::from(u128::to_ne_bytes(0x25add729cce683cd0cda41d35436bdc6));
let id2 = Id::from(u128::to_ne_bytes(0x2862115d0691fe180f2aeb26af3c2e5e));
for key_id in 0..SHARED_ENTRIES as u64 {
let index1 = shared.index(&Credentials {
id: id1,
key_id: KeyId::new(key_id).unwrap(),
});
let index2 = shared.index(&Credentials {
id: id2,
key_id: KeyId::new(key_id).unwrap(),
});
assert_ne!(index1, index2);
}
}
#[test]
fn shared_no_entries() {
let shared = Shared::new();
for key_id in 0..SHARED_ENTRIES as u64 {
assert_eq!(
shared.remove(&Credentials {
id: Id::from([0; 16]),
key_id: KeyId::new(key_id).unwrap(),
}),
Err(Error::Unknown)
);
}
}