Compare commits

..

No commits in common. "main" and "0.1.0" have entirely different histories.
main ... 0.1.0

38 changed files with 52 additions and 427 deletions

View File

@ -7,12 +7,9 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
nim: [stable, 1.6.18]
os: [ubuntu-latest, macOS-latest, windows-latest]
steps:
- uses: actions/checkout@v2
- uses: iffy/install-nim@v3
with:
version: ${{ matrix.nim }}
- name: Test
run: nimble test -y

5
.gitignore vendored
View File

@ -1,6 +1,3 @@
*
!*/
!*.*
nimble.develop
nimble.paths
nimbledeps
!*.*

1
.tool-versions Normal file
View File

@ -0,0 +1 @@
nim 1.4.2

View File

@ -1,5 +0,0 @@
Licensed and distributed under either of
[MIT license](http://opensource.org/licenses/MIT) or
[Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0)
at your option. These files may not be copied, modified, or distributed except
according to those terms.

View File

@ -11,89 +11,43 @@ Use the [Nimble][2] package manager to add asynctest to an existing project.
Add the following to its .nimble file:
```nim
requires "asynctest >= 0.5.4 & < 0.6.0"
requires "asynctest >= 0.1.0 & < 0.2.0"
```
Usage
-----
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
```
Simply replace `test` with `asynctest` when you need to await asynchronous calls
in the test. The same holds for `asyncsetup` and `asyncteardown`, which allow
you to await asynchronous calls during test setup and teardown.
Example
-------
```nim
import asynctest/asyncdispatch/unittest
import unittest
import asynctest
import asyncdispatch # alternatively: import chronos
proc someAsyncProc {.async.} =
# perform some async operations using await
suite "test async proc":
setup:
# invoke await in each test setup:
asyncsetup:
# invoke await in the test setup:
await someAsyncProc()
teardown:
# invoke await in each test teardown:
asyncteardown:
# invoke await in the test teardown:
await someAsyncProc()
test "async test":
asynctest "some test":
# invoke await in tests:
await someAsyncProc()
```
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
------------------------
The `setup` and `teardown` code runs before and after every test, just like the
standard [unittest][1] module. In addition we provide `setupAll` and
`teardownAll`. The `setupAll` code runs once before all tests in the suite, and
the `teardownAll` runs once after all tests in the suite. Use these only as a
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.
[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/

View File

@ -1,4 +1,14 @@
{.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".}
template asynctest*(name, body) =
test name:
let asyncproc = proc {.async.} = body
waitFor asyncproc()
template asyncsetup*(body) =
setup:
let asyncproc = proc {.async.} = body
waitFor asyncproc()
template asyncteardown*(body) =
teardown:
let asyncproc = proc {.async.} = body
waitFor asyncproc()

View File

@ -1,13 +1,6 @@
version = "0.5.4"
version = "0.1.0"
author = "asynctest Authors"
description = "Test asynchronous code"
license = "MIT"
skipDirs = @["testmodules"]
task test, "Runs the test suite":
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"
requires "nim >= 1.2.0 & < 2.0.0"

View File

@ -1,10 +0,0 @@
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

View File

@ -1,10 +0,0 @@
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

View File

@ -1,10 +0,0 @@
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

View File

@ -1,10 +0,0 @@
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

View File

@ -1,14 +0,0 @@
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()

View File

@ -1,5 +0,0 @@
import std/asyncdispatch
template runAsync*(body): untyped =
let asyncProc = proc {.async.} = body
waitFor asyncProc()

View File

@ -1,24 +0,0 @@
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()

View File

@ -1,21 +0,0 @@
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()

View File

@ -1,5 +0,0 @@
import pkg/chronos
template runAsync*(body): untyped =
let asyncProc = proc {.async.} = body
waitFor asyncProc()

View File

@ -1,34 +0,0 @@
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

View File

@ -1,4 +0,0 @@
{.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".}

View File

@ -1,2 +0,0 @@
--path:"../.."
--hint:"XCannotRaiseY:off"

View File

@ -1,4 +0,0 @@
import pkg/asynctest/chronos/unittest
include ../common/testbody
include ../common/testfail

View File

@ -1,9 +0,0 @@
version = "0.1.0"
author = "Asynctest Authors"
description = "Asynctest tests for std/unittest and pkg/chronos"
license = "MIT"
requires "chronos >= 3.2.0 & < 4.0.0"
task test, "Runs the test suite":
exec "nim c -f -r --skipParentCfg test.nim"

View File

@ -1,3 +0,0 @@
--path:"../.."
--hint:"XCannotRaiseY:off"
--define:"chronosPreviewV4" # TODO: remove once chronos v4 is released

View File

@ -1,18 +0,0 @@
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()

View File

@ -1,9 +0,0 @@
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"

View File

@ -1,90 +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 "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

View File

@ -1,34 +0,0 @@
template tryImport(module) = import module
when compiles tryImport std/exitprocs:
import std/exitprocs
else:
template getProgramResult: auto = programResult
template setProgramResult(value) = programResult = value
template silent(body) =
let exitcode = getProgramResult()
resetOutputFormatters()
addOutputFormatter(OutputFormatter())
body
resetOutputFormatters()
addOutputFormatter(defaultConsoleFormatter())
setProgramResult(exitcode)
suite "reports unhandled exception when teardown handles exceptions too":
silent:
proc someAsyncProc {.async.} = discard
teardown:
try:
await someAsyncProc()
except CatchableError:
discard
test "should fail, but not crash":
if true:
raise newException(ValueError, "This exception is expected")

View File

@ -1 +0,0 @@
--path:"../.."

View File

@ -1,4 +0,0 @@
import pkg/asynctest/asyncdispatch/unittest
include ../common/testbody
include ../common/testfail

View File

@ -1,7 +0,0 @@
version = "0.1.0"
author = "Asynctest Authors"
description = "Asynctest tests for std/unittest and std/asyncdispatch"
license = "MIT"
task test, "Runs the test suite":
exec "nim c -f -r --skipParentCfg test.nim"

View File

@ -1,2 +0,0 @@
--path:"../.."
--hint:"XCannotRaiseY:off"

View File

@ -1,4 +0,0 @@
import pkg/asynctest/asyncdispatch/unittest2
include ../common/testbody
include ../common/testfail

View File

@ -1,10 +0,0 @@
version = "0.1.0"
author = "Asynctest Authors"
description = "Asynctest tests for pkg/unittest2 and pkg/chronos"
license = "MIT"
requires "unittest2"
requires "chronos"
task test, "Runs the test suite":
exec "nim c -f -r --skipParentCfg test.nim"

1
tests/nim.cfg Normal file
View File

@ -0,0 +1 @@
--path:".."

21
tests/testAsyncTest.nim Normal file
View File

@ -0,0 +1,21 @@
import std/unittest
import std/asyncdispatch
import pkg/asynctest
proc someAsyncProc {.async.} =
# perform some async operations using await
discard
suite "test async proc":
asyncsetup:
# invoke await in the test setup:
await someAsyncProc()
asyncteardown:
# invoke await in the test teardown:
await someAsyncProc()
asynctest "async test":
# invoke await in tests:
await someAsyncProc()