From d05be2a426aac0b93cbe9aece1e360f7478a5bb5 Mon Sep 17 00:00:00 2001
From: Stephen Gallagher <sgallagh@redhat.com>
Date: May 18 2018 15:41:58 +0000
Subject: [PATCH 1/2] Add validation script for module defaults


This script should be run on all pull-requests to ensure that no
invalid module defaults get merged.

Signed-off-by: Stephen Gallagher <sgallagh@redhat.com>

---

diff --git a/Makefile b/Makefile
index 9e7f759..775b12c 100644
--- a/Makefile
+++ b/Makefile
@@ -1,2 +1,2 @@
 check:
-	/usr/bin/true
+	@python3 tests/validate.py
diff --git a/tests/diffname.yaml b/tests/diffname.yaml
new file mode 100644
index 0000000..7d53018
--- /dev/null
+++ b/tests/diffname.yaml
@@ -0,0 +1,8 @@
+document: modulemd-defaults
+version: 1
+data:
+    module: samplemodule
+    profiles:
+        6: [default]
+        8: [default]
+        9: [default]
diff --git a/tests/empty.yaml b/tests/empty.yaml
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/empty.yaml
diff --git a/tests/exclusions.txt b/tests/exclusions.txt
new file mode 100644
index 0000000..f111e68
--- /dev/null
+++ b/tests/exclusions.txt
@@ -0,0 +1,22 @@
+# This file lists repo contents that should not be validated by the CI system
+
+# Skip the CI integration bits
+.cico.pipeline
+run_tests.sh
+Makefile
+
+# Skip the README file
+README.md
+
+# Skip the validator files
+tests/exclusions.txt
+tests/validate.py
+
+# Comment out the following when testing the validator
+tests/diffname.yaml
+tests/empty.yaml
+tests/intents.yaml
+tests/missingstream.yaml
+tests/module.yaml
+tests/nodejs.yaml
+
diff --git a/tests/intents.yaml b/tests/intents.yaml
new file mode 100644
index 0000000..681dd82
--- /dev/null
+++ b/tests/intents.yaml
@@ -0,0 +1,18 @@
+document: modulemd-defaults
+version: 1
+data:
+    module: intents
+    stream: x.y
+    profiles:
+        'x.y': []
+        bar: [baz, snafu]
+    intents:
+        desktop:
+            stream: y.z
+            profiles:
+                'y.z': [blah]
+                'x.y': [other]
+        server:
+            stream: x.y
+            profiles:
+                'x.y': []
diff --git a/tests/missingstream.yaml b/tests/missingstream.yaml
new file mode 100644
index 0000000..42996a8
--- /dev/null
+++ b/tests/missingstream.yaml
@@ -0,0 +1,5 @@
+document: modulemd-defaults
+version: 1
+data:
+    module: missingstream
+    stream: 2.5
\ No newline at end of file
diff --git a/tests/module.yaml b/tests/module.yaml
new file mode 100644
index 0000000..04bda2b
--- /dev/null
+++ b/tests/module.yaml
@@ -0,0 +1,51 @@
+---
+document: modulemd
+version: 2
+data:
+  summary: Javascript runtime
+  description: >-
+    Node.js is a platform built on Chrome''s JavaScript runtime for easily building
+    fast, scalable network applications. Node.js uses an event-driven, non-blocking
+    I/O model that makes it lightweight and efficient, perfect for data-intensive
+    real-time applications that run across distributed devices.
+  license:
+    module:
+    - MIT
+  dependencies:
+  - buildrequires:
+      platform: []
+    requires:
+      platform: []
+  references:
+    community: http://nodejs.org
+    documentation: http://nodejs.org/en/docs
+    tracker: https://github.com/nodejs/node/issues
+  profiles:
+    default:
+      rpms:
+      - nodejs
+      - npm
+    development:
+      rpms:
+      - nodejs
+      - nodejs-devel
+      - npm
+    minimal:
+      rpms:
+      - nodejs
+  api:
+    rpms:
+    - nodejs
+    - nodejs-devel
+    - npm
+  components:
+    rpms:
+      libuv:
+        rationale: Platform abstraction layer for Node.js
+        ref: 1.20
+        buildorder: 0
+      nodejs:
+        rationale: Javascript runtime and npm package manager.
+        ref: 10
+        buildorder: 10
+...
diff --git a/tests/nodejs.yaml b/tests/nodejs.yaml
new file mode 100644
index 0000000..a964e46
--- /dev/null
+++ b/tests/nodejs.yaml
@@ -0,0 +1,8 @@
+document: modulemd-defaults
+version: 1
+data:
+    module: nodejs
+    profiles:
+        6: [different]
+        8: [default]
+        9: [default]
diff --git a/tests/validate.py b/tests/validate.py
new file mode 100755
index 0000000..c8f9887
--- /dev/null
+++ b/tests/validate.py
@@ -0,0 +1,140 @@
+#!/usr/bin/python3
+
+
+import gi
+import git
+import os
+import sys
+
+from gi.repository import GLib
+gi.require_version('Modulemd', '1.0')
+from gi.repository import Modulemd
+
+def do_validate(filename):
+    # Valid filenames must end in ".yaml" to be properly included by Pungi
+    if not filename.endswith(".yaml"):
+        print("%s does not end with .yaml. It will not be included by "
+              "Pungi. If this file does not contain defaults, it should "
+              "be added to the tests/exclusions.txt file." % filename,
+              file=sys.stderr,)
+        return False, None
+
+    # The files must parse correctly
+    try:
+        (objects, failures) = Modulemd.objects_from_file_ext(filename)
+    except GLib.Error as e:
+        print(e.message, file=sys.stderr)
+        return False, None
+
+    if (failures):
+        for failure in failures:
+            print("Failed subdocument (%s): \n%s\n", failure.message,
+                  failure.doc, file=sys.stderr)
+        return False, None
+
+    # There must be exactly one object per file
+    if len(objects) != 1:
+        print("There must be exactly one subdocument", file=sys.stderr)
+        return False, None
+
+    # The files must have exactly one Modulemd.Defaults object
+    try:
+        (default, def_failures) = \
+            Modulemd.Defaults.new_from_file_ext (filename)
+    except GLib.Error as e:
+        print(e.message, file=sys.stderr)
+        return False, None
+
+    # Filenames must match their contents
+    expected_name = os.path.basename(filename).rsplit('.', maxsplit=1)[0]
+
+    if expected_name != default.props.module_name:
+        print("Module name \"%s\" doesn't match filename \"%s.yaml\"" % (
+              default.props.module_name, expected_name),
+              file=sys.stderr)
+        return False, None
+
+    if default.props.default_stream:
+        # Default streams must also appear in the profiles list
+        if not default.props.default_stream in default.props.profile_defaults:
+            print("Stream '%s' missing from profiles" % (
+                  default.props.default_stream),
+                  file=sys.stderr)
+            return False, None
+
+    # Modules in Fedora must not specify "Intents"
+    try:
+        if (default.props.intents):
+            print ('Module name "%s" includes the intents section, which is '
+                   'not currently permitted in Fedora.' % (
+                       default.props.module_name),
+                   file=sys.stderr)
+            return False, None
+    except AttributeError:
+        # The available version of libmodulemd does not support intents
+        pass
+
+    print("%s is valid" % filename)
+    return (True, default)
+
+
+def main():
+    result = os.EX_OK
+
+    script_dir = os.path.dirname(os.path.realpath(__file__))
+
+    # Get the repo we're running in
+    repo = git.Repo(script_dir,
+                    search_parent_directories=True)
+
+    # Get the list of files in this repository
+    files = [x for (x, y) in repo.index.entries.keys()]
+
+    # Get the list of excluded files
+    exclusions = []
+    with open(os.path.join(script_dir, "exclusions.txt")) as f:
+        for line in f:
+            stripped_line = line.strip()
+            if not stripped_line or stripped_line.startswith("#"):
+                # Ignore comments and empty lines
+                continue
+            exclusions.append(line.strip())
+
+    # Validate all of the files
+    prioritizer = Modulemd.Prioritizer()
+    for file in files:
+        excluded = False
+        for excl in exclusions:
+            if (file.startswith(excl)):
+                excluded = True
+                break
+        if not excluded:
+            (valid, obj) = do_validate(file)
+            if not valid:
+                print("%s failed to validate" % file, file=sys.stderr)
+                result = os.EX_DATAERR
+            else:
+                try:
+                    prioritizer.add([obj,], 0)
+                except GLib.Error as e:
+                    print("Could not merge %s with other defaults: %s" % (
+                          file, e.message))
+                    result = os.EX_DATAERR
+
+    if result == os.EX_DATAERR:
+        return result
+
+    # For sanity's sake, also do a merge of all the defaults to make sure no
+    # conflicts arise that weren't detected by the above tests. This should be
+    # impossible.
+    try:
+        prioritizer.resolve()
+    except GLib.Error as e:
+        print("Could not merge all defaults: %s" % e.message)
+        result = os.EX_DATAERR
+
+
+    return result
+
+if __name__ == "__main__":
+    sys.exit(main())
\ No newline at end of file

From 81c054a349704e601f54ba72ce924ebb137bb0cd Mon Sep 17 00:00:00 2001
From: Stephen Gallagher <sgallagh@redhat.com>
Date: May 18 2018 16:25:00 +0000
Subject: [PATCH 2/2] [DO_NOT_MERGE] Test validation errors


Signed-off-by: Stephen Gallagher <sgallagh@redhat.com>

---

diff --git a/tests/exclusions.txt b/tests/exclusions.txt
index f111e68..0a87e86 100644
--- a/tests/exclusions.txt
+++ b/tests/exclusions.txt
@@ -13,10 +13,10 @@ tests/exclusions.txt
 tests/validate.py
 
 # Comment out the following when testing the validator
-tests/diffname.yaml
-tests/empty.yaml
-tests/intents.yaml
-tests/missingstream.yaml
-tests/module.yaml
-tests/nodejs.yaml
+#tests/diffname.yaml
+#tests/empty.yaml
+#tests/intents.yaml
+#tests/missingstream.yaml
+#tests/module.yaml
+#tests/nodejs.yaml
 

