silk.io.memory_byte_duplex.
A scripted ByteDuplex transport in memory, for tests of protocol code.
When to use#
Use MemoryByteDuplex to test code that uses silk.io.byte_duplex.ByteDuplex without a network.
A script controls what each read returns, how many bytes each write accepts, and when failures
happen. After the test, check the bytes that the code wrote with MemoryByteDuplex.outbound
and the order of operations with MemoryByteDuplex.audit.
Details#
MemoryByteDuplex.make takes the read events, the write events, and the sizes of the output
and audit storage. It allocates everything one time. The operations do not allocate.
Each event has a readyAt time on the active MonotonicClock. An operation waits until the
event is ready. If the deadline comes first, the operation fails with Timeout. When an event
time is equal to the deadline, the deadline wins.
A read returns the bytes of the current Data event, as many as fit in the output slice. When
the read script is empty, reads return End. After End, reads return End and do not use the
script.
Gotchas#
Each write uses the next write event. A vectored write is one write event that takes bytes from
its slices in order. If there are no more write events, or the output storage is full, the
write fails with a Provider error, code 1 or 2. If the audit storage is full, the next
operation fails with code 3. Make the storage large enough for the whole test.
This transport models a reliable byte stream only. It does not model packets, DNS, or certificates.
Examples#
Answer a request and check the response#
import silk.mem.allocator { Allocator, OutOfMemoryError }
import silk.io.buffered_duplex { BufferedDuplex, withBufferedCapacity }
import silk.io.buffered_input { BufferError }
import silk.io.byte_duplex { ByteDuplex }
import silk.collections.bytes { Bytes }
import silk.core.effect { Effect }
import silk.collections.list { List }
import silk.io.memory_byte_duplex {
MemoryByteDuplex,
MemoryReadEvent,
MemoryWriteAction,
MemoryWriteEvent
}
import silk.system.monotonic_clock { MonotonicClock }
import silk.core.option { Option }
import silk.system.system_clock { Instant, SystemClock }
struct FixedClock {}
impl MonotonicClock for FixedClock {
effect fn now(self: &mut Self) -> Instant {
return SystemClock.make(0, 0)
}
effect fn getResolution(self: &mut Self) -> u64 {
return 1
}
effect fn waitUntil(self: &mut Self, when: Instant) -> () {
return ()
}
effect fn waitFor(self: &mut Self, howLong: u64) -> () {
return ()
}
}
effect fn transport() -> MemoryByteDuplex
! OutOfMemoryError
? &mut Allocator {
let mut reads = List<MemoryReadEvent>.make()
let bytes = run Bytes.copy(&b"ping")
run List<MemoryReadEvent>.append(
&mut reads,
MemoryReadEvent.Data { readyAt: SystemClock.make(0, 0), bytes: move bytes }
)
let mut writes = List<MemoryWriteEvent>.make()
run List<MemoryWriteEvent>.append(
&mut writes,
MemoryWriteEvent {
readyAt: SystemClock.make(0, 0),
action: MemoryWriteAction.Accept { count: 4 }
}
)
return run MemoryByteDuplex.make(move reads, move writes, 4, 8, Option<i32>.none())
}
// Reads a 4-byte request and answers it.
effect<'session> fn answer<'session, P>(session: &'session mut BufferedDuplex<'session, P>) -> ()
! BufferError
? &mut MonotonicClock where
&'session mut P provides &ByteDuplex from &mut ByteDuplex | &mut MonotonicClock {
let mut request: [u8; 4] = [0, 0, 0, 0]
run BufferedDuplex.readExact(&mut session.*, &mut request, Option<Instant>.none())
run BufferedDuplex.writeAll(&mut session.*, b"pong", Option<Instant>.none())
return run BufferedDuplex.flush(&mut session.*, Option<Instant>.none())
}
pub effect fn main() -> i32
! BufferError | OutOfMemoryError {
let mut allocator = Allocator.systemAllocatorProvider()
let mut clock = FixedClock {}
let mut duplex = run transport() |> Effect.provideMut<Allocator>(&mut allocator)
run withBufferedCapacity<(), BufferError>(&mut duplex, 16, 16, answer)
|> Effect.provideMut<MonotonicClock>(&mut clock)
|> Effect.provideMut<Allocator>(&mut allocator)
let sent = MemoryByteDuplex.outbound(&duplex)
if sent.length != 4 || sent[0] != 112 || sent[1] != 111 {
return 1
}
return 0
}Import as MemoryByteDuplex with import silk.io.memory_byte_duplex { MemoryByteDuplex }.
Public declarations: 6.
MemoryReadEvent#
pub union MemoryReadEventOne step of the read script.
Data#
DataMakes bytes available at readyAt. Reads take them in order, as many as fit in each output
slice.
Field readyAt#
pub readyAt: InstantThe time when the event is ready.
Field bytes#
pub bytes: BytesThe bytes that reads return.
End#
EndEnds the stream at readyAt.
Field readyAt#
pub readyAt: InstantThe time when the event is ready.
Failure#
FailureMakes the read fail at readyAt with ByteIoError.Provider.
Field readyAt#
pub readyAt: InstantThe time when the event is ready.
Field code#
pub code: i32The code of the Provider error.
InvalidCount#
InvalidCountMakes the read report count without writing bytes, to test the count check.
Field readyAt#
pub readyAt: InstantThe time when the event is ready.
Field count#
pub count: usizeThe count to report. Use 0 or a value larger than the output slice.
MemoryWriteAction#
pub union MemoryWriteActionWhat one write does.
Accept#
AcceptAccepts up to count bytes from the start of the input.
Field count#
pub count: usizeThe maximum number of bytes to accept.
Failure#
FailureMakes the write fail with ByteIoError.Provider. No bytes are accepted.
Field code#
pub code: i32The code of the Provider error.
InvalidCount#
InvalidCountMakes the write report count without accepting bytes, to test the count check.
Field count#
pub count: usizeThe count to report.
MemoryWriteEvent#
pub struct MemoryWriteEventOne step of the write script.
Field readyAt#
pub readyAt: InstantThe time when the write can happen.
Field action#
pub action: MemoryWriteActionWhat the write does.
MemoryByteDuplexPhase#
pub enum MemoryByteDuplexPhaseThe state of a MemoryByteDuplex, for checks in tests.
Open#
OpenReads and writes work.
WriteShutdown#
WriteShutdownThe write direction is closed. Reads still work.
Closed#
ClosedThe transport is closed.
Invalid#
InvalidA write failed, or the script or storage ran out. Reads and writes fail.
MemoryByteDuplexAudit#
pub struct MemoryByteDuplexAuditOne recorded operation.
Field operation#
pub operation: ByteIoOperationThe operation.
Field count#
pub count: usizeThe number of bytes that the operation moved. It is 0 for flush, shutdown, close, and failures.
Implementation Copy for MemoryWriteAction#
impl Copy for MemoryWriteActionImplementation Copy for MemoryByteDuplexAudit#
impl Copy for MemoryByteDuplexAuditMemoryByteDuplex#
pub struct MemoryByteDuplexA scripted ByteDuplex transport in memory, for tests of protocol code. Make one with
MemoryByteDuplex.make and provide it as ByteDuplex.
When to use#
Use it to test code that uses silk.io.byte_duplex.ByteDuplex without a network. A script controls
what each read returns, how many bytes each write accepts, and when failures happen. After the
test, check the bytes that the code wrote with MemoryByteDuplex.outbound and the order of
operations with MemoryByteDuplex.audit.
Details#
MemoryByteDuplex.make allocates everything one time. The operations do not allocate.
Each event has a readyAt time on the active MonotonicClock. An operation waits until the
event is ready. If the deadline comes first, the operation fails with Timeout. When an event
time is equal to the deadline, the deadline wins.
A read returns the bytes of the current Data event, as many as fit in the output slice. When
the read script is empty, reads return End. After End, reads return End and do not use the
script.
Gotchas#
Each write uses the next write event. A vectored write is one write event that takes bytes from
its slices in order. If there are no more write events, or the output storage is full, the
write fails with a Provider error, code 1 or 2. If the audit storage is full, the next
operation fails with code 3. Make the storage large enough for the whole test.
This transport models a reliable byte stream only. It does not model packets, DNS, or certificates.
Implementation MemoryByteDuplex#
impl MemoryByteDuplexAssociated function make#
pub effect fn make(
reads: List<MemoryReadEvent>,
writes: List<MemoryWriteEvent>,
outboundCapacity: usize,
auditCapacity: usize,
closeFailure: Option<i32>,
) -> MemoryByteDuplex ! OutOfMemoryError ? &mut AllocatorReturns a transport that follows reads and writes.
Details#
outboundCapacity is the maximum number of bytes that writes can accept in total.
auditCapacity is the maximum number of operations that the audit records, including the
close. If closeFailure is present, the first close fails with that provider code.
Errors#
OutOfMemoryErrorif theAllocatorcannot supply the storage.
Method outbound#
pub fn outbound<'a>(self: &'a Self) -> &'a [u8]Returns all bytes that writes accepted, in order.
Method audit#
pub fn audit<'a>(self: &'a Self) -> &'a [MemoryByteDuplexAudit]Returns the recorded operations, in order.
Method phase#
pub fn phase(self: &Self) -> MemoryByteDuplexPhaseReturns the current state.
Method closeAttempts#
pub fn closeAttempts(self: &Self) -> usizeReturns how many times the transport was closed, including closes that did nothing.
Implementation ByteDuplex for MemoryByteDuplex#
impl ByteDuplex for MemoryByteDuplexOperation readSomeRaw#
readSomeRaw: MemoryByteDuplex.readOperation writeSomeRaw#
writeSomeRaw: MemoryByteDuplex.writeOperation writeSomeVectoredRaw#
writeSomeVectoredRaw: MemoryByteDuplex.writeVectoredOperation flushRaw#
flushRaw: MemoryByteDuplex.flushOutputOperation shutdownWriteRaw#
shutdownWriteRaw: MemoryByteDuplex.shutdownOutputOperation closeRaw#
closeRaw: MemoryByteDuplex.closeLeaseCategory: Input and output.
Profiles: aarch64-apple-darwin, aarch64-unknown-linux-gnu, aarch64-unknown-linux-gnu-no-libc, x86_64-unknown-linux-gnu, x86_64-unknown-linux-gnu-no-libc, wasm32-unknown-unknown, wasm32-wasip1, wasm32-wasip2.