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