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 372648 02a96520e63c668b915d81e3b85846d4ccc8f4b4
parent 372647 b33e00016ea95fd98173609fa42509f4521c92e2
child 372649 0930fdc4cf8e0116e19577005f57f7ec12336ced
push id10863
push userjlorenzo@mozilla.com
push dateMon, 06 Mar 2017 23:02:23 +0000
treeherdermozilla-aurora@0931190cd725 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdustin, merge
bugs1339604
milestone54.0a1
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))