From 5f6d60898b0decca82239a41ae580bac2e3d66f8 Mon Sep 17 00:00:00 2001
From: Filip Valder <fvalder@redhat.com>
Date: Nov 10 2021 10:35:25 +0000
Subject: Add module obsoletes tests


---

diff --git a/obsoletes/.gitkeep b/obsoletes/.gitkeep
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/obsoletes/.gitkeep
diff --git a/tests/obsoletes/nodejs.yaml b/tests/obsoletes/nodejs.yaml
new file mode 100644
index 0000000..1b849b3
--- /dev/null
+++ b/tests/obsoletes/nodejs.yaml
@@ -0,0 +1,10 @@
+document: modulemd-obsoletes
+version: 1
+data:
+  modified: 2018-05-23T14:25Z
+  module: nodejs
+  stream: "11"
+  message: "Module stream nodejs:11 is no longer supported. It is recommended to switch to nodejs:12"
+  obsoleted_by:
+    module: nodejs
+    stream: "12"
diff --git a/tests/validate.py b/tests/validate.py
index e1cb943..ce4aa44 100755
--- a/tests/validate.py
+++ b/tests/validate.py
@@ -1,6 +1,5 @@
 #!/usr/bin/python3
 
-
 import gi
 import git
 import os
@@ -16,7 +15,7 @@ from gi.repository import Modulemd  # noqa
 logging.getLogger().setLevel(logging.INFO)
 
 
-def do_validate(filename):
+def do_validate(filename, obsoletes=False):
     # Valid filenames must end in ".yaml" to be properly included by Pungi
     if not filename.endswith(".yaml"):
         error(
@@ -49,11 +48,23 @@ def do_validate(filename):
         error("There must be exactly one module represented by this file")
         return False, None
 
-    # The files must have exactly one Modulemd.Defaults object
+    # The files must have exactly one Modulemd object
     module = idx.get_module(names[0])
-    defaults = module.get_defaults()
 
-    if defaults is None:
+    if obsoletes:
+        mmds = module.get_obsoletes()
+        if not len(mmds) == 1:
+            error(
+                "There must be exactly one module obsoletes document for {}".format(
+                    module.props.module_name
+                )
+            )
+            return False, None
+        mmd = mmds[0]
+    else:
+        mmd = module.get_defaults()
+
+    if mmd is None:
         error(
             "No defaults document provided for {}".format(
                 module.props.module_name
@@ -69,21 +80,29 @@ def do_validate(filename):
     # Filenames must match their contents
     expected_name = os.path.basename(filename).rsplit(".", maxsplit=1)[0]
 
-    if expected_name != defaults.props.module_name:
+    if expected_name != mmd.props.module_name:
         error(
             'Module name "{}" doesn\'t match filename "{}.yaml"'.format(
-                defaults.props.module_name, expected_name
+                mmd.props.module_name, expected_name
             )
         )
         return False, None
 
-    default_stream = defaults.get_default_stream()
+    if obsoletes:
+        is_valid = mmd.validate()
+        if is_valid:
+            info("{} obsoletes are valid".format(filename))
+        else:
+            error("{} obsoletes are NOT valid".format(filename))
+        return (mmd.validate(), mmd)
+
+    default_stream = mmd.get_default_stream()
     if default_stream:
         # Default streams must also appear in the profiles list
-        if defaults.get_default_profiles_for_stream(default_stream) is None:
+        if mmd.get_default_profiles_for_stream(default_stream) is None:
             error(
                 "Stream '{}' is missing from the profiles for '{}'".format(
-                    default_stream, defaults.get_module_name()
+                    default_stream, mmd.get_module_name()
                 )
             )
             return False, None
@@ -91,7 +110,7 @@ def do_validate(filename):
     # Modules in Fedora must not specify "Intents"
     # TODO: This needs a new interface exposed in libmodulemd v2
 
-    info("{} is valid".format(filename))
+    info("{} defaults are valid".format(filename))
     return (True, idx)
 
 
@@ -104,12 +123,23 @@ def main():
         script_dir = os.path.dirname(os.path.realpath(__file__))
     defaults_dir = os.path.abspath(os.path.join(script_dir, '..'))
     overrides_dir = os.path.join(defaults_dir, 'overrides')
+    #obsoletes_dir = os.path.abspath(os.path.join(defaults_dir, 'obsoletes'))
+    #obsoletes_tests_dir = os.path.abspath(os.path.join(script_dir, 'obsoletes'))
 
     # Get the repo we're running in
     repo = git.Repo(defaults_dir, search_parent_directories=True)
 
     # Get the list of files in this repository
-    files = [x for (x, y) in repo.index.entries.keys()]
+    defaults_files = list()
+    obsoletes_files = list()
+    obsoletes_test_files = list()
+    for (x, y) in repo.index.entries.keys():
+        if x.startswith('obsoletes/'):
+            obsoletes_files.append(x)
+        elif x.startswith('tests/obsoletes/'):
+            obsoletes_test_files.append(x)
+        else:
+            defaults_files.append(x)
 
     # Get the list of excluded files
     exclusions = []
@@ -121,15 +151,24 @@ def main():
                 continue
             exclusions.append(line.strip())
 
-    # Validate all of the files
-    for file in files:
-        excluded = False
+    # Validate module defaults files
+    for file in defaults_files:
+        for excl in exclusions:
+            if file.startswith(excl):
+                break
+        else:
+            (valid, _) = do_validate(file)
+            if not valid:
+                error("{} failed to validate".format(file))
+                result = os.EX_DATAERR
+
+    # Validate module obsoletes test files
+    for file in obsoletes_test_files:
         for excl in exclusions:
             if file.startswith(excl):
-                excluded = True
                 break
-        if not excluded:
-            (valid, idx) = do_validate(file)
+        else:
+            (valid, _) = do_validate(file, obsoletes=True)
             if not valid:
                 error("{} failed to validate".format(file))
                 result = os.EX_DATAERR
@@ -141,9 +180,14 @@ def main():
     # conflicts arise that weren't detected by the above tests. This should be
     # impossible.
     try:
-        idx = Modulemd.ModuleIndex()
-        idx.update_from_defaults_directory(path=defaults_dir,
-                                           strict=True)
+        idx_runtime = Modulemd.ModuleIndex()
+        idx_runtime.update_from_defaults_directory(path=defaults_dir,
+                                                   strict=True)
+        # for obsoletes tests, because simple index copy is not possible
+        # due to: GObject descendants' instances are non-copyable
+        idx_tests = Modulemd.ModuleIndex()
+        idx_tests.update_from_defaults_directory(path=defaults_dir,
+                                                 strict=True)
     except GLib.Error as e:
         error("Could not merge all defaults: {}".format(e.message))
         result = os.EX_DATAERR
@@ -153,14 +197,14 @@ def main():
 
     print("\nDefault streams (Runtime):")
     print("================")
-    for m, s in idx.get_default_streams().items():
+    for m, s in idx_runtime.get_default_streams().items():
         print("{}:{}".format(m, s))
 
     try:
-        idx = Modulemd.ModuleIndex()
-        idx.update_from_defaults_directory(path=defaults_dir,
-                                           overrides_path=overrides_dir,
-                                           strict=True)
+        idx_buildroot = Modulemd.ModuleIndex()
+        idx_buildroot.update_from_defaults_directory(path=defaults_dir,
+                                                     overrides_path=overrides_dir,
+                                                     strict=True)
     except GLib.Error as e:
         error("Could not merge all defaults: {}".format(e.message))
         result = os.EX_DATAERR
@@ -170,9 +214,51 @@ def main():
 
     print("\nDefault streams (Buildroot):")
     print("================")
-    for m, s in idx.get_default_streams().items():
+    for m, s in idx_buildroot.get_default_streams().items():
         print("{}:{}".format(m, s))
 
+    # Try merging TEST module obsoletes into the module defaults index.
+    for file in obsoletes_test_files:
+        for excl in exclusions:
+            if file.startswith(excl):
+                break
+        else:
+            (_, idx) = do_validate(file, obsoletes=True)
+            idx_tests.add_obsoletes(idx)
+
+    print("\nObsoletes (tests):")
+    print("================")
+    for module_name in idx_tests.get_module_names():
+        module = idx_tests.get_module(module_name)
+        obsoletes = module.get_obsoletes()
+        if not obsoletes:
+            continue
+        if len(obsoletes) > 1:
+            result = os.EX_DATAERR
+            break
+        print("{}:{}".format(module_name, obsoletes[0]))
+
+    # Try merging module obsoletes into the module defaults index.
+    for file in obsoletes_files:
+        for excl in exclusions:
+            if file.startswith(excl):
+                break
+        else:
+            (_, idx) = do_validate(file, obsoletes=True)
+            idx_runtime.add_obsoletes(idx)
+
+    print("\nObsoletes:")
+    print("================")
+    for module_name in idx_runtime.get_module_names():
+        module = idx_runtime.get_module(module_name)
+        obsoletes = module.get_obsoletes()
+        if not obsoletes:
+            continue
+        if len(obsoletes) > 1:
+            result = os.EX_DATAERR
+            break
+        print("{}:{}".format(module_name, obsoletes[0]))
+
     return result
 
 

