PageRenderTime 44ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 1ms

/google/appengine/tools/devappserver2/http_runtime_test.py

https://github.com/theosp/google_appengine
Python | 378 lines | 334 code | 23 blank | 21 comment | 7 complexity | 205d7e18b29cf124e4ddeda261804b6d MD5 | raw file
  1. #!/usr/bin/env python
  2. #
  3. # Copyright 2007 Google Inc.
  4. #
  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
  8. #
  9. # http://www.apache.org/licenses/LICENSE-2.0
  10. #
  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.
  16. #
  17. """Tests for google.appengine.tools.devappserver2.http_runtime."""
  18. import base64
  19. import os
  20. import re
  21. import shutil
  22. import subprocess
  23. import tempfile
  24. import time
  25. import unittest
  26. import google
  27. import mox
  28. from google.appengine.api import appinfo
  29. from google.appengine.tools.devappserver2 import http_proxy
  30. from google.appengine.tools.devappserver2 import http_runtime
  31. from google.appengine.tools.devappserver2 import instance
  32. from google.appengine.tools.devappserver2 import login
  33. from google.appengine.tools.devappserver2 import runtime_config_pb2
  34. from google.appengine.tools.devappserver2 import safe_subprocess
  35. from google.appengine.tools.devappserver2 import wsgi_test_utils
  36. class MockMessage(object):
  37. def __init__(self, headers):
  38. self.headers = headers
  39. def __iter__(self):
  40. return iter(set(name for name, _ in self.headers))
  41. def getheaders(self, name):
  42. return [value for header_name, value in self.headers if header_name == name]
  43. class FakeHttpResponse(object):
  44. def __init__(self, status, reason, headers, body):
  45. self.body = body
  46. self.has_read = False
  47. self.partial_read_error = None
  48. self.status = status
  49. self.reason = reason
  50. self.headers = headers
  51. self.msg = MockMessage(headers)
  52. def read(self, amt=None):
  53. if not self.has_read:
  54. self.has_read = True
  55. return self.body
  56. elif self.partial_read_error:
  57. raise self.partial_read_error
  58. else:
  59. return ''
  60. def getheaders(self):
  61. return self.headers
  62. # We use a fake Tee to avoid the complexity of a real Tee's thread racing with
  63. # the mocking framework and possibly surviving (and calling stderr.readline())
  64. # after a test case completes.
  65. class FakeTee(object):
  66. def __init__(self, buf):
  67. self.buf = buf
  68. def get_buf(self):
  69. return self.buf
  70. def join(self, unused_timeout):
  71. pass
  72. class ModuleConfigurationStub(object):
  73. def __init__(self, application_root='/tmp', error_handlers=None):
  74. self.application_root = application_root
  75. self.error_handlers = error_handlers
  76. class HttpRuntimeProxyTest(wsgi_test_utils.WSGITestCase):
  77. def setUp(self):
  78. self.mox = mox.Mox()
  79. self.tmpdir = tempfile.mkdtemp()
  80. module_configuration = ModuleConfigurationStub(
  81. application_root=self.tmpdir,
  82. error_handlers=[
  83. appinfo.ErrorHandlers(error_code='over_quota', file='foo.html'),
  84. appinfo.ErrorHandlers(error_code='default', file='error.html'),
  85. ])
  86. self.runtime_config = runtime_config_pb2.Config()
  87. self.runtime_config.app_id = 'app'
  88. self.runtime_config.version_id = 'version'
  89. self.runtime_config.api_port = 12345
  90. self.runtime_config.application_root = self.tmpdir
  91. self.runtime_config.datacenter = 'us1'
  92. self.runtime_config.instance_id = 'abc3dzac4'
  93. self.runtime_config.auth_domain = 'gmail.com'
  94. self.runtime_config_getter = lambda: self.runtime_config
  95. self.proxy = http_runtime.HttpRuntimeProxy(
  96. ['/runtime'], self.runtime_config_getter, module_configuration,
  97. env={'foo': 'bar'})
  98. self.proxy._port = 23456
  99. self.process = self.mox.CreateMock(subprocess.Popen)
  100. self.process.stdin = self.mox.CreateMockAnything()
  101. self.process.stdout = self.mox.CreateMockAnything()
  102. self.process.stderr = self.mox.CreateMockAnything()
  103. self.mox.StubOutWithMock(safe_subprocess, 'start_process')
  104. self.mox.StubOutWithMock(login, 'get_user_info')
  105. self.url_map = appinfo.URLMap(url=r'/(get|post).*',
  106. script=r'\1.py')
  107. self.mox.StubOutWithMock(http_proxy.HttpProxy, 'wait_for_connection')
  108. http_proxy.HttpProxy.wait_for_connection()
  109. self._saved_quit_with_sigterm = None
  110. def tearDown(self):
  111. shutil.rmtree(self.tmpdir)
  112. self.mox.UnsetStubs()
  113. if self._saved_quit_with_sigterm is not None:
  114. http_runtime.HttpRuntimeProxy.stop_runtimes_with_sigterm(
  115. self._saved_quit_with_sigterm)
  116. def _test_start_and_quit(self, quit_with_sigterm):
  117. ## Test start()
  118. # start()
  119. self._saved_quit_with_sigterm = (
  120. http_runtime.HttpRuntimeProxy.stop_runtimes_with_sigterm(
  121. quit_with_sigterm))
  122. safe_subprocess.start_process(
  123. ['/runtime'],
  124. base64.b64encode(self.runtime_config.SerializeToString()),
  125. stdout=subprocess.PIPE,
  126. stderr=subprocess.PIPE,
  127. env={'foo': 'bar'},
  128. cwd=self.tmpdir).AndReturn(self.process)
  129. self.process.stdout.readline().AndReturn('30000')
  130. self.proxy._stderr_tee = FakeTee('')
  131. self.mox.ReplayAll()
  132. self.proxy.start()
  133. self.mox.VerifyAll()
  134. self.mox.ResetAll()
  135. ## Test quit()
  136. if quit_with_sigterm:
  137. self.process.terminate()
  138. else:
  139. self.process.kill()
  140. self.mox.ReplayAll()
  141. self.proxy.quit()
  142. self.mox.VerifyAll()
  143. def test_start_and_quit(self):
  144. self._test_start_and_quit(quit_with_sigterm=False)
  145. def test_start_and_quit_with_sigterm(self):
  146. self._test_start_and_quit(quit_with_sigterm=True)
  147. def test_start_bad_port(self):
  148. safe_subprocess.start_process(
  149. ['/runtime'],
  150. base64.b64encode(self.runtime_config.SerializeToString()),
  151. stdout=subprocess.PIPE,
  152. stderr=subprocess.PIPE,
  153. env={'foo': 'bar'},
  154. cwd=self.tmpdir).AndReturn(self.process)
  155. self.process.stdout.readline().AndReturn('hello 30001')
  156. header = "bad runtime process port ['hello 30001']\n\n"
  157. stderr0 = "I've just picked up a fault in the AE35 unit.\n"
  158. stderr1 = "It's going to go 100% failure in 72 hours.\n"
  159. self.proxy._stderr_tee = FakeTee(stderr0 + stderr1)
  160. self.mox.ReplayAll()
  161. self.proxy.start()
  162. expected_headers = {
  163. 'Content-Type': 'text/plain',
  164. 'Content-Length': str(len(header) + len(stderr0) + len(stderr1)),
  165. }
  166. self.assertResponse('500 Internal Server Error', expected_headers,
  167. header + stderr0 + stderr1,
  168. self.proxy.handle, {},
  169. url_map=self.url_map,
  170. match=re.match(self.url_map.url, '/get%20request'),
  171. request_id='request id',
  172. request_type=instance.NORMAL_REQUEST)
  173. self.mox.VerifyAll()
  174. class HttpRuntimeProxyFileFlavorTest(wsgi_test_utils.WSGITestCase):
  175. def setUp(self):
  176. self.mox = mox.Mox()
  177. self.tmpdir = tempfile.mkdtemp()
  178. module_configuration = ModuleConfigurationStub(application_root=self.tmpdir)
  179. self.runtime_config = runtime_config_pb2.Config()
  180. self.runtime_config.app_id = 'app'
  181. self.runtime_config.version_id = 'version'
  182. self.runtime_config.api_port = 12345
  183. self.runtime_config.application_root = self.tmpdir
  184. self.runtime_config.datacenter = 'us1'
  185. self.runtime_config.instance_id = 'abc3dzac4'
  186. self.runtime_config.auth_domain = 'gmail.com'
  187. self.runtime_config_getter = lambda: self.runtime_config
  188. self.proxy = http_runtime.HttpRuntimeProxy(
  189. ['/runtime'], self.runtime_config_getter, module_configuration,
  190. env={'foo': 'bar'},
  191. start_process_flavor=http_runtime.START_PROCESS_FILE)
  192. self.mox.StubOutWithMock(self.proxy, '_process_lock')
  193. self.process = self.mox.CreateMock(subprocess.Popen)
  194. self.process.stdin = self.mox.CreateMockAnything()
  195. self.process.stdout = self.mox.CreateMockAnything()
  196. self.process.stderr = self.mox.CreateMockAnything()
  197. self.process.child_out = self.mox.CreateMockAnything()
  198. self.mox.StubOutWithMock(safe_subprocess, 'start_process_file')
  199. self.mox.StubOutWithMock(os, 'remove')
  200. self.mox.StubOutWithMock(time, 'sleep')
  201. self.url_map = appinfo.URLMap(url=r'/(get|post).*',
  202. script=r'\1.py')
  203. self.mox.StubOutWithMock(http_proxy.HttpProxy, 'wait_for_connection')
  204. http_proxy.HttpProxy.wait_for_connection()
  205. def tearDown(self):
  206. shutil.rmtree(self.tmpdir)
  207. self.mox.UnsetStubs()
  208. def test_basic(self):
  209. """Basic functionality test of START_PROCESS_FILE flavor."""
  210. # start()
  211. # As the lock is mocked out, this provides a mox expectation.
  212. with self.proxy._process_lock:
  213. safe_subprocess.start_process_file(
  214. args=['/runtime'],
  215. input_string=self.runtime_config.SerializeToString(),
  216. env={'foo': 'bar'},
  217. cwd=self.tmpdir,
  218. stderr=subprocess.PIPE).AndReturn(self.process)
  219. self.process.poll().AndReturn(None)
  220. self.process.child_out.seek(0).AndReturn(None)
  221. self.process.child_out.read().AndReturn('1234\n')
  222. self.process.child_out.close().AndReturn(None)
  223. self.process.child_out.name = '/tmp/c-out.ABC'
  224. os.remove('/tmp/c-out.ABC').AndReturn(None)
  225. self.proxy._stderr_tee = FakeTee('')
  226. self.mox.ReplayAll()
  227. self.proxy.start()
  228. self.assertEquals(1234, self.proxy._proxy._port)
  229. self.mox.VerifyAll()
  230. def test_slow_shattered(self):
  231. """The port number is received slowly in chunks."""
  232. # start()
  233. # As the lock is mocked out, this provides a mox expectation.
  234. with self.proxy._process_lock:
  235. safe_subprocess.start_process_file(
  236. args=['/runtime'],
  237. input_string=self.runtime_config.SerializeToString(),
  238. env={'foo': 'bar'},
  239. cwd=self.tmpdir,
  240. stderr=subprocess.PIPE).AndReturn(self.process)
  241. for response, sleeptime in [
  242. ('', .125), ('43', .25), ('4321', .5), ('4321\n', None)]:
  243. self.process.poll().AndReturn(None)
  244. self.process.child_out.seek(0).AndReturn(None)
  245. self.process.child_out.read().AndReturn(response)
  246. if sleeptime is not None:
  247. time.sleep(sleeptime).AndReturn(None)
  248. self.process.child_out.close().AndReturn(None)
  249. self.process.child_out.name = '/tmp/c-out.ABC'
  250. os.remove('/tmp/c-out.ABC').AndReturn(None)
  251. self.proxy._stderr_tee = FakeTee('')
  252. self.mox.ReplayAll()
  253. self.proxy.start()
  254. self.assertEquals(4321, self.proxy._proxy._port)
  255. self.mox.VerifyAll()
  256. def test_runtime_instance_dies_immediately(self):
  257. """Runtime instance dies without sending a port."""
  258. # start()
  259. # As the lock is mocked out, this provides a mox expectation.
  260. with self.proxy._process_lock:
  261. safe_subprocess.start_process_file(
  262. args=['/runtime'],
  263. input_string=self.runtime_config.SerializeToString(),
  264. env={'foo': 'bar'},
  265. cwd=self.tmpdir,
  266. stderr=subprocess.PIPE).AndReturn(self.process)
  267. self.process.poll().AndReturn(1)
  268. self.process.child_out.close().AndReturn(None)
  269. self.process.child_out.name = '/tmp/c-out.ABC'
  270. os.remove('/tmp/c-out.ABC').AndReturn(None)
  271. header = "bad runtime process port ['']\n\n"
  272. stderr0 = 'Go away..\n'
  273. self.proxy._stderr_tee = FakeTee(stderr0)
  274. time.sleep(.1).AndReturn(None)
  275. self.mox.ReplayAll()
  276. self.proxy.start()
  277. expected_headers = {
  278. 'Content-Type': 'text/plain',
  279. 'Content-Length': str(len(header) + len(stderr0)),
  280. }
  281. self.assertResponse('500 Internal Server Error', expected_headers,
  282. header + stderr0,
  283. self.proxy.handle, {},
  284. url_map=self.url_map,
  285. match=re.match(self.url_map.url, '/get%20request'),
  286. request_id='request id',
  287. request_type=instance.NORMAL_REQUEST)
  288. self.mox.VerifyAll()
  289. def test_runtime_instance_invalid_response(self):
  290. """Runtime instance does not terminate port with a newline."""
  291. # start()
  292. # As the lock is mocked out, this provides a mox expectation.
  293. with self.proxy._process_lock:
  294. safe_subprocess.start_process_file(
  295. args=['/runtime'],
  296. input_string=self.runtime_config.SerializeToString(),
  297. env={'foo': 'bar'},
  298. cwd=self.tmpdir,
  299. stderr=subprocess.PIPE).AndReturn(self.process)
  300. for response, sleeptime in [
  301. ('30000', .125), ('30000', .25), ('30000', .5), ('30000', 1.0),
  302. ('30000', 2.0), ('30000', 4.0), ('30000', 8.0), ('30000', 16.0),
  303. ('30000', 32.0), ('30000', None)]:
  304. self.process.poll().AndReturn(None)
  305. self.process.child_out.seek(0).AndReturn(None)
  306. self.process.child_out.read().AndReturn(response)
  307. if sleeptime is not None:
  308. time.sleep(sleeptime).AndReturn(None)
  309. self.process.child_out.close().AndReturn(None)
  310. self.process.child_out.name = '/tmp/c-out.ABC'
  311. os.remove('/tmp/c-out.ABC').AndReturn(None)
  312. header = "bad runtime process port ['']\n\n"
  313. stderr0 = 'Go away..\n'
  314. self.proxy._stderr_tee = FakeTee(stderr0)
  315. time.sleep(.1)
  316. self.mox.ReplayAll()
  317. self.proxy.start()
  318. expected_headers = {
  319. 'Content-Type': 'text/plain',
  320. 'Content-Length': str(len(header) + len(stderr0)),
  321. }
  322. self.assertResponse('500 Internal Server Error', expected_headers,
  323. header + stderr0,
  324. self.proxy.handle, {},
  325. url_map=self.url_map,
  326. match=re.match(self.url_map.url, '/get%20request'),
  327. request_id='request id',
  328. request_type=instance.NORMAL_REQUEST)
  329. self.mox.VerifyAll()
  330. if __name__ == '__main__':
  331. unittest.main()