refslots 0.1.0

Reference-counted handles to slots in a slotmap
Documentation
use crate::recorder::{
    HandleId, Recorder, RecorderSnapshot, collect_sorted,
    messages::{self, SlotDisplayFormatter},
};
use slotmap::Key;
use std::fmt::Write;

/// Asserts that all handles for all slots have been released.
///
/// # Panics
///
/// Panics if the assertion is false.
pub fn all_handles_released<K: Key>(recorder: &Recorder<K>) {
    if recorder.slots.is_empty() {
        return;
    }

    let slot_count = recorder.slots.len();
    let mut out = String::new();
    for (key, record) in collect_sorted(&recorder.slots) {
        debug_assert!(!record.handles.is_empty());
        let message = messages::handles_leaked(*key, record);
        write!(out, "{message}").unwrap();
    }
    panic!("refslots: {slot_count} slots with undropped handles\n\n{out}");
}

/// Asserts that all handles for the slot `key` have been released.
///
/// # Panics
///
/// Panics if the assertion is false.
pub fn handles_released<K: Key>(recorder: &Recorder<K>, key: K) {
    let Some(record) = recorder.slots.get(&key) else { return };
    debug_assert!(!record.handles.is_empty());

    let out = messages::handles_leaked(key, record);
    panic!(
        "refslots: {} has {} undropped handles\n\n{out}",
        SlotDisplayFormatter { label: record.label, key },
        record.handles.len()
    );
}

/// Asserts that all handles created after `snapshot` have been released.
///
/// Handles that existed when the snapshot was taken are ignored, even if they are still alive.
///
/// # Panics
///
/// Panics if the assertion is false.
pub fn handles_released_since<K: Key>(recorder: &Recorder<K>, snapshot: &RecorderSnapshot<K>) {
    let mut out = String::new();
    let mut new_handle_count = 0;
    for (key, record) in collect_sorted(&recorder.slots) {
        let count = count_handles_created_since(record.handles.keys(), snapshot.next_handle_id);
        if count > 0 {
            let message = messages::new_handles(*key, record, count);
            writeln!(out, "{message}").unwrap();
            new_handle_count += count;
        }
    }
    if new_handle_count > 0 {
        panic!("refslots: {new_handle_count} live handles created since last snapshot\n\n{out}");
    }
}

fn count_handles_created_since<'a, I>(handles: I, min_handle_id: u64) -> usize
where
    I: Iterator<Item = &'a HandleId>,
{
    handles.filter(|id| id.0 >= min_handle_id).count()
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::recorder::tests::{Recorder, make_key, record_create, shutdown};

    mod all_handles_released {
        use super::*;

        #[test]
        fn released() {
            let mut recorder = Recorder::default();
            let key = make_key(1);
            let handle_id = record_create(&mut recorder, key);
            recorder.record_drop_handle(key, handle_id);
            all_handles_released(&recorder);
        }

        #[test]
        #[should_panic(expected = "1 slots with undropped handles")]
        fn unreleased() {
            let mut recorder = Recorder::default();
            let key = make_key(1);
            record_create(&mut recorder, key);
            all_handles_released(&recorder);
            shutdown(&mut recorder);
        }

        #[test]
        fn empty() {
            let recorder = Recorder::default();
            all_handles_released(&recorder);
        }
    }

    mod handles_released {
        use super::*;

        #[test]
        fn released() {
            let mut recorder = Recorder::default();
            let key = make_key(1);
            let handle_id = record_create(&mut recorder, key);
            recorder.record_drop_handle(key, handle_id);
            handles_released(&recorder, key);
        }

        #[test]
        #[should_panic(expected = "1 undropped handle")]
        fn unreleased() {
            let mut recorder = Recorder::default();
            let key = make_key(1);
            record_create(&mut recorder, key);
            handles_released(&recorder, key);
            shutdown(&mut recorder);
        }

        #[test]
        fn empty() {
            let recorder = Recorder::default();
            let key = make_key(1);
            handles_released(&recorder, key);
        }
    }

    mod handles_released_since {
        use super::*;

        #[test]
        fn released_slot() {
            let mut recorder = Recorder::default();
            record_create(&mut recorder, make_key(1));
            let snapshot = recorder.snapshot();

            let key = make_key(2);
            let handle_id = record_create(&mut recorder, key);
            recorder.record_drop_handle(key, handle_id);

            handles_released_since(&recorder, &snapshot);
            shutdown(&mut recorder);
        }

        #[test]
        #[should_panic(expected = "1 live handles created since last snapshot")]
        fn unreleased_slot() {
            let mut recorder = Recorder::default();
            record_create(&mut recorder, make_key(1));
            let snapshot = recorder.snapshot();

            record_create(&mut recorder, make_key(2));
            handles_released_since(&recorder, &snapshot);
            shutdown(&mut recorder);
        }

        #[test]
        fn released_handle() {
            let mut recorder = Recorder::default();
            let key = make_key(1);
            record_create(&mut recorder, key);
            let snapshot = recorder.snapshot();

            let handle_id = record_create(&mut recorder, key);
            recorder.record_drop_handle(key, handle_id);

            handles_released_since(&recorder, &snapshot);
            shutdown(&mut recorder);
        }

        #[test]
        #[should_panic(expected = "1 live handles created since last snapshot")]
        fn unreleased_handle() {
            let mut recorder = Recorder::default();
            let key = make_key(1);
            record_create(&mut recorder, key);
            let snapshot = recorder.snapshot();

            record_create(&mut recorder, key);
            handles_released_since(&recorder, &snapshot);
            shutdown(&mut recorder);
        }

        #[test]
        fn unchanged() {
            let mut recorder = Recorder::default();
            record_create(&mut recorder, make_key(1));
            let snapshot = recorder.snapshot();
            handles_released_since(&recorder, &snapshot);
            shutdown(&mut recorder);
        }

        #[test]
        fn empty() {
            let recorder = Recorder::default();
            let snapshot = recorder.snapshot();
            handles_released_since(&recorder, &snapshot);
        }
    }
}