Don't import/export chronos by default (#20)

Chronos support is optional and should not have to be imported in order
to use faststreams for non-async use cases, as doing so pollutes the
global namespace and slows down compilation.

`async` support must now explicitly be enabled with
-d:async_backend=chronos|asyncdispatch
This commit is contained in:
Jacek Sieka 2021-08-20 13:10:26 +02:00 • committed by GitHub
commit 3a0ab42573
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 841 additions and 705 deletions

View file

@ -1,7 +1,7 @@
mode = ScriptMode.Verbose mode = ScriptMode.Verbose
packageName = "faststreams" packageName = "faststreams"
version = "0.2.0" version = "0.3.0"
author = "Status Research & Development GmbH" author = "Status Research & Development GmbH"
description = "Nearly zero-overhead input/output streams for Nim" description = "Nearly zero-overhead input/output streams for Nim"
license = "Apache License 2.0" license = "Apache License 2.0"
@ -21,7 +21,12 @@ proc test(env, path: string) =
lang = getEnv"TEST_LANG" lang = getEnv"TEST_LANG"
exec "nim " & lang & " " & env & exec "nim " & lang & " " & env &
" -r --hints:off --skipParentCfg " & path " -d:async_backend=none -r --hints:off --skipParentCfg " & path
exec "nim " & lang & " " & env &
" -d:async_backend=chronos -r --hints:off --skipParentCfg " & path
# TODO std backend is broken / untested
# exec "nim " & lang & " " & env &
# " -d:async_backend=chronos -r --hints:off --skipParentCfg " & path
task test, "Run all tests": task test, "Run all tests":
test "-d:debug --threads:on", "tests/all_tests" test "-d:debug --threads:on", "tests/all_tests"

View file

@ -1,14 +1,25 @@
const const
faststreams_async_backend {.strdefine.} = "chronos" # To compile with async support, use `-d:async_backend=chronos|asyncdispatch`
async_backend {.strdefine.} = "none"
const
faststreams_async_backend {.strdefine.} = ""
when faststreams_async_backend != "":
{.fatal: "use `-d:async_backend` instead".}
type type
CloseBehavior* = enum CloseBehavior* = enum
waitAsyncClose waitAsyncClose
dontWaitAsyncClose dontWaitAsyncClose
const debugHelpers* = defined(debugHelpers) const
debugHelpers* = defined(debugHelpers)
fsAsyncSupport* = async_backend != "none"
when faststreams_async_backend == "chronos": when async_backend == "none":
discard
elif async_backend == "chronos":
import import
chronos chronos
@ -18,7 +29,7 @@ when faststreams_async_backend == "chronos":
template fsAwait*(f: Future): untyped = template fsAwait*(f: Future): untyped =
await f await f
elif faststreams_async_backend in ["std", "asyncdispatch"]: elif async_backend in ["std", "asyncdispatch"]:
import import
std/asyncdispatch std/asyncdispatch
@ -36,7 +47,7 @@ elif faststreams_async_backend in ["std", "asyncdispatch"]:
type Duration* = int type Duration* = int
else: else:
{.fatal: "Unrecognized network backend: " & faststreams_async_backend.} {.fatal: "Unrecognized network backend: " & async_backend.}
when defined(danger): when defined(danger):
template fsAssert*(x) = discard template fsAssert*(x) = discard

View file

@ -22,6 +22,7 @@ type
maxBufferedBytes*: Natural maxBufferedBytes*: Natural
queue*: Deque[PageRef] queue*: Deque[PageRef]
when fsAsyncSupport:
waitingReader*: Future[void] waitingReader*: Future[void]
waitingWriter*: Future[void] waitingWriter*: Future[void]

View file

@ -6,7 +6,9 @@ import
export export
options, CloseBehavior options, CloseBehavior
type when fsAsyncSupport:
# Circular type refs prevent more targeted `when`
type
InputStream* = ref object of RootObj InputStream* = ref object of RootObj
vtable*: ptr InputStreamVTable # This is nil for unsafe memory inputs vtable*: ptr InputStreamVTable # This is nil for unsafe memory inputs
buffers*: PageBuffers # This is nil for unsafe memory inputs buffers*: PageBuffers # This is nil for unsafe memory inputs
@ -16,12 +18,6 @@ type
when debugHelpers: when debugHelpers:
name*: string name*: string
LayeredInputStream* = ref object of InputStream
source*: InputStream
allowWaitFor*: bool
InputStreamHandle* = object
s*: InputStream
AsyncInputStream* {.borrow: `.`.} = distinct InputStream AsyncInputStream* {.borrow: `.`.} = distinct InputStream
@ -42,10 +38,47 @@ type
InputStreamVTable* = object InputStreamVTable* = object
readSync*: ReadSyncProc readSync*: ReadSyncProc
readAsync*: ReadAsyncProc
closeSync*: CloseSyncProc closeSync*: CloseSyncProc
closeAsync*: CloseAsyncProc
getLenSync*: GetLenSyncProc getLenSync*: GetLenSyncProc
readAsync*: ReadAsyncProc
closeAsync*: CloseAsyncProc
MaybeAsyncInputStream* = InputStream | AsyncInputStream
else:
type
InputStream* = ref object of RootObj
vtable*: ptr InputStreamVTable # This is nil for unsafe memory inputs
buffers*: PageBuffers # This is nil for unsafe memory inputs
span*: PageSpan
spanEndPos*: Natural
when debugHelpers:
name*: string
ReadSyncProc* = proc (s: InputStream, dst: pointer, dstLen: Natural): Natural
{.nimcall, gcsafe, raises: [IOError, Defect].}
CloseSyncProc* = proc (s: InputStream)
{.nimcall, gcsafe, raises: [IOError, Defect].}
GetLenSyncProc* = proc (s: InputStream): Option[Natural]
{.nimcall, gcsafe, raises: [IOError, Defect].}
InputStreamVTable* = object
readSync*: ReadSyncProc
closeSync*: CloseSyncProc
getLenSync*: GetLenSyncProc
MaybeAsyncInputStream* = InputStream
type
LayeredInputStream* = ref object of InputStream
source*: InputStream
allowWaitFor*: bool
InputStreamHandle* = object
s*: InputStream
MemFileInputStream = ref object of InputStream MemFileInputStream = ref object of InputStream
file: MemFile file: MemFile
@ -54,29 +87,37 @@ type
file: File file: File
template Sync*(s: InputStream): InputStream = s template Sync*(s: InputStream): InputStream = s
template Async*(s: InputStream): AsyncInputStream = AsyncInputStream(s)
template Sync*(s: AsyncInputStream): InputStream = InputStream(s) when fsAsyncSupport:
template Async*(s: AsyncInputStream): AsyncInputStream = s template Async*(s: InputStream): AsyncInputStream = AsyncInputStream(s)
template Sync*(s: AsyncInputStream): InputStream = InputStream(s)
template Async*(s: AsyncInputStream): AsyncInputStream = s
proc disconnectInputDevice(s: InputStream) = proc disconnectInputDevice(s: InputStream) =
# TODO # TODO
# Document the behavior that closeAsync is preferred # Document the behavior that closeAsync is preferred
if s.vtable != nil: if s.vtable != nil:
when fsAsyncSupport:
if s.vtable.closeAsync != nil: if s.vtable.closeAsync != nil:
s.closeFut = s.vtable.closeAsync(s) s.closeFut = s.vtable.closeAsync(s)
elif s.vtable.closeSync != nil: elif s.vtable.closeSync != nil:
s.vtable.closeSync(s) s.vtable.closeSync(s)
else:
if s.vtable.closeSync != nil:
s.vtable.closeSync(s)
s.vtable = nil s.vtable = nil
template disconnectInputDevice(s: AsyncInputStream) = when fsAsyncSupport:
template disconnectInputDevice(s: AsyncInputStream) =
disconnectInputDevice InputStream(s) disconnectInputDevice InputStream(s)
proc preventFurtherReading(s: InputStream) = proc preventFurtherReading(s: InputStream) =
s.vtable = nil s.vtable = nil
s.span = default(PageSpan) s.span = default(PageSpan)
template preventFurtherReading(s: AsyncInputStream) = when fsAsyncSupport:
template preventFurtherReading(s: AsyncInputStream) =
preventFurtherReading InputStream(s) preventFurtherReading InputStream(s)
template makeHandle*(sp: InputStream): InputStreamHandle = template makeHandle*(sp: InputStream): InputStreamHandle =
@ -94,6 +135,7 @@ proc close*(s: InputStream,
## `waitFor` to block until the async operation completes. ## `waitFor` to block until the async operation completes.
s.disconnectInputDevice() s.disconnectInputDevice()
s.preventFurtherReading() s.preventFurtherReading()
when fsAsyncSupport:
if s.closeFut != nil: if s.closeFut != nil:
fsTranslateErrors "Stream closing failed": fsTranslateErrors "Stream closing failed":
if behavior == waitAsyncClose: if behavior == waitAsyncClose:
@ -101,7 +143,8 @@ proc close*(s: InputStream,
else: else:
asyncCheck s.closeFut asyncCheck s.closeFut
template close*(sp: AsyncInputStream) = when fsAsyncSupport:
template close*(sp: AsyncInputStream) =
## Starts the asychronous closing of the stream and returns a future that ## Starts the asychronous closing of the stream and returns a future that
## tracks the closing operation. ## tracks the closing operation.
let s = InputStream sp let s = InputStream sp
@ -110,7 +153,7 @@ template close*(sp: AsyncInputStream) =
if s.closeFut != nil: if s.closeFut != nil:
fsAwait s.closeFut fsAwait s.closeFut
template closeNoWait*(sp: AsyncInputStream|InputStream) = template closeNoWait*(sp: MaybeAsyncInputStream) =
## Close the stream without waiting even if's async. ## Close the stream without waiting even if's async.
## This operation will use `asyncCheck` internally to detect unhandled ## This operation will use `asyncCheck` internally to detect unhandled
## errors from the closing operation. ## errors from the closing operation.
@ -224,17 +267,11 @@ proc readableNow*(s: InputStream): bool =
getNewSpan s getNewSpan s
s.span.hasRunway s.span.hasRunway
template readableNow*(s: AsyncInputStream): bool = when fsAsyncSupport:
template readableNow*(s: AsyncInputStream): bool =
readableNow InputStream(s) readableNow InputStream(s)
# TODO: The pure async interface should be moved in a separate module proc readOnce*(sp: AsyncInputStream): Future[Natural] {.async.} =
# to make FastStreams more light-weight when the async back-end
# is not used (e.g. in Confutils)
#
# The problem is that the `async` macro will pull the entire
# event loop right now.
proc readOnce*(sp: AsyncInputStream): Future[Natural] {.async.} =
let s = InputStream(sp) let s = InputStream(sp)
fsAssert s.buffers != nil and s.vtable != nil fsAssert s.buffers != nil and s.vtable != nil
@ -246,7 +283,7 @@ proc readOnce*(sp: AsyncInputStream): Future[Natural] {.async.} =
if result > 0 and s.span.len == 0: if result > 0 and s.span.len == 0:
getNewSpan s getNewSpan s
proc timeoutToNextByteImpl(s: AsyncInputStream, proc timeoutToNextByteImpl(s: AsyncInputStream,
deadline: Future): Future[bool] {.async.} = deadline: Future): Future[bool] {.async.} =
let readFut = s.readOnce let readFut = s.readOnce
fsAwait readFut or deadline fsAwait readFut or deadline
@ -256,24 +293,26 @@ proc timeoutToNextByteImpl(s: AsyncInputStream,
else: else:
return false return false
template timeoutToNextByte*(sp: AsyncInputStream, deadline: Future): bool = template timeoutToNextByte*(sp: AsyncInputStream, deadline: Future): bool =
let s = sp let s = sp
if readableNow(s): if readableNow(s):
true true
else: else:
fsAwait timeoutToNextByteImpl(s, deadline) fsAwait timeoutToNextByteImpl(s, deadline)
template timeoutToNextByte*(sp: AsyncInputStream, timeout: Duration): bool = template timeoutToNextByte*(sp: AsyncInputStream, timeout: Duration): bool =
let s = sp let s = sp
if readableNow(s): if readableNow(s):
true true
else: else:
fsAwait timeoutToNextByteImpl(s, sleepAsync(timeout)) fsAwait timeoutToNextByteImpl(s, sleepAsync(timeout))
proc closeAsync*(s: AsyncInputStream) {.async.} = proc closeAsync*(s: AsyncInputStream) {.async.} =
close s close s
# TODO: End of purely async interface template totalUnconsumedBytes*(s: AsyncInputStream): Natural =
## Alias for InputStream.totalUnconsumedBytes
totalUnconsumedBytes InputStream(s)
func getBestContiguousRunway(s: InputStream): Natural = func getBestContiguousRunway(s: InputStream): Natural =
result = s.span.len result = s.span.len
@ -298,10 +337,6 @@ func totalUnconsumedBytes*(s: InputStream): Natural =
localRunway + runwayInBuffers localRunway + runwayInBuffers
template totalUnconsumedBytes*(s: AsyncInputStream): Natural =
## Alias for InputStream.totalUnconsumedBytes
totalUnconsumedBytes InputStream(s)
proc limitReadableRange(s: InputStream, rangeLen: Natural): Natural = proc limitReadableRange(s: InputStream, rangeLen: Natural): Natural =
s.vtable = nil s.vtable = nil
@ -317,7 +352,7 @@ proc limitReadableRange(s: InputStream, rangeLen: Natural): Natural =
s.buffers.queue.peekFirst.consumedTo -= bytesToUnconsume s.buffers.queue.peekFirst.consumedTo -= bytesToUnconsume
return s.buffers.setFauxEof(s.spanEndPos) return s.buffers.setFauxEof(s.spanEndPos)
template withReadableRange*(sp: InputStream|AsyncInputStream, template withReadableRange*(sp: MaybeAsyncInputStream,
rangeLen: Natural, rangeLen: Natural,
rangeStreamVarName, blk: untyped) = rangeStreamVarName, blk: untyped) =
let let
@ -409,7 +444,8 @@ proc len*(s: InputStream): Option[Natural] {.raises: [Defect, IOError].} =
else: else:
none Natural none Natural
template len*(s: AsyncInputStream): Option[Natural] = when fsAsyncSupport:
template len*(s: AsyncInputStream): Option[Natural] =
len InputStream(s) len InputStream(s)
func memoryInput*(buffers: PageBuffers): InputStreamHandle = func memoryInput*(buffers: PageBuffers): InputStreamHandle =
@ -541,7 +577,8 @@ template readable*(sp: InputStream): bool =
let s = sp let s = sp
hasRunway(s.span) or bufferMoreDataSync(s) hasRunway(s.span) or bufferMoreDataSync(s)
template readable*(sp: AsyncInputStream): bool = when fsAsyncSupport:
template readable*(sp: AsyncInputStream): bool =
## Async version of `readable`. ## Async version of `readable`.
## The intended API usage is the same. Instead of blocking, an async ## The intended API usage is the same. Instead of blocking, an async
## stream will use `await` while waiting for more data. ## stream will use `await` while waiting for more data.
@ -615,7 +652,8 @@ proc readable*(s: InputStream, n: int): bool =
## for futher discussion of this. ## for futher discussion of this.
readableNImpl(s, n, noAwait, readSync) readableNImpl(s, n, noAwait, readSync)
template readable*(sp: AsyncInputStream, np: int): bool = when fsAsyncSupport:
template readable*(sp: AsyncInputStream, np: int): bool =
## Async version of `readable(n)`. ## Async version of `readable(n)`.
## The intended API usage is the same. Instead of blocking, an async ## The intended API usage is the same. Instead of blocking, an async
## stream will use `await` while waiting for more data. ## stream will use `await` while waiting for more data.
@ -633,7 +671,8 @@ template peek*(sp: InputStream): byte =
getNewSpanOrDieTrying s getNewSpanOrDieTrying s
s.span.startAddr[] s.span.startAddr[]
template peek*(s: AsyncInputStream): byte = when fsAsyncSupport:
template peek*(s: AsyncInputStream): byte =
peek InputStream(s) peek InputStream(s)
func readFromNewSpan(s: InputStream): byte = func readFromNewSpan(s: InputStream): byte =
@ -650,7 +689,8 @@ template read*(sp: InputStream): byte =
else: else:
readFromNewSpan s readFromNewSpan s
template read*(s: AsyncInputStream): byte = when fsAsyncSupport:
template read*(s: AsyncInputStream): byte =
read InputStream(s) read InputStream(s)
proc peekAt*(s: InputStream, pos: int): byte {.inline.} = proc peekAt*(s: InputStream, pos: int): byte {.inline.} =
@ -659,7 +699,8 @@ proc peekAt*(s: InputStream, pos: int): byte {.inline.} =
fsAssert cast[uint](peekHead) < cast[uint](s.span.endAddr) fsAssert cast[uint](peekHead) < cast[uint](s.span.endAddr)
return peekHead[] return peekHead[]
template peekAt*(s: AsyncInputStream, pos: int): byte = when fsAsyncSupport:
template peekAt*(s: AsyncInputStream, pos: int): byte =
peekAt InputStream(s), pos peekAt InputStream(s), pos
proc advance*(s: InputStream) = proc advance*(s: InputStream) =
@ -673,10 +714,11 @@ proc advance*(s: InputStream, n: Natural) =
for i in 0 ..< n: for i in 0 ..< n:
advance s advance s
template advance*(s: AsyncInputStream) = when fsAsyncSupport:
template advance*(s: AsyncInputStream) =
advance InputStream(s) advance InputStream(s)
template advance*(s: AsyncInputStream, n: Natural) = template advance*(s: AsyncInputStream, n: Natural) =
advance InputStream(s), n advance InputStream(s), n
proc drainBuffersInto*(s: InputStream, dstAddr: ptr byte, dstLen: Natural): Natural = proc drainBuffersInto*(s: InputStream, dstAddr: ptr byte, dstLen: Natural): Natural =
@ -789,14 +831,15 @@ proc readInto*(s: InputStream, target: var openarray[byte]): bool =
## regarding the number of bytes read, see `readIntoEx`. ## regarding the number of bytes read, see `readIntoEx`.
s.readIntoEx(target) == target.len s.readIntoEx(target) == target.len
template readIntoEx*(sp: AsyncInputStream, dst: var openarray[byte]): int = when fsAsyncSupport:
template readIntoEx*(sp: AsyncInputStream, dst: var openarray[byte]): int =
let s = InputStream(sp) let s = InputStream(sp)
# BEWARE! `openArrayToPair` here is needed to avoid # BEWARE! `openArrayToPair` here is needed to avoid
# double evaluation of the `dst` expression: # double evaluation of the `dst` expression:
let (dstAddr, dstLen) = openArrayToPair(dst) let (dstAddr, dstLen) = openArrayToPair(dst)
readIntoExImpl(s, dstAddr, dstLen, fsAwait, readAsync) readIntoExImpl(s, dstAddr, dstLen, fsAwait, readAsync)
template readInto*(sp: AsyncInputStream, dst: var openarray[byte]): bool = template readInto*(sp: AsyncInputStream, dst: var openarray[byte]): bool =
## Asynchronously read data into the destination buffer. ## Asynchronously read data into the destination buffer.
## ##
## Returns `false` if EOF was reached before the buffer ## Returns `false` if EOF was reached before the buffer
@ -869,7 +912,8 @@ template read*(sp: InputStream, np: static Natural): openarray[byte] =
template read*(s: InputStream, n: Natural): openarray[byte] = template read*(s: InputStream, n: Natural): openarray[byte] =
readNImpl(s, n, useHeapMem) readNImpl(s, n, useHeapMem)
template read*(s: AsyncInputStream, n: Natural): openarray[byte] = when fsAsyncSupport:
template read*(s: AsyncInputStream, n: Natural): openarray[byte] =
read InputStream(s), n read InputStream(s), n
proc lookAheadMatch*(s: InputStream, data: openarray[byte]): bool = proc lookAheadMatch*(s: InputStream, data: openarray[byte]): bool =
@ -879,14 +923,16 @@ proc lookAheadMatch*(s: InputStream, data: openarray[byte]): bool =
return true return true
template lookAheadMatch*(s: AsyncInputStream, data: openarray[byte]): bool = when fsAsyncSupport:
template lookAheadMatch*(s: AsyncInputStream, data: openarray[byte]): bool =
lookAheadMatch InputStream(s) lookAheadMatch InputStream(s)
proc next*(s: InputStream): Option[byte] = proc next*(s: InputStream): Option[byte] =
if readable(s): if readable(s):
result = some read(s) result = some read(s)
template next*(sp: AsyncInputStream): Option[byte] = when fsAsyncSupport:
template next*(sp: AsyncInputStream): Option[byte] =
let s = sp let s = sp
if readable(s): if readable(s):
some read(s) some read(s)
@ -896,6 +942,7 @@ template next*(sp: AsyncInputStream): Option[byte] =
proc pos*(s: InputStream): int {.inline.} = proc pos*(s: InputStream): int {.inline.} =
s.spanEndPos - s.span.len s.spanEndPos - s.span.len
template pos*(s: AsyncInputStream): int = when fsAsyncSupport:
template pos*(s: AsyncInputStream): int =
pos InputStream(s) pos InputStream(s)

View file

@ -1,11 +1,15 @@
import import
stew/shims/macros, async_backend
async_backend, inputs, outputs
export export
async_backend async_backend
macro fsMultiSync*(body: untyped) = when fsAsyncSupport:
import
stew/shims/macros,
"."/[inputs, outputs]
macro fsMultiSync*(body: untyped) =
# We will produce an identical copy of the annotated proc, # We will produce an identical copy of the annotated proc,
# but taking async parameters and having the async pragma. # but taking async parameters and having the async pragma.
var var
@ -32,4 +36,5 @@ macro fsMultiSync*(body: untyped) =
result = newStmtList(body, asyncProcBody) result = newStmtList(body, asyncProcBody)
when defined(debugSupportAsync): when defined(debugSupportAsync):
echo result.repr echo result.repr
else:
macro fsMultiSync*(body: untyped) = body

View file

@ -14,7 +14,9 @@ import
export export
initPageBuffers, CloseBehavior initPageBuffers, CloseBehavior
type when fsAsyncSupport:
# Circular type refs prevent more targeted `when`
type
OutputStream* = ref object of RootObj OutputStream* = ref object of RootObj
vtable*: ptr OutputStreamVTable # This is nil for any memory output vtable*: ptr OutputStreamVTable # This is nil for any memory output
buffers*: PageBuffers # This is nil for unsafe memory outputs buffers*: PageBuffers # This is nil for unsafe memory outputs
@ -25,17 +27,6 @@ type
when debugHelpers: when debugHelpers:
name*: string name*: string
WriteCursor* = object
span: PageSpan
stream: OutputStream
LayeredOutputStream* = ref object of OutputStream
destination*: OutputStream
allowWaitFor*: bool
OutputStreamHandle* = object
s*: OutputStream
AsyncOutputStream* {.borrow: `.`.} = distinct OutputStream AsyncOutputStream* {.borrow: `.`.} = distinct OutputStream
WriteSyncProc* = proc (s: OutputStream, src: pointer, srcLen: Natural) WriteSyncProc* = proc (s: OutputStream, src: pointer, srcLen: Natural)
@ -64,26 +55,76 @@ type
closeSync*: CloseSyncProc closeSync*: CloseSyncProc
closeAsync*: CloseAsyncProc closeAsync*: CloseAsyncProc
MaybeAsyncOutputStream* = OutputStream | AsyncOutputStream
else:
type
OutputStream* = ref object of RootObj
vtable*: ptr OutputStreamVTable # This is nil for any memory output
buffers*: PageBuffers # This is nil for unsafe memory outputs
span*: PageSpan
spanEndPos*: Natural
extCursorsCount: int
when debugHelpers:
name*: string
WriteSyncProc* = proc (s: OutputStream, src: pointer, srcLen: Natural)
{.nimcall, gcsafe, raises: [IOError, Defect].}
FlushSyncProc* = proc (s: OutputStream)
{.nimcall, gcsafe, raises: [IOError, Defect].}
CloseSyncProc* = proc (s: OutputStream)
{.nimcall, gcsafe, raises: [IOError, Defect].}
OutputStreamVTable* = object
writeSync*: WriteSyncProc
flushSync*: FlushSyncProc
closeSync*: CloseSyncProc
MaybeAsyncOutputStream* = OutputStream
type
WriteCursor* = object
span: PageSpan
stream: OutputStream
LayeredOutputStream* = ref object of OutputStream
destination*: OutputStream
allowWaitFor*: bool
OutputStreamHandle* = object
s*: OutputStream
VarSizeWriteCursor* = distinct WriteCursor VarSizeWriteCursor* = distinct WriteCursor
FileOutputStream = ref object of OutputStream FileOutputStream = ref object of OutputStream
file: File file: File
template Sync*(s: OutputStream): OutputStream = s template Sync*(s: OutputStream): OutputStream = s
template Async*(s: OutputStream): AsyncOutputStream = AsyncOutputStream(s)
template Sync*(s: AsyncOutputStream): OutputStream = OutputStream(s) when fsAsyncSupport:
template Async*(s: AsyncOutputStream): AsyncOutputStream = s template Async*(s: OutputStream): AsyncOutputStream = AsyncOutputStream(s)
template Sync*(s: AsyncOutputStream): OutputStream = OutputStream(s)
template Async*(s: AsyncOutputStream): AsyncOutputStream = s
proc disconnectOutputDevice(s: OutputStream) = proc disconnectOutputDevice(s: OutputStream) =
if s.vtable != nil: if s.vtable != nil:
when fsAsyncSupport:
if s.vtable.closeAsync != nil: if s.vtable.closeAsync != nil:
s.closeFut = s.vtable.closeAsync(s) s.closeFut = s.vtable.closeAsync(s)
elif s.vtable.closeSync != nil: elif s.vtable.closeSync != nil:
s.vtable.closeSync(s) s.vtable.closeSync(s)
else:
if s.vtable.closeSync != nil:
s.vtable.closeSync(s)
s.vtable = nil s.vtable = nil
template disconnectOutputDevice(s: AsyncOutputStream) = when fsAsyncSupport:
template disconnectOutputDevice(s: AsyncOutputStream) =
disconnectOutputDevice OutputStream(s) disconnectOutputDevice OutputStream(s)
template flushImpl(s: OutputStream, awaiter, writeOp, flushOp: untyped) = template flushImpl(s: OutputStream, awaiter, writeOp, flushOp: untyped) =
@ -99,18 +140,12 @@ template flushImpl(s: OutputStream, awaiter, writeOp, flushOp: untyped) =
proc flush*(s: OutputStream) = proc flush*(s: OutputStream) =
flushImpl(s, noAwait, writeSync, flushSync) flushImpl(s, noAwait, writeSync, flushSync)
template flush*(sp: AsyncOutputStream) =
let s = OutputStream sp
flushImpl(s, fsAwait, writeAsync, flushAsync)
proc flushAsync*(s: AsyncOutputStream) {.async.} =
flush s
proc close*(s: OutputStream, proc close*(s: OutputStream,
behavior = dontWaitAsyncClose) behavior = dontWaitAsyncClose)
{.raises: [IOError, Defect].} = {.raises: [IOError, Defect].} =
flush s flush s
disconnectOutputDevice(s) disconnectOutputDevice(s)
when fsAsyncSupport:
if s.closeFut != nil: if s.closeFut != nil:
fsTranslateErrors "Stream closing failed": fsTranslateErrors "Stream closing failed":
if behavior == waitAsyncClose: if behavior == waitAsyncClose:
@ -118,17 +153,7 @@ proc close*(s: OutputStream,
else: else:
asyncCheck s.closeFut asyncCheck s.closeFut
template close*(sp: AsyncOutputStream) = template closeNoWait*(sp: MaybeAsyncOutputStream) =
let s = OutputStream sp
flush(Async s)
disconnectOutputDevice(s)
if s.closeFut != nil:
fsAwait s.closeFut
proc closeAsync*(s: AsyncOutputStream) {.async.} =
close s
template closeNoWait*(sp: AsyncOutputStream|OutputStream) =
## Close the stream without waiting even if's async. ## Close the stream without waiting even if's async.
## This operation will use `asyncCheck` internally to detect unhandled ## This operation will use `asyncCheck` internally to detect unhandled
## errors from the closing operation. ## errors from the closing operation.
@ -196,7 +221,8 @@ proc ensureRunway*(s: OutputStream, neededRunway: Natural) =
s.buffers.ensureRunway(s.span, neededRunway) s.buffers.ensureRunway(s.span, neededRunway)
s.spanEndPos += (s.span.len - runway) s.spanEndPos += (s.span.len - runway)
template ensureRunway*(s: AsyncOutputStream, neededRunway: Natural) = when fsAsyncSupport:
template ensureRunway*(s: AsyncOutputStream, neededRunway: Natural) =
ensureRunway OutputStream(s), neededRunway ensureRunway OutputStream(s), neededRunway
template implementWrites*(buffersParam: PageBuffers, template implementWrites*(buffersParam: PageBuffers,
@ -266,7 +292,8 @@ proc fileOutput*(filename: string,
proc pos*(s: OutputStream): int = proc pos*(s: OutputStream): int =
s.spanEndPos - s.span.len s.spanEndPos - s.span.len
template pos*(s: AsyncOutputStream): int = when fsAsyncSupport:
template pos*(s: AsyncOutputStream): int =
pos OutputStream(s) pos OutputStream(s)
proc getBuffers*(s: OutputStream): PageBuffers = proc getBuffers*(s: OutputStream): PageBuffers =
@ -313,7 +340,8 @@ proc drainAllBuffersSync(s: OutputStream, buf: pointer, bufSize: Natural) =
s.span = s.buffers.getWritableSpan() s.span = s.buffers.getWritableSpan()
s.spanEndPos += s.span.len s.spanEndPos += s.span.len
proc drainAllBuffersAsync(s: OutputStream, buf: pointer, bufSize: Natural) {.async.} = when fsAsyncSupport:
proc drainAllBuffersAsync(s: OutputStream, buf: pointer, bufSize: Natural) {.async.} =
fsAwait s.vtable.writeAsync(s, buf, bufSize) fsAwait s.vtable.writeAsync(s, buf, bufSize)
s.span = s.buffers.getWritableSpan() s.span = s.buffers.getWritableSpan()
s.spanEndPos += s.span.len s.spanEndPos += s.span.len
@ -533,20 +561,21 @@ template write*(sp: OutputStream, b: byte) =
else: else:
writeToNewSpan(s, b) writeToNewSpan(s, b)
proc write*(sp: AsyncOutputStream, b: byte) = when fsAsyncSupport:
proc write*(sp: AsyncOutputStream, b: byte) =
let s = OutputStream sp let s = OutputStream sp
if atEnd(s.span): if atEnd(s.span):
addPage(s) addPage(s)
writeByte(s.span, b) writeByte(s.span, b)
template writeAndWait*(sp: AsyncOutputStream, b: byte) = template writeAndWait*(sp: AsyncOutputStream, b: byte) =
let s = OutputStream sp let s = OutputStream sp
if hasRunway(s.span): if hasRunway(s.span):
writeByte(s.span, b) writeByte(s.span, b)
else: else:
writeToNewSpanImpl(s, b, fsAwait, writeAsync, drainAllBuffersAsync) writeToNewSpanImpl(s, b, fsAwait, writeAsync, drainAllBuffersAsync)
template write*(s: AsyncOutputStream, x: char) = template write*(s: AsyncOutputStream, x: char) =
write s, byte(x) write s, byte(x)
template write*(s: OutputStream|var WriteCursor, x: char) = template write*(s: OutputStream|var WriteCursor, x: char) =
@ -606,7 +635,7 @@ proc write*(s: OutputStream, bytes: openArray[byte]) =
proc write*(s: OutputStream, chars: openArray[char]) = proc write*(s: OutputStream, chars: openArray[char]) =
write s, charsToBytes(chars) write s, charsToBytes(chars)
proc write*(s: OutputStream|AsyncOutputStream, value: string) {.inline.} = proc write*(s: MaybeAsyncOutputStream, value: string) {.inline.} =
write s, value.toOpenArrayByte(0, value.len - 1) write s, value.toOpenArrayByte(0, value.len - 1)
template memCopyToBytes(value: auto): untyped = template memCopyToBytes(value: auto): untyped =
@ -618,24 +647,26 @@ template memCopyToBytes(value: auto): untyped =
proc writeMemCopy*(s: OutputStream, value: auto) = proc writeMemCopy*(s: OutputStream, value: auto) =
write s, memCopyToBytes(value) write s, memCopyToBytes(value)
proc writeBytesAsyncImpl(sp: OutputStream, when fsAsyncSupport:
proc writeBytesAsyncImpl(sp: OutputStream,
bytes: openarray[byte]): Future[void] = bytes: openarray[byte]): Future[void] =
let s = sp let s = sp
writeBytesImpl(s, bytes): writeBytesImpl(s, bytes):
return s.vtable.writeAsync(s, unsafeAddr bytes[0], bytes.len) return s.vtable.writeAsync(s, unsafeAddr bytes[0], bytes.len)
proc writeBytesAsyncImpl(s: OutputStream, proc writeBytesAsyncImpl(s: OutputStream,
chars: openarray[char]): Future[void] = chars: openarray[char]): Future[void] =
writeBytesAsyncImpl s, charsToBytes(chars) writeBytesAsyncImpl s, charsToBytes(chars)
proc writeBytesAsyncImpl(s: OutputStream, proc writeBytesAsyncImpl(s: OutputStream,
str: string): Future[void] = str: string): Future[void] =
writeBytesAsyncImpl s, toOpenArray(str, 0, str.len - 1) writeBytesAsyncImpl s, toOpenArray(str, 0, str.len - 1)
template writeAndWait*(s: OutputStream, value: untyped) = template writeAndWait*(s: OutputStream, value: untyped) =
write s, value write s, value
template writeAndWait*(sp: AsyncOutputStream, value: untyped) = when fsAsyncSupport:
template writeAndWait*(sp: AsyncOutputStream, value: untyped) =
bind writeBytesAsyncImpl bind writeBytesAsyncImpl
let let
@ -647,7 +678,7 @@ template writeAndWait*(sp: AsyncOutputStream, value: untyped) =
s.span = getWritableSpan s.buffers s.span = getWritableSpan s.buffers
s.spanEndPos += s.span.len s.spanEndPos += s.span.len
template writeMemCopyAndWait*(sp: AsyncOutputStream, value: auto) = template writeMemCopyAndWait*(sp: AsyncOutputStream, value: auto) =
writeAndWait(sp, memCopyToBytes(value)) writeAndWait(sp, memCopyToBytes(value))
proc writeBytesToCursor(c: var WriteCursor, bytes: openarray[byte]) = proc writeBytesToCursor(c: var WriteCursor, bytes: openarray[byte]) =
@ -795,9 +826,28 @@ template getOutput*(s: OutputStream, T: type seq[byte]): seq[byte] =
template getOutput*(s: OutputStream): seq[byte] = template getOutput*(s: OutputStream): seq[byte] =
cast[seq[byte]](s.getOutput(string)) cast[seq[byte]](s.getOutput(string))
template getOutput*(s: AsyncOutputStream): seq[byte] = when fsAsyncSupport:
template getOutput*(s: AsyncOutputStream): seq[byte] =
getOutput OutputStream(s) getOutput OutputStream(s)
template getOutput*(s: AsyncOutputStream, T: type): untyped = template getOutput*(s: AsyncOutputStream, T: type): untyped =
getOutput OutputStream(s), T getOutput OutputStream(s), T
when fsAsyncSupport:
template flush*(sp: AsyncOutputStream) =
let s = OutputStream sp
flushImpl(s, fsAwait, writeAsync, flushAsync)
proc flushAsync*(s: AsyncOutputStream) {.async.} =
flush s
template close*(sp: AsyncOutputStream) =
let s = OutputStream sp
flush(Async s)
disconnectOutputDevice(s)
if s.closeFut != nil:
fsAwait s.closeFut
proc closeAsync*(s: AsyncOutputStream) {.async.} =
close s

View file

@ -1,29 +1,33 @@
import import
macros, "."/[inputs, outputs, async_backend]
inputs, outputs, buffers, async_backend
export export
inputs, outputs, async_backend inputs, outputs, async_backend
type when fsAsyncSupport:
import
std/macros,
./buffers
type
Pipe* = ref object Pipe* = ref object
# TODO: Make these stream handles # TODO: Make these stream handles
input*: AsyncInputStream input*: AsyncInputStream
output*: AsyncOutputStream output*: AsyncOutputStream
buffers*: PageBuffers buffers*: PageBuffers
template enterWait(fut: var Future, context: static string) = template enterWait(fut: var Future, context: static string) =
let wait = newFuture[void](context) let wait = newFuture[void](context)
fut = wait fut = wait
try: fsAwait wait try: fsAwait wait
finally: fut = nil finally: fut = nil
template awake(fp: Future) = template awake(fp: Future) =
let f = fp let f = fp
if f != nil and not finished(f): if f != nil and not finished(f):
complete f complete f
proc pipeRead(s: LayeredInputStream, proc pipeRead(s: LayeredInputStream,
dst: pointer, dstLen: Natural): Future[Natural] {.async.} = dst: pointer, dstLen: Natural): Future[Natural] {.async.} =
let buffers = s.buffers let buffers = s.buffers
if buffers.eofReached: return 0 if buffers.eofReached: return 0
@ -49,7 +53,7 @@ proc pipeRead(s: LayeredInputStream,
return bytesInBuffersNow - bytesInBuffersAtStart return bytesInBuffersNow - bytesInBuffersAtStart
proc pipeWrite(s: LayeredOutputStream, src: pointer, srcLen: Natural) {.async.} = proc pipeWrite(s: LayeredOutputStream, src: pointer, srcLen: Natural) {.async.} =
let buffers = s.buffers let buffers = s.buffers
while buffers.canAcceptWrite(srcLen) == false: while buffers.canAcceptWrite(srcLen) == false:
buffers.waitingWriter.enterWait "waiting for reader to drain the buffers" buffers.waitingWriter.enterWait "waiting for reader to drain the buffers"
@ -60,12 +64,12 @@ proc pipeWrite(s: LayeredOutputStream, src: pointer, srcLen: Natural) {.async.}
awake buffers.waitingReader awake buffers.waitingReader
describeBuffers "pipeWrite", buffers describeBuffers "pipeWrite", buffers
template completedFuture(name: static string): untyped = template completedFuture(name: static string): untyped =
let fut = newFuture[void](name) let fut = newFuture[void](name)
complete fut complete fut
fut fut
let pipeInputVTable = InputStreamVTable( let pipeInputVTable = InputStreamVTable(
readSync: proc (s: InputStream, dst: pointer, dstLen: Natural): Natural readSync: proc (s: InputStream, dst: pointer, dstLen: Natural): Natural
{.nimcall, gcsafe, raises: [IOError, Defect].} = {.nimcall, gcsafe, raises: [IOError, Defect].} =
fsTranslateErrors "Failed to read from pipe": fsTranslateErrors "Failed to read from pipe":
@ -99,9 +103,9 @@ let pipeInputVTable = InputStreamVTable(
return closeAsync(Async source) return closeAsync(Async source)
else: else:
return completedFuture("pipeInput.closeAsync") return completedFuture("pipeInput.closeAsync")
) )
let pipeOutputVTable = OutputStreamVTable( let pipeOutputVTable = OutputStreamVTable(
writeSync: proc (s: OutputStream, src: pointer, srcLen: Natural) writeSync: proc (s: OutputStream, src: pointer, srcLen: Natural)
{.nimcall, gcsafe, raises: [IOError, Defect].} = {.nimcall, gcsafe, raises: [IOError, Defect].} =
fsTranslateErrors "Failed to write all bytes to pipe": fsTranslateErrors "Failed to write all bytes to pipe":
@ -156,9 +160,9 @@ let pipeOutputVTable = OutputStreamVTable(
return closeAsync(Async destination) return closeAsync(Async destination)
else: else:
return completedFuture("pipeOutput.closeAsync") return completedFuture("pipeOutput.closeAsync")
) )
func pipeInput*(source: InputStream, func pipeInput*(source: InputStream,
pageSize = defaultPageSize, pageSize = defaultPageSize,
allowWaitFor = false): AsyncInputStream = allowWaitFor = false): AsyncInputStream =
fsAssert pageSize > 0 fsAssert pageSize > 0
@ -169,7 +173,7 @@ func pipeInput*(source: InputStream,
allowWaitFor: allowWaitFor, allowWaitFor: allowWaitFor,
source: source) source: source)
func pipeInput*(buffers: PageBuffers, func pipeInput*(buffers: PageBuffers,
allowWaitFor = false, allowWaitFor = false,
source: InputStream = nil): AsyncInputStream = source: InputStream = nil): AsyncInputStream =
var spanEndPos = Natural 0 var spanEndPos = Natural 0
@ -184,7 +188,7 @@ func pipeInput*(buffers: PageBuffers,
allowWaitFor: allowWaitFor, allowWaitFor: allowWaitFor,
source: source) source: source)
proc pipeOutput*(destination: OutputStream, proc pipeOutput*(destination: OutputStream,
pageSize = defaultPageSize, pageSize = defaultPageSize,
maxBufferedBytes = defaultPageSize * 4, maxBufferedBytes = defaultPageSize * 4,
allowWaitFor = false): AsyncOutputStream = allowWaitFor = false): AsyncOutputStream =
@ -202,7 +206,7 @@ proc pipeOutput*(destination: OutputStream,
allowWaitFor: allowWaitFor, allowWaitFor: allowWaitFor,
destination: destination) destination: destination)
proc pipeOutput*(buffers: PageBuffers, proc pipeOutput*(buffers: PageBuffers,
allowWaitFor = false, allowWaitFor = false,
destination: OutputStream = nil): AsyncOutputStream = destination: OutputStream = nil): AsyncOutputStream =
var span = buffers.getWritableSpan() var span = buffers.getWritableSpan()
@ -218,27 +222,27 @@ proc pipeOutput*(buffers: PageBuffers,
allowWaitFor: allowWaitFor, allowWaitFor: allowWaitFor,
destination: destination) destination: destination)
func asyncPipe*(pageSize = defaultPageSize, func asyncPipe*(pageSize = defaultPageSize,
maxBufferedBytes = defaultPageSize * 4): Pipe = maxBufferedBytes = defaultPageSize * 4): Pipe =
fsAssert pageSize > 0 fsAssert pageSize > 0
Pipe(buffers: initPageBuffers(pageSize, maxBufferedBytes)) Pipe(buffers: initPageBuffers(pageSize, maxBufferedBytes))
func initReader*(pipe: Pipe): AsyncInputStream = func initReader*(pipe: Pipe): AsyncInputStream =
result = pipeInput(pipe.buffers) result = pipeInput(pipe.buffers)
pipe.input = result pipe.input = result
func initWriter*(pipe: Pipe): AsyncOutputStream = func initWriter*(pipe: Pipe): AsyncOutputStream =
result = pipeOutput(pipe.buffers) result = pipeOutput(pipe.buffers)
pipe.output = result pipe.output = result
proc exchangeBuffersAfterPipilineStep(input: InputStream, output: OutputStream) = proc exchangeBuffersAfterPipilineStep(input: InputStream, output: OutputStream) =
let formerInputBuffers = input.buffers let formerInputBuffers = input.buffers
let formerOutputBuffers = output.getBuffers let formerOutputBuffers = output.getBuffers
input.resetBuffers formerOutputBuffers input.resetBuffers formerOutputBuffers
output.recycleBuffers formerInputBuffers output.recycleBuffers formerInputBuffers
macro executePipeline*(start: InputStream, steps: varargs[untyped]): untyped = macro executePipeline*(start: InputStream, steps: varargs[untyped]): untyped =
result = newTree(nnkStmtListExpr) result = newTree(nnkStmtListExpr)
var var
@ -278,7 +282,7 @@ macro executePipeline*(start: InputStream, steps: varargs[untyped]): untyped =
if defined(debugMacros) or defined(debugPipelines): if defined(debugMacros) or defined(debugPipelines):
echo result.repr echo result.repr
macro executePipeline*(start: AsyncInputStream, steps: varargs[untyped]): untyped = macro executePipeline*(start: AsyncInputStream, steps: varargs[untyped]): untyped =
var var
stream = ident "stream" stream = ident "stream"
pipelineSteps = ident "pipelineSteps" pipelineSteps = ident "pipelineSteps"

View file

@ -2,15 +2,11 @@
import import
os, unittest2, strutils, random, os, unittest2, strutils, random,
stew/ranges/ptr_arith, testutils, testutils,
../faststreams, ../faststreams/textio ../faststreams, ../faststreams/textio
setCurrentDir getAppDir() setCurrentDir getAppDir()
proc bytes(s: string): seq[byte] =
result = newSeqOfCap[byte](s.len)
for c in s: result.add byte(c)
proc str(bytes: openarray[byte]): string = proc str(bytes: openarray[byte]): string =
result = newStringOfCap(bytes.len) result = newStringOfCap(bytes.len)
for b in items(bytes): for b in items(bytes):
@ -28,7 +24,7 @@ const
asciiTableFile = "files" / "ascii_table.txt" asciiTableFile = "files" / "ascii_table.txt"
asciiTableContents = slurp(asciiTableFile) asciiTableContents = slurp(asciiTableFile)
procSuite "input stream": suite "input stream":
template emptyInputTests(suiteName, setupCode: untyped) = template emptyInputTests(suiteName, setupCode: untyped) =
suite suiteName & " empty inputs": suite suiteName & " empty inputs":
setup setupCode setup setupCode

View file

@ -154,6 +154,15 @@ suite "output stream":
checkOutputsMatch() checkOutputsMatch()
test "memcpy":
var x = 0x42'u8
nimSeq.add x
memStream.writeMemCopy x
let memStreamRes = memStream.getOutput
check memStreamRes == nimSeq
template undelayedOutput(content: seq[byte]) {.dirty.} = template undelayedOutput(content: seq[byte]) {.dirty.} =
nimSeq.add content nimSeq.add content
streamWritingToExistingBuffer.write content streamWritingToExistingBuffer.write content

View file

@ -1,24 +1,29 @@
{.used.} {.used.}
import import
testutils/unittests,
# FastStreams modules:
../faststreams/[pipelines, multisync]
when fsAsyncSupport:
import
# Std lib: # Std lib:
std/[strutils, random, base64, terminal], std/[strutils, random, base64, terminal],
# Other packages:
testutils/unittests,
# FastStreams modules: # FastStreams modules:
../faststreams/[pipelines, multisync], ../faststreams/[pipelines, multisync],
# Testing modules: # Testing modules:
./base64 as fsBase64 ./base64 as fsBase64
include system/timers include system/timers
type type
TestTimes = object TestTimes = object
fsPipeline: Nanos fsPipeline: Nanos
fsAsyncPipeline: Nanos fsAsyncPipeline: Nanos
stdFunctionCalls: Nanos stdFunctionCalls: Nanos
proc upcaseAllCharacters(i: InputStream, o: OutputStream) {.fsMultiSync.} = proc upcaseAllCharacters(i: InputStream, o: OutputStream) {.fsMultiSync.} =
let inputLen = i.len let inputLen = i.len
if inputLen.isSome: if inputLen.isSome:
o.ensureRunway inputLen.get o.ensureRunway inputLen.get
@ -28,17 +33,17 @@ proc upcaseAllCharacters(i: InputStream, o: OutputStream) {.fsMultiSync.} =
close o close o
proc printTimes(t: TestTimes) = proc printTimes(t: TestTimes) =
styledEcho " cpu time [FS Sync ]: ", styleBright, $t.fsPipeline, "ms" styledEcho " cpu time [FS Sync ]: ", styleBright, $t.fsPipeline, "ms"
styledEcho " cpu time [FS Async ]: ", styleBright, $t.fsAsyncPipeline, "ms" styledEcho " cpu time [FS Async ]: ", styleBright, $t.fsAsyncPipeline, "ms"
styledEcho " cpu time [Std Lib ]: ", styleBright, $t.stdFunctionCalls, "ms" styledEcho " cpu time [Std Lib ]: ", styleBright, $t.stdFunctionCalls, "ms"
template timeit(timerVar: var Nanos, code: untyped) = template timeit(timerVar: var Nanos, code: untyped) =
let t0 = getTicks() let t0 = getTicks()
code code
timerVar = int(getTicks() - t0) div 1000000 timerVar = int(getTicks() - t0) div 1000000
proc getOutput(sp: AsyncInputStream, T: type string): Future[string] {.async.} = proc getOutput(sp: AsyncInputStream, T: type string): Future[string] {.async.} =
# this proc is a quick hack to let the test pass # this proc is a quick hack to let the test pass
# do not use it in production code # do not use it in production code
let size = sp.totalUnconsumedBytes() let size = sp.totalUnconsumedBytes()
@ -47,8 +52,8 @@ proc getOutput(sp: AsyncInputStream, T: type string): Future[string] {.async.} =
discard sp.readinto(data) discard sp.readinto(data)
result = cast[string](data) result = cast[string](data)
procSuite "pipelines": suite "pipelines":
let loremIpsum = """ const loremIpsum = """
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim
veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex
@ -126,3 +131,6 @@ procSuite "pipelines":
let fsAsyncres = await f let fsAsyncres = await f
check fsAsyncRes == toUpperAscii(inputText) check fsAsyncRes == toUpperAscii(inputText)
else:
test "pipelines":
skip