You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@buildstream.apache.org by tv...@apache.org on 2021/02/04 07:19:39 UTC

[buildstream] branch validation created (now dc8c5fb)

This is an automated email from the ASF dual-hosted git repository.

tvb pushed a change to branch validation
in repository https://gitbox.apache.org/repos/asf/buildstream.git.


      at dc8c5fb  Fix affected test cases

This branch includes the following new commits:

     new 2f545d5  Add _yaml.validate_node
     new 94570a3  Add element node validations
     new fbd3a02  project.py: Add project config node validations
     new 2898557  context.py: Add user config node validations
     new d5499e2  Add element plugin node validations
     new be94c71  Add source plugin node validations
     new dc8c5fb  Fix affected test cases

The 7 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



[buildstream] 04/07: context.py: Add user config node validations

Posted by tv...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

tvb pushed a commit to branch validation
in repository https://gitbox.apache.org/repos/asf/buildstream.git

commit 2898557b8d0cbe30b4429b71ab7910a766deafbe
Author: Tristan Maat <tr...@codethink.co.uk>
AuthorDate: Thu Sep 7 11:04:17 2017 +0100

    context.py: Add user config node validations
---
 buildstream/context.py | 24 ++++++++++++++++++++++++
 1 file changed, 24 insertions(+)

diff --git a/buildstream/context.py b/buildstream/context.py
index afc46fe..f9c49e0 100644
--- a/buildstream/context.py
+++ b/buildstream/context.py
@@ -153,9 +153,23 @@ class Context():
         # Load default config
         #
         defaults = _yaml.load(_site.default_user_config)
+        _yaml.validate_node(defaults, [
+            'strict', 'sourcedir',
+            'builddir', 'artifactdir',
+            'logdir', 'scheduler',
+            'artifacts', 'logging'
+        ])
+
         if config:
             self.config_origin = os.path.abspath(config)
             user_config = _yaml.load(config)
+            _yaml.validate_node(user_config, [
+                'strict', 'sourcedir',
+                'builddir', 'artifactdir',
+                'logdir', 'scheduler',
+                'artifacts', 'logging'
+            ])
+
             _yaml.composite(defaults, user_config, typesafe=True)
 
         self.strict_build_plan = _yaml.node_get(defaults, bool, 'strict')
@@ -171,12 +185,18 @@ class Context():
 
         # Load artifact share configuration
         artifacts = _yaml.node_get(defaults, Mapping, 'artifacts')
+        _yaml.validate_node(artifacts, ['pull-url', 'push-url', 'push-port'])
         self.artifact_pull = _yaml.node_get(artifacts, str, 'pull-url', default_value='') or None
         self.artifact_push = _yaml.node_get(artifacts, str, 'push-url', default_value='') or None
         self.artifact_push_port = _yaml.node_get(artifacts, int, 'push-port', default_value=22)
 
         # Load logging config
         logging = _yaml.node_get(defaults, Mapping, 'logging')
+        _yaml.validate_node(logging, [
+            'key-length', 'verbose',
+            'error-lines', 'message-lines',
+            'debug', 'element-format'
+        ])
         self.log_key_length = _yaml.node_get(logging, int, 'key-length')
         self.log_debug = _yaml.node_get(logging, bool, 'debug')
         self.log_verbose = _yaml.node_get(logging, bool, 'verbose')
@@ -186,6 +206,10 @@ class Context():
 
         # Load scheduler config
         scheduler = _yaml.node_get(defaults, Mapping, 'scheduler')
+        _yaml.validate_node(scheduler, [
+            'on-error', 'fetchers', 'builders',
+            'pushers', 'network-retries'
+        ])
         self.sched_error_action = _yaml.node_get(scheduler, str, 'on-error')
         self.sched_fetchers = _yaml.node_get(scheduler, int, 'fetchers')
         self.sched_builders = _yaml.node_get(scheduler, int, 'builders')


[buildstream] 02/07: Add element node validations

Posted by tv...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

tvb pushed a commit to branch validation
in repository https://gitbox.apache.org/repos/asf/buildstream.git

commit 94570a3c4338d344e9d3ca0759c61a6b2d3ba1c1
Author: Tristan Maat <tr...@codethink.co.uk>
AuthorDate: Wed Sep 6 17:50:24 2017 +0100

    Add element node validations
---
 buildstream/_loader.py                                    |  9 +++++++++
 tests/loader/basics.py                                    | 12 ++++++++++++
 tests/loader/basics/onefile/elements/invalidkey.bst       |  3 +++
 tests/loader/basics/onefile/elements/invalidsourcekey.bst |  6 ++++++
 4 files changed, 30 insertions(+)

diff --git a/buildstream/_loader.py b/buildstream/_loader.py
index 72b7ed3..b7a1ccb 100644
--- a/buildstream/_loader.py
+++ b/buildstream/_loader.py
@@ -211,6 +211,15 @@ class LoadElement():
         # These are shared with the owning Loader object
         self.basedir = basedir
 
+        # Ensure the root node is valid
+        _yaml.validate_node(self.data, [
+            'kind', 'depends', 'sources',
+            'variables', 'environment',
+            'config', 'public', 'description',
+            'arches', 'variants', 'host-arches',
+            'choice'
+        ])
+
         # Process arch conditionals
         resolve_arch(self.data, self.host_arch, self.target_arch)
 
diff --git a/tests/loader/basics.py b/tests/loader/basics.py
index 4830702..99f5edb 100644
--- a/tests/loader/basics.py
+++ b/tests/loader/basics.py
@@ -72,3 +72,15 @@ def test_fail_fullpath_target(datafiles):
         loader = Loader(basedir, fullpath, None, None, None, [])
 
     assert (exc.value.reason == LoadErrorReason.INVALID_DATA)
+
+
+@pytest.mark.datafiles(os.path.join(DATA_DIR, 'onefile'))
+def test_invalid_key(datafiles):
+
+    basedir = os.path.join(datafiles.dirname, datafiles.basename)
+    loader = Loader(basedir, 'elements/invalidkey.bst', None, None, None, [])
+
+    with pytest.raises(LoadError) as exc:
+        element = loader.load()
+
+    assert (exc.value.reason == LoadErrorReason.INVALID_YAML)
diff --git a/tests/loader/basics/onefile/elements/invalidkey.bst b/tests/loader/basics/onefile/elements/invalidkey.bst
new file mode 100644
index 0000000..5674ab7
--- /dev/null
+++ b/tests/loader/basics/onefile/elements/invalidkey.bst
@@ -0,0 +1,3 @@
+kind: pony
+description: This is the pony
+wings: blue
diff --git a/tests/loader/basics/onefile/elements/invalidsourcekey.bst b/tests/loader/basics/onefile/elements/invalidsourcekey.bst
new file mode 100644
index 0000000..5677af3
--- /dev/null
+++ b/tests/loader/basics/onefile/elements/invalidsourcekey.bst
@@ -0,0 +1,6 @@
+kind: pony
+description: This is the pony
+sources:
+  - kind: ponyland
+    url: ptp://pw.ponies.p/
+    weather: great


[buildstream] 05/07: Add element plugin node validations

Posted by tv...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

tvb pushed a commit to branch validation
in repository https://gitbox.apache.org/repos/asf/buildstream.git

commit d5499e2c505342ceea6266013ee1c8329facf786
Author: Tristan Maat <tr...@codethink.co.uk>
AuthorDate: Thu Sep 7 12:20:43 2017 +0100

    Add element plugin node validations
---
 buildstream/buildelement.py                   |  9 +++++----
 buildstream/plugin.py                         | 23 +++++++++++++++++++++++
 buildstream/plugins/elements/compose.py       |  4 ++++
 buildstream/plugins/elements/dpkg_deploy.py   |  5 +++++
 buildstream/plugins/elements/script.py        |  6 ++++++
 buildstream/plugins/elements/x86image.py      |  3 +++
 tests/project/data/plugins/elements/custom.py |  1 +
 7 files changed, 47 insertions(+), 4 deletions(-)

diff --git a/buildstream/buildelement.py b/buildstream/buildelement.py
index 859b682..0401991 100644
--- a/buildstream/buildelement.py
+++ b/buildstream/buildelement.py
@@ -114,11 +114,12 @@ class BuildElement(Element):
     def configure(self, node):
 
         self.commands = {}
+        command_names = [prefix + step for step in _command_steps for prefix in _command_prefixes]
 
-        for step in _command_steps:
-            for prefix in _command_prefixes:
-                command_name = prefix + step
-                self.commands[command_name] = self._get_commands(node, command_name)
+        self.node_validate(node, command_names)
+
+        for command_name in command_names:
+            self.commands[command_name] = self._get_commands(node, command_name)
 
     def preflight(self):
         pass
diff --git a/buildstream/plugin.py b/buildstream/plugin.py
index 7752154..5cfd235 100644
--- a/buildstream/plugin.py
+++ b/buildstream/plugin.py
@@ -207,6 +207,29 @@ class Plugin():
         """
         return _yaml.node_get(node, expected_type, member_name, default_value=default_value)
 
+    def node_validate(self, node, valid_keys):
+        """Validate that a node only contains keys from a list of valid
+        keys. This is useful to avoid typos (such as knd: autotools).
+
+        Args:
+            node (dict): A dictionary loaded from YAML
+            valid_keys (iterable): A list of valid keys for the node
+
+        Raises:
+            :class:`.LoadError`: When an invalid key is found
+
+        **Example:**
+
+        .. code:: python
+
+          # Ensure our node only contains valid autotools config keys
+          self.node_validate(node, [
+              'configure-commands', 'build-commands',
+              'install-commands', 'strip-commands'
+          ])
+        """
+        _yaml.validate_node(node, valid_keys)
+
     def node_get_list_element(self, node, expected_type, member_name, indices):
         """Fetch the value of a list element from a node member, raising an error if the
         value is incorrectly typed.
diff --git a/buildstream/plugins/elements/compose.py b/buildstream/plugins/elements/compose.py
index 4d58c1c..9d701c7 100644
--- a/buildstream/plugins/elements/compose.py
+++ b/buildstream/plugins/elements/compose.py
@@ -48,6 +48,10 @@ class ComposeElement(Element):
     BST_STRICT_REBUILD = True
 
     def configure(self, node):
+        self.node_validate(node, [
+            'integrate', 'include', 'exclude', 'include-orphans'
+        ])
+
         # We name this variable 'integration' only to avoid
         # collision with the Element.integrate() method.
         self.integration = self.node_get_member(node, bool, 'integrate')
diff --git a/buildstream/plugins/elements/dpkg_deploy.py b/buildstream/plugins/elements/dpkg_deploy.py
index e2b3470..c87357c 100644
--- a/buildstream/plugins/elements/dpkg_deploy.py
+++ b/buildstream/plugins/elements/dpkg_deploy.py
@@ -141,6 +141,11 @@ class DpkgDeployElement(ScriptElement):
         prefixes = ["pre-", "", "post-"]
         groups = ["build-commands"]
 
+        self.node_validate(node, [
+            'pre-build-commands', 'build-commands', 'post-build-commands',
+            'base', 'input'
+        ])
+
         self.__input = self.node_subst_member(node, 'input')
         self.layout_add(self.node_subst_member(node, 'base'), "/")
         self.layout_add(None, '/buildstream')
diff --git a/buildstream/plugins/elements/script.py b/buildstream/plugins/elements/script.py
index 7198af8..2b3ca96 100644
--- a/buildstream/plugins/elements/script.py
+++ b/buildstream/plugins/elements/script.py
@@ -48,6 +48,12 @@ class ScriptElement(buildstream.ScriptElement):
 
         cmds = []
         prefixes = ["pre-", "", "post-"]
+
+        self.node_validate(node, [
+            'pre-commands', 'commands', 'post-commands',
+            'root-read-only', 'layout'
+        ])
+
         if "commands" not in node:
             raise ElementError("{}: Unexpectedly missing command group 'commands'"
                                .format(self))
diff --git a/buildstream/plugins/elements/x86image.py b/buildstream/plugins/elements/x86image.py
index 410b53d..4bd0ca9 100644
--- a/buildstream/plugins/elements/x86image.py
+++ b/buildstream/plugins/elements/x86image.py
@@ -41,6 +41,9 @@ class X86ImageElement(ScriptElement):
             "partition-commands",
             "final-commands"
         ]
+
+        self.node_validate(node, (prefix + group for group in groups for prefix in prefixes))
+
         for group in groups:
             cmds = []
             if group not in node:
diff --git a/tests/project/data/plugins/elements/custom.py b/tests/project/data/plugins/elements/custom.py
index 2894a92..9737931 100644
--- a/tests/project/data/plugins/elements/custom.py
+++ b/tests/project/data/plugins/elements/custom.py
@@ -5,6 +5,7 @@ class CustomElement(Element):
 
     def configure(self, node):
         print("Element Data: %s" % node)
+        self.node_validate(node, ['configuration'])
         self.configuration = self.node_subst_member(node, "configuration", default_value='')
 
     def preflight(self):


[buildstream] 03/07: project.py: Add project config node validations

Posted by tv...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

tvb pushed a commit to branch validation
in repository https://gitbox.apache.org/repos/asf/buildstream.git

commit fbd3a02a5579a5f40baea6c7e2baf63bf9d850ea
Author: Tristan Maat <tr...@codethink.co.uk>
AuthorDate: Thu Sep 7 11:04:09 2017 +0100

    project.py: Add project config node validations
---
 buildstream/project.py | 16 ++++++++++++++++
 1 file changed, 16 insertions(+)

diff --git a/buildstream/project.py b/buildstream/project.py
index fb91d30..442aaeb 100644
--- a/buildstream/project.py
+++ b/buildstream/project.py
@@ -142,6 +142,13 @@ class Project():
         # Load builtin default
         projectfile = os.path.join(self.directory, "project.conf")
         config = _yaml.load(_site.default_project_config)
+        _yaml.validate_node(config, [
+            'required-versions',
+            'element-path', 'variables',
+            'environment', 'environment-nocache',
+            'split-rules', 'elements', 'plugins',
+            'aliases', 'name'
+        ])
 
         # Special variables which have a computed default value must
         # be processed here before compositing any overrides
@@ -157,6 +164,13 @@ class Project():
 
         # Load project local config and override the builtin
         project_conf = _yaml.load(projectfile)
+        _yaml.validate_node(project_conf, [
+            'required-versions',
+            'element-path', 'variables',
+            'environment', 'environment-nocache',
+            'split-rules', 'elements', 'plugins',
+            'aliases', 'name'
+        ])
         _yaml.composite(config, project_conf, typesafe=True)
 
         # Resolve arches keyword, project may have arch conditionals
@@ -233,6 +247,7 @@ class Project():
 
         # Version requirements
         versions = _yaml.node_get(self._unresolved_config, Mapping, 'required-versions')
+        _yaml.validate_node(versions, ['project', 'elements', 'sources'])
 
         # Assert project version first
         format_version = _yaml.node_get(versions, int, 'project')
@@ -259,6 +274,7 @@ class Project():
 
         # Load the plugin paths
         plugins = _yaml.node_get(self._unresolved_config, Mapping, 'plugins', default_value={})
+        _yaml.validate_node(plugins, ['elements', 'sources'])
         self._plugin_source_paths = [os.path.join(self.directory, path)
                                      for path in self._extract_plugin_paths(plugins, 'sources')]
         self._plugin_element_paths = [os.path.join(self.directory, path)


[buildstream] 01/07: Add _yaml.validate_node

Posted by tv...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

tvb pushed a commit to branch validation
in repository https://gitbox.apache.org/repos/asf/buildstream.git

commit 2f545d57b982d97638951bca7facb45f43f63169
Author: Tristan Maat <tr...@codethink.co.uk>
AuthorDate: Wed Sep 6 16:52:26 2017 +0100

    Add _yaml.validate_node
---
 buildstream/_yaml.py         | 18 ++++++++++++++++++
 tests/yaml/data/invalid.yaml |  8 ++++++++
 tests/yaml/yaml.py           | 22 ++++++++++++++++++++++
 3 files changed, 48 insertions(+)

diff --git a/buildstream/_yaml.py b/buildstream/_yaml.py
index 527f537..c07996b 100644
--- a/buildstream/_yaml.py
+++ b/buildstream/_yaml.py
@@ -587,6 +587,24 @@ def node_sanitize(node):
     return node
 
 
+def validate_node(node, valid_keys):
+
+    # Probably the fastest way to do this: https://stackoverflow.com/a/23062482
+    valid_keys = set(valid_keys)
+    valid_keys.add(PROVENANCE_KEY)
+    invalid_keys = [key for key in node if key not in valid_keys]
+
+    if invalid_keys:
+        provenance = node_get_provenance(node)
+        error_prefix = ""
+        if provenance:
+            error_prefix = "[%s]: " % str(provenance)
+
+        key_list = ', '.join(invalid_keys)
+        raise LoadError(LoadErrorReason.INVALID_YAML,
+                        "{}Unexpected keys: {}".format(error_prefix, key_list))
+
+
 def node_chain_copy(source):
     copy = collections.ChainMap({}, source)
     for key, value in source.items():
diff --git a/tests/yaml/data/invalid.yaml b/tests/yaml/data/invalid.yaml
new file mode 100644
index 0000000..bc7f1ae
--- /dev/null
+++ b/tests/yaml/data/invalid.yaml
@@ -0,0 +1,8 @@
+kind: pony
+description: The vehicle of choice for rainbow travel
+mods:
+  - happy
+  - sad
+children:
+  - naam: dopey
+    mood: silly
diff --git a/tests/yaml/yaml.py b/tests/yaml/yaml.py
index ec63267..a6e6f49 100644
--- a/tests/yaml/yaml.py
+++ b/tests/yaml/yaml.py
@@ -163,6 +163,28 @@ def test_composited_array_append_provenance(datafiles):
 
 
 @pytest.mark.datafiles(os.path.join(DATA_DIR))
+def test_validate_node(datafiles):
+
+    valid = os.path.join(datafiles.dirname,
+                         datafiles.basename,
+                         'basics.yaml')
+    invalid = os.path.join(datafiles.dirname,
+                           datafiles.basename,
+                           'invalid.yaml')
+
+    base = _yaml.load(valid)
+
+    _yaml.validate_node(base, ['kind', 'description', 'moods', 'children', 'extra'])
+
+    base = _yaml.load(invalid)
+
+    with pytest.raises(LoadError) as exc:
+        _yaml.validate_node(base, ['kind', 'description', 'moods', 'children', 'extra'])
+
+    assert (exc.value.reason == LoadErrorReason.INVALID_YAML)
+
+
+@pytest.mark.datafiles(os.path.join(DATA_DIR))
 def test_node_get(datafiles):
 
     filename = os.path.join(datafiles.dirname,


[buildstream] 07/07: Fix affected test cases

Posted by tv...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

tvb pushed a commit to branch validation
in repository https://gitbox.apache.org/repos/asf/buildstream.git

commit dc8c5fb9fbbe4bacffbd84568913b4224365398c
Author: Tristan Maat <tr...@codethink.co.uk>
AuthorDate: Thu Sep 7 14:59:23 2017 +0100

    Fix affected test cases
---
 tests/project/project.py | 2 +-
 tests/sources/git.py     | 6 +++---
 2 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/tests/project/project.py b/tests/project/project.py
index f4c5b3e..c8f7909 100644
--- a/tests/project/project.py
+++ b/tests/project/project.py
@@ -29,7 +29,7 @@ def test_missing_project_name(datafiles):
         project = Project(directory, 'x86_64')
         project._resolve(None)
 
-    assert (exc.value.reason == LoadErrorReason.INVALID_DATA)
+    assert (exc.value.reason == LoadErrorReason.INVALID_YAML)
 
 
 @pytest.mark.datafiles(os.path.join(DATA_DIR))
diff --git a/tests/sources/git.py b/tests/sources/git.py
index 43a64a0..a3649a8 100644
--- a/tests/sources/git.py
+++ b/tests/sources/git.py
@@ -80,9 +80,9 @@ class GitSubmoduleSetup(GitSetup):
         if url:
             template += "  ref: {ref}\n"
 
-        template += "submodules:\n" + \
-                    "  subrepo:\n" + \
-                    "    url: {subrepo}\n"
+        template += "  submodules:\n" + \
+                    "    subrepo:\n" + \
+                    "      url: {subrepo}\n"
 
         final = template.format(url=url, ref=ref, track=track, subrepo=self.subrepo_url)
 


[buildstream] 06/07: Add source plugin node validations

Posted by tv...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

tvb pushed a commit to branch validation
in repository https://gitbox.apache.org/repos/asf/buildstream.git

commit be94c716210b7e9530be67dd20a8c625d7b731ae
Author: Tristan Maat <tr...@codethink.co.uk>
AuthorDate: Thu Sep 7 15:40:57 2017 +0100

    Add source plugin node validations
---
 buildstream/plugins/sources/bzr.py           | 2 ++
 buildstream/plugins/sources/git.py           | 2 ++
 buildstream/plugins/sources/local.py         | 2 ++
 buildstream/plugins/sources/ostree.py        | 2 ++
 buildstream/plugins/sources/tar.py           | 5 +++++
 tests/project/data/plugins/sources/custom.py | 1 +
 6 files changed, 14 insertions(+)

diff --git a/buildstream/plugins/sources/bzr.py b/buildstream/plugins/sources/bzr.py
index 42d3482..e74d87f 100644
--- a/buildstream/plugins/sources/bzr.py
+++ b/buildstream/plugins/sources/bzr.py
@@ -58,6 +58,8 @@ from buildstream import utils
 class BzrSource(Source):
 
     def configure(self, node):
+        self.node_validate(node, ['kind', 'url', 'track', 'ref'])
+
         self.original_url = self.node_get_member(node, str, 'url')
         self.tracking = self.node_get_member(node, str, 'track')
         self.ref = self.node_get_member(node, str, 'ref', '') or None
diff --git a/buildstream/plugins/sources/git.py b/buildstream/plugins/sources/git.py
index e1cac97..dfc8e61 100644
--- a/buildstream/plugins/sources/git.py
+++ b/buildstream/plugins/sources/git.py
@@ -214,6 +214,8 @@ class GitSource(Source):
     def configure(self, node):
         ref = self.node_get_member(node, str, 'ref', '') or None
 
+        self.node_validate(node, ['kind', 'url', 'track', 'ref', 'submodules'])
+
         self.original_url = self.node_get_member(node, str, 'url')
         self.mirror = GitMirror(self, '', self.original_url, ref)
         self.tracking = self.node_get_member(node, str, 'track', '') or None
diff --git a/buildstream/plugins/sources/local.py b/buildstream/plugins/sources/local.py
index a2f0992..2611a6f 100644
--- a/buildstream/plugins/sources/local.py
+++ b/buildstream/plugins/sources/local.py
@@ -45,6 +45,8 @@ class LocalSource(Source):
     def configure(self, node):
         project = self.get_project()
 
+        self.node_validate(node, ['path', 'kind', 'directory'])
+
         self.path = self.node_get_member(node, str, 'path')
         self.fullpath = os.path.join(project.directory, self.path)
 
diff --git a/buildstream/plugins/sources/ostree.py b/buildstream/plugins/sources/ostree.py
index f08afc5..f1cec28 100644
--- a/buildstream/plugins/sources/ostree.py
+++ b/buildstream/plugins/sources/ostree.py
@@ -63,6 +63,8 @@ class OSTreeSource(Source):
     def configure(self, node):
         project = self.get_project()
 
+        self.node_validate(node, ['kind', 'url', 'ref', 'track', 'gpg-key'])
+
         self.original_url = self.node_get_member(node, str, 'url')
         self.url = project.translate_url(self.original_url)
         self.ref = self.node_get_member(node, str, 'ref', '') or None
diff --git a/buildstream/plugins/sources/tar.py b/buildstream/plugins/sources/tar.py
index f174c79..f0facdc 100644
--- a/buildstream/plugins/sources/tar.py
+++ b/buildstream/plugins/sources/tar.py
@@ -62,6 +62,11 @@ class TarSource(Source):
     def configure(self, node):
         project = self.get_project()
 
+        self.node_validate(node, [
+            'kind', 'url', 'ref', 'base-dir',
+            'track', 'directory', 'sha256sum'
+        ])
+
         self.original_url = self.node_get_member(node, str, 'url')
         self.ref = self.node_get_member(node, str, 'ref', '') or None
         self.base_dir = self.node_get_member(node, str, 'base-dir', '*') or None
diff --git a/tests/project/data/plugins/sources/custom.py b/tests/project/data/plugins/sources/custom.py
index c3cddb3..411b0b9 100644
--- a/tests/project/data/plugins/sources/custom.py
+++ b/tests/project/data/plugins/sources/custom.py
@@ -5,6 +5,7 @@ class CustomSource(Source):
 
     def configure(self, node):
         print("Source Data: %s" % node)
+        self.node_validate(node, ['configuration'])
         self.configuration = self.node_get_member(node, str, "configuration")
 
     def preflight(self):