Skip to content

Commit d5d0570

Browse files
committed
Add tests for TaskSeq.skip and TaskSeq.drop
1 parent bd2d616 commit d5d0570

File tree

2 files changed

+234
-0
lines changed

2 files changed

+234
-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
@@ -35,6 +35,7 @@
3535
<Compile Include="TaskSeq.OfXXX.Tests.fs" />
3636
<Compile Include="TaskSeq.Pick.Tests.fs" />
3737
<Compile Include="TaskSeq.Singleton.Tests.fs" />
38+
<Compile Include="TaskSeq.Skip.Tests.fs" />
3839
<Compile Include="TaskSeq.Tail.Tests.fs" />
3940
<Compile Include="TaskSeq.Take.Tests.fs" />
4041
<Compile Include="TaskSeq.TakeWhile.Tests.fs" />
Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
module TaskSeq.Tests.Skip
2+
3+
open System
4+
5+
open Xunit
6+
open FsUnit.Xunit
7+
8+
open FSharp.Control
9+
10+
//
11+
// TaskSeq.skip
12+
// TaskSeq.drop
13+
//
14+
15+
exception SideEffectPastEnd of string
16+
17+
[<AutoOpen>]
18+
module With =
19+
/// Turns a sequence of numbers into a string, starting with A for '1'
20+
let verifyAsString expected =
21+
TaskSeq.map char
22+
>> TaskSeq.map ((+) '@')
23+
>> TaskSeq.toArrayAsync
24+
>> Task.map (String >> should equal expected)
25+
26+
module EmptySeq =
27+
[<Theory; ClassData(typeof<TestEmptyVariants>)>]
28+
let ``TaskSeq-skip(0) has no effect on empty input`` variant =
29+
// no `task` block needed
30+
Gen.getEmptyVariant variant |> TaskSeq.skip 0 |> verifyEmpty
31+
32+
[<Theory; ClassData(typeof<TestEmptyVariants>)>]
33+
let ``TaskSeq-skip(1) on empty input should throw InvalidOperation`` variant =
34+
fun () ->
35+
Gen.getEmptyVariant variant
36+
|> TaskSeq.skip 1
37+
|> consumeTaskSeq
38+
39+
|> should throwAsyncExact typeof<ArgumentException>
40+
41+
[<Fact>]
42+
let ``TaskSeq-skip(-1) should throw ArgumentException on any input`` () =
43+
fun () -> TaskSeq.empty<int> |> TaskSeq.skip -1 |> consumeTaskSeq
44+
|> should throwAsyncExact typeof<ArgumentException>
45+
46+
fun () -> TaskSeq.init 10 id |> TaskSeq.skip -1 |> consumeTaskSeq
47+
|> should throwAsyncExact typeof<ArgumentException>
48+
49+
[<Fact>]
50+
let ``TaskSeq-skip(-1) should throw ArgumentException before awaiting`` () =
51+
fun () ->
52+
taskSeq {
53+
do! longDelay ()
54+
55+
if false then
56+
yield 0 // type inference
57+
}
58+
|> TaskSeq.skip -1
59+
|> ignore // throws even without running the async. Bad coding, don't ignore a task!
60+
61+
|> should throw typeof<ArgumentException>
62+
63+
[<Theory; ClassData(typeof<TestEmptyVariants>)>]
64+
let ``TaskSeq-drop(0) has no effect on empty input`` variant = Gen.getEmptyVariant variant |> TaskSeq.drop 0 |> verifyEmpty
65+
66+
[<Theory; ClassData(typeof<TestEmptyVariants>)>]
67+
let ``TaskSeq-drop(99) does not throw on empty input`` variant =
68+
Gen.getEmptyVariant variant
69+
|> TaskSeq.drop 99
70+
|> verifyEmpty
71+
72+
73+
[<Fact>]
74+
let ``TaskSeq-drop(-1) should throw ArgumentException on any input`` () =
75+
fun () -> TaskSeq.empty<int> |> TaskSeq.drop -1 |> consumeTaskSeq
76+
|> should throwAsyncExact typeof<ArgumentException>
77+
78+
fun () -> TaskSeq.init 10 id |> TaskSeq.drop -1 |> consumeTaskSeq
79+
|> should throwAsyncExact typeof<ArgumentException>
80+
81+
[<Fact>]
82+
let ``TaskSeq-drop(-1) should throw ArgumentException before awaiting`` () =
83+
fun () ->
84+
taskSeq {
85+
do! longDelay ()
86+
87+
if false then
88+
yield 0 // type inference
89+
}
90+
|> TaskSeq.drop -1
91+
|> ignore // throws even without running the async. Bad coding, don't ignore a task!
92+
93+
|> should throw typeof<ArgumentException>
94+
95+
module Immutable =
96+
97+
[<Theory; ClassData(typeof<TestImmTaskSeq>)>]
98+
let ``TaskSeq-skip skips over exactly 'count' items`` variant = task {
99+
100+
do!
101+
Gen.getSeqImmutable variant
102+
|> TaskSeq.skip 0
103+
|> verifyAsString "ABCDEFGHIJ"
104+
105+
do!
106+
Gen.getSeqImmutable variant
107+
|> TaskSeq.skip 1
108+
|> verifyAsString "BCDEFGHIJ"
109+
110+
do!
111+
Gen.getSeqImmutable variant
112+
|> TaskSeq.skip 5
113+
|> verifyAsString "FGHIJ"
114+
115+
do!
116+
Gen.getSeqImmutable variant
117+
|> TaskSeq.skip 10
118+
|> verifyEmpty
119+
}
120+
121+
[<Theory; ClassData(typeof<TestImmTaskSeq>)>]
122+
let ``TaskSeq-skip throws when there are not enough elements`` variant =
123+
fun () -> TaskSeq.init 1 id |> TaskSeq.skip 2 |> consumeTaskSeq
124+
125+
|> should throwAsyncExact typeof<ArgumentException>
126+
127+
fun () ->
128+
Gen.getSeqImmutable variant
129+
|> TaskSeq.skip 11
130+
|> consumeTaskSeq
131+
132+
|> should throwAsyncExact typeof<ArgumentException>
133+
134+
fun () ->
135+
Gen.getSeqImmutable variant
136+
|> TaskSeq.skip 10_000_000
137+
|> consumeTaskSeq
138+
139+
|> should throwAsyncExact typeof<ArgumentException>
140+
141+
[<Theory; ClassData(typeof<TestImmTaskSeq>)>]
142+
let ``TaskSeq-drop skips over at least 'count' items`` variant = task {
143+
do!
144+
Gen.getSeqImmutable variant
145+
|> TaskSeq.drop 0
146+
|> verifyAsString "ABCDEFGHIJ"
147+
148+
do!
149+
Gen.getSeqImmutable variant
150+
|> TaskSeq.drop 1
151+
|> verifyAsString "BCDEFGHIJ"
152+
153+
do!
154+
Gen.getSeqImmutable variant
155+
|> TaskSeq.drop 5
156+
|> verifyAsString "FGHIJ"
157+
158+
do!
159+
Gen.getSeqImmutable variant
160+
|> TaskSeq.drop 10
161+
|> verifyEmpty
162+
163+
do!
164+
Gen.getSeqImmutable variant
165+
|> TaskSeq.drop 11 // no exception
166+
|> verifyEmpty
167+
168+
do!
169+
Gen.getSeqImmutable variant
170+
|> TaskSeq.drop 10_000_000 // no exception
171+
|> verifyEmpty
172+
}
173+
174+
module SideEffects =
175+
[<Theory; ClassData(typeof<TestSideEffectTaskSeq>)>]
176+
let ``TaskSeq-skip skips over enough items`` variant =
177+
Gen.getSeqWithSideEffect variant
178+
|> TaskSeq.skip 5
179+
|> verifyAsString "FGHIJ"
180+
181+
[<Theory; ClassData(typeof<TestSideEffectTaskSeq>)>]
182+
let ``TaskSeq-drop skips over enough items`` variant =
183+
Gen.getSeqWithSideEffect variant
184+
|> TaskSeq.drop 5
185+
|> verifyAsString "FGHIJ"
186+
187+
[<Fact>]
188+
let ``TaskSeq-skip prove we do not skip side effects`` () = task {
189+
let mutable x = 42 // for this test, the potential mutation should not actually occur
190+
191+
let items = taskSeq {
192+
yield x
193+
yield x * 2
194+
x <- x + 1 // we are proving we never get here
195+
}
196+
197+
let! first = items |> TaskSeq.skip 2 |> TaskSeq.toArrayAsync
198+
let! repeat = items |> TaskSeq.skip 2 |> TaskSeq.toArrayAsync
199+
200+
first |> should equal Array.empty<int>
201+
repeat |> should equal Array.empty<int>
202+
x |> should equal 44 // expect: side-effect is executed twice by now
203+
}
204+
205+
[<Fact>]
206+
let ``TaskSeq-skip prove that an exception from the taskseq is thrown instead of exception from function`` () =
207+
let items = taskSeq {
208+
yield 42
209+
yield! [ 1; 2 ]
210+
do SideEffectPastEnd "at the end" |> raise // we SHOULD get here before ArgumentException is raised
211+
}
212+
213+
fun () -> items |> TaskSeq.skip 4 |> consumeTaskSeq // this would raise ArgumentException normally
214+
|> should throwAsyncExact typeof<SideEffectPastEnd>
215+
216+
217+
[<Fact>]
218+
let ``TaskSeq-drop prove we do not skip side effects at the end`` () = task {
219+
let mutable x = 42 // for this test, the potential mutation should not actually occur
220+
221+
let items = taskSeq {
222+
yield x
223+
yield x * 2
224+
x <- x + 1 // we are proving we never get here
225+
}
226+
227+
let! first = items |> TaskSeq.drop 2 |> TaskSeq.toArrayAsync
228+
let! repeat = items |> TaskSeq.drop 2 |> TaskSeq.toArrayAsync
229+
230+
first |> should equal Array.empty<int>
231+
repeat |> should equal Array.empty<int>
232+
x |> should equal 44 // expect: side-effect at end is executed twice by now
233+
}

0 commit comments

Comments
 (0)