drop-tracker-0.1.3/.cargo_vcs_info.json0000644000000001360000000000100134320ustar { "git": { "sha1": "8fd34e7bb43d707b70a83766c502a808dc22903c" }, "path_in_vcs": "" }drop-tracker-0.1.3/.gitignore000064400000000000000000000000241046102023000142060ustar 00000000000000/Cargo.lock /target drop-tracker-0.1.3/CHANGELOG.md000064400000000000000000000012761046102023000140410ustar 00000000000000# Changelog ## drop-tracker 0.1.3 * Added `track_many()` to `DropTracker`. ## drop-tracker 0.1.2 * Added assertions methods to `DropTracker` (`assert_alive()`, `assert_dropped()`, etc) to make test code more concise. * Added `fully_alive()` and `fully_dropped()` to `DropTracker` to quickly check if all the tracked keys are in a the desired state. * `DropItem` now implements traits to compare its keys with primitive types (`i32`, `char`, `bool`, `str`, ...). ## drop-tracker 0.1.1 * `DropItem` can now hold arbitrary values, making it easy to implement items that are comparable and hashable. * `DropItem` is now marked with `#[must_use]`. ## drop-tracker 0.1.0 * Initial release drop-tracker-0.1.3/Cargo.toml0000644000000017240000000000100114340ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" name = "drop-tracker" version = "0.1.3" authors = ["Andrea Corbellini "] description = "Crate to check when a variable gets dropped. Useful for testing wrappers and containers that use unsafe memory management." readme = "README.md" keywords = [ "testing", "drop", ] categories = [ "development-tools::testing", "rust-patterns", ] license = "BSD-3-Clause" repository = "https://github.com/andreacorbellini/rust-drop-tracker" [dependencies] drop-tracker-0.1.3/Cargo.toml.orig000064400000000000000000000007301046102023000151110ustar 00000000000000[package] name = "drop-tracker" version = "0.1.3" edition = "2021" authors = ["Andrea Corbellini "] license = "BSD-3-Clause" description = "Crate to check when a variable gets dropped. Useful for testing wrappers and containers that use unsafe memory management." repository = "https://github.com/andreacorbellini/rust-drop-tracker" keywords = ["testing", "drop"] categories = ["development-tools::testing", "rust-patterns"] [dependencies] drop-tracker-0.1.3/LICENSE.txt000064400000000000000000000027051046102023000140510ustar 00000000000000Copyright 2022 Andrea Corbellini Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. drop-tracker-0.1.3/README.md000064400000000000000000000061171046102023000135060ustar 00000000000000# Rust Drop Tracker [![Crate](https://img.shields.io/crates/v/drop-tracker)](https://crates.io/crates/drop-tracker) [![Documentation](https://img.shields.io/docsrs/drop-tracker)](https://docs.rs/drop-tracker/latest/drop_tracker/) [![License](https://img.shields.io/crates/l/drop-tracker)](https://choosealicense.com/licenses/bsd-3-clause/) Rust crate to check if a variable got correctly [dropped]. This crate is mostly useful in unit tests for code involving [`ManuallyDrop`], [`MaybeUninit`], unsafe memory management, custom containers, and more. More specifically, this crate allows you to test if a variable is alive or has been dropped, and also detects when a variable gets dropped twice. These features can be used to detect bugs in your custom wrappers or containers that make use of unsafe memory management and cannot be checked at compile time by the Rust compiler. [dropped]: https://doc.rust-lang.org/reference/destructors.html [`ManuallyDrop`]: std::mem::ManuallyDrop [`MaybeUninit`]: std::mem::MaybeUninit # Concepts The main struct of this crate is `DropTracker`. Once you initialize a tracker, you call `DropTracker::track` on it to get a `DropItem`. Each drop item is identified by a key; the key can be used at any time to check the state of the item and see if it's alive or if it has been dropped. # Examples This is how you would test that a container like `Vec` drops all its items when the container is dropped: ```rust use drop_tracker::DropTracker; let mut tracker = DropTracker::new(); // Create a new vector and add a bunch of elements to it. The elements in this case are // identified by integer keys (1, 2, 3), but any hashable type would work. let v = vec![tracker.track(1), tracker.track(2), tracker.track(3)]; // Assert that all elements in the vector are alive tracker.all_alive(1..=3) .expect("expected all elements to be alive"); // Once the vector is dropped, all items should be dropped with it drop(v); tracker.all_dropped(1..=3) .expect("expected all elements to be dropped"); ``` This is how you would test a struct that involves `MaybeUninit`: ```rust use std::mem::MaybeUninit; struct MyOption { set: bool, data: MaybeUninit, } impl MyOption { fn none() -> Self { Self { set: false, data: MaybeUninit::uninit() } } fn some(x: T) -> Self { Self { set: true, data: MaybeUninit::new(x) } } } // BUG: MyOption does not implement Drop! // BUG: The instance inside `data` may be initialized but not be properly destructed! // BUG: The following code will silently leak memory: let opt = MyOption::some(String::from("hello")); drop(opt); // the String does not get deallocated // DropTracker is able to catch this sort of bugs: use drop_tracker::DropTracker; let mut tracker = DropTracker::new(); let opt = MyOption::some(tracker.track("item")); tracker.state(&"item") .alive() .expect("item is expected to be alive"); // works drop(opt); tracker.state(&"item") .dropped() .expect("item is expected to be dropped"); // panics, meaning that the bug was detected ``` drop-tracker-0.1.3/src/itemtraits.rs000064400000000000000000000162611046102023000155520ustar 00000000000000use crate::DropItem; use crate::DroppedError; use crate::State; use std::borrow::Borrow; use std::borrow::BorrowMut; use std::cmp::Ordering; use std::fmt; use std::hash; use std::ops::Deref; use std::ops::DerefMut; impl DropItem { fn inner_ref(&self) -> &V { self.try_inner_ref().expect("cannot borrow value") } fn try_inner_ref(&self) -> Result<&V, DroppedError> { match self.state { Some(ref state) if state.is_alive() => { // SAFETY: The item is alive. It is possible that another thread may have dropped // the item since we last checked, but in that case it's the caller's // responsibility to ensure safety. Ok(unsafe { self.value.assume_init_ref() }) }, _ => Err(DroppedError), } } fn inner_mut(&mut self) -> &mut V { self.try_inner_mut().expect("cannot borrow mutable value") } fn try_inner_mut(&mut self) -> Result<&mut V, DroppedError> { match self.state { Some(ref state) if state.is_alive() => { // SAFETY: The item is alive. It is possible that another thread may have dropped // the item since we last checked, but in that case it's the caller's // responsibility to ensure safety. Ok(unsafe { self.value.assume_init_mut() }) }, _ => Err(DroppedError), } } } impl Deref for DropItem { type Target = V; fn deref(&self) -> &Self::Target { self.inner_ref() } } impl DerefMut for DropItem { fn deref_mut(&mut self) -> &mut Self::Target { self.inner_mut() } } macro_rules! impl_ref_trait { () => {}; ( impl <{ $( $params:tt )* }> Borrow < $target:ty > for DropItem < $type_bound:ty > ; $( $rest:tt )* ) => { impl<$($params)*> Borrow<$target> for DropItem<$type_bound> { #[inline(always)] fn borrow(&self) -> &$target { self.inner_ref().borrow() } } impl_ref_trait! { $($rest)* } }; ( impl <{ $( $params:tt )* }> BorrowMut < $target:ty > for DropItem < $type_bound:ty > ; $( $rest:tt )* ) => { impl<$($params)*> BorrowMut<$target> for DropItem<$type_bound> { #[inline(always)] fn borrow_mut(&mut self) -> &mut $target { self.inner_mut().borrow_mut() } } impl_ref_trait! { $($rest)* } }; ( impl <{ $( $params:tt )* }> AsRef < $target:ty > for DropItem < $type_bound:ty > ; $( $rest:tt )* ) => { impl<$($params)*> AsRef<$target> for DropItem<$type_bound> { #[inline(always)] fn as_ref(&self) -> &$target { self.inner_ref().as_ref() } } impl_ref_trait! { $($rest)* } }; ( impl <{ $( $params:tt )* }> AsMut < $target:ty > for DropItem < $type_bound:ty > ; $( $rest:tt )* ) => { impl<$($params)*> AsMut<$target> for DropItem<$type_bound> { #[inline(always)] fn as_mut(&mut self) -> &mut $target { self.inner_mut().as_mut() } } impl_ref_trait! { $($rest)* } }; } impl_ref_trait! { impl<{V}> Borrow for DropItem; impl<{V: Borrow}> Borrow for DropItem; impl<{V: Borrow<[T]>, T}> Borrow<[T]> for DropItem; impl<{V}> BorrowMut for DropItem; impl<{V: BorrowMut}> BorrowMut for DropItem; impl<{V: BorrowMut<[T]>, T}> BorrowMut<[T]> for DropItem; impl<{V: AsRef, T}> AsRef for DropItem; impl<{V: AsMut, T}> AsMut for DropItem; } impl Eq for DropItem { } impl, W> PartialEq> for DropItem { fn eq(&self, other: &DropItem) -> bool { self.inner_ref().eq(other.inner_ref()) } } impl, W> PartialOrd> for DropItem { fn partial_cmp(&self, other: &DropItem) -> Option { self.inner_ref().partial_cmp(other.inner_ref()) } } macro_rules! impl_cmp_trait { () => {}; ( impl < $( $lifetime:lifetime , )? $param:ident $( , $extra_param:ident )? > PartialEq + PartialOrd < $other_type:ty > for DropItem < $item_bound:ty > ; $( $rest:tt )* ) => { impl<$($lifetime,)? $param $(, $extra_param)?> PartialEq<$other_type> for DropItem<$item_bound> where $param: PartialEq<$other_type> { fn eq(&self, other: &$other_type) -> bool { self.inner_ref().eq(other) } } impl<$($lifetime,)? $param $(, $extra_param)?> PartialOrd<$other_type> for DropItem<$item_bound> where $param: PartialOrd<$other_type> { fn partial_cmp(&self, other: &$other_type) -> Option { self.inner_ref().partial_cmp(other) } } impl_cmp_trait! { $($rest)* } }; } impl_cmp_trait! { impl PartialEq+PartialOrd for DropItem; impl PartialEq+PartialOrd for DropItem; impl PartialEq+PartialOrd for DropItem; impl PartialEq+PartialOrd for DropItem; impl PartialEq+PartialOrd for DropItem; impl PartialEq+PartialOrd for DropItem; impl PartialEq+PartialOrd for DropItem; impl PartialEq+PartialOrd for DropItem; impl PartialEq+PartialOrd for DropItem; impl PartialEq+PartialOrd for DropItem; impl PartialEq+PartialOrd for DropItem; impl PartialEq+PartialOrd for DropItem; impl PartialEq+PartialOrd for DropItem; impl PartialEq+PartialOrd for DropItem; impl PartialEq+PartialOrd<()> for DropItem; impl PartialEq+PartialOrd for DropItem; impl<'a, V> PartialEq+PartialOrd<&'a str> for DropItem; impl PartialEq+PartialOrd<[T]> for DropItem; impl<'a, V, T> PartialEq+PartialOrd<&'a [T]> for DropItem; } impl hash::Hash for DropItem { fn hash(&self, state: &mut H) { self.inner_ref().hash(state) } } impl fmt::Display for DropItem { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.inner_ref().fmt(f) } } impl fmt::Debug for DropItem { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self.try_inner_ref() { Ok(value) => { f.debug_struct("DropItem") .field("value", value) .field("state", &State::Alive) .finish() }, Err(_) => { f.debug_struct("DropItem") .field("value", &"") .field("state", &State::Dropped) .finish() } } } } drop-tracker-0.1.3/src/lib.rs000064400000000000000000001600461046102023000141340ustar 00000000000000//! Crate to check if a variable got correctly [dropped]. This crate is mostly useful in unit //! tests for code involving [`ManuallyDrop`], [`MaybeUninit`], unsafe memory management, //! custom containers, and more. //! //! [dropped]: https://doc.rust-lang.org/reference/destructors.html //! [`ManuallyDrop`]: std::mem::ManuallyDrop //! [`MaybeUninit`]: std::mem::MaybeUninit //! //! # Concepts //! //! The main struct of this crate is [`DropTracker`]. Once you initialize a tracker, you call //! [`DropTracker::track`] on it to get a [`DropItem`]. Each drop item is identified by a key; //! the key can be used at any time to check the state of the item and see if it's alive or if //! it has been dropped. //! //! # Examples //! //! This is how you would test that a container like [`Vec`] drops all its items when the container //! is dropped: //! //! ``` //! use drop_tracker::DropTracker; //! //! let mut tracker = DropTracker::new(); //! //! // Create a new vector and add a bunch of elements to it. The elements in this case are //! // identified by integer keys (1, 2, 3), but any hashable type would work. //! let v = vec![tracker.track(1), //! tracker.track(2), //! tracker.track(3)]; //! //! // Assert that all elements in the vector are alive //! tracker.all_alive(1..=3) //! .expect("expected all elements to be alive"); //! //! // Once the vector is dropped, all items should be dropped with it //! drop(v); //! tracker.all_dropped(1..=3) //! .expect("expected all elements to be dropped"); //! ``` //! //! This is how you would test a struct that involves [`MaybeUninit`]: //! //! ```should_panic //! # #![allow(dead_code)] //! use std::mem::MaybeUninit; //! //! struct MyOption { //! set: bool, //! data: MaybeUninit, //! } //! //! impl MyOption { //! fn none() -> Self { //! Self { set: false, data: MaybeUninit::uninit() } //! } //! //! fn some(x: T) -> Self { //! Self { set: true, data: MaybeUninit::new(x) } //! } //! } //! //! // BUG: MyOption does not implement Drop! //! // BUG: The instance inside `data` may be initialized but not be properly destructed! //! //! // BUG: The following code will silently leak memory: //! let opt = MyOption::some(String::from("hello")); //! drop(opt); // the String does not get deallocated //! //! // DropTracker is able to catch this sort of bugs: //! use drop_tracker::DropTracker; //! //! let mut tracker = DropTracker::new(); //! let opt = MyOption::some(tracker.track("item")); //! //! tracker.state(&"item") //! .alive() //! .expect("item is expected to be alive"); // works //! //! drop(opt); //! //! tracker.state(&"item") //! .dropped() //! .expect("item is expected to be dropped"); // panics, meaning that the bug was detected //! ``` //! //! If you want to write more succint code and don't care about the error message, you can also use //! the following `assert` methods: //! //! * [`assert_alive(...)`](DropTracker::assert_alive) equivalent to //! `state(...).alive().expect("error message")` //! * [`assert_dropped(...)`](DropTracker::assert_dropped) equivalent to //! `state(...).dropped().expect("error message")` //! * [`assert_all_alive(...)`](DropTracker::assert_all_alive) equivalent to //! `all_alive(...).expect("error message")` //! * [`assert_all_dropped(...)`](DropTracker::assert_all_dropped) equivalent to //! `all_dropped(...).expect("error message")` //! //! Here is how the first example above could be rewritten more consisely using `assert` methods: //! //! ``` //! use drop_tracker::DropTracker; //! //! let mut tracker = DropTracker::new(); //! let v = vec![tracker.track(1), //! tracker.track(2), //! tracker.track(3)]; //! //! tracker.assert_all_alive(1..=3); //! drop(v); //! tracker.assert_all_dropped(1..=3); //! ``` //! //! # Double drop //! //! [`DropItem`] will panic if it gets dropped twice or more, as this is generally a bug and may //! cause undefined behavior. This feature can be used to identify bugs with code using //! [`ManuallyDrop`](std::mem::ManuallyDrop), [`MaybeUninit`](std::mem::MaybeUninit) or //! [`std::ptr::drop_in_place`], like in the following example: //! //! ```should_panic //! use std::ptr; //! use drop_tracker::DropTracker; //! //! let mut tracker = DropTracker::new(); //! let mut item = tracker.track("something"); //! //! unsafe { ptr::drop_in_place(&mut item); } // ok //! unsafe { ptr::drop_in_place(&mut item); } // panic! //! ``` //! //! # Use in collections //! //! The [`DropItem`] instances returned by [`DropTracker::track`] hold a clone of the key passed //! to `track`. The `DropItem`s are [comparable](std::cmp) and [hashable](std::hash) if the //! underlying key is. This makes `DropItem` instances usable directly in collections like //! [`HashMap`](std::collections::HashMap), [`BTreeMap`](std::collections::BTreeMap), //! [`HashSet`](std::collections::HashSet) and many more. //! //! Here is an example involving [`HashSet`](std::collections::HashSet): //! //! ``` //! use drop_tracker::DropTracker; //! use std::collections::HashSet; //! //! let mut tracker = DropTracker::new(); //! //! let mut set = HashSet::from([ //! tracker.track(1), //! tracker.track(2), //! tracker.track(3), //! ]); //! //! set.remove(&3); //! //! tracker.state(&1).alive().expect("first item should be alive"); //! tracker.state(&2).alive().expect("second item should be alive"); //! tracker.state(&3).dropped().expect("third item should be dropped"); //! ``` //! //! Keys are required to be hashable and unique. If you need [`DropItem`] to hold a non-hashable //! value, or a repeated value, you can construct a [`DropItem`] with an arbitrary value using //! [`DropTracker::track_with_value`]: //! //! ``` //! use drop_tracker::DropTracker; //! //! let mut tracker = DropTracker::new(); //! //! // Construct items identified by integers and holding floats (which are not hashable) //! let item1 = tracker.track_with_value(1, 7.52); //! let item2 = tracker.track_with_value(2, 3.89); //! //! // Items compare according to their value //! assert!(item1 > item2); // 7.52 > 3.89 //! //! // Items that support comparison can be put in a vector and sorted //! let mut v = vec![item1, item2]; //! v.sort_by(|x, y| x.partial_cmp(y).unwrap()); //! ``` #![warn(missing_debug_implementations)] #![warn(missing_docs)] #![warn(pointer_structural_match)] #![warn(unreachable_pub)] #![warn(unused_crate_dependencies)] #![warn(unused_qualifications)] #![doc(test(attr(deny(warnings))))] #[cfg(test)] mod tests; mod itemtraits; use std::borrow::Borrow; use std::collections::HashMap; use std::collections::hash_map::Entry; use std::error::Error; use std::fmt; use std::hash::Hash; use std::iter::FusedIterator; use std::mem::MaybeUninit; use std::sync::Arc; use std::sync::atomic::AtomicBool; use std::sync::atomic::Ordering; /// A type that represents the state of a [`DropItem`]: either alive or dropped. /// /// See the [module documentation](self) for details. #[must_use = "you should check whether the status is alive or dropped"] #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] pub enum State { /// The item is alive. Alive, /// The item has been dropped, and its destructor has been called. Dropped, } impl State { /// Returns `true` if the state is [`Alive`](State::Alive). /// /// # Examples /// /// ``` /// use drop_tracker::State; /// /// assert!(State::Alive.is_alive()); /// assert!(!State::Dropped.is_alive()); /// ``` #[inline] #[must_use = "if you intended to assert that this is alive, consider `.alive().expect()`"] pub const fn is_alive(&self) -> bool { match self { Self::Alive => true, Self::Dropped => false, } } /// Returns `true` if the state is [`Dropped`](State::Dropped). /// /// # Examples /// /// ``` /// use drop_tracker::State; /// /// assert!(State::Dropped.is_dropped()); /// assert!(!State::Alive.is_dropped()); /// ``` #[inline] #[must_use = "if you intended to assert that this is dropped, consider `.dropped().expect()`"] pub const fn is_dropped(&self) -> bool { match self { Self::Alive => false, Self::Dropped => true, } } /// Returns [`Ok`] if the state is [`Alive`](State::Alive), [`Err`] otherwise. /// /// # Examples /// /// ``` /// use drop_tracker::DroppedError; /// use drop_tracker::State; /// /// assert_eq!(State::Alive.alive(), Ok(())); /// assert_eq!(State::Dropped.alive(), Err(DroppedError)); /// ``` #[inline] #[must_use = "if you intended to assert that this is alive, consider `.alive().expect()`"] pub const fn alive(&self) -> Result<(), DroppedError> { match self { Self::Alive => Ok(()), Self::Dropped => Err(DroppedError), } } /// Returns [`Ok`] if the state is [`Dropped`](State::Dropped), [`Err`] otherwise. /// /// # Examples /// /// ``` /// use drop_tracker::AliveError; /// use drop_tracker::State; /// /// assert_eq!(State::Dropped.dropped(), Ok(())); /// assert_eq!(State::Alive.dropped(), Err(AliveError)); /// ``` #[inline] #[must_use = "if you intended to assert that this is dropped, consider `.dropped().expect()`"] pub const fn dropped(&self) -> Result<(), AliveError> { match self { Self::Alive => Err(AliveError), Self::Dropped => Ok(()), } } } // Uses an `AtomicBool` (as opposed to e.g. a `RefCell`) to ensure that `DropTracker` and // `DropItem` are `Send`, `Sync` and `UnwindSafe`. #[derive(Clone, Debug)] struct StateCell(Arc); impl StateCell { #[inline] #[must_use] fn new(state: State) -> Self { Self(Arc::new(AtomicBool::new(state.is_dropped()))) } #[inline] fn get(&self) -> State { match self.0.load(Ordering::Relaxed) { false => State::Alive, true => State::Dropped, } } #[inline] fn replace(&mut self, state: State) -> State { match self.0.swap(state.is_dropped(), Ordering::Relaxed) { false => State::Alive, true => State::Dropped, } } #[inline] #[must_use] fn is_alive(&self) -> bool { self.get().is_alive() } #[inline] #[must_use] fn is_dropped(&self) -> bool { self.get().is_dropped() } } /// Creates [`DropItem`]s and tracks their state. /// /// [`DropItem`]s can be created using [`track`](DropTracker::track) or /// [`try_track`](DropTracker::try_track) and their state can be later checked using /// [`state`](DropTracker::state). /// /// [`DropItem`]s are identified by keys. A key can be of any type that implement the [`Hash`] /// and [`Eq`] traits, which include, for example: [`u32`], [`char`], [`str`], ... /// /// See the [module documentation](self) for details. #[derive(Default, Debug)] pub struct DropTracker { tracked: HashMap, } impl DropTracker { /// Creates a new empty `DropTracker`. /// /// # Examples /// /// ``` /// use drop_tracker::DropTracker; /// /// let tracker = DropTracker::::new(); /// assert_eq!(tracker.tracked().count(), 0); /// ``` #[must_use] pub fn new() -> Self { Self { tracked: HashMap::new(), } } /// Returns an iterator over the keys tracked by this `DropTracker`. /// /// The order of keys returned by this iterator is non deterministic. /// /// # Examples /// /// ``` /// # #![allow(unused_variables)] /// use drop_tracker::DropTracker; /// /// let mut tracker = DropTracker::new(); /// let item_a = tracker.track("a"); /// let item_b = tracker.track("b"); /// let item_c = tracker.track("c"); /// /// let mut keys = tracker.tracked() /// .collect::>(); /// keys.sort(); /// assert_eq!(keys, [&"a", &"b", &"c"]); /// ``` pub fn tracked(&self) -> impl Clone + Iterator + ExactSizeIterator + FusedIterator { self.tracked.keys() } /// Returns an iterator over the keys tracked by this `DropTracker` that are alive. /// /// The order of keys returned by this iterator is non deterministic. /// /// # Examples /// /// ``` /// use drop_tracker::DropTracker; /// /// let mut tracker = DropTracker::new(); /// let item_a = tracker.track("a"); /// let item_b = tracker.track("b"); /// let item_c = tracker.track("c"); /// /// drop(item_c); /// /// let mut alive_keys = tracker.alive() /// .collect::>(); /// alive_keys.sort(); /// assert_eq!(alive_keys, [&"a", &"b"]); /// /// drop(item_a); /// drop(item_b); /// /// assert_eq!(tracker.alive().count(), 0); /// ``` pub fn alive(&self) -> impl Clone + Iterator + FusedIterator { self.tracked.iter() .filter(|(_, state)| state.is_alive()) .map(|(key, _)| key) } /// Returns an iterator over the keys tracked by this `DropTracker` that have been dropped. /// /// The order of keys returned by this iterator is non deterministic. /// /// # Examples /// /// ``` /// # #![allow(unused_variables)] /// use drop_tracker::DropTracker; /// /// let mut tracker = DropTracker::new(); /// let item_a = tracker.track("a"); /// let item_b = tracker.track("b"); /// let item_c = tracker.track("c"); /// /// assert_eq!(tracker.dropped().count(), 0); /// /// drop(item_a); /// drop(item_b); /// /// let mut alive_keys = tracker.dropped() /// .collect::>(); /// alive_keys.sort(); /// assert_eq!(alive_keys, [&"a", &"b"]); /// ``` pub fn dropped(&self) -> impl Clone + Iterator + FusedIterator { self.tracked.iter() .filter(|(_, state)| state.is_dropped()) .map(|(key, _)| key) } /// Forgets all the items tracked by this `DropTracker`. /// /// The `DropItem`s previously returned by the tracker will still work normally, but it will no /// longer be possible to query their status after forgetting them. /// /// # Examples /// /// ``` /// # #![allow(unused_variables)] /// use drop_tracker::DropTracker; /// /// let mut tracker = DropTracker::new(); /// assert_eq!(tracker.tracked().count(), 0); /// /// let item_a = tracker.track("a"); /// let item_b = tracker.track("b"); /// let item_c = tracker.track("c"); /// assert_eq!(tracker.tracked().count(), 3); /// /// tracker.forget_all(); /// assert_eq!(tracker.tracked().count(), 0); /// ``` pub fn forget_all(&mut self) { self.tracked.clear(); } /// Forgets all the items tracked by this `DropTracker` that have been dropped. /// /// The `DropItem`s previously returned by the tracker will still work normally, but it will no /// longer be possible to query their status after forgetting them. /// /// # Examples /// /// ``` /// # #![allow(unused_variables)] /// use drop_tracker::DropTracker; /// /// let mut tracker = DropTracker::new(); /// assert_eq!(tracker.tracked().count(), 0); /// /// let item_a = tracker.track("a"); /// let item_b = tracker.track("b"); /// let item_c = tracker.track("c"); /// assert_eq!(tracker.tracked().count(), 3); /// /// // After dropping an item, the item is still tracked /// drop(item_a); /// drop(item_b); /// assert_eq!(tracker.tracked().count(), 3); /// /// // Use `forget_dropped` to lose track of items that have been dropped /// tracker.forget_dropped(); /// assert_eq!(tracker.tracked().count(), 1); /// /// let mut keys = tracker.tracked() /// .collect::>(); /// keys.sort(); /// assert_eq!(keys, [&"c"]); /// ``` pub fn forget_dropped(&mut self) { self.tracked.retain(|_, state| state.is_alive()) } } impl DropTracker { /// Creates a new [`DropItem`] identified by the given key. /// /// The value held by the `DropItem` is a clone of the key. Use /// [`DropTracker::track_with_value`] if you wish to specify a custom value. /// /// # Panics /// /// Panics if the key is already used by another tracked item. /// /// Call [`forget`](DropTracker::forget), /// [`forget_dropped`](DropTracker::forget_dropped) or /// [`forget_all`](DropTracker::forget_all) if you wish to reuse a key from an item you no /// longer need to track. /// /// See [`try_track`](DropTracker::try_track) for a variant of this method that does not panic. /// /// # Examples /// /// ``` /// use drop_tracker::DropTracker; /// use drop_tracker::State; /// /// let mut tracker = DropTracker::new(); /// /// let item = tracker.track("abc"); /// assert_eq!(tracker.state("abc"), State::Alive); /// /// drop(item); /// assert_eq!(tracker.state("abc"), State::Dropped); /// ``` /// /// Using the same key twice causes a panic: /// /// ```should_panic /// # #![allow(unused_variables)] /// use drop_tracker::DropTracker; /// /// let mut tracker = DropTracker::new(); /// /// let item1 = tracker.track("abc"); /// let item2 = tracker.track("abc"); // panics! /// ``` /// /// Use [`forget`](DropTracker::forget) to reuse the same key: /// /// ``` /// # #![allow(unused_variables)] /// use drop_tracker::DropTracker; /// /// let mut tracker = DropTracker::new(); /// /// let item1 = tracker.track("abc"); /// let _ = tracker.forget("abc"); /// let item2 = tracker.track("abc"); // works /// ``` pub fn track(&mut self, key: K) -> DropItem where K: Clone { self.try_track(key).expect("cannot track key") } /// Creates a new [`DropItem`] identified by the given key, or [`Err`] if the key is /// already in use. /// /// The value held by the `DropItem` is a clone of the key. Use /// [`DropTracker::try_track_with_value`] if you wish to specify a custom value. /// /// See also [`track`](DropTracker::track). /// /// # Examples /// /// ``` /// # #![allow(unused_variables)] /// use drop_tracker::DropTracker; /// /// let mut tracker = DropTracker::new(); /// /// let item = tracker.try_track("abc"); /// assert!(item.is_ok()); /// /// let item = tracker.try_track("abc"); /// assert!(item.is_err()); // key is already used /// ``` pub fn try_track(&mut self, key: K) -> Result, CollisionError> where K: Clone { let value = key.clone(); self.try_track_with_value(key, value) } /// Creates a new [`DropItem`] identified by the given key and holding the given value. /// /// # Panics /// /// Panics if the key is already used by another tracked item. /// /// Call [`forget`](DropTracker::forget), /// [`forget_dropped`](DropTracker::forget_dropped) or /// [`forget_all`](DropTracker::forget_all) if you wish to reuse a key from an item you no /// longer need to track. /// /// See [`try_track_with_value`](DropTracker::try_track_with_value) for a variant of this /// method that does not panic. /// /// # Examples /// /// ``` /// use drop_tracker::DropTracker; /// use drop_tracker::State; /// /// let mut tracker = DropTracker::new(); /// /// let item = tracker.track_with_value("abc", vec![1, 2, 3]); /// assert_eq!(tracker.state("abc"), State::Alive); /// /// drop(item); /// assert_eq!(tracker.state("abc"), State::Dropped); /// ``` /// /// Using the same key twice causes a panic: /// /// ```should_panic /// # #![allow(unused_variables)] /// use drop_tracker::DropTracker; /// /// let mut tracker = DropTracker::new(); /// /// let item1 = tracker.track_with_value("abc", vec![1, 2, 3]); /// let item2 = tracker.track_with_value("abc", vec![4, 5, 6]); // panics! /// ``` /// /// Use [`forget`](DropTracker::forget) to reuse the same key: /// /// ``` /// # #![allow(unused_variables)] /// use drop_tracker::DropTracker; /// /// let mut tracker = DropTracker::new(); /// /// let item1 = tracker.track_with_value("abc", vec![1, 2, 3]); /// let _ = tracker.forget("abc"); /// let item2 = tracker.track_with_value("abc", vec![4, 5, 6]); // works /// ``` pub fn track_with_value(&mut self, key: K, value: V) -> DropItem { self.try_track_with_value(key, value).expect("cannot track key") } /// Creates a new [`DropItem`] identified by the given key and holding the given value, or /// [`Err`] if the key is already in use. /// /// See also [`track_with_value`](DropTracker::track_with_value). /// /// # Examples /// /// ``` /// # #![allow(unused_variables)] /// use drop_tracker::DropTracker; /// /// let mut tracker = DropTracker::new(); /// /// let item = tracker.try_track_with_value("abc", vec![1, 2, 3]); /// assert!(item.is_ok()); /// /// let item = tracker.try_track_with_value("abc", vec![4, 5, 6]); /// assert!(item.is_err()); // key is already used /// ``` pub fn try_track_with_value(&mut self, key: K, value: V) -> Result, CollisionError> { let state = StateCell::new(State::Alive); match self.tracked.entry(key) { Entry::Occupied(_) => Err(CollisionError), Entry::Vacant(entry) => { entry.insert(state.clone()); Ok(DropItem::new(value, state)) }, } } /// Creates multiple new [`DropItem`] structs, each identified by a key from the given /// iterable. /// /// Calling `track_many` is equivalent to calling [`track`](DropItem::track) multiple times. /// /// # Examples /// /// ``` /// use drop_tracker::DropTracker; /// use drop_tracker::State; /// /// let mut tracker = DropTracker::new(); /// /// let mut items = tracker.track_many(["abc", "def", "ghi"]); /// /// let abc = items.next().unwrap(); /// let def = items.next().unwrap(); /// let ghi = items.next().unwrap(); /// assert_eq!(items.next(), None); /// drop(items); /// /// assert_eq!(abc, "abc"); /// assert_eq!(def, "def"); /// assert_eq!(ghi, "ghi"); /// /// assert_eq!(tracker.state("abc"), State::Alive); /// assert_eq!(tracker.state("def"), State::Alive); /// assert_eq!(tracker.state("ghi"), State::Alive); /// /// drop(def); /// /// assert_eq!(tracker.state("abc"), State::Alive); /// assert_eq!(tracker.state("def"), State::Dropped); /// assert_eq!(tracker.state("ghi"), State::Alive); /// /// # drop(abc); /// # drop(ghi); /// ``` pub fn track_many<'a, Iter>(&'a mut self, keys: Iter) -> impl Iterator> + 'a where Iter: IntoIterator + 'a, K: Clone { keys.into_iter().map(|key| self.track(key)) } } impl DropTracker { /// Checks the state of a [`DropItem`] tracked by this `DropTracker`: [alive](State::Alive) or /// [dropped](State::Dropped). /// /// # Panics /// /// Panics if the given key is not tracked. /// /// See [`try_state`](DropTracker::try_state) for a variant of this method that does not panic. /// /// # Examples /// /// ``` /// use drop_tracker::DropTracker; /// use drop_tracker::State; /// /// let mut tracker = DropTracker::new(); /// /// let item = tracker.track("abc"); /// assert_eq!(tracker.state("abc"), State::Alive); /// /// drop(item); /// assert_eq!(tracker.state("abc"), State::Dropped); /// ``` /// /// Querying a key that is not tracked causes a panic: /// /// ```should_panic /// # #![allow(unused_variables)] /// use drop_tracker::DropTracker; /// /// let mut tracker = DropTracker::new(); /// /// let item = tracker.track("abc"); /// let state = tracker.state("def"); // panics! /// ``` pub fn state(&self, key: &Q) -> State where K: Borrow, Q: Hash + Eq + ?Sized { self.try_state(key).expect("cannot get state") } /// Checks the state of a [`DropItem`] tracked by this `DropTracker`: [alive](State::Alive) or /// [dropped](State::Dropped). Returns [`Err`] it the given key is not tracked. /// /// See also [`state`](DropTracker::state). /// /// # Examples /// /// ``` /// use drop_tracker::DropTracker; /// use drop_tracker::NotTrackedError; /// use drop_tracker::State; /// /// let mut tracker = DropTracker::new(); /// /// let item = tracker.track("abc"); /// assert_eq!(tracker.try_state("abc"), Ok(State::Alive)); /// assert_eq!(tracker.try_state("def"), Err(NotTrackedError)); /// /// drop(item); /// assert_eq!(tracker.try_state("abc"), Ok(State::Dropped)); /// assert_eq!(tracker.try_state("def"), Err(NotTrackedError)); /// ``` pub fn try_state(&self, key: &Q) -> Result where K: Borrow, Q: Hash + Eq + ?Sized { self.tracked.get(key) .ok_or(NotTrackedError) .map(|state| state.get()) } /// Forgets an item tracked by this `DropTracker`, and returns its current state /// ([alive](State::Alive) or [dropped](State::Dropped)). /// /// The `DropItem`s previously returned by the tracker will still work normally, but it will no /// longer be possible to query their status after forgetting them. /// /// # Panics /// /// Panics if the given key is not tracked. /// /// See [`try_forget`](DropTracker::try_forget) for a variant of this method that does not panic. /// /// # Examples /// /// ``` /// # #![allow(unused_variables)] /// use drop_tracker::DropTracker; /// use drop_tracker::State; /// /// let mut tracker = DropTracker::new(); /// /// let item = tracker.track("a"); /// assert!(tracker.is_tracked("a")); /// /// assert_eq!(tracker.forget("a"), State::Alive); /// assert!(!tracker.is_tracked("a")); /// ``` /// /// Forgetting a key that is not tracked causes a panic: /// /// ```should_panic /// # #![allow(unused_variables)] /// use drop_tracker::DropTracker; /// /// let mut tracker = DropTracker::new(); /// /// let item = tracker.track("abc"); /// let state = tracker.forget("def"); // panics! /// ``` pub fn forget(&mut self, key: &Q) -> State where K: Borrow, Q: Hash + Eq + ?Sized { self.try_forget(key).expect("cannot forget item") } /// Forgets an item tracked by this `DropTracker`, and returns its current state /// ([alive](State::Alive) or [dropped](State::Dropped)), or [`Err`] if the item is not /// tracked. /// /// The `DropItem`s previously returned by the tracker will still work normally, but it will no /// longer be possible to query their status after forgetting them. /// /// See also [`forget`](DropTracker::forget). /// /// # Examples /// /// ``` /// # #![allow(unused_variables)] /// use drop_tracker::DropTracker; /// use drop_tracker::NotTrackedError; /// use drop_tracker::State; /// /// let mut tracker = DropTracker::new(); /// /// let item = tracker.track("a"); /// assert!(tracker.is_tracked("a")); /// /// assert_eq!(tracker.try_forget("a"), Ok(State::Alive)); /// assert_eq!(tracker.try_forget("b"), Err(NotTrackedError)); /// ``` pub fn try_forget(&mut self, key: &Q) -> Result where K: Borrow, Q: Hash + Eq + ?Sized { self.tracked.remove(key) .ok_or(NotTrackedError) .map(|state| state.get()) } /// Returns [`true`] if an item identified by the given key is tracked by this `DropTracker`, /// [`false`] otherwise. /// /// # Examples /// /// ``` /// # #![allow(unused_variables)] /// use drop_tracker::DropTracker; /// /// let mut tracker = DropTracker::new(); /// assert!(!tracker.is_tracked("abc")); /// /// let item = tracker.track("abc"); /// assert!(tracker.is_tracked("abc")); /// ``` #[must_use] pub fn is_tracked(&self, key: &Q) -> bool where K: Borrow, Q: Hash + Eq + ?Sized { self.try_state(key).is_ok() } /// Returns [`Ok`] if all the given keys point to items that are [alive](State::Alive), /// [`Err`] otherwise. /// /// An error may be returned in two cases: either a key is not tracked, or it has been dropped. /// /// This method returns `Ok` if the sequence of keys passed is empty. /// /// # Examples /// /// ``` /// # #![allow(unused_variables)] /// use drop_tracker::DropTracker; /// use drop_tracker::NotAllAliveError; /// /// let mut tracker = DropTracker::new(); /// /// let item1 = tracker.track(1); /// let item2 = tracker.track(2); /// let item3 = tracker.track(3); /// let item4 = tracker.track(4); /// /// drop(item3); /// drop(item4); /// /// assert_eq!(tracker.all_alive([1, 2]), Ok(())); /// /// assert_eq!(tracker.all_alive([1, 2, 3, 4, 5, 6]), /// Err(NotAllAliveError { /// dropped: vec![3, 4], /// untracked: vec![5, 6], /// })); /// ``` /// /// Passing an empty set of keys returns `Ok`: /// /// ``` /// use drop_tracker::DropTracker; /// /// let tracker = DropTracker::<()>::new(); /// assert_eq!(tracker.all_alive([(); 0]), Ok(())); /// ``` pub fn all_alive(&self, iter: Iter) -> Result<(), NotAllAliveError> where K: Borrow, Q: Hash + Eq + ?Sized, Item: Borrow, Iter: IntoIterator { // Vec won't allocate any memory until items are pushed to it, so if this method does not // fail, no memory will be allocated let mut err = NotAllAliveError { dropped: Vec::new(), untracked: Vec::new(), }; for key in iter { match self.try_state(key.borrow()) { Ok(State::Alive) => (), Ok(State::Dropped) => err.dropped.push(key), Err(NotTrackedError) => err.untracked.push(key), } } if err.dropped.is_empty() && err.untracked.is_empty() { Ok(()) } else { Err(err) } } /// Returns [`Ok`] if all the given keys point to items that are [dropped](State::Dropped), /// [`Err`] otherwise. /// /// An error may be returned in two cases: either a key is not tracked, or it is alive. /// /// This method returns `Ok` if the sequence of keys passed is empty. /// /// # Examples /// /// ``` /// # #![allow(unused_variables)] /// use drop_tracker::DropTracker; /// use drop_tracker::NotAllDroppedError; /// /// let mut tracker = DropTracker::new(); /// /// let item1 = tracker.track(1); /// let item2 = tracker.track(2); /// let item3 = tracker.track(3); /// let item4 = tracker.track(4); /// /// drop(item3); /// drop(item4); /// /// assert_eq!(tracker.all_dropped([3, 4]), Ok(())); /// /// assert_eq!(tracker.all_dropped([1, 2, 3, 4, 5, 6]), /// Err(NotAllDroppedError { /// alive: vec![1, 2], /// untracked: vec![5, 6], /// })); /// ``` /// /// Passing an empty set of keys returns `Ok`: /// /// ``` /// use drop_tracker::DropTracker; /// /// let tracker = DropTracker::<()>::new(); /// assert_eq!(tracker.all_dropped([(); 0]), Ok(())); /// ``` pub fn all_dropped(&self, iter: Iter) -> Result<(), NotAllDroppedError> where K: Borrow, Q: Hash + Eq + ?Sized, Item: Borrow, Iter: IntoIterator { // Vec won't allocate any memory until items are pushed to it, so if this method does not // fail, no memory will be allocated let mut err = NotAllDroppedError { alive: Vec::new(), untracked: Vec::new(), }; for key in iter { match self.try_state(key.borrow()) { Ok(State::Alive) => err.alive.push(key), Ok(State::Dropped) => (), Err(NotTrackedError) => err.untracked.push(key), } } if err.alive.is_empty() && err.untracked.is_empty() { Ok(()) } else { Err(err) } } /// Returns [`Ok`] if all the keys tracked are [alive](State::Alive), [`Err`] otherwise. /// /// The error returned references an arbitrary keys that was found [dropped](State::Dropped). /// /// If the tracker is empty, this method returns `Ok`. /// /// # Examples /// /// ``` /// # #![allow(unused_variables)] /// use drop_tracker::DropTracker; /// use drop_tracker::SomeDroppedError; /// /// let mut tracker = DropTracker::new(); /// /// let item1 = tracker.track(1); /// let item2 = tracker.track(2); /// let item3 = tracker.track(3); /// /// assert_eq!(tracker.fully_alive(), Ok(())); /// /// drop(item1); /// /// assert_eq!(tracker.fully_alive(), Err(SomeDroppedError { dropped: &1 })); /// ``` /// /// Calling `fully_alive()` on an empty tracker always returns `Ok`: /// /// ``` /// use drop_tracker::DropTracker; /// /// let tracker = DropTracker::<()>::new(); /// assert_eq!(tracker.fully_alive(), Ok(())); /// ``` pub fn fully_alive(&self) -> Result<(), SomeDroppedError<'_, K>> { let dropped = self.tracked.iter() .find(|(_, state)| state.is_dropped()) .map(|(key, _)| key); match dropped { None => Ok(()), Some(dropped) => Err(SomeDroppedError { dropped }), } } /// Returns [`Ok`] if all the keys tracked are [dropped](State::Dropped), [`Err`] otherwise. /// /// The error returned references an arbitrary keys that was found [alive](State::Alive). /// /// If the tracker is empty, this method returns `Ok`. /// /// # Examples /// /// ``` /// # #![allow(unused_variables)] /// use drop_tracker::DropTracker; /// use drop_tracker::SomeAliveError; /// /// let mut tracker = DropTracker::new(); /// /// let item1 = tracker.track(1); /// let item2 = tracker.track(2); /// let item3 = tracker.track(3); /// /// drop(item1); /// drop(item2); /// /// assert_eq!(tracker.fully_dropped(), Err(SomeAliveError { alive: &3 })); /// /// drop(item3); /// /// assert_eq!(tracker.fully_dropped(), Ok(())); /// ``` /// /// Calling `fully_dropped()` on an empty tracker always returns `Ok`: /// /// ``` /// use drop_tracker::DropTracker; /// /// let tracker = DropTracker::<()>::new(); /// assert_eq!(tracker.fully_dropped(), Ok(())); /// ``` pub fn fully_dropped(&self) -> Result<(), SomeAliveError<'_, K>> { let alive = self.tracked.iter() .find(|(_, state)| state.is_alive()) .map(|(key, _)| key); match alive { None => Ok(()), Some(alive) => Err(SomeAliveError { alive }), } } /// Checks that all the given key points to an item that is [alive](State::Alive), panics /// otherwise. /// /// `assert_alive(...)` is a shortcut for `state(...).alive().unwrap()`. See /// [`state()`](DropTracker::state) for more details. /// /// # Panics /// /// If the key is not tracked, or if it has been dropped. /// /// # Examples /// /// ```should_panic /// # #![allow(unused_variables)] /// use drop_tracker::DropTracker; /// /// let mut tracker = DropTracker::new(); /// /// let item2 = tracker.track(1); /// let item2 = tracker.track(2); /// /// drop(item2); /// /// tracker.assert_alive(&1); // succeeds /// tracker.assert_alive(&2); // panics (item was dropped) /// tracker.assert_alive(&3); // panics (key is not tracked) /// ``` pub fn assert_alive(&self, key: &Q) where K: Borrow, Q: Hash + Eq + ?Sized { let state = match self.try_state(key) { Ok(state) => state, Err(err) => panic!("{err}"), }; match state.alive() { Ok(()) => (), Err(err) => panic!("{err}"), } } /// Checks that all the given key points to an item that is [dropped](State::Dropped), panics /// otherwise. /// /// `assert_dropped(...)` is a shortcut for `state(...).dropped().unwrap()`. See /// [`state()`](DropTracker::state) for more details. /// /// # Panics /// /// If the key is not tracked, or if it is alive. /// /// # Examples /// /// ```should_panic /// # #![allow(unused_variables)] /// use drop_tracker::DropTracker; /// /// let mut tracker = DropTracker::new(); /// /// let item1 = tracker.track(1); /// let item2 = tracker.track(2); /// /// drop(item1); /// /// tracker.assert_dropped(&1); // succeeds /// tracker.assert_dropped(&2); // panics (item is alive) /// tracker.assert_dropped(&3); // panics (key is not tracked) /// ``` pub fn assert_dropped(&self, key: &Q) where K: Borrow, Q: Hash + Eq + ?Sized { let state = match self.try_state(key) { Ok(state) => state, Err(err) => panic!("{err}"), }; match state.dropped() { Ok(()) => (), Err(err) => panic!("{err}"), } } /// Checks that all the given keys point to items that are [alive](State::Alive), panics /// otherwise. /// /// `assert_all_alive(...)` is a shortcut for `all_alive(...).unwrap()`. See /// [`all_alive()`](DropTracker::all_alive) for more details. /// /// # Panics /// /// If a key is not tracked, or if it has been dropped. /// /// # Examples /// /// ```should_panic /// # #![allow(unused_variables)] /// use drop_tracker::DropTracker; /// /// let mut tracker = DropTracker::new(); /// /// let item1 = tracker.track(1); /// let item2 = tracker.track(2); /// let item3 = tracker.track(3); /// let item4 = tracker.track(4); /// /// drop(item3); /// drop(item4); /// /// tracker.assert_all_alive([1, 2]); // succeeds /// tracker.assert_all_alive([3, 4]); // panics (items were dropped) /// tracker.assert_all_alive([5, 6]); // panics (keys are not tracked) /// ``` /// /// Passing an empty set of keys succeeds: /// /// ``` /// use drop_tracker::DropTracker; /// /// let tracker = DropTracker::<()>::new(); /// tracker.assert_all_alive([(); 0]); /// ``` pub fn assert_all_alive(&self, iter: Iter) where K: Borrow, Q: Hash + Eq + ?Sized, Item: Borrow + fmt::Debug, Iter: IntoIterator { match self.all_alive(iter) { Ok(()) => (), Err(err) => panic!("{err}"), } } /// Checks that all the given keys point to items that are [dropped](State::Dropped), panics /// otherwise. /// /// `assert_all_dropped(...)` is a shortcut for `all_dropped(...).unwrap()`. See /// [`all_dropped()`](DropTracker::all_dropped) for more details. /// /// # Panics /// /// If a key is not tracked, or if it is alive. /// /// # Examples /// /// ```should_panic /// # #![allow(unused_variables)] /// use drop_tracker::DropTracker; /// /// let mut tracker = DropTracker::new(); /// /// let item1 = tracker.track(1); /// let item2 = tracker.track(2); /// let item3 = tracker.track(3); /// let item4 = tracker.track(4); /// /// drop(item1); /// drop(item2); /// /// tracker.assert_all_dropped([1, 2]); // succeeds /// tracker.assert_all_dropped([3, 4]); // panics (items are alive) /// tracker.assert_all_dropped([5, 6]); // panics (keys are not tracked) /// ``` /// /// Passing an empty set of keys succeeds: /// /// ``` /// use drop_tracker::DropTracker; /// /// let tracker = DropTracker::<()>::new(); /// tracker.assert_all_dropped([(); 0]); /// ``` pub fn assert_all_dropped(&self, iter: Iter) where K: Borrow, Q: Hash + Eq + ?Sized, Item: Borrow + fmt::Debug, Iter: IntoIterator { match self.all_dropped(iter) { Ok(()) => (), Err(err) => panic!("{err}"), } } /// Checks that all the keys tracked are [alive](State::Alive), panics otherwise. /// /// `assert_fully_alive()` is a shortcut for `fully_alive().unwrap()`. See /// [`fully_alive()`](DropTracker::fully_alive) for more details. /// /// # Panics /// /// If one or more items were found to have been [dropped](State::Dropped). /// /// # Examples /// /// Calling `assert_fully_alive()` when all items are alive succeeds: /// /// ``` /// # #![allow(unused_variables)] /// use drop_tracker::DropTracker; /// /// let mut tracker = DropTracker::new(); /// /// let item1 = tracker.track(1); /// let item2 = tracker.track(2); /// /// tracker.assert_fully_alive(); // succeeds /// ``` /// /// Calling `assert_fully_alive()` when one or more items are dropped causes a panic: /// /// ```should_panic /// # #![allow(unused_variables)] /// use drop_tracker::DropTracker; /// /// let mut tracker = DropTracker::new(); /// /// let item1 = tracker.track(1); /// let item2 = tracker.track(2); /// /// drop(item1); /// /// tracker.assert_fully_alive(); // panics /// ``` /// /// Calling `assert_fully_alive()` when the tracker is empty succeeds: /// /// ``` /// use drop_tracker::DropTracker; /// /// let tracker = DropTracker::<()>::new(); /// tracker.assert_fully_alive(); // succeeds /// ``` pub fn assert_fully_alive(&self) where K: fmt::Debug { match self.fully_alive() { Ok(()) => (), Err(err) => panic!("{err}"), } } /// Checks that all the keys tracked are [dropped](State::Dropped), panics otherwise. /// /// `assert_fully_dropped()` is a shortcut for `fully_dropped().unwrap()`. See /// [`fully_dropped()`](DropTracker::fully_dropped) for more details. /// /// # Panics /// /// If one or more items were found [alive](State::Alive). /// /// # Examples /// /// Calling `assert_fully_dropped()` when items are alive causes a panic: /// /// ```should_panic /// # #![allow(unused_variables)] /// use drop_tracker::DropTracker; /// /// let mut tracker = DropTracker::new(); /// /// let item1 = tracker.track(1); /// let item2 = tracker.track(2); /// /// tracker.assert_fully_dropped(); // panics /// ``` /// /// Calling `assert_fully_dropped()` when all items are dropped succeeds: /// /// ``` /// use drop_tracker::DropTracker; /// /// let mut tracker = DropTracker::new(); /// /// let item1 = tracker.track(1); /// let item2 = tracker.track(2); /// /// drop(item1); /// drop(item2); /// /// tracker.assert_fully_dropped(); // succeeds /// ``` /// /// Calling `assert_fully_dropped()` when the tracker is empty succeeds: /// /// ``` /// use drop_tracker::DropTracker; /// /// let tracker = DropTracker::<()>::new(); /// tracker.assert_fully_dropped(); // succeeds /// ``` pub fn assert_fully_dropped(&self) where K: fmt::Debug { match self.fully_dropped() { Ok(()) => (), Err(err) => panic!("{err}"), } } } /// An item that will notify the parent [`DropTracker`] once it gets dropped. /// /// `DropItem` instances are created by [`DropTracker::track`], [`DropTracker::track_with_value`], /// and related functions. `DropItem` instances may contain an "underlying value" that affects the /// item behavior when used with standard traits. The underlying value is either: /// /// * a clone of `key` when constructing an item using `track(key)` (implicit); or /// * `value` when constructing an item using `track_with_value(key, value)` (explicit). /// /// To check whether an item is alive or has been dropped, use [`DropTracker::state`] or see the /// documentation for [`DropTracker`] for alternatives. /// /// # Coercing and borrowing /// /// `DropItem` instances may be [_coerced_](std::ops::Deref) and [_borrowed_](std::borrow::Borrow) /// as the the underlying value type. This means that, for example, if you create a `DropItem` /// using `track(String::from("abc"))`, you may call all of the `String` methods on that item. /// /// `DropItem` also implements the standard traits [`PartialEq`](std::cmp::PartialEq), /// [`Eq`](std::cmp::Eq), [`PartialOrd`](std::cmp::PartialOrd), [`Ord`](std::cmp::Ord) and /// [`Hash`](std::hash::Hash), [`Display`](std::fmt::Display), [`Debug`](std::fmt::Debug) if the /// type of the underlying value implements them. /// /// # Cloning /// /// `DropItem` does not implement the [`Clone`] trait as it would introduce ambiguity with respect /// to understanding whether the item has been dropped or is still alive when using /// [`DropTracker::state`]. /// /// # Double drop /// /// `DropItem` instances can be dropped twice or more. Doing so will cause a panic, but will not /// cause undefined behavior (unless you're calling drop on an invalid memory location). The panic /// on double drop is an useful feature to detect logic errors in destructors. /// /// # Safety /// /// Borrowing or performing operations on the underlying value of a `DropItem` is generally safe /// when using safe Rust code. However, `DropItem`s are often used in unsafe code and are used to /// detect potential bugs. In those circumstances, it is possible to trigger undefined behavior. /// In particular, borrowing or performing operations on a `DropItem` while another thread is /// dropping will result in undefined behavior (although it must be noted that this is a bug in the /// caller code and is not something that should happen in safe Rust code). /// /// Only [`Drop`](std::ops::Drop) on a `DropItem` is guaranteed to be safe in all circumstances. /// /// # Examples /// /// ``` /// use drop_tracker::DropTracker; /// /// let mut tracker = DropTracker::::new(); /// /// // Create an item using `123u32` as the key. Implicitly, this also sets its value to `123u32` /// let item = tracker.track(123); /// /// // Check that the item is alive /// tracker.state(&123).alive().expect("item should be alive"); /// /// // Dereference the value of the item /// assert_eq!(*item, 123); /// assert!(!item.is_power_of_two()); /// /// // Drop the item and check that it really got dropped /// drop(item); /// tracker.state(&123).dropped().expect("item should be dropped"); /// /// // Create a new item, this time using an explicit `String` value /// let abc_item = tracker.track_with_value(111, String::from("abc")); /// /// // Comparison with other items using `String` work using the underlying `String` /// // operations /// assert_eq!(abc_item, tracker.track_with_value(222, String::from("abc"))); /// assert_ne!(abc_item, tracker.track_with_value(333, String::from("def"))); /// assert!(abc_item < tracker.track_with_value(444, String::from("def"))); /// /// // Display, debug and hashing also work using the underlying `String` operations /// assert_eq!(format!("{}", abc_item), "abc"); /// assert_eq!(format!("{:?}", abc_item), "DropItem { value: \"abc\", state: Alive }"); /// /// use std::collections::hash_map::DefaultHasher; /// use std::hash::Hash; /// use std::hash::Hasher; /// fn hash(x: &T) -> u64 { /// let mut hasher = DefaultHasher::new(); /// x.hash(&mut hasher); /// hasher.finish() /// } /// assert_eq!(hash(&abc_item), hash(&"abc")); /// /// // Methods on `String` can be called transparently on items /// assert_eq!(abc_item.to_ascii_uppercase(), "ABC"); /// ``` /// /// Using hashable items in a set, with an implicit underlying value: /// /// ``` /// use drop_tracker::DropTracker; /// use std::collections::HashSet; /// /// let mut tracker = DropTracker::new(); /// /// let mut set = HashSet::from([ /// tracker.track(1), /// tracker.track(2), /// tracker.track(3), /// ]); /// /// set.remove(&3); /// /// tracker.state(&1).alive().expect("first item should be alive"); /// tracker.state(&2).alive().expect("second item should be alive"); /// tracker.state(&3).dropped().expect("third item should be dropped"); /// ``` /// /// Using hashable items in a set, with an explicit underlying value: /// /// ``` /// use drop_tracker::DropTracker; /// use std::collections::HashSet; /// /// let mut tracker = DropTracker::new(); /// /// let mut set = HashSet::from([ /// tracker.track_with_value(1, String::from("first")), /// tracker.track_with_value(2, String::from("second")), /// tracker.track_with_value(3, String::from("third")), /// ]); /// /// set.remove("third"); /// /// tracker.state(&1).alive().expect("first item should be alive"); /// tracker.state(&2).alive().expect("second item should be alive"); /// tracker.state(&3).dropped().expect("third item should be dropped"); /// ``` #[must_use = "if you don't use this item, it will get automatically dropped"] pub struct DropItem { value: MaybeUninit, state: Option, } impl DropItem { const fn new(value: V, state: StateCell) -> Self { Self { value: MaybeUninit::new(value), state: Some(state), } } } impl Drop for DropItem { fn drop(&mut self) { // The use of an Option might seem redundant, but it's actually needed to safely detect and // report double drops. Without the Option, we would be touching shared memory behind an Rc // that probably does not exist anymore, causing memory corruption. The Option makes this a // bit safer (assuming that the DropItem memory has not been moved or altered), and also // prevents a double drop on the Rc. match self.state.take() { Some(mut state) => { if state.replace(State::Dropped).is_dropped() { panic!("item dropped twice"); } // SAFETY: `state` was `Some(State::Alive)`, which means that `value` has not been // dropped yet and that `value` is initialized. unsafe { self.value.assume_init_drop() }; }, None => { panic!("item dropped twice"); }, } } } /// Error signaling that an item was expected to have been dropped, but it's [alive](State::Alive). /// /// See [`State::dropped`] for more information and examples. #[derive(PartialEq, Eq, Debug)] pub struct AliveError; impl fmt::Display for AliveError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt("item is alive", f) } } impl Error for AliveError { } /// Error signaling that an item was expected to be alive, but it was [dropped](State::Dropped). /// /// See [`State::alive`] for more information and examples. #[derive(PartialEq, Eq, Debug)] pub struct DroppedError; impl fmt::Display for DroppedError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt("item is dropped", f) } } impl Error for DroppedError { } /// Error returned when trying to place multiple items with the same key inside the same [`DropTracker`]. /// /// See [`DropTracker::try_track`] for more information and examples. #[derive(PartialEq, Eq, Debug)] pub struct CollisionError; impl fmt::Display for CollisionError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt("another item with the same key is already tracked", f) } } impl Error for CollisionError { } /// Error returned when failing to query the status of an item with a key that is not known to [`DropTracker`]. /// /// See [`DropTracker::try_state`] for more information and examples. #[derive(PartialEq, Eq, Debug)] pub struct NotTrackedError; impl fmt::Display for NotTrackedError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt("item is not tracked", f) } } impl Error for NotTrackedError { } /// Error returned when failing to assert that a set of items is all [alive](State::Alive). /// /// See [`DropTracker::all_alive`] for more information and examples. #[derive(PartialEq, Eq, Debug)] pub struct NotAllAliveError { /// Sequence of keys that were expected to be alive, but were dropped. pub dropped: Vec, /// Sequence of keys that were expected to be alive, but are not tracked by the /// [`DropTracker`]. pub untracked: Vec, } impl fmt::Display for NotAllAliveError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "not all items are alive: ")?; if !self.dropped.is_empty() { write!(f, "dropped: {:?}", &self.dropped)?; } if !self.untracked.is_empty() { if !self.dropped.is_empty() { write!(f, ", ")?; } write!(f, "not tracked: {:?}", &self.untracked)?; } Ok(()) } } impl Error for NotAllAliveError { } /// Error returned when failing to assert that a set of items is all [dropped](State::Dropped). /// /// See [`DropTracker::all_dropped`] for more information and examples. #[derive(PartialEq, Eq, Debug)] pub struct NotAllDroppedError { /// Sequence of keys that were expected to be dropped, but are alive. pub alive: Vec, /// Sequence of keys that were expected to be dropped, but are not tracked by the /// [`DropTracker`]. pub untracked: Vec, } impl fmt::Display for NotAllDroppedError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "not all items are dropped: ")?; if !self.alive.is_empty() { write!(f, "alive: {:?}", &self.alive)?; } if !self.untracked.is_empty() { if !self.alive.is_empty() { write!(f, ", ")?; } write!(f, "not tracked: {:?}", &self.untracked)?; } Ok(()) } } impl Error for NotAllDroppedError { } /// Error returned when failing to assert that all tracked items are [dropped](State::Dropped). /// /// See [`DropTracker::fully_dropped`] for more information and examples. #[derive(PartialEq, Eq, Debug)] pub struct SomeAliveError<'a, K> { /// Reference to the first key that was found [alive](State::Alive). pub alive: &'a K, } impl<'a, K: fmt::Debug> fmt::Display for SomeAliveError<'a, K> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "item is alive: {:?}", self.alive) } } impl<'a, K: fmt::Debug> Error for SomeAliveError<'a, K> { } /// Error returned when failing to assert that all tracked items are [alive](State::Alive). /// /// See [`DropTracker::fully_alive`] for more information and examples. #[derive(PartialEq, Eq, Debug)] pub struct SomeDroppedError<'a, K> { /// Reference to the first key that was found to have been be [dropped](State::Dropped). pub dropped: &'a K, } impl<'a, K: fmt::Debug> fmt::Display for SomeDroppedError<'a, K> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "item is dropped: {:?}", self.dropped) } } impl<'a, K: fmt::Debug> Error for SomeDroppedError<'a, K> { } drop-tracker-0.1.3/src/tests.rs000064400000000000000000000350041046102023000145230ustar 00000000000000#![cfg(test)] use crate::DropTracker; use crate::State::Alive; use crate::State::Dropped; use std::collections::HashSet; use std::mem::ManuallyDrop; use std::panic::UnwindSafe; use std::panic; #[test] fn state() { let mut tracker = DropTracker::new(); let item = tracker.track(123); assert_eq!(tracker.state(&123), Alive); assert!(tracker.state(&123).is_alive()); assert!(!tracker.state(&123).is_dropped()); drop(item); assert_eq!(tracker.state(&123), Dropped); assert!(!tracker.state(&123).is_alive()); assert!(tracker.state(&123).is_dropped()); } #[test] fn iterators() { let mut tracker = DropTracker::new(); assert_eq!(tracker.tracked().copied().collect::>(), HashSet::from([])); assert_eq!(tracker.alive().copied().collect::>(), HashSet::from([])); assert_eq!(tracker.dropped().copied().collect::>(), HashSet::from([])); let item1 = tracker.track(1); let item2 = tracker.track(2); let item3 = tracker.track(3); let item4 = tracker.track(4); let item5 = tracker.track(5); assert_eq!(tracker.tracked().copied().collect::>(), HashSet::from([1, 2, 3, 4, 5])); assert_eq!(tracker.alive().copied().collect::>(), HashSet::from([1, 2, 3, 4, 5])); assert_eq!(tracker.dropped().copied().collect::>(), HashSet::from([])); drop(item1); drop(item2); assert_eq!(tracker.tracked().copied().collect::>(), HashSet::from([1, 2, 3, 4, 5])); assert_eq!(tracker.alive().copied().collect::>(), HashSet::from([3, 4, 5])); assert_eq!(tracker.dropped().copied().collect::>(), HashSet::from([1, 2])); drop(item3); drop(item4); assert_eq!(tracker.tracked().copied().collect::>(), HashSet::from([1, 2, 3, 4, 5])); assert_eq!(tracker.alive().copied().collect::>(), HashSet::from([5])); assert_eq!(tracker.dropped().copied().collect::>(), HashSet::from([1, 2, 3, 4])); drop(item5); assert_eq!(tracker.tracked().copied().collect::>(), HashSet::from([1, 2, 3, 4, 5])); assert_eq!(tracker.alive().copied().collect::>(), HashSet::from([])); assert_eq!(tracker.dropped().copied().collect::>(), HashSet::from([1, 2, 3, 4, 5])); } #[test] fn forget_dropped() { let mut tracker = DropTracker::new(); tracker.forget_dropped(); assert_eq!(tracker.tracked().copied().collect::>(), HashSet::from([])); assert_eq!(tracker.alive().copied().collect::>(), HashSet::from([])); assert_eq!(tracker.dropped().copied().collect::>(), HashSet::from([])); let item1 = tracker.track(1); let item2 = tracker.track(2); let item3 = tracker.track(3); let item4 = tracker.track(4); let item5 = tracker.track(5); tracker.forget_dropped(); assert_eq!(tracker.tracked().copied().collect::>(), HashSet::from([1, 2, 3, 4, 5])); assert_eq!(tracker.alive().copied().collect::>(), HashSet::from([1, 2, 3, 4, 5])); assert_eq!(tracker.dropped().copied().collect::>(), HashSet::from([])); drop(item1); drop(item2); tracker.forget_dropped(); assert_eq!(tracker.tracked().copied().collect::>(), HashSet::from([3, 4, 5])); assert_eq!(tracker.alive().copied().collect::>(), HashSet::from([3, 4, 5])); assert_eq!(tracker.dropped().copied().collect::>(), HashSet::from([])); drop(item3); drop(item4); tracker.forget_dropped(); assert_eq!(tracker.tracked().copied().collect::>(), HashSet::from([5])); assert_eq!(tracker.alive().copied().collect::>(), HashSet::from([5])); assert_eq!(tracker.dropped().copied().collect::>(), HashSet::from([])); drop(item5); tracker.forget_dropped(); assert_eq!(tracker.tracked().copied().collect::>(), HashSet::from([])); assert_eq!(tracker.alive().copied().collect::>(), HashSet::from([])); assert_eq!(tracker.dropped().copied().collect::>(), HashSet::from([])); } fn catch_panic_error(f: F) -> String { let err = panic::catch_unwind(f) .expect_err("expected a panic"); if let Some(s) = err.downcast_ref::<&str>() { s.to_string() } else if let Some(s) = err.downcast_ref::() { s.clone() } else { panic!("expected the panic payload to be a string"); } } #[test] fn state_errors() { let mut tracker = DropTracker::new(); let item = tracker.track(()); let state = tracker.state(&()); let err = state.dropped() .expect_err("item is expected to be alive"); assert_eq!(err.to_string(), "item is alive"); let panic_err = catch_panic_error(move || state.dropped().expect("foo")); assert_eq!(panic_err, "foo: AliveError"); drop(item); let state = tracker.state(&()); let err = state.alive() .expect_err("item is expected to be dropped"); assert_eq!(err.to_string(), "item is dropped"); let panic_err = catch_panic_error(move || state.alive().expect("foo")); assert_eq!(panic_err, "foo: DroppedError"); } #[test] fn all_alive() { let mut tracker = DropTracker::new(); let item1 = tracker.track(1); let item2 = tracker.track(2); let item3 = tracker.track(3); let item4 = tracker.track(4); drop(item3); drop(item4); let res = tracker.all_alive([&1, &2]); assert_eq!(res, Ok(())); let err = tracker.all_alive([&1, &2, &3, &4, &5, &6]) .expect_err("not all items are expected to be alive"); assert_eq!(err.dropped, [&3, &4]); assert_eq!(err.untracked, [&5, &6]); assert_eq!(err.to_string(), "not all items are alive: dropped: [3, 4], not tracked: [5, 6]"); let res = tracker.all_alive([&1, &2, &3, &4, &5, &6]); let panic_err = catch_panic_error(move || res.expect("foo")); assert_eq!(panic_err, "foo: NotAllAliveError { dropped: [3, 4], untracked: [5, 6] }"); drop(item1); drop(item2); } #[test] fn all_dropped() { let mut tracker = DropTracker::new(); let item1 = tracker.track(1); let item2 = tracker.track(2); let item3 = tracker.track(3); let item4 = tracker.track(4); drop(item3); drop(item4); let res = tracker.all_dropped([&3, &4]); assert_eq!(res, Ok(())); let err = tracker.all_dropped([&1, &2, &3, &4, &5, &6]) .expect_err("not all items are expected to be dropped"); assert_eq!(err.alive, [&1, &2]); assert_eq!(err.untracked, [&5, &6]); assert_eq!(err.to_string(), "not all items are dropped: alive: [1, 2], not tracked: [5, 6]"); let res = tracker.all_dropped([&1, &2, &3, &4, &5, &6]); let panic_err = catch_panic_error(move || res.expect("foo")); assert_eq!(panic_err, "foo: NotAllDroppedError { alive: [1, 2], untracked: [5, 6] }"); drop(item1); drop(item2); } #[test] fn double_drop() { let mut tracker = DropTracker::new(); let mut item = ManuallyDrop::new(tracker.track(())); tracker.state(&()).alive().expect("item should be alive"); unsafe { ManuallyDrop::drop(&mut item); } tracker.state(&()).dropped().expect("item should be dropped"); // Our DropItem implementation is safe and can be dropped twice or more let panic_err = catch_panic_error(panic::AssertUnwindSafe( || unsafe { ManuallyDrop::drop(&mut item) } )); assert_eq!(panic_err, "item dropped twice"); } #[test] fn assertions() { let mut tracker = DropTracker::new(); tracker.assert_all_alive([0u32; 0]); tracker.assert_all_dropped([0u32; 0]); tracker.assert_fully_alive(); tracker.assert_fully_dropped(); let panic_err = catch_panic_error(|| tracker.assert_alive(&1)); assert_eq!(panic_err, "item is not tracked"); let panic_err = catch_panic_error(|| tracker.assert_dropped(&1)); assert_eq!(panic_err, "item is not tracked"); let panic_err = catch_panic_error(|| tracker.assert_all_alive([1])); assert_eq!(panic_err, "not all items are alive: not tracked: [1]"); let panic_err = catch_panic_error(|| tracker.assert_all_dropped([1])); assert_eq!(panic_err, "not all items are dropped: not tracked: [1]"); let item = tracker.track(1); tracker.assert_alive(&1); tracker.assert_all_alive([1]); tracker.assert_fully_alive(); let panic_err = catch_panic_error(|| tracker.assert_dropped(&1)); assert_eq!(panic_err, "item is alive"); let panic_err = catch_panic_error(|| tracker.assert_all_dropped([1])); assert_eq!(panic_err, "not all items are dropped: alive: [1]"); let panic_err = catch_panic_error(|| tracker.assert_fully_dropped()); assert_eq!(panic_err, "item is alive: 1"); drop(item); tracker.assert_dropped(&1); tracker.assert_all_dropped([1]); tracker.assert_fully_dropped(); let panic_err = catch_panic_error(|| tracker.assert_alive(&1)); assert_eq!(panic_err, "item is dropped"); let panic_err = catch_panic_error(|| tracker.assert_all_alive([1])); assert_eq!(panic_err, "not all items are alive: dropped: [1]"); let panic_err = catch_panic_error(|| tracker.assert_fully_alive()); assert_eq!(panic_err, "item is dropped: 1"); } #[test] fn primitive_eq() { assert_eq!(DropTracker::new().track(123i8), 123i8); assert_eq!(DropTracker::new().track(123i16), 123i16); assert_eq!(DropTracker::new().track(123i32), 123i32); assert_eq!(DropTracker::new().track(123i64), 123i64); assert_eq!(DropTracker::new().track(123i128), 123i128); assert_eq!(DropTracker::new().track(123u8), 123u8); assert_eq!(DropTracker::new().track(123u16), 123u16); assert_eq!(DropTracker::new().track(123u32), 123u32); assert_eq!(DropTracker::new().track(123u64), 123u64); assert_eq!(DropTracker::new().track(123u128), 123u128); assert_eq!(DropTracker::new().track_with_value('a', 123f32), 123f32); assert_eq!(DropTracker::new().track_with_value('b', 123f64), 123f64); assert_eq!(DropTracker::new().track('x'), 'x'); assert_eq!(DropTracker::new().track(true), true); assert_eq!(DropTracker::new().track(false), false); assert_eq!(DropTracker::new().track(()), ()); assert_eq!(DropTracker::new().track("abc"), "abc"); assert_eq!(DropTracker::new().track("abc".to_owned()), "abc"); assert_eq!(DropTracker::new().track([1, 2, 3]), [1, 2, 3][..]); assert_eq!(DropTracker::new().track(vec![1, 2, 3]), [1, 2, 3][..]); } #[test] fn primitive_ne() { assert_ne!(DropTracker::new().track(123i8), 100i8); assert_ne!(DropTracker::new().track(123i16), 100i16); assert_ne!(DropTracker::new().track(123i32), 100i32); assert_ne!(DropTracker::new().track(123i64), 100i64); assert_ne!(DropTracker::new().track(123i128), 100i128); assert_ne!(DropTracker::new().track(123u8), 100u8); assert_ne!(DropTracker::new().track(123u16), 100u16); assert_ne!(DropTracker::new().track(123u32), 100u32); assert_ne!(DropTracker::new().track(123u64), 100u64); assert_ne!(DropTracker::new().track(123u128), 100u128); assert_ne!(DropTracker::new().track_with_value('a', 123f32), 100f32); assert_ne!(DropTracker::new().track_with_value('b', 123f64), 100f64); assert_ne!(DropTracker::new().track('x'), 'y'); assert_ne!(DropTracker::new().track(true), false); assert_ne!(DropTracker::new().track(false), true); assert_ne!(DropTracker::new().track("abc"), "def"); assert_ne!(DropTracker::new().track("abc".to_owned()), "def"); assert_ne!(DropTracker::new().track([1, 2, 3]), [4, 5, 6][..]); assert_ne!(DropTracker::new().track(vec![1, 2, 3]), [4, 5, 6][..]); } #[test] fn primitive_lt() { assert!(DropTracker::new().track(123i8) < 127i8); assert!(DropTracker::new().track(123i16) < 200i16); assert!(DropTracker::new().track(123i32) < 200i32); assert!(DropTracker::new().track(123i64) < 200i64); assert!(DropTracker::new().track(123i128) < 200i128); assert!(DropTracker::new().track(123u8) < 200u8); assert!(DropTracker::new().track(123u16) < 200u16); assert!(DropTracker::new().track(123u32) < 200u32); assert!(DropTracker::new().track(123u64) < 200u64); assert!(DropTracker::new().track(123u128) < 200u128); assert!(DropTracker::new().track_with_value('a', 123f32) < 200f32); assert!(DropTracker::new().track_with_value('b', 123f64) < 200f64); assert!(DropTracker::new().track('x') < 'y'); assert!(DropTracker::new().track(false) < true); } #[test] fn primitive_gt() { assert!(DropTracker::new().track(123i8) > 100i8); assert!(DropTracker::new().track(123i16) > 100i16); assert!(DropTracker::new().track(123i32) > 100i32); assert!(DropTracker::new().track(123i64) > 100i64); assert!(DropTracker::new().track(123i128) > 100i128); assert!(DropTracker::new().track(123u8) > 100u8); assert!(DropTracker::new().track(123u16) > 100u16); assert!(DropTracker::new().track(123u32) > 100u32); assert!(DropTracker::new().track(123u64) > 100u64); assert!(DropTracker::new().track(123u128) > 100u128); assert!(DropTracker::new().track_with_value('a', 123f32) > 100f32); assert!(DropTracker::new().track_with_value('b', 123f64) > 100f64); assert!(DropTracker::new().track('x') > 'w'); assert!(DropTracker::new().track(true) > false); }