Repository: rust-itertools/itertools
Pull request #847 (2024-01-13 04:56:42)

add test coverage workflow

codecov-commenter: ## Welcome to [Codecov](https://codecov.io?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) :tada:

Once merged to your default branch, Codecov will compare your coverage reports and display the results in this comment.

Thanks for integrating Codecov - We've got you covered :open_umbrella:
Philippe-Cholet: After reading the [readme](https://crates.io/crates/cargo-llvm-cov), I learnt that nightly is required to support doctests (and without it, coverage would probably be quite lacking for non-adaptor `Itertools` methods, as I found out when experimenting with tarpaulin a while back).
Looks good otherwise.
danieleades: > After reading the [readme](https://crates.io/crates/cargo-llvm-cov), I learnt that nightly is required to support doctests (and without it, coverage would probably be quite lacking for non-adaptor `Itertools` methods, as I found out when experimenting with tarpaulin a while back). Looks good otherwise.

i'm not sure what you mean? Nightly is required for the doctests coverage, which is why that's the toolchain used in the github action. Is that an issue?
Philippe-Cholet: Not an issue but an observation that I found useful to share, that's all.
Looks good ~~otherwise~~.
danieleades: > Shouldn't the triggers for this be basically identical to our triggers for CI testing?
> 
> https://github.com/rust-itertools/itertools/blob/f60fe7e9548ffb047420c3d9ec576aa792d8e9ca/.github/workflows/ci.yml#L3-L9

sure, i can update with this. it will also need to run on main though, so would be something like (i guess)-

```yml
 on: 
   pull_request: 
     paths-ignore: 
       - "**.md" 
  push: 
    branches: [ master ]
     paths-ignore: 
       - "**.md" 
   merge_group: 
     paths-ignore: 
       - "**.md" 
 ```

that look right?

Patch:
diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml
new file mode 100644
index 0000000..42c7b30
--- /dev/null
+++ b/.github/workflows/coverage.yml
@@ -0,0 +1,34 @@
+on:
+  push:
+    branches: [master]
+    paths-ignore: 
+      - "**.md" 
+  pull_request:
+    paths-ignore: 
+      - "**.md" 
+
+name: Code Coverage
+
+jobs:
+  coverage:
+    name: coverage
+    runs-on: ubuntu-latest
+    steps:
+      - name: checkout source
+        uses: actions/checkout@v4
+
+      - name: Install nightly toolchain
+        uses: dtolnay/rust-toolchain@nightly
+        with:
+          components: llvm-tools-preview
+
+      - name: Install cargo-llvm-cov
+        uses: taiki-e/install-action@cargo-llvm-cov
+
+      - name: Run llvm-cov
+        run: cargo llvm-cov --all-features --doctests --workspace --lcov --output-path lcov.info
+
+      - name: Upload coverage to Codecov
+        uses: codecov/codecov-action@v3
+        with:
+          files: lcov.info
diff --git a/src/adaptors/mod.rs b/src/adaptors/mod.rs
index 08d91e3..a2bfb92 100644
--- a/src/adaptors/mod.rs
+++ b/src/adaptors/mod.rs
@@ -76,6 +76,30 @@ where
     fn size_hint(&self) -> (usize, Option<usize>) {
         size_hint::add(self.a.size_hint(), self.b.size_hint())
     }
+
+    fn fold<B, F>(self, mut init: B, mut f: F) -> B
+    where
+        F: FnMut(B, Self::Item) -> B,
+    {
+        let Self { mut a, mut b, flag } = self;
+        if flag {
+            match b.next() {
+                Some(y) => init = f(init, y),
+                None => return a.fold(init, f),
+            }
+        }
+        let res = a.try_fold(init, |mut acc, x| {
+            acc = f(acc, x);
+            match b.next() {
+                Some(y) => Ok(f(acc, y)),
+                None => Err(acc),
+            }
+        });
+        match res {
+            Ok(acc) => b.fold(acc, f),
+            Err(acc) => a.fold(acc, f),
+        }
+    }
 }
 
 impl<I, J> FusedIterator for Interleave<I, J>
@@ -171,6 +195,34 @@ where
         };
         (lower, upper)
     }
+
+    fn fold<B, F>(self, mut init: B, mut f: F) -> B
+    where
+        F: FnMut(B, Self::Item) -> B,
+    {
+        let Self {
+            mut it0,
+            mut it1,
+            phase,
+        } = self;
+        if phase {
+            match it1.next() {
+                Some(y) => init = f(init, y),
+                None => return init,
+            }
+        }
+        let res = it0.try_fold(init, |mut acc, x| {
+            acc = f(acc, x);
+            match it1.next() {
+                Some(y) => Ok(f(acc, y)),
+                None => Err(acc),
+            }
+        });
+        match res {
+            Ok(val) => val,
+            Err(val) => val,
+        }
+    }
 }
 
 impl<I, J> FusedIterator for InterleaveShortest<I, J>
diff --git a/src/diff.rs b/src/diff.rs
index 0d3d358..acef16e 100644
--- a/src/diff.rs
+++ b/src/diff.rs
@@ -5,6 +5,8 @@
 //! describes the difference between two non-`Clone` iterators `I` and `J` after breaking ASAP from
 //! a lock-step comparison.
 
+use std::fmt;
+
 use crate::free::put_back;
 use crate::structs::PutBack;
 
@@ -26,6 +28,43 @@ where
     Longer(usize, PutBack<J>),
 }
 
+impl<I, J> fmt::Debug for Diff<I, J>
+where
+    I: Iterator,
+    J: Iterator,
+    PutBack<I>: fmt::Debug,
+    PutBack<J>: fmt::Debug,
+{
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        match self {
+            Self::FirstMismatch(idx, i, j) => f
+                .debug_tuple("FirstMismatch")
+                .field(idx)
+                .field(i)
+                .field(j)
+                .finish(),
+            Self::Shorter(idx, i) => f.debug_tuple("Shorter").field(idx).field(i).finish(),
+            Self::Longer(idx, j) => f.debug_tuple("Longer").field(idx).field(j).finish(),
+        }
+    }
+}
+
+impl<I, J> Clone for Diff<I, J>
+where
+    I: Iterator,
+    J: Iterator,
+    PutBack<I>: Clone,
+    PutBack<J>: Clone,
+{
+    fn clone(&self) -> Self {
+        match self {
+            Self::FirstMismatch(idx, i, j) => Self::FirstMismatch(*idx, i.clone(), j.clone()),
+            Self::Shorter(idx, i) => Self::Shorter(*idx, i.clone()),
+            Self::Longer(idx, j) => Self::Longer(*idx, j.clone()),
+        }
+    }
+}
+
 /// Compares every element yielded by both `i` and `j` with the given function in lock-step and
 /// returns a [`Diff`] which describes how `j` differs from `i`.
 ///
diff --git a/src/peeking_take_while.rs b/src/peeking_take_while.rs
index b4852c1..b224907 100644
--- a/src/peeking_take_while.rs
+++ b/src/peeking_take_while.rs
@@ -1,6 +1,7 @@
 use crate::PutBack;
 #[cfg(feature = "use_alloc")]
 use crate::PutBackN;
+use crate::RepeatN;
 use std::iter::Peekable;
 
 /// An iterator that allows peeking at an element before deciding to accept it.
@@ -91,6 +92,19 @@ where
     }
 }
 
+impl<T: Clone> PeekingNext for RepeatN<T> {
+    fn peeking_next<F>(&mut self, accept: F) -> Option<Self::Item>
+    where
+        F: FnOnce(&Self::Item) -> bool,
+    {
+        let r = self.elt.as_ref()?;
+        if !accept(r) {
+            return None;
+        }
+        self.next()
+    }
+}
+
 /// An iterator adaptor that takes items while a closure returns `true`.
 ///
 /// See [`.peeking_take_while()`](crate::Itertools::peeking_take_while)
diff --git a/src/repeatn.rs b/src/repeatn.rs
index 539c426..d86ad9f 100644
--- a/src/repeatn.rs
+++ b/src/repeatn.rs
@@ -6,7 +6,7 @@ use std::iter::FusedIterator;
 #[must_use = "iterators are lazy and do nothing unless consumed"]
 #[derive(Clone, Debug)]
 pub struct RepeatN<A> {
-    elt: Option<A>,
+    pub(crate) elt: Option<A>,
     n: usize,
 }
 
diff --git a/src/zip_longest.rs b/src/zip_longest.rs
index f390ede..6eb4bfb 100644
--- a/src/zip_longest.rs
+++ b/src/zip_longest.rs
@@ -54,17 +54,20 @@ where
     }
 
     #[inline]
-    fn fold<B, F>(self, mut init: B, mut f: F) -> B
+    fn fold<B, F>(self, init: B, mut f: F) -> B
     where
         Self: Sized,
         F: FnMut(B, Self::Item) -> B,
     {
-        let Self { a, mut b } = self;
-        init = a.fold(init, |init, a| match b.next() {
-            Some(b) => f(init, EitherOrBoth::Both(a, b)),
-            None => f(init, EitherOrBoth::Left(a)),
+        let Self { mut a, mut b } = self;
+        let res = a.try_fold(init, |init, a| match b.next() {
+            Some(b) => Ok(f(init, EitherOrBoth::Both(a, b))),
+            None => Err(f(init, EitherOrBoth::Left(a))),
         });
-        b.fold(init, |init, b| f(init, EitherOrBoth::Right(b)))
+        match res {
+            Ok(acc) => b.map(EitherOrBoth::Right).fold(acc, f),
+            Err(acc) => a.map(EitherOrBoth::Left).fold(acc, f),
+        }
     }
 }
 


Test patch:
diff --git a/tests/test_std.rs b/tests/test_std.rs
index be163e9..5d28645 100644
--- a/tests/test_std.rs
+++ b/tests/test_std.rs
@@ -8,6 +8,7 @@ use crate::it::izip;
 use crate::it::multipeek;
 use crate::it::multizip;
 use crate::it::peek_nth;
+use crate::it::repeat_n;
 use crate::it::ExactlyOneError;
 use crate::it::FoldWhile;
 use crate::it::Itertools;
@@ -674,6 +675,21 @@ fn test_multipeek_peeking_next() {
     assert_eq!(mp.peek(), None);
 }
 
+#[test]
+fn test_repeat_n_peeking_next() {
+    use crate::it::PeekingNext;
+    let mut rn = repeat_n(0, 5);
+    assert_eq!(rn.peeking_next(|&x| x != 0), None);
+    assert_eq!(rn.peeking_next(|&x| x <= 0), Some(0));
+    assert_eq!(rn.next(), Some(0));
+    assert_eq!(rn.peeking_next(|&x| x <= 0), Some(0));
+    assert_eq!(rn.peeking_next(|&x| x != 0), None);
+    assert_eq!(rn.peeking_next(|&x| x >= 0), Some(0));
+    assert_eq!(rn.next(), Some(0));
+    assert_eq!(rn.peeking_next(|&x| x <= 0), None);
+    assert_eq!(rn.next(), None);
+}
+
 #[test]
 fn test_peek_nth() {
     let nums = vec![1u8, 2, 3, 4, 5];