OLD | NEW |
1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
2 # Copyright (c) 2013 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2013 The Chromium 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 import collections | 6 import collections |
7 import glob | 7 import glob |
8 import json | 8 import json |
| 9 import multiprocessing |
9 import optparse | 10 import optparse |
10 import os | 11 import os |
11 import pipes | 12 import pipes |
12 import shutil | 13 import shutil |
13 import subprocess | 14 import subprocess |
14 import sys | 15 import sys |
15 | 16 |
16 sys.path.append(os.path.join(os.path.dirname(__file__), '..')) | 17 sys.path.append(os.path.join(os.path.dirname(__file__), '..')) |
| 18 from pylib import android_commands |
17 from pylib import buildbot_report | 19 from pylib import buildbot_report |
18 from pylib import constants | 20 from pylib import constants |
19 from pylib.gtest import gtest_config | 21 from pylib.gtest import gtest_config |
20 | 22 |
| 23 sys.path.append(os.path.join( |
| 24 constants.CHROME_DIR, 'third_party', 'android_testrunner')) |
| 25 import errors |
| 26 |
21 | 27 |
22 TESTING = 'BUILDBOT_TESTING' in os.environ | 28 TESTING = 'BUILDBOT_TESTING' in os.environ |
23 | 29 |
24 CHROME_SRC = constants.CHROME_DIR | 30 CHROME_SRC = constants.CHROME_DIR |
25 | 31 |
26 # Describes an instrumation test suite: | 32 # Describes an instrumation test suite: |
27 # test: Name of test we're running. | 33 # test: Name of test we're running. |
28 # apk: apk to be installed. | 34 # apk: apk to be installed. |
29 # apk_package: package for the apk to be installed. | 35 # apk_package: package for the apk to be installed. |
30 # test_apk: apk to run tests on. | 36 # test_apk: apk to run tests on. |
(...skipping 15 matching lines...) Expand all Loading... |
46 I_TEST('AndroidWebView', | 52 I_TEST('AndroidWebView', |
47 'AndroidWebView.apk', | 53 'AndroidWebView.apk', |
48 'org.chromium.android_webview', | 54 'org.chromium.android_webview', |
49 'AndroidWebViewTest', | 55 'AndroidWebViewTest', |
50 'webview:android_webview/test/data/device_files'), | 56 'webview:android_webview/test/data/device_files'), |
51 ]) | 57 ]) |
52 | 58 |
53 VALID_TESTS = set(['ui', 'unit', 'webkit', 'webkit_layout']) | 59 VALID_TESTS = set(['ui', 'unit', 'webkit', 'webkit_layout']) |
54 | 60 |
55 | 61 |
| 62 |
56 def SpawnCmd(command): | 63 def SpawnCmd(command): |
57 """Spawn a process without waiting for termination.""" | 64 """Spawn a process without waiting for termination.""" |
58 # Add adb binary to path. In the future, might use build_internal copy. | |
59 env = dict(os.environ) | |
60 env['PATH'] = os.pathsep.join([ | |
61 env['PATH'], os.path.join(constants.ANDROID_SDK_ROOT, 'platform-tools')]) | |
62 print '>', ' '.join(map(pipes.quote, command)) | 65 print '>', ' '.join(map(pipes.quote, command)) |
63 sys.stdout.flush() | 66 sys.stdout.flush() |
64 if TESTING: | 67 if TESTING: |
65 class MockPopen(object): | 68 class MockPopen(object): |
66 @staticmethod | 69 @staticmethod |
67 def wait(): | 70 def wait(): |
68 return 0 | 71 return 0 |
69 return MockPopen() | 72 return MockPopen() |
70 | 73 |
71 return subprocess.Popen(command, cwd=CHROME_SRC, env=env) | 74 return subprocess.Popen(command, cwd=CHROME_SRC) |
72 | 75 |
73 def RunCmd(command, flunk_on_failure=True): | 76 def RunCmd(command, flunk_on_failure=True): |
74 """Run a command relative to the chrome source root.""" | 77 """Run a command relative to the chrome source root.""" |
75 code = SpawnCmd(command).wait() | 78 code = SpawnCmd(command).wait() |
76 print '<', ' '.join(map(pipes.quote, command)) | 79 print '<', ' '.join(map(pipes.quote, command)) |
77 if code != 0: | 80 if code != 0: |
78 print 'ERROR: non-zero status %d from %s' % (code, command) | 81 print 'ERROR: process exited with code %d' % code |
79 if flunk_on_failure: | 82 if flunk_on_failure: |
80 buildbot_report.PrintError() | 83 buildbot_report.PrintError() |
81 else: | 84 else: |
82 buildbot_report.PrintWarning() | 85 buildbot_report.PrintWarning() |
83 return code | 86 return code |
84 | 87 |
85 | 88 |
| 89 # multiprocessing map_async requires a top-level function for pickle library. |
| 90 def RebootDeviceSafe(device): |
| 91 """Reboot a device, wait for it to start, and squelch timeout exceptions.""" |
| 92 try: |
| 93 android_commands.AndroidCommands(device).Reboot(True) |
| 94 except errors.DeviceUnresponsiveError as e: |
| 95 return e |
| 96 |
| 97 |
| 98 def RebootDevices(): |
| 99 """Reboot all attached and online devices.""" |
| 100 buildbot_report.PrintNamedStep('Reboot devices') |
| 101 devices = android_commands.GetAttachedDevices() |
| 102 print 'Rebooting: %s' % devices |
| 103 if devices and not TESTING: |
| 104 pool = multiprocessing.Pool(len(devices)) |
| 105 results = pool.map_async(RebootDeviceSafe, devices).get(99999) |
| 106 |
| 107 for device, result in zip(devices, results): |
| 108 if result: |
| 109 print '%s failed to startup.' % device |
| 110 |
| 111 if any(results): |
| 112 buildbot_report.PrintWarning() |
| 113 else: |
| 114 print 'Reboots complete.' |
| 115 |
| 116 |
86 def RunTestSuites(options, suites): | 117 def RunTestSuites(options, suites): |
87 """Manages an invocation of run_tests.py. | 118 """Manages an invocation of run_tests.py. |
88 | 119 |
89 Args: | 120 Args: |
90 options: options object. | 121 options: options object. |
91 suites: List of suites to run. | 122 suites: List of suites to run. |
92 """ | 123 """ |
93 args = ['--verbose'] | 124 args = ['--verbose'] |
94 if options.target == 'Release': | 125 if options.target == 'Release': |
95 args.append('--release') | 126 args.append('--release') |
(...skipping 66 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
162 '--builder-name', options.build_properties.get('buildername', ''), | 193 '--builder-name', options.build_properties.get('buildername', ''), |
163 '--build-number', options.build_properties.get('buildnumber', ''), | 194 '--build-number', options.build_properties.get('buildnumber', ''), |
164 '--master-name', options.build_properties.get('mastername', ''), | 195 '--master-name', options.build_properties.get('mastername', ''), |
165 '--build-name', options.build_properties.get('buildername', ''), | 196 '--build-name', options.build_properties.get('buildername', ''), |
166 '--platform=chromium-android', | 197 '--platform=chromium-android', |
167 '--test-results-server', | 198 '--test-results-server', |
168 options.factory_properties.get('test_results_server', '')]) | 199 options.factory_properties.get('test_results_server', '')]) |
169 | 200 |
170 | 201 |
171 def MainTestWrapper(options): | 202 def MainTestWrapper(options): |
| 203 # Restart adb to work around bugs, sleep to wait for usb discovery. |
| 204 RunCmd(['adb', 'kill-server']) |
| 205 RunCmd(['adb', 'start-server']) |
| 206 RunCmd(['sleep', '1']) |
| 207 |
| 208 # Spawn logcat monitor |
| 209 logcat_dir = os.path.join(CHROME_SRC, 'out/logcat') |
| 210 shutil.rmtree(logcat_dir, ignore_errors=True) |
| 211 SpawnCmd(['build/android/adb_logcat_monitor.py', logcat_dir]) |
| 212 |
| 213 # Wait for logcat_monitor to pull existing logcat |
| 214 RunCmd(['sleep', '5']) |
| 215 |
| 216 if options.reboot: |
| 217 RebootDevices() |
| 218 |
172 # Device check and alert emails | 219 # Device check and alert emails |
173 buildbot_report.PrintNamedStep('device_status_check') | 220 buildbot_report.PrintNamedStep('device_status_check') |
174 RunCmd(['build/android/device_status_check.py'], flunk_on_failure=False) | 221 RunCmd(['build/android/device_status_check.py'], flunk_on_failure=False) |
175 | 222 |
176 if options.install: | 223 if options.install: |
177 test_obj = INSTRUMENTATION_TESTS[options.install] | 224 test_obj = INSTRUMENTATION_TESTS[options.install] |
178 InstallApk(options, test_obj, print_step=True) | 225 InstallApk(options, test_obj, print_step=True) |
179 | 226 |
180 if not options.test_filter: | |
181 return | |
182 | |
183 # Spawn logcat monitor | |
184 logcat_dir = os.path.join(CHROME_SRC, 'out/logcat') | |
185 shutil.rmtree(logcat_dir, ignore_errors=True) | |
186 SpawnCmd(['build/android/adb_logcat_monitor.py', logcat_dir]) | |
187 | |
188 if 'unit' in options.test_filter: | 227 if 'unit' in options.test_filter: |
189 RunTestSuites(options, gtest_config.STABLE_TEST_SUITES) | 228 RunTestSuites(options, gtest_config.STABLE_TEST_SUITES) |
190 if 'ui' in options.test_filter: | 229 if 'ui' in options.test_filter: |
191 for test in INSTRUMENTATION_TESTS.itervalues(): | 230 for test in INSTRUMENTATION_TESTS.itervalues(): |
192 RunInstrumentationSuite(options, test) | 231 RunInstrumentationSuite(options, test) |
193 if 'webkit' in options.test_filter: | 232 if 'webkit' in options.test_filter: |
194 RunTestSuites(options, ['webkit_unit_tests', 'TestWebKitAPI']) | 233 RunTestSuites(options, ['webkit_unit_tests', 'TestWebKitAPI']) |
195 RunWebkitLint(options.target) | 234 RunWebkitLint(options.target) |
196 if 'webkit_layout' in options.test_filter: | 235 if 'webkit_layout' in options.test_filter: |
197 RunWebkitLayoutTests(options) | 236 RunWebkitLayoutTests(options) |
198 | 237 |
199 if options.experimental: | 238 if options.experimental: |
200 RunTestSuites(options, gtest_config.EXPERIMENTAL_TEST_SUITES) | 239 RunTestSuites(options, gtest_config.EXPERIMENTAL_TEST_SUITES) |
201 | 240 |
202 # Print logcat, kill logcat monitor | 241 # Print logcat, kill logcat monitor |
203 buildbot_report.PrintNamedStep('logcat_dump') | 242 buildbot_report.PrintNamedStep('logcat_dump') |
204 RunCmd(['build/android/adb_logcat_printer.py', logcat_dir]) | 243 RunCmd(['build/android/adb_logcat_printer.py', logcat_dir]) |
205 | 244 |
206 buildbot_report.PrintNamedStep('test_report') | 245 buildbot_report.PrintNamedStep('test_report') |
207 for report in glob.glob( | 246 for report in glob.glob( |
208 os.path.join(CHROME_SRC, 'out', options.target, 'test_logs', '*.log')): | 247 os.path.join(CHROME_SRC, 'out', options.target, 'test_logs', '*.log')): |
209 subprocess.Popen(['cat', report]).wait() | 248 RunCmd(['cat', report]) |
210 os.remove(report) | 249 os.remove(report) |
211 | 250 |
212 | 251 |
213 def main(argv): | 252 def main(argv): |
214 parser = optparse.OptionParser() | 253 parser = optparse.OptionParser() |
215 | 254 |
216 def convert_json(option, _, value, parser): | 255 def convert_json(option, _, value, parser): |
217 setattr(parser.values, option.dest, json.loads(value)) | 256 setattr(parser.values, option.dest, json.loads(value)) |
218 | 257 |
219 parser.add_option('--build-properties', action='callback', | 258 parser.add_option('--build-properties', action='callback', |
220 callback=convert_json, type='string', default={}, | 259 callback=convert_json, type='string', default={}, |
221 help='build properties in JSON format') | 260 help='build properties in JSON format') |
222 parser.add_option('--factory-properties', action='callback', | 261 parser.add_option('--factory-properties', action='callback', |
223 callback=convert_json, type='string', default={}, | 262 callback=convert_json, type='string', default={}, |
224 help='factory properties in JSON format') | 263 help='factory properties in JSON format') |
225 parser.add_option('--slave-properties', action='callback', | 264 parser.add_option('--slave-properties', action='callback', |
226 callback=convert_json, type='string', default={}, | 265 callback=convert_json, type='string', default={}, |
227 help='Properties set by slave script in JSON format') | 266 help='Properties set by slave script in JSON format') |
228 parser.add_option('--experimental', action='store_true', | 267 parser.add_option('--experimental', action='store_true', |
229 help='Run experiemental tests') | 268 help='Run experiemental tests') |
230 parser.add_option('-f', '--test-filter', metavar='<filter>', default=[], | 269 parser.add_option('-f', '--test-filter', metavar='<filter>', default=[], |
231 action='append', | 270 action='append', |
232 help=('Run a test suite. Test suites: "%s"' % | 271 help=('Run a test suite. Test suites: "%s"' % |
233 '", "'.join(VALID_TESTS))) | 272 '", "'.join(VALID_TESTS))) |
234 parser.add_option('--asan', action='store_true', help='Run tests with asan.') | 273 parser.add_option('--asan', action='store_true', help='Run tests with asan.') |
235 parser.add_option('--install', metavar='<apk name>', | 274 parser.add_option('--install', metavar='<apk name>', |
236 help='Install an apk by name') | 275 help='Install an apk by name') |
| 276 parser.add_option('--reboot', action='store_true', |
| 277 help='Reboot devices before running tests') |
237 options, args = parser.parse_args(argv[1:]) | 278 options, args = parser.parse_args(argv[1:]) |
238 | 279 |
239 def ParserError(msg): | 280 def ParserError(msg): |
240 """We avoid parser.error because it calls sys.exit.""" | 281 """We avoid parser.error because it calls sys.exit.""" |
241 parser.print_help() | 282 parser.print_help() |
242 print >> sys.stderr, '\nERROR:', msg | 283 print >> sys.stderr, '\nERROR:', msg |
243 return 1 | 284 return 1 |
244 | 285 |
245 if args: | 286 if args: |
246 return ParserError('Unused args %s' % args) | 287 return ParserError('Unused args %s' % args) |
247 | 288 |
248 unknown_tests = set(options.test_filter) - VALID_TESTS | 289 unknown_tests = set(options.test_filter) - VALID_TESTS |
249 if unknown_tests: | 290 if unknown_tests: |
250 return ParserError('Unknown tests %s' % list(unknown_tests)) | 291 return ParserError('Unknown tests %s' % list(unknown_tests)) |
251 | 292 |
252 setattr(options, 'target', options.factory_properties.get('target', 'Debug')) | 293 setattr(options, 'target', options.factory_properties.get('target', 'Debug')) |
253 | 294 |
| 295 # Add adb binary and chromium-source platform-tools to tip of PATH variable. |
| 296 android_paths = [os.path.join(constants.ANDROID_SDK_ROOT, 'platform-tools')] |
| 297 |
| 298 # Bots checkout chrome in /b/build/slave/<name>/build/src |
| 299 build_internal_android = os.path.abspath(os.path.join( |
| 300 CHROME_SRC, '..', '..', '..', '..', '..', 'build_internal', 'scripts', |
| 301 'slave', 'android')) |
| 302 if os.path.exists(build_internal_android): |
| 303 android_paths.insert(0, build_internal_android) |
| 304 os.environ['PATH'] = os.pathsep.join(android_paths + [os.environ['PATH']]) |
| 305 |
254 MainTestWrapper(options) | 306 MainTestWrapper(options) |
255 | 307 |
256 | 308 |
257 if __name__ == '__main__': | 309 if __name__ == '__main__': |
258 sys.exit(main(sys.argv)) | 310 sys.exit(main(sys.argv)) |
OLD | NEW |