-
Notifications
You must be signed in to change notification settings - Fork 326
Expand file tree
/
Copy pathTestAsync.fs
More file actions
588 lines (520 loc) · 16.7 KB
/
Copy pathTestAsync.fs
File metadata and controls
588 lines (520 loc) · 16.7 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
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
module Fable.Tests.Async
open System
open Util.Testing
type DisposableAction(f) =
interface IDisposable with
member _.Dispose() = f()
type MyException(value) =
inherit Exception()
member _.Value: int = value
let asyncMap f a = async {
let! a = a
return f a
}
let sleepAndAssign token (res : Ref<bool>) =
Async.StartImmediate(async {
do! Async.Sleep 200
res.Value <- true
}, token)
let successWork: Async<string> = Async.FromContinuations(fun (onSuccess,_,_) -> onSuccess "success")
let errorWork: Async<string> = Async.FromContinuations(fun (_,onError,_) -> onError (exn "error"))
let cancelWork: Async<string> = Async.FromContinuations(fun (_,_,onCancel) ->
System.OperationCanceledException("cancelled") |> onCancel)
[<Fact>]
let ``test Simple async translates without exception`` () =
async { return () }
|> Async.StartImmediate
[<Fact>]
let ``test Async while binding works correctly`` () =
let mutable result = 0
async {
while result < 10 do
result <- result + 1
} |> Async.StartImmediate
equal result 10
[<Fact>]
let ``test Async for binding works correctly`` () =
let inputs = [|1; 2; 3|]
let mutable result = 0
async {
for inp in inputs do
result <- result + inp
} |> Async.StartImmediate
equal result 6
[<Fact>]
let ``test Async exceptions are handled correctly`` () =
let mutable result = 0
let f shouldThrow =
async {
try
if shouldThrow then failwith "boom!"
else result <- 12
with _ -> result <- 10
} |> Async.StartImmediate
result
f true + f false |> equal 22
[<Fact>]
let ``test Simple async is executed correctly`` () =
let mutable result = false
let x = async { return 99 }
async {
let! x = x
let y = 99
result <- x = y
}
|> Async.StartImmediate
equal result true
[<Fact>]
let ``test async use statements should dispose of resources when they go out of scope`` () =
let mutable isDisposed = false
let mutable step1ok = false
let mutable step2ok = false
let resource = async {
return new DisposableAction(fun () -> isDisposed <- true)
}
async {
use! r = resource
step1ok <- not isDisposed
}
//TODO: RunSynchronously would make more sense here but in JS I think this will be ok.
|> Async.StartImmediate
step2ok <- isDisposed
(step1ok && step2ok) |> equal true
[<Fact>]
let ``test Try ... with ... expressions inside async expressions work the same`` () =
let result = ref ""
let throw() : unit =
raise(exn "Boo!")
let append(x) =
result.Value <- result.Value + x
let innerAsync() =
async {
append "b"
try append "c"
throw()
append "1"
with _ -> append "d"
append "e"
}
async {
append "a"
try do! innerAsync()
with _ -> append "2"
append "f"
} |> Async.StartImmediate
equal "abcdef" result.Value
// Disable this test for dotnet as it's failing too many times in Appveyor
#if FABLE_COMPILER
[<Fact>]
let ``test async cancellation works`` () =
async {
let res1, res2, res3 = ref false, ref false, ref false
let tcs1 = new System.Threading.CancellationTokenSource(50)
let tcs2 = new System.Threading.CancellationTokenSource()
let tcs3 = new System.Threading.CancellationTokenSource()
sleepAndAssign tcs1.Token res1
sleepAndAssign tcs2.Token res2
sleepAndAssign tcs3.Token res3
tcs2.Cancel()
tcs3.CancelAfter(1000)
do! Async.Sleep 500
equal false res1.Value
equal false res2.Value
equal true res3.Value
} |> Async.StartImmediate
[<Fact>]
let ``test CancellationTokenSourceRegister works`` () =
async {
let mutable x = 0
let res1 = ref false
let tcs1 = new System.Threading.CancellationTokenSource(50)
let foo = tcs1.Token.Register(fun () ->
x <- x + 1)
sleepAndAssign tcs1.Token res1
do! Async.Sleep 500
equal false res1.Value
equal 1 x
} |> Async.StartImmediate
#endif
[<Fact>]
let ``test Async StartWithContinuations works`` () =
let res1, res2, res3 = ref "", ref "", ref ""
Async.StartWithContinuations(successWork, (fun x -> res1.Value <- x), ignore, ignore)
Async.StartWithContinuations(errorWork, ignore, (fun x -> res2.Value <- x.Message), ignore)
Async.StartWithContinuations(cancelWork, ignore, ignore, (fun x -> res3.Value <- x.Message))
equal "success" res1.Value
equal "error" res2.Value
equal "cancelled" res3.Value
[<Fact>]
let ``test Async.Catch works`` () =
let assign (res: Ref<string>) = function
| Choice1Of2 msg -> res.Value <- msg
| Choice2Of2 (ex: Exception) -> res.Value <- "ERROR: " + ex.Message
let res1 = ref ""
let res2 = ref ""
async {
let! x1 = successWork |> Async.Catch
assign res1 x1
let! x2 = errorWork |> Async.Catch
assign res2 x2
} |> Async.StartImmediate
equal "success" res1.Value
equal "ERROR: error" res2.Value
[<Fact>]
let ``test Async.Ignore works`` () =
let res = ref false
async {
do! successWork |> Async.Ignore
res.Value <- true
} |> Async.StartImmediate
equal true res.Value
[<Fact>]
let ``test Async.Parallel works`` () =
async {
let makeWork i =
async {
do! Async.Sleep 200
return i
}
let res: int[] ref = ref [||]
let works = [makeWork 1; makeWork 2; makeWork 3]
async {
let! x = Async.Parallel works
res.Value <- x
} |> Async.StartImmediate
do! Async.Sleep 500
res.Value |> Array.sum |> equal 6
} |> Async.RunSynchronously
[<Fact>]
let ``test Async.Parallel is lazy`` () =
async {
let mutable x = 0
let add i =
#if FABLE_COMPILER
x <- x + i
#else
System.Threading.Interlocked.Add(&x, i) |> ignore<int>
#endif
let a = Async.Parallel [
async { add 1 }
async { add 2 }
]
do! Async.Sleep 100
equal 0 x
let! _ = a
equal 3 x
} |> Async.RunSynchronously
[<Fact>]
let ``test Async.Sequential works`` () =
async {
let mutable _aggregate = 0
let makeWork i =
async {
// check that the individual work items run sequentially and not interleaved
_aggregate <- _aggregate + i
let copyOfI = _aggregate
do! Async.Sleep 100
equal copyOfI _aggregate
do! Async.Sleep 100
equal copyOfI _aggregate
return i
}
let works = [ for i in 1 .. 5 -> makeWork i ]
let now = DateTimeOffset.Now
let! result = Async.Sequential works
let ``then`` = DateTimeOffset.Now
let d = ``then`` - now
if d.TotalSeconds < 0.99 then
failwithf "expected sequential operations to take 1 second or more, but took %.3f" d.TotalSeconds
result |> equal [| 1 .. 5 |]
result |> Seq.sum |> equal _aggregate
} |> Async.RunSynchronously
[<Fact>]
let ``test Async.Sequential is lazy`` () =
async {
let mutable x = 0
let a = Async.Sequential [
async { x <- x + 1 }
async { x <- x + 2 }
]
do! Async.Sleep 100
equal 0 x
let! _ = a
equal 3 x
} |> Async.RunSynchronously
[<Fact>]
let ``test Interaction between Async and Task works`` () =
async {
let mutable res = false
do!
async { res <- true }
|> Async.StartAsTask
|> Async.AwaitTask
equal true res
} |> Async.RunSynchronously
#if FABLE_COMPILER
[<Fact>]
let ``test Tasks can be cancelled`` () =
async {
let mutable res = 0
let tcs = new System.Threading.CancellationTokenSource(50)
let work =
let work = async {
do! Async.Sleep 75
res <- -1
}
Async.StartAsTask(work, cancellationToken=tcs.Token) |> Async.AwaitTask
// behavior change: a cancelled task is now triggering the exception continuation instead of
// the cancellation continuation, see: https://github.com/Microsoft/visualfsharp/issues/1416
// also, System.OperationCanceledException will be changed to TaskCanceledException (not yet)
Async.StartWithContinuations(work, ignore, ignore, (fun _ -> res <- 1))
do! Async.Sleep 100
equal 1 res
} |> Async.StartImmediate
#endif
[<Fact>]
let ``test Deep recursion with async doesn't cause stack overflow`` () =
async {
let result = ref false
let rec trampolineTest (res: bool ref) i = async {
if i > 100000
then res.Value <- true
else return! trampolineTest res (i+1)
}
do! trampolineTest result 0
equal result.Value true
} |> Async.StartImmediate
[<Fact>]
let ``test Nested failure propagates in async expressions`` () =
async {
let data = ref ""
let f1 x =
async {
try
failwith "1"
return x
with
| e -> return! failwith ("2 " + e.Message)
}
let f2 x =
async {
try
return! f1 x
with
| e -> return! failwith ("3 " + e.Message)
}
let f() =
async {
try
let! y = f2 4
return ()
with
| e -> data.Value <- e.Message
}
|> Async.StartImmediate
f()
do! Async.Sleep 100
equal "3 2 1" data.Value
} |> Async.StartImmediate
[<Fact>]
let ``test Try .. finally expressions inside async expressions work`` () =
async {
let data = ref ""
async {
try data.Value <- data.Value + "1 "
finally data.Value <- data.Value + "2 "
} |> Async.StartImmediate
async {
try
try failwith "boom!"
finally data.Value <- data.Value + "3"
with _ -> ()
} |> Async.StartImmediate
do! Async.Sleep 100
equal "1 2 3" data.Value
} |> Async.StartImmediate
[<Fact>]
let ``test Final statement inside async expressions can throw`` () =
async {
let data = ref ""
let f() = async {
try data.Value <- data.Value + "1 "
finally failwith "boom!"
}
async {
try
do! f()
return ()
with
| e -> data.Value <- data.Value + e.Message
}
|> Async.StartImmediate
do! Async.Sleep 100
equal "1 boom!" data.Value
} |> Async.StartImmediate
[<Fact>]
let ``test Async.Bind propagates exceptions`` () = // See #724
async {
let task1 name = async {
// printfn "testing with %s" name
if name = "fail" then
failwith "Invalid access credentials"
return "Ok"
}
let task2 name = async {
// printfn "testing with %s" name
do! Async.Sleep 100 //difference between task1 and task2
if name = "fail" then
failwith "Invalid access credentials"
return "Ok"
}
let doWork name task =
let catch comp = async {
let! res = Async.Catch comp
return
match res with
| Choice1Of2 str -> str
| Choice2Of2 ex -> ex.Message
}
// printfn "doing work - %s" name
async {
let! a = task "work" |> catch
// printfn "work - %A" a
let! b = task "fail" |> catch
// printfn "fail - %A" b
return a, b
}
let! res1 = doWork "task1" task1
let! res2 = doWork "task2" task2
equal ("Ok", "Invalid access credentials") res1
equal ("Ok", "Invalid access credentials") res2
} |> Async.StartImmediate
[<Fact>]
let ``test Async.StartChild works`` () =
async {
let mutable x = ""
let taskA = async {
do! Async.Sleep 500
x <- x + "D"
return "E"
}
let taskB = async {
do! Async.Sleep 100
x <- x + "C"
return "F"
}
let! result1Async = taskA |> Async.StartChild // start first request but do not wait
let! result2Async = taskB |> Async.StartChild // start second request in parallel
x <- x + "AB"
let! result1 = result1Async
let! result2 = result2Async
x <- x + result1 + result2
equal x "ABCDEF"
} |> Async.StartImmediate
[<Fact>]
let ``test Async.StartChild applies timeout`` () =
async {
let mutable x = ""
let task = async {
x <- x + "A"
do! Async.Sleep 1_000
x <- x + "X" // Never hit
}
try
let! childTask = Async.StartChild (task, 200)
do! childTask
with
| :? TimeoutException ->
x <- x + "B"
x <- x + "C"
equal x "ABC"
} |> Async.StartImmediate
[<Fact>]
let ``test Async.StartChild with timeout completes when computation finishes before timeout`` () = // See #4481
async {
let fast = async { do! Async.Sleep 10 }
try
let! child = Async.StartChild(fast, 1_000)
do! child
equal true true // should reach here
with
| :? TimeoutException ->
failwith "should not time out"
} |> Async.StartImmediate
[<Fact>]
let ``test Unit arguments are erased`` () = // See #1832
let mutable token = 0
async {
let! res =
async.Return 5
|> asyncMap (fun x -> token <- x)
equal 5 token
res
} |> Async.StartImmediate
[<Fact>]
let ``test Can use custom exceptions in async workflows #2396`` () =
let workflow(): Async<unit> = async {
return MyException(7) |> raise
}
let parentWorkflow() =
async {
try
do! workflow()
return 100
with
| :? MyException as ex -> return ex.Value
}
async {
let! res = parentWorkflow()
equal 7 res
} |> Async.StartImmediate
[<Fact>]
let ``test Async.Sleep works correctly with TimeSpan argument`` () =
async {
let mutable executionOrder = ""
// Test that TimeSpan(0, 1, 0) means 1 minute = 60,000 milliseconds, not 600,000,000 milliseconds
let shortTask = async {
do! Async.Sleep(System.TimeSpan(0, 0, 0, 0, 100)) // 100 milliseconds
executionOrder <- executionOrder + "A"
}
let mediumTask = async {
do! Async.Sleep(System.TimeSpan(0, 0, 0, 0, 300)) // 300 milliseconds
executionOrder <- executionOrder + "B"
}
// Start both tasks in parallel
let! shortTaskAsync = shortTask |> Async.StartChild
let! mediumTaskAsync = mediumTask |> Async.StartChild
// Wait for both to complete
do! shortTaskAsync
do! mediumTaskAsync
// Verify execution order - shorter TimeSpan should complete first
equal "AB" executionOrder
// Test direct comparison with integer milliseconds
let startTime = System.DateTime.Now
do! Async.Sleep(System.TimeSpan(0, 0, 0, 0, 200)) // 200ms TimeSpan
let endTime = System.DateTime.Now
let elapsedMs = (endTime - startTime).TotalMilliseconds
// Should be approximately 200ms, not 2,000,000ms (which would be 2000 seconds)
// Allow some tolerance for timing variations
let isReasonableTime = elapsedMs >= 150.0 && elapsedMs <= 500.0
equal true isReasonableTime
} |> Async.StartImmediate
[<Fact>]
let ``test Async.AwaitEvent fires continuation when event is triggered`` () =
let ev = Event<int>()
Async.StartImmediate(async {
let! v = Async.AwaitEvent ev.Publish
equal 42 v
})
ev.Trigger(42)
[<Fact>]
let ``test Async.AwaitEvent with cancelAction invokes it on cancellation`` () =
let ev = Event<int>()
let cts = new System.Threading.CancellationTokenSource()
let mutable cancelCalled = false
Async.StartImmediate(async {
let! _ = Async.AwaitEvent(ev.Publish, fun () -> cancelCalled <- true)
()
}, cts.Token)
cts.Cancel()
equal true cancelCalled