def test_check_for_mutable_changes(self): info1 = appinfo.AppInfoExternal( application='app', module='default', version='version', runtime='python27', threadsafe=False, libraries=[appinfo.Library(name='django', version='latest')], skip_files='.*', handlers=[], inbound_services=['warmup'], env_variables=appinfo.EnvironmentVariables(), error_handlers=[appinfo.ErrorHandlers(file='error.html')], ) info2 = appinfo.AppInfoExternal( application='app', module='default', version='version', runtime='python27', threadsafe=False, libraries=[appinfo.Library(name='jinja2', version='latest')], skip_files=r'.*\.py', handlers=[appinfo.URLMap()], inbound_services=[], ) application_configuration.ModuleConfiguration._parse_configuration( '/appdir/app.yaml').AndReturn((info1, [])) os.path.getmtime('/appdir/app.yaml').AndReturn(10) os.path.getmtime('/appdir/app.yaml').AndReturn(11) application_configuration.ModuleConfiguration._parse_configuration( '/appdir/app.yaml').AndReturn((info2, [])) os.path.getmtime('/appdir/app.yaml').AndReturn(11) self.mox.ReplayAll() config = application_configuration.ModuleConfiguration( '/appdir/app.yaml') self.assertSequenceEqual( set([ application_configuration.NORMALIZED_LIBRARIES_CHANGED, application_configuration.SKIP_FILES_CHANGED, application_configuration.HANDLERS_CHANGED, application_configuration.INBOUND_SERVICES_CHANGED, application_configuration.ENV_VARIABLES_CHANGED, application_configuration.ERROR_HANDLERS_CHANGED ]), config.check_for_updates()) self.mox.VerifyAll() self.assertEqual(info2.GetNormalizedLibraries(), config.normalized_libraries) self.assertEqual(info2.skip_files, config.skip_files) self.assertEqual(info2.error_handlers, config.error_handlers) self.assertEqual(info2.handlers, config.handlers) self.assertEqual(info2.inbound_services, config.inbound_services) self.assertEqual(info2.env_variables, config.env_variables)
def test_check_for_updates_with_includes(self): info = appinfo.AppInfoExternal(application='app', module='default', version='version', runtime='python27', includes=['/appdir/include.yaml'], threadsafe=False) application_configuration.ModuleConfiguration._parse_configuration( '/appdir/app.yaml').AndReturn((info, ['/appdir/include.yaml'])) os.path.getmtime('/appdir/app.yaml').InAnyOrder().AndReturn(10) os.path.getmtime('/appdir/include.yaml').InAnyOrder().AndReturn(10) os.path.getmtime('/appdir/app.yaml').AndReturn(10) os.path.getmtime('/appdir/include.yaml').AndReturn(11) application_configuration.ModuleConfiguration._parse_configuration( '/appdir/app.yaml').AndReturn((info, ['/appdir/include.yaml'])) os.path.getmtime('/appdir/app.yaml').InAnyOrder().AndReturn(10) os.path.getmtime('/appdir/include.yaml').InAnyOrder().AndReturn(11) self.mox.ReplayAll() config = application_configuration.ModuleConfiguration( '/appdir/app.yaml') self.assertEqual({ '/appdir/app.yaml': 10, '/appdir/include.yaml': 10 }, config._mtimes) config._mtimes = collections.OrderedDict([('/appdir/app.yaml', 10), ('/appdir/include.yaml', 10) ]) self.assertSequenceEqual(set(), config.check_for_updates()) self.mox.VerifyAll() self.assertEqual({ '/appdir/app.yaml': 10, '/appdir/include.yaml': 11 }, config._mtimes)
def test_vm_health_check_taken_into_account(self): manual_scaling = appinfo.ManualScaling() vm_settings = appinfo.VmSettings() vm_settings['vm_runtime'] = 'myawesomeruntime' vm_settings['forwarded_ports'] = '49111:49111,5002:49112,8000' vm_health_check = appinfo.VmHealthCheck(enable_health_check=False) info = appinfo.AppInfoExternal( application='app', module='module1', version='1', runtime='vm', vm_settings=vm_settings, threadsafe=False, manual_scaling=manual_scaling, vm_health_check=vm_health_check ) appinfo_includes.ParseAndReturnIncludePaths(mox.IgnoreArg()).AndReturn( (info, [])) os.path.getmtime('/appdir/app.yaml').AndReturn(10) self.mox.ReplayAll() config = application_configuration.ModuleConfiguration('/appdir/app.yaml') self.mox.VerifyAll() # tests if it is not overriden from the defaults of health_check self.assertIs(config.health_check.enable_health_check, False)
def test_connection_error_process_quit(self): self.proxy = http_runtime.HttpRuntimeProxy(['/runtime'], self.runtime_config_getter, appinfo.AppInfoExternal()) self.proxy._process = self.mox.CreateMockAnything() self.proxy._port = 123 login.get_user_info(None).AndReturn(('', False, '')) httplib.HTTPConnection.connect().AndRaise(socket.error()) self.proxy._process.poll().AndReturn(1) shutdown.async_quit() httplib.HTTPConnection.close() self.mox.ReplayAll() expected_headers = { 'Content-Type': 'text/plain', 'Content-Length': '110', } expected_content = ( 'the runtime process for the instance running on port ' '123 has unexpectedly quit; exiting the development ' 'server') self.assertResponse('500 Internal Server Error', expected_headers, expected_content, self.proxy.handle, {'PATH_INFO': '/'}, url_map=self.url_map, match=re.match(self.url_map.url, '/get%20error'), request_id='request id', request_type=instance.NORMAL_REQUEST) self.mox.VerifyAll()
def test_good_configuration_dynamic_scaling(self): automatic_scaling = appinfo.AutomaticScaling( min_pending_latency='1.0s', max_pending_latency='2.0s', min_idle_instances=1, max_idle_instances=2) error_handlers = [appinfo.ErrorHandlers(file='error.html')] handlers = [appinfo.URLMap()] env_variables = appinfo.EnvironmentVariables() info = appinfo.AppInfoExternal( application='app', module='module1', version='1', runtime='python27', threadsafe=False, automatic_scaling=automatic_scaling, skip_files=r'\*.gif', error_handlers=error_handlers, handlers=handlers, inbound_services=['warmup'], env_variables=env_variables, ) backend_entry = backendinfo.BackendEntry(name='dynamic', instances='3', options='public, dynamic', start='handler') application_configuration.ModuleConfiguration._parse_configuration( '/appdir/app.yaml').AndReturn((info, [])) os.path.getmtime('/appdir/app.yaml').AndReturn(10) self.mox.ReplayAll() module_config = application_configuration.ModuleConfiguration( '/appdir/app.yaml') config = application_configuration.BackendConfiguration( module_config, None, backend_entry) self.mox.VerifyAll() self.assertEqual(os.path.realpath('/appdir'), config.application_root) self.assertEqual('app', config.application) self.assertEqual('dynamic', config.module_name) self.assertEqual('1', config.major_version) self.assertRegexpMatches(config.version_id, r'dynamic:1\.\d+') self.assertEqual('python27', config.runtime) self.assertFalse(config.threadsafe) self.assertEqual(None, config.automatic_scaling) self.assertEqual(None, config.manual_scaling) self.assertEqual(appinfo.BasicScaling(max_instances='3'), config.basic_scaling) self.assertEqual(info.GetNormalizedLibraries(), config.normalized_libraries) self.assertEqual(r'\*.gif', config.skip_files) self.assertEqual(error_handlers, config.error_handlers) start_handler = appinfo.URLMap(url='/_ah/start', script=backend_entry.start, login='******') self.assertEqual([start_handler] + handlers, config.handlers) self.assertEqual(['warmup'], config.inbound_services) self.assertEqual(env_variables, config.env_variables)
def test_check_for_updates_immutable_changes(self): automatic_scaling1 = appinfo.AutomaticScaling( min_pending_latency='0.1s', max_pending_latency='1.0s', min_idle_instances=1, max_idle_instances=2) info1 = appinfo.AppInfoExternal(application='app', module='default', version='version', runtime='python27', threadsafe=False, automatic_scaling=automatic_scaling1) info2 = appinfo.AppInfoExternal( application='app2', module='default2', version='version2', runtime='python', threadsafe=True, automatic_scaling=appinfo.AutomaticScaling( min_pending_latency='1.0s', max_pending_latency='2.0s', min_idle_instances=1, max_idle_instances=2)) application_configuration.ModuleConfiguration._parse_configuration( '/appdir/app.yaml').AndReturn((info1, [])) os.path.getmtime('/appdir/app.yaml').AndReturn(10) os.path.getmtime('/appdir/app.yaml').AndReturn(11) application_configuration.ModuleConfiguration._parse_configuration( '/appdir/app.yaml').AndReturn((info2, [])) os.path.getmtime('/appdir/app.yaml').AndReturn(11) self.mox.ReplayAll() config = application_configuration.ModuleConfiguration( '/appdir/app.yaml') self.assertSequenceEqual(set(), config.check_for_updates()) self.mox.VerifyAll() self.assertEqual('app', config.application) self.assertEqual('default', config.module_name) self.assertEqual('version', config.major_version) self.assertRegexpMatches(config.version_id, r'^version\.\d+$') self.assertEqual('python27', config.runtime) self.assertFalse(config.threadsafe) self.assertEqual(automatic_scaling1, config.automatic_scaling)
def test_good_app_yaml_configuration(self): automatic_scaling = appinfo.AutomaticScaling( min_pending_latency='1.0s', max_pending_latency='2.0s', min_idle_instances=1, max_idle_instances=2) error_handlers = [appinfo.ErrorHandlers(file='error.html')] handlers = [appinfo.URLMap()] env_variables = appinfo.EnvironmentVariables() info = appinfo.AppInfoExternal( application='app', module='module1', version='1', runtime='python27', threadsafe=False, automatic_scaling=automatic_scaling, skip_files=r'\*.gif', error_handlers=error_handlers, handlers=handlers, inbound_services=['warmup'], env_variables=env_variables, ) appinfo_includes.ParseAndReturnIncludePaths(mox.IgnoreArg()).AndReturn( (info, [])) os.path.getmtime('/appdir/app.yaml').AndReturn(10) self.mox.ReplayAll() config = application_configuration.ModuleConfiguration( '/appdir/app.yaml') self.mox.VerifyAll() self.assertEqual(os.path.realpath('/appdir'), config.application_root) self.assertEqual(os.path.realpath('/appdir/app.yaml'), config.config_path) self.assertEqual('dev~app', config.application) self.assertEqual('app', config.application_external_name) self.assertEqual('dev', config.partition) self.assertEqual('module1', config.module_name) self.assertEqual('1', config.major_version) self.assertRegexpMatches(config.version_id, r'module1:1\.\d+') self.assertEqual('python27', config.runtime) self.assertFalse(config.threadsafe) self.assertEqual(automatic_scaling, config.automatic_scaling) self.assertEqual(info.GetNormalizedLibraries(), config.normalized_libraries) self.assertEqual(r'\*.gif', config.skip_files) self.assertEqual(error_handlers, config.error_handlers) self.assertEqual(handlers, config.handlers) self.assertEqual(['warmup'], config.inbound_services) self.assertEqual(env_variables, config.env_variables) self.assertEqual({'/appdir/app.yaml': 10}, config._mtimes)
def test_check_for_updates_unchanged_mtime(self): info = appinfo.AppInfoExternal(application='app', module='default', version='version', runtime='python27', threadsafe=False) application_configuration.ModuleConfiguration._parse_configuration( '/appdir/app.yaml').AndReturn((info, [])) os.path.getmtime('/appdir/app.yaml').AndReturn(10) os.path.getmtime('/appdir/app.yaml').AndReturn(10) self.mox.ReplayAll() config = application_configuration.ModuleConfiguration( '/appdir/app.yaml') self.assertSequenceEqual(set(), config.check_for_updates()) self.mox.VerifyAll()
def test_override_app_id(self): info = appinfo.AppInfoExternal(application='ignored-app', module='default', version='version', runtime='python27', threadsafe=False) application_configuration.ModuleConfiguration._parse_configuration( '/appdir/app.yaml').AndReturn((info, ['/appdir/app.yaml'])) os.path.getmtime('/appdir/app.yaml').AndReturn(10) self.mox.ReplayAll() config = application_configuration.ModuleConfiguration( '/appdir/app.yaml', 'overriding-app') self.mox.VerifyAll() self.assertEqual('overriding-app', config.application_external_name) self.assertEqual('dev~overriding-app', config.application)
def test_vm_app_yaml_configuration(self): automatic_scaling = appinfo.AutomaticScaling(min_pending_latency='1.0s', max_pending_latency='2.0s', min_idle_instances=1, max_idle_instances=2) vm_settings = appinfo.VmSettings() vm_settings['vm_runtime'] = 'myawesomeruntime' info = appinfo.AppInfoExternal( application='app', module='module1', version='1', runtime='vm', vm_settings=vm_settings, threadsafe=False, automatic_scaling=automatic_scaling, ) backend_entry = backendinfo.BackendEntry( name='static', instances='3', options='public') application_configuration.ModuleConfiguration._parse_configuration( '/appdir/app.yaml').AndReturn((info, ['/appdir/app.yaml'])) os.path.getmtime('/appdir/app.yaml').AndReturn(10) self.mox.ReplayAll() module_config = application_configuration.ModuleConfiguration( '/appdir/app.yaml') config = application_configuration.BackendConfiguration( module_config, None, backend_entry) self.mox.VerifyAll() self.assertEqual(os.path.realpath('/appdir'), config.application_root) self.assertEqual('dev~app', config.application) self.assertEqual('app', config.application_external_name) self.assertEqual('dev', config.partition) self.assertEqual('static', config.module_name) self.assertEqual('1', config.major_version) self.assertRegexpMatches(config.version_id, r'static:1\.\d+') self.assertEqual('vm', config.runtime) self.assertEqual(vm_settings['vm_runtime'], config.effective_runtime) self.assertFalse(config.threadsafe) # Resident backends are assigned manual scaling. self.assertEqual(None, config.automatic_scaling) self.assertEqual(None, config.basic_scaling) self.assertEqual(appinfo.ManualScaling(instances='3'), config.manual_scaling)
def test_handle_with_error_no_error_handler(self): self.proxy = http_runtime.HttpRuntimeProxy(['/runtime'], self.runtime_config_getter, appinfo.AppInfoExternal()) self.proxy._port = 23456 response = FakeHttpResponse( 500, 'Internal Server Error', [(http_runtime_constants.ERROR_CODE_HEADER, '1')], '') login.get_user_info(None).AndReturn(('', False, '')) httplib.HTTPConnection.connect() httplib.HTTPConnection.request( 'GET', '/get%20error', '', { 'HEADER': 'value', http_runtime_constants.REQUEST_ID_HEADER: 'request id', 'X-AppEngine-Country': 'ZZ', 'X-Appengine-Internal-User-Email': '', 'X-Appengine-Internal-User-Id': '', 'X-Appengine-Internal-User-Is-Admin': '0', 'X-Appengine-Internal-User-Nickname': '', 'X-Appengine-Internal-User-Organization': '', 'X-APPENGINE-INTERNAL-SCRIPT': 'get.py', 'X-APPENGINE-INTERNAL-SERVER-NAME': 'localhost', 'X-APPENGINE-INTERNAL-SERVER-PORT': '8080', 'X-APPENGINE-INTERNAL-SERVER-PROTOCOL': 'HTTP/1.1', }) httplib.HTTPConnection.getresponse().AndReturn(response) httplib.HTTPConnection.close() environ = { 'HTTP_HEADER': 'value', 'PATH_INFO': '/get error', 'QUERY_STRING': '', 'HTTP_X_APPENGINE_INTERNAL_USER_ID': '123', 'SERVER_NAME': 'localhost', 'SERVER_PORT': '8080', 'SERVER_PROTOCOL': 'HTTP/1.1', } self.mox.ReplayAll() self.assertResponse('500 Internal Server Error', {}, '', self.proxy.handle, environ, url_map=self.url_map, match=re.match(self.url_map.url, '/get%20error'), request_id='request id', request_type=instance.NORMAL_REQUEST) self.mox.VerifyAll()
def test_vm_app_yaml_configuration_network(self): manual_scaling = appinfo.ManualScaling() vm_settings = appinfo.VmSettings() vm_settings['vm_runtime'] = 'myawesomeruntime' network = appinfo.Network() network.forwarded_ports = ['49111:49111', '5002:49112', 8000] health_check = appinfo.HealthCheck() health_check.enable_health_check = False info = appinfo.AppInfoExternal( application='app', module='module1', version='1', runtime='vm', vm_settings=vm_settings, threadsafe=False, manual_scaling=manual_scaling, health_check=health_check, network=network ) appinfo_includes.ParseAndReturnIncludePaths(mox.IgnoreArg()).AndReturn( (info, [])) os.path.getmtime('/appdir/app.yaml').AndReturn(10) self.mox.ReplayAll() config = application_configuration.ModuleConfiguration('/appdir/app.yaml') self.mox.VerifyAll() self.assertEqual(os.path.realpath('/appdir'), config.application_root) self.assertEqual(os.path.realpath('/appdir/app.yaml'), config.config_path) self.assertEqual('dev~app', config.application) self.assertEqual('app', config.application_external_name) self.assertEqual('dev', config.partition) self.assertEqual('module1', config.module_name) self.assertEqual('1', config.major_version) self.assertRegexpMatches(config.version_id, r'module1:1\.\d+') self.assertEqual('vm', config.runtime) self.assertEqual(vm_settings['vm_runtime'], config.effective_runtime) self.assertItemsEqual( {49111: 49111, 5002: 49112, 8000: 8000}, config.forwarded_ports) self.assertFalse(config.threadsafe) self.assertEqual(manual_scaling, config.manual_scaling) self.assertEqual({'/appdir/app.yaml': 10}, config._mtimes) self.assertEqual(info.health_check, config.health_check)
def test_connection_error(self): self.proxy = http_runtime.HttpRuntimeProxy( ['/runtime'], self.runtime_config_getter, appinfo.AppInfoExternal()) self.proxy._process = self.mox.CreateMockAnything() login.get_user_info(None).AndReturn(('', False, '')) httplib.HTTPConnection.connect().AndRaise(socket.error()) self.proxy._process.poll().AndReturn(None) httplib.HTTPConnection.close() self.mox.ReplayAll() self.assertRaises(socket.error, self.proxy.handle( {'PATH_INFO': '/'}, start_response=None, # Not used. url_map=self.url_map, match=re.match(self.url_map.url, '/get%20error'), request_id='request id', request_type=instance.NORMAL_REQUEST).next) self.mox.VerifyAll()
def test_check_for_updates_no_changes(self): info = appinfo.AppInfoExternal( application='app', module='default', version='version', runtime='python27', threadsafe=False) appinfo_includes.ParseAndReturnIncludePaths(mox.IgnoreArg()).AndReturn( (info, [])) os.path.getmtime('/appdir/app.yaml').AndReturn(10) os.path.getmtime('/appdir/app.yaml').AndReturn(11) appinfo_includes.ParseAndReturnIncludePaths(mox.IgnoreArg()).AndReturn( (info, [])) os.path.getmtime('/appdir/app.yaml').AndReturn(11) self.mox.ReplayAll() config = application_configuration.ModuleConfiguration('/appdir/app.yaml') self.assertSequenceEqual(set(), config.check_for_updates()) self.mox.VerifyAll() self.assertEqual({'/appdir/app.yaml': 11}, config._mtimes)
def test_vm_no_version(self): manual_scaling = appinfo.ManualScaling() info = appinfo.AppInfoExternal( application='app', module='module1', runtime='vm', threadsafe=False, manual_scaling=manual_scaling, ) appinfo_includes.ParseAndReturnIncludePaths(mox.IgnoreArg()).AndReturn( (info, [])) os.path.getmtime('/appdir/app.yaml').AndReturn(10) self.mox.StubOutWithMock(application_configuration, 'generate_version_id') application_configuration.generate_version_id().AndReturn( 'generated-version') self.mox.ReplayAll() config = application_configuration.ModuleConfiguration('/appdir/app.yaml') self.mox.VerifyAll() self.assertEqual(config.major_version, 'generated-version')
def test_override_app_id(self): info = appinfo.AppInfoExternal(application='ignored-app', module='default', version='version', runtime='python27', threadsafe=False) appinfo_includes.ParseAndReturnIncludePaths(mox.IgnoreArg()).AndReturn( (info, [])) os.path.getmtime('/appdir/app.yaml').AndReturn(10) os.path.getmtime('/appdir/app.yaml').AndReturn(20) os.path.getmtime('/appdir/app.yaml').AndReturn(20) appinfo_includes.ParseAndReturnIncludePaths(mox.IgnoreArg()).AndReturn( (info, [])) self.mox.ReplayAll() config = application_configuration.ModuleConfiguration( '/appdir/app.yaml', 'overriding-app') self.assertEqual('overriding-app', config.application_external_name) self.assertEqual('dev~overriding-app', config.application) config.check_for_updates() self.assertEqual('overriding-app', config.application_external_name) self.assertEqual('dev~overriding-app', config.application) self.mox.VerifyAll()
def test_vm_app_yaml_configuration(self): manual_scaling = appinfo.ManualScaling() vm_settings = appinfo.VmSettings() vm_settings['vm_runtime'] = 'myawesomeruntime' info = appinfo.AppInfoExternal( application='app', module='module1', version='1', runtime='vm', vm_settings=vm_settings, threadsafe=False, manual_scaling=manual_scaling, ) appinfo_includes.ParseAndReturnIncludePaths(mox.IgnoreArg()).AndReturn( (info, [])) os.path.getmtime('/appdir/app.yaml').AndReturn(10) self.mox.ReplayAll() config = application_configuration.ModuleConfiguration( '/appdir/app.yaml') self.mox.VerifyAll() self.assertEqual(os.path.realpath('/appdir'), config.application_root) self.assertEqual(os.path.realpath('/appdir/app.yaml'), config.config_path) self.assertEqual('dev~app', config.application) self.assertEqual('app', config.application_external_name) self.assertEqual('dev', config.partition) self.assertEqual('module1', config.module_name) self.assertEqual('1', config.major_version) self.assertRegexpMatches(config.version_id, r'module1:1\.\d+') self.assertEqual('vm', config.runtime) self.assertEqual(vm_settings['vm_runtime'], config.effective_runtime) self.assertFalse(config.threadsafe) self.assertEqual(manual_scaling, config.manual_scaling) self.assertEqual({'/appdir/app.yaml': 10}, config._mtimes)
def test_good_configuration(self): automatic_scaling = appinfo.AutomaticScaling( min_pending_latency='1.0s', max_pending_latency='2.0s', min_idle_instances=1, max_idle_instances=2) error_handlers = [appinfo.ErrorHandlers(file='error.html')] handlers = [appinfo.URLMap()] env_variables = appinfo.EnvironmentVariables() info = appinfo.AppInfoExternal( application='app', module='module1', version='1', runtime='python27', threadsafe=False, automatic_scaling=automatic_scaling, skip_files=r'\*.gif', error_handlers=error_handlers, handlers=handlers, inbound_services=['warmup'], env_variables=env_variables, ) backend_entry = backendinfo.BackendEntry(name='static', instances='3', options='public') application_configuration.ModuleConfiguration._parse_configuration( '/appdir/app.yaml').AndReturn((info, [])) os.path.getmtime('/appdir/app.yaml').AndReturn(10) self.mox.ReplayAll() module_config = application_configuration.ModuleConfiguration( '/appdir/app.yaml') config = application_configuration.BackendConfiguration( module_config, None, backend_entry) self.mox.VerifyAll() self.assertEqual(os.path.realpath('/appdir'), config.application_root) self.assertEqual('app', config.application) self.assertEqual('static', config.module_name) self.assertEqual('1', config.major_version) self.assertRegexpMatches(config.version_id, r'static:1\.\d+') self.assertEqual('python27', config.runtime) self.assertFalse(config.threadsafe) self.assertEqual(None, config.automatic_scaling) self.assertEqual(None, config.basic_scaling) self.assertEqual(appinfo.ManualScaling(instances='3'), config.manual_scaling) self.assertEqual(info.GetNormalizedLibraries(), config.normalized_libraries) self.assertEqual(r'\*.gif', config.skip_files) self.assertEqual(error_handlers, config.error_handlers) self.assertEqual(handlers, config.handlers) self.assertEqual(['warmup'], config.inbound_services) self.assertEqual(env_variables, config.env_variables) whitelist_fields = [ 'module_name', 'version_id', 'automatic_scaling', 'manual_scaling', 'basic_scaling', 'is_backend' ] # Check that all public attributes and methods in a ModuleConfiguration # exist in a BackendConfiguration. for field in dir(module_config): if not field.startswith('_'): self.assertTrue(hasattr(config, field), 'Missing field: %s' % field) value = getattr(module_config, field) if field not in whitelist_fields and not callable(value): # Check that the attributes other than those in the whitelist have # equal values in the BackendConfiguration to the ModuleConfiguration # from which it inherits. self.assertEqual(value, getattr(config, field))