Skip to content

Commit a311dbb

Browse files
authored
Merge pull request #378 from fsprojects/repo-assist/test-splitAt-zipWith-sideeffects-2026-04-04-821c94b644eb99e1
[Repo Assist] test: add SideEffects tests to TaskSeq.splitAt and TaskSeq.zipWith
2 parents 6d6505a + de6ab49 commit a311dbb

File tree

2 files changed

+157
-0
lines changed

2 files changed

+157
-0
lines changed

src/FSharp.Control.TaskSeq.Test/TaskSeq.SplitAt.Tests.fs

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,3 +95,77 @@ module Immutable =
9595
let combined = Array.append prefix restArr
9696
combined |> should equal data
9797
}
98+
99+
module SideEffects =
100+
[<Theory; ClassData(typeof<TestSideEffectTaskSeq>)>]
101+
let ``TaskSeq-splitAt prefix gets first n elements from side-effect seq`` variant = task {
102+
let ts = Gen.getSeqWithSideEffect variant
103+
let! prefix, _ = TaskSeq.splitAt 3 ts
104+
prefix |> should equal [| 1; 2; 3 |]
105+
}
106+
107+
[<Theory; ClassData(typeof<TestSideEffectTaskSeq>)>]
108+
let ``TaskSeq-splitAt rest yields remaining elements from side-effect seq`` variant = task {
109+
let ts = Gen.getSeqWithSideEffect variant
110+
let! _, rest = TaskSeq.splitAt 3 ts
111+
let! restArr = TaskSeq.toArrayAsync rest
112+
restArr |> should equal [| 4..10 |]
113+
}
114+
115+
[<Theory; ClassData(typeof<TestSideEffectTaskSeq>)>]
116+
let ``TaskSeq-splitAt prefix and rest together cover all elements of side-effect seq`` variant = task {
117+
let ts = Gen.getSeqWithSideEffect variant
118+
let! prefix, rest = TaskSeq.splitAt 5 ts
119+
let! restArr = TaskSeq.toArrayAsync rest
120+
let combined = Array.append prefix restArr
121+
combined |> should equal [| 1..10 |]
122+
}
123+
124+
[<Fact>]
125+
let ``TaskSeq-splitAt rest is lazy: side effects in rest not triggered until consumed`` () = task {
126+
let mutable restSideEffectCount = 0
127+
128+
let ts = taskSeq {
129+
yield 1
130+
yield 2
131+
yield 3
132+
133+
// These yields are in the "rest" portion
134+
restSideEffectCount <- restSideEffectCount + 1
135+
yield 4
136+
restSideEffectCount <- restSideEffectCount + 1
137+
yield 5
138+
}
139+
140+
let! _prefix, rest = TaskSeq.splitAt 3 ts
141+
// rest has NOT been consumed yet; the side effects in it should not have fired
142+
restSideEffectCount |> should equal 0
143+
144+
// Now consume rest
145+
let! restArr = TaskSeq.toArrayAsync rest
146+
restArr |> should equal [| 4; 5 |]
147+
restSideEffectCount |> should equal 2
148+
}
149+
150+
[<Fact>]
151+
let ``TaskSeq-splitAt second evaluation of side-effect seq yields next batch`` () = task {
152+
let mutable i = 0
153+
154+
let ts = taskSeq {
155+
for _ = 1 to 10 do
156+
i <- i + 1
157+
yield i
158+
}
159+
160+
// First split
161+
let! prefix1, rest1 = TaskSeq.splitAt 4 ts
162+
let! restArr1 = TaskSeq.toArrayAsync rest1
163+
prefix1 |> should equal [| 1; 2; 3; 4 |]
164+
restArr1 |> should equal [| 5..10 |]
165+
166+
// Second split of the same 'ts' uses the same 'i' capture; i is now 10
167+
let! prefix2, rest2 = TaskSeq.splitAt 4 ts
168+
let! restArr2 = TaskSeq.toArrayAsync rest2
169+
prefix2 |> should equal [| 11; 12; 13; 14 |]
170+
restArr2 |> should equal [| 15..20 |]
171+
}

src/FSharp.Control.TaskSeq.Test/TaskSeq.ZipWith.Tests.fs

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,3 +172,86 @@ module Immutable =
172172

173173
viaZipWith |> should equal viaZipMap
174174
}
175+
176+
module SideEffects =
177+
[<Theory; ClassData(typeof<TestSideEffectTaskSeq>)>]
178+
let ``TaskSeq-zipWith on two side-effect seqs combines elements correctly`` variant = task {
179+
let s1 = Gen.getSeqWithSideEffect variant
180+
let s2 = Gen.getSeqWithSideEffect variant
181+
182+
// Both sequences yield 1..10 on first iteration; side effects increment independently
183+
let! result = TaskSeq.zipWith (+) s1 s2 |> TaskSeq.toArrayAsync
184+
185+
result
186+
|> should equal (Array.init 10 (fun i -> (i + 1) + (i + 1)))
187+
}
188+
189+
[<Theory; ClassData(typeof<TestSideEffectTaskSeq>)>]
190+
let ``TaskSeq-zipWithAsync on two side-effect seqs combines elements correctly`` variant = task {
191+
let s1 = Gen.getSeqWithSideEffect variant
192+
let s2 = Gen.getSeqWithSideEffect variant
193+
194+
let! result =
195+
TaskSeq.zipWithAsync (fun a b -> Task.fromResult (a * b)) s1 s2
196+
|> TaskSeq.toArrayAsync
197+
198+
result
199+
|> should equal (Array.init 10 (fun i -> (i + 1) * (i + 1)))
200+
}
201+
202+
[<Fact>]
203+
let ``TaskSeq-zipWith consumes both sequences one element at a time`` () = task {
204+
let mutable count1 = 0
205+
let mutable count2 = 0
206+
207+
let s1 = taskSeq {
208+
for i in 1..5 do
209+
count1 <- count1 + 1
210+
yield i
211+
}
212+
213+
let s2 = taskSeq {
214+
for i in 10..14 do
215+
count2 <- count2 + 1
216+
yield i
217+
}
218+
219+
let! result = TaskSeq.zipWith (+) s1 s2 |> TaskSeq.toArrayAsync
220+
result |> should equal [| 11; 13; 15; 17; 19 |]
221+
count1 |> should equal 5
222+
count2 |> should equal 5
223+
}
224+
225+
[<Fact>]
226+
let ``TaskSeq-zipWith truncates at shorter side-effect seq, output is correct`` () = task {
227+
let mutable longCount = 0
228+
229+
let short = taskSeq { yield! [ 1; 2 ] }
230+
231+
let long = taskSeq {
232+
for i in 10..20 do
233+
longCount <- longCount + 1
234+
yield i
235+
}
236+
237+
let! result = TaskSeq.zipWith (+) short long |> TaskSeq.toArrayAsync
238+
result |> should equal [| 11; 13 |]
239+
// The implementation reads one element from each sequence to check for stop condition,
240+
// so the longer sequence is advanced one step beyond the last paired element.
241+
longCount |> should be (greaterThanOrEqualTo 2)
242+
longCount |> should be (lessThanOrEqualTo 3)
243+
}
244+
245+
[<Theory; ClassData(typeof<TestSideEffectTaskSeq>)>]
246+
let ``TaskSeq-zipWith3 on three side-effect seqs combines elements correctly`` variant = task {
247+
let s1 = Gen.getSeqWithSideEffect variant
248+
let s2 = Gen.getSeqWithSideEffect variant
249+
let s3 = Gen.getSeqWithSideEffect variant
250+
251+
let! result =
252+
TaskSeq.zipWith3 (fun a b c -> a + b + c) s1 s2 s3
253+
|> TaskSeq.toArrayAsync
254+
255+
result
256+
|> should equal (Array.init 10 (fun i -> 3 * (i + 1)))
257+
}

0 commit comments

Comments
 (0)