Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions release-notes.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
54 changes: 54 additions & 0 deletions src/FSharp.Control.TaskSeq.Test/TaskSeq.WithCancellation.Tests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -170,3 +170,57 @@ module ``Sequence contents`` =

collected |> Seq.toArray |> should equal [| 1..5 |]
}

module SideEffects =

[<Theory; ClassData(typeof<TestSideEffectTaskSeq>)>]
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 |]
}

[<Theory; ClassData(typeof<TestSideEffectTaskSeq>)>]
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 |]
}

[<Fact>]
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
}
Loading