From 17fb5ab4b76e3fe2d5e3ff4bfbddbb3981484b66 Mon Sep 17 00:00:00 2001 From: Mark Spanbroek Date: Thu, 21 Dec 2023 10:01:45 +0100 Subject: [PATCH 1/4] 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. --- asynctest.nim | 3 - asynctest/asyncdispatch/unittest.nim | 10 ++++ asynctest/asyncdispatch/unittest2.nim | 10 ++++ asynctest/chronos/unittest.nim | 10 ++++ asynctest/chronos/unittest2.nim | 10 ++++ .../asyncdispatch}/eventually.nim | 11 +--- asynctest/private/asyncdispatch/runasync.nim | 5 ++ asynctest/private/chronos/eventually.nim | 13 +++++ .../private/chronos/unittest/runasync.nim | 14 +++++ .../private/chronos/unittest2/runasync.nim | 5 ++ asynctest/private/suite.nim | 34 +++++++++++ asynctest/templates.nim | 58 ------------------- asynctest/unittest.nim | 7 --- asynctest/unittest2.nim | 7 --- testmodules/chronosv3/test.nim | 7 +-- testmodules/chronosv4/test.nim | 7 +-- testmodules/{stdlib => common}/testbody.nim | 0 testmodules/{stdlib => common}/testfail.nim | 0 testmodules/stdlib/test.nim | 7 +-- testmodules/unittest2/test.nim | 7 +-- 20 files changed, 126 insertions(+), 99 deletions(-) delete mode 100644 asynctest.nim create mode 100644 asynctest/asyncdispatch/unittest.nim create mode 100644 asynctest/asyncdispatch/unittest2.nim create mode 100644 asynctest/chronos/unittest.nim create mode 100644 asynctest/chronos/unittest2.nim rename asynctest/{ => private/asyncdispatch}/eventually.nim (53%) create mode 100644 asynctest/private/asyncdispatch/runasync.nim create mode 100644 asynctest/private/chronos/eventually.nim create mode 100644 asynctest/private/chronos/unittest/runasync.nim create mode 100644 asynctest/private/chronos/unittest2/runasync.nim create mode 100644 asynctest/private/suite.nim delete mode 100644 asynctest/templates.nim delete mode 100644 asynctest/unittest.nim delete mode 100644 asynctest/unittest2.nim rename testmodules/{stdlib => common}/testbody.nim (100%) rename testmodules/{stdlib => common}/testfail.nim (100%) diff --git a/asynctest.nim b/asynctest.nim deleted file mode 100644 index bfc35e4..0000000 --- a/asynctest.nim +++ /dev/null @@ -1,3 +0,0 @@ -import ./asynctest/unittest - -export unittest diff --git a/asynctest/asyncdispatch/unittest.nim b/asynctest/asyncdispatch/unittest.nim new file mode 100644 index 0000000..1129a2d --- /dev/null +++ b/asynctest/asyncdispatch/unittest.nim @@ -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 diff --git a/asynctest/asyncdispatch/unittest2.nim b/asynctest/asyncdispatch/unittest2.nim new file mode 100644 index 0000000..28a74db --- /dev/null +++ b/asynctest/asyncdispatch/unittest2.nim @@ -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 diff --git a/asynctest/chronos/unittest.nim b/asynctest/chronos/unittest.nim new file mode 100644 index 0000000..609443e --- /dev/null +++ b/asynctest/chronos/unittest.nim @@ -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 diff --git a/asynctest/chronos/unittest2.nim b/asynctest/chronos/unittest2.nim new file mode 100644 index 0000000..1a3a6bb --- /dev/null +++ b/asynctest/chronos/unittest2.nim @@ -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 diff --git a/asynctest/eventually.nim b/asynctest/private/asyncdispatch/eventually.nim similarity index 53% rename from asynctest/eventually.nim rename to asynctest/private/asyncdispatch/eventually.nim index fd0dc2f..6b6df42 100644 --- a/asynctest/eventually.nim +++ b/asynctest/private/asyncdispatch/eventually.nim @@ -1,19 +1,14 @@ -import std/times except milliseconds +import std/asyncdispatch +import std/times template eventually*(expression: untyped, timeout=5000): bool = - template sleep(millis: int): auto = - when compiles(await sleepAsync(millis.milliseconds)): - sleepAsync(millis.milliseconds) # chronos - else: - sleepAsync(millis) # asyncdispatch - proc eventually: Future[bool] {.async.} = let endTime = getTime() + initDuration(milliseconds=timeout) while not expression: if endTime < getTime(): return false - await sleep(10) + await sleepAsync(10) return true await eventually() diff --git a/asynctest/private/asyncdispatch/runasync.nim b/asynctest/private/asyncdispatch/runasync.nim new file mode 100644 index 0000000..ed10b20 --- /dev/null +++ b/asynctest/private/asyncdispatch/runasync.nim @@ -0,0 +1,5 @@ +import std/asyncdispatch + +template runAsync*(body): untyped = + let asyncProc = proc {.async.} = body + waitFor asyncProc() diff --git a/asynctest/private/chronos/eventually.nim b/asynctest/private/chronos/eventually.nim new file mode 100644 index 0000000..352681c --- /dev/null +++ b/asynctest/private/chronos/eventually.nim @@ -0,0 +1,13 @@ +import pkg/chronos + +template eventually*(expression: untyped, timeout=5000): bool = + + proc eventually: Future[bool] {.async.} = + let endTime = Moment.now() + timeout.milliseconds + while not expression: + if endTime < Moment.now(): + return false + await sleepAsync(10.milliseconds) + return true + + await eventually() diff --git a/asynctest/private/chronos/unittest/runasync.nim b/asynctest/private/chronos/unittest/runasync.nim new file mode 100644 index 0000000..1306038 --- /dev/null +++ b/asynctest/private/chronos/unittest/runasync.nim @@ -0,0 +1,14 @@ +import pkg/chronos +import pkg/chronos/config + +when compiles(config.chronosHandleException): # detect chronos v4 + + template runAsync*(body): untyped = + let asyncProc = proc {.async: (raises: [Exception]).} = body + waitFor asyncProc() + +else: + + template runAsync*(body): untyped = + let asyncProc = proc {.async.} = body + waitFor asyncProc() diff --git a/asynctest/private/chronos/unittest2/runasync.nim b/asynctest/private/chronos/unittest2/runasync.nim new file mode 100644 index 0000000..2c139bf --- /dev/null +++ b/asynctest/private/chronos/unittest2/runasync.nim @@ -0,0 +1,5 @@ +import pkg/chronos + +template runAsync*(body): untyped = + let asyncProc = proc {.async.} = body + waitFor asyncProc() diff --git a/asynctest/private/suite.nim b/asynctest/private/suite.nim new file mode 100644 index 0000000..933173d --- /dev/null +++ b/asynctest/private/suite.nim @@ -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 diff --git a/asynctest/templates.nim b/asynctest/templates.nim deleted file mode 100644 index b347554..0000000 --- a/asynctest/templates.nim +++ /dev/null @@ -1,58 +0,0 @@ -template launderExceptions(body: typed) = - ## Chronos V4 requires that all procs which raise Exception annotate it - ## with {.async: (raises: [Exception]).}, but this construct does not - ## exist in asyncdispatch. We therefore launder all real instances of - ## Exception into CatchableError and make Chronos happy while not losing - ## context information for the remainder of the exception types. - {.push warning[BareExcept]:off.} - try: - {.push warning[BareExcept]:on.} - body - {.pop.} - except Defect as ex: - raise ex - except CatchableError as ex: - raise ex - except Exception as ex: - raise newException(Defect, ex.msg, ex) - {.pop.} - -template suite*(name, body) = - - suite name: - let suiteproc = proc = # Avoids GcUnsafe2 warnings with chronos - - ## Runs before all tests in the suite - template setupAll(setupAllBody) {.used.} = - let b = proc {.async.} = launderExceptions: setupAllBody - waitFor b() - - ## Runs after all tests in the suite - template teardownAll(teardownAllBody) {.used.} = - template teardownAllIMPL: untyped {.inject.} = - let a = proc {.async.} = launderExceptions: teardownAllBody - waitFor a() - - template setup(setupBody) {.used.} = - setup: - let asyncproc = proc {.async.} = launderExceptions: setupBody - waitFor asyncproc() - - template teardown(teardownBody) {.used.} = - teardown: - let exception = getCurrentException() - let asyncproc = proc {.async.} = launderExceptions: teardownBody - waitFor asyncproc() - setCurrentException(exception) - - body - - when declared(teardownAllIMPL): - teardownAllIMPL() - - suiteproc() - -template test*(name, body) = - test name: - let asyncproc = proc {.async.} = launderExceptions: body - waitFor asyncproc() diff --git a/asynctest/unittest.nim b/asynctest/unittest.nim deleted file mode 100644 index 8efbf33..0000000 --- a/asynctest/unittest.nim +++ /dev/null @@ -1,7 +0,0 @@ -import std/unittest -import ./eventually - -export unittest except suite, test -export eventually - -include ./templates diff --git a/asynctest/unittest2.nim b/asynctest/unittest2.nim deleted file mode 100644 index 67ad9cb..0000000 --- a/asynctest/unittest2.nim +++ /dev/null @@ -1,7 +0,0 @@ -import pkg/unittest2 -import ./eventually - -export unittest2 except suite, test -export eventually - -include ./templates diff --git a/testmodules/chronosv3/test.nim b/testmodules/chronosv3/test.nim index 22d3773..6383c09 100644 --- a/testmodules/chronosv3/test.nim +++ b/testmodules/chronosv3/test.nim @@ -1,5 +1,4 @@ -import pkg/asynctest -import pkg/chronos +import pkg/asynctest/chronos/unittest -include ../stdlib/testbody -include ../stdlib/testfail +include ../common/testbody +include ../common/testfail diff --git a/testmodules/chronosv4/test.nim b/testmodules/chronosv4/test.nim index 22d3773..6383c09 100644 --- a/testmodules/chronosv4/test.nim +++ b/testmodules/chronosv4/test.nim @@ -1,5 +1,4 @@ -import pkg/asynctest -import pkg/chronos +import pkg/asynctest/chronos/unittest -include ../stdlib/testbody -include ../stdlib/testfail +include ../common/testbody +include ../common/testfail diff --git a/testmodules/stdlib/testbody.nim b/testmodules/common/testbody.nim similarity index 100% rename from testmodules/stdlib/testbody.nim rename to testmodules/common/testbody.nim diff --git a/testmodules/stdlib/testfail.nim b/testmodules/common/testfail.nim similarity index 100% rename from testmodules/stdlib/testfail.nim rename to testmodules/common/testfail.nim diff --git a/testmodules/stdlib/test.nim b/testmodules/stdlib/test.nim index bfa135d..665f607 100644 --- a/testmodules/stdlib/test.nim +++ b/testmodules/stdlib/test.nim @@ -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 diff --git a/testmodules/unittest2/test.nim b/testmodules/unittest2/test.nim index b4dfb23..c71cb27 100644 --- a/testmodules/unittest2/test.nim +++ b/testmodules/unittest2/test.nim @@ -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 From 7bcc20b93f5adbd51e8153324992f7a9030215e7 Mon Sep 17 00:00:00 2001 From: Mark Spanbroek Date: Thu, 21 Dec 2023 10:17:17 +0100 Subject: [PATCH 2/4] Fix BareExcept warnings when using stdlib unittest with chronos v4 --- asynctest/private/chronos/unittest/runasync.nim | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/asynctest/private/chronos/unittest/runasync.nim b/asynctest/private/chronos/unittest/runasync.nim index 1306038..10bc1b8 100644 --- a/asynctest/private/chronos/unittest/runasync.nim +++ b/asynctest/private/chronos/unittest/runasync.nim @@ -4,7 +4,14 @@ import pkg/chronos/config when compiles(config.chronosHandleException): # detect chronos v4 template runAsync*(body): untyped = - let asyncProc = proc {.async: (raises: [Exception]).} = body + # 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: From 0fac3d3988b0830fbd42e7b3525d474db724f707 Mon Sep 17 00:00:00 2001 From: Mark Spanbroek Date: Thu, 21 Dec 2023 13:04:33 +0100 Subject: [PATCH 3/4] Add helpful compiler errors on old import paths --- asynctest.nim | 4 ++++ asynctest/unittest2.nim | 4 ++++ 2 files changed, 8 insertions(+) create mode 100644 asynctest.nim create mode 100644 asynctest/unittest2.nim diff --git a/asynctest.nim b/asynctest.nim new file mode 100644 index 0000000..4f74474 --- /dev/null +++ b/asynctest.nim @@ -0,0 +1,4 @@ +{.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".} diff --git a/asynctest/unittest2.nim b/asynctest/unittest2.nim new file mode 100644 index 0000000..292e9e2 --- /dev/null +++ b/asynctest/unittest2.nim @@ -0,0 +1,4 @@ +{.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".} From 607557d684e1f1204cbfd701e4c5e8e9b4acb2ed Mon Sep 17 00:00:00 2001 From: Mark Spanbroek Date: Thu, 21 Dec 2023 14:42:54 +0100 Subject: [PATCH 4/4] Update Readme with new imports --- Readme.md | 46 ++++++++++++++++++++++++++++------------------ 1 file changed, 28 insertions(+), 18 deletions(-) diff --git a/Readme.md b/Readme.md index fdf743c..91fdf5b 100644 --- a/Readme.md +++ b/Readme.md @@ -17,16 +17,29 @@ requires "asynctest >= 0.4.3 & < 0.5.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,17 +60,6 @@ suite "test async proc": ``` -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. - check eventually ---------------- @@ -78,12 +80,20 @@ check eventually x == 42 await future ``` -Unittest2 ---------- +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. -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/