App Engine Python SDK version 1.9.9
[gae.git] / python / google / appengine / tools / devappserver2 / application_configuration_test.py
blob7c6983bc3100f55c4c598304fa1bcde4f759e901
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 class TestModuleConfiguration(unittest.TestCase):
54 """Tests for application_configuration.ModuleConfiguration."""
56 def setUp(self):
57 self.mox = mox.Mox()
58 self.mox.StubOutWithMock(appinfo_includes, 'ParseAndReturnIncludePaths')
59 self.mox.StubOutWithMock(os.path, 'getmtime')
60 application_configuration.open = self._fake_open
62 def tearDown(self):
63 self.mox.UnsetStubs()
64 del application_configuration.open
66 @staticmethod
67 def _fake_open(unused_filename):
68 return io.BytesIO()
70 def test_good_app_yaml_configuration(self):
71 automatic_scaling = appinfo.AutomaticScaling(min_pending_latency='1.0s',
72 max_pending_latency='2.0s',
73 min_idle_instances=1,
74 max_idle_instances=2)
75 error_handlers = [appinfo.ErrorHandlers(file='error.html')]
76 handlers = [appinfo.URLMap()]
77 env_variables = appinfo.EnvironmentVariables()
78 info = appinfo.AppInfoExternal(
79 application='app',
80 module='module1',
81 version='1',
82 runtime='python27',
83 threadsafe=False,
84 automatic_scaling=automatic_scaling,
85 skip_files=r'\*.gif',
86 error_handlers=error_handlers,
87 handlers=handlers,
88 inbound_services=['warmup'],
89 env_variables=env_variables,
91 appinfo_includes.ParseAndReturnIncludePaths(mox.IgnoreArg()).AndReturn(
92 (info, []))
93 os.path.getmtime('/appdir/app.yaml').AndReturn(10)
95 self.mox.ReplayAll()
96 config = application_configuration.ModuleConfiguration('/appdir/app.yaml')
97 self.mox.VerifyAll()
99 self.assertEqual(os.path.realpath('/appdir'), config.application_root)
100 self.assertEqual(os.path.realpath('/appdir/app.yaml'), config.config_path)
101 self.assertEqual('dev~app', config.application)
102 self.assertEqual('app', config.application_external_name)
103 self.assertEqual('dev', config.partition)
104 self.assertEqual('module1', config.module_name)
105 self.assertEqual('1', config.major_version)
106 self.assertRegexpMatches(config.version_id, r'module1:1\.\d+')
107 self.assertEqual('python27', config.runtime)
108 self.assertFalse(config.threadsafe)
109 self.assertEqual(automatic_scaling, config.automatic_scaling)
110 self.assertEqual(info.GetNormalizedLibraries(),
111 config.normalized_libraries)
112 self.assertEqual(r'\*.gif', config.skip_files)
113 self.assertEqual(error_handlers, config.error_handlers)
114 self.assertEqual(handlers, config.handlers)
115 self.assertEqual(['warmup'], config.inbound_services)
116 self.assertEqual(env_variables, config.env_variables)
117 self.assertEqual({'/appdir/app.yaml': 10}, config._mtimes)
119 def test_vm_app_yaml_configuration(self):
120 manual_scaling = appinfo.ManualScaling()
121 vm_settings = appinfo.VmSettings()
122 vm_settings['vm_runtime'] = 'myawesomeruntime'
123 info = appinfo.AppInfoExternal(
124 application='app',
125 module='module1',
126 version='1',
127 runtime='vm',
128 vm_settings=vm_settings,
129 threadsafe=False,
130 manual_scaling=manual_scaling,
132 appinfo_includes.ParseAndReturnIncludePaths(mox.IgnoreArg()).AndReturn(
133 (info, []))
134 os.path.getmtime('/appdir/app.yaml').AndReturn(10)
136 self.mox.ReplayAll()
137 config = application_configuration.ModuleConfiguration('/appdir/app.yaml')
139 self.mox.VerifyAll()
140 self.assertEqual(os.path.realpath('/appdir'), config.application_root)
141 self.assertEqual(os.path.realpath('/appdir/app.yaml'), config.config_path)
142 self.assertEqual('dev~app', config.application)
143 self.assertEqual('app', config.application_external_name)
144 self.assertEqual('dev', config.partition)
145 self.assertEqual('module1', config.module_name)
146 self.assertEqual('1', config.major_version)
147 self.assertRegexpMatches(config.version_id, r'module1:1\.\d+')
148 self.assertEqual('vm', config.runtime)
149 self.assertEqual(vm_settings['vm_runtime'], config.effective_runtime)
150 self.assertFalse(config.threadsafe)
151 self.assertEqual(manual_scaling, config.manual_scaling)
152 self.assertEqual({'/appdir/app.yaml': 10}, config._mtimes)
154 def test_override_app_id(self):
155 info = appinfo.AppInfoExternal(
156 application='ignored-app',
157 module='default',
158 version='version',
159 runtime='python27',
160 threadsafe=False)
161 appinfo_includes.ParseAndReturnIncludePaths(mox.IgnoreArg()).AndReturn(
162 (info, []))
163 os.path.getmtime('/appdir/app.yaml').AndReturn(10)
164 os.path.getmtime('/appdir/app.yaml').AndReturn(20)
165 os.path.getmtime('/appdir/app.yaml').AndReturn(20)
166 appinfo_includes.ParseAndReturnIncludePaths(mox.IgnoreArg()).AndReturn(
167 (info, []))
169 self.mox.ReplayAll()
170 config = application_configuration.ModuleConfiguration(
171 '/appdir/app.yaml', 'overriding-app')
172 self.assertEqual('overriding-app', config.application_external_name)
173 self.assertEqual('dev~overriding-app', config.application)
174 config.check_for_updates()
175 self.assertEqual('overriding-app', config.application_external_name)
176 self.assertEqual('dev~overriding-app', config.application)
177 self.mox.VerifyAll()
179 def test_check_for_updates_unchanged_mtime(self):
180 info = appinfo.AppInfoExternal(
181 application='app',
182 module='default',
183 version='version',
184 runtime='python27',
185 threadsafe=False)
186 appinfo_includes.ParseAndReturnIncludePaths(mox.IgnoreArg()).AndReturn(
187 (info, []))
188 os.path.getmtime('/appdir/app.yaml').AndReturn(10)
189 os.path.getmtime('/appdir/app.yaml').AndReturn(10)
191 self.mox.ReplayAll()
192 config = application_configuration.ModuleConfiguration('/appdir/app.yaml')
193 self.assertSequenceEqual(set(), config.check_for_updates())
194 self.mox.VerifyAll()
196 def test_check_for_updates_with_includes(self):
197 info = appinfo.AppInfoExternal(
198 application='app',
199 module='default',
200 version='version',
201 runtime='python27',
202 includes=['/appdir/include.yaml'],
203 threadsafe=False)
204 appinfo_includes.ParseAndReturnIncludePaths(mox.IgnoreArg()).AndReturn(
205 (info, ['/appdir/include.yaml']))
206 os.path.getmtime('/appdir/app.yaml').InAnyOrder().AndReturn(10)
207 os.path.getmtime('/appdir/include.yaml').InAnyOrder().AndReturn(10)
208 os.path.getmtime('/appdir/app.yaml').AndReturn(10)
209 os.path.getmtime('/appdir/include.yaml').AndReturn(11)
211 appinfo_includes.ParseAndReturnIncludePaths(mox.IgnoreArg()).AndReturn(
212 (info, ['/appdir/include.yaml']))
213 os.path.getmtime('/appdir/app.yaml').InAnyOrder().AndReturn(10)
214 os.path.getmtime('/appdir/include.yaml').InAnyOrder().AndReturn(11)
216 self.mox.ReplayAll()
217 config = application_configuration.ModuleConfiguration('/appdir/app.yaml')
218 self.assertEqual({'/appdir/app.yaml': 10, '/appdir/include.yaml': 10},
219 config._mtimes)
220 config._mtimes = collections.OrderedDict([('/appdir/app.yaml', 10),
221 ('/appdir/include.yaml', 10)])
222 self.assertSequenceEqual(set(), config.check_for_updates())
223 self.mox.VerifyAll()
224 self.assertEqual({'/appdir/app.yaml': 10, '/appdir/include.yaml': 11},
225 config._mtimes)
227 def test_check_for_updates_no_changes(self):
228 info = appinfo.AppInfoExternal(
229 application='app',
230 module='default',
231 version='version',
232 runtime='python27',
233 threadsafe=False)
234 appinfo_includes.ParseAndReturnIncludePaths(mox.IgnoreArg()).AndReturn(
235 (info, []))
236 os.path.getmtime('/appdir/app.yaml').AndReturn(10)
237 os.path.getmtime('/appdir/app.yaml').AndReturn(11)
238 appinfo_includes.ParseAndReturnIncludePaths(mox.IgnoreArg()).AndReturn(
239 (info, []))
240 os.path.getmtime('/appdir/app.yaml').AndReturn(11)
242 self.mox.ReplayAll()
243 config = application_configuration.ModuleConfiguration('/appdir/app.yaml')
244 self.assertSequenceEqual(set(), config.check_for_updates())
245 self.mox.VerifyAll()
246 self.assertEqual({'/appdir/app.yaml': 11}, config._mtimes)
248 def test_check_for_updates_immutable_changes(self):
249 automatic_scaling1 = appinfo.AutomaticScaling(
250 min_pending_latency='0.1s',
251 max_pending_latency='1.0s',
252 min_idle_instances=1,
253 max_idle_instances=2)
254 info1 = appinfo.AppInfoExternal(
255 application='app',
256 module='default',
257 version='version',
258 runtime='python27',
259 threadsafe=False,
260 automatic_scaling=automatic_scaling1)
262 info2 = appinfo.AppInfoExternal(
263 application='app2',
264 module='default2',
265 version='version2',
266 runtime='python',
267 threadsafe=True,
268 automatic_scaling=appinfo.AutomaticScaling(
269 min_pending_latency='1.0s',
270 max_pending_latency='2.0s',
271 min_idle_instances=1,
272 max_idle_instances=2))
274 appinfo_includes.ParseAndReturnIncludePaths(mox.IgnoreArg()).AndReturn(
275 (info1, []))
276 os.path.getmtime('/appdir/app.yaml').AndReturn(10)
277 os.path.getmtime('/appdir/app.yaml').AndReturn(11)
278 appinfo_includes.ParseAndReturnIncludePaths(mox.IgnoreArg()).AndReturn(
279 (info2, []))
280 os.path.getmtime('/appdir/app.yaml').AndReturn(11)
282 self.mox.ReplayAll()
283 config = application_configuration.ModuleConfiguration('/appdir/app.yaml')
284 self.assertSequenceEqual(set(), config.check_for_updates())
285 self.mox.VerifyAll()
287 self.assertEqual('dev~app', config.application)
288 self.assertEqual('default', config.module_name)
289 self.assertEqual('version', config.major_version)
290 self.assertRegexpMatches(config.version_id, r'^version\.\d+$')
291 self.assertEqual('python27', config.runtime)
292 self.assertFalse(config.threadsafe)
293 self.assertEqual(automatic_scaling1, config.automatic_scaling)
295 def test_check_for_updates_mutable_changes(self):
296 info1 = appinfo.AppInfoExternal(
297 application='app',
298 module='default',
299 version='version',
300 runtime='python27',
301 threadsafe=False,
302 libraries=[appinfo.Library(name='django', version='latest')],
303 skip_files='.*',
304 handlers=[],
305 inbound_services=['warmup'],
306 env_variables=appinfo.EnvironmentVariables(),
307 error_handlers=[appinfo.ErrorHandlers(file='error.html')],
309 info2 = appinfo.AppInfoExternal(
310 application='app',
311 module='default',
312 version='version',
313 runtime='python27',
314 threadsafe=False,
315 libraries=[appinfo.Library(name='jinja2', version='latest')],
316 skip_files=r'.*\.py',
317 handlers=[appinfo.URLMap()],
318 inbound_services=[],
321 appinfo_includes.ParseAndReturnIncludePaths(mox.IgnoreArg()).AndReturn(
322 (info1, []))
323 os.path.getmtime('/appdir/app.yaml').AndReturn(10)
324 os.path.getmtime('/appdir/app.yaml').AndReturn(11)
325 appinfo_includes.ParseAndReturnIncludePaths(mox.IgnoreArg()).AndReturn(
326 (info2, []))
327 os.path.getmtime('/appdir/app.yaml').AndReturn(11)
329 self.mox.ReplayAll()
330 config = application_configuration.ModuleConfiguration('/appdir/app.yaml')
331 self.assertSequenceEqual(
332 set([application_configuration.NORMALIZED_LIBRARIES_CHANGED,
333 application_configuration.SKIP_FILES_CHANGED,
334 application_configuration.HANDLERS_CHANGED,
335 application_configuration.INBOUND_SERVICES_CHANGED,
336 application_configuration.ENV_VARIABLES_CHANGED,
337 application_configuration.ERROR_HANDLERS_CHANGED]),
338 config.check_for_updates())
339 self.mox.VerifyAll()
341 self.assertEqual(info2.GetNormalizedLibraries(),
342 config.normalized_libraries)
343 self.assertEqual(info2.skip_files, config.skip_files)
344 self.assertEqual(info2.error_handlers, config.error_handlers)
345 self.assertEqual(info2.handlers, config.handlers)
346 self.assertEqual(info2.inbound_services, config.inbound_services)
347 self.assertEqual(info2.env_variables, config.env_variables)
351 class TestBackendsConfiguration(unittest.TestCase):
352 def setUp(self):
353 self.mox = mox.Mox()
354 self.mox.StubOutWithMock(
355 application_configuration.BackendsConfiguration,
356 '_parse_configuration')
357 self.mox.StubOutWithMock(application_configuration, 'BackendConfiguration')
359 def tearDown(self):
360 self.mox.UnsetStubs()
362 def test_good_configuration(self):
363 self.mox.StubOutWithMock(application_configuration, 'ModuleConfiguration')
364 static_backend_entry = backendinfo.BackendEntry(name='static')
365 dynamic_backend_entry = backendinfo.BackendEntry(name='dynamic')
366 backend_info = backendinfo.BackendInfoExternal(
367 backends=[static_backend_entry, dynamic_backend_entry])
368 module_config = object()
369 application_configuration.ModuleConfiguration(
370 '/appdir/app.yaml', None).AndReturn(module_config)
371 application_configuration.BackendsConfiguration._parse_configuration(
372 '/appdir/backends.yaml').AndReturn(backend_info)
373 static_configuration = object()
374 dynamic_configuration = object()
375 application_configuration.BackendConfiguration(
376 module_config,
377 mox.IgnoreArg(),
378 static_backend_entry).InAnyOrder().AndReturn(static_configuration)
379 application_configuration.BackendConfiguration(
380 module_config,
381 mox.IgnoreArg(),
382 dynamic_backend_entry).InAnyOrder().AndReturn(dynamic_configuration)
384 self.mox.ReplayAll()
385 config = application_configuration.BackendsConfiguration(
386 '/appdir/app.yaml',
387 '/appdir/backends.yaml')
388 self.assertItemsEqual([static_configuration, dynamic_configuration],
389 config.get_backend_configurations())
390 self.mox.VerifyAll()
392 def test_no_backends(self):
393 self.mox.StubOutWithMock(application_configuration, 'ModuleConfiguration')
394 backend_info = backendinfo.BackendInfoExternal()
395 module_config = object()
396 application_configuration.ModuleConfiguration(
397 '/appdir/app.yaml', None).AndReturn(module_config)
398 application_configuration.BackendsConfiguration._parse_configuration(
399 '/appdir/backends.yaml').AndReturn(backend_info)
401 self.mox.ReplayAll()
402 config = application_configuration.BackendsConfiguration(
403 '/appdir/app.yaml',
404 '/appdir/backends.yaml')
405 self.assertEqual([], config.get_backend_configurations())
406 self.mox.VerifyAll()
408 def test_check_for_changes(self):
409 static_backend_entry = backendinfo.BackendEntry(name='static')
410 dynamic_backend_entry = backendinfo.BackendEntry(name='dynamic')
411 backend_info = backendinfo.BackendInfoExternal(
412 backends=[static_backend_entry, dynamic_backend_entry])
413 module_config = self.mox.CreateMock(
414 application_configuration.ModuleConfiguration)
415 self.mox.StubOutWithMock(application_configuration, 'ModuleConfiguration')
416 application_configuration.ModuleConfiguration(
417 '/appdir/app.yaml', None).AndReturn(module_config)
418 application_configuration.BackendsConfiguration._parse_configuration(
419 '/appdir/backends.yaml').AndReturn(backend_info)
420 module_config.check_for_updates().AndReturn(set())
421 module_config.check_for_updates().AndReturn(set([1]))
422 module_config.check_for_updates().AndReturn(set([2]))
423 module_config.check_for_updates().AndReturn(set())
425 self.mox.ReplayAll()
426 config = application_configuration.BackendsConfiguration(
427 '/appdir/app.yaml',
428 '/appdir/backends.yaml')
429 self.assertEqual(set(), config.check_for_updates('dynamic'))
430 self.assertEqual(set([1]), config.check_for_updates('static'))
431 self.assertEqual(set([1, 2]), config.check_for_updates('dynamic'))
432 self.assertEqual(set([2]), config.check_for_updates('static'))
433 self.mox.VerifyAll()
436 class TestDispatchConfiguration(unittest.TestCase):
437 def setUp(self):
438 self.mox = mox.Mox()
439 self.mox.StubOutWithMock(os.path, 'getmtime')
440 self.mox.StubOutWithMock(
441 application_configuration.DispatchConfiguration,
442 '_parse_configuration')
444 def tearDown(self):
445 self.mox.UnsetStubs()
447 def test_good_configuration(self):
448 info = dispatchinfo.DispatchInfoExternal(
449 application='appid',
450 dispatch=[
451 dispatchinfo.DispatchEntry(url='*/path', module='foo'),
452 dispatchinfo.DispatchEntry(url='domain.com/path', module='bar'),
453 dispatchinfo.DispatchEntry(url='*/path/*', module='baz'),
454 dispatchinfo.DispatchEntry(url='*.domain.com/path/*', module='foo'),
457 os.path.getmtime('/appdir/dispatch.yaml').AndReturn(123.456)
458 application_configuration.DispatchConfiguration._parse_configuration(
459 '/appdir/dispatch.yaml').AndReturn(info)
461 self.mox.ReplayAll()
462 config = application_configuration.DispatchConfiguration(
463 '/appdir/dispatch.yaml')
464 self.mox.VerifyAll()
466 self.assertEqual(123.456, config._mtime)
467 self.assertEqual(2, len(config.dispatch))
468 self.assertEqual(vars(dispatchinfo.ParsedURL('*/path')),
469 vars(config.dispatch[0][0]))
470 self.assertEqual('foo', config.dispatch[0][1])
471 self.assertEqual(vars(dispatchinfo.ParsedURL('*/path/*')),
472 vars(config.dispatch[1][0]))
473 self.assertEqual('baz', config.dispatch[1][1])
475 def test_check_for_updates_no_modification(self):
476 info = dispatchinfo.DispatchInfoExternal(
477 application='appid',
478 dispatch=[])
480 os.path.getmtime('/appdir/dispatch.yaml').AndReturn(123.456)
481 application_configuration.DispatchConfiguration._parse_configuration(
482 '/appdir/dispatch.yaml').AndReturn(info)
483 os.path.getmtime('/appdir/dispatch.yaml').AndReturn(123.456)
485 self.mox.ReplayAll()
486 config = application_configuration.DispatchConfiguration(
487 '/appdir/dispatch.yaml')
488 config.check_for_updates()
489 self.mox.VerifyAll()
491 def test_check_for_updates_with_invalid_modification(self):
492 info = dispatchinfo.DispatchInfoExternal(
493 application='appid',
494 dispatch=[
495 dispatchinfo.DispatchEntry(url='*/path', module='bar'),
498 os.path.getmtime('/appdir/dispatch.yaml').AndReturn(123.456)
499 application_configuration.DispatchConfiguration._parse_configuration(
500 '/appdir/dispatch.yaml').AndReturn(info)
501 os.path.getmtime('/appdir/dispatch.yaml').AndReturn(124.456)
502 application_configuration.DispatchConfiguration._parse_configuration(
503 '/appdir/dispatch.yaml').AndRaise(Exception)
505 self.mox.ReplayAll()
506 config = application_configuration.DispatchConfiguration(
507 '/appdir/dispatch.yaml')
508 self.assertEqual('bar', config.dispatch[0][1])
509 config.check_for_updates()
510 self.mox.VerifyAll()
511 self.assertEqual('bar', config.dispatch[0][1])
513 def test_check_for_updates_with_modification(self):
514 info = dispatchinfo.DispatchInfoExternal(
515 application='appid',
516 dispatch=[
517 dispatchinfo.DispatchEntry(url='*/path', module='bar'),
519 new_info = dispatchinfo.DispatchInfoExternal(
520 application='appid',
521 dispatch=[
522 dispatchinfo.DispatchEntry(url='*/path', module='foo'),
525 os.path.getmtime('/appdir/dispatch.yaml').AndReturn(123.456)
526 application_configuration.DispatchConfiguration._parse_configuration(
527 '/appdir/dispatch.yaml').AndReturn(info)
528 os.path.getmtime('/appdir/dispatch.yaml').AndReturn(124.456)
529 application_configuration.DispatchConfiguration._parse_configuration(
530 '/appdir/dispatch.yaml').AndReturn(new_info)
532 self.mox.ReplayAll()
533 config = application_configuration.DispatchConfiguration(
534 '/appdir/dispatch.yaml')
535 self.assertEqual('bar', config.dispatch[0][1])
536 config.check_for_updates()
537 self.mox.VerifyAll()
538 self.assertEqual('foo', config.dispatch[0][1])
541 class TestBackendConfiguration(unittest.TestCase):
542 def setUp(self):
543 self.mox = mox.Mox()
544 self.mox.StubOutWithMock(
545 application_configuration.ModuleConfiguration,
546 '_parse_configuration')
547 self.mox.StubOutWithMock(os.path, 'getmtime')
549 def tearDown(self):
550 self.mox.UnsetStubs()
552 def test_good_configuration(self):
553 automatic_scaling = appinfo.AutomaticScaling(min_pending_latency='1.0s',
554 max_pending_latency='2.0s',
555 min_idle_instances=1,
556 max_idle_instances=2)
557 error_handlers = [appinfo.ErrorHandlers(file='error.html')]
558 handlers = [appinfo.URLMap()]
559 env_variables = appinfo.EnvironmentVariables()
560 info = appinfo.AppInfoExternal(
561 application='app',
562 module='module1',
563 version='1',
564 runtime='python27',
565 threadsafe=False,
566 automatic_scaling=automatic_scaling,
567 skip_files=r'\*.gif',
568 error_handlers=error_handlers,
569 handlers=handlers,
570 inbound_services=['warmup'],
571 env_variables=env_variables,
573 backend_entry = backendinfo.BackendEntry(
574 name='static',
575 instances='3',
576 options='public')
578 application_configuration.ModuleConfiguration._parse_configuration(
579 '/appdir/app.yaml').AndReturn((info, ['/appdir/app.yaml']))
580 os.path.getmtime('/appdir/app.yaml').AndReturn(10)
582 self.mox.ReplayAll()
583 module_config = application_configuration.ModuleConfiguration(
584 '/appdir/app.yaml')
585 config = application_configuration.BackendConfiguration(
586 module_config, None, backend_entry)
587 self.mox.VerifyAll()
589 self.assertEqual(os.path.realpath('/appdir'), config.application_root)
590 self.assertEqual('dev~app', config.application)
591 self.assertEqual('app', config.application_external_name)
592 self.assertEqual('dev', config.partition)
593 self.assertEqual('static', config.module_name)
594 self.assertEqual('1', config.major_version)
595 self.assertRegexpMatches(config.version_id, r'static:1\.\d+')
596 self.assertEqual('python27', config.runtime)
597 self.assertFalse(config.threadsafe)
598 self.assertEqual(None, config.automatic_scaling)
599 self.assertEqual(None, config.basic_scaling)
600 self.assertEqual(appinfo.ManualScaling(instances='3'),
601 config.manual_scaling)
602 self.assertEqual(info.GetNormalizedLibraries(),
603 config.normalized_libraries)
604 self.assertEqual(r'\*.gif', config.skip_files)
605 self.assertEqual(error_handlers, config.error_handlers)
606 self.assertEqual(handlers, config.handlers)
607 self.assertEqual(['warmup'], config.inbound_services)
608 self.assertEqual(env_variables, config.env_variables)
610 whitelist_fields = ['module_name', 'version_id', 'automatic_scaling',
611 'manual_scaling', 'basic_scaling', 'is_backend',
612 'minor_version']
613 # Check that all public attributes and methods in a ModuleConfiguration
614 # exist in a BackendConfiguration.
615 for field in dir(module_config):
616 if not field.startswith('_'):
617 self.assertTrue(hasattr(config, field), 'Missing field: %s' % field)
618 value = getattr(module_config, field)
619 if field not in whitelist_fields and not callable(value):
620 # Check that the attributes other than those in the whitelist have
621 # equal values in the BackendConfiguration to the ModuleConfiguration
622 # from which it inherits.
623 self.assertEqual(value, getattr(config, field))
625 def test_vm_app_yaml_configuration(self):
626 automatic_scaling = appinfo.AutomaticScaling(min_pending_latency='1.0s',
627 max_pending_latency='2.0s',
628 min_idle_instances=1,
629 max_idle_instances=2)
630 vm_settings = appinfo.VmSettings()
631 vm_settings['vm_runtime'] = 'myawesomeruntime'
632 info = appinfo.AppInfoExternal(
633 application='app',
634 module='module1',
635 version='1',
636 runtime='vm',
637 vm_settings=vm_settings,
638 threadsafe=False,
639 automatic_scaling=automatic_scaling,
641 backend_entry = backendinfo.BackendEntry(
642 name='static',
643 instances='3',
644 options='public')
645 application_configuration.ModuleConfiguration._parse_configuration(
646 '/appdir/app.yaml').AndReturn((info, ['/appdir/app.yaml']))
647 os.path.getmtime('/appdir/app.yaml').AndReturn(10)
649 self.mox.ReplayAll()
650 module_config = application_configuration.ModuleConfiguration(
651 '/appdir/app.yaml')
652 config = application_configuration.BackendConfiguration(
653 module_config, None, backend_entry)
655 self.mox.VerifyAll()
656 self.assertEqual(os.path.realpath('/appdir'), config.application_root)
657 self.assertEqual('dev~app', config.application)
658 self.assertEqual('app', config.application_external_name)
659 self.assertEqual('dev', config.partition)
660 self.assertEqual('static', config.module_name)
661 self.assertEqual('1', config.major_version)
662 self.assertRegexpMatches(config.version_id, r'static:1\.\d+')
663 self.assertEqual('vm', config.runtime)
664 self.assertEqual(vm_settings['vm_runtime'], config.effective_runtime)
665 self.assertFalse(config.threadsafe)
666 # Resident backends are assigned manual scaling.
667 self.assertEqual(None, config.automatic_scaling)
668 self.assertEqual(None, config.basic_scaling)
669 self.assertEqual(appinfo.ManualScaling(instances='3'),
670 config.manual_scaling)
672 def test_good_configuration_dynamic_scaling(self):
673 automatic_scaling = appinfo.AutomaticScaling(min_pending_latency='1.0s',
674 max_pending_latency='2.0s',
675 min_idle_instances=1,
676 max_idle_instances=2)
677 error_handlers = [appinfo.ErrorHandlers(file='error.html')]
678 handlers = [appinfo.URLMap()]
679 env_variables = appinfo.EnvironmentVariables()
680 info = appinfo.AppInfoExternal(
681 application='app',
682 module='module1',
683 version='1',
684 runtime='python27',
685 threadsafe=False,
686 automatic_scaling=automatic_scaling,
687 skip_files=r'\*.gif',
688 error_handlers=error_handlers,
689 handlers=handlers,
690 inbound_services=['warmup'],
691 env_variables=env_variables,
693 backend_entry = backendinfo.BackendEntry(
694 name='dynamic',
695 instances='3',
696 options='public, dynamic',
697 start='handler')
699 application_configuration.ModuleConfiguration._parse_configuration(
700 '/appdir/app.yaml').AndReturn((info, ['/appdir/app.yaml']))
701 os.path.getmtime('/appdir/app.yaml').AndReturn(10)
703 self.mox.ReplayAll()
704 module_config = application_configuration.ModuleConfiguration(
705 '/appdir/app.yaml')
706 config = application_configuration.BackendConfiguration(
707 module_config, None, backend_entry)
708 self.mox.VerifyAll()
710 self.assertEqual(os.path.realpath('/appdir'), config.application_root)
711 self.assertEqual('dev~app', config.application)
712 self.assertEqual('dynamic', config.module_name)
713 self.assertEqual('1', config.major_version)
714 self.assertRegexpMatches(config.version_id, r'dynamic:1\.\d+')
715 self.assertEqual('python27', config.runtime)
716 self.assertFalse(config.threadsafe)
717 self.assertEqual(None, config.automatic_scaling)
718 self.assertEqual(None, config.manual_scaling)
719 self.assertEqual(appinfo.BasicScaling(max_instances='3'),
720 config.basic_scaling)
721 self.assertEqual(info.GetNormalizedLibraries(),
722 config.normalized_libraries)
723 self.assertEqual(r'\*.gif', config.skip_files)
724 self.assertEqual(error_handlers, config.error_handlers)
725 start_handler = appinfo.URLMap(url='/_ah/start',
726 script=backend_entry.start,
727 login='admin')
728 self.assertEqual([start_handler] + handlers, config.handlers)
729 self.assertEqual(['warmup'], config.inbound_services)
730 self.assertEqual(env_variables, config.env_variables)
732 def test_check_for_changes(self):
733 backends_config = self.mox.CreateMock(
734 application_configuration.BackendsConfiguration)
735 config = application_configuration.BackendConfiguration(
736 None, backends_config, backendinfo.BackendEntry(name='backend'))
737 changes = object()
738 backends_config.check_for_updates('backend').AndReturn([])
739 backends_config.check_for_updates('backend').AndReturn(changes)
740 minor_version = config.minor_version
741 self.mox.ReplayAll()
742 self.assertEqual([], config.check_for_updates())
743 self.assertEqual(minor_version, config.minor_version)
744 self.assertEqual(changes, config.check_for_updates())
745 self.assertNotEqual(minor_version, config.minor_version)
746 self.mox.VerifyAll()
749 class ModuleConfigurationStub(object):
750 def __init__(self, application='myapp', module_name='module'):
751 self.application = application
752 self.module_name = module_name
755 class DispatchConfigurationStub(object):
756 def __init__(self, dispatch):
757 self.dispatch = dispatch
760 class TestApplicationConfiguration(unittest.TestCase):
761 """Tests for application_configuration.ApplicationConfiguration."""
763 def setUp(self):
764 self.mox = mox.Mox()
765 self.mox.StubOutWithMock(application_configuration, 'ModuleConfiguration')
766 self.mox.StubOutWithMock(application_configuration, 'BackendsConfiguration')
767 self.mox.StubOutWithMock(application_configuration, 'DispatchConfiguration')
768 self.tmpdir = tempfile.mkdtemp(dir=os.getenv('TEST_TMPDIR'))
770 def tearDown(self):
771 self.mox.UnsetStubs()
772 shutil.rmtree(self.tmpdir)
774 def _make_file_hierarchy(self, filenames):
775 absnames = []
776 for filename in filenames:
777 absname = os.path.normpath(self.tmpdir + '/' + filename)
778 absnames += [absname]
779 dirname = os.path.dirname(absname)
780 if not os.path.exists(dirname):
781 os.makedirs(dirname)
782 open(absname, 'w').close()
783 return absnames
785 def test_yaml_files(self):
786 absnames = self._make_file_hierarchy(
787 ['appdir/app.yaml', 'appdir/other.yaml'])
789 module_config1 = ModuleConfigurationStub()
790 application_configuration.ModuleConfiguration(
791 absnames[0], None).AndReturn(module_config1)
793 module_config2 = ModuleConfigurationStub(module_name='other')
794 application_configuration.ModuleConfiguration(
795 absnames[1], None).AndReturn(module_config2)
797 self.mox.ReplayAll()
798 config = application_configuration.ApplicationConfiguration(
799 absnames)
800 self.mox.VerifyAll()
801 self.assertEqual('myapp', config.app_id)
802 self.assertSequenceEqual([module_config1, module_config2], config.modules)
804 def test_yaml_files_with_different_app_ids(self):
805 absnames = self._make_file_hierarchy(
806 ['appdir/app.yaml', 'appdir/other.yaml'])
808 module_config1 = ModuleConfigurationStub()
809 application_configuration.ModuleConfiguration(
810 absnames[0], None).AndReturn(module_config1)
812 module_config2 = ModuleConfigurationStub(application='other_app',
813 module_name='other')
814 application_configuration.ModuleConfiguration(
815 absnames[1], None).AndReturn(module_config2)
817 self.mox.ReplayAll()
818 self.assertRaises(errors.InvalidAppConfigError,
819 application_configuration.ApplicationConfiguration,
820 absnames)
821 self.mox.VerifyAll()
823 def test_yaml_files_with_duplicate_module_names(self):
824 absnames = self._make_file_hierarchy(
825 ['appdir/app.yaml', 'appdir/other.yaml'])
827 application_configuration.ModuleConfiguration(
828 absnames[0], None).AndReturn(ModuleConfigurationStub())
830 application_configuration.ModuleConfiguration(
831 absnames[1], None).AndReturn(ModuleConfigurationStub())
833 self.mox.ReplayAll()
834 self.assertRaises(errors.InvalidAppConfigError,
835 application_configuration.ApplicationConfiguration,
836 absnames)
837 self.mox.VerifyAll()
839 def test_directory(self):
840 absnames = self._make_file_hierarchy(['appdir/app.yaml'])
842 module_config = ModuleConfigurationStub()
843 application_configuration.ModuleConfiguration(
844 absnames[0], None).AndReturn(module_config)
846 self.mox.ReplayAll()
847 config = application_configuration.ApplicationConfiguration(
848 [os.path.dirname(absnames[0])])
849 self.mox.VerifyAll()
850 self.assertEqual('myapp', config.app_id)
851 self.assertSequenceEqual([module_config], config.modules)
853 def test_directory_and_module(self):
854 absnames = self._make_file_hierarchy(
855 ['appdir/app.yaml', 'otherdir/mymodule.yaml'])
857 app_yaml_config = ModuleConfigurationStub()
858 application_configuration.ModuleConfiguration(
859 absnames[0], None).AndReturn(app_yaml_config)
860 my_module_config = ModuleConfigurationStub(module_name='my_module')
861 application_configuration.ModuleConfiguration(
862 absnames[1], None).AndReturn(my_module_config)
863 self.mox.ReplayAll()
864 config = application_configuration.ApplicationConfiguration(
865 [os.path.dirname(absnames[0]), absnames[1]])
866 self.mox.VerifyAll()
867 self.assertSequenceEqual(
868 [app_yaml_config, my_module_config], config.modules)
870 def test_directory_app_yml_only(self):
871 absnames = self._make_file_hierarchy(['appdir/app.yml'])
873 module_config = ModuleConfigurationStub()
874 application_configuration.ModuleConfiguration(
875 absnames[0], None).AndReturn(module_config)
877 self.mox.ReplayAll()
878 config = application_configuration.ApplicationConfiguration(
879 [os.path.dirname(absnames[0])])
880 self.mox.VerifyAll()
881 self.assertEqual('myapp', config.app_id)
882 self.assertSequenceEqual([module_config], config.modules)
884 def test_directory_app_yaml_and_app_yml(self):
885 absnames = self._make_file_hierarchy(['appdir/app.yaml', 'appdir/app.yml'])
886 self.mox.ReplayAll()
887 self.assertRaises(errors.InvalidAppConfigError,
888 application_configuration.ApplicationConfiguration,
889 [os.path.dirname(absnames[0])])
890 self.mox.VerifyAll()
892 def test_directory_no_app_yamls(self):
893 absnames = self._make_file_hierarchy(['appdir/somethingelse.yaml'])
895 self.mox.ReplayAll()
896 self.assertRaises(errors.AppConfigNotFoundError,
897 application_configuration.ApplicationConfiguration,
898 [os.path.dirname(absnames[0])])
899 self.mox.VerifyAll()
901 def test_directory_no_app_yamls_or_web_inf(self):
902 absnames = self._make_file_hierarchy(['appdir/somethingelse.yaml'])
904 self.mox.ReplayAll()
905 with _java_temporarily_supported():
906 self.assertRaises(errors.AppConfigNotFoundError,
907 application_configuration.ApplicationConfiguration,
908 [os.path.dirname(absnames[0])])
909 self.mox.VerifyAll()
911 def test_app_yaml(self):
912 absnames = self._make_file_hierarchy(['appdir/app.yaml'])
914 module_config = ModuleConfigurationStub()
915 application_configuration.ModuleConfiguration(
916 absnames[0], None).AndReturn(module_config)
918 self.mox.ReplayAll()
919 config = application_configuration.ApplicationConfiguration(absnames)
920 self.mox.VerifyAll()
921 self.assertEqual('myapp', config.app_id)
922 self.assertSequenceEqual([module_config], config.modules)
924 def test_directory_with_backends_yaml(self):
925 absnames = self._make_file_hierarchy(
926 ['appdir/app.yaml', 'appdir/backends.yaml'])
928 module_config = ModuleConfigurationStub()
929 application_configuration.ModuleConfiguration(
930 absnames[0], None).AndReturn(module_config)
931 backend_config = ModuleConfigurationStub(module_name='backend')
932 backends_config = self.mox.CreateMock(
933 application_configuration.BackendsConfiguration)
934 backends_config.get_backend_configurations().AndReturn([backend_config])
935 application_configuration.BackendsConfiguration(
936 absnames[0], absnames[1], None).AndReturn(backends_config)
938 self.mox.ReplayAll()
939 config = application_configuration.ApplicationConfiguration(
940 [os.path.dirname(absnames[0])])
941 self.mox.VerifyAll()
942 self.assertEqual('myapp', config.app_id)
943 self.assertSequenceEqual([module_config, backend_config], config.modules)
945 def test_yaml_files_with_backends_yaml(self):
946 absnames = self._make_file_hierarchy(
947 ['appdir/app.yaml', 'appdir/backends.yaml'])
949 module_config = ModuleConfigurationStub()
950 application_configuration.ModuleConfiguration(
951 absnames[0], None).AndReturn(module_config)
953 backend_config = ModuleConfigurationStub(module_name='backend')
954 backends_config = self.mox.CreateMock(
955 application_configuration.BackendsConfiguration)
956 backends_config.get_backend_configurations().AndReturn([backend_config])
957 application_configuration.BackendsConfiguration(
958 absnames[0], absnames[1], None).AndReturn(backends_config)
960 self.mox.ReplayAll()
961 config = application_configuration.ApplicationConfiguration(absnames)
962 self.mox.VerifyAll()
963 self.assertEqual('myapp', config.app_id)
964 self.assertSequenceEqual([module_config, backend_config], config.modules)
966 def test_yaml_files_with_backends_and_dispatch_yaml(self):
967 absnames = self._make_file_hierarchy(
968 ['appdir/app.yaml', 'appdir/backends.yaml', 'appdir/dispatch.yaml'])
970 module_config = ModuleConfigurationStub(module_name='default')
971 application_configuration.ModuleConfiguration(
972 absnames[0], None).AndReturn(module_config)
974 backend_config = ModuleConfigurationStub(module_name='backend')
975 backends_config = self.mox.CreateMock(
976 application_configuration.BackendsConfiguration)
977 backends_config.get_backend_configurations().AndReturn([backend_config])
978 application_configuration.BackendsConfiguration(
979 absnames[0], absnames[1], None).AndReturn(backends_config)
980 dispatch_config = DispatchConfigurationStub(
981 [(None, 'default'), (None, 'backend')])
982 application_configuration.DispatchConfiguration(
983 absnames[2]).AndReturn(dispatch_config)
985 self.mox.ReplayAll()
986 config = application_configuration.ApplicationConfiguration(absnames)
987 self.mox.VerifyAll()
988 self.assertEqual('myapp', config.app_id)
989 self.assertSequenceEqual([module_config, backend_config], config.modules)
990 self.assertEqual(dispatch_config, config.dispatch)
992 def test_yaml_files_dispatch_yaml_and_no_default_module(self):
993 absnames = self._make_file_hierarchy(
994 ['appdir/app.yaml', 'appdir/dispatch.yaml'])
996 module_config = ModuleConfigurationStub(module_name='not-default')
997 application_configuration.ModuleConfiguration(
998 absnames[0], None).AndReturn(module_config)
1000 dispatch_config = DispatchConfigurationStub([(None, 'default')])
1001 application_configuration.DispatchConfiguration(
1002 absnames[1]).AndReturn(dispatch_config)
1004 self.mox.ReplayAll()
1005 self.assertRaises(errors.InvalidAppConfigError,
1006 application_configuration.ApplicationConfiguration,
1007 absnames)
1008 self.mox.VerifyAll()
1010 def test_yaml_files_dispatch_yaml_and_missing_dispatch_target(self):
1011 absnames = self._make_file_hierarchy(
1012 ['appdir/app.yaml', 'appdir/dispatch.yaml'])
1014 module_config = ModuleConfigurationStub(module_name='default')
1015 application_configuration.ModuleConfiguration(
1016 absnames[0], None).AndReturn(module_config)
1018 dispatch_config = DispatchConfigurationStub(
1019 [(None, 'default'), (None, 'fake-module')])
1020 application_configuration.DispatchConfiguration(
1021 absnames[1]).AndReturn(dispatch_config)
1023 self.mox.ReplayAll()
1024 self.assertRaises(errors.InvalidAppConfigError,
1025 application_configuration.ApplicationConfiguration,
1026 absnames)
1027 self.mox.VerifyAll()
1029 def test_directory_web_inf(self):
1030 absnames = self._make_file_hierarchy(
1031 ['appdir/WEB-INF/appengine-web.xml', 'appdir/WEB-INF/web.xml'])
1032 appdir = os.path.dirname(os.path.dirname(absnames[0]))
1034 module_config = ModuleConfigurationStub(module_name='default')
1035 application_configuration.ModuleConfiguration(
1036 absnames[0], None).AndReturn(module_config)
1038 self.mox.ReplayAll()
1039 with _java_temporarily_supported():
1040 config = application_configuration.ApplicationConfiguration([appdir])
1041 self.mox.VerifyAll()
1043 self.assertEqual('myapp', config.app_id)
1044 self.assertSequenceEqual([module_config], config.modules)
1046 def test_directory_web_inf_missing_appengine_xml(self):
1047 absnames = self._make_file_hierarchy(['appdir/WEB-INF/web.xml'])
1048 appdir = os.path.dirname(os.path.dirname(absnames[0]))
1050 self.mox.ReplayAll()
1051 with _java_temporarily_supported():
1052 self.assertRaises(errors.AppConfigNotFoundError,
1053 application_configuration.ApplicationConfiguration,
1054 [appdir])
1055 self.mox.VerifyAll()
1057 def test_directory_web_inf_missing_web_xml(self):
1058 absnames = self._make_file_hierarchy(['appdir/WEB-INF/appengine-web.xml'])
1059 appdir = os.path.dirname(os.path.dirname(absnames[0]))
1061 self.mox.ReplayAll()
1062 with _java_temporarily_supported():
1063 self.assertRaises(errors.AppConfigNotFoundError,
1064 application_configuration.ApplicationConfiguration,
1065 [appdir])
1066 self.mox.VerifyAll()
1068 def test_config_with_yaml_and_xml(self):
1069 absnames = self._make_file_hierarchy(
1070 ['module1/app.yaml', 'module1/dispatch.yaml',
1071 'module2/WEB-INF/appengine-web.xml', 'module2/WEB-INF/web.xml'])
1072 app_yaml = absnames[0]
1073 dispatch_yaml = absnames[1]
1074 appengine_web_xml = absnames[2]
1075 module2 = os.path.dirname(os.path.dirname(appengine_web_xml))
1077 module1_config = ModuleConfigurationStub(module_name='default')
1078 application_configuration.ModuleConfiguration(
1079 app_yaml, None).AndReturn(module1_config)
1080 dispatch_config = DispatchConfigurationStub(
1081 [(None, 'default'), (None, 'module2')])
1082 application_configuration.DispatchConfiguration(
1083 dispatch_yaml).AndReturn(dispatch_config)
1084 module2_config = ModuleConfigurationStub(module_name='module2')
1085 application_configuration.ModuleConfiguration(
1086 appengine_web_xml, None).AndReturn(module2_config)
1088 self.mox.ReplayAll()
1089 with _java_temporarily_supported():
1090 config = application_configuration.ApplicationConfiguration(
1091 [app_yaml, dispatch_yaml, module2])
1092 self.mox.VerifyAll()
1094 self.assertEqual('myapp', config.app_id)
1095 self.assertSequenceEqual(
1096 [module1_config, module2_config], config.modules)
1097 self.assertEqual(dispatch_config, config.dispatch)
1100 if __name__ == '__main__':
1101 unittest.main()