Compare commits

...

32 Commits
0.3.2 ... main

Author SHA1 Message Date
Mark Spanbroek
572c897a4e version 0.5.4 2025-04-08 10:38:46 +02:00
gmega
73c08f77af feat: add tunable polling interval with conservative defaults to async predicate checks 2025-03-21 14:52:32 -03:00
Ben
5154c0d79d
bumps ci to nim 1.6.18 2024-08-14 10:44:49 +02:00
Ben
32df0f19d6
version 0.5.2 2024-08-14 10:11:51 +02:00
gmega
12c356672d add exception handling for Chronos V4 2024-01-15 19:28:09 -03:00
Mark Spanbroek
8e2f4e73b9 version 0.5.1 2024-01-10 13:14:47 +01:00
Mark Spanbroek
d22828cb8b Fix: ensure that eventually works when std/times is imported 2024-01-10 13:13:18 +01:00
Mark Spanbroek
98f99df4b4 version 0.5.0 2024-01-09 11:42:42 +01:00
Mark Spanbroek
db3d1e0441 Update Readme with new imports 2024-01-09 11:40:31 +01:00
Mark Spanbroek
eb6940c5de Add helpful compiler errors on old import paths 2024-01-09 11:40:31 +01:00
Mark Spanbroek
4f89a82a49 Fix BareExcept warnings when using stdlib unittest with chronos v4 2024-01-09 11:40:31 +01:00
Mark Spanbroek
90c1b35b67 Explicitly import either asyncdispatch or chronos version
Reorganizes the code into separate versions for asyncdispatch and
chronos so that we no longer have to rely on hard-to-maintain code
that implicitly works with both asyncdispatch and chronos.

This is a backwards incompatible change.
2024-01-09 11:40:31 +01:00
Mark Spanbroek
c9423b198f Tests for chronos v4 2023-12-21 09:55:34 +01:00
Mark Spanbroek
1a0cd2c496 Tests for chronos v3 2023-12-21 09:55:34 +01:00
Mark Spanbroek
bb07f702fb Fix for unittest2 > 0.0.9
Define our templates inside a new scope, to make sure
that they don't clash with the unittest2 templates.
2023-12-21 09:55:19 +01:00
gmega
9f31323a5f
add missing launderExceptions; actual version 0.4.3 2023-12-15 15:32:12 -03:00
gmega
1479949bd7
version 0.4.3 2023-12-15 14:51:32 -03:00
Mark Spanbroek
6799f9e1cc Fix BareExcept warnings 2023-12-14 08:32:12 -03:00
Mark Spanbroek
3f89e84f7c Raise Defect when unexpected exceptions occur 2023-12-14 08:32:12 -03:00
gmega
50dfe3aab1 add exception handling shim for Chronos V4 2023-12-14 08:32:12 -03:00
Mark Spanbroek
048d8e419e version 0.4.2 2023-08-28 17:35:03 +02:00
Mark Spanbroek
a27ce97f9e Do not install the 'testmodules' folder 2023-08-28 17:35:03 +02:00
Mark Spanbroek
476191f7dd Fix local dependencies in tests 2023-08-28 17:35:03 +02:00
Mark Spanbroek
fde8a33173 Bump Nim 1.6.x version in CI 2023-08-28 17:35:03 +02:00
Mark Spanbroek
8184a4bd8e Disable 'XCannotRaiseY' warnings caused by chronos 2023-08-28 17:35:03 +02:00
Mark Spanbroek
47480f2ade Remove Nim version requirement from nimble file 2023-08-28 17:35:03 +02:00
Mark Spanbroek
fe1a34caf5 version 0.4.1 2023-07-11 11:25:54 +02:00
Mark Spanbroek
8359603f60 Fix warning when using check eventually with chronos 2023-07-11 11:24:25 +02:00
Mark Spanbroek
e29612543e Version 0.4.0 2023-06-22 10:12:43 +02:00
Mark Spanbroek
698d0f77d6 Remove compatibility with Nim versions < 1.6
The `check eventually` template is incompatible
with Nim 1.2. It uses `await` in a template,
which is not possible until after 1.2.

https://github.com/nim-lang/Nim/pull/12085#issuecomment-526210003
2023-06-22 10:12:43 +02:00
Mark Spanbroek
b551eb3705 Fix tests on Nim 1.6.12
Ignore the nimble.lock at the root level when running tests
2023-06-22 10:12:43 +02:00
Mark Spanbroek
c8d324cfa6 Add eventually to wait for a condition to become true 2023-06-22 10:12:43 +02:00
39 changed files with 334 additions and 121 deletions

View File

@ -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
View File

@ -3,3 +3,4 @@
!*.*
nimble.develop
nimble.paths
nimbledeps

View File

@ -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/

View File

@ -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".}

View File

@ -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"

View 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

View 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

View 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

View 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

View 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()

View File

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

View 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()

View 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()

View File

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

View 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

View File

@ -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()

View File

@ -1,4 +0,0 @@
import std/unittest
export unittest except suite, test
include ./templates

View File

@ -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".}

View File

@ -1,4 +0,0 @@
# begin Nimble config (version 1)
when fileExists("nimble.paths"):
include "nimble.paths"
# end Nimble config

View File

@ -1,4 +0,0 @@
{
"version": 1,
"packages": {}
}

View File

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

View File

@ -1,5 +0,0 @@
import pkg/asynctest
import pkg/chronos
include ../stdlib/testbody
include ../stdlib/testfail

View File

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

View File

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

View File

@ -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"

View File

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

View File

View 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()

View 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"

View 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

View File

@ -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":

View File

@ -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

View File

@ -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"

View File

@ -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()

View File

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

View File

View 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

View File

@ -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"