1# Copyright 2015 The Chromium Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5import copy
6import logging
7import os
8import pickle
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 test_result
18from pylib.instrumentation import instrumentation_parser
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 instrumentation_tracing
24from pylib.utils import proguard
25from pylib.utils import shared_preference_utils
26from pylib.utils import test_filter
27
28
29with host_paths.SysPath(host_paths.BUILD_COMMON_PATH):
30  import unittest_util # pylint: disable=import-error
31
32# Ref: http://developer.android.com/reference/android/app/Activity.html
33_ACTIVITY_RESULT_CANCELED = 0
34_ACTIVITY_RESULT_OK = -1
35
36_COMMAND_LINE_PARAMETER = 'cmdlinearg-parameter'
37_DEFAULT_ANNOTATIONS = [
38    'SmallTest', 'MediumTest', 'LargeTest', 'EnormousTest', 'IntegrationTest']
39_EXCLUDE_UNLESS_REQUESTED_ANNOTATIONS = [
40    'DisabledTest', 'FlakyTest', 'Manual']
41_VALID_ANNOTATIONS = set(_DEFAULT_ANNOTATIONS +
42                         _EXCLUDE_UNLESS_REQUESTED_ANNOTATIONS)
43
44_EXTRA_DRIVER_TEST_LIST = (
45    'org.chromium.test.driver.OnDeviceInstrumentationDriver.TestList')
46_EXTRA_DRIVER_TEST_LIST_FILE = (
47    'org.chromium.test.driver.OnDeviceInstrumentationDriver.TestListFile')
48_EXTRA_DRIVER_TARGET_PACKAGE = (
49    'org.chromium.test.driver.OnDeviceInstrumentationDriver.TargetPackage')
50_EXTRA_DRIVER_TARGET_CLASS = (
51    'org.chromium.test.driver.OnDeviceInstrumentationDriver.TargetClass')
52_EXTRA_TIMEOUT_SCALE = (
53    'org.chromium.test.driver.OnDeviceInstrumentationDriver.TimeoutScale')
54_TEST_LIST_JUNIT4_RUNNERS = [
55    'org.chromium.base.test.BaseChromiumAndroidJUnitRunner']
56
57_SKIP_PARAMETERIZATION = 'SkipCommandLineParameterization'
58_PARAMETERIZED_COMMAND_LINE_FLAGS = 'ParameterizedCommandLineFlags'
59_PARAMETERIZED_COMMAND_LINE_FLAGS_SWITCHES = (
60    'ParameterizedCommandLineFlags$Switches')
61_NATIVE_CRASH_RE = re.compile('(process|native) crash', re.IGNORECASE)
62_PICKLE_FORMAT_VERSION = 12
63
64
65class MissingSizeAnnotationError(test_exception.TestException):
66  def __init__(self, class_name):
67    super(MissingSizeAnnotationError, self).__init__(class_name +
68        ': Test method is missing required size annotation. Add one of: ' +
69        ', '.join('@' + a for a in _VALID_ANNOTATIONS))
70
71
72class CommandLineParameterizationException(test_exception.TestException):
73
74  def __init__(self, msg):
75    super(CommandLineParameterizationException, self).__init__(msg)
76
77
78class TestListPickleException(test_exception.TestException):
79  pass
80
81
82# TODO(jbudorick): Make these private class methods of
83# InstrumentationTestInstance once the instrumentation junit3_runner_class is
84# deprecated.
85def ParseAmInstrumentRawOutput(raw_output):
86  """Parses the output of an |am instrument -r| call.
87
88  Args:
89    raw_output: the output of an |am instrument -r| call as a list of lines
90  Returns:
91    A 3-tuple containing:
92      - the instrumentation code as an integer
93      - the instrumentation result as a list of lines
94      - the instrumentation statuses received as a list of 2-tuples
95        containing:
96        - the status code as an integer
97        - the bundle dump as a dict mapping string keys to a list of
98          strings, one for each line.
99  """
100  parser = instrumentation_parser.InstrumentationParser(raw_output)
101  statuses = list(parser.IterStatus())
102  code, bundle = parser.GetResult()
103  return (code, bundle, statuses)
104
105
106def GenerateTestResults(
107    result_code, result_bundle, statuses, start_ms, duration_ms, device_abi,
108    symbolizer):
109  """Generate test results from |statuses|.
110
111  Args:
112    result_code: The overall status code as an integer.
113    result_bundle: The summary bundle dump as a dict.
114    statuses: A list of 2-tuples containing:
115      - the status code as an integer
116      - the bundle dump as a dict mapping string keys to string values
117      Note that this is the same as the third item in the 3-tuple returned by
118      |_ParseAmInstrumentRawOutput|.
119    start_ms: The start time of the test in milliseconds.
120    duration_ms: The duration of the test in milliseconds.
121    device_abi: The device_abi, which is needed for symbolization.
122    symbolizer: The symbolizer used to symbolize stack.
123
124  Returns:
125    A list containing an instance of InstrumentationTestResult for each test
126    parsed.
127  """
128
129  results = []
130
131  current_result = None
132
133  for status_code, bundle in statuses:
134    test_class = bundle.get('class', '')
135    test_method = bundle.get('test', '')
136    if test_class and test_method:
137      test_name = '%s#%s' % (test_class, test_method)
138    else:
139      continue
140
141    if status_code == instrumentation_parser.STATUS_CODE_START:
142      if current_result:
143        results.append(current_result)
144      current_result = test_result.InstrumentationTestResult(
145          test_name, base_test_result.ResultType.UNKNOWN, start_ms, duration_ms)
146    else:
147      if status_code == instrumentation_parser.STATUS_CODE_OK:
148        if bundle.get('test_skipped', '').lower() in ('true', '1', 'yes'):
149          current_result.SetType(base_test_result.ResultType.SKIP)
150        elif current_result.GetType() == base_test_result.ResultType.UNKNOWN:
151          current_result.SetType(base_test_result.ResultType.PASS)
152      elif status_code == instrumentation_parser.STATUS_CODE_SKIP:
153        current_result.SetType(base_test_result.ResultType.SKIP)
154      elif status_code == instrumentation_parser.STATUS_CODE_ASSUMPTION_FAILURE:
155        current_result.SetType(base_test_result.ResultType.SKIP)
156      else:
157        if status_code not in (instrumentation_parser.STATUS_CODE_ERROR,
158                               instrumentation_parser.STATUS_CODE_FAILURE):
159          logging.error('Unrecognized status code %d. Handling as an error.',
160                        status_code)
161        current_result.SetType(base_test_result.ResultType.FAIL)
162    if 'stack' in bundle:
163      if symbolizer and device_abi:
164        current_result.SetLog(
165            '%s\n%s' % (
166              bundle['stack'],
167              '\n'.join(symbolizer.ExtractAndResolveNativeStackTraces(
168                  bundle['stack'], device_abi))))
169      else:
170        current_result.SetLog(bundle['stack'])
171
172  if current_result:
173    if current_result.GetType() == base_test_result.ResultType.UNKNOWN:
174      crashed = (result_code == _ACTIVITY_RESULT_CANCELED
175                 and any(_NATIVE_CRASH_RE.search(l)
176                         for l in result_bundle.itervalues()))
177      if crashed:
178        current_result.SetType(base_test_result.ResultType.CRASH)
179
180    results.append(current_result)
181
182  return results
183
184
185def FilterTests(tests, filter_str=None, annotations=None,
186                excluded_annotations=None):
187  """Filter a list of tests
188
189  Args:
190    tests: a list of tests. e.g. [
191           {'annotations": {}, 'class': 'com.example.TestA', 'method':'test1'},
192           {'annotations": {}, 'class': 'com.example.TestB', 'method':'test2'}]
193    filter_str: googletest-style filter string.
194    annotations: a dict of wanted annotations for test methods.
195    exclude_annotations: a dict of annotations to exclude.
196
197  Return:
198    A list of filtered tests
199  """
200  def gtest_filter(t):
201    if not filter_str:
202      return True
203    # Allow fully-qualified name as well as an omitted package.
204    unqualified_class_test = {
205      'class': t['class'].split('.')[-1],
206      'method': t['method']
207    }
208    names = [
209      GetTestName(t, sep='.'),
210      GetTestName(unqualified_class_test, sep='.'),
211      GetUniqueTestName(t, sep='.')
212    ]
213
214    if t['is_junit4']:
215      names += [
216          GetTestNameWithoutParameterPostfix(t, sep='.'),
217          GetTestNameWithoutParameterPostfix(unqualified_class_test, sep='.')
218      ]
219
220    pattern_groups = filter_str.split('-')
221    if len(pattern_groups) > 1:
222      negative_filter = pattern_groups[1]
223      if unittest_util.FilterTestNames(names, negative_filter):
224        return []
225
226    positive_filter = pattern_groups[0]
227    return unittest_util.FilterTestNames(names, positive_filter)
228
229  def annotation_filter(all_annotations):
230    if not annotations:
231      return True
232    return any_annotation_matches(annotations, all_annotations)
233
234  def excluded_annotation_filter(all_annotations):
235    if not excluded_annotations:
236      return True
237    return not any_annotation_matches(excluded_annotations,
238                                      all_annotations)
239
240  def any_annotation_matches(filter_annotations, all_annotations):
241    return any(
242        ak in all_annotations
243        and annotation_value_matches(av, all_annotations[ak])
244        for ak, av in filter_annotations)
245
246  def annotation_value_matches(filter_av, av):
247    if filter_av is None:
248      return True
249    elif isinstance(av, dict):
250      tav_from_dict = av['value']
251      # If tav_from_dict is an int, the 'in' operator breaks, so convert
252      # filter_av and manually compare. See https://crbug.com/1019707
253      if isinstance(tav_from_dict, int):
254        return int(filter_av) == tav_from_dict
255      else:
256        return filter_av in tav_from_dict
257    elif isinstance(av, list):
258      return filter_av in av
259    return filter_av == av
260
261  filtered_tests = []
262  for t in tests:
263    # Gtest filtering
264    if not gtest_filter(t):
265      continue
266
267    # Enforce that all tests declare their size.
268    if not any(a in _VALID_ANNOTATIONS for a in t['annotations']):
269      raise MissingSizeAnnotationError(GetTestName(t))
270
271    if (not annotation_filter(t['annotations'])
272        or not excluded_annotation_filter(t['annotations'])):
273      continue
274
275    filtered_tests.append(t)
276
277  return filtered_tests
278
279
280# TODO(yolandyan): remove this once the tests are converted to junit4
281def GetAllTestsFromJar(test_jar):
282  pickle_path = '%s-proguard.pickle' % test_jar
283  try:
284    tests = GetTestsFromPickle(pickle_path, os.path.getmtime(test_jar))
285  except TestListPickleException as e:
286    logging.info('Could not get tests from pickle: %s', e)
287    logging.info('Getting tests from JAR via proguard.')
288    tests = _GetTestsFromProguard(test_jar)
289    SaveTestsToPickle(pickle_path, tests)
290  return tests
291
292
293def GetAllTestsFromApk(test_apk):
294  pickle_path = '%s-dexdump.pickle' % test_apk
295  try:
296    tests = GetTestsFromPickle(pickle_path, os.path.getmtime(test_apk))
297  except TestListPickleException as e:
298    logging.info('Could not get tests from pickle: %s', e)
299    logging.info('Getting tests from dex via dexdump.')
300    tests = _GetTestsFromDexdump(test_apk)
301    SaveTestsToPickle(pickle_path, tests)
302  return tests
303
304def GetTestsFromPickle(pickle_path, test_mtime):
305  if not os.path.exists(pickle_path):
306    raise TestListPickleException('%s does not exist.' % pickle_path)
307  if os.path.getmtime(pickle_path) <= test_mtime:
308    raise TestListPickleException('File is stale: %s' % pickle_path)
309
310  with open(pickle_path, 'r') as f:
311    pickle_data = pickle.load(f)
312  if pickle_data['VERSION'] != _PICKLE_FORMAT_VERSION:
313    raise TestListPickleException('PICKLE_FORMAT_VERSION has changed.')
314  return pickle_data['TEST_METHODS']
315
316
317# TODO(yolandyan): remove this once the test listing from java runner lands
318@instrumentation_tracing.no_tracing
319def _GetTestsFromProguard(jar_path):
320  p = proguard.Dump(jar_path)
321  class_lookup = dict((c['class'], c) for c in p['classes'])
322
323  def is_test_class(c):
324    return c['class'].endswith('Test')
325
326  def is_test_method(m):
327    return m['method'].startswith('test')
328
329  def recursive_class_annotations(c):
330    s = c['superclass']
331    if s in class_lookup:
332      a = recursive_class_annotations(class_lookup[s])
333    else:
334      a = {}
335    a.update(c['annotations'])
336    return a
337
338  def stripped_test_class(c):
339    return {
340      'class': c['class'],
341      'annotations': recursive_class_annotations(c),
342      'methods': [m for m in c['methods'] if is_test_method(m)],
343      'superclass': c['superclass'],
344    }
345
346  return [stripped_test_class(c) for c in p['classes']
347          if is_test_class(c)]
348
349
350def _GetTestsFromDexdump(test_apk):
351  dump = dexdump.Dump(test_apk)
352  tests = []
353
354  def get_test_methods(methods):
355    return [
356        {
357          'method': m,
358          # No annotation info is available from dexdump.
359          # Set MediumTest annotation for default.
360          'annotations': {'MediumTest': None},
361        } for m in methods if m.startswith('test')]
362
363  for package_name, package_info in dump.iteritems():
364    for class_name, class_info in package_info['classes'].iteritems():
365      if class_name.endswith('Test'):
366        tests.append({
367            'class': '%s.%s' % (package_name, class_name),
368            'annotations': {},
369            'methods': get_test_methods(class_info['methods']),
370            'superclass': class_info['superclass'],
371        })
372  return tests
373
374def SaveTestsToPickle(pickle_path, tests):
375  pickle_data = {
376    'VERSION': _PICKLE_FORMAT_VERSION,
377    'TEST_METHODS': tests,
378  }
379  with open(pickle_path, 'w') as pickle_file:
380    pickle.dump(pickle_data, pickle_file)
381
382
383class MissingJUnit4RunnerException(test_exception.TestException):
384  """Raised when JUnit4 runner is not provided or specified in apk manifest"""
385
386  def __init__(self):
387    super(MissingJUnit4RunnerException, self).__init__(
388        'JUnit4 runner is not provided or specified in test apk manifest.')
389
390
391def GetTestName(test, sep='#'):
392  """Gets the name of the given test.
393
394  Note that this may return the same name for more than one test, e.g. if a
395  test is being run multiple times with different parameters.
396
397  Args:
398    test: the instrumentation test dict.
399    sep: the character(s) that should join the class name and the method name.
400  Returns:
401    The test name as a string.
402  """
403  test_name = '%s%s%s' % (test['class'], sep, test['method'])
404  assert ' *-:' not in test_name, (
405      'The test name must not contain any of the characters in " *-:". See '
406      'https://crbug.com/912199')
407  return test_name
408
409
410def GetTestNameWithoutParameterPostfix(
411      test, sep='#', parameterization_sep='__'):
412  """Gets the name of the given JUnit4 test without parameter postfix.
413
414  For most WebView JUnit4 javatests, each test is parameterizatized with
415  "__sandboxed_mode" to run in both non-sandboxed mode and sandboxed mode.
416
417  This function returns the name of the test without parameterization
418  so test filters can match both parameterized and non-parameterized tests.
419
420  Args:
421    test: the instrumentation test dict.
422    sep: the character(s) that should join the class name and the method name.
423    parameterization_sep: the character(s) that seperate method name and method
424                          parameterization postfix.
425  Returns:
426    The test name without parameter postfix as a string.
427  """
428  name = GetTestName(test, sep=sep)
429  return name.split(parameterization_sep)[0]
430
431
432def GetUniqueTestName(test, sep='#'):
433  """Gets the unique name of the given test.
434
435  This will include text to disambiguate between tests for which GetTestName
436  would return the same name.
437
438  Args:
439    test: the instrumentation test dict.
440    sep: the character(s) that should join the class name and the method name.
441  Returns:
442    The unique test name as a string.
443  """
444  display_name = GetTestName(test, sep=sep)
445  if test.get('flags', [None])[0]:
446    sanitized_flags = [x.replace('-', '_') for x in test['flags']]
447    display_name = '%s_with_%s' % (display_name, '_'.join(sanitized_flags))
448
449  assert ' *-:' not in display_name, (
450      'The test name must not contain any of the characters in " *-:". See '
451      'https://crbug.com/912199')
452
453  return display_name
454
455
456class InstrumentationTestInstance(test_instance.TestInstance):
457
458  def __init__(self, args, data_deps_delegate, error_func):
459    super(InstrumentationTestInstance, self).__init__()
460
461    self._additional_apks = []
462    self._apk_under_test = None
463    self._apk_under_test_incremental_install_json = None
464    self._modules = None
465    self._fake_modules = None
466    self._package_info = None
467    self._suite = None
468    self._test_apk = None
469    self._test_apk_incremental_install_json = None
470    self._test_jar = None
471    self._test_package = None
472    self._junit3_runner_class = None
473    self._junit4_runner_class = None
474    self._junit4_runner_supports_listing = None
475    self._test_support_apk = None
476    self._initializeApkAttributes(args, error_func)
477
478    self._data_deps = None
479    self._data_deps_delegate = None
480    self._runtime_deps_path = None
481    self._initializeDataDependencyAttributes(args, data_deps_delegate)
482
483    self._annotations = None
484    self._excluded_annotations = None
485    self._test_filter = None
486    self._initializeTestFilterAttributes(args)
487
488    self._flags = None
489    self._use_apk_under_test_flags_file = False
490    self._initializeFlagAttributes(args)
491
492    self._driver_apk = None
493    self._driver_package = None
494    self._driver_name = None
495    self._initializeDriverAttributes()
496
497    self._screenshot_dir = None
498    self._timeout_scale = None
499    self._wait_for_java_debugger = None
500    self._initializeTestControlAttributes(args)
501
502    self._coverage_directory = None
503    self._initializeTestCoverageAttributes(args)
504
505    self._store_tombstones = False
506    self._symbolizer = None
507    self._enable_java_deobfuscation = False
508    self._deobfuscator = None
509    self._initializeLogAttributes(args)
510
511    self._edit_shared_prefs = []
512    self._initializeEditPrefsAttributes(args)
513
514    self._replace_system_package = None
515    self._initializeReplaceSystemPackageAttributes(args)
516
517    self._use_webview_provider = None
518    self._initializeUseWebviewProviderAttributes(args)
519
520    self._skia_gold_properties = None
521    self._initializeSkiaGoldAttributes(args)
522
523    self._external_shard_index = args.test_launcher_shard_index
524    self._total_external_shards = args.test_launcher_total_shards
525
526  def _initializeApkAttributes(self, args, error_func):
527    if args.apk_under_test:
528      apk_under_test_path = args.apk_under_test
529      if (not args.apk_under_test.endswith('.apk')
530          and not args.apk_under_test.endswith('.apks')):
531        apk_under_test_path = os.path.join(
532            constants.GetOutDirectory(), constants.SDK_BUILD_APKS_DIR,
533            '%s.apk' % args.apk_under_test)
534
535      # TODO(jbudorick): Move the realpath up to the argument parser once
536      # APK-by-name is no longer supported.
537      apk_under_test_path = os.path.realpath(apk_under_test_path)
538
539      if not os.path.exists(apk_under_test_path):
540        error_func('Unable to find APK under test: %s' % apk_under_test_path)
541
542      self._apk_under_test = apk_helper.ToHelper(apk_under_test_path)
543
544    test_apk_path = args.test_apk
545    if not os.path.exists(test_apk_path):
546      test_apk_path = os.path.join(
547          constants.GetOutDirectory(), constants.SDK_BUILD_APKS_DIR,
548          '%s.apk' % args.test_apk)
549      # TODO(jbudorick): Move the realpath up to the argument parser once
550      # APK-by-name is no longer supported.
551      test_apk_path = os.path.realpath(test_apk_path)
552
553    if not os.path.exists(test_apk_path):
554      error_func('Unable to find test APK: %s' % test_apk_path)
555
556    self._test_apk = apk_helper.ToHelper(test_apk_path)
557    self._suite = os.path.splitext(os.path.basename(args.test_apk))[0]
558
559    self._apk_under_test_incremental_install_json = (
560        args.apk_under_test_incremental_install_json)
561    self._test_apk_incremental_install_json = (
562        args.test_apk_incremental_install_json)
563
564    if self._test_apk_incremental_install_json:
565      assert self._suite.endswith('_incremental')
566      self._suite = self._suite[:-len('_incremental')]
567
568    self._modules = args.modules
569    self._fake_modules = args.fake_modules
570
571    self._test_jar = args.test_jar
572    self._test_support_apk = apk_helper.ToHelper(os.path.join(
573        constants.GetOutDirectory(), constants.SDK_BUILD_TEST_JAVALIB_DIR,
574        '%sSupport.apk' % self._suite))
575
576    if not self._test_jar:
577      logging.warning('Test jar not specified. Test runner will not have '
578                      'Java annotation info available. May not handle test '
579                      'timeouts correctly.')
580    elif not os.path.exists(self._test_jar):
581      error_func('Unable to find test JAR: %s' % self._test_jar)
582
583    self._test_package = self._test_apk.GetPackageName()
584    all_instrumentations = self._test_apk.GetAllInstrumentations()
585    all_junit3_runner_classes = [
586        x for x in all_instrumentations if ('0xffffffff' in x.get(
587            'chromium-junit3', ''))]
588    all_junit4_runner_classes = [
589        x for x in all_instrumentations if ('0xffffffff' not in x.get(
590            'chromium-junit3', ''))]
591
592    if len(all_junit3_runner_classes) > 1:
593      logging.warning('This test apk has more than one JUnit3 instrumentation')
594    if len(all_junit4_runner_classes) > 1:
595      logging.warning('This test apk has more than one JUnit4 instrumentation')
596
597    self._junit3_runner_class = (
598      all_junit3_runner_classes[0]['android:name']
599      if all_junit3_runner_classes else self.test_apk.GetInstrumentationName())
600
601    self._junit4_runner_class = (
602      all_junit4_runner_classes[0]['android:name']
603      if all_junit4_runner_classes else None)
604
605    if self._junit4_runner_class:
606      if self._test_apk_incremental_install_json:
607        self._junit4_runner_supports_listing = next(
608            (True for x in self._test_apk.GetAllMetadata()
609             if 'real-instr' in x[0] and x[1] in _TEST_LIST_JUNIT4_RUNNERS),
610            False)
611      else:
612        self._junit4_runner_supports_listing = (
613            self._junit4_runner_class in _TEST_LIST_JUNIT4_RUNNERS)
614
615    self._package_info = None
616    if self._apk_under_test:
617      package_under_test = self._apk_under_test.GetPackageName()
618      for package_info in constants.PACKAGE_INFO.itervalues():
619        if package_under_test == package_info.package:
620          self._package_info = package_info
621          break
622    if not self._package_info:
623      logging.warning(("Unable to find package info for %s. " +
624                       "(This may just mean that the test package is " +
625                       "currently being installed.)"),
626                       self._test_package)
627
628    for apk in args.additional_apks:
629      if not os.path.exists(apk):
630        error_func('Unable to find additional APK: %s' % apk)
631    self._additional_apks = (
632        [apk_helper.ToHelper(x) for x in args.additional_apks])
633
634  def _initializeDataDependencyAttributes(self, args, data_deps_delegate):
635    self._data_deps = []
636    self._data_deps_delegate = data_deps_delegate
637    self._runtime_deps_path = args.runtime_deps_path
638
639    if not self._runtime_deps_path:
640      logging.warning('No data dependencies will be pushed.')
641
642  def _initializeTestFilterAttributes(self, args):
643    self._test_filter = test_filter.InitializeFilterFromArgs(args)
644
645    def annotation_element(a):
646      a = a.split('=', 1)
647      return (a[0], a[1] if len(a) == 2 else None)
648
649    if args.annotation_str:
650      self._annotations = [
651          annotation_element(a) for a in args.annotation_str.split(',')]
652    elif not self._test_filter:
653      self._annotations = [
654          annotation_element(a) for a in _DEFAULT_ANNOTATIONS]
655    else:
656      self._annotations = []
657
658    if args.exclude_annotation_str:
659      self._excluded_annotations = [
660          annotation_element(a) for a in args.exclude_annotation_str.split(',')]
661    else:
662      self._excluded_annotations = []
663
664    requested_annotations = set(a[0] for a in self._annotations)
665    if not args.run_disabled:
666      self._excluded_annotations.extend(
667          annotation_element(a) for a in _EXCLUDE_UNLESS_REQUESTED_ANNOTATIONS
668          if a not in requested_annotations)
669
670  def _initializeFlagAttributes(self, args):
671    self._use_apk_under_test_flags_file = args.use_apk_under_test_flags_file
672    self._flags = ['--enable-test-intents']
673    if args.command_line_flags:
674      self._flags.extend(args.command_line_flags)
675    if args.device_flags_file:
676      with open(args.device_flags_file) as device_flags_file:
677        stripped_lines = (l.strip() for l in device_flags_file)
678        self._flags.extend(flag for flag in stripped_lines if flag)
679    if args.strict_mode and args.strict_mode != 'off' and (
680        # TODO(yliuyliu): Turn on strict mode for coverage once
681        # crbug/1006397 is fixed.
682        not args.coverage_dir):
683      self._flags.append('--strict-mode=' + args.strict_mode)
684
685  def _initializeDriverAttributes(self):
686    self._driver_apk = os.path.join(
687        constants.GetOutDirectory(), constants.SDK_BUILD_APKS_DIR,
688        'OnDeviceInstrumentationDriver.apk')
689    if os.path.exists(self._driver_apk):
690      driver_apk = apk_helper.ApkHelper(self._driver_apk)
691      self._driver_package = driver_apk.GetPackageName()
692      self._driver_name = driver_apk.GetInstrumentationName()
693    else:
694      self._driver_apk = None
695
696  def _initializeTestControlAttributes(self, args):
697    self._screenshot_dir = args.screenshot_dir
698    self._timeout_scale = args.timeout_scale or 1
699    self._wait_for_java_debugger = args.wait_for_java_debugger
700
701  def _initializeTestCoverageAttributes(self, args):
702    self._coverage_directory = args.coverage_dir
703
704  def _initializeLogAttributes(self, args):
705    self._enable_java_deobfuscation = args.enable_java_deobfuscation
706    self._store_tombstones = args.store_tombstones
707    self._symbolizer = stack_symbolizer.Symbolizer(
708        self.apk_under_test.path if self.apk_under_test else None)
709
710  def _initializeEditPrefsAttributes(self, args):
711    if not hasattr(args, 'shared_prefs_file') or not args.shared_prefs_file:
712      return
713    if not isinstance(args.shared_prefs_file, str):
714      logging.warning("Given non-string for a filepath")
715      return
716    self._edit_shared_prefs = shared_preference_utils.ExtractSettingsFromJson(
717        args.shared_prefs_file)
718
719  def _initializeReplaceSystemPackageAttributes(self, args):
720    if (not hasattr(args, 'replace_system_package')
721        or not args.replace_system_package):
722      return
723    self._replace_system_package = args.replace_system_package
724
725  def _initializeUseWebviewProviderAttributes(self, args):
726    if (not hasattr(args, 'use_webview_provider')
727        or not args.use_webview_provider):
728      return
729    self._use_webview_provider = args.use_webview_provider
730
731  def _initializeSkiaGoldAttributes(self, args):
732    self._skia_gold_properties = gold_utils.SkiaGoldProperties(args)
733
734  @property
735  def additional_apks(self):
736    return self._additional_apks
737
738  @property
739  def apk_under_test(self):
740    return self._apk_under_test
741
742  @property
743  def apk_under_test_incremental_install_json(self):
744    return self._apk_under_test_incremental_install_json
745
746  @property
747  def modules(self):
748    return self._modules
749
750  @property
751  def fake_modules(self):
752    return self._fake_modules
753
754  @property
755  def coverage_directory(self):
756    return self._coverage_directory
757
758  @property
759  def driver_apk(self):
760    return self._driver_apk
761
762  @property
763  def driver_package(self):
764    return self._driver_package
765
766  @property
767  def driver_name(self):
768    return self._driver_name
769
770  @property
771  def edit_shared_prefs(self):
772    return self._edit_shared_prefs
773
774  @property
775  def external_shard_index(self):
776    return self._external_shard_index
777
778  @property
779  def flags(self):
780    return self._flags
781
782  @property
783  def junit3_runner_class(self):
784    return self._junit3_runner_class
785
786  @property
787  def junit4_runner_class(self):
788    return self._junit4_runner_class
789
790  @property
791  def junit4_runner_supports_listing(self):
792    return self._junit4_runner_supports_listing
793
794  @property
795  def package_info(self):
796    return self._package_info
797
798  @property
799  def replace_system_package(self):
800    return self._replace_system_package
801
802  @property
803  def use_webview_provider(self):
804    return self._use_webview_provider
805
806  @property
807  def screenshot_dir(self):
808    return self._screenshot_dir
809
810  @property
811  def skia_gold_properties(self):
812    return self._skia_gold_properties
813
814  @property
815  def store_tombstones(self):
816    return self._store_tombstones
817
818  @property
819  def suite(self):
820    return self._suite
821
822  @property
823  def symbolizer(self):
824    return self._symbolizer
825
826  @property
827  def test_apk(self):
828    return self._test_apk
829
830  @property
831  def test_apk_incremental_install_json(self):
832    return self._test_apk_incremental_install_json
833
834  @property
835  def test_jar(self):
836    return self._test_jar
837
838  @property
839  def test_support_apk(self):
840    return self._test_support_apk
841
842  @property
843  def test_package(self):
844    return self._test_package
845
846  @property
847  def timeout_scale(self):
848    return self._timeout_scale
849
850  @property
851  def total_external_shards(self):
852    return self._total_external_shards
853
854  @property
855  def use_apk_under_test_flags_file(self):
856    return self._use_apk_under_test_flags_file
857
858  @property
859  def wait_for_java_debugger(self):
860    return self._wait_for_java_debugger
861
862  #override
863  def TestType(self):
864    return 'instrumentation'
865
866  #override
867  def GetPreferredAbis(self):
868    # We could alternatively take the intersection of what they all support,
869    # but it should never be the case that they support different things.
870    apks = [self._test_apk, self._apk_under_test] + self._additional_apks
871    for apk in apks:
872      if apk:
873        ret = apk.GetAbis()
874        if ret:
875          return ret
876    return []
877
878  #override
879  def SetUp(self):
880    self._data_deps.extend(
881        self._data_deps_delegate(self._runtime_deps_path))
882    if self._enable_java_deobfuscation:
883      self._deobfuscator = deobfuscator.DeobfuscatorPool(
884          self.test_apk.path + '.mapping')
885
886  def GetDataDependencies(self):
887    return self._data_deps
888
889  def GetTests(self):
890    if self.test_jar:
891      raw_tests = GetAllTestsFromJar(self.test_jar)
892    else:
893      raw_tests = GetAllTestsFromApk(self.test_apk.path)
894    return self.ProcessRawTests(raw_tests)
895
896  def MaybeDeobfuscateLines(self, lines):
897    if not self._deobfuscator:
898      return lines
899    return self._deobfuscator.TransformLines(lines)
900
901  def ProcessRawTests(self, raw_tests):
902    inflated_tests = self._ParameterizeTestsWithFlags(
903        self._InflateTests(raw_tests))
904    if self._junit4_runner_class is None and any(
905        t['is_junit4'] for t in inflated_tests):
906      raise MissingJUnit4RunnerException()
907    filtered_tests = FilterTests(
908        inflated_tests, self._test_filter, self._annotations,
909        self._excluded_annotations)
910    if self._test_filter and not filtered_tests:
911      for t in inflated_tests:
912        logging.debug('  %s', GetUniqueTestName(t))
913      logging.warning('Unmatched Filter: %s', self._test_filter)
914    return filtered_tests
915
916  # pylint: disable=no-self-use
917  def _InflateTests(self, tests):
918    inflated_tests = []
919    for c in tests:
920      for m in c['methods']:
921        a = dict(c['annotations'])
922        a.update(m['annotations'])
923        inflated_tests.append({
924            'class': c['class'],
925            'method': m['method'],
926            'annotations': a,
927            'is_junit4': c['superclass'] == 'java.lang.Object'
928        })
929    return inflated_tests
930
931  def _ParameterizeTestsWithFlags(self, tests):
932
933    def _checkParameterization(annotations):
934      types = [
935          _PARAMETERIZED_COMMAND_LINE_FLAGS_SWITCHES,
936          _PARAMETERIZED_COMMAND_LINE_FLAGS,
937      ]
938      if types[0] in annotations and types[1] in annotations:
939        raise CommandLineParameterizationException(
940            'Multiple command-line parameterization types: {}.'.format(
941                ', '.join(types)))
942
943    def _switchesToFlags(switches):
944      return ['--{}'.format(s) for s in switches if s]
945
946    def _annotationToSwitches(clazz, methods):
947      if clazz == _PARAMETERIZED_COMMAND_LINE_FLAGS_SWITCHES:
948        return [methods['value']]
949      elif clazz == _PARAMETERIZED_COMMAND_LINE_FLAGS:
950        list_of_switches = []
951        for annotation in methods['value']:
952          for clazz, methods in annotation.iteritems():
953            list_of_switches += _annotationToSwitches(clazz, methods)
954        return list_of_switches
955      else:
956        return []
957
958    def _setTestFlags(test, flags):
959      if flags:
960        test['flags'] = flags
961      elif 'flags' in test:
962        del test['flags']
963
964    new_tests = []
965    for t in tests:
966      annotations = t['annotations']
967      list_of_switches = []
968      _checkParameterization(annotations)
969      if _SKIP_PARAMETERIZATION not in annotations:
970        for clazz, methods in annotations.iteritems():
971          list_of_switches += _annotationToSwitches(clazz, methods)
972      if list_of_switches:
973        _setTestFlags(t, _switchesToFlags(list_of_switches[0]))
974        for p in list_of_switches[1:]:
975          parameterized_t = copy.copy(t)
976          _setTestFlags(parameterized_t, _switchesToFlags(p))
977          new_tests.append(parameterized_t)
978    return tests + new_tests
979
980  def GetDriverEnvironmentVars(
981      self, test_list=None, test_list_file_path=None):
982    env = {
983      _EXTRA_DRIVER_TARGET_PACKAGE: self.test_package,
984      _EXTRA_DRIVER_TARGET_CLASS: self.junit3_runner_class,
985      _EXTRA_TIMEOUT_SCALE: self._timeout_scale,
986    }
987
988    if test_list:
989      env[_EXTRA_DRIVER_TEST_LIST] = ','.join(test_list)
990
991    if test_list_file_path:
992      env[_EXTRA_DRIVER_TEST_LIST_FILE] = (
993          os.path.basename(test_list_file_path))
994
995    return env
996
997  @staticmethod
998  def ParseAmInstrumentRawOutput(raw_output):
999    return ParseAmInstrumentRawOutput(raw_output)
1000
1001  @staticmethod
1002  def GenerateTestResults(
1003      result_code, result_bundle, statuses, start_ms, duration_ms,
1004      device_abi, symbolizer):
1005    return GenerateTestResults(result_code, result_bundle, statuses,
1006                               start_ms, duration_ms, device_abi, symbolizer)
1007
1008  #override
1009  def TearDown(self):
1010    self.symbolizer.CleanUp()
1011    if self._deobfuscator:
1012      self._deobfuscator.Close()
1013      self._deobfuscator = None
1014