This is an automated email from the ASF dual-hosted git repository.

alamb pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow-rs.git


The following commit(s) were added to refs/heads/main by this push:
     new 87154eb17b fix: `ArrayIter` does not report size hint correctly after 
advancing from the iterator back (#8728)
87154eb17b is described below

commit 87154eb17b520d705be385b1b21fa1b514aaeeb6
Author: Raz Luvaton <[email protected]>
AuthorDate: Mon Nov 3 14:13:58 2025 +0200

    fix: `ArrayIter` does not report size hint correctly after advancing from 
the iterator back (#8728)
    
    # Which issue does this PR close?
    
    N/A
    
    # Rationale for this change
    
    for the fix: the array iterator is marked as exact size iterator and
    double ended iterator so it should report the current length when
    accessed through the other side
    
    # What changes are included in this PR?
    
    fix by using `current_end` instead of `array.len()`
    and also adds a LOT of tests extracted from (which is how I found that
    bug):
    - #8697
    
    # Are these changes tested?
    
    Yes
    
    # Are there any user-facing changes?
    
    Kinda
---
 arrow-array/src/iterator.rs | 884 +++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 879 insertions(+), 5 deletions(-)

diff --git a/arrow-array/src/iterator.rs b/arrow-array/src/iterator.rs
index 6708da3d5d..e72b259ef0 100644
--- a/arrow-array/src/iterator.rs
+++ b/arrow-array/src/iterator.rs
@@ -44,7 +44,7 @@ use arrow_buffer::NullBuffer;
 /// [`PrimitiveArray`]: crate::PrimitiveArray
 /// [`compute::unary`]: 
https://docs.rs/arrow/latest/arrow/compute/fn.unary.html
 /// [`compute::try_unary`]: 
https://docs.rs/arrow/latest/arrow/compute/fn.try_unary.html
-#[derive(Debug)]
+#[derive(Debug, Clone)]
 pub struct ArrayIter<T: ArrayAccessor> {
     array: T,
     logical_nulls: Option<NullBuffer>,
@@ -98,8 +98,8 @@ impl<T: ArrayAccessor> Iterator for ArrayIter<T> {
 
     fn size_hint(&self) -> (usize, Option<usize>) {
         (
-            self.array.len() - self.current,
-            Some(self.array.len() - self.current),
+            self.current_end - self.current,
+            Some(self.current_end - self.current),
         )
     }
 }
@@ -147,9 +147,12 @@ pub type MapArrayIter<'a> = ArrayIter<&'a MapArray>;
 pub type GenericListViewArrayIter<'a, O> = ArrayIter<&'a 
GenericListViewArray<O>>;
 #[cfg(test)]
 mod tests {
-    use std::sync::Arc;
-
     use crate::array::{ArrayRef, BinaryArray, BooleanArray, Int32Array, 
StringArray};
+    use crate::iterator::ArrayIter;
+    use rand::rngs::StdRng;
+    use rand::{Rng, SeedableRng};
+    use std::fmt::Debug;
+    use std::sync::Arc;
 
     #[test]
     fn test_primitive_array_iter_round_trip() {
@@ -264,4 +267,875 @@ mod tests {
         // check if ExactSizeIterator is implemented
         let _ = array.iter().rposition(|opt_b| opt_b == Some(true));
     }
+
+    trait SharedBetweenArrayIterAndSliceIter:
+        ExactSizeIterator<Item = Option<i32>> + DoubleEndedIterator<Item = 
Option<i32>> + Clone
+    {
+    }
+    impl<T: Clone + ExactSizeIterator<Item = Option<i32>> + 
DoubleEndedIterator<Item = Option<i32>>>
+        SharedBetweenArrayIterAndSliceIter for T
+    {
+    }
+
+    fn get_int32_iterator_cases() -> impl Iterator<Item = (Int32Array, 
Vec<Option<i32>>)> {
+        let mut rng = StdRng::seed_from_u64(42);
+
+        let no_nulls_and_no_duplicates = 
(0..10).map(Some).collect::<Vec<Option<i32>>>();
+        let no_nulls_random_values = (0..10)
+            .map(|_| rng.random::<i32>())
+            .map(Some)
+            .collect::<Vec<Option<i32>>>();
+
+        let all_nulls = (0..10).map(|_| None).collect::<Vec<Option<i32>>>();
+        let only_start_nulls = (0..10)
+            .map(|item| if item < 4 { None } else { Some(item) })
+            .collect::<Vec<Option<i32>>>();
+        let only_end_nulls = (0..10)
+            .map(|item| if item > 8 { None } else { Some(item) })
+            .collect::<Vec<Option<i32>>>();
+        let only_middle_nulls = (0..10)
+            .map(|item| {
+                if (4..=8).contains(&item) && rng.random_bool(0.9) {
+                    None
+                } else {
+                    Some(item)
+                }
+            })
+            .collect::<Vec<Option<i32>>>();
+        let random_values_with_random_nulls = (0..10)
+            .map(|_| {
+                if rng.random_bool(0.3) {
+                    None
+                } else {
+                    Some(rng.random::<i32>())
+                }
+            })
+            .collect::<Vec<Option<i32>>>();
+
+        let no_nulls_and_some_duplicates = (0..10)
+            .map(|item| item % 3)
+            .map(Some)
+            .collect::<Vec<Option<i32>>>();
+        let no_nulls_and_all_same_value =
+            (0..10).map(|_| 9).map(Some).collect::<Vec<Option<i32>>>();
+        let no_nulls_and_continues_duplicates = [0, 0, 0, 1, 1, 2, 2, 2, 2, 3]
+            .map(Some)
+            .into_iter()
+            .collect::<Vec<Option<i32>>>();
+
+        let single_null_and_no_duplicates = (0..10)
+            .map(|item| if item == 4 { None } else { Some(item) })
+            .collect::<Vec<Option<i32>>>();
+        let multiple_nulls_and_no_duplicates = (0..10)
+            .map(|item| if item % 3 == 2 { None } else { Some(item) })
+            .collect::<Vec<Option<i32>>>();
+        let continues_nulls_and_no_duplicates = [
+            Some(0),
+            Some(1),
+            None,
+            None,
+            Some(2),
+            Some(3),
+            None,
+            Some(4),
+            Some(5),
+            None,
+        ]
+        .into_iter()
+        .collect::<Vec<Option<i32>>>();
+
+        [
+            no_nulls_and_no_duplicates,
+            no_nulls_random_values,
+            no_nulls_and_some_duplicates,
+            no_nulls_and_all_same_value,
+            no_nulls_and_continues_duplicates,
+            all_nulls,
+            only_start_nulls,
+            only_end_nulls,
+            only_middle_nulls,
+            random_values_with_random_nulls,
+            single_null_and_no_duplicates,
+            multiple_nulls_and_no_duplicates,
+            continues_nulls_and_no_duplicates,
+        ]
+        .map(|case| (Int32Array::from(case.clone()), case))
+        .into_iter()
+    }
+
+    trait SetupIter {
+        fn description(&self) -> String;
+        fn setup<I: SharedBetweenArrayIterAndSliceIter>(&self, iter: &mut I);
+    }
+
+    struct NoSetup;
+    impl SetupIter for NoSetup {
+        fn description(&self) -> String {
+            "no setup".to_string()
+        }
+        fn setup<I: SharedBetweenArrayIterAndSliceIter>(&self, _iter: &mut I) {
+            // none
+        }
+    }
+
+    fn setup_and_assert_cases_on_single_operation(
+        o: &impl ConsumingArrayIteratorOp,
+        setup_iterator: impl SetupIter,
+    ) {
+        for (array, source) in get_int32_iterator_cases() {
+            let mut actual = ArrayIter::new(&array);
+            let mut expected = source.iter().copied();
+
+            setup_iterator.setup(&mut actual);
+            setup_iterator.setup(&mut expected);
+
+            let current_iterator_values: Vec<Option<i32>> = 
expected.clone().collect();
+
+            assert_eq!(
+                o.get_value(actual),
+                o.get_value(expected),
+                "Failed on op {} for {} (left actual, right expected) 
({current_iterator_values:?})",
+                o.name(),
+                setup_iterator.description(),
+            );
+        }
+    }
+
+    /// Trait representing an operation on a [`ArrayIter`]
+    /// that can be compared against a slice iterator
+    ///
+    /// this is for consuming operations (e.g. `count`, `last`, etc)
+    trait ConsumingArrayIteratorOp {
+        /// What the operation returns (e.g. Option<i32> for last, usize for 
count, etc)
+        type Output: PartialEq + Debug;
+
+        /// The name of the operation, used for error messages
+        fn name(&self) -> String;
+
+        /// Get the value of the operation for the provided iterator
+        /// This will be either a [`ArrayIter`] or a slice iterator to make 
sure they produce the same result
+        ///
+        /// Example implementation:
+        /// 1. for `last` it will be the last value
+        /// 2. for `count` it will be the returned length
+        fn get_value<T: SharedBetweenArrayIterAndSliceIter>(&self, iter: T) -> 
Self::Output;
+    }
+
+    /// Trait representing an operation on a [`ArrayIter`]
+    /// that can be compared against a slice iterator.
+    ///
+    /// This is for mutating operations (e.g. `position`, `any`, `find`, etc)
+    trait MutatingArrayIteratorOp {
+        /// What the operation returns (e.g. Option<i32> for last, usize for 
count, etc)
+        type Output: PartialEq + Debug;
+
+        /// The name of the operation, used for error messages
+        fn name(&self) -> String;
+
+        /// Get the value of the operation for the provided iterator
+        /// This will be either a [`ArrayIter`] or a slice iterator to make 
sure they produce the same result
+        ///
+        /// Example implementation:
+        /// 1. for `for_each` it will be the iterator element that the 
function was called with
+        /// 2. for `fold` it will be the accumulator and the iterator element 
from each call, as well as the final result
+        fn get_value<T: SharedBetweenArrayIterAndSliceIter>(&self, iter: &mut 
T) -> Self::Output;
+    }
+
+    /// Helper function that will assert that the provided operation
+    /// produces the same result for both [`ArrayIter`] and slice iterator
+    /// under various consumption patterns (e.g. some calls to 
next/next_back/consume_all/etc)
+    fn assert_array_iterator_cases<O: ConsumingArrayIteratorOp>(o: O) {
+        setup_and_assert_cases_on_single_operation(&o, NoSetup);
+
+        struct Next;
+        impl SetupIter for Next {
+            fn description(&self) -> String {
+                "new iter after consuming 1 element from the start".to_string()
+            }
+            fn setup<I: SharedBetweenArrayIterAndSliceIter>(&self, iter: &mut 
I) {
+                iter.next();
+            }
+        }
+        setup_and_assert_cases_on_single_operation(&o, Next);
+
+        struct NextBack;
+        impl SetupIter for NextBack {
+            fn description(&self) -> String {
+                "new iter after consuming 1 element from the end".to_string()
+            }
+
+            fn setup<I: SharedBetweenArrayIterAndSliceIter>(&self, iter: &mut 
I) {
+                iter.next_back();
+            }
+        }
+
+        setup_and_assert_cases_on_single_operation(&o, NextBack);
+
+        struct NextAndBack;
+        impl SetupIter for NextAndBack {
+            fn description(&self) -> String {
+                "new iter after consuming 1 element from start and 
end".to_string()
+            }
+
+            fn setup<I: SharedBetweenArrayIterAndSliceIter>(&self, iter: &mut 
I) {
+                iter.next();
+                iter.next_back();
+            }
+        }
+
+        setup_and_assert_cases_on_single_operation(&o, NextAndBack);
+
+        struct NextUntilLast;
+        impl SetupIter for NextUntilLast {
+            fn description(&self) -> String {
+                "new iter after consuming all from the start but 1".to_string()
+            }
+            fn setup<I: SharedBetweenArrayIterAndSliceIter>(&self, iter: &mut 
I) {
+                let len = iter.len();
+                if len > 1 {
+                    iter.nth(len - 2);
+                }
+            }
+        }
+        setup_and_assert_cases_on_single_operation(&o, NextUntilLast);
+
+        struct NextBackUntilFirst;
+        impl SetupIter for NextBackUntilFirst {
+            fn description(&self) -> String {
+                "new iter after consuming all from the end but 1".to_string()
+            }
+
+            fn setup<I: SharedBetweenArrayIterAndSliceIter>(&self, iter: &mut 
I) {
+                let len = iter.len();
+                if len > 1 {
+                    iter.nth_back(len - 2);
+                }
+            }
+        }
+        setup_and_assert_cases_on_single_operation(&o, NextBackUntilFirst);
+
+        struct NextFinish;
+        impl SetupIter for NextFinish {
+            fn description(&self) -> String {
+                "new iter after consuming all from the start".to_string()
+            }
+            fn setup<I: SharedBetweenArrayIterAndSliceIter>(&self, iter: &mut 
I) {
+                iter.nth(iter.len());
+            }
+        }
+        setup_and_assert_cases_on_single_operation(&o, NextFinish);
+
+        struct NextBackFinish;
+        impl SetupIter for NextBackFinish {
+            fn description(&self) -> String {
+                "new iter after consuming all from the end".to_string()
+            }
+            fn setup<I: SharedBetweenArrayIterAndSliceIter>(&self, iter: &mut 
I) {
+                iter.nth_back(iter.len());
+            }
+        }
+        setup_and_assert_cases_on_single_operation(&o, NextBackFinish);
+
+        struct NextUntilLastNone;
+        impl SetupIter for NextUntilLastNone {
+            fn description(&self) -> String {
+                "new iter that have no nulls left".to_string()
+            }
+            fn setup<I: SharedBetweenArrayIterAndSliceIter>(&self, iter: &mut 
I) {
+                let last_null_position = iter.clone().rposition(|item| 
item.is_none());
+
+                // move the iterator to the location where there are no nulls 
anymore
+                if let Some(last_null_position) = last_null_position {
+                    iter.nth(last_null_position);
+                }
+            }
+        }
+        setup_and_assert_cases_on_single_operation(&o, NextUntilLastNone);
+
+        struct NextUntilLastSome;
+        impl SetupIter for NextUntilLastSome {
+            fn description(&self) -> String {
+                "iter that only have nulls left".to_string()
+            }
+            fn setup<I: SharedBetweenArrayIterAndSliceIter>(&self, iter: &mut 
I) {
+                let last_some_position = iter.clone().rposition(|item| 
item.is_some());
+
+                // move the iterator to the location where there are only nulls
+                if let Some(last_some_position) = last_some_position {
+                    iter.nth(last_some_position);
+                }
+            }
+        }
+        setup_and_assert_cases_on_single_operation(&o, NextUntilLastSome);
+    }
+
+    /// Helper function that will assert that the provided operation
+    /// produces the same result for both [`ArrayIter`] and slice iterator
+    /// under various consumption patterns (e.g. some calls to 
next/next_back/consume_all/etc)
+    ///
+    /// this is different from [`assert_array_iterator_cases`] as this also 
check that the state after the call is correct
+    /// to make sure we don't leave the iterator in incorrect state
+    fn assert_array_iterator_cases_mutate<O: MutatingArrayIteratorOp>(o: O) {
+        struct Adapter<O: MutatingArrayIteratorOp> {
+            o: O,
+        }
+
+        #[derive(Debug, PartialEq)]
+        struct AdapterOutput<Value> {
+            value: Value,
+            /// collect on the iterator after running the operation
+            leftover: Vec<Option<i32>>,
+        }
+
+        impl<O: MutatingArrayIteratorOp> ConsumingArrayIteratorOp for 
Adapter<O> {
+            type Output = AdapterOutput<O::Output>;
+
+            fn name(&self) -> String {
+                self.o.name()
+            }
+
+            fn get_value<T: SharedBetweenArrayIterAndSliceIter>(
+                &self,
+                mut iter: T,
+            ) -> Self::Output {
+                let value = self.o.get_value(&mut iter);
+
+                // Get the rest of the iterator to make sure we leave the 
iterator in a valid state
+                let leftover: Vec<_> = iter.collect();
+
+                AdapterOutput { value, leftover }
+            }
+        }
+
+        assert_array_iterator_cases(Adapter { o })
+    }
+
+    #[derive(Debug, PartialEq)]
+    struct CallTrackingAndResult<Result: Debug + PartialEq, CallArgs: Debug + 
PartialEq> {
+        result: Result,
+        calls: Vec<CallArgs>,
+    }
+    type CallTrackingWithInputType<Result> = CallTrackingAndResult<Result, 
Option<i32>>;
+    type CallTrackingOnly = CallTrackingWithInputType<()>;
+
+    #[test]
+    fn assert_position() {
+        struct PositionOp {
+            reverse: bool,
+            number_of_false: usize,
+        }
+
+        impl MutatingArrayIteratorOp for PositionOp {
+            type Output = CallTrackingWithInputType<Option<usize>>;
+            fn name(&self) -> String {
+                if self.reverse {
+                    format!("rposition with {} false returned", 
self.number_of_false)
+                } else {
+                    format!("position with {} false returned", 
self.number_of_false)
+                }
+            }
+
+            fn get_value<T: SharedBetweenArrayIterAndSliceIter>(
+                &self,
+                iter: &mut T,
+            ) -> Self::Output {
+                let mut items = vec![];
+
+                let mut count = 0;
+
+                let cb = |item| {
+                    items.push(item);
+
+                    if count < self.number_of_false {
+                        count += 1;
+                        false
+                    } else {
+                        true
+                    }
+                };
+
+                let position_result = if self.reverse {
+                    iter.rposition(cb)
+                } else {
+                    iter.position(cb)
+                };
+
+                CallTrackingAndResult {
+                    result: position_result,
+                    calls: items,
+                }
+            }
+        }
+
+        for reverse in [false, true] {
+            for number_of_false in [0, 1, 2, usize::MAX] {
+                assert_array_iterator_cases_mutate(PositionOp {
+                    reverse,
+                    number_of_false,
+                });
+            }
+        }
+    }
+
+    #[test]
+    fn assert_nth() {
+        for (array, source) in get_int32_iterator_cases() {
+            let actual = ArrayIter::new(&array);
+            let expected = source.iter().copied();
+            {
+                let mut actual = actual.clone();
+                let mut expected = expected.clone();
+                for _ in 0..expected.len() {
+                    #[allow(clippy::iter_nth_zero)]
+                    let actual_val = actual.nth(0);
+                    #[allow(clippy::iter_nth_zero)]
+                    let expected_val = expected.nth(0);
+                    assert_eq!(actual_val, expected_val, "Failed on nth(0)");
+                }
+            }
+
+            {
+                let mut actual = actual.clone();
+                let mut expected = expected.clone();
+                for _ in 0..expected.len() {
+                    let actual_val = actual.nth(1);
+                    let expected_val = expected.nth(1);
+                    assert_eq!(actual_val, expected_val, "Failed on nth(1)");
+                }
+            }
+
+            {
+                let mut actual = actual.clone();
+                let mut expected = expected.clone();
+                for _ in 0..expected.len() {
+                    let actual_val = actual.nth(2);
+                    let expected_val = expected.nth(2);
+                    assert_eq!(actual_val, expected_val, "Failed on nth(2)");
+                }
+            }
+        }
+    }
+
+    #[test]
+    fn assert_nth_back() {
+        for (array, source) in get_int32_iterator_cases() {
+            let actual = ArrayIter::new(&array);
+            let expected = source.iter().copied();
+            {
+                let mut actual = actual.clone();
+                let mut expected = expected.clone();
+                for _ in 0..expected.len() {
+                    #[allow(clippy::iter_nth_zero)]
+                    let actual_val = actual.nth_back(0);
+                    #[allow(clippy::iter_nth_zero)]
+                    let expected_val = expected.nth_back(0);
+                    assert_eq!(actual_val, expected_val, "Failed on 
nth_back(0)");
+                }
+            }
+
+            {
+                let mut actual = actual.clone();
+                let mut expected = expected.clone();
+                for _ in 0..expected.len() {
+                    let actual_val = actual.nth_back(1);
+                    let expected_val = expected.nth_back(1);
+                    assert_eq!(actual_val, expected_val, "Failed on 
nth_back(1)");
+                }
+            }
+
+            {
+                let mut actual = actual.clone();
+                let mut expected = expected.clone();
+                for _ in 0..expected.len() {
+                    let actual_val = actual.nth_back(2);
+                    let expected_val = expected.nth_back(2);
+                    assert_eq!(actual_val, expected_val, "Failed on 
nth_back(2)");
+                }
+            }
+        }
+    }
+
+    #[test]
+    fn assert_last() {
+        for (array, source) in get_int32_iterator_cases() {
+            let mut actual_forward = ArrayIter::new(&array);
+            let mut expected_forward = source.iter().copied();
+
+            for _ in 0..source.len() + 1 {
+                {
+                    let actual_forward_clone = actual_forward.clone();
+                    let expected_forward_clone = expected_forward.clone();
+
+                    assert_eq!(actual_forward_clone.last(), 
expected_forward_clone.last());
+                }
+
+                actual_forward.next();
+                expected_forward.next();
+            }
+
+            let mut actual_backward = ArrayIter::new(&array);
+            let mut expected_backward = source.iter().copied();
+            for _ in 0..source.len() + 1 {
+                {
+                    assert_eq!(
+                        actual_backward.clone().last(),
+                        expected_backward.clone().last()
+                    );
+                }
+
+                actual_backward.next_back();
+                expected_backward.next_back();
+            }
+        }
+    }
+
+    #[test]
+    fn assert_for_each() {
+        struct ForEachOp;
+
+        impl ConsumingArrayIteratorOp for ForEachOp {
+            type Output = CallTrackingOnly;
+
+            fn name(&self) -> String {
+                "for_each".to_string()
+            }
+
+            fn get_value<T: SharedBetweenArrayIterAndSliceIter>(&self, iter: 
T) -> Self::Output {
+                let mut items = Vec::with_capacity(iter.len());
+
+                iter.for_each(|item| {
+                    items.push(item);
+                });
+
+                CallTrackingAndResult {
+                    calls: items,
+                    result: (),
+                }
+            }
+        }
+
+        assert_array_iterator_cases(ForEachOp)
+    }
+
+    #[test]
+    fn assert_fold() {
+        struct FoldOp {
+            reverse: bool,
+        }
+
+        #[derive(Debug, PartialEq)]
+        struct CallArgs {
+            acc: Option<i32>,
+            item: Option<i32>,
+        }
+
+        impl ConsumingArrayIteratorOp for FoldOp {
+            type Output = CallTrackingAndResult<Option<i32>, CallArgs>;
+
+            fn name(&self) -> String {
+                if self.reverse {
+                    "rfold".to_string()
+                } else {
+                    "fold".to_string()
+                }
+            }
+
+            fn get_value<T: SharedBetweenArrayIterAndSliceIter>(&self, iter: 
T) -> Self::Output {
+                let mut items = Vec::with_capacity(iter.len());
+
+                let cb = |acc, item| {
+                    items.push(CallArgs { item, acc });
+
+                    item.map(|val| val + 100)
+                };
+
+                let result = if self.reverse {
+                    iter.rfold(Some(1), cb)
+                } else {
+                    #[allow(clippy::manual_try_fold)]
+                    iter.fold(Some(1), cb)
+                };
+
+                CallTrackingAndResult {
+                    calls: items,
+                    result,
+                }
+            }
+        }
+
+        assert_array_iterator_cases(FoldOp { reverse: false });
+        assert_array_iterator_cases(FoldOp { reverse: true });
+    }
+
+    #[test]
+    fn assert_count() {
+        struct CountOp;
+
+        impl ConsumingArrayIteratorOp for CountOp {
+            type Output = usize;
+
+            fn name(&self) -> String {
+                "count".to_string()
+            }
+
+            fn get_value<T: SharedBetweenArrayIterAndSliceIter>(&self, iter: 
T) -> Self::Output {
+                iter.count()
+            }
+        }
+
+        assert_array_iterator_cases(CountOp)
+    }
+
+    #[test]
+    fn assert_any() {
+        struct AnyOp {
+            false_count: usize,
+        }
+
+        impl MutatingArrayIteratorOp for AnyOp {
+            type Output = CallTrackingWithInputType<bool>;
+
+            fn name(&self) -> String {
+                format!("any with {} false returned", self.false_count)
+            }
+
+            fn get_value<T: SharedBetweenArrayIterAndSliceIter>(
+                &self,
+                iter: &mut T,
+            ) -> Self::Output {
+                let mut items = Vec::with_capacity(iter.len());
+
+                let mut count = 0;
+                let res = iter.any(|item| {
+                    items.push(item);
+
+                    if count < self.false_count {
+                        count += 1;
+                        false
+                    } else {
+                        true
+                    }
+                });
+
+                CallTrackingWithInputType {
+                    calls: items,
+                    result: res,
+                }
+            }
+        }
+
+        for false_count in [0, 1, 2, usize::MAX] {
+            assert_array_iterator_cases_mutate(AnyOp { false_count });
+        }
+    }
+
+    #[test]
+    fn assert_all() {
+        struct AllOp {
+            true_count: usize,
+        }
+
+        impl MutatingArrayIteratorOp for AllOp {
+            type Output = CallTrackingWithInputType<bool>;
+
+            fn name(&self) -> String {
+                format!("all with {} false returned", self.true_count)
+            }
+
+            fn get_value<T: SharedBetweenArrayIterAndSliceIter>(
+                &self,
+                iter: &mut T,
+            ) -> Self::Output {
+                let mut items = Vec::with_capacity(iter.len());
+
+                let mut count = 0;
+                let res = iter.all(|item| {
+                    items.push(item);
+
+                    if count < self.true_count {
+                        count += 1;
+                        true
+                    } else {
+                        false
+                    }
+                });
+
+                CallTrackingWithInputType {
+                    calls: items,
+                    result: res,
+                }
+            }
+        }
+
+        for true_count in [0, 1, 2, usize::MAX] {
+            assert_array_iterator_cases_mutate(AllOp { true_count });
+        }
+    }
+
+    #[test]
+    fn assert_find() {
+        struct FindOp {
+            reverse: bool,
+            false_count: usize,
+        }
+
+        impl MutatingArrayIteratorOp for FindOp {
+            type Output = CallTrackingWithInputType<Option<Option<i32>>>;
+
+            fn name(&self) -> String {
+                if self.reverse {
+                    format!("rfind with {} false returned", self.false_count)
+                } else {
+                    format!("find with {} false returned", self.false_count)
+                }
+            }
+
+            fn get_value<T: SharedBetweenArrayIterAndSliceIter>(
+                &self,
+                iter: &mut T,
+            ) -> Self::Output {
+                let mut items = vec![];
+
+                let mut count = 0;
+
+                let cb = |item: &Option<i32>| {
+                    items.push(*item);
+
+                    if count < self.false_count {
+                        count += 1;
+                        false
+                    } else {
+                        true
+                    }
+                };
+
+                let position_result = if self.reverse {
+                    iter.rfind(cb)
+                } else {
+                    iter.find(cb)
+                };
+
+                CallTrackingWithInputType {
+                    calls: items,
+                    result: position_result,
+                }
+            }
+        }
+
+        for reverse in [false, true] {
+            for false_count in [0, 1, 2, usize::MAX] {
+                assert_array_iterator_cases_mutate(FindOp {
+                    reverse,
+                    false_count,
+                });
+            }
+        }
+    }
+
+    #[test]
+    fn assert_find_map() {
+        struct FindMapOp {
+            number_of_nones: usize,
+        }
+
+        impl MutatingArrayIteratorOp for FindMapOp {
+            type Output = CallTrackingWithInputType<Option<&'static str>>;
+
+            fn name(&self) -> String {
+                format!("find_map with {} None returned", self.number_of_nones)
+            }
+
+            fn get_value<T: SharedBetweenArrayIterAndSliceIter>(
+                &self,
+                iter: &mut T,
+            ) -> Self::Output {
+                let mut items = vec![];
+
+                let mut count = 0;
+
+                let result = iter.find_map(|item| {
+                    items.push(item);
+
+                    if count < self.number_of_nones {
+                        count += 1;
+                        None
+                    } else {
+                        Some("found it")
+                    }
+                });
+
+                CallTrackingAndResult {
+                    result,
+                    calls: items,
+                }
+            }
+        }
+
+        for number_of_nones in [0, 1, 2, usize::MAX] {
+            assert_array_iterator_cases_mutate(FindMapOp { number_of_nones });
+        }
+    }
+
+    #[test]
+    fn assert_partition() {
+        struct PartitionOp<F: Fn(usize, &Option<i32>) -> bool> {
+            description: &'static str,
+            predicate: F,
+        }
+
+        #[derive(Debug, PartialEq)]
+        struct PartitionResult {
+            left: Vec<Option<i32>>,
+            right: Vec<Option<i32>>,
+        }
+
+        impl<F: Fn(usize, &Option<i32>) -> bool> ConsumingArrayIteratorOp for 
PartitionOp<F> {
+            type Output = CallTrackingWithInputType<PartitionResult>;
+
+            fn name(&self) -> String {
+                format!("partition by {}", self.description)
+            }
+
+            fn get_value<T: SharedBetweenArrayIterAndSliceIter>(&self, iter: 
T) -> Self::Output {
+                let mut items = vec![];
+
+                let mut index = 0;
+
+                let (left, right) = iter.partition(|item| {
+                    items.push(*item);
+
+                    let res = (self.predicate)(index, item);
+
+                    index += 1;
+                    res
+                });
+
+                CallTrackingAndResult {
+                    result: PartitionResult { left, right },
+                    calls: items,
+                }
+            }
+        }
+
+        assert_array_iterator_cases(PartitionOp {
+            description: "None on one side and Some(*) on the other",
+            predicate: |_, item| item.is_none(),
+        });
+
+        assert_array_iterator_cases(PartitionOp {
+            description: "all true",
+            predicate: |_, _| true,
+        });
+
+        assert_array_iterator_cases(PartitionOp {
+            description: "all false",
+            predicate: |_, _| false,
+        });
+
+        let random_values = (0..100).map(|_| 
rand::random_bool(0.5)).collect::<Vec<_>>();
+        assert_array_iterator_cases(PartitionOp {
+            description: "random",
+            predicate: |index, _| random_values[index % random_values.len()],
+        });
+    }
 }

Reply via email to