mirror of
https://github.com/logos-storage/asynctest.git
synced 2026-01-02 13:03:07 +00:00
Compare commits
32 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
572c897a4e | ||
|
|
73c08f77af | ||
|
|
5154c0d79d | ||
|
|
32df0f19d6 | ||
|
|
12c356672d | ||
|
|
8e2f4e73b9 | ||
|
|
d22828cb8b | ||
|
|
98f99df4b4 | ||
|
|
db3d1e0441 | ||
|
|
eb6940c5de | ||
|
|
4f89a82a49 | ||
|
|
90c1b35b67 | ||
|
|
c9423b198f | ||
|
|
1a0cd2c496 | ||
|
|
bb07f702fb | ||
|
|
9f31323a5f | ||
|
|
1479949bd7 | ||
|
|
6799f9e1cc | ||
|
|
3f89e84f7c | ||
|
|
50dfe3aab1 | ||
|
|
048d8e419e | ||
|
|
a27ce97f9e | ||
|
|
476191f7dd | ||
|
|
fde8a33173 | ||
|
|
8184a4bd8e | ||
|
|
47480f2ade | ||
|
|
fe1a34caf5 | ||
|
|
8359603f60 | ||
|
|
e29612543e | ||
|
|
698d0f77d6 | ||
|
|
b551eb3705 | ||
|
|
c8d324cfa6 |
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
@ -7,7 +7,7 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
nim: [stable, 1.2.6]
|
||||
nim: [stable, 1.6.18]
|
||||
os: [ubuntu-latest, macOS-latest, windows-latest]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@ -3,3 +3,4 @@
|
||||
!*.*
|
||||
nimble.develop
|
||||
nimble.paths
|
||||
nimbledeps
|
||||
|
||||
48
Readme.md
48
Readme.md
@ -11,22 +11,35 @@ Use the [Nimble][2] package manager to add asynctest to an existing project.
|
||||
Add the following to its .nimble file:
|
||||
|
||||
```nim
|
||||
requires "asynctest >= 0.3.2 & < 0.4.0"
|
||||
requires "asynctest >= 0.5.4 & < 0.6.0"
|
||||
```
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
Simply replace `import unittest` with `import asynctest`, and you can await
|
||||
Replace `import unittest` with one of the following imports, and you can await
|
||||
asynchronous calls in tests, setup and teardown.
|
||||
|
||||
When you use Nim's standard library only ([asyncdispatch][4] and [unittest][1]):
|
||||
```nim
|
||||
import asynctest/asyncdispatch/unittest
|
||||
```
|
||||
|
||||
When you use [chronos][5] or [unittest2][3], pick the import that matches your
|
||||
choices:
|
||||
|
||||
```nim
|
||||
import asynctest/asyncdispatch/unittest2 # standard async and unittest2
|
||||
import asynctest/chronos/unittest # chronos and standard unittest
|
||||
import asynctest/chronos/unittest2 # chronos and unittest2
|
||||
```
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
```nim
|
||||
|
||||
import asynctest
|
||||
import asyncdispatch # alternatively: import chronos
|
||||
import asynctest/asyncdispatch/unittest
|
||||
|
||||
proc someAsyncProc {.async.} =
|
||||
# perform some async operations using await
|
||||
@ -47,6 +60,26 @@ suite "test async proc":
|
||||
|
||||
```
|
||||
|
||||
check eventually
|
||||
----------------
|
||||
|
||||
When you find yourself adding calls to `sleepAsync` to your tests, you might
|
||||
want to consider using `check eventually` instead. It will repeatedly check
|
||||
an expression until it becomes true. It has a built-in timeout of 5 seconds that
|
||||
you can override.
|
||||
|
||||
```nim
|
||||
var x: int
|
||||
|
||||
proc slowProcedure {.async.} =
|
||||
# perform a slow operation
|
||||
x = 42
|
||||
|
||||
let future = slowProcedure()
|
||||
check eventually x == 42
|
||||
await future
|
||||
```
|
||||
|
||||
setupAll and teardownAll
|
||||
------------------------
|
||||
|
||||
@ -58,12 +91,9 @@ last resort when setting up the test environment is very costly. Be careful that
|
||||
the tests do not modify the environment that you set up, lest you introduce
|
||||
dependencies between tests.
|
||||
|
||||
Unittest2
|
||||
---------
|
||||
|
||||
The [unittest2][3] package is supported. Make sure that you
|
||||
`import asynctest/unittest2` instead of the normal import.
|
||||
|
||||
[1]: https://nim-lang.org/docs/unittest.html
|
||||
[2]: https://github.com/nim-lang/nimble
|
||||
[3]: https://github.com/status-im/nim-unittest2
|
||||
[4]: https://nim-lang.org/docs/asyncdispatch.html
|
||||
[5]: https://github.com/status-im/nim-chronos/
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import ./asynctest/unittest
|
||||
|
||||
export unittest
|
||||
{.error:
|
||||
"As of version 0.5.0 you need to import either " &
|
||||
"asynctest/asyncdispatch/unittest or " &
|
||||
"asynctest/chronos/unittest depending on your choice of async framework".}
|
||||
|
||||
@ -1,12 +1,13 @@
|
||||
version = "0.3.2"
|
||||
version = "0.5.4"
|
||||
author = "asynctest Authors"
|
||||
description = "Test asynchronous code"
|
||||
license = "MIT"
|
||||
|
||||
requires "nim >= 1.2.0 & < 2.0.0"
|
||||
skipDirs = @["testmodules"]
|
||||
|
||||
task test, "Runs the test suite":
|
||||
for module in ["stdlib", "chronos", "unittest2"]:
|
||||
for module in ["stdlib", "chronosv3", "chronosv4", "unittest2"]:
|
||||
withDir "testmodules/" & module:
|
||||
delEnv "NIMBLE_DIR" # use nimbledeps dir
|
||||
exec "nimble install -d -y"
|
||||
exec "nimble test -y"
|
||||
|
||||
10
asynctest/asyncdispatch/unittest.nim
Normal file
10
asynctest/asyncdispatch/unittest.nim
Normal file
@ -0,0 +1,10 @@
|
||||
import std/asyncdispatch
|
||||
import std/unittest
|
||||
import ../private/asyncdispatch/eventually
|
||||
import ../private/asyncdispatch/runasync
|
||||
|
||||
export asyncdispatch
|
||||
export unittest except suite, test
|
||||
export eventually
|
||||
|
||||
include ../private/suite
|
||||
10
asynctest/asyncdispatch/unittest2.nim
Normal file
10
asynctest/asyncdispatch/unittest2.nim
Normal file
@ -0,0 +1,10 @@
|
||||
import std/asyncdispatch
|
||||
import pkg/unittest2
|
||||
import ../private/asyncdispatch/eventually
|
||||
import ../private/asyncdispatch/runasync
|
||||
|
||||
export asyncdispatch
|
||||
export unittest2 except suite, test
|
||||
export eventually
|
||||
|
||||
include ../private/suite
|
||||
10
asynctest/chronos/unittest.nim
Normal file
10
asynctest/chronos/unittest.nim
Normal file
@ -0,0 +1,10 @@
|
||||
import pkg/chronos
|
||||
import std/unittest
|
||||
import ../private/chronos/eventually
|
||||
import ../private/chronos/unittest/runasync
|
||||
|
||||
export chronos
|
||||
export unittest except suite, test
|
||||
export eventually
|
||||
|
||||
include ../private/suite
|
||||
10
asynctest/chronos/unittest2.nim
Normal file
10
asynctest/chronos/unittest2.nim
Normal file
@ -0,0 +1,10 @@
|
||||
import pkg/chronos
|
||||
import pkg/unittest2
|
||||
import ../private/chronos/eventually
|
||||
import ../private/chronos/unittest2/runasync
|
||||
|
||||
export chronos
|
||||
export unittest2 except suite, test
|
||||
export eventually
|
||||
|
||||
include ../private/suite
|
||||
14
asynctest/private/asyncdispatch/eventually.nim
Normal file
14
asynctest/private/asyncdispatch/eventually.nim
Normal file
@ -0,0 +1,14 @@
|
||||
import std/asyncdispatch
|
||||
import std/times
|
||||
|
||||
template eventually*(expression: untyped, timeout=5000, pollInterval=1000): bool =
|
||||
|
||||
proc eventually: Future[bool] {.async.} =
|
||||
let endTime = getTime() + initDuration(milliseconds=timeout)
|
||||
while not expression:
|
||||
if endTime < getTime():
|
||||
return false
|
||||
await sleepAsync(pollInterval)
|
||||
return true
|
||||
|
||||
await eventually()
|
||||
5
asynctest/private/asyncdispatch/runasync.nim
Normal file
5
asynctest/private/asyncdispatch/runasync.nim
Normal file
@ -0,0 +1,5 @@
|
||||
import std/asyncdispatch
|
||||
|
||||
template runAsync*(body): untyped =
|
||||
let asyncProc = proc {.async.} = body
|
||||
waitFor asyncProc()
|
||||
24
asynctest/private/chronos/eventually.nim
Normal file
24
asynctest/private/chronos/eventually.nim
Normal file
@ -0,0 +1,24 @@
|
||||
import pkg/chronos
|
||||
import pkg/chronos/config
|
||||
|
||||
template eventuallyProcSignature(body: untyped): untyped =
|
||||
when compiles(config.chronosHandleException):
|
||||
proc eventually: Future[bool] {.async: (handleException: true,
|
||||
raises: [AsyncExceptionError, CancelledError]).} =
|
||||
body
|
||||
else:
|
||||
proc eventually: Future[bool] {.async.} =
|
||||
body
|
||||
|
||||
template eventually*(expression: untyped, timeout=5000, pollInterval=1000): bool =
|
||||
bind Moment, now, milliseconds
|
||||
|
||||
eventuallyProcSignature:
|
||||
let endTime = Moment.now() + timeout.milliseconds
|
||||
while not expression:
|
||||
if endTime < Moment.now():
|
||||
return false
|
||||
await sleepAsync(pollInterval.milliseconds)
|
||||
return true
|
||||
|
||||
await eventually()
|
||||
21
asynctest/private/chronos/unittest/runasync.nim
Normal file
21
asynctest/private/chronos/unittest/runasync.nim
Normal file
@ -0,0 +1,21 @@
|
||||
import pkg/chronos
|
||||
import pkg/chronos/config
|
||||
|
||||
when compiles(config.chronosHandleException): # detect chronos v4
|
||||
|
||||
template runAsync*(body): untyped =
|
||||
# The unittest module from stdlib can still raise a bare Exception,
|
||||
# so we allow chronos to convert it to an AsyncExceptionError, and
|
||||
# we disable the ensuing BareExcept warning.
|
||||
{.push warning[BareExcept]:off.}
|
||||
let asyncProc =
|
||||
proc {.async: (handleException: true, raises: [AsyncExceptionError]).} =
|
||||
body
|
||||
{.pop.}
|
||||
waitFor asyncProc()
|
||||
|
||||
else:
|
||||
|
||||
template runAsync*(body): untyped =
|
||||
let asyncProc = proc {.async.} = body
|
||||
waitFor asyncProc()
|
||||
5
asynctest/private/chronos/unittest2/runasync.nim
Normal file
5
asynctest/private/chronos/unittest2/runasync.nim
Normal file
@ -0,0 +1,5 @@
|
||||
import pkg/chronos
|
||||
|
||||
template runAsync*(body): untyped =
|
||||
let asyncProc = proc {.async.} = body
|
||||
waitFor asyncProc()
|
||||
34
asynctest/private/suite.nim
Normal file
34
asynctest/private/suite.nim
Normal file
@ -0,0 +1,34 @@
|
||||
template suite*(name, body) =
|
||||
|
||||
suite name:
|
||||
let suiteproc = proc =
|
||||
|
||||
## Runs before all tests in the suite
|
||||
template setupAll(setupAllBody) {.used.} =
|
||||
runAsync setupAllBody
|
||||
|
||||
## Runs after all tests in the suite
|
||||
template teardownAll(teardownAllBody) {.used.} =
|
||||
template teardownAllIMPL: untyped {.inject.} =
|
||||
runAsync teardownAllBody
|
||||
|
||||
template setup(setupBody) {.used.} =
|
||||
setup:
|
||||
runAsync setupBody
|
||||
|
||||
template teardown(teardownBody) {.used.} =
|
||||
teardown:
|
||||
let exception = getCurrentException()
|
||||
runAsync teardownBody
|
||||
setCurrentException(exception)
|
||||
|
||||
body
|
||||
|
||||
when declared(teardownAllIMPL):
|
||||
teardownAllIMPL()
|
||||
|
||||
suiteproc()
|
||||
|
||||
template test*(name, body) =
|
||||
test name:
|
||||
runAsync body
|
||||
@ -1,39 +0,0 @@
|
||||
template suite*(name, body) =
|
||||
|
||||
suite name:
|
||||
|
||||
## Runs before all tests in the suite
|
||||
template setupAll(setupAllBody) {.used.} =
|
||||
let b = proc {.async.} = setupAllBody
|
||||
waitFor b()
|
||||
|
||||
## Runs after all tests in the suite
|
||||
template teardownAll(teardownAllBody) {.used.} =
|
||||
template teardownAllIMPL: untyped {.inject.} =
|
||||
let a = proc {.async.} = teardownAllBody
|
||||
waitFor a()
|
||||
|
||||
template setup(setupBody) {.used.} =
|
||||
setup:
|
||||
let asyncproc = proc {.async.} = setupBody
|
||||
waitFor asyncproc()
|
||||
|
||||
template teardown(teardownBody) {.used.} =
|
||||
teardown:
|
||||
let exception = getCurrentException()
|
||||
let asyncproc = proc {.async.} = teardownBody
|
||||
waitFor asyncproc()
|
||||
setCurrentException(exception)
|
||||
|
||||
let suiteproc = proc = # Avoids GcUnsafe2 warnings with chronos
|
||||
body
|
||||
|
||||
when declared(teardownAllIMPL):
|
||||
teardownAllIMPL()
|
||||
|
||||
suiteproc()
|
||||
|
||||
template test*(name, body) =
|
||||
test name:
|
||||
let asyncproc = proc {.async.} = body
|
||||
waitFor asyncproc()
|
||||
@ -1,4 +0,0 @@
|
||||
import std/unittest
|
||||
export unittest except suite, test
|
||||
|
||||
include ./templates
|
||||
@ -1,4 +1,4 @@
|
||||
import pkg/unittest2
|
||||
export unittest2 except suite, test
|
||||
|
||||
include ./templates
|
||||
{.error:
|
||||
"As of version 0.5.0 you need to import either " &
|
||||
"asynctest/asyncdispatch/unittest2 or " &
|
||||
"asynctest/chronos/unittest2 depending on your choice of async framework".}
|
||||
|
||||
@ -1,4 +0,0 @@
|
||||
# begin Nimble config (version 1)
|
||||
when fileExists("nimble.paths"):
|
||||
include "nimble.paths"
|
||||
# end Nimble config
|
||||
@ -1,4 +0,0 @@
|
||||
{
|
||||
"version": 1,
|
||||
"packages": {}
|
||||
}
|
||||
@ -1 +0,0 @@
|
||||
--path:"../.."
|
||||
@ -1,5 +0,0 @@
|
||||
import pkg/asynctest
|
||||
import pkg/chronos
|
||||
|
||||
include ../stdlib/testbody
|
||||
include ../stdlib/testfail
|
||||
2
testmodules/chronosv3/nim.cfg
Normal file
2
testmodules/chronosv3/nim.cfg
Normal file
@ -0,0 +1,2 @@
|
||||
--path:"../.."
|
||||
--hint:"XCannotRaiseY:off"
|
||||
4
testmodules/chronosv3/test.nim
Normal file
4
testmodules/chronosv3/test.nim
Normal file
@ -0,0 +1,4 @@
|
||||
import pkg/asynctest/chronos/unittest
|
||||
|
||||
include ../common/testbody
|
||||
include ../common/testfail
|
||||
@ -3,7 +3,7 @@ author = "Asynctest Authors"
|
||||
description = "Asynctest tests for std/unittest and pkg/chronos"
|
||||
license = "MIT"
|
||||
|
||||
requires "chronos"
|
||||
requires "chronos >= 3.2.0 & < 4.0.0"
|
||||
|
||||
task test, "Runs the test suite":
|
||||
exec "nim c -f -r test.nim"
|
||||
exec "nim c -f -r --skipParentCfg test.nim"
|
||||
3
testmodules/chronosv4/nim.cfg
Normal file
3
testmodules/chronosv4/nim.cfg
Normal file
@ -0,0 +1,3 @@
|
||||
--path:"../.."
|
||||
--hint:"XCannotRaiseY:off"
|
||||
--define:"chronosPreviewV4" # TODO: remove once chronos v4 is released
|
||||
0
testmodules/chronosv4/nimbledeps/.keep
Normal file
0
testmodules/chronosv4/nimbledeps/.keep
Normal file
18
testmodules/chronosv4/test.nim
Normal file
18
testmodules/chronosv4/test.nim
Normal file
@ -0,0 +1,18 @@
|
||||
import pkg/asynctest/chronos/unittest
|
||||
|
||||
include ../common/testbody
|
||||
include ../common/testfail
|
||||
|
||||
import std/times
|
||||
|
||||
suite "eventually and std/times":
|
||||
|
||||
test "compiles when both chronos and std/times are imported":
|
||||
check eventually true
|
||||
|
||||
test "compiles when the expression handed to eventually can raise Exception":
|
||||
proc aProc(): bool {.raises: [Exception].} = raise newException(
|
||||
Exception, "an exception")
|
||||
check:
|
||||
compiles:
|
||||
discard eventually aProc()
|
||||
9
testmodules/chronosv4/test.nimble
Normal file
9
testmodules/chronosv4/test.nimble
Normal file
@ -0,0 +1,9 @@
|
||||
version = "0.1.0"
|
||||
author = "Asynctest Authors"
|
||||
description = "Asynctest tests for std/unittest and pkg/chronos"
|
||||
license = "MIT"
|
||||
|
||||
requires "chronos#head" # TODO: use "chronos >= 4.0.0 & < 5.0.0" once it's released
|
||||
|
||||
task test, "Runs the test suite":
|
||||
exec "nim c -f -r --skipParentCfg test.nim"
|
||||
90
testmodules/common/testbody.nim
Normal file
90
testmodules/common/testbody.nim
Normal file
@ -0,0 +1,90 @@
|
||||
proc someAsyncProc {.async.} =
|
||||
# perform some async operations using await
|
||||
discard
|
||||
|
||||
suite "test async proc":
|
||||
|
||||
setup:
|
||||
# invoke await in the test setup:
|
||||
await someAsyncProc()
|
||||
|
||||
teardown:
|
||||
# invoke await in the test teardown:
|
||||
await someAsyncProc()
|
||||
|
||||
test "async test":
|
||||
# invoke await in tests:
|
||||
await someAsyncProc()
|
||||
|
||||
suite "setupAll and teardownAll":
|
||||
|
||||
setupAll:
|
||||
# invoke await in the test setup:
|
||||
await someAsyncProc()
|
||||
|
||||
teardownAll:
|
||||
# invoke await in the test teardown:
|
||||
await someAsyncProc()
|
||||
|
||||
test "async test":
|
||||
# invoke await in tests:
|
||||
await someAsyncProc()
|
||||
|
||||
from std/os import sleep
|
||||
|
||||
suite "eventually":
|
||||
|
||||
test "becomes true":
|
||||
|
||||
var tries = 0
|
||||
|
||||
proc becomesTrue: bool =
|
||||
inc tries
|
||||
tries == 3
|
||||
|
||||
check eventually(becomesTrue(), pollInterval=10)
|
||||
|
||||
test "becomes false after timeout":
|
||||
|
||||
proc remainsFalse: bool = false
|
||||
|
||||
check not eventually(remainsFalse(), timeout=100, pollInterval=10)
|
||||
|
||||
test "becomes true during timeout":
|
||||
|
||||
proc slowTrue: bool =
|
||||
sleep(100)
|
||||
true
|
||||
|
||||
check eventually(slowTrue(), timeout=50, pollInterval=10)
|
||||
|
||||
test "works with async procedures":
|
||||
|
||||
var x: int
|
||||
|
||||
proc slowProcedure {.async.} =
|
||||
when compiles(await sleepAsync(100.milliseconds)):
|
||||
await sleepAsync(100.milliseconds) # chronos
|
||||
else:
|
||||
await sleepAsync(100) # asyncdispatch
|
||||
x = 42
|
||||
|
||||
let future = slowProcedure()
|
||||
check eventually(x == 42, pollInterval=10)
|
||||
await future
|
||||
|
||||
test "respects poll interval":
|
||||
var evaluations: int = 0
|
||||
|
||||
# If we try to access this from the closure, it will crash later with
|
||||
# a segfault, so pass as var.
|
||||
proc expensivePredicate(counter: var int): bool =
|
||||
inc counter
|
||||
return false
|
||||
|
||||
check not eventually(evaluations.expensivePredicate(), pollInterval=100, timeout=500)
|
||||
check 1 <= evaluations and evaluations <= 6
|
||||
|
||||
evaluations = 0
|
||||
check not eventually(evaluations.expensivePredicate(), pollInterval=10, timeout=500)
|
||||
check 20 <= evaluations and evaluations <= 51
|
||||
@ -26,7 +26,7 @@ suite "reports unhandled exception when teardown handles exceptions too":
|
||||
teardown:
|
||||
try:
|
||||
await someAsyncProc()
|
||||
except:
|
||||
except CatchableError:
|
||||
discard
|
||||
|
||||
test "should fail, but not crash":
|
||||
@ -1,5 +1,4 @@
|
||||
import std/asyncdispatch
|
||||
import pkg/asynctest
|
||||
import pkg/asynctest/asyncdispatch/unittest
|
||||
|
||||
include ./testbody
|
||||
include ./testfail
|
||||
include ../common/testbody
|
||||
include ../common/testfail
|
||||
|
||||
@ -4,4 +4,4 @@ description = "Asynctest tests for std/unittest and std/asyncdispatch"
|
||||
license = "MIT"
|
||||
|
||||
task test, "Runs the test suite":
|
||||
exec "nim c -f -r test.nim"
|
||||
exec "nim c -f -r --skipParentCfg test.nim"
|
||||
|
||||
@ -1,31 +0,0 @@
|
||||
proc someAsyncProc {.async.} =
|
||||
# perform some async operations using await
|
||||
discard
|
||||
|
||||
suite "test async proc":
|
||||
|
||||
setup:
|
||||
# invoke await in the test setup:
|
||||
await someAsyncProc()
|
||||
|
||||
teardown:
|
||||
# invoke await in the test teardown:
|
||||
await someAsyncProc()
|
||||
|
||||
test "async test":
|
||||
# invoke await in tests:
|
||||
await someAsyncProc()
|
||||
|
||||
suite "test async setupAll and teardownAll, allow multiple suites":
|
||||
|
||||
setupAll:
|
||||
# invoke await in the test setup:
|
||||
await someAsyncProc()
|
||||
|
||||
teardownAll:
|
||||
# invoke await in the test teardown:
|
||||
await someAsyncProc()
|
||||
|
||||
test "async test":
|
||||
# invoke await in tests:
|
||||
await someAsyncProc()
|
||||
@ -1 +1,2 @@
|
||||
--path:"../.."
|
||||
--hint:"XCannotRaiseY:off"
|
||||
|
||||
0
testmodules/unittest2/nimbledeps/.keep
Normal file
0
testmodules/unittest2/nimbledeps/.keep
Normal file
@ -1,5 +1,4 @@
|
||||
import pkg/asynctest/unittest2
|
||||
import pkg/chronos
|
||||
import pkg/asynctest/asyncdispatch/unittest2
|
||||
|
||||
include ../stdlib/testbody
|
||||
include ../stdlib/testfail
|
||||
include ../common/testbody
|
||||
include ../common/testfail
|
||||
|
||||
@ -7,4 +7,4 @@ requires "unittest2"
|
||||
requires "chronos"
|
||||
|
||||
task test, "Runs the test suite":
|
||||
exec "nim c -f -r test.nim"
|
||||
exec "nim c -f -r --skipParentCfg test.nim"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user