Skip to content
Merged
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
74 changes: 74 additions & 0 deletions src/FSharp.Control.TaskSeq.Test/TaskSeq.SplitAt.Tests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -95,3 +95,77 @@ module Immutable =
let combined = Array.append prefix restArr
combined |> should equal data
}

module SideEffects =
[<Theory; ClassData(typeof<TestSideEffectTaskSeq>)>]
let ``TaskSeq-splitAt prefix gets first n elements from side-effect seq`` variant = task {
let ts = Gen.getSeqWithSideEffect variant
let! prefix, _ = TaskSeq.splitAt 3 ts
prefix |> should equal [| 1; 2; 3 |]
}

[<Theory; ClassData(typeof<TestSideEffectTaskSeq>)>]
let ``TaskSeq-splitAt rest yields remaining elements from side-effect seq`` variant = task {
let ts = Gen.getSeqWithSideEffect variant
let! _, rest = TaskSeq.splitAt 3 ts
let! restArr = TaskSeq.toArrayAsync rest
restArr |> should equal [| 4..10 |]
}

[<Theory; ClassData(typeof<TestSideEffectTaskSeq>)>]
let ``TaskSeq-splitAt prefix and rest together cover all elements of side-effect seq`` variant = task {
let ts = Gen.getSeqWithSideEffect variant
let! prefix, rest = TaskSeq.splitAt 5 ts
let! restArr = TaskSeq.toArrayAsync rest
let combined = Array.append prefix restArr
combined |> should equal [| 1..10 |]
}

[<Fact>]
let ``TaskSeq-splitAt rest is lazy: side effects in rest not triggered until consumed`` () = task {
let mutable restSideEffectCount = 0

let ts = taskSeq {
yield 1
yield 2
yield 3

// These yields are in the "rest" portion
restSideEffectCount <- restSideEffectCount + 1
yield 4
restSideEffectCount <- restSideEffectCount + 1
yield 5
}

let! _prefix, rest = TaskSeq.splitAt 3 ts
// rest has NOT been consumed yet; the side effects in it should not have fired
restSideEffectCount |> should equal 0

// Now consume rest
let! restArr = TaskSeq.toArrayAsync rest
restArr |> should equal [| 4; 5 |]
restSideEffectCount |> should equal 2
}

[<Fact>]
let ``TaskSeq-splitAt second evaluation of side-effect seq yields next batch`` () = task {
let mutable i = 0

let ts = taskSeq {
for _ = 1 to 10 do
i <- i + 1
yield i
}

// First split
let! prefix1, rest1 = TaskSeq.splitAt 4 ts
let! restArr1 = TaskSeq.toArrayAsync rest1
prefix1 |> should equal [| 1; 2; 3; 4 |]
restArr1 |> should equal [| 5..10 |]

// Second split of the same 'ts' uses the same 'i' capture; i is now 10
let! prefix2, rest2 = TaskSeq.splitAt 4 ts
let! restArr2 = TaskSeq.toArrayAsync rest2
prefix2 |> should equal [| 11; 12; 13; 14 |]
restArr2 |> should equal [| 15..20 |]
}
83 changes: 83 additions & 0 deletions src/FSharp.Control.TaskSeq.Test/TaskSeq.ZipWith.Tests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -172,3 +172,86 @@ module Immutable =

viaZipWith |> should equal viaZipMap
}

module SideEffects =
[<Theory; ClassData(typeof<TestSideEffectTaskSeq>)>]
let ``TaskSeq-zipWith on two side-effect seqs combines elements correctly`` variant = task {
let s1 = Gen.getSeqWithSideEffect variant
let s2 = Gen.getSeqWithSideEffect variant

// Both sequences yield 1..10 on first iteration; side effects increment independently
let! result = TaskSeq.zipWith (+) s1 s2 |> TaskSeq.toArrayAsync

result
|> should equal (Array.init 10 (fun i -> (i + 1) + (i + 1)))
}

[<Theory; ClassData(typeof<TestSideEffectTaskSeq>)>]
let ``TaskSeq-zipWithAsync on two side-effect seqs combines elements correctly`` variant = task {
let s1 = Gen.getSeqWithSideEffect variant
let s2 = Gen.getSeqWithSideEffect variant

let! result =
TaskSeq.zipWithAsync (fun a b -> Task.fromResult (a * b)) s1 s2
|> TaskSeq.toArrayAsync

result
|> should equal (Array.init 10 (fun i -> (i + 1) * (i + 1)))
}

[<Fact>]
let ``TaskSeq-zipWith consumes both sequences one element at a time`` () = task {
let mutable count1 = 0
let mutable count2 = 0

let s1 = taskSeq {
for i in 1..5 do
count1 <- count1 + 1
yield i
}

let s2 = taskSeq {
for i in 10..14 do
count2 <- count2 + 1
yield i
}

let! result = TaskSeq.zipWith (+) s1 s2 |> TaskSeq.toArrayAsync
result |> should equal [| 11; 13; 15; 17; 19 |]
count1 |> should equal 5
count2 |> should equal 5
}

[<Fact>]
let ``TaskSeq-zipWith truncates at shorter side-effect seq, output is correct`` () = task {
let mutable longCount = 0

let short = taskSeq { yield! [ 1; 2 ] }

let long = taskSeq {
for i in 10..20 do
longCount <- longCount + 1
yield i
}

let! result = TaskSeq.zipWith (+) short long |> TaskSeq.toArrayAsync
result |> should equal [| 11; 13 |]
// The implementation reads one element from each sequence to check for stop condition,
// so the longer sequence is advanced one step beyond the last paired element.
longCount |> should be (greaterThanOrEqualTo 2)
longCount |> should be (lessThanOrEqualTo 3)
}

[<Theory; ClassData(typeof<TestSideEffectTaskSeq>)>]
let ``TaskSeq-zipWith3 on three side-effect seqs combines elements correctly`` variant = task {
let s1 = Gen.getSeqWithSideEffect variant
let s2 = Gen.getSeqWithSideEffect variant
let s3 = Gen.getSeqWithSideEffect variant

let! result =
TaskSeq.zipWith3 (fun a b c -> a + b + c) s1 s2 s3
|> TaskSeq.toArrayAsync

result
|> should equal (Array.init 10 (fun i -> 3 * (i + 1)))
}
Loading