testdrop 0.1.1

A utility to help test drop implementations
Documentation
//! This crate helps test the following drop issues:
//!
//! - Assert that an item was dropped
//! - Assert that an item was not dropped
//! - Assert that an item was not dropped multiple times (this is implicit tested)
//!
//! This kind of test is useful for objects that manages the lifetime of other objects, like smart
//! pointers and containers.
//!
//! # Examples
//!
//! Test if [`std::mem::forget`](https://doc.rust-lang.org/stable/std/mem/fn.forget.html) works.
//!
//! ```
//! use testdrop::TestDrop;
//! use std::mem;
//!
//! let td = TestDrop::new();
//! let (id, item) = td.new_item();
//!
//! mem::forget(item);
//!
//! td.assert_no_drop(id);
//! ```
//!
//! Test if the [`std::rc::Rc`](https://doc.rust-lang.org/stable/std/rc/struct.Rc.html) drop
//! implementation works.
//!
//! ```
//! use testdrop::TestDrop;
//! use std::rc::Rc;
//!
//! let td = TestDrop::new();
//! let (id, item) = td.new_item();
//! let item = Rc::new(item);
//! let item_clone = item.clone();
//!
//! // Decrease the reference counter, but do not drop.
//! drop(item_clone);
//! td.assert_no_drop(id);
//!
//! // Decrease the reference counter and then drop.
//! drop(item);
//! td.assert_drop(id);
//! ```
//!
//! Test if the [`std::vec::Vec`](https://doc.rust-lang.org/stable/std/vec/struct.Vec.html) drop
//! implementation works.
//!
//! ```
//! use testdrop::TestDrop;
//!
//! let td = TestDrop::new();
//! let v: Vec<_> = (0..10).map(|_| td.new_item().1).collect();
//!
//! drop(v);
//!
//! // Vec::drop should drop all items.
//! assert_eq!(10, td.num_tracked_items());
//! assert_eq!(10, td.num_dropped_items());
//! ```

use std::cell::{Cell, RefCell};
use std::collections::HashSet;

/// A struct to help test drop related issues.
///
/// See the [module](index.html) documentation for examples of usage.
#[derive(Default, Debug)]
pub struct TestDrop {
    id: Cell<usize>,
    drops: RefCell<HashSet<usize>>,
}

impl TestDrop {
    /// Creates a new `TestDrop`.
    pub fn new() -> TestDrop {
        TestDrop::default()
    }

    /// Creates a new [`Item`](struct.Item.html) and returns the `id` of the item and the item.
    /// The `id` of the item can be used with [`assert_drop`](struct.TestDrop#tymethod.assert_drop)
    /// and [`assert_no_drop`](struct.TestDrop#tymethod.assert_no_drop).
    pub fn new_item(&self) -> (usize, Item) {
        let id = self.id.get();
        self.id.set(id + 1);
        (id,
         Item {
            id: id,
            parent: self,
        })
    }

    /// Returns the number of tracked items.
    pub fn num_tracked_items(&self) -> usize {
        self.id.get()
    }

    /// Returns the number of dropped items so far.
    pub fn num_dropped_items(&self) -> usize {
        self.drops.borrow().len()
    }

    /// Asserts that an item was dropped.
    ///
    /// # Panics
    ///
    /// If the item was not dropped.
    pub fn assert_drop(&self, id: usize) {
        assert!(self.drops.borrow().contains(&id),
                "{} should be dropped, but was not",
                id);
    }

    /// Asserts that an item was not dropped.
    ///
    /// # Panics
    ///
    /// If the item was dropped.
    pub fn assert_no_drop(&self, id: usize) {
        assert!(!self.drops.borrow().contains(&id),
                "{} should not be dropped, but was",
                id);
    }

    fn add_drop(&self, id: usize) {
        if !self.drops.borrow_mut().insert(id) {
            panic!("{} is already dropped", id)
        }
    }
}

/// An item tracked by `TestDrop`.
///
/// This `struct` is created by [`TestDrop::new_item`](struct.TestDrop.html). See its documentation
/// for more.
#[derive(Debug)]
pub struct Item<'a> {
    id: usize,
    parent: &'a TestDrop,
}

impl<'a> PartialEq for Item<'a> {
    fn eq(&self, other: &Self) -> bool {
        self.id == other.id && self.parent as *const _ == other.parent as *const _
    }
}

impl<'a> Drop for Item<'a> {
    fn drop(&mut self) {
        self.parent.add_drop(self.id)
    }
}

impl<'a> Item<'a> {
    /// Returns the `id` of this item.
    pub fn id(&self) -> usize {
        self.id
    }
}


#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    #[should_panic(expected = "0 should be dropped, but was not")]
    fn assert_drop() {
        let td = TestDrop::new();
        let (id, _item) = td.new_item();
        td.assert_drop(id);
    }

    #[test]
    #[should_panic(expected = "0 should not be dropped, but was")]
    fn assert_no_drop() {
        let td = TestDrop::new();
        let (id, item) = td.new_item();
        drop(item);
        td.assert_no_drop(id);
    }

    #[test]
    #[should_panic(expected = "0 is already dropped")]
    fn drop_more_than_once() {
        let td = TestDrop::new();
        let (_, a) = td.new_item();
        unsafe { ::std::ptr::read(&a as *const _) };
    }

    #[test]
    fn count() {
        let td = TestDrop::new();
        assert_eq!(0, td.num_tracked_items());
        assert_eq!(0, td.num_dropped_items());

        let (_, a) = td.new_item();
        let (_, b) = td.new_item();
        assert_eq!(2, td.num_tracked_items());
        assert_eq!(0, td.num_dropped_items());

        drop(a);
        assert_eq!(2, td.num_tracked_items());
        assert_eq!(1, td.num_dropped_items());

        drop(b);
        assert_eq!(2, td.num_tracked_items());
        assert_eq!(2, td.num_dropped_items());
    }
}