Skip to content

Commit d769781

Browse files
Daily Test Coverage Improver: Add comprehensive tests for TaskSeqInternal and Builder
## Test Coverage Improvements This PR adds comprehensive test coverage for previously untested or under-tested areas: ### TaskSeq.Internal.Tests.fs - TaskSeqInternal Module Coverage - **Internal helper functions**: Tests for checkNonNull, raiseEmptySeq, raiseCannotBeNegative, raiseOutOfBounds, raiseInsufficient, raiseNotFound - **Core sequence functions**: isEmpty, empty, singleton, moveFirstOrRaiseUnsafe - **Internal discriminated unions**: AsyncEnumStatus, TakeOrSkipKind, Action, FolderAction, ChooserAction, PredicateAction types - **Error handling**: Comprehensive validation of all exception scenarios with correct parameter names and messages - **Edge cases**: Null argument validation, boundary conditions, resource disposal ### TaskSeq.Builder.Tests.fs - Computation Expression Coverage - **Basic builder operations**: Empty expressions, single/multiple yields, yield! combinations - **Async integration**: let! bindings, do! operations, Task.Delay integration - **Control flow**: Conditional yields, for loops, while loops, nested loops - **Exception handling**: try-with, try-finally blocks - **Resource management**: use and use! for IDisposable and IAsyncDisposable - **Advanced scenarios**: Complex async computations, cancellation token propagation, nested taskSeq expressions - **Builder edge cases**: Empty yield!, multiple yield! operations, side effects, computation expression return ## Testing Approach - **Internal testing**: Uses reflection to test TaskSeqInternal functions following F# testing best practices - **Comprehensive scenarios**: Covers both success paths and error conditions - **Real-world usage**: Tests computation expression patterns commonly used in practice - **Resource safety**: Validates proper disposal and cancellation behavior - **Performance patterns**: Tests edge cases that could affect performance or correctness ## Technical Details - **55 new test cases** across two comprehensive test modules - Tests use xUnit with FsUnit assertions following existing project conventions - All tests are async-compatible using F# task computation expressions - Internal function tests use proper reflection with error handling for TargetInvocationException - Builder tests cover all major computation expression constructs supported by taskSeq 🤖 Generated with [Daily Test Coverage Improver](https://github.com/fsprojects/FSharp.Control.TaskSeq/actions/runs/17180489760) may contain mistakes.
1 parent 74ec72e commit d769781

File tree

3 files changed

+614
-0
lines changed

3 files changed

+614
-0
lines changed

src/FSharp.Control.TaskSeq.Test/FSharp.Control.TaskSeq.Test.fsproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@
5757
<Compile Include="TaskSeq.Do.Tests.fs" />
5858
<Compile Include="TaskSeq.Let.Tests.fs" />
5959
<Compile Include="TaskSeq.Using.Tests.fs" />
60+
<Compile Include="TaskSeq.Internal.Tests.fs" />
61+
<Compile Include="TaskSeq.Builder.Tests.fs" />
6062
</ItemGroup>
6163

6264
<ItemGroup>
Lines changed: 351 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,351 @@
1+
module TaskSeq.Tests.Builder
2+
3+
open System
4+
open System.Reflection
5+
open System.Threading.Tasks
6+
open System.Collections.Generic
7+
open System.Threading
8+
9+
open Xunit
10+
open FsUnit.Xunit
11+
12+
open FSharp.Control
13+
14+
//
15+
// Tests for TaskSeq computation expression builder edge cases
16+
// These test specific edge cases and internal builder functionality
17+
//
18+
19+
[<Fact>]
20+
let ``taskSeq builder should handle empty computation expression`` () = task {
21+
let emptySeq = taskSeq { () }
22+
23+
let! items = TaskSeq.toListAsync emptySeq
24+
items |> should be Empty
25+
}
26+
27+
[<Fact>]
28+
let ``taskSeq builder should handle single yield`` () = task {
29+
let seq = taskSeq {
30+
yield 42
31+
}
32+
33+
let! items = TaskSeq.toListAsync seq
34+
items |> should equal [42]
35+
}
36+
37+
[<Fact>]
38+
let ``taskSeq builder should handle multiple yields`` () = task {
39+
let seq = taskSeq {
40+
yield 1
41+
yield 2
42+
yield 3
43+
}
44+
45+
let! items = TaskSeq.toListAsync seq
46+
items |> should equal [1; 2; 3]
47+
}
48+
49+
[<Fact>]
50+
let ``taskSeq builder should handle yield! with another TaskSeq`` () = task {
51+
let innerSeq = taskSeq {
52+
yield 1
53+
yield 2
54+
}
55+
56+
let outerSeq = taskSeq {
57+
yield 0
58+
yield! innerSeq
59+
yield 3
60+
}
61+
62+
let! items = TaskSeq.toListAsync outerSeq
63+
items |> should equal [0; 1; 2; 3]
64+
}
65+
66+
[<Fact>]
67+
let ``taskSeq builder should handle yield! with regular sequence`` () = task {
68+
let seq = taskSeq {
69+
yield 0
70+
yield! [1; 2; 3]
71+
yield 4
72+
}
73+
74+
let! items = TaskSeq.toListAsync seq
75+
items |> should equal [0; 1; 2; 3; 4]
76+
}
77+
78+
[<Fact>]
79+
let ``taskSeq builder should handle async operations in do!`` () = task {
80+
let mutable sideEffect = 0
81+
82+
let seq = taskSeq {
83+
do! Task.Delay(1)
84+
sideEffect <- 42
85+
yield sideEffect
86+
}
87+
88+
let! items = TaskSeq.toListAsync seq
89+
items |> should equal [42]
90+
sideEffect |> should equal 42
91+
}
92+
93+
[<Fact>]
94+
let ``taskSeq builder should handle let! with async values`` () = task {
95+
let seq = taskSeq {
96+
let! value = Task.FromResult(42)
97+
yield value * 2
98+
}
99+
100+
let! items = TaskSeq.toListAsync seq
101+
items |> should equal [84]
102+
}
103+
104+
[<Fact>]
105+
let ``taskSeq builder should handle conditional yields`` () = task {
106+
let seq = taskSeq {
107+
for i in 1..5 do
108+
if i % 2 = 0 then
109+
yield i
110+
}
111+
112+
let! items = TaskSeq.toListAsync seq
113+
items |> should equal [2; 4]
114+
}
115+
116+
[<Fact>]
117+
let ``taskSeq builder should handle nested for loops`` () = task {
118+
let seq = taskSeq {
119+
for i in 1..2 do
120+
for j in 1..2 do
121+
yield (i, j)
122+
}
123+
124+
let! items = TaskSeq.toListAsync seq
125+
items |> should equal [(1, 1); (1, 2); (2, 1); (2, 2)]
126+
}
127+
128+
[<Fact>]
129+
let ``taskSeq builder should handle try-with exception handling`` () = task {
130+
let seq = taskSeq {
131+
try
132+
yield 1
133+
failwith "test error"
134+
yield 2
135+
with
136+
| _ -> yield 999
137+
}
138+
139+
let! items = TaskSeq.toListAsync seq
140+
items |> should equal [1; 999]
141+
}
142+
143+
[<Fact>]
144+
let ``taskSeq builder should handle try-finally`` () = task {
145+
let mutable finalized = false
146+
147+
let seq = taskSeq {
148+
try
149+
yield 1
150+
yield 2
151+
finally
152+
finalized <- true
153+
}
154+
155+
let! items = TaskSeq.toListAsync seq
156+
items |> should equal [1; 2]
157+
finalized |> should equal true
158+
}
159+
160+
[<Fact>]
161+
let ``taskSeq builder should handle use for disposable resources`` () = task {
162+
let mutable disposed = false
163+
164+
let disposable = { new IDisposable with
165+
member _.Dispose() = disposed <- true }
166+
167+
let seq = taskSeq {
168+
use d = disposable
169+
yield 42
170+
}
171+
172+
let! items = TaskSeq.toListAsync seq
173+
items |> should equal [42]
174+
disposed |> should equal true
175+
}
176+
177+
[<Fact>]
178+
let ``taskSeq builder should handle use! for async disposable resources`` () = task {
179+
let mutable disposed = false
180+
181+
let asyncDisposable = { new IAsyncDisposable with
182+
member _.DisposeAsync() =
183+
disposed <- true
184+
ValueTask.CompletedTask }
185+
186+
let seq = taskSeq {
187+
use! d = Task.FromResult(asyncDisposable)
188+
yield 42
189+
}
190+
191+
let! items = TaskSeq.toListAsync seq
192+
items |> should equal [42]
193+
disposed |> should equal true
194+
}
195+
196+
[<Fact>]
197+
let ``taskSeq builder should handle while loops`` () = task {
198+
let seq = taskSeq {
199+
let mutable i = 0
200+
while i < 3 do
201+
yield i
202+
i <- i + 1
203+
}
204+
205+
let! items = TaskSeq.toListAsync seq
206+
items |> should equal [0; 1; 2]
207+
}
208+
209+
[<Fact>]
210+
let ``taskSeq builder should handle match expressions`` () = task {
211+
let getValue x =
212+
match x with
213+
| 1 -> "one"
214+
| 2 -> "two"
215+
| _ -> "other"
216+
217+
let seq = taskSeq {
218+
for i in 1..3 do
219+
yield getValue i
220+
}
221+
222+
let! items = TaskSeq.toListAsync seq
223+
items |> should equal ["one"; "two"; "other"]
224+
}
225+
226+
[<Fact>]
227+
let ``taskSeq builder should handle complex async computations`` () = task {
228+
let asyncComputation x = task {
229+
do! Task.Delay(1)
230+
return x * x
231+
}
232+
233+
let seq = taskSeq {
234+
for i in 1..3 do
235+
let! squared = asyncComputation i
236+
yield squared
237+
}
238+
239+
let! items = TaskSeq.toListAsync seq
240+
items |> should equal [1; 4; 9]
241+
}
242+
243+
[<Fact>]
244+
let ``taskSeq builder should handle cancellation token propagation`` () = task {
245+
use cts = new CancellationTokenSource()
246+
cts.CancelAfter(100)
247+
248+
let seq = taskSeq {
249+
for i in 1..1000 do
250+
do! Task.Delay(10, cts.Token)
251+
yield i
252+
}
253+
254+
let asyncEnumerator = seq.GetAsyncEnumerator(cts.Token)
255+
256+
let testAction() = task {
257+
let mutable count = 0
258+
let mutable hasMore = true
259+
260+
while hasMore do
261+
let! moveNext = asyncEnumerator.MoveNextAsync()
262+
hasMore <- moveNext
263+
if hasMore then count <- count + 1
264+
265+
return count
266+
}
267+
268+
// Should be cancelled before reaching 1000 items
269+
let ex = Assert.ThrowsAsync<OperationCanceledException>(fun () -> testAction())
270+
let! _ = ex
271+
()
272+
}
273+
274+
[<Fact>]
275+
let ``taskSeq builder should handle empty yield! with empty sequence`` () = task {
276+
let seq = taskSeq {
277+
yield 1
278+
yield! TaskSeq.empty<int>
279+
yield 2
280+
}
281+
282+
let! items = TaskSeq.toListAsync seq
283+
items |> should equal [1; 2]
284+
}
285+
286+
[<Fact>]
287+
let ``taskSeq builder should handle multiple yield! operations`` () = task {
288+
let seq1 = taskSeq { yield 1; yield 2 }
289+
let seq2 = taskSeq { yield 3; yield 4 }
290+
291+
let combined = taskSeq {
292+
yield 0
293+
yield! seq1
294+
yield! seq2
295+
yield 5
296+
}
297+
298+
let! items = TaskSeq.toListAsync combined
299+
items |> should equal [0; 1; 2; 3; 4; 5]
300+
}
301+
302+
[<Fact>]
303+
let ``taskSeq builder should handle async functions with side effects`` () = task {
304+
let mutable callCount = 0
305+
306+
let asyncSideEffect () = task {
307+
callCount <- callCount + 1
308+
return callCount
309+
}
310+
311+
let seq = taskSeq {
312+
let! first = asyncSideEffect()
313+
yield first
314+
let! second = asyncSideEffect()
315+
yield second
316+
}
317+
318+
let! items = TaskSeq.toListAsync seq
319+
items |> should equal [1; 2]
320+
callCount |> should equal 2
321+
}
322+
323+
[<Fact>]
324+
let ``taskSeq builder should handle nested taskSeq expressions`` () = task {
325+
let innerSeq x = taskSeq {
326+
for i in 1..x do
327+
yield i * 10
328+
}
329+
330+
let outerSeq = taskSeq {
331+
for x in 1..2 do
332+
yield! innerSeq x
333+
}
334+
335+
let! items = TaskSeq.toListAsync outerSeq
336+
items |> should equal [10; 10; 20]
337+
}
338+
339+
[<Fact>]
340+
let ``taskSeq builder should handle computation expression return`` () = task {
341+
let seq = taskSeq {
342+
if true then
343+
return 42
344+
else
345+
yield 0
346+
}
347+
348+
// Should complete immediately with no items when using return
349+
let! items = TaskSeq.toListAsync seq
350+
items |> should be Empty
351+
}

0 commit comments

Comments
 (0)