cranpose-render-common 0.1.164

Common rendering contracts for Cranpose
Documentation
use super::BoundedLruCache;

fn cache<K, V>(cap: usize) -> BoundedLruCache<K, V>
where
    K: Clone + Eq + std::hash::Hash,
{
    BoundedLruCache::with_capacity_at_least_one(cap)
}

#[test]
fn clamped_constructor_uses_minimum_nonzero_capacity() {
    let mut cache = BoundedLruCache::with_capacity_at_least_one(0);
    assert_eq!(cache.cap().get(), 1);
    assert_eq!(cache.push("a", 1), None);
    assert_eq!(cache.push("b", 2), Some(("a", 1)));
    assert_eq!(cache.get(&"b"), Some(&2));
}

#[test]
fn get_promotes_entry_and_push_evicts_lru() {
    let mut cache = cache(2);
    assert_eq!(cache.push("a", 1), None);
    assert_eq!(cache.push("b", 2), None);

    assert_eq!(cache.get(&"a"), Some(&1));
    assert_eq!(cache.push("c", 3), Some(("b", 2)));

    assert!(cache.contains(&"a"));
    assert!(cache.contains(&"c"));
    assert!(!cache.contains(&"b"));
}

#[test]
fn push_existing_replaces_value_and_keeps_capacity() {
    let mut cache = cache(2);
    cache.push("a", 1);
    cache.push("b", 2);

    assert_eq!(cache.push("a", 3), Some(("a", 1)));
    assert_eq!(cache.len(), 2);
    assert_eq!(cache.get(&"a"), Some(&3));
}

#[test]
fn pop_removes_requested_entry_and_preserves_lru_order() {
    let mut cache = cache(3);
    cache.push("a", 1);
    cache.push("b", 2);
    cache.push("c", 3);

    assert_eq!(cache.pop(&"b"), Some(2));
    assert_eq!(cache.get(&"a"), Some(&1));
    assert_eq!(cache.pop_lru(), Some(("c", 3)));
    assert_eq!(cache.len(), 1);
}

#[test]
fn a_full_cache_that_only_misses_keeps_evicting_in_order() {
    let mut cache = cache(4);
    for step in 0..4 {
        assert_eq!(cache.push(step, step * 10), None);
    }

    for step in 4..64 {
        let evicted = cache.push(step, step * 10);
        assert_eq!(
            evicted,
            Some((step - 4, (step - 4) * 10)),
            "insert {step} must evict the oldest entry"
        );
        assert_eq!(cache.len(), 4);
    }

    let live: Vec<_> = cache.iter().map(|(key, value)| (*key, *value)).collect();
    assert_eq!(live, vec![(63, 630), (62, 620), (61, 610), (60, 600)]);
}

#[test]
fn reused_slots_do_not_resurrect_the_entries_that_vacated_them() {
    let mut cache = cache(3);
    cache.push("a", 1);
    cache.push("b", 2);
    cache.push("c", 3);

    assert_eq!(cache.pop(&"b"), Some(2));
    assert_eq!(cache.push("d", 4), None);

    assert!(!cache.contains(&"b"));
    assert_eq!(cache.peek(&"d"), Some(&4));
    assert_eq!(cache.len(), 3);
    assert_eq!(cache.pop_lru(), Some(("a", 1)));
    assert_eq!(cache.pop_lru(), Some(("c", 3)));
    assert_eq!(cache.pop_lru(), Some(("d", 4)));
    assert_eq!(cache.pop_lru(), None);
    assert!(cache.is_empty());
}

#[test]
fn a_promoted_entry_survives_the_next_eviction() {
    let mut cache = cache(3);
    cache.push("a", 1);
    cache.push("b", 2);
    cache.push("c", 3);

    assert_eq!(cache.get(&"a"), Some(&1));
    assert_eq!(cache.get_mut(&"b").map(|value| *value), Some(2));

    assert_eq!(cache.push("d", 4), Some(("c", 3)));
    assert!(cache.contains(&"a"));
    assert!(cache.contains(&"b"));
}

#[test]
fn peek_reads_without_promoting_entry() {
    let mut cache = cache(2);
    cache.push("a", 1);
    cache.push("b", 2);

    assert_eq!(cache.peek(&"a"), Some(&1));
    assert_eq!(cache.push("c", 3), Some(("a", 1)));
}

#[test]
fn iter_reports_mru_to_lru_entries() {
    let mut cache = cache(3);
    cache.push("a", 1);
    cache.push("b", 2);
    cache.get(&"a");

    let entries: Vec<_> = cache.iter().map(|(key, value)| (*key, *value)).collect();
    assert_eq!(entries, vec![("a", 1), ("b", 2)]);
}