App Engine Python SDK version 1.8.1
[gae.git] / python / google / appengine / tools / devappserver2 / wsgi_server_test.py
blob02530dd96bb21483922cd9f3a0ce0b5635a700aa
1 #!/usr/bin/env python
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."""
20 import errno
21 import json
22 import select
23 import socket
24 import time
25 import unittest
26 import urllib2
28 import google
30 from cherrypy import wsgiserver
31 import mox
33 from google.appengine.tools.devappserver2 import wsgi_server
36 class TestError(Exception):
37 pass
40 class _SingleAddressWsgiServerTest(unittest.TestCase):
41 def setUp(self):
42 super(_SingleAddressWsgiServerTest, self).setUp()
43 self.server = wsgi_server._SingleAddressWsgiServer(('localhost', 0),
44 self.wsgi_application)
45 self.server.start()
47 def tearDown(self):
48 super(_SingleAddressWsgiServerTest, self).tearDown()
49 self.server.quit()
51 def test_serve(self):
52 result = urllib2.urlopen('http://localhost:%d/foo?bar=baz' %
53 self.server.port)
54 body = result.read()
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' %
74 self.server.port)
75 body = result.read()
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' %
82 self.server.port)
83 self.assertEqual(204, result.code)
86 class SharedCherryPyThreadPoolTest(unittest.TestCase):
88 def setUp(self):
89 self.mox = mox.Mox()
90 self.mox.StubOutWithMock(wsgi_server._THREAD_POOL, 'submit')
91 self.thread_pool = wsgi_server.SharedCherryPyThreadPool()
93 def tearDown(self):
94 self.mox.UnsetStubs()
96 def test_put(self):
97 connection = object()
98 wsgi_server._THREAD_POOL.submit(self.thread_pool._handle, connection)
99 self.mox.ReplayAll()
100 self.thread_pool.put(connection)
101 self.mox.VerifyAll()
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()
109 connection.close()
110 self.thread_pool._condition.notify()
111 self.mox.ReplayAll()
112 self.thread_pool._handle(connection)
113 self.mox.VerifyAll()
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)
121 connection.close()
122 self.thread_pool._condition.notify()
123 self.mox.ReplayAll()
124 self.assertRaises(TestError, self.thread_pool._handle, connection)
125 self.mox.VerifyAll()
126 self.assertEqual(set(), self.thread_pool._connections)
128 def test_stop(self):
129 wsgi_server._THREAD_POOL.submit(self.thread_pool._stop, 3)
130 self.mox.ReplayAll()
131 self.thread_pool.stop(3)
132 self.mox.VerifyAll()
134 def test__stop_no_connections(self):
135 self.mox.ReplayAll()
136 self.thread_pool._stop(0.1)
137 self.mox.VerifyAll()
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)
145 self.mox.ReplayAll()
146 self.thread_pool._stop(1)
147 self.mox.VerifyAll()
149 def test_shutdown_connection(self):
151 class DummyObect(object):
152 pass
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)
160 self.mox.ReplayAll()
161 self.thread_pool._shutdown_connection(connection)
162 self.mox.VerifyAll()
164 def test_shutdown_connection_rfile_already_close(self):
166 class DummyObect(object):
167 pass
169 connection = DummyObect()
170 connection.rfile = DummyObect()
171 connection.rfile.closed = True
172 connection.socket = self.mox.CreateMockAnything()
174 self.mox.ReplayAll()
175 self.thread_pool._shutdown_connection(connection)
176 self.mox.VerifyAll()
179 class SelectThreadTest(unittest.TestCase):
181 class _MockSocket(object):
182 def fileno(self):
183 return id(self)
185 def setUp(self):
186 self.select_thread = wsgi_server.SelectThread()
187 self.original_has_poll = wsgi_server._HAS_POLL
188 self.mox = mox.Mox()
189 self.mox.StubOutWithMock(select, 'select')
190 if hasattr(select, 'poll'):
191 self.mox.StubOutWithMock(select, 'poll')
192 self.mox.StubOutWithMock(time, 'sleep')
194 def tearDown(self):
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()
206 callback = object()
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()
218 callback1 = object()
219 s2 = self._MockSocket()
220 callback2 = object()
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):
240 time.sleep(1)
241 self.mox.ReplayAll()
242 self.select_thread._select()
243 self.mox.VerifyAll()
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()], [], []))
251 callback()
252 self.mox.ReplayAll()
253 self.select_thread.add_socket(s, callback)
254 self.select_thread._select()
255 self.mox.VerifyAll()
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)])
267 callback()
268 self.mox.ReplayAll()
269 self.select_thread.add_socket(s, callback)
270 self.select_thread._select()
271 self.mox.VerifyAll()
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(([], [], []))
278 self.mox.ReplayAll()
279 self.select_thread.add_socket(s, callback)
280 self.select_thread._select()
281 self.mox.VerifyAll()
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([])
293 self.mox.ReplayAll()
294 self.select_thread.add_socket(s, callback)
295 self.select_thread._select()
296 self.mox.VerifyAll()
299 class WsgiServerStartupTest(unittest.TestCase):
301 def setUp(self):
302 self.mox = mox.Mox()
303 self.server = wsgi_server.WsgiServer(('localhost', 123), None)
305 def tearDown(self):
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(
323 failing_server)
324 wsgi_server._SingleAddressWsgiServer((1, 2), None).AndReturn(
325 starting_server)
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()
332 self.mox.ReplayAll()
333 self.server.start()
334 self.mox.VerifyAll()
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(
347 failing_server)
348 failing_server.start().AndRaise(wsgi_server.BindError)
350 self.mox.ReplayAll()
351 self.assertRaises(wsgi_server.BindError, self.server.start)
352 self.mox.VerifyAll()
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(
365 foo_server)
366 foo_server.start()
367 wsgi_server._SingleAddressWsgiServer(('::1', 123), None).AndReturn(
368 foo2_server)
369 foo2_server.start()
371 self.mox.ReplayAll()
372 self.server.start()
373 self.mox.VerifyAll()
375 def test_quit(self):
376 running_server = self.mox.CreateMock(
377 wsgi_server._SingleAddressWsgiServer)
378 self.server._servers = [running_server]
379 running_server.quit()
380 self.mox.ReplayAll()
381 self.server.quit()
382 self.mox.VerifyAll()
385 class WsgiServerPort0StartupTest(unittest.TestCase):
387 def setUp(self):
388 self.mox = mox.Mox()
389 self.server = wsgi_server.WsgiServer(('localhost', 0), None)
391 def tearDown(self):
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(
404 inet4_server)
405 inet4_server.start()
406 inet4_server.port = 123
407 wsgi_server._SingleAddressWsgiServer(('::1', 123), None).AndReturn(
408 inet6_server)
409 inet6_server.start()
410 self.mox.ReplayAll()
411 self.server.start()
412 self.mox.VerifyAll()
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'))])
429 # First try
430 wsgi_server._SingleAddressWsgiServer(('127.0.0.1', 0), None).AndReturn(
431 inet4_server)
432 inet4_server.start()
433 inet4_server.port = 123
434 wsgi_server._SingleAddressWsgiServer(('::1', 123), None).AndReturn(
435 inet6_server)
436 inet6_server.start().AndRaise(
437 wsgi_server.BindError('message', (errno.EADDRINUSE, 'in use')))
438 inet4_server.quit()
439 # Retry
440 wsgi_server._SingleAddressWsgiServer(('127.0.0.1', 0), None).AndReturn(
441 inet4_server_retry)
442 inet4_server_retry.start()
443 inet4_server_retry.port = 456
444 wsgi_server._SingleAddressWsgiServer(('::1', 456), None).AndReturn(
445 inet6_server_retry)
446 inet6_server_retry.start()
447 self.mox.ReplayAll()
448 self.server.start()
449 self.mox.VerifyAll()
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(
467 inet4_server)
468 inet4_server.start()
469 inet4_server.port = offset + 1
470 wsgi_server._SingleAddressWsgiServer(('::1', offset + 1), None).AndReturn(
471 inet6_server)
472 inet6_server.start().AndRaise(
473 wsgi_server.BindError('message', (errno.EADDRINUSE, 'in use')))
474 inet4_server.quit()
475 self.mox.ReplayAll()
476 self.assertRaises(wsgi_server.BindError, self.server.start)
477 self.mox.VerifyAll()
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(
489 inet4_server)
490 inet4_server.start()
491 inet4_server.port = 123
492 wsgi_server._SingleAddressWsgiServer(('::1', 123), None).AndReturn(
493 inet6_server)
494 inet6_server.start().AndRaise(
495 wsgi_server.BindError('message', (errno.ENOPROTOOPT, 'no protocol')))
496 self.mox.ReplayAll()
497 self.server.start()
498 self.mox.VerifyAll()
499 self.assertItemsEqual([inet4_server],
500 self.server._servers)
503 class _SingleAddressWsgiServerStartupTest(unittest.TestCase):
505 def setUp(self):
506 self.mox = mox.Mox()
507 self.server = wsgi_server._SingleAddressWsgiServer(('localhost', 0), None)
509 def tearDown(self):
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')
515 af = object()
516 socktype = object()
517 proto = object()
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)
522 self.mox.ReplayAll()
523 self.assertRaises(wsgi_server.BindError, self.server.start)
524 self.mox.VerifyAll()
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),
531 self.server.tick)
532 self.mox.ReplayAll()
533 self.server.start()
534 self.mox.VerifyAll()
536 def test_quit(self):
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)
543 self.mox.ReplayAll()
544 self.server.quit()
545 self.mox.VerifyAll()
547 if __name__ == '__main__':
548 unittest.main()