| OLD | NEW |
| 1 #!/usr/bin/python | 1 #!/usr/bin/python |
| 2 # Copyright (c) 2010 The Chromium OS Authors. All rights reserved. | 2 # Copyright (c) 2010 The Chromium OS Authors. All rights reserved. |
| 3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
| 4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
| 5 | 5 |
| 6 """Runs tests on VMs in parallel.""" | 6 """Runs tests on VMs in parallel.""" |
| 7 | 7 |
| 8 import optparse | 8 import optparse |
| 9 import os | 9 import os |
| 10 import subprocess | 10 import subprocess |
| 11 import sys | 11 import sys |
| 12 import tempfile | 12 import tempfile |
| 13 | 13 |
| 14 sys.path.append(os.path.join(os.path.dirname(__file__), '../lib')) | 14 sys.path.append(os.path.join(os.path.dirname(__file__), '../lib')) |
| 15 from cros_build_lib import Die | 15 from cros_build_lib import Die |
| 16 from cros_build_lib import Info | 16 from cros_build_lib import Info |
| 17 | 17 |
| 18 | 18 |
| 19 _DEFAULT_BASE_SSH_PORT = 9222 | 19 _DEFAULT_BASE_SSH_PORT = 9222 |
| 20 | 20 |
| 21 class ParallelTestRunner(object): | 21 class ParallelTestRunner(object): |
| 22 """Runs tests on VMs in parallel. | 22 """Runs tests on VMs in parallel. |
| 23 | 23 |
| 24 This class is a simple wrapper around cros_run_vm_test that provides an easy | 24 This class is a simple wrapper around cros_run_vm_test that provides an easy |
| 25 way to spawn several test instances in parallel and aggregate the results when | 25 way to spawn several test instances in parallel and aggregate the results when |
| 26 the tests complete. | 26 the tests complete. |
| 27 """ | 27 """ |
| 28 | 28 |
| 29 def __init__(self, tests, base_ssh_port=_DEFAULT_BASE_SSH_PORT, board=None, | 29 def __init__(self, tests, base_ssh_port=_DEFAULT_BASE_SSH_PORT, board=None, |
| 30 image_path=None, order_output=False, results_dir_root=None, | 30 image_path=None, order_output=False, quiet=False, |
| 31 use_emerged=False): | 31 results_dir_root=None, use_emerged=False): |
| 32 """Constructs and initializes the test runner class. | 32 """Constructs and initializes the test runner class. |
| 33 | 33 |
| 34 Args: | 34 Args: |
| 35 tests: A list of test names (see run_remote_tests.sh). | 35 tests: A list of test names (see run_remote_tests.sh). |
| 36 base_ssh_port: The base SSH port. Spawned VMs listen to localhost SSH | 36 base_ssh_port: The base SSH port. Spawned VMs listen to localhost SSH |
| 37 ports incrementally allocated starting from the base one. | 37 ports incrementally allocated starting from the base one. |
| 38 board: The target board. If none, cros_run_vm_tests will use the default | 38 board: The target board. If none, cros_run_vm_tests will use the default |
| 39 board. | 39 board. |
| 40 image_path: Full path to the VM image. If none, cros_run_vm_tests will use | 40 image_path: Full path to the VM image. If none, cros_run_vm_tests will use |
| 41 the latest image. | 41 the latest image. |
| 42 order_output: If True, output of individual VMs will be piped to | 42 order_output: If True, output of individual VMs will be piped to |
| 43 temporary files and emitted at the end. | 43 temporary files and emitted at the end. |
| 44 quiet: Emits no output from the VMs. Forces --order_output to be false, |
| 45 and requires specifying --results_dir_root |
| 44 results_dir_root: The results directory root. If provided, the results | 46 results_dir_root: The results directory root. If provided, the results |
| 45 directory root for each test will be created under it with the SSH port | 47 directory root for each test will be created under it with the SSH port |
| 46 appended to the test name. | 48 appended to the test name. |
| 49 use_emerged: Force use of emerged autotest packages. |
| 47 """ | 50 """ |
| 48 self._tests = tests | 51 self._tests = tests |
| 49 self._base_ssh_port = base_ssh_port | 52 self._base_ssh_port = base_ssh_port |
| 50 self._board = board | 53 self._board = board |
| 51 self._image_path = image_path | 54 self._image_path = image_path |
| 52 self._order_output = order_output | 55 self._order_output = order_output |
| 56 self._quiet = quiet |
| 53 self._results_dir_root = results_dir_root | 57 self._results_dir_root = results_dir_root |
| 54 self._use_emerged = use_emerged | 58 self._use_emerged = use_emerged |
| 55 | 59 |
| 56 def _SpawnTests(self): | 60 def _SpawnTests(self): |
| 57 """Spawns VMs and starts the test runs on them. | 61 """Spawns VMs and starts the test runs on them. |
| 58 | 62 |
| 59 Runs all tests in |self._tests|. Each test is executed on a separate VM. | 63 Runs all tests in |self._tests|. Each test is executed on a separate VM. |
| 60 | 64 |
| 61 Returns: | 65 Returns: |
| 62 A list of test process info objects containing the following dictionary | 66 A list of test process info objects containing the following dictionary |
| 63 entries: | 67 entries: |
| 64 'test': the test name; | 68 'test': the test name; |
| 65 'proc': the Popen process instance for this test run. | 69 'proc': the Popen process instance for this test run. |
| 66 """ | 70 """ |
| 67 ssh_port = self._base_ssh_port | 71 ssh_port = self._base_ssh_port |
| 68 spawned_tests = [] | 72 spawned_tests = [] |
| 69 for test in self._tests: | 73 for test in self._tests: |
| 70 args = [ os.path.join(os.path.dirname(__file__), 'cros_run_vm_test'), | 74 args = [ os.path.join(os.path.dirname(__file__), 'cros_run_vm_test'), |
| 71 '--snapshot', # The image is shared so don't modify it. | 75 '--snapshot', # The image is shared so don't modify it. |
| 72 '--no_graphics', | 76 '--no_graphics', |
| 73 '--ssh_port=%d' % ssh_port ] | 77 '--ssh_port=%d' % ssh_port ] |
| 74 if self._board: args.append('--board=%s' % self._board) | 78 if self._board: args.append('--board=%s' % self._board) |
| 75 if self._image_path: args.append('--image_path=%s' % self._image_path) | 79 if self._image_path: args.append('--image_path=%s' % self._image_path) |
| 80 results_dir = None |
| 76 if self._results_dir_root: | 81 if self._results_dir_root: |
| 77 args.append('--results_dir_root=%s/%s.%d' % | 82 results_dir = '%s/%s.%d' % (self._results_dir_root, test, ssh_port) |
| 78 (self._results_dir_root, test, ssh_port)) | 83 args.append('--results_dir_root=%s' % results_dir) |
| 79 if self._use_emerged: args.append('--use_emerged') | 84 if self._use_emerged: args.append('--use_emerged') |
| 80 args.append(test) | 85 args.append(test) |
| 81 Info('Running %r...' % args) | 86 Info('Running %r...' % args) |
| 82 output = None | 87 output = None |
| 83 if self._order_output: | 88 if self._quiet: |
| 89 output = open('/dev/null', mode='w') |
| 90 Info('Log files are in %s' % results_dir) |
| 91 elif self._order_output: |
| 84 output = tempfile.NamedTemporaryFile(prefix='parallel_vm_test_') | 92 output = tempfile.NamedTemporaryFile(prefix='parallel_vm_test_') |
| 85 Info('Piping output to %s.' % output.name) | 93 Info('Piping output to %s.' % output.name) |
| 86 proc = subprocess.Popen(args, stdout=output, stderr=output) | 94 proc = subprocess.Popen(args, stdout=output, stderr=output) |
| 87 test_info = { 'test': test, | 95 test_info = { 'test': test, |
| 88 'proc': proc, | 96 'proc': proc, |
| 89 'output': output } | 97 'output': output } |
| 90 spawned_tests.append(test_info) | 98 spawned_tests.append(test_info) |
| 91 ssh_port = ssh_port + 1 | 99 ssh_port = ssh_port + 1 |
| 92 return spawned_tests | 100 return spawned_tests |
| 93 | 101 |
| 94 def _WaitForCompletion(self, spawned_tests): | 102 def _WaitForCompletion(self, spawned_tests): |
| 95 """Waits for tests to complete and returns a list of failed tests. | 103 """Waits for tests to complete and returns a list of failed tests. |
| 96 | 104 |
| 97 If the test output was piped to a file, dumps the file contents to stdout. | 105 If the test output was piped to a file, dumps the file contents to stdout. |
| 98 | 106 |
| 99 Args: | 107 Args: |
| 100 spawned_tests: A list of test info objects (see _SpawnTests). | 108 spawned_tests: A list of test info objects (see _SpawnTests). |
| 101 | 109 |
| 102 Returns: | 110 Returns: |
| 103 A list of failed test names. | 111 A list of failed test names. |
| 104 """ | 112 """ |
| 105 failed_tests = [] | 113 failed_tests = [] |
| 106 for test_info in spawned_tests: | 114 for test_info in spawned_tests: |
| 107 proc = test_info['proc'] | 115 proc = test_info['proc'] |
| 108 proc.wait() | 116 proc.wait() |
| 109 if proc.returncode: failed_tests.append(test_info['test']) | 117 if proc.returncode: failed_tests.append(test_info['test']) |
| 110 output = test_info['output'] | 118 output = test_info['output'] |
| 111 if output: | 119 if output and not self._quiet: |
| 112 test = test_info['test'] | 120 test = test_info['test'] |
| 113 Info('------ START %s:%s ------' % (test, output.name)) | 121 Info('------ START %s:%s ------' % (test, output.name)) |
| 114 output.seek(0) | 122 output.seek(0) |
| 115 for line in output: | 123 for line in output: |
| 116 print line, | 124 print line, |
| 117 Info('------ END %s:%s ------' % (test, output.name)) | 125 Info('------ END %s:%s ------' % (test, output.name)) |
| 118 return failed_tests | 126 return failed_tests |
| 119 | 127 |
| 120 def Run(self): | 128 def Run(self): |
| 121 """Runs the tests in |self._tests| on separate VMs in parallel.""" | 129 """Runs the tests in |self._tests| on separate VMs in parallel.""" |
| (...skipping 13 matching lines...) Expand all Loading... |
| 135 parser.add_option('--board', | 143 parser.add_option('--board', |
| 136 help='The target board. If none specified, ' | 144 help='The target board. If none specified, ' |
| 137 'cros_run_vm_test will use the default board.') | 145 'cros_run_vm_test will use the default board.') |
| 138 parser.add_option('--image_path', | 146 parser.add_option('--image_path', |
| 139 help='Full path to the VM image. If none specified, ' | 147 help='Full path to the VM image. If none specified, ' |
| 140 'cros_run_vm_test will use the latest image.') | 148 'cros_run_vm_test will use the latest image.') |
| 141 parser.add_option('--order_output', action='store_true', default=False, | 149 parser.add_option('--order_output', action='store_true', default=False, |
| 142 help='Rather than emitting interleaved progress output ' | 150 help='Rather than emitting interleaved progress output ' |
| 143 'from the individual VMs, accumulate the outputs in ' | 151 'from the individual VMs, accumulate the outputs in ' |
| 144 'temporary files and dump them at the end.') | 152 'temporary files and dump them at the end.') |
| 153 parser.add_option('--quiet', action='store_true', default=False, |
| 154 help='Emits no output from the VMs. Forces --order_output' |
| 155 'to be false, and requires specifying --results_dir_root') |
| 145 parser.add_option('--results_dir_root', | 156 parser.add_option('--results_dir_root', |
| 146 help='Root results directory. If none specified, each test ' | 157 help='Root results directory. If none specified, each test ' |
| 147 'will store its results in a separate /tmp directory.') | 158 'will store its results in a separate /tmp directory.') |
| 148 parser.add_option('--use_emerged', action='store_true', default=False, | 159 parser.add_option('--use_emerged', action='store_true', default=False, |
| 149 help='Force use of emerged autotest packages') | 160 help='Force use of emerged autotest packages') |
| 150 (options, args) = parser.parse_args() | 161 (options, args) = parser.parse_args() |
| 151 | 162 |
| 152 if not args: | 163 if not args: |
| 153 parser.print_help() | 164 parser.print_help() |
| 154 Die('no tests provided') | 165 Die('no tests provided') |
| 155 | 166 |
| 167 if options.quiet: |
| 168 options.order_output = False |
| 169 if not options.results_dir_root: |
| 170 Die('--quiet requires --results_dir_root') |
| 156 runner = ParallelTestRunner(args, options.base_ssh_port, options.board, | 171 runner = ParallelTestRunner(args, options.base_ssh_port, options.board, |
| 157 options.image_path, options.order_output, | 172 options.image_path, options.order_output, |
| 158 options.results_dir_root, options.use_emerged) | 173 options.quiet, options.results_dir_root, |
| 174 options.use_emerged) |
| 159 runner.Run() | 175 runner.Run() |
| 160 | 176 |
| 161 | 177 |
| 162 if __name__ == '__main__': | 178 if __name__ == '__main__': |
| 163 main() | 179 main() |
| OLD | NEW |