summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--morphlib/__init__.py2
-rw-r--r--morphlib/morph3.py45
-rw-r--r--morphlib/morph3_tests.py48
-rw-r--r--morphlib/morphloader.py343
-rw-r--r--morphlib/morphloader_tests.py474
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)
+
+