summaryrefslogtreecommitdiff
path: root/chromium/testing/legion/tools/legion.py
blob: 74014cc67c90b4c587697a5ee35dcbdd795aef17 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
#!/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(cmd_args):
  default_dimension = ['pool', 'Legion']
  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=default_dimension,
                      help='Dimensions to pass to '
                      'swarming.py. This is in the form of --dimension key '
                      'value. The minimum required is --dimension os <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 '
                      '<ISOLATED HASH>.')
  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(cmd_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, extra_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(extra_args)
  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:
    if args.format_only:
      cmd.extend(['--' + name, isolated + '_test_only'])
    else:
      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():
  if '--' not in sys.argv:
    cmd_args = sys.argv[1:]
    extra_args = []
  else:
    index = sys.argv.index('--')
    cmd_args = sys.argv[1:index]
    extra_args = sys.argv[index+1:]
  args = GetArgs(cmd_args)
  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, extra_args)
  if not args.format_only:
    RunCommand(cmd, True)
  return 0


if __name__ == '__main__':
  sys.exit(main())