#226 Add module obsoletes tests
Closed 3 years ago by ppisar. Opened 3 years ago by fivaldi.
releng/ fivaldi/fedora-module-defaults obsoletes  into  main

Add module obsoletes tests
Filip Valder • 3 years ago  
empty or binary file added
@@ -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"

file modified
+113 -27
@@ -1,6 +1,5 @@ 

  #!/usr/bin/python3

  

- 

  import gi

  import git

  import os
@@ -16,7 +15,7 @@ 

  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 @@ 

          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 @@ 

      # 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)

Do not run mmd.validate() twice. I recommend "return(is_valid, mmd)" instead.

+ 

+     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 @@ 

      # 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 @@ 

          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'))

Could you remove this line?

+     #obsoletes_tests_dir = os.path.abspath(os.path.join(script_dir, 'obsoletes'))

And remove this line too.

  

      # 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 @@ 

                  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 @@ 

      # 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 @@ 

  

      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,

I'm not sure why you call the index with overriden defaults "buildroot" (and the other "runtime").

+                                                      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 @@ 

  

      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:

This exclusion cycle exists in the script 4 times. In addition, it processes obsoletes_test_files twice. Could filter all the 3 lists (defaults_files, obsoletes_test_files, obsoletes_files) at the beginning just when then are populated? Instead of excluding them when they are used?

+             if file.startswith(excl):

+                 break

+         else:

+             (_, idx) = do_validate(file, obsoletes=True)

+             idx_tests.add_obsoletes(idx)

The "idx" variable is not an index. It's a modulemd-obsoletes object. Rename the variable to e.g. "mmd".

+ 

+     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)

This "idx" is also not an index. It's a modulemd-obsoletes object. Can you rename it.
Moreover, do_validate() returns only the first modulemd-obsoletes object from the "file". What if the file contains multiple modulemd-obsoletes documents. E.g. You want to obsolete both nodejs:11 and nodejs:12 in Fedora 36. Or will be there a rule that each obsoleted stream must be in a separate file?

+ 

+     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

  

  

Additional test data will be added in subsequent PRs.

Metadata Update from @ppisar:
- Request assigned

3 years ago

Could you remove this line?

And remove this line too.

Do not run mmd.validate() twice. I recommend "return(is_valid, mmd)" instead.

The "idx" variable is not an index. It's a modulemd-obsoletes object. Rename the variable to e.g. "mmd".

This "idx" is also not an index. It's a modulemd-obsoletes object. Can you rename it.
Moreover, do_validate() returns only the first modulemd-obsoletes object from the "file". What if the file contains multiple modulemd-obsoletes documents. E.g. You want to obsolete both nodejs:11 and nodejs:12 in Fedora 36. Or will be there a rule that each obsoleted stream must be in a separate file?

This exclusion cycle exists in the script 4 times. In addition, it processes obsoletes_test_files twice. Could filter all the 3 lists (defaults_files, obsoletes_test_files, obsoletes_files) at the beginning just when then are populated? Instead of excluding them when they are used?

I'm not sure why you call the index with overriden defaults "buildroot" (and the other "runtime").

Filip, could you address the findings of this review?

I tried running the test and it fails:

$ python3 tests/validate.py
[...]
Obsoletes (tests):
================
nodejs:<Modulemd.Obsoletes object at 0x7fe1eda2b980 (ModulemdObsoletes at 0x563b3e9a5260)>
ERROR:root:obsoletes/.gitkeep 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.
Traceback (most recent call last):
  File "/home/petr/fedora-modules/fedora-module-defaults/tests/validate.py", line 266, in <module>
    sys.exit(main())
  File "/home/petr/fedora-modules/fedora-module-defaults/tests/validate.py", line 248, in main
    idx_runtime.add_obsoletes(idx)
TypeError: Argument 1 does not allow None as a value

I merged the commit and fixed the fatal errors. The minor issues reported in this review are not addressed.

I corrected the minor issues.

The only remaining question for you is:

do_validate() returns only the first modulemd-obsoletes object from the "file". What if the file contains multiple modulemd-obsoletes documents. E.g. You want to obsolete both nodejs:11 and nodejs:12 in Fedora 36. Or will be there a rule that each obsoleted stream must be in a separate file?

Pull-Request has been closed by ppisar

3 years ago