# 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('', mozharness['build-artifact-name']) test_packages_url = ARTIFACT_URL.format('', 'public/build/target.test_packages.json') mozharness_url = ARTIFACT_URL.format('', '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('', 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( '', 'public/build/{}.zip'.format(target)) test_packages_url = ARTIFACT_URL.format( '', 'public/build/{}.test_packages.json'.format(target)) mozharness_url = ARTIFACT_URL.format( '', '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('', 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('', mozharness['build-artifact-name']) test_packages_url = ARTIFACT_URL.format('', 'public/build/target.test_packages.json') mozharness_url = ARTIFACT_URL.format('', '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('', 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)