#!/usr/bin/env python # Copyright 2015 The Chromium Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. """A helper module to run Legion multi-machine tests. Example usage with 1 task machine: $ testing/legion/tools/legion.py run \ --controller-isolated out/Release/example_test_controller.isolated \ --dimension os Ubuntu-14.04 \ --task-name test-task-name \ --task task_machine out/Release/example_task_machine.isolated Example usage with 2 task machines with the same isolated file: $ testing/legion/tools/legion.py run \ --controller-isolated out/Release/example_test_controller.isolated \ --dimension os Ubuntu-14.04 \ --task-name test-task-name \ --task task_machine_1 out/Release/example_task_machine.isolated \ --task task_machine_2 out/Release/example_task_machine.isolated Example usage with 2 task machines with different isolated file: $ testing/legion/tools/legion.py run \ --controller-isolated out/Release/example_test_controller.isolated \ --dimension os Ubuntu-14.04 \ --task-name test-task-name \ --task task_machine_1 out/Release/example_task_machine_1.isolated \ --task task_machine_2 out/Release/example_task_machine_2.isolated """ import argparse import logging import os import subprocess import sys THIS_DIR = os.path.split(__file__)[0] SWARMING_DIR = os.path.join(THIS_DIR, '..', '..', '..', 'tools', 'swarming_client') ISOLATE_PY = os.path.join(SWARMING_DIR, 'isolate.py') SWARMING_PY = os.path.join(SWARMING_DIR, 'swarming.py') LOGGING_LEVELS = ['DEBUG', 'INFO', 'WARNING', 'ERROR'] class Error(Exception): pass class ArgumentError(Error): pass def GetArgs(): parser = argparse.ArgumentParser(description=__doc__) parser.add_argument('action', choices=['run', 'trigger'], help='The swarming action to perform.') parser.add_argument('-f', '--format-only', action='store_true', help='If true the .isolated files are archived but ' 'swarming is not called, only the command line is built.') parser.add_argument('--controller-isolated', required=True, help='The isolated file for the test controller.') parser.add_argument('--isolate-server', help='Optional. The isolated server ' 'to use.', default=os.environ.get('ISOLATE_SERVER', '')) parser.add_argument('--swarming-server', help='Optional. The swarming server ' 'to use.', default=os.environ.get('SWARMING_SERVER', '')) parser.add_argument('--task-name', help='Optional. The swarming task name ' 'to use.') parser.add_argument('--dimension', action='append', dest='dimensions', nargs=2, default=[], help='Dimensions to pass to ' 'swarming.py. This is in the form of --dimension key ' 'value. The minimum required is --dimension os ') parser.add_argument('--task', action='append', dest='tasks', nargs=2, default=[], help='List of task names used in ' 'the test controller. This is in the form of --task name ' '.isolated and is passed to the controller as --name ' '.') parser.add_argument('--controller-var', action='append', dest='controller_vars', nargs=2, default=[], help='Command line vars to pass to the controller. These ' 'are in the form of --controller-var name value and are ' 'passed to the controller as --name value.') parser.add_argument('-v', '--verbosity', default=0, action='count') return parser.parse_args() def RunCommand(cmd, stream_stdout=False): """Runs the command line and streams stdout if requested.""" kwargs = { 'args': cmd, 'stderr': subprocess.PIPE, } if not stream_stdout: kwargs['stdout'] = subprocess.PIPE p = subprocess.Popen(**kwargs) stdout, stderr = p.communicate() if p.returncode: raise Error(stderr) if not stream_stdout: logging.debug(stdout) return stdout def Archive(isolated, isolate_server): """Calls isolate.py archive with the given args.""" cmd = [ sys.executable, ISOLATE_PY, 'archive', '--isolated', isolated, ] cmd.extend(['--isolate-server', isolate_server]) print ' '.join(cmd) return RunCommand(cmd).split()[0] # The isolated hash def GetSwarmingCommandLine(args): """Builds and returns the command line for swarming.py run|trigger.""" cmd = [ sys.executable, SWARMING_PY, args.action, args.controller_isolated, ] cmd.extend(['--isolate-server', args.isolate_server]) cmd.extend(['--swarming', args.swarming_server]) if args.task_name: cmd.extend(['--task-name', args.task_name]) # swarming.py dimensions for name, value in args.dimensions: cmd.extend(['--dimension', name, value]) cmd.append('--') cmd.extend(['--swarming-server', args.swarming_server]) cmd.extend(['--isolate-server', args.isolate_server]) # Specify the output dir cmd.extend(['--output-dir', '${ISOLATED_OUTDIR}']) # Task name/hash values for name, isolated in args.tasks: cmd.extend(['--' + name, Archive(isolated, args.isolate_server)]) # Test controller args for name, value in args.controller_vars: cmd.extend(['--' + name, value]) print ' '.join(cmd) return cmd def main(): args = GetArgs() if not args.swarming_server: raise ArgumentError('Missing required argument: --swarming-server') if not args.isolate_server: raise ArgumentError('Missing required argument: --isolate-server') logging.basicConfig( format='%(asctime)s %(filename)s:%(lineno)s %(levelname)s] %(message)s', datefmt='%H:%M:%S', level=LOGGING_LEVELS[len(LOGGING_LEVELS)-args.verbosity-1]) cmd = GetSwarmingCommandLine(args) if not args.format_only: RunCommand(cmd, True) return 0 if __name__ == '__main__': sys.exit(main())