summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRichard Maw <richard.maw@codethink.co.uk>2014-01-07 16:47:54 (GMT)
committerRichard Maw <richard.maw@codethink.co.uk>2014-01-16 16:58:24 (GMT)
commit7fb78e54f4c7206ff391fd4edb0ece200109f1e7 (patch)
treec7746afa751caab8866efeddad188bd3d1111f34
parentbea188ba004eb1b8d3057a5cfa2c1a167ef72d14 (diff)
downloadmorph-7fb78e54f4c7206ff391fd4edb0ece200109f1e7.tar.gz
MorphologyLoader: Validate new fields
-rw-r--r--morphlib/morphloader.py116
-rw-r--r--morphlib/morphloader_tests.py94
2 files changed, 204 insertions, 6 deletions
diff --git a/morphlib/morphloader.py b/morphlib/morphloader.py
index e1ec15b..637544b 100644
--- a/morphlib/morphloader.py
+++ b/morphlib/morphloader.py
@@ -44,16 +44,33 @@ class UnknownKindError(morphlib.Error):
class MissingFieldError(morphlib.Error):
- def __init__(self, field, morphology):
+ def __init__(self, field, morphology_name):
+ self.field = field
+ self.morphology_name = morphology_name
self.msg = (
- 'Missing field %s from morphology %s' % (field, morphology))
+ 'Missing field %s from morphology %s' % (field, morphology_name))
class InvalidFieldError(morphlib.Error):
- def __init__(self, field, morphology):
+ def __init__(self, field, morphology_name):
+ self.field = field
+ self.morphology_name = morphology_name
self.msg = (
- 'Field %s not allowed in morphology %s' % (field, morphology))
+ 'Field %s not allowed in morphology %s' % (field, morphology_name))
+
+
+class InvalidTypeError(morphlib.Error):
+
+ def __init__(self, field, expected, actual, morphology_name):
+ self.field = field
+ self.expected = expected
+ self.actual = actual
+ self.morphology_name = morphology_name
+ self.msg = (
+ 'Field %s expected type %s, got %s in morphology %s' %
+ (field, expected, actual, morphology_name))
+
class ObsoleteFieldsError(morphlib.Error):
@@ -140,6 +157,16 @@ class EmptySystemError(morphlib.Error):
self, 'System %(system_name)s has no strata.' % locals())
+class MultipleValidationErrors(morphlib.Error):
+
+ def __init__(self, name, errors):
+ self.name = name
+ self.errors = errors
+ self.msg = 'Multiple errors when validating %(name)s:'
+ for error in errors:
+ self.msg += ('\t' + str(error))
+
+
class MorphologyLoader(object):
'''Load morphologies from disk, or save them back to disk.'''
@@ -193,6 +220,7 @@ class MorphologyLoader(object):
'chunks': [],
'description': '',
'build-depends': [],
+ 'products': [],
},
'system': {
'description': '',
@@ -356,8 +384,84 @@ class MorphologyLoader(object):
spec.get('alias', spec['name']),
morph.filename)
- def _validate_chunk(self, morph):
- pass
+ @classmethod
+ def _validate_chunk(cls, morphology):
+ errors = []
+
+ if 'products' in morphology:
+ cls._validate_products(morphology['name'],
+ morphology['products'], errors)
+
+ if len(errors) == 1:
+ raise errors[0]
+ elif errors:
+ raise MultipleValidationErrors(morphology['name'], errors)
+
+ @classmethod
+ def _validate_products(cls, morphology_name, products, errors):
+ '''Validate the products field is of the correct type.'''
+ if (not isinstance(products, collections.Iterable)
+ or isinstance(products, collections.Mapping)):
+ raise InvalidTypeError('products', list,
+ type(products), morphology_name)
+
+ for spec_index, spec in enumerate(products):
+
+ if not isinstance(spec, collections.Mapping):
+ e = InvalidTypeError('products[%d]' % spec_index,
+ dict, type(spec), morphology_name)
+ errors.append(e)
+ continue
+
+ cls._validate_products_spec_fields_exist(morphology_name,
+ spec_index, spec, errors)
+
+ if 'include' in spec:
+ cls._validate_products_specs_include(
+ morphology_name, spec_index, spec['include'], errors)
+
+ product_spec_required_fields = ('artifact', 'include')
+ @classmethod
+ def _validate_products_spec_fields_exist(
+ cls, morphology_name, spec_index, spec, errors):
+
+ given_fields = sorted(spec.iterkeys())
+ missing = (field for field in cls.product_spec_required_fields
+ if field not in given_fields)
+ for field in missing:
+ e = MissingFieldError('products[%d].%s' % (spec_index, field),
+ morphology_name)
+ errors.append(e)
+ unexpected = (field for field in given_fields
+ if field not in cls.product_spec_required_fields)
+ for field in unexpected:
+ e = InvalidFieldError('products[%d].%s' % (spec_index, field),
+ morphology_name)
+ errors.append(e)
+
+ @classmethod
+ def _validate_products_specs_include(cls, morphology_name, spec_index,
+ include_patterns, errors):
+ '''Validate that products' include field is a list of strings.'''
+ # Allow include to be most iterables, but not a mapping
+ # or a string, since iter of a mapping is just the keys,
+ # and the iter of a string is a 1 character length string,
+ # which would also validate as an iterable of strings.
+ if (not isinstance(include_patterns, collections.Iterable)
+ or isinstance(include_patterns, collections.Mapping)
+ or isinstance(include_patterns, basestring)):
+
+ e = InvalidTypeError('products[%d].include' % spec_index, list,
+ type(include_patterns), morphology_name)
+ errors.append(e)
+ else:
+ for pattern_index, pattern in enumerate(include_patterns):
+ pattern_path = ('products[%d].include[%d]' %
+ (spec_index, pattern_index))
+ if not isinstance(pattern, basestring):
+ e = InvalidTypeError(pattern_path, str,
+ type(pattern), morphology_name)
+ errors.append(e)
def _require_field(self, field, morphology):
if field not in morphology:
diff --git a/morphlib/morphloader_tests.py b/morphlib/morphloader_tests.py
index 907f376..c2fbc5e 100644
--- a/morphlib/morphloader_tests.py
+++ b/morphlib/morphloader_tests.py
@@ -78,6 +78,99 @@ build-system: dummy
self.assertRaises(
morphlib.morphloader.InvalidFieldError, self.loader.validate, m)
+ def test_validate_requires_products_list(self):
+ m = morphlib.morph3.Morphology(
+ kind='chunk',
+ name='foo',
+ products={
+ 'foo-runtime': ['.'],
+ 'foo-devel': ['.'],
+ })
+ with self.assertRaises(morphlib.morphloader.InvalidTypeError) as cm:
+ self.loader.validate(m)
+ e = cm.exception
+ self.assertEqual(e.field, 'products')
+ self.assertEqual(e.expected, list)
+ self.assertEqual(e.actual, dict)
+ self.assertEqual(e.morphology_name, 'foo')
+
+ def test_validate_requires_products_list_of_mappings(self):
+ m = morphlib.morph3.Morphology(
+ kind='chunk',
+ name='foo',
+ products=[
+ 'foo-runtime',
+ ])
+ with self.assertRaises(morphlib.morphloader.InvalidTypeError) as cm:
+ self.loader.validate(m)
+ e = cm.exception
+ self.assertEqual(e.field, 'products[0]')
+ self.assertEqual(e.expected, dict)
+ self.assertEqual(e.actual, str)
+ self.assertEqual(e.morphology_name, 'foo')
+
+ def test_validate_requires_products_list_required_fields(self):
+ m = morphlib.morph3.Morphology(
+ kind='chunk',
+ name='foo',
+ products=[
+ {
+ 'factiart': 'foo-runtime',
+ 'cludein': [],
+ }
+ ])
+ with self.assertRaises(morphlib.morphloader.MultipleValidationErrors) \
+ as cm:
+ self.loader.validate(m)
+ exs = cm.exception.errors
+ self.assertEqual(type(exs[0]), morphlib.morphloader.MissingFieldError)
+ self.assertEqual(exs[0].field, 'products[0].artifact')
+ self.assertEqual(type(exs[1]), morphlib.morphloader.MissingFieldError)
+ self.assertEqual(exs[1].field, 'products[0].include')
+ self.assertEqual(type(exs[2]), morphlib.morphloader.InvalidFieldError)
+ self.assertEqual(exs[2].field, 'products[0].cludein')
+ self.assertEqual(type(exs[3]), morphlib.morphloader.InvalidFieldError)
+ self.assertEqual(exs[3].field, 'products[0].factiart')
+
+ def test_validate_requires_products_list_include_is_list(self):
+ m = morphlib.morph3.Morphology(
+ kind='chunk',
+ name='foo',
+ products=[
+ {
+ 'artifact': 'foo-runtime',
+ 'include': '.*',
+ }
+ ])
+ with self.assertRaises(morphlib.morphloader.InvalidTypeError) as cm:
+ self.loader.validate(m)
+ ex = cm.exception
+ self.assertEqual(ex.field, 'products[0].include')
+ self.assertEqual(ex.expected, list)
+ self.assertEqual(ex.actual, str)
+ self.assertEqual(ex.morphology_name, 'foo')
+
+ def test_validate_requires_products_list_include_is_list_of_strings(self):
+ m = morphlib.morph3.Morphology(
+ kind='chunk',
+ name='foo',
+ products=[
+ {
+ 'artifact': 'foo-runtime',
+ 'include': [
+ 123,
+ ]
+ }
+ ])
+ with self.assertRaises(morphlib.morphloader.InvalidTypeError) as cm:
+ self.loader.validate(m)
+ ex = cm.exception
+ self.assertEqual(ex.field, 'products[0].include[0]')
+ self.assertEqual(ex.expected, str)
+ self.assertEqual(ex.actual, int)
+ self.assertEqual(ex.morphology_name, 'foo')
+
+
def test_fails_to_validate_stratum_with_no_fields(self):
m = morphlib.morph3.Morphology({
'kind': 'stratum',
@@ -491,6 +584,7 @@ name: foo
'build-depends': [],
},
],
+ 'products': [],
})
def test_unsets_defaults_for_strata(self):