Skip to content

Commit 9769e75

Browse files
bors[bot]robinmoussuphimuemue
authored
Merge #449 #461
449: Improve `tuple_windows()` doc (add a reference to `pairwise`) r=jswrenn a=robinmoussu As shown in #388, tuple_windows can be hard to find if you come from other environment (python, RxJs, …) since people may expect to find a `pairwise()` method instead. 461: Clean up specializations tests r=jswrenn a=phimuemue In response to this comment #413 (comment): > If the contribution is an optimized specialization of an `Iterator` method (such as `nth` or `fold`), the specialization needs to be very thoroughly tested (preferably with quickcheck tests) to ensure it behaves identically (_especially_ on edge cases, and with when it panics). I thought about having a simple way to test specializations: If we had *one* test function testing all specializations, we would have an easy way to quickcheck specializations for our iterators. We already had quite a bit in place, so I took it and streamlined it a bit. * `fold` is tested by remembering all elements provided to the closure (instead of only XORing elements). * cleaned up some things (unused specialization of `size_hint`, some redundant lifetimes, superfluous arguments) * test all specializations in one function (instead of only some specializations in two functions) * remove manual special case tests in favor of quickcheck'd ones If we decide to do it like this, we can, in a future commit, just do the following for all iterators (possibly via a macro) to test specializations uniformly (and hopefully reliably): ``` quickcheck! { fn test_specializations_qc(params_for_iterator) -> () { test_specializations(&make_iterator_to_be_tested(params_for_iterator)); } } ``` This way, we would also test methods that are not even specialized, but for simplicity, I'd be willing to accept this. If we see that it slows things down considerably, we could still devise a simple solution that would allow to specify the methods to be tested. Co-authored-by: Robin Moussu <[email protected]> Co-authored-by: philipp <[email protected]>
3 parents 96539ff + c5c755c + 189db4a commit 9769e75

File tree

2 files changed

+33
-92
lines changed

2 files changed

+33
-92
lines changed

src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -584,6 +584,8 @@ pub trait Itertools : Iterator {
584584
/// ```
585585
/// use itertools::Itertools;
586586
/// let mut v = Vec::new();
587+
///
588+
/// // pairwise iteration
587589
/// for (a, b) in (1..5).tuple_windows() {
588590
/// v.push((a, b));
589591
/// }

tests/specializations.rs

Lines changed: 31 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
use itertools::{EitherOrBoth, Itertools};
1+
use itertools::Itertools;
22
use std::fmt::Debug;
3-
use std::ops::BitXor;
43
use quickcheck::quickcheck;
54

65
struct Unspecialized<I>(I);
@@ -14,17 +13,11 @@ where
1413
fn next(&mut self) -> Option<Self::Item> {
1514
self.0.next()
1615
}
17-
18-
#[inline(always)]
19-
fn size_hint(&self) -> (usize, Option<usize>) {
20-
self.0.size_hint()
21-
}
2216
}
2317

2418
fn check_specialized<'a, V, IterItem, Iter, F>(iterator: &Iter, mapper: F)
2519
where
2620
V: Eq + Debug,
27-
IterItem: 'a,
2821
Iter: Iterator<Item = IterItem> + Clone + 'a,
2922
F: Fn(Box<dyn Iterator<Item = IterItem> + 'a>) -> V,
3023
{
@@ -34,27 +27,43 @@ where
3427
)
3528
}
3629

37-
fn check_specialized_count_last_nth_sizeh<'a, IterItem, Iter>(
30+
fn test_specializations<IterItem, Iter>(
3831
it: &Iter,
39-
known_expected_size: Option<usize>,
4032
) where
41-
IterItem: 'a + Eq + Debug,
42-
Iter: Iterator<Item = IterItem> + Clone + 'a,
33+
IterItem: Eq + Debug + Clone,
34+
Iter: Iterator<Item = IterItem> + Clone,
4335
{
44-
let size = it.clone().count();
45-
if let Some(expected_size) = known_expected_size {
46-
assert_eq!(size, expected_size);
47-
}
4836
check_specialized(it, |i| i.count());
4937
check_specialized(it, |i| i.last());
38+
check_specialized(it, |i| i.collect::<Vec<_>>());
39+
check_specialized(it, |i| {
40+
let mut parameters_from_fold = vec![];
41+
let fold_result = i.fold(vec![], |mut acc, v: IterItem| {
42+
parameters_from_fold.push((acc.clone(), v.clone()));
43+
acc.push(v);
44+
acc
45+
});
46+
(parameters_from_fold, fold_result)
47+
});
48+
check_specialized(it, |mut i| {
49+
let mut parameters_from_all = vec![];
50+
let first = i.next();
51+
let all_result = i.all(|x| {
52+
parameters_from_all.push(x.clone());
53+
Some(x)==first
54+
});
55+
(parameters_from_all, all_result)
56+
});
57+
let size = it.clone().count();
5058
for n in 0..size + 2 {
5159
check_specialized(it, |mut i| i.nth(n));
5260
}
61+
// size_hint is a bit harder to check
5362
let mut it_sh = it.clone();
5463
for n in 0..size + 2 {
5564
let len = it_sh.clone().count();
5665
let (min, max) = it_sh.size_hint();
57-
assert_eq!((size - n.min(size)), len);
66+
assert_eq!(size - n.min(size), len);
5867
assert!(min <= len);
5968
if let Some(max) = max {
6069
assert!(len <= max);
@@ -63,87 +72,17 @@ fn check_specialized_count_last_nth_sizeh<'a, IterItem, Iter>(
6372
}
6473
}
6574

66-
fn check_specialized_fold_xor<'a, IterItem, Iter>(it: &Iter)
67-
where
68-
IterItem: 'a
69-
+ BitXor
70-
+ Eq
71-
+ Debug
72-
+ BitXor<<IterItem as BitXor>::Output, Output = <IterItem as BitXor>::Output>
73-
+ Clone,
74-
<IterItem as BitXor>::Output:
75-
BitXor<Output = <IterItem as BitXor>::Output> + Eq + Debug + Clone,
76-
Iter: Iterator<Item = IterItem> + Clone + 'a,
77-
{
78-
check_specialized(it, |mut i| {
79-
let first = i.next().map(|f| f.clone() ^ (f.clone() ^ f));
80-
i.fold(first, |acc, v: IterItem| acc.map(move |a| v ^ a))
81-
});
82-
}
83-
84-
fn put_back_test(test_vec: Vec<i32>, known_expected_size: Option<usize>) {
85-
{
86-
// Lexical lifetimes support
87-
let pb = itertools::put_back(test_vec.iter());
88-
check_specialized_count_last_nth_sizeh(&pb, known_expected_size);
89-
check_specialized_fold_xor(&pb);
90-
}
91-
92-
let mut pb = itertools::put_back(test_vec.into_iter());
93-
pb.put_back(1);
94-
check_specialized_count_last_nth_sizeh(&pb, known_expected_size.map(|x| x + 1));
95-
check_specialized_fold_xor(&pb)
96-
}
97-
98-
#[test]
99-
fn put_back() {
100-
put_back_test(vec![7, 4, 1], Some(3));
101-
}
102-
10375
quickcheck! {
10476
fn put_back_qc(test_vec: Vec<i32>) -> () {
105-
put_back_test(test_vec, None)
77+
test_specializations(&itertools::put_back(test_vec.iter()));
78+
let mut pb = itertools::put_back(test_vec.into_iter());
79+
pb.put_back(1);
80+
test_specializations(&pb);
10681
}
10782
}
10883

109-
fn merge_join_by_test(i1: Vec<usize>, i2: Vec<usize>, known_expected_size: Option<usize>) {
110-
let i1 = i1.into_iter();
111-
let i2 = i2.into_iter();
112-
let mjb = i1.clone().merge_join_by(i2.clone(), std::cmp::Ord::cmp);
113-
check_specialized_count_last_nth_sizeh(&mjb, known_expected_size);
114-
// Rust 1.24 compatibility:
115-
fn eob_left_z(eob: EitherOrBoth<usize, usize>) -> usize {
116-
eob.left().unwrap_or(0)
117-
}
118-
fn eob_right_z(eob: EitherOrBoth<usize, usize>) -> usize {
119-
eob.right().unwrap_or(0)
120-
}
121-
fn eob_both_z(eob: EitherOrBoth<usize, usize>) -> usize {
122-
let (a, b) = eob.both().unwrap_or((0, 0));
123-
assert_eq!(a, b);
124-
a
125-
}
126-
check_specialized_fold_xor(&mjb.clone().map(eob_left_z));
127-
check_specialized_fold_xor(&mjb.clone().map(eob_right_z));
128-
check_specialized_fold_xor(&mjb.clone().map(eob_both_z));
129-
130-
// And the other way around
131-
let mjb = i2.merge_join_by(i1, std::cmp::Ord::cmp);
132-
check_specialized_count_last_nth_sizeh(&mjb, known_expected_size);
133-
check_specialized_fold_xor(&mjb.clone().map(eob_left_z));
134-
check_specialized_fold_xor(&mjb.clone().map(eob_right_z));
135-
check_specialized_fold_xor(&mjb.clone().map(eob_both_z));
136-
}
137-
138-
#[test]
139-
fn merge_join_by() {
140-
let i1 = vec![1, 3, 5, 7, 8, 9];
141-
let i2 = vec![0, 3, 4, 5];
142-
merge_join_by_test(i1, i2, Some(8));
143-
}
144-
14584
quickcheck! {
14685
fn merge_join_by_qc(i1: Vec<usize>, i2: Vec<usize>) -> () {
147-
merge_join_by_test(i1, i2, None)
86+
test_specializations(&i1.into_iter().merge_join_by(i2.into_iter(), std::cmp::Ord::cmp));
14887
}
14988
}

0 commit comments

Comments
 (0)