finish test coverage/fixes for connector.py
This commit is contained in:
parent
6ad6f8f40f
commit
a458fe9ab9
|
@ -116,10 +116,9 @@ class Connector(object):
|
||||||
pass # pragma: no cover
|
pass # pragma: no cover
|
||||||
|
|
||||||
# TODO: unify the tense of these method-name verbs
|
# TODO: unify the tense of these method-name verbs
|
||||||
@m.input()
|
|
||||||
def listener_ready(self, hint_objs):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
# add_relay() and got_hints() are called by the Manager as it receives
|
||||||
|
# messages from our peer. stop() is called when the Manager shuts down
|
||||||
@m.input()
|
@m.input()
|
||||||
def add_relay(self, hint_objs):
|
def add_relay(self, hint_objs):
|
||||||
pass
|
pass
|
||||||
|
@ -129,16 +128,25 @@ class Connector(object):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@m.input()
|
@m.input()
|
||||||
def add_candidate(self, c): # called by DilatedConnectionProtocol
|
def stop(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
# called by ourselves, when _start_listener() is ready
|
||||||
|
@m.input()
|
||||||
|
def listener_ready(self, hint_objs):
|
||||||
|
pass
|
||||||
|
|
||||||
|
# called when DilatedConnectionProtocol submits itself, after KCM
|
||||||
|
# received
|
||||||
|
@m.input()
|
||||||
|
def add_candidate(self, c):
|
||||||
|
pass
|
||||||
|
|
||||||
|
# called by ourselves, via consider()
|
||||||
@m.input()
|
@m.input()
|
||||||
def accept(self, c):
|
def accept(self, c):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@m.input()
|
|
||||||
def stop(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@m.output()
|
@m.output()
|
||||||
def use_hints(self, hint_objs):
|
def use_hints(self, hint_objs):
|
||||||
|
@ -199,17 +207,12 @@ class Connector(object):
|
||||||
[c.loseConnection() for c in self._pending_connections]
|
[c.loseConnection() for c in self._pending_connections]
|
||||||
return d
|
return d
|
||||||
|
|
||||||
def stop_winner(self):
|
|
||||||
d = self._winner.when_disconnected()
|
|
||||||
self._winner.disconnect()
|
|
||||||
return d
|
|
||||||
|
|
||||||
def break_cycles(self):
|
def break_cycles(self):
|
||||||
# help GC by forgetting references to things that reference us
|
# help GC by forgetting references to things that reference us
|
||||||
self._listeners.clear()
|
self._listeners.clear()
|
||||||
self._pending_connectors.clear()
|
self._pending_connectors.clear()
|
||||||
self._pending_connections.clear()
|
self._pending_connections.clear()
|
||||||
self._winner = None
|
self._winning_connection = None
|
||||||
|
|
||||||
connecting.upon(listener_ready, enter=connecting, outputs=[publish_hints])
|
connecting.upon(listener_ready, enter=connecting, outputs=[publish_hints])
|
||||||
connecting.upon(add_relay, enter=connecting, outputs=[use_hints,
|
connecting.upon(add_relay, enter=connecting, outputs=[use_hints,
|
||||||
|
@ -224,6 +227,8 @@ class Connector(object):
|
||||||
connected.upon(listener_ready, enter=connected, outputs=[])
|
connected.upon(listener_ready, enter=connected, outputs=[])
|
||||||
connected.upon(add_relay, enter=connected, outputs=[])
|
connected.upon(add_relay, enter=connected, outputs=[])
|
||||||
connected.upon(got_hints, enter=connected, outputs=[])
|
connected.upon(got_hints, enter=connected, outputs=[])
|
||||||
|
# TODO: tell them to disconnect? will they hang out forever? I *think*
|
||||||
|
# they'll drop this once they get a KCM on the winning connection.
|
||||||
connected.upon(add_candidate, enter=connected, outputs=[])
|
connected.upon(add_candidate, enter=connected, outputs=[])
|
||||||
connected.upon(accept, enter=connected, outputs=[])
|
connected.upon(accept, enter=connected, outputs=[])
|
||||||
connected.upon(stop, enter=stopped, outputs=[stop_everything])
|
connected.upon(stop, enter=stopped, outputs=[stop_everything])
|
||||||
|
@ -232,20 +237,23 @@ class Connector(object):
|
||||||
# maybe add_candidate, accept
|
# maybe add_candidate, accept
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
self._start_listener()
|
if not self._no_listen and not self._tor:
|
||||||
|
addresses = self._get_listener_addresses()
|
||||||
|
self._start_listener(addresses)
|
||||||
if self._transit_relays:
|
if self._transit_relays:
|
||||||
self._publish_hints(self._transit_relays)
|
self._publish_hints(self._transit_relays)
|
||||||
self._use_hints(self._transit_relays)
|
self._use_hints(self._transit_relays)
|
||||||
|
|
||||||
def _start_listener(self):
|
def _get_listener_addresses(self):
|
||||||
if self._no_listen or self._tor:
|
|
||||||
return
|
|
||||||
addresses = ipaddrs.find_addresses()
|
addresses = ipaddrs.find_addresses()
|
||||||
non_loopback_addresses = [a for a in addresses if a != "127.0.0.1"]
|
non_loopback_addresses = [a for a in addresses if a != "127.0.0.1"]
|
||||||
if non_loopback_addresses:
|
if non_loopback_addresses:
|
||||||
# some test hosts, including the appveyor VMs, *only* have
|
# some test hosts, including the appveyor VMs, *only* have
|
||||||
# 127.0.0.1, and the tests will hang badly if we remove it.
|
# 127.0.0.1, and the tests will hang badly if we remove it.
|
||||||
addresses = non_loopback_addresses
|
addresses = non_loopback_addresses
|
||||||
|
return addresses
|
||||||
|
|
||||||
|
def _start_listener(self, addresses):
|
||||||
# TODO: listen on a fixed port, if possible, for NAT/p2p benefits, also
|
# TODO: listen on a fixed port, if possible, for NAT/p2p benefits, also
|
||||||
# to make firewall configs easier
|
# to make firewall configs easier
|
||||||
# TODO: retain listening port between connection generations?
|
# TODO: retain listening port between connection generations?
|
||||||
|
@ -263,6 +271,14 @@ class Connector(object):
|
||||||
d.addCallback(_listening)
|
d.addCallback(_listening)
|
||||||
d.addErrback(log.err)
|
d.addErrback(log.err)
|
||||||
|
|
||||||
|
def _schedule_connection(self, delay, h, is_relay):
|
||||||
|
ep = endpoint_from_hint_obj(h, self._tor, self._reactor)
|
||||||
|
desc = describe_hint_obj(h, is_relay, self._tor)
|
||||||
|
d = deferLater(self._reactor, delay,
|
||||||
|
self._connect, ep, desc, is_relay)
|
||||||
|
d.addErrback(log.err)
|
||||||
|
self._pending_connectors.add(d)
|
||||||
|
|
||||||
def _use_hints(self, hints):
|
def _use_hints(self, hints):
|
||||||
# first, pull out all the relays, we'll connect to them later
|
# first, pull out all the relays, we'll connect to them later
|
||||||
relays = []
|
relays = []
|
||||||
|
@ -279,12 +295,7 @@ class Connector(object):
|
||||||
for h in direct[p]:
|
for h in direct[p]:
|
||||||
if isinstance(h, TorTCPV1Hint) and not self._tor:
|
if isinstance(h, TorTCPV1Hint) and not self._tor:
|
||||||
continue
|
continue
|
||||||
ep = endpoint_from_hint_obj(h, self._tor, self._reactor)
|
self._schedule_connection(delay, h, is_relay=False)
|
||||||
desc = describe_hint_obj(h, False, self._tor)
|
|
||||||
d = deferLater(self._reactor, delay,
|
|
||||||
self._connect, ep, desc, is_relay=False)
|
|
||||||
d.addErrback(log.err)
|
|
||||||
self._pending_connectors.add(d)
|
|
||||||
made_direct = True
|
made_direct = True
|
||||||
# Make all direct connections immediately. Later, we'll change
|
# Make all direct connections immediately. Later, we'll change
|
||||||
# the add_candidate() function to look at the priority when
|
# the add_candidate() function to look at the priority when
|
||||||
|
@ -314,12 +325,7 @@ class Connector(object):
|
||||||
# quickly or hang for a long time.
|
# quickly or hang for a long time.
|
||||||
for r in relays:
|
for r in relays:
|
||||||
for h in r.hints:
|
for h in r.hints:
|
||||||
ep = endpoint_from_hint_obj(h, self._tor, self._reactor)
|
self._schedule_connection(delay, h, is_relay=True)
|
||||||
desc = describe_hint_obj(h, True, self._tor)
|
|
||||||
d = deferLater(self._reactor, delay,
|
|
||||||
self._connect, ep, desc, is_relay=True)
|
|
||||||
d.addErrback(log.err)
|
|
||||||
self._pending_connectors.add(d)
|
|
||||||
# TODO:
|
# TODO:
|
||||||
# if not contenders:
|
# if not contenders:
|
||||||
# raise TransitError("No contenders for connection")
|
# raise TransitError("No contenders for connection")
|
||||||
|
|
|
@ -5,20 +5,19 @@ from zope.interface import alsoProvides
|
||||||
from twisted.trial import unittest
|
from twisted.trial import unittest
|
||||||
from twisted.internet.task import Clock
|
from twisted.internet.task import Clock
|
||||||
from twisted.internet.defer import Deferred
|
from twisted.internet.defer import Deferred
|
||||||
#from twisted.internet import endpoints
|
|
||||||
from ...eventual import EventualQueue
|
from ...eventual import EventualQueue
|
||||||
from ..._interfaces import IDilationManager, IDilationConnector
|
from ..._interfaces import IDilationManager, IDilationConnector
|
||||||
from ..._dilation import roles
|
from ..._dilation import roles
|
||||||
from ..._hints import DirectTCPV1Hint, RelayV1Hint, TorTCPV1Hint
|
from ..._hints import DirectTCPV1Hint, RelayV1Hint, TorTCPV1Hint
|
||||||
from ..._dilation.connector import (#describe_hint_obj, parse_hint_argv,
|
from ..._dilation.connection import KCM
|
||||||
#parse_tcp_v1_hint, parse_hint, encode_hint,
|
from ..._dilation.connector import (Connector,
|
||||||
Connector,
|
|
||||||
build_sided_relay_handshake,
|
build_sided_relay_handshake,
|
||||||
build_noise,
|
build_noise,
|
||||||
OutboundConnectionFactory,
|
OutboundConnectionFactory,
|
||||||
InboundConnectionFactory,
|
InboundConnectionFactory,
|
||||||
PROLOGUE_LEADER, PROLOGUE_FOLLOWER,
|
PROLOGUE_LEADER, PROLOGUE_FOLLOWER,
|
||||||
)
|
)
|
||||||
|
from .common import clear_mock_calls
|
||||||
|
|
||||||
class Handshake(unittest.TestCase):
|
class Handshake(unittest.TestCase):
|
||||||
def test_build(self):
|
def test_build(self):
|
||||||
|
@ -149,67 +148,125 @@ class TestConnector(unittest.TestCase):
|
||||||
c.stop()
|
c.stop()
|
||||||
# we stop while we're connecting, so no connections must be stopped
|
# we stop while we're connecting, so no connections must be stopped
|
||||||
|
|
||||||
def test_basic(self):
|
def test_empty(self):
|
||||||
c, h = make_connector(listen=False, relay=None, role=roles.LEADER)
|
c, h = make_connector(listen=False, relay=None, role=roles.LEADER)
|
||||||
|
c._schedule_connection = mock.Mock()
|
||||||
c.start()
|
c.start()
|
||||||
# no relays, so it publishes no hints
|
# no relays, so it publishes no hints
|
||||||
self.assertEqual(h.manager.mock_calls, [])
|
self.assertEqual(h.manager.mock_calls, [])
|
||||||
# and no listener, so nothing happens until we provide a hint
|
# and no listener, so nothing happens until we provide a hint
|
||||||
|
self.assertEqual(c._schedule_connection.mock_calls, [])
|
||||||
|
c.stop()
|
||||||
|
|
||||||
ep0 = mock.Mock()
|
def test_basic(self):
|
||||||
ep0_connect_d = Deferred()
|
c, h = make_connector(listen=False, relay=None, role=roles.LEADER)
|
||||||
ep0.connect = mock.Mock(return_value=ep0_connect_d)
|
c._schedule_connection = mock.Mock()
|
||||||
efho = mock.Mock(side_effect=[ep0])
|
c.start()
|
||||||
hint0 = DirectTCPV1Hint("foo", 55, 0.0)
|
# no relays, so it publishes no hints
|
||||||
dho = mock.Mock(side_effect=["desc0"])
|
self.assertEqual(h.manager.mock_calls, [])
|
||||||
|
# and no listener, so nothing happens until we provide a hint
|
||||||
|
self.assertEqual(c._schedule_connection.mock_calls, [])
|
||||||
|
|
||||||
|
hint = DirectTCPV1Hint("foo", 55, 0.0)
|
||||||
|
c.got_hints([hint])
|
||||||
|
|
||||||
|
# received hints don't get published
|
||||||
|
self.assertEqual(h.manager.mock_calls, [])
|
||||||
|
# they just schedule a connection
|
||||||
|
self.assertEqual(c._schedule_connection.mock_calls,
|
||||||
|
[mock.call(0.0, DirectTCPV1Hint("foo", 55, 0.0),
|
||||||
|
is_relay=False)])
|
||||||
|
|
||||||
|
def test_listen_addresses(self):
|
||||||
|
c, h = make_connector(listen=True, role=roles.LEADER)
|
||||||
|
with mock.patch("wormhole.ipaddrs.find_addresses",
|
||||||
|
return_value=["127.0.0.1", "1.2.3.4", "5.6.7.8"]):
|
||||||
|
self.assertEqual(c._get_listener_addresses(),
|
||||||
|
["1.2.3.4", "5.6.7.8"])
|
||||||
|
with mock.patch("wormhole.ipaddrs.find_addresses",
|
||||||
|
return_value=["127.0.0.1"]):
|
||||||
|
# some test hosts, including the appveyor VMs, *only* have
|
||||||
|
# 127.0.0.1, and the tests will hang badly if we remove it.
|
||||||
|
self.assertEqual(c._get_listener_addresses(), ["127.0.0.1"])
|
||||||
|
|
||||||
|
def test_listen(self):
|
||||||
|
c, h = make_connector(listen=True, role=roles.LEADER)
|
||||||
|
c._start_listener = mock.Mock()
|
||||||
|
with mock.patch("wormhole.ipaddrs.find_addresses",
|
||||||
|
return_value=["127.0.0.1", "1.2.3.4", "5.6.7.8"]):
|
||||||
|
c.start()
|
||||||
|
self.assertEqual(c._start_listener.mock_calls,
|
||||||
|
[mock.call(["1.2.3.4", "5.6.7.8"])])
|
||||||
|
|
||||||
|
def test_start_listen(self):
|
||||||
|
c, h = make_connector(listen=True, role=roles.LEADER)
|
||||||
|
ep = mock.Mock()
|
||||||
|
d = Deferred()
|
||||||
|
ep.listen = mock.Mock(return_value=d)
|
||||||
|
with mock.patch("wormhole._dilation.connector.serverFromString",
|
||||||
|
return_value=ep) as sfs:
|
||||||
|
c._start_listener(["1.2.3.4", "5.6.7.8"])
|
||||||
|
self.assertEqual(sfs.mock_calls, [mock.call(h.reactor, "tcp:0")])
|
||||||
|
lp = mock.Mock()
|
||||||
|
host = mock.Mock()
|
||||||
|
host.port = 66
|
||||||
|
lp.getHost = mock.Mock(return_value=host)
|
||||||
|
d.callback(lp)
|
||||||
|
self.assertEqual(h.manager.mock_calls,
|
||||||
|
[mock.call.send_hints([{"type": "direct-tcp-v1",
|
||||||
|
"hostname": "1.2.3.4",
|
||||||
|
"port": 66,
|
||||||
|
"priority": 0.0
|
||||||
|
},
|
||||||
|
{"type": "direct-tcp-v1",
|
||||||
|
"hostname": "5.6.7.8",
|
||||||
|
"port": 66,
|
||||||
|
"priority": 0.0
|
||||||
|
},
|
||||||
|
])])
|
||||||
|
|
||||||
|
def test_schedule_connection_no_relay(self):
|
||||||
|
c, h = make_connector(listen=True, role=roles.LEADER)
|
||||||
|
hint = DirectTCPV1Hint("foo", 55, 0.0)
|
||||||
|
ep = mock.Mock()
|
||||||
with mock.patch("wormhole._dilation.connector.endpoint_from_hint_obj",
|
with mock.patch("wormhole._dilation.connector.endpoint_from_hint_obj",
|
||||||
efho):
|
side_effect=[ep]) as efho:
|
||||||
with mock.patch("wormhole._dilation.connector.describe_hint_obj", dho):
|
c._schedule_connection(0.0, hint, False)
|
||||||
c.got_hints([hint0])
|
self.assertEqual(efho.mock_calls, [mock.call(hint, h.tor, h.reactor)])
|
||||||
self.assertEqual(efho.mock_calls, [mock.call(hint0, h.tor, h.reactor)])
|
self.assertEqual(ep.mock_calls, [])
|
||||||
self.assertEqual(dho.mock_calls, [mock.call(hint0, False, h.tor)])
|
d = Deferred()
|
||||||
f0 = mock.Mock()
|
ep.connect = mock.Mock(side_effect=[d])
|
||||||
|
# direct hints are scheduled for T+0.0
|
||||||
|
f = mock.Mock()
|
||||||
with mock.patch("wormhole._dilation.connector.OutboundConnectionFactory",
|
with mock.patch("wormhole._dilation.connector.OutboundConnectionFactory",
|
||||||
return_value=f0) as ocf:
|
return_value=f) as ocf:
|
||||||
h.clock.advance(c.RELAY_DELAY / 2 + 0.01)
|
h.clock.advance(1.0)
|
||||||
self.assertEqual(ocf.mock_calls, [mock.call(c, None)])
|
self.assertEqual(ocf.mock_calls, [mock.call(c, None)])
|
||||||
self.assertEqual(ep0.connect.mock_calls, [mock.call(f0)])
|
self.assertEqual(ep.connect.mock_calls, [mock.call(f)])
|
||||||
|
|
||||||
p = mock.Mock()
|
p = mock.Mock()
|
||||||
ep0_connect_d.callback(p)
|
d.callback(p)
|
||||||
self.assertEqual(p.mock_calls,
|
self.assertEqual(p.mock_calls,
|
||||||
[mock.call.when_disconnected(),
|
[mock.call.when_disconnected(),
|
||||||
mock.call.when_disconnected().addCallback(c._pending_connections.discard)])
|
mock.call.when_disconnected().addCallback(c._pending_connections.discard)])
|
||||||
|
|
||||||
def test_listen(self):
|
def test_schedule_connection_relay(self):
|
||||||
c, h = make_connector(listen=True, role=roles.LEADER)
|
c, h = make_connector(listen=True, role=roles.LEADER)
|
||||||
d = Deferred()
|
hint = DirectTCPV1Hint("foo", 55, 0.0)
|
||||||
ep = mock.Mock()
|
ep = mock.Mock()
|
||||||
ep.listen = mock.Mock(return_value=d)
|
with mock.patch("wormhole._dilation.connector.endpoint_from_hint_obj",
|
||||||
|
side_effect=[ep]) as efho:
|
||||||
|
c._schedule_connection(0.0, hint, True)
|
||||||
|
self.assertEqual(efho.mock_calls, [mock.call(hint, h.tor, h.reactor)])
|
||||||
|
self.assertEqual(ep.mock_calls, [])
|
||||||
|
d = Deferred()
|
||||||
|
ep.connect = mock.Mock(side_effect=[d])
|
||||||
|
# direct hints are scheduled for T+0.0
|
||||||
f = mock.Mock()
|
f = mock.Mock()
|
||||||
with mock.patch("wormhole.ipaddrs.find_addresses",
|
with mock.patch("wormhole._dilation.connector.OutboundConnectionFactory",
|
||||||
return_value=["127.0.0.1", "1.2.3.4", "5.6.7.8"]):
|
return_value=f) as ocf:
|
||||||
with mock.patch("wormhole._dilation.connector.serverFromString",
|
h.clock.advance(1.0)
|
||||||
side_effect=[ep]):
|
handshake = build_sided_relay_handshake(h.dilation_key, h.side)
|
||||||
with mock.patch("wormhole._dilation.connector.InboundConnectionFactory",
|
self.assertEqual(ocf.mock_calls, [mock.call(c, handshake)])
|
||||||
return_value=f):
|
|
||||||
c.start()
|
|
||||||
# no relays and the listener isn't ready yet, so no hints yet
|
|
||||||
self.assertEqual(h.manager.mock_calls, [])
|
|
||||||
# but a listener was started
|
|
||||||
self.assertEqual(ep.mock_calls, [mock.call.listen(f)])
|
|
||||||
lp = mock.Mock()
|
|
||||||
host = mock.Mock()
|
|
||||||
host.port = 2345
|
|
||||||
lp.getHost = mock.Mock(return_value=host)
|
|
||||||
d.callback(lp)
|
|
||||||
self.assertEqual(h.manager.mock_calls,
|
|
||||||
[mock.call.send_hints(
|
|
||||||
[{"type": "direct-tcp-v1", "hostname": "1.2.3.4",
|
|
||||||
"port": 2345, "priority": 0.0},
|
|
||||||
{"type": "direct-tcp-v1", "hostname": "5.6.7.8",
|
|
||||||
"port": 2345, "priority": 0.0},
|
|
||||||
])])
|
|
||||||
|
|
||||||
def test_listen_but_tor(self):
|
def test_listen_but_tor(self):
|
||||||
c, h = make_connector(listen=True, tor=True, role=roles.LEADER)
|
c, h = make_connector(listen=True, tor=True, role=roles.LEADER)
|
||||||
|
@ -221,66 +278,35 @@ class TestConnector(unittest.TestCase):
|
||||||
# no relays and the listener isn't ready yet, so no hints yet
|
# no relays and the listener isn't ready yet, so no hints yet
|
||||||
self.assertEqual(h.manager.mock_calls, [])
|
self.assertEqual(h.manager.mock_calls, [])
|
||||||
|
|
||||||
def test_listen_only_loopback(self):
|
def test_no_listen(self):
|
||||||
# some test hosts, including the appveyor VMs, *only* have
|
c, h = make_connector(listen=False, tor=False, role=roles.LEADER)
|
||||||
# 127.0.0.1, and the tests will hang badly if we remove it.
|
with mock.patch("wormhole.ipaddrs.find_addresses",
|
||||||
c, h = make_connector(listen=True, role=roles.LEADER)
|
return_value=["127.0.0.1", "1.2.3.4", "5.6.7.8"]) as fa:
|
||||||
d = Deferred()
|
|
||||||
ep = mock.Mock()
|
|
||||||
ep.listen = mock.Mock(return_value=d)
|
|
||||||
f = mock.Mock()
|
|
||||||
with mock.patch("wormhole.ipaddrs.find_addresses", return_value=["127.0.0.1"]):
|
|
||||||
with mock.patch("wormhole._dilation.connector.serverFromString",
|
|
||||||
side_effect=[ep]):
|
|
||||||
with mock.patch("wormhole._dilation.connector.InboundConnectionFactory",
|
|
||||||
return_value=f):
|
|
||||||
c.start()
|
c.start()
|
||||||
# no relays and the listener isn't ready yet, so no hints yet
|
# don't even look up addresses
|
||||||
|
self.assertEqual(fa.mock_calls, [])
|
||||||
self.assertEqual(h.manager.mock_calls, [])
|
self.assertEqual(h.manager.mock_calls, [])
|
||||||
# but a listener was started
|
|
||||||
self.assertEqual(ep.mock_calls, [mock.call.listen(f)])
|
|
||||||
lp = mock.Mock()
|
|
||||||
host = mock.Mock()
|
|
||||||
host.port = 2345
|
|
||||||
lp.getHost = mock.Mock(return_value=host)
|
|
||||||
d.callback(lp)
|
|
||||||
self.assertEqual(h.manager.mock_calls,
|
|
||||||
[mock.call.send_hints(
|
|
||||||
[{"type": "direct-tcp-v1", "hostname": "127.0.0.1",
|
|
||||||
"port": 2345, "priority": 0.0},
|
|
||||||
])])
|
|
||||||
|
|
||||||
def OFFtest_relay_delay(self):
|
def test_relay_delay(self):
|
||||||
# given a direct connection and a relay, we should see the direct
|
# given a direct connection and a relay, we should see the direct
|
||||||
# connection initiated at T+0 seconds, and the relay at T+RELAY_DELAY
|
# connection initiated at T+0 seconds, and the relay at T+RELAY_DELAY
|
||||||
c, h = make_connector(listen=False, relay="tcp:foo:55", role=roles.LEADER)
|
c, h = make_connector(listen=True, relay=None, role=roles.LEADER)
|
||||||
|
c._schedule_connection = mock.Mock()
|
||||||
|
c._start_listener = mock.Mock()
|
||||||
c.start()
|
c.start()
|
||||||
hint1 = DirectTCPV1Hint("foo", 55, 0.0)
|
hint1 = DirectTCPV1Hint("foo", 55, 0.0)
|
||||||
hint2 = DirectTCPV1Hint("bar", 55, 0.0)
|
hint2 = DirectTCPV1Hint("bar", 55, 0.0)
|
||||||
hint3 = RelayV1Hint([DirectTCPV1Hint("relay", 55, 0.0)])
|
hint3 = RelayV1Hint([DirectTCPV1Hint("relay", 55, 0.0)])
|
||||||
ep1, ep2, ep3 = mock.Mock(), mock.Mock(), mock.Mock()
|
|
||||||
with mock.patch("wormhole._dilation.connector.endpoint_from_hint_obj",
|
|
||||||
side_effect=[ep1, ep2, ep3]):
|
|
||||||
c.got_hints([hint1, hint2, hint3])
|
c.got_hints([hint1, hint2, hint3])
|
||||||
self.assertEqual(ep1.mock_calls, [])
|
self.assertEqual(c._schedule_connection.mock_calls,
|
||||||
self.assertEqual(ep2.mock_calls, [])
|
[mock.call(0.0, hint1, is_relay=False),
|
||||||
self.assertEqual(ep3.mock_calls, [])
|
mock.call(0.0, hint2, is_relay=False),
|
||||||
|
mock.call(c.RELAY_DELAY, hint3.hints[0], is_relay=True),
|
||||||
h.clock.advance(c.RELAY_DELAY / 2 + 0.01)
|
])
|
||||||
self.assertEqual(len(ep1.mock_calls), 2)
|
|
||||||
self.assertEqual(len(ep2.mock_calls), 2)
|
|
||||||
self.assertEqual(ep3.mock_calls, [])
|
|
||||||
|
|
||||||
h.clock.advance(c.RELAY_DELAY)
|
|
||||||
self.assertEqual(len(ep1.mock_calls), 2)
|
|
||||||
self.assertEqual(len(ep2.mock_calls), 2)
|
|
||||||
self.assertEqual(len(ep3.mock_calls), 2)
|
|
||||||
|
|
||||||
def test_initial_relay(self):
|
def test_initial_relay(self):
|
||||||
c, h = make_connector(listen=False, relay="tcp:foo:55", role=roles.LEADER)
|
c, h = make_connector(listen=False, relay="tcp:foo:55", role=roles.LEADER)
|
||||||
ep = mock.Mock()
|
c._schedule_connection = mock.Mock()
|
||||||
with mock.patch("wormhole._dilation.connector.endpoint_from_hint_obj",
|
|
||||||
side_effect=[ep]) as efho:
|
|
||||||
c.start()
|
c.start()
|
||||||
self.assertEqual(h.manager.mock_calls,
|
self.assertEqual(h.manager.mock_calls,
|
||||||
[mock.call.send_hints([{"type": "relay-v1",
|
[mock.call.send_hints([{"type": "relay-v1",
|
||||||
|
@ -292,38 +318,143 @@ class TestConnector(unittest.TestCase):
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}])])
|
}])])
|
||||||
self.assertEqual(len(efho.mock_calls), 1)
|
self.assertEqual(c._schedule_connection.mock_calls,
|
||||||
|
[mock.call(0.0, DirectTCPV1Hint("foo", 55, 0.0),
|
||||||
|
is_relay=True)])
|
||||||
|
|
||||||
|
def test_add_relay(self):
|
||||||
|
c, h = make_connector(listen=False, relay=None, role=roles.LEADER)
|
||||||
|
c._schedule_connection = mock.Mock()
|
||||||
|
c.start()
|
||||||
|
self.assertEqual(h.manager.mock_calls, [])
|
||||||
|
self.assertEqual(c._schedule_connection.mock_calls, [])
|
||||||
|
hint = RelayV1Hint([DirectTCPV1Hint("foo", 55, 0.0)])
|
||||||
|
c.add_relay([hint])
|
||||||
|
self.assertEqual(h.manager.mock_calls,
|
||||||
|
[mock.call.send_hints([{"type": "relay-v1",
|
||||||
|
"hints": [
|
||||||
|
{"type": "direct-tcp-v1",
|
||||||
|
"hostname": "foo",
|
||||||
|
"port": 55,
|
||||||
|
"priority": 0.0
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}])])
|
||||||
|
self.assertEqual(c._schedule_connection.mock_calls,
|
||||||
|
[mock.call(0.0, DirectTCPV1Hint("foo", 55, 0.0),
|
||||||
|
is_relay=True)])
|
||||||
|
|
||||||
def test_tor_no_manager(self):
|
def test_tor_no_manager(self):
|
||||||
# tor hints should be ignored if we don't have a Tor manager to use them
|
# tor hints should be ignored if we don't have a Tor manager to use them
|
||||||
c, h = make_connector(listen=False, role=roles.LEADER)
|
c, h = make_connector(listen=False, role=roles.LEADER)
|
||||||
|
c._schedule_connection = mock.Mock()
|
||||||
c.start()
|
c.start()
|
||||||
hint = TorTCPV1Hint("foo", 55, 0.0)
|
hint = TorTCPV1Hint("foo", 55, 0.0)
|
||||||
ep = mock.Mock()
|
|
||||||
with mock.patch("wormhole._dilation.connector.endpoint_from_hint_obj",
|
|
||||||
side_effect=[ep]):
|
|
||||||
c.got_hints([hint])
|
c.got_hints([hint])
|
||||||
self.assertEqual(ep.mock_calls, [])
|
self.assertEqual(h.manager.mock_calls, [])
|
||||||
|
self.assertEqual(c._schedule_connection.mock_calls, [])
|
||||||
h.clock.advance(c.RELAY_DELAY * 2)
|
|
||||||
self.assertEqual(ep.mock_calls, [])
|
|
||||||
|
|
||||||
def test_tor_with_manager(self):
|
def test_tor_with_manager(self):
|
||||||
# tor hints should be processed if we do have a Tor manager
|
# tor hints should be processed if we do have a Tor manager
|
||||||
c, h = make_connector(listen=False, tor=True, role=roles.LEADER)
|
c, h = make_connector(listen=False, tor=True, role=roles.LEADER)
|
||||||
|
c._schedule_connection = mock.Mock()
|
||||||
c.start()
|
c.start()
|
||||||
hint = TorTCPV1Hint("foo", 55, 0.0)
|
hint = TorTCPV1Hint("foo", 55, 0.0)
|
||||||
ep = mock.Mock()
|
|
||||||
with mock.patch("wormhole._dilation.connector.endpoint_from_hint_obj",
|
|
||||||
side_effect=[ep]):
|
|
||||||
c.got_hints([hint])
|
c.got_hints([hint])
|
||||||
self.assertEqual(ep.mock_calls, [])
|
self.assertEqual(c._schedule_connection.mock_calls,
|
||||||
|
[mock.call(0.0, hint, is_relay=False)])
|
||||||
h.clock.advance(c.RELAY_DELAY * 2)
|
|
||||||
self.assertEqual(len(ep.mock_calls), 2)
|
|
||||||
|
|
||||||
|
|
||||||
def test_priorities(self):
|
def test_priorities(self):
|
||||||
# given two hints with different priorities, we should somehow prefer
|
# given two hints with different priorities, we should somehow prefer
|
||||||
# one. This is a placeholder to fill in once we implement priorities.
|
# one. This is a placeholder to fill in once we implement priorities.
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Race(unittest.TestCase):
|
||||||
|
def test_one_leader(self):
|
||||||
|
c, h = make_connector(listen=True, role=roles.LEADER)
|
||||||
|
lp = mock.Mock()
|
||||||
|
def start_listener(addresses):
|
||||||
|
c._listeners.add(lp)
|
||||||
|
c._start_listener = start_listener
|
||||||
|
c._schedule_connection = mock.Mock()
|
||||||
|
c.start()
|
||||||
|
self.assertEqual(c._listeners, set([lp]))
|
||||||
|
|
||||||
|
p1 = mock.Mock() # DilatedConnectionProtocol instance
|
||||||
|
c.add_candidate(p1)
|
||||||
|
self.assertEqual(h.manager.mock_calls, [])
|
||||||
|
h.eq.flush_sync()
|
||||||
|
self.assertEqual(h.manager.mock_calls, [mock.call.use_connection(p1)])
|
||||||
|
self.assertEqual(p1.mock_calls,
|
||||||
|
[mock.call.select(h.manager),
|
||||||
|
mock.call.send_record(KCM())])
|
||||||
|
self.assertEqual(lp.mock_calls[0], mock.call.stopListening())
|
||||||
|
# stop_listeners() uses a DeferredList, so we ignore the second call
|
||||||
|
|
||||||
|
def test_one_follower(self):
|
||||||
|
c, h = make_connector(listen=True, role=roles.FOLLOWER)
|
||||||
|
lp = mock.Mock()
|
||||||
|
def start_listener(addresses):
|
||||||
|
c._listeners.add(lp)
|
||||||
|
c._start_listener = start_listener
|
||||||
|
c._schedule_connection = mock.Mock()
|
||||||
|
c.start()
|
||||||
|
self.assertEqual(c._listeners, set([lp]))
|
||||||
|
|
||||||
|
p1 = mock.Mock() # DilatedConnectionProtocol instance
|
||||||
|
c.add_candidate(p1)
|
||||||
|
self.assertEqual(h.manager.mock_calls, [])
|
||||||
|
h.eq.flush_sync()
|
||||||
|
self.assertEqual(h.manager.mock_calls, [mock.call.use_connection(p1)])
|
||||||
|
# just like LEADER, but follower doesn't send KCM now (it sent one
|
||||||
|
# earlier, to tell the leader that this connection looks viable)
|
||||||
|
self.assertEqual(p1.mock_calls,
|
||||||
|
[mock.call.select(h.manager)])
|
||||||
|
self.assertEqual(lp.mock_calls[0], mock.call.stopListening())
|
||||||
|
# stop_listeners() uses a DeferredList, so we ignore the second call
|
||||||
|
|
||||||
|
# TODO: make sure a pending connection is abandoned when the listener
|
||||||
|
# answers successfully
|
||||||
|
|
||||||
|
# TODO: make sure a second pending connection is abandoned when the first
|
||||||
|
# connection succeeds
|
||||||
|
|
||||||
|
def test_late(self):
|
||||||
|
c, h = make_connector(listen=False, role=roles.LEADER)
|
||||||
|
c._schedule_connection = mock.Mock()
|
||||||
|
c.start()
|
||||||
|
|
||||||
|
p1 = mock.Mock() # DilatedConnectionProtocol instance
|
||||||
|
c.add_candidate(p1)
|
||||||
|
self.assertEqual(h.manager.mock_calls, [])
|
||||||
|
h.eq.flush_sync()
|
||||||
|
self.assertEqual(h.manager.mock_calls, [mock.call.use_connection(p1)])
|
||||||
|
clear_mock_calls(h.manager)
|
||||||
|
self.assertEqual(p1.mock_calls,
|
||||||
|
[mock.call.select(h.manager),
|
||||||
|
mock.call.send_record(KCM())])
|
||||||
|
|
||||||
|
# late connection is ignored
|
||||||
|
p2 = mock.Mock()
|
||||||
|
c.add_candidate(p2)
|
||||||
|
self.assertEqual(h.manager.mock_calls, [])
|
||||||
|
|
||||||
|
|
||||||
|
# make sure an established connection is dropped when stop() is called
|
||||||
|
def test_stop(self):
|
||||||
|
c, h = make_connector(listen=False, role=roles.LEADER)
|
||||||
|
c._schedule_connection = mock.Mock()
|
||||||
|
c.start()
|
||||||
|
|
||||||
|
p1 = mock.Mock() # DilatedConnectionProtocol instance
|
||||||
|
c.add_candidate(p1)
|
||||||
|
self.assertEqual(h.manager.mock_calls, [])
|
||||||
|
h.eq.flush_sync()
|
||||||
|
self.assertEqual(h.manager.mock_calls, [mock.call.use_connection(p1)])
|
||||||
|
self.assertEqual(p1.mock_calls,
|
||||||
|
[mock.call.select(h.manager),
|
||||||
|
mock.call.send_record(KCM())])
|
||||||
|
|
||||||
|
c.stop()
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user