-
Notifications
You must be signed in to change notification settings - Fork 11
Expand file tree
/
Copy pathTaskSeq.UpdateAt.Tests.fs
More file actions
257 lines (199 loc) · 7.73 KB
/
TaskSeq.UpdateAt.Tests.fs
File metadata and controls
257 lines (199 loc) · 7.73 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
module TaskSeq.Tests.UpdateAt
open System
open Xunit
open FsUnit.Xunit
open FSharp.Control
//
// TaskSeq.updateAt
//
exception SideEffectPastEnd of string
module EmptySeq =
[<Fact>]
let ``Null source is invalid`` () = assertNullArg <| fun () -> TaskSeq.updateAt 0 99 null
[<Theory; ClassData(typeof<TestEmptyVariants>)>]
let ``TaskSeq-updateAt(0) on empty input should throw ArgumentException`` variant =
fun () ->
Gen.getEmptyVariant variant
|> TaskSeq.updateAt 0 42
|> consumeTaskSeq
|> should throwAsyncExact typeof<ArgumentException>
[<Fact>]
let ``TaskSeq-updateAt(-1) should throw ArgumentException on any input`` () =
fun () ->
TaskSeq.empty<int>
|> TaskSeq.updateAt -1 42
|> consumeTaskSeq
|> should throwAsyncExact typeof<ArgumentException>
fun () ->
TaskSeq.init 10 id
|> TaskSeq.updateAt -1 42
|> consumeTaskSeq
|> should throwAsyncExact typeof<ArgumentException>
[<Fact>]
let ``TaskSeq-updateAt(-1) should throw ArgumentException before awaiting`` () =
fun () ->
taskSeq {
do! longDelay ()
if false then
yield 0 // type inference
}
|> TaskSeq.updateAt -1 42
|> ignore // throws even without running the async. Bad coding, don't ignore a task!
// test without awaiting the async
|> should throw typeof<ArgumentException>
module Immutable =
[<Theory; ClassData(typeof<TestImmTaskSeq>)>]
let ``TaskSeq-updateAt can update at end of sequence`` variant = task {
do!
Gen.getSeqImmutable variant
|> TaskSeq.updateAt 9 99
|> verifyDigitsAsString "ABCDEFGHI£"
}
[<Theory; ClassData(typeof<TestImmTaskSeq>)>]
let ``TaskSeq-updateAt past end of sequence throws ArgumentException`` variant =
fun () ->
Gen.getSeqImmutable variant
|> TaskSeq.updateAt 10 99
|> consumeTaskSeq
|> should throwAsyncExact typeof<ArgumentException>
[<Theory; ClassData(typeof<TestImmTaskSeq>)>]
let ``TaskSeq-updateAt updates item immediately after the indexed position`` variant = task {
do!
Gen.getSeqImmutable variant
|> TaskSeq.updateAt 0 99
|> verifyDigitsAsString "£BCDEFGHIJ"
do!
Gen.getSeqImmutable variant
|> TaskSeq.updateAt 1 99
|> verifyDigitsAsString "A£CDEFGHIJ"
do!
Gen.getSeqImmutable variant
|> TaskSeq.updateAt 5 99
|> verifyDigitsAsString "ABCDE£GHIJ"
do!
Gen.getSeqImmutable variant
|> TaskSeq.updateAt 9 99
|> verifyDigitsAsString "ABCDEFGHI£"
}
[<Theory; ClassData(typeof<TestImmTaskSeq>)>]
let ``TaskSeq-updateAt can be repeated in a chain`` variant = task {
do!
Gen.getSeqImmutable variant
|> TaskSeq.updateAt 0 99
|> TaskSeq.updateAt 1 99
|> TaskSeq.updateAt 2 99
|> TaskSeq.updateAt 3 99
|> TaskSeq.updateAt 4 99
|> verifyDigitsAsString "£££££FGHIJ"
do!
Gen.getSeqImmutable variant
|> TaskSeq.updateAt 9 99
|> TaskSeq.updateAt 8 99
|> TaskSeq.updateAt 6 99
|> TaskSeq.updateAt 4 99
|> TaskSeq.updateAt 2 99
|> verifyDigitsAsString "AB£D£F£H££"
}
[<Fact>]
let ``TaskSeq-updateAt can be applied to an infinite task sequence`` () =
TaskSeq.initInfinite id
|> TaskSeq.updateAt 1_000_000 12345
|> TaskSeq.item 1_000_000
|> Task.map (should equal 12345)
[<Theory; ClassData(typeof<TestImmTaskSeq>)>]
let ``TaskSeq-updateAt throws when there are not enough elements`` variant =
fun () ->
TaskSeq.singleton 1
// update after 1
|> TaskSeq.updateAt 2 99
|> consumeTaskSeq
|> should throwAsyncExact typeof<ArgumentException>
fun () ->
Gen.getSeqImmutable variant
|> TaskSeq.updateAt 10 99
|> consumeTaskSeq
|> should throwAsyncExact typeof<ArgumentException>
fun () ->
Gen.getSeqImmutable variant
|> TaskSeq.updateAt 10_000_000 99
|> consumeTaskSeq
|> should throwAsyncExact typeof<ArgumentException>
module SideEffects =
// PoC test
[<Fact>]
let ``Seq-updateAt (poc-proof) will NOT execute side effect just after index`` () =
// NOTE: this test is for documentation purposes only, to show this behavior that is tested in this module
// this shows that Seq.updateAt executes no extra side effects.
let mutable x = 42
let items = seq {
yield x
x <- x + 1 // we are proving this gets executed with updateAt(0)
yield x * 2
}
items
|> Seq.updateAt 0 99
|> Seq.item 0 // put enumerator to updated item
|> ignore
x |> should equal 42 // one time side effect executed. QED
[<Fact>]
let ``TaskSeq-updateAt(0) will execute side effects at start of sequence`` () =
// NOTE: while not strictly necessary, this mirrors behavior of Seq.updateAt
let mutable x = 42 // for this test, the potential mutation should not actually occur
let items = taskSeq {
x <- x + 1 // this is executed even with updateAt(0)
yield x
yield x * 2
}
items
|> TaskSeq.updateAt 0 99
|> TaskSeq.item 0 // consume only the first item
|> Task.map (should equal 99)
|> Task.map (fun () -> x |> should equal 43) // the mutable was updated
[<Fact>]
let ``TaskSeq-updateAt will NOT execute last side effect when inserting past end`` () =
let mutable x = 42
let items = taskSeq {
yield x
yield x * 2
yield x * 4
x <- x + 1 // this is executed when inserting past last item
}
items
|> TaskSeq.updateAt 2 99
|> TaskSeq.item 2
|> Task.map (should equal 99)
|> Task.map (fun () -> x |> should equal 42) // as with 'seq', see first test in this block, we prove NO SIDE EFFECTS
[<Fact>]
let ``TaskSeq-updateAt will NOT execute side effect just before index`` () =
let mutable x = 42
let items = taskSeq {
yield x
x <- x + 1 // this is executed, even though we insert after the first item
yield x * 2
yield x * 4
}
items
|> TaskSeq.updateAt 0 99
|> TaskSeq.item 0
|> Task.map (should equal 99)
|> Task.map (fun () -> x |> should equal 42) // as with 'seq', see first test in this block, we prove NO SIDE EFFECTS
[<Fact>]
let ``TaskSeq-updateAt exception at update index is NOT thrown`` () =
taskSeq {
yield 1
yield! [ 2; 3 ]
do SideEffectPastEnd "at the end" |> raise // this is NOT raised
yield 4
}
|> TaskSeq.updateAt 2 99
|> TaskSeq.item 2
|> Task.map (should equal 99)
[<Fact>]
let ``TaskSeq-updateAt prove that an exception from the taskSeq is thrown instead of exception from function`` () =
let items = taskSeq {
yield 42
yield! [ 1; 2 ]
do SideEffectPastEnd "at the end" |> raise // we SHOULD get here before ArgumentException is raised
}
fun () -> items |> TaskSeq.updateAt 4 99 |> consumeTaskSeq // this would raise ArgumentException normally, but not now
|> should throwAsyncExact typeof<SideEffectPastEnd>