Bug 1320194 - Refactor test metadata related backend code into a partial TestManifestBackend r=gps
authorAndrew Halberstadt <ahalberstadt@mozilla.com>
Fri, 27 Jan 2017 11:47:34 -0500
changeset 392026 c07984424f625c775cc2e0daca24b97f685dd5ee
parent 392025 03804db09cf364ff228fc61409702d9a86d44ade
child 392027 0fc91140e9411efe2af562105ab41c98daa201e7
push id1468
push userasasaki@mozilla.com
push dateMon, 05 Jun 2017 19:31:07 +0000
treeherdermozilla-release@0641fc6ee9d1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgps
bugs1320194
milestone54.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1320194 - Refactor test metadata related backend code into a partial TestManifestBackend r=gps 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]