From 7302b948a5f4a6ccbf290a140cf62985dc9af641 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 22 Sep 2026 18:06:51 +0000 Subject: [PATCH] Tests: add coverage for AsyncSeq.zipWithIndexAsync and AsyncSeq.ofIQueryable Both functions previously had no dedicated test coverage. zipWithIndexAsync is an obsolete alias for mapiAsync (retained for backward compatibility); ofIQueryable casts an IQueryable<'T> to IAsyncEnumerable<'T> for LINQ provider integrations such as EF Core. No functional changes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- RELEASE_NOTES.md | 1 + .../AsyncSeqTests.fs | 66 +++++++++++++++++++ 2 files changed, 67 insertions(+) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index d96928c..d9169ed 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,5 +1,6 @@ ### Unreleased +* Test coverage: Added tests for `AsyncSeq.zipWithIndexAsync` (obsolete alias for `mapiAsync`) and `AsyncSeq.ofIQueryable`, which previously had no dedicated test coverage. No functional changes. * 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. diff --git a/tests/FSharp.Control.AsyncSeq.Tests/AsyncSeqTests.fs b/tests/FSharp.Control.AsyncSeq.Tests/AsyncSeqTests.fs index 03f8dc0..167ecd5 100644 --- a/tests/FSharp.Control.AsyncSeq.Tests/AsyncSeqTests.fs +++ b/tests/FSharp.Control.AsyncSeq.Tests/AsyncSeqTests.fs @@ -6,6 +6,8 @@ #endif module AsyncSeqTests +#nowarn "44" // suppress Obsolete warnings for intentional tests of obsolete API (e.g. AsyncSeq.zipWithIndexAsync) + open NUnit.Framework open FSharp.Control open System @@ -5277,3 +5279,67 @@ let ``AsyncSeq.compareWithAsync empty sequences returns 0`` () = AsyncSeq.empty |> Async.RunSynchronously Assert.AreEqual(0, result) + +// ===== zipWithIndexAsync (obsolete alias for mapiAsync) ===== + +[] +let ``AsyncSeq.zipWithIndexAsync maps elements with their int64 index`` () = + let result = + AsyncSeq.ofSeq ["a"; "b"; "c"] + |> AsyncSeq.zipWithIndexAsync (fun i x -> async { return sprintf "%d:%s" i x }) + |> AsyncSeq.toArrayAsync + |> Async.RunSynchronously + Assert.AreEqual([| "0:a"; "1:b"; "2:c" |], result) + +[] +let ``AsyncSeq.zipWithIndexAsync on empty sequence returns empty`` () = + let result = + AsyncSeq.empty + |> AsyncSeq.zipWithIndexAsync (fun i x -> async { return sprintf "%d:%s" i x }) + |> AsyncSeq.toArrayAsync + |> Async.RunSynchronously + Assert.AreEqual([||], result) + +[] +let ``AsyncSeq.zipWithIndexAsync matches mapiAsync behavior`` () = + let xs = AsyncSeq.ofSeq [10; 20; 30] + let f i x = async { return int i * x } + let viaZipWithIndexAsync = xs |> AsyncSeq.zipWithIndexAsync f |> AsyncSeq.toArrayAsync |> Async.RunSynchronously + let viaMapiAsync = xs |> AsyncSeq.mapiAsync f |> AsyncSeq.toArrayAsync |> Async.RunSynchronously + Assert.AreEqual(viaMapiAsync, viaZipWithIndexAsync) + +#if (NETSTANDARD2_1 || NETCOREAPP3_0) +// ===== ofIQueryable ===== + +/// Minimal IQueryable<'T> implementation that also implements IAsyncEnumerable<'T>, +/// as required by AsyncSeq.ofIQueryable (which casts its input to IAsyncEnumerable<'T>). +type private FakeAsyncQueryable<'T>(data: 'T[]) = + let inner = data.AsQueryable() + interface System.Collections.IEnumerable with + member _.GetEnumerator() : System.Collections.IEnumerator = (inner :> System.Collections.IEnumerable).GetEnumerator() + interface Collections.Generic.IEnumerable<'T> with + member _.GetEnumerator() : Collections.Generic.IEnumerator<'T> = (inner :> Collections.Generic.IEnumerable<'T>).GetEnumerator() + interface Linq.IQueryable<'T> with + member _.ElementType = inner.ElementType + member _.Expression = inner.Expression + member _.Provider = inner.Provider + interface Collections.Generic.IAsyncEnumerable<'T> with + member _.GetAsyncEnumerator(_ct: CancellationToken) : Collections.Generic.IAsyncEnumerator<'T> = + let e = (data :> Collections.Generic.IEnumerable<'T>).GetEnumerator() + { new Collections.Generic.IAsyncEnumerator<'T> with + member _.Current = e.Current + member _.MoveNextAsync() = Threading.Tasks.ValueTask(e.MoveNext()) + member _.DisposeAsync() = e.Dispose(); Threading.Tasks.ValueTask() } + +[] +let ``AsyncSeq.ofIQueryable returns elements in order`` () = + let query = FakeAsyncQueryable([| 1; 2; 3 |]) :> Linq.IQueryable + let result = AsyncSeq.ofIQueryable query |> AsyncSeq.toArrayAsync |> Async.RunSynchronously + Assert.AreEqual([| 1; 2; 3 |], result) + +[] +let ``AsyncSeq.ofIQueryable on empty queryable returns empty`` () = + let query = FakeAsyncQueryable([||]) :> Linq.IQueryable + let result = AsyncSeq.ofIQueryable query |> AsyncSeq.toArrayAsync |> Async.RunSynchronously + Assert.AreEqual([||], result) +#endif