This commit is contained in:
2024-12-17 14:36:15 -08:00
parent b2dbf46d28
commit 06d106de53
17731 changed files with 3037186 additions and 144 deletions

View File

@@ -0,0 +1,6 @@
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Tests for L{twisted.internet}.
"""

View File

@@ -0,0 +1,169 @@
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
POSIX implementation of local network interface enumeration.
"""
import socket
import sys
from ctypes import (
CDLL,
POINTER,
Structure,
c_char_p,
c_int,
c_ubyte,
c_uint8,
c_uint32,
c_ushort,
c_void_p,
cast,
pointer,
)
from ctypes.util import find_library
from socket import AF_INET, AF_INET6, inet_ntop
from typing import Any, List, Tuple
from twisted.python.compat import nativeString
libc = CDLL(find_library("c") or "")
if sys.platform.startswith("freebsd") or sys.platform == "darwin":
_sockaddrCommon: List[Tuple[str, Any]] = [
("sin_len", c_uint8),
("sin_family", c_uint8),
]
else:
_sockaddrCommon: List[Tuple[str, Any]] = [
("sin_family", c_ushort),
]
class in_addr(Structure):
_fields_ = [
("in_addr", c_ubyte * 4),
]
class in6_addr(Structure):
_fields_ = [
("in_addr", c_ubyte * 16),
]
class sockaddr(Structure):
_fields_ = _sockaddrCommon + [
("sin_port", c_ushort),
]
class sockaddr_in(Structure):
_fields_ = _sockaddrCommon + [
("sin_port", c_ushort),
("sin_addr", in_addr),
]
class sockaddr_in6(Structure):
_fields_ = _sockaddrCommon + [
("sin_port", c_ushort),
("sin_flowinfo", c_uint32),
("sin_addr", in6_addr),
]
class ifaddrs(Structure):
pass
ifaddrs_p = POINTER(ifaddrs)
ifaddrs._fields_ = [
("ifa_next", ifaddrs_p),
("ifa_name", c_char_p),
("ifa_flags", c_uint32),
("ifa_addr", POINTER(sockaddr)),
("ifa_netmask", POINTER(sockaddr)),
("ifa_dstaddr", POINTER(sockaddr)),
("ifa_data", c_void_p),
]
getifaddrs = libc.getifaddrs
getifaddrs.argtypes = [POINTER(ifaddrs_p)]
getifaddrs.restype = c_int
freeifaddrs = libc.freeifaddrs
freeifaddrs.argtypes = [ifaddrs_p]
def _maybeCleanupScopeIndex(family, packed):
"""
On FreeBSD, kill the embedded interface indices in link-local scoped
addresses.
@param family: The address family of the packed address - one of the
I{socket.AF_*} constants.
@param packed: The packed representation of the address (ie, the bytes of a
I{in_addr} field).
@type packed: L{bytes}
@return: The packed address with any FreeBSD-specific extra bits cleared.
@rtype: L{bytes}
@see: U{https://twistedmatrix.com/trac/ticket/6843}
@see: U{http://www.freebsd.org/doc/en/books/developers-handbook/ipv6.html#ipv6-scope-index}
@note: Indications are that the need for this will be gone in FreeBSD >=10.
"""
if sys.platform.startswith("freebsd") and packed[:2] == b"\xfe\x80":
return packed[:2] + b"\x00\x00" + packed[4:]
return packed
def _interfaces():
"""
Call C{getifaddrs(3)} and return a list of tuples of interface name, address
family, and human-readable address representing its results.
"""
ifaddrs = ifaddrs_p()
if getifaddrs(pointer(ifaddrs)) < 0:
raise OSError()
results = []
try:
while ifaddrs:
if ifaddrs[0].ifa_addr:
family = ifaddrs[0].ifa_addr[0].sin_family
if family == AF_INET:
addr = cast(ifaddrs[0].ifa_addr, POINTER(sockaddr_in))
elif family == AF_INET6:
addr = cast(ifaddrs[0].ifa_addr, POINTER(sockaddr_in6))
else:
addr = None
if addr:
packed = bytes(addr[0].sin_addr.in_addr[:])
packed = _maybeCleanupScopeIndex(family, packed)
results.append(
(ifaddrs[0].ifa_name, family, inet_ntop(family, packed))
)
ifaddrs = ifaddrs[0].ifa_next
finally:
freeifaddrs(ifaddrs)
return results
def posixGetLinkLocalIPv6Addresses():
"""
Return a list of strings in colon-hex format representing all the link local
IPv6 addresses available on the system, as reported by I{getifaddrs(3)}.
"""
retList = []
for interface, family, address in _interfaces():
interface = nativeString(interface)
address = nativeString(address)
if family == socket.AF_INET6 and address.startswith("fe80:"):
retList.append(f"{address}%{interface}")
return retList

View File

@@ -0,0 +1,137 @@
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Windows implementation of local network interface enumeration.
"""
from ctypes import ( # type: ignore[attr-defined]
POINTER,
Structure,
WinDLL,
byref,
c_int,
c_void_p,
cast,
create_string_buffer,
create_unicode_buffer,
wstring_at,
)
from socket import AF_INET6, SOCK_STREAM, socket
WS2_32 = WinDLL("ws2_32")
SOCKET = c_int
DWORD = c_int
LPVOID = c_void_p
LPSOCKADDR = c_void_p
LPWSAPROTOCOL_INFO = c_void_p
LPTSTR = c_void_p
LPDWORD = c_void_p
LPWSAOVERLAPPED = c_void_p
LPWSAOVERLAPPED_COMPLETION_ROUTINE = c_void_p
# http://msdn.microsoft.com/en-us/library/ms741621(v=VS.85).aspx
# int WSAIoctl(
# __in SOCKET s,
# __in DWORD dwIoControlCode,
# __in LPVOID lpvInBuffer,
# __in DWORD cbInBuffer,
# __out LPVOID lpvOutBuffer,
# __in DWORD cbOutBuffer,
# __out LPDWORD lpcbBytesReturned,
# __in LPWSAOVERLAPPED lpOverlapped,
# __in LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
# );
WSAIoctl = WS2_32.WSAIoctl
WSAIoctl.argtypes = [
SOCKET,
DWORD,
LPVOID,
DWORD,
LPVOID,
DWORD,
LPDWORD,
LPWSAOVERLAPPED,
LPWSAOVERLAPPED_COMPLETION_ROUTINE,
]
WSAIoctl.restype = c_int
# http://msdn.microsoft.com/en-us/library/ms741516(VS.85).aspx
# INT WSAAPI WSAAddressToString(
# __in LPSOCKADDR lpsaAddress,
# __in DWORD dwAddressLength,
# __in_opt LPWSAPROTOCOL_INFO lpProtocolInfo,
# __inout LPTSTR lpszAddressString,
# __inout LPDWORD lpdwAddressStringLength
# );
WSAAddressToString = WS2_32.WSAAddressToStringW
WSAAddressToString.argtypes = [LPSOCKADDR, DWORD, LPWSAPROTOCOL_INFO, LPTSTR, LPDWORD]
WSAAddressToString.restype = c_int
SIO_ADDRESS_LIST_QUERY = 0x48000016
WSAEFAULT = 10014
class SOCKET_ADDRESS(Structure):
_fields_ = [("lpSockaddr", c_void_p), ("iSockaddrLength", c_int)]
def make_SAL(ln):
class SOCKET_ADDRESS_LIST(Structure):
_fields_ = [("iAddressCount", c_int), ("Address", SOCKET_ADDRESS * ln)]
return SOCKET_ADDRESS_LIST
def win32GetLinkLocalIPv6Addresses():
"""
Return a list of strings in colon-hex format representing all the link local
IPv6 addresses available on the system, as reported by
I{WSAIoctl}/C{SIO_ADDRESS_LIST_QUERY}.
"""
s = socket(AF_INET6, SOCK_STREAM)
size = 4096
retBytes = c_int()
for i in range(2):
buf = create_string_buffer(size)
ret = WSAIoctl(
s.fileno(), SIO_ADDRESS_LIST_QUERY, 0, 0, buf, size, byref(retBytes), 0, 0
)
# WSAIoctl might fail with WSAEFAULT, which means there was not enough
# space in the buffer we gave it. There's no way to check the errno
# until Python 2.6, so we don't even try. :/ Maybe if retBytes is still
# 0 another error happened, though.
if ret and retBytes.value:
size = retBytes.value
else:
break
# If it failed, then we'll just have to give up. Still no way to see why.
if ret:
raise RuntimeError("WSAIoctl failure")
addrList = cast(buf, POINTER(make_SAL(0)))
addrCount = addrList[0].iAddressCount
addrList = cast(buf, POINTER(make_SAL(addrCount)))
addressStringBufLength = 1024
addressStringBuf = create_unicode_buffer(addressStringBufLength)
retList = []
for i in range(addrList[0].iAddressCount):
retBytes.value = addressStringBufLength
address = addrList[0].Address[i]
ret = WSAAddressToString(
address.lpSockaddr,
address.iSockaddrLength,
0,
addressStringBuf,
byref(retBytes),
)
if ret:
raise RuntimeError("WSAAddressToString failure")
retList.append(wstring_at(addressStringBuf))
return [addr for addr in retList if "%" in addr]

View File

@@ -0,0 +1,594 @@
# -*- test-case-name: twisted.internet.test.test_tcp -*-
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Various helpers for tests for connection-oriented transports.
"""
import socket
from gc import collect
from typing import Optional
from weakref import ref
from zope.interface.verify import verifyObject
from twisted.internet.defer import Deferred, gatherResults
from twisted.internet.interfaces import IConnector, IReactorFDSet
from twisted.internet.protocol import ClientFactory, Protocol, ServerFactory
from twisted.internet.test.reactormixins import needsRunningReactor
from twisted.python import context, log
from twisted.python.failure import Failure
from twisted.python.log import ILogContext, err, msg
from twisted.python.runtime import platform
from twisted.test.test_tcp import ClosingProtocol
from twisted.trial.unittest import SkipTest
def findFreePort(interface="127.0.0.1", family=socket.AF_INET, type=socket.SOCK_STREAM):
"""
Ask the platform to allocate a free port on the specified interface, then
release the socket and return the address which was allocated.
@param interface: The local address to try to bind the port on.
@type interface: C{str}
@param type: The socket type which will use the resulting port.
@return: A two-tuple of address and port, like that returned by
L{socket.getsockname}.
"""
addr = socket.getaddrinfo(interface, 0)[0][4]
probe = socket.socket(family, type)
try:
probe.bind(addr)
if family == socket.AF_INET6:
sockname = probe.getsockname()
hostname = socket.getnameinfo(
sockname, socket.NI_NUMERICHOST | socket.NI_NUMERICSERV
)[0]
return (hostname, sockname[1])
else:
return probe.getsockname()
finally:
probe.close()
class ConnectableProtocol(Protocol):
"""
A protocol to be used with L{runProtocolsWithReactor}.
The protocol and its pair should eventually disconnect from each other.
@ivar reactor: The reactor used in this test.
@ivar disconnectReason: The L{Failure} passed to C{connectionLost}.
@ivar _done: A L{Deferred} which will be fired when the connection is
lost.
"""
disconnectReason = None
def _setAttributes(self, reactor, done):
"""
Set attributes on the protocol that are known only externally; this
will be called by L{runProtocolsWithReactor} when this protocol is
instantiated.
@param reactor: The reactor used in this test.
@param done: A L{Deferred} which will be fired when the connection is
lost.
"""
self.reactor = reactor
self._done = done
def connectionLost(self, reason):
self.disconnectReason = reason
self._done.callback(None)
del self._done
class EndpointCreator:
"""
Create client and server endpoints that know how to connect to each other.
"""
def server(self, reactor):
"""
Return an object providing C{IStreamServerEndpoint} for use in creating
a server to use to establish the connection type to be tested.
"""
raise NotImplementedError()
def client(self, reactor, serverAddress):
"""
Return an object providing C{IStreamClientEndpoint} for use in creating
a client to use to establish the connection type to be tested.
"""
raise NotImplementedError()
class _SingleProtocolFactory(ClientFactory):
"""
Factory to be used by L{runProtocolsWithReactor}.
It always returns the same protocol (i.e. is intended for only a single
connection).
"""
def __init__(self, protocol):
self._protocol = protocol
def buildProtocol(self, addr):
return self._protocol
def runProtocolsWithReactor(
reactorBuilder, serverProtocol, clientProtocol, endpointCreator
):
"""
Connect two protocols using endpoints and a new reactor instance.
A new reactor will be created and run, with the client and server protocol
instances connected to each other using the given endpoint creator. The
protocols should run through some set of tests, then disconnect; when both
have disconnected the reactor will be stopped and the function will
return.
@param reactorBuilder: A L{ReactorBuilder} instance.
@param serverProtocol: A L{ConnectableProtocol} that will be the server.
@param clientProtocol: A L{ConnectableProtocol} that will be the client.
@param endpointCreator: An instance of L{EndpointCreator}.
@return: The reactor run by this test.
"""
reactor = reactorBuilder.buildReactor()
serverProtocol._setAttributes(reactor, Deferred())
clientProtocol._setAttributes(reactor, Deferred())
serverFactory = _SingleProtocolFactory(serverProtocol)
clientFactory = _SingleProtocolFactory(clientProtocol)
# Listen on a port:
serverEndpoint = endpointCreator.server(reactor)
d = serverEndpoint.listen(serverFactory)
# Connect to the port:
def gotPort(p):
clientEndpoint = endpointCreator.client(reactor, p.getHost())
return clientEndpoint.connect(clientFactory)
d.addCallback(gotPort)
# Stop reactor when both connections are lost:
def failed(result):
log.err(result, "Connection setup failed.")
disconnected = gatherResults([serverProtocol._done, clientProtocol._done])
d.addCallback(lambda _: disconnected)
d.addErrback(failed)
d.addCallback(lambda _: needsRunningReactor(reactor, reactor.stop))
reactorBuilder.runReactor(reactor)
return reactor
def _getWriters(reactor):
"""
Like L{IReactorFDSet.getWriters}, but with support for IOCP reactor as
well.
"""
if IReactorFDSet.providedBy(reactor):
return reactor.getWriters()
elif "IOCP" in reactor.__class__.__name__:
return reactor.handles
else:
# Cannot tell what is going on.
raise Exception(f"Cannot find writers on {reactor!r}")
class _AcceptOneClient(ServerFactory):
"""
This factory fires a L{Deferred} with a protocol instance shortly after it
is constructed (hopefully long enough afterwards so that it has been
connected to a transport).
@ivar reactor: The reactor used to schedule the I{shortly}.
@ivar result: A L{Deferred} which will be fired with the protocol instance.
"""
def __init__(self, reactor, result):
self.reactor = reactor
self.result = result
def buildProtocol(self, addr):
protocol = ServerFactory.buildProtocol(self, addr)
self.reactor.callLater(0, self.result.callback, protocol)
return protocol
class _SimplePullProducer:
"""
A pull producer which writes one byte whenever it is resumed. For use by
C{test_unregisterProducerAfterDisconnect}.
"""
def __init__(self, consumer):
self.consumer = consumer
def stopProducing(self):
pass
def resumeProducing(self):
log.msg("Producer.resumeProducing")
self.consumer.write(b"x")
class Stop(ClientFactory):
"""
A client factory which stops a reactor when a connection attempt fails.
"""
failReason = None
def __init__(self, reactor):
self.reactor = reactor
def clientConnectionFailed(self, connector, reason):
self.failReason = reason
msg(f"Stop(CF) cCFailed: {reason.getErrorMessage()}")
self.reactor.stop()
class ClosingLaterProtocol(ConnectableProtocol):
"""
ClosingLaterProtocol exchanges one byte with its peer and then disconnects
itself. This is mostly a work-around for the fact that connectionMade is
called before the SSL handshake has completed.
"""
def __init__(self, onConnectionLost):
self.lostConnectionReason = None
self.onConnectionLost = onConnectionLost
def connectionMade(self):
msg("ClosingLaterProtocol.connectionMade")
def dataReceived(self, bytes):
msg(f"ClosingLaterProtocol.dataReceived {bytes!r}")
self.transport.loseConnection()
def connectionLost(self, reason):
msg("ClosingLaterProtocol.connectionLost")
self.lostConnectionReason = reason
self.onConnectionLost.callback(self)
class ConnectionTestsMixin:
"""
This mixin defines test methods which should apply to most L{ITransport}
implementations.
"""
endpoints: Optional[EndpointCreator] = None
def test_logPrefix(self):
"""
Client and server transports implement L{ILoggingContext.logPrefix} to
return a message reflecting the protocol they are running.
"""
class CustomLogPrefixProtocol(ConnectableProtocol):
def __init__(self, prefix):
self._prefix = prefix
self.system = None
def connectionMade(self):
self.transport.write(b"a")
def logPrefix(self):
return self._prefix
def dataReceived(self, bytes):
self.system = context.get(ILogContext)["system"]
self.transport.write(b"b")
# Only close connection if both sides have received data, so
# that both sides have system set.
if b"b" in bytes:
self.transport.loseConnection()
client = CustomLogPrefixProtocol("Custom Client")
server = CustomLogPrefixProtocol("Custom Server")
runProtocolsWithReactor(self, server, client, self.endpoints)
self.assertIn("Custom Client", client.system)
self.assertIn("Custom Server", server.system)
def test_writeAfterDisconnect(self):
"""
After a connection is disconnected, L{ITransport.write} and
L{ITransport.writeSequence} are no-ops.
"""
reactor = self.buildReactor()
finished = []
serverConnectionLostDeferred = Deferred()
protocol = lambda: ClosingLaterProtocol(serverConnectionLostDeferred)
portDeferred = self.endpoints.server(reactor).listen(
ServerFactory.forProtocol(protocol)
)
def listening(port):
msg(f"Listening on {port.getHost()!r}")
endpoint = self.endpoints.client(reactor, port.getHost())
lostConnectionDeferred = Deferred()
protocol = lambda: ClosingLaterProtocol(lostConnectionDeferred)
client = endpoint.connect(ClientFactory.forProtocol(protocol))
def write(proto):
msg(f"About to write to {proto!r}")
proto.transport.write(b"x")
client.addCallbacks(write, lostConnectionDeferred.errback)
def disconnected(proto):
msg(f"{proto!r} disconnected")
proto.transport.write(b"some bytes to get lost")
proto.transport.writeSequence([b"some", b"more"])
finished.append(True)
lostConnectionDeferred.addCallback(disconnected)
serverConnectionLostDeferred.addCallback(disconnected)
return gatherResults([lostConnectionDeferred, serverConnectionLostDeferred])
def onListen():
portDeferred.addCallback(listening)
portDeferred.addErrback(err)
portDeferred.addCallback(lambda ignored: reactor.stop())
needsRunningReactor(reactor, onListen)
self.runReactor(reactor)
self.assertEqual(finished, [True, True])
def test_protocolGarbageAfterLostConnection(self):
"""
After the connection a protocol is being used for is closed, the
reactor discards all of its references to the protocol.
"""
lostConnectionDeferred = Deferred()
clientProtocol = ClosingLaterProtocol(lostConnectionDeferred)
clientRef = ref(clientProtocol)
reactor = self.buildReactor()
portDeferred = self.endpoints.server(reactor).listen(
ServerFactory.forProtocol(Protocol)
)
def listening(port):
msg(f"Listening on {port.getHost()!r}")
endpoint = self.endpoints.client(reactor, port.getHost())
client = endpoint.connect(ClientFactory.forProtocol(lambda: clientProtocol))
def disconnect(proto):
msg(f"About to disconnect {proto!r}")
proto.transport.loseConnection()
client.addCallback(disconnect)
client.addErrback(lostConnectionDeferred.errback)
return lostConnectionDeferred
def onListening():
portDeferred.addCallback(listening)
portDeferred.addErrback(err)
portDeferred.addBoth(lambda ignored: reactor.stop())
needsRunningReactor(reactor, onListening)
self.runReactor(reactor)
# Drop the reference and get the garbage collector to tell us if there
# are no references to the protocol instance left in the reactor.
clientProtocol = None
collect()
self.assertIsNone(clientRef())
class LogObserverMixin:
"""
Mixin for L{TestCase} subclasses which want to observe log events.
"""
def observe(self):
loggedMessages = []
log.addObserver(loggedMessages.append)
self.addCleanup(log.removeObserver, loggedMessages.append)
return loggedMessages
class BrokenContextFactory:
"""
A context factory with a broken C{getContext} method, for exercising the
error handling for such a case.
"""
message = "Some path was wrong maybe"
def getContext(self):
raise ValueError(self.message)
class StreamClientTestsMixin:
"""
This mixin defines tests applicable to SOCK_STREAM client implementations.
This must be mixed in to a L{ReactorBuilder
<twisted.internet.test.reactormixins.ReactorBuilder>} subclass, as it
depends on several of its methods.
Then the methods C{connect} and C{listen} must defined, defining a client
and a server communicating with each other.
"""
def test_interface(self):
"""
The C{connect} method returns an object providing L{IConnector}.
"""
reactor = self.buildReactor()
connector = self.connect(reactor, ClientFactory())
self.assertTrue(verifyObject(IConnector, connector))
def test_clientConnectionFailedStopsReactor(self):
"""
The reactor can be stopped by a client factory's
C{clientConnectionFailed} method.
"""
reactor = self.buildReactor()
needsRunningReactor(reactor, lambda: self.connect(reactor, Stop(reactor)))
self.runReactor(reactor)
def test_connectEvent(self):
"""
This test checks that we correctly get notifications event for a
client. This ought to prevent a regression under Windows using the
GTK2 reactor. See #3925.
"""
reactor = self.buildReactor()
self.listen(reactor, ServerFactory.forProtocol(Protocol))
connected = []
class CheckConnection(Protocol):
def connectionMade(self):
connected.append(self)
reactor.stop()
clientFactory = Stop(reactor)
clientFactory.protocol = CheckConnection
needsRunningReactor(reactor, lambda: self.connect(reactor, clientFactory))
reactor.run()
self.assertTrue(connected)
def test_unregisterProducerAfterDisconnect(self):
"""
If a producer is unregistered from a transport after the transport has
been disconnected (by the peer) and after C{loseConnection} has been
called, the transport is not re-added to the reactor as a writer as
would be necessary if the transport were still connected.
"""
reactor = self.buildReactor()
self.listen(reactor, ServerFactory.forProtocol(ClosingProtocol))
finished = Deferred()
finished.addErrback(log.err)
finished.addCallback(lambda ign: reactor.stop())
writing = []
class ClientProtocol(Protocol):
"""
Protocol to connect, register a producer, try to lose the
connection, wait for the server to disconnect from us, and then
unregister the producer.
"""
def connectionMade(self):
log.msg("ClientProtocol.connectionMade")
self.transport.registerProducer(
_SimplePullProducer(self.transport), False
)
self.transport.loseConnection()
def connectionLost(self, reason):
log.msg("ClientProtocol.connectionLost")
self.unregister()
writing.append(self.transport in _getWriters(reactor))
finished.callback(None)
def unregister(self):
log.msg("ClientProtocol unregister")
self.transport.unregisterProducer()
clientFactory = ClientFactory()
clientFactory.protocol = ClientProtocol
self.connect(reactor, clientFactory)
self.runReactor(reactor)
self.assertFalse(writing[0], "Transport was writing after unregisterProducer.")
def test_disconnectWhileProducing(self):
"""
If C{loseConnection} is called while a producer is registered with the
transport, the connection is closed after the producer is unregistered.
"""
reactor = self.buildReactor()
# For some reason, pyobject/pygtk will not deliver the close
# notification that should happen after the unregisterProducer call in
# this test. The selectable is in the write notification set, but no
# notification ever arrives. Probably for the same reason #5233 led
# win32eventreactor to be broken.
skippedReactors = ["Glib2Reactor", "Gtk2Reactor"]
reactorClassName = reactor.__class__.__name__
if reactorClassName in skippedReactors and platform.isWindows():
raise SkipTest(
"A pygobject/pygtk bug disables this functionality " "on Windows."
)
class Producer:
def resumeProducing(self):
log.msg("Producer.resumeProducing")
self.listen(reactor, ServerFactory.forProtocol(Protocol))
finished = Deferred()
finished.addErrback(log.err)
finished.addCallback(lambda ign: reactor.stop())
class ClientProtocol(Protocol):
"""
Protocol to connect, register a producer, try to lose the
connection, unregister the producer, and wait for the connection to
actually be lost.
"""
def connectionMade(self):
log.msg("ClientProtocol.connectionMade")
self.transport.registerProducer(Producer(), False)
self.transport.loseConnection()
# Let the reactor tick over, in case synchronously calling
# loseConnection and then unregisterProducer is the same as
# synchronously calling unregisterProducer and then
# loseConnection (as it is in several reactors).
reactor.callLater(0, reactor.callLater, 0, self.unregister)
def unregister(self):
log.msg("ClientProtocol unregister")
self.transport.unregisterProducer()
# This should all be pretty quick. Fail the test
# if we don't get a connectionLost event really
# soon.
reactor.callLater(
1.0, finished.errback, Failure(Exception("Connection was not lost"))
)
def connectionLost(self, reason):
log.msg("ClientProtocol.connectionLost")
finished.callback(None)
clientFactory = ClientFactory()
clientFactory.protocol = ClientProtocol
self.connect(reactor, clientFactory)
self.runReactor(reactor)
# If the test failed, we logged an error already and trial
# will catch it.

View File

@@ -0,0 +1,49 @@
This is a concatenation of thing1.pem and thing2.pem.
-----BEGIN CERTIFICATE-----
MIID6DCCAtACAwtEVjANBgkqhkiG9w0BAQsFADCBtzELMAkGA1UEBhMCVFIxDzAN
BgNVBAgMBsOHb3J1bTEUMBIGA1UEBwwLQmHFn21ha8OnxLExEjAQBgNVBAMMCWxv
Y2FsaG9zdDEcMBoGA1UECgwTVHdpc3RlZCBNYXRyaXggTGFiczEkMCIGA1UECwwb
QXV0b21hdGVkIFRlc3RpbmcgQXV0aG9yaXR5MSkwJwYJKoZIhvcNAQkBFhpzZWN1
cml0eUB0d2lzdGVkbWF0cml4LmNvbTAgFw0yMjA4MjMyMzUxMTVaGA8yMTIyMDcz
MDIzNTExNVowgbcxCzAJBgNVBAYTAlRSMQ8wDQYDVQQIDAbDh29ydW0xFDASBgNV
BAcMC0JhxZ9tYWvDp8SxMRIwEAYDVQQDDAlsb2NhbGhvc3QxHDAaBgNVBAoME1R3
aXN0ZWQgTWF0cml4IExhYnMxJDAiBgNVBAsMG0F1dG9tYXRlZCBUZXN0aW5nIEF1
dGhvcml0eTEpMCcGCSqGSIb3DQEJARYac2VjdXJpdHlAdHdpc3RlZG1hdHJpeC5j
b20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDxEgrHBfUQuzIyDaZM
RqMy7h8tFCaQ4+0EshqENOjf4AMpxFeyxdHhR0IPBvMDZ7FkWg/mh8NImD2BgfhC
Z8fuWIfUCmF/sA2BInfwMwJAKy28g6wpg+ZJGpyadKq0+OrN/fmT3wsaEP/wcOuD
Pqk6wKt6Ry7eF3p7obgHHVVyku7gGQ/8bxshWNtoFT8oyCsO54VluEnL2XTBKQsS
EQQKCk0RdIAo2kCuA4AE+SxFlCBp9XiIux6a/z1rHTewuuCeM3DgxTgHDstUHMFO
YF29JlESeBvBpjiKCKrJk8K+Szhwza4eSfoB+dTenZX9Z/u370tUvppkC4Gf62lL
fqndAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAERd0Gt9An/6gKqOaEvTvHIubSIi
BCN4/udreXSH19z32DksCPW9sTG93O7PX51T2GN0FKgF2AcNSl5aNpKxQexn3uBJ
F4nxM4AGv0ltkHzeJdltyCVQyzcYOxAHAGTTNWqaWsJezXngirpvFRE15OaJcMRA
M5ygRh52YKYS+DvhaRwPs5xsTSLaJtyGYmXoXu8zTcvqVyWWdqj4PEHkV/g7OFoS
Mc+0s22i7FMvMRJozHA8hHJv4Dg6it6ifvQiZh6ihEO+kTSb1cpDfyu3Uhw50dAW
23/mit+5faDT5g6lC5AG3yU/DOWFwJqXi73YhcggqTtWBufQfavq/2QmdXw=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIID6DCCAtACAwtEVjANBgkqhkiG9w0BAQsFADCBtzELMAkGA1UEBhMCVFIxDzAN
BgNVBAgMBsOHb3J1bTEUMBIGA1UEBwwLQmHFn21ha8OnxLExEjAQBgNVBAMMCWxv
Y2FsaG9zdDEcMBoGA1UECgwTVHdpc3RlZCBNYXRyaXggTGFiczEkMCIGA1UECwwb
QXV0b21hdGVkIFRlc3RpbmcgQXV0aG9yaXR5MSkwJwYJKoZIhvcNAQkBFhpzZWN1
cml0eUB0d2lzdGVkbWF0cml4LmNvbTAgFw0yMjA4MjMyMzUxMzdaGA8yMTIyMDcz
MDIzNTEzN1owgbcxCzAJBgNVBAYTAlRSMQ8wDQYDVQQIDAbDh29ydW0xFDASBgNV
BAcMC0JhxZ9tYWvDp8SxMRIwEAYDVQQDDAlsb2NhbGhvc3QxHDAaBgNVBAoME1R3
aXN0ZWQgTWF0cml4IExhYnMxJDAiBgNVBAsMG0F1dG9tYXRlZCBUZXN0aW5nIEF1
dGhvcml0eTEpMCcGCSqGSIb3DQEJARYac2VjdXJpdHlAdHdpc3RlZG1hdHJpeC5j
b20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCB/ICkiDmAALdHzezV
fxg6/azHX9eymmBS2PEizw0QOkt+VriWu5Ik0KHDBP9NWuLiQF3G1Zch6YVkOfPU
vbvfCv4GbFdssnoVPX/0VOT/YEsEHtyCjgGo22FAE/vd4EcnnJx017B/5mzxLaf6
u9XwOZaIojw6I20iaKDjpfyvVBJA5gXH1YGrBYRLIqGaS4lbZcTy6ZoqanYMOmJn
kd3IBOJOaFwfazROVvaVkQQlP9SgAKwaEcEF8Hk/E4N5nVWS8QnN/PWhzouJSAtQ
WzXgLTZrUdDItBxlG+IeVSWdYUoBPFruZ33tdZta7zJ2FrrkxX27azd+nCIC6NLQ
h1DfAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAGmx33QaOIw+dTzdBmPGZy5kZv6Y
a1z/rfyBtnR37iAZ+5rhJZkcQSLyUY+kiuEPxTouh3Amx8EOVccerfZvzCFAb21L
TOeSoeJ1TSn2ppIBqzjjdJ/JHcQTMEB/5D8S069Rp/D6wbgCbmJ2CJcU+OYY+W7z
fT6fEy7MmNXC3RwoD5Vc0RaS4fDSCMOG/bL/rylQoimmvm4rQAmZJK2PuKvqtkzc
Fs9dq4VJ3Eba1/qA2000CtzHHBYMalL9+EQxwjy0QmVL08usUSMiRPfySCUJBEYA
xZmEEvyHkD7dFVngJvjW64pFg7d/mV0e8dQv1QURoCDb7XOuCWY1vltp7iE=
-----END CERTIFICATE-----

View File

@@ -0,0 +1 @@
This file is not a certificate; it is present to make sure that it will be skipped.

View File

@@ -0,0 +1,27 @@
This certificate, and thing2.pem, are generated by re-running
twisted/test/thing1.pem and copying the certificate out (without its private
key) a few times.
-----BEGIN CERTIFICATE-----
MIID6DCCAtACAwtEVjANBgkqhkiG9w0BAQsFADCBtzELMAkGA1UEBhMCVFIxDzAN
BgNVBAgMBsOHb3J1bTEUMBIGA1UEBwwLQmHFn21ha8OnxLExEjAQBgNVBAMMCWxv
Y2FsaG9zdDEcMBoGA1UECgwTVHdpc3RlZCBNYXRyaXggTGFiczEkMCIGA1UECwwb
QXV0b21hdGVkIFRlc3RpbmcgQXV0aG9yaXR5MSkwJwYJKoZIhvcNAQkBFhpzZWN1
cml0eUB0d2lzdGVkbWF0cml4LmNvbTAgFw0yMjA4MjMyMzUxMTVaGA8yMTIyMDcz
MDIzNTExNVowgbcxCzAJBgNVBAYTAlRSMQ8wDQYDVQQIDAbDh29ydW0xFDASBgNV
BAcMC0JhxZ9tYWvDp8SxMRIwEAYDVQQDDAlsb2NhbGhvc3QxHDAaBgNVBAoME1R3
aXN0ZWQgTWF0cml4IExhYnMxJDAiBgNVBAsMG0F1dG9tYXRlZCBUZXN0aW5nIEF1
dGhvcml0eTEpMCcGCSqGSIb3DQEJARYac2VjdXJpdHlAdHdpc3RlZG1hdHJpeC5j
b20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDxEgrHBfUQuzIyDaZM
RqMy7h8tFCaQ4+0EshqENOjf4AMpxFeyxdHhR0IPBvMDZ7FkWg/mh8NImD2BgfhC
Z8fuWIfUCmF/sA2BInfwMwJAKy28g6wpg+ZJGpyadKq0+OrN/fmT3wsaEP/wcOuD
Pqk6wKt6Ry7eF3p7obgHHVVyku7gGQ/8bxshWNtoFT8oyCsO54VluEnL2XTBKQsS
EQQKCk0RdIAo2kCuA4AE+SxFlCBp9XiIux6a/z1rHTewuuCeM3DgxTgHDstUHMFO
YF29JlESeBvBpjiKCKrJk8K+Szhwza4eSfoB+dTenZX9Z/u370tUvppkC4Gf62lL
fqndAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAERd0Gt9An/6gKqOaEvTvHIubSIi
BCN4/udreXSH19z32DksCPW9sTG93O7PX51T2GN0FKgF2AcNSl5aNpKxQexn3uBJ
F4nxM4AGv0ltkHzeJdltyCVQyzcYOxAHAGTTNWqaWsJezXngirpvFRE15OaJcMRA
M5ygRh52YKYS+DvhaRwPs5xsTSLaJtyGYmXoXu8zTcvqVyWWdqj4PEHkV/g7OFoS
Mc+0s22i7FMvMRJozHA8hHJv4Dg6it6ifvQiZh6ihEO+kTSb1cpDfyu3Uhw50dAW
23/mit+5faDT5g6lC5AG3yU/DOWFwJqXi73YhcggqTtWBufQfavq/2QmdXw=
-----END CERTIFICATE-----

View File

@@ -0,0 +1,23 @@
-----BEGIN CERTIFICATE-----
MIID6DCCAtACAwtEVjANBgkqhkiG9w0BAQsFADCBtzELMAkGA1UEBhMCVFIxDzAN
BgNVBAgMBsOHb3J1bTEUMBIGA1UEBwwLQmHFn21ha8OnxLExEjAQBgNVBAMMCWxv
Y2FsaG9zdDEcMBoGA1UECgwTVHdpc3RlZCBNYXRyaXggTGFiczEkMCIGA1UECwwb
QXV0b21hdGVkIFRlc3RpbmcgQXV0aG9yaXR5MSkwJwYJKoZIhvcNAQkBFhpzZWN1
cml0eUB0d2lzdGVkbWF0cml4LmNvbTAgFw0yMjA4MjMyMzUxMzdaGA8yMTIyMDcz
MDIzNTEzN1owgbcxCzAJBgNVBAYTAlRSMQ8wDQYDVQQIDAbDh29ydW0xFDASBgNV
BAcMC0JhxZ9tYWvDp8SxMRIwEAYDVQQDDAlsb2NhbGhvc3QxHDAaBgNVBAoME1R3
aXN0ZWQgTWF0cml4IExhYnMxJDAiBgNVBAsMG0F1dG9tYXRlZCBUZXN0aW5nIEF1
dGhvcml0eTEpMCcGCSqGSIb3DQEJARYac2VjdXJpdHlAdHdpc3RlZG1hdHJpeC5j
b20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCB/ICkiDmAALdHzezV
fxg6/azHX9eymmBS2PEizw0QOkt+VriWu5Ik0KHDBP9NWuLiQF3G1Zch6YVkOfPU
vbvfCv4GbFdssnoVPX/0VOT/YEsEHtyCjgGo22FAE/vd4EcnnJx017B/5mzxLaf6
u9XwOZaIojw6I20iaKDjpfyvVBJA5gXH1YGrBYRLIqGaS4lbZcTy6ZoqanYMOmJn
kd3IBOJOaFwfazROVvaVkQQlP9SgAKwaEcEF8Hk/E4N5nVWS8QnN/PWhzouJSAtQ
WzXgLTZrUdDItBxlG+IeVSWdYUoBPFruZ33tdZta7zJ2FrrkxX27azd+nCIC6NLQ
h1DfAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAGmx33QaOIw+dTzdBmPGZy5kZv6Y
a1z/rfyBtnR37iAZ+5rhJZkcQSLyUY+kiuEPxTouh3Amx8EOVccerfZvzCFAb21L
TOeSoeJ1TSn2ppIBqzjjdJ/JHcQTMEB/5D8S069Rp/D6wbgCbmJ2CJcU+OYY+W7z
fT6fEy7MmNXC3RwoD5Vc0RaS4fDSCMOG/bL/rylQoimmvm4rQAmZJK2PuKvqtkzc
Fs9dq4VJ3Eba1/qA2000CtzHHBYMalL9+EQxwjy0QmVL08usUSMiRPfySCUJBEYA
xZmEEvyHkD7dFVngJvjW64pFg7d/mV0e8dQv1QURoCDb7XOuCWY1vltp7iE=
-----END CERTIFICATE-----

View File

@@ -0,0 +1,23 @@
-----BEGIN CERTIFICATE-----
MIID6DCCAtACAwtEVjANBgkqhkiG9w0BAQsFADCBtzELMAkGA1UEBhMCVFIxDzAN
BgNVBAgMBsOHb3J1bTEUMBIGA1UEBwwLQmHFn21ha8OnxLExEjAQBgNVBAMMCWxv
Y2FsaG9zdDEcMBoGA1UECgwTVHdpc3RlZCBNYXRyaXggTGFiczEkMCIGA1UECwwb
QXV0b21hdGVkIFRlc3RpbmcgQXV0aG9yaXR5MSkwJwYJKoZIhvcNAQkBFhpzZWN1
cml0eUB0d2lzdGVkbWF0cml4LmNvbTAgFw0yMjA4MjMyMzUxMzdaGA8yMTIyMDcz
MDIzNTEzN1owgbcxCzAJBgNVBAYTAlRSMQ8wDQYDVQQIDAbDh29ydW0xFDASBgNV
BAcMC0JhxZ9tYWvDp8SxMRIwEAYDVQQDDAlsb2NhbGhvc3QxHDAaBgNVBAoME1R3
aXN0ZWQgTWF0cml4IExhYnMxJDAiBgNVBAsMG0F1dG9tYXRlZCBUZXN0aW5nIEF1
dGhvcml0eTEpMCcGCSqGSIb3DQEJARYac2VjdXJpdHlAdHdpc3RlZG1hdHJpeC5j
b20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCB/ICkiDmAALdHzezV
fxg6/azHX9eymmBS2PEizw0QOkt+VriWu5Ik0KHDBP9NWuLiQF3G1Zch6YVkOfPU
vbvfCv4GbFdssnoVPX/0VOT/YEsEHtyCjgGo22FAE/vd4EcnnJx017B/5mzxLaf6
u9XwOZaIojw6I20iaKDjpfyvVBJA5gXH1YGrBYRLIqGaS4lbZcTy6ZoqanYMOmJn
kd3IBOJOaFwfazROVvaVkQQlP9SgAKwaEcEF8Hk/E4N5nVWS8QnN/PWhzouJSAtQ
WzXgLTZrUdDItBxlG+IeVSWdYUoBPFruZ33tdZta7zJ2FrrkxX27azd+nCIC6NLQ
h1DfAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAGmx33QaOIw+dTzdBmPGZy5kZv6Y
a1z/rfyBtnR37iAZ+5rhJZkcQSLyUY+kiuEPxTouh3Amx8EOVccerfZvzCFAb21L
TOeSoeJ1TSn2ppIBqzjjdJ/JHcQTMEB/5D8S069Rp/D6wbgCbmJ2CJcU+OYY+W7z
fT6fEy7MmNXC3RwoD5Vc0RaS4fDSCMOG/bL/rylQoimmvm4rQAmZJK2PuKvqtkzc
Fs9dq4VJ3Eba1/qA2000CtzHHBYMalL9+EQxwjy0QmVL08usUSMiRPfySCUJBEYA
xZmEEvyHkD7dFVngJvjW64pFg7d/mV0e8dQv1QURoCDb7XOuCWY1vltp7iE=
-----END CERTIFICATE-----

View File

@@ -0,0 +1,63 @@
# -*- test-case-name: twisted.internet.test.test_endpoints -*-
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Fake client and server endpoint string parser plugins for testing purposes.
"""
from zope.interface.declarations import implementer
from twisted.internet.interfaces import (
IStreamClientEndpoint,
IStreamClientEndpointStringParserWithReactor,
IStreamServerEndpoint,
IStreamServerEndpointStringParser,
)
from twisted.plugin import IPlugin
@implementer(IPlugin)
class PluginBase:
def __init__(self, pfx):
self.prefix = pfx
@implementer(IStreamClientEndpointStringParserWithReactor)
class FakeClientParserWithReactor(PluginBase):
def parseStreamClient(self, *a, **kw):
return StreamClient(self, a, kw)
@implementer(IStreamServerEndpointStringParser)
class FakeParser(PluginBase):
def parseStreamServer(self, *a, **kw):
return StreamServer(self, a, kw)
class EndpointBase:
def __init__(self, parser, args, kwargs):
self.parser = parser
self.args = args
self.kwargs = kwargs
@implementer(IStreamClientEndpoint)
class StreamClient(EndpointBase):
def connect(self, protocolFactory=None):
# IStreamClientEndpoint.connect
pass
@implementer(IStreamServerEndpoint)
class StreamServer(EndpointBase):
def listen(self, protocolFactory=None):
# IStreamClientEndpoint.listen
pass
# Instantiate plugin interface providers to register them.
fake = FakeParser("fake")
fakeClientWithReactor = FakeClientParserWithReactor("crfake")
fakeClientWithReactorAndPreference = FakeClientParserWithReactor("cpfake")

View File

@@ -0,0 +1,61 @@
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Testing helpers related to the module system.
"""
__all__ = ["NoReactor", "AlternateReactor"]
import sys
import twisted.internet
from twisted.test.test_twisted import SetAsideModule
class NoReactor(SetAsideModule):
"""
Context manager that uninstalls the reactor, if any, and then restores it
afterwards.
"""
def __init__(self):
SetAsideModule.__init__(self, "twisted.internet.reactor")
def __enter__(self):
SetAsideModule.__enter__(self)
if "twisted.internet.reactor" in self.modules:
del twisted.internet.reactor
def __exit__(self, excType, excValue, traceback):
SetAsideModule.__exit__(self, excType, excValue, traceback)
# Clean up 'reactor' attribute that may have been set on
# twisted.internet:
reactor = self.modules.get("twisted.internet.reactor", None)
if reactor is not None:
twisted.internet.reactor = reactor
else:
try:
del twisted.internet.reactor
except AttributeError:
pass
class AlternateReactor(NoReactor):
"""
A context manager which temporarily installs a different object as the
global reactor.
"""
def __init__(self, reactor):
"""
@param reactor: Any object to install as the global reactor.
"""
NoReactor.__init__(self)
self.alternate = reactor
def __enter__(self):
NoReactor.__enter__(self)
twisted.internet.reactor = self.alternate
sys.modules["twisted.internet.reactor"] = self.alternate

View File

@@ -0,0 +1,22 @@
import os
import sys
try:
# On Windows, stdout is not opened in binary mode by default,
# so newline characters are munged on writing, interfering with
# the tests.
import msvcrt
msvcrt.setmode( # type:ignore[attr-defined]
sys.stdout.fileno(), os.O_BINARY
)
except ImportError:
pass
# Loop over each of the arguments given and print it to stdout
for arg in sys.argv[1:]:
res = arg + chr(0)
sys.stdout.buffer.write(res.encode(sys.getfilesystemencoding(), "surrogateescape"))
sys.stdout.flush()

View File

@@ -0,0 +1,8 @@
import os
import sys
while 1:
line = sys.stdin.readline().strip()
if not line:
break
os.close(int(line))

View File

@@ -0,0 +1,25 @@
import sys
# Override theSystemPath so it throws KeyError on gi.pygtkcompat:
from twisted.python import modules
from twisted.python.reflect import requireModule
modules.theSystemPath = modules.PythonPath([], moduleDict={})
# Now, when we import gireactor it shouldn't use pygtkcompat, and should
# instead prevent gobject from being importable:
gireactor = requireModule("twisted.internet.gireactor")
for name in gireactor._PYGTK_MODULES:
if sys.modules[name] is not None:
sys.stdout.write(
"failure, sys.modules[%r] is %r, instead of None"
% (name, sys.modules["gobject"])
)
sys.exit(0)
try:
import gobject
except ImportError:
sys.stdout.write("success")
else:
sys.stdout.write(f"failure: {gobject.__path__} was imported")

View File

@@ -0,0 +1,46 @@
# A program which exits after starting a child which inherits its
# stdin/stdout/stderr and keeps them open until stdin is closed.
import os
import sys
def grandchild() -> None:
sys.stdout.write("grandchild started")
sys.stdout.flush()
sys.stdin.read()
def main() -> None:
if sys.argv[1] == "child":
if sys.argv[2] == "windows":
import win32api as api
import win32process as proc
info = proc.STARTUPINFO()
info.hStdInput = api.GetStdHandle(api.STD_INPUT_HANDLE)
info.hStdOutput = api.GetStdHandle(api.STD_OUTPUT_HANDLE)
info.hStdError = api.GetStdHandle(api.STD_ERROR_HANDLE)
python = sys.executable
scriptDir = os.path.dirname(__file__)
scriptName = os.path.basename(__file__)
proc.CreateProcess(
None,
" ".join((python, scriptName, "grandchild")),
None,
None,
1,
0,
os.environ,
scriptDir,
info,
)
else:
if os.fork() == 0:
grandchild()
else:
grandchild()
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,418 @@
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Utilities for unit testing reactor implementations.
The main feature of this module is L{ReactorBuilder}, a base class for use when
writing interface/blackbox tests for reactor implementations. Test case classes
for reactor features should subclass L{ReactorBuilder} instead of
L{SynchronousTestCase}. All of the features of L{SynchronousTestCase} will be
available. Additionally, the tests will automatically be applied to all
available reactor implementations.
"""
__all__ = ["TestTimeoutError", "ReactorBuilder", "needsRunningReactor"]
import os
import signal
import time
from typing import TYPE_CHECKING, Callable, Dict, Optional, Sequence, Type, Union, cast
from zope.interface import Interface
from twisted.python import log
from twisted.python.deprecate import _fullyQualifiedName as fullyQualifiedName
from twisted.python.failure import Failure
from twisted.python.reflect import namedAny
from twisted.python.runtime import platform
from twisted.trial.unittest import SkipTest, SynchronousTestCase
from twisted.trial.util import DEFAULT_TIMEOUT_DURATION, acquireAttribute
if TYPE_CHECKING:
# Only bring in this name to support the type annotation below. We don't
# really want to import a reactor module this early at runtime.
from twisted.internet import asyncioreactor
# Access private APIs.
try:
from twisted.internet import process as _process
except ImportError:
process = None
else:
process = _process
class TestTimeoutError(Exception):
"""
The reactor was still running after the timeout period elapsed in
L{ReactorBuilder.runReactor}.
"""
def needsRunningReactor(reactor, thunk):
"""
Various functions within these tests need an already-running reactor at
some point. They need to stop the reactor when the test has completed, and
that means calling reactor.stop(). However, reactor.stop() raises an
exception if the reactor isn't already running, so if the L{Deferred} that
a particular API under test returns fires synchronously (as especially an
endpoint's C{connect()} method may do, if the connect is to a local
interface address) then the test won't be able to stop the reactor being
tested and finish. So this calls C{thunk} only once C{reactor} is running.
(This is just an alias for
L{twisted.internet.interfaces.IReactorCore.callWhenRunning} on the given
reactor parameter, in order to centrally reference the above paragraph and
repeating it everywhere as a comment.)
@param reactor: the L{twisted.internet.interfaces.IReactorCore} under test
@param thunk: a 0-argument callable, which eventually finishes the test in
question, probably in a L{Deferred} callback.
"""
reactor.callWhenRunning(thunk)
def stopOnError(case, reactor, publisher=None):
"""
Stop the reactor as soon as any error is logged on the given publisher.
This is beneficial for tests which will wait for a L{Deferred} to fire
before completing (by passing or failing). Certain implementation bugs may
prevent the L{Deferred} from firing with any result at all (consider a
protocol's {dataReceived} method that raises an exception: this exception
is logged but it won't ever cause a L{Deferred} to fire). In that case the
test would have to complete by timing out which is a much less desirable
outcome than completing as soon as the unexpected error is encountered.
@param case: A L{SynchronousTestCase} to use to clean up the necessary log
observer when the test is over.
@param reactor: The reactor to stop.
@param publisher: A L{LogPublisher} to watch for errors. If L{None}, the
global log publisher will be watched.
"""
if publisher is None:
from twisted.python import log as publisher
running = [None]
def stopIfError(event):
if running and event.get("isError"):
running.pop()
reactor.stop()
publisher.addObserver(stopIfError)
case.addCleanup(publisher.removeObserver, stopIfError)
class ReactorBuilder:
"""
L{SynchronousTestCase} mixin which provides a reactor-creation API. This
mixin defines C{setUp} and C{tearDown}, so mix it in before
L{SynchronousTestCase} or call its methods from the overridden ones in the
subclass.
@cvar skippedReactors: A dict mapping FQPN strings of reactors for
which the tests defined by this class will be skipped to strings
giving the skip message.
@cvar requiredInterfaces: A C{list} of interfaces which the reactor must
provide or these tests will be skipped. The default, L{None}, means
that no interfaces are required.
@ivar reactorFactory: A no-argument callable which returns the reactor to
use for testing.
@ivar originalHandler: The SIGCHLD handler which was installed when setUp
ran and which will be re-installed when tearDown runs.
@ivar _reactors: A list of FQPN strings giving the reactors for which
L{SynchronousTestCase}s will be created.
"""
_reactors = [
# Select works everywhere
"twisted.internet.selectreactor.SelectReactor",
]
if platform.isWindows():
# PortableGtkReactor is only really interesting on Windows,
# but not really Windows specific; if you want you can
# temporarily move this up to the all-platforms list to test
# it on other platforms. It's not there in general because
# it's not _really_ worth it to support on other platforms,
# since no one really wants to use it on other platforms.
_reactors.extend(
[
"twisted.internet.gireactor.PortableGIReactor",
"twisted.internet.win32eventreactor.Win32Reactor",
"twisted.internet.iocpreactor.reactor.IOCPReactor",
]
)
else:
_reactors.extend(
[
"twisted.internet.gireactor.GIReactor",
]
)
_reactors.append("twisted.internet.test.reactormixins.AsyncioSelectorReactor")
_reactors.append("twisted.internet._threadedselect.ThreadedSelectReactor")
if platform.isMacOSX():
_reactors.append("twisted.internet.cfreactor.CFReactor")
else:
_reactors.extend(
[
"twisted.internet.pollreactor.PollReactor",
"twisted.internet.epollreactor.EPollReactor",
]
)
if not platform.isLinux():
# Presumably Linux is not going to start supporting kqueue, so
# skip even trying this configuration.
_reactors.extend(
[
# Support KQueue on non-OS-X POSIX platforms for now.
"twisted.internet.kqreactor.KQueueReactor",
]
)
reactorFactory: Optional[Callable[[], object]] = None
originalHandler = None
requiredInterfaces: Optional[Sequence[Type[Interface]]] = None
skippedReactors: Dict[str, str] = {}
def setUp(self):
"""
Clear the SIGCHLD handler, if there is one, to ensure an environment
like the one which exists prior to a call to L{reactor.run}.
"""
if not platform.isWindows():
self.originalHandler = signal.signal(signal.SIGCHLD, signal.SIG_DFL)
def tearDown(self):
"""
Restore the original SIGCHLD handler and reap processes as long as
there seem to be any remaining.
"""
if self.originalHandler is not None:
signal.signal(signal.SIGCHLD, self.originalHandler)
if process is not None:
begin = time.time()
while process.reapProcessHandlers:
log.msg(
"ReactorBuilder.tearDown reaping some processes %r"
% (process.reapProcessHandlers,)
)
process.reapAllProcesses()
# The process should exit on its own. However, if it
# doesn't, we're stuck in this loop forever. To avoid
# hanging the test suite, eventually give the process some
# help exiting and move on.
time.sleep(0.001)
if time.time() - begin > 60:
for pid in process.reapProcessHandlers:
os.kill(pid, signal.SIGKILL)
raise Exception(
"Timeout waiting for child processes to exit: %r"
% (process.reapProcessHandlers,)
)
def _unbuildReactor(self, reactor):
"""
Clean up any resources which may have been allocated for the given
reactor by its creation or by a test which used it.
"""
# Chris says:
#
# XXX These explicit calls to clean up the waker (and any other
# internal readers) should become obsolete when bug #3063 is
# fixed. -radix, 2008-02-29. Fortunately it should probably cause an
# error when bug #3063 is fixed, so it should be removed in the same
# branch that fixes it.
#
# -exarkun
reactor._uninstallHandler()
if getattr(reactor, "_internalReaders", None) is not None:
for reader in reactor._internalReaders:
reactor.removeReader(reader)
reader.connectionLost(None)
reactor._internalReaders.clear()
# Here's an extra thing unrelated to wakers but necessary for
# cleaning up after the reactors we make. -exarkun
reactor.disconnectAll()
# It would also be bad if any timed calls left over were allowed to
# run.
calls = reactor.getDelayedCalls()
for c in calls:
c.cancel()
# Restore the original reactor state:
from twisted.internet import reactor as globalReactor
globalReactor.__dict__ = reactor._originalReactorDict
globalReactor.__class__ = reactor._originalReactorClass
def buildReactor(self):
"""
Create and return a reactor using C{self.reactorFactory}.
"""
try:
from twisted.internet import reactor as globalReactor
from twisted.internet.cfreactor import CFReactor
except ImportError:
pass
else:
if (
isinstance(globalReactor, CFReactor)
and self.reactorFactory is CFReactor
):
raise SkipTest(
"CFReactor uses APIs which manipulate global state, "
"so it's not safe to run its own reactor-builder tests "
"under itself"
)
try:
assert self.reactorFactory is not None
reactor = self.reactorFactory()
reactor._originalReactorDict = globalReactor.__dict__
reactor._originalReactorClass = globalReactor.__class__
# Make twisted.internet.reactor point to the new reactor,
# temporarily; this is undone in unbuildReactor().
globalReactor.__dict__ = reactor.__dict__
globalReactor.__class__ = reactor.__class__
except BaseException:
# Unfortunately, not all errors which result in a reactor
# being unusable are detectable without actually
# instantiating the reactor. So we catch some more here
# and skip the test if necessary. We also log it to aid
# with debugging, but flush the logged error so the test
# doesn't fail.
log.err(None, "Failed to install reactor")
self.flushLoggedErrors()
raise SkipTest(Failure().getErrorMessage())
else:
if self.requiredInterfaces is not None:
missing = [
required
for required in self.requiredInterfaces
if not required.providedBy(reactor)
]
if missing:
self._unbuildReactor(reactor)
raise SkipTest(
"%s does not provide %s"
% (
fullyQualifiedName(reactor.__class__),
",".join([fullyQualifiedName(x) for x in missing]),
)
)
self.addCleanup(self._unbuildReactor, reactor)
return reactor
def getTimeout(self):
"""
Determine how long to run the test before considering it failed.
@return: A C{int} or C{float} giving a number of seconds.
"""
return acquireAttribute(self._parents, "timeout", DEFAULT_TIMEOUT_DURATION)
def runReactor(self, reactor, timeout=None):
"""
Run the reactor for at most the given amount of time.
@param reactor: The reactor to run.
@type timeout: C{int} or C{float}
@param timeout: The maximum amount of time, specified in seconds, to
allow the reactor to run. If the reactor is still running after
this much time has elapsed, it will be stopped and an exception
raised. If L{None}, the default test method timeout imposed by
Trial will be used. This depends on the L{IReactorTime}
implementation of C{reactor} for correct operation.
@raise TestTimeoutError: If the reactor is still running after
C{timeout} seconds.
"""
if timeout is None:
timeout = self.getTimeout()
timedOut = []
def stop():
timedOut.append(None)
reactor.stop()
timedOutCall = reactor.callLater(timeout, stop)
reactor.run()
if timedOut:
raise TestTimeoutError(f"reactor still running after {timeout} seconds")
else:
timedOutCall.cancel()
@classmethod
def makeTestCaseClasses(
cls: Type["ReactorBuilder"],
) -> Dict[str, Union[Type["ReactorBuilder"], Type[SynchronousTestCase]]]:
"""
Create a L{SynchronousTestCase} subclass which mixes in C{cls} for each
known reactor and return a dict mapping their names to them.
"""
classes: Dict[
str, Union[Type["ReactorBuilder"], Type[SynchronousTestCase]]
] = {}
for reactor in cls._reactors:
shortReactorName = reactor.split(".")[-1]
name = (cls.__name__ + "." + shortReactorName + "Tests").replace(".", "_")
class testcase(cls, SynchronousTestCase): # type: ignore[valid-type,misc]
__module__ = cls.__module__
if reactor in cls.skippedReactors:
skip = cls.skippedReactors[reactor]
try:
reactorFactory = namedAny(reactor)
except BaseException:
skip = Failure().getErrorMessage()
testcase.__name__ = name
testcase.__qualname__ = ".".join(cls.__qualname__.split()[0:-1] + [name])
classes[testcase.__name__] = testcase
return classes
def asyncioSelectorReactor(self: object) -> "asyncioreactor.AsyncioSelectorReactor":
"""
Make a new asyncio reactor associated with a new event loop.
The test suite prefers this constructor because having a new event loop
for each reactor provides better test isolation. The real constructor
prefers to re-use (or create) a global loop because of how this interacts
with other asyncio-based libraries and applications (though maybe it
shouldn't).
@param self: The L{ReactorBuilder} subclass this is being called on. We
don't use this parameter but we get called with it anyway.
"""
from asyncio import get_event_loop, new_event_loop, set_event_loop
from twisted.internet import asyncioreactor
asTestCase = cast(SynchronousTestCase, self)
originalLoop = get_event_loop()
loop = new_event_loop()
set_event_loop(loop)
@asTestCase.addCleanup
def cleanUp():
loop.close()
set_event_loop(originalLoop)
return asyncioreactor.AsyncioSelectorReactor(loop)
# Give it an alias that makes the names of the generated test classes fit the
# pattern.
AsyncioSelectorReactor = asyncioSelectorReactor

View File

@@ -0,0 +1,144 @@
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Tests for L{twisted.internet.abstract}, a collection of APIs for implementing
reactors.
"""
from __future__ import annotations
from typing import Union
from hypothesis import example, given, strategies as st
from twisted.internet.abstract import FileDescriptor, isIPv6Address
from twisted.trial.unittest import SynchronousTestCase
from .test_tcp import _FakeFDSetReactor
class IPv6AddressTests(SynchronousTestCase):
"""
Tests for L{isIPv6Address}, a function for determining if a particular
string is an IPv6 address literal.
"""
def test_empty(self) -> None:
"""
The empty string is not an IPv6 address literal.
"""
self.assertFalse(isIPv6Address(""))
def test_colon(self) -> None:
"""
A single C{":"} is not an IPv6 address literal.
"""
self.assertFalse(isIPv6Address(":"))
def test_loopback(self) -> None:
"""
C{"::1"} is the IPv6 loopback address literal.
"""
self.assertTrue(isIPv6Address("::1"))
def test_scopeID(self) -> None:
"""
An otherwise valid IPv6 address literal may also include a C{"%"}
followed by an arbitrary scope identifier.
"""
self.assertTrue(isIPv6Address("fe80::1%eth0"))
self.assertTrue(isIPv6Address("fe80::2%1"))
self.assertTrue(isIPv6Address("fe80::3%en2"))
def test_invalidWithScopeID(self) -> None:
"""
An otherwise invalid IPv6 address literal is still invalid with a
trailing scope identifier.
"""
self.assertFalse(isIPv6Address("%eth0"))
self.assertFalse(isIPv6Address(":%eth0"))
self.assertFalse(isIPv6Address("hello%eth0"))
def test_unicodeAndBytes(self) -> None:
"""
L{isIPv6Address} evaluates ASCII-encoded bytes as well as text.
"""
# the type annotation only supports str, but bytes is supported at
# runtime
self.assertTrue(isIPv6Address(b"fe80::2%1")) # type: ignore[arg-type]
self.assertTrue(isIPv6Address("fe80::2%1"))
self.assertFalse(isIPv6Address("\u4321"))
self.assertFalse(isIPv6Address("hello%eth0"))
self.assertFalse(isIPv6Address(b"hello%eth0")) # type: ignore[arg-type]
class TrackingFileDescriptor(FileDescriptor):
"""
Write a limited amount, and track what gets written.
"""
# Annoying implementation details we need to make it work:
connected = True
_writeDisconnected = False
def __init__(
self, operations: list[Union[int, bytes]], written: list[bytes], send_limit: int
):
self.operations = operations
self.written = written
self.SEND_LIMIT = send_limit
FileDescriptor.__init__(self, _FakeFDSetReactor())
def writeSomeData(self, data: bytes) -> int:
toWrite = self.operations.pop(0)
assert isinstance(toWrite, int)
toWrite = min(toWrite, len(data))
self.written.append(data[:toWrite])
return toWrite
class WriteBufferingTests(SynchronousTestCase):
"""
Tests for the complex logic in the L{FileDescriptor} class.
"""
@given(
operations=st.lists(
st.one_of(
st.binary(min_size=1, max_size=10),
st.integers(min_value=0, max_value=10),
),
min_size=3,
max_size=30,
)
)
# This catches a bug that was introduced by a performance refactoring:
@example(operations=[b"abcdef", 0, b"g"])
def test_writeBuffering(self, operations: list[Union[bytes, int]]) -> None:
"""
A sequence of C{write()} and C{doWrite()} will eventually write all the
data correctly and in order.
@param operations: A list of C{bytes} (indicating a C{write()}) or
C{int} (indicating C{doWrite()} with the integer being how much
C{writeSomeData()} writeSomeData will successfully write).
"""
expected = b"".join(op for op in operations if isinstance(op, bytes))
written: list[bytes] = []
# Send at most 5 bytes per call to writeSomeData(); default is much
# higher, of course, but made it smaller so we can have faster
# tests.
SEND_LIMIT = 5
fd = TrackingFileDescriptor(operations, written, SEND_LIMIT)
# Make sure we flush whatever is left at the end:
operations += [SEND_LIMIT * 2] * (1 + len(expected) // SEND_LIMIT)
while operations:
if isinstance(operations[0], bytes):
fd.write(operations.pop(0)) # type: ignore[arg-type]
else:
fd.doWrite()
result = b"".join(written)
self.assertEqual(expected, result)

View File

@@ -0,0 +1,249 @@
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
import os
import socket
from unittest import skipIf
from twisted.internet.address import (
HostnameAddress,
IPv4Address,
IPv6Address,
UNIXAddress,
)
from twisted.python.compat import nativeString
from twisted.python.runtime import platform
from twisted.trial.unittest import SynchronousTestCase, TestCase
symlinkSkip = not platform._supportsSymlinks()
try:
socket.AF_UNIX
except AttributeError:
unixSkip = True
else:
unixSkip = False
class AddressTestCaseMixin:
def test_addressComparison(self):
"""
Two different address instances, sharing the same properties are
considered equal by C{==} and not considered not equal by C{!=}.
Note: When applied via UNIXAddress class, this uses the same
filename for both objects being compared.
"""
self.assertTrue(self.buildAddress() == self.buildAddress())
self.assertFalse(self.buildAddress() != self.buildAddress())
def test_hash(self):
"""
C{__hash__} can be used to get a hash of an address, allowing
addresses to be used as keys in dictionaries, for instance.
"""
addr = self.buildAddress()
d = {addr: True}
self.assertTrue(d[self.buildAddress()])
def test_differentNamesComparison(self):
"""
Check that comparison operators work correctly on address objects
when a different name is passed in
"""
self.assertFalse(self.buildAddress() == self.buildDifferentAddress())
self.assertFalse(self.buildDifferentAddress() == self.buildAddress())
self.assertTrue(self.buildAddress() != self.buildDifferentAddress())
self.assertTrue(self.buildDifferentAddress() != self.buildAddress())
def assertDeprecations(self, testMethod, message):
"""
Assert that the a DeprecationWarning with the given message was
emitted against the given method.
"""
warnings = self.flushWarnings([testMethod])
self.assertEqual(warnings[0]["category"], DeprecationWarning)
self.assertEqual(warnings[0]["message"], message)
self.assertEqual(len(warnings), 1)
class IPv4AddressTestCaseMixin(AddressTestCaseMixin):
addressArgSpec = (("type", "%s"), ("host", "%r"), ("port", "%d"))
class HostnameAddressTests(TestCase, AddressTestCaseMixin):
"""
Test case for L{HostnameAddress}.
"""
addressArgSpec = (("hostname", "%s"), ("port", "%d"))
def buildAddress(self):
"""
Create an arbitrary new L{HostnameAddress} instance.
@return: A L{HostnameAddress} instance.
"""
return HostnameAddress(b"example.com", 0)
def buildDifferentAddress(self):
"""
Like L{buildAddress}, but with a different hostname.
@return: A L{HostnameAddress} instance.
"""
return HostnameAddress(b"example.net", 0)
class IPv4AddressTCPTests(SynchronousTestCase, IPv4AddressTestCaseMixin):
def buildAddress(self):
"""
Create an arbitrary new L{IPv4Address} instance with a C{"TCP"}
type. A new instance is created for each call, but always for the
same address.
"""
return IPv4Address("TCP", "127.0.0.1", 0)
def buildDifferentAddress(self):
"""
Like L{buildAddress}, but with a different fixed address.
"""
return IPv4Address("TCP", "127.0.0.2", 0)
class IPv4AddressUDPTests(SynchronousTestCase, IPv4AddressTestCaseMixin):
def buildAddress(self):
"""
Create an arbitrary new L{IPv4Address} instance with a C{"UDP"}
type. A new instance is created for each call, but always for the
same address.
"""
return IPv4Address("UDP", "127.0.0.1", 0)
def buildDifferentAddress(self):
"""
Like L{buildAddress}, but with a different fixed address.
"""
return IPv4Address("UDP", "127.0.0.2", 0)
class IPv6AddressTests(SynchronousTestCase, AddressTestCaseMixin):
addressArgSpec = (("type", "%s"), ("host", "%r"), ("port", "%d"))
def buildAddress(self):
"""
Create an arbitrary new L{IPv6Address} instance with a C{"TCP"}
type. A new instance is created for each call, but always for the
same address.
"""
return IPv6Address("TCP", "::1", 0)
def buildDifferentAddress(self):
"""
Like L{buildAddress}, but with a different fixed address.
"""
return IPv6Address("TCP", "::2", 0)
@skipIf(unixSkip, "Platform doesn't support UNIX sockets.")
class UNIXAddressTests(SynchronousTestCase):
addressArgSpec = (("name", "%r"),)
def setUp(self):
self._socketAddress = self.mktemp()
self._otherAddress = self.mktemp()
def buildAddress(self):
"""
Create an arbitrary new L{UNIXAddress} instance. A new instance is
created for each call, but always for the same address.
"""
return UNIXAddress(self._socketAddress)
def buildDifferentAddress(self):
"""
Like L{buildAddress}, but with a different fixed address.
"""
return UNIXAddress(self._otherAddress)
def test_repr(self):
"""
The repr of L{UNIXAddress} returns with the filename that the
L{UNIXAddress} is for.
"""
self.assertEqual(
repr(self.buildAddress()),
"UNIXAddress('%s')" % (nativeString(self._socketAddress)),
)
@skipIf(symlinkSkip, "Platform does not support symlinks")
def test_comparisonOfLinkedFiles(self):
"""
UNIXAddress objects compare as equal if they link to the same file.
"""
linkName = self.mktemp()
with open(self._socketAddress, "w") as self.fd:
os.symlink(os.path.abspath(self._socketAddress), linkName)
self.assertEqual(UNIXAddress(self._socketAddress), UNIXAddress(linkName))
self.assertEqual(UNIXAddress(linkName), UNIXAddress(self._socketAddress))
@skipIf(symlinkSkip, "Platform does not support symlinks")
def test_hashOfLinkedFiles(self):
"""
UNIXAddress Objects that compare as equal have the same hash value.
"""
linkName = self.mktemp()
with open(self._socketAddress, "w") as self.fd:
os.symlink(os.path.abspath(self._socketAddress), linkName)
self.assertEqual(
hash(UNIXAddress(self._socketAddress)), hash(UNIXAddress(linkName))
)
@skipIf(unixSkip, "platform doesn't support UNIX sockets.")
class EmptyUNIXAddressTests(SynchronousTestCase, AddressTestCaseMixin):
"""
Tests for L{UNIXAddress} operations involving a L{None} address.
"""
addressArgSpec = (("name", "%r"),)
def setUp(self):
self._socketAddress = self.mktemp()
def buildAddress(self):
"""
Create an arbitrary new L{UNIXAddress} instance. A new instance is
created for each call, but always for the same address. This builds it
with a fixed address of L{None}.
"""
return UNIXAddress(None)
def buildDifferentAddress(self):
"""
Like L{buildAddress}, but with a random temporary directory.
"""
return UNIXAddress(self._socketAddress)
@skipIf(symlinkSkip, "Platform does not support symlinks")
def test_comparisonOfLinkedFiles(self):
"""
A UNIXAddress referring to a L{None} address does not
compare equal to a UNIXAddress referring to a symlink.
"""
linkName = self.mktemp()
with open(self._socketAddress, "w") as self.fd:
os.symlink(os.path.abspath(self._socketAddress), linkName)
self.assertNotEqual(UNIXAddress(self._socketAddress), UNIXAddress(None))
self.assertNotEqual(UNIXAddress(None), UNIXAddress(self._socketAddress))
def test_emptyHash(self):
"""
C{__hash__} can be used to get a hash of an address, even one referring
to L{None} rather than a real path.
"""
addr = self.buildAddress()
d = {addr: True}
self.assertTrue(d[self.buildAddress()])

View File

@@ -0,0 +1,296 @@
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Tests for L{twisted.internet.asyncioreactor}.
"""
import gc
import sys
from asyncio import (
AbstractEventLoop,
AbstractEventLoopPolicy,
DefaultEventLoopPolicy,
Future,
SelectorEventLoop,
get_event_loop,
get_event_loop_policy,
set_event_loop,
set_event_loop_policy,
)
from unittest import skipIf
from twisted.internet.asyncioreactor import AsyncioSelectorReactor
from twisted.python.runtime import platform
from twisted.trial.unittest import SynchronousTestCase
from .reactormixins import ReactorBuilder
hasWindowsProactorEventLoopPolicy = False
hasWindowsSelectorEventLoopPolicy = False
try:
if sys.platform.startswith("win32"):
from asyncio import (
WindowsProactorEventLoopPolicy,
WindowsSelectorEventLoopPolicy,
)
hasWindowsProactorEventLoopPolicy = True
hasWindowsSelectorEventLoopPolicy = True
except ImportError:
pass
_defaultEventLoop = DefaultEventLoopPolicy().new_event_loop()
_defaultEventLoopIsSelector = isinstance(_defaultEventLoop, SelectorEventLoop)
_defaultEventLoop.close()
class AsyncioSelectorReactorTests(ReactorBuilder, SynchronousTestCase):
"""
L{AsyncioSelectorReactor} tests.
"""
def assertReactorWorksWithAsyncioFuture(self, reactor):
"""
Ensure that C{reactor} has an event loop that works
properly with L{asyncio.Future}.
"""
future = Future()
result = []
def completed(future):
result.append(future.result())
reactor.stop()
future.add_done_callback(completed)
future.set_result(True)
self.assertEqual(result, [])
self.runReactor(reactor, timeout=1)
self.assertEqual(result, [True])
def newLoop(self, policy: AbstractEventLoopPolicy) -> AbstractEventLoop:
"""
Make a new asyncio loop from a policy for use with a reactor, and add
appropriate cleanup to restore any global state.
"""
existingLoop = get_event_loop()
existingPolicy = get_event_loop_policy()
result = policy.new_event_loop()
@self.addCleanup
def cleanUp():
result.close()
set_event_loop(existingLoop)
set_event_loop_policy(existingPolicy)
return result
@skipIf(
not _defaultEventLoopIsSelector,
"default event loop: {}\nis not of type SelectorEventLoop "
"on Python {}.{} ({})".format(
type(_defaultEventLoop),
sys.version_info.major,
sys.version_info.minor,
platform.getType(),
),
)
def test_defaultSelectorEventLoopFromGlobalPolicy(self):
"""
L{AsyncioSelectorReactor} wraps the global policy's event loop
by default. This ensures that L{asyncio.Future}s and
coroutines created by library code that uses
L{asyncio.get_event_loop} are bound to the same loop.
"""
reactor = AsyncioSelectorReactor()
self.assertReactorWorksWithAsyncioFuture(reactor)
@skipIf(
not _defaultEventLoopIsSelector,
"default event loop: {}\nis not of type SelectorEventLoop "
"on Python {}.{} ({})".format(
type(_defaultEventLoop),
sys.version_info.major,
sys.version_info.minor,
platform.getType(),
),
)
def test_newSelectorEventLoopFromDefaultEventLoopPolicy(self):
"""
If we use the L{asyncio.DefaultLoopPolicy} to create a new event loop,
and then pass that event loop to a new L{AsyncioSelectorReactor},
this reactor should work properly with L{asyncio.Future}.
"""
event_loop = self.newLoop(DefaultEventLoopPolicy())
reactor = AsyncioSelectorReactor(event_loop)
set_event_loop(event_loop)
self.assertReactorWorksWithAsyncioFuture(reactor)
@skipIf(
_defaultEventLoopIsSelector,
"default event loop: {}\nis of type SelectorEventLoop "
"on Python {}.{} ({})".format(
type(_defaultEventLoop),
sys.version_info.major,
sys.version_info.minor,
platform.getType(),
),
)
def test_defaultNotASelectorEventLoopFromGlobalPolicy(self):
"""
On Windows Python 3.5 to 3.7, L{get_event_loop()} returns a
L{WindowsSelectorEventLoop} by default.
On Windows Python 3.8+, L{get_event_loop()} returns a
L{WindowsProactorEventLoop} by default.
L{AsyncioSelectorReactor} should raise a
L{TypeError} if the default event loop is not a
L{WindowsSelectorEventLoop}.
"""
self.assertRaises(TypeError, AsyncioSelectorReactor)
@skipIf(
not hasWindowsProactorEventLoopPolicy, "WindowsProactorEventLoop not available"
)
def test_WindowsProactorEventLoop(self):
"""
L{AsyncioSelectorReactor} will raise a L{TypeError}
if instantiated with a L{asyncio.WindowsProactorEventLoop}
"""
event_loop = self.newLoop(WindowsProactorEventLoopPolicy())
self.assertRaises(TypeError, AsyncioSelectorReactor, event_loop)
@skipIf(
not hasWindowsSelectorEventLoopPolicy,
"WindowsSelectorEventLoop only on Windows",
)
def test_WindowsSelectorEventLoop(self):
"""
L{WindowsSelectorEventLoop} works with L{AsyncioSelectorReactor}
"""
event_loop = self.newLoop(WindowsSelectorEventLoopPolicy())
reactor = AsyncioSelectorReactor(event_loop)
set_event_loop(event_loop)
self.assertReactorWorksWithAsyncioFuture(reactor)
@skipIf(
not hasWindowsProactorEventLoopPolicy,
"WindowsProactorEventLoopPolicy only on Windows",
)
def test_WindowsProactorEventLoopPolicy(self):
"""
L{AsyncioSelectorReactor} will raise a L{TypeError}
if L{asyncio.WindowsProactorEventLoopPolicy} is default.
"""
set_event_loop_policy(WindowsProactorEventLoopPolicy())
self.addCleanup(lambda: set_event_loop_policy(None))
with self.assertRaises(TypeError):
AsyncioSelectorReactor()
@skipIf(
not hasWindowsSelectorEventLoopPolicy,
"WindowsSelectorEventLoopPolicy only on Windows",
)
def test_WindowsSelectorEventLoopPolicy(self):
"""
L{AsyncioSelectorReactor} will work if
if L{asyncio.WindowsSelectorEventLoopPolicy} is default.
"""
set_event_loop_policy(WindowsSelectorEventLoopPolicy())
self.addCleanup(lambda: set_event_loop_policy(None))
reactor = AsyncioSelectorReactor()
self.assertReactorWorksWithAsyncioFuture(reactor)
def test_seconds(self):
"""L{seconds} should return a plausible epoch time."""
if hasWindowsSelectorEventLoopPolicy:
set_event_loop_policy(WindowsSelectorEventLoopPolicy())
self.addCleanup(lambda: set_event_loop_policy(None))
reactor = AsyncioSelectorReactor()
result = reactor.seconds()
# greater than 2020-01-01
self.assertGreater(result, 1577836800)
# less than 2120-01-01
self.assertLess(result, 4733510400)
def test_delayedCallResetToLater(self):
"""
L{DelayedCall.reset()} properly reschedules timer to later time
"""
if hasWindowsSelectorEventLoopPolicy:
set_event_loop_policy(WindowsSelectorEventLoopPolicy())
self.addCleanup(lambda: set_event_loop_policy(None))
reactor = AsyncioSelectorReactor()
timer_called_at = [None]
def on_timer():
timer_called_at[0] = reactor.seconds()
start_time = reactor.seconds()
dc = reactor.callLater(0, on_timer)
dc.reset(0.5)
reactor.callLater(1, reactor.stop)
reactor.run()
self.assertIsNotNone(timer_called_at[0])
self.assertGreater(timer_called_at[0] - start_time, 0.4)
def test_delayedCallResetToEarlier(self):
"""
L{DelayedCall.reset()} properly reschedules timer to earlier time
"""
if hasWindowsSelectorEventLoopPolicy:
set_event_loop_policy(WindowsSelectorEventLoopPolicy())
reactor = AsyncioSelectorReactor()
timer_called_at = [None]
def on_timer():
timer_called_at[0] = reactor.seconds()
start_time = reactor.seconds()
dc = reactor.callLater(0.5, on_timer)
dc.reset(0)
reactor.callLater(1, reactor.stop)
import io
from contextlib import redirect_stderr
stderr = io.StringIO()
with redirect_stderr(stderr):
reactor.run()
self.assertEqual(stderr.getvalue(), "")
self.assertIsNotNone(timer_called_at[0])
self.assertLess(timer_called_at[0] - start_time, 0.4)
if hasWindowsSelectorEventLoopPolicy:
set_event_loop_policy(None)
def test_noCycleReferencesInCallLater(self):
"""
L{AsyncioSelectorReactor.callLater()} doesn't leave cyclic references
"""
if hasWindowsSelectorEventLoopPolicy:
set_event_loop_policy(WindowsSelectorEventLoopPolicy())
gc_was_enabled = gc.isenabled()
gc.disable()
try:
objects_before = len(gc.get_objects())
timer_count = 1000
reactor = AsyncioSelectorReactor()
for _ in range(timer_count):
reactor.callLater(0, lambda: None)
reactor.runUntilCurrent()
objects_after = len(gc.get_objects())
self.assertLess((objects_after - objects_before) / timer_count, 1)
finally:
if gc_was_enabled:
gc.enable()
if hasWindowsSelectorEventLoopPolicy:
set_event_loop_policy(None)

View File

@@ -0,0 +1,472 @@
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Tests for L{twisted.internet.base}.
"""
import socket
from queue import Queue
from typing import Callable
from unittest import skipIf
from zope.interface import implementer
from typing_extensions import ParamSpec
from twisted.internet._resolver import FirstOneWins
from twisted.internet.base import DelayedCall, ReactorBase, ThreadedResolver
from twisted.internet.defer import Deferred
from twisted.internet.error import DNSLookupError
from twisted.internet.interfaces import IReactorThreads, IReactorTime, IResolverSimple
from twisted.internet.task import Clock
from twisted.python.threadpool import ThreadPool
from twisted.trial.unittest import SkipTest, TestCase
try:
import signal as _signal
except ImportError:
signal = None
else:
signal = _signal
_P = ParamSpec("_P")
@implementer(IReactorTime, IReactorThreads)
class FakeReactor:
"""
A fake reactor implementation which just supports enough reactor APIs for
L{ThreadedResolver}.
"""
def __init__(self):
self._clock = Clock()
self.callLater = self._clock.callLater
self._threadpool = ThreadPool()
self._threadpool.start()
self.getThreadPool = lambda: self._threadpool
self._threadCalls = Queue()
def callFromThread(
self, callable: Callable[_P, object], *args: _P.args, **kwargs: _P.kwargs
) -> None:
self._threadCalls.put((callable, args, kwargs))
def _runThreadCalls(self):
f, args, kwargs = self._threadCalls.get()
f(*args, **kwargs)
def _stop(self):
self._threadpool.stop()
def getDelayedCalls(self):
# IReactorTime.getDelayedCalls
pass
def seconds(self) -> float: # type: ignore[empty-body]
# IReactorTime.seconds
pass
def callInThread(
self, callable: Callable[_P, object], *args: _P.args, **kwargs: _P.kwargs
) -> None:
# IReactorInThreads.callInThread
pass
def suggestThreadPoolSize(self, size):
# IReactorThreads.suggestThreadPoolSize
pass
class ThreadedResolverTests(TestCase):
"""
Tests for L{ThreadedResolver}.
"""
def test_success(self):
"""
L{ThreadedResolver.getHostByName} returns a L{Deferred} which fires
with the value returned by the call to L{socket.gethostbyname} in the
threadpool of the reactor passed to L{ThreadedResolver.__init__}.
"""
ip = "10.0.0.17"
name = "foo.bar.example.com"
timeout = 30
reactor = FakeReactor()
self.addCleanup(reactor._stop)
lookedUp = []
resolvedTo = []
def fakeGetHostByName(name):
lookedUp.append(name)
return ip
self.patch(socket, "gethostbyname", fakeGetHostByName)
resolver = ThreadedResolver(reactor)
d = resolver.getHostByName(name, (timeout,))
d.addCallback(resolvedTo.append)
reactor._runThreadCalls()
self.assertEqual(lookedUp, [name])
self.assertEqual(resolvedTo, [ip])
# Make sure that any timeout-related stuff gets cleaned up.
reactor._clock.advance(timeout + 1)
self.assertEqual(reactor._clock.calls, [])
def test_failure(self):
"""
L{ThreadedResolver.getHostByName} returns a L{Deferred} which fires a
L{Failure} if the call to L{socket.gethostbyname} raises an exception.
"""
timeout = 30
reactor = FakeReactor()
self.addCleanup(reactor._stop)
def fakeGetHostByName(name):
raise OSError("ENOBUFS (this is a funny joke)")
self.patch(socket, "gethostbyname", fakeGetHostByName)
failedWith = []
resolver = ThreadedResolver(reactor)
d = resolver.getHostByName("some.name", (timeout,))
self.assertFailure(d, DNSLookupError)
d.addCallback(failedWith.append)
reactor._runThreadCalls()
self.assertEqual(len(failedWith), 1)
# Make sure that any timeout-related stuff gets cleaned up.
reactor._clock.advance(timeout + 1)
self.assertEqual(reactor._clock.calls, [])
def test_timeout(self):
"""
If L{socket.gethostbyname} does not complete before the specified
timeout elapsed, the L{Deferred} returned by
L{ThreadedResolver.getHostByName} fails with L{DNSLookupError}.
"""
timeout = 10
reactor = FakeReactor()
self.addCleanup(reactor._stop)
result = Queue()
def fakeGetHostByName(name):
raise result.get()
self.patch(socket, "gethostbyname", fakeGetHostByName)
failedWith = []
resolver = ThreadedResolver(reactor)
d = resolver.getHostByName("some.name", (timeout,))
self.assertFailure(d, DNSLookupError)
d.addCallback(failedWith.append)
reactor._clock.advance(timeout - 1)
self.assertEqual(failedWith, [])
reactor._clock.advance(1)
self.assertEqual(len(failedWith), 1)
# Eventually the socket.gethostbyname does finish - in this case, with
# an exception. Nobody cares, though.
result.put(IOError("The I/O was errorful"))
def test_resolverGivenStr(self):
"""
L{ThreadedResolver.getHostByName} is passed L{str}, encoded using IDNA
if required.
"""
calls = []
@implementer(IResolverSimple)
class FakeResolver:
def getHostByName(self, name, timeouts=()):
calls.append(name)
return Deferred()
class JustEnoughReactor(ReactorBase):
def installWaker(self):
pass
fake = FakeResolver()
reactor = JustEnoughReactor()
reactor.installResolver(fake)
rec = FirstOneWins(Deferred())
reactor.nameResolver.resolveHostName(rec, "example.example")
reactor.nameResolver.resolveHostName(rec, "example.example")
reactor.nameResolver.resolveHostName(rec, "v\xe4\xe4ntynyt.example")
reactor.nameResolver.resolveHostName(rec, "\u0440\u0444.example")
reactor.nameResolver.resolveHostName(rec, "xn----7sbb4ac0ad0be6cf.xn--p1ai")
self.assertEqual(len(calls), 5)
self.assertEqual(list(map(type, calls)), [str] * 5)
self.assertEqual("example.example", calls[0])
self.assertEqual("example.example", calls[1])
self.assertEqual("xn--vntynyt-5waa.example", calls[2])
self.assertEqual("xn--p1ai.example", calls[3])
self.assertEqual("xn----7sbb4ac0ad0be6cf.xn--p1ai", calls[4])
def nothing():
"""
Function used by L{DelayedCallTests.test_str}.
"""
class DelayedCallMixin:
"""
L{DelayedCall}
"""
def _getDelayedCallAt(self, time):
"""
Get a L{DelayedCall} instance at a given C{time}.
@param time: The absolute time at which the returned L{DelayedCall}
will be scheduled.
"""
def noop(call):
pass
return DelayedCall(time, lambda: None, (), {}, noop, noop, None)
def setUp(self):
"""
Create two L{DelayedCall} instanced scheduled to run at different
times.
"""
self.zero = self._getDelayedCallAt(0)
self.one = self._getDelayedCallAt(1)
def test_str(self):
"""
The string representation of a L{DelayedCall} instance, as returned by
L{str}, includes the unsigned id of the instance, as well as its state,
the function to be called, and the function arguments.
"""
dc = DelayedCall(12, nothing, (3,), {"A": 5}, None, None, lambda: 1.5)
self.assertEqual(
str(dc),
"<DelayedCall 0x%x [10.5s] called=0 cancelled=0 nothing(3, A=5)>"
% (id(dc),),
)
def test_repr(self):
"""
The string representation of a L{DelayedCall} instance, as returned by
{repr}, is identical to that returned by L{str}.
"""
dc = DelayedCall(13, nothing, (6,), {"A": 9}, None, None, lambda: 1.6)
self.assertEqual(str(dc), repr(dc))
def test_lt(self):
"""
For two instances of L{DelayedCall} C{a} and C{b}, C{a < b} is true
if and only if C{a} is scheduled to run before C{b}.
"""
zero, one = self.zero, self.one
self.assertTrue(zero < one)
self.assertFalse(one < zero)
self.assertFalse(zero < zero)
self.assertFalse(one < one)
def test_le(self):
"""
For two instances of L{DelayedCall} C{a} and C{b}, C{a <= b} is true
if and only if C{a} is scheduled to run before C{b} or at the same
time as C{b}.
"""
zero, one = self.zero, self.one
self.assertTrue(zero <= one)
self.assertFalse(one <= zero)
self.assertTrue(zero <= zero)
self.assertTrue(one <= one)
def test_gt(self):
"""
For two instances of L{DelayedCall} C{a} and C{b}, C{a > b} is true
if and only if C{a} is scheduled to run after C{b}.
"""
zero, one = self.zero, self.one
self.assertTrue(one > zero)
self.assertFalse(zero > one)
self.assertFalse(zero > zero)
self.assertFalse(one > one)
def test_ge(self):
"""
For two instances of L{DelayedCall} C{a} and C{b}, C{a > b} is true
if and only if C{a} is scheduled to run after C{b} or at the same
time as C{b}.
"""
zero, one = self.zero, self.one
self.assertTrue(one >= zero)
self.assertFalse(zero >= one)
self.assertTrue(zero >= zero)
self.assertTrue(one >= one)
def test_eq(self):
"""
A L{DelayedCall} instance is only equal to itself.
"""
# Explicitly use == here, instead of assertEqual, to be more
# confident __eq__ is being tested.
self.assertFalse(self.zero == self.one)
self.assertTrue(self.zero == self.zero)
self.assertTrue(self.one == self.one)
def test_ne(self):
"""
A L{DelayedCall} instance is not equal to any other object.
"""
# Explicitly use != here, instead of assertEqual, to be more
# confident __ne__ is being tested.
self.assertTrue(self.zero != self.one)
self.assertFalse(self.zero != self.zero)
self.assertFalse(self.one != self.one)
class DelayedCallNoDebugTests(DelayedCallMixin, TestCase):
"""
L{DelayedCall}
"""
def setUp(self):
"""
Turn debug off.
"""
self.patch(DelayedCall, "debug", False)
DelayedCallMixin.setUp(self)
def test_str(self):
"""
The string representation of a L{DelayedCall} instance, as returned by
L{str}, includes the unsigned id of the instance, as well as its state,
the function to be called, and the function arguments.
"""
dc = DelayedCall(12, nothing, (3,), {"A": 5}, None, None, lambda: 1.5)
expected = (
"<DelayedCall 0x{:x} [10.5s] called=0 cancelled=0 "
"nothing(3, A=5)>".format(id(dc))
)
self.assertEqual(str(dc), expected)
def test_switchToDebug(self):
"""
If L{DelayedCall.debug} changes from C{0} to C{1} between
L{DelayeCall.__init__} and L{DelayedCall.__repr__} then
L{DelayedCall.__repr__} returns a string that does not include the
creator stack.
"""
dc = DelayedCall(3, lambda: None, (), {}, nothing, nothing, lambda: 2)
dc.debug = 1
self.assertNotIn("traceback at creation", repr(dc))
class DelayedCallDebugTests(DelayedCallMixin, TestCase):
"""
L{DelayedCall}
"""
def setUp(self):
"""
Turn debug on.
"""
self.patch(DelayedCall, "debug", True)
DelayedCallMixin.setUp(self)
def test_str(self):
"""
The string representation of a L{DelayedCall} instance, as returned by
L{str}, includes the unsigned id of the instance, as well as its state,
the function to be called, and the function arguments.
"""
dc = DelayedCall(12, nothing, (3,), {"A": 5}, None, None, lambda: 1.5)
expectedRegexp = (
"<DelayedCall 0x{:x} \\[10.5s\\] called=0 cancelled=0 "
"nothing\\(3, A=5\\)\n\n"
"traceback at creation:".format(id(dc))
)
self.assertRegex(str(dc), expectedRegexp)
def test_switchFromDebug(self):
"""
If L{DelayedCall.debug} changes from C{1} to C{0} between
L{DelayeCall.__init__} and L{DelayedCall.__repr__} then
L{DelayedCall.__repr__} returns a string that includes the creator
stack (we captured it, we might as well display it).
"""
dc = DelayedCall(3, lambda: None, (), {}, nothing, nothing, lambda: 2)
dc.debug = 0
self.assertIn("traceback at creation", repr(dc))
class TestSpySignalCapturingReactor(ReactorBase):
"""
Subclass of ReactorBase to capture signals delivered to the
reactor for inspection.
"""
def installWaker(self):
"""
Required method, unused.
"""
@skipIf(not signal, "signal module not available")
class ReactorBaseSignalTests(TestCase):
"""
Tests to exercise ReactorBase's signal exit reporting path.
"""
def test_exitSignalDefaultsToNone(self):
"""
The default value of the _exitSignal attribute is None.
"""
reactor = TestSpySignalCapturingReactor()
self.assertIs(None, reactor._exitSignal)
def test_captureSIGINT(self):
"""
ReactorBase's SIGINT handler saves the value of SIGINT to the
_exitSignal attribute.
"""
reactor = TestSpySignalCapturingReactor()
reactor.sigInt(signal.SIGINT, None)
self.assertEquals(signal.SIGINT, reactor._exitSignal)
def test_captureSIGTERM(self):
"""
ReactorBase's SIGTERM handler saves the value of SIGTERM to the
_exitSignal attribute.
"""
reactor = TestSpySignalCapturingReactor()
reactor.sigTerm(signal.SIGTERM, None)
self.assertEquals(signal.SIGTERM, reactor._exitSignal)
def test_captureSIGBREAK(self):
"""
ReactorBase's SIGBREAK handler saves the value of SIGBREAK to the
_exitSignal attribute.
"""
if not hasattr(signal, "SIGBREAK"):
raise SkipTest("signal module does not have SIGBREAK")
reactor = TestSpySignalCapturingReactor()
reactor.sigBreak(signal.SIGBREAK, None)
self.assertEquals(signal.SIGBREAK, reactor._exitSignal)

View File

@@ -0,0 +1,75 @@
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Tests for L{twisted.internet._baseprocess} which implements process-related
functionality that is useful in all platforms supporting L{IReactorProcess}.
"""
from twisted.internet._baseprocess import BaseProcess
from twisted.python.deprecate import getWarningMethod, setWarningMethod
from twisted.trial.unittest import TestCase
class BaseProcessTests(TestCase):
"""
Tests for L{BaseProcess}, a parent class for other classes which represent
processes which implements functionality common to many different process
implementations.
"""
def test_callProcessExited(self):
"""
L{BaseProcess._callProcessExited} calls the C{processExited} method of
its C{proto} attribute and passes it a L{Failure} wrapping the given
exception.
"""
class FakeProto:
reason = None
def processExited(self, reason):
self.reason = reason
reason = RuntimeError("fake reason")
process = BaseProcess(FakeProto())
process._callProcessExited(reason)
process.proto.reason.trap(RuntimeError)
self.assertIs(reason, process.proto.reason.value)
def test_callProcessExitedMissing(self):
"""
L{BaseProcess._callProcessExited} emits a L{DeprecationWarning} if the
object referred to by its C{proto} attribute has no C{processExited}
method.
"""
class FakeProto:
pass
reason = object()
process = BaseProcess(FakeProto())
self.addCleanup(setWarningMethod, getWarningMethod())
warnings = []
def collect(message, category, stacklevel):
warnings.append((message, category, stacklevel))
setWarningMethod(collect)
process._callProcessExited(reason)
[(message, category, stacklevel)] = warnings
self.assertEqual(
message,
"Since Twisted 8.2, IProcessProtocol.processExited is required. "
"%s.%s must implement it." % (FakeProto.__module__, FakeProto.__name__),
)
self.assertIs(category, DeprecationWarning)
# The stacklevel doesn't really make sense for this kind of
# deprecation. Requiring it to be 0 will at least avoid pointing to
# any part of Twisted or a random part of the application's code, which
# I think would be more misleading than having it point inside the
# warning system itself. -exarkun
self.assertEqual(stacklevel, 0)

View File

@@ -0,0 +1,101 @@
from typing import TYPE_CHECKING, List
from twisted.trial.unittest import SynchronousTestCase
from .reactormixins import ReactorBuilder
if TYPE_CHECKING:
fakeBase = SynchronousTestCase
else:
fakeBase = object
def noop() -> None:
"""
Do-nothing callable. Stub for testing.
"""
noop() # Exercise for coverage, since it will never be called below.
class CoreFoundationSpecificTests(ReactorBuilder, fakeBase):
"""
Tests for platform interactions of the CoreFoundation-based reactor.
"""
_reactors = ["twisted.internet.cfreactor.CFReactor"]
def test_whiteboxStopSimulating(self) -> None:
"""
CFReactor's simulation timer is None after CFReactor crashes.
"""
r = self.buildReactor()
r.callLater(0, r.crash)
r.callLater(100, noop)
self.runReactor(r)
self.assertIs(r._currentSimulator, None)
def test_callLaterLeakage(self) -> None:
"""
callLater should not leak global state into CoreFoundation which will
be invoked by a different reactor running the main loop.
@note: this test may actually be usable for other reactors as well, so
we may wish to promote it to ensure this invariant across other
foreign-main-loop reactors.
"""
r = self.buildReactor()
delayed = r.callLater(0, noop)
r2 = self.buildReactor()
def stopBlocking() -> None:
r2.callLater(0, r2stop)
def r2stop() -> None:
r2.stop()
r2.callLater(0, stopBlocking)
self.runReactor(r2)
self.assertEqual(r.getDelayedCalls(), [delayed])
def test_whiteboxIterate(self) -> None:
"""
C{.iterate()} should remove the CFTimer that will run Twisted's
callLaters from the loop, even if one is still pending. We test this
state indirectly with a white-box assertion by verifying the
C{_currentSimulator} is set to C{None}, since CoreFoundation does not
allow us to enumerate all active timers or sources.
"""
r = self.buildReactor()
x: List[int] = []
r.callLater(0, x.append, 1)
delayed = r.callLater(100, noop)
r.iterate()
self.assertIs(r._currentSimulator, None)
self.assertEqual(r.getDelayedCalls(), [delayed])
self.assertEqual(x, [1])
def test_noTimers(self) -> None:
"""
The loop can wake up just fine even if there are no timers in it.
"""
r = self.buildReactor()
stopped = []
def doStop() -> None:
r.stop()
stopped.append("yes")
def sleepThenStop() -> None:
r.callFromThread(doStop)
r.callLater(0, r.callInThread, sleepThenStop)
# Can't use runReactor here because it does a callLater. This is
# therefore a somewhat risky test: inherently, this is the "no timed
# events anywhere in the reactor" test case and so we can't have a
# timeout for it.
r.run()
self.assertEqual(stopped, ["yes"])
globals().update(CoreFoundationSpecificTests.makeTestCaseClasses())

View File

@@ -0,0 +1,316 @@
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Tests for implementations of L{IReactorCore}.
"""
import signal
import time
from types import FrameType
from typing import Callable, List, Optional, Tuple, Union, cast
from twisted.internet.abstract import FileDescriptor
from twisted.internet.defer import Deferred
from twisted.internet.error import ReactorAlreadyRunning, ReactorNotRestartable
from twisted.internet.test.reactormixins import ReactorBuilder
from twisted.python.failure import Failure
from twisted.trial.unittest import SynchronousTestCase
class SystemEventTestsBuilder(ReactorBuilder):
"""
Builder defining tests relating to L{IReactorCore.addSystemEventTrigger}
and L{IReactorCore.fireSystemEvent}.
"""
def test_stopWhenNotStarted(self) -> None:
"""
C{reactor.stop()} raises L{RuntimeError} when called when the reactor
has not been started.
"""
reactor = self.buildReactor()
cast(SynchronousTestCase, self).assertRaises(RuntimeError, reactor.stop)
def test_stopWhenAlreadyStopped(self) -> None:
"""
C{reactor.stop()} raises L{RuntimeError} when called after the reactor
has been stopped.
"""
reactor = self.buildReactor()
reactor.callWhenRunning(reactor.stop)
self.runReactor(reactor)
cast(SynchronousTestCase, self).assertRaises(RuntimeError, reactor.stop)
def test_callWhenRunningOrder(self) -> None:
"""
Functions are run in the order that they were passed to
L{reactor.callWhenRunning}.
"""
reactor = self.buildReactor()
events: List[str] = []
reactor.callWhenRunning(events.append, "first")
reactor.callWhenRunning(events.append, "second")
reactor.callWhenRunning(reactor.stop)
self.runReactor(reactor)
cast(SynchronousTestCase, self).assertEqual(events, ["first", "second"])
def test_runningForStartupEvents(self) -> None:
"""
The reactor is not running when C{"before"} C{"startup"} triggers are
called and is running when C{"during"} and C{"after"} C{"startup"}
triggers are called.
"""
reactor = self.buildReactor()
state = {}
def beforeStartup() -> None:
state["before"] = reactor.running
def duringStartup() -> None:
state["during"] = reactor.running
def afterStartup() -> None:
state["after"] = reactor.running
testCase = cast(SynchronousTestCase, self)
reactor.addSystemEventTrigger("before", "startup", beforeStartup)
reactor.addSystemEventTrigger("during", "startup", duringStartup)
reactor.addSystemEventTrigger("after", "startup", afterStartup)
reactor.callWhenRunning(reactor.stop)
testCase.assertEqual(state, {})
self.runReactor(reactor)
testCase.assertEqual(state, {"before": False, "during": True, "after": True})
def test_signalHandlersInstalledDuringStartup(self) -> None:
"""
Signal handlers are installed in responsed to the C{"during"}
C{"startup"}.
"""
reactor = self.buildReactor()
phase: Optional[str] = None
def beforeStartup() -> None:
nonlocal phase
phase = "before"
def afterStartup() -> None:
nonlocal phase
phase = "after"
reactor.addSystemEventTrigger("before", "startup", beforeStartup)
reactor.addSystemEventTrigger("after", "startup", afterStartup)
sawPhase = []
def fakeSignal(signum: int, action: Callable[[int, FrameType], None]) -> None:
sawPhase.append(phase)
testCase = cast(SynchronousTestCase, self)
testCase.patch(signal, "signal", fakeSignal)
reactor.callWhenRunning(reactor.stop)
testCase.assertIsNone(phase)
testCase.assertEqual(sawPhase, [])
self.runReactor(reactor)
testCase.assertIn("before", sawPhase)
testCase.assertEqual(phase, "after")
def test_stopShutDownEvents(self) -> None:
"""
C{reactor.stop()} fires all three phases of shutdown event triggers
before it makes C{reactor.run()} return.
"""
reactor = self.buildReactor()
events = []
reactor.addSystemEventTrigger(
"before", "shutdown", lambda: events.append(("before", "shutdown"))
)
reactor.addSystemEventTrigger(
"during", "shutdown", lambda: events.append(("during", "shutdown"))
)
reactor.addSystemEventTrigger(
"after", "shutdown", lambda: events.append(("after", "shutdown"))
)
reactor.callWhenRunning(reactor.stop)
self.runReactor(reactor)
cast(SynchronousTestCase, self).assertEqual(
events,
[("before", "shutdown"), ("during", "shutdown"), ("after", "shutdown")],
)
def test_shutdownFiresTriggersAsynchronously(self) -> None:
"""
C{"before"} C{"shutdown"} triggers are not run synchronously from
L{reactor.stop}.
"""
reactor = self.buildReactor()
events: List[str] = []
reactor.addSystemEventTrigger(
"before", "shutdown", events.append, "before shutdown"
)
def stopIt() -> None:
reactor.stop()
events.append("stopped")
testCase = cast(SynchronousTestCase, self)
reactor.callWhenRunning(stopIt)
testCase.assertEqual(events, [])
self.runReactor(reactor)
testCase.assertEqual(events, ["stopped", "before shutdown"])
def test_shutdownDisconnectsCleanly(self) -> None:
"""
A L{IFileDescriptor.connectionLost} implementation which raises an
exception does not prevent the remaining L{IFileDescriptor}s from
having their C{connectionLost} method called.
"""
lostOK = [False]
# Subclass FileDescriptor to get logPrefix
class ProblematicFileDescriptor(FileDescriptor):
def connectionLost(self, reason: Failure) -> None:
raise RuntimeError("simulated connectionLost error")
class OKFileDescriptor(FileDescriptor):
def connectionLost(self, reason: Failure) -> None:
lostOK[0] = True
testCase = cast(SynchronousTestCase, self)
reactor = self.buildReactor()
# Unfortunately, it is necessary to patch removeAll to directly control
# the order of the returned values. The test is only valid if
# ProblematicFileDescriptor comes first. Also, return these
# descriptors only the first time removeAll is called so that if it is
# called again the file descriptors aren't re-disconnected.
fds = iter([ProblematicFileDescriptor(), OKFileDescriptor()])
reactor.removeAll = lambda: fds
reactor.callWhenRunning(reactor.stop)
self.runReactor(reactor)
testCase.assertEqual(len(testCase.flushLoggedErrors(RuntimeError)), 1)
testCase.assertTrue(lostOK[0])
def test_multipleRun(self) -> None:
"""
C{reactor.run()} raises L{ReactorAlreadyRunning} when called when
the reactor is already running.
"""
events: List[str] = []
testCase = cast(SynchronousTestCase, self)
def reentrantRun() -> None:
testCase.assertRaises(ReactorAlreadyRunning, reactor.run)
events.append("tested")
reactor = self.buildReactor()
reactor.callWhenRunning(reentrantRun)
reactor.callWhenRunning(reactor.stop)
self.runReactor(reactor)
testCase.assertEqual(events, ["tested"])
def test_runWithAsynchronousBeforeStartupTrigger(self) -> None:
"""
When there is a C{'before'} C{'startup'} trigger which returns an
unfired L{Deferred}, C{reactor.run()} starts the reactor and does not
return until after C{reactor.stop()} is called
"""
events = []
def trigger() -> Deferred[object]:
events.append("trigger")
d: Deferred[object] = Deferred()
d.addCallback(callback)
reactor.callLater(0, d.callback, None)
return d
def callback(ignored: object) -> None:
events.append("callback")
reactor.stop()
reactor = self.buildReactor()
reactor.addSystemEventTrigger("before", "startup", trigger)
self.runReactor(reactor)
cast(SynchronousTestCase, self).assertEqual(events, ["trigger", "callback"])
def test_iterate(self) -> None:
"""
C{reactor.iterate()} does not block.
"""
reactor = self.buildReactor()
t = reactor.callLater(5, reactor.crash)
start = time.time()
reactor.iterate(0) # Shouldn't block
elapsed = time.time() - start
cast(SynchronousTestCase, self).assertTrue(elapsed < 2)
t.cancel()
def test_crash(self) -> None:
"""
C{reactor.crash()} stops the reactor and does not fire shutdown
triggers.
"""
reactor = self.buildReactor()
events = []
reactor.addSystemEventTrigger(
"before", "shutdown", lambda: events.append(("before", "shutdown"))
)
reactor.callWhenRunning(reactor.callLater, 0, reactor.crash)
self.runReactor(reactor)
testCase = cast(SynchronousTestCase, self)
testCase.assertFalse(reactor.running)
testCase.assertFalse(
events, "Shutdown triggers invoked but they should not have been."
)
def test_runAfterCrash(self) -> None:
"""
C{reactor.run()} restarts the reactor after it has been stopped by
C{reactor.crash()}.
"""
events: List[Union[str, Tuple[str, bool]]] = []
def crash() -> None:
events.append("crash")
reactor.crash()
reactor = self.buildReactor()
reactor.callWhenRunning(crash)
self.runReactor(reactor)
def stop() -> None:
events.append(("stop", reactor.running))
reactor.stop()
reactor.callWhenRunning(stop)
self.runReactor(reactor)
cast(SynchronousTestCase, self).assertEqual(events, ["crash", ("stop", True)])
def test_runAfterStop(self) -> None:
"""
C{reactor.run()} raises L{ReactorNotRestartable} when called when
the reactor is being run after getting stopped priorly.
"""
events: List[str] = []
testCase = cast(SynchronousTestCase, self)
def restart() -> None:
testCase.assertRaises(ReactorNotRestartable, reactor.run)
events.append("tested")
reactor = self.buildReactor()
reactor.callWhenRunning(reactor.stop)
reactor.addSystemEventTrigger("after", "shutdown", restart)
self.runReactor(reactor)
testCase.assertEqual(events, ["tested"])
globals().update(SystemEventTestsBuilder.makeTestCaseClasses())

View File

@@ -0,0 +1,114 @@
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Tests for L{twisted.internet.default}.
"""
from __future__ import annotations
import select
import sys
from typing import Callable
from twisted.internet import default
from twisted.internet.default import _getInstallFunction, install
from twisted.internet.interfaces import IReactorCore
from twisted.internet.test.test_main import NoReactor
from twisted.python.reflect import requireModule
from twisted.python.runtime import Platform
from twisted.trial.unittest import SynchronousTestCase
unix = Platform("posix", "other")
linux = Platform("posix", "linux2")
windows = Platform("nt", "win32")
osx = Platform("posix", "darwin")
class PollReactorTests(SynchronousTestCase):
"""
Tests for the cases of L{twisted.internet.default._getInstallFunction}
in which it picks the poll(2) or epoll(7)-based reactors.
"""
def assertIsPoll(self, install: Callable[..., object]) -> None:
"""
Assert the given function will install the poll() reactor, or select()
if poll() is unavailable.
"""
if hasattr(select, "poll"):
self.assertEqual(install.__module__, "twisted.internet.pollreactor")
else:
self.assertEqual(install.__module__, "twisted.internet.selectreactor")
def test_unix(self) -> None:
"""
L{_getInstallFunction} chooses the poll reactor on arbitrary Unix
platforms, falling back to select(2) if it is unavailable.
"""
install = _getInstallFunction(unix)
self.assertIsPoll(install)
def test_linux(self) -> None:
"""
L{_getInstallFunction} chooses the epoll reactor on Linux, or poll if
epoll is unavailable.
"""
install = _getInstallFunction(linux)
if requireModule("twisted.internet.epollreactor") is None:
self.assertIsPoll(install)
else:
self.assertEqual(install.__module__, "twisted.internet.epollreactor")
class SelectReactorTests(SynchronousTestCase):
"""
Tests for the cases of L{twisted.internet.default._getInstallFunction}
in which it picks the select(2)-based reactor.
"""
def test_osx(self) -> None:
"""
L{_getInstallFunction} chooses the select reactor on macOS.
"""
install = _getInstallFunction(osx)
self.assertEqual(install.__module__, "twisted.internet.selectreactor")
def test_windows(self) -> None:
"""
L{_getInstallFunction} chooses the select reactor on Windows.
"""
install = _getInstallFunction(windows)
self.assertEqual(install.__module__, "twisted.internet.selectreactor")
class InstallationTests(SynchronousTestCase):
"""
Tests for actual installation of the reactor.
"""
def test_install(self) -> None:
"""
L{install} installs a reactor.
"""
with NoReactor():
install()
self.assertIn("twisted.internet.reactor", sys.modules)
def test_reactor(self) -> None:
"""
Importing L{twisted.internet.reactor} installs the default reactor if
none is installed.
"""
installed: list[bool] = []
def installer() -> object:
installed.append(True)
return install()
self.patch(default, "install", installer)
with NoReactor():
from twisted.internet import reactor
self.assertTrue(IReactorCore.providedBy(reactor))
self.assertEqual(installed, [True])

View File

@@ -0,0 +1,241 @@
# -*- test-case-name: twisted.internet.test.test_defer_await -*-
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Tests for C{await} support in Deferreds.
"""
import types
from typing_extensions import NoReturn
from twisted.internet.defer import (
Deferred,
ensureDeferred,
fail,
maybeDeferred,
succeed,
)
from twisted.internet.task import Clock
from twisted.python.failure import Failure
from twisted.trial.unittest import TestCase
class SampleException(Exception):
"""
A specific sample exception for testing.
"""
class AwaitTests(TestCase):
"""
Tests for using Deferreds in conjunction with PEP-492.
"""
def test_awaitReturnsIterable(self) -> None:
"""
C{Deferred.__await__} returns an iterable.
"""
d: Deferred[None] = Deferred()
awaitedDeferred = d.__await__()
self.assertEqual(awaitedDeferred, iter(awaitedDeferred))
def test_deferredFromCoroutine(self) -> None:
"""
L{Deferred.fromCoroutine} will turn a coroutine into a L{Deferred}.
"""
async def run() -> str:
d = succeed("bar")
await d
res = await run2()
return res
async def run2() -> str:
d = succeed("foo")
res = await d
return res
# It's a coroutine...
r = run()
self.assertIsInstance(r, types.CoroutineType)
# Now it's a Deferred.
d = Deferred.fromCoroutine(r)
self.assertIsInstance(d, Deferred)
# The Deferred has the result we want.
res = self.successResultOf(d)
self.assertEqual(res, "foo")
def test_basic(self) -> None:
"""
L{Deferred.fromCoroutine} allows a function to C{await} on a
L{Deferred}.
"""
async def run() -> str:
d = succeed("foo")
res = await d
return res
d = Deferred.fromCoroutine(run())
res = self.successResultOf(d)
self.assertEqual(res, "foo")
def test_basicEnsureDeferred(self) -> None:
"""
L{ensureDeferred} allows a function to C{await} on a L{Deferred}.
"""
async def run() -> str:
d = succeed("foo")
res = await d
return res
d = ensureDeferred(run())
res = self.successResultOf(d)
self.assertEqual(res, "foo")
def test_exception(self) -> None:
"""
An exception in a coroutine scheduled with L{Deferred.fromCoroutine}
will cause the returned L{Deferred} to fire with a failure.
"""
async def run() -> NoReturn:
d = succeed("foo")
await d
raise ValueError("Oh no!")
d = Deferred.fromCoroutine(run())
res = self.failureResultOf(d)
self.assertEqual(type(res.value), ValueError)
self.assertEqual(res.value.args, ("Oh no!",))
def test_synchronousDeferredFailureTraceback(self) -> None:
"""
When a Deferred is awaited upon that has already failed with a Failure
that has a traceback, both the place that the synchronous traceback
comes from and the awaiting line are shown in the traceback.
"""
def raises() -> None:
raise SampleException()
it = maybeDeferred(raises)
async def doomed() -> None:
return await it
failure = self.failureResultOf(Deferred.fromCoroutine(doomed()))
self.assertIn(", in doomed\n", failure.getTraceback())
self.assertIn(", in raises\n", failure.getTraceback())
def test_asyncDeferredFailureTraceback(self) -> None:
"""
When a Deferred is awaited upon that later fails with a Failure that
has a traceback, both the place that the synchronous traceback comes
from and the awaiting line are shown in the traceback.
"""
def returnsFailure() -> Failure:
try:
raise SampleException()
except SampleException:
return Failure()
it: Deferred[None] = Deferred()
async def doomed() -> None:
return await it
started = Deferred.fromCoroutine(doomed())
self.assertNoResult(started)
it.errback(returnsFailure())
failure = self.failureResultOf(started)
self.assertIn(", in doomed\n", failure.getTraceback())
self.assertIn(", in returnsFailure\n", failure.getTraceback())
def test_twoDeep(self) -> None:
"""
A coroutine scheduled with L{Deferred.fromCoroutine} that awaits a
L{Deferred} suspends its execution until the inner L{Deferred} fires.
"""
reactor = Clock()
sections = []
async def runone() -> str:
sections.append(2)
d: Deferred[int] = Deferred()
reactor.callLater(1, d.callback, 2)
await d
sections.append(3)
return "Yay!"
async def run() -> str:
sections.append(1)
result = await runone()
sections.append(4)
d: Deferred[int] = Deferred()
reactor.callLater(1, d.callback, 1)
await d
sections.append(5)
return result
d = Deferred.fromCoroutine(run())
reactor.advance(0.9)
self.assertEqual(sections, [1, 2])
reactor.advance(0.1)
self.assertEqual(sections, [1, 2, 3, 4])
reactor.advance(0.9)
self.assertEqual(sections, [1, 2, 3, 4])
reactor.advance(0.1)
self.assertEqual(sections, [1, 2, 3, 4, 5])
res = self.successResultOf(d)
self.assertEqual(res, "Yay!")
def test_reraise(self) -> None:
"""
Awaiting an already failed Deferred will raise the exception.
"""
async def test() -> int:
try:
await fail(ValueError("Boom"))
except ValueError as e:
self.assertEqual(e.args, ("Boom",))
return 1
return 0
res = self.successResultOf(Deferred.fromCoroutine(test()))
self.assertEqual(res, 1)
def test_chained(self) -> None:
"""
Awaiting a paused & chained Deferred will give the result when it has
one.
"""
reactor = Clock()
async def test() -> None:
d: Deferred[None] = Deferred()
d2: Deferred[None] = Deferred()
d.addCallback(lambda ignored: d2)
d.callback(None)
reactor.callLater(0, d2.callback, "bye")
return await d
d = Deferred.fromCoroutine(test())
reactor.advance(0.1)
res = self.successResultOf(d)
self.assertEqual(res, "bye")

View File

@@ -0,0 +1,182 @@
# -*- test-case-name: twisted.internet.test.test_defer_yieldfrom -*-
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Tests for C{yield from} support in Deferreds.
"""
import types
from twisted.internet.defer import Deferred, ensureDeferred, fail, succeed
from twisted.internet.task import Clock
from twisted.trial.unittest import TestCase
class YieldFromTests(TestCase):
"""
Tests for using Deferreds in conjunction with PEP-380.
"""
def test_ensureDeferred(self) -> None:
"""
L{ensureDeferred} will turn a coroutine into a L{Deferred}.
"""
def run():
d = succeed("foo")
res = yield from d
return res
# It's a generator...
r = run()
self.assertIsInstance(r, types.GeneratorType)
# Now it's a Deferred.
d = ensureDeferred(r)
self.assertIsInstance(d, Deferred)
# The Deferred has the result we want.
res = self.successResultOf(d)
self.assertEqual(res, "foo")
def test_DeferredfromCoroutine(self) -> None:
"""
L{Deferred.fromCoroutine} will turn a coroutine into a L{Deferred}.
"""
def run():
d = succeed("bar")
yield from d
res = yield from run2()
return res
def run2():
d = succeed("foo")
res = yield from d
return res
# It's a generator...
r = run()
self.assertIsInstance(r, types.GeneratorType)
# Now it's a Deferred.
d = Deferred.fromCoroutine(r)
self.assertIsInstance(d, Deferred)
# The Deferred has the result we want.
res = self.successResultOf(d)
self.assertEqual(res, "foo")
def test_basic(self) -> None:
"""
L{Deferred.fromCoroutine} allows a function to C{yield from} a
L{Deferred}.
"""
def run():
d = succeed("foo")
res = yield from d
return res
d = Deferred.fromCoroutine(run())
res = self.successResultOf(d)
self.assertEqual(res, "foo")
def test_exception(self) -> None:
"""
An exception in a generator scheduled with L{Deferred.fromCoroutine}
will cause the returned L{Deferred} to fire with a failure.
"""
def run():
d = succeed("foo")
yield from d
raise ValueError("Oh no!")
d = Deferred.fromCoroutine(run())
res = self.failureResultOf(d)
self.assertEqual(type(res.value), ValueError)
self.assertEqual(res.value.args, ("Oh no!",))
def test_twoDeep(self) -> None:
"""
An exception in a generator scheduled with L{Deferred.fromCoroutine}
will cause the returned L{Deferred} to fire with a failure.
"""
reactor = Clock()
sections = []
def runone():
sections.append(2)
d = Deferred()
reactor.callLater(1, d.callback, None)
yield from d
sections.append(3)
return "Yay!"
def run():
sections.append(1)
result = yield from runone()
sections.append(4)
d = Deferred()
reactor.callLater(1, d.callback, None)
yield from d
sections.append(5)
return result
d = Deferred.fromCoroutine(run())
reactor.advance(0.9)
self.assertEqual(sections, [1, 2])
reactor.advance(0.1)
self.assertEqual(sections, [1, 2, 3, 4])
reactor.advance(0.9)
self.assertEqual(sections, [1, 2, 3, 4])
reactor.advance(0.1)
self.assertEqual(sections, [1, 2, 3, 4, 5])
res = self.successResultOf(d)
self.assertEqual(res, "Yay!")
def test_reraise(self) -> None:
"""
Yielding from an already failed Deferred will raise the exception.
"""
def test():
try:
yield from fail(ValueError("Boom"))
except ValueError as e:
self.assertEqual(e.args, ("Boom",))
return 1
return 0
res = self.successResultOf(Deferred.fromCoroutine(test()))
self.assertEqual(res, 1)
def test_chained(self) -> None:
"""
Yielding from a paused & chained Deferred will give the result when it
has one.
"""
reactor = Clock()
def test():
d = Deferred()
d2 = Deferred()
d.addCallback(lambda ignored: d2)
d.callback(None)
reactor.callLater(0, d2.callback, "bye")
res = yield from d
return res
d = Deferred.fromCoroutine(test())
reactor.advance(0.1)
res = self.successResultOf(d)
self.assertEqual(res, "bye")

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,230 @@
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Tests for L{twisted.internet.epollreactor}.
"""
from unittest import skipIf
from twisted.internet.error import ConnectionDone
from twisted.internet.posixbase import _ContinuousPolling
from twisted.internet.task import Clock
from twisted.trial.unittest import TestCase
try:
from twisted.internet import epollreactor
except ImportError:
epollreactor = None # type: ignore[assignment]
class Descriptor:
"""
Records reads and writes, as if it were a C{FileDescriptor}.
"""
def __init__(self):
self.events = []
def fileno(self):
return 1
def doRead(self):
self.events.append("read")
def doWrite(self):
self.events.append("write")
def connectionLost(self, reason):
reason.trap(ConnectionDone)
self.events.append("lost")
@skipIf(not epollreactor, "epoll not supported in this environment.")
class ContinuousPollingTests(TestCase):
"""
L{_ContinuousPolling} can be used to read and write from C{FileDescriptor}
objects.
"""
def test_addReader(self):
"""
Adding a reader when there was previously no reader starts up a
C{LoopingCall}.
"""
poller = _ContinuousPolling(Clock())
self.assertIsNone(poller._loop)
reader = object()
self.assertFalse(poller.isReading(reader))
poller.addReader(reader)
self.assertIsNotNone(poller._loop)
self.assertTrue(poller._loop.running)
self.assertIs(poller._loop.clock, poller._reactor)
self.assertTrue(poller.isReading(reader))
def test_addWriter(self):
"""
Adding a writer when there was previously no writer starts up a
C{LoopingCall}.
"""
poller = _ContinuousPolling(Clock())
self.assertIsNone(poller._loop)
writer = object()
self.assertFalse(poller.isWriting(writer))
poller.addWriter(writer)
self.assertIsNotNone(poller._loop)
self.assertTrue(poller._loop.running)
self.assertIs(poller._loop.clock, poller._reactor)
self.assertTrue(poller.isWriting(writer))
def test_removeReader(self):
"""
Removing a reader stops the C{LoopingCall}.
"""
poller = _ContinuousPolling(Clock())
reader = object()
poller.addReader(reader)
poller.removeReader(reader)
self.assertIsNone(poller._loop)
self.assertEqual(poller._reactor.getDelayedCalls(), [])
self.assertFalse(poller.isReading(reader))
def test_removeWriter(self):
"""
Removing a writer stops the C{LoopingCall}.
"""
poller = _ContinuousPolling(Clock())
writer = object()
poller.addWriter(writer)
poller.removeWriter(writer)
self.assertIsNone(poller._loop)
self.assertEqual(poller._reactor.getDelayedCalls(), [])
self.assertFalse(poller.isWriting(writer))
def test_removeUnknown(self):
"""
Removing unknown readers and writers silently does nothing.
"""
poller = _ContinuousPolling(Clock())
poller.removeWriter(object())
poller.removeReader(object())
def test_multipleReadersAndWriters(self):
"""
Adding multiple readers and writers results in a single
C{LoopingCall}.
"""
poller = _ContinuousPolling(Clock())
writer = object()
poller.addWriter(writer)
self.assertIsNotNone(poller._loop)
poller.addWriter(object())
self.assertIsNotNone(poller._loop)
poller.addReader(object())
self.assertIsNotNone(poller._loop)
poller.addReader(object())
poller.removeWriter(writer)
self.assertIsNotNone(poller._loop)
self.assertTrue(poller._loop.running)
self.assertEqual(len(poller._reactor.getDelayedCalls()), 1)
def test_readerPolling(self):
"""
Adding a reader causes its C{doRead} to be called every 1
milliseconds.
"""
reactor = Clock()
poller = _ContinuousPolling(reactor)
desc = Descriptor()
poller.addReader(desc)
self.assertEqual(desc.events, [])
reactor.advance(0.00001)
self.assertEqual(desc.events, ["read"])
reactor.advance(0.00001)
self.assertEqual(desc.events, ["read", "read"])
reactor.advance(0.00001)
self.assertEqual(desc.events, ["read", "read", "read"])
def test_writerPolling(self):
"""
Adding a writer causes its C{doWrite} to be called every 1
milliseconds.
"""
reactor = Clock()
poller = _ContinuousPolling(reactor)
desc = Descriptor()
poller.addWriter(desc)
self.assertEqual(desc.events, [])
reactor.advance(0.001)
self.assertEqual(desc.events, ["write"])
reactor.advance(0.001)
self.assertEqual(desc.events, ["write", "write"])
reactor.advance(0.001)
self.assertEqual(desc.events, ["write", "write", "write"])
def test_connectionLostOnRead(self):
"""
If a C{doRead} returns a value indicating disconnection,
C{connectionLost} is called on it.
"""
reactor = Clock()
poller = _ContinuousPolling(reactor)
desc = Descriptor()
desc.doRead = lambda: ConnectionDone()
poller.addReader(desc)
self.assertEqual(desc.events, [])
reactor.advance(0.001)
self.assertEqual(desc.events, ["lost"])
def test_connectionLostOnWrite(self):
"""
If a C{doWrite} returns a value indicating disconnection,
C{connectionLost} is called on it.
"""
reactor = Clock()
poller = _ContinuousPolling(reactor)
desc = Descriptor()
desc.doWrite = lambda: ConnectionDone()
poller.addWriter(desc)
self.assertEqual(desc.events, [])
reactor.advance(0.001)
self.assertEqual(desc.events, ["lost"])
def test_removeAll(self):
"""
L{_ContinuousPolling.removeAll} removes all descriptors and returns
the readers and writers.
"""
poller = _ContinuousPolling(Clock())
reader = object()
writer = object()
both = object()
poller.addReader(reader)
poller.addReader(both)
poller.addWriter(writer)
poller.addWriter(both)
removed = poller.removeAll()
self.assertEqual(poller.getReaders(), [])
self.assertEqual(poller.getWriters(), [])
self.assertEqual(len(removed), 3)
self.assertEqual(set(removed), {reader, writer, both})
def test_getReaders(self):
"""
L{_ContinuousPolling.getReaders} returns a list of the read
descriptors.
"""
poller = _ContinuousPolling(Clock())
reader = object()
poller.addReader(reader)
self.assertIn(reader, poller.getReaders())
def test_getWriters(self):
"""
L{_ContinuousPolling.getWriters} returns a list of the write
descriptors.
"""
poller = _ContinuousPolling(Clock())
writer = object()
poller.addWriter(writer)
self.assertIn(writer, poller.getWriters())

View File

@@ -0,0 +1,40 @@
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Tests for L{twisted.internet.error}
"""
from twisted.internet import error
from twisted.trial.unittest import SynchronousTestCase
class ConnectionAbortedTests(SynchronousTestCase):
"""
Tests for the L{twisted.internet.error.ConnectionAborted} exception.
"""
def test_str(self) -> None:
"""
The default message of L{ConnectionAborted} is a sentence which points
to L{ITCPTransport.abortConnection()}
"""
self.assertEqual(
("Connection was aborted locally" " using ITCPTransport.abortConnection."),
str(error.ConnectionAborted()),
)
def test_strArgs(self) -> None:
"""
Any arguments passed to L{ConnectionAborted} are included in its
message.
"""
self.assertEqual(
(
"Connection was aborted locally using"
" ITCPTransport.abortConnection:"
" foo bar."
),
str(error.ConnectionAborted("foo", "bar")),
)

View File

@@ -0,0 +1,422 @@
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Tests for implementations of L{IReactorFDSet}.
"""
import os
import socket
import traceback
from typing import TYPE_CHECKING
from unittest import skipIf
from zope.interface import implementer
from twisted.internet.abstract import FileDescriptor
from twisted.internet.interfaces import IReactorFDSet, IReadDescriptor
from twisted.internet.tcp import EINPROGRESS, EWOULDBLOCK
from twisted.internet.test.reactormixins import ReactorBuilder
from twisted.python.runtime import platform
from twisted.trial.unittest import SkipTest, SynchronousTestCase
if TYPE_CHECKING:
PretendTestCase = SynchronousTestCase
else:
PretendTestCase = object
def socketpair():
serverSocket = socket.socket()
serverSocket.bind(("127.0.0.1", 0))
serverSocket.listen(1)
try:
client = socket.socket()
try:
client.setblocking(False)
try:
client.connect(("127.0.0.1", serverSocket.getsockname()[1]))
except OSError as e:
if e.args[0] not in (EINPROGRESS, EWOULDBLOCK):
raise
server, addr = serverSocket.accept()
except BaseException:
client.close()
raise
finally:
serverSocket.close()
return client, server
class ReactorFDSetTestsBuilder(ReactorBuilder, PretendTestCase):
"""
Builder defining tests relating to L{IReactorFDSet}.
"""
requiredInterfaces = [IReactorFDSet]
def _connectedPair(self):
"""
Return the two sockets which make up a new TCP connection.
"""
client, server = socketpair()
self.addCleanup(client.close)
self.addCleanup(server.close)
return client, server
def _simpleSetup(self):
reactor = self.buildReactor()
client, server = self._connectedPair()
fd = FileDescriptor(reactor)
fd.fileno = client.fileno
return reactor, fd, server
def test_addReader(self):
"""
C{reactor.addReader()} accepts an L{IReadDescriptor} provider and calls
its C{doRead} method when there may be data available on its C{fileno}.
"""
reactor, fd, server = self._simpleSetup()
def removeAndStop():
reactor.removeReader(fd)
reactor.stop()
fd.doRead = removeAndStop
reactor.addReader(fd)
server.sendall(b"x")
# The reactor will only stop if it calls fd.doRead.
self.runReactor(reactor)
# Nothing to assert, just be glad we got this far.
def test_removeReader(self):
"""
L{reactor.removeReader()} accepts an L{IReadDescriptor} provider
previously passed to C{reactor.addReader()} and causes it to no longer
be monitored for input events.
"""
reactor, fd, server = self._simpleSetup()
def fail():
self.fail("doRead should not be called")
fd.doRead = fail
reactor.addReader(fd)
reactor.removeReader(fd)
server.sendall(b"x")
# Give the reactor two timed event passes to notice that there's I/O
# (if it is incorrectly watching for I/O).
reactor.callLater(0, reactor.callLater, 0, reactor.stop)
self.runReactor(reactor)
# Getting here means the right thing happened probably.
def test_addWriter(self):
"""
C{reactor.addWriter()} accepts an L{IWriteDescriptor} provider and
calls its C{doWrite} method when it may be possible to write to its
C{fileno}.
"""
reactor, fd, server = self._simpleSetup()
def removeAndStop():
reactor.removeWriter(fd)
reactor.stop()
fd.doWrite = removeAndStop
reactor.addWriter(fd)
self.runReactor(reactor)
# Getting here is great.
def _getFDTest(self, kind):
"""
Helper for getReaders and getWriters tests.
"""
reactor = self.buildReactor()
get = getattr(reactor, "get" + kind + "s")
add = getattr(reactor, "add" + kind)
remove = getattr(reactor, "remove" + kind)
client, server = self._connectedPair()
self.assertNotIn(client, get())
self.assertNotIn(server, get())
add(client)
self.assertIn(client, get())
self.assertNotIn(server, get())
remove(client)
self.assertNotIn(client, get())
self.assertNotIn(server, get())
def test_getReaders(self):
"""
L{IReactorFDSet.getReaders} reflects the additions and removals made
with L{IReactorFDSet.addReader} and L{IReactorFDSet.removeReader}.
"""
self._getFDTest("Reader")
def test_removeWriter(self):
"""
L{reactor.removeWriter()} accepts an L{IWriteDescriptor} provider
previously passed to C{reactor.addWriter()} and causes it to no longer
be monitored for outputability.
"""
reactor, fd, server = self._simpleSetup()
def fail():
self.fail("doWrite should not be called")
fd.doWrite = fail
reactor.addWriter(fd)
reactor.removeWriter(fd)
# Give the reactor two timed event passes to notice that there's I/O
# (if it is incorrectly watching for I/O).
reactor.callLater(0, reactor.callLater, 0, reactor.stop)
self.runReactor(reactor)
# Getting here means the right thing happened probably.
def test_getWriters(self):
"""
L{IReactorFDSet.getWriters} reflects the additions and removals made
with L{IReactorFDSet.addWriter} and L{IReactorFDSet.removeWriter}.
"""
self._getFDTest("Writer")
def test_removeAll(self):
"""
C{reactor.removeAll()} removes all registered L{IReadDescriptor}
providers and all registered L{IWriteDescriptor} providers and returns
them.
"""
reactor = self.buildReactor()
reactor, fd, server = self._simpleSetup()
fd.doRead = lambda: self.fail("doRead should not be called")
fd.doWrite = lambda: self.fail("doWrite should not be called")
server.sendall(b"x")
reactor.addReader(fd)
reactor.addWriter(fd)
removed = reactor.removeAll()
# Give the reactor two timed event passes to notice that there's I/O
# (if it is incorrectly watching for I/O).
reactor.callLater(0, reactor.callLater, 0, reactor.stop)
self.runReactor(reactor)
# Getting here means the right thing happened probably.
self.assertEqual(removed, [fd])
def test_removedFromReactor(self):
"""
A descriptor's C{fileno} method should not be called after the
descriptor has been removed from the reactor.
"""
reactor = self.buildReactor()
descriptor = RemovingDescriptor(reactor)
reactor.callWhenRunning(descriptor.start)
self.runReactor(reactor)
self.assertEqual(descriptor.calls, [])
def test_negativeOneFileDescriptor(self):
"""
If L{FileDescriptor.fileno} returns C{-1}, the descriptor is removed
from the reactor.
"""
reactor = self.buildReactor()
client, server = self._connectedPair()
class DisappearingDescriptor(FileDescriptor):
_fileno = server.fileno()
_received = b""
def fileno(self):
return self._fileno
def doRead(self):
self._fileno = -1
self._received += server.recv(1)
client.send(b"y")
def connectionLost(self, reason):
reactor.stop()
descriptor = DisappearingDescriptor(reactor)
reactor.addReader(descriptor)
client.send(b"x")
self.runReactor(reactor)
self.assertEqual(descriptor._received, b"x")
@skipIf(platform.isWindows(), "Cannot duplicate socket filenos on Windows")
def test_lostFileDescriptor(self):
"""
The file descriptor underlying a FileDescriptor may be closed and
replaced by another at some point. Bytes which arrive on the new
descriptor must not be delivered to the FileDescriptor which was
originally registered with the original descriptor of the same number.
Practically speaking, this is difficult or impossible to detect. The
implementation relies on C{fileno} raising an exception if the original
descriptor has gone away. If C{fileno} continues to return the original
file descriptor value, the reactor may deliver events from that
descriptor. This is a best effort attempt to ease certain debugging
situations. Applications should not rely on it intentionally.
"""
reactor = self.buildReactor()
name = reactor.__class__.__name__
if name in (
"EPollReactor",
"KQueueReactor",
"CFReactor",
"AsyncioSelectorReactor",
):
# Closing a file descriptor immediately removes it from the epoll
# set without generating a notification. That means epollreactor
# will not call any methods on Victim after the close, so there's
# no chance to notice the socket is no longer valid.
raise SkipTest(f"{name!r} cannot detect lost file descriptors")
client, server = self._connectedPair()
class Victim(FileDescriptor):
"""
This L{FileDescriptor} will have its socket closed out from under it
and another socket will take its place. It will raise a
socket.error from C{fileno} after this happens (because socket
objects remember whether they have been closed), so as long as the
reactor calls the C{fileno} method the problem will be detected.
"""
def fileno(self):
return server.fileno()
def doRead(self):
raise Exception("Victim.doRead should never be called")
def connectionLost(self, reason):
"""
When the problem is detected, the reactor should disconnect this
file descriptor. When that happens, stop the reactor so the
test ends.
"""
reactor.stop()
reactor.addReader(Victim())
# Arrange for the socket to be replaced at some unspecified time.
# Significantly, this will not be while any I/O processing code is on
# the stack. It is something that happens independently and cannot be
# relied upon to happen at a convenient time, such as within a call to
# doRead.
def messItUp():
newC, newS = self._connectedPair()
fileno = server.fileno()
server.close()
os.dup2(newS.fileno(), fileno)
newC.send(b"x")
reactor.callLater(0, messItUp)
self.runReactor(reactor)
# If the implementation feels like logging the exception raised by
# MessedUp.fileno, that's fine.
self.flushLoggedErrors(socket.error)
def test_connectionLostOnShutdown(self):
"""
Any file descriptors added to the reactor have their C{connectionLost}
called when C{reactor.stop} is called.
"""
reactor = self.buildReactor()
class DoNothingDescriptor(FileDescriptor):
def doRead(self):
return None
def doWrite(self):
return None
client, server = self._connectedPair()
fd1 = DoNothingDescriptor(reactor)
fd1.fileno = client.fileno
fd2 = DoNothingDescriptor(reactor)
fd2.fileno = server.fileno
reactor.addReader(fd1)
reactor.addWriter(fd2)
reactor.callWhenRunning(reactor.stop)
self.runReactor(reactor)
self.assertTrue(fd1.disconnected)
self.assertTrue(fd2.disconnected)
@implementer(IReadDescriptor)
class RemovingDescriptor:
"""
A read descriptor which removes itself from the reactor as soon as it
gets a chance to do a read and keeps track of when its own C{fileno}
method is called.
@ivar insideReactor: A flag which is true as long as the reactor has
this descriptor as a reader.
@ivar calls: A list of the bottom of the call stack for any call to
C{fileno} when C{insideReactor} is false.
"""
def __init__(self, reactor):
self.reactor = reactor
self.insideReactor = False
self.calls = []
self.read, self.write = socketpair()
def start(self):
self.insideReactor = True
self.reactor.addReader(self)
self.write.send(b"a")
def logPrefix(self):
return "foo"
def doRead(self):
self.reactor.removeReader(self)
self.insideReactor = False
self.reactor.stop()
self.read.close()
self.write.close()
def fileno(self):
if not self.insideReactor:
self.calls.append(traceback.extract_stack(limit=5)[:-1])
return self.read.fileno()
def connectionLost(self, reason):
# Ideally we'd close the descriptors here... but actually
# connectionLost is never called because we remove ourselves from the
# reactor before it stops.
pass
globals().update(ReactorFDSetTestsBuilder.makeTestCaseClasses())

View File

@@ -0,0 +1,94 @@
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Whitebox tests for L{twisted.internet.abstract.FileDescriptor}.
"""
from zope.interface.verify import verifyClass
from twisted.internet.abstract import FileDescriptor
from twisted.internet.interfaces import IPushProducer
from twisted.trial.unittest import SynchronousTestCase
class MemoryFile(FileDescriptor):
"""
A L{FileDescriptor} customization which writes to a Python list in memory
with certain limitations.
@ivar _written: A C{list} of C{bytes} which have been accepted as written.
@ivar _freeSpace: A C{int} giving the number of bytes which will be accepted
by future writes.
"""
connected = True
def __init__(self):
FileDescriptor.__init__(self, reactor=object())
self._written = []
self._freeSpace = 0
def startWriting(self):
pass
def stopWriting(self):
pass
def writeSomeData(self, data):
"""
Copy at most C{self._freeSpace} bytes from C{data} into C{self._written}.
@return: A C{int} indicating how many bytes were copied from C{data}.
"""
acceptLength = min(self._freeSpace, len(data))
if acceptLength:
self._freeSpace -= acceptLength
self._written.append(data[:acceptLength])
return acceptLength
class FileDescriptorTests(SynchronousTestCase):
"""
Tests for L{FileDescriptor}.
"""
def test_writeWithUnicodeRaisesException(self):
"""
L{FileDescriptor.write} doesn't accept unicode data.
"""
fileDescriptor = FileDescriptor(reactor=object())
self.assertRaises(TypeError, fileDescriptor.write, "foo")
def test_writeSequenceWithUnicodeRaisesException(self):
"""
L{FileDescriptor.writeSequence} doesn't accept unicode data.
"""
fileDescriptor = FileDescriptor(reactor=object())
self.assertRaises(
TypeError, fileDescriptor.writeSequence, [b"foo", "bar", b"baz"]
)
def test_implementInterfaceIPushProducer(self):
"""
L{FileDescriptor} should implement L{IPushProducer}.
"""
self.assertTrue(verifyClass(IPushProducer, FileDescriptor))
class WriteDescriptorTests(SynchronousTestCase):
"""
Tests for L{FileDescriptor}'s implementation of L{IWriteDescriptor}.
"""
def test_kernelBufferFull(self):
"""
When L{FileDescriptor.writeSomeData} returns C{0} to indicate no more
data can be written immediately, L{FileDescriptor.doWrite} returns
L{None}.
"""
descriptor = MemoryFile()
descriptor.write(b"hello, world")
self.assertIsNone(descriptor.doWrite())

View File

@@ -0,0 +1,216 @@
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
GObject Introspection reactor tests; i.e. `gireactor` module for gio/glib/gtk
integration.
"""
from __future__ import annotations
from unittest import skipIf
try:
from gi.repository import Gio
except ImportError:
giImported = False
gtkVersion = None
else:
giImported = True
# If we can import Gio, we ought to be able to import our reactor.
from os import environ
from gi import get_required_version, require_version
from twisted.internet import gireactor
def requireEach(someVersion: str) -> str:
try:
require_version("Gtk", someVersion)
except ValueError as ve:
return str(ve)
else:
return ""
errorMessage = ", ".join(
requireEach(version)
for version in environ.get("TWISTED_TEST_GTK_VERSION", "4.0,3.0").split(",")
)
actualVersion = get_required_version("Gtk")
gtkVersion = actualVersion if actualVersion is not None else errorMessage
from twisted.internet.error import ReactorAlreadyRunning
from twisted.internet.test.reactormixins import ReactorBuilder
from twisted.trial.unittest import SkipTest, TestCase
# Skip all tests if gi is unavailable:
if not giImported:
skip = "GObject Introspection `gi` module not importable"
noGtkSkip = (gtkVersion is None) or (gtkVersion not in ("3.0", "4.0"))
noGtkMessage = f"Unknown GTK version: {repr(gtkVersion)}"
if not noGtkSkip:
from gi.repository import Gtk
class GApplicationRegistrationTests(ReactorBuilder, TestCase):
"""
GtkApplication and GApplication are supported by
L{twisted.internet.gtk3reactor} and L{twisted.internet.gireactor}.
We inherit from L{ReactorBuilder} in order to use some of its
reactor-running infrastructure, but don't need its test-creation
functionality.
"""
def runReactor( # type: ignore[override]
self,
app: Gio.Application,
reactor: gireactor.GIReactor,
) -> None:
"""
Register the app, run the reactor, make sure app was activated, and
that reactor was running, and that reactor can be stopped.
"""
if not hasattr(app, "quit"):
raise SkipTest("Version of PyGObject is too old.")
result = []
def stop() -> None:
result.append("stopped")
reactor.stop()
def activate(widget: object) -> None:
result.append("activated")
reactor.callLater(0, stop)
app.connect("activate", activate)
# We want reactor.stop() to *always* stop the event loop, even if
# someone has called hold() on the application and never done the
# corresponding release() -- for more details see
# http://developer.gnome.org/gio/unstable/GApplication.html.
app.hold()
reactor.registerGApplication(app)
ReactorBuilder.runReactor(self, reactor)
self.assertEqual(result, ["activated", "stopped"])
def test_gApplicationActivate(self) -> None:
"""
L{Gio.Application} instances can be registered with a gireactor.
"""
self.reactorFactory = lambda: gireactor.GIReactor(useGtk=False)
reactor = self.buildReactor()
app = Gio.Application(
application_id="com.twistedmatrix.trial.gireactor",
flags=Gio.ApplicationFlags.FLAGS_NONE,
)
self.runReactor(app, reactor)
@skipIf(noGtkSkip, noGtkMessage)
def test_gtkAliases(self) -> None:
"""
L{twisted.internet.gtk3reactor} is now just a set of compatibility
aliases for L{twisted.internet.GIReactor}.
"""
from twisted.internet.gtk3reactor import (
Gtk3Reactor,
PortableGtk3Reactor,
install,
)
self.assertIs(Gtk3Reactor, gireactor.GIReactor)
self.assertIs(PortableGtk3Reactor, gireactor.PortableGIReactor)
self.assertIs(install, gireactor.install)
warnings = self.flushWarnings()
self.assertEqual(len(warnings), 1)
self.assertIn(
"twisted.internet.gtk3reactor was deprecated", warnings[0]["message"]
)
@skipIf(noGtkSkip, noGtkMessage)
def test_gtkApplicationActivate(self) -> None:
"""
L{Gtk.Application} instances can be registered with a gtk3reactor.
"""
self.reactorFactory = gireactor.GIReactor
reactor = self.buildReactor()
app = Gtk.Application(
application_id="com.twistedmatrix.trial.gtk3reactor",
flags=Gio.ApplicationFlags.FLAGS_NONE,
)
self.runReactor(app, reactor)
def test_portable(self) -> None:
"""
L{gireactor.PortableGIReactor} doesn't support application
registration at this time.
"""
self.reactorFactory = gireactor.PortableGIReactor
reactor = self.buildReactor()
app = Gio.Application(
application_id="com.twistedmatrix.trial.gireactor",
flags=Gio.ApplicationFlags.FLAGS_NONE,
)
self.assertRaises(NotImplementedError, reactor.registerGApplication, app)
def test_noQuit(self) -> None:
"""
Older versions of PyGObject lack C{Application.quit}, and so won't
allow registration.
"""
self.reactorFactory = lambda: gireactor.GIReactor(useGtk=False)
reactor = self.buildReactor()
# An app with no "quit" method:
app = object()
exc = self.assertRaises(RuntimeError, reactor.registerGApplication, app)
self.assertTrue(exc.args[0].startswith("Application registration is not"))
def test_cantRegisterAfterRun(self) -> None:
"""
It is not possible to register a C{Application} after the reactor has
already started.
"""
self.reactorFactory = lambda: gireactor.GIReactor(useGtk=False)
reactor = self.buildReactor()
app = Gio.Application(
application_id="com.twistedmatrix.trial.gireactor",
flags=Gio.ApplicationFlags.FLAGS_NONE,
)
def tryRegister() -> None:
exc = self.assertRaises(
ReactorAlreadyRunning, reactor.registerGApplication, app
)
self.assertEqual(
exc.args[0], "Can't register application after reactor was started."
)
reactor.stop()
reactor.callLater(0, tryRegister)
ReactorBuilder.runReactor(self, reactor)
def test_cantRegisterTwice(self) -> None:
"""
It is not possible to register more than one C{Application}.
"""
self.reactorFactory = lambda: gireactor.GIReactor(useGtk=False)
reactor = self.buildReactor()
app = Gio.Application(
application_id="com.twistedmatrix.trial.gireactor",
flags=Gio.ApplicationFlags.FLAGS_NONE,
)
reactor.registerGApplication(app)
app2 = Gio.Application(
application_id="com.twistedmatrix.trial.gireactor2",
flags=Gio.ApplicationFlags.FLAGS_NONE,
)
exc = self.assertRaises(RuntimeError, reactor.registerGApplication, app2)
self.assertEqual(
exc.args[0], "Can't register more than one application instance."
)

View File

@@ -0,0 +1,100 @@
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Tests for twisted.internet.glibbase.
"""
import sys
from twisted.internet._glibbase import ensureNotImported
from twisted.trial.unittest import TestCase
class EnsureNotImportedTests(TestCase):
"""
L{ensureNotImported} protects against unwanted past and future imports.
"""
def test_ensureWhenNotImported(self):
"""
If the specified modules have never been imported, and import
prevention is requested, L{ensureNotImported} makes sure they will not
be imported in the future.
"""
modules = {}
self.patch(sys, "modules", modules)
ensureNotImported(["m1", "m2"], "A message.", preventImports=["m1", "m2", "m3"])
self.assertEqual(modules, {"m1": None, "m2": None, "m3": None})
def test_ensureWhenNotImportedDontPrevent(self):
"""
If the specified modules have never been imported, and import
prevention is not requested, L{ensureNotImported} has no effect.
"""
modules = {}
self.patch(sys, "modules", modules)
ensureNotImported(["m1", "m2"], "A message.")
self.assertEqual(modules, {})
def test_ensureWhenFailedToImport(self):
"""
If the specified modules have been set to L{None} in C{sys.modules},
L{ensureNotImported} does not complain.
"""
modules = {"m2": None}
self.patch(sys, "modules", modules)
ensureNotImported(["m1", "m2"], "A message.", preventImports=["m1", "m2"])
self.assertEqual(modules, {"m1": None, "m2": None})
def test_ensureFailsWhenImported(self):
"""
If one of the specified modules has been previously imported,
L{ensureNotImported} raises an exception.
"""
module = object()
modules = {"m2": module}
self.patch(sys, "modules", modules)
e = self.assertRaises(
ImportError,
ensureNotImported,
["m1", "m2"],
"A message.",
preventImports=["m1", "m2"],
)
self.assertEqual(modules, {"m2": module})
self.assertEqual(e.args, ("A message.",))
try:
from twisted.internet import gireactor as _gireactor
except ImportError:
gireactor = None
else:
gireactor = _gireactor
missingGlibReactor = None
if gireactor is None:
missingGlibReactor = "gi reactor not available"
class GlibReactorBaseTests(TestCase):
"""
Tests for the private C{twisted.internet._glibbase.GlibReactorBase}
done via the public C{twisted.internet.gireactor.PortableGIReactor}
"""
skip = missingGlibReactor
def test_simulate(self):
"""
C{simulate} can be called without raising any errors when there are
no delayed calls for the reactor and hence there is no defined sleep
period.
"""
sut = gireactor.PortableGIReactor(useGtk=False)
# Double check that reactor has no sleep period.
self.assertIs(None, sut.timeout())
sut.simulate()

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,569 @@
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Tests for the inotify wrapper in L{twisted.internet.inotify}.
"""
import sys
from twisted.internet import defer, reactor
from twisted.python import filepath, runtime
from twisted.python.reflect import requireModule
from twisted.trial import unittest
if requireModule("twisted.python._inotify") is not None:
from twisted.internet import inotify
else:
inotify = None # type: ignore[assignment]
class INotifyTests(unittest.TestCase):
"""
Define all the tests for the basic functionality exposed by
L{inotify.INotify}.
"""
if not runtime.platform.supportsINotify():
skip = "This platform doesn't support INotify."
def setUp(self):
self.dirname = filepath.FilePath(self.mktemp())
self.dirname.createDirectory()
self.inotify = inotify.INotify()
self.inotify.startReading()
self.addCleanup(self.inotify.loseConnection)
def test_initializationErrors(self):
"""
L{inotify.INotify} emits a C{RuntimeError} when initialized
in an environment that doesn't support inotify as we expect it.
We just try to raise an exception for every possible case in
the for loop in L{inotify.INotify._inotify__init__}.
"""
class FakeINotify:
def init(self):
raise inotify.INotifyError()
self.patch(inotify.INotify, "_inotify", FakeINotify())
self.assertRaises(inotify.INotifyError, inotify.INotify)
def _notificationTest(self, mask, operation, expectedPath=None):
"""
Test notification from some filesystem operation.
@param mask: The event mask to use when setting up the watch.
@param operation: A function which will be called with the
name of a file in the watched directory and which should
trigger the event.
@param expectedPath: Optionally, the name of the path which is
expected to come back in the notification event; this will
also be passed to C{operation} (primarily useful when the
operation is being done to the directory itself, not a
file in it).
@return: A L{Deferred} which fires successfully when the
expected event has been received or fails otherwise.
"""
if expectedPath is None:
expectedPath = self.dirname.child("foo.bar")
notified = defer.Deferred()
def cbNotified(result):
(watch, filename, events) = result
self.assertEqual(filename.asBytesMode(), expectedPath.asBytesMode())
self.assertTrue(events & mask)
notified.addCallback(cbNotified)
self.inotify.watch(
self.dirname, mask=mask, callbacks=[lambda *args: notified.callback(args)]
)
operation(expectedPath)
return notified
def test_access(self):
"""
Reading from a file in a monitored directory sends an
C{inotify.IN_ACCESS} event to the callback.
"""
def operation(path):
path.setContent(b"foo")
path.getContent()
return self._notificationTest(inotify.IN_ACCESS, operation)
def test_modify(self):
"""
Writing to a file in a monitored directory sends an
C{inotify.IN_MODIFY} event to the callback.
"""
def operation(path):
with path.open("w") as fObj:
fObj.write(b"foo")
return self._notificationTest(inotify.IN_MODIFY, operation)
def test_attrib(self):
"""
Changing the metadata of a file in a monitored directory
sends an C{inotify.IN_ATTRIB} event to the callback.
"""
def operation(path):
path.touch()
path.touch()
return self._notificationTest(inotify.IN_ATTRIB, operation)
def test_closeWrite(self):
"""
Closing a file which was open for writing in a monitored
directory sends an C{inotify.IN_CLOSE_WRITE} event to the
callback.
"""
def operation(path):
path.open("w").close()
return self._notificationTest(inotify.IN_CLOSE_WRITE, operation)
def test_closeNoWrite(self):
"""
Closing a file which was open for reading but not writing in a
monitored directory sends an C{inotify.IN_CLOSE_NOWRITE} event
to the callback.
"""
def operation(path):
path.touch()
path.open("r").close()
return self._notificationTest(inotify.IN_CLOSE_NOWRITE, operation)
def test_open(self):
"""
Opening a file in a monitored directory sends an
C{inotify.IN_OPEN} event to the callback.
"""
def operation(path):
path.open("w").close()
return self._notificationTest(inotify.IN_OPEN, operation)
def test_movedFrom(self):
"""
Moving a file out of a monitored directory sends an
C{inotify.IN_MOVED_FROM} event to the callback.
"""
def operation(path):
path.open("w").close()
path.moveTo(filepath.FilePath(self.mktemp()))
return self._notificationTest(inotify.IN_MOVED_FROM, operation)
def test_movedTo(self):
"""
Moving a file into a monitored directory sends an
C{inotify.IN_MOVED_TO} event to the callback.
"""
def operation(path):
p = filepath.FilePath(self.mktemp())
p.touch()
p.moveTo(path)
return self._notificationTest(inotify.IN_MOVED_TO, operation)
def test_create(self):
"""
Creating a file in a monitored directory sends an
C{inotify.IN_CREATE} event to the callback.
"""
def operation(path):
path.open("w").close()
return self._notificationTest(inotify.IN_CREATE, operation)
def test_delete(self):
"""
Deleting a file in a monitored directory sends an
C{inotify.IN_DELETE} event to the callback.
"""
def operation(path):
path.touch()
path.remove()
return self._notificationTest(inotify.IN_DELETE, operation)
def test_deleteSelf(self):
"""
Deleting the monitored directory itself sends an
C{inotify.IN_DELETE_SELF} event to the callback.
"""
def operation(path):
path.remove()
return self._notificationTest(
inotify.IN_DELETE_SELF, operation, expectedPath=self.dirname
)
def test_deleteSelfForced(self):
"""
Deleting the monitored directory itself sends an
C{inotify.IN_DELETE_SELF} event to the callback
even if the mask isn't specified by the call to watch().
"""
def cbNotified(result):
(watch, filename, events) = result
self.assertEqual(filename.asBytesMode(), self.dirname.asBytesMode())
self.assertTrue(events & inotify.IN_DELETE_SELF)
self.inotify.watch(
self.dirname, mask=0x0, callbacks=[lambda *args: d.callback(args)]
)
d = defer.Deferred()
d.addCallback(cbNotified)
self.dirname.remove()
return d
def test_moveSelf(self):
"""
Renaming the monitored directory itself sends an
C{inotify.IN_MOVE_SELF} event to the callback.
"""
def operation(path):
path.moveTo(filepath.FilePath(self.mktemp()))
return self._notificationTest(
inotify.IN_MOVE_SELF, operation, expectedPath=self.dirname
)
def test_simpleSubdirectoryAutoAdd(self):
"""
L{inotify.INotify} when initialized with autoAdd==True adds
also adds the created subdirectories to the watchlist.
"""
def _callback(wp, filename, mask):
# We are notified before we actually process new
# directories, so we need to defer this check.
def _():
try:
self.assertTrue(self.inotify._isWatched(subdir))
d.callback(None)
except Exception:
d.errback()
reactor.callLater(0, _)
checkMask = inotify.IN_ISDIR | inotify.IN_CREATE
self.inotify.watch(
self.dirname, mask=checkMask, autoAdd=True, callbacks=[_callback]
)
subdir = self.dirname.child("test")
d = defer.Deferred()
subdir.createDirectory()
return d
def test_simpleDeleteDirectory(self):
"""
L{inotify.INotify} removes a directory from the watchlist when
it's removed from the filesystem.
"""
calls = []
def _callback(wp, filename, mask):
# We are notified before we actually process new
# directories, so we need to defer this check.
def _():
try:
self.assertTrue(self.inotify._isWatched(subdir))
subdir.remove()
except Exception:
d.errback()
def _eb():
# second call, we have just removed the subdir
try:
self.assertFalse(self.inotify._isWatched(subdir))
d.callback(None)
except Exception:
d.errback()
if not calls:
# first call, it's the create subdir
calls.append(filename)
reactor.callLater(0, _)
else:
reactor.callLater(0, _eb)
checkMask = inotify.IN_ISDIR | inotify.IN_CREATE
self.inotify.watch(
self.dirname, mask=checkMask, autoAdd=True, callbacks=[_callback]
)
subdir = self.dirname.child("test")
d = defer.Deferred()
subdir.createDirectory()
return d
def test_deleteSelfLoseConnection(self):
"""
L{inotify.INotify} closes the file descriptor after removing a
directory from the filesystem (and therefore from the watchlist).
"""
def cbNotified(result):
def _():
try:
self.assertFalse(self.inotify._isWatched(self.dirname))
self.assertFalse(self.inotify.connected)
d.callback(None)
except Exception:
d.errback()
(ignored, filename, events) = result
self.assertEqual(filename.asBytesMode(), self.dirname.asBytesMode())
self.assertTrue(events & inotify.IN_DELETE_SELF)
reactor.callLater(0, _)
self.assertTrue(
self.inotify.watch(
self.dirname,
mask=inotify.IN_DELETE_SELF,
callbacks=[lambda *args: notified.callback(args)],
)
)
notified = defer.Deferred()
notified.addCallback(cbNotified)
self.dirname.remove()
d = defer.Deferred()
return d
def test_ignoreDirectory(self):
"""
L{inotify.INotify.ignore} removes a directory from the watchlist
"""
self.inotify.watch(self.dirname, autoAdd=True)
self.assertTrue(self.inotify._isWatched(self.dirname))
self.inotify.ignore(self.dirname)
self.assertFalse(self.inotify._isWatched(self.dirname))
def test_humanReadableMask(self):
"""
L{inotify.humaReadableMask} translates all the possible event
masks to a human readable string.
"""
for mask, value in inotify._FLAG_TO_HUMAN:
self.assertEqual(inotify.humanReadableMask(mask)[0], value)
checkMask = inotify.IN_CLOSE_WRITE | inotify.IN_ACCESS | inotify.IN_OPEN
self.assertEqual(
set(inotify.humanReadableMask(checkMask)),
{"close_write", "access", "open"},
)
def test_recursiveWatch(self):
"""
L{inotify.INotify.watch} with recursive==True will add all the
subdirectories under the given path to the watchlist.
"""
subdir = self.dirname.child("test")
subdir2 = subdir.child("test2")
subdir3 = subdir2.child("test3")
subdir3.makedirs()
dirs = [subdir, subdir2, subdir3]
self.inotify.watch(self.dirname, recursive=True)
# let's even call this twice so that we test that nothing breaks
self.inotify.watch(self.dirname, recursive=True)
for d in dirs:
self.assertTrue(self.inotify._isWatched(d))
def test_connectionLostError(self):
"""
L{inotify.INotify.connectionLost} if there's a problem while closing
the fd shouldn't raise the exception but should log the error
"""
import os
in_ = inotify.INotify()
os.close(in_._fd)
in_.loseConnection()
self.flushLoggedErrors()
def test_noAutoAddSubdirectory(self):
"""
L{inotify.INotify.watch} with autoAdd==False will stop inotify
from watching subdirectories created under the watched one.
"""
def _callback(wp, fp, mask):
# We are notified before we actually process new
# directories, so we need to defer this check.
def _():
try:
self.assertFalse(self.inotify._isWatched(subdir))
d.callback(None)
except Exception:
d.errback()
reactor.callLater(0, _)
checkMask = inotify.IN_ISDIR | inotify.IN_CREATE
self.inotify.watch(
self.dirname, mask=checkMask, autoAdd=False, callbacks=[_callback]
)
subdir = self.dirname.child("test")
d = defer.Deferred()
subdir.createDirectory()
return d
def test_seriesOfWatchAndIgnore(self):
"""
L{inotify.INotify} will watch a filepath for events even if the same
path is repeatedly added/removed/re-added to the watchpoints.
"""
expectedPath = self.dirname.child("foo.bar2")
expectedPath.touch()
notified = defer.Deferred()
def cbNotified(result):
(ignored, filename, events) = result
self.assertEqual(filename.asBytesMode(), expectedPath.asBytesMode())
self.assertTrue(events & inotify.IN_DELETE_SELF)
def callIt(*args):
notified.callback(args)
# Watch, ignore, watch again to get into the state being tested.
self.assertTrue(self.inotify.watch(expectedPath, callbacks=[callIt]))
self.inotify.ignore(expectedPath)
self.assertTrue(
self.inotify.watch(
expectedPath, mask=inotify.IN_DELETE_SELF, callbacks=[callIt]
)
)
notified.addCallback(cbNotified)
# Apparently in kernel version < 2.6.25, inofify has a bug in the way
# similar events are coalesced. So, be sure to generate a different
# event here than the touch() at the top of this method might have
# generated.
expectedPath.remove()
return notified
def test_ignoreFilePath(self):
"""
L{inotify.INotify} will ignore a filepath after it has been removed from
the watch list.
"""
expectedPath = self.dirname.child("foo.bar2")
expectedPath.touch()
expectedPath2 = self.dirname.child("foo.bar3")
expectedPath2.touch()
notified = defer.Deferred()
def cbNotified(result):
(ignored, filename, events) = result
self.assertEqual(filename.asBytesMode(), expectedPath2.asBytesMode())
self.assertTrue(events & inotify.IN_DELETE_SELF)
def callIt(*args):
notified.callback(args)
self.assertTrue(
self.inotify.watch(expectedPath, inotify.IN_DELETE_SELF, callbacks=[callIt])
)
notified.addCallback(cbNotified)
self.assertTrue(
self.inotify.watch(
expectedPath2, inotify.IN_DELETE_SELF, callbacks=[callIt]
)
)
self.inotify.ignore(expectedPath)
expectedPath.remove()
expectedPath2.remove()
return notified
def test_ignoreNonWatchedFile(self):
"""
L{inotify.INotify} will raise KeyError if a non-watched filepath is
ignored.
"""
expectedPath = self.dirname.child("foo.ignored")
expectedPath.touch()
self.assertRaises(KeyError, self.inotify.ignore, expectedPath)
def test_complexSubdirectoryAutoAdd(self):
"""
L{inotify.INotify} with autoAdd==True for a watched path
generates events for every file or directory already present
in a newly created subdirectory under the watched one.
This tests that we solve a race condition in inotify even though
we may generate duplicate events.
"""
calls = set()
def _callback(wp, filename, mask):
calls.add(filename)
if len(calls) == 6:
try:
self.assertTrue(self.inotify._isWatched(subdir))
self.assertTrue(self.inotify._isWatched(subdir2))
self.assertTrue(self.inotify._isWatched(subdir3))
created = someFiles + [subdir, subdir2, subdir3]
created = {f.asBytesMode() for f in created}
self.assertEqual(len(calls), len(created))
self.assertEqual(calls, created)
except Exception:
d.errback()
else:
d.callback(None)
checkMask = inotify.IN_ISDIR | inotify.IN_CREATE
self.inotify.watch(
self.dirname, mask=checkMask, autoAdd=True, callbacks=[_callback]
)
subdir = self.dirname.child("test")
subdir2 = subdir.child("test2")
subdir3 = subdir2.child("test3")
d = defer.Deferred()
subdir3.makedirs()
someFiles = [
subdir.child("file1.dat"),
subdir2.child("file2.dat"),
subdir3.child("file3.dat"),
]
# Add some files in pretty much all the directories so that we
# see that we process all of them.
for i, filename in enumerate(someFiles):
filename.setContent(filename.path.encode(sys.getfilesystemencoding()))
return d

View File

@@ -0,0 +1,212 @@
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Tests for L{twisted.internet.iocpreactor}.
"""
import errno
import sys
import time
from array import array
from socket import AF_INET, AF_INET6, SOCK_STREAM, SOL_SOCKET, socket
from struct import pack
from unittest import skipIf
from zope.interface.verify import verifyClass
from twisted.internet.interfaces import IPushProducer
from twisted.python.log import msg
from twisted.trial.unittest import TestCase
try:
from twisted.internet.iocpreactor import iocpsupport as _iocp, tcp, udp
from twisted.internet.iocpreactor.abstract import FileHandle
from twisted.internet.iocpreactor.const import SO_UPDATE_ACCEPT_CONTEXT
from twisted.internet.iocpreactor.interfaces import IReadWriteHandle
from twisted.internet.iocpreactor.reactor import (
EVENTS_PER_LOOP,
KEY_NORMAL,
IOCPReactor,
)
except ImportError:
if sys.platform == "win32":
raise
skip = "This test only applies to IOCPReactor"
try:
socket(AF_INET6, SOCK_STREAM).close()
except OSError as e:
ipv6Skip = True
ipv6SkipReason = str(e)
else:
ipv6Skip = False
ipv6SkipReason = ""
class SupportTests(TestCase):
"""
Tests for L{twisted.internet.iocpreactor.iocpsupport}, low-level reactor
implementation helpers.
"""
def _acceptAddressTest(self, family, localhost):
"""
Create a C{SOCK_STREAM} connection to localhost using a socket with an
address family of C{family} and assert that the result of
L{iocpsupport.get_accept_addrs} is consistent with the result of
C{socket.getsockname} and C{socket.getpeername}.
A port starts listening (is bound) at the low-level socket without
calling accept() yet.
A client is then connected.
After the client is connected IOCP accept() is called, which is the
target of these tests.
Most of the time, the socket is ready instantly, but sometimes
the socket is not ready right away after calling IOCP accept().
It should not take more than 5 seconds for a socket to be ready, as
the client connection is already made over the loopback interface.
These are flaky tests.
Tweak the failure rate by changing the number of retries and the
wait/sleep between retries.
If you will need to update the retries to wait more than 5 seconds
for the port to be available, then there might a bug in the code and
not the test (or a very, very busy VM running the tests).
"""
msg(f"family = {family!r}")
port = socket(family, SOCK_STREAM)
self.addCleanup(port.close)
port.bind(("", 0))
port.listen(1)
client = socket(family, SOCK_STREAM)
self.addCleanup(client.close)
client.setblocking(False)
try:
client.connect((localhost, port.getsockname()[1]))
except OSError as e:
self.assertIn(e.errno, (errno.EINPROGRESS, errno.EWOULDBLOCK))
server = socket(family, SOCK_STREAM)
self.addCleanup(server.close)
buff = array("B", b"\0" * 256)
self.assertEqual(0, _iocp.accept(port.fileno(), server.fileno(), buff, None))
for attemptsRemaining in reversed(range(5)):
# Calling setsockopt after _iocp.accept might fail for both IPv4
# and IPV6 with "[Errno 10057] A request to send or receive ..."
# This is when ERROR_IO_PENDING is returned and means that the
# socket is not yet ready and accept will be handled via the
# callback event.
# For this test, with the purpose of keeping the test simple,
# we don't implement the event callback.
# The event callback functionality is tested via the high level
# tests for general reactor API.
# We retry multiple times to cover.
try:
server.setsockopt(
SOL_SOCKET, SO_UPDATE_ACCEPT_CONTEXT, pack("P", port.fileno())
)
break
except OSError as socketError:
# getattr is used below to make mypy happy.
if socketError.errno != getattr(errno, "WSAENOTCONN"):
# This is not the expected error so re-raise the error without retrying.
raise
# The socket is not yet ready to accept connections,
# setsockopt fails.
if attemptsRemaining == 0:
# We ran out of retries.
raise
# Without a sleep here even retrying 20 times will fail.
# This should allow other threads to execute and hopefully with the next
# try setsockopt will succeed.
time.sleep(0.2)
self.assertEqual(
(family, client.getpeername()[:2], client.getsockname()[:2]),
_iocp.get_accept_addrs(server.fileno(), buff),
)
def test_ipv4AcceptAddress(self):
"""
L{iocpsupport.get_accept_addrs} returns a three-tuple of address
information about the socket associated with the file descriptor passed
to it. For a connection using IPv4:
- the first element is C{AF_INET}
- the second element is a two-tuple of a dotted decimal notation IPv4
address and a port number giving the peer address of the connection
- the third element is the same type giving the host address of the
connection
"""
self._acceptAddressTest(AF_INET, "127.0.0.1")
@skipIf(ipv6Skip, ipv6SkipReason)
def test_ipv6AcceptAddress(self):
"""
Like L{test_ipv4AcceptAddress}, but for IPv6 connections.
In this case:
- the first element is C{AF_INET6}
- the second element is a two-tuple of a hexadecimal IPv6 address
literal and a port number giving the peer address of the connection
- the third element is the same type giving the host address of the
connection
"""
self._acceptAddressTest(AF_INET6, "::1")
class IOCPReactorTests(TestCase):
def test_noPendingTimerEvents(self):
"""
Test reactor behavior (doIteration) when there are no pending time
events.
"""
ir = IOCPReactor()
ir.wakeUp()
self.assertFalse(ir.doIteration(None))
def test_reactorInterfaces(self):
"""
Verify that IOCP socket-representing classes implement IReadWriteHandle
"""
self.assertTrue(verifyClass(IReadWriteHandle, tcp.Connection))
self.assertTrue(verifyClass(IReadWriteHandle, udp.Port))
def test_fileHandleInterfaces(self):
"""
Verify that L{Filehandle} implements L{IPushProducer}.
"""
self.assertTrue(verifyClass(IPushProducer, FileHandle))
def test_maxEventsPerIteration(self):
"""
Verify that we don't lose an event when more than EVENTS_PER_LOOP
events occur in the same reactor iteration
"""
class FakeFD:
counter = 0
def logPrefix(self):
return "FakeFD"
def cb(self, rc, bytes, evt):
self.counter += 1
ir = IOCPReactor()
fd = FakeFD()
event = _iocp.Event(fd.cb, fd)
for _ in range(EVENTS_PER_LOOP + 1):
ir.port.postEvent(0, KEY_NORMAL, event)
ir.doIteration(None)
self.assertEqual(fd.counter, EVENTS_PER_LOOP)
ir.doIteration(0)
self.assertEqual(fd.counter, EVENTS_PER_LOOP + 1)

View File

@@ -0,0 +1,73 @@
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Tests for L{twisted.internet.kqueuereactor}.
"""
from __future__ import annotations
import errno
from zope.interface import implementer
from twisted.trial.unittest import TestCase
try:
from twisted.internet.kqreactor import KQueueReactor, _IKQueue
kqueueSkip = None
except ImportError:
kqueueSkip = "KQueue not available."
def _fakeKEvent(*args: object, **kwargs: object) -> None:
"""
Do nothing.
"""
def makeFakeKQueue(testKQueue: object, testKEvent: object) -> _IKQueue:
"""
Create a fake that implements L{_IKQueue}.
@param testKQueue: Something that acts like L{select.kqueue}.
@param testKEvent: Something that acts like L{select.kevent}.
@return: An implementation of L{_IKQueue} that includes C{testKQueue} and
C{testKEvent}.
"""
@implementer(_IKQueue)
class FakeKQueue:
kqueue = testKQueue
kevent = testKEvent
return FakeKQueue()
class KQueueTests(TestCase):
"""
These are tests for L{KQueueReactor}'s implementation, not its real world
behaviour. For that, look at
L{twisted.internet.test.reactormixins.ReactorBuilder}.
"""
skip = kqueueSkip
def test_EINTR(self) -> None:
"""
L{KQueueReactor} handles L{errno.EINTR} in C{doKEvent} by returning.
"""
class FakeKQueue:
"""
A fake KQueue that raises L{errno.EINTR} when C{control} is called,
like a real KQueue would if it was interrupted.
"""
def control(self, *args: object, **kwargs: object) -> None:
raise OSError(errno.EINTR, "Interrupted")
reactor = KQueueReactor(makeFakeKQueue(FakeKQueue, _fakeKEvent))
# This should return cleanly -- should not raise the OSError we're
# spawning, nor get upset and raise about the incomplete KQueue fake.
reactor.doKEvent(0)

View File

@@ -0,0 +1,45 @@
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Tests for L{twisted.internet.main}.
"""
from twisted.internet.error import ReactorAlreadyInstalledError
from twisted.internet.main import installReactor
from twisted.internet.test.modulehelpers import NoReactor
from twisted.trial import unittest
class InstallReactorTests(unittest.SynchronousTestCase):
"""
Tests for L{installReactor}.
"""
def test_installReactor(self) -> None:
"""
L{installReactor} installs a new reactor if none is present.
"""
with NoReactor():
newReactor = object()
installReactor(newReactor)
from twisted.internet import reactor
self.assertIs(newReactor, reactor)
def test_alreadyInstalled(self) -> None:
"""
If a reactor is already installed, L{installReactor} raises
L{ReactorAlreadyInstalledError}.
"""
with NoReactor():
installReactor(object())
self.assertRaises(ReactorAlreadyInstalledError, installReactor, object())
def test_errorIsAnAssertionError(self) -> None:
"""
For backwards compatibility, L{ReactorAlreadyInstalledError} is an
L{AssertionError}.
"""
self.assertTrue(issubclass(ReactorAlreadyInstalledError, AssertionError))

View File

@@ -0,0 +1,202 @@
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Tests for L{twisted.internet._newtls}.
"""
from twisted.internet import interfaces
from twisted.internet.test.connectionmixins import (
ConnectableProtocol,
runProtocolsWithReactor,
)
from twisted.internet.test.reactormixins import ReactorBuilder
from twisted.internet.test.test_tcp import TCPCreator
from twisted.internet.test.test_tls import (
ContextGeneratingMixin,
SSLCreator,
StartTLSClientCreator,
TLSMixin,
)
from twisted.trial import unittest
try:
from twisted.internet import _newtls as __newtls
from twisted.protocols import tls
except ImportError:
_newtls = None
else:
_newtls = __newtls
from zope.interface import implementer
class BypassTLSTests(unittest.TestCase):
"""
Tests for the L{_newtls._BypassTLS} class.
"""
if not _newtls:
skip = "Couldn't import _newtls, perhaps pyOpenSSL is old or missing"
def test_loseConnectionPassThrough(self):
"""
C{_BypassTLS.loseConnection} calls C{loseConnection} on the base
class, while preserving any default argument in the base class'
C{loseConnection} implementation.
"""
default = object()
result = []
class FakeTransport:
def loseConnection(self, _connDone=default):
result.append(_connDone)
bypass = _newtls._BypassTLS(FakeTransport, FakeTransport())
# The default from FakeTransport is used:
bypass.loseConnection()
self.assertEqual(result, [default])
# And we can pass our own:
notDefault = object()
bypass.loseConnection(notDefault)
self.assertEqual(result, [default, notDefault])
class FakeProducer:
"""
A producer that does nothing.
"""
def pauseProducing(self):
pass
def resumeProducing(self):
pass
def stopProducing(self):
pass
@implementer(interfaces.IHandshakeListener)
class ProducerProtocol(ConnectableProtocol):
"""
Register a producer, unregister it, and verify the producer hooks up to
innards of C{TLSMemoryBIOProtocol}.
"""
def __init__(self, producer, result):
self.producer = producer
self.result = result
def handshakeCompleted(self):
if not isinstance(self.transport.protocol, tls.BufferingTLSTransport):
# Either the test or the code have a bug...
raise RuntimeError("TLSMemoryBIOProtocol not hooked up.")
self.transport.registerProducer(self.producer, True)
# The producer was registered with the TLSMemoryBIOProtocol:
self.result.append(self.transport.protocol._producer._producer)
self.transport.unregisterProducer()
# The producer was unregistered from the TLSMemoryBIOProtocol:
self.result.append(self.transport.protocol._producer)
self.transport.loseConnection()
class ProducerTestsMixin(ReactorBuilder, TLSMixin, ContextGeneratingMixin):
"""
Test the new TLS code integrates C{TLSMemoryBIOProtocol} correctly.
"""
if not _newtls:
skip = "Could not import twisted.internet._newtls"
def test_producerSSLFromStart(self):
"""
C{registerProducer} and C{unregisterProducer} on TLS transports
created as SSL from the get go are passed to the
C{TLSMemoryBIOProtocol}, not the underlying transport directly.
"""
result = []
producer = FakeProducer()
runProtocolsWithReactor(
self,
ConnectableProtocol(),
ProducerProtocol(producer, result),
SSLCreator(),
)
self.assertEqual(result, [producer, None])
def test_producerAfterStartTLS(self):
"""
C{registerProducer} and C{unregisterProducer} on TLS transports
created by C{startTLS} are passed to the C{TLSMemoryBIOProtocol}, not
the underlying transport directly.
"""
result = []
producer = FakeProducer()
runProtocolsWithReactor(
self,
ConnectableProtocol(),
ProducerProtocol(producer, result),
StartTLSClientCreator(),
)
self.assertEqual(result, [producer, None])
def startTLSAfterRegisterProducer(self, streaming):
"""
When a producer is registered, and then startTLS is called,
the producer is re-registered with the C{TLSMemoryBIOProtocol}.
"""
clientContext = self.getClientContext()
serverContext = self.getServerContext()
result = []
producer = FakeProducer()
class RegisterTLSProtocol(ConnectableProtocol):
def connectionMade(self):
self.transport.registerProducer(producer, streaming)
self.transport.startTLS(serverContext)
# Store TLSMemoryBIOProtocol and underlying transport producer
# status:
if streaming:
# _ProducerMembrane -> producer:
result.append(self.transport.protocol._producer._producer)
result.append(self.transport.producer._producer)
else:
# _ProducerMembrane -> _PullToPush -> producer:
result.append(self.transport.protocol._producer._producer._producer)
result.append(self.transport.producer._producer._producer)
self.transport.unregisterProducer()
self.transport.loseConnection()
class StartTLSProtocol(ConnectableProtocol):
def connectionMade(self):
self.transport.startTLS(clientContext)
runProtocolsWithReactor(
self, RegisterTLSProtocol(), StartTLSProtocol(), TCPCreator()
)
self.assertEqual(result, [producer, producer])
def test_startTLSAfterRegisterProducerStreaming(self):
"""
When a streaming producer is registered, and then startTLS is called,
the producer is re-registered with the C{TLSMemoryBIOProtocol}.
"""
self.startTLSAfterRegisterProducer(True)
def test_startTLSAfterRegisterProducerNonStreaming(self):
"""
When a non-streaming producer is registered, and then startTLS is
called, the producer is re-registered with the
C{TLSMemoryBIOProtocol}.
"""
self.startTLSAfterRegisterProducer(False)
globals().update(ProducerTestsMixin.makeTestCaseClasses())

View File

@@ -0,0 +1,41 @@
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Tests for L{twisted.internet._pollingfile}.
"""
from unittest import skipIf
from twisted.python.runtime import platform
from twisted.trial.unittest import TestCase
if platform.isWindows():
from twisted.internet import _pollingfile
else:
_pollingfile = None # type: ignore[assignment]
@skipIf(_pollingfile is None, "Test will run only on Windows.")
class PollableWritePipeTests(TestCase):
"""
Tests for L{_pollingfile._PollableWritePipe}.
"""
def test_writeUnicode(self) -> None:
"""
L{_pollingfile._PollableWritePipe.write} raises a C{TypeError} if an
attempt is made to append unicode data to the output buffer.
"""
p = _pollingfile._PollableWritePipe(1, lambda: None)
self.assertRaises(TypeError, p.write, "test")
def test_writeSequenceUnicode(self) -> None:
"""
L{_pollingfile._PollableWritePipe.writeSequence} raises a C{TypeError}
if unicode data is part of the data sequence to be appended to the
output buffer.
"""
p = _pollingfile._PollableWritePipe(1, lambda: None)
self.assertRaises(TypeError, p.writeSequence, ["test"])
self.assertRaises(TypeError, p.writeSequence, ("test",))

View File

@@ -0,0 +1,348 @@
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Tests for L{twisted.internet.posixbase} and supporting code.
"""
import os
from twisted.internet.defer import Deferred
from twisted.internet.interfaces import IReadDescriptor
from twisted.internet.posixbase import PosixReactorBase, _Waker
from twisted.internet.protocol import ServerFactory
from twisted.python.runtime import platform
from twisted.trial.unittest import TestCase
skipSockets = None
try:
from twisted.internet import unix
from twisted.test.test_unix import ClientProto
except ImportError:
skipSockets = "Platform does not support AF_UNIX sockets"
from twisted.internet import reactor
from twisted.internet.tcp import Port
class WarningCheckerTestCase(TestCase):
"""
A test case that will make sure that no warnings are left unchecked at the end of a test run.
"""
def setUp(self):
super().setUp()
# FIXME:
# https://twistedmatrix.com/trac/ticket/10332
# For now, try to start each test without previous warnings
# on Windows CI environment.
# We still want to see failures on local Windows development environment to make it easier to fix them,
# rather than ignoring the errors.
if os.environ.get("CI", "").lower() == "true" and platform.isWindows():
self.flushWarnings()
def tearDown(self):
try:
super().tearDown()
finally:
warnings = self.flushWarnings()
if os.environ.get("CI", "").lower() == "true" and platform.isWindows():
# FIXME:
# https://twistedmatrix.com/trac/ticket/10332
# For now don't raise errors on Windows as the existing tests are dirty and we don't have the dev resources to fix this.
# If you care about Twisted on Windows, enable this check and hunt for the test that is generating the warnings.
# Note that even with this check disabled, you can still see flaky tests on Windows, as due to stray delayed calls
# the warnings can be generated while another test is running.
return
self.assertEqual(
len(warnings), 0, f"Warnings found at the end of the test:\n{warnings}"
)
class TrivialReactor(PosixReactorBase):
def __init__(self):
self._readers = {}
self._writers = {}
PosixReactorBase.__init__(self)
def addReader(self, reader):
self._readers[reader] = True
def removeReader(self, reader):
del self._readers[reader]
def addWriter(self, writer):
self._writers[writer] = True
def removeWriter(self, writer):
del self._writers[writer]
class PosixReactorBaseTests(WarningCheckerTestCase):
"""
Tests for L{PosixReactorBase}.
"""
def _checkWaker(self, reactor):
self.assertIsInstance(reactor.waker, _Waker)
self.assertIn(reactor.waker, reactor._internalReaders)
self.assertIn(reactor.waker, reactor._readers)
def test_wakerIsInternalReader(self):
"""
When L{PosixReactorBase} is instantiated, it creates a waker and adds
it to its internal readers set.
"""
reactor = TrivialReactor()
self._checkWaker(reactor)
def test_removeAllSkipsInternalReaders(self):
"""
Any L{IReadDescriptor}s in L{PosixReactorBase._internalReaders} are
left alone by L{PosixReactorBase._removeAll}.
"""
reactor = TrivialReactor()
extra = object()
reactor._internalReaders.add(extra)
reactor.addReader(extra)
reactor._removeAll(reactor._readers, reactor._writers)
self._checkWaker(reactor)
self.assertIn(extra, reactor._internalReaders)
self.assertIn(extra, reactor._readers)
def test_removeAllReturnsRemovedDescriptors(self):
"""
L{PosixReactorBase._removeAll} returns a list of removed
L{IReadDescriptor} and L{IWriteDescriptor} objects.
"""
reactor = TrivialReactor()
reader = object()
writer = object()
reactor.addReader(reader)
reactor.addWriter(writer)
removed = reactor._removeAll(reactor._readers, reactor._writers)
self.assertEqual(set(removed), {reader, writer})
self.assertNotIn(reader, reactor._readers)
self.assertNotIn(writer, reactor._writers)
class TCPPortTests(WarningCheckerTestCase):
"""
Tests for L{twisted.internet.tcp.Port}.
"""
if not isinstance(reactor, PosixReactorBase):
skip = "Non-posixbase reactor"
def test_connectionLostFailed(self):
"""
L{Port.stopListening} returns a L{Deferred} which errbacks if
L{Port.connectionLost} raises an exception.
"""
port = Port(12345, ServerFactory())
port.connected = True
port.connectionLost = lambda reason: 1 // 0
return self.assertFailure(port.stopListening(), ZeroDivisionError)
class TimeoutReportReactor(PosixReactorBase):
"""
A reactor which is just barely runnable and which cannot monitor any
readers or writers, and which fires a L{Deferred} with the timeout
passed to its C{doIteration} method as soon as that method is invoked.
"""
def __init__(self):
PosixReactorBase.__init__(self)
self.iterationTimeout = Deferred()
self.now = 100
def addReader(self, reader: IReadDescriptor) -> None:
"""
Ignore the reader. This is necessary because the waker will be
added. However, we won't actually monitor it for any events.
"""
def removeReader(self, reader: IReadDescriptor) -> None:
"""
See L{addReader}.
"""
def removeAll(self):
"""
There are no readers or writers, so there is nothing to remove.
This will be called when the reactor stops, though, so it must be
implemented.
"""
return []
def seconds(self):
"""
Override the real clock with a deterministic one that can be easily
controlled in a unit test.
"""
return self.now
def doIteration(self, timeout):
d = self.iterationTimeout
if d is not None:
self.iterationTimeout = None
d.callback(timeout)
class IterationTimeoutTests(WarningCheckerTestCase):
"""
Tests for the timeout argument L{PosixReactorBase.run} calls
L{PosixReactorBase.doIteration} with in the presence of various delayed
calls.
"""
def _checkIterationTimeout(self, reactor):
timeout = []
reactor.iterationTimeout.addCallback(timeout.append)
reactor.iterationTimeout.addCallback(lambda ignored: reactor.stop())
reactor.run()
return timeout[0]
def test_noCalls(self):
"""
If there are no delayed calls, C{doIteration} is called with a
timeout of L{None}.
"""
reactor = TimeoutReportReactor()
timeout = self._checkIterationTimeout(reactor)
self.assertIsNone(timeout)
def test_delayedCall(self):
"""
If there is a delayed call, C{doIteration} is called with a timeout
which is the difference between the current time and the time at
which that call is to run.
"""
reactor = TimeoutReportReactor()
reactor.callLater(100, lambda: None)
timeout = self._checkIterationTimeout(reactor)
self.assertEqual(timeout, 100)
def test_timePasses(self):
"""
If a delayed call is scheduled and then some time passes, the
timeout passed to C{doIteration} is reduced by the amount of time
which passed.
"""
reactor = TimeoutReportReactor()
reactor.callLater(100, lambda: None)
reactor.now += 25
timeout = self._checkIterationTimeout(reactor)
self.assertEqual(timeout, 75)
def test_multipleDelayedCalls(self):
"""
If there are several delayed calls, C{doIteration} is called with a
timeout which is the difference between the current time and the
time at which the earlier of the two calls is to run.
"""
reactor = TimeoutReportReactor()
reactor.callLater(50, lambda: None)
reactor.callLater(10, lambda: None)
reactor.callLater(100, lambda: None)
timeout = self._checkIterationTimeout(reactor)
self.assertEqual(timeout, 10)
def test_resetDelayedCall(self):
"""
If a delayed call is reset, the timeout passed to C{doIteration} is
based on the interval between the time when reset is called and the
new delay of the call.
"""
reactor = TimeoutReportReactor()
call = reactor.callLater(50, lambda: None)
reactor.now += 25
call.reset(15)
timeout = self._checkIterationTimeout(reactor)
self.assertEqual(timeout, 15)
def test_delayDelayedCall(self):
"""
If a delayed call is re-delayed, the timeout passed to
C{doIteration} is based on the remaining time before the call would
have been made and the additional amount of time passed to the delay
method.
"""
reactor = TimeoutReportReactor()
call = reactor.callLater(50, lambda: None)
reactor.now += 10
call.delay(20)
timeout = self._checkIterationTimeout(reactor)
self.assertEqual(timeout, 60)
def test_cancelDelayedCall(self):
"""
If the only delayed call is canceled, L{None} is the timeout passed
to C{doIteration}.
"""
reactor = TimeoutReportReactor()
call = reactor.callLater(50, lambda: None)
call.cancel()
timeout = self._checkIterationTimeout(reactor)
self.assertIsNone(timeout)
class ConnectedDatagramPortTests(WarningCheckerTestCase):
"""
Test connected datagram UNIX sockets.
"""
if skipSockets is not None:
skip = skipSockets
def test_connectionFailedDoesntCallLoseConnection(self):
"""
L{ConnectedDatagramPort} does not call the deprecated C{loseConnection}
in L{ConnectedDatagramPort.connectionFailed}.
"""
def loseConnection():
"""
Dummy C{loseConnection} method. C{loseConnection} is deprecated and
should not get called.
"""
self.fail("loseConnection is deprecated and should not get called.")
port = unix.ConnectedDatagramPort(None, ClientProto())
port.loseConnection = loseConnection
port.connectionFailed("goodbye")
def test_connectionFailedCallsStopListening(self):
"""
L{ConnectedDatagramPort} calls L{ConnectedDatagramPort.stopListening}
instead of the deprecated C{loseConnection} in
L{ConnectedDatagramPort.connectionFailed}.
"""
self.called = False
def stopListening():
"""
Dummy C{stopListening} method.
"""
self.called = True
port = unix.ConnectedDatagramPort(None, ClientProto())
port.stopListening = stopListening
port.connectionFailed("goodbye")
self.assertTrue(self.called)
class WakerTests(WarningCheckerTestCase):
def test_noWakerConstructionWarnings(self):
"""
No warnings are generated when constructing the waker.
"""
waker = _Waker()
warnings = self.flushWarnings()
self.assertEqual(len(warnings), 0, warnings)
# Explicitly close the waker to leave a clean state at the end of the test.
waker.connectionLost(None)
warnings = self.flushWarnings()
self.assertEqual(len(warnings), 0, warnings)

View File

@@ -0,0 +1,349 @@
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Tests for POSIX-based L{IReactorProcess} implementations.
"""
import errno
import os
import sys
from typing import Optional
platformSkip: Optional[str]
try:
import fcntl
except ImportError:
platformSkip = "non-POSIX platform"
else:
from twisted.internet import process
platformSkip = None
from twisted.trial.unittest import TestCase
class FakeFile:
"""
A dummy file object which records when it is closed.
"""
def __init__(self, testcase, fd):
self.testcase = testcase
self.fd = fd
def close(self):
self.testcase._files.remove(self.fd)
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, traceback):
self.close()
class FakeResourceModule:
"""
Fake version of L{resource} which hard-codes a particular rlimit for maximum
open files.
@ivar _limit: The value to return for the hard limit of number of open files.
"""
RLIMIT_NOFILE = 1
def __init__(self, limit):
self._limit = limit
def getrlimit(self, no):
"""
A fake of L{resource.getrlimit} which returns a pre-determined result.
"""
if no == self.RLIMIT_NOFILE:
return [0, self._limit]
return [123, 456]
class FDDetectorTests(TestCase):
"""
Tests for _FDDetector class in twisted.internet.process, which detects
which function to drop in place for the _listOpenFDs method.
@ivar devfs: A flag indicating whether the filesystem fake will indicate
that /dev/fd exists.
@ivar accurateDevFDResults: A flag indicating whether the /dev/fd fake
returns accurate open file information.
@ivar procfs: A flag indicating whether the filesystem fake will indicate
that /proc/<pid>/fd exists.
"""
skip = platformSkip
devfs = False
accurateDevFDResults = False
procfs = False
def getpid(self):
"""
Fake os.getpid, always return the same thing
"""
return 123
def listdir(self, arg):
"""
Fake os.listdir, depending on what mode we're in to simulate behaviour.
@param arg: the directory to list
"""
accurate = map(str, self._files)
if self.procfs and arg == ("/proc/%d/fd" % (self.getpid(),)):
return accurate
if self.devfs and arg == "/dev/fd":
if self.accurateDevFDResults:
return accurate
return ["0", "1", "2"]
raise OSError()
def openfile(self, fname, mode):
"""
This is a mock for L{open}. It keeps track of opened files so extra
descriptors can be returned from the mock for L{os.listdir} when used on
one of the list-of-filedescriptors directories.
A L{FakeFile} is returned which can be closed to remove the new
descriptor from the open list.
"""
# Find the smallest unused file descriptor and give it to the new file.
f = FakeFile(self, min(set(range(1024)) - set(self._files)))
self._files.append(f.fd)
return f
def hideResourceModule(self):
"""
Make the L{resource} module unimportable for the remainder of the
current test method.
"""
sys.modules["resource"] = None
def revealResourceModule(self, limit):
"""
Make a L{FakeResourceModule} instance importable at the L{resource}
name.
@param limit: The value which will be returned for the hard limit of
number of open files by the fake resource module's C{getrlimit}
function.
"""
sys.modules["resource"] = FakeResourceModule(limit)
def replaceResourceModule(self, value):
"""
Restore the original resource module to L{sys.modules}.
"""
if value is None:
try:
del sys.modules["resource"]
except KeyError:
pass
else:
sys.modules["resource"] = value
def setUp(self):
"""
Set up the tests, giving ourselves a detector object to play with and
setting up its testable knobs to refer to our mocked versions.
"""
self.detector = process._FDDetector()
self.detector.listdir = self.listdir
self.detector.getpid = self.getpid
self.detector.openfile = self.openfile
self._files = [0, 1, 2]
self.addCleanup(self.replaceResourceModule, sys.modules.get("resource"))
def test_selectFirstWorking(self):
"""
L{FDDetector._getImplementation} returns the first method from its
C{_implementations} list which returns results which reflect a newly
opened file descriptor.
"""
def failWithException():
raise ValueError("This does not work")
def failWithWrongResults():
return [0, 1, 2]
def correct():
return self._files[:]
self.detector._implementations = [
failWithException,
failWithWrongResults,
correct,
]
self.assertIs(correct, self.detector._getImplementation())
def test_selectLast(self):
"""
L{FDDetector._getImplementation} returns the last method from its
C{_implementations} list if none of the implementations manage to return
results which reflect a newly opened file descriptor.
"""
def failWithWrongResults():
return [3, 5, 9]
def failWithOtherWrongResults():
return [0, 1, 2]
self.detector._implementations = [
failWithWrongResults,
failWithOtherWrongResults,
]
self.assertIs(failWithOtherWrongResults, self.detector._getImplementation())
def test_identityOfListOpenFDsChanges(self):
"""
Check that the identity of _listOpenFDs changes after running
_listOpenFDs the first time, but not after the second time it's run.
In other words, check that the monkey patching actually works.
"""
# Create a new instance
detector = process._FDDetector()
first = detector._listOpenFDs.__name__
detector._listOpenFDs()
second = detector._listOpenFDs.__name__
detector._listOpenFDs()
third = detector._listOpenFDs.__name__
self.assertNotEqual(first, second)
self.assertEqual(second, third)
def test_devFDImplementation(self):
"""
L{_FDDetector._devFDImplementation} raises L{OSError} if there is no
I{/dev/fd} directory, otherwise it returns the basenames of its children
interpreted as integers.
"""
self.devfs = False
self.assertRaises(OSError, self.detector._devFDImplementation)
self.devfs = True
self.accurateDevFDResults = False
self.assertEqual([0, 1, 2], self.detector._devFDImplementation())
def test_procFDImplementation(self):
"""
L{_FDDetector._procFDImplementation} raises L{OSError} if there is no
I{/proc/<pid>/fd} directory, otherwise it returns the basenames of its
children interpreted as integers.
"""
self.procfs = False
self.assertRaises(OSError, self.detector._procFDImplementation)
self.procfs = True
self.assertEqual([0, 1, 2], self.detector._procFDImplementation())
def test_resourceFDImplementation(self):
"""
L{_FDDetector._fallbackFDImplementation} uses the L{resource} module if
it is available, returning a range of integers from 0 to the
minimum of C{1024} and the hard I{NOFILE} limit.
"""
# When the resource module is here, use its value.
self.revealResourceModule(512)
self.assertEqual(
list(range(512)), list(self.detector._fallbackFDImplementation())
)
# But limit its value to the arbitrarily selected value 1024.
self.revealResourceModule(2048)
self.assertEqual(
list(range(1024)), list(self.detector._fallbackFDImplementation())
)
def test_fallbackFDImplementation(self):
"""
L{_FDDetector._fallbackFDImplementation}, the implementation of last
resort, succeeds with a fixed range of integers from 0 to 1024 when the
L{resource} module is not importable.
"""
self.hideResourceModule()
self.assertEqual(
list(range(1024)), list(self.detector._fallbackFDImplementation())
)
class FileDescriptorTests(TestCase):
"""
Tests for L{twisted.internet.process._listOpenFDs}
"""
skip = platformSkip
def test_openFDs(self):
"""
File descriptors returned by L{_listOpenFDs} are mostly open.
This test assumes that zero-legth writes fail with EBADF on closed
file descriptors.
"""
for fd in process._listOpenFDs():
try:
fcntl.fcntl(fd, fcntl.F_GETFL)
except OSError as err:
self.assertEqual(
errno.EBADF,
err.errno,
"fcntl(%d, F_GETFL) failed with unexpected errno %d"
% (fd, err.errno),
)
def test_expectedFDs(self):
"""
L{_listOpenFDs} lists expected file descriptors.
"""
# This is a tricky test. A priori, there is no way to know what file
# descriptors are open now, so there is no way to know what _listOpenFDs
# should return. Work around this by creating some new file descriptors
# which we can know the state of and then just making assertions about
# their presence or absence in the result.
# Expect a file we just opened to be listed.
f = open(os.devnull)
openfds = process._listOpenFDs()
self.assertIn(f.fileno(), openfds)
# Expect a file we just closed not to be listed - with a caveat. The
# implementation may need to open a file to discover the result. That
# open file descriptor will be allocated the same number as the one we
# just closed. So, instead, create a hole in the file descriptor space
# to catch that internal descriptor and make the assertion about a
# different closed file descriptor.
# This gets allocated a file descriptor larger than f's, since nothing
# has been closed since we opened f.
fd = os.dup(f.fileno())
# But sanity check that; if it fails the test is invalid.
self.assertTrue(
fd > f.fileno(),
"Expected duplicate file descriptor to be greater than original",
)
try:
# Get rid of the original, creating the hole. The copy should still
# be open, of course.
f.close()
self.assertIn(fd, process._listOpenFDs())
finally:
# Get rid of the copy now
os.close(fd)
# And it should not appear in the result.
self.assertNotIn(fd, process._listOpenFDs())

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,545 @@
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Tests for L{twisted.internet.protocol}.
"""
from io import BytesIO
from zope.interface import implementer
from zope.interface.verify import verifyObject
from twisted.internet.defer import CancelledError
from twisted.internet.interfaces import (
IConsumer,
ILoggingContext,
IProtocol,
IProtocolFactory,
)
from twisted.internet.protocol import (
ClientCreator,
ConsumerToProtocolAdapter,
Factory,
FileWrapper,
Protocol,
ProtocolToConsumerAdapter,
)
from twisted.internet.testing import MemoryReactorClock, StringTransport
from twisted.logger import LogLevel, globalLogPublisher
from twisted.python.failure import Failure
from twisted.trial.unittest import TestCase
class ClientCreatorTests(TestCase):
"""
Tests for L{twisted.internet.protocol.ClientCreator}.
"""
def _basicConnectTest(self, check):
"""
Helper for implementing a test to verify that one of the I{connect}
methods of L{ClientCreator} passes the right arguments to the right
reactor method.
@param check: A function which will be invoked with a reactor and a
L{ClientCreator} instance and which should call one of the
L{ClientCreator}'s I{connect} methods and assert that all of its
arguments except for the factory are passed on as expected to the
reactor. The factory should be returned.
"""
class SomeProtocol(Protocol):
pass
reactor = MemoryReactorClock()
cc = ClientCreator(reactor, SomeProtocol)
factory = check(reactor, cc)
protocol = factory.buildProtocol(None)
self.assertIsInstance(protocol, SomeProtocol)
def test_connectTCP(self):
"""
L{ClientCreator.connectTCP} calls C{reactor.connectTCP} with the host
and port information passed to it, and with a factory which will
construct the protocol passed to L{ClientCreator.__init__}.
"""
def check(reactor, cc):
cc.connectTCP("example.com", 1234, 4321, ("1.2.3.4", 9876))
host, port, factory, timeout, bindAddress = reactor.tcpClients.pop()
self.assertEqual(host, "example.com")
self.assertEqual(port, 1234)
self.assertEqual(timeout, 4321)
self.assertEqual(bindAddress, ("1.2.3.4", 9876))
return factory
self._basicConnectTest(check)
def test_connectUNIX(self):
"""
L{ClientCreator.connectUNIX} calls C{reactor.connectUNIX} with the
filename passed to it, and with a factory which will construct the
protocol passed to L{ClientCreator.__init__}.
"""
def check(reactor, cc):
cc.connectUNIX("/foo/bar", 123, True)
address, factory, timeout, checkPID = reactor.unixClients.pop()
self.assertEqual(address, "/foo/bar")
self.assertEqual(timeout, 123)
self.assertTrue(checkPID)
return factory
self._basicConnectTest(check)
def test_connectSSL(self):
"""
L{ClientCreator.connectSSL} calls C{reactor.connectSSL} with the host,
port, and context factory passed to it, and with a factory which will
construct the protocol passed to L{ClientCreator.__init__}.
"""
def check(reactor, cc):
expectedContextFactory = object()
cc.connectSSL(
"example.com", 1234, expectedContextFactory, 4321, ("4.3.2.1", 5678)
)
(
host,
port,
factory,
contextFactory,
timeout,
bindAddress,
) = reactor.sslClients.pop()
self.assertEqual(host, "example.com")
self.assertEqual(port, 1234)
self.assertIs(contextFactory, expectedContextFactory)
self.assertEqual(timeout, 4321)
self.assertEqual(bindAddress, ("4.3.2.1", 5678))
return factory
self._basicConnectTest(check)
def _cancelConnectTest(self, connect):
"""
Helper for implementing a test to verify that cancellation of the
L{Deferred} returned by one of L{ClientCreator}'s I{connect} methods is
implemented to cancel the underlying connector.
@param connect: A function which will be invoked with a L{ClientCreator}
instance as an argument and which should call one its I{connect}
methods and return the result.
@return: A L{Deferred} which fires when the test is complete or fails if
there is a problem.
"""
reactor = MemoryReactorClock()
cc = ClientCreator(reactor, Protocol)
d = connect(cc)
connector = reactor.connectors.pop()
self.assertFalse(connector._disconnected)
d.cancel()
self.assertTrue(connector._disconnected)
return self.assertFailure(d, CancelledError)
def test_cancelConnectTCP(self):
"""
The L{Deferred} returned by L{ClientCreator.connectTCP} can be cancelled
to abort the connection attempt before it completes.
"""
def connect(cc):
return cc.connectTCP("example.com", 1234)
return self._cancelConnectTest(connect)
def test_cancelConnectUNIX(self):
"""
The L{Deferred} returned by L{ClientCreator.connectTCP} can be cancelled
to abort the connection attempt before it completes.
"""
def connect(cc):
return cc.connectUNIX("/foo/bar")
return self._cancelConnectTest(connect)
def test_cancelConnectSSL(self):
"""
The L{Deferred} returned by L{ClientCreator.connectTCP} can be cancelled
to abort the connection attempt before it completes.
"""
def connect(cc):
return cc.connectSSL("example.com", 1234, object())
return self._cancelConnectTest(connect)
def _cancelConnectTimeoutTest(self, connect):
"""
Like L{_cancelConnectTest}, but for the case where the L{Deferred} is
cancelled after the connection is set up but before it is fired with the
resulting protocol instance.
"""
reactor = MemoryReactorClock()
cc = ClientCreator(reactor, Protocol)
d = connect(reactor, cc)
connector = reactor.connectors.pop()
# Sanity check - there is an outstanding delayed call to fire the
# Deferred.
self.assertEqual(len(reactor.getDelayedCalls()), 1)
# Cancel the Deferred, disconnecting the transport just set up and
# cancelling the delayed call.
d.cancel()
self.assertEqual(reactor.getDelayedCalls(), [])
# A real connector implementation is responsible for disconnecting the
# transport as well. For our purposes, just check that someone told the
# connector to disconnect.
self.assertTrue(connector._disconnected)
return self.assertFailure(d, CancelledError)
def test_cancelConnectTCPTimeout(self):
"""
L{ClientCreator.connectTCP} inserts a very short delayed call between
the time the connection is established and the time the L{Deferred}
returned from one of its connect methods actually fires. If the
L{Deferred} is cancelled in this interval, the established connection is
closed, the timeout is cancelled, and the L{Deferred} fails with
L{CancelledError}.
"""
def connect(reactor, cc):
d = cc.connectTCP("example.com", 1234)
host, port, factory, timeout, bindAddress = reactor.tcpClients.pop()
protocol = factory.buildProtocol(None)
transport = StringTransport()
protocol.makeConnection(transport)
return d
return self._cancelConnectTimeoutTest(connect)
def test_cancelConnectUNIXTimeout(self):
"""
L{ClientCreator.connectUNIX} inserts a very short delayed call between
the time the connection is established and the time the L{Deferred}
returned from one of its connect methods actually fires. If the
L{Deferred} is cancelled in this interval, the established connection is
closed, the timeout is cancelled, and the L{Deferred} fails with
L{CancelledError}.
"""
def connect(reactor, cc):
d = cc.connectUNIX("/foo/bar")
address, factory, timeout, bindAddress = reactor.unixClients.pop()
protocol = factory.buildProtocol(None)
transport = StringTransport()
protocol.makeConnection(transport)
return d
return self._cancelConnectTimeoutTest(connect)
def test_cancelConnectSSLTimeout(self):
"""
L{ClientCreator.connectSSL} inserts a very short delayed call between
the time the connection is established and the time the L{Deferred}
returned from one of its connect methods actually fires. If the
L{Deferred} is cancelled in this interval, the established connection is
closed, the timeout is cancelled, and the L{Deferred} fails with
L{CancelledError}.
"""
def connect(reactor, cc):
d = cc.connectSSL("example.com", 1234, object())
(
host,
port,
factory,
contextFactory,
timeout,
bindADdress,
) = reactor.sslClients.pop()
protocol = factory.buildProtocol(None)
transport = StringTransport()
protocol.makeConnection(transport)
return d
return self._cancelConnectTimeoutTest(connect)
def _cancelConnectFailedTimeoutTest(self, connect):
"""
Like L{_cancelConnectTest}, but for the case where the L{Deferred} is
cancelled after the connection attempt has failed but before it is fired
with the resulting failure.
"""
reactor = MemoryReactorClock()
cc = ClientCreator(reactor, Protocol)
d, factory = connect(reactor, cc)
connector = reactor.connectors.pop()
factory.clientConnectionFailed(
connector, Failure(Exception("Simulated failure"))
)
# Sanity check - there is an outstanding delayed call to fire the
# Deferred.
self.assertEqual(len(reactor.getDelayedCalls()), 1)
# Cancel the Deferred, cancelling the delayed call.
d.cancel()
self.assertEqual(reactor.getDelayedCalls(), [])
return self.assertFailure(d, CancelledError)
def test_cancelConnectTCPFailedTimeout(self):
"""
Similar to L{test_cancelConnectTCPTimeout}, but for the case where the
connection attempt fails.
"""
def connect(reactor, cc):
d = cc.connectTCP("example.com", 1234)
host, port, factory, timeout, bindAddress = reactor.tcpClients.pop()
return d, factory
return self._cancelConnectFailedTimeoutTest(connect)
def test_cancelConnectUNIXFailedTimeout(self):
"""
Similar to L{test_cancelConnectUNIXTimeout}, but for the case where the
connection attempt fails.
"""
def connect(reactor, cc):
d = cc.connectUNIX("/foo/bar")
address, factory, timeout, bindAddress = reactor.unixClients.pop()
return d, factory
return self._cancelConnectFailedTimeoutTest(connect)
def test_cancelConnectSSLFailedTimeout(self):
"""
Similar to L{test_cancelConnectSSLTimeout}, but for the case where the
connection attempt fails.
"""
def connect(reactor, cc):
d = cc.connectSSL("example.com", 1234, object())
(
host,
port,
factory,
contextFactory,
timeout,
bindADdress,
) = reactor.sslClients.pop()
return d, factory
return self._cancelConnectFailedTimeoutTest(connect)
class ProtocolTests(TestCase):
"""
Tests for L{twisted.internet.protocol.Protocol}.
"""
def test_interfaces(self):
"""
L{Protocol} instances provide L{IProtocol} and L{ILoggingContext}.
"""
proto = Protocol()
self.assertTrue(verifyObject(IProtocol, proto))
self.assertTrue(verifyObject(ILoggingContext, proto))
def test_logPrefix(self):
"""
L{Protocol.logPrefix} returns the protocol class's name.
"""
class SomeThing(Protocol):
pass
self.assertEqual("SomeThing", SomeThing().logPrefix())
def test_makeConnection(self):
"""
L{Protocol.makeConnection} sets the given transport on itself, and
then calls C{connectionMade}.
"""
result = []
class SomeProtocol(Protocol):
def connectionMade(self):
result.append(self.transport)
transport = object()
protocol = SomeProtocol()
protocol.makeConnection(transport)
self.assertEqual(result, [transport])
class FactoryTests(TestCase):
"""
Tests for L{protocol.Factory}.
"""
def test_interfaces(self):
"""
L{Factory} instances provide both L{IProtocolFactory} and
L{ILoggingContext}.
"""
factory = Factory()
self.assertTrue(verifyObject(IProtocolFactory, factory))
self.assertTrue(verifyObject(ILoggingContext, factory))
def test_logPrefix(self):
"""
L{Factory.logPrefix} returns the name of the factory class.
"""
class SomeKindOfFactory(Factory):
pass
self.assertEqual("SomeKindOfFactory", SomeKindOfFactory().logPrefix())
def test_defaultBuildProtocol(self):
"""
L{Factory.buildProtocol} by default constructs a protocol by calling
its C{protocol} attribute, and attaches the factory to the result.
"""
class SomeProtocol(Protocol):
pass
f = Factory()
f.protocol = SomeProtocol
protocol = f.buildProtocol(None)
self.assertIsInstance(protocol, SomeProtocol)
self.assertIs(protocol.factory, f)
def test_forProtocol(self):
"""
L{Factory.forProtocol} constructs a Factory, passing along any
additional arguments, and sets its C{protocol} attribute to the given
Protocol subclass.
"""
class ArgTakingFactory(Factory):
def __init__(self, *args, **kwargs):
self.args, self.kwargs = args, kwargs
factory = ArgTakingFactory.forProtocol(Protocol, 1, 2, foo=12)
self.assertEqual(factory.protocol, Protocol)
self.assertEqual(factory.args, (1, 2))
self.assertEqual(factory.kwargs, {"foo": 12})
def test_doStartLoggingStatement(self):
"""
L{Factory.doStart} logs that it is starting a factory, followed by
the L{repr} of the L{Factory} instance that is being started.
"""
events = []
globalLogPublisher.addObserver(events.append)
self.addCleanup(lambda: globalLogPublisher.removeObserver(events.append))
f = Factory()
f.doStart()
self.assertIs(events[0]["factory"], f)
self.assertEqual(events[0]["log_level"], LogLevel.info)
self.assertEqual(events[0]["log_format"], "Starting factory {factory!r}")
def test_doStopLoggingStatement(self):
"""
L{Factory.doStop} logs that it is stopping a factory, followed by
the L{repr} of the L{Factory} instance that is being stopped.
"""
events = []
globalLogPublisher.addObserver(events.append)
self.addCleanup(lambda: globalLogPublisher.removeObserver(events.append))
class MyFactory(Factory):
numPorts = 1
f = MyFactory()
f.doStop()
self.assertIs(events[0]["factory"], f)
self.assertEqual(events[0]["log_level"], LogLevel.info)
self.assertEqual(events[0]["log_format"], "Stopping factory {factory!r}")
class AdapterTests(TestCase):
"""
Tests for L{ProtocolToConsumerAdapter} and L{ConsumerToProtocolAdapter}.
"""
def test_protocolToConsumer(self):
"""
L{IProtocol} providers can be adapted to L{IConsumer} providers using
L{ProtocolToConsumerAdapter}.
"""
result = []
p = Protocol()
p.dataReceived = result.append
consumer = IConsumer(p)
consumer.write(b"hello")
self.assertEqual(result, [b"hello"])
self.assertIsInstance(consumer, ProtocolToConsumerAdapter)
def test_consumerToProtocol(self):
"""
L{IConsumer} providers can be adapted to L{IProtocol} providers using
L{ProtocolToConsumerAdapter}.
"""
result = []
@implementer(IConsumer)
class Consumer:
def write(self, d):
result.append(d)
c = Consumer()
protocol = IProtocol(c)
protocol.dataReceived(b"hello")
self.assertEqual(result, [b"hello"])
self.assertIsInstance(protocol, ConsumerToProtocolAdapter)
class FileWrapperTests(TestCase):
"""
L{twisted.internet.protocol.FileWrapper}
"""
def test_write(self):
"""
L{twisted.internet.protocol.FileWrapper.write}
"""
wrapper = FileWrapper(BytesIO())
wrapper.write(b"test1")
self.assertEqual(wrapper.file.getvalue(), b"test1")
wrapper = FileWrapper(BytesIO())
# BytesIO() cannot accept unicode, so this will
# cause an exception to be thrown which will be
# handled by FileWrapper.handle_exception().
wrapper.write("stuff")
self.assertNotEqual(wrapper.file.getvalue(), "stuff")
def test_writeSequence(self):
"""
L{twisted.internet.protocol.FileWrapper.writeSequence}
"""
wrapper = FileWrapper(BytesIO())
wrapper.writeSequence([b"test1", b"test2"])
self.assertEqual(wrapper.file.getvalue(), b"test1test2")
wrapper = FileWrapper(BytesIO())
# In Python 3, b"".join([u"a", u"b"]) will raise a TypeError
self.assertRaises(TypeError, wrapper.writeSequence, ["test3", "test4"])

View File

@@ -0,0 +1,61 @@
"""
Tests L{twisted.internet.test.reactormixins}, the reactor-testing support
module.
"""
from hamcrest import assert_that, equal_to, has_length
from typing_extensions import NoReturn
# Trial should expose matches_result publically.
# https://github.com/twisted/twisted/issues/11709
from twisted.trial._dist.test.matchers import matches_result
from twisted.trial.reporter import TestResult
from twisted.trial.runner import TestLoader
from twisted.trial.unittest import SynchronousTestCase, TestSuite
from .reactormixins import ReactorBuilder
UNSUPPORTED = "This reactor is unsupported."
def unsupportedReactor(self: ReactorBuilder) -> NoReturn:
"""
A function that can be used as a factory for L{ReactorBuilder} tests but
which always raises an exception.
This gives the appearance of a reactor type which is unsupported in the
current runtime configuration for some reason.
"""
raise Exception(UNSUPPORTED)
class ReactorBuilderTests(SynchronousTestCase):
"""
Tests for L{ReactorBuilder}.
"""
def test_buildReactorFails(self) -> None:
"""
If the reactor factory raises any exception then
L{ReactorBuilder.buildReactor} raises L{SkipTest}.
"""
class BrokenReactorFactory(ReactorBuilder, SynchronousTestCase):
_reactors = [
"twisted.internet.test.test_reactormixins.unsupportedReactor",
]
def test_brokenFactory(self) -> None:
"""
Try, and fail, to build an unsupported reactor.
"""
self.buildReactor()
cases = BrokenReactorFactory.makeTestCaseClasses().values()
loader = TestLoader()
suite = TestSuite(loader.loadClass(cls) for cls in cases)
result = TestResult()
suite.run(result)
assert_that(result, matches_result(skips=has_length(1)))
[(_, skip)] = result.skips
assert_that(skip, equal_to(UNSUPPORTED))

Some files were not shown because too many files have changed in this diff Show More