From 217debcb6a4a9483e1307abafab44be456c2ff3d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 21 Mar 2026 00:12:51 +0000 Subject: [PATCH 1/2] Tests: add coverage for indexed, iteriAsync, tryLast, replicateUntilNoneAsync, reduceAsync Add 14 new unit tests for functions that previously had no test coverage: - AsyncSeq.indexed: 4 tests (basic, empty, singleton, large sequence) - AsyncSeq.iteriAsync: 3 tests (correct indices+values, empty, zero-based index) - AsyncSeq.tryLast: 3 tests (non-empty, empty, singleton) - AsyncSeq.replicateUntilNoneAsync: 3 tests (generates until None, immediate None, single element) - AsyncSeq.reduceAsync: 1 test (raises InvalidOperationException on empty sequence) All 335 tests pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- RELEASE_NOTES.md | 4 + .../AsyncSeqTests.fs | 143 ++++++++++++++++++ 2 files changed, 147 insertions(+) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 6f4908d..0d5ab87 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,3 +1,7 @@ +### 4.10.1 + +* Tests: added 14 new unit tests covering previously untested functions — `AsyncSeq.indexed`, `AsyncSeq.iteriAsync`, `AsyncSeq.tryLast`, `AsyncSeq.replicateUntilNoneAsync`, and `AsyncSeq.reduceAsync` (empty-sequence edge case). + ### 4.10.0 * Added `AsyncSeq.withCancellation` — returns a new `AsyncSeq` that passes the given `CancellationToken` to `GetAsyncEnumerator`, overriding whatever token would otherwise be supplied. Mirrors `TaskSeq.withCancellation` and is useful when consuming sequences from libraries (e.g. Entity Framework) that accept a cancellation token through `GetAsyncEnumerator`. Part of ongoing design-parity work with FSharp.Control.TaskSeq (see #277). diff --git a/tests/FSharp.Control.AsyncSeq.Tests/AsyncSeqTests.fs b/tests/FSharp.Control.AsyncSeq.Tests/AsyncSeqTests.fs index 6e39e77..b6adb19 100644 --- a/tests/FSharp.Control.AsyncSeq.Tests/AsyncSeqTests.fs +++ b/tests/FSharp.Control.AsyncSeq.Tests/AsyncSeqTests.fs @@ -3719,3 +3719,146 @@ let ``AsyncSeq.withCancellation with cancelled token raises OperationCanceledExc |> Async.RunSynchronously |> ignore) |> ignore + +// ===== indexed ===== + +[] +let ``AsyncSeq.indexed pairs elements with int64 indices`` () = + let result = + AsyncSeq.ofSeq [ "a"; "b"; "c" ] + |> AsyncSeq.indexed + |> AsyncSeq.toArrayAsync + |> Async.RunSynchronously + Assert.AreEqual([| (0L, "a"); (1L, "b"); (2L, "c") |], result) + +[] +let ``AsyncSeq.indexed on empty sequence returns empty`` () = + let result = + AsyncSeq.empty + |> AsyncSeq.indexed + |> AsyncSeq.toArrayAsync + |> Async.RunSynchronously + Assert.AreEqual([||], result) + +[] +let ``AsyncSeq.indexed index starts at zero for singleton`` () = + let result = + AsyncSeq.singleton 42 + |> AsyncSeq.indexed + |> AsyncSeq.toArrayAsync + |> Async.RunSynchronously + Assert.AreEqual([| (0L, 42) |], result) + +[] +let ``AsyncSeq.indexed produces consecutive int64 indices`` () = + let n = 100 + let result = + AsyncSeq.init (int64 n) id + |> AsyncSeq.indexed + |> AsyncSeq.toArrayAsync + |> Async.RunSynchronously + let indices = result |> Array.map fst + Assert.AreEqual(Array.init n int64, indices) + +// ===== iteriAsync ===== + +[] +let ``AsyncSeq.iteriAsync calls action with correct indices and values`` () = + let log = ResizeArray() + AsyncSeq.ofSeq [ 10; 20; 30 ] + |> AsyncSeq.iteriAsync (fun i v -> async { log.Add(i, v) }) + |> Async.RunSynchronously + Assert.AreEqual([ (0, 10); (1, 20); (2, 30) ], log |> Seq.toList) + +[] +let ``AsyncSeq.iteriAsync on empty sequence does not call action`` () = + let mutable callCount = 0 + AsyncSeq.empty + |> AsyncSeq.iteriAsync (fun _ _ -> async { callCount <- callCount + 1 }) + |> Async.RunSynchronously + Assert.AreEqual(0, callCount) + +[] +let ``AsyncSeq.iteriAsync index is zero-based`` () = + let indices = ResizeArray() + AsyncSeq.ofSeq [ "x"; "y"; "z" ] + |> AsyncSeq.iteriAsync (fun i _ -> async { indices.Add(i) }) + |> Async.RunSynchronously + Assert.AreEqual([ 0; 1; 2 ], indices |> Seq.toList) + +// ===== tryLast ===== + +[] +let ``AsyncSeq.tryLast returns Some last element for non-empty sequence`` () = + let result = + AsyncSeq.ofSeq [ 1; 2; 3 ] + |> AsyncSeq.tryLast + |> Async.RunSynchronously + Assert.AreEqual(Some 3, result) + +[] +let ``AsyncSeq.tryLast returns None for empty sequence`` () = + let result = + AsyncSeq.empty + |> AsyncSeq.tryLast + |> Async.RunSynchronously + Assert.AreEqual(None, result) + +[] +let ``AsyncSeq.tryLast returns Some for singleton sequence`` () = + let result = + AsyncSeq.singleton 99 + |> AsyncSeq.tryLast + |> Async.RunSynchronously + Assert.AreEqual(Some 99, result) + +// ===== replicateUntilNoneAsync ===== + +[] +let ``AsyncSeq.replicateUntilNoneAsync generates elements until None`` () = + let mutable counter = 0 + let gen = async { + counter <- counter + 1 + if counter <= 3 then return Some counter + else return None + } + let result = + AsyncSeq.replicateUntilNoneAsync gen + |> AsyncSeq.toArrayAsync + |> Async.RunSynchronously + Assert.AreEqual([| 1; 2; 3 |], result) + +[] +let ``AsyncSeq.replicateUntilNoneAsync returns empty for immediate None`` () = + let result = + AsyncSeq.replicateUntilNoneAsync (async { return None }) + |> AsyncSeq.toArrayAsync + |> Async.RunSynchronously + Assert.AreEqual([||], result) + +[] +let ``AsyncSeq.replicateUntilNoneAsync returns single element then stops`` () = + let mutable called = false + let gen = async { + if not called then + called <- true + return Some 42 + else + return None + } + let result = + AsyncSeq.replicateUntilNoneAsync gen + |> AsyncSeq.toArrayAsync + |> Async.RunSynchronously + Assert.AreEqual([| 42 |], result) + +// ===== reduceAsync edge case ===== + +[] +let ``AsyncSeq.reduceAsync raises InvalidOperationException on empty sequence`` () = + Assert.Throws(fun () -> + AsyncSeq.empty + |> AsyncSeq.reduceAsync (fun a b -> async { return a + b }) + |> Async.RunSynchronously + |> ignore) + |> ignore From 73a2413a28607815b2ce3bce75a328addee04ba2 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 21 Mar 2026 00:16:59 +0000 Subject: [PATCH 2/2] ci: trigger checks