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 hashlib | 8 import hashlib |
| 9 import json |
9 import multiprocessing | 10 import multiprocessing |
10 import os | 11 import os |
11 import random | 12 import random |
12 import re | 13 import re |
13 import shutil | 14 import shutil |
14 import sys | 15 import sys |
15 | 16 |
16 import bb_utils | 17 import bb_utils |
17 import bb_annotations | 18 import bb_annotations |
18 | 19 |
(...skipping 20 matching lines...) Expand all Loading... |
39 # apk_package: package for the apk to be installed. | 40 # apk_package: package for the apk to be installed. |
40 # test_apk: apk to run tests on. | 41 # test_apk: apk to run tests on. |
41 # test_data: data folder in format destination:source. | 42 # test_data: data folder in format destination:source. |
42 # host_driven_root: The host-driven test root directory. | 43 # host_driven_root: The host-driven test root directory. |
43 # annotation: Annotation of the tests to include. | 44 # annotation: Annotation of the tests to include. |
44 # exclude_annotation: The annotation of the tests to exclude. | 45 # exclude_annotation: The annotation of the tests to exclude. |
45 I_TEST = collections.namedtuple('InstrumentationTest', [ | 46 I_TEST = collections.namedtuple('InstrumentationTest', [ |
46 'name', 'apk', 'apk_package', 'test_apk', 'test_data', 'host_driven_root', | 47 'name', 'apk', 'apk_package', 'test_apk', 'test_data', 'host_driven_root', |
47 'annotation', 'exclude_annotation', 'extra_flags']) | 48 'annotation', 'exclude_annotation', 'extra_flags']) |
48 | 49 |
| 50 |
49 def I(name, apk, apk_package, test_apk, test_data, host_driven_root=None, | 51 def I(name, apk, apk_package, test_apk, test_data, host_driven_root=None, |
50 annotation=None, exclude_annotation=None, extra_flags=None): | 52 annotation=None, exclude_annotation=None, extra_flags=None): |
51 return I_TEST(name, apk, apk_package, test_apk, test_data, host_driven_root, | 53 return I_TEST(name, apk, apk_package, test_apk, test_data, host_driven_root, |
52 annotation, exclude_annotation, extra_flags) | 54 annotation, exclude_annotation, extra_flags) |
53 | 55 |
54 INSTRUMENTATION_TESTS = dict((suite.name, suite) for suite in [ | 56 INSTRUMENTATION_TESTS = dict((suite.name, suite) for suite in [ |
55 I('ContentShell', | 57 I('ContentShell', |
56 'ContentShell.apk', | 58 'ContentShell.apk', |
57 'org.chromium.content_shell_apk', | 59 'org.chromium.content_shell_apk', |
58 'ContentShellTest', | 60 'ContentShellTest', |
(...skipping 75 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
134 args.append('--release') | 136 args.append('--release') |
135 if options.asan: | 137 if options.asan: |
136 args.append('--tool=asan') | 138 args.append('--tool=asan') |
137 for suite in suites: | 139 for suite in suites: |
138 bb_annotations.PrintNamedStep(suite) | 140 bb_annotations.PrintNamedStep(suite) |
139 cmd = ['build/android/test_runner.py', 'gtest', '-s', suite] + args | 141 cmd = ['build/android/test_runner.py', 'gtest', '-s', suite] + args |
140 if suite == 'content_browsertests': | 142 if suite == 'content_browsertests': |
141 cmd.append('--num_retries=1') | 143 cmd.append('--num_retries=1') |
142 RunCmd(cmd) | 144 RunCmd(cmd) |
143 | 145 |
| 146 |
144 def RunChromeDriverTests(options): | 147 def RunChromeDriverTests(options): |
145 """Run all the steps for running chromedriver tests.""" | 148 """Run all the steps for running chromedriver tests.""" |
146 bb_annotations.PrintNamedStep('chromedriver_annotation') | 149 bb_annotations.PrintNamedStep('chromedriver_annotation') |
147 RunCmd(['chrome/test/chromedriver/run_buildbot_steps.py', | 150 RunCmd(['chrome/test/chromedriver/run_buildbot_steps.py', |
148 '--android-packages=%s,%s,%s' % | 151 '--android-packages=%s,%s,%s' % |
149 (constants.PACKAGE_INFO['chromium_test_shell'].package, | 152 (constants.PACKAGE_INFO['chromium_test_shell'].package, |
150 constants.PACKAGE_INFO['chrome_stable'].package, | 153 constants.PACKAGE_INFO['chrome_stable'].package, |
151 constants.PACKAGE_INFO['chrome_beta'].package), | 154 constants.PACKAGE_INFO['chrome_beta'].package), |
152 '--revision=%s' % _GetRevision(options), | 155 '--revision=%s' % _GetRevision(options), |
153 '--update-log']) | 156 '--update-log']) |
154 | 157 |
| 158 |
155 def InstallApk(options, test, print_step=False): | 159 def InstallApk(options, test, print_step=False): |
156 """Install an apk to all phones. | 160 """Install an apk to all phones. |
157 | 161 |
158 Args: | 162 Args: |
159 options: options object | 163 options: options object |
160 test: An I_TEST namedtuple | 164 test: An I_TEST namedtuple |
161 print_step: Print a buildbot step | 165 print_step: Print a buildbot step |
162 """ | 166 """ |
163 if print_step: | 167 if print_step: |
164 bb_annotations.PrintNamedStep('install_%s' % test.name.lower()) | 168 bb_annotations.PrintNamedStep('install_%s' % test.name.lower()) |
(...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
220 RunCmd(['webkit/tools/layout_tests/run_webkit_tests.py', | 224 RunCmd(['webkit/tools/layout_tests/run_webkit_tests.py', |
221 '--lint-test-files', | 225 '--lint-test-files', |
222 '--chromium', | 226 '--chromium', |
223 '--target', target]) | 227 '--target', target]) |
224 | 228 |
225 | 229 |
226 def RunWebkitLayoutTests(options): | 230 def RunWebkitLayoutTests(options): |
227 """Run layout tests on an actual device.""" | 231 """Run layout tests on an actual device.""" |
228 bb_annotations.PrintNamedStep('webkit_tests') | 232 bb_annotations.PrintNamedStep('webkit_tests') |
229 cmd_args = [ | 233 cmd_args = [ |
230 '--no-show-results', | 234 '--no-show-results', |
231 '--no-new-test-results', | 235 '--no-new-test-results', |
232 '--full-results-html', | 236 '--full-results-html', |
233 '--clobber-old-results', | 237 '--clobber-old-results', |
234 '--exit-after-n-failures', '5000', | 238 '--exit-after-n-failures', '5000', |
235 '--exit-after-n-crashes-or-timeouts', '100', | 239 '--exit-after-n-crashes-or-timeouts', '100', |
236 '--debug-rwt-logging', | 240 '--debug-rwt-logging', |
237 '--results-directory', '../layout-test-results', | 241 '--results-directory', '../layout-test-results', |
238 '--target', options.target, | 242 '--target', options.target, |
239 '--builder-name', options.build_properties.get('buildername', ''), | 243 '--builder-name', options.build_properties.get('buildername', ''), |
240 '--build-number', str(options.build_properties.get('buildnumber', '')), | 244 '--build-number', str(options.build_properties.get('buildnumber', '')), |
241 '--master-name', 'ChromiumWebkit', # TODO: Get this from the cfg. | 245 '--master-name', 'ChromiumWebkit', # TODO: Get this from the cfg. |
242 '--build-name', options.build_properties.get('buildername', ''), | 246 '--build-name', options.build_properties.get('buildername', ''), |
243 '--platform=android'] | 247 '--platform=android'] |
244 | 248 |
245 for flag in 'test_results_server', 'driver_name', 'additional_drt_flag': | 249 for flag in 'test_results_server', 'driver_name', 'additional_drt_flag': |
246 if flag in options.factory_properties: | 250 if flag in options.factory_properties: |
247 cmd_args.extend(['--%s' % flag.replace('_', '-'), | 251 cmd_args.extend(['--%s' % flag.replace('_', '-'), |
248 options.factory_properties.get(flag)]) | 252 options.factory_properties.get(flag)]) |
249 | 253 |
250 for f in options.factory_properties.get('additional_expectations', []): | 254 for f in options.factory_properties.get('additional_expectations', []): |
251 cmd_args.extend( | 255 cmd_args.extend( |
252 ['--additional-expectations=%s' % os.path.join(CHROME_SRC_DIR, *f)]) | 256 ['--additional-expectations=%s' % os.path.join(CHROME_SRC_DIR, *f)]) |
253 | 257 |
254 # TODO(dpranke): Remove this block after | 258 # TODO(dpranke): Remove this block after |
255 # https://codereview.chromium.org/12927002/ lands. | 259 # https://codereview.chromium.org/12927002/ lands. |
256 for f in options.factory_properties.get('additional_expectations_files', []): | 260 for f in options.factory_properties.get('additional_expectations_files', []): |
257 cmd_args.extend( | 261 cmd_args.extend( |
258 ['--additional-expectations=%s' % os.path.join(CHROME_SRC_DIR, *f)]) | 262 ['--additional-expectations=%s' % os.path.join(CHROME_SRC_DIR, *f)]) |
259 | 263 |
260 RunCmd(['webkit/tools/layout_tests/run_webkit_tests.py'] + cmd_args) | 264 exit_code = RunCmd(['webkit/tools/layout_tests/run_webkit_tests.py'] + |
| 265 cmd_args) |
| 266 if exit_code == 254: # AKA -1, internal error. |
| 267 bb_annotations.PrintMsg('?? (crashed or hung)') |
| 268 else: |
| 269 full_results_path = os.path.join('..', 'layout-test-results', |
| 270 'full_results.json') |
| 271 if os.path.exists(full_results_path): |
| 272 full_results = json.load(open(full_results_path)) |
| 273 unexpected_failures, unexpected_flakes, unexpected_passes = ( |
| 274 _ParseLayoutTestResults(full_results)) |
| 275 if unexpected_failures: |
| 276 _PrintDashboardLink('failed', unexpected_failures, |
| 277 max_tests=25) |
| 278 elif unexpected_passes: |
| 279 _PrintDashboardLink('unexpected passes', unexpected_passes, |
| 280 max_tests=10) |
| 281 if unexpected_flakes: |
| 282 _PrintDashboardLink('unexpected flakes', unexpected_flakes, |
| 283 max_tests=10) |
| 284 else: |
| 285 bb_annotations.PrintMsg('?? (results missing)') |
261 | 286 |
262 if options.factory_properties.get('archive_webkit_results', False): | 287 if options.factory_properties.get('archive_webkit_results', False): |
263 bb_annotations.PrintNamedStep('archive_webkit_results') | 288 bb_annotations.PrintNamedStep('archive_webkit_results') |
264 base = 'https://storage.googleapis.com/chromium-layout-test-archives' | 289 base = 'https://storage.googleapis.com/chromium-layout-test-archives' |
265 builder_name = options.build_properties.get('buildername', '') | 290 builder_name = options.build_properties.get('buildername', '') |
266 build_number = str(options.build_properties.get('buildnumber', '')) | 291 build_number = str(options.build_properties.get('buildnumber', '')) |
267 bb_annotations.PrintLink('results', | 292 results_link = '%s/%s/%s/layout-test-results/results.html' % ( |
268 '%s/%s/%s/layout-test-results/results.html' % ( | 293 base, EscapeBuilderName(builder_name), build_number) |
269 base, EscapeBuilderName(builder_name), build_number)) | 294 bb_annotations.PrintLink('results', results_link) |
270 bb_annotations.PrintLink('(zip)', '%s/%s/%s/layout-test-results.zip' % ( | 295 bb_annotations.PrintLink('(zip)', '%s/%s/%s/layout-test-results.zip' % ( |
271 base, EscapeBuilderName(builder_name), build_number)) | 296 base, EscapeBuilderName(builder_name), build_number)) |
272 gs_bucket = 'gs://chromium-layout-test-archives' | 297 gs_bucket = 'gs://chromium-layout-test-archives' |
273 RunCmd([os.path.join(SLAVE_SCRIPTS_DIR, 'chromium', | 298 RunCmd([os.path.join(SLAVE_SCRIPTS_DIR, 'chromium', |
274 'archive_layout_test_results.py'), | 299 'archive_layout_test_results.py'), |
275 '--results-dir', '../../layout-test-results', | 300 '--results-dir', '../../layout-test-results', |
276 '--build-dir', CHROME_OUT_DIR, | 301 '--build-dir', CHROME_OUT_DIR, |
277 '--build-number', build_number, | 302 '--build-number', build_number, |
278 '--builder-name', builder_name, | 303 '--builder-name', builder_name, |
279 '--gs-bucket', gs_bucket]) | 304 '--gs-bucket', gs_bucket]) |
| 305 |
| 306 |
| 307 def _ParseLayoutTestResults(results): |
| 308 """Extract the failures from the test run.""" |
| 309 # Cloned from third_party/WebKit/Tools/Scripts/print-json-test-results |
| 310 tests = _ConvertTrieToFlatPaths(results['tests']) |
| 311 failures = {} |
| 312 flakes = {} |
| 313 passes = {} |
| 314 for (test, result) in tests.iteritems(): |
| 315 if result.get('is_unexpected'): |
| 316 actual_result = result['actual'] |
| 317 if ' PASS' in actual_result: |
| 318 flakes[test] = actual_result |
| 319 elif actual_result == 'PASS': |
| 320 passes[test] = result |
| 321 else: |
| 322 failures[test] = actual_result |
| 323 |
| 324 return (passes, failures, flakes) |
| 325 |
| 326 |
| 327 def _ConvertTrieToFlatPaths(trie, prefix=None): |
| 328 """Flatten the trie of failures into a list.""" |
| 329 # Cloned from third_party/WebKit/Tools/Scripts/print-json-test-results |
| 330 result = {} |
| 331 for name, data in trie.iteritems(): |
| 332 if prefix: |
| 333 name = prefix + '/' + name |
| 334 |
| 335 if len(data) and 'actual' not in data and 'expected' not in data: |
| 336 result.update(_ConvertTrieToFlatPaths(data, name)) |
| 337 else: |
| 338 result[name] = data |
| 339 |
| 340 return result |
| 341 |
| 342 |
| 343 def _PrintDashboardLink(link_text, tests, max_tests): |
| 344 """Add a link to the flakiness dashboard in the step annotations.""" |
| 345 if len(tests) > max_tests: |
| 346 test_list_text = ' '.join(tests[:max_tests]) + ' and more' |
| 347 else: |
| 348 test_list_text = ' '.join(tests) |
| 349 |
| 350 dashboard_base = ('http://test-results.appspot.com' |
| 351 '/dashboards/flakiness_dashboard.html#' |
| 352 'master=ChromiumWebkit&tests=') |
| 353 |
| 354 bb_annotations.PrintLink('%d %s: %s' % |
| 355 (len(tests), link_text, test_list_text), |
| 356 dashboard_base + ','.join(tests)) |
280 | 357 |
281 | 358 |
282 def EscapeBuilderName(builder_name): | 359 def EscapeBuilderName(builder_name): |
283 return re.sub('[ ()]', '_', builder_name) | 360 return re.sub('[ ()]', '_', builder_name) |
284 | 361 |
285 | 362 |
286 def SpawnLogcatMonitor(): | 363 def SpawnLogcatMonitor(): |
287 shutil.rmtree(LOGCAT_DIR, ignore_errors=True) | 364 shutil.rmtree(LOGCAT_DIR, ignore_errors=True) |
288 bb_utils.SpawnCmd([ | 365 bb_utils.SpawnCmd([ |
289 os.path.join(CHROME_SRC_DIR, 'build', 'android', 'adb_logcat_monitor.py'), | 366 os.path.join(CHROME_SRC_DIR, 'build', 'android', 'adb_logcat_monitor.py'), |
290 LOGCAT_DIR]) | 367 LOGCAT_DIR]) |
291 | 368 |
292 # Wait for logcat_monitor to pull existing logcat | 369 # Wait for logcat_monitor to pull existing logcat |
293 RunCmd(['sleep', '5']) | 370 RunCmd(['sleep', '5']) |
294 | 371 |
| 372 |
295 def ProvisionDevices(options): | 373 def ProvisionDevices(options): |
296 bb_annotations.PrintNamedStep('provision_devices') | 374 bb_annotations.PrintNamedStep('provision_devices') |
297 | 375 |
298 if not bb_utils.TESTING: | 376 if not bb_utils.TESTING: |
299 # Restart adb to work around bugs, sleep to wait for usb discovery. | 377 # Restart adb to work around bugs, sleep to wait for usb discovery. |
300 adb = android_commands.AndroidCommands() | 378 adb = android_commands.AndroidCommands() |
301 adb.RestartAdbServer() | 379 adb.RestartAdbServer() |
302 RunCmd(['sleep', '1']) | 380 RunCmd(['sleep', '1']) |
303 | 381 |
304 if options.reboot: | 382 if options.reboot: |
305 RebootDevices() | 383 RebootDevices() |
306 provision_cmd = ['build/android/provision_devices.py', '-t', options.target] | 384 provision_cmd = ['build/android/provision_devices.py', '-t', options.target] |
307 if options.auto_reconnect: | 385 if options.auto_reconnect: |
308 provision_cmd.append('--auto-reconnect') | 386 provision_cmd.append('--auto-reconnect') |
309 RunCmd(provision_cmd) | 387 RunCmd(provision_cmd) |
310 | 388 |
311 | 389 |
312 def DeviceStatusCheck(_): | 390 def DeviceStatusCheck(_): |
313 bb_annotations.PrintNamedStep('device_status_check') | 391 bb_annotations.PrintNamedStep('device_status_check') |
314 RunCmd(['build/android/buildbot/bb_device_status_check.py'], | 392 RunCmd(['build/android/buildbot/bb_device_status_check.py'], |
315 halt_on_failure=True) | 393 halt_on_failure=True) |
316 | 394 |
317 | 395 |
318 def GetDeviceSetupStepCmds(): | 396 def GetDeviceSetupStepCmds(): |
319 return [ | 397 return [ |
320 ('provision_devices', ProvisionDevices), | 398 ('provision_devices', ProvisionDevices), |
321 ('device_status_check', DeviceStatusCheck), | 399 ('device_status_check', DeviceStatusCheck), |
322 ] | 400 ] |
323 | 401 |
324 | 402 |
325 def RunUnitTests(options): | 403 def RunUnitTests(options): |
326 RunTestSuites(options, gtest_config.STABLE_TEST_SUITES) | 404 RunTestSuites(options, gtest_config.STABLE_TEST_SUITES) |
327 | 405 |
328 | 406 |
329 def RunInstrumentationTests(options): | 407 def RunInstrumentationTests(options): |
330 for test in INSTRUMENTATION_TESTS.itervalues(): | 408 for test in INSTRUMENTATION_TESTS.itervalues(): |
331 RunInstrumentationSuite(options, test) | 409 RunInstrumentationSuite(options, test) |
(...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
371 link_text: Link text to be displayed on the step. | 449 link_text: Link text to be displayed on the step. |
372 link_rel_path: Link path relative to |dir_to_upload|. | 450 link_rel_path: Link path relative to |dir_to_upload|. |
373 gs_url: Google storage URL. | 451 gs_url: Google storage URL. |
374 """ | 452 """ |
375 revision = _GetRevision(options) | 453 revision = _GetRevision(options) |
376 bot_id = options.build_properties.get('buildername', 'testing') | 454 bot_id = options.build_properties.get('buildername', 'testing') |
377 randhash = hashlib.sha1(str(random.random())).hexdigest() | 455 randhash = hashlib.sha1(str(random.random())).hexdigest() |
378 gs_path = '%s/%s/%s/%s' % (gs_base_dir, bot_id, revision, randhash) | 456 gs_path = '%s/%s/%s/%s' % (gs_base_dir, bot_id, revision, randhash) |
379 RunCmd([bb_utils.GSUTIL_PATH, 'cp', '-R', dir_to_upload, 'gs://%s' % gs_path]) | 457 RunCmd([bb_utils.GSUTIL_PATH, 'cp', '-R', dir_to_upload, 'gs://%s' % gs_path]) |
380 bb_annotations.PrintLink(link_text, | 458 bb_annotations.PrintLink(link_text, |
381 '%s/%s/%s' % (gs_url, gs_path, link_rel_path)) | 459 '%s/%s/%s' % (gs_url, gs_path, link_rel_path)) |
382 | 460 |
383 | 461 |
384 def GenerateJavaCoverageReport(options): | 462 def GenerateJavaCoverageReport(options): |
385 """Generates an HTML coverage report using EMMA and uploads it.""" | 463 """Generates an HTML coverage report using EMMA and uploads it.""" |
386 bb_annotations.PrintNamedStep('java_coverage_report') | 464 bb_annotations.PrintNamedStep('java_coverage_report') |
387 | 465 |
388 coverage_html = os.path.join(options.coverage_dir, 'coverage_html') | 466 coverage_html = os.path.join(options.coverage_dir, 'coverage_html') |
389 RunCmd(['build/android/generate_emma_html.py', | 467 RunCmd(['build/android/generate_emma_html.py', |
390 '--coverage-dir', options.coverage_dir, | 468 '--coverage-dir', options.coverage_dir, |
391 '--metadata-dir', os.path.join(CHROME_OUT_DIR, options.target), | 469 '--metadata-dir', os.path.join(CHROME_OUT_DIR, options.target), |
(...skipping 66 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
458 parser.add_option('--asan', action='store_true', help='Run tests with asan.') | 536 parser.add_option('--asan', action='store_true', help='Run tests with asan.') |
459 parser.add_option('--install', metavar='<apk name>', | 537 parser.add_option('--install', metavar='<apk name>', |
460 help='Install an apk by name') | 538 help='Install an apk by name') |
461 parser.add_option('--reboot', action='store_true', | 539 parser.add_option('--reboot', action='store_true', |
462 help='Reboot devices before running tests') | 540 help='Reboot devices before running tests') |
463 parser.add_option('--coverage-bucket', | 541 parser.add_option('--coverage-bucket', |
464 help=('Bucket name to store coverage results. Coverage is ' | 542 help=('Bucket name to store coverage results. Coverage is ' |
465 'only run if this is set.')) | 543 'only run if this is set.')) |
466 parser.add_option( | 544 parser.add_option( |
467 '--flakiness-server', | 545 '--flakiness-server', |
468 help='The flakiness dashboard server to which the results should be ' | 546 help=('The flakiness dashboard server to which the results should be ' |
469 'uploaded.') | 547 'uploaded.')) |
470 parser.add_option( | 548 parser.add_option( |
471 '--auto-reconnect', action='store_true', | 549 '--auto-reconnect', action='store_true', |
472 help='Push script to device which restarts adbd on disconnections.') | 550 help='Push script to device which restarts adbd on disconnections.') |
473 parser.add_option( | 551 parser.add_option( |
474 '--logcat-dump-output', | 552 '--logcat-dump-output', |
475 help='The logcat dump output will be "tee"-ed into this file') | 553 help='The logcat dump output will be "tee"-ed into this file') |
476 | 554 |
477 return parser | 555 return parser |
478 | 556 |
479 | 557 |
(...skipping 11 matching lines...) Expand all Loading... |
491 setattr(options, 'target', options.factory_properties.get('target', 'Debug')) | 569 setattr(options, 'target', options.factory_properties.get('target', 'Debug')) |
492 if options.coverage_bucket: | 570 if options.coverage_bucket: |
493 setattr(options, 'coverage_dir', | 571 setattr(options, 'coverage_dir', |
494 os.path.join(CHROME_OUT_DIR, options.target, 'coverage')) | 572 os.path.join(CHROME_OUT_DIR, options.target, 'coverage')) |
495 | 573 |
496 MainTestWrapper(options) | 574 MainTestWrapper(options) |
497 | 575 |
498 | 576 |
499 if __name__ == '__main__': | 577 if __name__ == '__main__': |
500 sys.exit(main(sys.argv)) | 578 sys.exit(main(sys.argv)) |
OLD | NEW |