tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

instrumentation_test_instance.py (41637B)


      1 # Copyright 2015 The Chromium Authors
      2 # Use of this source code is governed by a BSD-style license that can be
      3 # found in the LICENSE file.
      4 
      5 
      6 import copy
      7 import logging
      8 import os
      9 import re
     10 
     11 from devil.android import apk_helper
     12 from pylib import constants
     13 from pylib.base import base_test_result
     14 from pylib.base import test_exception
     15 from pylib.base import test_instance
     16 from pylib.constants import host_paths
     17 from pylib.instrumentation import instrumentation_parser
     18 from pylib.instrumentation import test_result
     19 from pylib.symbols import deobfuscator
     20 from pylib.symbols import stack_symbolizer
     21 from pylib.utils import dexdump
     22 from pylib.utils import gold_utils
     23 from pylib.utils import test_filter
     24 
     25 with host_paths.SysPath(host_paths.BUILD_UTIL_PATH):
     26  from lib.common import unittest_util
     27 
     28 # Ref: http://developer.android.com/reference/android/app/Activity.html
     29 _ACTIVITY_RESULT_CANCELED = 0
     30 _ACTIVITY_RESULT_OK = -1
     31 
     32 _COMMAND_LINE_PARAMETER = 'cmdlinearg-parameter'
     33 _DEFAULT_ANNOTATIONS = [
     34    'SmallTest', 'MediumTest', 'LargeTest', 'EnormousTest', 'IntegrationTest']
     35 # This annotation is for disabled tests that should not be run in Test Reviver.
     36 _DO_NOT_REVIVE_ANNOTATIONS = ['DoNotRevive', 'Manual']
     37 _EXCLUDE_UNLESS_REQUESTED_ANNOTATIONS = [
     38    'DisabledTest', 'FlakyTest', 'Manual']
     39 _VALID_ANNOTATIONS = set(_DEFAULT_ANNOTATIONS + _DO_NOT_REVIVE_ANNOTATIONS +
     40                         _EXCLUDE_UNLESS_REQUESTED_ANNOTATIONS)
     41 
     42 _BASE_INSTRUMENTATION_CLASS_NAME = (
     43    'org.chromium.base.test.BaseChromiumAndroidJUnitRunner')
     44 
     45 _SKIP_PARAMETERIZATION = 'SkipCommandLineParameterization'
     46 _PARAMETERIZED_COMMAND_LINE_FLAGS = 'ParameterizedCommandLineFlags'
     47 _PARAMETERIZED_COMMAND_LINE_FLAGS_SWITCHES = (
     48    'ParameterizedCommandLineFlags$Switches')
     49 _NATIVE_CRASH_RE = re.compile('(process|native) crash', re.IGNORECASE)
     50 
     51 # The ID of the bundle value Instrumentation uses to report which test index the
     52 # results are for in a collection of tests. Note that this index is 1-based.
     53 _BUNDLE_CURRENT_ID = 'current'
     54 # The ID of the bundle value Instrumentation uses to report the test class.
     55 _BUNDLE_CLASS_ID = 'class'
     56 # The ID of the bundle value Instrumentation uses to report the test name.
     57 _BUNDLE_TEST_ID = 'test'
     58 # The ID of the bundle value Instrumentation uses to report the crash stack, if
     59 # the test crashed.
     60 _BUNDLE_STACK_ID = 'stack'
     61 
     62 # The ID of the bundle value Chrome uses to report the test duration.
     63 _BUNDLE_DURATION_ID = 'duration_ms'
     64 
     65 # The following error messages are too general to be useful in failure
     66 # clustering. The runner doesn't report failure reason when such failure
     67 # reason is parsed from test logs.
     68 _BANNED_FAILURE_REASONS = [
     69    # Default error message from org.chromium.base.test.util.CallbackHelper
     70    # when timeout at expecting call back.
     71    'java.util.concurrent.TimeoutException: waitForCallback timed out!',
     72 ]
     73 
     74 
     75 class MissingSizeAnnotationError(test_exception.TestException):
     76  def __init__(self, class_name):
     77    super().__init__(
     78        class_name +
     79        ': Test method is missing required size annotation. Add one of: ' +
     80        ', '.join('@' + a for a in _VALID_ANNOTATIONS))
     81 
     82 
     83 class CommandLineParameterizationException(test_exception.TestException):
     84  pass
     85 
     86 
     87 def GenerateTestResults(result_code, result_bundle, statuses, duration_ms,
     88                        device_abi, symbolizer):
     89  """Generate test results from |statuses|.
     90 
     91  Args:
     92    result_code: The overall status code as an integer.
     93    result_bundle: The summary bundle dump as a dict.
     94    statuses: A list of 2-tuples containing:
     95      - the status code as an integer
     96      - the bundle dump as a dict mapping string keys to string values
     97      Note that this is the same as the third item in the 3-tuple returned by
     98      |_ParseAmInstrumentRawOutput|.
     99    duration_ms: The duration of the test in milliseconds.
    100    device_abi: The device_abi, which is needed for symbolization.
    101    symbolizer: The symbolizer used to symbolize stack.
    102 
    103  Returns:
    104    A list containing an instance of InstrumentationTestResult for each test
    105    parsed.
    106  """
    107 
    108  results = []
    109  # Results from synthetic ClassName#null tests, which occur from exceptions in
    110  # @BeforeClass / @AfterClass.
    111  class_failure_results = []
    112 
    113  def add_result(result):
    114    if result.GetName().endswith('#null'):
    115      if result.GetType() == base_test_result.ResultType.UNKNOWN:
    116        # Not sure how this happens. http://crbug.com/397924393
    117        # It presumably means two START events happen in a row.
    118        pass
    119      else:
    120        assert result.GetType() == base_test_result.ResultType.FAIL, (
    121            'test result of %r should be %r, but got %r' %
    122            (result.GetName(), base_test_result.ResultType.FAIL,
    123             result.GetType()))
    124        class_failure_results.append(result)
    125    else:
    126      results.append(result)
    127 
    128  current_result = None
    129  cumulative_duration = 0
    130 
    131  for status_code, bundle in statuses:
    132    # If the last test was a failure already, don't override that failure with
    133    # post-test failures that could be caused by the original failure.
    134    if (status_code == instrumentation_parser.STATUS_CODE_BATCH_FAILURE
    135        and current_result.GetType() != base_test_result.ResultType.FAIL):
    136      current_result.SetType(base_test_result.ResultType.FAIL)
    137      _MaybeSetLog(bundle, current_result, symbolizer, device_abi)
    138      continue
    139 
    140    if status_code == instrumentation_parser.STATUS_CODE_TEST_DURATION:
    141      # For the first result, duration will be set below to the difference
    142      # between the reported and actual durations to account for overhead like
    143      # starting instrumentation.
    144      if results:
    145        current_duration = int(bundle.get(_BUNDLE_DURATION_ID, duration_ms))
    146        current_result.SetDuration(current_duration)
    147        cumulative_duration += current_duration
    148      continue
    149 
    150    test_class = bundle.get(_BUNDLE_CLASS_ID, '')
    151    test_method = bundle.get(_BUNDLE_TEST_ID, '')
    152    if test_class and test_method:
    153      test_name = '%s#%s' % (test_class, test_method)
    154    else:
    155      continue
    156 
    157    if status_code == instrumentation_parser.STATUS_CODE_START:
    158      if current_result:
    159        add_result(current_result)
    160      current_result = test_result.InstrumentationTestResult(
    161          test_name, base_test_result.ResultType.UNKNOWN, duration_ms)
    162    else:
    163      if status_code == instrumentation_parser.STATUS_CODE_OK:
    164        if current_result.GetType() == base_test_result.ResultType.UNKNOWN:
    165          current_result.SetType(base_test_result.ResultType.PASS)
    166      elif status_code == instrumentation_parser.STATUS_CODE_SKIP:
    167        current_result.SetType(base_test_result.ResultType.SKIP)
    168      elif status_code == instrumentation_parser.STATUS_CODE_ASSUMPTION_FAILURE:
    169        current_result.SetType(base_test_result.ResultType.SKIP)
    170      else:
    171        if status_code not in (instrumentation_parser.STATUS_CODE_ERROR,
    172                               instrumentation_parser.STATUS_CODE_FAILURE):
    173          logging.error('Unrecognized status code %d. Handling as an error.',
    174                        status_code)
    175        current_result.SetType(base_test_result.ResultType.FAIL)
    176    _MaybeSetLog(bundle, current_result, symbolizer, device_abi)
    177 
    178  if current_result:
    179    if current_result.GetType() == base_test_result.ResultType.UNKNOWN:
    180      crashed = (result_code == _ACTIVITY_RESULT_CANCELED and any(
    181          _NATIVE_CRASH_RE.search(l) for l in result_bundle.values()))
    182      if crashed:
    183        current_result.SetType(base_test_result.ResultType.CRASH)
    184 
    185    add_result(current_result)
    186 
    187  if results:
    188    logging.info('Adding cumulative overhead to test %s: %dms',
    189                 results[0].GetName(), duration_ms - cumulative_duration)
    190    results[0].SetDuration(duration_ms - cumulative_duration)
    191 
    192  # Copy failures from @BeforeClass / @AfterClass into all tests that are
    193  # marked as passing.
    194  for class_result in class_failure_results:
    195    prefix = class_result.GetName()[:-len('null')]
    196    for result in results:
    197      if (result.GetName().startswith(prefix)
    198          and result.GetType() == base_test_result.ResultType.PASS):
    199        result.SetType(base_test_result.ResultType.FAIL)
    200        result.SetLog(class_result.GetLog())
    201        result.SetFailureReason(class_result.GetFailureReason())
    202 
    203  return results
    204 
    205 
    206 def _MaybeSetLog(bundle, current_result, symbolizer, device_abi):
    207  if _BUNDLE_STACK_ID in bundle:
    208    stack = bundle[_BUNDLE_STACK_ID]
    209    if symbolizer and device_abi:
    210      current_result.SetLog('%s\n%s' % (stack, '\n'.join(
    211          symbolizer.ExtractAndResolveNativeStackTraces(stack, device_abi))))
    212    else:
    213      current_result.SetLog(stack)
    214 
    215    parsed_failure_reason = _ParseExceptionMessage(stack)
    216    if parsed_failure_reason not in _BANNED_FAILURE_REASONS:
    217      current_result.SetFailureReason(parsed_failure_reason)
    218 
    219 
    220 def _ParseExceptionMessage(stack):
    221  """Extracts the exception message from the given stack trace.
    222  """
    223  # This interprets stack traces reported via InstrumentationResultPrinter:
    224  # https://source.chromium.org/chromium/chromium/src/+/main:third_party/android_support_test_runner/runner/src/main/java/android/support/test/internal/runner/listener/InstrumentationResultPrinter.java;l=181?q=InstrumentationResultPrinter&type=cs
    225  # This is a standard Java stack trace, of the form:
    226  # <Result of Exception.toString()>
    227  #     at SomeClass.SomeMethod(...)
    228  #     at ...
    229  lines = stack.split('\n')
    230  for i, line in enumerate(lines):
    231    if line.startswith('\tat'):
    232      return '\n'.join(lines[0:i])
    233  # No call stack found, so assume everything is the exception message.
    234  return stack
    235 
    236 
    237 def FilterTests(tests,
    238                filter_strs=None,
    239                annotations=None,
    240                excluded_annotations=None):
    241  """Filter a list of tests
    242 
    243  Args:
    244    tests: a list of tests. e.g. [
    245           {'annotations": {}, 'class': 'com.example.TestA', 'method':'test1'},
    246           {'annotations": {}, 'class': 'com.example.TestB', 'method':'test2'}]
    247    filter_strs: list of googletest-style filter string.
    248    annotations: a dict of wanted annotations for test methods.
    249    excluded_annotations: a dict of annotations to exclude.
    250 
    251  Return:
    252    A list of filtered tests
    253  """
    254 
    255  def test_names_from_pattern(combined_pattern, test_names):
    256    patterns = combined_pattern.split(':')
    257 
    258    hashable_patterns = set()
    259    filename_patterns = []
    260    for pattern in patterns:
    261      if ('*' in pattern or '?' in pattern or '[' in pattern):
    262        filename_patterns.append(pattern)
    263      else:
    264        hashable_patterns.add(pattern)
    265 
    266    filter_test_names = set(
    267        unittest_util.FilterTestNames(test_names, ':'.join(
    268            filename_patterns))) if len(filename_patterns) > 0 else set()
    269 
    270    for test_name in test_names:
    271      if test_name in hashable_patterns:
    272        filter_test_names.add(test_name)
    273 
    274    return filter_test_names
    275 
    276  def get_test_names(test):
    277    test_names = set()
    278    # Allow fully-qualified name as well as an omitted package.
    279    unqualified_class_test = {
    280        'class': test['class'].split('.')[-1],
    281        'method': test['method']
    282    }
    283 
    284    test_name = GetTestName(test, sep='.')
    285    test_names.add(test_name)
    286 
    287    unqualified_class_test_name = GetTestName(unqualified_class_test, sep='.')
    288    test_names.add(unqualified_class_test_name)
    289 
    290    unique_test_name = GetUniqueTestName(test, sep='.')
    291    test_names.add(unique_test_name)
    292 
    293    junit4_test_name = GetTestNameWithoutParameterSuffix(test, sep='.')
    294    test_names.add(junit4_test_name)
    295 
    296    unqualified_junit4_test_name = GetTestNameWithoutParameterSuffix(
    297        unqualified_class_test, sep='.')
    298    test_names.add(unqualified_junit4_test_name)
    299    return test_names
    300 
    301  def get_tests_from_names(tests, test_names, tests_to_names):
    302    ''' Returns the tests for which the given names apply
    303 
    304    Args:
    305      tests: a list of tests. e.g. [
    306            {'annotations": {}, 'class': 'com.example.TestA', 'method':'test1'},
    307            {'annotations": {}, 'class': 'com.example.TestB', 'method':'test2'}]
    308      test_names: a collection of names determining tests to return.
    309 
    310    Return:
    311      A list of tests that match the given test names
    312    '''
    313    filtered_tests = []
    314    for t in tests:
    315      current_test_names = tests_to_names[id(t)]
    316 
    317      for current_test_name in current_test_names:
    318        if current_test_name in test_names:
    319          filtered_tests.append(t)
    320          break
    321 
    322    return filtered_tests
    323 
    324  def remove_tests_from_names(tests, remove_test_names, tests_to_names):
    325    ''' Returns the tests from the given list with given names removed
    326 
    327    Args:
    328      tests: a list of tests. e.g. [
    329            {'annotations": {}, 'class': 'com.example.TestA', 'method':'test1'},
    330            {'annotations": {}, 'class': 'com.example.TestB', 'method':'test2'}]
    331      remove_test_names: a collection of names determining tests to remove.
    332      tests_to_names: a dcitionary of test ids to a collection of applicable
    333            names for that test
    334 
    335    Return:
    336      A list of tests that don't match the given test names
    337    '''
    338    filtered_tests = []
    339 
    340    for t in tests:
    341      for name in tests_to_names[id(t)]:
    342        if name in remove_test_names:
    343          break
    344      else:
    345        filtered_tests.append(t)
    346    return filtered_tests
    347 
    348  def gtests_filter(tests, combined_filters):
    349    ''' Returns the tests after the combined_filters have been applied
    350 
    351    Args:
    352      tests: a list of tests. e.g. [
    353            {'annotations": {}, 'class': 'com.example.TestA', 'method':'test1'},
    354            {'annotations": {}, 'class': 'com.example.TestB', 'method':'test2'}]
    355      combined_filters: the filter string representing tests to exclude
    356 
    357    Return:
    358      A list of tests that should still be included after the combined_filters
    359      are applied to their names
    360    '''
    361 
    362    if not combined_filters:
    363      return tests
    364 
    365    # Collect all test names
    366    all_test_names = set()
    367    tests_to_names = {}
    368    for t in tests:
    369      tests_to_names[id(t)] = get_test_names(t)
    370      for name in tests_to_names[id(t)]:
    371        all_test_names.add(name)
    372 
    373    for combined_filter in combined_filters:
    374      pattern_groups = combined_filter.split('-')
    375      negative_pattern = pattern_groups[1] if len(pattern_groups) > 1 else None
    376      positive_pattern = pattern_groups[0]
    377      if positive_pattern:
    378        # Only use the test names that match the positive pattern
    379        positive_test_names = test_names_from_pattern(positive_pattern,
    380                                                      all_test_names)
    381        tests = get_tests_from_names(tests, positive_test_names, tests_to_names)
    382 
    383      if negative_pattern:
    384        # Remove any test the negative filter matches
    385        remove_names = test_names_from_pattern(negative_pattern, all_test_names)
    386        tests = remove_tests_from_names(tests, remove_names, tests_to_names)
    387 
    388    return tests
    389 
    390  def annotation_filter(all_annotations):
    391    if not annotations:
    392      return True
    393    return any_annotation_matches(annotations, all_annotations)
    394 
    395  def excluded_annotation_filter(all_annotations):
    396    if not excluded_annotations:
    397      return True
    398    return not any_annotation_matches(excluded_annotations,
    399                                      all_annotations)
    400 
    401  def any_annotation_matches(filter_annotations, all_annotations):
    402    return any(
    403        ak in all_annotations
    404        and annotation_value_matches(av, all_annotations[ak])
    405        for ak, av in filter_annotations)
    406 
    407  def annotation_value_matches(filter_av, av):
    408    if filter_av is None:
    409      return True
    410    if isinstance(av, dict):
    411      tav_from_dict = av['value']
    412      # If tav_from_dict is an int, the 'in' operator breaks, so convert
    413      # filter_av and manually compare. See https://crbug.com/1019707
    414      if isinstance(tav_from_dict, int):
    415        return int(filter_av) == tav_from_dict
    416      return filter_av in tav_from_dict
    417    if isinstance(av, list):
    418      return filter_av in av
    419    return filter_av == av
    420 
    421  return_tests = []
    422  for t in gtests_filter(tests, filter_strs):
    423    # Enforce that all tests declare their size.
    424    if not any(a in _VALID_ANNOTATIONS for a in t['annotations']):
    425      raise MissingSizeAnnotationError(GetTestName(t))
    426 
    427    if (not annotation_filter(t['annotations'])
    428        or not excluded_annotation_filter(t['annotations'])):
    429      continue
    430    return_tests.append(t)
    431 
    432  return return_tests
    433 
    434 
    435 def GetTestsFromDexdump(test_apk):
    436  dex_dumps = dexdump.Dump(test_apk)
    437  tests = []
    438 
    439  def get_test_methods(methods, annotations):
    440    test_methods = []
    441 
    442    for method in methods:
    443      if method.startswith('test'):
    444        method_annotations = annotations.get(method, {})
    445 
    446        # Dexdump used to not return any annotation info
    447        # So MediumTest annotation was added to all methods
    448        # Preserving this behaviour by adding MediumTest if none of the
    449        # size annotations are included in these annotations
    450        if not any(valid in method_annotations for valid in _VALID_ANNOTATIONS):
    451          method_annotations.update({'MediumTest': None})
    452 
    453        test_methods.append({
    454            'method': method,
    455            'annotations': method_annotations
    456        })
    457 
    458    return test_methods
    459 
    460  for dump in dex_dumps:
    461    for package_name, package_info in dump.items():
    462      for class_name, class_info in package_info['classes'].items():
    463        if class_name.endswith('Test') and not class_info['is_abstract']:
    464          classAnnotations, methodsAnnotations = class_info['annotations']
    465          tests.append({
    466              'class':
    467              '%s.%s' % (package_name, class_name),
    468              'annotations':
    469              classAnnotations,
    470              'methods':
    471              get_test_methods(class_info['methods'], methodsAnnotations),
    472          })
    473  return tests
    474 
    475 
    476 def GetTestName(test, sep='#'):
    477  """Gets the name of the given test.
    478 
    479  Note that this may return the same name for more than one test, e.g. if a
    480  test is being run multiple times with different parameters.
    481 
    482  Args:
    483    test: the instrumentation test dict.
    484    sep: the character(s) that should join the class name and the method name.
    485  Returns:
    486    The test name as a string.
    487  """
    488  test_name = '%s%s%s' % (test['class'], sep, test['method'])
    489  assert not any(char in test_name for char in ' *-:'), (
    490      'The test name must not contain any of the characters in " *-:". See '
    491      'https://crbug.com/912199')
    492  return test_name
    493 
    494 
    495 def GetTestNameWithoutParameterSuffix(test, sep='#', parameterization_sep='__'):
    496  """Gets the name of the given JUnit4 test without parameter suffix.
    497 
    498  For most WebView JUnit4 javatests, each test is parameterizatized with
    499  "__sandboxed_mode" to run in both non-sandboxed mode and sandboxed mode.
    500 
    501  This function returns the name of the test without parameterization
    502  so test filters can match both parameterized and non-parameterized tests.
    503 
    504  Args:
    505    test: the instrumentation test dict.
    506    sep: the character(s) that should join the class name and the method name.
    507    parameterization_sep: the character(s) that separate method name and method
    508                          parameterization suffix.
    509  Returns:
    510    The test name without parameter suffix as a string.
    511  """
    512  name = GetTestName(test, sep=sep)
    513  return name.split(parameterization_sep)[0]
    514 
    515 
    516 def GetUniqueTestName(test, sep='#'):
    517  """Gets the unique name of the given test.
    518 
    519  This will include text to disambiguate between tests for which GetTestName
    520  would return the same name.
    521 
    522  Args:
    523    test: the instrumentation test dict.
    524    sep: the character(s) that should join the class name and the method name.
    525  Returns:
    526    The unique test name as a string.
    527  """
    528  display_name = GetTestName(test, sep=sep)
    529  if test.get('flags', [None])[0]:
    530    sanitized_flags = [x.replace('-', '_') for x in test['flags']]
    531    display_name = '%s_with_%s' % (display_name, '_'.join(sanitized_flags))
    532 
    533  assert not any(char in display_name for char in ' *-:'), (
    534      'The test name must not contain any of the characters in " *-:". See '
    535      'https://crbug.com/912199')
    536 
    537  return display_name
    538 
    539 
    540 class InstrumentationTestInstance(test_instance.TestInstance):
    541 
    542  def __init__(self, args, data_deps_delegate, error_func):
    543    super().__init__()
    544 
    545    self._additional_apks = []
    546    self._additional_apexs = []
    547    self._forced_queryable_additional_apks = []
    548    self._instant_additional_apks = []
    549    self._apk_under_test = None
    550    self._apk_under_test_incremental_install_json = None
    551    self._modules = None
    552    self._fake_modules = None
    553    self._additional_locales = None
    554    self._package_info = None
    555    self._suite = None
    556    self._test_apk = None
    557    self._test_apk_as_instant = False
    558    self._test_apk_incremental_install_json = None
    559    self._test_package = None
    560    self._junit4_runner_class = None
    561    self._uses_base_instrumentation = None
    562    self._has_chromium_test_listener = None
    563    self._use_native_coverage_listener = None
    564    self._test_support_apk = None
    565    self._initializeApkAttributes(args, error_func)
    566 
    567    self._data_deps = None
    568    self._data_deps_delegate = None
    569    self._runtime_deps_path = None
    570    self._variations_test_seed_path = args.variations_test_seed_path
    571    self._webview_variations_test_seed_path = (
    572        args.webview_variations_test_seed_path)
    573    self._store_data_dependencies_in_temp = False
    574    self._initializeDataDependencyAttributes(args, data_deps_delegate)
    575    self._annotations = None
    576    self._excluded_annotations = None
    577    self._has_external_annotation_filters = None
    578    self._test_filters = None
    579    self._initializeTestFilterAttributes(args)
    580 
    581    self._run_setup_commands = []
    582    self._run_teardown_commands = []
    583    self._initializeSetupTeardownCommandAttributes(args)
    584 
    585    self._flags = None
    586    self._use_apk_under_test_flags_file = False
    587    self._webview_flags = args.webview_command_line_arg
    588    self._initializeFlagAttributes(args)
    589 
    590    self._screenshot_dir = None
    591    self._timeout_scale = None
    592    self._wait_for_java_debugger = None
    593    self._initializeTestControlAttributes(args)
    594 
    595    self._coverage_directory = None
    596    self._initializeTestCoverageAttributes(args)
    597 
    598    self._store_tombstones = False
    599    self._symbolizer = None
    600    self._enable_breakpad_dump = False
    601    self._proguard_mapping_path = None
    602    self._deobfuscator = None
    603    self._initializeLogAttributes(args)
    604 
    605    self._replace_system_package = None
    606    self._initializeReplaceSystemPackageAttributes(args)
    607 
    608    self._system_packages_to_remove = None
    609    self._initializeSystemPackagesToRemoveAttributes(args)
    610 
    611    self._use_voice_interaction_service = None
    612    self._initializeUseVoiceInteractionService(args)
    613 
    614    self._use_webview_provider = None
    615    self._initializeUseWebviewProviderAttributes(args)
    616 
    617    self._skia_gold_properties = None
    618    self._initializeSkiaGoldAttributes(args)
    619 
    620    self._test_launcher_batch_limit = None
    621    self._initializeTestLauncherAttributes(args)
    622 
    623    self._approve_app_links_domain = None
    624    self._approve_app_links_package = None
    625    self._initializeApproveAppLinksAttributes(args)
    626 
    627    self._webview_process_mode = args.webview_process_mode
    628 
    629    self._webview_rebaseline_mode = args.webview_rebaseline_mode
    630 
    631    self._wpr_enable_record = args.wpr_enable_record
    632 
    633    self._external_shard_index = args.test_launcher_shard_index
    634    self._total_external_shards = args.test_launcher_total_shards
    635 
    636    self._is_unit_test = False
    637    self._initializeUnitTestFlag(args)
    638 
    639    self._run_disabled = args.run_disabled
    640 
    641  def _initializeApkAttributes(self, args, error_func):
    642    if args.apk_under_test:
    643      apk_under_test_path = args.apk_under_test
    644      if (not args.apk_under_test.endswith('.apk')
    645          and not args.apk_under_test.endswith('.apks')):
    646        apk_under_test_path = os.path.join(
    647            constants.GetOutDirectory(), constants.SDK_BUILD_APKS_DIR,
    648            '%s.apk' % args.apk_under_test)
    649 
    650      # TODO(jbudorick): Move the realpath up to the argument parser once
    651      # APK-by-name is no longer supported.
    652      apk_under_test_path = os.path.realpath(apk_under_test_path)
    653 
    654      if not os.path.exists(apk_under_test_path):
    655        error_func('Unable to find APK under test: %s' % apk_under_test_path)
    656 
    657      self._apk_under_test = apk_helper.ToHelper(apk_under_test_path)
    658 
    659    test_apk_path = args.test_apk
    660    if (not args.test_apk.endswith('.apk')
    661        and not args.test_apk.endswith('.apks')):
    662      test_apk_path = os.path.join(
    663          constants.GetOutDirectory(), constants.SDK_BUILD_APKS_DIR,
    664          '%s.apk' % args.test_apk)
    665 
    666    # TODO(jbudorick): Move the realpath up to the argument parser once
    667    # APK-by-name is no longer supported.
    668    test_apk_path = os.path.realpath(test_apk_path)
    669 
    670    if not os.path.exists(test_apk_path):
    671      error_func('Unable to find test APK: %s' % test_apk_path)
    672 
    673    self._test_apk = apk_helper.ToHelper(test_apk_path)
    674    self._suite = os.path.splitext(os.path.basename(args.test_apk))[0]
    675 
    676    self._test_apk_as_instant = args.test_apk_as_instant
    677 
    678    self._apk_under_test_incremental_install_json = (
    679        args.apk_under_test_incremental_install_json)
    680    self._test_apk_incremental_install_json = (
    681        args.test_apk_incremental_install_json)
    682 
    683    if self._test_apk_incremental_install_json:
    684      assert self._suite.endswith('_incremental')
    685      self._suite = self._suite[:-len('_incremental')]
    686 
    687    self._modules = args.modules
    688    self._fake_modules = args.fake_modules
    689    self._additional_locales = args.additional_locales
    690 
    691    self._test_support_apk = apk_helper.ToHelper(os.path.join(
    692        constants.GetOutDirectory(), constants.SDK_BUILD_TEST_JAVALIB_DIR,
    693        '%sSupport.apk' % self._suite))
    694 
    695    self._test_package = self._test_apk.GetPackageName()
    696    all_instrumentations = self._test_apk.GetAllInstrumentations()
    697 
    698    if len(all_instrumentations) > 1:
    699      logging.warning('This test apk has more than one instrumentation')
    700 
    701    self._junit4_runner_class = (all_instrumentations[0]['android:name']
    702                                 if all_instrumentations else None)
    703 
    704    test_apk_metadata = dict(self._test_apk.GetAllMetadata())
    705    self._has_chromium_test_listener = bool(
    706        test_apk_metadata.get('org.chromium.hasTestRunListener'))
    707    self._use_native_coverage_listener = bool(
    708        test_apk_metadata.get('org.chromium.useNativeCoverageListener'))
    709    if self._junit4_runner_class:
    710      if self._test_apk_incremental_install_json:
    711        for name, value in test_apk_metadata.items():
    712          if (name.startswith('incremental-install-instrumentation-')
    713              and value == _BASE_INSTRUMENTATION_CLASS_NAME):
    714            self._uses_base_instrumentation = True
    715            break
    716      else:
    717        self._uses_base_instrumentation = (
    718            self._junit4_runner_class == _BASE_INSTRUMENTATION_CLASS_NAME)
    719 
    720    self._package_info = None
    721    if self._apk_under_test:
    722      package_under_test = self._apk_under_test.GetPackageName()
    723      for package_info in constants.PACKAGE_INFO.values():
    724        if package_under_test == package_info.package:
    725          self._package_info = package_info
    726          break
    727    if not self._package_info:
    728      logging.warning(
    729          'Unable to find package info for %s. '
    730          '(This may just mean that the test package is '
    731          'currently being installed.)', self._test_package)
    732 
    733    for x in set(args.additional_apks + args.forced_queryable_additional_apks +
    734                 args.instant_additional_apks):
    735      if not os.path.exists(x):
    736        error_func('Unable to find additional APK: %s' % x)
    737 
    738      apk = apk_helper.ToHelper(x)
    739      self._additional_apks.append(apk)
    740 
    741      if x in args.forced_queryable_additional_apks:
    742        self._forced_queryable_additional_apks.append(apk)
    743 
    744      if x in args.instant_additional_apks:
    745        self._instant_additional_apks.append(apk)
    746 
    747    self._additional_apexs = args.additional_apexs
    748 
    749  def _initializeDataDependencyAttributes(self, args, data_deps_delegate):
    750    self._data_deps = []
    751    self._data_deps_delegate = data_deps_delegate
    752    self._store_data_dependencies_in_temp = args.store_data_dependencies_in_temp
    753    self._runtime_deps_path = args.runtime_deps_path
    754 
    755    if not self._runtime_deps_path:
    756      logging.warning('No data dependencies will be pushed.')
    757 
    758  def _initializeTestFilterAttributes(self, args):
    759    self._test_filters = test_filter.InitializeFiltersFromArgs(args)
    760    self._has_external_annotation_filters = bool(args.annotation_str
    761                                                 or args.exclude_annotation_str)
    762 
    763    def annotation_element(a):
    764      a = a.split('=', 1)
    765      return (a[0], a[1] if len(a) == 2 else None)
    766 
    767    if args.annotation_str:
    768      self._annotations = [
    769          annotation_element(a) for a in args.annotation_str.split(',')]
    770    elif not self._test_filters:
    771      self._annotations = [
    772          annotation_element(a) for a in _DEFAULT_ANNOTATIONS]
    773    else:
    774      self._annotations = []
    775 
    776    if args.exclude_annotation_str:
    777      self._excluded_annotations = [
    778          annotation_element(a) for a in args.exclude_annotation_str.split(',')]
    779    else:
    780      self._excluded_annotations = []
    781 
    782    requested_annotations = set(a[0] for a in self._annotations)
    783    if args.run_disabled:
    784      self._excluded_annotations.extend(
    785          annotation_element(a) for a in _DO_NOT_REVIVE_ANNOTATIONS
    786          if a not in requested_annotations)
    787    else:
    788      self._excluded_annotations.extend(
    789          annotation_element(a) for a in _EXCLUDE_UNLESS_REQUESTED_ANNOTATIONS
    790          if a not in requested_annotations)
    791 
    792  def _initializeSetupTeardownCommandAttributes(self, args):
    793    self._run_setup_commands = args.run_setup_commands
    794    self._run_teardown_commands = args.run_teardown_commands
    795 
    796  def _initializeFlagAttributes(self, args):
    797    self._use_apk_under_test_flags_file = args.use_apk_under_test_flags_file
    798    self._flags = ['--enable-test-intents']
    799    if args.command_line_flags:
    800      self._flags.extend(args.command_line_flags)
    801    if args.device_flags_file:
    802      with open(args.device_flags_file) as device_flags_file:
    803        stripped_lines = (l.strip() for l in device_flags_file)
    804        self._flags.extend(flag for flag in stripped_lines if flag)
    805    if args.strict_mode and args.strict_mode != 'off' and (
    806        # TODO(yliuyliu): Turn on strict mode for coverage once
    807        # crbug/1006397 is fixed.
    808        not args.coverage_dir):
    809      self._flags.append('--strict-mode=' + args.strict_mode)
    810 
    811  def _initializeTestControlAttributes(self, args):
    812    self._screenshot_dir = args.screenshot_dir
    813    self._timeout_scale = args.timeout_scale or 1
    814    self._wait_for_java_debugger = args.wait_for_java_debugger
    815 
    816  def _initializeTestCoverageAttributes(self, args):
    817    self._coverage_directory = args.coverage_dir
    818 
    819  def _initializeLogAttributes(self, args):
    820    self._enable_breakpad_dump = args.enable_breakpad_dump
    821    self._proguard_mapping_path = args.proguard_mapping_path
    822    self._store_tombstones = args.store_tombstones
    823    self._symbolizer = stack_symbolizer.Symbolizer(
    824        self.apk_under_test.path if self.apk_under_test else None)
    825 
    826  def _initializeReplaceSystemPackageAttributes(self, args):
    827    if (not hasattr(args, 'replace_system_package')
    828        or not args.replace_system_package):
    829      return
    830    self._replace_system_package = args.replace_system_package
    831 
    832  def _initializeSystemPackagesToRemoveAttributes(self, args):
    833    if (not hasattr(args, 'system_packages_to_remove')
    834        or not args.system_packages_to_remove):
    835      return
    836    self._system_packages_to_remove = args.system_packages_to_remove
    837 
    838  def _initializeUseVoiceInteractionService(self, args):
    839    if (not hasattr(args, 'use_voice_interaction_service')
    840        or not args.use_voice_interaction_service):
    841      return
    842    self._use_voice_interaction_service = args.use_voice_interaction_service
    843 
    844  def _initializeUseWebviewProviderAttributes(self, args):
    845    if (not hasattr(args, 'use_webview_provider')
    846        or not args.use_webview_provider):
    847      return
    848    self._use_webview_provider = args.use_webview_provider
    849 
    850  def _initializeSkiaGoldAttributes(self, args):
    851    self._skia_gold_properties = gold_utils.AndroidSkiaGoldProperties(args)
    852 
    853  def _initializeTestLauncherAttributes(self, args):
    854    if hasattr(args, 'test_launcher_batch_limit'):
    855      self._test_launcher_batch_limit = args.test_launcher_batch_limit
    856 
    857  def _initializeApproveAppLinksAttributes(self, args):
    858    if (not hasattr(args, 'approve_app_links') or not args.approve_app_links):
    859      return
    860 
    861    # The argument will be formatted as com.android.thing:www.example.com .
    862    app_links = args.approve_app_links.split(':')
    863 
    864    if (len(app_links) != 2 or not app_links[0] or not app_links[1]):
    865      logging.warning('--approve_app_links option provided, but malformed.')
    866      return
    867 
    868    self._approve_app_links_package = app_links[0]
    869    self._approve_app_links_domain = app_links[1]
    870 
    871  def _initializeUnitTestFlag(self, args):
    872    self._is_unit_test = args.is_unit_test
    873 
    874  @property
    875  def additional_apks(self):
    876    return self._additional_apks
    877 
    878  @property
    879  def additional_apexs(self):
    880    return self._additional_apexs
    881 
    882  @property
    883  def apk_under_test(self):
    884    return self._apk_under_test
    885 
    886  @property
    887  def apk_under_test_incremental_install_json(self):
    888    return self._apk_under_test_incremental_install_json
    889 
    890  @property
    891  def approve_app_links_package(self):
    892    return self._approve_app_links_package
    893 
    894  @property
    895  def approve_app_links_domain(self):
    896    return self._approve_app_links_domain
    897 
    898  @property
    899  def modules(self):
    900    return self._modules
    901 
    902  @property
    903  def fake_modules(self):
    904    return self._fake_modules
    905 
    906  @property
    907  def additional_locales(self):
    908    return self._additional_locales
    909 
    910  @property
    911  def coverage_directory(self):
    912    return self._coverage_directory
    913 
    914  @property
    915  def enable_breakpad_dump(self):
    916    return self._enable_breakpad_dump
    917 
    918  @property
    919  def external_shard_index(self):
    920    return self._external_shard_index
    921 
    922  @property
    923  def flags(self):
    924    return self._flags[:]
    925 
    926  @property
    927  def is_unit_test(self):
    928    return self._is_unit_test
    929 
    930  @property
    931  def junit4_runner_class(self):
    932    return self._junit4_runner_class
    933 
    934  @property
    935  def has_chromium_test_listener(self):
    936    return self._has_chromium_test_listener
    937 
    938  @property
    939  def has_external_annotation_filters(self):
    940    return self._has_external_annotation_filters
    941 
    942  @property
    943  def uses_base_instrumentation(self):
    944    return self._uses_base_instrumentation
    945 
    946  @property
    947  def use_native_coverage_listener(self):
    948    return self._use_native_coverage_listener
    949 
    950  @property
    951  def package_info(self):
    952    return self._package_info
    953 
    954  @property
    955  def replace_system_package(self):
    956    return self._replace_system_package
    957 
    958  @property
    959  def run_setup_commands(self):
    960    return self._run_setup_commands
    961 
    962  @property
    963  def run_teardown_commands(self):
    964    return self._run_teardown_commands
    965 
    966  @property
    967  def use_voice_interaction_service(self):
    968    return self._use_voice_interaction_service
    969 
    970  @property
    971  def use_webview_provider(self):
    972    return self._use_webview_provider
    973 
    974  @property
    975  def webview_flags(self):
    976    return self._webview_flags[:]
    977 
    978  @property
    979  def screenshot_dir(self):
    980    return self._screenshot_dir
    981 
    982  @property
    983  def skia_gold_properties(self):
    984    return self._skia_gold_properties
    985 
    986  @property
    987  def store_data_dependencies_in_temp(self):
    988    return self._store_data_dependencies_in_temp
    989 
    990  @property
    991  def store_tombstones(self):
    992    return self._store_tombstones
    993 
    994  @property
    995  def suite(self):
    996    return self._suite
    997 
    998  @property
    999  def symbolizer(self):
   1000    return self._symbolizer
   1001 
   1002  @property
   1003  def system_packages_to_remove(self):
   1004    return self._system_packages_to_remove
   1005 
   1006  @property
   1007  def test_apk(self):
   1008    return self._test_apk
   1009 
   1010  @property
   1011  def test_apk_as_instant(self):
   1012    return self._test_apk_as_instant
   1013 
   1014  @property
   1015  def test_apk_incremental_install_json(self):
   1016    return self._test_apk_incremental_install_json
   1017 
   1018  @property
   1019  def test_filters(self):
   1020    return self._test_filters
   1021 
   1022  @property
   1023  def test_launcher_batch_limit(self):
   1024    return self._test_launcher_batch_limit
   1025 
   1026  @property
   1027  def test_support_apk(self):
   1028    return self._test_support_apk
   1029 
   1030  @property
   1031  def test_package(self):
   1032    return self._test_package
   1033 
   1034  @property
   1035  def timeout_scale(self):
   1036    return self._timeout_scale
   1037 
   1038  @property
   1039  def total_external_shards(self):
   1040    return self._total_external_shards
   1041 
   1042  @property
   1043  def use_apk_under_test_flags_file(self):
   1044    return self._use_apk_under_test_flags_file
   1045 
   1046  @property
   1047  def variations_test_seed_path(self):
   1048    return self._variations_test_seed_path
   1049 
   1050  @property
   1051  def webview_variations_test_seed_path(self):
   1052    return self._webview_variations_test_seed_path
   1053 
   1054  @property
   1055  def wait_for_java_debugger(self):
   1056    return self._wait_for_java_debugger
   1057 
   1058  @property
   1059  def wpr_record_mode(self):
   1060    return self._wpr_enable_record
   1061 
   1062  @property
   1063  def webview_process_mode(self):
   1064    return self._webview_process_mode
   1065 
   1066  @property
   1067  def webview_rebaseline_mode(self):
   1068    return self._webview_rebaseline_mode
   1069 
   1070  @property
   1071  def wpr_replay_mode(self):
   1072    return not self._wpr_enable_record
   1073 
   1074  #override
   1075  def TestType(self):
   1076    return 'instrumentation'
   1077 
   1078  #override
   1079  def GetPreferredAbis(self):
   1080    # We could alternatively take the intersection of what they all support,
   1081    # but it should never be the case that they support different things.
   1082    apks = [self._test_apk, self._apk_under_test] + self._additional_apks
   1083    for apk in apks:
   1084      if apk:
   1085        ret = apk.GetAbis()
   1086        if ret:
   1087          return ret
   1088    return []
   1089 
   1090  #override
   1091  def SetUp(self):
   1092    self._data_deps.extend(
   1093        self._data_deps_delegate(self._runtime_deps_path))
   1094    if self._proguard_mapping_path:
   1095      self._deobfuscator = deobfuscator.DeobfuscatorPool(
   1096          self._proguard_mapping_path)
   1097 
   1098  def GetDataDependencies(self):
   1099    return self._data_deps
   1100 
   1101  def GetRunDisabledFlag(self):
   1102    return self._run_disabled
   1103 
   1104  def MaybeDeobfuscateLines(self, lines):
   1105    if not self._deobfuscator:
   1106      return lines
   1107    return self._deobfuscator.TransformLines(lines)
   1108 
   1109  def ProcessRawTests(self, raw_tests):
   1110    inflated_tests = self._ParameterizeTestsWithFlags(
   1111        self._InflateTests(raw_tests))
   1112    filtered_tests = FilterTests(inflated_tests, self._test_filters,
   1113                                 self._annotations, self._excluded_annotations)
   1114    if self._test_filters and not filtered_tests:
   1115      for t in inflated_tests:
   1116        logging.debug('  %s', GetUniqueTestName(t))
   1117      logging.warning('Unmatched Filters: %s', self._test_filters)
   1118    return filtered_tests
   1119 
   1120  def IsApkForceQueryable(self, apk):
   1121    return apk in self._forced_queryable_additional_apks
   1122 
   1123  def IsApkInstant(self, apk):
   1124    return apk in self._instant_additional_apks
   1125 
   1126  # pylint: disable=no-self-use
   1127  def _InflateTests(self, tests):
   1128    inflated_tests = []
   1129    for clazz in tests:
   1130      for method in clazz['methods']:
   1131        annotations = dict(clazz['annotations'])
   1132        annotations.update(method['annotations'])
   1133 
   1134        # Preserve historic default.
   1135        if (not self._uses_base_instrumentation
   1136            and not any(a in _VALID_ANNOTATIONS for a in annotations)):
   1137          annotations['MediumTest'] = None
   1138 
   1139        inflated_tests.append({
   1140            'class': clazz['class'],
   1141            'method': method['method'],
   1142            'annotations': annotations,
   1143        })
   1144    return inflated_tests
   1145 
   1146  def _ParameterizeTestsWithFlags(self, tests):
   1147 
   1148    def _checkParameterization(annotations):
   1149      types = [
   1150          _PARAMETERIZED_COMMAND_LINE_FLAGS_SWITCHES,
   1151          _PARAMETERIZED_COMMAND_LINE_FLAGS,
   1152      ]
   1153      if types[0] in annotations and types[1] in annotations:
   1154        raise CommandLineParameterizationException(
   1155            'Multiple command-line parameterization types: {}.'.format(
   1156                ', '.join(types)))
   1157 
   1158    def _switchesToFlags(switches):
   1159      return ['--{}'.format(s) for s in switches if s]
   1160 
   1161    def _annotationToSwitches(clazz, methods):
   1162      if clazz == _PARAMETERIZED_COMMAND_LINE_FLAGS_SWITCHES:
   1163        return [methods['value']]
   1164      if clazz == _PARAMETERIZED_COMMAND_LINE_FLAGS:
   1165        list_of_switches = []
   1166        for annotation in methods['value']:
   1167          for c, m in annotation.items():
   1168            list_of_switches += _annotationToSwitches(c, m)
   1169        return list_of_switches
   1170      return []
   1171 
   1172    def _setTestFlags(test, flags):
   1173      if flags:
   1174        test['flags'] = flags
   1175      elif 'flags' in test:
   1176        del test['flags']
   1177 
   1178    new_tests = []
   1179    for t in tests:
   1180      annotations = t['annotations']
   1181      list_of_switches = []
   1182      _checkParameterization(annotations)
   1183      if _SKIP_PARAMETERIZATION not in annotations:
   1184        for clazz, methods in annotations.items():
   1185          list_of_switches += _annotationToSwitches(clazz, methods)
   1186      if list_of_switches:
   1187        _setTestFlags(t, _switchesToFlags(list_of_switches[0]))
   1188        for p in list_of_switches[1:]:
   1189          parameterized_t = copy.copy(t)
   1190          _setTestFlags(parameterized_t, _switchesToFlags(p))
   1191          new_tests.append(parameterized_t)
   1192    return tests + new_tests
   1193 
   1194  @staticmethod
   1195  def GenerateTestResults(result_code, result_bundle, statuses, duration_ms,
   1196                          device_abi, symbolizer):
   1197    return GenerateTestResults(result_code, result_bundle, statuses,
   1198                               duration_ms, device_abi, symbolizer)
   1199 
   1200  #override
   1201  def TearDown(self):
   1202    self.symbolizer.CleanUp()
   1203    if self._deobfuscator:
   1204      self._deobfuscator.Close()
   1205      self._deobfuscator = None