use rustc_hash::FxHashMap;
use std::hash::Hash;
use crate::ds::intrusive_list::IntrusiveList;
use crate::ds::slot_arena::SlotId;
#[derive(Debug)]
pub struct GhostList<K> {
list: IntrusiveList<K>,
index: FxHashMap<K, SlotId>,
capacity: usize,
}
impl<K> Clone for GhostList<K>
where
K: Eq + Hash + Clone,
{
fn clone(&self) -> Self {
let mut new_list = IntrusiveList::with_capacity(self.capacity);
let mut new_index = FxHashMap::with_capacity_and_hasher(self.capacity, Default::default());
for (_, key) in self.list.iter_entries() {
let id = new_list.push_back(key.clone());
new_index.insert(key.clone(), id);
}
Self {
list: new_list,
index: new_index,
capacity: self.capacity,
}
}
}
pub struct Iter<'a, K> {
inner: MapToKey<'a, K>,
}
type MapToKey<'a, K> = std::iter::Map<
crate::ds::intrusive_list::IntrusiveListEntryIter<'a, K>,
fn((SlotId, &'a K)) -> &'a K,
>;
impl<'a, K: std::fmt::Debug> std::fmt::Debug for Iter<'a, K> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Iter").finish_non_exhaustive()
}
}
impl<'a, K> Iterator for Iter<'a, K> {
type Item = &'a K;
fn next(&mut self) -> Option<Self::Item> {
self.inner.next()
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.inner.size_hint()
}
}
impl<'a, K> ExactSizeIterator for Iter<'a, K> {}
impl<'a, K> std::iter::FusedIterator for Iter<'a, K> {}
#[derive(Debug)]
pub struct IntoIter<K> {
inner: std::vec::IntoIter<K>,
}
impl<K> Iterator for IntoIter<K> {
type Item = K;
fn next(&mut self) -> Option<Self::Item> {
self.inner.next()
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.inner.size_hint()
}
}
impl<K> ExactSizeIterator for IntoIter<K> {}
impl<K> std::iter::FusedIterator for IntoIter<K> {}
impl<K> IntoIterator for GhostList<K>
where
K: Eq + Hash + Clone,
{
type Item = K;
type IntoIter = IntoIter<K>;
fn into_iter(self) -> Self::IntoIter {
let keys: Vec<K> = self.list.iter_entries().map(|(_, k)| k.clone()).collect();
IntoIter {
inner: keys.into_iter(),
}
}
}
impl<'a, K> IntoIterator for &'a GhostList<K>
where
K: Eq + Hash + Clone,
{
type Item = &'a K;
type IntoIter = Iter<'a, K>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
impl<K> PartialEq for GhostList<K>
where
K: Eq + Hash + Clone,
{
fn eq(&self, other: &Self) -> bool {
self.capacity == other.capacity
&& self.len() == other.len()
&& self.iter().zip(other.iter()).all(|(a, b)| a == b)
}
}
impl<K> Eq for GhostList<K> where K: Eq + Hash + Clone {}
impl<K> Extend<K> for GhostList<K>
where
K: Eq + Hash + Clone,
{
fn extend<I: IntoIterator<Item = K>>(&mut self, iter: I) {
for key in iter {
self.record(key);
}
}
}
impl<K> FromIterator<K> for GhostList<K>
where
K: Eq + Hash + Clone,
{
fn from_iter<I: IntoIterator<Item = K>>(iter: I) -> Self {
let iter = iter.into_iter();
let (lower, _) = iter.size_hint();
let mut ghost = Self::new(lower.max(16));
for key in iter {
ghost.record(key);
}
ghost.capacity = ghost.len();
ghost
}
}
impl<K> Default for GhostList<K>
where
K: Eq + Hash + Clone,
{
fn default() -> Self {
Self::new(0)
}
}
impl<K> GhostList<K>
where
K: Eq + Hash + Clone,
{
pub fn new(capacity: usize) -> Self {
Self {
list: IntrusiveList::with_capacity(capacity),
index: FxHashMap::with_capacity_and_hasher(capacity, Default::default()),
capacity,
}
}
pub fn capacity(&self) -> usize {
self.capacity
}
pub fn len(&self) -> usize {
self.list.len()
}
pub fn is_empty(&self) -> bool {
self.list.is_empty()
}
pub fn contains(&self, key: &K) -> bool {
self.index.contains_key(key)
}
pub fn record(&mut self, key: K) -> Option<K> {
if self.capacity == 0 {
return None;
}
if let Some(&id) = self.index.get(&key) {
self.list.move_to_front(id);
return None;
}
let evicted = if self.list.len() >= self.capacity {
let old_key = self.list.pop_back()?;
self.index.remove(&old_key);
Some(old_key)
} else {
None
};
let id = self.list.push_front(key.clone());
self.index.insert(key, id);
evicted
}
pub fn record_batch<'a, I>(&mut self, keys: I) -> usize
where
I: IntoIterator<Item = &'a K>,
K: 'a,
{
let mut count = 0;
for key in keys {
self.record(key.clone());
count += 1;
}
count
}
pub fn remove(&mut self, key: &K) -> bool {
let id = match self.index.remove(key) {
Some(id) => id,
None => return false,
};
self.list.remove(id);
true
}
pub fn evict_lru(&mut self) -> Option<K> {
let key = self.list.pop_back()?;
self.index.remove(&key);
Some(key)
}
pub fn remove_batch<'a, I>(&mut self, keys: I) -> usize
where
I: IntoIterator<Item = &'a K>,
K: 'a,
{
let mut removed = 0;
for key in keys {
if self.remove(key) {
removed += 1;
}
}
removed
}
pub fn clear(&mut self) {
self.list.clear();
self.index.clear();
}
pub fn clear_shrink(&mut self) {
self.clear();
self.list.clear_shrink();
self.index.shrink_to_fit();
}
pub fn approx_bytes(&self) -> usize {
std::mem::size_of::<Self>()
+ self.list.approx_bytes()
+ self.index.capacity() * std::mem::size_of::<(K, SlotId)>()
}
pub fn iter(&self) -> Iter<'_, K> {
fn extract_key<K>((_, key): (SlotId, &K)) -> &K {
key
}
Iter {
inner: self.list.iter_entries().map(extract_key),
}
}
pub fn keys(&self) -> Iter<'_, K> {
self.iter()
}
#[cfg(any(test, debug_assertions))]
pub fn debug_snapshot_keys(&self) -> Vec<K>
where
K: Clone,
{
self.list
.iter_entries()
.map(|(_, key)| key.clone())
.collect()
}
#[cfg(any(test, debug_assertions))]
pub fn debug_peek_lru(&self) -> Option<&K> {
self.list.iter_entries().last().map(|(_, k)| k)
}
#[cfg(any(test, debug_assertions))]
pub fn debug_peek_mru(&self) -> Option<&K> {
self.list.iter_entries().next().map(|(_, k)| k)
}
#[cfg(any(test, debug_assertions))]
pub fn debug_snapshot_ids(&self) -> Vec<SlotId> {
self.list.iter_ids().collect()
}
#[cfg(any(test, debug_assertions))]
pub fn debug_snapshot_ids_sorted(&self) -> Vec<SlotId> {
let mut ids: Vec<_> = self.list.iter_ids().collect();
ids.sort_by_key(|id| id.index());
ids
}
#[cfg(any(test, debug_assertions))]
pub fn debug_snapshot_keys_sorted(&self) -> Vec<K>
where
K: Clone,
{
let mut ids: Vec<_> = self.list.iter_ids().collect();
ids.sort_by_key(|id| id.index());
ids.into_iter()
.filter_map(|id| self.list.get(id).cloned())
.collect()
}
#[cfg(any(test, debug_assertions))]
pub fn debug_validate_invariants(&self) {
assert_eq!(self.list.len(), self.index.len());
assert!(self.list.len() <= self.capacity);
if self.capacity == 0 {
assert!(self.list.is_empty());
}
for &id in self.index.values() {
assert!(self.list.contains(id));
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ghost_list_records_and_evictions() {
let mut ghost = GhostList::new(2);
ghost.record("a");
ghost.record("b");
assert!(ghost.contains(&"a"));
assert!(ghost.contains(&"b"));
ghost.record("a");
ghost.record("c");
assert!(ghost.contains(&"a"));
assert!(ghost.contains(&"c"));
assert!(!ghost.contains(&"b"));
}
#[test]
fn ghost_list_zero_capacity_is_noop() {
let mut ghost = GhostList::new(0);
ghost.record("a");
ghost.record("b");
assert!(ghost.is_empty());
assert_eq!(ghost.len(), 0);
assert!(!ghost.contains(&"a"));
assert!(!ghost.contains(&"b"));
}
#[test]
fn ghost_list_record_existing_moves_to_front() {
let mut ghost = GhostList::new(3);
ghost.record("a");
ghost.record("b");
ghost.record("c");
assert!(ghost.contains(&"a"));
assert!(ghost.contains(&"b"));
assert!(ghost.contains(&"c"));
ghost.record("a");
ghost.record("d");
assert!(ghost.contains(&"a"));
assert!(!ghost.contains(&"b"));
assert!(ghost.contains(&"c"));
assert!(ghost.contains(&"d"));
}
#[test]
fn ghost_list_remove_existing_and_missing() {
let mut ghost = GhostList::new(2);
ghost.record("a");
ghost.record("b");
assert!(ghost.remove(&"a"));
assert!(!ghost.contains(&"a"));
assert_eq!(ghost.len(), 1);
assert!(!ghost.remove(&"missing"));
assert_eq!(ghost.len(), 1);
}
#[test]
fn ghost_list_clear_resets_state() {
let mut ghost = GhostList::new(2);
ghost.record("a");
ghost.record("b");
ghost.clear();
assert!(ghost.is_empty());
assert_eq!(ghost.len(), 0);
assert!(!ghost.contains(&"a"));
assert!(!ghost.contains(&"b"));
}
#[test]
fn ghost_list_debug_invariants_hold() {
let mut ghost = GhostList::new(2);
ghost.record("a");
ghost.record("b");
ghost.record("a");
ghost.debug_validate_invariants();
}
#[test]
fn ghost_list_debug_snapshots() {
let mut ghost = GhostList::new(2);
ghost.record("a");
ghost.record("b");
let keys = ghost.debug_snapshot_keys();
let ids = ghost.debug_snapshot_ids();
assert_eq!(keys.len(), 2);
assert_eq!(ids.len(), 2);
assert_eq!(ghost.debug_snapshot_ids_sorted().len(), 2);
assert_eq!(ghost.debug_snapshot_keys_sorted().len(), 2);
}
#[test]
fn ghost_list_batch_ops() {
let mut ghost = GhostList::new(3);
assert_eq!(ghost.record_batch(&["a", "b", "c"]), 3);
assert_eq!(ghost.remove_batch(&["b", "d"]), 1);
assert!(ghost.contains(&"a"));
assert!(ghost.contains(&"c"));
assert!(!ghost.contains(&"b"));
}
#[test]
fn ghost_list_iter() {
let mut ghost = GhostList::new(5);
ghost.record("a");
ghost.record("b");
ghost.record("c");
let keys: Vec<_> = ghost.iter().cloned().collect();
assert_eq!(keys, vec!["c", "b", "a"]);
ghost.clear();
assert_eq!(ghost.iter().count(), 0);
}
#[test]
fn ghost_list_debug_peek_lru_mru() {
let mut ghost = GhostList::new(3);
assert_eq!(ghost.debug_peek_lru(), None);
assert_eq!(ghost.debug_peek_mru(), None);
ghost.record("a");
assert_eq!(ghost.debug_peek_lru(), Some(&"a"));
assert_eq!(ghost.debug_peek_mru(), Some(&"a"));
ghost.record("b");
ghost.record("c");
assert_eq!(ghost.debug_peek_lru(), Some(&"a")); assert_eq!(ghost.debug_peek_mru(), Some(&"c"));
assert_eq!(ghost.record("a"), None); assert_eq!(ghost.debug_peek_lru(), Some(&"b"));
assert_eq!(ghost.debug_peek_mru(), Some(&"a"));
}
#[test]
fn ghost_list_record_returns_evicted() {
let mut ghost = GhostList::new(2);
assert_eq!(ghost.record("a"), None);
assert_eq!(ghost.record("b"), None);
assert_eq!(ghost.len(), 2);
assert_eq!(ghost.record("c"), Some("a")); assert!(!ghost.contains(&"a"));
assert!(ghost.contains(&"b"));
assert!(ghost.contains(&"c"));
assert_eq!(ghost.record("b"), None);
assert_eq!(ghost.record("d"), Some("c"));
let mut zero_ghost = GhostList::new(0);
assert_eq!(zero_ghost.record("x"), None);
assert!(zero_ghost.is_empty());
}
#[test]
fn ghost_list_keys_alias() {
let mut ghost = GhostList::new(3);
ghost.record("a");
ghost.record("b");
ghost.record("c");
let keys_via_keys: Vec<_> = ghost.keys().cloned().collect();
let keys_via_iter: Vec<_> = ghost.iter().cloned().collect();
assert_eq!(keys_via_keys, keys_via_iter);
assert_eq!(keys_via_keys, vec!["c", "b", "a"]);
}
#[test]
fn ghost_list_default() {
let ghost: GhostList<String> = GhostList::default();
assert_eq!(ghost.capacity(), 0);
assert!(ghost.is_empty());
assert_eq!(ghost.len(), 0);
}
#[test]
fn ghost_list_clone() {
let mut ghost = GhostList::new(3);
ghost.record("a");
ghost.record("b");
ghost.record("c");
let cloned = ghost.clone();
assert_eq!(cloned.len(), ghost.len());
assert_eq!(cloned.capacity(), ghost.capacity());
assert!(cloned.contains(&"a"));
assert!(cloned.contains(&"b"));
assert!(cloned.contains(&"c"));
ghost.record("d");
assert!(ghost.contains(&"d"));
assert!(!cloned.contains(&"d"));
}
#[test]
fn ghost_list_into_iter_yields_mru_to_lru() {
let mut ghost = GhostList::new(3);
ghost.record("a");
ghost.record("b");
ghost.record("c");
let keys: Vec<_> = ghost.into_iter().collect();
assert_eq!(keys, vec!["c", "b", "a"]);
}
#[test]
fn ghost_list_into_iter_empty() {
let ghost: GhostList<&str> = GhostList::new(5);
let keys: Vec<_> = ghost.into_iter().collect();
assert!(keys.is_empty());
}
#[test]
fn ghost_list_into_iter_size_hint() {
let mut ghost = GhostList::new(4);
ghost.record("a");
ghost.record("b");
ghost.record("c");
let iter = ghost.into_iter();
assert_eq!(iter.size_hint(), (3, Some(3)));
}
#[test]
fn ghost_list_into_iter_exact_size() {
let mut ghost = GhostList::new(3);
ghost.record("x");
ghost.record("y");
let mut iter = ghost.into_iter();
assert_eq!(iter.len(), 2);
iter.next();
assert_eq!(iter.len(), 1);
iter.next();
assert_eq!(iter.len(), 0);
}
#[test]
fn ghost_list_ref_into_iter_yields_mru_to_lru() {
let mut ghost = GhostList::new(3);
ghost.record("a");
ghost.record("b");
ghost.record("c");
let keys: Vec<_> = (&ghost).into_iter().cloned().collect();
assert_eq!(keys, vec!["c", "b", "a"]);
assert_eq!(ghost.len(), 3);
}
#[test]
fn ghost_list_ref_into_iter_via_for_loop() {
let mut ghost = GhostList::new(3);
ghost.record(1u32);
ghost.record(2u32);
ghost.record(3u32);
let mut seen = Vec::new();
for key in &ghost {
seen.push(*key);
}
assert_eq!(seen, vec![3u32, 2, 1]); }
#[test]
fn ghost_list_equal_same_content_and_capacity() {
let mut a = GhostList::new(3);
a.record("x");
a.record("y");
let mut b = GhostList::new(3);
b.record("x");
b.record("y");
assert_eq!(a, b);
}
#[test]
fn ghost_list_not_equal_different_order() {
let mut a = GhostList::new(3);
a.record("x");
a.record("y");
let mut b = GhostList::new(3);
b.record("y");
b.record("x");
assert_ne!(a, b);
}
#[test]
fn ghost_list_not_equal_different_capacity() {
let mut a = GhostList::new(3);
a.record("x");
let mut b = GhostList::new(5);
b.record("x");
assert_ne!(a, b);
}
#[test]
fn ghost_list_not_equal_different_content() {
let mut a = GhostList::new(3);
a.record("x");
a.record("y");
let mut b = GhostList::new(3);
b.record("x");
b.record("z");
assert_ne!(a, b);
}
#[test]
fn ghost_list_equal_empty_lists_same_capacity() {
let a: GhostList<&str> = GhostList::new(10);
let b: GhostList<&str> = GhostList::new(10);
assert_eq!(a, b);
}
#[test]
fn ghost_list_partial_eq_is_reflexive() {
let mut ghost = GhostList::new(3);
ghost.record("a");
ghost.record("b");
assert_eq!(ghost, ghost.clone());
}
#[test]
fn ghost_list_extend_records_all_keys() {
let mut ghost = GhostList::new(5);
ghost.extend(["a", "b", "c"]);
assert_eq!(ghost.len(), 3);
assert!(ghost.contains(&"a"));
assert!(ghost.contains(&"b"));
assert!(ghost.contains(&"c"));
}
#[test]
fn ghost_list_extend_respects_capacity() {
let mut ghost = GhostList::new(2);
ghost.extend(["a", "b", "c", "d"]);
assert_eq!(ghost.len(), 2);
assert!(ghost.contains(&"c"));
assert!(ghost.contains(&"d"));
assert!(!ghost.contains(&"a"));
assert!(!ghost.contains(&"b"));
}
#[test]
fn ghost_list_extend_promotes_existing_keys() {
let mut ghost = GhostList::new(3);
ghost.record("a");
ghost.record("b");
ghost.record("c");
ghost.extend(["a"]);
let keys: Vec<_> = ghost.iter().cloned().collect();
assert_eq!(keys[0], "a"); assert_eq!(ghost.len(), 3);
}
#[test]
fn ghost_list_extend_from_iterator() {
let mut ghost = GhostList::new(10);
let keys = vec!["p", "q", "r", "s"];
ghost.extend(keys);
assert_eq!(ghost.len(), 4);
assert!(ghost.contains(&"p"));
assert!(ghost.contains(&"s"));
}
#[test]
fn ghost_list_evict_lru_removes_oldest() {
let mut ghost = GhostList::new(3);
ghost.record("a");
ghost.record("b");
ghost.record("c");
assert_eq!(ghost.evict_lru(), Some("a"));
assert!(!ghost.contains(&"a"));
assert_eq!(ghost.len(), 2);
assert_eq!(ghost.evict_lru(), Some("b"));
assert_eq!(ghost.evict_lru(), Some("c"));
assert_eq!(ghost.evict_lru(), None); assert!(ghost.is_empty());
}
#[test]
fn ghost_list_evict_lru_empty_returns_none() {
let mut ghost: GhostList<&str> = GhostList::new(5);
assert_eq!(ghost.evict_lru(), None);
}
#[test]
fn ghost_list_evict_lru_after_promotion() {
let mut ghost = GhostList::new(3);
ghost.record("a");
ghost.record("b");
ghost.record("c");
ghost.record("a");
assert_eq!(ghost.evict_lru(), Some("b"));
assert!(ghost.contains(&"a"));
assert!(ghost.contains(&"c"));
}
#[test]
fn ghost_list_iter_debug() {
let mut ghost = GhostList::new(3);
ghost.record("a");
ghost.record("b");
let iter = ghost.iter();
let debug_str = format!("{:?}", iter);
assert!(debug_str.contains("Iter"));
}
#[test]
fn ghost_list_into_iter_debug() {
let mut ghost = GhostList::new(3);
ghost.record("a");
let iter = ghost.into_iter();
let debug_str = format!("{:?}", iter);
assert!(debug_str.contains("IntoIter"));
}
}
#[cfg(test)]
mod property_tests {
use super::*;
use proptest::prelude::*;
proptest! {
#[cfg_attr(miri, ignore)]
#[test]
fn prop_invariants_always_hold(
capacity in 1usize..20,
ops in prop::collection::vec((0u8..3, any::<u32>()), 0..50)
) {
let mut ghost: GhostList<u32> = GhostList::new(capacity);
for (op, key) in ops {
match op % 3 {
0 => { ghost.record(key); }
1 => { ghost.remove(&key); }
2 => { let _ = ghost.contains(&key); }
_ => unreachable!(),
}
ghost.debug_validate_invariants();
}
}
#[cfg_attr(miri, ignore)]
#[test]
fn prop_len_never_exceeds_capacity(
capacity in 1usize..30,
keys in prop::collection::vec(any::<u32>(), 0..100)
) {
let mut ghost: GhostList<u32> = GhostList::new(capacity);
for key in keys {
ghost.record(key);
prop_assert!(ghost.len() <= capacity);
}
}
#[cfg_attr(miri, ignore)]
#[test]
fn prop_empty_state_consistent(
capacity in 1usize..20,
keys in prop::collection::vec(any::<u32>(), 1..20)
) {
let mut ghost: GhostList<u32> = GhostList::new(capacity);
for key in keys {
ghost.record(key);
}
ghost.clear();
prop_assert!(ghost.is_empty());
prop_assert_eq!(ghost.len(), 0);
prop_assert_eq!(ghost.capacity(), capacity);
}
}
proptest! {
#[cfg_attr(miri, ignore)]
#[test]
fn prop_lru_eviction_order(
capacity in 2usize..10,
keys in prop::collection::vec(0u32..50, 1..30)
) {
let mut ghost: GhostList<u32> = GhostList::new(capacity);
let mut reference: std::collections::VecDeque<u32> = std::collections::VecDeque::new();
for key in keys {
ghost.record(key);
if let Some(pos) = reference.iter().position(|&k| k == key) {
reference.remove(pos);
} else if reference.len() >= capacity {
reference.pop_back();
}
reference.push_front(key);
}
prop_assert_eq!(ghost.len(), reference.len());
for &ref_key in &reference {
prop_assert!(ghost.contains(&ref_key));
}
let snapshot = ghost.debug_snapshot_keys();
prop_assert_eq!(snapshot, reference.iter().copied().collect::<Vec<_>>());
ghost.debug_validate_invariants();
}
#[cfg_attr(miri, ignore)]
#[test]
fn prop_rerecord_no_length_increase(
capacity in 2usize..10,
key in any::<u32>()
) {
let mut ghost: GhostList<u32> = GhostList::new(capacity);
ghost.record(key);
let len_after_first = ghost.len();
ghost.record(key);
let len_after_second = ghost.len();
prop_assert_eq!(len_after_first, len_after_second);
prop_assert_eq!(len_after_first, 1);
}
}
proptest! {
#[cfg_attr(miri, ignore)]
#[test]
fn prop_rerecord_promotes_to_mru(
capacity in 2usize..10,
keys in prop::collection::vec(0u32..20, 2..10)
) {
prop_assume!(keys.len() >= 2);
let mut ghost: GhostList<u32> = GhostList::new(capacity);
for &key in &keys {
ghost.record(key);
}
if ghost.is_empty() {
return Ok(());
}
let snapshot = ghost.debug_snapshot_keys();
if snapshot.is_empty() {
return Ok(());
}
let promoted_key = snapshot[snapshot.len() - 1];
ghost.record(promoted_key);
let new_snapshot = ghost.debug_snapshot_keys();
if !new_snapshot.is_empty() {
prop_assert_eq!(new_snapshot[0], promoted_key);
}
}
#[cfg_attr(miri, ignore)]
#[test]
fn prop_promoted_keys_survive_longer(
capacity in 3usize..8,
fill_keys in prop::collection::vec(0u32..10, 3..8)
) {
prop_assume!(fill_keys.len() >= capacity);
let mut ghost: GhostList<u32> = GhostList::new(capacity);
let mut unique = Vec::new();
for &key in &fill_keys {
if !unique.contains(&key) && unique.len() < capacity {
ghost.record(key);
unique.push(key);
}
}
if unique.len() < capacity {
return Ok(());
}
let snapshot = ghost.debug_snapshot_keys();
let promoted_key = snapshot[snapshot.len() - 1];
ghost.record(promoted_key);
for i in 100..(100 + capacity - 1) {
ghost.record(i as u32);
}
prop_assert!(ghost.contains(&promoted_key));
}
}
proptest! {
#[cfg_attr(miri, ignore)]
#[test]
fn prop_contains_after_record(
capacity in 1usize..20,
key in any::<u32>()
) {
let mut ghost: GhostList<u32> = GhostList::new(capacity);
ghost.record(key);
prop_assert!(ghost.contains(&key));
ghost.remove(&key);
prop_assert!(!ghost.contains(&key));
}
#[cfg_attr(miri, ignore)]
#[test]
fn prop_remove_returns_correct_bool(
capacity in 1usize..10,
keys in prop::collection::vec(any::<u32>(), 1..20)
) {
let mut ghost: GhostList<u32> = GhostList::new(capacity);
for &key in &keys {
ghost.record(key);
}
for &key in &keys {
let was_present = ghost.contains(&key);
let remove_result = ghost.remove(&key);
if was_present {
prop_assert!(remove_result);
prop_assert!(!ghost.contains(&key));
}
prop_assert!(!ghost.remove(&key));
}
}
}
proptest! {
#[cfg_attr(miri, ignore)]
#[test]
fn prop_clear_resets_state(
capacity in 1usize..20,
keys in prop::collection::vec(any::<u32>(), 1..30)
) {
let mut ghost: GhostList<u32> = GhostList::new(capacity);
for key in keys {
ghost.record(key);
}
ghost.clear();
prop_assert!(ghost.is_empty());
prop_assert_eq!(ghost.len(), 0);
prop_assert_eq!(ghost.capacity(), capacity);
ghost.debug_validate_invariants();
}
#[cfg_attr(miri, ignore)]
#[test]
fn prop_clear_shrink_same_as_clear(
capacity in 1usize..20,
keys in prop::collection::vec(any::<u32>(), 1..30)
) {
let mut ghost1: GhostList<u32> = GhostList::new(capacity);
let mut ghost2: GhostList<u32> = GhostList::new(capacity);
for &key in &keys {
ghost1.record(key);
ghost2.record(key);
}
ghost1.clear();
ghost2.clear_shrink();
prop_assert_eq!(ghost1.len(), ghost2.len());
prop_assert_eq!(ghost1.is_empty(), ghost2.is_empty());
prop_assert_eq!(ghost1.capacity(), ghost2.capacity());
}
#[cfg_attr(miri, ignore)]
#[test]
fn prop_usable_after_clear(
capacity in 1usize..10,
keys1 in prop::collection::vec(any::<u32>(), 1..20),
keys2 in prop::collection::vec(any::<u32>(), 1..20)
) {
let mut ghost: GhostList<u32> = GhostList::new(capacity);
for key in keys1 {
ghost.record(key);
}
ghost.clear();
for &key in &keys2 {
ghost.record(key);
}
let unique_keys2: std::collections::HashSet<_> = keys2.into_iter().collect();
let expected_len = unique_keys2.len().min(capacity);
prop_assert_eq!(ghost.len(), expected_len);
}
}
proptest! {
#[cfg_attr(miri, ignore)]
#[test]
fn prop_zero_capacity_always_empty(
keys in prop::collection::vec(any::<u32>(), 0..30)
) {
let mut ghost: GhostList<u32> = GhostList::new(0);
for key in keys {
ghost.record(key);
prop_assert!(ghost.is_empty());
prop_assert_eq!(ghost.len(), 0);
prop_assert_eq!(ghost.capacity(), 0);
prop_assert!(!ghost.contains(&key));
}
}
}
proptest! {
#[cfg_attr(miri, ignore)]
#[test]
fn prop_record_batch_records_all(
capacity in 5usize..20,
keys in prop::collection::vec(0u32..20, 1..10)
) {
let mut ghost: GhostList<u32> = GhostList::new(capacity);
let count = ghost.record_batch(&keys);
prop_assert_eq!(count, keys.len());
let unique_keys: std::collections::HashSet<_> = keys.into_iter().collect();
let expected_len = unique_keys.len().min(capacity);
prop_assert_eq!(ghost.len(), expected_len);
}
#[cfg_attr(miri, ignore)]
#[test]
fn prop_remove_batch_only_present(
capacity in 5usize..20,
record_keys in prop::collection::vec(0u32..10, 1..10),
remove_keys in prop::collection::vec(0u32..20, 1..10)
) {
let mut ghost: GhostList<u32> = GhostList::new(capacity);
ghost.record_batch(&record_keys);
let removed = ghost.remove_batch(&remove_keys);
let record_set: std::collections::HashSet<_> = record_keys.into_iter().collect();
let remove_set: std::collections::HashSet<_> = remove_keys.into_iter().collect();
let mut expected_removed = 0;
for key in remove_set {
if record_set.contains(&key) && !ghost.is_empty() {
expected_removed += 1;
}
}
prop_assert!(removed <= expected_removed || removed <= record_set.len());
}
}
proptest! {
#[cfg_attr(miri, ignore)]
#[test]
fn prop_matches_reference_implementation(
capacity in 2usize..10,
keys in prop::collection::vec(0u32..20, 0..30)
) {
let mut ghost: GhostList<u32> = GhostList::new(capacity);
let mut reference: std::collections::VecDeque<u32> = std::collections::VecDeque::new();
for key in keys {
ghost.record(key);
if let Some(pos) = reference.iter().position(|&k| k == key) {
reference.remove(pos);
} else if reference.len() >= capacity {
reference.pop_back(); }
reference.push_front(key);
prop_assert_eq!(ghost.len(), reference.len());
for &ref_key in &reference {
prop_assert!(ghost.contains(&ref_key));
}
let snapshot = ghost.debug_snapshot_keys();
prop_assert_eq!(snapshot.len(), reference.len());
for (snap_key, ref_key) in snapshot.iter().zip(reference.iter()) {
prop_assert_eq!(snap_key, ref_key);
}
}
}
}
}