mirror of
https://github.com/logos-messaging/nim-sds.git
synced 2026-01-03 06:33:06 +00:00
243 lines
7.6 KiB
Nim
243 lines
7.6 KiB
Nim
import unittest, results, chronos
|
|
import ../src/reliability
|
|
import ../src/common
|
|
import ../src/protobuf
|
|
import ../src/utils
|
|
|
|
suite "ReliabilityManager":
|
|
var rm: ReliabilityManager
|
|
|
|
setup:
|
|
let rmResult = newReliabilityManager("testChannel")
|
|
check rmResult.isOk()
|
|
rm = rmResult.get()
|
|
|
|
teardown:
|
|
if not rm.isNil:
|
|
rm.cleanup()
|
|
|
|
test "can create with default config":
|
|
let config = defaultConfig()
|
|
check config.bloomFilterCapacity == DefaultBloomFilterCapacity
|
|
check config.bloomFilterErrorRate == DefaultBloomFilterErrorRate
|
|
check config.bloomFilterWindow == DefaultBloomFilterWindow
|
|
|
|
test "wrapOutgoingMessage":
|
|
let msg = @[byte(1), 2, 3]
|
|
let msgId = "test-msg-1"
|
|
let wrappedResult = rm.wrapOutgoingMessage(msg, msgId)
|
|
check wrappedResult.isOk()
|
|
let wrapped = wrappedResult.get()
|
|
check wrapped.len > 0
|
|
|
|
test "unwrapReceivedMessage":
|
|
let msg = @[byte(1), 2, 3]
|
|
let msgId = "test-msg-1"
|
|
let wrappedResult = rm.wrapOutgoingMessage(msg, msgId)
|
|
check wrappedResult.isOk()
|
|
let wrapped = wrappedResult.get()
|
|
let unwrapResult = rm.unwrapReceivedMessage(wrapped)
|
|
check unwrapResult.isOk()
|
|
let (unwrapped, missingDeps) = unwrapResult.get()
|
|
check:
|
|
unwrapped == msg
|
|
missingDeps.len == 0
|
|
|
|
test "marking dependencies":
|
|
var messageReadyCount = 0
|
|
var messageSentCount = 0
|
|
var missingDepsCount = 0
|
|
|
|
rm.setCallbacks(
|
|
proc(messageId: MessageID) {.gcsafe.} = messageReadyCount += 1,
|
|
proc(messageId: MessageID) {.gcsafe.} = messageSentCount += 1,
|
|
proc(messageId: MessageID, missingDeps: seq[MessageID]) {.gcsafe.} = missingDepsCount += 1
|
|
)
|
|
|
|
# We'll create dependency IDs that aren't in the bloom filter yet
|
|
let id1 = "msg1"
|
|
let id2 = "msg2"
|
|
|
|
# Create a message that depends on these IDs
|
|
let msg3 = Message(
|
|
messageId: "msg3",
|
|
lamportTimestamp: 1,
|
|
causalHistory: @[id1, id2], # Depends on messages we haven't seen
|
|
channelId: "testChannel",
|
|
content: @[byte(3)],
|
|
bloomFilter: @[]
|
|
)
|
|
|
|
let serializedMsg3 = serializeMessage(msg3)
|
|
check serializedMsg3.isOk()
|
|
|
|
# Process the message - should identify missing dependencies
|
|
let unwrapResult = rm.unwrapReceivedMessage(serializedMsg3.get())
|
|
check unwrapResult.isOk()
|
|
let (_, missingDeps) = unwrapResult.get()
|
|
|
|
# Verify missing dependencies were identified
|
|
check missingDepsCount == 1
|
|
check missingDeps.len == 2
|
|
check id1 in missingDeps
|
|
check id2 in missingDeps
|
|
|
|
# Now mark dependencies as met
|
|
let markResult = rm.markDependenciesMet(missingDeps)
|
|
check markResult.isOk()
|
|
|
|
# Process the message again - should now be ready
|
|
let reprocessResult = rm.unwrapReceivedMessage(serializedMsg3.get())
|
|
check reprocessResult.isOk()
|
|
let (_, remainingDeps) = reprocessResult.get()
|
|
|
|
# Verify message is now processed
|
|
check remainingDeps.len == 0
|
|
check messageReadyCount == 1 # msg3 should now be ready
|
|
check missingDepsCount == 1 # Only the first attempt should report missing deps
|
|
|
|
test "callbacks work correctly":
|
|
var messageReadyCount = 0
|
|
var messageSentCount = 0
|
|
var missingDepsCount = 0
|
|
|
|
rm.setCallbacks(
|
|
proc(messageId: MessageID) {.gcsafe.} = messageReadyCount += 1,
|
|
proc(messageId: MessageID) {.gcsafe.} = messageSentCount += 1,
|
|
proc(messageId: MessageID, missingDeps: seq[MessageID]) {.gcsafe.} = missingDepsCount += 1
|
|
)
|
|
|
|
# First send our own message
|
|
let msg1 = @[byte(1)]
|
|
let id1 = "msg1"
|
|
let wrap1 = rm.wrapOutgoingMessage(msg1, id1)
|
|
check wrap1.isOk()
|
|
|
|
# Create a message that has our message in causal history
|
|
let msg2 = Message(
|
|
messageId: "msg2",
|
|
lamportTimestamp: rm.lamportTimestamp + 1,
|
|
causalHistory: @[id1], # Include our message in causal history
|
|
channelId: "testChannel",
|
|
content: @[byte(2)],
|
|
bloomFilter: @[] # Test with an empty bloom filter
|
|
)
|
|
|
|
let serializedMsg2 = serializeMessage(msg2)
|
|
check serializedMsg2.isOk()
|
|
|
|
# Process the "received" message - should trigger callbacks
|
|
let unwrapResult = rm.unwrapReceivedMessage(serializedMsg2.get())
|
|
check unwrapResult.isOk()
|
|
|
|
check messageReadyCount == 1 # For msg2 which we "received"
|
|
check messageSentCount == 1 # For msg1 which was acknowledged via causal history
|
|
|
|
test "bloom filter acknowledgment":
|
|
var messageSentCount = 0
|
|
|
|
rm.setCallbacks(
|
|
proc(messageId: MessageID) {.gcsafe.} = discard,
|
|
proc(messageId: MessageID) {.gcsafe.} = messageSentCount += 1,
|
|
proc(messageId: MessageID, missingDeps: seq[MessageID]) {.gcsafe.} = discard
|
|
)
|
|
|
|
# First send our own message
|
|
let msg1 = @[byte(1)]
|
|
let id1 = "msg1"
|
|
let wrap1 = rm.wrapOutgoingMessage(msg1, id1)
|
|
check wrap1.isOk()
|
|
|
|
# Create a message simulating another party's message
|
|
# with bloom filter containing our message
|
|
var otherPartyBloomFilter = newRollingBloomFilter(
|
|
DefaultBloomFilterCapacity,
|
|
DefaultBloomFilterErrorRate,
|
|
DefaultBloomFilterWindow
|
|
)
|
|
otherPartyBloomFilter.add(id1) # Add our message to their bloom filter
|
|
|
|
let bfResult = serializeBloomFilter(otherPartyBloomFilter.filter)
|
|
check bfResult.isOk()
|
|
|
|
let msg2 = Message(
|
|
messageId: "msg2",
|
|
lamportTimestamp: rm.lamportTimestamp + 1,
|
|
causalHistory: @[], # Empty causal history as we're using bloom filter
|
|
channelId: "testChannel",
|
|
content: @[byte(2)],
|
|
bloomFilter: bfResult.get()
|
|
)
|
|
|
|
let serializedMsg2 = serializeMessage(msg2)
|
|
check serializedMsg2.isOk()
|
|
|
|
# Process the "received" message - should trigger acknowledgment
|
|
let unwrapResult = rm.unwrapReceivedMessage(serializedMsg2.get())
|
|
check unwrapResult.isOk()
|
|
|
|
check messageSentCount == 1 # Our message should be acknowledged via bloom filter
|
|
|
|
test "periodic sync callback works":
|
|
var syncCallCount = 0
|
|
rm.setCallbacks(
|
|
proc(messageId: MessageID) {.gcsafe.} = discard,
|
|
proc(messageId: MessageID) {.gcsafe.} = discard,
|
|
proc(messageId: MessageID, missingDeps: seq[MessageID]) {.gcsafe.} = discard,
|
|
proc() {.gcsafe.} = syncCallCount += 1
|
|
)
|
|
|
|
rm.startPeriodicTasks()
|
|
# Sleep briefly to allow periodic tasks to run
|
|
waitFor sleepAsync(chronos.seconds(1))
|
|
rm.cleanup()
|
|
|
|
check(syncCallCount > 0)
|
|
|
|
test "protobuf serialization":
|
|
let msg = @[byte(1), 2, 3]
|
|
let msgId = "test-msg-1"
|
|
let msgResult = rm.wrapOutgoingMessage(msg, msgId)
|
|
check msgResult.isOk()
|
|
let wrapped = msgResult.get()
|
|
|
|
let unwrapResult = rm.unwrapReceivedMessage(wrapped)
|
|
check unwrapResult.isOk()
|
|
let (unwrapped, _) = unwrapResult.get()
|
|
|
|
check:
|
|
unwrapped == msg
|
|
unwrapped.len == msg.len
|
|
|
|
test "handles empty message":
|
|
let msg: seq[byte] = @[]
|
|
let msgId = "test-empty-msg"
|
|
let wrappedResult = rm.wrapOutgoingMessage(msg, msgId)
|
|
check(not wrappedResult.isOk())
|
|
check(wrappedResult.error == reInvalidArgument)
|
|
|
|
test "handles message too large":
|
|
let msg = newSeq[byte](MaxMessageSize + 1)
|
|
let msgId = "test-large-msg"
|
|
let wrappedResult = rm.wrapOutgoingMessage(msg, msgId)
|
|
check(not wrappedResult.isOk())
|
|
check(wrappedResult.error == reMessageTooLarge)
|
|
|
|
suite "cleanup":
|
|
test "cleanup works correctly":
|
|
let rmResult = newReliabilityManager("testChannel")
|
|
check rmResult.isOk()
|
|
let rm = rmResult.get()
|
|
|
|
# Add some messages
|
|
let msg = @[byte(1), 2, 3]
|
|
let msgId = "test-msg-1"
|
|
discard rm.wrapOutgoingMessage(msg, msgId)
|
|
|
|
# Cleanup
|
|
rm.cleanup()
|
|
|
|
# Check buffers are empty
|
|
check(rm.outgoingBuffer.len == 0)
|
|
check(rm.incomingBuffer.len == 0)
|
|
check(rm.messageHistory.len == 0) |