Skip to content

Commit 76645cd

Browse files
authored
Merge pull request #298 from fsprojects/repo-assist/test-choose-expand-2026-03-2bf13a937c3f20af
[Repo Assist] test: expand choose/chooseAsync and filter/filterAsync test coverage
2 parents e7dbf26 + 3b0db0a commit 76645cd

File tree

2 files changed

+160
-0
lines changed

2 files changed

+160
-0
lines changed

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

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,84 @@ module Immutable =
6666
String letters2 |> should equal "ABCDE"
6767
}
6868

69+
module Immutable2 =
70+
[<Theory; ClassData(typeof<TestImmTaskSeq>)>]
71+
let ``TaskSeq-choose returns all when chooser always returns Some`` variant = task {
72+
let ts = Gen.getSeqImmutable variant
73+
let! xs = ts |> TaskSeq.choose Some |> TaskSeq.toArrayAsync
74+
xs |> should equal [| 1..10 |]
75+
}
76+
77+
[<Theory; ClassData(typeof<TestImmTaskSeq>)>]
78+
let ``TaskSeq-chooseAsync returns all when chooser always returns Some`` variant = task {
79+
let ts = Gen.getSeqImmutable variant
80+
81+
let! xs =
82+
ts
83+
|> TaskSeq.chooseAsync (fun x -> task { return Some x })
84+
|> TaskSeq.toArrayAsync
85+
86+
xs |> should equal [| 1..10 |]
87+
}
88+
89+
[<Theory; ClassData(typeof<TestImmTaskSeq>)>]
90+
let ``TaskSeq-choose returns empty when chooser always returns None`` variant = task {
91+
let ts = Gen.getSeqImmutable variant
92+
93+
do! ts |> TaskSeq.choose (fun _ -> None) |> verifyEmpty
94+
}
95+
96+
[<Theory; ClassData(typeof<TestImmTaskSeq>)>]
97+
let ``TaskSeq-chooseAsync returns empty when chooser always returns None`` variant = task {
98+
let ts = Gen.getSeqImmutable variant
99+
100+
do!
101+
ts
102+
|> TaskSeq.chooseAsync (fun _ -> task { return None })
103+
|> verifyEmpty
104+
}
105+
106+
[<Fact>]
107+
let ``TaskSeq-choose with singleton sequence and Some chooser returns singleton`` () = task {
108+
let! xs =
109+
taskSeq { yield 42 }
110+
|> TaskSeq.choose (fun x -> Some(x * 2))
111+
|> TaskSeq.toListAsync
112+
113+
xs |> should equal [ 84 ]
114+
}
115+
116+
[<Fact>]
117+
let ``TaskSeq-choose with singleton sequence and None chooser returns empty`` () =
118+
taskSeq { yield 42 }
119+
|> TaskSeq.choose (fun _ -> None)
120+
|> verifyEmpty
121+
122+
[<Fact>]
123+
let ``TaskSeq-choose can change the element type`` () = task {
124+
// choose maps int -> string option, verifying type-changing behavior
125+
let chooser n = if n % 2 = 0 then Some(sprintf "even-%d" n) else None
126+
127+
let! xs =
128+
taskSeq { yield! [ 1..6 ] }
129+
|> TaskSeq.choose chooser
130+
|> TaskSeq.toListAsync
131+
132+
xs |> should equal [ "even-2"; "even-4"; "even-6" ]
133+
}
134+
135+
[<Fact>]
136+
let ``TaskSeq-chooseAsync can change the element type`` () = task {
137+
let chooser n = task { return if n % 2 = 0 then Some(sprintf "even-%d" n) else None }
138+
139+
let! xs =
140+
taskSeq { yield! [ 1..6 ] }
141+
|> TaskSeq.chooseAsync chooser
142+
|> TaskSeq.toListAsync
143+
144+
xs |> should equal [ "even-2"; "even-4"; "even-6" ]
145+
}
146+
69147
module SideEffects =
70148
[<Theory; ClassData(typeof<TestSideEffectTaskSeq>)>]
71149
let ``TaskSeq-choose applied multiple times`` variant = task {
@@ -94,3 +172,41 @@ module SideEffects =
94172
String lettersK |> should equal "KLMNO"
95173
String lettersU |> should equal "UVWXY"
96174
}
175+
176+
[<Fact>]
177+
let ``TaskSeq-choose evaluates each source element exactly once`` () = task {
178+
let mutable count = 0
179+
180+
let ts = taskSeq {
181+
for i in 1..5 do
182+
count <- count + 1
183+
yield i
184+
}
185+
186+
let! xs =
187+
ts
188+
|> TaskSeq.choose (fun x -> if x < 3 then Some x else None)
189+
|> TaskSeq.toListAsync
190+
191+
count |> should equal 5 // all 5 elements were visited
192+
xs |> should equal [ 1; 2 ]
193+
}
194+
195+
[<Fact>]
196+
let ``TaskSeq-chooseAsync evaluates each source element exactly once`` () = task {
197+
let mutable count = 0
198+
199+
let ts = taskSeq {
200+
for i in 1..5 do
201+
count <- count + 1
202+
yield i
203+
}
204+
205+
let! xs =
206+
ts
207+
|> TaskSeq.chooseAsync (fun x -> task { return if x < 3 then Some x else None })
208+
|> TaskSeq.toListAsync
209+
210+
count |> should equal 5
211+
xs |> should equal [ 1; 2 ]
212+
}

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

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,50 @@ module Immutable =
8787

8888
}
8989

90+
module Immutable2 =
91+
[<Theory; ClassData(typeof<TestImmTaskSeq>)>]
92+
let ``TaskSeq-filter keeps all when predicate is always true`` variant =
93+
Gen.getSeqImmutable variant
94+
|> TaskSeq.filter (fun _ -> true)
95+
|> verify1To10
96+
97+
[<Theory; ClassData(typeof<TestImmTaskSeq>)>]
98+
let ``TaskSeq-filterAsync keeps all when predicate is always true`` variant =
99+
Gen.getSeqImmutable variant
100+
|> TaskSeq.filterAsync (fun _ -> Task.fromResult true)
101+
|> verify1To10
102+
103+
[<Theory; ClassData(typeof<TestImmTaskSeq>)>]
104+
let ``TaskSeq-filter returns empty when predicate is always false`` variant =
105+
Gen.getSeqImmutable variant
106+
|> TaskSeq.filter (fun _ -> false)
107+
|> verifyEmpty
108+
109+
[<Theory; ClassData(typeof<TestImmTaskSeq>)>]
110+
let ``TaskSeq-filterAsync returns empty when predicate is always false`` variant =
111+
Gen.getSeqImmutable variant
112+
|> TaskSeq.filterAsync (fun _ -> Task.fromResult false)
113+
|> verifyEmpty
114+
115+
[<Fact>]
116+
let ``TaskSeq-filter evaluates each element exactly once`` () = task {
117+
let mutable count = 0
118+
119+
let ts = taskSeq {
120+
for i in 1..5 do
121+
count <- count + 1
122+
yield i
123+
}
124+
125+
let! xs =
126+
ts
127+
|> TaskSeq.filter (fun x -> x % 2 = 0)
128+
|> TaskSeq.toListAsync
129+
130+
count |> should equal 5
131+
xs |> should equal [ 2; 4 ]
132+
}
133+
90134
module SideEffects =
91135
[<Theory; ClassData(typeof<TestSideEffectTaskSeq>)>]
92136
let ``TaskSeq-filter filters correctly`` variant = task {

0 commit comments

Comments
 (0)