Bug 1379163: Implement the "Run Missing Tests" action; r=armenzg,bstack
authorDustin J. Mitchell <dustin@mozilla.com>
Wed, 19 Jul 2017 21:54:13 +0000
changeset 614716 aff22b63df7a936f2f3f94ac86ad78d43123f12f
parent 614715 2e143d567065e0ae90fa11268225b6b5572bdedd
child 614717 03896a3c7a68c0672e1fda121e357f9bc7511e62
push id70091
push userhikezoe@mozilla.com
push dateMon, 24 Jul 2017 22:05:18 +0000
reviewersarmenzg, bstack
bugs1379163
milestone56.0a1
Bug 1379163: Implement the "Run Missing Tests" action; r=armenzg,bstack This implements an action to run all test tasks which were optimized out by the decision task. MozReview-Commit-ID: qPflBlxMg7
taskcluster/actions/run_missing_tests.py
taskcluster/actions/util.py
new file mode 100644
--- /dev/null
+++ b/taskcluster/actions/run_missing_tests.py
@@ -0,0 +1,68 @@
+# -*- coding: utf-8 -*-
+
+# 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, print_function, unicode_literals
+
+import logging
+from slugid import nice as slugid
+
+from .registry import register_callback_action
+from actions.util import create_task, find_decision_task
+from taskgraph.util.taskcluster import get_artifact
+from taskgraph.util.parameterization import resolve_task_references
+from taskgraph.taskgraph import TaskGraph
+
+logger = logging.getLogger(__name__)
+
+
+@register_callback_action(
+    name='run-missing-tests',
+    title='Run Missing Tests',
+    symbol='rmt',
+    description="""
+    Run tests in the selected push that were optimized away, usually by SETA.
+
+    This action is for use on pushes that will be merged into another branch,
+    to check that optimization hasn't hidden any failures.
+    """,
+    order=100,  # Useful for sheriffs, but not top of the list
+    context=[],  # Applies to any task
+    schema={},
+)
+def run_missing_tests(parameters, input, task_group_id, task_id, task):
+    decision_task_id = find_decision_task(parameters)
+
+    full_task_graph = get_artifact(decision_task_id, "public/full-task-graph.json")
+    _, full_task_graph = TaskGraph.from_json(full_task_graph)
+    target_tasks = get_artifact(decision_task_id, "public/target-tasks.json")
+    label_to_taskid = get_artifact(decision_task_id, "public/label-to-taskid.json")
+
+    # The idea here is to schedule all tasks of the `test` kind that were
+    # targetted but did not appear in the final task-graph -- those were the
+    # optimized tasks.
+    to_run = []
+    already_run = 0
+    for label in target_tasks:
+        task = full_task_graph.tasks[label]
+        if task.kind != 'test':
+            continue  # not a test
+        if label in label_to_taskid:
+            already_run += 1
+            continue
+        to_run.append(task)
+
+    for task in to_run:
+
+        # fix up the task's dependencies, similar to how optimization would
+        # have done in the decision
+        dependencies = {name: label_to_taskid[label]
+                        for name, label in task.dependencies.iteritems()}
+        task_def = resolve_task_references(task.label, task.task, dependencies)
+        task_def.setdefault('dependencies', []).extend(dependencies.itervalues())
+        create_task(slugid(), task_def)
+
+    logger.info('Out of {} test tasks, {} already existed and the action created {}'.format(
+        already_run + len(to_run), already_run, len(to_run)))
--- a/taskcluster/actions/util.py
+++ b/taskcluster/actions/util.py
@@ -3,22 +3,30 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 from __future__ import absolute_import, print_function, unicode_literals
 
 import json
 import sys
 
 from taskgraph import create
-from taskgraph.util.taskcluster import get_session
+from taskgraph.util.taskcluster import get_session, find_task_id
 
 # this is set to true for `mach taskgraph action-callback --test`
 testing = False
 
 
+def find_decision_task(parameters):
+    """Given the parameters for this action, find the taskId of the decision
+    task"""
+    return find_task_id('gecko.v2.{}.pushlog-id.{}.decision'.format(
+        parameters['project'],
+        parameters['pushlog_id']))
+
+
 def create_task(task_id, task_def):
     """Create a new task.  The task definition will have {relative-datestamp':
     '..'} rendered just like in a decision task.  Action callbacks should use
     this function to create new tasks, as it has the additional advantage of
     allowing easy debugging with `mach taskgraph action-callback --test`."""
     if testing:
         json.dump([task_id, task_def], sys.stdout,
                   sort_keys=True, indent=4, separators=(',', ': '))