## Wire Protocol + Storage Fuzz Tests import std/unittest import std/random import std/strutils import std/os import std/monotimes import std/tables import std/sets import std/algorithm import barabadb/protocol/wire import barabadb/storage/lsm import barabadb/storage/wal suite "Wire Protocol Fuzz": # ────────────────────────────────────────────────── # Core robustness # ────────────────────────────────────────────────── test "deserializeValue survives random bytes": var rng = initRand(12345) for i in 0..<500: var buf = newSeq[byte](rng.rand(1..256)) for b in buf.mitems: b = byte(rng.rand(0..255)) var pos = 0 try: discard deserializeValue(buf, pos) except: discard # Exceptions are expected for garbage input test "deserializeValue survives truncated buffers": var rng = initRand(12346) for i in 0..<200: var buf: seq[byte] = @[byte(rng.rand(0..12))] let extra = rng.rand(0..64) for j in 0..= 12 test "serializeMessage roundtrip with random payloads": var rng = initRand(12350) for i in 0..<100: let reqId = uint32(rng.rand(int64(high(uint32)))) var msg = WireMessage( header: MessageHeader(kind: mkQuery, length: uint32(rng.rand(0..1000)), requestId: reqId), payload: newSeq[byte](rng.rand(0..200)) ) for j in 0..= 12 test "serializeMessage with max payload length": var rng = initRand(5004) for i in 0..<50: let reqId = uint32(rng.rand(int64(high(uint32)))) let payloadLen = rng.rand(0..4096) var msg = WireMessage( header: MessageHeader(kind: mkQuery, length: uint32(payloadLen), requestId: reqId), payload: newSeq[byte](payloadLen) ) for j in 0.. 0 test "makeErrorMessage with long error string": var rng = initRand(5005) for i in 0..<50: let reqId = uint32(rng.rand(int64(high(uint32)))) var errStr = "" let errLen = rng.rand(0..1000) for j in 0..= 12 + errStr.len # ────────────────────────────────────────────────── # SerializeValue roundtrip for all field kinds # ────────────────────────────────────────────────── test "serializeValue/deserializeValue roundtrip — fkNull": var buf: seq[byte] = @[] var val = WireValue(kind: fkNull) serializeValue(buf, val) var pos = 0 let back = deserializeValue(buf, pos) check back.kind == fkNull test "serializeValue/deserializeValue roundtrip — fkBool": var rng = initRand(5006) for i in 0..<100: var buf: seq[byte] = @[] let bv = rng.rand(0..1) == 1 var val = WireValue(kind: fkBool, boolVal: bv) serializeValue(buf, val) var pos = 0 let back = deserializeValue(buf, pos) check back.kind == fkBool check back.boolVal == bv test "serializeValue/deserializeValue roundtrip — fkInt64": var rng = initRand(5007) for i in 0..<100: var buf: seq[byte] = @[] let iv = int64(rng.rand(int64.low..int64.high)) var val = WireValue(kind: fkInt64, int64Val: iv) serializeValue(buf, val) var pos = 0 let back = deserializeValue(buf, pos) check back.kind == fkInt64 check back.int64Val == iv test "serializeValue/deserializeValue roundtrip — fkFloat64": var rng = initRand(5008) for i in 0..<100: var buf: seq[byte] = @[] let fv = float64(rng.rand(-1000.0..1000.0)) var val = WireValue(kind: fkFloat64, float64Val: fv) serializeValue(buf, val) var pos = 0 let back = deserializeValue(buf, pos) check back.kind == fkFloat64 check back.float64Val == fv test "serializeValue/deserializeValue roundtrip — fkString": var rng = initRand(5009) for i in 0..<100: var buf: seq[byte] = @[] var s = "" let sl = rng.rand(0..200) for j in 0.. 0: let idx = rng.rand(0..buf.len-1) buf[idx] = byte(rng.rand(0..255)) var pos = 0 try: discard deserializeValue(buf, pos) except: discard test "Mutated serialized messages parse without crash": var rng = initRand(5018) for i in 0..<200: let header = MessageHeader(kind: mkQuery, length: 10, requestId: uint32(i)) var payload = newSeq[byte](10) for j in 0..<10: payload[j] = byte(rng.rand(0..255)) var msg = WireMessage(header: header, payload: payload) var s = serializeMessage(msg) for mutation in 0..<3: if s.len > 0: s[rng.rand(0..s.len-1)] = byte(rng.rand(0..255)) check s.len >= 12 test "makeQueryMessage with empty query": let msg = makeQueryMessage(1, "") check msg.len >= 12 test "makeQueryMessage with special characters": var rng = initRand(5019) for i in 0..<50: var query = "" let sl = rng.rand(0..100) for j in 0..= 12 # ────────────────────────────────────────────────── # makeCompleteMessage # ────────────────────────────────────────────────── test "makeCompleteMessage with random affected rows": var rng = initRand(5020) for i in 0..<50: let affectedRows = rng.rand(0..100_000) let msg = makeCompleteMessage(uint32(i), affectedRows) check msg.len >= 12 # ────────────────────────────────────────────────── # makeAuthOkMessage # ────────────────────────────────────────────────── test "makeAuthOkMessage produces valid message": let msg = makeAuthOkMessage(42) check msg.len == 12 # ────────────────────────────────────────────────── # makeAuthChallengeMessage # ────────────────────────────────────────────────── test "makeAuthChallengeMessage with random challenges": var rng = initRand(5021) for i in 0..<50: var challenge = "" let cl = rng.rand(0..100) for j in 0..= 12 # ────────────────────────────────────────────────── # Stress: many iterations # ────────────────────────────────────────────────── test "Stress: 2000 random deserializeValue calls": var rng = initRand(5022) for i in 0..<2000: var buf = newSeq[byte](rng.rand(1..128)) for b in buf.mitems: b = byte(rng.rand(0..255)) var pos = 0 try: discard deserializeValue(buf, pos) except: discard test "Stress: 1000 serializeMessage with random content": var rng = initRand(5023) for i in 0..<1000: let kind = cast[MsgKind](uint32(rng.rand(1..0xFF))) let payloadLen = rng.rand(0..256) var payload = newSeq[byte](payloadLen) for j in 0.. 16: let truncSize = rng.rand(16..int(origSize - 1)) let f = open(walPath, fmReadWriteExisting) f.setFilePos(truncSize) # Nim has no direct truncate; use posix f.close() # Re-open truncated WAL — should not crash var wal2 = newWriteAheadLog(tmpDir) let entries = readEntries(walPath) # Either we get some entries or none; the key is no crash check true wal2.close() wal.close() test "WAL entryCount is accurate after restart": let tmpDir = getTempDir() / "baradb_fuzz_wal_count_" & $getCurrentProcessId() createDir(tmpDir) defer: removeDir(tmpDir) var wal = newWriteAheadLog(tmpDir) wal.writePut(cast[seq[byte]]("key1"), @[byte 1, 2, 3], 1'u64) wal.writePut(cast[seq[byte]]("key2"), @[byte 4, 5], 2'u64) wal.writeCommit(3'u64) check wal.entryCount == 3 wal.close() var wal2 = newWriteAheadLog(tmpDir) check wal2.entryCount == 3 wal2.close() # ────────────────────────────────────────────────── # LSM Fuzz — put/get/delete with random keys/values # ────────────────────────────────────────────────── test "LSM put/get roundtrip with random keys/values": var rng = initRand(6001) let tmpDir = getTempDir() / "baradb_fuzz_lsm_" & $getCurrentProcessId() createDir(tmpDir) defer: removeDir(tmpDir) var db = newLSMTree(tmpDir, memMaxSize = 64 * 1024) defer: db.close() var expected = initTable[string, seq[byte]]() for i in 0..<500: let k = randKey(rng, 1, 32) let v = randValue(rng, 0, 128) db.put(k, v) expected[k] = v for k, v in expected: let (found, got) = db.get(k) check found check got == v test "LSM delete removes key and get returns false": var rng = initRand(6002) let tmpDir = getTempDir() / "baradb_fuzz_lsm_del_" & $getCurrentProcessId() removeDir(tmpDir) createDir(tmpDir) defer: removeDir(tmpDir) var db = newLSMTree(tmpDir, memMaxSize = 32 * 1024) defer: db.close() var keys: seq[string] = @[] var seen = initHashSet[string]() while keys.len < 200: let k = randKey(rng, 1, 16) if k notin seen: seen.incl(k) let v = randValue(rng, 0, 64) db.put(k, v) keys.add(k) # Delete half for i in 0..