Skip to content

Commit 25ad66e

Browse files
committed
Enhance TryWith tests for stack trace preservation and finalize checks
1 parent c4db4fa commit 25ad66e

File tree

2 files changed

+219
-13
lines changed

2 files changed

+219
-13
lines changed

tests/IcedTasks.Tests/Expect.fs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ module Task =
1616
}
1717
)
1818

19+
let runInThreadPool (f: unit -> Task<'T>) : Task<'T> = Task.Run<'T>(fun _ -> f ())
20+
1921
module TestHelpers =
2022
open System.Threading
2123

@@ -81,6 +83,10 @@ module Expect =
8183

8284
t2.IsAssignableFrom t1
8385

86+
let stringContainsNot (actual: string) (expectedSubstring: string) message =
87+
if actual.Contains(expectedSubstring) then
88+
failtestf "%s. Expected %s to NOT contain substring %s" message actual expectedSubstring
89+
8490
/// Expects the passed function to throw `'texn`.
8591
[<RequiresExplicitTypeArguments>]
8692
let throwsTAsync<'texn when 'texn :> exn> f message =

tests/IcedTasks.Tests/TaskTests_Net10.fs

Lines changed: 213 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,7 @@ module TaskTests_Net10 =
211211
]
212212

213213
testList "TryWith" [
214-
testCaseAsync "try with"
214+
testCaseAsync "try with syntax"
215215
<| async {
216216
let data = 42
217217

@@ -230,27 +230,227 @@ module TaskTests_Net10 =
230230

231231
Expect.equal actual data "TryWith should work"
232232
}
233+
234+
testList "StackTracePreservation" [
235+
testCaseAsync "threadpool"
236+
<| async {
237+
let data = 42
238+
239+
let func0 (x: int) : Task<int> =
240+
Task.Run<int>(fun () ->
241+
task {
242+
do! Task.Yield()
243+
return x + 1
244+
}
245+
)
246+
247+
let func1 (x: int) : Task<int> =
248+
Task.Run<int>(fun () ->
249+
task {
250+
do! Task.Yield()
251+
failwith "boom"
252+
return! func0 x
253+
}
254+
)
255+
256+
let func2 (x: int) =
257+
Task.Run<int>(fun () ->
258+
task {
259+
let y = x + 1
260+
do! Task.Yield()
261+
return! func1 y
262+
}
263+
)
264+
265+
let func3 (x: int) =
266+
Task.Run<int>(fun () ->
267+
task {
268+
let y = x + 1
269+
do! Task.Yield()
270+
return! func2 y
271+
}
272+
)
273+
274+
let mutable exn = None
275+
276+
let! actual =
277+
task {
278+
let data = data
279+
280+
try
281+
let! _ = func3 3
282+
()
283+
with e ->
284+
exn <- Some e
285+
286+
return data
287+
}
288+
|> Async.AwaitTask
289+
290+
291+
Expect.equal actual data "TryWith should work"
292+
Expect.isSome exn "Exception should have been caught"
293+
294+
exn
295+
|> Option.iter (fun e ->
296+
Expect.equal e.Message "boom" "Exception message should match"
297+
Expect.stringContains e.StackTrace "func3" ""
298+
Expect.stringContains e.StackTrace "func2" ""
299+
Expect.stringContains e.StackTrace "func1" ""
300+
Expect.stringContainsNot e.StackTrace "func0" ""
301+
)
302+
}
303+
304+
305+
testCaseAsync "yield"
306+
<| async {
307+
let data = 42
308+
309+
let func0 (x: int) : Task<int> =
310+
task {
311+
do! Task.Yield()
312+
return x + 1
313+
}
314+
315+
let func1 (x: int) : Task<int> =
316+
task {
317+
do! Task.Yield()
318+
failwith "boom"
319+
return! func0 x
320+
}
321+
322+
323+
let func2 (x: int) =
324+
task {
325+
let y = x + 1
326+
do! Task.Yield()
327+
return! func1 y
328+
}
329+
330+
331+
let func3 (x: int) =
332+
task {
333+
let y = x + 1
334+
do! Task.Yield()
335+
return! func2 y
336+
}
337+
338+
339+
let mutable exn = None
340+
341+
let! actual =
342+
task {
343+
let data = data
344+
345+
try
346+
let! _ = func3 3
347+
()
348+
with e ->
349+
exn <- Some e
350+
351+
return data
352+
}
353+
|> Async.AwaitTask
354+
355+
356+
Expect.equal actual data "TryWith should work"
357+
Expect.isSome exn "Exception should have been caught"
358+
359+
exn
360+
|> Option.iter (fun e ->
361+
Expect.equal e.Message "boom" "Exception message should match"
362+
Expect.stringContains e.StackTrace "func3" ""
363+
Expect.stringContains e.StackTrace "func2" ""
364+
Expect.stringContains e.StackTrace "func1" ""
365+
Expect.stringContainsNot e.StackTrace "func0" ""
366+
)
367+
}
368+
369+
370+
testCaseAsync "sync"
371+
<| async {
372+
let data = 42
373+
374+
let func0 (x: int) : Task<int> =
375+
task {
376+
do! Task.Yield()
377+
return x + 1
378+
}
379+
380+
let func1 (x: int) : Task<int> =
381+
382+
task {
383+
failwith "boom"
384+
return! func0 x
385+
}
386+
387+
let func2 (x: int) =
388+
task {
389+
let y = x + 1
390+
return! func1 y
391+
}
392+
393+
let func3 (x: int) =
394+
task {
395+
let y = x + 1
396+
return! func2 y
397+
}
398+
399+
let mutable exn = None
400+
401+
let! actual =
402+
task {
403+
let data = data
404+
405+
try
406+
let! _ = func3 3
407+
()
408+
with e ->
409+
exn <- Some e
410+
411+
return data
412+
}
413+
|> Async.AwaitTask
414+
415+
416+
Expect.equal actual data "TryWith should work"
417+
Expect.isSome exn "Exception should have been caught"
418+
419+
exn
420+
|> Option.iter (fun e ->
421+
Expect.equal e.Message "boom" "Exception message should match"
422+
Expect.stringContains e.StackTrace "func3" ""
423+
Expect.stringContains e.StackTrace "func2" ""
424+
Expect.stringContains e.StackTrace "func1" ""
425+
Expect.stringContainsNot e.StackTrace "func0" ""
426+
)
427+
}
428+
429+
]
233430
]
234431

235432
testList "TryFinally" [
236433
testCaseAsync "try finally"
237434
<| async {
238435
let data = 42
239436

437+
let mutable wasFinalized = false
438+
240439
let! actual =
241440
task {
242441
let data = data
243442

244443
try
245444
()
246445
finally
247-
()
446+
wasFinalized <- true
248447

249448
return data
250449
}
251450
|> Async.AwaitTask
252451

253452
Expect.equal actual data "TryFinally should work"
453+
Expect.isTrue wasFinalized "Finally block should have run"
254454
}
255455
]
256456

@@ -697,17 +897,17 @@ module TaskTests_Net10 =
697897

698898
let fakeWork id yieldTimes (l: ResizeArray<_>) =
699899
// TODO: Figure out why we need to wrap this in Task.Run to avoid deadlocks
700-
// Task.Run<DateTimeOffset>(fun () ->
701-
backgroundTask {
702-
// task {
703-
lock l (fun () -> l.Add(id))
704-
do! Task.yieldMany yieldTimes
705-
// do! Task.Delay(250)
706-
let dt = DateTimeOffset.UtcNow
707-
lock l (fun () -> l.Add(id))
708-
return dt
709-
}
710-
// )
900+
Task.Run<DateTimeOffset>(fun () ->
901+
task {
902+
// task {
903+
lock l (fun () -> l.Add(id))
904+
do! Task.yieldMany yieldTimes
905+
// do! Task.Delay(250)
906+
let dt = DateTimeOffset.UtcNow
907+
lock l (fun () -> l.Add(id))
908+
return dt
909+
}
910+
)
711911

712912
// Have earlier tasks take longer to complete
713913
// so we can see if they are sequenced or not

0 commit comments

Comments
 (0)