App Engine Python SDK version 1.9.12
[gae.git] / python / google / appengine / tools / devappserver2 / application_configuration_test.py
blob8471704a957aa3579f4b758a0d2f1605ae6ee530
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.apphosting.tools.devappserver2.application_configuration."""
20 import collections
21 from contextlib import contextmanager
22 import io
23 import os.path
24 import shutil
25 import tempfile
26 import unittest
28 import google
29 import mox
31 from google.appengine.api import appinfo
32 from google.appengine.api import appinfo_includes
33 from google.appengine.api import backendinfo
34 from google.appengine.api import dispatchinfo
35 from google.appengine.tools.devappserver2 import application_configuration
36 from google.appengine.tools.devappserver2 import errors
39 @contextmanager
40 def _java_temporarily_supported():
41 """Make the java_supported() function return True temporarily.
43 Use as:
44 with _java_temporarily_supported():
45 ...test that relies on Java being supported...
46 """
47 old_java_supported = application_configuration.java_supported
48 application_configuration.java_supported = lambda: True
49 yield
50 application_configuration.java_supported = old_java_supported
53 _DEFAULT_HEALTH_CHECK = appinfo.VmHealthCheck(
54 enable_health_check=True,
55 check_interval_sec=5,
56 timeout_sec=4,
57 unhealthy_threshold=2,
58 healthy_threshold=2,
59 restart_threshold=60,
60 host='127.0.0.1')
63 class TestModuleConfiguration(unittest.TestCase):
64 """Tests for application_configuration.ModuleConfiguration."""
66 def setUp(self):
67 self.mox = mox.Mox()
68 self.mox.StubOutWithMock(appinfo_includes, 'ParseAndReturnIncludePaths')
69 self.mox.StubOutWithMock(os.path, 'getmtime')
70 application_configuration.open = self._fake_open
72 def tearDown(self):
73 self.mox.UnsetStubs()
74 del application_configuration.open
76 @staticmethod
77 def _fake_open(unused_filename):
78 return io.BytesIO()
80 def test_good_app_yaml_configuration(self):
81 automatic_scaling = appinfo.AutomaticScaling(min_pending_latency='1.0s',
82 max_pending_latency='2.0s',
83 min_idle_instances=1,
84 max_idle_instances=2)
85 error_handlers = [appinfo.ErrorHandlers(file='error.html')]
86 handlers = [appinfo.URLMap()]
87 env_variables = appinfo.EnvironmentVariables()
88 info = appinfo.AppInfoExternal(
89 application='app',
90 module='module1',
91 version='1',
92 runtime='python27',
93 threadsafe=False,
94 automatic_scaling=automatic_scaling,
95 skip_files=r'\*.gif',
96 error_handlers=error_handlers,
97 handlers=handlers,
98 inbound_services=['warmup'],
99 env_variables=env_variables,
101 appinfo_includes.ParseAndReturnIncludePaths(mox.IgnoreArg()).AndReturn(
102 (info, []))
103 os.path.getmtime('/appdir/app.yaml').AndReturn(10)
105 self.mox.ReplayAll()
106 config = application_configuration.ModuleConfiguration('/appdir/app.yaml')
107 self.mox.VerifyAll()
109 self.assertEqual(os.path.realpath('/appdir'), config.application_root)
110 self.assertEqual(os.path.realpath('/appdir/app.yaml'), config.config_path)
111 self.assertEqual('dev~app', config.application)
112 self.assertEqual('app', config.application_external_name)
113 self.assertEqual('dev', config.partition)
114 self.assertEqual('module1', config.module_name)
115 self.assertEqual('1', config.major_version)
116 self.assertRegexpMatches(config.version_id, r'module1:1\.\d+')
117 self.assertEqual('python27', config.runtime)
118 self.assertFalse(config.threadsafe)
119 self.assertEqual(automatic_scaling, config.automatic_scaling)
120 self.assertEqual(info.GetNormalizedLibraries(),
121 config.normalized_libraries)
122 self.assertEqual(r'\*.gif', config.skip_files)
123 self.assertEqual(error_handlers, config.error_handlers)
124 self.assertEqual(handlers, config.handlers)
125 self.assertEqual(['warmup'], config.inbound_services)
126 self.assertEqual(env_variables, config.env_variables)
127 self.assertEqual({'/appdir/app.yaml': 10}, config._mtimes)
128 self.assertEqual(_DEFAULT_HEALTH_CHECK, config.vm_health_check)
130 def test_vm_app_yaml_configuration(self):
131 manual_scaling = appinfo.ManualScaling()
132 vm_settings = appinfo.VmSettings()
133 vm_settings['vm_runtime'] = 'myawesomeruntime'
134 vm_settings['forwarded_ports'] = '49111:49111,5002:49112,8000'
135 health_check = appinfo.VmHealthCheck()
136 health_check.enable_health_check = False
137 info = appinfo.AppInfoExternal(
138 application='app',
139 module='module1',
140 version='1',
141 runtime='vm',
142 vm_settings=vm_settings,
143 threadsafe=False,
144 manual_scaling=manual_scaling,
145 vm_health_check=health_check
148 appinfo_includes.ParseAndReturnIncludePaths(mox.IgnoreArg()).AndReturn(
149 (info, []))
150 os.path.getmtime('/appdir/app.yaml').AndReturn(10)
152 self.mox.ReplayAll()
153 config = application_configuration.ModuleConfiguration('/appdir/app.yaml')
155 self.mox.VerifyAll()
156 self.assertEqual(os.path.realpath('/appdir'), config.application_root)
157 self.assertEqual(os.path.realpath('/appdir/app.yaml'), config.config_path)
158 self.assertEqual('dev~app', config.application)
159 self.assertEqual('app', config.application_external_name)
160 self.assertEqual('dev', config.partition)
161 self.assertEqual('module1', config.module_name)
162 self.assertEqual('1', config.major_version)
163 self.assertRegexpMatches(config.version_id, r'module1:1\.\d+')
164 self.assertEqual('vm', config.runtime)
165 self.assertEqual(vm_settings['vm_runtime'], config.effective_runtime)
166 self.assertItemsEqual(
167 {49111: 49111, 5002: 49112, 8000: 8000},
168 config.forwarded_ports)
169 self.assertFalse(config.threadsafe)
170 self.assertEqual(manual_scaling, config.manual_scaling)
171 self.assertEqual({'/appdir/app.yaml': 10}, config._mtimes)
172 self.assertEqual(info.vm_health_check, config.vm_health_check)
174 def test_set_health_check_defaults(self):
175 # Pass nothing in.
176 self.assertEqual(
177 _DEFAULT_HEALTH_CHECK,
178 application_configuration._set_health_check_defaults(None))
180 # Pass in an empty object.
181 self.assertEqual(
182 _DEFAULT_HEALTH_CHECK,
183 application_configuration._set_health_check_defaults(
184 appinfo.VmHealthCheck()))
186 # Override some.
187 health_check = appinfo.VmHealthCheck(restart_threshold=7,
188 healthy_threshold=4)
189 defaults_set = application_configuration._set_health_check_defaults(
190 health_check)
192 self.assertEqual(defaults_set.enable_health_check,
193 _DEFAULT_HEALTH_CHECK.enable_health_check)
194 self.assertEqual(defaults_set.check_interval_sec,
195 _DEFAULT_HEALTH_CHECK.check_interval_sec)
196 self.assertEqual(defaults_set.timeout_sec,
197 _DEFAULT_HEALTH_CHECK.timeout_sec)
198 self.assertEqual(defaults_set.unhealthy_threshold,
199 _DEFAULT_HEALTH_CHECK.unhealthy_threshold)
200 self.assertEqual(defaults_set.healthy_threshold, 4)
201 self.assertEqual(defaults_set.restart_threshold, 7)
202 self.assertEqual(defaults_set.host,
203 _DEFAULT_HEALTH_CHECK.host)
205 def test_override_app_id(self):
206 info = appinfo.AppInfoExternal(
207 application='ignored-app',
208 module='default',
209 version='version',
210 runtime='python27',
211 threadsafe=False)
212 appinfo_includes.ParseAndReturnIncludePaths(mox.IgnoreArg()).AndReturn(
213 (info, []))
214 os.path.getmtime('/appdir/app.yaml').AndReturn(10)
215 os.path.getmtime('/appdir/app.yaml').AndReturn(20)
216 os.path.getmtime('/appdir/app.yaml').AndReturn(20)
217 appinfo_includes.ParseAndReturnIncludePaths(mox.IgnoreArg()).AndReturn(
218 (info, []))
220 self.mox.ReplayAll()
221 config = application_configuration.ModuleConfiguration(
222 '/appdir/app.yaml', 'overriding-app')
223 self.assertEqual('overriding-app', config.application_external_name)
224 self.assertEqual('dev~overriding-app', config.application)
225 config.check_for_updates()
226 self.assertEqual('overriding-app', config.application_external_name)
227 self.assertEqual('dev~overriding-app', config.application)
228 self.mox.VerifyAll()
230 def test_check_for_updates_unchanged_mtime(self):
231 info = appinfo.AppInfoExternal(
232 application='app',
233 module='default',
234 version='version',
235 runtime='python27',
236 threadsafe=False)
237 appinfo_includes.ParseAndReturnIncludePaths(mox.IgnoreArg()).AndReturn(
238 (info, []))
239 os.path.getmtime('/appdir/app.yaml').AndReturn(10)
240 os.path.getmtime('/appdir/app.yaml').AndReturn(10)
242 self.mox.ReplayAll()
243 config = application_configuration.ModuleConfiguration('/appdir/app.yaml')
244 self.assertSequenceEqual(set(), config.check_for_updates())
245 self.mox.VerifyAll()
247 def test_check_for_updates_with_includes(self):
248 info = appinfo.AppInfoExternal(
249 application='app',
250 module='default',
251 version='version',
252 runtime='python27',
253 includes=['/appdir/include.yaml'],
254 threadsafe=False)
255 appinfo_includes.ParseAndReturnIncludePaths(mox.IgnoreArg()).AndReturn(
256 (info, ['/appdir/include.yaml']))
257 os.path.getmtime('/appdir/app.yaml').InAnyOrder().AndReturn(10)
258 os.path.getmtime('/appdir/include.yaml').InAnyOrder().AndReturn(10)
259 os.path.getmtime('/appdir/app.yaml').AndReturn(10)
260 os.path.getmtime('/appdir/include.yaml').AndReturn(11)
262 appinfo_includes.ParseAndReturnIncludePaths(mox.IgnoreArg()).AndReturn(
263 (info, ['/appdir/include.yaml']))
264 os.path.getmtime('/appdir/app.yaml').InAnyOrder().AndReturn(10)
265 os.path.getmtime('/appdir/include.yaml').InAnyOrder().AndReturn(11)
267 self.mox.ReplayAll()
268 config = application_configuration.ModuleConfiguration('/appdir/app.yaml')
269 self.assertEqual({'/appdir/app.yaml': 10, '/appdir/include.yaml': 10},
270 config._mtimes)
271 config._mtimes = collections.OrderedDict([('/appdir/app.yaml', 10),
272 ('/appdir/include.yaml', 10)])
273 self.assertSequenceEqual(set(), config.check_for_updates())
274 self.mox.VerifyAll()
275 self.assertEqual({'/appdir/app.yaml': 10, '/appdir/include.yaml': 11},
276 config._mtimes)
278 def test_check_for_updates_no_changes(self):
279 info = appinfo.AppInfoExternal(
280 application='app',
281 module='default',
282 version='version',
283 runtime='python27',
284 threadsafe=False)
285 appinfo_includes.ParseAndReturnIncludePaths(mox.IgnoreArg()).AndReturn(
286 (info, []))
287 os.path.getmtime('/appdir/app.yaml').AndReturn(10)
288 os.path.getmtime('/appdir/app.yaml').AndReturn(11)
289 appinfo_includes.ParseAndReturnIncludePaths(mox.IgnoreArg()).AndReturn(
290 (info, []))
291 os.path.getmtime('/appdir/app.yaml').AndReturn(11)
293 self.mox.ReplayAll()
294 config = application_configuration.ModuleConfiguration('/appdir/app.yaml')
295 self.assertSequenceEqual(set(), config.check_for_updates())
296 self.mox.VerifyAll()
297 self.assertEqual({'/appdir/app.yaml': 11}, config._mtimes)
299 def test_check_for_updates_immutable_changes(self):
300 automatic_scaling1 = appinfo.AutomaticScaling(
301 min_pending_latency='0.1s',
302 max_pending_latency='1.0s',
303 min_idle_instances=1,
304 max_idle_instances=2)
305 info1 = appinfo.AppInfoExternal(
306 application='app',
307 module='default',
308 version='version',
309 runtime='python27',
310 threadsafe=False,
311 automatic_scaling=automatic_scaling1)
313 info2 = appinfo.AppInfoExternal(
314 application='app2',
315 module='default2',
316 version='version2',
317 runtime='python',
318 threadsafe=True,
319 automatic_scaling=appinfo.AutomaticScaling(
320 min_pending_latency='1.0s',
321 max_pending_latency='2.0s',
322 min_idle_instances=1,
323 max_idle_instances=2))
325 appinfo_includes.ParseAndReturnIncludePaths(mox.IgnoreArg()).AndReturn(
326 (info1, []))
327 os.path.getmtime('/appdir/app.yaml').AndReturn(10)
328 os.path.getmtime('/appdir/app.yaml').AndReturn(11)
329 appinfo_includes.ParseAndReturnIncludePaths(mox.IgnoreArg()).AndReturn(
330 (info2, []))
331 os.path.getmtime('/appdir/app.yaml').AndReturn(11)
333 self.mox.ReplayAll()
334 config = application_configuration.ModuleConfiguration('/appdir/app.yaml')
335 self.assertSequenceEqual(set(), config.check_for_updates())
336 self.mox.VerifyAll()
338 self.assertEqual('dev~app', config.application)
339 self.assertEqual('default', config.module_name)
340 self.assertEqual('version', config.major_version)
341 self.assertRegexpMatches(config.version_id, r'^version\.\d+$')
342 self.assertEqual('python27', config.runtime)
343 self.assertFalse(config.threadsafe)
344 self.assertEqual(automatic_scaling1, config.automatic_scaling)
346 def test_check_for_updates_mutable_changes(self):
347 info1 = appinfo.AppInfoExternal(
348 application='app',
349 module='default',
350 version='version',
351 runtime='python27',
352 threadsafe=False,
353 libraries=[appinfo.Library(name='django', version='latest')],
354 skip_files='.*',
355 handlers=[],
356 inbound_services=['warmup'],
357 env_variables=appinfo.EnvironmentVariables(),
358 error_handlers=[appinfo.ErrorHandlers(file='error.html')],
360 info2 = appinfo.AppInfoExternal(
361 application='app',
362 module='default',
363 version='version',
364 runtime='python27',
365 threadsafe=False,
366 libraries=[appinfo.Library(name='jinja2', version='latest')],
367 skip_files=r'.*\.py',
368 handlers=[appinfo.URLMap()],
369 inbound_services=[],
372 appinfo_includes.ParseAndReturnIncludePaths(mox.IgnoreArg()).AndReturn(
373 (info1, []))
374 os.path.getmtime('/appdir/app.yaml').AndReturn(10)
375 os.path.getmtime('/appdir/app.yaml').AndReturn(11)
376 appinfo_includes.ParseAndReturnIncludePaths(mox.IgnoreArg()).AndReturn(
377 (info2, []))
378 os.path.getmtime('/appdir/app.yaml').AndReturn(11)
380 self.mox.ReplayAll()
381 config = application_configuration.ModuleConfiguration('/appdir/app.yaml')
382 self.assertSequenceEqual(
383 set([application_configuration.NORMALIZED_LIBRARIES_CHANGED,
384 application_configuration.SKIP_FILES_CHANGED,
385 application_configuration.HANDLERS_CHANGED,
386 application_configuration.INBOUND_SERVICES_CHANGED,
387 application_configuration.ENV_VARIABLES_CHANGED,
388 application_configuration.ERROR_HANDLERS_CHANGED]),
389 config.check_for_updates())
390 self.mox.VerifyAll()
392 self.assertEqual(info2.GetNormalizedLibraries(),
393 config.normalized_libraries)
394 self.assertEqual(info2.skip_files, config.skip_files)
395 self.assertEqual(info2.error_handlers, config.error_handlers)
396 self.assertEqual(info2.handlers, config.handlers)
397 self.assertEqual(info2.inbound_services, config.inbound_services)
398 self.assertEqual(info2.env_variables, config.env_variables)
402 class TestBackendsConfiguration(unittest.TestCase):
403 def setUp(self):
404 self.mox = mox.Mox()
405 self.mox.StubOutWithMock(
406 application_configuration.BackendsConfiguration,
407 '_parse_configuration')
408 self.mox.StubOutWithMock(application_configuration, 'BackendConfiguration')
410 def tearDown(self):
411 self.mox.UnsetStubs()
413 def test_good_configuration(self):
414 self.mox.StubOutWithMock(application_configuration, 'ModuleConfiguration')
415 static_backend_entry = backendinfo.BackendEntry(name='static')
416 dynamic_backend_entry = backendinfo.BackendEntry(name='dynamic')
417 backend_info = backendinfo.BackendInfoExternal(
418 backends=[static_backend_entry, dynamic_backend_entry])
419 module_config = object()
420 application_configuration.ModuleConfiguration(
421 '/appdir/app.yaml', None).AndReturn(module_config)
422 application_configuration.BackendsConfiguration._parse_configuration(
423 '/appdir/backends.yaml').AndReturn(backend_info)
424 static_configuration = object()
425 dynamic_configuration = object()
426 application_configuration.BackendConfiguration(
427 module_config,
428 mox.IgnoreArg(),
429 static_backend_entry).InAnyOrder().AndReturn(static_configuration)
430 application_configuration.BackendConfiguration(
431 module_config,
432 mox.IgnoreArg(),
433 dynamic_backend_entry).InAnyOrder().AndReturn(dynamic_configuration)
435 self.mox.ReplayAll()
436 config = application_configuration.BackendsConfiguration(
437 '/appdir/app.yaml',
438 '/appdir/backends.yaml')
439 self.assertItemsEqual([static_configuration, dynamic_configuration],
440 config.get_backend_configurations())
441 self.mox.VerifyAll()
443 def test_no_backends(self):
444 self.mox.StubOutWithMock(application_configuration, 'ModuleConfiguration')
445 backend_info = backendinfo.BackendInfoExternal()
446 module_config = object()
447 application_configuration.ModuleConfiguration(
448 '/appdir/app.yaml', None).AndReturn(module_config)
449 application_configuration.BackendsConfiguration._parse_configuration(
450 '/appdir/backends.yaml').AndReturn(backend_info)
452 self.mox.ReplayAll()
453 config = application_configuration.BackendsConfiguration(
454 '/appdir/app.yaml',
455 '/appdir/backends.yaml')
456 self.assertEqual([], config.get_backend_configurations())
457 self.mox.VerifyAll()
459 def test_check_for_changes(self):
460 static_backend_entry = backendinfo.BackendEntry(name='static')
461 dynamic_backend_entry = backendinfo.BackendEntry(name='dynamic')
462 backend_info = backendinfo.BackendInfoExternal(
463 backends=[static_backend_entry, dynamic_backend_entry])
464 module_config = self.mox.CreateMock(
465 application_configuration.ModuleConfiguration)
466 self.mox.StubOutWithMock(application_configuration, 'ModuleConfiguration')
467 application_configuration.ModuleConfiguration(
468 '/appdir/app.yaml', None).AndReturn(module_config)
469 application_configuration.BackendsConfiguration._parse_configuration(
470 '/appdir/backends.yaml').AndReturn(backend_info)
471 module_config.check_for_updates().AndReturn(set())
472 module_config.check_for_updates().AndReturn(set([1]))
473 module_config.check_for_updates().AndReturn(set([2]))
474 module_config.check_for_updates().AndReturn(set())
476 self.mox.ReplayAll()
477 config = application_configuration.BackendsConfiguration(
478 '/appdir/app.yaml',
479 '/appdir/backends.yaml')
480 self.assertEqual(set(), config.check_for_updates('dynamic'))
481 self.assertEqual(set([1]), config.check_for_updates('static'))
482 self.assertEqual(set([1, 2]), config.check_for_updates('dynamic'))
483 self.assertEqual(set([2]), config.check_for_updates('static'))
484 self.mox.VerifyAll()
487 class TestDispatchConfiguration(unittest.TestCase):
488 def setUp(self):
489 self.mox = mox.Mox()
490 self.mox.StubOutWithMock(os.path, 'getmtime')
491 self.mox.StubOutWithMock(
492 application_configuration.DispatchConfiguration,
493 '_parse_configuration')
495 def tearDown(self):
496 self.mox.UnsetStubs()
498 def test_good_configuration(self):
499 info = dispatchinfo.DispatchInfoExternal(
500 application='appid',
501 dispatch=[
502 dispatchinfo.DispatchEntry(url='*/path', module='foo'),
503 dispatchinfo.DispatchEntry(url='domain.com/path', module='bar'),
504 dispatchinfo.DispatchEntry(url='*/path/*', module='baz'),
505 dispatchinfo.DispatchEntry(url='*.domain.com/path/*', module='foo'),
508 os.path.getmtime('/appdir/dispatch.yaml').AndReturn(123.456)
509 application_configuration.DispatchConfiguration._parse_configuration(
510 '/appdir/dispatch.yaml').AndReturn(info)
512 self.mox.ReplayAll()
513 config = application_configuration.DispatchConfiguration(
514 '/appdir/dispatch.yaml')
515 self.mox.VerifyAll()
517 self.assertEqual(123.456, config._mtime)
518 self.assertEqual(2, len(config.dispatch))
519 self.assertEqual(vars(dispatchinfo.ParsedURL('*/path')),
520 vars(config.dispatch[0][0]))
521 self.assertEqual('foo', config.dispatch[0][1])
522 self.assertEqual(vars(dispatchinfo.ParsedURL('*/path/*')),
523 vars(config.dispatch[1][0]))
524 self.assertEqual('baz', config.dispatch[1][1])
526 def test_check_for_updates_no_modification(self):
527 info = dispatchinfo.DispatchInfoExternal(
528 application='appid',
529 dispatch=[])
531 os.path.getmtime('/appdir/dispatch.yaml').AndReturn(123.456)
532 application_configuration.DispatchConfiguration._parse_configuration(
533 '/appdir/dispatch.yaml').AndReturn(info)
534 os.path.getmtime('/appdir/dispatch.yaml').AndReturn(123.456)
536 self.mox.ReplayAll()
537 config = application_configuration.DispatchConfiguration(
538 '/appdir/dispatch.yaml')
539 config.check_for_updates()
540 self.mox.VerifyAll()
542 def test_check_for_updates_with_invalid_modification(self):
543 info = dispatchinfo.DispatchInfoExternal(
544 application='appid',
545 dispatch=[
546 dispatchinfo.DispatchEntry(url='*/path', module='bar'),
549 os.path.getmtime('/appdir/dispatch.yaml').AndReturn(123.456)
550 application_configuration.DispatchConfiguration._parse_configuration(
551 '/appdir/dispatch.yaml').AndReturn(info)
552 os.path.getmtime('/appdir/dispatch.yaml').AndReturn(124.456)
553 application_configuration.DispatchConfiguration._parse_configuration(
554 '/appdir/dispatch.yaml').AndRaise(Exception)
556 self.mox.ReplayAll()
557 config = application_configuration.DispatchConfiguration(
558 '/appdir/dispatch.yaml')
559 self.assertEqual('bar', config.dispatch[0][1])
560 config.check_for_updates()
561 self.mox.VerifyAll()
562 self.assertEqual('bar', config.dispatch[0][1])
564 def test_check_for_updates_with_modification(self):
565 info = dispatchinfo.DispatchInfoExternal(
566 application='appid',
567 dispatch=[
568 dispatchinfo.DispatchEntry(url='*/path', module='bar'),
570 new_info = dispatchinfo.DispatchInfoExternal(
571 application='appid',
572 dispatch=[
573 dispatchinfo.DispatchEntry(url='*/path', module='foo'),
576 os.path.getmtime('/appdir/dispatch.yaml').AndReturn(123.456)
577 application_configuration.DispatchConfiguration._parse_configuration(
578 '/appdir/dispatch.yaml').AndReturn(info)
579 os.path.getmtime('/appdir/dispatch.yaml').AndReturn(124.456)
580 application_configuration.DispatchConfiguration._parse_configuration(
581 '/appdir/dispatch.yaml').AndReturn(new_info)
583 self.mox.ReplayAll()
584 config = application_configuration.DispatchConfiguration(
585 '/appdir/dispatch.yaml')
586 self.assertEqual('bar', config.dispatch[0][1])
587 config.check_for_updates()
588 self.mox.VerifyAll()
589 self.assertEqual('foo', config.dispatch[0][1])
592 class TestBackendConfiguration(unittest.TestCase):
593 def setUp(self):
594 self.mox = mox.Mox()
595 self.mox.StubOutWithMock(
596 application_configuration.ModuleConfiguration,
597 '_parse_configuration')
598 self.mox.StubOutWithMock(os.path, 'getmtime')
600 def tearDown(self):
601 self.mox.UnsetStubs()
603 def test_good_configuration(self):
604 automatic_scaling = appinfo.AutomaticScaling(min_pending_latency='1.0s',
605 max_pending_latency='2.0s',
606 min_idle_instances=1,
607 max_idle_instances=2)
608 error_handlers = [appinfo.ErrorHandlers(file='error.html')]
609 handlers = [appinfo.URLMap()]
610 env_variables = appinfo.EnvironmentVariables()
611 info = appinfo.AppInfoExternal(
612 application='app',
613 module='module1',
614 version='1',
615 runtime='python27',
616 threadsafe=False,
617 automatic_scaling=automatic_scaling,
618 skip_files=r'\*.gif',
619 error_handlers=error_handlers,
620 handlers=handlers,
621 inbound_services=['warmup'],
622 env_variables=env_variables,
624 backend_entry = backendinfo.BackendEntry(
625 name='static',
626 instances='3',
627 options='public')
629 application_configuration.ModuleConfiguration._parse_configuration(
630 '/appdir/app.yaml').AndReturn((info, ['/appdir/app.yaml']))
631 os.path.getmtime('/appdir/app.yaml').AndReturn(10)
633 self.mox.ReplayAll()
634 module_config = application_configuration.ModuleConfiguration(
635 '/appdir/app.yaml')
636 config = application_configuration.BackendConfiguration(
637 module_config, None, backend_entry)
638 self.mox.VerifyAll()
640 self.assertEqual(os.path.realpath('/appdir'), config.application_root)
641 self.assertEqual('dev~app', config.application)
642 self.assertEqual('app', config.application_external_name)
643 self.assertEqual('dev', config.partition)
644 self.assertEqual('static', config.module_name)
645 self.assertEqual('1', config.major_version)
646 self.assertRegexpMatches(config.version_id, r'static:1\.\d+')
647 self.assertEqual('python27', config.runtime)
648 self.assertFalse(config.threadsafe)
649 self.assertEqual(None, config.automatic_scaling)
650 self.assertEqual(None, config.basic_scaling)
651 self.assertEqual(appinfo.ManualScaling(instances='3'),
652 config.manual_scaling)
653 self.assertEqual(info.GetNormalizedLibraries(),
654 config.normalized_libraries)
655 self.assertEqual(r'\*.gif', config.skip_files)
656 self.assertEqual(error_handlers, config.error_handlers)
657 self.assertEqual(handlers, config.handlers)
658 self.assertEqual(['warmup'], config.inbound_services)
659 self.assertEqual(env_variables, config.env_variables)
661 whitelist_fields = ['module_name', 'version_id', 'automatic_scaling',
662 'manual_scaling', 'basic_scaling', 'is_backend',
663 'minor_version']
664 # Check that all public attributes and methods in a ModuleConfiguration
665 # exist in a BackendConfiguration.
666 for field in dir(module_config):
667 if not field.startswith('_'):
668 self.assertTrue(hasattr(config, field), 'Missing field: %s' % field)
669 value = getattr(module_config, field)
670 if field not in whitelist_fields and not callable(value):
671 # Check that the attributes other than those in the whitelist have
672 # equal values in the BackendConfiguration to the ModuleConfiguration
673 # from which it inherits.
674 self.assertEqual(value, getattr(config, field))
676 def test_vm_app_yaml_configuration(self):
677 automatic_scaling = appinfo.AutomaticScaling(min_pending_latency='1.0s',
678 max_pending_latency='2.0s',
679 min_idle_instances=1,
680 max_idle_instances=2)
681 vm_settings = appinfo.VmSettings()
682 vm_settings['vm_runtime'] = 'myawesomeruntime'
683 info = appinfo.AppInfoExternal(
684 application='app',
685 module='module1',
686 version='1',
687 runtime='vm',
688 vm_settings=vm_settings,
689 threadsafe=False,
690 automatic_scaling=automatic_scaling,
692 backend_entry = backendinfo.BackendEntry(
693 name='static',
694 instances='3',
695 options='public')
696 application_configuration.ModuleConfiguration._parse_configuration(
697 '/appdir/app.yaml').AndReturn((info, ['/appdir/app.yaml']))
698 os.path.getmtime('/appdir/app.yaml').AndReturn(10)
700 self.mox.ReplayAll()
701 module_config = application_configuration.ModuleConfiguration(
702 '/appdir/app.yaml')
703 config = application_configuration.BackendConfiguration(
704 module_config, None, backend_entry)
706 self.mox.VerifyAll()
707 self.assertEqual(os.path.realpath('/appdir'), config.application_root)
708 self.assertEqual('dev~app', config.application)
709 self.assertEqual('app', config.application_external_name)
710 self.assertEqual('dev', config.partition)
711 self.assertEqual('static', config.module_name)
712 self.assertEqual('1', config.major_version)
713 self.assertRegexpMatches(config.version_id, r'static:1\.\d+')
714 self.assertEqual('vm', config.runtime)
715 self.assertEqual(vm_settings['vm_runtime'], config.effective_runtime)
716 self.assertFalse(config.threadsafe)
717 # Resident backends are assigned manual scaling.
718 self.assertEqual(None, config.automatic_scaling)
719 self.assertEqual(None, config.basic_scaling)
720 self.assertEqual(appinfo.ManualScaling(instances='3'),
721 config.manual_scaling)
723 def test_good_configuration_dynamic_scaling(self):
724 automatic_scaling = appinfo.AutomaticScaling(min_pending_latency='1.0s',
725 max_pending_latency='2.0s',
726 min_idle_instances=1,
727 max_idle_instances=2)
728 error_handlers = [appinfo.ErrorHandlers(file='error.html')]
729 handlers = [appinfo.URLMap()]
730 env_variables = appinfo.EnvironmentVariables()
731 info = appinfo.AppInfoExternal(
732 application='app',
733 module='module1',
734 version='1',
735 runtime='python27',
736 threadsafe=False,
737 automatic_scaling=automatic_scaling,
738 skip_files=r'\*.gif',
739 error_handlers=error_handlers,
740 handlers=handlers,
741 inbound_services=['warmup'],
742 env_variables=env_variables,
744 backend_entry = backendinfo.BackendEntry(
745 name='dynamic',
746 instances='3',
747 options='public, dynamic',
748 start='handler')
750 application_configuration.ModuleConfiguration._parse_configuration(
751 '/appdir/app.yaml').AndReturn((info, ['/appdir/app.yaml']))
752 os.path.getmtime('/appdir/app.yaml').AndReturn(10)
754 self.mox.ReplayAll()
755 module_config = application_configuration.ModuleConfiguration(
756 '/appdir/app.yaml')
757 config = application_configuration.BackendConfiguration(
758 module_config, None, backend_entry)
759 self.mox.VerifyAll()
761 self.assertEqual(os.path.realpath('/appdir'), config.application_root)
762 self.assertEqual('dev~app', config.application)
763 self.assertEqual('dynamic', config.module_name)
764 self.assertEqual('1', config.major_version)
765 self.assertRegexpMatches(config.version_id, r'dynamic:1\.\d+')
766 self.assertEqual('python27', config.runtime)
767 self.assertFalse(config.threadsafe)
768 self.assertEqual(None, config.automatic_scaling)
769 self.assertEqual(None, config.manual_scaling)
770 self.assertEqual(appinfo.BasicScaling(max_instances='3'),
771 config.basic_scaling)
772 self.assertEqual(info.GetNormalizedLibraries(),
773 config.normalized_libraries)
774 self.assertEqual(r'\*.gif', config.skip_files)
775 self.assertEqual(error_handlers, config.error_handlers)
776 start_handler = appinfo.URLMap(url='/_ah/start',
777 script=backend_entry.start,
778 login='admin')
779 self.assertEqual([start_handler] + handlers, config.handlers)
780 self.assertEqual(['warmup'], config.inbound_services)
781 self.assertEqual(env_variables, config.env_variables)
783 def test_check_for_changes(self):
784 backends_config = self.mox.CreateMock(
785 application_configuration.BackendsConfiguration)
786 config = application_configuration.BackendConfiguration(
787 None, backends_config, backendinfo.BackendEntry(name='backend'))
788 changes = object()
789 backends_config.check_for_updates('backend').AndReturn([])
790 backends_config.check_for_updates('backend').AndReturn(changes)
791 minor_version = config.minor_version
792 self.mox.ReplayAll()
793 self.assertEqual([], config.check_for_updates())
794 self.assertEqual(minor_version, config.minor_version)
795 self.assertEqual(changes, config.check_for_updates())
796 self.assertNotEqual(minor_version, config.minor_version)
797 self.mox.VerifyAll()
800 class ModuleConfigurationStub(object):
801 def __init__(self, application='myapp', module_name='module'):
802 self.application = application
803 self.module_name = module_name
806 class DispatchConfigurationStub(object):
807 def __init__(self, dispatch):
808 self.dispatch = dispatch
811 class TestApplicationConfiguration(unittest.TestCase):
812 """Tests for application_configuration.ApplicationConfiguration."""
814 def setUp(self):
815 self.mox = mox.Mox()
816 self.mox.StubOutWithMock(application_configuration, 'ModuleConfiguration')
817 self.mox.StubOutWithMock(application_configuration, 'BackendsConfiguration')
818 self.mox.StubOutWithMock(application_configuration, 'DispatchConfiguration')
819 self.tmpdir = tempfile.mkdtemp(dir=os.getenv('TEST_TMPDIR'))
821 def tearDown(self):
822 self.mox.UnsetStubs()
823 shutil.rmtree(self.tmpdir)
825 def _make_file_hierarchy(self, filenames):
826 absnames = []
827 for filename in filenames:
828 absname = os.path.normpath(self.tmpdir + '/' + filename)
829 absnames += [absname]
830 dirname = os.path.dirname(absname)
831 if not os.path.exists(dirname):
832 os.makedirs(dirname)
833 open(absname, 'w').close()
834 return absnames
836 def test_yaml_files(self):
837 absnames = self._make_file_hierarchy(
838 ['appdir/app.yaml', 'appdir/other.yaml'])
840 module_config1 = ModuleConfigurationStub()
841 application_configuration.ModuleConfiguration(
842 absnames[0], None).AndReturn(module_config1)
844 module_config2 = ModuleConfigurationStub(module_name='other')
845 application_configuration.ModuleConfiguration(
846 absnames[1], None).AndReturn(module_config2)
848 self.mox.ReplayAll()
849 config = application_configuration.ApplicationConfiguration(
850 absnames)
851 self.mox.VerifyAll()
852 self.assertEqual('myapp', config.app_id)
853 self.assertSequenceEqual([module_config1, module_config2], config.modules)
855 def test_yaml_files_with_different_app_ids(self):
856 absnames = self._make_file_hierarchy(
857 ['appdir/app.yaml', 'appdir/other.yaml'])
859 module_config1 = ModuleConfigurationStub()
860 application_configuration.ModuleConfiguration(
861 absnames[0], None).AndReturn(module_config1)
863 module_config2 = ModuleConfigurationStub(application='other_app',
864 module_name='other')
865 application_configuration.ModuleConfiguration(
866 absnames[1], None).AndReturn(module_config2)
868 self.mox.ReplayAll()
869 self.assertRaises(errors.InvalidAppConfigError,
870 application_configuration.ApplicationConfiguration,
871 absnames)
872 self.mox.VerifyAll()
874 def test_yaml_files_with_duplicate_module_names(self):
875 absnames = self._make_file_hierarchy(
876 ['appdir/app.yaml', 'appdir/other.yaml'])
878 application_configuration.ModuleConfiguration(
879 absnames[0], None).AndReturn(ModuleConfigurationStub())
881 application_configuration.ModuleConfiguration(
882 absnames[1], None).AndReturn(ModuleConfigurationStub())
884 self.mox.ReplayAll()
885 self.assertRaises(errors.InvalidAppConfigError,
886 application_configuration.ApplicationConfiguration,
887 absnames)
888 self.mox.VerifyAll()
890 def test_directory(self):
891 absnames = self._make_file_hierarchy(['appdir/app.yaml'])
893 module_config = ModuleConfigurationStub()
894 application_configuration.ModuleConfiguration(
895 absnames[0], None).AndReturn(module_config)
897 self.mox.ReplayAll()
898 config = application_configuration.ApplicationConfiguration(
899 [os.path.dirname(absnames[0])])
900 self.mox.VerifyAll()
901 self.assertEqual('myapp', config.app_id)
902 self.assertSequenceEqual([module_config], config.modules)
904 def test_directory_and_module(self):
905 absnames = self._make_file_hierarchy(
906 ['appdir/app.yaml', 'otherdir/mymodule.yaml'])
908 app_yaml_config = ModuleConfigurationStub()
909 application_configuration.ModuleConfiguration(
910 absnames[0], None).AndReturn(app_yaml_config)
911 my_module_config = ModuleConfigurationStub(module_name='my_module')
912 application_configuration.ModuleConfiguration(
913 absnames[1], None).AndReturn(my_module_config)
914 self.mox.ReplayAll()
915 config = application_configuration.ApplicationConfiguration(
916 [os.path.dirname(absnames[0]), absnames[1]])
917 self.mox.VerifyAll()
918 self.assertSequenceEqual(
919 [app_yaml_config, my_module_config], config.modules)
921 def test_directory_app_yml_only(self):
922 absnames = self._make_file_hierarchy(['appdir/app.yml'])
924 module_config = ModuleConfigurationStub()
925 application_configuration.ModuleConfiguration(
926 absnames[0], None).AndReturn(module_config)
928 self.mox.ReplayAll()
929 config = application_configuration.ApplicationConfiguration(
930 [os.path.dirname(absnames[0])])
931 self.mox.VerifyAll()
932 self.assertEqual('myapp', config.app_id)
933 self.assertSequenceEqual([module_config], config.modules)
935 def test_directory_app_yaml_and_app_yml(self):
936 absnames = self._make_file_hierarchy(['appdir/app.yaml', 'appdir/app.yml'])
937 self.mox.ReplayAll()
938 self.assertRaises(errors.InvalidAppConfigError,
939 application_configuration.ApplicationConfiguration,
940 [os.path.dirname(absnames[0])])
941 self.mox.VerifyAll()
943 def test_directory_no_app_yamls(self):
944 absnames = self._make_file_hierarchy(['appdir/somethingelse.yaml'])
946 self.mox.ReplayAll()
947 self.assertRaises(errors.AppConfigNotFoundError,
948 application_configuration.ApplicationConfiguration,
949 [os.path.dirname(absnames[0])])
950 self.mox.VerifyAll()
952 def test_directory_no_app_yamls_or_web_inf(self):
953 absnames = self._make_file_hierarchy(['appdir/somethingelse.yaml'])
955 self.mox.ReplayAll()
956 with _java_temporarily_supported():
957 self.assertRaises(errors.AppConfigNotFoundError,
958 application_configuration.ApplicationConfiguration,
959 [os.path.dirname(absnames[0])])
960 self.mox.VerifyAll()
962 def test_app_yaml(self):
963 absnames = self._make_file_hierarchy(['appdir/app.yaml'])
965 module_config = ModuleConfigurationStub()
966 application_configuration.ModuleConfiguration(
967 absnames[0], None).AndReturn(module_config)
969 self.mox.ReplayAll()
970 config = application_configuration.ApplicationConfiguration(absnames)
971 self.mox.VerifyAll()
972 self.assertEqual('myapp', config.app_id)
973 self.assertSequenceEqual([module_config], config.modules)
975 def test_directory_with_backends_yaml(self):
976 absnames = self._make_file_hierarchy(
977 ['appdir/app.yaml', 'appdir/backends.yaml'])
979 module_config = ModuleConfigurationStub()
980 application_configuration.ModuleConfiguration(
981 absnames[0], None).AndReturn(module_config)
982 backend_config = ModuleConfigurationStub(module_name='backend')
983 backends_config = self.mox.CreateMock(
984 application_configuration.BackendsConfiguration)
985 backends_config.get_backend_configurations().AndReturn([backend_config])
986 application_configuration.BackendsConfiguration(
987 absnames[0], absnames[1], None).AndReturn(backends_config)
989 self.mox.ReplayAll()
990 config = application_configuration.ApplicationConfiguration(
991 [os.path.dirname(absnames[0])])
992 self.mox.VerifyAll()
993 self.assertEqual('myapp', config.app_id)
994 self.assertSequenceEqual([module_config, backend_config], config.modules)
996 def test_yaml_files_with_backends_yaml(self):
997 absnames = self._make_file_hierarchy(
998 ['appdir/app.yaml', 'appdir/backends.yaml'])
1000 module_config = ModuleConfigurationStub()
1001 application_configuration.ModuleConfiguration(
1002 absnames[0], None).AndReturn(module_config)
1004 backend_config = ModuleConfigurationStub(module_name='backend')
1005 backends_config = self.mox.CreateMock(
1006 application_configuration.BackendsConfiguration)
1007 backends_config.get_backend_configurations().AndReturn([backend_config])
1008 application_configuration.BackendsConfiguration(
1009 absnames[0], absnames[1], None).AndReturn(backends_config)
1011 self.mox.ReplayAll()
1012 config = application_configuration.ApplicationConfiguration(absnames)
1013 self.mox.VerifyAll()
1014 self.assertEqual('myapp', config.app_id)
1015 self.assertSequenceEqual([module_config, backend_config], config.modules)
1017 def test_yaml_files_with_backends_and_dispatch_yaml(self):
1018 absnames = self._make_file_hierarchy(
1019 ['appdir/app.yaml', 'appdir/backends.yaml', 'appdir/dispatch.yaml'])
1021 module_config = ModuleConfigurationStub(module_name='default')
1022 application_configuration.ModuleConfiguration(
1023 absnames[0], None).AndReturn(module_config)
1025 backend_config = ModuleConfigurationStub(module_name='backend')
1026 backends_config = self.mox.CreateMock(
1027 application_configuration.BackendsConfiguration)
1028 backends_config.get_backend_configurations().AndReturn([backend_config])
1029 application_configuration.BackendsConfiguration(
1030 absnames[0], absnames[1], None).AndReturn(backends_config)
1031 dispatch_config = DispatchConfigurationStub(
1032 [(None, 'default'), (None, 'backend')])
1033 application_configuration.DispatchConfiguration(
1034 absnames[2]).AndReturn(dispatch_config)
1036 self.mox.ReplayAll()
1037 config = application_configuration.ApplicationConfiguration(absnames)
1038 self.mox.VerifyAll()
1039 self.assertEqual('myapp', config.app_id)
1040 self.assertSequenceEqual([module_config, backend_config], config.modules)
1041 self.assertEqual(dispatch_config, config.dispatch)
1043 def test_yaml_files_dispatch_yaml_and_no_default_module(self):
1044 absnames = self._make_file_hierarchy(
1045 ['appdir/app.yaml', 'appdir/dispatch.yaml'])
1047 module_config = ModuleConfigurationStub(module_name='not-default')
1048 application_configuration.ModuleConfiguration(
1049 absnames[0], None).AndReturn(module_config)
1051 dispatch_config = DispatchConfigurationStub([(None, 'default')])
1052 application_configuration.DispatchConfiguration(
1053 absnames[1]).AndReturn(dispatch_config)
1055 self.mox.ReplayAll()
1056 self.assertRaises(errors.InvalidAppConfigError,
1057 application_configuration.ApplicationConfiguration,
1058 absnames)
1059 self.mox.VerifyAll()
1061 def test_yaml_files_dispatch_yaml_and_missing_dispatch_target(self):
1062 absnames = self._make_file_hierarchy(
1063 ['appdir/app.yaml', 'appdir/dispatch.yaml'])
1065 module_config = ModuleConfigurationStub(module_name='default')
1066 application_configuration.ModuleConfiguration(
1067 absnames[0], None).AndReturn(module_config)
1069 dispatch_config = DispatchConfigurationStub(
1070 [(None, 'default'), (None, 'fake-module')])
1071 application_configuration.DispatchConfiguration(
1072 absnames[1]).AndReturn(dispatch_config)
1074 self.mox.ReplayAll()
1075 self.assertRaises(errors.InvalidAppConfigError,
1076 application_configuration.ApplicationConfiguration,
1077 absnames)
1078 self.mox.VerifyAll()
1080 def test_directory_web_inf(self):
1081 absnames = self._make_file_hierarchy(
1082 ['appdir/WEB-INF/appengine-web.xml', 'appdir/WEB-INF/web.xml'])
1083 appdir = os.path.dirname(os.path.dirname(absnames[0]))
1085 module_config = ModuleConfigurationStub(module_name='default')
1086 application_configuration.ModuleConfiguration(
1087 absnames[0], None).AndReturn(module_config)
1089 self.mox.ReplayAll()
1090 with _java_temporarily_supported():
1091 config = application_configuration.ApplicationConfiguration([appdir])
1092 self.mox.VerifyAll()
1094 self.assertEqual('myapp', config.app_id)
1095 self.assertSequenceEqual([module_config], config.modules)
1097 def test_directory_web_inf_missing_appengine_xml(self):
1098 absnames = self._make_file_hierarchy(['appdir/WEB-INF/web.xml'])
1099 appdir = os.path.dirname(os.path.dirname(absnames[0]))
1101 self.mox.ReplayAll()
1102 with _java_temporarily_supported():
1103 self.assertRaises(errors.AppConfigNotFoundError,
1104 application_configuration.ApplicationConfiguration,
1105 [appdir])
1106 self.mox.VerifyAll()
1108 def test_directory_web_inf_missing_web_xml(self):
1109 absnames = self._make_file_hierarchy(['appdir/WEB-INF/appengine-web.xml'])
1110 appdir = os.path.dirname(os.path.dirname(absnames[0]))
1112 self.mox.ReplayAll()
1113 with _java_temporarily_supported():
1114 self.assertRaises(errors.AppConfigNotFoundError,
1115 application_configuration.ApplicationConfiguration,
1116 [appdir])
1117 self.mox.VerifyAll()
1119 def test_config_with_yaml_and_xml(self):
1120 absnames = self._make_file_hierarchy(
1121 ['module1/app.yaml', 'module1/dispatch.yaml',
1122 'module2/WEB-INF/appengine-web.xml', 'module2/WEB-INF/web.xml'])
1123 app_yaml = absnames[0]
1124 dispatch_yaml = absnames[1]
1125 appengine_web_xml = absnames[2]
1126 module2 = os.path.dirname(os.path.dirname(appengine_web_xml))
1128 module1_config = ModuleConfigurationStub(module_name='default')
1129 application_configuration.ModuleConfiguration(
1130 app_yaml, None).AndReturn(module1_config)
1131 dispatch_config = DispatchConfigurationStub(
1132 [(None, 'default'), (None, 'module2')])
1133 application_configuration.DispatchConfiguration(
1134 dispatch_yaml).AndReturn(dispatch_config)
1135 module2_config = ModuleConfigurationStub(module_name='module2')
1136 application_configuration.ModuleConfiguration(
1137 appengine_web_xml, None).AndReturn(module2_config)
1139 self.mox.ReplayAll()
1140 with _java_temporarily_supported():
1141 config = application_configuration.ApplicationConfiguration(
1142 [app_yaml, dispatch_yaml, module2])
1143 self.mox.VerifyAll()
1145 self.assertEqual('myapp', config.app_id)
1146 self.assertSequenceEqual(
1147 [module1_config, module2_config], config.modules)
1148 self.assertEqual(dispatch_config, config.dispatch)
1151 if __name__ == '__main__':
1152 unittest.main()