Mypal/taskcluster/taskgraph/transforms/tests/make_task_description.py

446 lines
17 KiB
Python

# 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/.
"""
These transforms construct a task description to run the given test, based on a
test description. The implementation here is shared among all test kinds, but
contains specific support for how we run tests in Gecko (via mozharness,
invoked in particular ways).
This is a good place to translate a test-description option such as
`single-core: true` to the implementation of that option in a task description
(worker options, mozharness commandline, environment variables, etc.)
The test description should be fully formed by the time it reaches these
transforms, and these transforms should not embody any specific knowledge about
what should run where. this is the wrong place for special-casing platforms,
for example - use `all_tests.py` instead.
"""
from __future__ import absolute_import, print_function, unicode_literals
from taskgraph.transforms.base import TransformSequence
from taskgraph.transforms.job.common import (
docker_worker_support_vcs_checkout,
)
import logging
import os.path
ARTIFACT_URL = 'https://queue.taskcluster.net/v1/task/{}/artifacts/{}'
WORKER_TYPE = {
# default worker types keyed by instance-size
'large': 'aws-provisioner-v1/gecko-t-linux-large',
'xlarge': 'aws-provisioner-v1/gecko-t-linux-xlarge',
'legacy': 'aws-provisioner-v1/gecko-t-linux-medium',
'default': 'aws-provisioner-v1/gecko-t-linux-large',
# windows worker types keyed by test-platform
'windows7-32-vm': 'aws-provisioner-v1/gecko-t-win7-32',
'windows7-32': 'aws-provisioner-v1/gecko-t-win7-32-gpu',
'windows10-64-vm': 'aws-provisioner-v1/gecko-t-win10-64',
'windows10-64': 'aws-provisioner-v1/gecko-t-win10-64-gpu'
}
ARTIFACTS = [
# (artifact name prefix, in-image path)
("public/logs/", "build/upload/logs/"),
("public/test", "artifacts/"),
("public/test_info/", "build/blobber_upload_dir/"),
]
logger = logging.getLogger(__name__)
transforms = TransformSequence()
@transforms.add
def make_task_description(config, tests):
"""Convert *test* descriptions to *task* descriptions (input to
taskgraph.transforms.task)"""
for test in tests:
label = '{}-{}-{}'.format(config.kind, test['test-platform'], test['test-name'])
if test['chunks'] > 1:
label += '-{}'.format(test['this-chunk'])
build_label = test['build-label']
unittest_try_name = test.get('unittest-try-name', test['test-name'])
attr_build_platform, attr_build_type = test['build-platform'].split('/', 1)
suite = test['suite']
if '/' in suite:
suite, flavor = suite.split('/', 1)
else:
flavor = suite
attributes = test.get('attributes', {})
attributes.update({
'build_platform': attr_build_platform,
'build_type': attr_build_type,
# only keep the first portion of the test platform
'test_platform': test['test-platform'].split('/')[0],
'test_chunk': str(test['this-chunk']),
'unittest_suite': suite,
'unittest_flavor': flavor,
'unittest_try_name': unittest_try_name,
})
taskdesc = {}
taskdesc['label'] = label
taskdesc['description'] = test['description']
taskdesc['attributes'] = attributes
taskdesc['dependencies'] = {'build': build_label}
taskdesc['deadline-after'] = '1 day'
taskdesc['expires-after'] = test['expires-after']
taskdesc['routes'] = []
taskdesc['run-on-projects'] = test.get('run-on-projects', ['all'])
taskdesc['scopes'] = []
taskdesc['extra'] = {
'chunks': {
'current': test['this-chunk'],
'total': test['chunks'],
},
'suite': {
'name': suite,
'flavor': flavor,
},
}
taskdesc['treeherder'] = {
'symbol': test['treeherder-symbol'],
'kind': 'test',
'tier': test['tier'],
'platform': test.get('treeherder-machine-platform', test['build-platform']),
}
# the remainder (the worker-type and worker) differs depending on the
# worker implementation
worker_setup_functions[test['worker-implementation']](config, test, taskdesc)
# yield only the task description, discarding the test description
yield taskdesc
worker_setup_functions = {}
def worker_setup_function(name):
def wrap(func):
worker_setup_functions[name] = func
return func
return wrap
@worker_setup_function("docker-engine")
@worker_setup_function("docker-worker")
def docker_worker_setup(config, test, taskdesc):
artifacts = [
# (artifact name prefix, in-image path)
("public/logs/", "/home/worker/workspace/build/upload/logs/"),
("public/test", "/home/worker/artifacts/"),
("public/test_info/", "/home/worker/workspace/build/blobber_upload_dir/"),
]
mozharness = test['mozharness']
installer_url = ARTIFACT_URL.format('<build>', mozharness['build-artifact-name'])
test_packages_url = ARTIFACT_URL.format('<build>',
'public/build/target.test_packages.json')
mozharness_url = ARTIFACT_URL.format('<build>',
'public/build/mozharness.zip')
taskdesc['worker-type'] = WORKER_TYPE[test['instance-size']]
worker = taskdesc['worker'] = {}
worker['implementation'] = test['worker-implementation']
worker['docker-image'] = test['docker-image']
worker['allow-ptrace'] = True # required for all tests, for crashreporter
worker['relengapi-proxy'] = False # but maybe enabled for tooltool below
worker['loopback-video'] = test['loopback-video']
worker['loopback-audio'] = test['loopback-audio']
worker['max-run-time'] = test['max-run-time']
worker['retry-exit-status'] = test['retry-exit-status']
worker['artifacts'] = [{
'name': prefix,
'path': os.path.join('/home/worker/workspace', path),
'type': 'directory',
} for (prefix, path) in artifacts]
worker['caches'] = [{
'type': 'persistent',
'name': 'level-{}-{}-test-workspace'.format(
config.params['level'], config.params['project']),
'mount-point': "/home/worker/workspace",
}]
env = worker['env'] = {
'MOZHARNESS_CONFIG': ' '.join(mozharness['config']),
'MOZHARNESS_SCRIPT': mozharness['script'],
'MOZILLA_BUILD_URL': {'task-reference': installer_url},
'NEED_PULSEAUDIO': 'true',
'NEED_WINDOW_MANAGER': 'true',
}
if mozharness['set-moz-node-path']:
env['MOZ_NODE_PATH'] = '/usr/local/bin/node'
if 'actions' in mozharness:
env['MOZHARNESS_ACTIONS'] = ' '.join(mozharness['actions'])
if config.params['project'] == 'try':
env['TRY_COMMIT_MSG'] = config.params['message']
# handle some of the mozharness-specific options
if mozharness['tooltool-downloads']:
worker['relengapi-proxy'] = True
worker['caches'].append({
'type': 'persistent',
'name': 'tooltool-cache',
'mount-point': '/home/worker/tooltool-cache',
})
taskdesc['scopes'].extend([
'docker-worker:relengapi-proxy:tooltool.download.internal',
'docker-worker:relengapi-proxy:tooltool.download.public',
])
# assemble the command line
command = [
'/home/worker/bin/run-task',
# The workspace cache/volume is default owned by root:root.
'--chown', '/home/worker/workspace',
]
# Support vcs checkouts regardless of whether the task runs from
# source or not in case it is needed on an interactive loaner.
docker_worker_support_vcs_checkout(config, test, taskdesc)
# If we have a source checkout, run mozharness from it instead of
# downloading a zip file with the same content.
if test['checkout']:
command.extend(['--vcs-checkout', '/home/worker/checkouts/gecko'])
env['MOZHARNESS_PATH'] = '/home/worker/checkouts/gecko/testing/mozharness'
else:
env['MOZHARNESS_URL'] = {'task-reference': mozharness_url}
command.extend([
'--',
'/home/worker/bin/test-linux.sh',
])
if mozharness.get('no-read-buildbot-config'):
command.append("--no-read-buildbot-config")
command.extend([
{"task-reference": "--installer-url=" + installer_url},
{"task-reference": "--test-packages-url=" + test_packages_url},
])
command.extend(mozharness.get('extra-options', []))
# TODO: remove the need for run['chunked']
if mozharness.get('chunked') or test['chunks'] > 1:
# Implement mozharness['chunking-args'], modifying command in place
if mozharness['chunking-args'] == 'this-chunk':
command.append('--total-chunk={}'.format(test['chunks']))
command.append('--this-chunk={}'.format(test['this-chunk']))
elif mozharness['chunking-args'] == 'test-suite-suffix':
suffix = mozharness['chunk-suffix'].replace('<CHUNK>', str(test['this-chunk']))
for i, c in enumerate(command):
if isinstance(c, basestring) and c.startswith('--test-suite'):
command[i] += suffix
if 'download-symbols' in mozharness:
download_symbols = mozharness['download-symbols']
download_symbols = {True: 'true', False: 'false'}.get(download_symbols, download_symbols)
command.append('--download-symbols=' + download_symbols)
worker['command'] = command
def normpath(path):
return path.replace('/', '\\')
def get_firefox_version():
with open('browser/config/version.txt', 'r') as f:
return f.readline().strip()
@worker_setup_function('generic-worker')
def generic_worker_setup(config, test, taskdesc):
artifacts = [
{
'path': 'public\\logs\\localconfig.json',
'type': 'file'
},
{
'path': 'public\\logs\\log_critical.log',
'type': 'file'
},
{
'path': 'public\\logs\\log_error.log',
'type': 'file'
},
{
'path': 'public\\logs\\log_fatal.log',
'type': 'file'
},
{
'path': 'public\\logs\\log_info.log',
'type': 'file'
},
{
'path': 'public\\logs\\log_raw.log',
'type': 'file'
},
{
'path': 'public\\logs\\log_warning.log',
'type': 'file'
},
{
'path': 'public\\test_info',
'type': 'directory'
}
]
mozharness = test['mozharness']
build_platform = taskdesc['attributes']['build_platform']
test_platform = test['test-platform'].split('/')[0]
target = 'firefox-{}.en-US.{}'.format(get_firefox_version(), build_platform)
installer_url = ARTIFACT_URL.format(
'<build>', 'public/build/{}.zip'.format(target))
test_packages_url = ARTIFACT_URL.format(
'<build>', 'public/build/{}.test_packages.json'.format(target))
mozharness_url = ARTIFACT_URL.format(
'<build>', 'public/build/mozharness.zip')
taskdesc['worker-type'] = WORKER_TYPE[test_platform]
taskdesc['scopes'].extend(
['generic-worker:os-group:{}'.format(group) for group in test['os-groups']])
worker = taskdesc['worker'] = {}
worker['os-groups'] = test['os-groups']
worker['implementation'] = test['worker-implementation']
worker['max-run-time'] = test['max-run-time']
worker['artifacts'] = artifacts
env = worker['env'] = {
# Bug 1306989
'APPDATA': '%cd%\\AppData\\Roaming',
'LOCALAPPDATA': '%cd%\\AppData\\Local',
'TEMP': '%cd%\\AppData\\Local\\Temp',
'TMP': '%cd%\\AppData\\Local\\Temp',
'USERPROFILE': '%cd%',
}
# assemble the command line
mh_command = [
'c:\\mozilla-build\\python\\python.exe',
'-u',
'mozharness\\scripts\\' + normpath(mozharness['script'])
]
for mh_config in mozharness['config']:
mh_command.extend(['--cfg', 'mozharness\\configs\\' + normpath(mh_config)])
mh_command.extend(mozharness.get('extra-options', []))
if mozharness.get('no-read-buildbot-config'):
mh_command.append('--no-read-buildbot-config')
mh_command.extend(['--installer-url', installer_url])
mh_command.extend(['--test-packages-url', test_packages_url])
if mozharness.get('download-symbols'):
if isinstance(mozharness['download-symbols'], basestring):
mh_command.extend(['--download-symbols', mozharness['download-symbols']])
else:
mh_command.extend(['--download-symbols', 'true'])
# TODO: remove the need for run['chunked']
if mozharness.get('chunked') or test['chunks'] > 1:
# Implement mozharness['chunking-args'], modifying command in place
if mozharness['chunking-args'] == 'this-chunk':
mh_command.append('--total-chunk={}'.format(test['chunks']))
mh_command.append('--this-chunk={}'.format(test['this-chunk']))
elif mozharness['chunking-args'] == 'test-suite-suffix':
suffix = mozharness['chunk-suffix'].replace('<CHUNK>', str(test['this-chunk']))
for i, c in enumerate(mh_command):
if isinstance(c, basestring) and c.startswith('--test-suite'):
mh_command[i] += suffix
worker['command'] = [
'mkdir {} {}'.format(env['APPDATA'], env['TMP']),
{'task-reference': 'c:\\mozilla-build\\wget\\wget.exe {}'.format(mozharness_url)},
'c:\\mozilla-build\\info-zip\\unzip.exe mozharness.zip',
{'task-reference': ' '.join(mh_command)},
'xcopy build\\blobber_upload_dir public\\test_info /e /i',
'copy /y logs\\*.* public\\logs\\'
]
@worker_setup_function("macosx-engine")
def macosx_engine_setup(config, test, taskdesc):
mozharness = test['mozharness']
installer_url = ARTIFACT_URL.format('<build>', mozharness['build-artifact-name'])
test_packages_url = ARTIFACT_URL.format('<build>',
'public/build/target.test_packages.json')
mozharness_url = ARTIFACT_URL.format('<build>',
'public/build/mozharness.zip')
# for now we have only 10.10 machines
taskdesc['worker-type'] = 'tc-worker-provisioner/gecko-t-osx-10-10'
worker = taskdesc['worker'] = {}
worker['implementation'] = test['worker-implementation']
worker['artifacts'] = [{
'name': prefix.rstrip('/'),
'path': path.rstrip('/'),
'type': 'directory',
} for (prefix, path) in ARTIFACTS]
worker['env'] = {
'GECKO_HEAD_REPOSITORY': config.params['head_repository'],
'GECKO_HEAD_REV': config.params['head_rev'],
'MOZHARNESS_CONFIG': ' '.join(mozharness['config']),
'MOZHARNESS_SCRIPT': mozharness['script'],
'MOZHARNESS_URL': {'task-reference': mozharness_url},
'MOZILLA_BUILD_URL': {'task-reference': installer_url},
}
# assemble the command line
worker['link'] = '{}/raw-file/{}/taskcluster/scripts/tester/test-macosx.sh'.format(
config.params['head_repository'], config.params['head_rev']
)
command = worker['command'] = ["./test-macosx.sh"]
if mozharness.get('no-read-buildbot-config'):
command.append("--no-read-buildbot-config")
command.extend([
{"task-reference": "--installer-url=" + installer_url},
{"task-reference": "--test-packages-url=" + test_packages_url},
])
if mozharness.get('include-blob-upload-branch'):
command.append('--blob-upload-branch=' + config.params['project'])
command.extend(mozharness.get('extra-options', []))
# TODO: remove the need for run['chunked']
if mozharness.get('chunked') or test['chunks'] > 1:
# Implement mozharness['chunking-args'], modifying command in place
if mozharness['chunking-args'] == 'this-chunk':
command.append('--total-chunk={}'.format(test['chunks']))
command.append('--this-chunk={}'.format(test['this-chunk']))
elif mozharness['chunking-args'] == 'test-suite-suffix':
suffix = mozharness['chunk-suffix'].replace('<CHUNK>', str(test['this-chunk']))
for i, c in enumerate(command):
if isinstance(c, basestring) and c.startswith('--test-suite'):
command[i] += suffix
if 'download-symbols' in mozharness:
download_symbols = mozharness['download-symbols']
download_symbols = {True: 'true', False: 'false'}.get(download_symbols, download_symbols)
command.append('--download-symbols=' + download_symbols)