🧪 Chapter 9 — Testing & Benchmarking Your Vec
“Trust, but verify — with property tests, sanitizers, and time.”
9.1 unit tests: the obvious edge cases
#[test]
fn push_pop_roundtrip() {
let mut v = MyVec::new();
for i in 0..10_000 { v.push(i); }
for i in (0..10_000).rev() { assert_eq!(v.pop(), Some(i)); }
assert_eq!(v.pop(), None);
}
#[test]
fn insert_remove_middle() {
let mut v = MyVec::new();
for i in 0..10 { v.push(i); }
v.insert(5, 99);
assert_eq!(v.get(5), Some(&99));
assert_eq!(v.remove(5), 99);
assert_eq!(v.as_slice(), &[0,1,2,3,4,5,6,7,8,9]);
}
#[test]
fn drop_complex() {
use std::cell::Cell;
struct D<'a>(&'a Cell<i32>);
impl<'a> Drop for D<'a> { fn drop(&mut self){ self.0.set(self.0.get()+1); } }
let c = Cell::new(0);
{
let mut v = MyVec::new();
for _ in 0..123 { v.push(D(&c)); }
// clear triggers 123 drops
v.clear();
// re-use after clear
for _ in 0..7 { v.push(D(&c)); }
} // drop remaining 7
assert_eq!(c.get(), 130);
}
9.2 property tests (proptest/quickcheck)
randomized invariants catch corner cases:
len <= capalways- after
extend + clear,len == 0and furtherpushworks collect::<MyVec<_>>()matchesstd::Vec<_>()for same sequence
example (using proptest):
use proptest::prelude::*;
proptest! {
#[test]
fn prop_push_matches_std(xs: Vec<i32>) {
let mut a = MyVec::new();
let mut b = Vec::new();
for x in xs {
a.push(x);
b.push(x);
}
prop_assert_eq!(a.as_slice(), &b[..]);
}
}
9.3 UB/bounds checking with Miri
Miri interprets Rust and screams on UB (use-after-free, out-of-bounds, invalid drops).
cargo +nightly miri test
fix anything it complains about — miri is your best friend for unsafe code.
9.4 fuzzing (cargo-fuzz)
wrap public ops (push, insert, remove, clear, into_iter) with a libFuzzer harness and throw random sequences at them. compare behavior against std::Vec as an oracle.
9.5 performance benchmarking (criterion)
microbench push, extend, insert/remove, iteration.
use criterion::{criterion_group, criterion_main, Criterion, black_box};
fn bench_push(c: &mut Criterion) {
c.bench_function("myvec push 1e6", |b| {
b.iter(|| {
let mut v = MyVec::new();
for i in 0..1_000_000 { v.push(black_box(i)); }
black_box(v.len())
})
});
}
criterion_group!(benches, bench_push);
criterion_main!(benches);
💡 check growth policy: doubling vs 1.5× can change cache behavior & realloc cost.
9.6 sanitizer & valgrind notes
- address/thread sanitizers are less targeted than miri for UB, but can catch wild writes in FFI.
- valgrind can spot leaks if you accidentally skip a drop path.
9.7 summary
- unit + property tests ensure correctness on paths you didn’t think of.
- miri hunts UB; fuzzing hunts pathological sequences.
- criterion keeps you honest about perf regressions.