1
# Copyright (c) 2006 Twisted Matrix Laboratories.
2
# See LICENSE for details.
5
Whitebox tests for TCP APIs.
8
import errno, socket, os
15
from twisted.trial.unittest import TestCase
17
from twisted.python import log
18
from twisted.internet.tcp import ECONNABORTED, ENOMEM, ENFILE, EMFILE, ENOBUFS, EINPROGRESS, Port
19
from twisted.internet.protocol import ServerFactory
20
from twisted.python.runtime import platform
21
from twisted.internet.defer import maybeDeferred, gatherResults
22
from twisted.internet import reactor, interfaces
25
class PlatformAssumptionsTestCase(TestCase):
27
Test assumptions about platform behaviors.
33
if resource is not None:
34
self.originalFileLimit = resource.getrlimit(resource.RLIMIT_NOFILE)
35
resource.setrlimit(resource.RLIMIT_NOFILE, (128, self.originalFileLimit[1]))
36
self.socketLimit = 256
40
while self.openSockets:
41
self.openSockets.pop().close()
42
if resource is not None:
43
# OS X implicitly lowers the hard limit in the setrlimit call
44
# above. Retrieve the new hard limit to pass in to this
45
# setrlimit call, so that it doesn't give us a permission denied
47
currentHardLimit = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
48
newSoftLimit = min(self.originalFileLimit[0], currentHardLimit)
49
resource.setrlimit(resource.RLIMIT_NOFILE, (newSoftLimit, currentHardLimit))
54
Create and return a new socket object, also tracking it so it can be
55
closed in the test tear down.
58
self.openSockets.append(s)
62
def test_acceptOutOfFiles(self):
64
Test that the platform accept(2) call fails with either L{EMFILE} or
65
L{ENOBUFS} when there are too many file descriptors open.
67
# Make a server to which to connect
69
port.bind(('127.0.0.1', 0))
70
serverPortNumber = port.getsockname()[1]
73
# Use up all the file descriptors
74
for i in xrange(self.socketLimit):
77
except socket.error, e:
78
if e.args[0] in (EMFILE, ENOBUFS):
79
self.openSockets.pop().close()
84
self.fail("Could provoke neither EMFILE nor ENOBUFS from platform.")
86
# Make a client to use to connect to the server
87
client = self.socket()
88
client.setblocking(False)
90
# Non-blocking connect is supposed to fail, but this is not true
91
# everywhere (e.g. freeBSD)
92
self.assertIn(client.connect_ex(('127.0.0.1', serverPortNumber)),
95
# Make sure that the accept call fails in the way we expect.
96
exc = self.assertRaises(socket.error, port.accept)
97
self.assertIn(exc.args[0], (EMFILE, ENOBUFS))
98
if platform.getType() == "win32":
99
test_acceptOutOfFiles.skip = (
100
"Windows requires an unacceptably large amount of resources to "
101
"provoke this behavior in the naive manner.")
105
class SelectReactorTestCase(TestCase):
107
Tests for select-specific failure conditions.
113
log.addObserver(self.messages.append)
117
log.removeObserver(self.messages.append)
118
return gatherResults([
119
maybeDeferred(p.stopListening)
120
for p in self.ports])
123
def port(self, portNumber, factory, interface):
125
Create, start, and return a new L{Port}, also tracking it so it can
126
be stopped in the test tear down.
128
p = Port(portNumber, factory, interface=interface)
134
def _acceptFailureTest(self, socketErrorNumber):
136
Test behavior in the face of an exception from C{accept(2)}.
138
On any exception which indicates the platform is unable or unwilling
139
to allocate further resources to us, the existing port should remain
140
listening, a message should be logged, and the exception should not
141
propagate outward from doRead.
143
@param socketErrorNumber: The errno to simulate from accept.
145
class FakeSocket(object):
147
Pretend to be a socket in an overloaded system.
151
socketErrorNumber, os.strerror(socketErrorNumber))
153
factory = ServerFactory()
154
port = self.port(0, factory, interface='127.0.0.1')
155
originalSocket = port.socket
157
port.socket = FakeSocket()
161
expectedFormat = "Could not accept new connection (%s)"
162
expectedErrorCode = errno.errorcode[socketErrorNumber]
163
expectedMessage = expectedFormat % (expectedErrorCode,)
164
for msg in self.messages:
165
if msg.get('message') == (expectedMessage,):
168
self.fail("Log event for failed accept not found in "
169
"%r" % (self.messages,))
171
port.socket = originalSocket
174
def test_tooManyFilesFromAccept(self):
176
C{accept(2)} can fail with C{EMFILE} when there are too many open file
177
descriptors in the process. Test that this doesn't negatively impact
178
any other existing connections.
180
C{EMFILE} mainly occurs on Linux when the open file rlimit is
183
return self._acceptFailureTest(EMFILE)
186
def test_noBufferSpaceFromAccept(self):
188
Similar to L{test_tooManyFilesFromAccept}, but test the case where
189
C{accept(2)} fails with C{ENOBUFS}.
191
This mainly occurs on Windows and FreeBSD, but may be possible on
192
Linux and other platforms as well.
194
return self._acceptFailureTest(ENOBUFS)
197
def test_connectionAbortedFromAccept(self):
199
Similar to L{test_tooManyFilesFromAccept}, but test the case where
200
C{accept(2)} fails with C{ECONNABORTED}.
202
It is not clear whether this is actually possible for TCP
203
connections on modern versions of Linux.
205
return self._acceptFailureTest(ECONNABORTED)
208
def test_noFilesFromAccept(self):
210
Similar to L{test_tooManyFilesFromAccept}, but test the case where
211
C{accept(2)} fails with C{ENFILE}.
213
This can occur on Linux when the system has exhausted (!) its supply
216
return self._acceptFailureTest(ENFILE)
217
if platform.getType() == 'win32':
218
test_noFilesFromAccept.skip = "Windows accept(2) cannot generate ENFILE"
221
def test_noMemoryFromAccept(self):
223
Similar to L{test_tooManyFilesFromAccept}, but test the case where
224
C{accept(2)} fails with C{ENOMEM}.
226
On Linux at least, this can sensibly occur, even in a Python program
227
(which eats memory like no ones business), when memory has become
228
fragmented or low memory has been filled (d_alloc calls
229
kmem_cache_alloc calls kmalloc - kmalloc only allocates out of low
232
return self._acceptFailureTest(ENOMEM)
233
if platform.getType() == 'win32':
234
test_noMemoryFromAccept.skip = "Windows accept(2) cannot generate ENOMEM"
236
if not interfaces.IReactorFDSet.providedBy(reactor):
237
skipMsg = 'This test only applies to reactors that implement IReactorFDset'
238
PlatformAssumptionsTestCase.skip = skipMsg
239
SelectReactorTestCase.skip = skipMsg