Skip to content

Commit

Permalink
Tests for HashMap/HashSet::drain_filter
Browse files Browse the repository at this point in the history
  • Loading branch information
mbrubeck committed Sep 9, 2020
1 parent 49aef96 commit fb1fab5
Show file tree
Hide file tree
Showing 2 changed files with 232 additions and 0 deletions.
161 changes: 161 additions & 0 deletions library/std/src/collections/hash/map/tests.rs
Expand Up @@ -924,3 +924,164 @@ fn test_raw_entry() {
}
}
}

mod test_drain_filter {
use super::*;

use crate::panic::{catch_unwind, AssertUnwindSafe};
use crate::sync::atomic::{AtomicUsize, Ordering};

trait EqSorted: Iterator {
fn eq_sorted<I: IntoIterator<Item = Self::Item>>(self, other: I) -> bool;
}

impl<T: Iterator> EqSorted for T
where
T::Item: Eq + Ord,
{
fn eq_sorted<I: IntoIterator<Item = Self::Item>>(self, other: I) -> bool {
let mut v: Vec<_> = self.collect();
v.sort_unstable();
v.into_iter().eq(other)
}
}

#[test]
fn empty() {
let mut map: HashMap<i32, i32> = HashMap::new();
map.drain_filter(|_, _| unreachable!("there's nothing to decide on"));
assert!(map.is_empty());
}

#[test]
fn consuming_nothing() {
let pairs = (0..3).map(|i| (i, i));
let mut map: HashMap<_, _> = pairs.collect();
assert!(map.drain_filter(|_, _| false).eq_sorted(crate::iter::empty()));
assert_eq!(map.len(), 3);
}

#[test]
fn consuming_all() {
let pairs = (0..3).map(|i| (i, i));
let mut map: HashMap<_, _> = pairs.clone().collect();
assert!(map.drain_filter(|_, _| true).eq_sorted(pairs));
assert!(map.is_empty());
}

#[test]
fn mutating_and_keeping() {
let pairs = (0..3).map(|i| (i, i));
let mut map: HashMap<_, _> = pairs.collect();
assert!(
map.drain_filter(|_, v| {
*v += 6;
false
})
.eq_sorted(crate::iter::empty())
);
assert!(map.keys().copied().eq_sorted(0..3));
assert!(map.values().copied().eq_sorted(6..9));
}

#[test]
fn mutating_and_removing() {
let pairs = (0..3).map(|i| (i, i));
let mut map: HashMap<_, _> = pairs.collect();
assert!(
map.drain_filter(|_, v| {
*v += 6;
true
})
.eq_sorted((0..3).map(|i| (i, i + 6)))
);
assert!(map.is_empty());
}

#[test]
fn drop_panic_leak() {
static PREDS: AtomicUsize = AtomicUsize::new(0);
static DROPS: AtomicUsize = AtomicUsize::new(0);

struct D;
impl Drop for D {
fn drop(&mut self) {
if DROPS.fetch_add(1, Ordering::SeqCst) == 1 {
panic!("panic in `drop`");
}
}
}

let mut map = (0..3).map(|i| (i, D)).collect::<HashMap<_, _>>();

catch_unwind(move || {
drop(map.drain_filter(|_, _| {
PREDS.fetch_add(1, Ordering::SeqCst);
true
}))
})
.unwrap_err();

assert_eq!(PREDS.load(Ordering::SeqCst), 3);
assert_eq!(DROPS.load(Ordering::SeqCst), 3);
}

#[test]
fn pred_panic_leak() {
static PREDS: AtomicUsize = AtomicUsize::new(0);
static DROPS: AtomicUsize = AtomicUsize::new(0);

struct D;
impl Drop for D {
fn drop(&mut self) {
DROPS.fetch_add(1, Ordering::SeqCst);
}
}

let mut map = (0..3).map(|i| (i, D)).collect::<HashMap<_, _>>();

catch_unwind(AssertUnwindSafe(|| {
drop(map.drain_filter(|_, _| match PREDS.fetch_add(1, Ordering::SeqCst) {
0 => true,
_ => panic!(),
}))
}))
.unwrap_err();

assert_eq!(PREDS.load(Ordering::SeqCst), 2);
assert_eq!(DROPS.load(Ordering::SeqCst), 1);
assert_eq!(map.len(), 2);
}

// Same as above, but attempt to use the iterator again after the panic in the predicate
#[test]
fn pred_panic_reuse() {
static PREDS: AtomicUsize = AtomicUsize::new(0);
static DROPS: AtomicUsize = AtomicUsize::new(0);

struct D;
impl Drop for D {
fn drop(&mut self) {
DROPS.fetch_add(1, Ordering::SeqCst);
}
}

let mut map = (0..3).map(|i| (i, D)).collect::<HashMap<_, _>>();

{
let mut it = map.drain_filter(|_, _| match PREDS.fetch_add(1, Ordering::SeqCst) {
0 => true,
_ => panic!(),
});
catch_unwind(AssertUnwindSafe(|| while it.next().is_some() {})).unwrap_err();
// Iterator behaviour after a panic is explicitly unspecified,
// so this is just the current implementation:
let result = catch_unwind(AssertUnwindSafe(|| it.next()));
assert!(result.is_err());
}

assert_eq!(PREDS.load(Ordering::SeqCst), 3);
assert_eq!(DROPS.load(Ordering::SeqCst), 1);
assert_eq!(map.len(), 2);
}
}
71 changes: 71 additions & 0 deletions library/std/src/collections/hash/set/tests.rs
@@ -1,6 +1,9 @@
use super::super::map::RandomState;
use super::HashSet;

use crate::panic::{catch_unwind, AssertUnwindSafe};
use crate::sync::atomic::{AtomicU32, Ordering};

#[test]
fn test_zero_capacities() {
type HS = HashSet<i32>;
Expand Down Expand Up @@ -413,3 +416,71 @@ fn test_retain() {
assert!(set.contains(&4));
assert!(set.contains(&6));
}

#[test]
fn test_drain_filter() {
let mut x: HashSet<_> = [1].iter().copied().collect();
let mut y: HashSet<_> = [1].iter().copied().collect();

x.drain_filter(|_| true);
y.drain_filter(|_| false);
assert_eq!(x.len(), 0);
assert_eq!(y.len(), 1);
}

#[test]
fn test_drain_filter_drop_panic_leak() {
static PREDS: AtomicU32 = AtomicU32::new(0);
static DROPS: AtomicU32 = AtomicU32::new(0);

#[derive(PartialEq, Eq, PartialOrd, Hash)]
struct D(i32);
impl Drop for D {
fn drop(&mut self) {
if DROPS.fetch_add(1, Ordering::SeqCst) == 1 {
panic!("panic in `drop`");
}
}
}

let mut set = (0..3).map(|i| D(i)).collect::<HashSet<_>>();

catch_unwind(move || {
drop(set.drain_filter(|_| {
PREDS.fetch_add(1, Ordering::SeqCst);
true
}))
})
.ok();

assert_eq!(PREDS.load(Ordering::SeqCst), 3);
assert_eq!(DROPS.load(Ordering::SeqCst), 3);
}

#[test]
fn test_drain_filter_pred_panic_leak() {
static PREDS: AtomicU32 = AtomicU32::new(0);
static DROPS: AtomicU32 = AtomicU32::new(0);

#[derive(PartialEq, Eq, PartialOrd, Hash)]
struct D;
impl Drop for D {
fn drop(&mut self) {
DROPS.fetch_add(1, Ordering::SeqCst);
}
}

let mut set: HashSet<_> = (0..3).map(|_| D).collect();

catch_unwind(AssertUnwindSafe(|| {
drop(set.drain_filter(|_| match PREDS.fetch_add(1, Ordering::SeqCst) {
0 => true,
_ => panic!(),
}))
}))
.ok();

assert_eq!(PREDS.load(Ordering::SeqCst), 1);
assert_eq!(DROPS.load(Ordering::SeqCst), 3);
assert_eq!(set.len(), 0);
}

0 comments on commit fb1fab5

Please sign in to comment.