mirror of
https://github.com/waku-org/nwaku.git
synced 2025-01-15 09:26:38 +00:00
428 lines
12 KiB
Nim
428 lines
12 KiB
Nim
{.used.}
|
|
|
|
import
|
|
os, unittest2, random, strformat,
|
|
stew/ranges/ptr_arith,
|
|
../faststreams, ../faststreams/textio
|
|
|
|
proc bytes(s: string): seq[byte] =
|
|
result = newSeqOfCap[byte](s.len)
|
|
for c in s: result.add byte(c)
|
|
|
|
proc bytes(s: cstring): seq[byte] =
|
|
for c in s: result.add byte(c)
|
|
|
|
template bytes(c: char): byte = byte(c)
|
|
template bytes(b: seq[byte]): seq[byte] = b
|
|
template bytes[N, T](b: array[N, T]): seq[byte] = @b
|
|
|
|
proc repeat(b: byte, count: int): seq[byte] =
|
|
result = newSeq[byte](count)
|
|
for i in 0 ..< count: result[i] = b
|
|
|
|
const line = "123456789123456789123456789123456789\n\n\n\n\n"
|
|
|
|
proc randomBytes(n: int): seq[byte] =
|
|
result.newSeq n
|
|
for i in 0 ..< n:
|
|
result[i] = byte(line[rand(line.len - 1)])
|
|
|
|
proc readAllAndClose(s: InputStream): seq[byte] =
|
|
while s.readable:
|
|
result.add s.read
|
|
|
|
close(s)
|
|
|
|
import memfiles
|
|
|
|
suite "output stream":
|
|
setup:
|
|
var
|
|
nimSeq: seq[byte] = @[]
|
|
|
|
memStream = memoryOutput()
|
|
smallPageSizeStream = memoryOutput(pageSize = 10)
|
|
largePageSizeStream = memoryOutput(pageSize = 1000000)
|
|
|
|
fileOutputPath = getTempDir() / "faststreams_testfile"
|
|
unbufferedFileOutputPath = getTempDir() / "faststreams_testfile_unbuffered"
|
|
|
|
fileStream = fileOutput(fileOutputPath)
|
|
unbufferedFileStream = fileOutput(unbufferedFileOutputPath, pageSize = 0)
|
|
|
|
bufferSize = 1000000
|
|
buffer = alloc(bufferSize)
|
|
streamWritingToExistingBuffer = unsafeMemoryOutput(buffer, bufferSize)
|
|
|
|
teardown:
|
|
close fileStream
|
|
close unbufferedFileStream
|
|
removeFile fileOutputPath
|
|
removeFile unbufferedFileOutputPath
|
|
dealloc buffer
|
|
|
|
template output(val: auto) {.dirty.} =
|
|
nimSeq.add bytes(val)
|
|
memStream.write val
|
|
smallPageSizeStream.write val
|
|
largePageSizeStream.write val
|
|
fileStream.write val
|
|
unbufferedFileStream.write val
|
|
streamWritingToExistingBuffer.write val
|
|
|
|
template outputText(val: auto) =
|
|
let valAsStr = $val
|
|
nimSeq.add valAsStr.toOpenArrayByte(0, valAsStr.len - 1)
|
|
|
|
memStream.writeText val
|
|
smallPageSizeStream.writeText val
|
|
largePageSizeStream.writeText val
|
|
|
|
fileStream.writeText val
|
|
unbufferedFileStream.writeText val
|
|
|
|
streamWritingToExistingBuffer.writeText val
|
|
|
|
template checkOutputsMatch(showResults = false,
|
|
skipUnbufferedFile = false) =
|
|
close fileStream
|
|
close unbufferedFileStream
|
|
|
|
check fileExists(fileOutputPath) and
|
|
fileExists(unbufferedFileOutputPath)
|
|
|
|
let
|
|
memStreamRes = memStream.getOutput
|
|
readFileRes = readFile(fileOutputPath).string.bytes
|
|
fileInputRes = fileInput(fileOutputPath).readAllAndClose
|
|
memFileInputRes = memFileInput(fileOutputPath).readAllAndClose
|
|
fileInputWithSmallPagesRes = fileInput(fileOutputPath, pageSize = 10).readAllAndClose
|
|
|
|
when showResults:
|
|
checkpoint "Nim seq result"
|
|
checkpoint $nimSeq
|
|
|
|
checkpoint "Writes to existing buffer result"
|
|
checkpoint $makeOpenArray(cast[ptr byte](buffer),
|
|
streamWritingToExistingBuffer.pos)
|
|
|
|
checkpoint "mem stream result"
|
|
checkpoint $memStreamRes
|
|
|
|
checkpoint "readFile result"
|
|
checkpoint $readFileRes
|
|
|
|
checkpoint "fileInput result"
|
|
checkpoint $fileInputRes
|
|
|
|
checkpoint "memFileInput result"
|
|
checkpoint $memFileInputRes
|
|
|
|
checkpoint "fileInput with small pageSize result"
|
|
checkpoint $fileInputWithSmallPagesRes
|
|
|
|
let outputsMatch =
|
|
nimSeq == makeOpenArray(cast[ptr byte](buffer),
|
|
streamWritingToExistingBuffer.pos)
|
|
check outputsMatch
|
|
|
|
check nimSeq == memStreamRes
|
|
check nimSeq == readFileRes
|
|
check nimSeq == fileInputRes
|
|
check nimSeq == memFileInputRes
|
|
check nimSeq == fileInputWithSmallPagesRes
|
|
|
|
when not skipUnbufferedFile:
|
|
let unbufferedFileRes = readFile(unbufferedFileOutputPath).string.bytes
|
|
check nimSeq == unbufferedFileRes
|
|
|
|
test "no appends produce an empty output":
|
|
checkOutputsMatch()
|
|
|
|
test "write zero length slices":
|
|
output ""
|
|
output newSeq[byte]()
|
|
var arr: array[0, byte]
|
|
output arr
|
|
|
|
check nimSeq.len == 0
|
|
checkOutputsMatch()
|
|
|
|
test "text output":
|
|
for i in 1 .. 100:
|
|
outputText i
|
|
outputText " bottles on the wall"
|
|
outputText '\n'
|
|
|
|
checkOutputsMatch()
|
|
|
|
test "cstrings":
|
|
for i in 1 .. 100:
|
|
output cstring("cstring sent by output ")
|
|
output cstring("")
|
|
outputText cstring("cstring sent by outputText ")
|
|
outputText cstring("")
|
|
|
|
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.} =
|
|
nimSeq.add content
|
|
streamWritingToExistingBuffer.write content
|
|
|
|
test "delayed write":
|
|
output "initial output\n"
|
|
const delayedWriteContent = bytes "delayed write\n"
|
|
|
|
var memCursor = memStream.delayFixedSizeWrite(delayedWriteContent.len)
|
|
var fileCursor = fileStream.delayVarSizeWrite(delayedWriteContent.len + 50)
|
|
|
|
let cursorStart = memStream.pos
|
|
|
|
undelayedOutput delayedWriteContent
|
|
|
|
var bytesWritten = 0
|
|
for i, count in [2, 12, 342, 2121, 23, 1, 34012, 932]:
|
|
output repeat(byte(i), count)
|
|
bytesWritten += count
|
|
check memStream.pos - cursorStart == bytesWritten
|
|
|
|
memCursor.finalWrite delayedWriteContent
|
|
fileCursor.finalWrite delayedWriteContent
|
|
|
|
checkOutputsMatch(skipUnbufferedFile = true)
|
|
|
|
test "float output":
|
|
let basic: float64 = 12345.125
|
|
let small: float32 = 12345.125
|
|
let large: float64 = 9.99e+20
|
|
let tiny: float64 = -2.25e-35
|
|
|
|
outputText basic
|
|
outputText small
|
|
outputText large
|
|
outputText tiny
|
|
|
|
checkOutputsMatch()
|
|
|
|
proc writeBlock(data: openArray[byte], output: openArray[byte]): int =
|
|
doAssert data.len <= output.len
|
|
copyMem(unsafeAddr output[0], unsafeAddr data[0], data.len)
|
|
data.len
|
|
|
|
suite "randomized tests":
|
|
type
|
|
WriteTypes = enum
|
|
FixedSize
|
|
VarSize
|
|
Mixed
|
|
|
|
DelayedWrite = object
|
|
isFixedSize: bool
|
|
fixedSizeCursor: WriteCursor
|
|
varSizeCursor: VarSizeWriteCursor
|
|
content: seq[byte]
|
|
written: int
|
|
|
|
proc randomizedCursorsTestImpl(stream: OutputStream,
|
|
seed = 1000,
|
|
iterations = 1000,
|
|
minWriteSize = 500,
|
|
maxWriteSize = 1000,
|
|
writeTypes = Mixed,
|
|
varSizeVariance = 50): seq[byte] =
|
|
randomize seed
|
|
|
|
var delayedWrites = newSeq[DelayedWrite]()
|
|
let writeSizeSpread = maxWriteSize - minWriteSize
|
|
|
|
for i in 0 ..< iterations:
|
|
let decision = rand(100)
|
|
|
|
if decision < 20:
|
|
# Write at some random cursor
|
|
if delayedWrites.len > 0:
|
|
let
|
|
i = rand(delayedWrites.len - 1)
|
|
written = delayedWrites[i].written
|
|
remaining = delayedWrites[i].content.len - written
|
|
toWrite = min(rand(remaining) + 10, remaining)
|
|
|
|
if delayedWrites[i].isFixedSize:
|
|
delayedWrites[i].fixedSizeCursor.write delayedWrites[i].content[written ..< written + toWrite]
|
|
|
|
delayedWrites[i].written += toWrite
|
|
|
|
if remaining - toWrite == 0:
|
|
if delayedWrites[i].isFixedSize:
|
|
finalize delayedWrites[i].fixedSizeCursor
|
|
else:
|
|
finalWrite delayedWrites[i].varSizeCursor, delayedWrites[i].content
|
|
|
|
if i != delayedWrites.len - 1:
|
|
swap(delayedWrites[i], delayedWrites[^1])
|
|
delayedWrites.setLen(delayedWrites.len - 1)
|
|
|
|
continue
|
|
|
|
let
|
|
size = rand(writeSizeSpread) + minWriteSize
|
|
randomBytes = randomBytes(size)
|
|
|
|
if decision < 90:
|
|
# Normal write
|
|
result.add randomBytes
|
|
stream.write randomBytes
|
|
|
|
else:
|
|
# Create cursor
|
|
result.add randomBytes
|
|
|
|
let isFixedSize = case writeTypes
|
|
of FixedSize: true
|
|
of VarSize: false
|
|
of Mixed: rand(10) > 3
|
|
|
|
if isFixedSize:
|
|
let cursor = stream.delayFixedSizeWrite(randomBytes.len)
|
|
|
|
delayedWrites.add DelayedWrite(
|
|
fixedSizeCursor: cursor,
|
|
content: randomBytes,
|
|
written: 0,
|
|
isFixedSize: true)
|
|
else:
|
|
let
|
|
overestimatedBytes = rand(varSizeVariance)
|
|
cursorSize = randomBytes.len + overestimatedBytes
|
|
cursor = stream.delayVarSizeWrite(cursorSize)
|
|
|
|
delayedWrites.add DelayedWrite(
|
|
varSizeCursor: cursor,
|
|
content: randomBytes,
|
|
written: 0,
|
|
isFixedSize: false)
|
|
|
|
# Write all unwritten data to all outstanding cursors
|
|
if stream != nil:
|
|
for dw in mitems(delayedWrites):
|
|
if dw.isFixedSize:
|
|
let remaining = dw.content.len - dw.written
|
|
dw.fixedSizeCursor.write dw.content[dw.written ..< dw.written + remaining]
|
|
finalize dw.fixedSizeCursor
|
|
else:
|
|
dw.varSizeCursor.finalWrite dw.content
|
|
|
|
template randomizedCursorsTest(streamExpr: OutputStreamHandle,
|
|
writeTypesExpr: WriteTypes,
|
|
varSizeVarianceExpr: int,
|
|
customChecks: untyped = nil) =
|
|
const testName = "randomized cursor test [" & astToStr(streamExpr) &
|
|
";writes=" & $writeTypesExpr & ",variance=" & $varSizeVarianceExpr & "]"
|
|
test testName:
|
|
let s = streamExpr
|
|
var referenceResult = randomizedCursorsTestImpl(stream = s,
|
|
writeTypes = writeTypesExpr,
|
|
varSizeVariance = varSizeVarianceExpr)
|
|
|
|
when astToStr(customChecks) == "nil":
|
|
let streamResult = s.getOutput()
|
|
let resultsMatch = streamResult == referenceResult
|
|
when false:
|
|
if not resultsMatch:
|
|
writeFile("reference-result.txt", referenceResult)
|
|
writeFile("stream-result.txt", streamResult)
|
|
check resultsMatch
|
|
else:
|
|
customChecks
|
|
|
|
check referenceResult.len == s.pos
|
|
|
|
randomizedCursorsTest(memoryOutput(), FixedSize, 0)
|
|
randomizedCursorsTest(memoryOutput(), VarSize, 100)
|
|
randomizedCursorsTest(memoryOutput(pageSize = 10), Mixed, 10)
|
|
|
|
test "randomized file roundtrip":
|
|
const randomBytesFileName = "random_bytes_file"
|
|
|
|
var
|
|
referenceBytes, restoredBytes: seq[byte]
|
|
|
|
try:
|
|
let output = fileOutput randomBytesFileName
|
|
|
|
for i in 0 .. 3000:
|
|
let bytes = randomBytes(rand(9999) + 1)
|
|
referenceBytes.add bytes
|
|
|
|
var openArraySize = rand(12000)
|
|
if openArraySize >= bytes.len:
|
|
# Make sure that sometimes `writeBlock` populates the entire span
|
|
if i < 100: openArraySize = bytes.len
|
|
output.advance writeBlock(bytes, output.getWritableBytes(openArraySize))
|
|
else:
|
|
output.write bytes
|
|
|
|
close output
|
|
|
|
let input = fileInput randomBytesFileName
|
|
|
|
while input.readable(10000):
|
|
let r = 1 + rand(9999)
|
|
if r < 5000:
|
|
restoredBytes.add input.read(8000)
|
|
restoredBytes.add input.read(500)
|
|
elif r < 7000:
|
|
restoredBytes.add input.read(1)
|
|
restoredBytes.add input.read(2)
|
|
restoredBytes.add input.read(5)
|
|
restoredBytes.add input.read(17)
|
|
restoredBytes.add input.read(128)
|
|
else:
|
|
restoredBytes.add input.read(r)
|
|
|
|
while input.readable:
|
|
restoredBytes.add input.read
|
|
|
|
close input
|
|
|
|
doAssert referenceBytes == restoredBytes
|
|
|
|
finally:
|
|
if fileExists(randomBytesFileName):
|
|
removeFile randomBytesFileName
|
|
|
|
test "ensureRunway":
|
|
var output = memoryOutput()
|
|
|
|
const writes = 256
|
|
var buffer = newSeq[byte](writes)
|
|
let totalBytes = block:
|
|
var tmp = 0
|
|
for i in 0..<writes:
|
|
tmp += i
|
|
buffer[i] = byte(i)
|
|
tmp
|
|
|
|
output.ensureRunway(totalBytes)
|
|
|
|
for i in 0..<writes:
|
|
output.write(buffer.toOpenArray(0, i - 1))
|
|
|
|
output.flush()
|
|
|
|
let res = output.getOutput()
|
|
var j = 0
|
|
for i in 0..<writes:
|
|
check:
|
|
res[j..<j+i] == buffer[0..<i]
|
|
j += i
|