Skip to content

Commit fe54054

Browse files
committed
Small refactoring of new tests, each original test is still there, but function-choice is now lifted
1 parent f1fe870 commit fe54054

File tree

2 files changed

+104
-115
lines changed

2 files changed

+104
-115
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
<Compile Include="TaskSeq.Except.Tests.fs" />
2121
<Compile Include="TaskSeq.Exists.Tests.fs" />
2222
<Compile Include="TaskSeq.Filter.Tests.fs" />
23-
<Compile Include="TaskSeq.TakeWhile.Tests.fs" />
2423
<Compile Include="TaskSeq.FindIndex.Tests.fs" />
2524
<Compile Include="TaskSeq.Find.Tests.fs" />
2625
<Compile Include="TaskSeq.Fold.Tests.fs" />
@@ -36,6 +35,7 @@
3635
<Compile Include="TaskSeq.OfXXX.Tests.fs" />
3736
<Compile Include="TaskSeq.Pick.Tests.fs" />
3837
<Compile Include="TaskSeq.Singleton.Tests.fs" />
38+
<Compile Include="TaskSeq.TakeWhile.Tests.fs" />
3939
<Compile Include="TaskSeq.Tail.Tests.fs" />
4040
<Compile Include="TaskSeq.ToXXX.Tests.fs" />
4141
<Compile Include="TaskSeq.Zip.Tests.fs" />

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

Lines changed: 103 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -14,156 +14,140 @@ open FSharp.Control
1414
// TaskSeq.takeWhileInclusiveAsync
1515
//
1616

17+
[<AutoOpen>]
18+
module With =
19+
/// The only real difference in semantics between the base and the *Inclusive variant lies in whether the final item is returned.
20+
/// NOTE the semantics are very clear on only propagating a single failing item in the inclusive case.
21+
let getFunction inclusive isAsync =
22+
match inclusive, isAsync with
23+
| false, false -> TaskSeq.takeWhile
24+
| false, true -> fun pred -> TaskSeq.takeWhileAsync (pred >> Task.fromResult)
25+
| true, false -> TaskSeq.takeWhileInclusive
26+
| true, true -> fun pred -> TaskSeq.takeWhileInclusiveAsync (pred >> Task.fromResult)
27+
28+
let verifyAsString expected =
29+
TaskSeq.map char
30+
>> TaskSeq.map ((+) '@')
31+
>> TaskSeq.toArrayAsync
32+
>> Task.map (String >> should equal expected)
33+
34+
/// This is the base condition as one would expect in actual code
35+
let inline cond x = x <> 6
36+
37+
/// For each of the tests below, we add a guard that will trigger if the predicate is passed items known to be beyond the
38+
/// first failing item in the known sequence (which is 1..10)
39+
let inline condWithGuard x =
40+
let res = cond x
41+
42+
if x > 6 then
43+
failwith "Test sequence should not be enumerated beyond the first item failing the predicate"
44+
45+
res
46+
1747
module EmptySeq =
1848
[<Theory; ClassData(typeof<TestEmptyVariants>)>]
1949
let ``TaskSeq-takeWhile has no effect`` variant =
2050
Gen.getEmptyVariant variant
2151
|> TaskSeq.takeWhile ((=) 12)
22-
|> TaskSeq.toListAsync
23-
|> Task.map (List.isEmpty >> should be True)
52+
|> verifyEmpty
2453

2554
[<Theory; ClassData(typeof<TestEmptyVariants>)>]
2655
let ``TaskSeq-takeWhileAsync has no effect`` variant =
2756
Gen.getEmptyVariant variant
2857
|> TaskSeq.takeWhileAsync (fun x -> task { return x = 12 })
29-
|> TaskSeq.toListAsync
30-
|> Task.map (List.isEmpty >> should be True)
31-
32-
// The primary requirement is that items after the item failing the predicate must be excluded
33-
module FiltersAfterFail =
34-
[<Theory; InlineData false; InlineData true>]
35-
let ``TaskSeq-takeWhile(Inclusive)? excludes all items after predicate fails`` inclusive =
36-
// The only real difference in semantics between the base and the *Inclusive variant lies in whether the final item is returned
37-
// NOTE the semantics are very clear on only propagating a single failing item in the inclusive case
38-
let f, expected =
39-
if inclusive then TaskSeq.takeWhileInclusive, "ABBC"
40-
else TaskSeq.takeWhile, "ABB"
41-
seq { 1; 2; 2; 3; 3; 2; 1 }
58+
|> verifyEmpty
59+
60+
module Other =
61+
[<Theory>]
62+
[<InlineData(false, false)>]
63+
[<InlineData(false, true)>]
64+
[<InlineData(true, false)>]
65+
[<InlineData(true, true)>]
66+
let ``TaskSeq-takeWhileXXX exclude all items after predicate fails`` (inclusive, isAsync) =
67+
let functionToTest = With.getFunction inclusive isAsync
68+
69+
[ 1; 2; 2; 3; 3; 2; 1 ]
4270
|> TaskSeq.ofSeq
43-
|> f (fun x -> x <= 2)
44-
|> TaskSeq.map char
45-
|> TaskSeq.map ((+) '@')
46-
|> TaskSeq.toArrayAsync
47-
|> Task.map (String >> should equal expected)
48-
49-
// Same as preceding test, just with Async functions
50-
[<Theory; InlineData false; InlineData true>]
51-
let ``TaskSeq-takeWhile(Inclusive)?Async excludes all items after after predicate fails`` inclusive =
52-
let f, expected =
53-
if inclusive then TaskSeq.takeWhileInclusiveAsync, "ABBC"
54-
else TaskSeq.takeWhileAsync, "ABB"
55-
taskSeq { 1; 2; 2; 3; 3; 2; 1 }
56-
|> f (fun x -> task { return x <= 2 })
57-
|> TaskSeq.map char
58-
|> TaskSeq.map ((+) '@')
59-
|> TaskSeq.toArrayAsync
60-
|> Task.map (String >> should equal expected)
61-
62-
// Covers the fact that it's not sufficient to merely exclude successor items - it's also critical that the enumeration terminates
63-
module StopsEnumeratingAfterFail =
64-
[<Theory; InlineData false; InlineData true>]
65-
let ``TaskSeq-takeWhile(Inclusive)? stops consuming after predicate fails`` inclusive =
66-
let f, expected =
67-
if inclusive then TaskSeq.takeWhileInclusive, "ABBC"
68-
else TaskSeq.takeWhile, "ABB"
69-
seq { 1; 2; 2; 3; 3; failwith "Too far" }
71+
|> functionToTest (fun x -> x <= 2)
72+
|> verifyAsString (if inclusive then "ABBC" else "ABB")
73+
74+
[<Theory>]
75+
[<InlineData(false, false)>]
76+
[<InlineData(false, true)>]
77+
[<InlineData(true, false)>]
78+
[<InlineData(true, true)>]
79+
let ``TaskSeq-takeWhileXXX stops consuming after predicate fails`` (inclusive, isAsync) =
80+
let functionToTest = With.getFunction inclusive isAsync
81+
82+
seq {
83+
yield! [ 1; 2; 2; 3; 3 ]
84+
yield failwith "Too far"
85+
}
7086
|> TaskSeq.ofSeq
71-
|> f (fun x -> x <= 2)
72-
|> TaskSeq.map char
73-
|> TaskSeq.map ((+) '@')
74-
|> TaskSeq.toArrayAsync
75-
|> Task.map (String >> should equal expected)
76-
77-
[<Theory; InlineData false; InlineData true>]
78-
let ``TaskSeq-takeWhile(Inclusive)?Async stops consuming after predicate fails`` inclusive =
79-
let f, expected =
80-
if inclusive then TaskSeq.takeWhileInclusiveAsync, "ABBC"
81-
else TaskSeq.takeWhileAsync, "ABB"
82-
taskSeq { 1; 2; 2; 3; 3; failwith "Too far" }
83-
|> f (fun x -> task { return x <= 2 })
84-
|> TaskSeq.map char
85-
|> TaskSeq.map ((+) '@')
86-
|> TaskSeq.toArrayAsync
87-
|> Task.map (String >> should equal expected)
88-
89-
/// This is the base condition as one would expect in actual code
90-
let inline cond x = x <> 6
91-
92-
/// For each of the tests below, we add a guard that will trigger if the predicate is passed items known to be beyond the
93-
/// first failing item in the known sequence (which is 1..10)
94-
let inline condWithGuard x =
95-
let res = cond x
96-
if x > 6 then failwith "Test sequence should not be enumerated beyond the first item failing the predicate"
97-
res
87+
|> functionToTest (fun x -> x <= 2)
88+
|> verifyAsString (if inclusive then "ABBC" else "ABB")
89+
9890

9991
module Immutable =
92+
10093
[<Theory; ClassData(typeof<TestImmTaskSeq>)>]
10194
let ``TaskSeq-takeWhile filters correctly`` variant =
10295
Gen.getSeqImmutable variant
10396
|> TaskSeq.takeWhile condWithGuard
104-
|> TaskSeq.map char
105-
|> TaskSeq.map ((+) '@')
106-
|> TaskSeq.toArrayAsync
107-
|> Task.map (String >> should equal "ABCDE")
97+
|> verifyAsString "ABCDE"
10898

10999
[<Theory; ClassData(typeof<TestImmTaskSeq>)>]
110100
let ``TaskSeq-takeWhileAsync filters correctly`` variant =
111101
Gen.getSeqImmutable variant
112102
|> TaskSeq.takeWhileAsync (fun x -> task { return condWithGuard x })
113-
|> TaskSeq.map char
114-
|> TaskSeq.map ((+) '@')
115-
|> TaskSeq.toArrayAsync
116-
|> Task.map (String >> should equal "ABCDE")
103+
|> verifyAsString "ABCDE"
117104

118105
module SideEffects =
119106
[<Theory; ClassData(typeof<TestSideEffectTaskSeq>)>]
120107
let ``TaskSeq-takeWhile filters correctly`` variant =
121108
Gen.getSeqWithSideEffect variant
122109
|> TaskSeq.takeWhile condWithGuard
123-
|> TaskSeq.map char
124-
|> TaskSeq.map ((+) '@')
125-
|> TaskSeq.toArrayAsync
126-
|> Task.map (String >> should equal "ABCDE")
110+
|> verifyAsString "ABCDE"
127111

128112
[<Theory; ClassData(typeof<TestSideEffectTaskSeq>)>]
129113
let ``TaskSeq-takeWhileAsync filters correctly`` variant =
130114
Gen.getSeqWithSideEffect variant
131115
|> TaskSeq.takeWhileAsync (fun x -> task { return condWithGuard x })
132-
|> TaskSeq.map char
133-
|> TaskSeq.map ((+) '@')
134-
|> TaskSeq.toArrayAsync
135-
|> Task.map (String >> should equal "ABCDE")
136-
137-
[<Theory; InlineData(false, false); InlineData(true, false); InlineData(false, true); InlineData(true, true)>]
138-
let ``TaskSeq-takeWhile(Inclusive)?(Async)? __special-case__ prove it does not read beyond the failing yield`` (inclusive, async) = task {
116+
|> verifyAsString "ABCDE"
117+
118+
[<Theory>]
119+
[<InlineData(false, false)>]
120+
[<InlineData(false, true)>]
121+
[<InlineData(true, false)>]
122+
[<InlineData(true, true)>]
123+
let ``TaskSeq-takeWhileXXX prove it does not read beyond the failing yield`` (inclusive, isAsync) = task {
139124
let mutable x = 42 // for this test, the potential mutation should not actually occur
125+
let functionToTest = getFunction inclusive isAsync ((=) 42)
140126

141127
let items = taskSeq {
142128
yield x // Always passes the test; always returned
143129
yield x * 2 // the failing item (which will also be yielded in the result when using *Inclusive)
144130
x <- x + 1 // we are proving we never get here
145131
}
146132

147-
let f =
148-
match inclusive, async with
149-
| false, false -> TaskSeq.takeWhile (fun x -> x = 42)
150-
| true, false -> TaskSeq.takeWhileInclusive (fun x -> x = 42)
151-
| false, true -> TaskSeq.takeWhileAsync (fun x -> task { return x = 42 })
152-
| true, true -> TaskSeq.takeWhileInclusiveAsync (fun x -> task { return x = 42 })
153-
154133
let expected = if inclusive then [| 42; 84 |] else [| 42 |]
155134

156-
let! first = items |> f |> TaskSeq.toArrayAsync
157-
let! repeat = items |> f |> TaskSeq.toArrayAsync
135+
let! first = items |> functionToTest |> TaskSeq.toArrayAsync
136+
let! repeat = items |> functionToTest |> TaskSeq.toArrayAsync
158137

159138
first |> should equal expected
160139
repeat |> should equal expected
161140
x |> should equal 42
162141
}
163142

164-
[<Theory; InlineData(false, false); InlineData(true, false); InlineData(false, true); InlineData(true, true)>]
165-
let ``TaskSeq-takeWhile(Inclusive)?(Async)? __special-case__ prove side effects are executed`` (inclusive, async) = task {
143+
[<Theory>]
144+
[<InlineData(false, false)>]
145+
[<InlineData(false, true)>]
146+
[<InlineData(true, false)>]
147+
[<InlineData(true, true)>]
148+
let ``TaskSeq-takeWhileXXX prove side effects are executed`` (inclusive, isAsync) = task {
166149
let mutable x = 41
150+
let functionToTest = getFunction inclusive isAsync ((>) 50)
167151

168152
let items = taskSeq {
169153
x <- x + 1
@@ -173,19 +157,12 @@ module SideEffects =
173157
x <- x + 200 // as previously proven, we should not trigger this
174158
}
175159

176-
let f =
177-
match inclusive, async with
178-
| false, false -> TaskSeq.takeWhile (fun x -> x < 50)
179-
| true, false -> TaskSeq.takeWhileInclusive (fun x -> x < 50)
180-
| false, true -> TaskSeq.takeWhileAsync (fun x -> task { return x < 50 })
181-
| true, true -> TaskSeq.takeWhileInclusiveAsync (fun x -> task { return x < 50 })
160+
let expectedFirst = if inclusive then [| 42; 44 * 2 |] else [| 42 |]
161+
let expectedRepeat = if inclusive then [| 45; 47 * 2 |] else [| 45 |]
182162

183-
let expectedFirst = if inclusive then [| 42; 44*2 |] else [| 42 |]
184-
let expectedRepeat = if inclusive then [| 45; 47*2 |] else [| 45 |]
185-
186-
let! first = items |> f |> TaskSeq.toArrayAsync
163+
let! first = items |> functionToTest |> TaskSeq.toArrayAsync
187164
x |> should equal 44
188-
let! repeat = items |> f |> TaskSeq.toArrayAsync
165+
let! repeat = items |> functionToTest |> TaskSeq.toArrayAsync
189166
x |> should equal 47
190167

191168
first |> should equal expectedFirst
@@ -196,24 +173,36 @@ module SideEffects =
196173
let ``TaskSeq-takeWhile consumes the prefix of a longer sequence, with mutation`` variant = task {
197174
let ts = Gen.getSeqWithSideEffect variant
198175

199-
let! first = TaskSeq.takeWhile (fun x -> x < 5) ts |> TaskSeq.toArrayAsync
176+
let! first =
177+
TaskSeq.takeWhile (fun x -> x < 5) ts
178+
|> TaskSeq.toArrayAsync
179+
200180
let expected = [| 1..4 |]
201181
first |> should equal expected
202182

203183
// side effect, reiterating causes it to resume from where we left it (minus the failing item)
204-
let! repeat = TaskSeq.takeWhile (fun x -> x < 5) ts |> TaskSeq.toArrayAsync
184+
let! repeat =
185+
TaskSeq.takeWhile (fun x -> x < 5) ts
186+
|> TaskSeq.toArrayAsync
187+
205188
repeat |> should not' (equal expected)
206189
}
207190

208191
[<Theory; ClassData(typeof<TestSideEffectTaskSeq>)>]
209192
let ``TaskSeq-takeWhileInclusiveAsync consumes the prefix for a longer sequence, with mutation`` variant = task {
210193
let ts = Gen.getSeqWithSideEffect variant
211194

212-
let! first = TaskSeq.takeWhileInclusiveAsync (fun x -> task { return x < 5 }) ts |> TaskSeq.toArrayAsync
195+
let! first =
196+
TaskSeq.takeWhileInclusiveAsync (fun x -> task { return x < 5 }) ts
197+
|> TaskSeq.toArrayAsync
198+
213199
let expected = [| 1..5 |]
214200
first |> should equal expected
215201

216202
// side effect, reiterating causes it to resume from where we left it (minus the failing item)
217-
let! repeat = TaskSeq.takeWhileInclusiveAsync (fun x -> task { return x < 5 }) ts |> TaskSeq.toArrayAsync
203+
let! repeat =
204+
TaskSeq.takeWhileInclusiveAsync (fun x -> task { return x < 5 }) ts
205+
|> TaskSeq.toArrayAsync
206+
218207
repeat |> should not' (equal expected)
219208
}

0 commit comments

Comments
 (0)