Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(338)

Side by Side Diff: build/android/pylib/instrumentation/instrumentation_test_instance.py

Issue 2963683002: Simplify CommandLine parameterizing J3 Instrumentation Tests (Closed)
Patch Set: remove unnecessary changes in host script Created 3 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 # Copyright 2015 The Chromium Authors. All rights reserved. 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 2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file. 3 # found in the LICENSE file.
4 4
5 import collections
6 import copy 5 import copy
7 import logging 6 import logging
8 import os 7 import os
9 import pickle 8 import pickle
10 import re 9 import re
11 10
12 from devil.android import apk_helper 11 from devil.android import apk_helper
13 from devil.android import md5sum 12 from devil.android import md5sum
14 from pylib import constants 13 from pylib import constants
15 from pylib.base import base_test_result 14 from pylib.base import base_test_result
(...skipping 24 matching lines...) Expand all
40 'org.chromium.test.driver.OnDeviceInstrumentationDriver.TestList') 39 'org.chromium.test.driver.OnDeviceInstrumentationDriver.TestList')
41 _EXTRA_DRIVER_TEST_LIST_FILE = ( 40 _EXTRA_DRIVER_TEST_LIST_FILE = (
42 'org.chromium.test.driver.OnDeviceInstrumentationDriver.TestListFile') 41 'org.chromium.test.driver.OnDeviceInstrumentationDriver.TestListFile')
43 _EXTRA_DRIVER_TARGET_PACKAGE = ( 42 _EXTRA_DRIVER_TARGET_PACKAGE = (
44 'org.chromium.test.driver.OnDeviceInstrumentationDriver.TargetPackage') 43 'org.chromium.test.driver.OnDeviceInstrumentationDriver.TargetPackage')
45 _EXTRA_DRIVER_TARGET_CLASS = ( 44 _EXTRA_DRIVER_TARGET_CLASS = (
46 'org.chromium.test.driver.OnDeviceInstrumentationDriver.TargetClass') 45 'org.chromium.test.driver.OnDeviceInstrumentationDriver.TargetClass')
47 _EXTRA_TIMEOUT_SCALE = ( 46 _EXTRA_TIMEOUT_SCALE = (
48 'org.chromium.test.driver.OnDeviceInstrumentationDriver.TimeoutScale') 47 'org.chromium.test.driver.OnDeviceInstrumentationDriver.TimeoutScale')
49 48
50 _PARAMETERIZED_TEST_ANNOTATION = 'ParameterizedTest' 49 _SKIP_PARAMETERIZATION = 'SkipParameterization'
51 _PARAMETERIZED_TEST_SET_ANNOTATION = 'ParameterizedTest$Set' 50 _COMMANDLINE_PARAMETERIZATION = 'JUnit3CommandLineParameter'
52 _NATIVE_CRASH_RE = re.compile('(process|native) crash', re.IGNORECASE) 51 _NATIVE_CRASH_RE = re.compile('(process|native) crash', re.IGNORECASE)
53 _CMDLINE_NAME_SEGMENT_RE = re.compile( 52 _CMDLINE_NAME_SEGMENT_RE = re.compile(
54 r' with(?:out)? \{[^\}]*\}') 53 r' with(?:out)? \{[^\}]*\}')
55 _PICKLE_FORMAT_VERSION = 11 54 _PICKLE_FORMAT_VERSION = 12
56 55
57 56
58 class MissingSizeAnnotationError(test_exception.TestException): 57 class MissingSizeAnnotationError(test_exception.TestException):
59 def __init__(self, class_name): 58 def __init__(self, class_name):
60 super(MissingSizeAnnotationError, self).__init__(class_name + 59 super(MissingSizeAnnotationError, self).__init__(class_name +
61 ': Test method is missing required size annotation. Add one of: ' + 60 ': Test method is missing required size annotation. Add one of: ' +
62 ', '.join('@' + a for a in _VALID_ANNOTATIONS)) 61 ', '.join('@' + a for a in _VALID_ANNOTATIONS))
63 62
64 63
65 class TestListPickleException(test_exception.TestException): 64 class TestListPickleException(test_exception.TestException):
(...skipping 84 matching lines...) Expand 10 before | Expand all | Expand 10 after
150 and any(_NATIVE_CRASH_RE.search(l) 149 and any(_NATIVE_CRASH_RE.search(l)
151 for l in result_bundle.itervalues())) 150 for l in result_bundle.itervalues()))
152 if crashed: 151 if crashed:
153 current_result.SetType(base_test_result.ResultType.CRASH) 152 current_result.SetType(base_test_result.ResultType.CRASH)
154 153
155 results.append(current_result) 154 results.append(current_result)
156 155
157 return results 156 return results
158 157
159 158
160 def ParseCommandLineFlagParameters(annotations):
161 """Determines whether the test is parameterized to be run with different
162 command-line flags.
163
164 Args:
165 annotations: The annotations of the test.
166
167 Returns:
168 If the test is parameterized, returns a list of named tuples
169 with lists of flags, e.g.:
170
171 [(add=['--flag-to-add']), (remove=['--flag-to-remove']), ()]
172
173 That means, the test must be run three times, the first time with
174 "--flag-to-add" added to command-line, the second time with
175 "--flag-to-remove" to be removed from command-line, and the third time
176 with default command-line args. If the same flag is listed both for adding
177 and for removing, it is left unchanged.
178
179 If the test is not parametrized, returns None.
180
181 """
182 ParamsTuple = collections.namedtuple('ParamsTuple', ['add', 'remove'])
183 parameterized_tests = []
184 if _PARAMETERIZED_TEST_SET_ANNOTATION in annotations:
185 if annotations[_PARAMETERIZED_TEST_SET_ANNOTATION]:
186 parameterized_tests = annotations[
187 _PARAMETERIZED_TEST_SET_ANNOTATION].get('tests', [])
188 elif _PARAMETERIZED_TEST_ANNOTATION in annotations:
189 parameterized_tests = [annotations[_PARAMETERIZED_TEST_ANNOTATION]]
190 else:
191 return None
192
193 result = []
194 for pt in parameterized_tests:
195 if not pt:
196 continue
197 for p in pt['parameters']:
198 if p['tag'] == _COMMAND_LINE_PARAMETER:
199 to_add = []
200 to_remove = []
201 for a in p.get('arguments', []):
202 if a['name'] == 'add':
203 to_add = ['--%s' % f for f in a['stringArray']]
204 elif a['name'] == 'remove':
205 to_remove = ['--%s' % f for f in a['stringArray']]
206 result.append(ParamsTuple(to_add, to_remove))
207 return result if result else None
208
209
210 def FilterTests(tests, test_filter=None, annotations=None, 159 def FilterTests(tests, test_filter=None, annotations=None,
211 excluded_annotations=None): 160 excluded_annotations=None):
212 """Filter a list of tests 161 """Filter a list of tests
213 162
214 Args: 163 Args:
215 tests: a list of tests. e.g. [ 164 tests: a list of tests. e.g. [
216 {'annotations": {}, 'class': 'com.example.TestA', 'methods':[]}, 165 {'annotations": {}, 'class': 'com.example.TestA', 'methods':[]},
217 {'annotations": {}, 'class': 'com.example.TestB', 'methods':[]}] 166 {'annotations": {}, 'class': 'com.example.TestB', 'methods':[]}]
218 test_filter: googletest-style filter string. 167 test_filter: googletest-style filter string.
219 annotations: a dict of wanted annotations for test methods. 168 annotations: a dict of wanted annotations for test methods.
(...skipping 62 matching lines...) Expand 10 before | Expand all | Expand 10 after
282 raise MissingSizeAnnotationError(GetTestName(t)) 231 raise MissingSizeAnnotationError(GetTestName(t))
283 232
284 if (not annotation_filter(t['annotations']) 233 if (not annotation_filter(t['annotations'])
285 or not excluded_annotation_filter(t['annotations'])): 234 or not excluded_annotation_filter(t['annotations'])):
286 continue 235 continue
287 236
288 filtered_tests.append(t) 237 filtered_tests.append(t)
289 238
290 return filtered_tests 239 return filtered_tests
291 240
292
jbudorick 2017/07/13 18:45:53 nit: please don't pull the second newline from bet
the real yoland 2017/07/13 23:49:19 Done
293 def GetAllTestsFromJar(test_jar): 241 def GetAllTestsFromJar(test_jar):
294 pickle_path = '%s-proguard.pickle' % test_jar 242 pickle_path = '%s-proguard.pickle' % test_jar
295 try: 243 try:
296 tests = _GetTestsFromPickle(pickle_path, test_jar) 244 tests = _GetTestsFromPickle(pickle_path, test_jar)
297 except TestListPickleException as e: 245 except TestListPickleException as e:
298 logging.info('Could not get tests from pickle: %s', e) 246 logging.info('Could not get tests from pickle: %s', e)
299 logging.info('Getting tests from JAR via proguard.') 247 logging.info('Getting tests from JAR via proguard.')
300 tests = _GetTestsFromProguard(test_jar) 248 tests = _GetTestsFromProguard(test_jar)
301 _SaveTestsToPickle(pickle_path, test_jar, tests) 249 _SaveTestsToPickle(pickle_path, test_jar, tests)
302 return tests 250 return tests
303 251
304 252
305 def GetAllTestsFromApk(test_apk): 253 def GetAllTestsFromApk(test_apk):
306 pickle_path = '%s-dexdump.pickle' % test_apk 254 pickle_path = '%s-dexdump.pickle' % test_apk
307 try: 255 try:
308 tests = _GetTestsFromPickle(pickle_path, test_apk) 256 tests = _GetTestsFromPickle(pickle_path, test_apk)
309 except TestListPickleException as e: 257 except TestListPickleException as e:
310 logging.info('Could not get tests from pickle: %s', e) 258 logging.info('Could not get tests from pickle: %s', e)
311 logging.info('Getting tests from dex via dexdump.') 259 logging.info('Getting tests from dex via dexdump.')
312 tests = _GetTestsFromDexdump(test_apk) 260 tests = _GetTestsFromDexdump(test_apk)
313 _SaveTestsToPickle(pickle_path, test_apk, tests) 261 _SaveTestsToPickle(pickle_path, test_apk, tests)
314 return tests 262 return tests
315 263
316
317 def _GetTestsFromPickle(pickle_path, jar_path): 264 def _GetTestsFromPickle(pickle_path, jar_path):
318 if not os.path.exists(pickle_path): 265 if not os.path.exists(pickle_path):
319 raise TestListPickleException('%s does not exist.' % pickle_path) 266 raise TestListPickleException('%s does not exist.' % pickle_path)
320 if os.path.getmtime(pickle_path) <= os.path.getmtime(jar_path): 267 if os.path.getmtime(pickle_path) <= os.path.getmtime(jar_path):
321 raise TestListPickleException( 268 raise TestListPickleException(
322 '%s newer than %s.' % (jar_path, pickle_path)) 269 '%s newer than %s.' % (jar_path, pickle_path))
323 270
324 with open(pickle_path, 'r') as f: 271 with open(pickle_path, 'r') as f:
325 pickle_data = pickle.load(f) 272 pickle_data = pickle.load(f)
326 jar_md5 = md5sum.CalculateHostMd5Sums(jar_path)[jar_path] 273 jar_md5 = md5sum.CalculateHostMd5Sums(jar_path)[jar_path]
327 274
328 if pickle_data['VERSION'] != _PICKLE_FORMAT_VERSION: 275 if pickle_data['VERSION'] != _PICKLE_FORMAT_VERSION:
329 raise TestListPickleException('PICKLE_FORMAT_VERSION has changed.') 276 raise TestListPickleException('PICKLE_FORMAT_VERSION has changed.')
330 if pickle_data['JAR_MD5SUM'] != jar_md5: 277 if pickle_data['JAR_MD5SUM'] != jar_md5:
331 raise TestListPickleException('JAR file MD5 sum differs.') 278 raise TestListPickleException('JAR file MD5 sum differs.')
332 return pickle_data['TEST_METHODS'] 279 return pickle_data['TEST_METHODS']
333 280
334 281
282 # TODO(yolandyan): remove this once the test listing from java runner lands
335 def _GetTestsFromProguard(jar_path): 283 def _GetTestsFromProguard(jar_path):
336 p = proguard.Dump(jar_path) 284 p = proguard.Dump(jar_path)
337 class_lookup = dict((c['class'], c) for c in p['classes']) 285 class_lookup = dict((c['class'], c) for c in p['classes'])
338 286
339 def is_test_class(c): 287 def is_test_class(c):
340 return c['class'].endswith('Test') 288 return c['class'].endswith('Test')
341 289
342 def is_test_method(m): 290 def is_test_method(m):
343 return m['method'].startswith('test') 291 return m['method'].startswith('test')
344 292
(...skipping 91 matching lines...) Expand 10 before | Expand all | Expand 10 after
436 This will include text to disambiguate between tests for which GetTestName 384 This will include text to disambiguate between tests for which GetTestName
437 would return the same name. 385 would return the same name.
438 386
439 Args: 387 Args:
440 test: the instrumentation test dict. 388 test: the instrumentation test dict.
441 sep: the character(s) that should join the class name and the method name. 389 sep: the character(s) that should join the class name and the method name.
442 Returns: 390 Returns:
443 The unique test name as a string. 391 The unique test name as a string.
444 """ 392 """
445 display_name = GetTestName(test, sep=sep) 393 display_name = GetTestName(test, sep=sep)
446 if 'flags' in test: 394 if 'flags' in test and test['flags'] and test['flags'][0]:
mikecase (-- gone --) 2017/07/11 18:16:18 You could do something like... if test.get('flags
the real yoland 2017/07/13 23:49:19 Done
447 flags = test['flags'] 395 display_name = '%s with %s' % (display_name, ' '.join(test['flags']))
jbudorick 2017/07/13 18:45:53 Why does the if statement check the first element
the real yoland 2017/07/13 23:49:19 because if commandline annotation is set as @Comma
448 if flags.add:
449 display_name = '%s with {%s}' % (display_name, ' '.join(flags.add))
450 if flags.remove:
451 display_name = '%s without {%s}' % (display_name, ' '.join(flags.remove))
452 return display_name 396 return display_name
453 397
454 398
455 class InstrumentationTestInstance(test_instance.TestInstance): 399 class InstrumentationTestInstance(test_instance.TestInstance):
456 400
457 def __init__(self, args, data_deps_delegate, error_func): 401 def __init__(self, args, data_deps_delegate, error_func):
458 super(InstrumentationTestInstance, self).__init__() 402 super(InstrumentationTestInstance, self).__init__()
459 403
460 self._additional_apks = [] 404 self._additional_apks = []
461 self._apk_under_test = None 405 self._apk_under_test = None
(...skipping 105 matching lines...) Expand 10 before | Expand all | Expand 10 after
567 error_func('Unable to find test APK: %s' % self._test_apk.path) 511 error_func('Unable to find test APK: %s' % self._test_apk.path)
568 if not self._test_jar: 512 if not self._test_jar:
569 logging.warning('Test jar not specified. Test runner will not have ' 513 logging.warning('Test jar not specified. Test runner will not have '
570 'Java annotation info available. May not handle test ' 514 'Java annotation info available. May not handle test '
571 'timeouts correctly.') 515 'timeouts correctly.')
572 elif not os.path.exists(self._test_jar): 516 elif not os.path.exists(self._test_jar):
573 error_func('Unable to find test JAR: %s' % self._test_jar) 517 error_func('Unable to find test JAR: %s' % self._test_jar)
574 518
575 self._test_package = self._test_apk.GetPackageName() 519 self._test_package = self._test_apk.GetPackageName()
576 all_instrumentations = self._test_apk.GetAllInstrumentations() 520 all_instrumentations = self._test_apk.GetAllInstrumentations()
577 junit3_runners = [ 521 test_runners = [
578 x for x in all_instrumentations if ('true' not in x.get( 522 x for x in all_instrumentations if ('true' not in x.get(
579 'chromium-junit4', ''))] 523 'chromium-junit4', ''))]
580 junit4_runners = [ 524 test_runners_junit4 = [
581 x for x in all_instrumentations if ('true' in x.get( 525 x for x in all_instrumentations if ('true' in x.get(
582 'chromium-junit4', ''))] 526 'chromium-junit4', ''))]
583 527
584 if len(junit3_runners) > 1: 528 if len(test_runners) > 1:
585 logging.warning('This test apk has more than one JUnit3 instrumentation') 529 logging.warning('This test apk has more than one JUnit3 instrumentation')
586 if len(junit4_runners) > 1: 530 if len(test_runners_junit4) > 1:
587 logging.warning('This test apk has more than one JUnit4 instrumentation') 531 logging.warning('This test apk has more than one JUnit4 instrumentation')
588 532
589 self._test_runner = ( 533 self._test_runner = (
590 junit3_runners[0]['android:name'] if junit3_runners else 534 test_runners[0]['android:name'] if test_runners else
591 self.test_apk.GetInstrumentationName()) 535 self.test_apk.GetInstrumentationName())
592 self._test_runner_junit4 = ( 536 self._test_runner_junit4 = (
593 junit4_runners[0]['android:name'] if junit4_runners else None) 537 test_runners_junit4[0]['android:name'] if test_runners_junit4 else None)
594 538
595 self._package_info = None 539 self._package_info = None
596 if self._apk_under_test: 540 if self._apk_under_test:
597 package_under_test = self._apk_under_test.GetPackageName() 541 package_under_test = self._apk_under_test.GetPackageName()
598 for package_info in constants.PACKAGE_INFO.itervalues(): 542 for package_info in constants.PACKAGE_INFO.itervalues():
599 if package_under_test == package_info.package: 543 if package_under_test == package_info.package:
600 self._package_info = package_info 544 self._package_info = package_info
601 break 545 break
602 if not self._package_info: 546 if not self._package_info:
603 logging.warning('Unable to find package info for %s', self._test_package) 547 logging.warning('Unable to find package info for %s', self._test_package)
(...skipping 217 matching lines...) Expand 10 before | Expand all | Expand 10 after
821 self._data_deps_delegate(self._runtime_deps_path)) 765 self._data_deps_delegate(self._runtime_deps_path))
822 766
823 def GetDataDependencies(self): 767 def GetDataDependencies(self):
824 return self._data_deps 768 return self._data_deps
825 769
826 def GetTests(self): 770 def GetTests(self):
827 if self.test_jar: 771 if self.test_jar:
828 tests = GetAllTestsFromJar(self.test_jar) 772 tests = GetAllTestsFromJar(self.test_jar)
829 else: 773 else:
830 tests = GetAllTestsFromApk(self.test_apk.path) 774 tests = GetAllTestsFromApk(self.test_apk.path)
831 inflated_tests = self._ParametrizeTestsWithFlags(self._InflateTests(tests)) 775 inflated_tests = self._ParameterizeTestsWithFlags(self._InflateTests(tests))
832 if self._test_runner_junit4 is None and any( 776 if self._test_runner_junit4 is None and any(
833 t['is_junit4'] for t in inflated_tests): 777 t['is_junit4'] for t in inflated_tests):
834 raise MissingJUnit4RunnerException() 778 raise MissingJUnit4RunnerException()
835 filtered_tests = FilterTests( 779 filtered_tests = FilterTests(
836 inflated_tests, self._test_filter, self._annotations, 780 inflated_tests, self._test_filter, self._annotations,
837 self._excluded_annotations) 781 self._excluded_annotations)
838 if self._test_filter and not filtered_tests: 782 if self._test_filter and not filtered_tests:
839 for t in inflated_tests: 783 for t in inflated_tests:
840 logging.debug(' %s', GetUniqueTestName(t)) 784 logging.debug(' %s', GetUniqueTestName(t))
841 raise UnmatchedFilterException(self._test_filter) 785 raise UnmatchedFilterException(self._test_filter)
842 return filtered_tests 786 return filtered_tests
843 787
844 # pylint: disable=no-self-use 788 # pylint: disable=no-self-use
845 def _InflateTests(self, tests): 789 def _InflateTests(self, tests):
846 inflated_tests = [] 790 inflated_tests = []
847 for c in tests: 791 for c in tests:
848 for m in c['methods']: 792 for m in c['methods']:
849 a = dict(c['annotations']) 793 a = dict(c['annotations'])
850 a.update(m['annotations']) 794 a.update(m['annotations'])
851 inflated_tests.append({ 795 inflated_tests.append({
852 'class': c['class'], 796 'class': c['class'],
853 'method': m['method'], 797 'method': m['method'],
854 'annotations': a, 798 'annotations': a,
855 'is_junit4': c['superclass'] == 'java.lang.Object' 799 'is_junit4': c['superclass'] == 'java.lang.Object'
856 }) 800 })
857 return inflated_tests 801 return inflated_tests
858 802
859 def _ParametrizeTestsWithFlags(self, tests): 803 def _ParameterizeTestsWithFlags(self, tests):
860 new_tests = [] 804 new_tests = []
861 for t in tests: 805 for t in tests:
862 parameters = ParseCommandLineFlagParameters(t['annotations']) 806 annotations = t['annotations']
807 parameters = None
808 if annotations.get(_COMMANDLINE_PARAMETERIZATION):
809 parameters = (
810 annotations[_COMMANDLINE_PARAMETERIZATION]['value'] if
jbudorick 2017/07/13 18:45:53 nit: format this as annotations... if ... e
the real yoland 2017/07/13 23:49:18 Done
811 _SKIP_PARAMETERIZATION not in annotations else None)
863 if parameters: 812 if parameters:
864 t['flags'] = parameters[0] 813 t['flags'] = [parameters[0]]
865 for p in parameters[1:]: 814 for p in parameters[1:]:
866 parameterized_t = copy.copy(t) 815 parameterized_t = copy.copy(t)
867 parameterized_t['flags'] = p 816 parameterized_t['flags'] = [p]
868 new_tests.append(parameterized_t) 817 new_tests.append(parameterized_t)
869 return tests + new_tests 818 return tests + new_tests
870 819
871 def GetDriverEnvironmentVars( 820 def GetDriverEnvironmentVars(
872 self, test_list=None, test_list_file_path=None): 821 self, test_list=None, test_list_file_path=None):
873 env = { 822 env = {
874 _EXTRA_DRIVER_TARGET_PACKAGE: self.test_package, 823 _EXTRA_DRIVER_TARGET_PACKAGE: self.test_package,
875 _EXTRA_DRIVER_TARGET_CLASS: self.test_runner, 824 _EXTRA_DRIVER_TARGET_CLASS: self.test_runner,
876 _EXTRA_TIMEOUT_SCALE: self._timeout_scale, 825 _EXTRA_TIMEOUT_SCALE: self._timeout_scale,
877 } 826 }
(...skipping 13 matching lines...) Expand all
891 840
892 @staticmethod 841 @staticmethod
893 def GenerateTestResults( 842 def GenerateTestResults(
894 result_code, result_bundle, statuses, start_ms, duration_ms): 843 result_code, result_bundle, statuses, start_ms, duration_ms):
895 return GenerateTestResults(result_code, result_bundle, statuses, 844 return GenerateTestResults(result_code, result_bundle, statuses,
896 start_ms, duration_ms) 845 start_ms, duration_ms)
897 846
898 #override 847 #override
899 def TearDown(self): 848 def TearDown(self):
900 pass 849 pass
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698