From 6f02825c23ecf91959e3d2fecdeba59875057cde Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 4 Aug 2026 18:22:47 +0000 Subject: [PATCH 1/2] Tests: add coverage for zapp, zappAsync, and compareWithAsync These three functions had no dedicated tests. This commit adds: - 6 tests for AsyncSeq.zapp (function application, short-circuiting, empty cases) - 2 tests for AsyncSeq.zappAsync (async function application, empty case) - 5 tests for AsyncSeq.compareWithAsync (equal, shorter, longer, lexicographic, empty) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- RELEASE_NOTES.md | 4 + .../AsyncSeqTests.fs | 99 +++++++++++++++++++ 2 files changed, 103 insertions(+) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 47137e6..3729aff 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,3 +1,7 @@ +### 4.18.0 + +* Tests: Added comprehensive tests for `AsyncSeq.zapp`, `AsyncSeq.zappAsync`, and `AsyncSeq.compareWithAsync`, which previously had no dedicated test coverage. + ### 4.17.0 * Added `AsyncSeq.exists2` — asynchronously tests whether any corresponding pair of elements in two async sequences satisfies the predicate. Evaluates pairwise up to the shorter sequence; short-circuits on first match. Mirrors `Seq.exists2`. diff --git a/tests/FSharp.Control.AsyncSeq.Tests/AsyncSeqTests.fs b/tests/FSharp.Control.AsyncSeq.Tests/AsyncSeqTests.fs index 770f04c..474c425 100644 --- a/tests/FSharp.Control.AsyncSeq.Tests/AsyncSeqTests.fs +++ b/tests/FSharp.Control.AsyncSeq.Tests/AsyncSeqTests.fs @@ -5087,3 +5087,102 @@ let ``AsyncSeq.forall2Async returns true with async predicate`` () = (AsyncSeq.ofSeq [1;2;3]) |> Async.RunSynchronously Assert.IsTrue(result) + +// ===== zapp / zappAsync ===== + +[] +let ``AsyncSeq.zapp applies functions to corresponding elements`` () = + let fs = asyncSeq { yield (fun x -> x + 10); yield (fun x -> x * 2); yield (fun x -> x - 1) } + let vs = asyncSeq { yield 1; yield 2; yield 3 } + let result = AsyncSeq.zapp fs vs |> AsyncSeq.toArrayAsync |> Async.RunSynchronously + Assert.AreEqual([| 11; 4; 2 |], result) + +[] +let ``AsyncSeq.zapp stops when functions run out`` () = + let fs = asyncSeq { yield (fun x -> x + 1); yield (fun x -> x + 2) } + let vs = asyncSeq { yield 10; yield 20; yield 30 } + let result = AsyncSeq.zapp fs vs |> AsyncSeq.toArrayAsync |> Async.RunSynchronously + Assert.AreEqual([| 11; 22 |], result) + +[] +let ``AsyncSeq.zapp stops when values run out`` () = + let fs = asyncSeq { yield (fun x -> x + 1); yield (fun x -> x + 2); yield (fun x -> x + 3) } + let vs = asyncSeq { yield 5 } + let result = AsyncSeq.zapp fs vs |> AsyncSeq.toArrayAsync |> Async.RunSynchronously + Assert.AreEqual([| 6 |], result) + +[] +let ``AsyncSeq.zapp on empty functions returns empty`` () = + let fs = AsyncSeq.empty int> + let vs = asyncSeq { yield 1; yield 2; yield 3 } + let result = AsyncSeq.zapp fs vs |> AsyncSeq.toArrayAsync |> Async.RunSynchronously + Assert.AreEqual([||], result) + +[] +let ``AsyncSeq.zappAsync applies async functions to corresponding elements`` () = + let fs = asyncSeq { + yield (fun x -> async { return x + 10 }) + yield (fun x -> async { return x * 3 }) + } + let vs = asyncSeq { yield 5; yield 4 } + let result = AsyncSeq.zappAsync fs vs |> AsyncSeq.toArrayAsync |> Async.RunSynchronously + Assert.AreEqual([| 15; 12 |], result) + +[] +let ``AsyncSeq.zappAsync on empty source returns empty`` () = + let fs = asyncSeq { yield (fun x -> async { return x + 1 }) } + let vs = AsyncSeq.empty + let result = AsyncSeq.zappAsync fs vs |> AsyncSeq.toArrayAsync |> Async.RunSynchronously + Assert.AreEqual([||], result) + +// ===== compareWithAsync ===== + +[] +let ``AsyncSeq.compareWithAsync equal sequences returns 0`` () = + let result = + AsyncSeq.compareWithAsync + (fun a b -> async { return compare a b }) + (AsyncSeq.ofSeq [1;2;3]) + (AsyncSeq.ofSeq [1;2;3]) + |> Async.RunSynchronously + Assert.AreEqual(0, result) + +[] +let ``AsyncSeq.compareWithAsync shorter is less than longer`` () = + let result = + AsyncSeq.compareWithAsync + (fun a b -> async { return compare a b }) + (AsyncSeq.ofSeq [1;2]) + (AsyncSeq.ofSeq [1;2;3]) + |> Async.RunSynchronously + Assert.IsTrue(result < 0) + +[] +let ``AsyncSeq.compareWithAsync longer is greater than shorter`` () = + let result = + AsyncSeq.compareWithAsync + (fun a b -> async { return compare a b }) + (AsyncSeq.ofSeq [1;2;3]) + (AsyncSeq.ofSeq [1;2]) + |> Async.RunSynchronously + Assert.IsTrue(result > 0) + +[] +let ``AsyncSeq.compareWithAsync lexicographic difference`` () = + let result = + AsyncSeq.compareWithAsync + (fun a b -> async { return compare a b }) + (AsyncSeq.ofSeq [1;3]) + (AsyncSeq.ofSeq [1;2]) + |> Async.RunSynchronously + Assert.IsTrue(result > 0) + +[] +let ``AsyncSeq.compareWithAsync empty sequences returns 0`` () = + let result = + AsyncSeq.compareWithAsync + (fun a b -> async { return compare a b }) + AsyncSeq.empty + AsyncSeq.empty + |> Async.RunSynchronously + Assert.AreEqual(0, result) From f453d6be05b08309d71517f91bd3361053cf2efb Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 8 Sep 2026 17:55:19 +0000 Subject: [PATCH 2/2] Tests: add coverage for zapp, zappAsync, and compareWithAsync These three functions had no dedicated tests. This commit adds: - 6 tests for AsyncSeq.zapp (function application, short-circuiting, empty cases) - 2 tests for AsyncSeq.zappAsync (async function application, empty case) - 5 tests for AsyncSeq.compareWithAsync (equal, shorter, longer, lexicographic, empty) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- RELEASE_NOTES.md | 3 +- .../AsyncSeqTests.fs | 90 +++++++++++++++++++ 2 files changed, 92 insertions(+), 1 deletion(-) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index abd9fc8..d96928c 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,7 +1,8 @@ ### Unreleased -* Tests: Added comprehensive tests for `AsyncSeq.zapp`, `AsyncSeq.zappAsync`, and `AsyncSeq.compareWithAsync`, which previously had no dedicated test coverage. +* Test coverage: Added tests for previously-untested public API functions `AsyncSeq.tryFirst`, `AsyncSeq.firstOrDefault`, `AsyncSeq.zipWithParallel`, `AsyncSeq.combineLatestWithAsync`, and `AsyncSeq.toObservable`. No functional changes. * Fixed Fable CI build: `Microsoft.Bcl.AsyncInterfaces` was pinned to a specific version (`10.0.7`) that was older than the version resolved transitively via `System.Threading.Channels`, causing a `NU1605` package downgrade error that made Fable's project cracker fail during `dotnet fable`. The reference now uses `Version="*"` (matching `System.Threading.Channels`) so both resolve consistently. (#334) +* Tests: Added comprehensive tests for `AsyncSeq.zapp`, `AsyncSeq.zappAsync`, and `AsyncSeq.compareWithAsync`, which previously had no dedicated test coverage. ### 4.17.0 diff --git a/tests/FSharp.Control.AsyncSeq.Tests/AsyncSeqTests.fs b/tests/FSharp.Control.AsyncSeq.Tests/AsyncSeqTests.fs index 474c425..bd1d56f 100644 --- a/tests/FSharp.Control.AsyncSeq.Tests/AsyncSeqTests.fs +++ b/tests/FSharp.Control.AsyncSeq.Tests/AsyncSeqTests.fs @@ -5088,6 +5088,96 @@ let ``AsyncSeq.forall2Async returns true with async predicate`` () = |> Async.RunSynchronously Assert.IsTrue(result) +// ===== tryFirst / firstOrDefault ===== + +[] +let ``AsyncSeq.tryFirst returns Some first element for non-empty sequence`` () = + let result = AsyncSeq.tryFirst (AsyncSeq.ofSeq [1;2;3]) |> Async.RunSynchronously + Assert.AreEqual(Some 1, result) + +[] +let ``AsyncSeq.tryFirst returns None for empty sequence`` () = + let result = AsyncSeq.tryFirst (AsyncSeq.empty) |> Async.RunSynchronously + Assert.AreEqual(None, result) + +[] +let ``AsyncSeq.firstOrDefault returns first element when non-empty`` () = + let result = AsyncSeq.firstOrDefault -1 (AsyncSeq.ofSeq [5;6;7]) |> Async.RunSynchronously + Assert.AreEqual(5, result) + +[] +let ``AsyncSeq.firstOrDefault returns default when empty`` () = + let result = AsyncSeq.firstOrDefault -1 (AsyncSeq.empty) |> Async.RunSynchronously + Assert.AreEqual(-1, result) + +// ===== zipWithParallel ===== + +[] +let ``AsyncSeq.zipWithParallel combines values from both sequences`` () = + let result = + AsyncSeq.zipWithParallel (fun a b -> a + b) (AsyncSeq.ofSeq [1;2;3]) (AsyncSeq.ofSeq [10;20;30]) + |> AsyncSeq.toArrayAsync + |> Async.RunSynchronously + Assert.AreEqual([| 11;22;33 |], result) + +[] +let ``AsyncSeq.zipWithParallel stops at shorter sequence`` () = + let result = + AsyncSeq.zipWithParallel (fun a b -> a, b) (AsyncSeq.ofSeq [1;2]) (AsyncSeq.ofSeq ["a";"b";"c"]) + |> AsyncSeq.toArrayAsync + |> Async.RunSynchronously + Assert.AreEqual([| (1,"a"); (2,"b") |], result) + +// ===== combineLatestWithAsync ===== + +[] +let ``AsyncSeq.combineLatestWithAsync combines initial values then emits on each new update`` () = + let result = + AsyncSeq.combineLatestWithAsync + (fun a b -> async { return a + b }) + (AsyncSeq.ofSeq [1;2]) + (AsyncSeq.ofSeq [10]) + |> AsyncSeq.toArrayAsync + |> Async.RunSynchronously + // First combination of initial pair, then subsequent updates to source1 combined with latest source2 value. + Assert.AreEqual([| 11; 12 |], result) + +[] +let ``AsyncSeq.combineLatestWithAsync produces empty sequence when either source is empty`` () = + let result = + AsyncSeq.combineLatestWithAsync + (fun a b -> async { return a + b }) + (AsyncSeq.empty) + (AsyncSeq.ofSeq [1;2;3]) + |> AsyncSeq.toArrayAsync + |> Async.RunSynchronously + Assert.AreEqual([||], result) + +// ===== toObservable ===== + +type private TestObserver<'T>(onNext, onCompleted) = + interface IObserver<'T> with + member _.OnNext(v) = onNext v + member _.OnCompleted() = onCompleted () + member _.OnError(_e) = () + +[] +let ``AsyncSeq.toObservable emits all values then completes`` () = + let received = ResizeArray() + let completedEvent = new System.Threading.ManualResetEventSlim(false) + let observer = TestObserver(received.Add, completedEvent.Set) + use _sub = (AsyncSeq.toObservable (AsyncSeq.ofSeq [1;2;3])).Subscribe(observer) + Assert.IsTrue(completedEvent.Wait(2000)) + Assert.AreEqual([| 1;2;3 |], received.ToArray()) + +[] +let ``AsyncSeq.toObservable on empty sequence emits nothing`` () = + let received = ResizeArray() + let completedEvent = new System.Threading.ManualResetEventSlim(false) + let observer = TestObserver(received.Add, completedEvent.Set) + use _sub = (AsyncSeq.toObservable (AsyncSeq.empty)).Subscribe(observer) + Assert.IsTrue(completedEvent.Wait(2000)) + Assert.AreEqual([||], received.ToArray()) // ===== zapp / zappAsync ===== []