use crate::{
Config, DefaultConfig, GenMap, GetDisjointMutAtError, GetDisjointMutError, InsertError,
InsertWithError, Key, KeyLayout, KeyPiece, Packed,
};
use std::vec::Vec;
struct Retiring;
impl Config for Retiring {
type Idx = u16;
type Gen = u8;
type Layout = Packed<u16, 4>;
type Storage<S> = Vec<S>;
}
struct Small;
impl Config for Small {
type Idx = u8;
type Gen = u8;
type Layout = Packed<u8, 4>;
type Storage<S> = Vec<S>;
}
struct SmallWrap;
impl Config for SmallWrap {
type Idx = u8;
type Gen = u8;
type Layout = Packed<u8, 4>;
type Storage<S> = Vec<S>;
const WRAP_ON_OVERFLOW: bool = true;
}
#[cfg(feature = "arrayvec")]
struct InlineRetiring;
#[cfg(feature = "arrayvec")]
impl Config for InlineRetiring {
type Idx = u8;
type Gen = u8;
type Layout = Packed<u8, 4>;
type Storage<S> = arrayvec::ArrayVec<S, 12>;
}
#[cfg(feature = "arrayvec")]
struct InlineWrap;
#[cfg(feature = "arrayvec")]
impl Config for InlineWrap {
type Idx = u8;
type Gen = u8;
type Layout = Packed<u8, 4>;
type Storage<S> = arrayvec::ArrayVec<S, 16>;
const WRAP_ON_OVERFLOW: bool = true;
}
#[cfg(feature = "smallvec")]
struct Spilling;
#[cfg(feature = "smallvec")]
impl Config for Spilling {
type Idx = u16;
type Gen = u8;
type Layout = Packed<u16, 4>;
type Storage<S> = smallvec::SmallVec<S, 4>;
}
#[test]
fn the_default_config_agrees_with_the_model() {
run_seeds::<DefaultConfig>();
}
#[test]
fn a_packed_config_that_retires_agrees_with_the_model() {
run_seeds::<Retiring>();
}
#[test]
fn a_small_packed_config_agrees_with_the_model() {
run_seeds::<Small>();
}
#[test]
fn a_small_packed_config_that_wraps_agrees_with_the_model() {
run_seeds::<SmallWrap>();
}
#[cfg(feature = "arrayvec")]
#[test]
fn an_array_vec_that_fills_up_agrees_with_the_model() {
run_seeds::<InlineRetiring>();
}
#[cfg(feature = "arrayvec")]
#[test]
fn an_array_vec_as_large_as_the_index_agrees_with_the_model() {
run_seeds::<InlineWrap>();
}
#[cfg(feature = "smallvec")]
#[test]
fn a_small_vec_agrees_with_the_model() {
run_seeds::<Spilling>();
}
fn run_seeds<C: Config>() {
let (seeds, steps) = if cfg!(miri) { (2, 300) } else { (48, 2000) };
for seed in 0..seeds {
run::<C>(seed, steps);
}
}
struct Rng(u64);
impl Rng {
fn next(&mut self) -> u64 {
self.0 = self.0.wrapping_add(0x9E37_79B9_7F4A_7C15);
let mut z = self.0;
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
z ^ (z >> 31)
}
fn below(&mut self, n: usize) -> usize {
(self.next() % n as u64) as usize
}
fn chance(&mut self, percent: u64) -> bool {
self.next() % 100 < percent
}
}
struct Model<C: Config> {
live: Vec<(Key<C>, u32)>,
detached: Vec<Key<C>>,
dead: Vec<Key<C>>,
next_value: u32,
retired: usize,
}
impl<C: Config> Model<C> {
fn fresh_value(&mut self) -> u32 {
self.next_value += 1;
self.next_value
}
fn kill_all(&mut self) {
self.dead.extend(self.live.drain(..).map(|(key, _)| key));
}
}
struct Context {
seed: u64,
step: usize,
}
impl Drop for Context {
fn drop(&mut self) {
if std::thread::panicking() {
std::eprintln!(
"the model check failed at seed {} step {}",
self.seed,
self.step
);
}
}
}
fn run<C: Config>(seed: u64, steps: usize) {
let mut rng = Rng(seed);
let mut map = GenMap::<u32, C>::new_with_config();
let mut model = Model::<C> {
live: Vec::new(),
detached: Vec::new(),
dead: Vec::new(),
next_value: 0,
retired: 0,
};
let mut context = Context { seed, step: 0 };
for step in 0..steps {
context.step = step;
match rng.below(1000) {
0..=299 => insert(&mut map, &mut model, &mut rng),
300..=429 => remove(&mut map, &mut model, &mut rng),
430..=449 => retire(&mut map, &mut model, &mut rng),
450..=499 => look_up_invalid(&mut map, &mut model, &mut rng),
500..=599 => overwrite(&mut map, &mut model, &mut rng),
600..=669 => detach(&mut map, &mut model, &mut rng),
670..=739 => reattach(&mut map, &mut model, &mut rng),
740..=769 => retain(&mut map, &mut model, &mut rng),
770..=819 => get_disjoint(&mut map, &mut model, &mut rng),
820..=879 => look_up_by_index(&map, &model, &mut rng),
880..=909 => compare_clones(&map),
910..=949 => drain(&mut map, &mut model, &mut rng),
950..=989 => {
map.clear();
model.kill_all();
}
_ => {
map.reset();
model.live.clear();
model.detached.clear();
model.dead.clear();
model.retired = 0;
}
}
check(&map, &model, &mut rng);
}
for key in &model.dead {
assert!(map.get(*key).is_none());
}
}
fn largest_generation<C: Config>() -> C::Gen {
<C::Layout as KeyLayout<C::Idx, C::Gen>>::max_generation()
}
fn slot_count_limit<C: Config>() -> Option<usize> {
<C::Layout as KeyLayout<C::Idx, C::Gen>>::max_idx()
.into_usize()?
.checked_add(1)
}
fn insert<C: Config>(map: &mut GenMap<u32, C>, model: &mut Model<C>, rng: &mut Rng) {
let promised = map.vacant_entry().ok().map(|entry| entry.key());
if rng.chance(10) {
match map.try_insert_with_key(Err::<u32, _>) {
Err(InsertWithError::Rejected(key)) => assert_eq!(Some(key), promised),
Err(InsertWithError::Full(_)) => assert!(promised.is_none()),
Ok(_) => unreachable!("the closure always fails"),
}
}
let value = model.fresh_value();
match map.try_insert(value) {
Ok(key) => {
assert_eq!(Some(key), promised);
assert!(model.live.iter().all(|(live, _)| *live != key));
assert!(!model.detached.contains(&key));
if C::WRAP_ON_OVERFLOW {
model.dead.retain(|dead| *dead != key);
} else {
assert!(!model.dead.contains(&key));
}
model.live.push((key, value));
}
Err(InsertError::IndexExhausted(back)) => {
assert_eq!(back, value);
assert!(promised.is_none());
assert_eq!(Some(map.slots_len()), slot_count_limit::<C>());
assert_no_slot_is_free(map, model);
}
Err(InsertError::StorageFull(back, _)) => {
assert_eq!(back, value);
assert!(promised.is_none());
assert_eq!(map.slots_len(), map.capacity());
assert!(slot_count_limit::<C>().map_or(true, |limit| map.slots_len() < limit));
assert_no_slot_is_free(map, model);
}
}
}
fn assert_no_slot_is_free<C: Config>(map: &GenMap<u32, C>, model: &Model<C>) {
let retired = if C::WRAP_ON_OVERFLOW {
model.retired
} else {
(0..map.slots_len())
.filter(|&position| {
let idx = C::Idx::from_usize(position).unwrap();
map.generation_at(idx) == Some(C::Gen::ZERO)
})
.count()
};
assert_eq!(
model.live.len() + model.detached.len() + retired,
map.slots_len()
);
}
fn retire<C: Config>(map: &mut GenMap<u32, C>, model: &mut Model<C>, rng: &mut Rng) {
if model.live.is_empty() {
return;
}
let (key, value) = model.live.swap_remove(rng.below(model.live.len()));
assert_eq!(map.retire(key), Some(value));
assert_eq!(map.generation_at(key.idx()), Some(C::Gen::ZERO));
model.dead.push(key);
model.retired += 1;
}
fn remove<C: Config>(map: &mut GenMap<u32, C>, model: &mut Model<C>, rng: &mut Rng) {
if model.live.is_empty() {
return;
}
let (key, value) = model.live.swap_remove(rng.below(model.live.len()));
assert_eq!(map.remove(key), Some(value));
model.dead.push(key);
}
fn look_up_invalid<C: Config>(map: &mut GenMap<u32, C>, model: &mut Model<C>, rng: &mut Rng) {
let count = model.dead.len() + model.detached.len();
if count == 0 {
return;
}
let i = rng.below(count);
let key = match model.dead.get(i) {
Some(key) => *key,
None => model.detached[i - model.dead.len()],
};
assert!(map.get(key).is_none());
assert!(map.get_mut(key).is_none());
assert!(!map.contains_key(key));
assert!(map.remove(key).is_none());
assert!(map.retire(key).is_none());
assert!(map.detach(key).is_none());
}
fn overwrite<C: Config>(map: &mut GenMap<u32, C>, model: &mut Model<C>, rng: &mut Rng) {
if model.live.is_empty() {
return;
}
let i = rng.below(model.live.len());
let value = model.fresh_value();
let (key, old) = model.live[i];
assert_eq!(map[key], old);
map[key] = value;
model.live[i].1 = value;
}
fn detach<C: Config>(map: &mut GenMap<u32, C>, model: &mut Model<C>, rng: &mut Rng) {
if model.live.is_empty() {
return;
}
let (key, value) = model.live.swap_remove(rng.below(model.live.len()));
match map.detach(key) {
Some(taken) => {
assert_eq!(taken, value);
model.detached.push(key);
}
None => {
assert!(key.is_max_generation());
assert_eq!(key.generation(), largest_generation::<C>());
assert_eq!(map.get(key), Some(&value));
model.live.push((key, value));
}
}
}
fn reattach<C: Config>(map: &mut GenMap<u32, C>, model: &mut Model<C>, rng: &mut Rng) {
if model.detached.is_empty() {
return;
}
let key = model.detached.swap_remove(rng.below(model.detached.len()));
let value = model.fresh_value();
map.reattach(key, value);
model.live.push((key, value));
}
fn retain<C: Config>(map: &mut GenMap<u32, C>, model: &mut Model<C>, rng: &mut Rng) {
let mut visited = Vec::new();
map.retain(|key, value| {
let keep = rng.chance(70);
visited.push((key, *value, keep));
*value += 1_000_000;
keep
});
let mut expected = model.live.clone();
expected.sort();
let seen: Vec<_> = visited
.iter()
.map(|&(key, value, _)| (key, value))
.collect();
assert_eq!(seen, expected);
model.live.clear();
for (key, value, keep) in visited {
if keep {
model.live.push((key, value + 1_000_000));
} else {
model.dead.push(key);
}
}
}
fn get_disjoint<C: Config>(map: &mut GenMap<u32, C>, model: &mut Model<C>, rng: &mut Rng) {
if let Some(key) = model.dead.first() {
let first = model.live.first().map_or(*key, |(live, _)| *live);
assert_eq!(
map.get_disjoint_mut([first, *key]),
Err(GetDisjointMutError::InvalidKey)
);
}
if model.live.len() < 2 {
return;
}
let i = rng.below(model.live.len());
let j = (i + 1 + rng.below(model.live.len() - 1)) % model.live.len();
let (a, value_a) = model.live[i];
let (b, value_b) = model.live[j];
assert_eq!(
map.get_disjoint_mut([a, a]),
Err(GetDisjointMutError::OverlappingKeys)
);
assert_eq!(
map.get_disjoint_mut_at([a.idx(), a.idx()]),
Err(GetDisjointMutAtError::OverlappingIndices)
);
let [x, y] = map.get_disjoint_mut([a, b]).unwrap();
assert_eq!((*x, *y), (value_a, value_b));
core::mem::swap(x, y);
let [(key_b, y), (key_a, x)] = map.get_disjoint_mut_at([b.idx(), a.idx()]).unwrap();
assert_eq!((key_a, key_b), (a, b));
assert_eq!((*x, *y), (value_b, value_a));
model.live[i].1 = value_b;
model.live[j].1 = value_a;
}
fn look_up_by_index<C: Config>(map: &GenMap<u32, C>, model: &Model<C>, rng: &mut Rng) {
let position = rng.below(map.slots_len() + 1);
let Some(idx) = C::Idx::from_usize(position) else {
return;
};
let expected = model.live.iter().find(|(key, _)| key.idx() == idx);
assert_eq!(map.key_at(idx), expected.map(|(key, _)| *key));
assert_eq!(map.get_at(idx), expected.map(|(key, value)| (*key, value)));
assert_eq!(map.generation_at(idx).is_some(), position < map.slots_len());
if let Some((key, _)) = expected {
assert_eq!(map.generation_at(idx), Some(key.generation()));
}
}
fn compare_clones<C: Config>(map: &GenMap<u32, C>) {
let mut copy = map.clone();
let mut target = GenMap::<u32, C>::new_with_config();
target.insert(0);
target.clone_from(map);
let next = map.clone().vacant_entry().ok().map(|entry| entry.key());
for other in [&mut copy, &mut target] {
assert!(other.iter().eq(map.iter()));
assert_eq!(other.slots_len(), map.slots_len());
assert_eq!(other.vacant_entry().ok().map(|entry| entry.key()), next);
}
}
fn drain<C: Config>(map: &mut GenMap<u32, C>, model: &mut Model<C>, rng: &mut Rng) {
let mut expected = model.live.clone();
expected.sort();
let take = rng.below(expected.len() + 1);
let mut drain = map.drain();
assert_eq!(drain.len(), expected.len());
let taken: Vec<_> = drain.by_ref().take(take).collect();
drop(drain);
assert_eq!(taken, expected[..take]);
assert!(map.is_empty());
model.kill_all();
}
fn check<C: Config>(map: &GenMap<u32, C>, model: &Model<C>, rng: &mut Rng) {
assert_eq!(map.len(), model.live.len());
assert_eq!(map.is_empty(), model.live.is_empty());
let mut expected = model.live.clone();
expected.sort();
let actual: Vec<_> = map.iter().map(|(key, value)| (key, *value)).collect();
assert_eq!(actual, expected);
assert_eq!(map.iter().len(), expected.len());
for (key, value) in &model.live {
assert_eq!(map.get(*key), Some(value));
assert_eq!(map.key_at(key.idx()), Some(*key));
}
for key in &model.detached {
assert!(map.get(*key).is_none());
assert!(map.key_at(key.idx()).is_none());
}
for _ in 0..model.dead.len().min(8) {
let key = model.dead[rng.below(model.dead.len())];
assert!(map.get(key).is_none());
}
}