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