mirror of
https://github.com/logos-messaging/nim-sds.git
synced 2026-07-02 22:10:13 +00:00
Three asyncSpawn usages removed: - sds.nim startPeriodicTasks: stored the periodic-task futures on ReliabilityManager (new field `periodicTasks: seq[FutureBase]`) so cleanup can cancel them on shutdown instead of leaking the loops against a cleared manager. - library/sds_thread/sds_thread.nim: fireSync moved BEFORE processing, then `await SdsThreadRequest.process(...)` instead of asyncSpawn'ing it. Aligns the worker with the SP-channel + lock assumption that there are no concurrent requests; caller throughput is unchanged because the caller only waits for receipt (fireSync), not processing. - tests TestBus repair callback: replaced asyncSpawn(deliverExcept...) with an explicit pending-delivery queue drained by `bus.drain()`. Integration tests no longer rely on `sleepAsync(10ms)` to let spawned deliveries finish — they await drain instead. Tests also pick up an asyncSetup/asyncTeardown pair (tests/async_unittest.nim) so suite fixtures can `await` directly. All `waitFor` in setup/teardown blocks is gone; only the top-level asyncTest wrapper still uses waitFor (once, to drive the async proc to completion). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
70 lines
2.3 KiB
Nim
70 lines
2.3 KiB
Nim
## Shared async-aware wrappers around `unittest` so tests in this repo can
|
|
## `await` directly in setup/test/teardown blocks instead of sprinkling
|
|
## `waitFor` at each call site.
|
|
##
|
|
## Usage:
|
|
##
|
|
## ```nim
|
|
## import ./async_unittest
|
|
##
|
|
## suite "X":
|
|
## var rm: ReliabilityManager
|
|
##
|
|
## asyncSetup:
|
|
## rm = newReliabilityManager(...).get()
|
|
## check (await rm.ensureChannel("ch")).isOk()
|
|
##
|
|
## asyncTeardown:
|
|
## if not rm.isNil:
|
|
## await rm.cleanup()
|
|
##
|
|
## asyncTest "Y":
|
|
## await rm.wrapOutgoingMessage(...)
|
|
## ```
|
|
##
|
|
## All three blocks run inside the same async proc (per test). unittest's
|
|
## own `setup:`/`teardown:` still work for purely synchronous fixtures.
|
|
|
|
import unittest, chronos
|
|
export unittest, chronos
|
|
|
|
template asyncSetup*(body: untyped) {.dirty.} =
|
|
## Async counterpart to unittest's `setup:`. Runs inside each asyncTest's
|
|
## async proc, so `await` works.
|
|
template asyncTestSetupIMPL(): untyped {.dirty.} =
|
|
body
|
|
|
|
template asyncTeardown*(body: untyped) {.dirty.} =
|
|
## Async counterpart to unittest's `teardown:`. Runs in a `finally` so it
|
|
## executes even when the test body (or setup) raises.
|
|
template asyncTestTeardownIMPL(): untyped {.dirty.} =
|
|
body
|
|
|
|
template asyncTest*(name: string, body: untyped) =
|
|
## Wraps a unittest `test` body in an async proc so `await` works on the
|
|
## now-async ReliabilityManager API. unittest's `check` raises Exception,
|
|
## which is wider than chronos's default CatchableError; the exception is
|
|
## caught inside the async body, stashed, and re-raised after waitFor so
|
|
## unittest's normal failure handling sees it.
|
|
##
|
|
## `cast(gcsafe)` is needed because suite-level vars (e.g. `var rm`) look
|
|
## like globals to the async closure, but the FFI runtime is single-thread
|
|
## so the "not gcsafe" warning isn't a real hazard here.
|
|
test name:
|
|
var asyncTestErr {.inject.}: ref Exception = nil
|
|
proc inner() {.async.} =
|
|
{.cast(gcsafe).}:
|
|
try:
|
|
when declared(asyncTestSetupIMPL):
|
|
asyncTestSetupIMPL()
|
|
try:
|
|
body
|
|
finally:
|
|
when declared(asyncTestTeardownIMPL):
|
|
asyncTestTeardownIMPL()
|
|
except Exception as e:
|
|
asyncTestErr = e
|
|
waitFor inner()
|
|
if asyncTestErr != nil:
|
|
raise asyncTestErr
|