use super::*;
#[test]
fn a_lookup_that_read_the_hop_bits_before_a_move_finds_the_moved_key() {
let map = displacing_layout();
let (reader, arrival, release) = {
let map = Arc::clone(&map);
stopped_at(Point::LookupReadHops, move || map.get(&2))
};
arrival
.recv_timeout(MEET)
.expect("the lookup read key 2's hop bits");
assert_eq!(map.insert(64, 64), None);
assert_displaced(&map);
release.send(()).expect("let the lookup scan");
assert_eq!(reader.join().expect("the lookup"), Some(2));
}
#[test]
fn a_move_keeps_its_entry_visible_to_lookups_and_walks_at_every_step() {
for point in [Point::MoveLinkedTwice, Point::MoveUnlinked] {
let map = displacing_layout();
let (mover, arrival, release) = {
let map = Arc::clone(&map);
stopped_at(point, move || map.insert(64, 64))
};
arrival.recv_timeout(MEET).expect("the insert moves key 2");
let in_old_slot = (point == Point::MoveLinkedTwice).then_some(2);
assert_eq!(key_at(&map, 2), in_old_slot, "{point:?}");
assert_eq!(key_at(&map, 33), Some(2), "{point:?}");
assert_eq!(map.get(&2), Some(2), "{point:?}: a lookup missed the key");
assert_eq!(walked_keys(&map), keys_and(&[]), "{point:?}: a walk");
release.send(()).expect("let the move finish");
assert_eq!(mover.join().expect("the insert"), None);
assert_displaced(&map);
assert_eq!(map.get(&2), Some(2));
assert_eq!(walked_keys(&map), keys_and(&[64]));
}
}
#[test]
fn an_abandoned_pause_inside_a_move_leaves_the_map_whole() {
for point in [Point::MoveLinkedTwice, Point::MoveUnlinked] {
let map = displacing_layout();
let (mover, mover_arrival, mover_release) = {
let map = Arc::clone(&map);
stopped_at(point, move || map.insert(64, 64))
};
mover_arrival
.recv_timeout(MEET)
.expect("the insert moves key 2");
drop(mover_release);
assert_eq!(
mover.join().expect("the insert finishes"),
None,
"{point:?}"
);
assert_eq!(map.get(&2), Some(2), "{point:?}");
assert_eq!(map.get(&64), Some(64), "{point:?}");
assert_eq!(map.len(), 34, "{point:?}");
assert_eq!(walked_keys(&map), keys_and(&[64]), "{point:?}");
drop(map);
}
}
#[test]
fn a_claim_of_a_key_being_moved_finds_it() {
for point in [Point::MoveLinkedTwice, Point::MoveUnlinked] {
let map = displacing_layout();
let (mover, mover_arrival, mover_release) = {
let map = Arc::clone(&map);
stopped_at(point, move || map.insert(64, 64))
};
mover_arrival
.recv_timeout(MEET)
.expect("the insert moves key 2");
assert_eq!(map.insert_if_absent(2, 99), Some(2), "{point:?}");
assert_eq!(map.get_or_insert(2, 98), 2, "{point:?}");
mover_release.send(()).expect("let the move finish");
assert_eq!(mover.join().expect("the insert"), None);
assert_eq!(map.get(&2), Some(2));
assert_eq!(map.len(), 34);
assert_eq!(walked_keys(&map), keys_and(&[64]));
}
}
#[test]
fn two_claims_of_a_key_that_needs_a_displacement_have_one_winner() {
let map = displacing_layout();
let (first, first_arrival, first_release) = {
let map = Arc::clone(&map);
stopped_at(Point::MoveLinkedTwice, move || map.insert_if_absent(64, 1))
};
first_arrival
.recv_timeout(MEET)
.expect("the first claim moves key 2");
let (second, second_arrival, second_release) = {
let map = Arc::clone(&map);
stopped_at(Point::WriterMetHeldGuard, move || {
map.insert_if_absent(64, 2)
})
};
second_arrival
.recv_timeout(MEET)
.expect("the second claim waits for bucket 0's guard");
first_release.send(()).expect("let the first claim finish");
assert_eq!(first.join().expect("the first claim"), None);
second_release.send(()).expect("let the second claim run");
assert_eq!(second.join().expect("the second claim"), Some(1));
assert_displaced(&map);
assert_eq!(map.get(&64), Some(1));
assert_eq!(map.len(), 34);
assert_eq!(walked_keys(&map), keys_and(&[64]));
}
#[test]
fn a_remove_racing_the_move_of_its_key_removes_it() {
for point in [Point::MoveLinkedTwice, Point::MoveUnlinked] {
let map = displacing_layout();
let (mover, mover_arrival, mover_release) = {
let map = Arc::clone(&map);
stopped_at(point, move || map.insert(64, 64))
};
mover_arrival
.recv_timeout(MEET)
.expect("the insert moves key 2");
let (remover, remover_arrival, remover_release) = {
let map = Arc::clone(&map);
stopped_at(Point::WriterMetHeldGuard, move || map.remove(&2))
};
remover_arrival
.recv_timeout(MEET)
.expect("the remove waits for the move");
mover_release.send(()).expect("let the move finish");
assert_eq!(mover.join().expect("the insert"), None);
remover_release.send(()).expect("let the remove go on");
assert_eq!(remover.join().expect("the remove"), Some(2), "{point:?}");
assert_eq!(map.get(&2), None);
assert_eq!(map.len(), 33);
let want: Vec<u64> = (0..=32).filter(|k| *k != 2).chain([64]).collect();
assert_eq!(walked_keys(&map), want);
}
}
#[test]
fn a_walk_meets_a_key_a_displacement_moved_ahead_of_it_once() {
let map = displacing_layout();
let mut walk = map.iter();
let mut seen: Vec<u64> = walk.by_ref().take(3).map(|(k, _)| k).collect();
assert_eq!(seen, [0, 1, 2]);
assert_eq!(map.insert(64, 64), None);
assert_displaced(&map);
seen.extend(walk.map(|(k, _)| k));
seen.sort_unstable();
assert_eq!(seen, keys_and(&[]), "key 2 once: not met again in slot 33");
}
#[test]
fn a_walk_meets_a_key_once_when_it_is_moved_ahead_and_updated() {
let map = displacing_layout();
let mut walk = map.iter();
let mut seen: Vec<(u64, u64)> = walk.by_ref().take(3).collect();
assert_eq!(seen, [(0, 0), (1, 1), (2, 2)]);
assert_eq!(map.insert(64, 64), None);
assert_displaced(&map);
assert_eq!(map.insert(2, 200), Some(2), "key 2 is replaced in slot 33");
seen.extend(walk);
seen.sort_unstable();
let want: Vec<(u64, u64)> = (0..=32).map(|k| (k, k)).collect();
assert_eq!(seen, want, "key 2 once, as the walk first met it");
}
#[test]
fn a_fold_after_next_meets_a_key_a_displacement_moved_ahead_of_it_once() {
let map = displacing_layout();
let mut walk = map.iter();
let seen: Vec<u64> = walk.by_ref().take(3).map(|(k, _)| k).collect();
assert_eq!(seen, [0, 1, 2]);
assert_eq!(map.insert(64, 64), None);
assert_displaced(&map);
assert_eq!(walk.count(), 30, "key 2 once: not met again in slot 33");
}
fn upper_half_layout<S: BuildHasher + Default>() -> HopscotchMap<u64, u64, S> {
let map = HopscotchMap::with_capacity_and_hasher(64, S::default());
for k in 32..64 {
map.insert(k, k);
}
map
}
fn fold_counts<S: BuildHasher>(
map: &HopscotchMap<u64, u64, S>,
write: impl FnOnce(),
) -> std::collections::BTreeMap<u64, usize> {
let mut write = Some(write);
map.iter()
.fold(std::collections::BTreeMap::new(), |mut counts, (k, _)| {
if let Some(write) = write.take() {
write();
}
*counts.entry(k).or_insert(0) += 1;
counts
})
}
fn a_fold_meets_a_key_a_displacement_moved_ahead_of_it_once<S: BuildHasher + Default>() {
let map = upper_half_layout::<S>();
let counts = fold_counts(&map, || {
assert_eq!(map.insert(96, 96), None);
assert_eq!(
key_at(&map, 33),
Some(96),
"key 96 took the slot key 33 left"
);
assert_eq!(key_at(&map, 64), Some(33), "key 33 moved to slot 64");
});
for k in 32..64 {
assert_eq!(
counts.get(&k),
Some(&1),
"key {k}, present throughout, once"
);
}
assert!(counts.values().all(|&n| n == 1), "no key twice: {counts:?}");
}
#[test]
fn a_fold_meets_a_key_a_displacement_moved_ahead_of_it_once_identity() {
a_fold_meets_a_key_a_displacement_moved_ahead_of_it_once::<Identity>();
}
#[test]
fn a_fold_meets_a_key_a_displacement_moved_ahead_of_it_once_tagged() {
a_fold_meets_a_key_a_displacement_moved_ahead_of_it_once::<Tagged>();
}
fn a_fold_meets_a_key_reinserted_ahead_of_it_once<S: BuildHasher + Default>() {
let map = upper_half_layout::<S>();
let counts = fold_counts(&map, || {
assert_eq!(map.remove(&63), Some(63));
assert_eq!(map.insert(127, 127), None);
assert_eq!(map.insert(63, 630), None);
assert_eq!(key_at(&map, 63), Some(127));
assert_eq!(key_at(&map, 64), Some(63), "key 63 again, in slot 64");
});
for k in 32..64 {
assert_eq!(counts.get(&k), Some(&1), "key {k} once");
}
assert!(counts.values().all(|&n| n == 1), "no key twice: {counts:?}");
}
#[test]
fn a_fold_meets_a_key_reinserted_ahead_of_it_once_identity() {
a_fold_meets_a_key_reinserted_ahead_of_it_once::<Identity>();
}
#[test]
fn a_fold_meets_a_key_reinserted_ahead_of_it_once_tagged() {
a_fold_meets_a_key_reinserted_ahead_of_it_once::<Tagged>();
}
std::thread_local! {
static CLONED: core::cell::Cell<usize> = const { core::cell::Cell::new(0) };
}
#[derive(PartialEq, Eq, Hash)]
struct Clones(u64);
impl Clone for Clones {
fn clone(&self) -> Self {
CLONED.with(|cloned| cloned.set(cloned.get() + 1));
Self(self.0)
}
}
fn clones_in(walk: impl FnOnce()) -> usize {
let before = CLONED.with(core::cell::Cell::get);
walk();
CLONED.with(core::cell::Cell::get) - before
}
#[test]
fn keys_and_values_clone_only_what_they_yield() {
let by_key = HopscotchMap::<Clones, u64>::new();
let by_value = HopscotchMap::<u64, Clones>::new();
for k in 0..100 {
by_key.insert(Clones(k), k);
by_value.insert(k, Clones(k));
}
assert_eq!(clones_in(|| assert_eq!(by_value.keys().count(), 100)), 0);
assert_eq!(
clones_in(|| assert_eq!(by_value.keys().collect::<Vec<_>>().len(), 100)),
0
);
assert_eq!(clones_in(|| assert_eq!(by_key.values().count(), 100)), 0);
assert_eq!(
clones_in(|| assert_eq!(by_key.values().collect::<Vec<_>>().len(), 100)),
0
);
assert_eq!(clones_in(|| assert_eq!(by_key.keys().count(), 100)), 100);
assert_eq!(clones_in(|| assert_eq!(by_value.iter().count(), 100)), 100);
}
#[test]
fn a_displacement_moves_another_entry_when_one_home_is_held() {
let map = displacing_layout();
let (holder, holder_arrival, holder_release) = {
let map = Arc::clone(&map);
stopped_at(Point::InGuardBeforeClaim, move || {
map.insert_if_absent(66, 66)
})
};
holder_arrival
.recv_timeout(MEET)
.expect("the insert of key 66 holds bucket 2's guard");
assert_eq!(map.insert(64, 64), None);
assert_eq!(key_at(&map, 3), Some(64));
assert_eq!(key_at(&map, 33), Some(3));
assert_eq!(map.capacity(), 64, "a held home is not a full table");
holder_release.send(()).expect("let key 66 claim a slot");
assert_eq!(holder.join().expect("the insert of key 66"), None);
assert_eq!(key_at(&map, 4), Some(66));
assert_eq!(key_at(&map, 34), Some(4));
assert_eq!(map.capacity(), 64);
assert_eq!(map.len(), 35);
for k in keys_and(&[64, 66]) {
assert_eq!(map.get(&k), Some(k), "key {k}");
}
assert_eq!(walked_keys(&map), keys_and(&[64, 66]));
}