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 results_dir_root: The results directory root. If provided, the results | 44 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 | 45 directory root for each test will be created under it with the SSH port |
46 appended to the test name. | 46 appended to the test name. |
47 use_emerged: Force use of emerged autotest packages. | |
47 """ | 48 """ |
48 self._tests = tests | 49 self._tests = tests |
49 self._base_ssh_port = base_ssh_port | 50 self._base_ssh_port = base_ssh_port |
50 self._board = board | 51 self._board = board |
51 self._image_path = image_path | 52 self._image_path = image_path |
52 self._order_output = order_output | 53 self._order_output = order_output |
54 self._quiet = quiet | |
petkov
2011/03/02 23:08:34
document quiet too?
Chris Masone
2011/03/02 23:27:28
Done.
| |
53 self._results_dir_root = results_dir_root | 55 self._results_dir_root = results_dir_root |
54 self._use_emerged = use_emerged | 56 self._use_emerged = use_emerged |
55 | 57 |
56 def _SpawnTests(self): | 58 def _SpawnTests(self): |
57 """Spawns VMs and starts the test runs on them. | 59 """Spawns VMs and starts the test runs on them. |
58 | 60 |
59 Runs all tests in |self._tests|. Each test is executed on a separate VM. | 61 Runs all tests in |self._tests|. Each test is executed on a separate VM. |
60 | 62 |
61 Returns: | 63 Returns: |
62 A list of test process info objects containing the following dictionary | 64 A list of test process info objects containing the following dictionary |
63 entries: | 65 entries: |
64 'test': the test name; | 66 'test': the test name; |
65 'proc': the Popen process instance for this test run. | 67 'proc': the Popen process instance for this test run. |
66 """ | 68 """ |
67 ssh_port = self._base_ssh_port | 69 ssh_port = self._base_ssh_port |
68 spawned_tests = [] | 70 spawned_tests = [] |
69 for test in self._tests: | 71 for test in self._tests: |
70 args = [ os.path.join(os.path.dirname(__file__), 'cros_run_vm_test'), | 72 args = [ os.path.join(os.path.dirname(__file__), 'cros_run_vm_test'), |
71 '--snapshot', # The image is shared so don't modify it. | 73 '--snapshot', # The image is shared so don't modify it. |
72 '--no_graphics', | 74 '--no_graphics', |
73 '--ssh_port=%d' % ssh_port ] | 75 '--ssh_port=%d' % ssh_port ] |
74 if self._board: args.append('--board=%s' % self._board) | 76 if self._board: args.append('--board=%s' % self._board) |
75 if self._image_path: args.append('--image_path=%s' % self._image_path) | 77 if self._image_path: args.append('--image_path=%s' % self._image_path) |
78 results_dir = '%s/%s.%d' % (self._results_dir_root, test, ssh_port) | |
petkov
2011/03/02 23:08:34
subtle... it took me a while to think through the
Chris Masone
2011/03/02 23:27:28
Done.
| |
76 if self._results_dir_root: | 79 if self._results_dir_root: |
77 args.append('--results_dir_root=%s/%s.%d' % | 80 args.append('--results_dir_root=%s' % results_dir) |
78 (self._results_dir_root, test, ssh_port)) | |
79 if self._use_emerged: args.append('--use_emerged') | 81 if self._use_emerged: args.append('--use_emerged') |
80 args.append(test) | 82 args.append(test) |
81 Info('Running %r...' % args) | 83 Info('Running %r...' % args) |
82 output = None | 84 output = None |
83 if self._order_output: | 85 if self._order_output: |
84 output = tempfile.NamedTemporaryFile(prefix='parallel_vm_test_') | 86 output = tempfile.NamedTemporaryFile(prefix='parallel_vm_test_') |
85 Info('Piping output to %s.' % output.name) | 87 Info('Piping output to %s.' % output.name) |
88 if self._quiet: | |
petkov
2011/03/02 23:08:34
can you move this up and do "elif self._order_outp
Chris Masone
2011/03/02 23:27:28
Done.
| |
89 output = open('/dev/null', mode='w') | |
90 Info('Output is in %s' % results_dir) | |
petkov
2011/03/02 23:08:34
You mean the log files are there?
Chris Masone
2011/03/02 23:27:28
Yes.
| |
86 proc = subprocess.Popen(args, stdout=output, stderr=output) | 91 proc = subprocess.Popen(args, stdout=output, stderr=output) |
87 test_info = { 'test': test, | 92 test_info = { 'test': test, |
88 'proc': proc, | 93 'proc': proc, |
89 'output': output } | 94 'output': output } |
90 spawned_tests.append(test_info) | 95 spawned_tests.append(test_info) |
91 ssh_port = ssh_port + 1 | 96 ssh_port = ssh_port + 1 |
92 return spawned_tests | 97 return spawned_tests |
93 | 98 |
94 def _WaitForCompletion(self, spawned_tests): | 99 def _WaitForCompletion(self, spawned_tests): |
95 """Waits for tests to complete and returns a list of failed tests. | 100 """Waits for tests to complete and returns a list of failed tests. |
96 | 101 |
97 If the test output was piped to a file, dumps the file contents to stdout. | 102 If the test output was piped to a file, dumps the file contents to stdout. |
98 | 103 |
99 Args: | 104 Args: |
100 spawned_tests: A list of test info objects (see _SpawnTests). | 105 spawned_tests: A list of test info objects (see _SpawnTests). |
101 | 106 |
102 Returns: | 107 Returns: |
103 A list of failed test names. | 108 A list of failed test names. |
104 """ | 109 """ |
105 failed_tests = [] | 110 failed_tests = [] |
106 for test_info in spawned_tests: | 111 for test_info in spawned_tests: |
107 proc = test_info['proc'] | 112 proc = test_info['proc'] |
108 proc.wait() | 113 proc.wait() |
109 if proc.returncode: failed_tests.append(test_info['test']) | 114 if proc.returncode: failed_tests.append(test_info['test']) |
110 output = test_info['output'] | 115 output = test_info['output'] |
111 if output: | 116 if output and not self._quiet: |
112 test = test_info['test'] | 117 test = test_info['test'] |
113 Info('------ START %s:%s ------' % (test, output.name)) | 118 Info('------ START %s:%s ------' % (test, output.name)) |
114 output.seek(0) | 119 output.seek(0) |
115 for line in output: | 120 for line in output: |
116 print line, | 121 print line, |
117 Info('------ END %s:%s ------' % (test, output.name)) | 122 Info('------ END %s:%s ------' % (test, output.name)) |
118 return failed_tests | 123 return failed_tests |
119 | 124 |
120 def Run(self): | 125 def Run(self): |
121 """Runs the tests in |self._tests| on separate VMs in parallel.""" | 126 """Runs the tests in |self._tests| on separate VMs in parallel.""" |
(...skipping 13 matching lines...) Expand all Loading... | |
135 parser.add_option('--board', | 140 parser.add_option('--board', |
136 help='The target board. If none specified, ' | 141 help='The target board. If none specified, ' |
137 'cros_run_vm_test will use the default board.') | 142 'cros_run_vm_test will use the default board.') |
138 parser.add_option('--image_path', | 143 parser.add_option('--image_path', |
139 help='Full path to the VM image. If none specified, ' | 144 help='Full path to the VM image. If none specified, ' |
140 'cros_run_vm_test will use the latest image.') | 145 'cros_run_vm_test will use the latest image.') |
141 parser.add_option('--order_output', action='store_true', default=False, | 146 parser.add_option('--order_output', action='store_true', default=False, |
142 help='Rather than emitting interleaved progress output ' | 147 help='Rather than emitting interleaved progress output ' |
143 'from the individual VMs, accumulate the outputs in ' | 148 'from the individual VMs, accumulate the outputs in ' |
144 'temporary files and dump them at the end.') | 149 'temporary files and dump them at the end.') |
150 parser.add_option('--quiet', action='store_true', default=False, | |
151 help='Emits no output from the VMs. Forces --order_output' | |
152 'to be false, and requires specifying --results_dir_root') | |
145 parser.add_option('--results_dir_root', | 153 parser.add_option('--results_dir_root', |
146 help='Root results directory. If none specified, each test ' | 154 help='Root results directory. If none specified, each test ' |
147 'will store its results in a separate /tmp directory.') | 155 'will store its results in a separate /tmp directory.') |
148 parser.add_option('--use_emerged', action='store_true', default=False, | 156 parser.add_option('--use_emerged', action='store_true', default=False, |
149 help='Force use of emerged autotest packages') | 157 help='Force use of emerged autotest packages') |
150 (options, args) = parser.parse_args() | 158 (options, args) = parser.parse_args() |
151 | 159 |
152 if not args: | 160 if not args: |
153 parser.print_help() | 161 parser.print_help() |
154 Die('no tests provided') | 162 Die('no tests provided') |
155 | 163 |
164 if options.quiet: | |
165 options.order_output = False | |
166 if not options.results_dir_root: | |
167 Die('--quiet requires --results_dir_root') | |
156 runner = ParallelTestRunner(args, options.base_ssh_port, options.board, | 168 runner = ParallelTestRunner(args, options.base_ssh_port, options.board, |
157 options.image_path, options.order_output, | 169 options.image_path, options.order_output, |
158 options.results_dir_root, options.use_emerged) | 170 options.quiet, options.results_dir_root, |
171 options.use_emerged) | |
159 runner.Run() | 172 runner.Run() |
160 | 173 |
161 | 174 |
162 if __name__ == '__main__': | 175 if __name__ == '__main__': |
163 main() | 176 main() |
OLD | NEW |