Initial import to Tizen
[profile/ivi/python-twisted.git] / twisted / test / test_tcp_internals.py
1 # Copyright (c) Twisted Matrix Laboratories.
2 # See LICENSE for details.
3
4 """
5 Whitebox tests for TCP APIs.
6 """
7
8 import errno, socket, os
9
10 try:
11     import resource
12 except ImportError:
13     resource = None
14
15 from twisted.trial.unittest import TestCase
16
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
23
24
25 class PlatformAssumptionsTestCase(TestCase):
26     """
27     Test assumptions about platform behaviors.
28     """
29     socketLimit = 8192
30
31     def setUp(self):
32         self.openSockets = []
33         if resource is not None:
34             # On some buggy platforms we might leak FDs, and the test will
35             # fail creating the initial two sockets we *do* want to
36             # succeed. So, we make the soft limit the current number of fds
37             # plus two more (for the two sockets we want to succeed). If we've
38             # leaked too many fds for that to work, there's nothing we can
39             # do.
40             from twisted.internet.process import _listOpenFDs
41             newLimit = len(_listOpenFDs()) + 2
42             self.originalFileLimit = resource.getrlimit(resource.RLIMIT_NOFILE)
43             resource.setrlimit(resource.RLIMIT_NOFILE, (newLimit, self.originalFileLimit[1]))
44             self.socketLimit = newLimit + 100
45
46
47     def tearDown(self):
48         while self.openSockets:
49             self.openSockets.pop().close()
50         if resource is not None:
51             # OS X implicitly lowers the hard limit in the setrlimit call
52             # above.  Retrieve the new hard limit to pass in to this
53             # setrlimit call, so that it doesn't give us a permission denied
54             # error.
55             currentHardLimit = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
56             newSoftLimit = min(self.originalFileLimit[0], currentHardLimit)
57             resource.setrlimit(resource.RLIMIT_NOFILE, (newSoftLimit, currentHardLimit))
58
59
60     def socket(self):
61         """
62         Create and return a new socket object, also tracking it so it can be
63         closed in the test tear down.
64         """
65         s = socket.socket()
66         self.openSockets.append(s)
67         return s
68
69
70     def test_acceptOutOfFiles(self):
71         """
72         Test that the platform accept(2) call fails with either L{EMFILE} or
73         L{ENOBUFS} when there are too many file descriptors open.
74         """
75         # Make a server to which to connect
76         port = self.socket()
77         port.bind(('127.0.0.1', 0))
78         serverPortNumber = port.getsockname()[1]
79         port.listen(5)
80
81         # Make a client to use to connect to the server
82         client = self.socket()
83         client.setblocking(False)
84
85         # Use up all the rest of the file descriptors.
86         for i in xrange(self.socketLimit):
87             try:
88                 self.socket()
89             except socket.error, e:
90                 if e.args[0] in (EMFILE, ENOBUFS):
91                     # The desired state has been achieved.
92                     break
93                 else:
94                     # Some unexpected error occurred.
95                     raise
96         else:
97             self.fail("Could provoke neither EMFILE nor ENOBUFS from platform.")
98
99         # Non-blocking connect is supposed to fail, but this is not true
100         # everywhere (e.g. freeBSD)
101         self.assertIn(client.connect_ex(('127.0.0.1', serverPortNumber)),
102                       (0, EINPROGRESS))
103
104         # Make sure that the accept call fails in the way we expect.
105         exc = self.assertRaises(socket.error, port.accept)
106         self.assertIn(exc.args[0], (EMFILE, ENOBUFS))
107     if platform.getType() == "win32":
108         test_acceptOutOfFiles.skip = (
109             "Windows requires an unacceptably large amount of resources to "
110             "provoke this behavior in the naive manner.")
111
112
113
114 class SelectReactorTestCase(TestCase):
115     """
116     Tests for select-specific failure conditions.
117     """
118
119     def setUp(self):
120         self.ports = []
121         self.messages = []
122         log.addObserver(self.messages.append)
123
124
125     def tearDown(self):
126         log.removeObserver(self.messages.append)
127         return gatherResults([
128             maybeDeferred(p.stopListening)
129             for p in self.ports])
130
131
132     def port(self, portNumber, factory, interface):
133         """
134         Create, start, and return a new L{Port}, also tracking it so it can
135         be stopped in the test tear down.
136         """
137         p = Port(portNumber, factory, interface=interface)
138         p.startListening()
139         self.ports.append(p)
140         return p
141
142
143     def _acceptFailureTest(self, socketErrorNumber):
144         """
145         Test behavior in the face of an exception from C{accept(2)}.
146
147         On any exception which indicates the platform is unable or unwilling
148         to allocate further resources to us, the existing port should remain
149         listening, a message should be logged, and the exception should not
150         propagate outward from doRead.
151
152         @param socketErrorNumber: The errno to simulate from accept.
153         """
154         class FakeSocket(object):
155             """
156             Pretend to be a socket in an overloaded system.
157             """
158             def accept(self):
159                 raise socket.error(
160                     socketErrorNumber, os.strerror(socketErrorNumber))
161
162         factory = ServerFactory()
163         port = self.port(0, factory, interface='127.0.0.1')
164         originalSocket = port.socket
165         try:
166             port.socket = FakeSocket()
167
168             port.doRead()
169
170             expectedFormat = "Could not accept new connection (%s)"
171             expectedErrorCode = errno.errorcode[socketErrorNumber]
172             expectedMessage = expectedFormat % (expectedErrorCode,)
173             for msg in self.messages:
174                 if msg.get('message') == (expectedMessage,):
175                     break
176             else:
177                 self.fail("Log event for failed accept not found in "
178                           "%r" % (self.messages,))
179         finally:
180             port.socket = originalSocket
181
182
183     def test_tooManyFilesFromAccept(self):
184         """
185         C{accept(2)} can fail with C{EMFILE} when there are too many open file
186         descriptors in the process.  Test that this doesn't negatively impact
187         any other existing connections.
188
189         C{EMFILE} mainly occurs on Linux when the open file rlimit is
190         encountered.
191         """
192         return self._acceptFailureTest(EMFILE)
193
194
195     def test_noBufferSpaceFromAccept(self):
196         """
197         Similar to L{test_tooManyFilesFromAccept}, but test the case where
198         C{accept(2)} fails with C{ENOBUFS}.
199
200         This mainly occurs on Windows and FreeBSD, but may be possible on
201         Linux and other platforms as well.
202         """
203         return self._acceptFailureTest(ENOBUFS)
204
205
206     def test_connectionAbortedFromAccept(self):
207         """
208         Similar to L{test_tooManyFilesFromAccept}, but test the case where
209         C{accept(2)} fails with C{ECONNABORTED}.
210
211         It is not clear whether this is actually possible for TCP
212         connections on modern versions of Linux.
213         """
214         return self._acceptFailureTest(ECONNABORTED)
215
216
217     def test_noFilesFromAccept(self):
218         """
219         Similar to L{test_tooManyFilesFromAccept}, but test the case where
220         C{accept(2)} fails with C{ENFILE}.
221
222         This can occur on Linux when the system has exhausted (!) its supply
223         of inodes.
224         """
225         return self._acceptFailureTest(ENFILE)
226     if platform.getType() == 'win32':
227         test_noFilesFromAccept.skip = "Windows accept(2) cannot generate ENFILE"
228
229
230     def test_noMemoryFromAccept(self):
231         """
232         Similar to L{test_tooManyFilesFromAccept}, but test the case where
233         C{accept(2)} fails with C{ENOMEM}.
234
235         On Linux at least, this can sensibly occur, even in a Python program
236         (which eats memory like no ones business), when memory has become
237         fragmented or low memory has been filled (d_alloc calls
238         kmem_cache_alloc calls kmalloc - kmalloc only allocates out of low
239         memory).
240         """
241         return self._acceptFailureTest(ENOMEM)
242     if platform.getType() == 'win32':
243         test_noMemoryFromAccept.skip = "Windows accept(2) cannot generate ENOMEM"
244
245 if not interfaces.IReactorFDSet.providedBy(reactor):
246     skipMsg = 'This test only applies to reactors that implement IReactorFDset'
247     PlatformAssumptionsTestCase.skip = skipMsg
248     SelectReactorTestCase.skip = skipMsg
249