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