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 | 12 |
13 sys.path.append(os.path.join(os.path.dirname(__file__), '../lib')) | 13 sys.path.append(os.path.join(os.path.dirname(__file__), '../lib')) |
14 from cros_build_lib import Die | 14 from cros_build_lib import Die |
15 from cros_build_lib import Info | 15 from cros_build_lib import Info |
16 | 16 |
17 | 17 |
18 class ParallelTestRunner(object): | 18 class ParallelTestRunner(object): |
19 """Runs tests on VMs in parallel.""" | 19 """Runs tests on VMs in parallel.""" |
20 | 20 |
21 _DEFAULT_START_SSH_PORT = 9222 | 21 _DEFAULT_START_SSH_PORT = 9222 |
22 | 22 |
23 def __init__(self, tests): | 23 def __init__(self, tests, results_dir_root=None): |
| 24 """Constructs and initializes the test runner class. |
| 25 |
| 26 Args: |
| 27 tests: A list of test names (see run_remote_tests.sh). |
| 28 results_dir_root: The results directory root. If provided, the results |
| 29 directory root for each test will be created under it with the SSH port |
| 30 appended to the test name. |
| 31 """ |
24 self._tests = tests | 32 self._tests = tests |
| 33 self._results_dir_root = results_dir_root |
25 | 34 |
26 def _SpawnTests(self): | 35 def _SpawnTests(self): |
27 """Spawns VMs and starts the test runs on them. | 36 """Spawns VMs and starts the test runs on them. |
28 | 37 |
29 Runs all tests in |self._tests|. Each test is executed on a separate VM. | 38 Runs all tests in |self._tests|. Each test is executed on a separate VM. |
30 | 39 |
31 Returns: A list of test process info objects containing the following | 40 Returns: |
32 dictionary entries: | 41 A list of test process info objects containing the following dictionary |
33 'test': the test name; | 42 entries: |
34 'proc': the Popen process instance for this test run. | 43 'test': the test name; |
| 44 'proc': the Popen process instance for this test run. |
35 """ | 45 """ |
36 ssh_port = self._DEFAULT_START_SSH_PORT | 46 ssh_port = self._DEFAULT_START_SSH_PORT |
37 spawned_tests = [] | 47 spawned_tests = [] |
38 # Test runs shouldn't need anything from stdin. However, it seems that | 48 # Test runs shouldn't need anything from stdin. However, it seems that |
39 # running with stdin leaves the terminal in a bad state so redirect from | 49 # running with stdin leaves the terminal in a bad state so redirect from |
40 # /dev/null. | 50 # /dev/null. |
41 dev_null = open('/dev/null') | 51 dev_null = open('/dev/null') |
42 for test in self._tests: | 52 for test in self._tests: |
43 args = [ os.path.join(os.path.dirname(__file__), 'cros_run_vm_test'), | 53 args = [ os.path.join(os.path.dirname(__file__), 'cros_run_vm_test'), |
44 '--snapshot', # The image is shared so don't modify it. | 54 '--snapshot', # The image is shared so don't modify it. |
45 '--no_graphics', | 55 '--no_graphics', |
46 '--ssh_port=%d' % ssh_port, | 56 '--ssh_port=%d' % ssh_port, |
47 '--test_case=%s' % test ] | 57 '--test_case=%s' % test ] |
| 58 if self._results_dir_root: |
| 59 args.append('--results_dir_root=%s/%s.%d' % |
| 60 (self._results_dir_root, test, ssh_port)) |
48 Info('Running %r...' % args) | 61 Info('Running %r...' % args) |
49 proc = subprocess.Popen(args, stdin=dev_null) | 62 proc = subprocess.Popen(args, stdin=dev_null) |
50 test_info = { 'test': test, | 63 test_info = { 'test': test, |
51 'proc': proc } | 64 'proc': proc } |
52 spawned_tests.append(test_info) | 65 spawned_tests.append(test_info) |
53 ssh_port = ssh_port + 1 | 66 ssh_port = ssh_port + 1 |
54 return spawned_tests | 67 return spawned_tests |
55 | 68 |
56 def _WaitForCompletion(self, spawned_tests): | 69 def _WaitForCompletion(self, spawned_tests): |
57 """Waits for tests to complete and returns a list of failed tests. | 70 """Waits for tests to complete and returns a list of failed tests. |
58 | 71 |
59 Arguments: | 72 Args: |
60 spawned_tests: A list of test info objects (see _SpawnTests). | 73 spawned_tests: A list of test info objects (see _SpawnTests). |
61 | 74 |
62 Returns: A list of failed test names. | 75 Returns: |
| 76 A list of failed test names. |
63 """ | 77 """ |
64 failed_tests = [] | 78 failed_tests = [] |
65 for test_info in spawned_tests: | 79 for test_info in spawned_tests: |
66 proc = test_info['proc'] | 80 proc = test_info['proc'] |
67 proc.wait() | 81 proc.wait() |
68 if proc.returncode: failed_tests.append(test_info['test']) | 82 if proc.returncode: failed_tests.append(test_info['test']) |
69 return failed_tests | 83 return failed_tests |
70 | 84 |
71 def Run(self): | 85 def Run(self): |
72 """Runs the tests in |self._tests| on separate VMs in parallel.""" | 86 """Runs the tests in |self._tests| on separate VMs in parallel.""" |
73 spawned_tests = self._SpawnTests() | 87 spawned_tests = self._SpawnTests() |
74 failed_tests = self._WaitForCompletion(spawned_tests) | 88 failed_tests = self._WaitForCompletion(spawned_tests) |
75 if failed_tests: Die('Tests failed: %r' % failed_tests) | 89 if failed_tests: Die('Tests failed: %r' % failed_tests) |
76 | 90 |
77 | 91 |
78 def main(): | 92 def main(): |
79 usage = 'Usage: %prog [options] tests...' | 93 usage = 'Usage: %prog [options] tests...' |
80 parser = optparse.OptionParser(usage=usage) | 94 parser = optparse.OptionParser(usage=usage) |
| 95 parser.add_option('--results_dir_root', help='Root results directory.') |
81 (options, args) = parser.parse_args() | 96 (options, args) = parser.parse_args() |
82 | 97 |
83 if not args: | 98 if not args: |
84 parser.print_help() | 99 parser.print_help() |
85 Die('no tests provided') | 100 Die('no tests provided') |
86 | 101 |
87 runner = ParallelTestRunner(args) | 102 runner = ParallelTestRunner(args, options.results_dir_root) |
88 runner.Run() | 103 runner.Run() |
89 | 104 |
90 | 105 |
91 if __name__ == '__main__': | 106 if __name__ == '__main__': |
92 main() | 107 main() |
OLD | NEW |