ext: Update validate_extension to validate_extension_data

Adds more checks to catch extension errors and importantly tests for exercise
these code paths.
This commit is contained in:
Thomas Adamcik 2015-05-11 22:30:50 +02:00
parent 4566ddd9ae
commit 8b6553ec16
3 changed files with 68 additions and 22 deletions

View File

@ -95,7 +95,7 @@ def main():
extension = data.extension extension = data.extension
# TODO: factor out all of this to a helper that can be tested # TODO: factor out all of this to a helper that can be tested
if not ext.validate_extension(data.extension, data.entry_point): if not ext.validate_extension_data(data):
config[extension.ext_name] = {'enabled': False} config[extension.ext_name] = {'enabled': False}
config_errors[extension.ext_name] = { config_errors[extension.ext_name] = {
'enabled': 'extension disabled by self check.'} 'enabled': 'extension disabled by self check.'}

View File

@ -184,47 +184,70 @@ def load_extensions():
return installed_extensions return installed_extensions
def validate_extension(extension, entry_point): def validate_extension_data(data):
"""Verify extension's dependencies and environment. """Verify extension's dependencies and environment.
:param extensions: an extension to check :param extensions: an extension to check
:returns: if extension should be run :returns: if extension should be run
""" """
logger.debug('Validating extension: %s', extension.ext_name) logger.debug('Validating extension: %s', data.extension.ext_name)
if extension.ext_name != entry_point.name: if data.extension.ext_name != data.entry_point.name:
logger.warning( logger.warning(
'Disabled extension %(ep)s: entry point name (%(ep)s) ' 'Disabled extension %(ep)s: entry point name (%(ep)s) '
'does not match extension name (%(ext)s)', 'does not match extension name (%(ext)s)',
{'ep': entry_point.name, 'ext': extension.ext_name}) {'ep': data.entry_point.name, 'ext': data.extension.ext_name})
return False return False
try: try:
entry_point.require() data.entry_point.require()
except pkg_resources.DistributionNotFound as ex: except pkg_resources.DistributionNotFound as ex:
logger.info( logger.info(
'Disabled extension %s: Dependency %s not found', 'Disabled extension %s: Dependency %s not found',
extension.ext_name, ex) data.extension.ext_name, ex)
return False return False
except pkg_resources.VersionConflict as ex: except pkg_resources.VersionConflict as ex:
if len(ex.args) == 2: if len(ex.args) == 2:
found, required = ex.args found, required = ex.args
logger.info( logger.info(
'Disabled extension %s: %s required, but found %s at %s', 'Disabled extension %s: %s required, but found %s at %s',
extension.ext_name, required, found, found.location) data.extension.ext_name, required, found, found.location)
else: else:
logger.info( logger.info(
'Disabled extension %s: %s', extension.ext_name, ex) 'Disabled extension %s: %s', data.extension.ext_name, ex)
return False return False
try: try:
extension.validate_environment() data.extension.validate_environment()
except exceptions.ExtensionError as ex: except exceptions.ExtensionError as ex:
logger.info( logger.info(
'Disabled extension %s: %s', extension.ext_name, ex.message) 'Disabled extension %s: %s', data.extension.ext_name, ex.message)
return False return False
except Exception: except Exception:
return False # TODO: log logger.exception('Validating extension %s failed with an exception.',
data.extension.ext_name)
return False
if not data.config_schema:
logger.error('Extension %s does not have a config schema, disabling.',
data.extension.ext_name)
return False
elif not isinstance(data.config_schema.get('enabled'), config_lib.Boolean):
logger.error('Extension %s does not have the required "enabled" config'
' option, disabling.', data.extension.ext_name)
return False
for key, value in data.config_schema.items():
if not isinstance(value, config_lib.ConfigValue):
logger.error('Extension %s config schema contains an invalid value'
' for the option "%s", disabling.',
data.extension.ext_name, key)
return False
if not data.config_defaults:
logger.error('Extension %s does not have a default config, disabling.',
data.extension.ext_name)
return False
return True return True

View File

@ -160,7 +160,7 @@ def test_load_extensions_get_command_fails(iter_entry_points_mock):
get_command.assert_called_once_with() get_command.assert_called_once_with()
# ext.validate_extension # ext.validate_extension_data
@pytest.fixture @pytest.fixture
def ext_data(): def ext_data():
@ -178,18 +178,18 @@ def ext_data():
def test_validate_extension_name_mismatch(ext_data): def test_validate_extension_name_mismatch(ext_data):
ext_data.entry_point.name = 'barfoo' ext_data.entry_point.name = 'barfoo'
assert not ext.validate_extension(ext_data.extension, ext_data.entry_point) assert not ext.validate_extension_data(ext_data)
def test_validate_extension_distribution_not_found(ext_data): def test_validate_extension_distribution_not_found(ext_data):
error = pkg_resources.DistributionNotFound error = pkg_resources.DistributionNotFound
ext_data.entry_point.require.side_effect = error ext_data.entry_point.require.side_effect = error
assert not ext.validate_extension(ext_data.extension, ext_data.entry_point) assert not ext.validate_extension_data(ext_data)
def test_validate_extension_version_conflict(ext_data): def test_validate_extension_version_conflict(ext_data):
ext_data.entry_point.require.side_effect = pkg_resources.VersionConflict ext_data.entry_point.require.side_effect = pkg_resources.VersionConflict
assert not ext.validate_extension(ext_data.extension, ext_data.entry_point) assert not ext.validate_extension_data(ext_data)
def test_validate_extension_exception(ext_data): def test_validate_extension_exception(ext_data):
@ -197,8 +197,7 @@ def test_validate_extension_exception(ext_data):
# We trust that entry points are well behaved, so exception will bubble. # We trust that entry points are well behaved, so exception will bubble.
with pytest.raises(Exception): with pytest.raises(Exception):
assert not ext.validate_extension( assert not ext.validate_extension_data(ext_data)
ext_data.extension, ext_data.entry_point)
def test_validate_extension_instance_validate_env_ext_error(ext_data): def test_validate_extension_instance_validate_env_ext_error(ext_data):
@ -206,8 +205,7 @@ def test_validate_extension_instance_validate_env_ext_error(ext_data):
with mock.patch.object(extension, 'validate_environment') as validate: with mock.patch.object(extension, 'validate_environment') as validate:
validate.side_effect = exceptions.ExtensionError('error') validate.side_effect = exceptions.ExtensionError('error')
assert not ext.validate_extension( assert not ext.validate_extension_data(ext_data)
ext_data.extension, ext_data.entry_point)
validate.assert_called_once_with() validate.assert_called_once_with()
@ -216,6 +214,31 @@ def test_validate_extension_instance_validate_env_exception(ext_data):
with mock.patch.object(extension, 'validate_environment') as validate: with mock.patch.object(extension, 'validate_environment') as validate:
validate.side_effect = Exception validate.side_effect = Exception
assert not ext.validate_extension( assert not ext.validate_extension_data(ext_data)
ext_data.extension, ext_data.entry_point)
validate.assert_called_once_with() validate.assert_called_once_with()
def test_validate_extension_with_missing_schema(ext_data):
ext_data = ext_data._replace(config_schema=None)
assert not ext.validate_extension_data(ext_data)
def test_validate_extension_with_schema_that_is_missing_enabled(ext_data):
del ext_data.config_schema['enabled']
ext_data.config_schema['baz'] = config.String()
assert not ext.validate_extension_data(ext_data)
def test_validate_extension_with_schema_with_wrong_types(ext_data):
ext_data.config_schema['enabled'] = 123
assert not ext.validate_extension_data(ext_data)
def test_validate_extension_with_schema_with_invalid_type(ext_data):
ext_data.config_schema['baz'] = 123
assert not ext.validate_extension_data(ext_data)
def test_validate_extension_with_no_defaults(ext_data):
ext_data = ext_data._replace(config_defaults=None)
assert not ext.validate_extension_data(ext_data)