summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRichard Maw <richard.maw@codethink.co.uk>2014-01-07 16:47:54 +0000
committerRichard Maw <richard.maw@codethink.co.uk>2014-01-10 15:51:55 +0000
commit53cf77f659d5a60beb0f8ba441533dc88ffd9273 (patch)
treecb5d98a5fd5bc669bfe13bc5f941f978d067def6
parentea8b33d3e9ff9a19e2b5d625cd864a804c58d08f (diff)
downloadmorph-53cf77f659d5a60beb0f8ba441533dc88ffd9273.tar.gz
MorphologyLoader: Validate new fields
-rw-r--r--morphlib/morphloader.py96
-rw-r--r--morphlib/morphloader_tests.py93
2 files changed, 183 insertions, 6 deletions
diff --git a/morphlib/morphloader.py b/morphlib/morphloader.py
index e1ec15bd..cc293139 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,64 @@ class MorphologyLoader(object):
spec.get('alias', spec['name']),
morph.filename)
- def _validate_chunk(self, morph):
- pass
+ @classmethod
+ def _validate_chunk(cls, morph):
+ errors = []
+ products = morph.get('products')
+ if products is None:
+ return
+ name = morph['name']
+ if (not isinstance(products, collections.Iterable)
+ or isinstance(products, collections.Mapping)):
+
+ raise InvalidTypeError('products', list,
+ type(products), name)
+
+ product_spec_required_fields = set(('artifact', 'include'))
+ for i, spec in enumerate(products):
+
+ if not isinstance(spec, collections.Mapping):
+ errors.append(InvalidTypeError('products[%d]' % i, dict,
+ type(spec), name))
+ continue
+
+ fields = set(spec.iterkeys())
+ missing = product_spec_required_fields - fields
+ for field in missing:
+ errors.append(
+ MissingFieldError('products[%d].%s' % (i, field), name))
+ unexpected = fields - product_spec_required_fields
+ for field in unexpected:
+ errors.append(
+ InvalidFieldError('products[%d].%s' % (i, field), name))
+
+ if 'include' in spec:
+ include_patterns = spec['include']
+ # 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)):
+
+ errors.append(
+ InvalidTypeError(
+ 'products[%d].include' % i, list,
+ type(include_patterns), name))
+ else:
+ for j, pattern in enumerate(include_patterns):
+ if not isinstance(pattern, basestring):
+ errors.append(
+ InvalidTypeError(
+ 'products[%d].include[%d]' % (i, j),
+ str, type(pattern), name))
+
+ if len(errors) == 1:
+ raise errors[0]
+ elif errors:
+ raise MultipleValidationErrors(name, errors)
+
def _require_field(self, field, morphology):
if field not in morphology:
diff --git a/morphlib/morphloader_tests.py b/morphlib/morphloader_tests.py
index 907f3762..a223d522 100644
--- a/morphlib/morphloader_tests.py
+++ b/morphlib/morphloader_tests.py
@@ -78,6 +78,98 @@ 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, e.expected, e.actual, e.morphology_name),
+ ('products', list, dict, '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, e.expected, e.actual, e.morphology_name),
+ ('products[0]', dict, str, '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(
+ sorted((type(ex), ex.field) for ex in exs),
+ sorted((
+ (morphlib.morphloader.MissingFieldError,
+ 'products[0].artifact'),
+ (morphlib.morphloader.MissingFieldError,
+ 'products[0].include'),
+ (morphlib.morphloader.InvalidFieldError,
+ 'products[0].cludein'),
+ (morphlib.morphloader.InvalidFieldError,
+ '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(('products[0].include', list, str, 'foo'),
+ (ex.field, ex.expected, ex.actual,
+ ex.morphology_name))
+
+ 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(('products[0].include[0]', str, int, 'foo'),
+ (ex.field, ex.expected, ex.actual,
+ ex.morphology_name))
+
+
def test_fails_to_validate_stratum_with_no_fields(self):
m = morphlib.morph3.Morphology({
'kind': 'stratum',
@@ -491,6 +583,7 @@ name: foo
'build-depends': [],
},
],
+ 'products': [],
})
def test_unsets_defaults_for_strata(self):