Skip to content

Commit c0c5e80

Browse files
committed
Add tests for TaskSeq.min|max|minBy|maxBy and async variants
1 parent fdb316a commit c0c5e80

File tree

2 files changed

+318
-0
lines changed

2 files changed

+318
-0
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
<Compile Include="TaskSeq.Last.Tests.fs" />
3333
<Compile Include="TaskSeq.Length.Tests.fs" />
3434
<Compile Include="TaskSeq.Map.Tests.fs" />
35+
<Compile Include="TaskSeq.MaxMin.Tests.fs" />
3536
<Compile Include="TaskSeq.OfXXX.Tests.fs" />
3637
<Compile Include="TaskSeq.Pick.Tests.fs" />
3738
<Compile Include="TaskSeq.Singleton.Tests.fs" />
Lines changed: 317 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,317 @@
1+
module TaskSeq.Tests.MaxMin
2+
3+
open System
4+
5+
open Xunit
6+
open FsUnit.Xunit
7+
8+
open FSharp.Control
9+
10+
//
11+
// TaskSeq.max
12+
// TaskSeq.min
13+
// TaskSeq.maxBy
14+
// TaskSeq.minBy
15+
// TaskSeq.maxByAsync
16+
// TaskSeq.minByAsync
17+
//
18+
19+
type MinMax =
20+
| Max = 0
21+
| Min = 1
22+
| MaxBy = 2
23+
| MinBy = 3
24+
| MaxByAsync = 4
25+
| MinByAsync = 5
26+
27+
module MinMax =
28+
let getFunction =
29+
function
30+
| MinMax.Max -> TaskSeq.max
31+
| MinMax.Min -> TaskSeq.min
32+
| MinMax.MaxBy -> TaskSeq.maxBy id
33+
| MinMax.MinBy -> TaskSeq.minBy id
34+
| MinMax.MaxByAsync -> TaskSeq.maxByAsync Task.fromResult
35+
| MinMax.MinByAsync -> TaskSeq.minByAsync Task.fromResult
36+
| _ -> failwith "impossible"
37+
38+
let getByFunction =
39+
function
40+
| MinMax.MaxBy -> TaskSeq.maxBy
41+
| MinMax.MinBy -> TaskSeq.minBy
42+
| MinMax.MaxByAsync -> fun by -> TaskSeq.maxByAsync (by >> Task.fromResult)
43+
| MinMax.MinByAsync -> fun by -> TaskSeq.minByAsync (by >> Task.fromResult)
44+
| _ -> failwith "impossible"
45+
46+
let getAll () =
47+
[ MinMax.Max; MinMax.Min; MinMax.MaxBy; MinMax.MinBy; MinMax.MaxByAsync; MinMax.MinByAsync ]
48+
|> List.map getFunction
49+
50+
let getAllMin () =
51+
[ MinMax.Min; MinMax.MinBy; MinMax.MinByAsync ]
52+
|> List.map getFunction
53+
54+
let getAllMax () =
55+
[ MinMax.Max; MinMax.MaxBy; MinMax.MaxByAsync ]
56+
|> List.map getFunction
57+
58+
let isMin =
59+
function
60+
| MinMax.Min
61+
| MinMax.MinBy
62+
| MinMax.MinByAsync -> true
63+
| _ -> false
64+
65+
let isMax = isMin >> not
66+
67+
68+
type AllMinMaxFunctions() as this =
69+
inherit TheoryData<MinMax>()
70+
71+
do
72+
this.Add MinMax.Max
73+
this.Add MinMax.Min
74+
this.Add MinMax.MaxBy
75+
this.Add MinMax.MinBy
76+
this.Add MinMax.MaxByAsync
77+
this.Add MinMax.MinByAsync
78+
79+
type JustMin() as this =
80+
inherit TheoryData<MinMax>()
81+
82+
do
83+
this.Add MinMax.Min
84+
this.Add MinMax.MinBy
85+
this.Add MinMax.MinByAsync
86+
87+
type JustMax() as this =
88+
inherit TheoryData<MinMax>()
89+
90+
do
91+
this.Add MinMax.Max
92+
this.Add MinMax.MaxBy
93+
this.Add MinMax.MaxByAsync
94+
95+
type JustMinMaxBy() as this =
96+
inherit TheoryData<MinMax>()
97+
98+
do
99+
this.Add MinMax.MaxBy
100+
this.Add MinMax.MinBy
101+
this.Add MinMax.MaxByAsync
102+
this.Add MinMax.MinByAsync
103+
104+
module EmptySeq =
105+
[<Theory; ClassData(typeof<AllMinMaxFunctions>)>]
106+
let ``Null source raises ArgumentNullException`` (minMaxType: MinMax) =
107+
let minMax = MinMax.getFunction minMaxType
108+
109+
assertNullArg <| fun () -> minMax (null: TaskSeq<int>)
110+
111+
[<Theory; ClassData(typeof<TestEmptyVariants>)>]
112+
let ``Empty sequence raises ArgumentException`` variant =
113+
let test minMax =
114+
let data = Gen.getEmptyVariant variant
115+
116+
fun () -> minMax data |> Task.ignore
117+
|> should throwAsyncExact typeof<ArgumentException>
118+
119+
for minMax in MinMax.getAll () do
120+
test minMax
121+
122+
module Functionality =
123+
[<Fact>]
124+
let ``TaskSeq-max should return maximum`` () = task {
125+
let ts = [ 'A' .. 'Z' ] |> TaskSeq.ofList
126+
let! max = TaskSeq.max ts
127+
max |> should equal 'Z'
128+
}
129+
130+
[<Fact>]
131+
let ``TaskSeq-maxBy should return maximum of input, not the projection`` () = task {
132+
let ts = [ 'A' .. 'Z' ] |> TaskSeq.ofList
133+
let! max = TaskSeq.maxBy id ts
134+
max |> should equal 'Z'
135+
136+
let ts = [ 1..10 ] |> TaskSeq.ofList
137+
let! max = TaskSeq.maxBy (~-) ts
138+
max |> should equal 1 // as negated, -1 is highest, should not return projection, but original
139+
}
140+
141+
[<Fact>]
142+
let ``TaskSeq-maxByAsync should return maximum of input, not the projection`` () = task {
143+
let ts = [ 'A' .. 'Z' ] |> TaskSeq.ofList
144+
let! max = TaskSeq.maxByAsync Task.fromResult ts
145+
max |> should equal 'Z'
146+
147+
let ts = [ 1..10 ] |> TaskSeq.ofList
148+
let! max = TaskSeq.maxByAsync (fun x -> Task.fromResult -x) ts
149+
max |> should equal 1 // as negated, -1 is highest, should not return projection, but original
150+
}
151+
152+
[<Fact>]
153+
let ``TaskSeq-min should return minimum`` () = task {
154+
let ts = [ 'A' .. 'Z' ] |> TaskSeq.ofList
155+
let! min = TaskSeq.min ts
156+
min |> should equal 'A'
157+
}
158+
159+
[<Fact>]
160+
let ``TaskSeq-minBy should return minimum of input, not the projection`` () = task {
161+
let ts = [ 'A' .. 'Z' ] |> TaskSeq.ofList
162+
let! min = TaskSeq.minBy id ts
163+
min |> should equal 'A'
164+
165+
let ts = [ 1..10 ] |> TaskSeq.ofList
166+
let! min = TaskSeq.minBy (~-) ts
167+
min |> should equal 10 // as negated, -10 is lowest, should not return projection, but original
168+
}
169+
170+
[<Fact>]
171+
let ``TaskSeq-minByAsync should return minimum of input, not the projection`` () = task {
172+
let ts = [ 'A' .. 'Z' ] |> TaskSeq.ofList
173+
let! min = TaskSeq.minByAsync Task.fromResult ts
174+
min |> should equal 'A'
175+
176+
let ts = [ 1..10 ] |> TaskSeq.ofList
177+
let! min = TaskSeq.minByAsync (fun x -> Task.fromResult -x) ts
178+
min |> should equal 10 // as negated, 1 is highest, should not return projection, but original
179+
}
180+
181+
182+
module Immutable =
183+
[<Theory; ClassData(typeof<TestImmTaskSeq>)>]
184+
let ``TaskSeq-max, maxBy, maxByAsync returns maximum`` variant = task {
185+
let ts = Gen.getSeqImmutable variant
186+
187+
for max in MinMax.getAllMax () do
188+
let! max = max ts
189+
max |> should equal 10
190+
}
191+
192+
[<Theory; ClassData(typeof<TestImmTaskSeq>)>]
193+
let ``TaskSeq-min, minBy, minByAsync returns minimum`` variant = task {
194+
let ts = Gen.getSeqImmutable variant
195+
196+
for min in MinMax.getAllMin () do
197+
let! min = min ts
198+
min |> should equal 1
199+
}
200+
201+
[<Theory; ClassData(typeof<TestImmTaskSeq>)>]
202+
let ``TaskSeq-maxBy, maxByAsync returns maximum after projection`` variant = task {
203+
let ts = Gen.getSeqImmutable variant
204+
let! max = ts |> TaskSeq.maxBy (fun x -> -x)
205+
max |> should equal 1 // because -1 maps to item '1'
206+
}
207+
208+
209+
[<Theory; ClassData(typeof<TestImmTaskSeq>)>]
210+
let ``TaskSeq-minBy, minByAsync returns minimum after projection`` variant = task {
211+
let ts = Gen.getSeqImmutable variant
212+
let! min = ts |> TaskSeq.minBy (fun x -> -x)
213+
min |> should equal 10 // because -10 maps to item 10
214+
}
215+
216+
module SideSeffects =
217+
[<Fact>]
218+
let ``TaskSeq-max, maxBy, maxByAsync prove we execute after-effects`` () = task {
219+
let mutable i = 0
220+
221+
let ts = taskSeq {
222+
i <- i + 1
223+
i <- i + 1
224+
yield i // 2
225+
i <- i + 1
226+
yield i // 3
227+
yield i + 1 // 4
228+
i <- i + 1 // we should get here
229+
}
230+
231+
do! ts |> TaskSeq.max |> Task.map (should equal 4)
232+
do! ts |> TaskSeq.maxBy (~-) |> Task.map (should equal 6) // next iteration & negation "-6" maps to "6"
233+
234+
do!
235+
ts
236+
|> TaskSeq.maxByAsync Task.fromResult
237+
|> Task.map (should equal 12) // no negation
238+
239+
i |> should equal 12
240+
}
241+
242+
[<Fact>]
243+
let ``TaskSeq-min, minBy, minByAsync prove we execute after-effects test`` () = task {
244+
let mutable i = 0
245+
246+
let ts = taskSeq {
247+
i <- i + 1
248+
i <- i + 1
249+
yield i // 2
250+
i <- i + 1
251+
yield i // 3
252+
yield i + 1 // 4
253+
i <- i + 1 // we should get here
254+
}
255+
256+
do! ts |> TaskSeq.min |> Task.map (should equal 2)
257+
do! ts |> TaskSeq.minBy (~-) |> Task.map (should equal 8) // next iteration & negation
258+
259+
do!
260+
ts
261+
|> TaskSeq.minByAsync Task.fromResult
262+
|> Task.map (should equal 10) // no negation
263+
264+
i |> should equal 12
265+
}
266+
267+
268+
[<Theory; ClassData(typeof<JustMax>)>]
269+
let ``TaskSeq-max with sequence that changes length`` (minMax: MinMax) = task {
270+
let max = MinMax.getFunction minMax
271+
let mutable i = 0
272+
273+
let ts = taskSeq {
274+
i <- i + 10
275+
yield! [ 1..i ]
276+
}
277+
278+
do! max ts |> Task.map (should equal 10)
279+
do! max ts |> Task.map (should equal 20) // mutable state dangers!!
280+
do! max ts |> Task.map (should equal 30) // id
281+
}
282+
283+
[<Theory; ClassData(typeof<JustMin>)>]
284+
let ``TaskSeq-min with sequence that changes length`` (minMax: MinMax) = task {
285+
let min = MinMax.getFunction minMax
286+
let mutable i = 0
287+
288+
let ts = taskSeq {
289+
i <- i + 10
290+
yield! [ 1..i ]
291+
}
292+
293+
do! min ts |> Task.map (should equal 1)
294+
do! min ts |> Task.map (should equal 1) // same min after changing state
295+
do! min ts |> Task.map (should equal 1) // id
296+
}
297+
298+
[<Theory; ClassData(typeof<JustMinMaxBy>)>]
299+
let ``TaskSeq-minBy, maxBy with sequence that changes length`` (minMax: MinMax) = task {
300+
let mutable i = 0
301+
302+
let ts = taskSeq {
303+
i <- i + 10
304+
yield! [ 1..i ]
305+
}
306+
307+
let test minMaxFn v =
308+
if MinMax.isMin minMax then
309+
// this ensures the "min" version behaves like the "max" version
310+
minMaxFn (~-) ts |> Task.map (should equal v)
311+
else
312+
minMaxFn id ts |> Task.map (should equal v)
313+
314+
do! test (MinMax.getByFunction minMax) 10
315+
do! test (MinMax.getByFunction minMax) 20
316+
do! test (MinMax.getByFunction minMax) 30
317+
}

0 commit comments

Comments
 (0)