908 lines
33 KiB
Python
908 lines
33 KiB
Python
from __future__ import print_function
|
|
import json, itertools
|
|
from binascii import hexlify
|
|
from twisted.trial import unittest
|
|
from twisted.internet import protocol, reactor, defer
|
|
from twisted.internet.defer import inlineCallbacks, returnValue
|
|
from twisted.internet.endpoints import clientFromString, connectProtocol
|
|
from twisted.web.client import getPage, Agent, readBody
|
|
from autobahn.twisted import websocket
|
|
from .. import __version__
|
|
from .common import ServerBase
|
|
from ..server import rendezvous, transit_server
|
|
from ..server.rendezvous import Usage, SidedMessage
|
|
|
|
class Reachable(ServerBase, unittest.TestCase):
|
|
|
|
def test_getPage(self):
|
|
# client.getPage requires bytes URL, returns bytes
|
|
url = self.relayurl.replace("wormhole-relay/", "").encode("ascii")
|
|
d = getPage(url)
|
|
def _got(res):
|
|
self.failUnlessEqual(res, b"Wormhole Relay\n")
|
|
d.addCallback(_got)
|
|
return d
|
|
|
|
def test_agent(self):
|
|
url = self.relayurl.replace("wormhole-relay/", "").encode("ascii")
|
|
agent = Agent(reactor)
|
|
d = agent.request(b"GET", url)
|
|
def _check(resp):
|
|
self.failUnlessEqual(resp.code, 200)
|
|
return readBody(resp)
|
|
d.addCallback(_check)
|
|
def _got(res):
|
|
self.failUnlessEqual(res, b"Wormhole Relay\n")
|
|
d.addCallback(_got)
|
|
return d
|
|
|
|
class Server(ServerBase, unittest.TestCase):
|
|
def test_apps(self):
|
|
app1 = self._rendezvous.get_app(u"appid1")
|
|
self.assertIdentical(app1, self._rendezvous.get_app(u"appid1"))
|
|
app2 = self._rendezvous.get_app(u"appid2")
|
|
self.assertNotIdentical(app1, app2)
|
|
|
|
def test_nameplate_allocation(self):
|
|
app = self._rendezvous.get_app(u"appid")
|
|
nids = set()
|
|
# this takes a second, and claims all the short-numbered nameplates
|
|
def add():
|
|
nameplate_id = app.allocate_nameplate(u"side1", 0)
|
|
self.assertEqual(type(nameplate_id), type(u""))
|
|
nid = int(nameplate_id)
|
|
nids.add(nid)
|
|
for i in range(9): add()
|
|
self.assertNotIn(0, nids)
|
|
self.assertEqual(set(range(1,10)), nids)
|
|
|
|
for i in range(100-10): add()
|
|
self.assertEqual(len(nids), 99)
|
|
self.assertEqual(set(range(1,100)), nids)
|
|
|
|
for i in range(1000-100): add()
|
|
self.assertEqual(len(nids), 999)
|
|
self.assertEqual(set(range(1,1000)), nids)
|
|
|
|
add()
|
|
self.assertEqual(len(nids), 1000)
|
|
biggest = max(nids)
|
|
self.assert_(1000 <= biggest < 1000000, biggest)
|
|
|
|
def _nameplate(self, app, nameplate_id):
|
|
return app._db.execute("SELECT * FROM `nameplates`"
|
|
" WHERE `app_id`='appid' AND `id`=?",
|
|
(nameplate_id,)).fetchone()
|
|
|
|
def test_nameplate(self):
|
|
app = self._rendezvous.get_app(u"appid")
|
|
nameplate_id = app.allocate_nameplate(u"side1", 0)
|
|
self.assertEqual(type(nameplate_id), type(u""))
|
|
nid = int(nameplate_id)
|
|
self.assert_(0 < nid < 10, nid)
|
|
self.assertEqual(app.get_nameplate_ids(), set([nameplate_id]))
|
|
# allocate also does a claim
|
|
row = self._nameplate(app, nameplate_id)
|
|
self.assertEqual(row["side1"], u"side1")
|
|
self.assertEqual(row["side2"], None)
|
|
self.assertEqual(row["crowded"], False)
|
|
self.assertEqual(row["started"], 0)
|
|
self.assertEqual(row["second"], None)
|
|
|
|
mailbox_id = app.claim_nameplate(nameplate_id, u"side1", 1)
|
|
self.assertEqual(type(mailbox_id), type(u""))
|
|
# duplicate claims by the same side are combined
|
|
row = self._nameplate(app, nameplate_id)
|
|
self.assertEqual(row["side1"], u"side1")
|
|
self.assertEqual(row["side2"], None)
|
|
|
|
mailbox_id2 = app.claim_nameplate(nameplate_id, u"side1", 2)
|
|
self.assertEqual(mailbox_id, mailbox_id2)
|
|
row = self._nameplate(app, nameplate_id)
|
|
self.assertEqual(row["side1"], u"side1")
|
|
self.assertEqual(row["side2"], None)
|
|
self.assertEqual(row["started"], 0)
|
|
self.assertEqual(row["second"], None)
|
|
|
|
# claim by the second side is new
|
|
mailbox_id3 = app.claim_nameplate(nameplate_id, u"side2", 3)
|
|
self.assertEqual(mailbox_id, mailbox_id3)
|
|
row = self._nameplate(app, nameplate_id)
|
|
self.assertEqual(row["side1"], u"side1")
|
|
self.assertEqual(row["side2"], u"side2")
|
|
self.assertEqual(row["crowded"], False)
|
|
self.assertEqual(row["started"], 0)
|
|
self.assertEqual(row["second"], 3)
|
|
|
|
# a third claim marks the nameplate as "crowded", but leaves the two
|
|
# existing claims alone
|
|
self.assertRaises(rendezvous.CrowdedError,
|
|
app.claim_nameplate, nameplate_id, u"side3", 0)
|
|
row = self._nameplate(app, nameplate_id)
|
|
self.assertEqual(row["side1"], u"side1")
|
|
self.assertEqual(row["side2"], u"side2")
|
|
self.assertEqual(row["crowded"], True)
|
|
|
|
# releasing a non-existent nameplate is ignored
|
|
app.release_nameplate(nameplate_id+u"not", u"side4", 0)
|
|
|
|
# releasing a side that never claimed the nameplate is ignored
|
|
app.release_nameplate(nameplate_id, u"side4", 0)
|
|
row = self._nameplate(app, nameplate_id)
|
|
self.assertEqual(row["side1"], u"side1")
|
|
self.assertEqual(row["side2"], u"side2")
|
|
|
|
# releasing one side leaves the second claim
|
|
app.release_nameplate(nameplate_id, u"side1", 5)
|
|
row = self._nameplate(app, nameplate_id)
|
|
self.assertEqual(row["side1"], u"side2")
|
|
self.assertEqual(row["side2"], None)
|
|
|
|
# releasing one side multiple times is ignored
|
|
app.release_nameplate(nameplate_id, u"side1", 5)
|
|
row = self._nameplate(app, nameplate_id)
|
|
self.assertEqual(row["side1"], u"side2")
|
|
self.assertEqual(row["side2"], None)
|
|
|
|
# releasing the second side frees the nameplate, and adds usage
|
|
app.release_nameplate(nameplate_id, u"side2", 6)
|
|
row = self._nameplate(app, nameplate_id)
|
|
self.assertEqual(row, None)
|
|
usage = app._db.execute("SELECT * FROM `nameplate_usage`").fetchone()
|
|
self.assertEqual(usage["app_id"], u"appid")
|
|
self.assertEqual(usage["started"], 0)
|
|
self.assertEqual(usage["waiting_time"], 3)
|
|
self.assertEqual(usage["total_time"], 6)
|
|
self.assertEqual(usage["result"], u"crowded")
|
|
|
|
|
|
def _mailbox(self, app, mailbox_id):
|
|
return app._db.execute("SELECT * FROM `mailboxes`"
|
|
" WHERE `app_id`='appid' AND `id`=?",
|
|
(mailbox_id,)).fetchone()
|
|
|
|
def test_mailbox(self):
|
|
app = self._rendezvous.get_app(u"appid")
|
|
mailbox_id = u"mid"
|
|
m1 = app.open_mailbox(mailbox_id, u"side1", 0)
|
|
|
|
row = self._mailbox(app, mailbox_id)
|
|
self.assertEqual(row["side1"], u"side1")
|
|
self.assertEqual(row["side2"], None)
|
|
self.assertEqual(row["crowded"], False)
|
|
self.assertEqual(row["started"], 0)
|
|
self.assertEqual(row["second"], None)
|
|
|
|
# opening the same mailbox twice, by the same side, gets the same
|
|
# object
|
|
self.assertIdentical(m1, app.open_mailbox(mailbox_id, u"side1", 1))
|
|
row = self._mailbox(app, mailbox_id)
|
|
self.assertEqual(row["side1"], u"side1")
|
|
self.assertEqual(row["side2"], None)
|
|
self.assertEqual(row["crowded"], False)
|
|
self.assertEqual(row["started"], 0)
|
|
self.assertEqual(row["second"], None)
|
|
|
|
# opening a second side gets the same object, and adds a new claim
|
|
self.assertIdentical(m1, app.open_mailbox(mailbox_id, u"side2", 2))
|
|
row = self._mailbox(app, mailbox_id)
|
|
self.assertEqual(row["side1"], u"side1")
|
|
self.assertEqual(row["side2"], u"side2")
|
|
self.assertEqual(row["crowded"], False)
|
|
self.assertEqual(row["started"], 0)
|
|
self.assertEqual(row["second"], 2)
|
|
|
|
# a third open marks it as crowded
|
|
self.assertRaises(rendezvous.CrowdedError,
|
|
app.open_mailbox, mailbox_id, u"side3", 3)
|
|
row = self._mailbox(app, mailbox_id)
|
|
self.assertEqual(row["side1"], u"side1")
|
|
self.assertEqual(row["side2"], u"side2")
|
|
self.assertEqual(row["crowded"], True)
|
|
self.assertEqual(row["started"], 0)
|
|
self.assertEqual(row["second"], 2)
|
|
|
|
# closing a side that never claimed the mailbox is ignored
|
|
m1.close(u"side4", u"mood", 4)
|
|
row = self._mailbox(app, mailbox_id)
|
|
self.assertEqual(row["side1"], u"side1")
|
|
self.assertEqual(row["side2"], u"side2")
|
|
self.assertEqual(row["crowded"], True)
|
|
self.assertEqual(row["started"], 0)
|
|
self.assertEqual(row["second"], 2)
|
|
|
|
# closing one side leaves the second claim
|
|
m1.close(u"side1", u"mood", 5)
|
|
row = self._mailbox(app, mailbox_id)
|
|
self.assertEqual(row["side1"], u"side2")
|
|
self.assertEqual(row["side2"], None)
|
|
self.assertEqual(row["crowded"], True)
|
|
self.assertEqual(row["started"], 0)
|
|
self.assertEqual(row["second"], 2)
|
|
|
|
# closing one side multiple is ignored
|
|
m1.close(u"side1", u"mood", 6)
|
|
row = self._mailbox(app, mailbox_id)
|
|
self.assertEqual(row["side1"], u"side2")
|
|
self.assertEqual(row["side2"], None)
|
|
self.assertEqual(row["crowded"], True)
|
|
self.assertEqual(row["started"], 0)
|
|
self.assertEqual(row["second"], 2)
|
|
|
|
l1 = []; stop1 = []; stop1_f = lambda: stop1.append(True)
|
|
m1.add_listener("handle1", l1.append, stop1_f)
|
|
|
|
# closing the second side frees the mailbox, and adds usage
|
|
m1.close(u"side2", u"mood", 7)
|
|
self.assertEqual(stop1, [True])
|
|
|
|
row = self._mailbox(app, mailbox_id)
|
|
self.assertEqual(row, None)
|
|
usage = app._db.execute("SELECT * FROM `mailbox_usage`").fetchone()
|
|
self.assertEqual(usage["app_id"], u"appid")
|
|
self.assertEqual(usage["started"], 0)
|
|
self.assertEqual(usage["waiting_time"], 2)
|
|
self.assertEqual(usage["total_time"], 7)
|
|
self.assertEqual(usage["result"], u"crowded")
|
|
|
|
def _messages(self, app):
|
|
c = app._db.execute("SELECT * FROM `messages`"
|
|
" WHERE `app_id`='appid' AND `mailbox_id`='mid'")
|
|
return c.fetchall()
|
|
|
|
def test_messages(self):
|
|
app = self._rendezvous.get_app(u"appid")
|
|
mailbox_id = u"mid"
|
|
m1 = app.open_mailbox(mailbox_id, u"side1", 0)
|
|
m1.add_message(SidedMessage(side=u"side1", phase=u"phase",
|
|
body=u"body", server_rx=1,
|
|
msg_id=u"msgid"))
|
|
msgs = self._messages(app)
|
|
self.assertEqual(len(msgs), 1)
|
|
self.assertEqual(msgs[0]["body"], u"body")
|
|
|
|
l1 = []; stop1 = []; stop1_f = lambda: stop1.append(True)
|
|
l2 = []; stop2 = []; stop2_f = lambda: stop2.append(True)
|
|
old = m1.add_listener("handle1", l1.append, stop1_f)
|
|
self.assertEqual(len(old), 1)
|
|
self.assertEqual(old[0].side, u"side1")
|
|
self.assertEqual(old[0].body, u"body")
|
|
|
|
m1.add_message(SidedMessage(side=u"side1", phase=u"phase2",
|
|
body=u"body2", server_rx=1,
|
|
msg_id=u"msgid"))
|
|
self.assertEqual(len(l1), 1)
|
|
self.assertEqual(l1[0].body, u"body2")
|
|
old = m1.add_listener("handle2", l2.append, stop2_f)
|
|
self.assertEqual(len(old), 2)
|
|
|
|
m1.add_message(SidedMessage(side=u"side1", phase=u"phase3",
|
|
body=u"body3", server_rx=1,
|
|
msg_id=u"msgid"))
|
|
self.assertEqual(len(l1), 2)
|
|
self.assertEqual(l1[-1].body, u"body3")
|
|
self.assertEqual(len(l2), 1)
|
|
self.assertEqual(l2[-1].body, u"body3")
|
|
|
|
m1.remove_listener("handle1")
|
|
|
|
m1.add_message(SidedMessage(side=u"side1", phase=u"phase4",
|
|
body=u"body4", server_rx=1,
|
|
msg_id=u"msgid"))
|
|
self.assertEqual(len(l1), 2)
|
|
self.assertEqual(l1[-1].body, u"body3")
|
|
self.assertEqual(len(l2), 2)
|
|
self.assertEqual(l2[-1].body, u"body4")
|
|
|
|
m1._shutdown()
|
|
self.assertEqual(stop1, [])
|
|
self.assertEqual(stop2, [True])
|
|
|
|
# message adds are not idempotent: clients filter duplicates
|
|
m1.add_message(SidedMessage(side=u"side1", phase=u"phase",
|
|
body=u"body", server_rx=1,
|
|
msg_id=u"msgid"))
|
|
msgs = self._messages(app)
|
|
self.assertEqual(len(msgs), 5)
|
|
self.assertEqual(msgs[-1]["body"], u"body")
|
|
|
|
|
|
def strip_message(msg):
|
|
m2 = msg.copy()
|
|
m2.pop("id", None)
|
|
m2.pop("server_rx", None)
|
|
return m2
|
|
|
|
def strip_messages(messages):
|
|
return [strip_message(m) for m in messages]
|
|
|
|
class WSClient(websocket.WebSocketClientProtocol):
|
|
def __init__(self):
|
|
websocket.WebSocketClientProtocol.__init__(self)
|
|
self.events = []
|
|
self.errors = []
|
|
self.d = None
|
|
self.ping_counter = itertools.count(0)
|
|
def onOpen(self):
|
|
self.factory.d.callback(self)
|
|
def onMessage(self, payload, isBinary):
|
|
assert not isBinary
|
|
event = json.loads(payload.decode("utf-8"))
|
|
if event["type"] == "error":
|
|
self.errors.append(event)
|
|
if self.d:
|
|
assert not self.events
|
|
d,self.d = self.d,None
|
|
d.callback(event)
|
|
return
|
|
self.events.append(event)
|
|
|
|
def next_event(self):
|
|
assert not self.d
|
|
if self.events:
|
|
event = self.events.pop(0)
|
|
return defer.succeed(event)
|
|
self.d = defer.Deferred()
|
|
return self.d
|
|
|
|
@inlineCallbacks
|
|
def next_non_ack(self):
|
|
while True:
|
|
m = yield self.next_event()
|
|
if m["type"] != "ack":
|
|
returnValue(m)
|
|
|
|
def strip_acks(self):
|
|
self.events = [e for e in self.events if e["type"] != u"ack"]
|
|
|
|
def send(self, mtype, **kwargs):
|
|
kwargs["type"] = mtype
|
|
payload = json.dumps(kwargs).encode("utf-8")
|
|
self.sendMessage(payload, False)
|
|
|
|
def send_notype(self, **kwargs):
|
|
payload = json.dumps(kwargs).encode("utf-8")
|
|
self.sendMessage(payload, False)
|
|
|
|
@inlineCallbacks
|
|
def sync(self):
|
|
ping = next(self.ping_counter)
|
|
self.send("ping", ping=ping)
|
|
# queue all messages until the pong, then put them back
|
|
old_events = []
|
|
while True:
|
|
ev = yield self.next_event()
|
|
if ev["type"] == "pong" and ev["pong"] == ping:
|
|
self.events = old_events + self.events
|
|
returnValue(None)
|
|
old_events.append(ev)
|
|
|
|
class WSFactory(websocket.WebSocketClientFactory):
|
|
protocol = WSClient
|
|
|
|
class WSClientSync(unittest.TestCase):
|
|
# make sure my 'sync' method actually works
|
|
|
|
@inlineCallbacks
|
|
def test_sync(self):
|
|
sent = []
|
|
c = WSClient()
|
|
def _send(mtype, **kwargs):
|
|
sent.append( (mtype, kwargs) )
|
|
c.send = _send
|
|
def add(mtype, **kwargs):
|
|
kwargs["type"] = mtype
|
|
c.onMessage(json.dumps(kwargs).encode("utf-8"), False)
|
|
# no queued messages
|
|
sunc = []
|
|
d = c.sync()
|
|
d.addBoth(sunc.append)
|
|
self.assertEqual(sent, [("ping", {"ping": 0})])
|
|
self.assertEqual(sunc, [])
|
|
add("pong", pong=0)
|
|
yield d
|
|
self.assertEqual(c.events, [])
|
|
|
|
# one,two,ping,pong
|
|
add("one")
|
|
add("two", two=2)
|
|
sunc = []
|
|
d = c.sync()
|
|
d.addBoth(sunc.append)
|
|
add("pong", pong=1)
|
|
yield d
|
|
m = yield c.next_non_ack()
|
|
self.assertEqual(m["type"], "one")
|
|
m = yield c.next_non_ack()
|
|
self.assertEqual(m["type"], "two")
|
|
self.assertEqual(c.events, [])
|
|
|
|
# one,ping,two,pong
|
|
add("one")
|
|
sunc = []
|
|
d = c.sync()
|
|
d.addBoth(sunc.append)
|
|
add("two", two=2)
|
|
add("pong", pong=2)
|
|
yield d
|
|
m = yield c.next_non_ack()
|
|
self.assertEqual(m["type"], "one")
|
|
m = yield c.next_non_ack()
|
|
self.assertEqual(m["type"], "two")
|
|
self.assertEqual(c.events, [])
|
|
|
|
# ping,one,two,pong
|
|
sunc = []
|
|
d = c.sync()
|
|
d.addBoth(sunc.append)
|
|
add("one")
|
|
add("two", two=2)
|
|
add("pong", pong=3)
|
|
yield d
|
|
m = yield c.next_non_ack()
|
|
self.assertEqual(m["type"], "one")
|
|
m = yield c.next_non_ack()
|
|
self.assertEqual(m["type"], "two")
|
|
self.assertEqual(c.events, [])
|
|
|
|
|
|
|
|
class WebSocketAPI(ServerBase, unittest.TestCase):
|
|
def setUp(self):
|
|
self._clients = []
|
|
return ServerBase.setUp(self)
|
|
|
|
def tearDown(self):
|
|
for c in self._clients:
|
|
c.transport.loseConnection()
|
|
return ServerBase.tearDown(self)
|
|
|
|
@inlineCallbacks
|
|
def make_client(self):
|
|
f = WSFactory(self.rdv_ws_url)
|
|
f.d = defer.Deferred()
|
|
reactor.connectTCP("127.0.0.1", self.rdv_ws_port, f)
|
|
c = yield f.d
|
|
self._clients.append(c)
|
|
returnValue(c)
|
|
|
|
def check_welcome(self, data):
|
|
self.failUnlessIn("welcome", data)
|
|
self.failUnlessEqual(data["welcome"], {"current_version": __version__})
|
|
|
|
@inlineCallbacks
|
|
def test_welcome(self):
|
|
c1 = yield self.make_client()
|
|
msg = yield c1.next_non_ack()
|
|
self.check_welcome(msg)
|
|
self.assertEqual(self._rendezvous._apps, {})
|
|
|
|
@inlineCallbacks
|
|
def test_bind(self):
|
|
c1 = yield self.make_client()
|
|
yield c1.next_non_ack()
|
|
|
|
c1.send(u"bind", appid=u"appid") # missing side=
|
|
err = yield c1.next_non_ack()
|
|
self.assertEqual(err[u"type"], u"error")
|
|
self.assertEqual(err[u"error"], u"bind requires 'side'")
|
|
|
|
c1.send(u"bind", side=u"side") # missing appid=
|
|
err = yield c1.next_non_ack()
|
|
self.assertEqual(err[u"type"], u"error")
|
|
self.assertEqual(err[u"error"], u"bind requires 'appid'")
|
|
|
|
c1.send(u"bind", appid=u"appid", side=u"side")
|
|
yield c1.sync()
|
|
self.assertEqual(list(self._rendezvous._apps.keys()), [u"appid"])
|
|
|
|
c1.send(u"bind", appid=u"appid", side=u"side") # duplicate
|
|
err = yield c1.next_non_ack()
|
|
self.assertEqual(err[u"type"], u"error")
|
|
self.assertEqual(err[u"error"], u"already bound")
|
|
|
|
c1.send_notype(other="misc") # missing 'type'
|
|
err = yield c1.next_non_ack()
|
|
self.assertEqual(err[u"type"], u"error")
|
|
self.assertEqual(err[u"error"], u"missing 'type'")
|
|
|
|
c1.send("___unknown") # unknown type
|
|
err = yield c1.next_non_ack()
|
|
self.assertEqual(err[u"type"], u"error")
|
|
self.assertEqual(err[u"error"], u"unknown type")
|
|
|
|
c1.send("ping") # missing 'ping'
|
|
err = yield c1.next_non_ack()
|
|
self.assertEqual(err[u"type"], u"error")
|
|
self.assertEqual(err[u"error"], u"ping requires 'ping'")
|
|
|
|
@inlineCallbacks
|
|
def test_list(self):
|
|
c1 = yield self.make_client()
|
|
yield c1.next_non_ack()
|
|
|
|
c1.send(u"list") # too early, must bind first
|
|
err = yield c1.next_non_ack()
|
|
self.assertEqual(err[u"type"], u"error")
|
|
self.assertEqual(err[u"error"], u"must bind first")
|
|
|
|
c1.send(u"bind", appid=u"appid", side=u"side")
|
|
c1.send(u"list")
|
|
m = yield c1.next_non_ack()
|
|
self.assertEqual(m[u"type"], u"nameplates")
|
|
self.assertEqual(m[u"nameplates"], [])
|
|
|
|
app = self._rendezvous.get_app(u"appid")
|
|
nameplate_id1 = app.allocate_nameplate(u"side", 0)
|
|
app.claim_nameplate(u"np2", u"side", 0)
|
|
|
|
c1.send(u"list")
|
|
m = yield c1.next_non_ack()
|
|
self.assertEqual(m[u"type"], u"nameplates")
|
|
nids = set()
|
|
for n in m[u"nameplates"]:
|
|
self.assertEqual(type(n), dict)
|
|
self.assertEqual(list(n.keys()), [u"id"])
|
|
nids.add(n[u"id"])
|
|
self.assertEqual(nids, set([nameplate_id1, u"np2"]))
|
|
|
|
@inlineCallbacks
|
|
def test_allocate(self):
|
|
c1 = yield self.make_client()
|
|
yield c1.next_non_ack()
|
|
|
|
c1.send(u"allocate") # too early, must bind first
|
|
err = yield c1.next_non_ack()
|
|
self.assertEqual(err[u"type"], u"error")
|
|
self.assertEqual(err[u"error"], u"must bind first")
|
|
|
|
c1.send(u"bind", appid=u"appid", side=u"side")
|
|
app = self._rendezvous.get_app(u"appid")
|
|
c1.send(u"allocate")
|
|
m = yield c1.next_non_ack()
|
|
self.assertEqual(m[u"type"], u"allocated")
|
|
nameplate_id = m[u"nameplate"]
|
|
|
|
nids = app.get_nameplate_ids()
|
|
self.assertEqual(len(nids), 1)
|
|
self.assertEqual(nameplate_id, list(nids)[0])
|
|
|
|
c1.send(u"allocate")
|
|
err = yield c1.next_non_ack()
|
|
self.assertEqual(err[u"type"], u"error")
|
|
self.assertEqual(err[u"error"],
|
|
u"you already allocated one, don't be greedy")
|
|
|
|
c1.send(u"claim", nameplate=nameplate_id) # allocate+claim is ok
|
|
yield c1.sync()
|
|
row = app._db.execute("SELECT * FROM `nameplates`"
|
|
" WHERE `app_id`='appid' AND `id`=?",
|
|
(nameplate_id,)).fetchone()
|
|
self.assertEqual(row["side1"], u"side")
|
|
self.assertEqual(row["side2"], None)
|
|
|
|
@inlineCallbacks
|
|
def test_claim(self):
|
|
c1 = yield self.make_client()
|
|
yield c1.next_non_ack()
|
|
c1.send(u"bind", appid=u"appid", side=u"side")
|
|
app = self._rendezvous.get_app(u"appid")
|
|
|
|
c1.send(u"claim") # missing nameplate=
|
|
err = yield c1.next_non_ack()
|
|
self.assertEqual(err[u"type"], u"error")
|
|
self.assertEqual(err[u"error"], u"claim requires 'nameplate'")
|
|
|
|
c1.send(u"claim", nameplate=u"np1")
|
|
m = yield c1.next_non_ack()
|
|
self.assertEqual(m[u"type"], u"claimed")
|
|
mailbox_id = m[u"mailbox"]
|
|
self.assertEqual(type(mailbox_id), type(u""))
|
|
|
|
nids = app.get_nameplate_ids()
|
|
self.assertEqual(len(nids), 1)
|
|
self.assertEqual(u"np1", list(nids)[0])
|
|
|
|
# claiming a nameplate will assign a random mailbox id, but won't
|
|
# create the mailbox itself
|
|
mailboxes = app._db.execute("SELECT * FROM `mailboxes`"
|
|
" WHERE `app_id`='appid'").fetchall()
|
|
self.assertEqual(len(mailboxes), 0)
|
|
|
|
@inlineCallbacks
|
|
def test_claim_crowded(self):
|
|
c1 = yield self.make_client()
|
|
yield c1.next_non_ack()
|
|
c1.send(u"bind", appid=u"appid", side=u"side")
|
|
app = self._rendezvous.get_app(u"appid")
|
|
|
|
app.claim_nameplate(u"np1", u"side1", 0)
|
|
app.claim_nameplate(u"np1", u"side2", 0)
|
|
|
|
# the third claim will signal crowding
|
|
c1.send(u"claim", nameplate=u"np1")
|
|
err = yield c1.next_non_ack()
|
|
self.assertEqual(err[u"type"], u"error")
|
|
self.assertEqual(err[u"error"], u"crowded")
|
|
|
|
@inlineCallbacks
|
|
def test_release(self):
|
|
c1 = yield self.make_client()
|
|
yield c1.next_non_ack()
|
|
c1.send(u"bind", appid=u"appid", side=u"side")
|
|
app = self._rendezvous.get_app(u"appid")
|
|
|
|
app.claim_nameplate(u"np1", u"side2", 0)
|
|
|
|
c1.send(u"release") # didn't do claim first
|
|
err = yield c1.next_non_ack()
|
|
self.assertEqual(err[u"type"], u"error")
|
|
self.assertEqual(err[u"error"],
|
|
u"must claim a nameplate before releasing it")
|
|
|
|
c1.send(u"claim", nameplate=u"np1")
|
|
yield c1.next_non_ack()
|
|
|
|
c1.send(u"release")
|
|
yield c1.sync()
|
|
|
|
row = app._db.execute("SELECT * FROM `nameplates`"
|
|
" WHERE `app_id`='appid' AND `id`='np1'").fetchone()
|
|
self.assertEqual(row["side1"], u"side2")
|
|
self.assertEqual(row["side2"], None)
|
|
|
|
c1.send(u"release") # no longer claimed
|
|
err = yield c1.next_non_ack()
|
|
self.assertEqual(err[u"type"], u"error")
|
|
self.assertEqual(err[u"error"],
|
|
u"must claim a nameplate before releasing it")
|
|
|
|
@inlineCallbacks
|
|
def test_open(self):
|
|
c1 = yield self.make_client()
|
|
yield c1.next_non_ack()
|
|
c1.send(u"bind", appid=u"appid", side=u"side")
|
|
app = self._rendezvous.get_app(u"appid")
|
|
|
|
c1.send(u"open") # missing mailbox=
|
|
err = yield c1.next_non_ack()
|
|
self.assertEqual(err[u"type"], u"error")
|
|
self.assertEqual(err[u"error"], u"open requires 'mailbox'")
|
|
|
|
mb1 = app.open_mailbox(u"mb1", u"side2", 0)
|
|
mb1.add_message(SidedMessage(side=u"side2", phase=u"phase",
|
|
body=u"body", server_rx=0,
|
|
msg_id=u"msgid"))
|
|
|
|
c1.send(u"open", mailbox=u"mb1")
|
|
m = yield c1.next_non_ack()
|
|
self.assertEqual(m[u"type"], u"message")
|
|
self.assertEqual(m[u"body"], u"body")
|
|
|
|
mb1.add_message(SidedMessage(side=u"side2", phase=u"phase2",
|
|
body=u"body2", server_rx=0,
|
|
msg_id=u"msgid"))
|
|
m = yield c1.next_non_ack()
|
|
self.assertEqual(m[u"type"], u"message")
|
|
self.assertEqual(m[u"body"], u"body2")
|
|
|
|
c1.send(u"open", mailbox=u"mb1")
|
|
err = yield c1.next_non_ack()
|
|
self.assertEqual(err[u"type"], u"error")
|
|
self.assertEqual(err[u"error"], u"you already have a mailbox open")
|
|
|
|
@inlineCallbacks
|
|
def test_add(self):
|
|
c1 = yield self.make_client()
|
|
yield c1.next_non_ack()
|
|
c1.send(u"bind", appid=u"appid", side=u"side")
|
|
app = self._rendezvous.get_app(u"appid")
|
|
mb1 = app.open_mailbox(u"mb1", u"side2", 0)
|
|
l1 = []; stop1 = []; stop1_f = lambda: stop1.append(True)
|
|
mb1.add_listener("handle1", l1.append, stop1_f)
|
|
|
|
c1.send(u"add") # didn't open first
|
|
err = yield c1.next_non_ack()
|
|
self.assertEqual(err[u"type"], u"error")
|
|
self.assertEqual(err[u"error"], u"must open mailbox before adding")
|
|
|
|
c1.send(u"open", mailbox=u"mb1")
|
|
|
|
c1.send(u"add", body=u"body") # missing phase=
|
|
err = yield c1.next_non_ack()
|
|
self.assertEqual(err[u"type"], u"error")
|
|
self.assertEqual(err[u"error"], u"missing 'phase'")
|
|
|
|
c1.send(u"add", phase=u"phase") # missing body=
|
|
err = yield c1.next_non_ack()
|
|
self.assertEqual(err[u"type"], u"error")
|
|
self.assertEqual(err[u"error"], u"missing 'body'")
|
|
|
|
c1.send(u"add", phase=u"phase", body=u"body")
|
|
m = yield c1.next_non_ack() # echoed back
|
|
self.assertEqual(m[u"type"], u"message")
|
|
self.assertEqual(m[u"body"], u"body")
|
|
|
|
self.assertEqual(len(l1), 1)
|
|
self.assertEqual(l1[0].body, u"body")
|
|
|
|
@inlineCallbacks
|
|
def test_close(self):
|
|
c1 = yield self.make_client()
|
|
yield c1.next_non_ack()
|
|
c1.send(u"bind", appid=u"appid", side=u"side")
|
|
|
|
c1.send(u"close", mood=u"mood") # must open first
|
|
err = yield c1.next_non_ack()
|
|
self.assertEqual(err[u"type"], u"error")
|
|
self.assertEqual(err[u"error"], u"must open mailbox before closing")
|
|
|
|
c1.send(u"open", mailbox=u"mb1")
|
|
c1.send(u"close", mood=u"mood")
|
|
m = yield c1.next_non_ack()
|
|
self.assertEqual(m[u"type"], u"closed")
|
|
|
|
c1.send(u"close", mood=u"mood") # already closed
|
|
err = yield c1.next_non_ack()
|
|
self.assertEqual(err[u"type"], u"error")
|
|
self.assertEqual(err[u"error"], u"must open mailbox before closing")
|
|
|
|
|
|
class Summary(unittest.TestCase):
|
|
def test_mailbox(self):
|
|
c = rendezvous.Mailbox(None, None, None, False, None, None)
|
|
# starts at time 1, maybe gets second open at time 3, closes at 5
|
|
base_row = {u"started": 1, u"second": None,
|
|
u"first_mood": None, u"crowded": False}
|
|
def summ(num_sides, second_mood=None, pruned=False, **kwargs):
|
|
row = base_row.copy()
|
|
row.update(kwargs)
|
|
return c._summarize(row, num_sides, second_mood, 5, pruned)
|
|
|
|
self.assertEqual(summ(1), Usage(1, None, 4, u"lonely"))
|
|
self.assertEqual(summ(1, u"lonely"), Usage(1, None, 4, u"lonely"))
|
|
self.assertEqual(summ(1, u"errory"), Usage(1, None, 4, u"errory"))
|
|
self.assertEqual(summ(1, crowded=True), Usage(1, None, 4, u"crowded"))
|
|
|
|
self.assertEqual(summ(2, first_mood=u"happy",
|
|
second=3, second_mood=u"happy"),
|
|
Usage(1, 2, 4, u"happy"))
|
|
|
|
self.assertEqual(summ(2, first_mood=u"errory",
|
|
second=3, second_mood=u"happy"),
|
|
Usage(1, 2, 4, u"errory"))
|
|
|
|
self.assertEqual(summ(2, first_mood=u"happy",
|
|
second=3, second_mood=u"errory"),
|
|
Usage(1, 2, 4, u"errory"))
|
|
|
|
self.assertEqual(summ(2, first_mood=u"scary",
|
|
second=3, second_mood=u"happy"),
|
|
Usage(1, 2, 4, u"scary"))
|
|
|
|
self.assertEqual(summ(2, first_mood=u"scary",
|
|
second=3, second_mood=u"errory"),
|
|
Usage(1, 2, 4, u"scary"))
|
|
|
|
self.assertEqual(summ(2, first_mood=u"happy", second=3, pruned=True),
|
|
Usage(1, 2, 4, u"pruney"))
|
|
|
|
def test_nameplate(self):
|
|
a = rendezvous.AppNamespace(None, None, None, False, None)
|
|
# starts at time 1, maybe gets second open at time 3, closes at 5
|
|
base_row = {u"started": 1, u"second": None, u"crowded": False}
|
|
def summ(num_sides, pruned=False, **kwargs):
|
|
row = base_row.copy()
|
|
row.update(kwargs)
|
|
return a._summarize_nameplate_usage(row, 5, pruned)
|
|
|
|
self.assertEqual(summ(1), Usage(1, None, 4, u"lonely"))
|
|
self.assertEqual(summ(1, crowded=True), Usage(1, None, 4, u"crowded"))
|
|
|
|
self.assertEqual(summ(2, second=3), Usage(1, 2, 4, u"happy"))
|
|
|
|
self.assertEqual(summ(2, second=3, pruned=True),
|
|
Usage(1, 2, 4, u"pruney"))
|
|
|
|
class Accumulator(protocol.Protocol):
|
|
def __init__(self):
|
|
self.data = b""
|
|
self.count = 0
|
|
self._wait = None
|
|
def waitForBytes(self, more):
|
|
assert self._wait is None
|
|
self.count = more
|
|
self._wait = defer.Deferred()
|
|
self._check_done()
|
|
return self._wait
|
|
def dataReceived(self, data):
|
|
self.data = self.data + data
|
|
self._check_done()
|
|
def _check_done(self):
|
|
if self._wait and len(self.data) >= self.count:
|
|
d = self._wait
|
|
self._wait = None
|
|
d.callback(self)
|
|
def connectionLost(self, why):
|
|
if self._wait:
|
|
self._wait.errback(RuntimeError("closed"))
|
|
|
|
class Transit(ServerBase, unittest.TestCase):
|
|
def test_blur_size(self):
|
|
blur = transit_server.blur_size
|
|
self.failUnlessEqual(blur(0), 0)
|
|
self.failUnlessEqual(blur(1), 10e3)
|
|
self.failUnlessEqual(blur(10e3), 10e3)
|
|
self.failUnlessEqual(blur(10e3+1), 20e3)
|
|
self.failUnlessEqual(blur(15e3), 20e3)
|
|
self.failUnlessEqual(blur(20e3), 20e3)
|
|
self.failUnlessEqual(blur(1e6), 1e6)
|
|
self.failUnlessEqual(blur(1e6+1), 2e6)
|
|
self.failUnlessEqual(blur(1.5e6), 2e6)
|
|
self.failUnlessEqual(blur(2e6), 2e6)
|
|
self.failUnlessEqual(blur(900e6), 900e6)
|
|
self.failUnlessEqual(blur(1000e6), 1000e6)
|
|
self.failUnlessEqual(blur(1050e6), 1100e6)
|
|
self.failUnlessEqual(blur(1100e6), 1100e6)
|
|
self.failUnlessEqual(blur(1150e6), 1200e6)
|
|
|
|
@defer.inlineCallbacks
|
|
def test_basic(self):
|
|
ep = clientFromString(reactor, self.transit)
|
|
a1 = yield connectProtocol(ep, Accumulator())
|
|
a2 = yield connectProtocol(ep, Accumulator())
|
|
|
|
token1 = b"\x00"*32
|
|
a1.transport.write(b"please relay " + hexlify(token1) + b"\n")
|
|
a2.transport.write(b"please relay " + hexlify(token1) + b"\n")
|
|
|
|
# a correct handshake yields an ack, after which we can send
|
|
exp = b"ok\n"
|
|
yield a1.waitForBytes(len(exp))
|
|
self.assertEqual(a1.data, exp)
|
|
s1 = b"data1"
|
|
a1.transport.write(s1)
|
|
|
|
exp = b"ok\n"
|
|
yield a2.waitForBytes(len(exp))
|
|
self.assertEqual(a2.data, exp)
|
|
|
|
# all data they sent after the handshake should be given to us
|
|
exp = b"ok\n"+s1
|
|
yield a2.waitForBytes(len(exp))
|
|
self.assertEqual(a2.data, exp)
|
|
|
|
a1.transport.loseConnection()
|
|
a2.transport.loseConnection()
|
|
|
|
@defer.inlineCallbacks
|
|
def test_bad_handshake(self):
|
|
ep = clientFromString(reactor, self.transit)
|
|
a1 = yield connectProtocol(ep, Accumulator())
|
|
|
|
token1 = b"\x00"*32
|
|
# the server waits for the exact number of bytes in the expected
|
|
# handshake message. to trigger "bad handshake", we must match.
|
|
a1.transport.write(b"please DELAY " + hexlify(token1) + b"\n")
|
|
|
|
exp = b"bad handshake\n"
|
|
yield a1.waitForBytes(len(exp))
|
|
self.assertEqual(a1.data, exp)
|
|
|
|
a1.transport.loseConnection()
|
|
|
|
@defer.inlineCallbacks
|
|
def test_impatience(self):
|
|
ep = clientFromString(reactor, self.transit)
|
|
a1 = yield connectProtocol(ep, Accumulator())
|
|
|
|
token1 = b"\x00"*32
|
|
# sending too many bytes is impatience.
|
|
a1.transport.write(b"please RELAY NOWNOW " + hexlify(token1) + b"\n")
|
|
|
|
exp = b"impatient\n"
|
|
yield a1.waitForBytes(len(exp))
|
|
self.assertEqual(a1.data, exp)
|
|
|
|
a1.transport.loseConnection()
|