1# Copyright (c) 2012 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 5"""Gathers information about APKs.""" 6 7import collections 8import os 9import re 10 11import cmd_helper 12 13 14class ApkInfo(object): 15 """Helper class for inspecting APKs.""" 16 _PROGUARD_PATH = os.path.join(os.environ['ANDROID_SDK_ROOT'], 17 'tools/proguard/bin/proguard.sh') 18 if not os.path.exists(_PROGUARD_PATH): 19 _PROGUARD_PATH = os.path.join(os.environ['ANDROID_BUILD_TOP'], 20 'external/proguard/bin/proguard.sh') 21 _PROGUARD_CLASS_RE = re.compile(r'\s*?- Program class:\s*([\S]+)$') 22 _PROGUARD_METHOD_RE = re.compile(r'\s*?- Method:\s*(\S*)[(].*$') 23 _PROGUARD_ANNOTATION_RE = re.compile(r'\s*?- Annotation \[L(\S*);\]:$') 24 _PROGUARD_ANNOTATION_CONST_RE = re.compile(r'\s*?- Constant element value.*$') 25 _PROGUARD_ANNOTATION_VALUE_RE = re.compile(r'\s*?- \S+? \[(.*)\]$') 26 _AAPT_PACKAGE_NAME_RE = re.compile(r'package: .*name=\'(\S*)\'') 27 28 def __init__(self, apk_path, jar_path): 29 if not os.path.exists(apk_path): 30 raise Exception('%s not found, please build it' % apk_path) 31 self._apk_path = apk_path 32 if not os.path.exists(jar_path): 33 raise Exception('%s not found, please build it' % jar_path) 34 self._jar_path = jar_path 35 self._annotation_map = collections.defaultdict(list) 36 self._test_methods = [] 37 self._Initialize() 38 39 def _Initialize(self): 40 proguard_output = cmd_helper.GetCmdOutput([self._PROGUARD_PATH, 41 '-injars', self._jar_path, 42 '-dontshrink', 43 '-dontoptimize', 44 '-dontobfuscate', 45 '-dontpreverify', 46 '-dump', 47 ]).split('\n') 48 clazz = None 49 method = None 50 annotation = None 51 has_value = False 52 qualified_method = None 53 for line in proguard_output: 54 m = self._PROGUARD_CLASS_RE.match(line) 55 if m: 56 clazz = m.group(1).replace('/', '.') # Change package delim. 57 annotation = None 58 continue 59 m = self._PROGUARD_METHOD_RE.match(line) 60 if m: 61 method = m.group(1) 62 annotation = None 63 qualified_method = clazz + '#' + method 64 if method.startswith('test') and clazz.endswith('Test'): 65 self._test_methods += [qualified_method] 66 continue 67 m = self._PROGUARD_ANNOTATION_RE.match(line) 68 if m: 69 assert qualified_method 70 annotation = m.group(1).split('/')[-1] # Ignore the annotation package. 71 self._annotation_map[qualified_method].append(annotation) 72 has_value = False 73 continue 74 if annotation: 75 assert qualified_method 76 if not has_value: 77 m = self._PROGUARD_ANNOTATION_CONST_RE.match(line) 78 if m: 79 has_value = True 80 else: 81 m = self._PROGUARD_ANNOTATION_VALUE_RE.match(line) 82 if m: 83 value = m.group(1) 84 self._annotation_map[qualified_method].append( 85 annotation + ':' + value) 86 has_value = False 87 88 def _GetAnnotationMap(self): 89 return self._annotation_map 90 91 def _IsTestMethod(self, test): 92 class_name, method = test.split('#') 93 return class_name.endswith('Test') and method.startswith('test') 94 95 def GetApkPath(self): 96 return self._apk_path 97 98 def GetPackageName(self): 99 """Returns the package name of this APK.""" 100 aapt_output = cmd_helper.GetCmdOutput( 101 ['aapt', 'dump', 'badging', self._apk_path]).split('\n') 102 for line in aapt_output: 103 m = self._AAPT_PACKAGE_NAME_RE.match(line) 104 if m: 105 return m.group(1) 106 raise Exception('Failed to determine package name of %s' % self._apk_path) 107 108 def GetTestAnnotations(self, test): 109 """Returns a list of all annotations for the given |test|. May be empty.""" 110 if not self._IsTestMethod(test): 111 return [] 112 return self._GetAnnotationMap()[test] 113 114 def _AnnotationsMatchFilters(self, annotation_filter_list, annotations): 115 """Checks if annotations match any of the filters.""" 116 if not annotation_filter_list: 117 return True 118 for annotation_filter in annotation_filter_list: 119 filters = annotation_filter.split('=') 120 if len(filters) == 2: 121 key = filters[0] 122 value_list = filters[1].split(',') 123 for value in value_list: 124 if key + ':' + value in annotations: 125 return True 126 elif annotation_filter in annotations: 127 return True 128 return False 129 130 def GetAnnotatedTests(self, annotation_filter_list): 131 """Returns a list of all tests that match the given annotation filters.""" 132 return [test for test, annotations in self._GetAnnotationMap().iteritems() 133 if self._IsTestMethod(test) and self._AnnotationsMatchFilters( 134 annotation_filter_list, annotations)] 135 136 def GetTestMethods(self): 137 """Returns a list of all test methods in this apk as Class#testMethod.""" 138 return self._test_methods 139 140 @staticmethod 141 def IsPythonDrivenTest(test): 142 return 'pythonDrivenTests' in test 143