Bug 1279676 - Support --rebuild try flag to schedule tests N times. r=dustin
authorArmen Zambrano Gasparnian <armenzg@mozilla.com>
Thu, 28 Jul 2016 13:20:44 -0400
changeset 307977 34cefe01d821d6795993d0047bcaa01206b6fecf
parent 307976 c4de227304aa18ceb41047e222272f22fd99d5f9
child 307978 15664ea8ffb19d8dff59b7ca7e0bf6d98257272a
push id30526
push usercbook@mozilla.com
push dateThu, 04 Aug 2016 13:53:29 +0000
treeherdermozilla-central@62a983c56050 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdustin
bugs1279676
milestone51.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 1279676 - Support --rebuild try flag to schedule tests N times. r=dustin MozReview-Commit-ID: Lrxi8t53nwy If a developer adds '--rebuild N' to their try syntax they will get test jobs scheduled N times. This is useful to determine intermittency rate. This fixes a regression due to the recent refactoring on how we schedule tasks.
taskcluster/docs/attributes.rst
taskcluster/taskgraph/create.py
taskcluster/taskgraph/target_tasks.py
--- a/taskcluster/docs/attributes.rst
+++ b/taskcluster/docs/attributes.rst
@@ -14,16 +14,29 @@ The attributes, and acceptable values, a
 names and values are the short, lower-case form, with underscores.
 
 kind
 ====
 
 A task's ``kind`` attribute gives the name of the kind that generated it, e.g.,
 ``build`` or ``legacy``.
 
+task_duplicates
+===============
+
+This is used to indicate that we want multiple copies of the task created.
+This feature is used to track down intermittent job failures.
+
+If this value is set to N, the task-creation machinery will create a total of N
+copies of the task.  Only the first copy will be included in the taskgraph
+output artifacts, although all tasks will be contained in the same taskGroup.
+
+While most attributes are considered read-only, target task methods may alter
+this attribute of tasks they include in the target set.
+
 build_platform
 ==============
 
 The build platform defines the platform for which the binary was built.  It is
 set for both build and test jobs, although test jobs may have a different
 ``test_platform``.
 
 build_type
--- a/taskcluster/taskgraph/create.py
+++ b/taskcluster/taskgraph/create.py
@@ -49,16 +49,17 @@ def create_tasks(taskgraph, label_to_tas
         #
         # Using visit_postorder() here isn't the most efficient: we'll
         # block waiting for dependencies of task N to submit even though
         # dependencies for task N+1 may be finished. If we need to optimize
         # this further, we can build a graph of task dependencies and walk
         # that.
         for task_id in taskgraph.graph.visit_postorder():
             task_def = taskgraph.tasks[task_id].task
+            attributes = taskgraph.tasks[task_id].attributes
             # if this task has no dependencies, make it depend on this decision
             # task so that it does not start immediately; and so that if this loop
             # fails halfway through, none of the already-created tasks run.
             if decision_task_id and not task_def.get('dependencies'):
                 task_def['dependencies'] = [decision_task_id]
 
             task_def['taskGroupId'] = task_group_id
             task_def['schedulerId'] = '-'
@@ -67,16 +68,22 @@ def create_tasks(taskgraph, label_to_tas
             deps_fs = [fs[dep] for dep in task_def.get('dependencies', [])
                        if dep in fs]
             for f in futures.as_completed(deps_fs):
                 f.result()
 
             fs[task_id] = e.submit(_create_task, session, task_id,
                                    taskid_to_label[task_id], task_def)
 
+            # Schedule tasks as many times as task_duplicates indicates
+            for i in range(1, attributes.get('task_duplicates', 1)):
+                # We use slugid() since we want a distinct task id
+                fs[task_id] = e.submit(_create_task, session, slugid(),
+                                       taskid_to_label[task_id], task_def)
+
         # Wait for all futures to complete.
         for f in futures.as_completed(fs.values()):
             f.result()
 
 
 def _create_task(session, task_id, label, task_def):
     # create the task using 'http://taskcluster/queue', which is proxied to the queue service
     # with credentials appropriate to this job.
--- a/taskcluster/taskgraph/target_tasks.py
+++ b/taskcluster/taskgraph/target_tasks.py
@@ -37,18 +37,27 @@ def target_tasks_from_parameters(full_ta
     return parameters['target_tasks']
 
 
 @_target_task('try_option_syntax')
 def target_tasks_try_option_syntax(full_task_graph, parameters):
     """Generate a list of target tasks based on try syntax in
     parameters['message'] and, for context, the full task graph."""
     options = try_option_syntax.TryOptionSyntax(parameters['message'], full_task_graph)
-    return [t.label for t in full_task_graph.tasks.itervalues()
-            if options.task_matches(t.attributes)]
+    target_tasks_labels = [t.label for t in full_task_graph.tasks.itervalues()
+                           if options.task_matches(t.attributes)]
+
+    # If the developer wants test jobs to be rebuilt N times we add that value here
+    if int(options.trigger_tests) > 1:
+        for l in target_tasks_labels:
+            task = full_task_graph[l]
+            if 'unittest_suite' in task.attributes:
+                task.attributes['task_duplicates'] = options.trigger_tests
+
+    return target_tasks_labels
 
 
 @_target_task('all_builds_and_tests')
 def target_tasks_all_builds_and_tests(full_task_graph, parameters):
     """Trivially target all build and test tasks.  This is used for
     branches where we want to build "everyting", but "everything"
     does not include uninteresting things like docker images"""
     def filter(task):