refslots 0.1.0

Reference-counted handles to slots in a slotmap
Documentation
use crate::recorder::{SlotRecord, backtrace, collect_sorted};
use slotmap::Key;
use std::fmt::{self, Display, Write};

pub struct SlotDisplayFormatter<K: Key> {
    pub label: Option<&'static str>,
    pub key: K,
}

impl<K: Key> Display for SlotDisplayFormatter<K> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let mut debug = f.debug_tuple("slot");
        debug.field(&self.key);
        if let Some(label) = &self.label {
            debug.field(label);
        }
        debug.finish()
    }
}

pub fn new_handles<K: Key>(key: K, record: &SlotRecord, count: usize) -> String {
    format!(
        "{} with {count}/{} new handles",
        SlotDisplayFormatter { key, label: record.label },
        record.handles.len()
    )
}

pub fn handles_leaked<K: Key>(key: K, record: &SlotRecord) -> String {
    let mut out = String::default();
    for (handle_id, backtrace) in collect_sorted(&record.handles) {
        writeln!(
            out,
            "{} leaked handle: {handle_id:?}\n{:?}",
            SlotDisplayFormatter { label: record.label, key },
            backtrace::resolve_and_filter(backtrace.clone()),
        )
        .unwrap();
    }
    out
}

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

    #[test]
    fn handles() {
        let mut recorder = Recorder::default();
        let key = make_key(1);
        recorder.record_create_handle(key, Some("foo"), Backtrace::new_unresolved());
        record_create(&mut recorder, key);

        let message = new_handles(key, &recorder.slots[&key], 1);
        assert_eq!(message, r#"slot(DefaultKey(1v1), "foo") with 1/2 new handles"#);
        shutdown(&mut recorder);
    }

    #[test]
    fn leaked() {
        let mut recorder = Recorder::default();
        let key = make_key(1);
        let handle_id1 = record_create(&mut recorder, key);
        let handle_id2 = record_create(&mut recorder, key);

        let message = handles_leaked(key, &recorder.slots[&key]);
        assert_eq!(message.matches(" leaked handle: ").count(), 2);

        // Check handles are output in the correct order.
        let pos1 = message.find(&format!("{handle_id1:?}")).unwrap();
        let pos2 = message.find(&format!("{handle_id2:?}")).unwrap();
        assert!(pos1 < pos2);
        shutdown(&mut recorder);
    }

    #[test]
    fn debug() {
        let fmt = SlotDisplayFormatter { key: make_key(1), label: None };
        assert_eq!(format!("{fmt}"), "slot(DefaultKey(1v1))");
    }

    #[test]
    fn debug_with_label() {
        let fmt = SlotDisplayFormatter { key: make_key(1), label: Some("foo") };
        assert_eq!(format!("{fmt}"), r#"slot(DefaultKey(1v1), "foo")"#);
    }
}