Bug 1339604 - stylo builds + tests should only run on limited branches to reduce budget impact r=dustin a=merge
authorKim Moir <kmoir@mozilla.com>
Fri, 17 Feb 2017 13:31:39 -0500
changeset 343621 02a96520e63c668b915d81e3b85846d4ccc8f4b4
parent 343620 b33e00016ea95fd98173609fa42509f4521c92e2
child 343622 0930fdc4cf8e0116e19577005f57f7ec12336ced
push id31382
push userkwierso@gmail.com
push dateFri, 17 Feb 2017 21:41:52 +0000
treeherdermozilla-central@0930fdc4cf8e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdustin, merge
bugs1339604
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 1339604 - stylo builds + tests should only run on limited branches to reduce budget impact r=dustin a=merge MozReview-Commit-ID: 8SOmDuN6TTL
taskcluster/ci/build/linux.yml
taskcluster/ci/test/tests.yml
taskcluster/taskgraph/test/test_util_schema.py
taskcluster/taskgraph/transforms/tests.py
taskcluster/taskgraph/util/schema.py
--- a/taskcluster/ci/build/linux.yml
+++ b/taskcluster/ci/build/linux.yml
@@ -267,16 +267,17 @@ linux64-stylo/opt:
         actions: [get-secrets build check-test generate-build-stats update]
         config:
             - builds/releng_base_linux_64_builds.py
             - balrog/production.py
         script: "mozharness/scripts/fx_desktop_build.py"
         secrets: true
         custom-build-variant-cfg: stylo
         tooltool-downloads: public
+    run-on-projects: [ 'stylo', 'autoland', 'mozilla-inbound', 'mozilla-central' ]
 
 linux64-stylo/debug:
     description: "Linux64 Debug Stylo"
     index:
         product: firefox
         job-name: linux64-stylo-debug
     treeherder:
         platform: linux64-stylo/debug
@@ -291,16 +292,17 @@ linux64-stylo/debug:
         actions: [get-secrets build check-test generate-build-stats update]
         config:
             - builds/releng_base_linux_64_builds.py
             - balrog/production.py
         script: "mozharness/scripts/fx_desktop_build.py"
         secrets: true
         custom-build-variant-cfg: stylo-debug
         tooltool-downloads: public
+    run-on-projects: [ 'stylo', 'autoland', 'mozilla-inbound', 'mozilla-central' ]
 
 linux64-jsdcov/opt:
     description: "Linux64-JSDCov Opt"
     index:
         product: firefox
         job-name: linux64-jsdcov-opt
     treeherder:
         platform: linux64/jsdcov
--- a/taskcluster/ci/test/tests.yml
+++ b/taskcluster/ci/test/tests.yml
@@ -931,16 +931,27 @@ reftest-stylo:
     description: "Reftest run for Stylo"
     suite: reftest/reftest-stylo
     treeherder-symbol: tc-R(Rs)
     chunks: 16
     run-on-projects:
         by-test-platform:
             linux64-stylo/opt: []
             linux64-stylo/debug: []
+    e10s:
+        by-test-platform:
+            linux64-stylo/opt:
+                by-project:
+                    mozilla-inbound: false
+                    default: both
+            linux64-stylo/debug:
+                # only e10s on inbound
+                by-project:
+                    mozilla-inbound: true
+                    default: both
     mozharness:
         script: desktop_unittest.py
         no-read-buildbot-config: true
         config:
             - unittests/linux_unittest.py
             - remove_executables.py
         extra-options:
             - --reftest-suite=reftest-stylo
--- a/taskcluster/taskgraph/test/test_util_schema.py
+++ b/taskcluster/taskgraph/test/test_util_schema.py
@@ -48,16 +48,42 @@ class TestResolveKeyedBy(unittest.TestCa
             resolve_keyed_by({'x': 10}, 'x.y', 'n'),
             {'x': 10})
 
     def test_no_by_not_by(self):
         self.assertEqual(
             resolve_keyed_by({'x': {'a': 10}}, 'x', 'n'),
             {'x': {'a': 10}})
 
+    def test_nested(self):
+        x = {
+            'by-foo': {
+                'F1': {
+                    'by-bar': {
+                        'B1': 11,
+                        'B2': 12,
+                    },
+                },
+                'F2': 20,
+                'default': 0,
+            },
+        }
+        self.assertEqual(
+            resolve_keyed_by({'x': x}, 'x', 'x', foo='F1', bar='B1'),
+            {'x': 11})
+        self.assertEqual(
+            resolve_keyed_by({'x': x}, 'x', 'x', foo='F1', bar='B2'),
+            {'x': 12})
+        self.assertEqual(
+            resolve_keyed_by({'x': x}, 'x', 'x', foo='F2'),
+            {'x': 20})
+        self.assertEqual(
+            resolve_keyed_by({'x': x}, 'x', 'x', foo='F99', bar='B1'),
+            {'x': 0})
+
     def test_no_by_empty_dict(self):
         self.assertEqual(
             resolve_keyed_by({'x': {}}, 'x', 'n'),
             {'x': {}})
 
     def test_no_by_not_only_by(self):
         self.assertEqual(
             resolve_keyed_by({'x': {'by-y': True, 'a': 10}}, 'x', 'n'),
--- a/taskcluster/taskgraph/transforms/tests.py
+++ b/taskcluster/taskgraph/transforms/tests.py
@@ -114,17 +114,17 @@ test_description_schema = Schema({
     # the branch (see below)
     Optional('expires-after'): basestring,
 
     # Whether to run this task with e10s (desktop-test only).  If false, run
     # without e10s; if true, run with e10s; if 'both', run one task with and
     # one task without e10s.  E10s tasks have "-e10s" appended to the test name
     # and treeherder group.
     Required('e10s', default='both'): optionally_keyed_by(
-        'test-platform',
+        'test-platform', 'project',
         Any(bool, 'both')),
 
     # The EC2 instance size to run these tests on.
     Required('instance-size', default='default'): optionally_keyed_by(
         'test-platform',
         Any('default', 'large', 'xlarge', 'legacy')),
 
     # Whether the task requires loopback audio or video (whatever that may mean
@@ -451,17 +451,18 @@ def handle_keyed_by(config, tests):
         'suite',
         'run-on-projects',
         'os-groups',
         'mozharness.config',
         'mozharness.extra-options',
     ]
     for test in tests:
         for field in fields:
-            resolve_keyed_by(test, field, item_name=test['test-name'])
+            resolve_keyed_by(test, field, item_name=test['test-name'],
+                             project=config.params['project'])
         yield test
 
 
 @transforms.add
 def enable_code_coverage(config, tests):
     """Enable code coverage for the linux64-ccov/opt & linux64-jsdcov/opt build-platforms"""
     for test in tests:
         if test['build-platform'] == 'linux64-ccov/opt':
--- a/taskcluster/taskgraph/util/schema.py
+++ b/taskcluster/taskgraph/util/schema.py
@@ -29,88 +29,110 @@ def optionally_keyed_by(*arguments):
     """
     Mark a schema value as optionally keyed by any of a number of fields.  The
     schema is the last argument, and the remaining fields are taken to be the
     field names.  For example:
 
         'some-value': optionally_keyed_by(
             'test-platform', 'build-platform',
             Any('a', 'b', 'c'))
+
+    The resulting schema will allow nesting of `by-test-platform` and
+    `by-build-platform` in either order.
     """
-    subschema = arguments[-1]
+    schema = arguments[-1]
     fields = arguments[:-1]
-    options = [subschema]
-    for field in fields:
-        options.append({'by-' + field: {basestring: subschema}})
-    return voluptuous.Any(*options)
+
+    # build the nestable schema by generating schema = Any(schema,
+    # by-fld1, by-fld2, by-fld3) once for each field.  So we don't allow
+    # infinite nesting, but one level of nesting for each field.
+    for _ in arguments:
+        options = [schema]
+        for field in fields:
+            options.append({'by-' + field: {basestring: schema}})
+        schema = voluptuous.Any(*options)
+    return schema
 
 
 def resolve_keyed_by(item, field, item_name, **extra_values):
     """
     For values which can either accept a literal value, or be keyed by some
     other attribute of the item, perform that lookup and replacement in-place
     (modifying `item` directly).  The field is specified using dotted notation
     to traverse dictionaries.
 
-    For example, given item
+    For example, given item::
 
         job:
             test-platform: linux128
             chunks:
                 by-test-platform:
                     macosx-10.11/debug: 13
                     win.*: 6
                     default: 12
 
     a call to `resolve_keyed_by(item, 'job.chunks', item['thing-name'])
-    would mutate item in-place to
+    would mutate item in-place to::
 
         job:
             chunks: 12
 
     The `item_name` parameter is used to generate useful error messages.
 
     If extra_values are supplied, they represent additional values available
     for reference from by-<field>.
+
+    Items can be nested as deeply as the schema will allow::
+
+        chunks:
+            by-test-platform:
+                win.*:
+                    by-project:
+                        ash: ..
+                        cedar: ..
+                linux: 13
+                default: 12
     """
     # find the field, returning the item unchanged if anything goes wrong
     container, subfield = item, field
     while '.' in subfield:
         f, subfield = subfield.split('.', 1)
         if f not in container:
             return item
         container = container[f]
         if not isinstance(container, dict):
             return item
 
     if subfield not in container:
         return item
     value = container[subfield]
-    if not isinstance(value, dict) or len(value) != 1 or not value.keys()[0].startswith('by-'):
-        return item
+    while True:
+        if not isinstance(value, dict) or len(value) != 1 or not value.keys()[0].startswith('by-'):
+            return item
 
-    keyed_by = value.keys()[0][3:]  # strip off 'by-' prefix
-    key = extra_values.get(keyed_by) if keyed_by in extra_values else item[keyed_by]
-    alternatives = value.values()[0]
+        keyed_by = value.keys()[0][3:]  # strip off 'by-' prefix
+        key = extra_values.get(keyed_by) if keyed_by in extra_values else item[keyed_by]
+        alternatives = value.values()[0]
 
-    # exact match
-    if key in alternatives:
-        container[subfield] = alternatives[key]
-        return item
+        # exact match
+        if key in alternatives:
+            value = container[subfield] = alternatives[key]
+            continue
 
-    # regular expression match
-    matches = [(k, v) for k, v in alternatives.iteritems() if re.match(k + '$', key)]
-    if len(matches) > 1:
+        # regular expression match
+        matches = [(k, v) for k, v in alternatives.iteritems() if re.match(k + '$', key)]
+        if len(matches) > 1:
+            raise Exception(
+                "Multiple matching values for {} {!r} found while "
+                "determining item {} in {}".format(
+                    keyed_by, key, field, item_name))
+        elif matches:
+            value = container[subfield] = matches[0][1]
+            continue
+
+        # default
+        if 'default' in alternatives:
+            value = container[subfield] = alternatives['default']
+            continue
+
         raise Exception(
-            "Multiple matching values for {} {!r} found while determining item {} in {}".format(
+            "No {} matching {!r} nor 'default' found while determining item {} in {}".format(
                 keyed_by, key, field, item_name))
-    elif matches:
-        container[subfield] = matches[0][1]
-        return item
-
-    # default
-    if 'default' in alternatives:
-        container[subfield] = alternatives['default']
-        return item
-
-    raise Exception(
-        "No {} matching {!r} nor 'default' found while determining item {} in {}".format(
-            keyed_by, key, field, item_name))