diff options
-rw-r--r-- | morphlib/__init__.py | 2 | ||||
-rw-r--r-- | morphlib/morph3.py | 45 | ||||
-rw-r--r-- | morphlib/morph3_tests.py | 48 | ||||
-rw-r--r-- | morphlib/morphloader.py | 343 | ||||
-rw-r--r-- | morphlib/morphloader_tests.py | 474 |
5 files changed, 912 insertions, 0 deletions
diff --git a/morphlib/__init__.py b/morphlib/__init__.py index 544dcd09..b9b9924b 100644 --- a/morphlib/__init__.py +++ b/morphlib/__init__.py @@ -65,6 +65,8 @@ import localrepocache import mountableimage import morph2 import morphologyfactory +import morph3 +import morphloader import remoteartifactcache import remoterepocache import repoaliasresolver diff --git a/morphlib/morph3.py b/morphlib/morph3.py new file mode 100644 index 00000000..477cac1a --- /dev/null +++ b/morphlib/morph3.py @@ -0,0 +1,45 @@ +# Copyright (C) 2013 Codethink Limited +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# +# =*= License: GPL-2 =*= + + +import UserDict + + +class Morphology(UserDict.IterableUserDict): + + '''A container for a morphology, plus its metadata. + + A morphology is, basically, a dict. This class acts as that dict, + plus stores additional metadata about the morphology, such as where + it came from, and the ref that was used for it. It also has a dirty + attribute, to indicate whether the morphology has had changes done + to it, but does not itself set that attribute: the caller has to + maintain the flag themselves. + + This class does NO validation of the data, nor does it parse the + morphology text, or produce a textual form of itself. For those + things, see MorphologyLoader. + + ''' + + def __init__(self, *args, **kwargs): + UserDict.IterableUserDict.__init__(self, *args, **kwargs) + self.repo_url = None + self.ref = None + self.filename = None + self.dirty = None + diff --git a/morphlib/morph3_tests.py b/morphlib/morph3_tests.py new file mode 100644 index 00000000..e150bf33 --- /dev/null +++ b/morphlib/morph3_tests.py @@ -0,0 +1,48 @@ +# Copyright (C) 2013 Codethink Limited +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# +# =*= License: GPL-2 =*= + + +import unittest + +import morphlib + + +class MorphologyTests(unittest.TestCase): + + def setUp(self): + self.morph = morphlib.morph3.Morphology() + + def test_has_repo_url_attribute(self): + self.assertEqual(self.morph.repo_url, None) + self.morph.repo_url = 'foo' + self.assertEqual(self.morph.repo_url, 'foo') + + def test_has_ref_attribute(self): + self.assertEqual(self.morph.ref, None) + self.morph.ref = 'foo' + self.assertEqual(self.morph.ref, 'foo') + + def test_has_filename_attribute(self): + self.assertEqual(self.morph.filename, None) + self.morph.filename = 'foo' + self.assertEqual(self.morph.filename, 'foo') + + def test_has_dirty_attribute(self): + self.assertEqual(self.morph.dirty, None) + self.morph.dirty = True + self.assertEqual(self.morph.dirty, True) + diff --git a/morphlib/morphloader.py b/morphlib/morphloader.py new file mode 100644 index 00000000..843c6957 --- /dev/null +++ b/morphlib/morphloader.py @@ -0,0 +1,343 @@ +# Copyright (C) 2013 Codethink Limited +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# +# =*= License: GPL-2 =*= + + +import logging +import StringIO +import yaml + +import morphlib + + +class MorphologySyntaxError(morphlib.Error): + + def __init__(self, morphology): + self.msg = 'Syntax error in morphology %s' % morphology + + +class NotADictionaryError(morphlib.Error): + + def __init__(self, morphology): + self.msg = 'Not a dictionary: morphology %s' % morphology + + +class UnknownKindError(morphlib.Error): + + def __init__(self, kind, morphology): + self.msg = ( + 'Unknown kind %s in morphology %s' % (kind, morphology)) + + +class MissingFieldError(morphlib.Error): + + def __init__(self, field, morphology): + self.msg = ( + 'Missing field %s from morphology %s' % (field, morphology)) + + +class InvalidFieldError(morphlib.Error): + + def __init__(self, field, morphology): + self.msg = ( + 'Field %s not allowed in morphology %s' % (field, morphology)) + + +class UnknownArchitectureError(morphlib.Error): + + def __init__(self, arch, morphology): + self.msg = ( + 'Unknown architecture %s in morphology %s' % (arch, morphology)) + + +class InvalidSystemKindError(morphlib.Error): + + def __init__(self, system_kind, morphology): + self.msg = ( + 'system-kind %s not allowed (must be rootfs-tarball), in %s' % + (system_kind, morphology)) + + +class NoBuildDependenciesError(morphlib.Error): + + def __init__(self, stratum_name, chunk_name, morphology): + self.msg = ( + 'Stratum %s has no build dependencies for chunk %s in %s' % + (stratum_name, chunk_name, morphology)) + + +class NoStratumBuildDependenciesError(morphlib.Error): + + def __init__(self, stratum_name, morphology): + self.msg = ( + 'Stratum %s has no build dependencies in %s' % + (stratum_name, morphology)) + + +class EmptyStratumError(morphlib.Error): + + def __init__(self, stratum_name, morphology): + self.msg = ( + 'Stratum %s has no chunks in %s' % + (stratum_name, morphology)) + + +class MorphologyLoader(object): + + '''Load morphologies from disk, or save them back to disk.''' + + _required_fields = { + 'chunk': [ + 'name', + ], + 'stratum': [ + 'name', + ], + 'system': [ + 'name', + 'arch', + ], + } + + _static_defaults = { + 'chunk': { + 'description': '', + 'pre-configure-commands': [], + 'configure-commands': [], + 'post-configure-commands': [], + 'pre-build-commands': [], + 'build-commands': [], + 'post-build-commands': [], + 'pre-test-commands': [], + 'test-commands': [], + 'post-test-commands': [], + 'pre-install-commands': [], + 'install-commands': [], + 'post-install-commands': [], + 'devices': [], + 'chunks': [], + 'max-jobs': None, + 'build-system': 'manual', + }, + 'stratum': { + 'chunks': [], + 'description': '', + 'build-depends': [], + }, + 'system': { + 'strata': [], + 'description': '', + 'arch': None, + 'system-kind': 'rootfs-tarball', + 'configuration-extensions': [], + 'disk-size': '1G', + }, + } + + def parse_from_string(self, string, whence): + '''Parse a textual morphology. + + Return the new Morphology object, or raise an error indicating + the problem. This method does minimal validation: a syntactically + correct morphology is fine, even if none of the fields are + valid. It also does not set any default values for any of the + fields. See validate and set_defaults. + + whence is where the morphology text came from. It is used + in exception error messages. + + ''' + + try: + obj = yaml.safe_load(StringIO.StringIO(string)) + except yaml.error.YAMLError as e: + logging.error('Could not load morphology as YAML:\n%s' % str(e)) + raise MorphologySyntaxError(whence) + + if type(obj) != dict: + raise NotADictionaryError(whence) + + return morphlib.morph3.Morphology(obj) + + def load_from_string(self, string, filename='string'): + '''Load a morphology from a string. + + Return the Morphology object. + + ''' + + m = self.parse_from_string(string, filename) + m.filename = filename + self.validate(m) + self.set_defaults(m) + return m + + def load_from_file(self, filename): + '''Load a morphology from a named file. + + Return the Morphology object. + + ''' + + with open(filename) as f: + text = f.read() + return self.load_from_string(text, filename=filename) + + def save_to_string(self, morphology): + '''Return normalised textual form of morphology.''' + + f = StringIO.StringIO() + yaml.safe_dump(morphology.data, stream=f, default_flow_style=False) + return f.getvalue() + + def save_to_file(self, filename, morphology): + '''Save a morphology object to a named file.''' + + text = self.save_to_string(morphology) + with morphlib.savefile.SaveFile(filename, 'w') as f: + f.write(text) + + def validate(self, morph): + '''Validate a morphology.''' + + # Validate that the kind field is there. + self._require_field('kind', morph) + + # The rest of the validation is dependent on the kind. + + kind = morph['kind'] + if kind not in ('system', 'stratum', 'chunk'): + raise UnknownKindError(morph['kind'], morph.filename) + + required = ['kind'] + self._required_fields[kind] + allowed = self._static_defaults[kind].keys() + self._require_fields(required, morph) + self._deny_unknown_fields(required + allowed, morph) + + if kind == 'system': + self._validate_system(morph) + elif kind == 'stratum': + self._validate_stratum(morph) + else: + assert kind == 'chunk' + self._validate_chunk(morph) + + def _validate_system(self, morph): + # All stratum names should be unique within a system. + names = set() + for info in morph['strata']: + name = info.get('alias', info['morph']) + if name in names: + raise ValueError('Duplicate stratum "%s"' % name) + names.add(name) + + # We allow the ARMv7 little-endian architecture to be specified + # as armv7 and armv7l. Normalise. + if morph['arch'] == 'armv7': + morph['arch'] = 'armv7l' + + # Architecture name must be known. + if morph['arch'] not in morphlib.valid_archs: + raise UnknownArchitectureError(morph['arch'], morph.filename) + + # If system-kind is present, it must be rootfs-tarball. + if 'system-kind' in morph: + if morph['system-kind'] not in (None, 'rootfs-tarball'): + raise InvalidSystemKindError( + morph['system-kind'], morph.filename) + + def _validate_stratum(self, morph): + # All chunk names must be unique within a stratum. + names = set() + for info in morph['chunks']: + name = info.get('alias', info['name']) + if name in names: + raise ValueError('Duplicate chunk "%s"' % name) + names.add(name) + + # Require build-dependencies for the stratum itself, unless + # it has chunks built in bootstrap mode. + if 'build-depends' not in morph: + for spec in morph['chunks']: + if spec.get('build-mode') == 'bootstrap': + break + else: + raise NoStratumBuildDependenciesError( + morph['name'], morph.filename) + + # Require build-dependencies for each chunk. + for spec in morph['chunks']: + if 'build-depends' not in spec: + raise NoBuildDependenciesError( + morph['name'], + spec.get('alias', spec['name']), + morph.filename) + + # Require at least one chunk. + if len(morph.get('chunks', [])) == 0: + raise EmptyStratumError(morph['name'], morph.filename) + + def _validate_chunk(self, morph): + pass + + def _require_field(self, field, morphology): + if field not in morphology: + raise MissingFieldError(field, morphology.filename) + + def _require_fields(self, fields, morphology): + for field in fields: + self._require_field(field, morphology) + + def _deny_unknown_fields(self, allowed, morphology): + for field in morphology: + if field not in allowed: + raise InvalidFieldError(field, morphology.filename) + + def set_defaults(self, morphology): + '''Set all missing fields in the morpholoy to their defaults. + + The morphology is assumed to be valid. + + ''' + + kind = morphology['kind'] + defaults = self._static_defaults[kind] + for key in defaults: + if key not in morphology: + morphology[key] = defaults[key] + + if kind == 'system': + self._set_system_defaults(morphology) + elif kind == 'stratum': + self._set_stratum_defaults(morphology) + else: + assert kind == 'chunk' + self._set_chunk_defaults(morphology) + + def _set_system_defaults(self, morph): + pass + + def _set_stratum_defaults(self, morph): + for spec in morph['chunks']: + if 'repo' not in spec: + spec['repo'] = spec['name'] + if 'morph' not in spec: + spec['morph'] = spec['name'] + + def _set_chunk_defaults(self, morph): + if morph['max-jobs'] is not None: + morph['max-jobs'] = int(morph['max-jobs']) + diff --git a/morphlib/morphloader_tests.py b/morphlib/morphloader_tests.py new file mode 100644 index 00000000..7a1b2c0c --- /dev/null +++ b/morphlib/morphloader_tests.py @@ -0,0 +1,474 @@ +# Copyright (C) 2013 Codethink Limited +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# +# =*= License: GPL-2 =*= + + +import os +import shutil +import tempfile +import unittest + +import morphlib + + +class MorphologyLoaderTests(unittest.TestCase): + + def setUp(self): + self.loader = morphlib.morphloader.MorphologyLoader() + self.tempdir = tempfile.mkdtemp() + self.filename = os.path.join(self.tempdir, 'foo.morph') + + def tearDown(self): + shutil.rmtree(self.tempdir) + + def test_parses_yaml_from_string(self): + string = '''\ +name: foo +kind: chunk +build-system: dummy +''' + morph = self.loader.parse_from_string(string, 'test') + self.assertEqual(morph['kind'], 'chunk') + self.assertEqual(morph['name'], 'foo') + self.assertEqual(morph['build-system'], 'dummy') + + def test_fails_to_parse_utter_garbage(self): + self.assertRaises( + morphlib.morphloader.MorphologySyntaxError, + self.loader.parse_from_string, ',,,', 'test') + + def test_fails_to_parse_non_dict(self): + self.assertRaises( + morphlib.morphloader.NotADictionaryError, + self.loader.parse_from_string, '- item1\n- item2\n', 'test') + + def test_fails_to_validate_dict_without_kind(self): + m = morphlib.morph3.Morphology({ + 'invalid': 'field', + }) + self.assertRaises( + morphlib.morphloader.MissingFieldError, self.loader.validate, m) + + def test_fails_to_validate_chunk_with_no_fields(self): + m = morphlib.morph3.Morphology({ + 'kind': 'chunk', + }) + self.assertRaises( + morphlib.morphloader.MissingFieldError, self.loader.validate, m) + + def test_fails_to_validate_chunk_with_invalid_field(self): + m = morphlib.morph3.Morphology({ + 'kind': 'chunk', + 'name': 'foo', + 'invalid': 'field', + }) + self.assertRaises( + morphlib.morphloader.InvalidFieldError, self.loader.validate, m) + + def test_fails_to_validate_stratum_with_no_fields(self): + m = morphlib.morph3.Morphology({ + 'kind': 'stratum', + }) + self.assertRaises( + morphlib.morphloader.MissingFieldError, self.loader.validate, m) + + def test_fails_to_validate_stratum_with_invalid_field(self): + m = morphlib.morph3.Morphology({ + 'kind': 'stratum', + 'name': 'foo', + 'invalid': 'field', + }) + self.assertRaises( + morphlib.morphloader.InvalidFieldError, self.loader.validate, m) + + def test_fails_to_validate_system_with_no_fields(self): + m = morphlib.morph3.Morphology({ + 'kind': 'system', + }) + self.assertRaises( + morphlib.morphloader.MissingFieldError, self.loader.validate, m) + + def test_fails_to_validate_system_with_invalid_field(self): + m = morphlib.morph3.Morphology({ + 'kind': 'system', + 'name': 'name', + 'arch': 'x86_64', + 'invalid': 'field', + }) + self.assertRaises( + morphlib.morphloader.InvalidFieldError, self.loader.validate, m) + + def test_fails_to_validate_morphology_with_unknown_kind(self): + m = morphlib.morph3.Morphology({ + 'kind': 'invalid', + }) + self.assertRaises( + morphlib.morphloader.UnknownKindError, self.loader.validate, m) + + def test_validate_requires_unique_stratum_names_within_a_system(self): + m = morphlib.morph3.Morphology( + { + "kind": "system", + "name": "foo", + "arch": "x86-64", + "strata": [ + { + "morph": "stratum", + "repo": "test1", + "ref": "ref" + }, + { + "morph": "stratum", + "repo": "test2", + "ref": "ref" + } + ] + }) + self.assertRaises(ValueError, self.loader.validate, m) + + def test_validate_requires_unique_chunk_names_within_a_stratum(self): + m = morphlib.morph3.Morphology( + { + "kind": "stratum", + "name": "foo", + "chunks": [ + { + "name": "chunk", + "repo": "test1", + "ref": "ref" + }, + { + "name": "chunk", + "repo": "test2", + "ref": "ref" + } + ] + }) + self.assertRaises(ValueError, self.loader.validate, m) + + def test_validate_requires_a_valid_architecture(self): + m = morphlib.morph3.Morphology( + { + "kind": "system", + "name": "foo", + "arch": "blah", + "strata": [], + }) + self.assertRaises( + morphlib.morphloader.UnknownArchitectureError, + self.loader.validate, m) + + def test_validate_normalises_architecture_armv7_to_armv7l(self): + m = morphlib.morph3.Morphology( + { + "kind": "system", + "name": "foo", + "arch": "armv7", + "strata": [], + }) + self.loader.validate(m) + self.assertEqual(m['arch'], 'armv7l') + + def test_validate_requires_system_kind_to_be_tarball_if_present(self): + m = morphlib.morph3.Morphology( + { + "kind": "system", + "name": "foo", + "arch": "armv7l", + "strata": [], + "system-kind": "blah", + }) + + self.assertRaises( + morphlib.morphloader.InvalidSystemKindError, + self.loader.validate, m) + m['system-kind'] = 'rootfs-tarball' + self.loader.validate(m) + + def test_validate_requires_build_deps_for_chunks_in_strata(self): + m = morphlib.morph3.Morphology( + { + "kind": "stratum", + "name": "foo", + "chunks": [ + { + "name": "foo", + "repo": "foo", + "ref": "foo", + "morph": "foo", + "build-mode": "bootstrap", + } + ], + }) + + self.assertRaises( + morphlib.morphloader.NoBuildDependenciesError, + self.loader.validate, m) + + def test_validate_requires_build_deps_or_bootstrap_mode_for_strata(self): + m = morphlib.morph3.Morphology( + { + "name": "stratum-no-bdeps-no-bootstrap", + "kind": "stratum", + "chunks": [ + { + "name": "chunk", + "repo": "test:repo", + "ref": "sha1", + "build-depends": [] + } + ] + }) + + self.assertRaises( + morphlib.morphloader.NoStratumBuildDependenciesError, + self.loader.validate, m) + + m['build-depends'] = [ + { + "repo": "foo", + "ref": "foo", + "morph": "foo", + }, + ] + self.loader.validate(m) + + del m['build-depends'] + m['chunks'][0]['build-mode'] = 'bootstrap' + self.loader.validate(m) + + def test_validate_requires_chunks_in_strata(self): + m = morphlib.morph3.Morphology( + { + "name": "stratum", + "kind": "stratum", + "chunks": [ + ], + "build-depends": [ + { + "repo": "foo", + "ref": "foo", + "morph": "foo", + }, + ], + }) + + self.assertRaises( + morphlib.morphloader.EmptyStratumError, + self.loader.validate, m) + + def test_loads_yaml_from_string(self): + string = '''\ +name: foo +kind: chunk +build-system: dummy +''' + morph = self.loader.load_from_string(string) + self.assertEqual(morph['kind'], 'chunk') + self.assertEqual(morph['name'], 'foo') + self.assertEqual(morph['build-system'], 'dummy') + + def test_loads_json_from_string(self): + string = '''\ +{ + "name": "foo", + "kind": "chunk", + "build-system": "dummy" +} +''' + morph = self.loader.load_from_string(string) + self.assertEqual(morph['kind'], 'chunk') + self.assertEqual(morph['name'], 'foo') + self.assertEqual(morph['build-system'], 'dummy') + + def test_loads_from_file(self): + with open(self.filename, 'w') as f: + f.write('''\ +name: foo +kind: chunk +build-system: dummy +''') + morph = self.loader.load_from_file(self.filename) + self.assertEqual(morph['kind'], 'chunk') + self.assertEqual(morph['name'], 'foo') + self.assertEqual(morph['build-system'], 'dummy') + + def test_saves_to_string(self): + morph = morphlib.morph3.Morphology({ + 'name': 'foo', + 'kind': 'chunk', + 'build-system': 'dummy', + }) + text = self.loader.save_to_string(morph) + + # The following verifies that the YAML is written in a normalised + # fashion. + self.assertEqual(text, '''\ +build-system: dummy +kind: chunk +name: foo +''') + + def test_saves_to_file(self): + morph = morphlib.morph3.Morphology({ + 'name': 'foo', + 'kind': 'chunk', + 'build-system': 'dummy', + }) + self.loader.save_to_file(self.filename, morph) + + with open(self.filename) as f: + text = f.read() + + # The following verifies that the YAML is written in a normalised + # fashion. + self.assertEqual(text, '''\ +build-system: dummy +kind: chunk +name: foo +''') + + def test_validate_does_not_set_defaults(self): + m = morphlib.morph3.Morphology({ + 'kind': 'chunk', + 'name': 'foo', + }) + self.loader.validate(m) + self.assertEqual(sorted(m.keys()), sorted(['kind', 'name'])) + + def test_sets_defaults_for_chunks(self): + m = morphlib.morph3.Morphology({ + 'kind': 'chunk', + 'name': 'foo', + }) + self.loader.set_defaults(m) + self.loader.validate(m) + self.assertEqual( + dict(m), + { + 'kind': 'chunk', + 'name': 'foo', + 'description': '', + 'build-system': 'manual', + + 'configure-commands': [], + 'pre-configure-commands': [], + 'post-configure-commands': [], + + 'build-commands': [], + 'pre-build-commands': [], + 'post-build-commands': [], + + 'test-commands': [], + 'pre-test-commands': [], + 'post-test-commands': [], + + 'install-commands': [], + 'pre-install-commands': [], + 'post-install-commands': [], + + 'chunks': [], + 'devices': [], + 'max-jobs': None, + }) + + def test_sets_defaults_for_strata(self): + m = morphlib.morph3.Morphology({ + 'kind': 'stratum', + 'name': 'foo', + 'chunks': [ + { + 'name': 'bar', + 'repo': 'bar', + 'ref': 'bar', + 'morph': 'bar', + 'build-mode': 'bootstrap', + 'build-depends': [], + }, + ], + }) + self.loader.set_defaults(m) + self.loader.validate(m) + self.assertEqual( + dict(m), + { + 'kind': 'stratum', + 'name': 'foo', + 'description': '', + 'build-depends': [], + 'chunks': [ + { + 'name': 'bar', + "repo": "bar", + "ref": "bar", + "morph": "bar", + 'build-mode': 'bootstrap', + 'build-depends': [], + }, + ], + }) + + def test_sets_defaults_for_system(self): + m = morphlib.morph3.Morphology({ + 'kind': 'system', + 'name': 'foo', + 'arch': 'x86_64', + }) + self.loader.set_defaults(m) + self.loader.validate(m) + self.assertEqual( + dict(m), + { + 'kind': 'system', + 'system-kind': 'rootfs-tarball', + 'name': 'foo', + 'description': '', + 'arch': 'x86_64', + 'strata': [], + 'configuration-extensions': [], + 'disk-size': '1G', + }) + + def test_sets_stratum_chunks_repo_and_morph_from_name(self): + m = morphlib.morph3.Morphology( + { + "name": "foo", + "kind": "stratum", + "chunks": [ + { + "name": "le-chunk", + "ref": "ref", + "build-depends": [], + } + ] + }) + + self.loader.set_defaults(m) + self.loader.validate(m) + self.assertEqual(m['chunks'][0]['repo'], 'le-chunk') + self.assertEqual(m['chunks'][0]['morph'], 'le-chunk') + + def test_convertes_max_jobs_to_an_integer(self): + m = morphlib.morph3.Morphology( + { + "name": "foo", + "kind": "chunk", + "max-jobs": "42" + }) + self.loader.set_defaults(m) + self.assertEqual(m['max-jobs'], 42) + + |