Bug 1312520 - Add an option to the manifestparser to prevent defaults from propagating to individual section definitions. r=ahal draft
authorChris Manchester <cmanchester@mozilla.com>
Mon, 24 Oct 2016 19:24:05 -0700
changeset 428946 0a582ed5121974a6f0d577e9bef9f8b851f25691
parent 428736 c845bfd0accb7e0c29b41713255963b08006e701
child 428947 37618a19d16fc90962cdc51f247fd6137b199a93
push id33436
push userbmo:cmanchester@mozilla.com
push dateMon, 24 Oct 2016 20:11:53 +0000
reviewersahal
bugs1312520
milestone52.0a1
Bug 1312520 - Add an option to the manifestparser to prevent defaults from propagating to individual section definitions. r=ahal Consumers will be expected to process defaults themselves through the "manifest_defaults" member variable instead. MozReview-Commit-ID: IGnOj3zEJfE
testing/mozbase/manifestparser/manifestparser/ini.py
testing/mozbase/manifestparser/manifestparser/manifestparser.py
testing/mozbase/manifestparser/tests/default-suppfiles.ini
testing/mozbase/manifestparser/tests/test_default_overrides.py
--- a/testing/mozbase/manifestparser/manifestparser/ini.py
+++ b/testing/mozbase/manifestparser/manifestparser/ini.py
@@ -3,27 +3,28 @@
 # You can obtain one at http://mozilla.org/MPL/2.0/.
 
 import os
 
 __all__ = ['read_ini']
 
 
 def read_ini(fp, variables=None, default='DEFAULT', defaults_only=False,
-             comments=';#', separators=('=', ':'),
-             strict=True):
+             comments=';#', separators=('=', ':'), strict=True,
+             handle_defaults=True):
     """
     read an .ini file and return a list of [(section, values)]
     - fp : file pointer or path to read
     - variables : default set of variables
     - default : name of the section for the default section
     - defaults_only : if True, return the default section only
     - comments : characters that if they start a line denote a comment
     - separators : strings that denote key, value separation in order
     - strict : whether to be strict about parsing
+    - handle_defaults : whether to incorporate defaults into each section
     """
 
     # variables
     variables = variables or {}
     sections = []
     key = value = None
     section_names = set()
     if isinstance(fp, basestring):
@@ -106,18 +107,22 @@ def read_ini(fp, variables=None, default
         root = os.path.join(os.path.dirname(fp.name),
                             variables['server-root'])
         variables['server-root'] = os.path.abspath(root)
 
     # return the default section only if requested
     if defaults_only:
         return [(default, variables)]
 
-    # interpret the variables
+    # Interpret the variables in the context of inherited defaults if
+    # requested.
     def interpret_variables(global_dict, local_dict):
+        if not handle_defaults:
+            return local_dict
+
         variables = global_dict.copy()
 
         # These variables are combinable when they appear both in default
         # and per-entry.
         for field_name, pattern in (('skip-if', '(%s) || (%s)'),
                                     ('support-files', '%s %s')):
             local_value, global_value = local_dict.get(field_name), variables.get(field_name)
             if local_value and global_value:
--- a/testing/mozbase/manifestparser/manifestparser/manifestparser.py
+++ b/testing/mozbase/manifestparser/manifestparser/manifestparser.py
@@ -41,17 +41,17 @@ def denormalize_path(path):
 
 
 # objects for parsing manifests
 
 class ManifestParser(object):
     """read .ini manifests"""
 
     def __init__(self, manifests=(), defaults=None, strict=True, rootdir=None,
-                 finder=None):
+                 finder=None, omit_defaults=False):
         """Creates a ManifestParser from the given manifest files.
 
         :param manifests: An iterable of file paths or file objects corresponding
                           to manifests. If a file path refers to a manifest file that
                           does not exist, an IOError is raised.
         :param defaults: Variables to pre-define in the environment for evaluating
                          expressions in manifests.
         :param strict: If False, the provided manifests may contain references to
@@ -61,25 +61,30 @@ class ManifestParser(object):
                        section names, redefining variables, and defining empty
                        variables.
         :param rootdir: The directory used as the basis for conversion to and from
                         relative paths during manifest reading.
         :param finder: If provided, this finder object will be used for filesystem
                        interactions. Finder objects are part of the mozpack package,
                        documented at
                        http://gecko.readthedocs.org/en/latest/python/mozpack.html#module-mozpack.files
+        :param omit_defaults: If set, do not propagate manifest defaults to individual
+                              test objects. Callers are expected to manage per-manifest
+                              defaults themselves via the manifest_defaults member
+                              variable in this case.
         """
         self._defaults = defaults or {}
         self._ancestor_defaults = {}
         self.tests = []
         self.manifest_defaults = {}
         self.strict = strict
         self.rootdir = rootdir
         self.relativeRoot = None
         self.finder = finder
+        self._omit_defaults = omit_defaults
         if manifests:
             self.read(*manifests)
 
     def path_exists(self, path):
         if self.finder:
             return self.finder.get(path) is not None
         return os.path.exists(path)
 
@@ -134,17 +139,18 @@ class ManifestParser(object):
         # the microoptimization used below.
         if self.rootdir is None:
             rootdir = ""
         else:
             assert os.path.isabs(self.rootdir)
             rootdir = self.rootdir + os.path.sep
 
         # read the configuration
-        sections = read_ini(fp=fp, variables=defaults, strict=self.strict)
+        sections = read_ini(fp=fp, variables=defaults, strict=self.strict,
+                            handle_defaults=not self._omit_defaults)
         self.manifest_defaults[filename] = defaults
 
         parent_section_found = False
 
         # get the tests
         for section, data in sections:
             subsuite = ''
             if 'subsuite' in data:
--- a/testing/mozbase/manifestparser/tests/default-suppfiles.ini
+++ b/testing/mozbase/manifestparser/tests/default-suppfiles.ini
@@ -1,9 +1,9 @@
 [DEFAULT]
 support-files = foo.js # a comment
 
-[test1]
-[test2]
+[test7]
+[test8]
 support-files = bar.js # another comment
-[test3]
+[test9]
 foo = bar
 
--- a/testing/mozbase/manifestparser/tests/test_default_overrides.py
+++ b/testing/mozbase/manifestparser/tests/test_default_overrides.py
@@ -37,19 +37,61 @@ class TestDefaultSkipif(unittest.TestCas
 class TestDefaultSupportFiles(unittest.TestCase):
     """Tests combining support-files field in [DEFAULT] with the value for a test"""
 
     def test_defaults(self):
 
         default = os.path.join(here, 'default-suppfiles.ini')
         parser = ManifestParser(manifests=(default,))
         expected_supp_files = {
-            'test1': 'foo.js # a comment',
-            'test2': 'foo.js  bar.js ',
-            'test3': 'foo.js # a comment',
+            'test7': 'foo.js # a comment',
+            'test8': 'foo.js  bar.js ',
+            'test9': 'foo.js # a comment',
         }
         for test in parser.tests:
             expected = expected_supp_files[test['name']]
             self.assertEqual(test['support-files'], expected)
 
 
+class TestOmitDefaults(unittest.TestCase):
+    """Tests passing omit-defaults prevents defaults from propagating to definitions.
+    """
+
+    def test_defaults(self):
+        manifests = (os.path.join(here, 'default-suppfiles.ini'),
+                     os.path.join(here, 'default-skipif.ini'))
+        parser = ManifestParser(manifests=manifests, omit_defaults=True)
+        expected_supp_files = {
+            'test8': 'bar.js # another comment',
+        }
+        expected_skip_ifs = {
+            'test1': "debug",
+            'test2': "os == 'linux'",
+            'test3': "os == 'win'",
+            'test4': "os == 'win' && debug",
+            'test6': "debug # a second pesky comment",
+        }
+        for test in parser.tests:
+            for field, expectations in (('support-files', expected_supp_files),
+                                        ('skip-if', expected_skip_ifs)):
+                expected = expectations.get(test['name'])
+                if not expected:
+                    self.assertNotIn(field, test)
+                else:
+                    self.assertEqual(test[field], expected)
+
+        expected_defaults = {
+            os.path.join(here, 'default-suppfiles.ini'): {
+                "support-files": "foo.js # a comment",
+            },
+            os.path.join(here, 'default-skipif.ini'): {
+                "skip-if": "os == 'win' && debug # a pesky comment",
+            },
+        }
+        for path, defaults in expected_defaults.items():
+            self.assertIn(path, parser.manifest_defaults)
+            actual_defaults = parser.manifest_defaults[path]
+            for key, value in defaults.items():
+                self.assertIn(key, actual_defaults)
+                self.assertEqual(value, actual_defaults[key])
+
 if __name__ == '__main__':
     unittest.main()