Skip to content

Commit 48f8bfa

Browse files
authored
Merge pull request #348 from fsprojects/repo-assist/improve-fold-tests-20260316-10feb1bd15349016
[Repo Assist] test: expand TaskSeq.fold tests with call-count, ordering, and edge case coverage
2 parents 6f32989 + 6560fde commit 48f8bfa

File tree

2 files changed

+176
-0
lines changed

2 files changed

+176
-0
lines changed

release-notes.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ Release notes:
55
- adds TaskSeq.distinctUntilChangedWith and TaskSeq.distinctUntilChangedWithAsync, #345
66
- adds TaskSeq.withCancellation, #167
77
- adds docs/ with fsdocs-based documentation site covering generating, transforming, consuming, combining and advanced operations
8+
- test: adds 70 new tests to TaskSeq.Fold.Tests.fs covering call-count assertions, folder-not-called-on-empty, ordering, null initial state, and fold/foldAsync equivalence
89

910
0.7.0
1011
- performance: TaskSeq.exists, existsAsync, contains no longer allocate an intermediate Option value

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

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,181 @@ module EmptySeq =
3939
alphabet |> should equal '_'
4040
}
4141

42+
[<Theory; ClassData(typeof<TestEmptyVariants>)>]
43+
let ``TaskSeq-fold does not call folder function when empty`` variant = task {
44+
let mutable called = false
45+
46+
let! _ =
47+
Gen.getEmptyVariant variant
48+
|> TaskSeq.fold
49+
(fun state _ ->
50+
called <- true
51+
state)
52+
0
53+
54+
called |> should be False
55+
}
56+
57+
[<Theory; ClassData(typeof<TestEmptyVariants>)>]
58+
let ``TaskSeq-foldAsync does not call folder function when empty`` variant = task {
59+
let mutable called = false
60+
61+
let! _ =
62+
Gen.getEmptyVariant variant
63+
|> TaskSeq.foldAsync
64+
(fun state _ -> task {
65+
called <- true
66+
return state
67+
})
68+
0
69+
70+
called |> should be False
71+
}
72+
73+
module Functionality =
74+
[<Fact>]
75+
let ``TaskSeq-fold calls folder exactly N times for N elements`` () = task {
76+
let mutable callCount = 0
77+
78+
let! _ =
79+
TaskSeq.ofList [ 1; 2; 3; 4; 5 ]
80+
|> TaskSeq.fold
81+
(fun acc item ->
82+
callCount <- callCount + 1
83+
acc + item)
84+
0
85+
86+
callCount |> should equal 5
87+
}
88+
89+
[<Fact>]
90+
let ``TaskSeq-foldAsync calls folder exactly N times for N elements`` () = task {
91+
let mutable callCount = 0
92+
93+
let! _ =
94+
TaskSeq.ofList [ 1; 2; 3; 4; 5 ]
95+
|> TaskSeq.foldAsync
96+
(fun acc item -> task {
97+
callCount <- callCount + 1
98+
return acc + item
99+
})
100+
0
101+
102+
callCount |> should equal 5
103+
}
104+
105+
[<Fact>]
106+
let ``TaskSeq-fold over singleton calls folder once`` () = task {
107+
let mutable callCount = 0
108+
109+
let! result =
110+
TaskSeq.singleton 42
111+
|> TaskSeq.fold
112+
(fun acc item ->
113+
callCount <- callCount + 1
114+
acc + item)
115+
0
116+
117+
result |> should equal 42
118+
callCount |> should equal 1
119+
}
120+
121+
[<Fact>]
122+
let ``TaskSeq-fold over two elements calls folder twice`` () = task {
123+
let mutable callCount = 0
124+
125+
let! result =
126+
taskSeq {
127+
yield 10
128+
yield 20
129+
}
130+
|> TaskSeq.fold
131+
(fun acc item ->
132+
callCount <- callCount + 1
133+
acc + item)
134+
0
135+
136+
result |> should equal 30
137+
callCount |> should equal 2
138+
}
139+
140+
[<Fact>]
141+
let ``TaskSeq-fold is left-associative: applies folder left-to-right`` () = task {
142+
// For non-commutative ops like string concat, order matters.
143+
// fold f s [a;b;c] = f (f (f s a) b) c
144+
let! result =
145+
TaskSeq.ofList [ "b"; "c"; "d" ]
146+
|> TaskSeq.fold (fun acc item -> acc + item) "a"
147+
148+
result |> should equal "abcd"
149+
}
150+
151+
[<Fact>]
152+
let ``TaskSeq-foldAsync is left-associative: applies folder left-to-right`` () = task {
153+
let! result =
154+
TaskSeq.ofList [ "b"; "c"; "d" ]
155+
|> TaskSeq.foldAsync (fun acc item -> task { return acc + item }) "a"
156+
157+
result |> should equal "abcd"
158+
}
159+
160+
[<Fact>]
161+
let ``TaskSeq-fold with null initial state works for reference types`` () = task {
162+
let! result =
163+
TaskSeq.ofList [ "hello"; " "; "world" ]
164+
|> TaskSeq.fold
165+
(fun acc item ->
166+
match acc with
167+
| null -> item
168+
| _ -> acc + item)
169+
null
170+
171+
result |> should equal "hello world"
172+
}
173+
174+
[<Fact>]
175+
let ``TaskSeq-foldAsync and fold return the same result for pure functions`` () = task {
176+
let input = [ 1..10 ]
177+
178+
let! syncResult =
179+
TaskSeq.ofList input
180+
|> TaskSeq.fold (fun acc item -> acc + item) 0
181+
182+
let! asyncResult =
183+
TaskSeq.ofList input
184+
|> TaskSeq.foldAsync (fun acc item -> task { return acc + item }) 0
185+
186+
syncResult |> should equal asyncResult
187+
}
188+
189+
[<Fact>]
190+
let ``TaskSeq-fold accumulates a list in correct order`` () = task {
191+
let! result =
192+
TaskSeq.ofList [ 1; 2; 3; 4; 5 ]
193+
|> TaskSeq.fold (fun acc item -> acc @ [ item ]) []
194+
195+
result |> should equal [ 1; 2; 3; 4; 5 ]
196+
}
197+
198+
[<Theory; ClassData(typeof<TestImmTaskSeq>)>]
199+
let ``TaskSeq-fold sum over immutable variants`` variant = task {
200+
// items are 1..10; sum = 55
201+
let! result =
202+
Gen.getSeqImmutable variant
203+
|> TaskSeq.fold (fun acc item -> acc + item) 0
204+
205+
result |> should equal 55
206+
}
207+
208+
[<Theory; ClassData(typeof<TestImmTaskSeq>)>]
209+
let ``TaskSeq-foldAsync sum over immutable variants`` variant = task {
210+
let! result =
211+
Gen.getSeqImmutable variant
212+
|> TaskSeq.foldAsync (fun acc item -> task { return acc + item }) 0
213+
214+
result |> should equal 55
215+
}
216+
42217
module Immutable =
43218
[<Theory; ClassData(typeof<TestImmTaskSeq>)>]
44219
let ``TaskSeq-fold folds with every item`` variant = task {

0 commit comments

Comments
 (0)