Crate drop_tracker
source ·Expand description
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.
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
:
use std::mem::MaybeUninit;
struct MyOption<T> {
set: bool,
data: MaybeUninit<T>,
}
impl<T> MyOption<T> {
fn none() -> Self {
Self { set: false, data: MaybeUninit::uninit() }
}
fn some(x: T) -> Self {
Self { set: true, data: MaybeUninit::new(x) }
}
}
// BUG: MyOption<T> 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(...)
equivalent tostate(...).alive().expect("error message")
assert_dropped(...)
equivalent tostate(...).dropped().expect("error message")
assert_all_alive(...)
equivalent toall_alive(...).expect("error message")
assert_all_dropped(...)
equivalent toall_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
, MaybeUninit
or
std::ptr::drop_in_place
, like in the following example:
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 and hashable if the
underlying key is. This makes DropItem
instances usable directly in collections like
HashMap
, BTreeMap
,
HashSet
and many more.
Here is an example involving 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());
Structs
- Error signaling that an item was expected to have been dropped, but it’s alive.
- Error returned when trying to place multiple items with the same key inside the same
DropTracker
. - An item that will notify the parent
DropTracker
once it gets dropped. - Creates
DropItem
s and tracks their state. - Error signaling that an item was expected to be alive, but it was dropped.
- Error returned when failing to assert that a set of items is all alive.
- Error returned when failing to assert that a set of items is all dropped.
- Error returned when failing to query the status of an item with a key that is not known to
DropTracker
. - Error returned when failing to assert that all tracked items are dropped.
- Error returned when failing to assert that all tracked items are alive.
Enums
- A type that represents the state of a
DropItem
: either alive or dropped.