diff --git a/release-notes.txt b/release-notes.txt index 72c765f..d2ff455 100644 --- a/release-notes.txt +++ b/release-notes.txt @@ -3,6 +3,7 @@ Release notes: Unreleased - test: add SideEffects module and ImmTaskSeq variant tests to TaskSeq.ChunkBy.Tests.fs, improving coverage for chunkBy and chunkByAsync + - test: add SideEffects module to TaskSeq.WithCancellation.Tests.fs, verifying re-iteration semantics are preserved when wrapping with a CancellationToken - fixes: `Async.bind` signature corrected from `(Async<'T> -> Async<'U>)` to `('T -> Async<'U>)` to match standard monadic bind semantics (same as `Task.bind`); the previous signature made the function effectively equivalent to direct application - refactor: simplify splitAt 'rest' taskSeq to use while!, removing redundant go2 mutable and manual MoveNextAsync pre-advance diff --git a/src/FSharp.Control.TaskSeq.Test/TaskSeq.WithCancellation.Tests.fs b/src/FSharp.Control.TaskSeq.Test/TaskSeq.WithCancellation.Tests.fs index 10b31b7..5c42b4d 100644 --- a/src/FSharp.Control.TaskSeq.Test/TaskSeq.WithCancellation.Tests.fs +++ b/src/FSharp.Control.TaskSeq.Test/TaskSeq.WithCancellation.Tests.fs @@ -170,3 +170,57 @@ module ``Sequence contents`` = collected |> Seq.toArray |> should equal [| 1..5 |] } + +module SideEffects = + + [)>] + let ``TaskSeq-withCancellation applied multiple times`` variant = task { + let ts = Gen.getSeqWithSideEffect variant + let wrapped = TaskSeq.withCancellation CancellationToken.None ts + + let! first = wrapped |> TaskSeq.toArrayAsync + let! second = wrapped |> TaskSeq.toArrayAsync + let! third = wrapped |> TaskSeq.toArrayAsync + + first |> should equal [| 1..10 |] + second |> should equal [| 11..20 |] + third |> should equal [| 21..30 |] + } + + [)>] + let ``TaskSeq-withCancellation with active CancellationToken applied multiple times`` variant = task { + use cts = new CancellationTokenSource() + let ts = Gen.getSeqWithSideEffect variant + let wrapped = TaskSeq.withCancellation cts.Token ts + + let! first = wrapped |> TaskSeq.toArrayAsync + let! second = wrapped |> TaskSeq.toArrayAsync + + first |> should equal [| 1..10 |] + second |> should equal [| 11..20 |] + } + + [] + let ``TaskSeq-withCancellation evaluates each source element exactly once per iteration`` () = task { + let mutable count = 0 + + let ts = taskSeq { + for i in 1..5 do + count <- count + 1 + yield i + } + + let! _ = + ts + |> TaskSeq.withCancellation CancellationToken.None + |> TaskSeq.toArrayAsync + + count |> should equal 5 + + let! _ = + ts + |> TaskSeq.withCancellation CancellationToken.None + |> TaskSeq.toArrayAsync + + count |> should equal 10 + }