3 # Copyright 2007 Google Inc.
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
9 # http://www.apache.org/licenses/LICENSE-2.0
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
17 """Tests for google.appengine.tools.devappserver2.wsgi_server."""
30 from cherrypy
import wsgiserver
33 from google
.appengine
.tools
.devappserver2
import wsgi_server
36 class TestError(Exception):
40 class _SingleAddressWsgiServerTest(unittest
.TestCase
):
42 super(_SingleAddressWsgiServerTest
, self
).setUp()
43 self
.server
= wsgi_server
._SingleAddressWsgiServer
(('localhost', 0),
44 self
.wsgi_application
)
48 super(_SingleAddressWsgiServerTest
, self
).tearDown()
52 result
= urllib2
.urlopen('http://localhost:%d/foo?bar=baz' %
55 environ
= json
.loads(body
)
56 self
.assertEqual(200, result
.code
)
57 self
.assertEqual('/foo', environ
['PATH_INFO'])
58 self
.assertEqual('bar=baz', environ
['QUERY_STRING'])
60 def wsgi_application(self
, environ
, start_response
):
61 start_response('200 OK', [('Content-Type', 'application/json')])
62 serializable_environ
= environ
.copy()
63 del serializable_environ
['wsgi.input']
64 del serializable_environ
['wsgi.errors']
65 return [json
.dumps(serializable_environ
)]
67 def other_wsgi_application(self
, environ
, start_response
):
68 start_response('200 OK', [('Content-Type', 'text/plain')])
69 return ['Hello World']
71 def test_set_app(self
):
72 self
.server
.set_app(self
.other_wsgi_application
)
73 result
= urllib2
.urlopen('http://localhost:%d/foo?bar=baz' %
76 self
.assertEqual(200, result
.code
)
77 self
.assertEqual('Hello World', body
)
79 def test_set_error(self
):
80 self
.server
.set_error(204)
81 result
= urllib2
.urlopen('http://localhost:%d/foo?bar=baz' %
83 self
.assertEqual(204, result
.code
)
86 class SharedCherryPyThreadPoolTest(unittest
.TestCase
):
90 self
.mox
.StubOutWithMock(wsgi_server
._THREAD
_POOL
, 'submit')
91 self
.thread_pool
= wsgi_server
.SharedCherryPyThreadPool()
98 wsgi_server
._THREAD
_POOL
.submit(self
.thread_pool
._handle
, connection
)
100 self
.thread_pool
.put(connection
)
102 self
.assertEqual(set([connection
]), self
.thread_pool
._connections
)
104 def test_handle(self
):
105 connection
= self
.mox
.CreateMock(wsgiserver
.HTTPConnection
)
106 self
.mox
.StubOutWithMock(self
.thread_pool
._condition
, 'notify')
107 self
.thread_pool
._connections
.add(connection
)
108 connection
.communicate()
110 self
.thread_pool
._condition
.notify()
112 self
.thread_pool
._handle
(connection
)
114 self
.assertEqual(set(), self
.thread_pool
._connections
)
116 def test_handle_with_exception(self
):
117 connection
= self
.mox
.CreateMock(wsgiserver
.HTTPConnection
)
118 self
.mox
.StubOutWithMock(self
.thread_pool
._condition
, 'notify')
119 self
.thread_pool
._connections
.add(connection
)
120 connection
.communicate().AndRaise(TestError
)
122 self
.thread_pool
._condition
.notify()
124 self
.assertRaises(TestError
, self
.thread_pool
._handle
, connection
)
126 self
.assertEqual(set(), self
.thread_pool
._connections
)
129 wsgi_server
._THREAD
_POOL
.submit(self
.thread_pool
._stop
, 3)
131 self
.thread_pool
.stop(3)
134 def test__stop_no_connections(self
):
136 self
.thread_pool
._stop
(0.1)
139 def test__stop_with_connections(self
):
140 connection
= self
.mox
.CreateMock(wsgiserver
.HTTPConnection
)
141 self
.thread_pool
._connections
.add(connection
)
142 self
.mox
.StubOutWithMock(self
.thread_pool
, '_shutdown_connection')
143 self
.thread_pool
._shutdown
_connection
(connection
)
146 self
.thread_pool
._stop
(1)
149 def test_shutdown_connection(self
):
151 class DummyObect(object):
154 connection
= DummyObect()
155 connection
.rfile
= DummyObect()
156 connection
.rfile
.closed
= False
157 connection
.socket
= self
.mox
.CreateMockAnything()
158 connection
.socket
.shutdown(socket
.SHUT_RD
)
161 self
.thread_pool
._shutdown
_connection
(connection
)
164 def test_shutdown_connection_rfile_already_close(self
):
166 class DummyObect(object):
169 connection
= DummyObect()
170 connection
.rfile
= DummyObect()
171 connection
.rfile
.closed
= True
172 connection
.socket
= self
.mox
.CreateMockAnything()
175 self
.thread_pool
._shutdown
_connection
(connection
)
179 class SelectThreadTest(unittest
.TestCase
):
181 class _MockSocket(object):
186 self
.select_thread
= wsgi_server
.SelectThread()
187 self
.original_has_poll
= wsgi_server
._HAS
_POLL
189 self
.mox
.StubOutWithMock(select
, 'select')
190 if hasattr(select
, 'poll'):
191 self
.mox
.StubOutWithMock(select
, 'poll')
192 self
.mox
.StubOutWithMock(time
, 'sleep')
195 self
.mox
.UnsetStubs()
196 wsgi_server
._HAS
_POLL
= self
.original_has_poll
198 def test_add_socket(self
):
199 file_descriptors
= self
.select_thread
._file
_descriptors
200 file_descriptor_to_callback
= (
201 self
.select_thread
._file
_descriptor
_to
_callback
)
202 file_descriptors_copy
= frozenset(self
.select_thread
._file
_descriptors
)
203 file_descriptor_to_callback_copy
= (
204 self
.select_thread
._file
_descriptor
_to
_callback
.copy())
205 s
= self
._MockSocket
()
207 self
.select_thread
.add_socket(s
, callback
)
208 self
.assertEqual(file_descriptors_copy
, file_descriptors
)
209 self
.assertEqual(file_descriptor_to_callback_copy
,
210 file_descriptor_to_callback
)
211 self
.assertEqual(frozenset([s
.fileno()]),
212 self
.select_thread
._file
_descriptors
)
213 self
.assertEqual({s
.fileno(): callback
},
214 self
.select_thread
._file
_descriptor
_to
_callback
)
216 def test_remove_socket(self
):
217 s1
= self
._MockSocket
()
219 s2
= self
._MockSocket
()
221 self
.select_thread
._file
_descriptors
= frozenset([s1
.fileno(), s2
.fileno()])
222 self
.select_thread
._file
_descriptor
_to
_callback
= {
223 s1
.fileno(): callback1
, s2
.fileno(): callback2
}
224 file_descriptors
= self
.select_thread
._file
_descriptors
225 file_descriptor_to_callback
= (
226 self
.select_thread
._file
_descriptor
_to
_callback
)
227 file_descriptors_copy
= frozenset(self
.select_thread
._file
_descriptors
)
228 file_descriptor_to_callback_copy
= (
229 self
.select_thread
._file
_descriptor
_to
_callback
.copy())
230 self
.select_thread
.remove_socket(s1
)
231 self
.assertEqual(file_descriptors_copy
, file_descriptors
)
232 self
.assertEqual(file_descriptor_to_callback_copy
,
233 file_descriptor_to_callback
)
234 self
.assertEqual(frozenset([s2
.fileno()]),
235 self
.select_thread
._file
_descriptors
)
236 self
.assertEqual({s2
.fileno(): callback2
},
237 self
.select_thread
._file
_descriptor
_to
_callback
)
239 def test_select_no_sockets(self
):
242 self
.select_thread
._select
()
245 def test_select_no_poll(self
):
246 wsgi_server
._HAS
_POLL
= False
247 s
= self
._MockSocket
()
248 callback
= self
.mox
.CreateMockAnything()
249 select
.select(frozenset([s
.fileno()]), [], [], 1).AndReturn(
250 ([s
.fileno()], [], []))
253 self
.select_thread
.add_socket(s
, callback
)
254 self
.select_thread
._select
()
257 @unittest.skipUnless(wsgi_server
._HAS
_POLL
, 'requires select.poll')
258 def test_select_with_poll(self
):
259 s
= self
._MockSocket
()
260 callback
= self
.mox
.CreateMockAnything()
261 poll
= self
.mox
.CreateMockAnything()
263 select
.poll().AndReturn(poll
)
264 poll
.register(s
.fileno(), select
.POLLIN
)
265 poll
.poll(1).AndReturn([(s
.fileno(), select
.POLLIN
)])
269 self
.select_thread
.add_socket(s
, callback
)
270 self
.select_thread
._select
()
273 def test_select_not_ready_no_poll(self
):
274 wsgi_server
._HAS
_POLL
= False
275 s
= self
._MockSocket
()
276 callback
= self
.mox
.CreateMockAnything()
277 select
.select(frozenset([s
.fileno()]), [], [], 1).AndReturn(([], [], []))
279 self
.select_thread
.add_socket(s
, callback
)
280 self
.select_thread
._select
()
283 @unittest.skipUnless(wsgi_server
._HAS
_POLL
, 'requires select.poll')
284 def test_select_not_ready_with_poll(self
):
285 s
= self
._MockSocket
()
286 callback
= self
.mox
.CreateMockAnything()
287 poll
= self
.mox
.CreateMockAnything()
289 select
.poll().AndReturn(poll
)
290 poll
.register(s
.fileno(), select
.POLLIN
)
291 poll
.poll(1).AndReturn([])
294 self
.select_thread
.add_socket(s
, callback
)
295 self
.select_thread
._select
()
299 class WsgiServerStartupTest(unittest
.TestCase
):
303 self
.server
= wsgi_server
.WsgiServer(('localhost', 123), None)
306 self
.mox
.UnsetStubs()
308 def test_start_some_fail_to_bind(self
):
309 failing_server
= self
.mox
.CreateMock(
310 wsgi_server
._SingleAddressWsgiServer
)
311 starting_server
= self
.mox
.CreateMock(
312 wsgi_server
._SingleAddressWsgiServer
)
313 another_starting_server
= self
.mox
.CreateMock(
314 wsgi_server
._SingleAddressWsgiServer
)
315 self
.mox
.StubOutWithMock(wsgi_server
, '_SingleAddressWsgiServer')
316 self
.mox
.StubOutWithMock(socket
, 'getaddrinfo')
317 socket
.getaddrinfo('localhost', 123, socket
.AF_UNSPEC
, socket
.SOCK_STREAM
,
318 0, socket
.AI_PASSIVE
).AndReturn(
319 [(None, None, None, None, ('foo', 'bar', 'baz')),
320 (None, None, None, None, (1, 2, 3, 4, 5)),
321 (None, None, None, None, (3, 4))])
322 wsgi_server
._SingleAddressWsgiServer
(('foo', 'bar'), None).AndReturn(
324 wsgi_server
._SingleAddressWsgiServer
((1, 2), None).AndReturn(
326 wsgi_server
._SingleAddressWsgiServer
((3, 4), None).AndReturn(
327 another_starting_server
)
328 starting_server
.start()
329 failing_server
.start().AndRaise(wsgi_server
.BindError
)
330 another_starting_server
.start()
335 self
.assertItemsEqual([starting_server
, another_starting_server
],
336 self
.server
._servers
)
338 def test_start_all_fail_to_bind(self
):
339 failing_server
= self
.mox
.CreateMock(
340 wsgi_server
._SingleAddressWsgiServer
)
341 self
.mox
.StubOutWithMock(wsgi_server
, '_SingleAddressWsgiServer')
342 self
.mox
.StubOutWithMock(socket
, 'getaddrinfo')
343 socket
.getaddrinfo('localhost', 123, socket
.AF_UNSPEC
, socket
.SOCK_STREAM
,
344 0, socket
.AI_PASSIVE
).AndReturn(
345 [(None, None, None, None, ('foo', 'bar', 'baz'))])
346 wsgi_server
._SingleAddressWsgiServer
(('foo', 'bar'), None).AndReturn(
348 failing_server
.start().AndRaise(wsgi_server
.BindError
)
351 self
.assertRaises(wsgi_server
.BindError
, self
.server
.start
)
354 def test_remove_duplicates(self
):
355 foo_server
= self
.mox
.CreateMock(wsgi_server
._SingleAddressWsgiServer
)
356 foo2_server
= self
.mox
.CreateMock(wsgi_server
._SingleAddressWsgiServer
)
357 self
.mox
.StubOutWithMock(wsgi_server
, '_SingleAddressWsgiServer')
358 self
.mox
.StubOutWithMock(socket
, 'getaddrinfo')
359 socket
.getaddrinfo('localhost', 123, socket
.AF_UNSPEC
, socket
.SOCK_STREAM
,
360 0, socket
.AI_PASSIVE
).AndReturn(
361 [(0, 0, 0, '', ('127.0.0.1', 123)),
362 (0, 0, 0, '', ('::1', 123, 0, 0)),
363 (0, 0, 0, '', ('127.0.0.1', 123))])
364 wsgi_server
._SingleAddressWsgiServer
(('127.0.0.1', 123), None).AndReturn(
367 wsgi_server
._SingleAddressWsgiServer
(('::1', 123), None).AndReturn(
376 running_server
= self
.mox
.CreateMock(
377 wsgi_server
._SingleAddressWsgiServer
)
378 self
.server
._servers
= [running_server
]
379 running_server
.quit()
385 class WsgiServerPort0StartupTest(unittest
.TestCase
):
389 self
.server
= wsgi_server
.WsgiServer(('localhost', 0), None)
392 self
.mox
.UnsetStubs()
394 def test_basic_behavior(self
):
395 inet4_server
= self
.mox
.CreateMock(wsgi_server
._SingleAddressWsgiServer
)
396 inet6_server
= self
.mox
.CreateMock(wsgi_server
._SingleAddressWsgiServer
)
397 self
.mox
.StubOutWithMock(wsgi_server
, '_SingleAddressWsgiServer')
398 self
.mox
.StubOutWithMock(socket
, 'getaddrinfo')
399 socket
.getaddrinfo('localhost', 0, socket
.AF_UNSPEC
, socket
.SOCK_STREAM
, 0,
400 socket
.AI_PASSIVE
).AndReturn(
401 [(None, None, None, None, ('127.0.0.1', 0, 'baz')),
402 (None, None, None, None, ('::1', 0, 'baz'))])
403 wsgi_server
._SingleAddressWsgiServer
(('127.0.0.1', 0), None).AndReturn(
406 inet4_server
.port
= 123
407 wsgi_server
._SingleAddressWsgiServer
(('::1', 123), None).AndReturn(
413 self
.assertItemsEqual([inet4_server
, inet6_server
],
414 self
.server
._servers
)
416 def test_retry_eaddrinuse(self
):
417 inet4_server
= self
.mox
.CreateMock(wsgi_server
._SingleAddressWsgiServer
)
418 inet6_server
= self
.mox
.CreateMock(wsgi_server
._SingleAddressWsgiServer
)
419 inet4_server_retry
= self
.mox
.CreateMock(
420 wsgi_server
._SingleAddressWsgiServer
)
421 inet6_server_retry
= self
.mox
.CreateMock(
422 wsgi_server
._SingleAddressWsgiServer
)
423 self
.mox
.StubOutWithMock(wsgi_server
, '_SingleAddressWsgiServer')
424 self
.mox
.StubOutWithMock(socket
, 'getaddrinfo')
425 socket
.getaddrinfo('localhost', 0, socket
.AF_UNSPEC
, socket
.SOCK_STREAM
, 0,
426 socket
.AI_PASSIVE
).AndReturn(
427 [(None, None, None, None, ('127.0.0.1', 0, 'baz')),
428 (None, None, None, None, ('::1', 0, 'baz'))])
430 wsgi_server
._SingleAddressWsgiServer
(('127.0.0.1', 0), None).AndReturn(
433 inet4_server
.port
= 123
434 wsgi_server
._SingleAddressWsgiServer
(('::1', 123), None).AndReturn(
436 inet6_server
.start().AndRaise(
437 wsgi_server
.BindError('message', (errno
.EADDRINUSE
, 'in use')))
440 wsgi_server
._SingleAddressWsgiServer
(('127.0.0.1', 0), None).AndReturn(
442 inet4_server_retry
.start()
443 inet4_server_retry
.port
= 456
444 wsgi_server
._SingleAddressWsgiServer
(('::1', 456), None).AndReturn(
446 inet6_server_retry
.start()
450 self
.assertItemsEqual([inet4_server_retry
, inet6_server_retry
],
451 self
.server
._servers
)
453 def test_retry_limited(self
):
454 inet4_servers
= [self
.mox
.CreateMock(wsgi_server
._SingleAddressWsgiServer
)
455 for _
in range(wsgi_server
._PORT
_0_RETRIES
)]
456 inet6_servers
= [self
.mox
.CreateMock(wsgi_server
._SingleAddressWsgiServer
)
457 for _
in range(wsgi_server
._PORT
_0_RETRIES
)]
458 self
.mox
.StubOutWithMock(wsgi_server
, '_SingleAddressWsgiServer')
459 self
.mox
.StubOutWithMock(socket
, 'getaddrinfo')
460 socket
.getaddrinfo('localhost', 0, socket
.AF_UNSPEC
, socket
.SOCK_STREAM
, 0,
461 socket
.AI_PASSIVE
).AndReturn(
462 [(None, None, None, None, ('127.0.0.1', 0, 'baz')),
463 (None, None, None, None, ('::1', 0, 'baz'))])
464 for offset
, (inet4_server
, inet6_server
) in enumerate(zip(
465 inet4_servers
, inet6_servers
)):
466 wsgi_server
._SingleAddressWsgiServer
(('127.0.0.1', 0), None).AndReturn(
469 inet4_server
.port
= offset
+ 1
470 wsgi_server
._SingleAddressWsgiServer
(('::1', offset
+ 1), None).AndReturn(
472 inet6_server
.start().AndRaise(
473 wsgi_server
.BindError('message', (errno
.EADDRINUSE
, 'in use')))
476 self
.assertRaises(wsgi_server
.BindError
, self
.server
.start
)
479 def test_ignore_other_errors(self
):
480 inet4_server
= self
.mox
.CreateMock(wsgi_server
._SingleAddressWsgiServer
)
481 inet6_server
= self
.mox
.CreateMock(wsgi_server
._SingleAddressWsgiServer
)
482 self
.mox
.StubOutWithMock(wsgi_server
, '_SingleAddressWsgiServer')
483 self
.mox
.StubOutWithMock(socket
, 'getaddrinfo')
484 socket
.getaddrinfo('localhost', 0, socket
.AF_UNSPEC
, socket
.SOCK_STREAM
, 0,
485 socket
.AI_PASSIVE
).AndReturn(
486 [(None, None, None, None, ('127.0.0.1', 0, 'baz')),
487 (None, None, None, None, ('::1', 0, 'baz'))])
488 wsgi_server
._SingleAddressWsgiServer
(('127.0.0.1', 0), None).AndReturn(
491 inet4_server
.port
= 123
492 wsgi_server
._SingleAddressWsgiServer
(('::1', 123), None).AndReturn(
494 inet6_server
.start().AndRaise(
495 wsgi_server
.BindError('message', (errno
.ENOPROTOOPT
, 'no protocol')))
499 self
.assertItemsEqual([inet4_server
],
500 self
.server
._servers
)
503 class _SingleAddressWsgiServerStartupTest(unittest
.TestCase
):
507 self
.server
= wsgi_server
._SingleAddressWsgiServer
(('localhost', 0), None)
510 self
.mox
.UnsetStubs()
512 def test_start_port_in_use(self
):
513 self
.mox
.StubOutWithMock(socket
, 'getaddrinfo')
514 self
.mox
.StubOutWithMock(self
.server
, 'bind')
518 socket
.getaddrinfo('localhost', 0, socket
.AF_UNSPEC
, socket
.SOCK_STREAM
, 0,
519 socket
.AI_PASSIVE
).AndReturn(
520 [(af
, socktype
, proto
, None, None)])
521 self
.server
.bind(af
, socktype
, proto
).AndRaise(socket
.error
)
523 self
.assertRaises(wsgi_server
.BindError
, self
.server
.start
)
526 def test_start(self
):
527 # Ensure no CherryPy thread pools are started.
528 self
.mox
.StubOutWithMock(wsgiserver
.ThreadPool
, 'start')
529 self
.mox
.StubOutWithMock(wsgi_server
._SELECT
_THREAD
, 'add_socket')
530 wsgi_server
._SELECT
_THREAD
.add_socket(mox
.IsA(socket
.socket
),
537 self
.mox
.StubOutWithMock(wsgi_server
._SELECT
_THREAD
, 'remove_socket')
538 self
.server
.socket
= object()
539 self
.server
.requests
= self
.mox
.CreateMock(
540 wsgi_server
.SharedCherryPyThreadPool
)
541 wsgi_server
._SELECT
_THREAD
.remove_socket(self
.server
.socket
)
542 self
.server
.requests
.stop(timeout
=1)
547 if __name__
== '__main__':