Home | History | Annotate | Download | only in buildbot
      1 #!/usr/bin/env python
      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
      4 # found in the LICENSE file.
      5 
      6 import collections
      7 import glob
      8 import hashlib
      9 import json
     10 import os
     11 import random
     12 import re
     13 import shutil
     14 import sys
     15 
     16 import bb_utils
     17 import bb_annotations
     18 
     19 sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
     20 import provision_devices
     21 from pylib import android_commands
     22 from pylib import constants
     23 from pylib.device import device_utils
     24 from pylib.gtest import gtest_config
     25 
     26 CHROME_SRC_DIR = bb_utils.CHROME_SRC
     27 DIR_BUILD_ROOT = os.path.dirname(CHROME_SRC_DIR)
     28 CHROME_OUT_DIR = bb_utils.CHROME_OUT_DIR
     29 
     30 SLAVE_SCRIPTS_DIR = os.path.join(bb_utils.BB_BUILD_DIR, 'scripts', 'slave')
     31 LOGCAT_DIR = os.path.join(bb_utils.CHROME_OUT_DIR, 'logcat')
     32 GS_URL = 'https://storage.googleapis.com'
     33 GS_AUTH_URL = 'https://storage.cloud.google.com'
     34 
     35 # Describes an instrumation test suite:
     36 #   test: Name of test we're running.
     37 #   apk: apk to be installed.
     38 #   apk_package: package for the apk to be installed.
     39 #   test_apk: apk to run tests on.
     40 #   test_data: data folder in format destination:source.
     41 #   host_driven_root: The host-driven test root directory.
     42 #   annotation: Annotation of the tests to include.
     43 #   exclude_annotation: The annotation of the tests to exclude.
     44 I_TEST = collections.namedtuple('InstrumentationTest', [
     45     'name', 'apk', 'apk_package', 'test_apk', 'test_data', 'host_driven_root',
     46     'annotation', 'exclude_annotation', 'extra_flags'])
     47 
     48 
     49 def SrcPath(*path):
     50   return os.path.join(CHROME_SRC_DIR, *path)
     51 
     52 
     53 def I(name, apk, apk_package, test_apk, test_data, host_driven_root=None,
     54       annotation=None, exclude_annotation=None, extra_flags=None):
     55   return I_TEST(name, apk, apk_package, test_apk, test_data, host_driven_root,
     56                 annotation, exclude_annotation, extra_flags)
     57 
     58 INSTRUMENTATION_TESTS = dict((suite.name, suite) for suite in [
     59     I('ContentShell',
     60       'ContentShell.apk',
     61       'org.chromium.content_shell_apk',
     62       'ContentShellTest',
     63       'content:content/test/data/android/device_files'),
     64     I('ChromeShell',
     65       'ChromeShell.apk',
     66       'org.chromium.chrome.shell',
     67       'ChromeShellTest',
     68       'chrome:chrome/test/data/android/device_files',
     69       constants.CHROME_SHELL_HOST_DRIVEN_DIR),
     70     I('AndroidWebView',
     71       'AndroidWebView.apk',
     72       'org.chromium.android_webview.shell',
     73       'AndroidWebViewTest',
     74       'webview:android_webview/test/data/device_files'),
     75     ])
     76 
     77 VALID_TESTS = set(['chromedriver', 'gpu', 'mojo', 'telemetry_perf_unittests',
     78                    'ui', 'unit', 'webkit', 'webkit_layout', 'webrtc_chromium',
     79                    'webrtc_native'])
     80 
     81 RunCmd = bb_utils.RunCmd
     82 
     83 
     84 def _GetRevision(options):
     85   """Get the SVN revision number.
     86 
     87   Args:
     88     options: options object.
     89 
     90   Returns:
     91     The revision number.
     92   """
     93   revision = options.build_properties.get('got_revision')
     94   if not revision:
     95     revision = options.build_properties.get('revision', 'testing')
     96   return revision
     97 
     98 
     99 def RunTestSuites(options, suites, suites_options=None):
    100   """Manages an invocation of test_runner.py for gtests.
    101 
    102   Args:
    103     options: options object.
    104     suites: List of suite names to run.
    105     suites_options: Command line options dictionary for particular suites.
    106                     For example,
    107                     {'content_browsertests', ['--num_retries=1', '--release']}
    108                     will add the options only to content_browsertests.
    109   """
    110 
    111   if not suites_options:
    112     suites_options = {}
    113 
    114   args = ['--verbose']
    115   if options.target == 'Release':
    116     args.append('--release')
    117   if options.asan:
    118     args.append('--tool=asan')
    119   if options.gtest_filter:
    120     args.append('--gtest-filter=%s' % options.gtest_filter)
    121 
    122   for suite in suites:
    123     bb_annotations.PrintNamedStep(suite)
    124     cmd = ['build/android/test_runner.py', 'gtest', '-s', suite] + args
    125     cmd += suites_options.get(suite, [])
    126     if suite == 'content_browsertests':
    127       cmd.append('--num_retries=1')
    128     RunCmd(cmd)
    129 
    130 
    131 def RunChromeDriverTests(options):
    132   """Run all the steps for running chromedriver tests."""
    133   bb_annotations.PrintNamedStep('chromedriver_annotation')
    134   RunCmd(['chrome/test/chromedriver/run_buildbot_steps.py',
    135           '--android-packages=%s,%s,%s,%s' %
    136           ('chrome_shell',
    137            'chrome_stable',
    138            'chrome_beta',
    139            'chromedriver_webview_shell'),
    140           '--revision=%s' % _GetRevision(options),
    141           '--update-log'])
    142 
    143 
    144 def RunTelemetryPerfUnitTests(options):
    145   """Runs the telemetry perf unit tests.
    146 
    147   Args:
    148     options: options object.
    149   """
    150   InstallApk(options, INSTRUMENTATION_TESTS['ChromeShell'], False)
    151   args = ['--browser', 'android-chrome-shell']
    152   devices = android_commands.GetAttachedDevices()
    153   if devices:
    154     args = args + ['--device', devices[0]]
    155   bb_annotations.PrintNamedStep('telemetry_perf_unittests')
    156   RunCmd(['tools/perf/run_tests'] + args)
    157 
    158 
    159 def RunMojoTests(options):
    160   """Runs the mojo unit tests.
    161 
    162   Args:
    163     options: options object.
    164   """
    165   test = I('MojoTest',
    166            None,
    167            'org.chromium.mojo.tests',
    168            'MojoTest',
    169            None)
    170   RunInstrumentationSuite(options, test)
    171 
    172 
    173 def InstallApk(options, test, print_step=False):
    174   """Install an apk to all phones.
    175 
    176   Args:
    177     options: options object
    178     test: An I_TEST namedtuple
    179     print_step: Print a buildbot step
    180   """
    181   if print_step:
    182     bb_annotations.PrintNamedStep('install_%s' % test.name.lower())
    183 
    184   args = ['--apk_package', test.apk_package]
    185   if options.target == 'Release':
    186     args.append('--release')
    187   args.append(test.apk)
    188 
    189   RunCmd(['build/android/adb_install_apk.py'] + args, halt_on_failure=True)
    190 
    191 
    192 def RunInstrumentationSuite(options, test, flunk_on_failure=True,
    193                             python_only=False, official_build=False):
    194   """Manages an invocation of test_runner.py for instrumentation tests.
    195 
    196   Args:
    197     options: options object
    198     test: An I_TEST namedtuple
    199     flunk_on_failure: Flunk the step if tests fail.
    200     Python: Run only host driven Python tests.
    201     official_build: Run official-build tests.
    202   """
    203   bb_annotations.PrintNamedStep('%s_instrumentation_tests' % test.name.lower())
    204 
    205   if test.apk:
    206     InstallApk(options, test)
    207   args = ['--test-apk', test.test_apk, '--verbose']
    208   if test.test_data:
    209     args.extend(['--test_data', test.test_data])
    210   if options.target == 'Release':
    211     args.append('--release')
    212   if options.asan:
    213     args.append('--tool=asan')
    214   if options.flakiness_server:
    215     args.append('--flakiness-dashboard-server=%s' %
    216                 options.flakiness_server)
    217   if options.coverage_bucket:
    218     args.append('--coverage-dir=%s' % options.coverage_dir)
    219   if test.host_driven_root:
    220     args.append('--host-driven-root=%s' % test.host_driven_root)
    221   if test.annotation:
    222     args.extend(['-A', test.annotation])
    223   if test.exclude_annotation:
    224     args.extend(['-E', test.exclude_annotation])
    225   if test.extra_flags:
    226     args.extend(test.extra_flags)
    227   if python_only:
    228     args.append('-p')
    229   if official_build:
    230     # The option needs to be assigned 'True' as it does not have an action
    231     # associated with it.
    232     args.append('--official-build')
    233 
    234   RunCmd(['build/android/test_runner.py', 'instrumentation'] + args,
    235          flunk_on_failure=flunk_on_failure)
    236 
    237 
    238 def RunWebkitLint(target):
    239   """Lint WebKit's TestExpectation files."""
    240   bb_annotations.PrintNamedStep('webkit_lint')
    241   RunCmd([SrcPath('webkit/tools/layout_tests/run_webkit_tests.py'),
    242           '--lint-test-files',
    243           '--chromium',
    244           '--target', target])
    245 
    246 
    247 def RunWebkitLayoutTests(options):
    248   """Run layout tests on an actual device."""
    249   bb_annotations.PrintNamedStep('webkit_tests')
    250   cmd_args = [
    251       '--no-show-results',
    252       '--no-new-test-results',
    253       '--full-results-html',
    254       '--clobber-old-results',
    255       '--exit-after-n-failures', '5000',
    256       '--exit-after-n-crashes-or-timeouts', '100',
    257       '--debug-rwt-logging',
    258       '--results-directory', '../layout-test-results',
    259       '--target', options.target,
    260       '--builder-name', options.build_properties.get('buildername', ''),
    261       '--build-number', str(options.build_properties.get('buildnumber', '')),
    262       '--master-name', 'ChromiumWebkit',  # TODO: Get this from the cfg.
    263       '--build-name', options.build_properties.get('buildername', ''),
    264       '--platform=android']
    265 
    266   for flag in 'test_results_server', 'driver_name', 'additional_drt_flag':
    267     if flag in options.factory_properties:
    268       cmd_args.extend(['--%s' % flag.replace('_', '-'),
    269                        options.factory_properties.get(flag)])
    270 
    271   for f in options.factory_properties.get('additional_expectations', []):
    272     cmd_args.extend(
    273         ['--additional-expectations=%s' % os.path.join(CHROME_SRC_DIR, *f)])
    274 
    275   # TODO(dpranke): Remove this block after
    276   # https://codereview.chromium.org/12927002/ lands.
    277   for f in options.factory_properties.get('additional_expectations_files', []):
    278     cmd_args.extend(
    279         ['--additional-expectations=%s' % os.path.join(CHROME_SRC_DIR, *f)])
    280 
    281   exit_code = RunCmd([SrcPath('webkit/tools/layout_tests/run_webkit_tests.py')]
    282                      + cmd_args)
    283   if exit_code == 255: # test_run_results.UNEXPECTED_ERROR_EXIT_STATUS
    284     bb_annotations.PrintMsg('?? (crashed or hung)')
    285   elif exit_code == 254: # test_run_results.NO_DEVICES_EXIT_STATUS
    286     bb_annotations.PrintMsg('?? (no devices found)')
    287   elif exit_code == 253: # test_run_results.NO_TESTS_EXIT_STATUS
    288     bb_annotations.PrintMsg('?? (no tests found)')
    289   else:
    290     full_results_path = os.path.join('..', 'layout-test-results',
    291                                      'full_results.json')
    292     if os.path.exists(full_results_path):
    293       full_results = json.load(open(full_results_path))
    294       unexpected_passes, unexpected_failures, unexpected_flakes = (
    295           _ParseLayoutTestResults(full_results))
    296       if unexpected_failures:
    297         _PrintDashboardLink('failed', unexpected_failures,
    298                             max_tests=25)
    299       elif unexpected_passes:
    300         _PrintDashboardLink('unexpected passes', unexpected_passes,
    301                             max_tests=10)
    302       if unexpected_flakes:
    303         _PrintDashboardLink('unexpected flakes', unexpected_flakes,
    304                             max_tests=10)
    305 
    306       if exit_code == 0 and (unexpected_passes or unexpected_flakes):
    307         # If exit_code != 0, RunCmd() will have already printed an error.
    308         bb_annotations.PrintWarning()
    309     else:
    310       bb_annotations.PrintError()
    311       bb_annotations.PrintMsg('?? (results missing)')
    312 
    313   if options.factory_properties.get('archive_webkit_results', False):
    314     bb_annotations.PrintNamedStep('archive_webkit_results')
    315     base = 'https://storage.googleapis.com/chromium-layout-test-archives'
    316     builder_name = options.build_properties.get('buildername', '')
    317     build_number = str(options.build_properties.get('buildnumber', ''))
    318     results_link = '%s/%s/%s/layout-test-results/results.html' % (
    319         base, EscapeBuilderName(builder_name), build_number)
    320     bb_annotations.PrintLink('results', results_link)
    321     bb_annotations.PrintLink('(zip)', '%s/%s/%s/layout-test-results.zip' % (
    322         base, EscapeBuilderName(builder_name), build_number))
    323     gs_bucket = 'gs://chromium-layout-test-archives'
    324     RunCmd([os.path.join(SLAVE_SCRIPTS_DIR, 'chromium',
    325                          'archive_layout_test_results.py'),
    326             '--results-dir', '../../layout-test-results',
    327             '--build-number', build_number,
    328             '--builder-name', builder_name,
    329             '--gs-bucket', gs_bucket],
    330             cwd=DIR_BUILD_ROOT)
    331 
    332 
    333 def _ParseLayoutTestResults(results):
    334   """Extract the failures from the test run."""
    335   # Cloned from third_party/WebKit/Tools/Scripts/print-json-test-results
    336   tests = _ConvertTrieToFlatPaths(results['tests'])
    337   failures = {}
    338   flakes = {}
    339   passes = {}
    340   for (test, result) in tests.iteritems():
    341     if result.get('is_unexpected'):
    342       actual_results = result['actual'].split()
    343       expected_results = result['expected'].split()
    344       if len(actual_results) > 1:
    345         # We report the first failure type back, even if the second
    346         # was more severe.
    347         if actual_results[1] in expected_results:
    348           flakes[test] = actual_results[0]
    349         else:
    350           failures[test] = actual_results[0]
    351       elif actual_results[0] == 'PASS':
    352         passes[test] = result
    353       else:
    354         failures[test] = actual_results[0]
    355 
    356   return (passes, failures, flakes)
    357 
    358 
    359 def _ConvertTrieToFlatPaths(trie, prefix=None):
    360   """Flatten the trie of failures into a list."""
    361   # Cloned from third_party/WebKit/Tools/Scripts/print-json-test-results
    362   result = {}
    363   for name, data in trie.iteritems():
    364     if prefix:
    365       name = prefix + '/' + name
    366 
    367     if len(data) and 'actual' not in data and 'expected' not in data:
    368       result.update(_ConvertTrieToFlatPaths(data, name))
    369     else:
    370       result[name] = data
    371 
    372   return result
    373 
    374 
    375 def _PrintDashboardLink(link_text, tests, max_tests):
    376   """Add a link to the flakiness dashboard in the step annotations."""
    377   if len(tests) > max_tests:
    378     test_list_text = ' '.join(tests[:max_tests]) + ' and more'
    379   else:
    380     test_list_text = ' '.join(tests)
    381 
    382   dashboard_base = ('http://test-results.appspot.com'
    383                     '/dashboards/flakiness_dashboard.html#'
    384                     'master=ChromiumWebkit&tests=')
    385 
    386   bb_annotations.PrintLink('%d %s: %s' %
    387                            (len(tests), link_text, test_list_text),
    388                            dashboard_base + ','.join(tests))
    389 
    390 
    391 def EscapeBuilderName(builder_name):
    392   return re.sub('[ ()]', '_', builder_name)
    393 
    394 
    395 def SpawnLogcatMonitor():
    396   shutil.rmtree(LOGCAT_DIR, ignore_errors=True)
    397   bb_utils.SpawnCmd([
    398       os.path.join(CHROME_SRC_DIR, 'build', 'android', 'adb_logcat_monitor.py'),
    399       LOGCAT_DIR])
    400 
    401   # Wait for logcat_monitor to pull existing logcat
    402   RunCmd(['sleep', '5'])
    403 
    404 
    405 def ProvisionDevices(options):
    406   bb_annotations.PrintNamedStep('provision_devices')
    407 
    408   if not bb_utils.TESTING:
    409     # Restart adb to work around bugs, sleep to wait for usb discovery.
    410     device_utils.RestartServer()
    411     RunCmd(['sleep', '1'])
    412   provision_cmd = ['build/android/provision_devices.py', '-t', options.target]
    413   if options.auto_reconnect:
    414     provision_cmd.append('--auto-reconnect')
    415   if options.skip_wipe:
    416     provision_cmd.append('--skip-wipe')
    417   RunCmd(provision_cmd)
    418 
    419 
    420 def DeviceStatusCheck(options):
    421   bb_annotations.PrintNamedStep('device_status_check')
    422   cmd = ['build/android/buildbot/bb_device_status_check.py']
    423   if options.restart_usb:
    424     cmd.append('--restart-usb')
    425   RunCmd(cmd, halt_on_failure=True)
    426 
    427 
    428 def GetDeviceSetupStepCmds():
    429   return [
    430       ('device_status_check', DeviceStatusCheck),
    431       ('provision_devices', ProvisionDevices),
    432   ]
    433 
    434 
    435 def RunUnitTests(options):
    436   suites = gtest_config.STABLE_TEST_SUITES
    437   if options.asan:
    438     suites = [s for s in suites
    439               if s not in gtest_config.ASAN_EXCLUDED_TEST_SUITES]
    440   RunTestSuites(options, suites)
    441 
    442 
    443 def RunInstrumentationTests(options):
    444   for test in INSTRUMENTATION_TESTS.itervalues():
    445     RunInstrumentationSuite(options, test)
    446 
    447 
    448 def RunWebkitTests(options):
    449   RunTestSuites(options, ['webkit_unit_tests', 'blink_heap_unittests'])
    450   RunWebkitLint(options.target)
    451 
    452 
    453 def RunWebRTCChromiumTests(options):
    454   RunTestSuites(options, gtest_config.WEBRTC_CHROMIUM_TEST_SUITES)
    455 
    456 
    457 def RunWebRTCNativeTests(options):
    458   RunTestSuites(options, gtest_config.WEBRTC_NATIVE_TEST_SUITES)
    459 
    460 
    461 def RunGPUTests(options):
    462   revision = _GetRevision(options)
    463   builder_name = options.build_properties.get('buildername', 'noname')
    464 
    465   bb_annotations.PrintNamedStep('pixel_tests')
    466   RunCmd(['content/test/gpu/run_gpu_test.py',
    467           'pixel',
    468           '--browser',
    469           'android-content-shell',
    470           '--build-revision',
    471           str(revision),
    472           '--upload-refimg-to-cloud-storage',
    473           '--refimg-cloud-storage-bucket',
    474           'chromium-gpu-archive/reference-images',
    475           '--os-type',
    476           'android',
    477           '--test-machine-name',
    478           EscapeBuilderName(builder_name)])
    479 
    480   bb_annotations.PrintNamedStep('webgl_conformance_tests')
    481   RunCmd(['content/test/gpu/run_gpu_test.py',
    482           '--browser=android-content-shell', 'webgl_conformance',
    483           '--webgl-conformance-version=1.0.1'])
    484 
    485   bb_annotations.PrintNamedStep('gpu_rasterization_tests')
    486   RunCmd(['content/test/gpu/run_gpu_test.py',
    487           'gpu_rasterization',
    488           '--browser',
    489           'android-content-shell',
    490           '--build-revision',
    491           str(revision),
    492           '--test-machine-name',
    493           EscapeBuilderName(builder_name)])
    494 
    495 
    496 def GetTestStepCmds():
    497   return [
    498       ('chromedriver', RunChromeDriverTests),
    499       ('gpu', RunGPUTests),
    500       ('mojo', RunMojoTests),
    501       ('telemetry_perf_unittests', RunTelemetryPerfUnitTests),
    502       ('unit', RunUnitTests),
    503       ('ui', RunInstrumentationTests),
    504       ('webkit', RunWebkitTests),
    505       ('webkit_layout', RunWebkitLayoutTests),
    506       ('webrtc_chromium', RunWebRTCChromiumTests),
    507       ('webrtc_native', RunWebRTCNativeTests),
    508   ]
    509 
    510 
    511 def MakeGSPath(options, gs_base_dir):
    512   revision = _GetRevision(options)
    513   bot_id = options.build_properties.get('buildername', 'testing')
    514   randhash = hashlib.sha1(str(random.random())).hexdigest()
    515   gs_path = '%s/%s/%s/%s' % (gs_base_dir, bot_id, revision, randhash)
    516   # remove double slashes, happens with blank revisions and confuses gsutil
    517   gs_path = re.sub('/+', '/', gs_path)
    518   return gs_path
    519 
    520 def UploadHTML(options, gs_base_dir, dir_to_upload, link_text,
    521                link_rel_path='index.html', gs_url=GS_URL):
    522   """Uploads directory at |dir_to_upload| to Google Storage and output a link.
    523 
    524   Args:
    525     options: Command line options.
    526     gs_base_dir: The Google Storage base directory (e.g.
    527       'chromium-code-coverage/java')
    528     dir_to_upload: Absolute path to the directory to be uploaded.
    529     link_text: Link text to be displayed on the step.
    530     link_rel_path: Link path relative to |dir_to_upload|.
    531     gs_url: Google storage URL.
    532   """
    533   gs_path = MakeGSPath(options, gs_base_dir)
    534   RunCmd([bb_utils.GSUTIL_PATH, 'cp', '-R', dir_to_upload, 'gs://%s' % gs_path])
    535   bb_annotations.PrintLink(link_text,
    536                            '%s/%s/%s' % (gs_url, gs_path, link_rel_path))
    537 
    538 
    539 def GenerateJavaCoverageReport(options):
    540   """Generates an HTML coverage report using EMMA and uploads it."""
    541   bb_annotations.PrintNamedStep('java_coverage_report')
    542 
    543   coverage_html = os.path.join(options.coverage_dir, 'coverage_html')
    544   RunCmd(['build/android/generate_emma_html.py',
    545           '--coverage-dir', options.coverage_dir,
    546           '--metadata-dir', os.path.join(CHROME_OUT_DIR, options.target),
    547           '--cleanup',
    548           '--output', os.path.join(coverage_html, 'index.html')])
    549   return coverage_html
    550 
    551 
    552 def LogcatDump(options):
    553   # Print logcat, kill logcat monitor
    554   bb_annotations.PrintNamedStep('logcat_dump')
    555   logcat_file = os.path.join(CHROME_OUT_DIR, options.target, 'full_log.txt')
    556   RunCmd([SrcPath('build' , 'android', 'adb_logcat_printer.py'),
    557           '--output-path', logcat_file, LOGCAT_DIR])
    558   gs_path = MakeGSPath(options, 'chromium-android/logcat_dumps')
    559   RunCmd([bb_utils.GSUTIL_PATH, 'cp', '-z', 'txt', logcat_file,
    560           'gs://%s' % gs_path])
    561   bb_annotations.PrintLink('logcat dump', '%s/%s' % (GS_AUTH_URL, gs_path))
    562 
    563 
    564 def RunStackToolSteps(options):
    565   """Run stack tool steps.
    566 
    567   Stack tool is run for logcat dump, optionally for ASAN.
    568   """
    569   bb_annotations.PrintNamedStep('Run stack tool with logcat dump')
    570   logcat_file = os.path.join(CHROME_OUT_DIR, options.target, 'full_log.txt')
    571   RunCmd([os.path.join(CHROME_SRC_DIR, 'third_party', 'android_platform',
    572           'development', 'scripts', 'stack'),
    573           '--more-info', logcat_file])
    574   if options.asan_symbolize:
    575     bb_annotations.PrintNamedStep('Run stack tool for ASAN')
    576     RunCmd([
    577         os.path.join(CHROME_SRC_DIR, 'build', 'android', 'asan_symbolize.py'),
    578         '-l', logcat_file])
    579 
    580 
    581 def GenerateTestReport(options):
    582   bb_annotations.PrintNamedStep('test_report')
    583   for report in glob.glob(
    584       os.path.join(CHROME_OUT_DIR, options.target, 'test_logs', '*.log')):
    585     RunCmd(['cat', report])
    586     os.remove(report)
    587 
    588 
    589 def MainTestWrapper(options):
    590   try:
    591     # Spawn logcat monitor
    592     SpawnLogcatMonitor()
    593 
    594     # Run all device setup steps
    595     for _, cmd in GetDeviceSetupStepCmds():
    596       cmd(options)
    597 
    598     if options.install:
    599       test_obj = INSTRUMENTATION_TESTS[options.install]
    600       InstallApk(options, test_obj, print_step=True)
    601 
    602     if options.test_filter:
    603       bb_utils.RunSteps(options.test_filter, GetTestStepCmds(), options)
    604 
    605     if options.coverage_bucket:
    606       coverage_html = GenerateJavaCoverageReport(options)
    607       UploadHTML(options, '%s/java' % options.coverage_bucket, coverage_html,
    608                  'Coverage Report')
    609       shutil.rmtree(coverage_html, ignore_errors=True)
    610 
    611     if options.experimental:
    612       RunTestSuites(options, gtest_config.EXPERIMENTAL_TEST_SUITES)
    613 
    614   finally:
    615     # Run all post test steps
    616     LogcatDump(options)
    617     if not options.disable_stack_tool:
    618       RunStackToolSteps(options)
    619     GenerateTestReport(options)
    620     # KillHostHeartbeat() has logic to check if heartbeat process is running,
    621     # and kills only if it finds the process is running on the host.
    622     provision_devices.KillHostHeartbeat()
    623 
    624 
    625 def GetDeviceStepsOptParser():
    626   parser = bb_utils.GetParser()
    627   parser.add_option('--experimental', action='store_true',
    628                     help='Run experiemental tests')
    629   parser.add_option('-f', '--test-filter', metavar='<filter>', default=[],
    630                     action='append',
    631                     help=('Run a test suite. Test suites: "%s"' %
    632                           '", "'.join(VALID_TESTS)))
    633   parser.add_option('--gtest-filter',
    634                     help='Filter for running a subset of tests of a gtest test')
    635   parser.add_option('--asan', action='store_true', help='Run tests with asan.')
    636   parser.add_option('--install', metavar='<apk name>',
    637                     help='Install an apk by name')
    638   parser.add_option('--no-reboot', action='store_true',
    639                     help='Do not reboot devices during provisioning.')
    640   parser.add_option('--coverage-bucket',
    641                     help=('Bucket name to store coverage results. Coverage is '
    642                           'only run if this is set.'))
    643   parser.add_option('--restart-usb', action='store_true',
    644                     help='Restart usb ports before device status check.')
    645   parser.add_option(
    646       '--flakiness-server',
    647       help=('The flakiness dashboard server to which the results should be '
    648             'uploaded.'))
    649   parser.add_option(
    650       '--auto-reconnect', action='store_true',
    651       help='Push script to device which restarts adbd on disconnections.')
    652   parser.add_option('--skip-wipe', action='store_true',
    653                     help='Do not wipe devices during provisioning.')
    654   parser.add_option(
    655       '--logcat-dump-output',
    656       help='The logcat dump output will be "tee"-ed into this file')
    657   # During processing perf bisects, a seperate working directory created under
    658   # which builds are produced. Therefore we should look for relevent output
    659   # file under this directory.(/b/build/slave/<slave_name>/build/bisect/src/out)
    660   parser.add_option(
    661       '--chrome-output-dir',
    662       help='Chrome output directory to be used while bisecting.')
    663 
    664   parser.add_option('--disable-stack-tool',  action='store_true',
    665       help='Do not run stack tool.')
    666   parser.add_option('--asan-symbolize',  action='store_true',
    667       help='Run stack tool for ASAN')
    668   return parser
    669 
    670 
    671 def main(argv):
    672   parser = GetDeviceStepsOptParser()
    673   options, args = parser.parse_args(argv[1:])
    674 
    675   if args:
    676     return sys.exit('Unused args %s' % args)
    677 
    678   unknown_tests = set(options.test_filter) - VALID_TESTS
    679   if unknown_tests:
    680     return sys.exit('Unknown tests %s' % list(unknown_tests))
    681 
    682   setattr(options, 'target', options.factory_properties.get('target', 'Debug'))
    683 
    684   if options.chrome_output_dir:
    685     global CHROME_OUT_DIR
    686     global LOGCAT_DIR
    687     CHROME_OUT_DIR = options.chrome_output_dir
    688     LOGCAT_DIR = os.path.join(CHROME_OUT_DIR, 'logcat')
    689 
    690   if options.coverage_bucket:
    691     setattr(options, 'coverage_dir',
    692             os.path.join(CHROME_OUT_DIR, options.target, 'coverage'))
    693 
    694   MainTestWrapper(options)
    695 
    696 
    697 if __name__ == '__main__':
    698   sys.exit(main(sys.argv))
    699