mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-20 16:55:40 +00:00
cc77d57a4a
Instead of a boolean, it's now a set of projects for which tasks should be removed. If they project doesn't match the specified set it will be kept. This ensures tasks that have these optimzers applied won't run on |mach try auto|. Differential Revision: https://phabricator.services.mozilla.com/D76987
303 lines
8.4 KiB
Python
303 lines
8.4 KiB
Python
# Any copyright is dedicated to the public domain.
|
|
# http://creativecommons.org/publicdomain/zero/1.0/
|
|
|
|
from __future__ import absolute_import
|
|
|
|
import time
|
|
from datetime import datetime
|
|
from time import mktime
|
|
|
|
import pytest
|
|
from mozunit import main
|
|
|
|
from taskgraph.optimize import seta
|
|
from taskgraph.optimize.backstop import Backstop
|
|
from taskgraph.optimize.bugbug import (
|
|
BugBugPushSchedules,
|
|
DisperseGroups,
|
|
SkipUnlessDebug,
|
|
)
|
|
from taskgraph.task import Task
|
|
from taskgraph.util.bugbug import (
|
|
BUGBUG_BASE_URL,
|
|
BugbugTimeoutException,
|
|
push_schedules,
|
|
)
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def clear_push_schedules_memoize():
|
|
push_schedules.clear()
|
|
|
|
|
|
@pytest.fixture(scope='module')
|
|
def params():
|
|
return {
|
|
'branch': 'integration/autoland',
|
|
'head_repository': 'https://hg.mozilla.org/integration/autoland',
|
|
'head_rev': 'abcdef',
|
|
'project': 'autoland',
|
|
'pushlog_id': 1,
|
|
'pushdate': mktime(datetime.now().timetuple()),
|
|
}
|
|
|
|
|
|
def generate_tasks(*tasks):
|
|
for i, task in enumerate(tasks):
|
|
task.setdefault('label', 'task-{}'.format(i))
|
|
task.setdefault('kind', 'test')
|
|
task.setdefault('task', {})
|
|
task.setdefault('attributes', {})
|
|
task['attributes'].setdefault('e10s', True)
|
|
|
|
for attr in ('optimization', 'dependencies', 'soft_dependencies', 'release_artifacts'):
|
|
task.setdefault(attr, None)
|
|
|
|
task['task'].setdefault('label', task['label'])
|
|
yield Task.from_json(task)
|
|
|
|
|
|
# task sets
|
|
|
|
default_tasks = list(generate_tasks(
|
|
{'attributes': {'test_manifests': ['foo/test.ini', 'bar/test.ini']}},
|
|
{'attributes': {'test_manifests': ['bar/test.ini'], 'build_type': 'debug'}},
|
|
{'attributes': {'build_type': 'debug'}},
|
|
{'attributes': {'test_manifests': [], 'build_type': 'opt'}},
|
|
{'attributes': {'build_type': 'opt'}},
|
|
))
|
|
|
|
|
|
disperse_tasks = list(generate_tasks(
|
|
{'attributes': {
|
|
'test_manifests': ['foo/test.ini', 'bar/test.ini'],
|
|
'test_platform': 'linux/opt',
|
|
}},
|
|
{'attributes': {
|
|
'test_manifests': ['bar/test.ini'],
|
|
'test_platform': 'linux/opt',
|
|
}},
|
|
{'attributes': {
|
|
'test_manifests': ['bar/test.ini'],
|
|
'test_platform': 'windows/debug',
|
|
}},
|
|
{'attributes': {
|
|
'test_manifests': ['bar/test.ini'],
|
|
'test_platform': 'linux/opt',
|
|
'unittest_variant': 'fission',
|
|
}},
|
|
{'attributes': {
|
|
'e10s': False,
|
|
'test_manifests': ['bar/test.ini'],
|
|
'test_platform': 'linux/opt',
|
|
}},
|
|
))
|
|
|
|
|
|
def idfn(param):
|
|
if isinstance(param, tuple):
|
|
return param[0].__name__
|
|
return None
|
|
|
|
|
|
@pytest.mark.parametrize("opt,tasks,arg,expected", [
|
|
# debug
|
|
pytest.param(
|
|
SkipUnlessDebug(),
|
|
default_tasks,
|
|
None,
|
|
['task-0', 'task-1', 'task-2'],
|
|
),
|
|
|
|
# disperse with no supplied importance
|
|
pytest.param(
|
|
DisperseGroups(),
|
|
disperse_tasks,
|
|
None,
|
|
[t.label for t in disperse_tasks],
|
|
),
|
|
|
|
# disperse with low importance
|
|
pytest.param(
|
|
DisperseGroups(),
|
|
disperse_tasks,
|
|
{'bar/test.ini': 'low'},
|
|
['task-0', 'task-2'],
|
|
),
|
|
|
|
# disperse with medium importance
|
|
pytest.param(
|
|
DisperseGroups(),
|
|
disperse_tasks,
|
|
{'bar/test.ini': 'medium'},
|
|
['task-0', 'task-1', 'task-2'],
|
|
),
|
|
|
|
# disperse with high importance
|
|
pytest.param(
|
|
DisperseGroups(),
|
|
disperse_tasks,
|
|
{'bar/test.ini': 'high'},
|
|
['task-0', 'task-1', 'task-2', 'task-3'],
|
|
),
|
|
], ids=idfn)
|
|
def test_optimization_strategy(responses, params, opt, tasks, arg, expected):
|
|
labels = [t.label for t in tasks if not opt.should_remove_task(t, params, arg)]
|
|
assert sorted(labels) == sorted(expected)
|
|
|
|
|
|
@pytest.mark.parametrize("args,data,expected", [
|
|
# empty
|
|
pytest.param(
|
|
(0.1,),
|
|
{},
|
|
[],
|
|
),
|
|
|
|
# only tasks without test manifests selected
|
|
pytest.param(
|
|
(0.1,),
|
|
{'tasks': {'task-1': 0.9, 'task-2': 0.1, 'task-3': 0.5}},
|
|
['task-2'],
|
|
),
|
|
|
|
# tasks which are unknown to bugbug are selected
|
|
pytest.param(
|
|
(0.1,),
|
|
{'tasks': {'task-1': 0.9, 'task-3': 0.5}, 'known_tasks': ['task-1', 'task-3', 'task-4']},
|
|
['task-2'],
|
|
),
|
|
|
|
# tasks containing groups selected
|
|
pytest.param(
|
|
(0.1,),
|
|
{'groups': {'foo/test.ini': 0.4}},
|
|
['task-0'],
|
|
),
|
|
|
|
# tasks matching "tasks" or "groups" selected
|
|
pytest.param(
|
|
(0.1,),
|
|
{'tasks': {'task-2': 0.2}, 'groups': {'foo/test.ini': 0.25, 'bar/test.ini': 0.75}},
|
|
['task-0', 'task-1', 'task-2'],
|
|
),
|
|
|
|
# tasks matching "tasks" or "groups" selected, when they exceed the confidence threshold
|
|
pytest.param(
|
|
(0.5,),
|
|
{
|
|
'tasks': {'task-2': 0.2, 'task-4': 0.5},
|
|
'groups': {'foo/test.ini': 0.65, 'bar/test.ini': 0.25}
|
|
},
|
|
['task-0', 'task-4'],
|
|
),
|
|
|
|
# tasks matching "reduced_tasks" are selected, when they exceed the confidence threshold
|
|
pytest.param(
|
|
(0.7, True),
|
|
{
|
|
'tasks': {'task-2': 0.7, 'task-4': 0.7},
|
|
'reduced_tasks': {'task-4': 0.7},
|
|
'groups': {'foo/test.ini': 0.75, 'bar/test.ini': 0.25}
|
|
},
|
|
['task-4'],
|
|
),
|
|
|
|
], ids=idfn)
|
|
def test_bugbug_push_schedules(responses, params, args, data, expected):
|
|
query = "/push/{branch}/{head_rev}/schedules".format(**params)
|
|
url = BUGBUG_BASE_URL + query
|
|
|
|
responses.add(
|
|
responses.GET,
|
|
url,
|
|
json=data,
|
|
status=200,
|
|
)
|
|
|
|
opt = BugBugPushSchedules(*args)
|
|
labels = [t.label for t in default_tasks if not opt.should_remove_task(t, params, {})]
|
|
assert sorted(labels) == sorted(expected)
|
|
|
|
|
|
def test_bugbug_timeout(monkeypatch, responses, params):
|
|
query = "/push/{branch}/{head_rev}/schedules".format(**params)
|
|
url = BUGBUG_BASE_URL + query
|
|
responses.add(
|
|
responses.GET,
|
|
url,
|
|
json={"ready": False},
|
|
status=202,
|
|
)
|
|
|
|
# Make sure the test runs fast.
|
|
monkeypatch.setattr(time, 'sleep', lambda i: None)
|
|
|
|
opt = BugBugPushSchedules(0.5)
|
|
with pytest.raises(BugbugTimeoutException):
|
|
opt.should_remove_task(default_tasks[0], params, None)
|
|
|
|
|
|
def test_bugbug_fallback(monkeypatch, responses, params):
|
|
query = "/push/{branch}/{head_rev}/schedules".format(**params)
|
|
url = BUGBUG_BASE_URL + query
|
|
responses.add(
|
|
responses.GET,
|
|
url,
|
|
json={"ready": False},
|
|
status=202,
|
|
)
|
|
|
|
# Make sure the test runs fast.
|
|
monkeypatch.setattr(time, 'sleep', lambda i: None)
|
|
|
|
monkeypatch.setattr(seta, 'is_low_value_task', lambda l, p: l == default_tasks[0].label)
|
|
|
|
opt = BugBugPushSchedules(0.5, fallback=True)
|
|
assert opt.should_remove_task(default_tasks[0], params, None)
|
|
|
|
# Make sure we don't hit bugbug more than once.
|
|
responses.reset()
|
|
|
|
assert not opt.should_remove_task(default_tasks[1], params, None)
|
|
|
|
|
|
def test_backstop(params):
|
|
all_labels = {t.label for t in default_tasks}
|
|
opt = Backstop(10, 60, {'try'}) # every 10th push or 1 hour
|
|
|
|
# If there's no previous push date, run tasks
|
|
params['pushlog_id'] = 8
|
|
scheduled = {t.label for t in default_tasks if not opt.should_remove_task(t, params, None)}
|
|
assert scheduled == all_labels
|
|
|
|
# Only multiples of 10 schedule tasks. Pushdate from push 8 was cached.
|
|
params['pushlog_id'] = 9
|
|
params['pushdate'] += 3599
|
|
scheduled = {t.label for t in default_tasks if not opt.should_remove_task(t, params, None)}
|
|
assert scheduled == set()
|
|
|
|
params['pushlog_id'] = 10
|
|
params['pushdate'] += 1
|
|
scheduled = {t.label for t in default_tasks if not opt.should_remove_task(t, params, None)}
|
|
assert scheduled == all_labels
|
|
|
|
# Tasks are also scheduled if an hour has passed.
|
|
params['pushlog_id'] = 11
|
|
params['pushdate'] += 3600
|
|
scheduled = {t.label for t in default_tasks if not opt.should_remove_task(t, params, None)}
|
|
assert scheduled == all_labels
|
|
|
|
# On non-autoland projects the 'remove_on_projects' value is used.
|
|
params['project'] = 'mozilla-central'
|
|
scheduled = {t.label for t in default_tasks if not opt.should_remove_task(t, params, None)}
|
|
assert scheduled == all_labels
|
|
|
|
params['project'] = 'try'
|
|
scheduled = {t.label for t in default_tasks if not opt.should_remove_task(t, params, None)}
|
|
assert scheduled == set()
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|