nim-chronos/tests/testfut.nim

1319 lines
34 KiB
Nim
Raw Normal View History

# Chronos Test Suite
# (c) Copyright 2018-Present
2018-05-22 23:28:16 +00:00
# Status Research & Development GmbH
#
# Licensed under either of
# Apache License, version 2.0, (LICENSE-APACHEv2)
# MIT license (LICENSE-MIT)
exception tracking (#166) * exception tracking This PR adds minimal exception tracking to chronos, moving the goalpost one step further. In particular, it becomes invalid to raise exceptions from `callSoon` callbacks: this is critical for writing correct error handling because there's no reasonable way that a user of chronos can possibly _reason_ about exceptions coming out of there: the event loop will be in an indeterminite state when the loop is executing an _random_ callback. As expected, there are several issues in the error handling of chronos: in particular, it will end up in an inconsistent internal state whenever the selector loop operations fail, because the internal state update functions are not written in an exception-safe way. This PR turns this into a Defect, which probably is not the optimal way of handling things - expect more work to be done here. Some API have no way of reporting back errors to callers - for example, when something fails in the accept loop, there's not much it can do, and no way to report it back to the user of the API - this has been fixed with the new accept flow - the old one should be deprecated. Finally, there is information loss in the API: in composite operations like `poll` and `waitFor` there's no way to differentiate internal errors from user-level errors originating from callbacks. * store `CatchableError` in future * annotate proc's with correct raises information * `selectors2` to avoid non-CatchableError IOSelectorsException * `$` should never raise * remove unnecessary gcsafe annotations * fix exceptions leaking out of timer waits * fix some imports * functions must signal raising the union of all exceptions across all platforms to enable cross-platform code * switch to unittest2 * add `selectors2` which supercedes the std library version and fixes several exception handling issues in there * fixes * docs, platform-independent eh specifiers for some functions * add feature flag for strict exception mode also bump version to 3.0.0 - _most_ existing code should be compatible with this version of exception handling but some things might need fixing - callbacks, existing raises specifications etc. * fix AsyncCheck for non-void T
2021-03-24 09:08:33 +00:00
import unittest2
import ../chronos
2018-05-22 23:28:16 +00:00
2019-10-24 13:01:57 +00:00
when defined(nimHasUsed): {.used.}
suite "Future[T] behavior test suite":
proc testFuture1(): Future[int] {.async.} =
await sleepAsync(0.milliseconds)
2018-05-22 23:28:16 +00:00
proc testFuture2(): Future[int] {.async.} =
return 1
2018-05-22 23:28:16 +00:00
proc testFuture3(): Future[int] {.async.} =
result = await testFuture2()
2018-05-22 23:28:16 +00:00
proc testFuture100(): Future[int] {.async.} =
await sleepAsync(100.milliseconds)
proc testFuture4(): Future[int] {.async.} =
## Test for not immediately completed future and timeout = -1
result = 0
try:
discard await wait(testFuture1(), InfiniteDuration)
result = 1
except:
result = 0
if result == 0:
return -1
## Test for immediately completed future and timeout = -1
result = 0
try:
discard await wait(testFuture2(), InfiniteDuration)
result = 2
except:
result = 0
if result == 0:
return -2
2019-03-15 00:54:16 +00:00
## Test for not immediately completed future and timeout = 0
result = 0
try:
discard await wait(testFuture1(), 0.milliseconds)
except AsyncTimeoutError:
result = 3
2019-03-15 00:54:16 +00:00
if result == 0:
return -3
2019-03-15 00:54:16 +00:00
## Test for immediately completed future and timeout = 0
result = 0
try:
discard await wait(testFuture2(), 0.milliseconds)
result = 4
except:
result = 0
2019-03-15 00:54:16 +00:00
if result == 0:
return -4
2019-03-15 00:54:16 +00:00
## Test for future which cannot be completed in timeout period
result = 0
try:
discard await wait(testFuture100(), 50.milliseconds)
except AsyncTimeoutError:
result = 5
if result == 0:
return -5
## Test for future which will be completed before timeout exceeded.
try:
discard await wait(testFuture100(), 500.milliseconds)
result = 6
except:
result = -6
2021-06-04 10:28:04 +00:00
test "Async undefined behavior (#7758) test":
var fut = testFuture1()
poll()
poll()
if not fut.finished:
poll()
2021-06-04 10:28:04 +00:00
check: fut.finished
2021-06-04 10:28:04 +00:00
test "Immediately completed asynchronous procedure test":
var fut = testFuture3()
2021-06-04 10:28:04 +00:00
check: fut.finished
2021-06-04 10:28:04 +00:00
test "Future[T] callbacks are invoked in reverse order (#7197) test":
var testResult = ""
var fut = testFuture1()
fut.addCallback proc(udata: pointer) =
testResult &= "1"
fut.addCallback proc(udata: pointer) =
testResult &= "2"
fut.addCallback proc(udata: pointer) =
testResult &= "3"
fut.addCallback proc(udata: pointer) =
testResult &= "4"
fut.addCallback proc(udata: pointer) =
testResult &= "5"
discard waitFor(fut)
poll()
2021-06-04 10:28:04 +00:00
check:
fut.finished
testResult == "12345"
test "Future[T] callbacks not changing order after removeCallback()":
var testResult = ""
var fut = testFuture1()
proc cb1(udata: pointer) =
testResult &= "1"
proc cb2(udata: pointer) =
testResult &= "2"
proc cb3(udata: pointer) =
testResult &= "3"
proc cb4(udata: pointer) =
testResult &= "4"
proc cb5(udata: pointer) =
testResult &= "5"
fut.addCallback cb1
fut.addCallback cb2
fut.addCallback cb3
fut.addCallback cb4
fut.addCallback cb5
fut.removeCallback cb3
discard waitFor(fut)
poll()
2021-06-04 10:28:04 +00:00
check:
fut.finished
testResult == "1245"
2021-06-04 10:28:04 +00:00
test "wait[T]() test":
check:
waitFor(testFuture4()) == 6
2021-06-04 10:28:04 +00:00
test "discard result Future[T] test":
var completedFutures = 0
proc client1() {.async.} =
await sleepAsync(100.milliseconds)
inc(completedFutures)
proc client2() {.async.} =
await sleepAsync(200.milliseconds)
inc(completedFutures)
proc client3() {.async.} =
await sleepAsync(300.milliseconds)
inc(completedFutures)
proc client4() {.async.} =
await sleepAsync(400.milliseconds)
inc(completedFutures)
proc client5() {.async.} =
await sleepAsync(500.milliseconds)
inc(completedFutures)
proc client1f() {.async.} =
await sleepAsync(100.milliseconds)
inc(completedFutures)
if true:
raise newException(ValueError, "")
proc client2f() {.async.} =
await sleepAsync(200.milliseconds)
inc(completedFutures)
if true:
raise newException(ValueError, "")
proc client3f() {.async.} =
await sleepAsync(300.milliseconds)
inc(completedFutures)
if true:
raise newException(ValueError, "")
proc client4f() {.async.} =
await sleepAsync(400.milliseconds)
inc(completedFutures)
if true:
raise newException(ValueError, "")
proc client5f() {.async.} =
await sleepAsync(500.milliseconds)
inc(completedFutures)
if true:
raise newException(ValueError, "")
discard client1()
discard client1f()
discard client2()
discard client2f()
discard client3()
discard client3f()
discard client4()
discard client4f()
discard client5()
discard client5f()
waitFor(sleepAsync(2000.milliseconds))
2021-06-04 10:28:04 +00:00
check:
completedFutures == 10
2021-06-04 10:28:04 +00:00
test "allFutures(zero) test":
var tseq = newSeq[Future[int]]()
var fut = allFutures(tseq)
2021-06-04 10:28:04 +00:00
check:
fut.finished
2021-06-04 10:28:04 +00:00
test "allFutures(varargs) test":
var completedFutures = 0
proc vlient1() {.async.} =
await sleepAsync(100.milliseconds)
inc(completedFutures)
proc vlient2() {.async.} =
await sleepAsync(200.milliseconds)
inc(completedFutures)
proc vlient3() {.async.} =
await sleepAsync(300.milliseconds)
inc(completedFutures)
proc vlient4() {.async.} =
await sleepAsync(400.milliseconds)
inc(completedFutures)
proc vlient5() {.async.} =
await sleepAsync(500.milliseconds)
inc(completedFutures)
proc vlient1f() {.async.} =
await sleepAsync(100.milliseconds)
inc(completedFutures)
if true:
raise newException(ValueError, "")
proc vlient2f() {.async.} =
await sleepAsync(100.milliseconds)
inc(completedFutures)
if true:
raise newException(ValueError, "")
proc vlient3f() {.async.} =
await sleepAsync(100.milliseconds)
inc(completedFutures)
if true:
raise newException(ValueError, "")
proc vlient4f() {.async.} =
await sleepAsync(100.milliseconds)
inc(completedFutures)
if true:
raise newException(ValueError, "")
proc vlient5f() {.async.} =
await sleepAsync(100.milliseconds)
inc(completedFutures)
if true:
raise newException(ValueError, "")
proc client1(): Future[int] {.async.} =
await sleepAsync(100.milliseconds)
inc(completedFutures)
result = 1
proc client2(): Future[int] {.async.} =
await sleepAsync(200.milliseconds)
inc(completedFutures)
result = 1
proc client3(): Future[int] {.async.} =
await sleepAsync(300.milliseconds)
inc(completedFutures)
result = 1
proc client4(): Future[int] {.async.} =
await sleepAsync(400.milliseconds)
inc(completedFutures)
result = 1
proc client5(): Future[int] {.async.} =
await sleepAsync(500.milliseconds)
inc(completedFutures)
result = 1
proc client1f(): Future[int] {.async.} =
await sleepAsync(100.milliseconds)
inc(completedFutures)
if true:
raise newException(ValueError, "")
proc client2f(): Future[int] {.async.} =
await sleepAsync(200.milliseconds)
inc(completedFutures)
if true:
raise newException(ValueError, "")
proc client3f(): Future[int] {.async.} =
await sleepAsync(300.milliseconds)
inc(completedFutures)
if true:
raise newException(ValueError, "")
proc client4f(): Future[int] {.async.} =
await sleepAsync(400.milliseconds)
inc(completedFutures)
if true:
raise newException(ValueError, "")
proc client5f(): Future[int] {.async.} =
await sleepAsync(500.milliseconds)
inc(completedFutures)
if true:
raise newException(ValueError, "")
waitFor(allFutures(vlient1(), vlient2(), vlient3(), vlient4(), vlient5()))
2021-06-04 10:28:04 +00:00
check: completedFutures == 5
completedFutures = 0
2021-06-04 10:28:04 +00:00
waitFor(allFutures(vlient1(), vlient1f(),
vlient2(), vlient2f(),
vlient3(), vlient3f(),
vlient4(), vlient4f(),
vlient5(), vlient5f()))
2021-06-04 10:28:04 +00:00
check: completedFutures == 10
completedFutures = 0
waitFor(allFutures(client1(), client2(), client3(), client4(), client5()))
2021-06-04 10:28:04 +00:00
check: completedFutures == 5
completedFutures = 0
waitFor(allFutures(client1(), client1f(),
client2(), client2f(),
client3(), client3f(),
client4(), client4f(),
client5(), client5f()))
2021-06-04 10:28:04 +00:00
check: completedFutures == 10
2021-06-04 10:28:04 +00:00
test "allFutures(varargs) test":
var completedFutures = 0
var vfutures = newSeq[Future[void]]()
var nfutures = newSeq[Future[int]]()
proc vlient1() {.async.} =
await sleepAsync(100.milliseconds)
inc(completedFutures)
proc vlient2() {.async.} =
await sleepAsync(200.milliseconds)
inc(completedFutures)
proc vlient3() {.async.} =
await sleepAsync(300.milliseconds)
inc(completedFutures)
proc vlient4() {.async.} =
await sleepAsync(400.milliseconds)
inc(completedFutures)
proc vlient5() {.async.} =
await sleepAsync(500.milliseconds)
inc(completedFutures)
proc vlient1f() {.async.} =
await sleepAsync(100.milliseconds)
inc(completedFutures)
if true:
raise newException(ValueError, "")
proc vlient2f() {.async.} =
await sleepAsync(100.milliseconds)
inc(completedFutures)
if true:
raise newException(ValueError, "")
proc vlient3f() {.async.} =
await sleepAsync(100.milliseconds)
inc(completedFutures)
if true:
raise newException(ValueError, "")
proc vlient4f() {.async.} =
await sleepAsync(100.milliseconds)
inc(completedFutures)
if true:
raise newException(ValueError, "")
proc vlient5f() {.async.} =
await sleepAsync(100.milliseconds)
inc(completedFutures)
if true:
raise newException(ValueError, "")
proc client1(): Future[int] {.async.} =
await sleepAsync(100.milliseconds)
inc(completedFutures)
result = 1
proc client2(): Future[int] {.async.} =
await sleepAsync(200.milliseconds)
inc(completedFutures)
result = 1
proc client3(): Future[int] {.async.} =
await sleepAsync(300.milliseconds)
inc(completedFutures)
result = 1
proc client4(): Future[int] {.async.} =
await sleepAsync(400.milliseconds)
inc(completedFutures)
result = 1
proc client5(): Future[int] {.async.} =
await sleepAsync(500.milliseconds)
inc(completedFutures)
result = 1
proc client1f(): Future[int] {.async.} =
await sleepAsync(100.milliseconds)
inc(completedFutures)
if true:
raise newException(ValueError, "")
proc client2f(): Future[int] {.async.} =
await sleepAsync(200.milliseconds)
inc(completedFutures)
if true:
raise newException(ValueError, "")
proc client3f(): Future[int] {.async.} =
await sleepAsync(300.milliseconds)
inc(completedFutures)
if true:
raise newException(ValueError, "")
proc client4f(): Future[int] {.async.} =
await sleepAsync(400.milliseconds)
inc(completedFutures)
if true:
raise newException(ValueError, "")
proc client5f(): Future[int] {.async.} =
await sleepAsync(500.milliseconds)
inc(completedFutures)
if true:
raise newException(ValueError, "")
vfutures.setLen(0)
for i in 0..<10:
vfutures.add(vlient1())
vfutures.add(vlient2())
vfutures.add(vlient3())
vfutures.add(vlient4())
vfutures.add(vlient5())
2019-03-15 00:54:16 +00:00
waitFor(allFutures(vfutures))
# 5 * 10 completed futures = 50
2021-06-04 10:28:04 +00:00
check: completedFutures == 50
completedFutures = 0
vfutures.setLen(0)
for i in 0..<10:
vfutures.add(vlient1())
vfutures.add(vlient1f())
vfutures.add(vlient2())
vfutures.add(vlient2f())
vfutures.add(vlient3())
vfutures.add(vlient3f())
vfutures.add(vlient4())
vfutures.add(vlient4f())
vfutures.add(vlient5())
vfutures.add(vlient5f())
waitFor(allFutures(vfutures))
# 10 * 10 completed futures = 100
2021-06-04 10:28:04 +00:00
check: completedFutures == 100
completedFutures = 0
nfutures.setLen(0)
for i in 0..<10:
nfutures.add(client1())
nfutures.add(client2())
nfutures.add(client3())
nfutures.add(client4())
nfutures.add(client5())
waitFor(allFutures(nfutures))
# 5 * 10 completed futures = 50
2021-06-04 10:28:04 +00:00
check: completedFutures == 50
completedFutures = 0
nfutures.setLen(0)
for i in 0..<10:
nfutures.add(client1())
nfutures.add(client1f())
nfutures.add(client2())
nfutures.add(client2f())
nfutures.add(client3())
nfutures.add(client3f())
nfutures.add(client4())
nfutures.add(client4f())
nfutures.add(client5())
nfutures.add(client5f())
waitFor(allFutures(nfutures))
# 10 * 10 completed futures = 100
2021-06-04 10:28:04 +00:00
check: completedFutures == 100
test "allFutures() already completed test":
proc client1(): Future[int] {.async.} =
result = 1
proc client2(): Future[int] {.async.} =
if true:
raise newException(ValueError, "")
var fut = allFutures(client1(), client2())
check:
fut.finished()
not(fut.failed())
2021-06-04 10:28:04 +00:00
test "allFinished() already completed test":
proc client1(): Future[int] {.async.} =
result = 1
proc client2(): Future[int] {.async.} =
if true:
raise newException(ValueError, "")
2021-06-04 10:28:04 +00:00
var fut = allFinished(client1(), client2())
check:
fut.finished()
not(fut.failed())
len(fut.read()) == 2
2021-06-04 10:28:04 +00:00
test "one(zero) test":
var tseq = newSeq[Future[int]]()
var fut = one(tseq)
2021-06-04 10:28:04 +00:00
check: fut.finished and fut.failed
2021-06-04 10:28:04 +00:00
test "one(varargs) test":
proc vlient1() {.async.} =
await sleepAsync(100.milliseconds)
proc vlient2() {.async.} =
await sleepAsync(200.milliseconds)
proc vlient3() {.async.} =
await sleepAsync(300.milliseconds)
proc client1(): Future[int] {.async.} =
await sleepAsync(100.milliseconds)
result = 10
proc client2(): Future[int] {.async.} =
await sleepAsync(200.milliseconds)
result = 20
proc client3(): Future[int] {.async.} =
await sleepAsync(300.milliseconds)
result = 30
var fut11 = vlient1()
var fut12 = vlient2()
var fut13 = vlient3()
var res1 = waitFor(one(fut11, fut12, fut13))
var fut21 = vlient2()
var fut22 = vlient1()
var fut23 = vlient3()
var res2 = waitFor(one(fut21, fut22, fut23))
var fut31 = vlient3()
var fut32 = vlient2()
var fut33 = vlient1()
var res3 = waitFor(one(fut31, fut32, fut33))
2021-06-04 10:28:04 +00:00
check:
fut11 == res1
fut22 == res2
fut33 == res3
var cut11 = client1()
var cut12 = client2()
var cut13 = client3()
var res4 = waitFor(one(cut11, cut12, cut13))
var cut21 = client2()
var cut22 = client1()
var cut23 = client3()
var res5 = waitFor(one(cut21, cut22, cut23))
var cut31 = client3()
var cut32 = client2()
var cut33 = client1()
var res6 = waitFor(one(cut31, cut32, cut33))
2021-06-04 10:28:04 +00:00
check:
cut11 == res4
cut22 == res5
cut33 == res6
2021-06-04 10:28:04 +00:00
test "one(seq) test":
proc vlient1() {.async.} =
await sleepAsync(100.milliseconds)
proc vlient2() {.async.} =
await sleepAsync(200.milliseconds)
proc vlient3() {.async.} =
await sleepAsync(300.milliseconds)
proc client1(): Future[int] {.async.} =
await sleepAsync(100.milliseconds)
result = 10
proc client2(): Future[int] {.async.} =
await sleepAsync(200.milliseconds)
result = 20
proc client3(): Future[int] {.async.} =
await sleepAsync(300.milliseconds)
result = 30
var v10 = vlient1()
var v11 = vlient2()
var v12 = vlient3()
var res1 = waitFor(one(@[v10, v11, v12]))
var v20 = vlient2()
var v21 = vlient1()
var v22 = vlient3()
var res2 = waitFor(one(@[v20, v21, v22]))
var v30 = vlient3()
var v31 = vlient2()
var v32 = vlient1()
var res3 = waitFor(one(@[v30, v31, v32]))
2021-06-04 10:28:04 +00:00
check:
res1 == v10
res2 == v21
res3 == v32
var c10 = client1()
var c11 = client2()
var c12 = client3()
var res4 = waitFor(one(@[c10, c11, c12]))
var c20 = client2()
var c21 = client1()
var c22 = client3()
var res5 = waitFor(one(@[c20, c21, c22]))
var c30 = client3()
var c31 = client2()
var c32 = client1()
var res6 = waitFor(one(@[c30, c31, c32]))
2021-06-04 10:28:04 +00:00
check:
res4 == c10
res5 == c21
res6 == c32
2021-06-04 10:28:04 +00:00
test "one(completed) test":
proc client1(): Future[int] {.async.} =
result = 1
proc client2(): Future[int] {.async.} =
if true:
raise newException(ValueError, "")
proc client3(): Future[int] {.async.} =
await sleepAsync(100.milliseconds)
result = 3
var f10 = client1()
var f20 = client2()
var f30 = client3()
var fut1 = one(f30, f10, f20)
var f11 = client1()
var f21 = client2()
var f31 = client3()
var fut2 = one(f31, f21, f11)
2021-06-04 10:28:04 +00:00
check:
fut1.finished()
not(fut1.failed())
fut1.read() == f10
fut2.finished()
not(fut2.failed())
fut2.read() == f21
proc waitForNeLocal[T](fut: Future[T]): Future[T] =
## **Blocks** the current thread until the specified future completes.
while not(fut.finished()):
poll()
result = fut
2021-06-04 10:28:04 +00:00
test "or() test":
proc client1() {.async.} =
await sleepAsync(200.milliseconds)
proc client2() {.async.} =
await sleepAsync(300.milliseconds)
proc client3() {.async.} =
await sleepAsync(100.milliseconds)
if true:
raise newException(ValueError, "")
proc client4() {.async.} =
await sleepAsync(400.milliseconds)
if true:
raise newException(KeyError, "")
var f1 = waitForNeLocal(client1() or client2())
var f2 = waitForNeLocal(client2() or client1())
var f3 = waitForNeLocal(client1() or client4())
var f4 = waitForNeLocal(client2() or client4())
var f5 = waitForNeLocal(client1() or client3())
var f6 = waitForNeLocal(client3() or client1())
var f7 = waitForNeLocal(client2() or client4())
var f8 = waitForNeLocal(client4() or client2())
var f9 = waitForNeLocal(client3() or client4())
var f10 = waitForNeLocal(client4() or client3())
2021-06-04 10:28:04 +00:00
check:
(f1.finished() and not(f1.failed()))
(f2.finished() and not(f2.failed()))
(f3.finished() and not(f3.failed()))
(f4.finished() and not(f4.failed()))
(f5.finished() and f5.failed())
(f6.finished() and f6.failed())
(f7.finished() and not(f7.failed()))
(f8.finished() and not(f8.failed()))
(f9.finished() and f9.failed())
(f10.finished() and f10.failed())
test "or() already completed test":
proc client1(): Future[int] {.async.} =
result = 1
proc client2(): Future[int] {.async.} =
if true:
raise newException(ValueError, "")
proc client3(): Future[int] {.async.} =
await sleepAsync(100.milliseconds)
result = 3
var f1 = client1() or client2()
var f2 = client1() or client3()
var f3 = client2() or client3()
var f4 = client2() or client1()
var f5 = client3() or client1()
var f6 = client3() or client2()
2021-06-04 10:28:04 +00:00
check:
(f1.finished() and not(f1.failed())) and
(f2.finished() and not(f2.failed())) and
(f3.finished() and f3.failed()) and
(f4.finished() and f4.failed()) and
(f5.finished() and not(f5.failed())) and
(f6.finished() and f6.failed())
2021-06-04 10:28:04 +00:00
test "cancel() async procedure test":
2019-06-26 12:36:01 +00:00
var completed = 0
proc client1() {.async.} =
await sleepAsync(1.seconds)
inc(completed)
proc client2() {.async.} =
await client1()
inc(completed)
proc client3() {.async.} =
await client2()
inc(completed)
proc client4() {.async.} =
await client3()
inc(completed)
var fut = client4()
fut.cancel()
# Future must not be cancelled immediately, because it has many nested
# futures.
2021-06-04 10:28:04 +00:00
check:
not fut.cancelled()
2019-06-26 12:36:01 +00:00
2021-06-04 10:28:04 +00:00
expect(CancelledError):
2019-06-26 12:36:01 +00:00
waitFor fut
2021-06-04 10:28:04 +00:00
check: completed == 0
test "cancelAndWait() test":
2019-06-26 12:36:01 +00:00
var completed = 0
proc client1() {.async.} =
await sleepAsync(1.seconds)
inc(completed)
proc client2() {.async.} =
await client1()
inc(completed)
proc client3() {.async.} =
await client2()
inc(completed)
proc client4() {.async.} =
await client3()
inc(completed)
var fut = client4()
waitFor cancelAndWait(fut)
2021-06-04 10:28:04 +00:00
check:
fut.cancelled()
2019-06-26 12:36:01 +00:00
2021-06-04 10:28:04 +00:00
test "Break cancellation propagation test":
2019-06-26 12:36:01 +00:00
var completed = 0
proc client1() {.async.} =
await sleepAsync(1.seconds)
inc(completed)
proc client2() {.async.} =
try:
await client1()
except CancelledError:
discard
inc(completed)
var fut1 = client2()
var fut2 = client2()
fut1.cancel()
waitFor fut1
waitFor cancelAndWait(fut2)
2021-06-04 10:28:04 +00:00
check:
not fut1.cancelled()
not fut2.cancelled()
completed == 2
2019-06-26 12:36:01 +00:00
2021-06-04 10:28:04 +00:00
test "Cancellation callback test":
2019-06-26 12:36:01 +00:00
var completed = 0
var cancelled = 0
proc client1(duration: Duration): Future[void] =
## Suspends the execution of the current async procedure for the next
## ``duration`` time.
var retFuture = newFuture[void]()
let moment = Moment.fromNow(duration)
proc completion(data: pointer) {.gcsafe.} =
inc(completed)
if not(retFuture.finished()):
retFuture.complete()
proc cancel(udata: pointer) {.gcsafe.} =
inc(cancelled)
if not(retFuture.finished()):
removeTimer(moment, completion, cast[pointer](retFuture))
retFuture.cancelCallback = cancel
discard setTimer(moment, completion, cast[pointer](retFuture))
2019-06-26 12:36:01 +00:00
return retFuture
var fut = client1(100.milliseconds)
fut.cancel()
2019-06-26 12:50:40 +00:00
waitFor(sleepAsync(500.milliseconds))
2019-06-26 12:36:01 +00:00
2021-06-04 10:28:04 +00:00
check:
fut.cancelled()
completed == 0
cancelled == 1
2019-06-26 12:36:01 +00:00
proc testWaitAsync(): Future[bool] {.async.} =
var neverFlag1, neverFlag2, neverFlag3: bool
var waitProc1, waitProc2: bool
proc neverEndingProc(): Future[void] =
var res = newFuture[void]()
proc continuation(udata: pointer) {.gcsafe.} =
neverFlag2 = true
proc cancellation(udata: pointer) {.gcsafe.} =
neverFlag3 = true
res.addCallback(continuation)
res.cancelCallback = cancellation
result = res
neverFlag1 = true
proc waitProc() {.async.} =
try:
await wait(neverEndingProc(), 100.milliseconds)
except CancelledError:
waitProc1 = true
finally:
waitProc2 = true
var fut = waitProc()
await cancelAndWait(fut)
result = (fut.state == FutureState.Finished) and
neverFlag1 and neverFlag2 and neverFlag3 and
waitProc1 and waitProc2
proc testWithTimeoutAsync(): Future[bool] {.async.} =
var neverFlag1, neverFlag2, neverFlag3: bool
var waitProc1, waitProc2: bool
proc neverEndingProc(): Future[void] =
var res = newFuture[void]()
proc continuation(udata: pointer) {.gcsafe.} =
neverFlag2 = true
proc cancellation(udata: pointer) {.gcsafe.} =
neverFlag3 = true
res.addCallback(continuation)
res.cancelCallback = cancellation
result = res
neverFlag1 = true
proc withTimeoutProc() {.async.} =
try:
discard await withTimeout(neverEndingProc(), 100.milliseconds)
doAssert(false)
except CancelledError:
waitProc1 = true
finally:
waitProc2 = true
var fut = withTimeoutProc()
await cancelAndWait(fut)
result = (fut.state == FutureState.Finished) and
neverFlag1 and neverFlag2 and neverFlag3 and
waitProc1 and waitProc2
proc testCancellationRaceAsync(): Future[bool] {.async.} =
var someFut = newFuture[void]()
proc raceProc(): Future[void] {.async.} =
await someFut
var raceFut1 = raceProc()
someFut.complete()
await cancelAndWait(raceFut1)
someFut = newFuture[void]()
var raceFut2 = raceProc()
someFut.fail(newException(ValueError, ""))
await cancelAndWait(raceFut2)
someFut = newFuture[void]()
var raceFut3 = raceProc()
someFut.cancel()
await cancelAndWait(raceFut3)
result = (raceFut1.state == FutureState.Cancelled) and
(raceFut2.state == FutureState.Cancelled) and
(raceFut3.state == FutureState.Cancelled)
2021-06-04 10:28:04 +00:00
test "Cancellation wait() test":
check: waitFor(testWaitAsync())
2021-06-04 10:28:04 +00:00
test "Cancellation withTimeout() test":
check: waitFor(testWithTimeoutAsync())
2021-06-04 10:28:04 +00:00
test "Cancellation race test":
check: waitFor(testCancellationRaceAsync())
proc testAsyncSpawnAsync(): Future[bool] {.async.} =
proc completeTask1() {.async.} =
discard
proc completeTask2() {.async.} =
await sleepAsync(100.milliseconds)
proc errorTask() {.async.} =
if true:
raise newException(ValueError, "")
proc cancelTask() {.async.} =
await sleepAsync(10.seconds)
try:
var fut1 = completeTask1()
var fut2 = completeTask2()
asyncSpawn fut1
asyncSpawn fut2
await sleepAsync(200.milliseconds)
if not(fut1.finished()) or not(fut2.finished()):
return false
if fut1.failed() or fut1.cancelled() or fut2.failed() or fut2.cancelled():
return false
except:
return false
try:
asyncSpawn errorTask()
return false
except FutureDefect:
discard
except:
return false
try:
var fut = cancelTask()
await cancelAndWait(fut)
asyncSpawn fut
return false
except FutureDefect:
discard
except:
return false
return true
2021-06-04 10:28:04 +00:00
test "asyncSpawn() test":
check: waitFor(testAsyncSpawnAsync())
2021-06-04 10:28:04 +00:00
test "location test":
# WARNING: This test is very sensitive to line numbers and module name.
proc macroFuture() {.async.} =
let someVar {.used.} = 5
let someOtherVar {.used.} = 4
if true:
let otherVar {.used.} = 3
template templateFuture(): untyped =
newFuture[void]("template")
proc procFuture(): Future[void] =
newFuture[void]("procedure")
var fut1 = macroFuture()
var fut2 = templateFuture()
var fut3 = procFuture()
fut2.complete()
fut3.complete()
let loc10 = fut1.location[0]
let loc11 = fut1.location[1]
let loc20 = fut2.location[0]
let loc21 = fut2.location[1]
let loc30 = fut3.location[0]
let loc31 = fut3.location[1]
proc chk(loc: ptr SrcLoc, file: string, line: int,
procedure: string): bool =
if len(procedure) == 0:
(loc.line == line) and ($loc.file == file)
else:
(loc.line == line) and ($loc.file == file) and
(loc.procedure == procedure)
2021-06-04 10:28:04 +00:00
check:
chk(loc10, "testfut.nim", 1041, "macroFuture")
chk(loc11, "testfut.nim", 1042, "")
chk(loc20, "testfut.nim", 1054, "template")
chk(loc21, "testfut.nim", 1057, "")
chk(loc30, "testfut.nim", 1051, "procedure")
chk(loc31, "testfut.nim", 1058, "")
2021-06-04 10:28:04 +00:00
test "withTimeout(fut) should wait cancellation test":
proc futureNeverEnds(): Future[void] =
newFuture[void]("neverending.future")
proc futureOneLevelMore() {.async.} =
await futureNeverEnds()
proc testWithTimeout(): Future[bool] {.async.} =
var fut = futureOneLevelMore()
try:
let res = await withTimeout(fut, 100.milliseconds)
# Because `fut` is never-ending Future[T], `withTimeout` should return
# `false` but it also has to wait until `fut` is cancelled.
if not(res) and fut.cancelled():
return true
else:
return false
except CatchableError:
return false
2021-06-04 10:28:04 +00:00
check: waitFor testWithTimeout()
2021-06-04 10:28:04 +00:00
test "wait(fut) should wait cancellation test":
proc futureNeverEnds(): Future[void] =
newFuture[void]("neverending.future")
proc futureOneLevelMore() {.async.} =
await futureNeverEnds()
proc testWait(): Future[bool] {.async.} =
var fut = futureOneLevelMore()
try:
await wait(fut, 100.milliseconds)
return false
except AsyncTimeoutError:
# Because `fut` is never-ending Future[T], `wait` should raise
# `AsyncTimeoutError`, but only after `fut` is cancelled.
if fut.cancelled():
return true
else:
return false
except CatchableError:
return false
2021-06-04 10:28:04 +00:00
check: waitFor testWait()
2021-06-04 10:28:04 +00:00
test "race(zero) test":
var tseq = newSeq[FutureBase]()
var fut1 = race(tseq)
var fut2 = race()
var fut3 = race([])
2021-06-04 10:28:04 +00:00
check:
fut1.failed()
fut2.failed()
fut3.failed()
2021-06-04 10:28:04 +00:00
test "race(varargs) test":
proc vlient1() {.async.} =
await sleepAsync(100.milliseconds)
proc vlient2() {.async.} =
await sleepAsync(200.milliseconds)
proc vlient3() {.async.} =
await sleepAsync(300.milliseconds)
proc ilient1(): Future[int] {.async.} =
await sleepAsync(100.milliseconds)
result = 10
proc ilient2(): Future[int] {.async.} =
await sleepAsync(200.milliseconds)
result = 20
proc ilient3(): Future[int] {.async.} =
await sleepAsync(300.milliseconds)
result = 30
proc slient1(): Future[string] {.async.} =
await sleepAsync(100.milliseconds)
result = "sclient1"
proc slient2(): Future[string] {.async.} =
await sleepAsync(200.milliseconds)
result = "sclient2"
proc slient3(): Future[string] {.async.} =
await sleepAsync(300.milliseconds)
result = "sclient3"
var fut11 = vlient1()
var fut12 = ilient2()
var fut13 = slient3()
var res1 = waitFor(race(fut11, fut12, fut13))
var fut21 = vlient2()
var fut22 = ilient1()
var fut23 = slient3()
var res2 = waitFor(race(fut21, fut22, fut23))
var fut31 = vlient3()
var fut32 = ilient2()
var fut33 = slient1()
var res3 = waitFor(race(fut31, fut32, fut33))
var fut41 = vlient1()
var fut42 = slient2()
var fut43 = ilient3()
var res4 = waitFor(race(fut41, fut42, fut43))
2021-06-04 10:28:04 +00:00
check:
FutureBase(fut11) == res1
FutureBase(fut22) == res2
FutureBase(fut33) == res3
FutureBase(fut41) == res4
2021-06-04 10:28:04 +00:00
test "race(seq) test":
proc vlient1() {.async.} =
await sleepAsync(100.milliseconds)
proc vlient2() {.async.} =
await sleepAsync(200.milliseconds)
proc vlient3() {.async.} =
await sleepAsync(300.milliseconds)
proc ilient1(): Future[int] {.async.} =
await sleepAsync(100.milliseconds)
result = 10
proc ilient2(): Future[int] {.async.} =
await sleepAsync(200.milliseconds)
result = 20
proc ilient3(): Future[int] {.async.} =
await sleepAsync(300.milliseconds)
result = 30
proc slient1(): Future[string] {.async.} =
await sleepAsync(100.milliseconds)
result = "slient1"
proc slient2(): Future[string] {.async.} =
await sleepAsync(200.milliseconds)
result = "slient2"
proc slient3(): Future[string] {.async.} =
await sleepAsync(300.milliseconds)
result = "slient3"
var v10 = vlient1()
var v11 = ilient2()
var v12 = slient3()
var res1 = waitFor(race(@[FutureBase(v10), FutureBase(v11),
FutureBase(v12)]))
var v20 = vlient2()
var v21 = ilient1()
var v22 = slient3()
var res2 = waitFor(race(@[FutureBase(v20), FutureBase(v21),
FutureBase(v22)]))
var v30 = vlient3()
var v31 = ilient2()
var v32 = slient1()
var res3 = waitFor(race(@[FutureBase(v30), FutureBase(v31),
FutureBase(v32)]))
var v40 = vlient1()
var v41 = slient2()
var v42 = ilient3()
var res4 = waitFor(race(@[FutureBase(v40), FutureBase(v41),
FutureBase(v42)]))
2021-06-04 10:28:04 +00:00
check:
res1 == FutureBase(v10)
res2 == FutureBase(v21)
res3 == FutureBase(v32)
res4 == FutureBase(v40)
2021-06-04 10:28:04 +00:00
test "race() already completed test":
proc client1(): Future[int] {.async.} =
result = 1
proc client2() {.async.} =
if true:
raise newException(ValueError, "")
proc client3(): Future[string] {.async.} =
await sleepAsync(100.milliseconds)
result = "client3"
var f10 = client1()
var f20 = client2()
var f30 = client3()
var fut1 = race(f30, f10, f20)
var f11 = client1()
var f21 = client2()
var f31 = client3()
var fut2 = race(f31, f21, f11)
2021-06-04 10:28:04 +00:00
check:
fut1.done() and fut1.read() == FutureBase(f10)
fut2.done() and fut2.read() == FutureBase(f21)
2021-06-04 10:28:04 +00:00
test "race() cancellation test":
proc client1() {.async.} =
await sleepAsync(100.milliseconds)
proc client2(): Future[int] {.async.} =
await sleepAsync(200.milliseconds)
return 10
proc client3(): Future[string] {.async.} =
await sleepAsync(300.milliseconds)
return "client3"
var f1 = client1()
var f2 = client2()
var f3 = client3()
var fut = race(f1, f2, f3)
waitFor(cancelAndWait(fut))
2021-06-04 10:28:04 +00:00
check:
not f1.finished()
not f2.finished()
not f3.finished()
waitFor(sleepAsync(400.milliseconds))
2021-06-04 10:28:04 +00:00
check:
f1.finished()
f2.finished()
f3.finished()
2019-06-26 12:36:01 +00:00