@@ -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+
1747module 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
9991module 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
118105module 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