1019 lines
33 KiB
Python
1019 lines
33 KiB
Python
# Copyright (c) Twisted Matrix Laboratories.
|
|
# See LICENSE for details.
|
|
|
|
"""
|
|
Tests for L{twisted.application} and its interaction with
|
|
L{twisted.persisted.sob}.
|
|
"""
|
|
|
|
|
|
import copy
|
|
import os
|
|
import pickle
|
|
from io import StringIO
|
|
|
|
try:
|
|
import asyncio
|
|
except ImportError:
|
|
asyncio = None # type: ignore[assignment]
|
|
|
|
from unittest import skipIf
|
|
|
|
from twisted.application import app, internet, reactors, service
|
|
from twisted.application.internet import backoffPolicy
|
|
from twisted.internet import defer, interfaces, protocol, reactor
|
|
from twisted.persisted import sob
|
|
from twisted.plugins import twisted_reactors
|
|
from twisted.protocols import basic, wire
|
|
from twisted.python import usage
|
|
from twisted.python.runtime import platformType
|
|
from twisted.python.test.modules_helpers import TwistedModulesMixin
|
|
from twisted.test.proto_helpers import MemoryReactor
|
|
from twisted.trial.unittest import SkipTest, TestCase
|
|
|
|
|
|
class Dummy:
|
|
processName = None
|
|
|
|
|
|
class ServiceTests(TestCase):
|
|
def testName(self):
|
|
s = service.Service()
|
|
s.setName("hello")
|
|
self.assertEqual(s.name, "hello")
|
|
|
|
def testParent(self):
|
|
s = service.Service()
|
|
p = service.MultiService()
|
|
s.setServiceParent(p)
|
|
self.assertEqual(list(p), [s])
|
|
self.assertEqual(s.parent, p)
|
|
|
|
def testApplicationAsParent(self):
|
|
s = service.Service()
|
|
p = service.Application("")
|
|
s.setServiceParent(p)
|
|
self.assertEqual(list(service.IServiceCollection(p)), [s])
|
|
self.assertEqual(s.parent, service.IServiceCollection(p))
|
|
|
|
def testNamedChild(self):
|
|
s = service.Service()
|
|
p = service.MultiService()
|
|
s.setName("hello")
|
|
s.setServiceParent(p)
|
|
self.assertEqual(list(p), [s])
|
|
self.assertEqual(s.parent, p)
|
|
self.assertEqual(p.getServiceNamed("hello"), s)
|
|
|
|
def testDoublyNamedChild(self):
|
|
s = service.Service()
|
|
p = service.MultiService()
|
|
s.setName("hello")
|
|
s.setServiceParent(p)
|
|
self.assertRaises(RuntimeError, s.setName, "lala")
|
|
|
|
def testDuplicateNamedChild(self):
|
|
s = service.Service()
|
|
p = service.MultiService()
|
|
s.setName("hello")
|
|
s.setServiceParent(p)
|
|
s = service.Service()
|
|
s.setName("hello")
|
|
self.assertRaises(RuntimeError, s.setServiceParent, p)
|
|
|
|
def testDisowning(self):
|
|
s = service.Service()
|
|
p = service.MultiService()
|
|
s.setServiceParent(p)
|
|
self.assertEqual(list(p), [s])
|
|
self.assertEqual(s.parent, p)
|
|
s.disownServiceParent()
|
|
self.assertEqual(list(p), [])
|
|
self.assertIsNone(s.parent)
|
|
|
|
def testRunning(self):
|
|
s = service.Service()
|
|
self.assertFalse(s.running)
|
|
s.startService()
|
|
self.assertTrue(s.running)
|
|
s.stopService()
|
|
self.assertFalse(s.running)
|
|
|
|
def testRunningChildren1(self):
|
|
s = service.Service()
|
|
p = service.MultiService()
|
|
s.setServiceParent(p)
|
|
self.assertFalse(s.running)
|
|
self.assertFalse(p.running)
|
|
p.startService()
|
|
self.assertTrue(s.running)
|
|
self.assertTrue(p.running)
|
|
p.stopService()
|
|
self.assertFalse(s.running)
|
|
self.assertFalse(p.running)
|
|
|
|
def testRunningChildren2(self):
|
|
s = service.Service()
|
|
|
|
def checkRunning():
|
|
self.assertTrue(s.running)
|
|
|
|
t = service.Service()
|
|
t.stopService = checkRunning
|
|
t.startService = checkRunning
|
|
p = service.MultiService()
|
|
s.setServiceParent(p)
|
|
t.setServiceParent(p)
|
|
p.startService()
|
|
p.stopService()
|
|
|
|
def testAddingIntoRunning(self):
|
|
p = service.MultiService()
|
|
p.startService()
|
|
s = service.Service()
|
|
self.assertFalse(s.running)
|
|
s.setServiceParent(p)
|
|
self.assertTrue(s.running)
|
|
s.disownServiceParent()
|
|
self.assertFalse(s.running)
|
|
|
|
def testPrivileged(self):
|
|
s = service.Service()
|
|
|
|
def pss():
|
|
s.privilegedStarted = 1
|
|
|
|
s.privilegedStartService = pss
|
|
s1 = service.Service()
|
|
p = service.MultiService()
|
|
s.setServiceParent(p)
|
|
s1.setServiceParent(p)
|
|
p.privilegedStartService()
|
|
self.assertTrue(s.privilegedStarted)
|
|
|
|
def testCopying(self):
|
|
s = service.Service()
|
|
s.startService()
|
|
s1 = copy.copy(s)
|
|
self.assertFalse(s1.running)
|
|
self.assertTrue(s.running)
|
|
|
|
|
|
if hasattr(os, "getuid"):
|
|
curuid = os.getuid()
|
|
curgid = os.getgid()
|
|
else:
|
|
curuid = curgid = 0
|
|
|
|
|
|
class ProcessTests(TestCase):
|
|
def testID(self):
|
|
p = service.Process(5, 6)
|
|
self.assertEqual(p.uid, 5)
|
|
self.assertEqual(p.gid, 6)
|
|
|
|
def testDefaults(self):
|
|
p = service.Process(5)
|
|
self.assertEqual(p.uid, 5)
|
|
self.assertIsNone(p.gid)
|
|
p = service.Process(gid=5)
|
|
self.assertIsNone(p.uid)
|
|
self.assertEqual(p.gid, 5)
|
|
p = service.Process()
|
|
self.assertIsNone(p.uid)
|
|
self.assertIsNone(p.gid)
|
|
|
|
def testProcessName(self):
|
|
p = service.Process()
|
|
self.assertIsNone(p.processName)
|
|
p.processName = "hello"
|
|
self.assertEqual(p.processName, "hello")
|
|
|
|
|
|
class InterfacesTests(TestCase):
|
|
def testService(self):
|
|
self.assertTrue(service.IService.providedBy(service.Service()))
|
|
|
|
def testMultiService(self):
|
|
self.assertTrue(service.IService.providedBy(service.MultiService()))
|
|
self.assertTrue(service.IServiceCollection.providedBy(service.MultiService()))
|
|
|
|
def testProcess(self):
|
|
self.assertTrue(service.IProcess.providedBy(service.Process()))
|
|
|
|
|
|
class ApplicationTests(TestCase):
|
|
def testConstructor(self):
|
|
service.Application("hello")
|
|
service.Application("hello", 5)
|
|
service.Application("hello", 5, 6)
|
|
|
|
def testProcessComponent(self):
|
|
a = service.Application("hello")
|
|
self.assertIsNone(service.IProcess(a).uid)
|
|
self.assertIsNone(service.IProcess(a).gid)
|
|
a = service.Application("hello", 5)
|
|
self.assertEqual(service.IProcess(a).uid, 5)
|
|
self.assertIsNone(service.IProcess(a).gid)
|
|
a = service.Application("hello", 5, 6)
|
|
self.assertEqual(service.IProcess(a).uid, 5)
|
|
self.assertEqual(service.IProcess(a).gid, 6)
|
|
|
|
def testServiceComponent(self):
|
|
a = service.Application("hello")
|
|
self.assertIs(service.IService(a), service.IServiceCollection(a))
|
|
self.assertEqual(service.IService(a).name, "hello")
|
|
self.assertIsNone(service.IService(a).parent)
|
|
|
|
def testPersistableComponent(self):
|
|
a = service.Application("hello")
|
|
p = sob.IPersistable(a)
|
|
self.assertEqual(p.style, "pickle")
|
|
self.assertEqual(p.name, "hello")
|
|
self.assertIs(p.original, a)
|
|
|
|
|
|
class LoadingTests(TestCase):
|
|
def test_simpleStoreAndLoad(self):
|
|
a = service.Application("hello")
|
|
p = sob.IPersistable(a)
|
|
for style in "source pickle".split():
|
|
p.setStyle(style)
|
|
p.save()
|
|
a1 = service.loadApplication("hello.ta" + style[0], style)
|
|
self.assertEqual(service.IService(a1).name, "hello")
|
|
with open("hello.tac", "w") as f:
|
|
f.writelines(
|
|
[
|
|
"from twisted.application import service\n",
|
|
"application = service.Application('hello')\n",
|
|
]
|
|
)
|
|
a1 = service.loadApplication("hello.tac", "python")
|
|
self.assertEqual(service.IService(a1).name, "hello")
|
|
|
|
|
|
class AppSupportTests(TestCase):
|
|
def testPassphrase(self):
|
|
self.assertIsNone(app.getPassphrase(0))
|
|
|
|
def testLoadApplication(self):
|
|
"""
|
|
Test loading an application file in different dump format.
|
|
"""
|
|
a = service.Application("hello")
|
|
baseconfig = {"file": None, "source": None, "python": None}
|
|
for style in "source pickle".split():
|
|
config = baseconfig.copy()
|
|
config[{"pickle": "file"}.get(style, style)] = "helloapplication"
|
|
sob.IPersistable(a).setStyle(style)
|
|
sob.IPersistable(a).save(filename="helloapplication")
|
|
a1 = app.getApplication(config, None)
|
|
self.assertEqual(service.IService(a1).name, "hello")
|
|
config = baseconfig.copy()
|
|
config["python"] = "helloapplication"
|
|
with open("helloapplication", "w") as f:
|
|
f.writelines(
|
|
[
|
|
"from twisted.application import service\n",
|
|
"application = service.Application('hello')\n",
|
|
]
|
|
)
|
|
a1 = app.getApplication(config, None)
|
|
self.assertEqual(service.IService(a1).name, "hello")
|
|
|
|
def test_convertStyle(self):
|
|
appl = service.Application("lala")
|
|
for instyle in "source pickle".split():
|
|
for outstyle in "source pickle".split():
|
|
sob.IPersistable(appl).setStyle(instyle)
|
|
sob.IPersistable(appl).save(filename="converttest")
|
|
app.convertStyle(
|
|
"converttest", instyle, None, "converttest.out", outstyle, 0
|
|
)
|
|
appl2 = service.loadApplication("converttest.out", outstyle)
|
|
self.assertEqual(service.IService(appl2).name, "lala")
|
|
|
|
def test_startApplication(self):
|
|
appl = service.Application("lala")
|
|
app.startApplication(appl, 0)
|
|
self.assertTrue(service.IService(appl).running)
|
|
|
|
|
|
class Foo(basic.LineReceiver):
|
|
def connectionMade(self):
|
|
self.transport.write(b"lalala\r\n")
|
|
|
|
def lineReceived(self, line):
|
|
self.factory.line = line
|
|
self.transport.loseConnection()
|
|
|
|
def connectionLost(self, reason):
|
|
self.factory.d.callback(self.factory.line)
|
|
|
|
|
|
class DummyApp:
|
|
processName = None
|
|
|
|
def addService(self, service):
|
|
self.services[service.name] = service
|
|
|
|
def removeService(self, service):
|
|
del self.services[service.name]
|
|
|
|
|
|
class TimerTarget:
|
|
def __init__(self):
|
|
self.l = []
|
|
|
|
def append(self, what):
|
|
self.l.append(what)
|
|
|
|
|
|
class TestEcho(wire.Echo):
|
|
def connectionLost(self, reason):
|
|
self.d.callback(True)
|
|
|
|
|
|
class InternetTests(TestCase):
|
|
def testTCP(self):
|
|
s = service.MultiService()
|
|
s.startService()
|
|
factory = protocol.ServerFactory()
|
|
factory.protocol = TestEcho
|
|
TestEcho.d = defer.Deferred()
|
|
t = internet.TCPServer(0, factory)
|
|
t.setServiceParent(s)
|
|
num = t._port.getHost().port
|
|
factory = protocol.ClientFactory()
|
|
factory.d = defer.Deferred()
|
|
factory.protocol = Foo
|
|
factory.line = None
|
|
internet.TCPClient("127.0.0.1", num, factory).setServiceParent(s)
|
|
factory.d.addCallback(self.assertEqual, b"lalala")
|
|
factory.d.addCallback(lambda x: s.stopService())
|
|
factory.d.addCallback(lambda x: TestEcho.d)
|
|
return factory.d
|
|
|
|
def test_UDP(self):
|
|
"""
|
|
Test L{internet.UDPServer} with a random port: starting the service
|
|
should give it valid port, and stopService should free it so that we
|
|
can start a server on the same port again.
|
|
"""
|
|
if not interfaces.IReactorUDP(reactor, None):
|
|
raise SkipTest("This reactor does not support UDP sockets")
|
|
p = protocol.DatagramProtocol()
|
|
t = internet.UDPServer(0, p)
|
|
t.startService()
|
|
num = t._port.getHost().port
|
|
self.assertNotEqual(num, 0)
|
|
|
|
def onStop(ignored):
|
|
t = internet.UDPServer(num, p)
|
|
t.startService()
|
|
return t.stopService()
|
|
|
|
return defer.maybeDeferred(t.stopService).addCallback(onStop)
|
|
|
|
def testPrivileged(self):
|
|
factory = protocol.ServerFactory()
|
|
factory.protocol = TestEcho
|
|
TestEcho.d = defer.Deferred()
|
|
t = internet.TCPServer(0, factory)
|
|
t.privileged = 1
|
|
t.privilegedStartService()
|
|
num = t._port.getHost().port
|
|
factory = protocol.ClientFactory()
|
|
factory.d = defer.Deferred()
|
|
factory.protocol = Foo
|
|
factory.line = None
|
|
c = internet.TCPClient("127.0.0.1", num, factory)
|
|
c.startService()
|
|
factory.d.addCallback(self.assertEqual, b"lalala")
|
|
factory.d.addCallback(lambda x: c.stopService())
|
|
factory.d.addCallback(lambda x: t.stopService())
|
|
factory.d.addCallback(lambda x: TestEcho.d)
|
|
return factory.d
|
|
|
|
def testConnectionGettingRefused(self):
|
|
factory = protocol.ServerFactory()
|
|
factory.protocol = wire.Echo
|
|
t = internet.TCPServer(0, factory)
|
|
t.startService()
|
|
num = t._port.getHost().port
|
|
t.stopService()
|
|
d = defer.Deferred()
|
|
factory = protocol.ClientFactory()
|
|
factory.clientConnectionFailed = lambda *args: d.callback(None)
|
|
c = internet.TCPClient("127.0.0.1", num, factory)
|
|
c.startService()
|
|
return d
|
|
|
|
@skipIf(
|
|
not interfaces.IReactorUNIX(reactor, None),
|
|
"This reactor does not support UNIX domain sockets",
|
|
)
|
|
def testUNIX(self):
|
|
# FIXME: This test is far too dense. It needs comments.
|
|
# -- spiv, 2004-11-07
|
|
s = service.MultiService()
|
|
s.startService()
|
|
factory = protocol.ServerFactory()
|
|
factory.protocol = TestEcho
|
|
TestEcho.d = defer.Deferred()
|
|
t = internet.UNIXServer("echo.skt", factory)
|
|
t.setServiceParent(s)
|
|
factory = protocol.ClientFactory()
|
|
factory.protocol = Foo
|
|
factory.d = defer.Deferred()
|
|
factory.line = None
|
|
internet.UNIXClient("echo.skt", factory).setServiceParent(s)
|
|
factory.d.addCallback(self.assertEqual, b"lalala")
|
|
factory.d.addCallback(lambda x: s.stopService())
|
|
factory.d.addCallback(lambda x: TestEcho.d)
|
|
factory.d.addCallback(self._cbTestUnix, factory, s)
|
|
return factory.d
|
|
|
|
def _cbTestUnix(self, ignored, factory, s):
|
|
TestEcho.d = defer.Deferred()
|
|
factory.line = None
|
|
factory.d = defer.Deferred()
|
|
s.startService()
|
|
factory.d.addCallback(self.assertEqual, b"lalala")
|
|
factory.d.addCallback(lambda x: s.stopService())
|
|
factory.d.addCallback(lambda x: TestEcho.d)
|
|
return factory.d
|
|
|
|
@skipIf(
|
|
not interfaces.IReactorUNIX(reactor, None),
|
|
"This reactor does not support UNIX domain sockets",
|
|
)
|
|
def testVolatile(self):
|
|
factory = protocol.ServerFactory()
|
|
factory.protocol = wire.Echo
|
|
t = internet.UNIXServer("echo.skt", factory)
|
|
t.startService()
|
|
self.failIfIdentical(t._port, None)
|
|
t1 = copy.copy(t)
|
|
self.assertIsNone(t1._port)
|
|
t.stopService()
|
|
self.assertIsNone(t._port)
|
|
self.assertFalse(t.running)
|
|
|
|
factory = protocol.ClientFactory()
|
|
factory.protocol = wire.Echo
|
|
t = internet.UNIXClient("echo.skt", factory)
|
|
t.startService()
|
|
self.failIfIdentical(t._connection, None)
|
|
t1 = copy.copy(t)
|
|
self.assertIsNone(t1._connection)
|
|
t.stopService()
|
|
self.assertIsNone(t._connection)
|
|
self.assertFalse(t.running)
|
|
|
|
@skipIf(
|
|
not interfaces.IReactorUNIX(reactor, None),
|
|
"This reactor does not support UNIX domain sockets",
|
|
)
|
|
def testStoppingServer(self):
|
|
factory = protocol.ServerFactory()
|
|
factory.protocol = wire.Echo
|
|
t = internet.UNIXServer("echo.skt", factory)
|
|
t.startService()
|
|
t.stopService()
|
|
self.assertFalse(t.running)
|
|
factory = protocol.ClientFactory()
|
|
d = defer.Deferred()
|
|
factory.clientConnectionFailed = lambda *args: d.callback(None)
|
|
reactor.connectUNIX("echo.skt", factory)
|
|
return d
|
|
|
|
def testPickledTimer(self):
|
|
target = TimerTarget()
|
|
t0 = internet.TimerService(1, target.append, "hello")
|
|
t0.startService()
|
|
s = pickle.dumps(t0)
|
|
t0.stopService()
|
|
|
|
t = pickle.loads(s)
|
|
self.assertFalse(t.running)
|
|
|
|
def testBrokenTimer(self):
|
|
d = defer.Deferred()
|
|
t = internet.TimerService(1, lambda: 1 // 0)
|
|
oldFailed = t._failed
|
|
|
|
def _failed(why):
|
|
oldFailed(why)
|
|
d.callback(None)
|
|
|
|
t._failed = _failed
|
|
t.startService()
|
|
d.addCallback(lambda x: t.stopService)
|
|
d.addCallback(
|
|
lambda x: self.assertEqual(
|
|
[ZeroDivisionError],
|
|
[o.value.__class__ for o in self.flushLoggedErrors(ZeroDivisionError)],
|
|
)
|
|
)
|
|
return d
|
|
|
|
def test_everythingThere(self):
|
|
"""
|
|
L{twisted.application.internet} dynamically defines a set of
|
|
L{service.Service} subclasses that in general have corresponding
|
|
reactor.listenXXX or reactor.connectXXX calls.
|
|
"""
|
|
trans = ["TCP", "UNIX", "SSL", "UDP", "UNIXDatagram", "Multicast"]
|
|
for tran in trans[:]:
|
|
if not getattr(interfaces, "IReactor" + tran)(reactor, None):
|
|
trans.remove(tran)
|
|
for tran in trans:
|
|
for side in ["Server", "Client"]:
|
|
if tran == "Multicast" and side == "Client":
|
|
continue
|
|
if tran == "UDP" and side == "Client":
|
|
continue
|
|
self.assertTrue(hasattr(internet, tran + side))
|
|
method = getattr(internet, tran + side).method
|
|
prefix = {"Server": "listen", "Client": "connect"}[side]
|
|
self.assertTrue(
|
|
hasattr(reactor, prefix + method)
|
|
or (prefix == "connect" and method == "UDP")
|
|
)
|
|
o = getattr(internet, tran + side)()
|
|
self.assertEqual(service.IService(o), o)
|
|
|
|
def test_importAll(self):
|
|
"""
|
|
L{twisted.application.internet} dynamically defines L{service.Service}
|
|
subclasses. This test ensures that the subclasses exposed by C{__all__}
|
|
are valid attributes of the module.
|
|
"""
|
|
for cls in internet.__all__:
|
|
self.assertTrue(
|
|
hasattr(internet, cls),
|
|
f"{cls} not importable from twisted.application.internet",
|
|
)
|
|
|
|
def test_reactorParametrizationInServer(self):
|
|
"""
|
|
L{internet._AbstractServer} supports a C{reactor} keyword argument
|
|
that can be used to parametrize the reactor used to listen for
|
|
connections.
|
|
"""
|
|
reactor = MemoryReactor()
|
|
|
|
factory = object()
|
|
t = internet.TCPServer(1234, factory, reactor=reactor)
|
|
t.startService()
|
|
self.assertEqual(reactor.tcpServers.pop()[:2], (1234, factory))
|
|
|
|
def test_reactorParametrizationInClient(self):
|
|
"""
|
|
L{internet._AbstractClient} supports a C{reactor} keyword arguments
|
|
that can be used to parametrize the reactor used to create new client
|
|
connections.
|
|
"""
|
|
reactor = MemoryReactor()
|
|
|
|
factory = protocol.ClientFactory()
|
|
t = internet.TCPClient("127.0.0.1", 1234, factory, reactor=reactor)
|
|
t.startService()
|
|
self.assertEqual(reactor.tcpClients.pop()[:3], ("127.0.0.1", 1234, factory))
|
|
|
|
def test_reactorParametrizationInServerMultipleStart(self):
|
|
"""
|
|
Like L{test_reactorParametrizationInServer}, but stop and restart the
|
|
service and check that the given reactor is still used.
|
|
"""
|
|
reactor = MemoryReactor()
|
|
|
|
factory = protocol.Factory()
|
|
t = internet.TCPServer(1234, factory, reactor=reactor)
|
|
t.startService()
|
|
self.assertEqual(reactor.tcpServers.pop()[:2], (1234, factory))
|
|
t.stopService()
|
|
t.startService()
|
|
self.assertEqual(reactor.tcpServers.pop()[:2], (1234, factory))
|
|
|
|
def test_reactorParametrizationInClientMultipleStart(self):
|
|
"""
|
|
Like L{test_reactorParametrizationInClient}, but stop and restart the
|
|
service and check that the given reactor is still used.
|
|
"""
|
|
reactor = MemoryReactor()
|
|
|
|
factory = protocol.ClientFactory()
|
|
t = internet.TCPClient("127.0.0.1", 1234, factory, reactor=reactor)
|
|
t.startService()
|
|
self.assertEqual(reactor.tcpClients.pop()[:3], ("127.0.0.1", 1234, factory))
|
|
t.stopService()
|
|
t.startService()
|
|
self.assertEqual(reactor.tcpClients.pop()[:3], ("127.0.0.1", 1234, factory))
|
|
|
|
|
|
class TimerBasicTests(TestCase):
|
|
def testTimerRuns(self):
|
|
d = defer.Deferred()
|
|
self.t = internet.TimerService(1, d.callback, "hello")
|
|
self.t.startService()
|
|
d.addCallback(self.assertEqual, "hello")
|
|
d.addCallback(lambda x: self.t.stopService())
|
|
d.addCallback(lambda x: self.assertFalse(self.t.running))
|
|
return d
|
|
|
|
def tearDown(self):
|
|
return self.t.stopService()
|
|
|
|
def testTimerRestart(self):
|
|
# restart the same TimerService
|
|
d1 = defer.Deferred()
|
|
d2 = defer.Deferred()
|
|
work = [(d2, "bar"), (d1, "foo")]
|
|
|
|
def trigger():
|
|
d, arg = work.pop()
|
|
d.callback(arg)
|
|
|
|
self.t = internet.TimerService(1, trigger)
|
|
self.t.startService()
|
|
|
|
def onFirstResult(result):
|
|
self.assertEqual(result, "foo")
|
|
return self.t.stopService()
|
|
|
|
def onFirstStop(ignored):
|
|
self.assertFalse(self.t.running)
|
|
self.t.startService()
|
|
return d2
|
|
|
|
def onSecondResult(result):
|
|
self.assertEqual(result, "bar")
|
|
self.t.stopService()
|
|
|
|
d1.addCallback(onFirstResult)
|
|
d1.addCallback(onFirstStop)
|
|
d1.addCallback(onSecondResult)
|
|
return d1
|
|
|
|
def testTimerLoops(self):
|
|
l = []
|
|
|
|
def trigger(data, number, d):
|
|
l.append(data)
|
|
if len(l) == number:
|
|
d.callback(l)
|
|
|
|
d = defer.Deferred()
|
|
self.t = internet.TimerService(0.01, trigger, "hello", 10, d)
|
|
self.t.startService()
|
|
d.addCallback(self.assertEqual, ["hello"] * 10)
|
|
d.addCallback(lambda x: self.t.stopService())
|
|
return d
|
|
|
|
|
|
class FakeReactor(reactors.Reactor):
|
|
"""
|
|
A fake reactor with a hooked install method.
|
|
"""
|
|
|
|
def __init__(self, install, *args, **kwargs):
|
|
"""
|
|
@param install: any callable that will be used as install method.
|
|
@type install: C{callable}
|
|
"""
|
|
reactors.Reactor.__init__(self, *args, **kwargs)
|
|
self.install = install
|
|
|
|
|
|
class PluggableReactorTests(TwistedModulesMixin, TestCase):
|
|
"""
|
|
Tests for the reactor discovery/inspection APIs.
|
|
"""
|
|
|
|
def setUp(self):
|
|
"""
|
|
Override the L{reactors.getPlugins} function, normally bound to
|
|
L{twisted.plugin.getPlugins}, in order to control which
|
|
L{IReactorInstaller} plugins are seen as available.
|
|
|
|
C{self.pluginResults} can be customized and will be used as the
|
|
result of calls to C{reactors.getPlugins}.
|
|
"""
|
|
self.pluginCalls = []
|
|
self.pluginResults = []
|
|
self.originalFunction = reactors.getPlugins
|
|
reactors.getPlugins = self._getPlugins
|
|
|
|
def tearDown(self):
|
|
"""
|
|
Restore the original L{reactors.getPlugins}.
|
|
"""
|
|
reactors.getPlugins = self.originalFunction
|
|
|
|
def _getPlugins(self, interface, package=None):
|
|
"""
|
|
Stand-in for the real getPlugins method which records its arguments
|
|
and returns a fixed result.
|
|
"""
|
|
self.pluginCalls.append((interface, package))
|
|
return list(self.pluginResults)
|
|
|
|
def test_getPluginReactorTypes(self):
|
|
"""
|
|
Test that reactor plugins are returned from L{getReactorTypes}
|
|
"""
|
|
name = "fakereactortest"
|
|
package = __name__ + ".fakereactor"
|
|
description = "description"
|
|
self.pluginResults = [reactors.Reactor(name, package, description)]
|
|
reactorTypes = reactors.getReactorTypes()
|
|
|
|
self.assertEqual(self.pluginCalls, [(reactors.IReactorInstaller, None)])
|
|
|
|
for r in reactorTypes:
|
|
if r.shortName == name:
|
|
self.assertEqual(r.description, description)
|
|
break
|
|
else:
|
|
self.fail("Reactor plugin not present in getReactorTypes() result")
|
|
|
|
def test_reactorInstallation(self):
|
|
"""
|
|
Test that L{reactors.Reactor.install} loads the correct module and
|
|
calls its install attribute.
|
|
"""
|
|
installed = []
|
|
|
|
def install():
|
|
installed.append(True)
|
|
|
|
fakeReactor = FakeReactor(install, "fakereactortest", __name__, "described")
|
|
modules = {"fakereactortest": fakeReactor}
|
|
self.replaceSysModules(modules)
|
|
installer = reactors.Reactor("fakereactor", "fakereactortest", "described")
|
|
installer.install()
|
|
self.assertEqual(installed, [True])
|
|
|
|
def test_installReactor(self):
|
|
"""
|
|
Test that the L{reactors.installReactor} function correctly installs
|
|
the specified reactor.
|
|
"""
|
|
installed = []
|
|
|
|
def install():
|
|
installed.append(True)
|
|
|
|
name = "fakereactortest"
|
|
package = __name__
|
|
description = "description"
|
|
self.pluginResults = [FakeReactor(install, name, package, description)]
|
|
reactors.installReactor(name)
|
|
self.assertEqual(installed, [True])
|
|
|
|
def test_installReactorReturnsReactor(self):
|
|
"""
|
|
Test that the L{reactors.installReactor} function correctly returns
|
|
the installed reactor.
|
|
"""
|
|
reactor = object()
|
|
|
|
def install():
|
|
from twisted import internet
|
|
|
|
self.patch(internet, "reactor", reactor)
|
|
|
|
name = "fakereactortest"
|
|
package = __name__
|
|
description = "description"
|
|
self.pluginResults = [FakeReactor(install, name, package, description)]
|
|
installed = reactors.installReactor(name)
|
|
self.assertIs(installed, reactor)
|
|
|
|
def test_installReactorMultiplePlugins(self):
|
|
"""
|
|
Test that the L{reactors.installReactor} function correctly installs
|
|
the specified reactor when there are multiple reactor plugins.
|
|
"""
|
|
installed = []
|
|
|
|
def install():
|
|
installed.append(True)
|
|
|
|
name = "fakereactortest"
|
|
package = __name__
|
|
description = "description"
|
|
fakeReactor = FakeReactor(install, name, package, description)
|
|
otherReactor = FakeReactor(lambda: None, "otherreactor", package, description)
|
|
self.pluginResults = [otherReactor, fakeReactor]
|
|
reactors.installReactor(name)
|
|
self.assertEqual(installed, [True])
|
|
|
|
def test_installNonExistentReactor(self):
|
|
"""
|
|
Test that L{reactors.installReactor} raises L{reactors.NoSuchReactor}
|
|
when asked to install a reactor which it cannot find.
|
|
"""
|
|
self.pluginResults = []
|
|
self.assertRaises(
|
|
reactors.NoSuchReactor, reactors.installReactor, "somereactor"
|
|
)
|
|
|
|
def test_installNotAvailableReactor(self):
|
|
"""
|
|
Test that L{reactors.installReactor} raises an exception when asked to
|
|
install a reactor which doesn't work in this environment.
|
|
"""
|
|
|
|
def install():
|
|
raise ImportError("Missing foo bar")
|
|
|
|
name = "fakereactortest"
|
|
package = __name__
|
|
description = "description"
|
|
self.pluginResults = [FakeReactor(install, name, package, description)]
|
|
self.assertRaises(ImportError, reactors.installReactor, name)
|
|
|
|
def test_reactorSelectionMixin(self):
|
|
"""
|
|
Test that the reactor selected is installed as soon as possible, ie
|
|
when the option is parsed.
|
|
"""
|
|
executed = []
|
|
INSTALL_EVENT = "reactor installed"
|
|
SUBCOMMAND_EVENT = "subcommands loaded"
|
|
|
|
class ReactorSelectionOptions(usage.Options, app.ReactorSelectionMixin):
|
|
@property
|
|
def subCommands(self):
|
|
executed.append(SUBCOMMAND_EVENT)
|
|
return [("subcommand", None, lambda: self, "test subcommand")]
|
|
|
|
def install():
|
|
executed.append(INSTALL_EVENT)
|
|
|
|
self.pluginResults = [
|
|
FakeReactor(install, "fakereactortest", __name__, "described")
|
|
]
|
|
|
|
options = ReactorSelectionOptions()
|
|
options.parseOptions(["--reactor", "fakereactortest", "subcommand"])
|
|
self.assertEqual(executed[0], INSTALL_EVENT)
|
|
self.assertEqual(executed.count(INSTALL_EVENT), 1)
|
|
self.assertEqual(options["reactor"], "fakereactortest")
|
|
|
|
def test_reactorSelectionMixinNonExistent(self):
|
|
"""
|
|
Test that the usage mixin exits when trying to use a non existent
|
|
reactor (the name not matching to any reactor), giving an error
|
|
message.
|
|
"""
|
|
|
|
class ReactorSelectionOptions(usage.Options, app.ReactorSelectionMixin):
|
|
pass
|
|
|
|
self.pluginResults = []
|
|
|
|
options = ReactorSelectionOptions()
|
|
options.messageOutput = StringIO()
|
|
e = self.assertRaises(
|
|
usage.UsageError,
|
|
options.parseOptions,
|
|
["--reactor", "fakereactortest", "subcommand"],
|
|
)
|
|
self.assertIn("fakereactortest", e.args[0])
|
|
self.assertIn("help-reactors", e.args[0])
|
|
|
|
def test_reactorSelectionMixinNotAvailable(self):
|
|
"""
|
|
Test that the usage mixin exits when trying to use a reactor not
|
|
available (the reactor raises an error at installation), giving an
|
|
error message.
|
|
"""
|
|
|
|
class ReactorSelectionOptions(usage.Options, app.ReactorSelectionMixin):
|
|
pass
|
|
|
|
message = "Missing foo bar"
|
|
|
|
def install():
|
|
raise ImportError(message)
|
|
|
|
name = "fakereactortest"
|
|
package = __name__
|
|
description = "description"
|
|
self.pluginResults = [FakeReactor(install, name, package, description)]
|
|
|
|
options = ReactorSelectionOptions()
|
|
options.messageOutput = StringIO()
|
|
e = self.assertRaises(
|
|
usage.UsageError,
|
|
options.parseOptions,
|
|
["--reactor", "fakereactortest", "subcommand"],
|
|
)
|
|
self.assertIn(message, e.args[0])
|
|
self.assertIn("help-reactors", e.args[0])
|
|
|
|
|
|
class HelpReactorsTests(TestCase):
|
|
"""
|
|
--help-reactors lists the available reactors
|
|
"""
|
|
|
|
def setUp(self):
|
|
"""
|
|
Get the text from --help-reactors
|
|
"""
|
|
self.options = app.ReactorSelectionMixin()
|
|
self.options.messageOutput = StringIO()
|
|
self.assertRaises(SystemExit, self.options.opt_help_reactors)
|
|
self.message = self.options.messageOutput.getvalue()
|
|
|
|
@skipIf(asyncio, "Not applicable, asyncio is available")
|
|
def test_lacksAsyncIO(self):
|
|
"""
|
|
--help-reactors should NOT display the asyncio reactor on Python < 3.4
|
|
"""
|
|
self.assertIn(twisted_reactors.asyncio.description, self.message)
|
|
self.assertIn("!" + twisted_reactors.asyncio.shortName, self.message)
|
|
|
|
@skipIf(not asyncio, "asyncio library not available")
|
|
def test_hasAsyncIO(self):
|
|
"""
|
|
--help-reactors should display the asyncio reactor on Python >= 3.4
|
|
"""
|
|
self.assertIn(twisted_reactors.asyncio.description, self.message)
|
|
self.assertNotIn("!" + twisted_reactors.asyncio.shortName, self.message)
|
|
|
|
@skipIf(platformType != "win32", "Test only applicable on Windows")
|
|
def test_iocpWin32(self):
|
|
"""
|
|
--help-reactors should display the iocp reactor on Windows
|
|
"""
|
|
self.assertIn(twisted_reactors.iocp.description, self.message)
|
|
self.assertNotIn("!" + twisted_reactors.iocp.shortName, self.message)
|
|
|
|
@skipIf(platformType == "win32", "Test not applicable on Windows")
|
|
def test_iocpNotWin32(self):
|
|
"""
|
|
--help-reactors should NOT display the iocp reactor on Windows
|
|
"""
|
|
self.assertIn(twisted_reactors.iocp.description, self.message)
|
|
self.assertIn("!" + twisted_reactors.iocp.shortName, self.message)
|
|
|
|
def test_onlySupportedReactors(self):
|
|
"""
|
|
--help-reactors with only supported reactors
|
|
"""
|
|
|
|
def getReactorTypes():
|
|
yield twisted_reactors.default
|
|
|
|
options = app.ReactorSelectionMixin()
|
|
options._getReactorTypes = getReactorTypes
|
|
options.messageOutput = StringIO()
|
|
self.assertRaises(SystemExit, options.opt_help_reactors)
|
|
message = options.messageOutput.getvalue()
|
|
self.assertNotIn("reactors not available", message)
|
|
|
|
|
|
class BackoffPolicyTests(TestCase):
|
|
"""
|
|
Tests of L{twisted.application.internet.backoffPolicy}
|
|
"""
|
|
|
|
def test_calculates_correct_values(self):
|
|
"""
|
|
Test that L{backoffPolicy()} calculates expected values
|
|
"""
|
|
pol = backoffPolicy(1.0, 60.0, 1.5, jitter=lambda: 1)
|
|
self.assertAlmostEqual(pol(0), 2)
|
|
self.assertAlmostEqual(pol(1), 2.5)
|
|
self.assertAlmostEqual(pol(10), 58.6650390625)
|
|
self.assertEqual(pol(20), 61)
|
|
self.assertEqual(pol(100), 61)
|
|
|
|
def test_does_not_overflow_on_high_attempts(self):
|
|
"""
|
|
L{backoffPolicy()} does not fail for large values of the attempt
|
|
parameter. In previous versions, this test failed when attempt was
|
|
larger than 1750.
|
|
|
|
See https://twistedmatrix.com/trac/ticket/9476
|
|
"""
|
|
pol = backoffPolicy(1.0, 60.0, 1.5, jitter=lambda: 1)
|
|
self.assertEqual(pol(1751), 61)
|
|
self.assertEqual(pol(1000000), 61)
|
|
|
|
def test_does_not_overflow_with_large_factor_value(self):
|
|
"""
|
|
Even with unusual parameters, any L{OverflowError} within
|
|
L{backoffPolicy()} will be caught and L{maxDelay} will be returned
|
|
instead
|
|
"""
|
|
pol = backoffPolicy(1.0, 60.0, 1e10, jitter=lambda: 1)
|
|
self.assertEqual(pol(1751), 61)
|