Upstream version 5.34.104.0
[platform/framework/web/crosswalk.git] / src / build / android / pylib / instrumentation / test_jar.py
1 # Copyright (c) 2013 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 """Helper class for instrumenation test jar."""
6 # pylint: disable=W0702
7
8 import collections
9 import logging
10 import os
11 import pickle
12 import re
13
14 from pylib import cmd_helper
15 from pylib import constants
16
17
18 # If you change the cached output of proguard, increment this number
19 PICKLE_FORMAT_VERSION = 1
20
21
22 class TestJar(object):
23   _ANNOTATIONS = frozenset(
24       ['Smoke', 'SmallTest', 'MediumTest', 'LargeTest', 'EnormousTest',
25        'FlakyTest', 'DisabledTest', 'Manual', 'PerfTest', 'HostDrivenTest'])
26   _DEFAULT_ANNOTATION = 'SmallTest'
27   _PROGUARD_CLASS_RE = re.compile(r'\s*?- Program class:\s*([\S]+)$')
28   _PROGUARD_METHOD_RE = re.compile(r'\s*?- Method:\s*(\S*)[(].*$')
29   _PROGUARD_ANNOTATION_RE = re.compile(r'\s*?- Annotation \[L(\S*);\]:$')
30   _PROGUARD_ANNOTATION_CONST_RE = (
31       re.compile(r'\s*?- Constant element value.*$'))
32   _PROGUARD_ANNOTATION_VALUE_RE = re.compile(r'\s*?- \S+? \[(.*)\]$')
33
34   def __init__(self, jar_path):
35     if not os.path.exists(jar_path):
36       raise Exception('%s not found, please build it' % jar_path)
37
38     sdk_root = os.getenv('ANDROID_SDK_ROOT', constants.ANDROID_SDK_ROOT)
39     self._PROGUARD_PATH = os.path.join(sdk_root,
40                                        'tools/proguard/bin/proguard.sh')
41     if not os.path.exists(self._PROGUARD_PATH):
42       self._PROGUARD_PATH = os.path.join(os.environ['ANDROID_BUILD_TOP'],
43                                          'external/proguard/bin/proguard.sh')
44     self._jar_path = jar_path
45     self._annotation_map = collections.defaultdict(list)
46     self._pickled_proguard_name = self._jar_path + '-proguard.pickle'
47     self._test_methods = []
48     if not self._GetCachedProguardData():
49       self._GetProguardData()
50
51   def _GetCachedProguardData(self):
52     if (os.path.exists(self._pickled_proguard_name) and
53         (os.path.getmtime(self._pickled_proguard_name) >
54          os.path.getmtime(self._jar_path))):
55       logging.info('Loading cached proguard output from %s',
56                    self._pickled_proguard_name)
57       try:
58         with open(self._pickled_proguard_name, 'r') as r:
59           d = pickle.loads(r.read())
60         if d['VERSION'] == PICKLE_FORMAT_VERSION:
61           self._annotation_map = d['ANNOTATION_MAP']
62           self._test_methods = d['TEST_METHODS']
63           return True
64       except:
65         logging.warning('PICKLE_FORMAT_VERSION has changed, ignoring cache')
66     return False
67
68   def _GetProguardData(self):
69     proguard_output = cmd_helper.GetCmdOutput([self._PROGUARD_PATH,
70                                                '-injars', self._jar_path,
71                                                '-dontshrink',
72                                                '-dontoptimize',
73                                                '-dontobfuscate',
74                                                '-dontpreverify',
75                                                '-dump',
76                                               ]).split('\n')
77     clazz = None
78     method = None
79     annotation = None
80     has_value = False
81     qualified_method = None
82     for line in proguard_output:
83       m = self._PROGUARD_CLASS_RE.match(line)
84       if m:
85         clazz = m.group(1).replace('/', '.')  # Change package delim.
86         annotation = None
87         continue
88
89       m = self._PROGUARD_METHOD_RE.match(line)
90       if m:
91         method = m.group(1)
92         annotation = None
93         qualified_method = clazz + '#' + method
94         if method.startswith('test') and clazz.endswith('Test'):
95           self._test_methods += [qualified_method]
96         continue
97
98       if not qualified_method:
99         # Ignore non-method annotations.
100         continue
101
102       m = self._PROGUARD_ANNOTATION_RE.match(line)
103       if m:
104         annotation = m.group(1).split('/')[-1]  # Ignore the annotation package.
105         self._annotation_map[qualified_method].append(annotation)
106         has_value = False
107         continue
108       if annotation:
109         if not has_value:
110           m = self._PROGUARD_ANNOTATION_CONST_RE.match(line)
111           if m:
112             has_value = True
113         else:
114           m = self._PROGUARD_ANNOTATION_VALUE_RE.match(line)
115           if m:
116             value = m.group(1)
117             self._annotation_map[qualified_method].append(
118                 annotation + ':' + value)
119             has_value = False
120
121     logging.info('Storing proguard output to %s', self._pickled_proguard_name)
122     d = {'VERSION': PICKLE_FORMAT_VERSION,
123          'ANNOTATION_MAP': self._annotation_map,
124          'TEST_METHODS': self._test_methods}
125     with open(self._pickled_proguard_name, 'w') as f:
126       f.write(pickle.dumps(d))
127
128   def _GetAnnotationMap(self):
129     return self._annotation_map
130
131   @staticmethod
132   def _IsTestMethod(test):
133     class_name, method = test.split('#')
134     return class_name.endswith('Test') and method.startswith('test')
135
136   def GetTestAnnotations(self, test):
137     """Returns a list of all annotations for the given |test|. May be empty."""
138     if not self._IsTestMethod(test):
139       return []
140     return self._GetAnnotationMap()[test]
141
142   @staticmethod
143   def _AnnotationsMatchFilters(annotation_filter_list, annotations):
144     """Checks if annotations match any of the filters."""
145     if not annotation_filter_list:
146       return True
147     for annotation_filter in annotation_filter_list:
148       filters = annotation_filter.split('=')
149       if len(filters) == 2:
150         key = filters[0]
151         value_list = filters[1].split(',')
152         for value in value_list:
153           if key + ':' + value in annotations:
154             return True
155       elif annotation_filter in annotations:
156         return True
157     return False
158
159   def GetAnnotatedTests(self, annotation_filter_list):
160     """Returns a list of all tests that match the given annotation filters."""
161     return [test for test, annotations in self._GetAnnotationMap().iteritems()
162             if self._IsTestMethod(test) and self._AnnotationsMatchFilters(
163                 annotation_filter_list, annotations)]
164
165   def GetTestMethods(self):
166     """Returns a list of all test methods in this apk as Class#testMethod."""
167     return self._test_methods
168
169   def _GetTestsMissingAnnotation(self):
170     """Get a list of test methods with no known annotations."""
171     tests_missing_annotations = []
172     for test_method in self.GetTestMethods():
173       annotations_ = frozenset(self.GetTestAnnotations(test_method))
174       if (annotations_.isdisjoint(self._ANNOTATIONS) and
175           not self.IsHostDrivenTest(test_method)):
176         tests_missing_annotations.append(test_method)
177     return sorted(tests_missing_annotations)
178
179   def GetAllMatchingTests(self, annotation_filter_list,
180                            exclude_annotation_list, test_filter):
181     """Get a list of tests matching any of the annotations and the filter.
182
183     Args:
184       annotation_filter_list: List of test annotations. A test must have at
185         least one of these annotations. A test without any annotations is
186         considered to be SmallTest.
187       exclude_annotation_list: List of test annotations. A test must not have
188         any of these annotations.
189       test_filter: Filter used for partial matching on the test method names.
190
191     Returns:
192       List of all matching tests.
193     """
194     if annotation_filter_list:
195       available_tests = self.GetAnnotatedTests(annotation_filter_list)
196       # Include un-annotated tests in SmallTest.
197       if annotation_filter_list.count(self._DEFAULT_ANNOTATION) > 0:
198         for test in self._GetTestsMissingAnnotation():
199           logging.warning(
200               '%s has no annotations. Assuming "%s".', test,
201               self._DEFAULT_ANNOTATION)
202           available_tests.append(test)
203       if exclude_annotation_list:
204         excluded_tests = self.GetAnnotatedTests(exclude_annotation_list)
205         available_tests = list(set(available_tests) - set(excluded_tests))
206     else:
207       available_tests = [m for m in self.GetTestMethods()
208                          if not self.IsHostDrivenTest(m)]
209
210     tests = []
211     if test_filter:
212       # |available_tests| are in adb instrument format: package.path.class#test.
213       filter_without_hash = test_filter.replace('#', '.')
214       tests = [t for t in available_tests
215                if filter_without_hash in t.replace('#', '.')]
216     else:
217       tests = available_tests
218
219     return tests
220
221   @staticmethod
222   def IsHostDrivenTest(test):
223     return 'pythonDrivenTests' in test