add dht tests
This commit is contained in:
parent
e9fd8eb096
commit
c7acb31614
4 changed files with 233 additions and 270 deletions
0
lbrynet/tests/dht/__init__.py
Normal file
0
lbrynet/tests/dht/__init__.py
Normal file
|
@ -8,10 +8,13 @@ import hashlib
|
|||
import unittest
|
||||
import struct
|
||||
|
||||
from twisted.internet import protocol, defer, selectreactor
|
||||
from lbrynet.dht.msgtypes import ResponseMessage
|
||||
import lbrynet.dht.node
|
||||
import lbrynet.dht.constants
|
||||
import lbrynet.dht.datastore
|
||||
|
||||
|
||||
class NodeIDTest(unittest.TestCase):
|
||||
""" Test case for the Node class's ID """
|
||||
def setUp(self):
|
||||
|
@ -19,66 +22,70 @@ class NodeIDTest(unittest.TestCase):
|
|||
|
||||
def testAutoCreatedID(self):
|
||||
""" Tests if a new node has a valid node ID """
|
||||
self.failUnlessEqual(type(self.node.id), str, 'Node does not have a valid ID')
|
||||
self.failUnlessEqual(len(self.node.id), 20, 'Node ID length is incorrect! Expected 160 bits, got %d bits.' % (len(self.node.id)*8))
|
||||
self.failUnlessEqual(type(self.node.node_id), str, 'Node does not have a valid ID')
|
||||
self.failUnlessEqual(len(self.node.node_id), 48, 'Node ID length is incorrect! '
|
||||
'Expected 384 bits, got %d bits.' %
|
||||
(len(self.node.node_id) * 8))
|
||||
|
||||
def testUniqueness(self):
|
||||
""" Tests the uniqueness of the values created by the NodeID generator
|
||||
"""
|
||||
""" Tests the uniqueness of the values created by the NodeID generator """
|
||||
generatedIDs = []
|
||||
for i in range(100):
|
||||
newID = self.node._generateID()
|
||||
# ugly uniqueness test
|
||||
self.failIf(newID in generatedIDs, 'Generated ID #%d not unique!' % (i+1))
|
||||
generatedIDs.append(newID)
|
||||
|
||||
|
||||
def testKeyLength(self):
|
||||
""" Tests the key Node ID key length """
|
||||
for i in range(20):
|
||||
id = self.node._generateID()
|
||||
# Key length: 20 bytes == 160 bits
|
||||
self.failUnlessEqual(len(id), 20, 'Length of generated ID is incorrect! Expected 160 bits, got %d bits.' % (len(id)*8))
|
||||
self.failUnlessEqual(len(id), 48,
|
||||
'Length of generated ID is incorrect! Expected 384 bits, '
|
||||
'got %d bits.' % (len(id)*8))
|
||||
|
||||
|
||||
class NodeDataTest(unittest.TestCase):
|
||||
""" Test case for the Node class's data-related functions """
|
||||
def setUp(self):
|
||||
import lbrynet.dht.contact
|
||||
h = hashlib.sha1()
|
||||
h = hashlib.sha384()
|
||||
h.update('test')
|
||||
self.node = lbrynet.dht.node.Node()
|
||||
self.contact = lbrynet.dht.contact.Contact(h.digest(), '127.0.0.1', 12345, self.node._protocol)
|
||||
self.contact = lbrynet.dht.contact.Contact(h.digest(), '127.0.0.1', 12345,
|
||||
self.node._protocol)
|
||||
self.token = self.node.make_token(self.contact.compact_ip())
|
||||
self.cases = []
|
||||
for i in xrange(5):
|
||||
h.update(str(i))
|
||||
self.cases.append((h.digest(), 5000+2*i))
|
||||
self.cases.append((h.digest(), 5001+2*i))
|
||||
<<<<<<< Updated upstream
|
||||
#(('a', 'hello there\nthis is a test'),
|
||||
# ('aMuchLongerKeyThanAnyOfThePreviousOnes', 'some data'))
|
||||
|
||||
=======
|
||||
|
||||
>>>>>>> Stashed changes
|
||||
def testStore(self):
|
||||
|
||||
def check_val_in_result(r, peer_info):
|
||||
self.failUnless
|
||||
|
||||
""" Tests if the node can store (and privately retrieve) some data """
|
||||
for key, value in self.cases:
|
||||
self.node.store(key, {'port': value, 'bbid': self.contact.id, 'token': self.token}, self.contact.id, _rpcNodeContact=self.contact)
|
||||
request = {
|
||||
'port': value,
|
||||
'lbryid': self.contact.id,
|
||||
'token': self.token
|
||||
}
|
||||
self.node.store(key, request, self.contact.id, _rpcNodeContact=self.contact)
|
||||
for key, value in self.cases:
|
||||
expected_result = self.contact.compact_ip() + str(struct.pack('>H', value)) + self.contact.id
|
||||
self.failUnless(self.node._dataStore.hasPeersForBlob(key), 'Stored key not found in node\'s DataStore: "%s"' % key)
|
||||
self.failUnless(expected_result in self.node._dataStore.getPeersForBlob(key), 'Stored val not found in node\'s DataStore: key:"%s" port:"%s" %s' % (key, value, self.node._dataStore.getPeersForBlob(key)))
|
||||
expected_result = self.contact.compact_ip() + str(struct.pack('>H', value)) + \
|
||||
self.contact.id
|
||||
self.failUnless(self.node._dataStore.hasPeersForBlob(key),
|
||||
'Stored key not found in node\'s DataStore: "%s"' % key)
|
||||
self.failUnless(expected_result in self.node._dataStore.getPeersForBlob(key),
|
||||
'Stored val not found in node\'s DataStore: key:"%s" port:"%s" %s'
|
||||
% (key, value, self.node._dataStore.getPeersForBlob(key)))
|
||||
|
||||
|
||||
class NodeContactTest(unittest.TestCase):
|
||||
""" Test case for the Node class's contact management-related functions """
|
||||
def setUp(self):
|
||||
self.node = lbrynet.dht.node.Node()
|
||||
|
||||
|
||||
def testAddContact(self):
|
||||
""" Tests if a contact can be added and retrieved correctly """
|
||||
import lbrynet.dht.contact
|
||||
|
@ -91,67 +98,55 @@ class NodeContactTest(unittest.TestCase):
|
|||
self.node.addContact(contact)
|
||||
# ...and request the closest nodes to it using FIND_NODE
|
||||
closestNodes = self.node._routingTable.findCloseNodes(contactID, lbrynet.dht.constants.k)
|
||||
self.failUnlessEqual(len(closestNodes), 1, 'Wrong amount of contacts returned; expected 1, got %d' % len(closestNodes))
|
||||
self.failUnless(contact in closestNodes, 'Added contact not found by issueing _findCloseNodes()')
|
||||
|
||||
self.failUnlessEqual(len(closestNodes), 1, 'Wrong amount of contacts returned; '
|
||||
'expected 1, got %d' % len(closestNodes))
|
||||
self.failUnless(contact in closestNodes, 'Added contact not found by issueing '
|
||||
'_findCloseNodes()')
|
||||
|
||||
def testAddSelfAsContact(self):
|
||||
""" Tests the node's behaviour when attempting to add itself as a contact """
|
||||
import lbrynet.dht.contact
|
||||
# Create a contact with the same ID as the local node's ID
|
||||
contact = lbrynet.dht.contact.Contact(self.node.id, '127.0.0.1', 91824, None)
|
||||
contact = lbrynet.dht.contact.Contact(self.node.node_id, '127.0.0.1', 91824, None)
|
||||
# Now try to add it
|
||||
self.node.addContact(contact)
|
||||
# ...and request the closest nodes to it using FIND_NODE
|
||||
closestNodes = self.node._routingTable.findCloseNodes(self.node.id, lbrynet.dht.constants.k)
|
||||
closestNodes = self.node._routingTable.findCloseNodes(self.node.node_id,
|
||||
lbrynet.dht.constants.k)
|
||||
self.failIf(contact in closestNodes, 'Node added itself as a contact')
|
||||
|
||||
|
||||
<<<<<<< Updated upstream
|
||||
# """ Test case for the Node class's iterative node lookup algorithm """
|
||||
|
||||
|
||||
# """ Ugly brute-force test to see if the iterative node lookup algorithm runs without failing """
|
||||
|
||||
=======
|
||||
>>>>>>> Stashed changes
|
||||
|
||||
"""Some scaffolding for the NodeLookupTest class. Allows isolated
|
||||
node testing by simulating remote node responses"""
|
||||
from twisted.internet import protocol, defer, selectreactor
|
||||
from lbrynet.dht.msgtypes import ResponseMessage
|
||||
|
||||
|
||||
class FakeRPCProtocol(protocol.DatagramProtocol):
|
||||
def __init__(self):
|
||||
self.reactor = selectreactor.SelectReactor()
|
||||
self.reactor = selectreactor.SelectReactor()
|
||||
self.testResponse = None
|
||||
self.network = None
|
||||
|
||||
def createNetwork(self, contactNetwork):
|
||||
""" set up a list of contacts together with their closest contacts
|
||||
@param contactNetwork: a sequence of tuples, each containing a contact together with its closest
|
||||
contacts: C{(<contact>, <closest contact 1, ...,closest contact n>)}
|
||||
"""
|
||||
self.network = contactNetwork
|
||||
|
||||
""" Fake RPC protocol; allows entangled.kademlia.contact.Contact objects to "send" RPCs """
|
||||
"""
|
||||
set up a list of contacts together with their closest contacts
|
||||
@param contactNetwork: a sequence of tuples, each containing a contact together with its
|
||||
closest contacts: C{(<contact>, <closest contact 1, ...,closest contact n>)}
|
||||
"""
|
||||
self.network = contactNetwork
|
||||
|
||||
def sendRPC(self, contact, method, args, rawResponse=False):
|
||||
|
||||
if method == "findNode":
|
||||
""" Fake RPC protocol; allows entangled.kademlia.contact.Contact objects to "send" RPCs"""
|
||||
|
||||
if method == "findNode":
|
||||
# get the specific contacts closest contacts
|
||||
closestContacts = []
|
||||
closestContactsList = []
|
||||
for contactTuple in self.network:
|
||||
if contact == contactTuple[0]:
|
||||
# get the list of closest contacts for this contact
|
||||
closestContactsList = contactTuple[1]
|
||||
|
||||
# Pack the closest contacts into a ResponseMessage
|
||||
# Pack the closest contacts into a ResponseMessage
|
||||
for closeContact in closestContactsList:
|
||||
closestContacts.append((closeContact.id, closeContact.address, closeContact.port))
|
||||
message = ResponseMessage("rpcId", contact.id, closestContacts)
|
||||
|
||||
df = defer.Deferred()
|
||||
df.callback((message,(contact.address, contact.port)))
|
||||
df.callback((message, (contact.address, contact.port)))
|
||||
return df
|
||||
elif method == "findValue":
|
||||
for contactTuple in self.network:
|
||||
|
@ -160,12 +155,10 @@ class FakeRPCProtocol(protocol.DatagramProtocol):
|
|||
dataDict = contactTuple[2]
|
||||
dataKey = dataDict.keys()[0]
|
||||
data = dataDict.get(dataKey)
|
||||
|
||||
# Check if this contact has the requested value
|
||||
if dataKey == args[0]:
|
||||
# Return the data value
|
||||
response = dataDict
|
||||
|
||||
print "data found at contact: " + contact.id
|
||||
else:
|
||||
# Return the closest contact to the requested data key
|
||||
|
@ -173,62 +166,52 @@ class FakeRPCProtocol(protocol.DatagramProtocol):
|
|||
closeContacts = contactTuple[1]
|
||||
closestContacts = []
|
||||
for closeContact in closeContacts:
|
||||
closestContacts.append((closeContact.id, closeContact.address, closeContact.port))
|
||||
closestContacts.append((closeContact.id, closeContact.address,
|
||||
closeContact.port))
|
||||
response = closestContacts
|
||||
|
||||
|
||||
# Create the response message
|
||||
message = ResponseMessage("rpcId", contact.id, response)
|
||||
df = defer.Deferred()
|
||||
df.callback((message,(contact.address, contact.port)))
|
||||
df.callback((message, (contact.address, contact.port)))
|
||||
return df
|
||||
|
||||
def _send(self, data, rpcID, address):
|
||||
""" fake sending data """
|
||||
|
||||
|
||||
|
||||
|
||||
class NodeLookupTest(unittest.TestCase):
|
||||
""" Test case for the Node class's iterativeFind node lookup algorithm """
|
||||
|
||||
|
||||
def setUp(self):
|
||||
|
||||
# create a fake protocol to imitate communication with other nodes
|
||||
self._protocol = FakeRPCProtocol()
|
||||
|
||||
# Note: The reactor is never started for this test. All deferred calls run sequentially,
|
||||
# Note: The reactor is never started for this test. All deferred calls run sequentially,
|
||||
# since there is no asynchronous network communication
|
||||
|
||||
# create the node to be tested in isolation
|
||||
self.node = lbrynet.dht.node.Node(None, 4000, None, None, self._protocol)
|
||||
|
||||
self.node = lbrynet.dht.node.Node('12345678901234567800', 4000, None, None, self._protocol)
|
||||
self.updPort = 81173
|
||||
|
||||
<<<<<<< Updated upstream
|
||||
# create a dummy reactor
|
||||
|
||||
=======
|
||||
>>>>>>> Stashed changes
|
||||
self.contactsAmount = 80
|
||||
# set the node ID manually for testing
|
||||
self.node.id = '12345678901234567800'
|
||||
|
||||
# Reinitialise the routing table
|
||||
self.node._routingTable = lbrynet.dht.routingtable.OptimizedTreeRoutingTable(self.node.id)
|
||||
|
||||
self.node._routingTable = lbrynet.dht.routingtable.OptimizedTreeRoutingTable(
|
||||
self.node.node_id)
|
||||
|
||||
# create 160 bit node ID's for test purposes
|
||||
self.testNodeIDs = []
|
||||
idNum = int(self.node.id)
|
||||
idNum = int(self.node.node_id)
|
||||
for i in range(self.contactsAmount):
|
||||
# create the testNodeIDs in ascending order, away from the actual node ID, with regards to the distance metric
|
||||
# create the testNodeIDs in ascending order, away from the actual node ID,
|
||||
# with regards to the distance metric
|
||||
self.testNodeIDs.append(idNum + i + 1)
|
||||
|
||||
# generate contacts
|
||||
self.contacts = []
|
||||
for i in range(self.contactsAmount):
|
||||
contact = lbrynet.dht.contact.Contact(str(self.testNodeIDs[i]), "127.0.0.1", self.updPort + i + 1, self._protocol)
|
||||
contact = lbrynet.dht.contact.Contact(str(self.testNodeIDs[i]), "127.0.0.1",
|
||||
self.updPort + i + 1, self._protocol)
|
||||
self.contacts.append(contact)
|
||||
|
||||
# create the network of contacts in format: (contact, closest contacts)
|
||||
|
||||
# create the network of contacts in format: (contact, closest contacts)
|
||||
contactNetwork = ((self.contacts[0], self.contacts[8:15]),
|
||||
(self.contacts[1], self.contacts[16:23]),
|
||||
(self.contacts[2], self.contacts[24:31]),
|
||||
|
@ -254,43 +237,27 @@ class NodeLookupTest(unittest.TestCase):
|
|||
contacts_with_datastores = []
|
||||
|
||||
for contact_tuple in contactNetwork:
|
||||
contacts_with_datastores.append((contact_tuple[0], contact_tuple[1], lbrynet.dht.datastore.DictDataStore()))
|
||||
|
||||
contacts_with_datastores.append((contact_tuple[0], contact_tuple[1],
|
||||
lbrynet.dht.datastore.DictDataStore()))
|
||||
self._protocol.createNetwork(contacts_with_datastores)
|
||||
|
||||
|
||||
def testNodeBootStrap(self):
|
||||
""" Test bootstrap with the closest possible contacts """
|
||||
|
||||
df = self.node._iterativeFind(self.node.id, self.contacts[0:8])
|
||||
|
||||
df = self.node._iterativeFind(self.node.node_id, self.contacts[0:8])
|
||||
# Set the expected result
|
||||
expectedResult = []
|
||||
|
||||
expectedResult = []
|
||||
for item in self.contacts[0:6]:
|
||||
expectedResult.append(item.id)
|
||||
|
||||
expectedResult.append(item.id)
|
||||
# Get the result from the deferred
|
||||
activeContacts = df.result
|
||||
|
||||
|
||||
|
||||
# Check the length of the active contacts
|
||||
self.failUnlessEqual(activeContacts.__len__(), expectedResult.__len__(), \
|
||||
"More active contacts should exist, there should be %d contacts" %expectedResult.__len__())
|
||||
|
||||
|
||||
self.failUnlessEqual(activeContacts.__len__(), expectedResult.__len__(),
|
||||
"More active contacts should exist, there should be %d "
|
||||
"contacts" % expectedResult.__len__())
|
||||
|
||||
# Check that the received active contacts are the same as the input contacts
|
||||
self.failUnlessEqual(activeContacts, expectedResult, \
|
||||
"Active should only contain the closest possible contacts which were used as input for the boostrap")
|
||||
|
||||
|
||||
def suite():
|
||||
suite = unittest.TestSuite()
|
||||
suite.addTest(unittest.makeSuite(NodeIDTest))
|
||||
suite.addTest(unittest.makeSuite(NodeDataTest))
|
||||
suite.addTest(unittest.makeSuite(NodeContactTest))
|
||||
suite.addTest(unittest.makeSuite(NodeLookupTest))
|
||||
return suite
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# If this module is executed from the commandline, run all its tests
|
||||
unittest.TextTestRunner().run(suite())
|
||||
self.failUnlessEqual(activeContacts, expectedResult,
|
||||
"Active should only contain the closest possible contacts"
|
||||
" which were used as input for the boostrap")
|
|
@ -1,88 +1,22 @@
|
|||
#!/usr/bin/env python
|
||||
#
|
||||
# This library is free software, distributed under the terms of
|
||||
# the GNU Lesser General Public License Version 3, or any later version.
|
||||
# See the COPYING file included in this archive
|
||||
|
||||
import time
|
||||
import unittest
|
||||
|
||||
from twisted.internet import defer
|
||||
from twisted.python import failure
|
||||
import twisted.internet.selectreactor
|
||||
from twisted.internet.protocol import DatagramProtocol
|
||||
|
||||
import lbrynet.dht.protocol
|
||||
import lbrynet.dht.contact
|
||||
import lbrynet.dht.constants
|
||||
import lbrynet.dht.msgtypes
|
||||
from lbrynet.dht.node import rpcmethod
|
||||
from lbrynet.dht.error import TimeoutError
|
||||
from lbrynet.dht.node import Node, rpcmethod
|
||||
|
||||
|
||||
class FakeNode(object):
|
||||
""" A fake node object implementing some RPC and non-RPC methods to
|
||||
test the Kademlia protocol's behaviour
|
||||
"""
|
||||
def __init__(self, id):
|
||||
self.id = id
|
||||
self.contacts = []
|
||||
|
||||
@rpcmethod
|
||||
def ping(self):
|
||||
return 'pong'
|
||||
|
||||
def pingNoRPC(self):
|
||||
return 'pong'
|
||||
|
||||
@rpcmethod
|
||||
def echo(self, value):
|
||||
return value
|
||||
|
||||
def addContact(self, contact):
|
||||
self.contacts.append(contact)
|
||||
|
||||
def removeContact(self, contact):
|
||||
self.contacts.remove(contact)
|
||||
|
||||
def indirectPingContact(self, protocol, contact):
|
||||
""" Pings the given contact (using the specified KademliaProtocol
|
||||
object, not the direct Contact API), and removes the contact
|
||||
on a timeout """
|
||||
df = protocol.sendRPC(contact, 'ping', {})
|
||||
def handleError(f):
|
||||
if f.check(lbrynet.dht.protocol.TimeoutError):
|
||||
self.removeContact(contact)
|
||||
return f
|
||||
else:
|
||||
# This is some other error
|
||||
return f
|
||||
df.addErrback(handleError)
|
||||
return df
|
||||
|
||||
class ClientDatagramProtocol(lbrynet.dht.protocol.KademliaProtocol):
|
||||
data = ''
|
||||
msgID = ''
|
||||
destination = ('127.0.0.1', 9182)
|
||||
|
||||
def __init__(self):
|
||||
lbrynet.dht.protocol.KademliaProtocol.__init__(self, None)
|
||||
|
||||
def startProtocol(self):
|
||||
self.sendDatagram()
|
||||
|
||||
def sendDatagram(self):
|
||||
if len(self.data):
|
||||
self._send(self.data, self.msgID, self.destination)
|
||||
|
||||
|
||||
|
||||
|
||||
class KademliaProtocolTest(unittest.TestCase):
|
||||
""" Test case for the Protocol class """
|
||||
|
||||
def setUp(self):
|
||||
del lbrynet.dht.protocol.reactor
|
||||
lbrynet.dht.protocol.reactor = twisted.internet.selectreactor.SelectReactor()
|
||||
self.node = FakeNode('node1')
|
||||
self.node = Node(node_id='node1', udpPort=9182, externalIP="127.0.0.1")
|
||||
self.protocol = lbrynet.dht.protocol.KademliaProtocol(self.node)
|
||||
|
||||
def testReactor(self):
|
||||
|
@ -93,36 +27,66 @@ class KademliaProtocolTest(unittest.TestCase):
|
|||
|
||||
def testRPCTimeout(self):
|
||||
""" Tests if a RPC message sent to a dead remote node times out correctly """
|
||||
|
||||
@rpcmethod
|
||||
def fake_ping(*args, **kwargs):
|
||||
time.sleep(lbrynet.dht.constants.rpcTimeout + 1)
|
||||
return 'pong'
|
||||
|
||||
real_ping = self.node.ping
|
||||
real_timeout = lbrynet.dht.constants.rpcTimeout
|
||||
real_attempts = lbrynet.dht.constants.rpcAttempts
|
||||
lbrynet.dht.constants.rpcAttempts = 1
|
||||
lbrynet.dht.constants.rpcTimeout = 1
|
||||
self.node.ping = fake_ping
|
||||
deadContact = lbrynet.dht.contact.Contact('node2', '127.0.0.1', 9182, self.protocol)
|
||||
self.node.addContact(deadContact)
|
||||
# Make sure the contact was added
|
||||
self.failIf(deadContact not in self.node.contacts, 'Contact not added to fake node (error in test code)')
|
||||
# Set the timeout to 0 for testing
|
||||
tempTimeout = lbrynet.dht.constants.rpcTimeout
|
||||
lbrynet.dht.constants.rpcTimeout = 0
|
||||
lbrynet.dht.protocol.reactor.listenUDP(0, self.protocol)
|
||||
# Run the PING RPC (which should timeout)
|
||||
df = self.node.indirectPingContact(self.protocol, deadContact)
|
||||
self.failIf(deadContact not in self.node.contacts,
|
||||
'Contact not added to fake node (error in test code)')
|
||||
lbrynet.dht.protocol.reactor.listenUDP(9182, self.protocol)
|
||||
|
||||
# Run the PING RPC (which should raise a timeout error)
|
||||
df = self.protocol.sendRPC(deadContact, 'ping', {})
|
||||
|
||||
def check_timeout(err):
|
||||
self.assertEqual(type(err), TimeoutError)
|
||||
|
||||
df.addErrback(check_timeout)
|
||||
|
||||
def reset_values():
|
||||
self.node.ping = real_ping
|
||||
lbrynet.dht.constants.rpcTimeout = real_timeout
|
||||
lbrynet.dht.constants.rpcAttempts = real_attempts
|
||||
|
||||
# See if the contact was removed due to the timeout
|
||||
def check_removed_contact():
|
||||
self.failIf(deadContact in self.node.contacts,
|
||||
'Contact was not removed after RPC timeout; check exception types.')
|
||||
|
||||
df.addCallback(lambda _: reset_values())
|
||||
|
||||
# Stop the reactor if a result arrives (timeout or not)
|
||||
df.addBoth(lambda _: lbrynet.dht.protocol.reactor.stop())
|
||||
df.addCallback(lambda _: check_removed_contact())
|
||||
lbrynet.dht.protocol.reactor.run()
|
||||
# See if the contact was removed due to the timeout
|
||||
self.failIf(deadContact in self.node.contacts, 'Contact was not removed after RPC timeout; check exception types.')
|
||||
# Restore the global timeout
|
||||
lbrynet.dht.constants.rpcTimeout = tempTimeout
|
||||
|
||||
|
||||
def testRPCRequest(self):
|
||||
""" Tests if a valid RPC request is executed and responded to correctly """
|
||||
remoteContact = lbrynet.dht.contact.Contact('node2', '127.0.0.1', 9182, self.protocol)
|
||||
self.node.addContact(remoteContact)
|
||||
self.error = None
|
||||
|
||||
def handleError(f):
|
||||
self.error = 'An RPC error occurred: %s' % f.getErrorMessage()
|
||||
|
||||
def handleResult(result):
|
||||
expectedResult = 'pong'
|
||||
if result != expectedResult:
|
||||
self.error = 'Result from RPC is incorrect; expected "%s", got "%s"' % (expectedResult, result)
|
||||
# Publish the "local" node on the network
|
||||
self.error = 'Result from RPC is incorrect; expected "%s", got "%s"' \
|
||||
% (expectedResult, result)
|
||||
|
||||
# Publish the "local" node on the network
|
||||
lbrynet.dht.protocol.reactor.listenUDP(9182, self.protocol)
|
||||
# Simulate the RPC
|
||||
df = remoteContact.ping()
|
||||
|
@ -132,17 +96,19 @@ class KademliaProtocolTest(unittest.TestCase):
|
|||
lbrynet.dht.protocol.reactor.run()
|
||||
self.failIf(self.error, self.error)
|
||||
# The list of sent RPC messages should be empty at this stage
|
||||
self.failUnlessEqual(len(self.protocol._sentMessages), 0, 'The protocol is still waiting for a RPC result, but the transaction is already done!')
|
||||
self.failUnlessEqual(len(self.protocol._sentMessages), 0,
|
||||
'The protocol is still waiting for a RPC result, '
|
||||
'but the transaction is already done!')
|
||||
|
||||
def testRPCAccess(self):
|
||||
""" Tests invalid RPC requests
|
||||
|
||||
Verifies that a RPC request for an existing but unpublished
|
||||
method is denied, and that the associated (remote) exception gets
|
||||
raised locally """
|
||||
remoteContact = lbrynet.dht.contact.Contact('node2', '127.0.0.1', 9182, self.protocol)
|
||||
self.node.addContact(remoteContact)
|
||||
self.error = None
|
||||
|
||||
def handleError(f):
|
||||
try:
|
||||
f.raiseException()
|
||||
|
@ -150,11 +116,14 @@ class KademliaProtocolTest(unittest.TestCase):
|
|||
# This is the expected outcome since the remote node did not publish the method
|
||||
self.error = None
|
||||
except Exception, e:
|
||||
self.error = 'The remote method failed, but the wrong exception was raised; expected AttributeError, got %s' % type(e)
|
||||
|
||||
self.error = 'The remote method failed, but the wrong exception was raised; ' \
|
||||
'expected AttributeError, got %s' % type(e)
|
||||
|
||||
def handleResult(result):
|
||||
self.error = 'The remote method executed successfully, returning: "%s"; this RPC should not have been allowed.' % result
|
||||
# Publish the "local" node on the network
|
||||
self.error = 'The remote method executed successfully, returning: "%s"; ' \
|
||||
'this RPC should not have been allowed.' % result
|
||||
|
||||
# Publish the "local" node on the network
|
||||
lbrynet.dht.protocol.reactor.listenUDP(9182, self.protocol)
|
||||
# Simulate the RPC
|
||||
df = remoteContact.pingNoRPC()
|
||||
|
@ -164,37 +133,35 @@ class KademliaProtocolTest(unittest.TestCase):
|
|||
lbrynet.dht.protocol.reactor.run()
|
||||
self.failIf(self.error, self.error)
|
||||
# The list of sent RPC messages should be empty at this stage
|
||||
self.failUnlessEqual(len(self.protocol._sentMessages), 0, 'The protocol is still waiting for a RPC result, but the transaction is already done!')
|
||||
self.failUnlessEqual(len(self.protocol._sentMessages), 0,
|
||||
'The protocol is still waiting for a RPC result, '
|
||||
'but the transaction is already done!')
|
||||
|
||||
def testRPCRequestArgs(self):
|
||||
""" Tests if an RPC requiring arguments is executed correctly """
|
||||
remoteContact = lbrynet.dht.contact.Contact('node2', '127.0.0.1', 9182, self.protocol)
|
||||
self.node.addContact(remoteContact)
|
||||
self.error = None
|
||||
|
||||
def handleError(f):
|
||||
self.error = 'An RPC error occurred: %s' % f.getErrorMessage()
|
||||
|
||||
def handleResult(result):
|
||||
expectedResult = 'This should be returned.'
|
||||
if result != 'This should be returned.':
|
||||
self.error = 'Result from RPC is incorrect; expected "%s", got "%s"' % (expectedResult, result)
|
||||
# Publish the "local" node on the network
|
||||
expectedResult = 'pong'
|
||||
if result != expectedResult:
|
||||
self.error = 'Result from RPC is incorrect; expected "%s", got "%s"' % \
|
||||
(expectedResult, result)
|
||||
|
||||
# Publish the "local" node on the network
|
||||
lbrynet.dht.protocol.reactor.listenUDP(9182, self.protocol)
|
||||
# Simulate the RPC
|
||||
df = remoteContact.echo('This should be returned.')
|
||||
df = remoteContact.ping()
|
||||
df.addCallback(handleResult)
|
||||
df.addErrback(handleError)
|
||||
df.addBoth(lambda _: lbrynet.dht.protocol.reactor.stop())
|
||||
lbrynet.dht.protocol.reactor.run()
|
||||
self.failIf(self.error, self.error)
|
||||
# The list of sent RPC messages should be empty at this stage
|
||||
self.failUnlessEqual(len(self.protocol._sentMessages), 0, 'The protocol is still waiting for a RPC result, but the transaction is already done!')
|
||||
|
||||
|
||||
def suite():
|
||||
suite = unittest.TestSuite()
|
||||
suite.addTest(unittest.makeSuite(KademliaProtocolTest))
|
||||
return suite
|
||||
|
||||
if __name__ == '__main__':
|
||||
# If this module is executed from the commandline, run all its tests
|
||||
unittest.TextTestRunner().run(suite())
|
||||
self.failUnlessEqual(len(self.protocol._sentMessages), 0,
|
||||
'The protocol is still waiting for a RPC result, '
|
||||
'but the transaction is already done!')
|
|
@ -10,6 +10,8 @@ import unittest
|
|||
import lbrynet.dht.constants
|
||||
import lbrynet.dht.routingtable
|
||||
import lbrynet.dht.contact
|
||||
import lbrynet.dht.node
|
||||
|
||||
|
||||
class FakeRPCProtocol(object):
|
||||
""" Fake RPC protocol; allows lbrynet.dht.contact.Contact objects to "send" RPCs """
|
||||
|
@ -21,6 +23,7 @@ class FakeDeferred(object):
|
|||
""" Fake Twisted Deferred object; allows the routing table to add callbacks that do nothing """
|
||||
def addCallback(self, *args, **kwargs):
|
||||
return
|
||||
|
||||
def addErrback(self, *args, **kwargs):
|
||||
return
|
||||
|
||||
|
@ -28,34 +31,36 @@ class FakeDeferred(object):
|
|||
class TreeRoutingTableTest(unittest.TestCase):
|
||||
""" Test case for the RoutingTable class """
|
||||
def setUp(self):
|
||||
h = hashlib.sha1()
|
||||
h = hashlib.sha384()
|
||||
h.update('node1')
|
||||
self.nodeID = h.digest()
|
||||
self.protocol = FakeRPCProtocol()
|
||||
self.routingTable = lbrynet.dht.routingtable.TreeRoutingTable(self.nodeID)
|
||||
|
||||
|
||||
def testDistance(self):
|
||||
""" Test to see if distance method returns correct result"""
|
||||
|
||||
|
||||
# testList holds a couple 3-tuple (variable1, variable2, result)
|
||||
basicTestList = [('123456789','123456789', 0L), ('12345', '98765', 34527773184L)]
|
||||
basicTestList = [('123456789', '123456789', 0L), ('12345', '98765', 34527773184L)]
|
||||
|
||||
for test in basicTestList:
|
||||
result = self.routingTable.distance(test[0], test[1])
|
||||
self.failIf(result != test[2], 'Result of _distance() should be %s but %s returned' % (test[2], result))
|
||||
result = lbrynet.dht.node.Distance(test[0])(test[1])
|
||||
self.failIf(result != test[2], 'Result of _distance() should be %s but %s returned' %
|
||||
(test[2], result))
|
||||
|
||||
baseIp = '146.64.19.111'
|
||||
ipTestList = ['146.64.29.222', '192.68.19.333']
|
||||
|
||||
distanceOne = self.routingTable.distance(baseIp, ipTestList[0])
|
||||
distanceTwo = self.routingTable.distance(baseIp, ipTestList[1])
|
||||
distanceOne = lbrynet.dht.node.Distance(baseIp)(ipTestList[0])
|
||||
distanceTwo = lbrynet.dht.node.Distance(baseIp)(ipTestList[1])
|
||||
|
||||
self.failIf(distanceOne > distanceTwo, '%s should be closer to the base ip %s than %s' %
|
||||
(ipTestList[0], baseIp, ipTestList[1]))
|
||||
|
||||
self.failIf(distanceOne > distanceTwo, '%s should be closer to the base ip %s than %s' % (ipTestList[0], baseIp, ipTestList[1]))
|
||||
|
||||
def testAddContact(self):
|
||||
""" Tests if a contact can be added and retrieved correctly """
|
||||
# Create the contact
|
||||
h = hashlib.sha1()
|
||||
h = hashlib.sha384()
|
||||
h.update('node2')
|
||||
contactID = h.digest()
|
||||
contact = lbrynet.dht.contact.Contact(contactID, '127.0.0.1', 91824, self.protocol)
|
||||
|
@ -63,12 +68,14 @@ class TreeRoutingTableTest(unittest.TestCase):
|
|||
self.routingTable.addContact(contact)
|
||||
# ...and request the closest nodes to it (will retrieve it)
|
||||
closestNodes = self.routingTable.findCloseNodes(contactID, lbrynet.dht.constants.k)
|
||||
self.failUnlessEqual(len(closestNodes), 1, 'Wrong amount of contacts returned; expected 1, got %d' % len(closestNodes))
|
||||
self.failUnless(contact in closestNodes, 'Added contact not found by issueing _findCloseNodes()')
|
||||
|
||||
self.failUnlessEqual(len(closestNodes), 1, 'Wrong amount of contacts returned; expected 1,'
|
||||
' got %d' % len(closestNodes))
|
||||
self.failUnless(contact in closestNodes, 'Added contact not found by issueing '
|
||||
'_findCloseNodes()')
|
||||
|
||||
def testGetContact(self):
|
||||
""" Tests if a specific existing contact can be retrieved correctly """
|
||||
h = hashlib.sha1()
|
||||
h = hashlib.sha384()
|
||||
h.update('node2')
|
||||
contactID = h.digest()
|
||||
contact = lbrynet.dht.contact.Contact(contactID, '127.0.0.1', 91824, self.protocol)
|
||||
|
@ -77,9 +84,12 @@ class TreeRoutingTableTest(unittest.TestCase):
|
|||
# ...and get it again
|
||||
sameContact = self.routingTable.getContact(contactID)
|
||||
self.failUnlessEqual(contact, sameContact, 'getContact() should return the same contact')
|
||||
|
||||
|
||||
def testAddParentNodeAsContact(self):
|
||||
""" Tests the routing table's behaviour when attempting to add its parent node as a contact """
|
||||
"""
|
||||
Tests the routing table's behaviour when attempting to add its parent node as a contact
|
||||
"""
|
||||
|
||||
# Create a contact with the same ID as the local node's ID
|
||||
contact = lbrynet.dht.contact.Contact(self.nodeID, '127.0.0.1', 91824, self.protocol)
|
||||
# Now try to add it
|
||||
|
@ -87,11 +97,11 @@ class TreeRoutingTableTest(unittest.TestCase):
|
|||
# ...and request the closest nodes to it using FIND_NODE
|
||||
closestNodes = self.routingTable.findCloseNodes(self.nodeID, lbrynet.dht.constants.k)
|
||||
self.failIf(contact in closestNodes, 'Node added itself as a contact')
|
||||
|
||||
|
||||
def testRemoveContact(self):
|
||||
""" Tests contact removal """
|
||||
# Create the contact
|
||||
h = hashlib.sha1()
|
||||
h = hashlib.sha384()
|
||||
h.update('node2')
|
||||
contactID = h.digest()
|
||||
contact = lbrynet.dht.contact.Contact(contactID, '127.0.0.1', 91824, self.protocol)
|
||||
|
@ -105,54 +115,73 @@ class TreeRoutingTableTest(unittest.TestCase):
|
|||
|
||||
def testSplitBucket(self):
|
||||
""" Tests if the the routing table correctly dynamically splits k-buckets """
|
||||
self.failUnlessEqual(self.routingTable._buckets[0].rangeMax, 2**160, 'Initial k-bucket range should be 0 <= range < 2**160')
|
||||
self.failUnlessEqual(self.routingTable._buckets[0].rangeMax, 2**384,
|
||||
'Initial k-bucket range should be 0 <= range < 2**384')
|
||||
# Add k contacts
|
||||
for i in range(lbrynet.dht.constants.k):
|
||||
h = hashlib.sha1()
|
||||
h = hashlib.sha384()
|
||||
h.update('remote node %d' % i)
|
||||
nodeID = h.digest()
|
||||
contact = lbrynet.dht.contact.Contact(nodeID, '127.0.0.1', 91824, self.protocol)
|
||||
self.routingTable.addContact(contact)
|
||||
self.failUnlessEqual(len(self.routingTable._buckets), 1, 'Only k nodes have been added; the first k-bucket should now be full, but should not yet be split')
|
||||
self.failUnlessEqual(len(self.routingTable._buckets), 1,
|
||||
'Only k nodes have been added; the first k-bucket should now '
|
||||
'be full, but should not yet be split')
|
||||
# Now add 1 more contact
|
||||
h = hashlib.sha1()
|
||||
h = hashlib.sha384()
|
||||
h.update('yet another remote node')
|
||||
nodeID = h.digest()
|
||||
contact = lbrynet.dht.contact.Contact(nodeID, '127.0.0.1', 91824, self.protocol)
|
||||
self.routingTable.addContact(contact)
|
||||
self.failUnlessEqual(len(self.routingTable._buckets), 2, 'k+1 nodes have been added; the first k-bucket should have been split into two new buckets')
|
||||
self.failIfEqual(self.routingTable._buckets[0].rangeMax, 2**160, 'K-bucket was split, but its range was not properly adjusted')
|
||||
self.failUnlessEqual(self.routingTable._buckets[1].rangeMax, 2**160, 'K-bucket was split, but the second (new) bucket\'s max range was not set properly')
|
||||
self.failUnlessEqual(self.routingTable._buckets[0].rangeMax, self.routingTable._buckets[1].rangeMin, 'K-bucket was split, but the min/max ranges were not divided properly')
|
||||
|
||||
self.failUnlessEqual(len(self.routingTable._buckets), 2,
|
||||
'k+1 nodes have been added; the first k-bucket should have been '
|
||||
'split into two new buckets')
|
||||
self.failIfEqual(self.routingTable._buckets[0].rangeMax, 2**384,
|
||||
'K-bucket was split, but its range was not properly adjusted')
|
||||
self.failUnlessEqual(self.routingTable._buckets[1].rangeMax, 2**384,
|
||||
'K-bucket was split, but the second (new) bucket\'s '
|
||||
'max range was not set properly')
|
||||
self.failUnlessEqual(self.routingTable._buckets[0].rangeMax,
|
||||
self.routingTable._buckets[1].rangeMin,
|
||||
'K-bucket was split, but the min/max ranges were '
|
||||
'not divided properly')
|
||||
|
||||
def testFullBucketNoSplit(self):
|
||||
""" Test that a bucket is not split if it full, but does not cover the range containing the parent node's ID """
|
||||
self.routingTable._parentNodeID = 21*'a' # more than 160 bits; this will not be in the range of _any_ k-bucket
|
||||
"""
|
||||
Test that a bucket is not split if it full, but does not cover the range
|
||||
containing the parent node's ID
|
||||
"""
|
||||
self.routingTable._parentNodeID = 49 * 'a'
|
||||
# more than 384 bits; this will not be in the range of _any_ k-bucket
|
||||
# Add k contacts
|
||||
for i in range(lbrynet.dht.constants.k):
|
||||
h = hashlib.sha1()
|
||||
h = hashlib.sha384()
|
||||
h.update('remote node %d' % i)
|
||||
nodeID = h.digest()
|
||||
contact = lbrynet.dht.contact.Contact(nodeID, '127.0.0.1', 91824, self.protocol)
|
||||
self.routingTable.addContact(contact)
|
||||
self.failUnlessEqual(len(self.routingTable._buckets), 1, 'Only k nodes have been added; the first k-bucket should now be full, and there should not be more than 1 bucket')
|
||||
self.failUnlessEqual(len(self.routingTable._buckets[0]._contacts), lbrynet.dht.constants.k, 'Bucket should have k contacts; expected %d got %d' % (lbrynet.dht.constants.k, len(self.routingTable._buckets[0]._contacts)))
|
||||
self.failUnlessEqual(len(self.routingTable._buckets), 1, 'Only k nodes have been added; '
|
||||
'the first k-bucket should now be '
|
||||
'full, and there should not be '
|
||||
'more than 1 bucket')
|
||||
self.failUnlessEqual(len(self.routingTable._buckets[0]._contacts), lbrynet.dht.constants.k,
|
||||
'Bucket should have k contacts; expected %d got %d' %
|
||||
(lbrynet.dht.constants.k,
|
||||
len(self.routingTable._buckets[0]._contacts)))
|
||||
# Now add 1 more contact
|
||||
h = hashlib.sha1()
|
||||
h = hashlib.sha384()
|
||||
h.update('yet another remote node')
|
||||
nodeID = h.digest()
|
||||
contact = lbrynet.dht.contact.Contact(nodeID, '127.0.0.1', 91824, self.protocol)
|
||||
self.routingTable.addContact(contact)
|
||||
self.failUnlessEqual(len(self.routingTable._buckets), 1, 'There should not be more than 1 bucket, since the bucket should not have been split (parent node ID not in range)')
|
||||
self.failUnlessEqual(len(self.routingTable._buckets[0]._contacts), lbrynet.dht.constants.k, 'Bucket should have k contacts; expected %d got %d' % (lbrynet.dht.constants.k, len(self.routingTable._buckets[0]._contacts)))
|
||||
self.failIf(contact in self.routingTable._buckets[0]._contacts, 'New contact should have been discarded (since RPC is faked in this test)')
|
||||
self.failUnlessEqual(len(self.routingTable._buckets), 1,
|
||||
'There should not be more than 1 bucket, since the bucket '
|
||||
'should not have been split (parent node ID not in range)')
|
||||
self.failUnlessEqual(len(self.routingTable._buckets[0]._contacts),
|
||||
lbrynet.dht.constants.k, 'Bucket should have k contacts; '
|
||||
'expected %d got %d' %
|
||||
(lbrynet.dht.constants.k,
|
||||
len(self.routingTable._buckets[0]._contacts)))
|
||||
self.failIf(contact in self.routingTable._buckets[0]._contacts,
|
||||
'New contact should have been discarded (since RPC is faked in this test)')
|
||||
|
||||
def suite():
|
||||
suite = unittest.TestSuite()
|
||||
suite.addTest(unittest.makeSuite(TreeRoutingTableTest))
|
||||
return suite
|
||||
|
||||
if __name__ == '__main__':
|
||||
# If this module is executed from the commandline, run all its tests
|
||||
unittest.TextTestRunner().run(suite())
|
Loading…
Reference in a new issue