Bug 1320194 - Refactor test metadata related backend code into a partial TestManifestBackend draft
authorAndrew Halberstadt <ahalberstadt@mozilla.com>
Fri, 27 Jan 2017 11:47:34 -0500
changeset 482036 7ce7e27bbf34f5525dfc048eabdb8eccbb251625
parent 482035 42b60312cf69531eb71c55d47b3787c727a8c89f
child 482037 ecd9bfd17fff0531e391156df903ac6ce7aa9fdd
push id44987
push userahalberstadt@mozilla.com
push dateFri, 10 Feb 2017 21:37:10 +0000
bugs1320194
milestone54.0a1
Bug 1320194 - Refactor test metadata related backend code into a partial TestManifestBackend Currently the CommonBackend is responsible for processing TestManifest objects and using them to generate the test metadata files (e.g all-tests.pkl et al). This patch pulls that logic out into a partial backend specifically for test manifests. This patch is solely a refactoring and shouldn't change any build behaviour. CommonBackend has a TestManifestBackend instance and calls consume_object directly on it. However, this is just a temporary measure to avoid checking in a broken commit. This commit also adds a test for the 'test-defaults.pkl' file which was previously missing. MozReview-Commit-ID: HOr2QVT8CJ1
python/mozbuild/mozbuild/backend/__init__.py
python/mozbuild/mozbuild/backend/common.py
python/mozbuild/mozbuild/backend/test_manifest.py
python/mozbuild/mozbuild/test/backend/test_recursivemake.py
python/mozbuild/mozbuild/test/backend/test_test_manifest.py
python/mozbuild/mozbuild/test/python.ini
--- a/python/mozbuild/mozbuild/backend/__init__.py
+++ b/python/mozbuild/mozbuild/backend/__init__.py
@@ -5,16 +5,17 @@
 backends = {
     'AndroidEclipse': 'mozbuild.backend.android_eclipse',
     'ChromeMap': 'mozbuild.codecoverage.chrome_map',
     'CompileDB': 'mozbuild.compilation.database',
     'CppEclipse': 'mozbuild.backend.cpp_eclipse',
     'FasterMake': 'mozbuild.backend.fastermake',
     'FasterMake+RecursiveMake': None,
     'RecursiveMake': 'mozbuild.backend.recursivemake',
+    'TestManifest': 'mozbuild.backend.test_manifest',
     'Tup': 'mozbuild.backend.tup',
     'VisualStudio': 'mozbuild.backend.visualstudio',
 }
 
 
 def get_backend_class(name):
     if '+' in name:
         from mozbuild.backend.base import HybridBackend
--- a/python/mozbuild/mozbuild/backend/common.py
+++ b/python/mozbuild/mozbuild/backend/common.py
@@ -1,22 +1,22 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 from __future__ import absolute_import, unicode_literals
 
-import cPickle as pickle
 import itertools
 import json
 import os
 
 import mozpack.path as mozpath
 
 from mozbuild.backend.base import BuildBackend
+from mozbuild.backend.test_manifest import TestManifestBackend
 
 from mozbuild.frontend.context import (
     Context,
     Path,
     RenamedSourcePath,
     VARIABLES,
 )
 from mozbuild.frontend.data import (
@@ -161,85 +161,43 @@ class WebIDLCollection(object):
 
     def generated_events_basenames(self):
         return [os.path.basename(s) for s in self.generated_events_sources]
 
     def generated_events_stems(self):
         return [os.path.splitext(b)[0] for b in self.generated_events_basenames()]
 
 
-class TestManager(object):
-    """Helps hold state related to tests."""
-
-    def __init__(self, config):
-        self.config = config
-        self.topsrcdir = mozpath.normpath(config.topsrcdir)
-
-        self.tests_by_path = defaultdict(list)
-        self.installs_by_path = defaultdict(list)
-        self.deferred_installs = set()
-        self.manifest_defaults = {}
-
-    def add(self, t, flavor, topsrcdir):
-        t = dict(t)
-        t['flavor'] = flavor
-
-        path = mozpath.normpath(t['path'])
-        assert mozpath.basedir(path, [topsrcdir])
-
-        key = path[len(topsrcdir)+1:]
-        t['file_relpath'] = key
-        t['dir_relpath'] = mozpath.dirname(key)
-
-        self.tests_by_path[key].append(t)
-
-    def add_defaults(self, manifest):
-        if not hasattr(manifest, 'manifest_defaults'):
-            return
-        for sub_manifest, defaults in manifest.manifest_defaults.items():
-            self.manifest_defaults[sub_manifest] = defaults
-
-    def add_installs(self, obj, topsrcdir):
-        for src, (dest, _) in obj.installs.iteritems():
-            key = src[len(topsrcdir)+1:]
-            self.installs_by_path[key].append((src, dest))
-        for src, pat, dest in obj.pattern_installs:
-            key = mozpath.join(src[len(topsrcdir)+1:], pat)
-            self.installs_by_path[key].append((src, pat, dest))
-        for path in obj.deferred_installs:
-            self.deferred_installs.add(path[2:])
-
-
 class BinariesCollection(object):
     """Tracks state of binaries produced by the build."""
 
     def __init__(self):
         self.shared_libraries = []
         self.programs = []
 
 
 class CommonBackend(BuildBackend):
     """Holds logic common to all build backends."""
 
     def _init(self):
         self._idl_manager = XPIDLManager(self.environment)
-        self._test_manager = TestManager(self.environment)
         self._webidls = WebIDLCollection()
         self._binaries = BinariesCollection()
         self._configs = set()
         self._ipdl_sources = set()
 
+        # Temporarily compose a partial TestManifestBackend, soon test manifest
+        # processing will no longer be part of the build and this will be removed.
+        self._test_backend = TestManifestBackend(self.environment)
+
     def consume_object(self, obj):
         self._configs.add(obj.config)
 
         if isinstance(obj, TestManifest):
-            for test in obj.tests:
-                self._test_manager.add(test, obj.flavor, obj.topsrcdir)
-            self._test_manager.add_defaults(obj.manifest)
-            self._test_manager.add_installs(obj, obj.topsrcdir)
+            self._test_backend.consume_object(obj)
 
         elif isinstance(obj, XPIDLFile):
             # TODO bug 1240134 tracks not processing XPIDL files during
             # artifact builds.
             self._idl_manager.register_idl(obj)
 
         elif isinstance(obj, ConfigFileSubstitution):
             # Do not handle ConfigFileSubstitution for Makefiles. Leave that
@@ -365,31 +323,20 @@ class CommonBackend(BuildBackend):
 
         self._write_unified_files(unified_source_mapping, ipdl_dir, poison_windows_h=False)
         self._handle_ipdl_sources(ipdl_dir, sorted_ipdl_sources, unified_source_mapping)
 
         for config in self._configs:
             self.backend_input_files.add(config.source)
 
         # Write out a machine-readable file describing every test.
-        topobjdir = self.environment.topobjdir
-        with self._write_file(mozpath.join(topobjdir, 'all-tests.pkl'), mode='rb') as fh:
-            pickle.dump(dict(self._test_manager.tests_by_path), fh, protocol=2)
-
-        with self._write_file(mozpath.join(topobjdir, 'test-defaults.pkl'), mode='rb') as fh:
-            pickle.dump(self._test_manager.manifest_defaults, fh, protocol=2)
-
-        path = mozpath.join(self.environment.topobjdir, 'test-installs.pkl')
-        with self._write_file(path, mode='rb') as fh:
-            pickle.dump({k: v for k, v in self._test_manager.installs_by_path.items()
-                         if k in self._test_manager.deferred_installs},
-                        fh,
-                        protocol=2)
+        self._test_backend.consume_finished()
 
         # Write out a machine-readable file describing binaries.
+        topobjdir = self.environment.topobjdir
         with self._write_file(mozpath.join(topobjdir, 'binaries.json')) as fh:
             d = {
                 'shared_libraries': [s.to_dict() for s in self._binaries.shared_libraries],
                 'programs': [p.to_dict() for p in self._binaries.programs],
             }
             json.dump(d, fh, sort_keys=True, indent=4)
 
     def _handle_webidl_collection(self, webidls):
new file mode 100644
--- /dev/null
+++ b/python/mozbuild/mozbuild/backend/test_manifest.py
@@ -0,0 +1,95 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+from __future__ import absolute_import, unicode_literals
+
+import cPickle as pickle
+from collections import defaultdict
+
+import mozpack.path as mozpath
+
+from mozbuild.backend.base import PartialBackend
+from mozbuild.frontend.data import TestManifest
+
+
+class TestManifestBackend(PartialBackend):
+    """Partial backend that generates test metadata files."""
+
+    def _init(self):
+        self.tests_by_path = defaultdict(list)
+        self.installs_by_path = defaultdict(list)
+        self.deferred_installs = set()
+        self.manifest_defaults = {}
+
+        # Add config.status so performing a build will invalidate this backend.
+        self.backend_input_files.add(mozpath.join(
+            self.environment.topobjdir, 'config.status'))
+
+    def consume_object(self, obj):
+        if not isinstance(obj, TestManifest):
+            return
+
+        self.backend_input_files.add(obj.path)
+        self.backend_input_files |= obj.context_all_paths
+        try:
+            from reftest import ReftestManifest
+
+            if isinstance(obj.manifest, ReftestManifest):
+                # Mark included files as part of the build backend so changes
+                # result in re-config.
+                self.backend_input_files |= obj.manifest.manifests
+        except ImportError:
+            # Ignore errors caused by the reftest module not being present.
+            # This can happen when building SpiderMonkey standalone, for example.
+            pass
+
+        for test in obj.tests:
+            self.add(test, obj.flavor, obj.topsrcdir)
+        self.add_defaults(obj.manifest)
+        self.add_installs(obj, obj.topsrcdir)
+
+    def consume_finished(self):
+        topobjdir = self.environment.topobjdir
+
+        with self._write_file(mozpath.join(topobjdir, 'all-tests.pkl'), mode='rb') as fh:
+            pickle.dump(dict(self.tests_by_path), fh, protocol=2)
+
+        with self._write_file(mozpath.join(topobjdir, 'test-defaults.pkl'), mode='rb') as fh:
+            pickle.dump(self.manifest_defaults, fh, protocol=2)
+
+        path = mozpath.join(topobjdir, 'test-installs.pkl')
+        with self._write_file(path, mode='rb') as fh:
+            pickle.dump({k: v for k, v in self.installs_by_path.items()
+                         if k in self.deferred_installs},
+                        fh,
+                        protocol=2)
+
+    def add(self, t, flavor, topsrcdir):
+        t = dict(t)
+        t['flavor'] = flavor
+
+        path = mozpath.normpath(t['path'])
+        assert mozpath.basedir(path, [topsrcdir])
+
+        key = path[len(topsrcdir)+1:]
+        t['file_relpath'] = key
+        t['dir_relpath'] = mozpath.dirname(key)
+
+        self.tests_by_path[key].append(t)
+
+    def add_defaults(self, manifest):
+        if not hasattr(manifest, 'manifest_defaults'):
+            return
+        for sub_manifest, defaults in manifest.manifest_defaults.items():
+            self.manifest_defaults[sub_manifest] = defaults
+
+    def add_installs(self, obj, topsrcdir):
+        for src, (dest, _) in obj.installs.iteritems():
+            key = src[len(topsrcdir)+1:]
+            self.installs_by_path[key].append((src, dest))
+        for src, pat, dest in obj.pattern_installs:
+            key = mozpath.join(src[len(topsrcdir)+1:], pat)
+            self.installs_by_path[key].append((src, pat, dest))
+        for path in obj.deferred_installs:
+            self.deferred_installs.add(path[2:])
--- a/python/mozbuild/mozbuild/test/backend/test_recursivemake.py
+++ b/python/mozbuild/mozbuild/test/backend/test_recursivemake.py
@@ -540,56 +540,36 @@ class TestRecursiveMakeBackend(BackendTe
         lines = [l.strip() for l in open(x_master, 'rt').readlines()]
         self.assertEqual(lines, [
             '# THIS FILE WAS AUTOMATICALLY GENERATED. DO NOT MODIFY BY HAND.',
             '',
             '[include:dir1/xpcshell.ini]',
             '[include:xpcshell.ini]',
         ])
 
-        all_tests_path = mozpath.join(env.topobjdir, 'all-tests.pkl')
-        self.assertTrue(os.path.exists(all_tests_path))
-
-        with open(all_tests_path, 'rb') as fh:
-            o = pickle.load(fh)
-
-            self.assertIn('xpcshell.js', o)
-            self.assertIn('dir1/test_bar.js', o)
-
-            self.assertEqual(len(o['xpcshell.js']), 1)
-
     def test_test_manifest_pattern_matches_recorded(self):
         """Pattern matches in test manifests' support-files should be recorded."""
         env = self._consume('test-manifests-written', RecursiveMakeBackend)
         m = InstallManifest(path=mozpath.join(env.topobjdir,
             '_build_manifests', 'install', '_test_files'))
 
         # This is not the most robust test in the world, but it gets the job
         # done.
         entries = [e for e in m._dests.keys() if '**' in e]
         self.assertEqual(len(entries), 1)
         self.assertIn('support/**', entries[0])
 
     def test_test_manifest_deffered_installs_written(self):
         """Shared support files are written to their own data file by the backend."""
         env = self._consume('test-manifest-shared-support', RecursiveMakeBackend)
-        all_tests_path = mozpath.join(env.topobjdir, 'all-tests.pkl')
-        self.assertTrue(os.path.exists(all_tests_path))
         test_installs_path = mozpath.join(env.topobjdir, 'test-installs.pkl')
 
         with open(test_installs_path, 'r') as fh:
             test_installs = pickle.load(fh)
 
-        self.assertEqual(set(test_installs.keys()),
-                         set(['child/test_sub.js',
-                              'child/data/**',
-                              'child/another-file.sjs']))
-        for key in test_installs.keys():
-            self.assertIn(key, test_installs)
-
         test_files_manifest = mozpath.join(env.topobjdir,
                                            '_build_manifests',
                                            'install',
                                            '_test_files')
 
         # First, read the generated for ini manifest contents.
         m = InstallManifest(path=test_files_manifest)
 
new file mode 100644
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/backend/test_test_manifest.py
@@ -0,0 +1,73 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+from __future__ import unicode_literals
+
+import cPickle as pickle
+import os
+
+import mozpack.path as mozpath
+from mozunit import main
+
+from mozbuild.backend.test_manifest import TestManifestBackend
+from mozbuild.test.backend.common import BackendTester
+
+
+class TestTestManifestBackend(BackendTester):
+
+    def test_all_tests_metadata_file_written(self):
+        """Ensure all-tests.pkl is generated."""
+        env = self._consume('test-manifests-written', TestManifestBackend)
+
+        all_tests_path = mozpath.join(env.topobjdir, 'all-tests.pkl')
+        self.assertTrue(os.path.exists(all_tests_path))
+
+        with open(all_tests_path, 'rb') as fh:
+            o = pickle.load(fh)
+
+        self.assertIn('xpcshell.js', o)
+        self.assertIn('dir1/test_bar.js', o)
+
+        self.assertEqual(len(o['xpcshell.js']), 1)
+
+    def test_test_installs_metadata_file_written(self):
+        """Ensure test-installs.pkl is generated."""
+        env = self._consume('test-manifest-shared-support', TestManifestBackend)
+        all_tests_path = mozpath.join(env.topobjdir, 'all-tests.pkl')
+        self.assertTrue(os.path.exists(all_tests_path))
+        test_installs_path = mozpath.join(env.topobjdir, 'test-installs.pkl')
+
+        with open(test_installs_path, 'r') as fh:
+            test_installs = pickle.load(fh)
+
+        self.assertEqual(set(test_installs.keys()),
+                         set(['child/test_sub.js',
+                              'child/data/**',
+                              'child/another-file.sjs']))
+
+        for key in test_installs.keys():
+            self.assertIn(key, test_installs)
+
+    def test_test_defaults_metadata_file_written(self):
+        """Ensure test-defaults.pkl is generated."""
+        env = self._consume('test-manifests-written', TestManifestBackend)
+
+        test_defaults_path = mozpath.join(env.topobjdir, 'test-defaults.pkl')
+        self.assertTrue(os.path.exists(test_defaults_path))
+
+        with open(test_defaults_path, 'rb') as fh:
+            o = {mozpath.normpath(k): v for k, v in pickle.load(fh).items()}
+
+        self.assertEquals(set(mozpath.relpath(k, env.topsrcdir) for k in o.keys()),
+                          set(['dir1/xpcshell.ini',
+                               'xpcshell.ini',
+                               'mochitest.ini']))
+
+        manifest_path = mozpath.join(env.topsrcdir, 'xpcshell.ini')
+        self.assertIn('here', o[manifest_path])
+        self.assertIn('support-files', o[manifest_path])
+
+
+if __name__ == '__main__':
+    main()
--- a/python/mozbuild/mozbuild/test/python.ini
+++ b/python/mozbuild/mozbuild/test/python.ini
@@ -1,15 +1,16 @@
 [action/test_buildlist.py]
 [action/test_generate_browsersearch.py]
 [action/test_package_fennec_apk.py]
 [backend/test_android_eclipse.py]
 [backend/test_build.py]
 [backend/test_configenvironment.py]
 [backend/test_recursivemake.py]
+[backend/test_test_manifest.py]
 [backend/test_visualstudio.py]
 [compilation/test_warnings.py]
 [configure/lint.py]
 [configure/test_checks_configure.py]
 [configure/test_compile_checks.py]
 [configure/test_configure.py]
 [configure/test_lint.py]
 [configure/test_moz_configure.py]