Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
Bug 1499822 - [tryselect] Implement |mach try chooser| r=sclements
Usage: $ ./mach try chooser Will start a local flask server and server a "trychooser-like" page that is dynamically generated from the taskgraph. Differential Revision: https://phabricator.services.mozilla.com/D14903 --HG-- extra : rebase_source : dafe631a4ed9850afdda3b3d91a67643802360a9 extra : absorb_source : 3cb0544110796ae38816cd6c0c20022816a3def1 extra : source : 31e16c2d94299dcc7076b024981b1997a264e5e1
- Loading branch information
Showing
13 changed files
with
670 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
Chooser Selector | ||
================ | ||
|
||
When pushing to try, there are a very large amount of builds and tests to choose from. Often too | ||
many to remember, making it easy to forget a set of tasks which should otherwise have been run. | ||
|
||
This selector allows you to select tasks from a web interface that lists all the possible build and | ||
test tasks and allows you to select them from a list. It is similar in concept to the old `try | ||
syntax chooser`_ page, except that the values are dynamically generated using the `taskgraph`_ as an | ||
input. This ensures that it will never be out of date. | ||
|
||
To use: | ||
|
||
.. code-block:: shell | ||
$ mach try chooser | ||
This will spin up a local web server (using Flask) which serves the chooser app. After making your | ||
selection, simply press ``Push`` and the rest will be handled from there. No need to copy/paste any | ||
syntax strings or the like. | ||
|
||
You can run: | ||
|
||
.. code-block:: shell | ||
$ mach try chooser --full | ||
To generate the interface using the full taskgraph instead. This will include tasks that don't run | ||
on mozilla-central. | ||
|
||
|
||
.. _try syntax chooser: https://mozilla-releng.net/trychooser | ||
.. _taskgraph: https://firefox-source-docs.mozilla.org/taskcluster/taskcluster/index.html |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
"use strict"; | ||
|
||
module.exports = { | ||
env: { | ||
"jquery": true | ||
}, | ||
globals: { | ||
"apply": true, | ||
"applyChunks": true, | ||
"tasks": true | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
# 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 os | ||
import webbrowser | ||
from threading import Timer | ||
|
||
from tryselect.cli import BaseTryParser | ||
from tryselect.tasks import generate_tasks | ||
from tryselect.push import check_working_directory, push_to_try, vcs | ||
|
||
here = os.path.abspath(os.path.dirname(__file__)) | ||
|
||
|
||
class ChooserParser(BaseTryParser): | ||
name = 'chooser' | ||
arguments = [] | ||
common_groups = ['push', 'task'] | ||
templates = ['artifact', 'env', 'rebuild', 'chemspill-prio', 'talos-profile'] | ||
|
||
|
||
def run_try_chooser(update=False, query=None, templates=None, full=False, parameters=None, | ||
save=False, preset=None, mod_presets=False, push=True, message='{msg}', | ||
**kwargs): | ||
from .app import create_application | ||
check_working_directory(push) | ||
|
||
tg = generate_tasks(parameters, full, root=vcs.path) | ||
app = create_application(tg) | ||
|
||
if os.environ.get('WERKZEUG_RUN_MAIN') == 'true': | ||
# we are in the reloader process, don't open the browser or do any try stuff | ||
app.run() | ||
return | ||
|
||
# give app a second to start before opening the browser | ||
Timer(1, lambda: webbrowser.open('http://127.0.0.1:5000')).start() | ||
app.run() | ||
|
||
selected = app.tasks | ||
if not selected: | ||
print("no tasks selected") | ||
return | ||
|
||
msg = "Try Chooser Enhanced ({} tasks selected)".format(len(selected)) | ||
return push_to_try('chooser', message.format(msg=msg), selected, templates, push=push, | ||
closed_tree=kwargs["closed_tree"]) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,190 @@ | ||
# 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 | ||
|
||
from abc import ABCMeta, abstractproperty | ||
from collections import defaultdict | ||
|
||
from flask import ( | ||
Flask, | ||
render_template, | ||
request, | ||
) | ||
|
||
SECTIONS = [] | ||
SUPPORTED_KINDS = set() | ||
|
||
|
||
def register_section(cls): | ||
assert issubclass(cls, Section) | ||
instance = cls() | ||
SECTIONS.append(instance) | ||
SUPPORTED_KINDS.update(instance.kind.split(',')) | ||
|
||
|
||
class Section(object): | ||
__metaclass__ = ABCMeta | ||
|
||
@abstractproperty | ||
def name(self): | ||
pass | ||
|
||
@abstractproperty | ||
def kind(self): | ||
pass | ||
|
||
@abstractproperty | ||
def title(self): | ||
pass | ||
|
||
@abstractproperty | ||
def attrs(self): | ||
pass | ||
|
||
def contains(self, task): | ||
return task.kind in self.kind.split(',') | ||
|
||
def get_context(self, tasks): | ||
labels = defaultdict(lambda: {'max_chunk': 0, 'attrs': defaultdict(list)}) | ||
|
||
for task in tasks.values(): | ||
if not self.contains(task): | ||
continue | ||
|
||
task = task.attributes | ||
label = labels[self.labelfn(task)] | ||
for attr in self.attrs: | ||
if attr in task and task[attr] not in label['attrs'][attr]: | ||
label['attrs'][attr].append(task[attr]) | ||
|
||
if 'test_chunk' in task: | ||
label['max_chunk'] = max(label['max_chunk'], int(task['test_chunk'])) | ||
|
||
return { | ||
'name': self.name, | ||
'kind': self.kind, | ||
'title': self.title, | ||
'labels': labels, | ||
} | ||
|
||
|
||
@register_section | ||
class Platform(Section): | ||
name = 'platform' | ||
kind = 'build' | ||
title = 'Platforms' | ||
attrs = ['build_platform'] | ||
|
||
def labelfn(self, task): | ||
return task['build_platform'] | ||
|
||
def contains(self, task): | ||
if not Section.contains(self, task): | ||
return False | ||
|
||
# android-stuff tasks aren't actual platforms | ||
return task.task['tags'].get('android-stuff', False) != "true" | ||
|
||
|
||
@register_section | ||
class Test(Section): | ||
name = 'test' | ||
kind = 'test' | ||
title = 'Test Suites' | ||
attrs = ['unittest_suite', 'unittest_flavor'] | ||
|
||
def labelfn(self, task): | ||
suite = task['unittest_suite'].replace(' ', '-') | ||
flavor = task['unittest_flavor'].replace(' ', '-') | ||
|
||
if flavor.endswith('chunked'): | ||
flavor = flavor[:-len('chunked')] | ||
|
||
if flavor.startswith(suite): | ||
flavor = flavor[len(suite):] | ||
flavor = flavor.strip('-') | ||
|
||
if flavor in ('crashtest', 'jsreftest'): | ||
return flavor | ||
|
||
if flavor: | ||
return '{}-{}'.format(suite, flavor) | ||
return suite | ||
|
||
def contains(self, task): | ||
if not Section.contains(self, task): | ||
return False | ||
return task.attributes['unittest_suite'] not in ('raptor', 'talos') | ||
|
||
|
||
@register_section | ||
class Perf(Section): | ||
name = 'perf' | ||
kind = 'test' | ||
title = 'Performance' | ||
attrs = ['unittest_suite', 'unittest_flavor', 'raptor_try_name', 'talos_try_name'] | ||
|
||
def labelfn(self, task): | ||
suite = task['unittest_suite'] | ||
label = task['{}_try_name'.format(suite)] | ||
|
||
if not label.startswith(suite): | ||
label = '{}-{}'.format(suite, label) | ||
|
||
if label.endswith('-e10s'): | ||
label = label[:-len('-e10s')] | ||
|
||
return label | ||
|
||
def contains(self, task): | ||
if not Section.contains(self, task): | ||
return False | ||
return task.attributes['unittest_suite'] in ('raptor', 'talos') | ||
|
||
|
||
@register_section | ||
class Analysis(Section): | ||
name = 'analysis' | ||
kind = 'build,static-analysis-autotest' | ||
title = 'Analysis' | ||
attrs = ['build_platform'] | ||
|
||
def labelfn(self, task): | ||
return task['build_platform'] | ||
|
||
def contains(self, task): | ||
if not Section.contains(self, task): | ||
return False | ||
if task.kind == 'build': | ||
return task.task['tags'].get('android-stuff', False) == "true" | ||
return True | ||
|
||
|
||
def create_application(tg): | ||
tasks = {l: t for l, t in tg.tasks.items() if t.kind in SUPPORTED_KINDS} | ||
sections = [s.get_context(tasks) for s in SECTIONS] | ||
context = { | ||
'tasks': {l: t.attributes for l, t in tasks.items()}, | ||
'sections': sections, | ||
} | ||
|
||
app = Flask(__name__) | ||
app.env = 'development' | ||
app.tasks = [] | ||
|
||
@app.route('/', methods=['GET', 'POST']) | ||
def chooser(): | ||
if request.method == 'GET': | ||
return render_template('chooser.html', **context) | ||
|
||
if request.form['action'] == 'Push': | ||
labels = request.form['selected-tasks'].splitlines() | ||
app.tasks.extend(labels) | ||
|
||
shutdown = request.environ.get('werkzeug.server.shutdown') | ||
shutdown() | ||
return render_template('close.html') | ||
|
||
return app |
Oops, something went wrong.