From daf556ca50790160c5cd048de698a851e8884757 Mon Sep 17 00:00:00 2001 From: Dmitri Bogomolov <4glitch@gmail.com> Date: Wed, 3 Oct 2018 18:42:12 +0300 Subject: [PATCH] Added tests for knownnodes: - import of pickled knownnodes; - default knownnodes if nothing imported; - knownnodes starvation (#1335), demanded changes in networkthread. --- src/bitmessagemain.py | 5 +-- src/network/networkthread.py | 15 +++++--- src/queues.py | 10 ++++-- src/tests/core.py | 70 +++++++++++++++++++++++++++++++++++- 4 files changed, 89 insertions(+), 11 deletions(-) diff --git a/src/bitmessagemain.py b/src/bitmessagemain.py index 9feaa2ee..419c591b 100755 --- a/src/bitmessagemain.py +++ b/src/bitmessagemain.py @@ -367,10 +367,11 @@ class Main: time.time() - state.last_api_response >= 30): self.stop() elif not state.enableGUI: - from tests import core - test_core_result = core.run() + from tests import core as test_core + test_core_result = test_core.run() state.enableGUI = True self.stop() + test_core.cleanup() sys.exit( 'Core tests failed!' if test_core_result.errors or test_core_result.failures diff --git a/src/network/networkthread.py b/src/network/networkthread.py index 5a709c8b..9ceb856b 100644 --- a/src/network/networkthread.py +++ b/src/network/networkthread.py @@ -1,11 +1,12 @@ import threading -from bmconfigparser import BMConfigParser +import network.asyncore_pollchoose as asyncore +import state from debug import logger from helper_threading import StoppableThread -import network.asyncore_pollchoose as asyncore from network.connectionpool import BMConnectionPool -import state +from queues import excQueue + class BMNetworkThread(threading.Thread, StoppableThread): def __init__(self): @@ -15,8 +16,12 @@ class BMNetworkThread(threading.Thread, StoppableThread): logger.info("init asyncore thread") def run(self): - while not self._stopped and state.shutdown == 0: - BMConnectionPool().loop() + try: + while not self._stopped and state.shutdown == 0: + BMConnectionPool().loop() + except Exception as e: + excQueue.put((self.name, e)) + raise def stopThread(self): super(BMNetworkThread, self).stopThread() diff --git a/src/queues.py b/src/queues.py index e8923dbd..7b6bbade 100644 --- a/src/queues.py +++ b/src/queues.py @@ -6,11 +6,15 @@ from multiqueue import MultiQueue workerQueue = Queue.Queue() UISignalQueue = Queue.Queue() addressGeneratorQueue = Queue.Queue() -# receiveDataThreads dump objects they hear on the network into this queue to be processed. +# receiveDataThreads dump objects they hear on the network into this +# queue to be processed. objectProcessorQueue = ObjectProcessorQueue() invQueue = MultiQueue() addrQueue = MultiQueue() portCheckerQueue = Queue.Queue() receiveDataQueue = Queue.Queue() -apiAddressGeneratorReturnQueue = Queue.Queue( - ) # The address generator thread uses this queue to get information back to the API thread. +# The address generator thread uses this queue to get information back +# to the API thread. +apiAddressGeneratorReturnQueue = Queue.Queue() +# Exceptions +excQueue = Queue.Queue() diff --git a/src/tests/core.py b/src/tests/core.py index 0f66754b..0a266555 100644 --- a/src/tests/core.py +++ b/src/tests/core.py @@ -3,11 +3,39 @@ Tests for core and those that do not work outside (because of import error for example) """ +import os +import pickle # nosec +import Queue import random # nosec import string +import time import unittest +import knownnodes +import state from helper_msgcoding import MsgEncode, MsgDecode +from queues import excQueue + +knownnodes_file = os.path.join(state.appdata, 'knownnodes.dat') + + +def pickle_knownnodes(): + now = time.time() + with open(knownnodes_file, 'wb') as dst: + pickle.dump({ + stream: { + state.Peer( + '%i.%i.%i.%i' % tuple([ + random.randint(1, 255) for i in range(4)]), + 8444): {'lastseen': now, 'rating': 0.1} + for i in range(1, 4) # 3 test nodes + } + for stream in range(1, 4) # 3 test streams + }, dst) + + +def cleanup(): + os.remove(knownnodes_file) class TestCore(unittest.TestCase): @@ -42,8 +70,48 @@ class TestCore(unittest.TestCase): self.assertEqual(msg_data['subject'], obj3e.subject) self.assertEqual(msg_data['body'], obj3e.body) + def _wipe_knownnodes(self): + with knownnodes.knownNodesLock: + knownnodes.knownNodes = {stream: {} for stream in range(1, 4)} + + def test_knownnodes_pickle(self): + """ensure that 3 nodes was imported for each stream""" + pickle_knownnodes() + self._wipe_knownnodes() + knownnodes.readKnownNodes() + for nodes in knownnodes.knownNodes.itervalues(): + self_count = n = 0 + for n, node in enumerate(nodes.itervalues()): + if node.get('self'): + self_count += 1 + self.assertEqual(n - self_count, 2) + + def test_knownnodes_default(self): + """test adding default knownnodes if nothing loaded""" + cleanup() + self._wipe_knownnodes() + knownnodes.readKnownNodes() + self.assertGreaterEqual( + len(knownnodes.knownNodes[1]), len(knownnodes.DEFAULT_NODES)) + + def test_0_cleaner(self): + """test knownnodes starvation leading to IndexError in Asyncore""" + for nodes in knownnodes.knownNodes.itervalues(): + for node in nodes.itervalues(): + node['lastseen'] -= 2419205 # older than 28 days + time.sleep(303) # singleCleaner wakes up every 5 min + while True: + try: + thread, exc = excQueue.get(block=False) + except Queue.Empty: + return + if thread == 'Asyncore' and isinstance(exc, IndexError): + self.fail("IndexError because of empty knownNodes!") + def run(): """Starts all tests defined in this module""" - suite = unittest.TestLoader().loadTestsFromTestCase(TestCore) + loader = unittest.TestLoader() + loader.sortTestMethodsUsing = None + suite = loader.loadTestsFromTestCase(TestCore) return unittest.TextTestRunner(verbosity=2).run(suite)