test(rendezvous): Refactor Rendezvous tests (#1644)

This commit is contained in:
Radosław Kamiński
2025-08-28 09:35:04 +01:00
committed by GitHub
parent 9865cc39b5
commit c7f29ed5db
7 changed files with 246 additions and 193 deletions

View File

@@ -0,0 +1,3 @@
{.used.}
import testdiscoverymngr, testrendezvous, testrendezvousinterface

View File

@@ -11,25 +11,16 @@
import options, chronos, sets
import
../libp2p/[
../../libp2p/[
protocols/rendezvous,
switch,
builders,
discovery/discoverymngr,
discovery/rendezvousinterface,
]
import ./helpers, ./utils/async_tests
proc createSwitch(rdv: RendezVous = RendezVous.new()): Switch =
SwitchBuilder
.new()
.withRng(newRng())
.withAddresses(@[MultiAddress.init(MemoryAutoAddress).tryGet()])
.withMemoryTransport()
.withMplex()
.withNoise()
.withRendezVous(rdv)
.build()
import ../helpers
import ../utils/async_tests
import ./utils
suite "Discovery":
teardown:

View File

@@ -0,0 +1,169 @@
{.used.}
# Nim-Libp2p
# Copyright (c) 2023 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
# at your option.
# This file may not be copied, modified, or distributed except according to
# those terms.
import sequtils, strutils
import chronos
import ../../libp2p/[protocols/rendezvous, switch]
import ../../libp2p/discovery/discoverymngr
import ../helpers
import ./utils
suite "RendezVous":
teardown:
checkTrackers()
asyncTest "Request locally returns 0 for empty namespace":
let (nodes, rdvs) = setupNodes(1)
nodes.startAndDeferStop()
const namespace = ""
check rdvs[0].requestLocally(namespace).len == 0
asyncTest "Request locally returns registered peers":
let (nodes, rdvs) = setupNodes(1)
nodes.startAndDeferStop()
const namespace = "foo"
await rdvs[0].advertise(namespace)
let peerRecords = rdvs[0].requestLocally(namespace)
check:
peerRecords.len == 1
peerRecords[0] == nodes[0].peerInfo.signedPeerRecord.data
asyncTest "Unsubscribe Locally removes registered peer":
let (nodes, rdvs) = setupNodes(1)
nodes.startAndDeferStop()
const namespace = "foo"
await rdvs[0].advertise(namespace)
check rdvs[0].requestLocally(namespace).len == 1
rdvs[0].unsubscribeLocally(namespace)
check rdvs[0].requestLocally(namespace).len == 0
asyncTest "Request returns 0 for empty namespace from remote":
let (rendezvousNode, peerNodes, peerRdvs, _) = setupRendezvousNodeWithPeerNodes(1)
(rendezvousNode & peerNodes).startAndDeferStop()
await connectNodes(peerNodes[0], rendezvousNode)
const namespace = "empty"
check (await peerRdvs[0].request(Opt.some(namespace))).len == 0
asyncTest "Request returns registered peers from remote":
let (rendezvousNode, peerNodes, peerRdvs, _) = setupRendezvousNodeWithPeerNodes(1)
(rendezvousNode & peerNodes).startAndDeferStop()
await connectNodes(peerNodes[0], rendezvousNode)
const namespace = "foo"
await peerRdvs[0].advertise(namespace)
let peerRecords = await peerRdvs[0].request(Opt.some(namespace))
check:
peerRecords.len == 1
peerRecords[0] == peerNodes[0].peerInfo.signedPeerRecord.data
asyncTest "Unsubscribe removes registered peer from remote":
let (rendezvousNode, peerNodes, peerRdvs, _) = setupRendezvousNodeWithPeerNodes(1)
(rendezvousNode & peerNodes).startAndDeferStop()
await connectNodes(peerNodes[0], rendezvousNode)
const namespace = "foo"
await peerRdvs[0].advertise(namespace)
check (await peerRdvs[0].request(Opt.some(namespace))).len == 1
await peerRdvs[0].unsubscribe(namespace)
check (await peerRdvs[0].request(Opt.some(namespace))).len == 0
asyncTest "Consecutive requests with namespace returns peers with pagination":
let (rendezvousNode, peerNodes, peerRdvs, _) = setupRendezvousNodeWithPeerNodes(11)
(rendezvousNode & peerNodes).startAndDeferStop()
await connectNodesToRendezvousNode(peerNodes, rendezvousNode)
const namespace = "foo"
await allFutures(peerRdvs.mapIt(it.advertise(namespace)))
var data = peerNodes.mapIt(it.peerInfo.signedPeerRecord.data)
var peerRecords = await peerRdvs[0].request(Opt.some(namespace), 5)
check:
peerRecords.len == 5
peerRecords.allIt(it in data)
data.keepItIf(it notin peerRecords)
peerRecords = await peerRdvs[0].request(Opt.some(namespace))
check:
peerRecords.len == 5
peerRecords.allIt(it in data)
check (await peerRdvs[0].request(Opt.some(namespace))).len == 0
asyncTest "Request without namespace returns all registered peers":
let (rendezvousNode, peerNodes, peerRdvs, _) = setupRendezvousNodeWithPeerNodes(10)
(rendezvousNode & peerNodes).startAndDeferStop()
await connectNodesToRendezvousNode(peerNodes, rendezvousNode)
const namespaceFoo = "foo"
const namespaceBar = "Bar"
await allFutures(peerRdvs[0 ..< 5].mapIt(it.advertise(namespaceFoo)))
await allFutures(peerRdvs[5 ..< 10].mapIt(it.advertise(namespaceBar)))
check (await peerRdvs[0].request()).len == 10
check (await peerRdvs[0].request(Opt.none(string))).len == 10
asyncTest "Consecutive requests with namespace keep cookie and retun only new peers":
let (rendezvousNode, peerNodes, peerRdvs, _) = setupRendezvousNodeWithPeerNodes(2)
(rendezvousNode & peerNodes).startAndDeferStop()
await connectNodesToRendezvousNode(peerNodes, rendezvousNode)
let
rdv0 = peerRdvs[0]
rdv1 = peerRdvs[1]
const namespace = "foo"
await rdv0.advertise(namespace)
discard await rdv0.request(Opt.some(namespace))
await rdv1.advertise(namespace)
let peerRecords = await rdv0.request(Opt.some(namespace))
check:
peerRecords.len == 1
peerRecords[0] == peerNodes[1].peerInfo.signedPeerRecord.data
asyncTest "Various local error":
let rdv = RendezVous.new(minDuration = 1.minutes, maxDuration = 72.hours)
expect AdvertiseError:
discard await rdv.request(Opt.some("A".repeat(300)))
expect AdvertiseError:
discard await rdv.request(Opt.some("A"), -1)
expect AdvertiseError:
discard await rdv.request(Opt.some("A"), 3000)
expect AdvertiseError:
await rdv.advertise("A".repeat(300))
expect AdvertiseError:
await rdv.advertise("A", 73.hours)
expect AdvertiseError:
await rdv.advertise("A", 30.seconds)
test "Various config error":
expect RendezVousError:
discard RendezVous.new(minDuration = 30.seconds)
expect RendezVousError:
discard RendezVous.new(maxDuration = 73.hours)
expect RendezVousError:
discard RendezVous.new(minDuration = 15.minutes, maxDuration = 10.minutes)

View File

@@ -9,22 +9,11 @@
# This file may not be copied, modified, or distributed except according to
# those terms.
import sequtils, strutils
import chronos
import ../libp2p/[protocols/rendezvous, switch, builders]
import ../libp2p/discovery/[rendezvousinterface, discoverymngr]
import ./helpers
proc createSwitch(rdv: RendezVous = RendezVous.new()): Switch =
SwitchBuilder
.new()
.withRng(newRng())
.withAddresses(@[MultiAddress.init("/ip4/0.0.0.0/tcp/0").tryGet()])
.withTcpTransport()
.withMplex()
.withNoise()
.withRendezVous(rdv)
.build()
import ../../libp2p/[protocols/rendezvous, switch, builders]
import ../../libp2p/discovery/[rendezvousinterface, discoverymngr]
import ../helpers
import ./utils
type
MockRendezVous = ref object of RendezVous
@@ -33,7 +22,9 @@ type
MockErrorRendezVous = ref object of MockRendezVous
method advertise*(self: MockRendezVous, namespace: string, ttl: Duration) {.async.} =
method advertise*(
self: MockRendezVous, namespace: string, ttl: Duration
) {.async: (raises: [CancelledError, AdvertiseError]).} =
if namespace == "ns1":
self.numAdvertiseNs1 += 1
elif namespace == "ns2":
@@ -43,9 +34,9 @@ method advertise*(self: MockRendezVous, namespace: string, ttl: Duration) {.asyn
method advertise*(
self: MockErrorRendezVous, namespace: string, ttl: Duration
) {.async.} =
) {.async: (raises: [CancelledError, AdvertiseError]).} =
await procCall MockRendezVous(self).advertise(namespace, ttl)
raise newException(CatchableError, "MockErrorRendezVous.advertise")
raise newException(AdvertiseError, "MockErrorRendezVous.advertise")
suite "RendezVous Interface":
teardown:

55
tests/discovery/utils.nim Normal file
View File

@@ -0,0 +1,55 @@
import sequtils
import chronos
import ../../libp2p/[protocols/rendezvous, switch, builders]
proc createSwitch*(rdv: RendezVous = RendezVous.new()): Switch =
SwitchBuilder
.new()
.withRng(newRng())
.withAddresses(@[MultiAddress.init(MemoryAutoAddress).tryGet()])
.withMemoryTransport()
.withMplex()
.withNoise()
.withRendezVous(rdv)
.build()
proc setupNodes*(count: int): (seq[Switch], seq[RendezVous]) =
doAssert(count > 0, "Count must be greater than 0")
var
nodes: seq[Switch] = @[]
rdvs: seq[RendezVous] = @[]
for x in 0 ..< count:
let rdv = RendezVous.new()
let node = createSwitch(rdv)
nodes.add(node)
rdvs.add(rdv)
return (nodes, rdvs)
proc setupRendezvousNodeWithPeerNodes*(
count: int
): (Switch, seq[Switch], seq[RendezVous], RendezVous) =
let
(nodes, rdvs) = setupNodes(count + 1)
rendezvousNode = nodes[0]
rendezvousRdv = rdvs[0]
peerNodes = nodes[1 ..^ 1]
peerRdvs = rdvs[1 ..^ 1]
return (rendezvousNode, peerNodes, peerRdvs, rendezvousRdv)
template startAndDeferStop*(nodes: seq[Switch]) =
await allFutures(nodes.mapIt(it.start()))
defer:
await allFutures(nodes.mapIt(it.stop()))
proc connectNodes*[T: Switch](dialer: T, target: T) {.async.} =
await dialer.connect(target.peerInfo.peerId, target.peerInfo.addrs)
proc connectNodesToRendezvousNode*[T: Switch](
nodes: seq[T], rendezvousNode: T
) {.async.} =
for node in nodes:
await connectNodes(node, rendezvousNode)

View File

@@ -30,10 +30,12 @@ import
import
testnameresolve, testmultistream, testbufferstream, testidentify,
testobservedaddrmanager, testconnmngr, testswitch, testnoise, testpeerinfo,
testpeerstore, testping, testmplex, testrelayv1, testrelayv2, testrendezvous,
testdiscovery, testyamux, testyamuxheader, testautonat, testautonatservice,
testautonatv2, testautorelay, testdcutr, testhpservice, testutility, testhelpers,
testwildcardresolverservice, testperf
testpeerstore, testping, testmplex, testrelayv1, testrelayv2, testyamux,
testyamuxheader, testautonat, testautonatservice, testautonatv2, testautorelay,
testdcutr, testhpservice, testutility, testhelpers, testwildcardresolverservice,
testperf
import discovery/testdiscovery
import kademlia/[testencoding, testroutingtable, testfindnode, testputval]

View File

@@ -1,158 +0,0 @@
{.used.}
# Nim-Libp2p
# Copyright (c) 2023 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
# at your option.
# This file may not be copied, modified, or distributed except according to
# those terms.
import sequtils, strutils
import chronos
import ../libp2p/[protocols/rendezvous, switch, builders]
import ../libp2p/discovery/discoverymngr
import ./helpers
proc createSwitch(rdv: RendezVous = RendezVous.new()): Switch =
SwitchBuilder
.new()
.withRng(newRng())
.withAddresses(@[MultiAddress.init("/ip4/0.0.0.0/tcp/0").tryGet()])
.withTcpTransport()
.withMplex()
.withNoise()
.withRendezVous(rdv)
.build()
suite "RendezVous":
teardown:
checkTrackers()
asyncTest "Simple local test":
let
rdv = RendezVous.new()
s = createSwitch(rdv)
await s.start()
let res0 = rdv.requestLocally("empty")
check res0.len == 0
await rdv.advertise("foo")
let res1 = rdv.requestLocally("foo")
check:
res1.len == 1
res1[0] == s.peerInfo.signedPeerRecord.data
let res2 = rdv.requestLocally("bar")
check res2.len == 0
rdv.unsubscribeLocally("foo")
let res3 = rdv.requestLocally("foo")
check res3.len == 0
await s.stop()
asyncTest "Simple remote test":
let
rdv = RendezVous.new()
client = createSwitch(rdv)
remoteSwitch = createSwitch()
await client.start()
await remoteSwitch.start()
await client.connect(remoteSwitch.peerInfo.peerId, remoteSwitch.peerInfo.addrs)
let res0 = await rdv.request(Opt.some("empty"))
check res0.len == 0
await rdv.advertise("foo")
let res1 = await rdv.request(Opt.some("foo"))
check:
res1.len == 1
res1[0] == client.peerInfo.signedPeerRecord.data
let res2 = await rdv.request(Opt.some("bar"))
check res2.len == 0
await rdv.unsubscribe("foo")
let res3 = await rdv.request(Opt.some("foo"))
check res3.len == 0
await allFutures(client.stop(), remoteSwitch.stop())
asyncTest "Harder remote test":
var
rdvSeq: seq[RendezVous] = @[]
clientSeq: seq[Switch] = @[]
remoteSwitch = createSwitch()
for x in 0 .. 10:
rdvSeq.add(RendezVous.new())
clientSeq.add(createSwitch(rdvSeq[^1]))
await remoteSwitch.start()
await allFutures(clientSeq.mapIt(it.start()))
await allFutures(
clientSeq.mapIt(remoteSwitch.connect(it.peerInfo.peerId, it.peerInfo.addrs))
)
await allFutures(rdvSeq.mapIt(it.advertise("foo")))
var data = clientSeq.mapIt(it.peerInfo.signedPeerRecord.data)
let res1 = await rdvSeq[0].request(Opt.some("foo"), 5)
check res1.len == 5
for d in res1:
check d in data
data.keepItIf(it notin res1)
let res2 = await rdvSeq[0].request(Opt.some("foo"))
check res2.len == 5
for d in res2:
check d in data
let res3 = await rdvSeq[0].request(Opt.some("foo"))
check res3.len == 0
let res4 = await rdvSeq[0].request()
check res4.len == 11
let res5 = await rdvSeq[0].request(Opt.none(string))
check res5.len == 11
await remoteSwitch.stop()
await allFutures(clientSeq.mapIt(it.stop()))
asyncTest "Simple cookie test":
let
rdvA = RendezVous.new()
rdvB = RendezVous.new()
clientA = createSwitch(rdvA)
clientB = createSwitch(rdvB)
remoteSwitch = createSwitch()
await clientA.start()
await clientB.start()
await remoteSwitch.start()
await clientA.connect(remoteSwitch.peerInfo.peerId, remoteSwitch.peerInfo.addrs)
await clientB.connect(remoteSwitch.peerInfo.peerId, remoteSwitch.peerInfo.addrs)
await rdvA.advertise("foo")
let res1 = await rdvA.request(Opt.some("foo"))
await rdvB.advertise("foo")
let res2 = await rdvA.request(Opt.some("foo"))
check:
res2.len == 1
res2[0] == clientB.peerInfo.signedPeerRecord.data
await allFutures(clientA.stop(), clientB.stop(), remoteSwitch.stop())
asyncTest "Various local error":
let
rdv = RendezVous.new(minDuration = 1.minutes, maxDuration = 72.hours)
switch = createSwitch(rdv)
expect AdvertiseError:
discard await rdv.request(Opt.some("A".repeat(300)))
expect AdvertiseError:
discard await rdv.request(Opt.some("A"), -1)
expect AdvertiseError:
discard await rdv.request(Opt.some("A"), 3000)
expect AdvertiseError:
await rdv.advertise("A".repeat(300))
expect AdvertiseError:
await rdv.advertise("A", 73.hours)
expect AdvertiseError:
await rdv.advertise("A", 30.seconds)
test "Various config error":
expect RendezVousError:
discard RendezVous.new(minDuration = 30.seconds)
expect RendezVousError:
discard RendezVous.new(maxDuration = 73.hours)
expect RendezVousError:
discard RendezVous.new(minDuration = 15.minutes, maxDuration = 10.minutes)