Twisted Trial: как протестировать MultiService с клиентом Reactor был нечист

Я унаследовал Twisted MultiService, к которому пытаюсь добавить тесты, но что бы я ни делал, я получаю ошибку DirtyReactorAggregateError. Служба подключается к серверу с именем twisted.application.internet.TCPClient. Я думаю, что ошибка связана с тем, что TCPClient не отключается, но я не уверен, как я должен его отключить. Как правильно протестировать Twisted Service с клиентом?

Вот тестовый пример:

from labrad.node import *
from twisted.trial import unittest
import os
from socket import gethostname

class NodeTestCase(unittest.TestCase):

    def setUp(self):
        import labrad
        name = os.environ.get('LABRADNODE', gethostname()) + '_test'
        self.node = Node(name,
            labrad.constants.MANAGER_HOST,
            labrad.constants.MANAGER_PORT)
        self.node.startService()
        #self.addCleanup(self.node.stopService)

    def test_nothing(self):
        self.assertEqual(3, 3)

    def tearDown(self):
        return self.node.stopService()

Вот сам сервис Node:

class Node(MultiService):
    """Parent Service that keeps the node running.

    If the manager is stopped or we lose the network connection,
    this service attempts to restart it so that we will come
    back online when the manager is back up.
    """
    reconnectDelay = 10

    def __init__(self, name, host, port):
        MultiService.__init__(self)
        self.name = name
        self.host = host
        self.port = port

    def startService(self):
        MultiService.startService(self)
        self.startConnection()

    def startConnection(self):
        """Attempt to start the node and connect to LabRAD."""
        print 'Connecting to %s:%d...' % (self.host, self.port)
        self.node = NodeServer(self.name, self.host, self.port)
        self.node.onStartup().addErrback(self._error)
        self.node.onShutdown().addCallbacks(self._disconnected, self._error)
        self.cxn = TCPClient(self.host, self.port, self.node)
        self.addService(self.cxn)

    def stopService(self):
        if hasattr(self, 'cxn'):
            d = defer.maybeDeferred(self.cxn.stopService)
            self.removeService(self.cxn)
            del self.cxn
            return defer.gatherResults([MultiService.stopService(self), d])
        else:
            return MultiService.stopService(self)

    def _disconnected(self, data):
        print 'Node disconnected from manager.'
        return self._reconnect()

    def _error(self, failure):
        r = failure.trap(UserError)
        if r == UserError:
            print "UserError found!"
            return None
        print failure.getErrorMessage()
        return self._reconnect()

    def _reconnect(self):
        """Clean up from the last run and reconnect."""
        ## hack: manually clearing the dispatcher...
        dispatcher.connections.clear()
        dispatcher.senders.clear()
        dispatcher._boundMethods.clear()
        ## end hack
        if hasattr(self, 'cxn'):
            self.removeService(self.cxn)
            del self.cxn
        reactor.callLater(self.reconnectDelay, self.startConnection)
        print 'Will try to reconnect in %d seconds...' % self.reconnectDelay

person Peter    schedule 20.08.2014    source источник


Ответы (2)


Вы должны реорганизовать свой сервис, чтобы он мог использовать что-то вроде MemoryReactor из twisted.test.proto_helpers (один общедоступный модуль в пакете twisted.test, хотя, надеюсь, в конечном итоге он выйдет из twisted.test).

Способ, которым вы используете MemoryReactor, заключается в том, чтобы передать его в свой код в качестве используемого реактора. Если затем вы хотите увидеть, что происходит, когда соединение установлено успешно, посмотрите на некоторые из его общедоступных атрибутов — tcpClients для connectTCP, tcpServers для listenTCP и т. д. Затем ваши тесты могут извлечь экземпляры Factory, которые были переданы connectTCP/listenTCP и т. д. и вызовите buildProtocol по ним и makeConnection по результату. Чтобы получить реализацию ITransport, вы можете использовать twisted.test.proto_helpers.StringTransportWithDisconnection. . Вы можете даже взглянуть на (частный API! будьте осторожны! он сломается без предупреждения! хотя он действительно должен быть public) twisted.test.iosim.IOPump для ретрансляции трафика между клиентами и серверами.

Если вам действительно нужно провести недетерминированное тестирование всей системы в реальных условиях, со всей сложностью и случайными несвязанными сбоями, которые это подразумевает, вот статья о полном отключении клиента и сервера.

person Glyph    schedule 20.08.2014
comment
Спасибо за комментарий. Я очень новичок в Twisted, поэтому я стараюсь избегать каких-либо серьезных рефакторингов, но если это то, что нужно, я разберусь. - person Peter; 21.08.2014
comment
[продолжение...] Я действительно наткнулся на эту статью во время поиска в Google и не мог понять, как заставить клиент TCP полностью закрыться; то есть я не мог понять, что именно мне нужно звонить, а что отложено ждать. Мне не удалось найти хорошую документацию по TCPService или Twisted Services в целом. (Я просмотрел всю trac/wiki, но, похоже, там ничего не было о сервисах, например.) Может быть, вы могли бы указать мне на какую-нибудь документацию? - person Peter; 21.08.2014
comment
Вы видели twistedmatrix.com/documents/current /ядро/как/ ? - person Glyph; 22.08.2014
comment
Не могли бы вы расширить свой совет по поводу MemoryReactor? В документации говорится: «Этот реактор на самом деле еще не делает много полезного». Он принимает попытки установки TCP-соединения, но они никогда не увенчаются успехом. Не похоже, чтобы это помогло ОП. - person Gareth Rees; 06.10.2015
comment
@GarethRees - хорошая мысль; Я добавил много слов. надеюсь, расширенный ответ будет вам полезен. - person Glyph; 08.10.2015

У меня была аналогичная проблема с попыткой протестировать экземпляр приложения. В итоге я создал базовый класс Python, который использовал методы setUp и tearDown для запуска/остановки приложения.

from twisted.application.app import startApplication
from twisted.application.service import IServiceCollection
from twisted.internet.defer import inlineCallbacks
from twisted.trial import unittest

class MyTest(unittest.TestCase):

    def setUp(self):
        startApplication(self.app, save=False)

    @inlineCallbacks
    def tearDown(self):
        sc = self.app.getComponent(IServiceCollection)
        yield sc.stopService()
person Chris    schedule 23.01.2016