add more pessimistic/optimistic test cases, multi transport

This commit is contained in:
Eric Mastro 2021-12-13 15:15:32 +11:00
parent 18427359c0
commit 1bad6eeda1
No known key found for this signature in database
GPG Key ID: 141E3048D95A4E63
3 changed files with 272 additions and 67 deletions

View File

@ -58,7 +58,9 @@ const
type
SwitchError* = object of LPError
SwitchListenError* = object of SwitchError
transport*: Transport
ListenErrorCallback = proc (
t: Transport,
err: ref TransportListenError): ref SwitchListenError
{.gcsafe, raises: [Defect].}
@ -256,7 +258,7 @@ proc start*(s: Switch) {.async, gcsafe.} =
try:
await t.start(addrs)
except TransportListenError as e:
let err = s.listenError(e)
let err = s.listenError(t, e)
if not err.isNil:
raise err
s.acceptFuts.add(s.accept(t))
@ -264,13 +266,18 @@ proc start*(s: Switch) {.async, gcsafe.} =
debug "Started libp2p node", peer = s.peerInfo
proc newSwitchListenError*(parent: ref TransportListenError = nil): ref SwitchListenError =
(ref SwitchListenError)(msg: "Failed to start one transport", parent: parent)
proc newSwitchListenError*(
t: Transport,
parent: ref TransportListenError = nil): ref SwitchListenError =
(ref SwitchListenError)(msg: "Failed to start one transport",
transport: t,
parent: parent)
const ListenErrorDefault =
proc(e: ref TransportListenError): ref SwitchListenError =
proc(t: Transport, e: ref TransportListenError): ref SwitchListenError =
error "Failed to start one transport", error = e.msg
return newSwitchListenError(e)
return newSwitchListenError(t, e)
proc newSwitch*(peerInfo: PeerInfo,
transports: seq[Transport],

View File

@ -119,3 +119,11 @@ method handles*(
.filterIt(
it == multiCodec("p2p-circuit")
).len == 0
method `==`*(
a: Transport, b: Transport): bool {.base, gcsafe.} =
return
a.addrs == b.addrs and
a.running == b.running and
a.listenError == b.listenError

View File

@ -977,11 +977,14 @@ suite "Switch":
await srcTcpSwitch.stop()
asyncTest "listenError callback default returns TransportListenError (pessimistic)":
let switch = newStandardSwitch()
let
switch = newStandardSwitch()
transport = Transport()
check switch.listenError.isNil.not
let exc = newException(TransportListenError, "test")
check not switch.listenError(exc).isNil
check not switch.listenError(transport, exc).isNil
await switch.stop()
@ -990,53 +993,22 @@ suite "Switch":
switch = newStandardSwitch()
transportListenError = newException(TransportListenError, "test1")
switchListenError = newException(SwitchListenError, "test2")
transport = Transport()
switch.listenError = proc(
t: Transport, exc: ref TransportListenError): ref SwitchListenError =
check:
exc == transportListenError
t == transport
switch.listenError = proc(exc: ref TransportListenError): ref SwitchListenError =
check exc == transportListenError
return switchListenError
check switch.listenError(transportListenError) == switchListenError
check:
switch.listenError(transport, transportListenError) == switchListenError
await switch.stop()
# asyncTest "listenError called when transport fails and does not re-raise":
# let
# exc1 = newException(CatchableError, "test1")
# startMock =
# proc(addrs: seq[MultiAddress]): Future[void] =
# echo "[test] inside mocktransport.start"
# raise exc1
# mockTransport =
# proc(upgr: Upgrade): Transport {.raises: [Defect].} =
# # try/except here to keep the compiler happy. `startMock` can raise
# # an exception if called, but it is not actually called in `new`, so
# # we can wrap it in try/except to tell the compiler the exceptions
# # won't bubble up to `TransportProvider`.
# try: return MockTransport.new(startMock)
# except: return
# rng = crypto.newRng()
# ma = Multiaddress.init("/ip4/0.0.0.0/tcp/0").tryGet()
# switch = SwitchBuilder
# .new()
# .withRng(rng) # Give the application RNG
# .withAddress(ma) # Our local address(es)
# .withTransport(mockTransport) # Use MockTransport as transport
# .withMplex() # Use Mplex as muxer
# .withNoise() # Use Noise as secure manager
# .build()
# switch.listenError = proc(exc: ref CatchableError): ref CatchableError =
# check exc == exc1
# return nil
# try:
# await switch.start()
# except CatchableError:
# # shouldn't re-raise error in listenError callback (returns nil)
# fail()
# await switch.stop()
asyncTest "fully pessimistic: swith/transport listenError callback defaults, should re-raise first address failure":
let
exc0 = newException(CatchableError, "test0")
@ -1156,7 +1128,9 @@ suite "Switch":
.build()
switch.listenError = proc(exc: ref TransportListenError): ref SwitchListenError =
switch.listenError = proc(
t: Transport, exc: ref TransportListenError): ref SwitchListenError =
let ma = exc.ma
if ma == ma0:
check exc.parent == exc0
@ -1233,7 +1207,8 @@ suite "Switch":
.build()
switch.listenError = proc(exc: ref TransportListenError): ref SwitchListenError =
switch.listenError = proc(
t: Transport, exc: ref TransportListenError): ref SwitchListenError =
fail()
try:
@ -1249,26 +1224,27 @@ suite "Switch":
await switch.stop()
asyncTest "partial: transport pessimistic, switch optimistic, listenError only gets first address exception":
asyncTest "partial: transport pessimistic, switch optimistic, transport.listenError only gets first address exception, switch.listenError gets called for each transport failure":
var
handledTransportErrs0 = initTable[MultiAddress, ref CatchableError]()
handledTransportErrs1 = initTable[MultiAddress, ref CatchableError]()
let
# exc0 = newException(CatchableError, "test0")
# excXY, where X = MockTransport index, Y = transport exception index
exc01 = newException(CatchableError, "test01")
exc02 = newException(CatchableError, "test02")
exc10 = newException(CatchableError, "test10")
transportListenError0 =
proc(ma: MultiAddress, ex: ref CatchableError): ref TransportListenError =
handledTransportErrs0[ma] = ex
return newTransportListenError(ma, ex) # pessimistic transport multiaddress failure
# pessimistic transport multiaddress failure
return newTransportListenError(ma, ex)
transportListenError1 =
proc(ma: MultiAddress, ex: ref CatchableError): ref TransportListenError =
handledTransportErrs1[ma] = ex
return newTransportListenError(ma, ex) # pessimistic transport multiaddress failure
# pessimistic transport multiaddress failure
return newTransportListenError(ma, ex)
transportStartMock0 =
proc(self: MockTransport, addrs: seq[MultiAddress]): Future[void] {.async.} =
@ -1279,12 +1255,10 @@ suite "Switch":
elif i == 1:
echo "[test.startMock] raising exc1"
raise exc01
elif i == 2:
echo "[test.startMock] raising exc2"
raise exc02
else:
fail()
except CatchableError as e:
let err = self.listenError(ma, e)
# check err == nil
if not err.isNil:
raise err
@ -1295,10 +1269,9 @@ suite "Switch":
if i == 0:
raise exc10
else:
continue
fail()
except CatchableError as e:
let err = self.listenError(ma, e)
# check err == nil
if not err.isNil:
raise err
@ -1339,32 +1312,249 @@ suite "Switch":
.withNoise() # Use Noise as secure manager
.build()
switch.listenError = proc(
t: Transport,
exc: ref TransportListenError): ref SwitchListenError =
switch.listenError = proc(exc: ref TransportListenError): ref SwitchListenError =
let ma = exc.ma
if ma == ma0:
check exc.parent == exc10
check t == switch.transports[1]
elif ma == ma1:
check exc.parent == exc01
check t == switch.transports[0]
else:
fail()
return nil # switch optimistic, continue with all transports
# switch optimistic, continue with all transports
return nil
try:
await switch.start()
except SwitchListenError:
echo "switch listen error fail"
# because switch.listenError returns nil, we shouldn't expect an error
# to be raised from switch.start
fail()
echo "handledTransportErrs1:"
echo "handledTransportErrs0:"
for ma, ex in handledTransportErrs0:
echo "ma: ", $ma, ", ex: ", ex.msg
echo "handledTransportErrs2:"
echo "handledTransportErrs1:"
for ma, ex in handledTransportErrs1:
echo "ma: ", $ma, ", ex: ", ex.msg
check handledTransportErrs0 == [(ma1, exc01)].toTable()
check handledTransportErrs1 == [(ma0, exc10)].toTable()
# await switch.stop()
await switch.stop()
asyncTest "partial: transport0 optimistic, transport1 pessimistic, transport2 optimistic, switch pessimistic, transport3 does not start":
var
handledTransportErrs0 = initTable[MultiAddress, ref CatchableError]()
handledTransportErrs1 = initTable[MultiAddress, ref CatchableError]()
let
# excXY, where X = MockTransport index, Y = transport exception index
exc01 = newException(CatchableError, "test01")
exc10 = newException(CatchableError, "test10")
transportListenError0 =
proc(ma: MultiAddress, ex: ref CatchableError): ref TransportListenError =
handledTransportErrs0[ma] = ex
# optimistic transport multiaddress failure
return nil
transportListenError1 =
proc(ma: MultiAddress, ex: ref CatchableError): ref TransportListenError =
handledTransportErrs1[ma] = ex
return newTransportListenError(ma, ex)
transportListenError2 =
proc(ma: MultiAddress, ex: ref CatchableError): ref TransportListenError =
# should not get here as switch is pessimistic so will stop at first
# failed transport (transpor1)
fail()
transportStartMock0 =
proc(self: MockTransport, addrs: seq[MultiAddress]): Future[void] {.async.} =
for i, ma in addrs:
try:
if i == 1:
raise exc01
else:
continue
except CatchableError as e:
let err = self.listenError(ma, e)
if not err.isNil:
raise err
transportStartMock1 =
proc(self: MockTransport, addrs: seq[MultiAddress]): Future[void] {.async.} =
for i, ma in addrs:
try:
if i == 0:
raise exc10
else:
continue
except CatchableError as e:
let err = self.listenError(ma, e)
if not err.isNil:
raise err
transportStartMock2 =
proc(self: MockTransport, addrs: seq[MultiAddress]): Future[void] {.async.} =
# should not start transpor2 as transport1 should fail pessimistically
fail()
mockTransport0 =
proc(upgr: Upgrade): Transport {.raises: [Defect].} =
# try/except here to keep the compiler happy. `startMock` can raise
# an exception if called, but it is not actually called in `new`, so
# we can wrap it in try/except to tell the compiler the exceptions
# won't bubble up to `TransportProvider`.
try: return MockTransport.new(
upgr,
startMock = transportStartMock0,
listenError = transportListenError0)
except: return
mockTransport1 =
proc(upgr: Upgrade): Transport {.raises: [Defect].} =
# try/except here to keep the compiler happy. `startMock` can raise
# an exception if called, but it is not actually called in `new`, so
# we can wrap it in try/except to tell the compiler the exceptions
# won't bubble up to `TransportProvider`.
try: return MockTransport.new(
upgr,
startMock = transportStartMock1,
listenError = transportListenError1)
except: return
mockTransport2 =
proc(upgr: Upgrade): Transport {.raises: [Defect].} =
# try/except here to keep the compiler happy. `startMock` can raise
# an exception if called, but it is not actually called in `new`, so
# we can wrap it in try/except to tell the compiler the exceptions
# won't bubble up to `TransportProvider`.
try: return MockTransport.new(
upgr,
startMock = transportStartMock2,
listenError = transportListenError2)
except: return
ma0 = Multiaddress.init("/ip4/0.0.0.0/tcp/0").tryGet()
ma1 = Multiaddress.init("/ip4/0.0.0.0/tcp/1").tryGet()
ma2 = Multiaddress.init("/ip4/0.0.0.0/tcp/2").tryGet()
switch = SwitchBuilder
.new()
.withRng(rng) # Give the application RNG
.withAddresses(@[ma0, ma1, ma2]) # Our local address(es)
.withTransport(mockTransport0) # Use MockTransport 0 as transport
.withTransport(mockTransport1) # Use MockTransport 1 as transport
.withTransport(mockTransport2) # Use MockTransport 2 as transport
.withMplex() # Use Mplex as muxer
.withNoise() # Use Noise as secure manager
.build()
switch.listenError = proc(
t: Transport,
exc: ref TransportListenError): ref SwitchListenError =
check t == switch.transports[1]
check exc.ma == ma0
# pessimistic
return newSwitchListenError(t, exc)
try:
await switch.start()
except SwitchListenError as e:
let tListenEx = (ref TransportListenError)(e.parent)
check tListenEx.ma == ma0
check tListenEx.parent == exc10
check e.transport == switch.transports[1]
echo "handledTransportErrs0:"
for ma, ex in handledTransportErrs0:
echo "ma: ", $ma, ", ex: ", ex.msg
echo "handledTransportErrs1:"
for ma, ex in handledTransportErrs1:
echo "ma: ", $ma, ", ex: ", ex.msg
check handledTransportErrs0 == [(ma1, exc01)].toTable()
check handledTransportErrs1 == [(ma0, exc10)].toTable()
await switch.stop()
asyncTest "no exceptions raised, listenError should not be called":
let
transportListenError0 =
proc(ma: MultiAddress, ex: ref CatchableError): ref TransportListenError =
fail()
transportListenError1 =
proc(ma: MultiAddress, ex: ref CatchableError): ref TransportListenError =
fail()
transportStartMock0 =
proc(self: MockTransport, addrs: seq[MultiAddress]): Future[void] {.async.} =
for i, ma in addrs:
continue
transportStartMock1 =
proc(self: MockTransport, addrs: seq[MultiAddress]): Future[void] {.async.} =
for i, ma in addrs:
continue
mockTransport0 =
proc(upgr: Upgrade): Transport {.raises: [Defect].} =
# try/except here to keep the compiler happy. `startMock` can raise
# an exception if called, but it is not actually called in `new`, so
# we can wrap it in try/except to tell the compiler the exceptions
# won't bubble up to `TransportProvider`.
try: return MockTransport.new(
upgr,
startMock = transportStartMock0,
listenError = transportListenError0)
except: return
mockTransport1 =
proc(upgr: Upgrade): Transport {.raises: [Defect].} =
# try/except here to keep the compiler happy. `startMock` can raise
# an exception if called, but it is not actually called in `new`, so
# we can wrap it in try/except to tell the compiler the exceptions
# won't bubble up to `TransportProvider`.
try: return MockTransport.new(
upgr,
startMock = transportStartMock1,
listenError = transportListenError1)
except: return
ma0 = Multiaddress.init("/ip4/0.0.0.0/tcp/0").tryGet()
ma1 = Multiaddress.init("/ip4/0.0.0.0/tcp/1").tryGet()
ma2 = Multiaddress.init("/ip4/0.0.0.0/tcp/2").tryGet()
switch = SwitchBuilder
.new()
.withRng(rng) # Give the application RNG
.withAddresses(@[ma0, ma1, ma2]) # Our local address(es)
.withTransport(mockTransport0) # Use MockTransport 0 as transport
.withTransport(mockTransport1) # Use MockTransport 1 as transport
.withMplex() # Use Mplex as muxer
.withNoise() # Use Noise as secure manager
.build()
switch.listenError = proc(
t: Transport,
exc: ref TransportListenError): ref SwitchListenError =
fail()
try:
await switch.start()
except SwitchListenError:
# because switch.listenError should never be called, we shouldn't expect
# an error to be raised from switch.start
fail()
await switch.stop()