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

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

Issue 2963683002: Simplify CommandLine parameterizing J3 Instrumentation Tests (Closed)
Patch Set: address comments 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 = 'SkipCommandLineParameterization'
51 _PARAMETERIZED_TEST_SET_ANNOTATION = 'ParameterizedTest$Set' 50 _COMMANDLINE_PARAMETERIZATION = 'CommandLineParameter'
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 86 matching lines...) Expand 10 before | Expand all | Expand 10 after
306 pickle_path = '%s-dexdump.pickle' % test_apk 255 pickle_path = '%s-dexdump.pickle' % test_apk
307 try: 256 try:
308 tests = _GetTestsFromPickle(pickle_path, test_apk) 257 tests = _GetTestsFromPickle(pickle_path, test_apk)
309 except TestListPickleException as e: 258 except TestListPickleException as e:
310 logging.info('Could not get tests from pickle: %s', e) 259 logging.info('Could not get tests from pickle: %s', e)
311 logging.info('Getting tests from dex via dexdump.') 260 logging.info('Getting tests from dex via dexdump.')
312 tests = _GetTestsFromDexdump(test_apk) 261 tests = _GetTestsFromDexdump(test_apk)
313 _SaveTestsToPickle(pickle_path, test_apk, tests) 262 _SaveTestsToPickle(pickle_path, test_apk, tests)
314 return tests 263 return tests
315 264
316
317 def _GetTestsFromPickle(pickle_path, jar_path): 265 def _GetTestsFromPickle(pickle_path, jar_path):
318 if not os.path.exists(pickle_path): 266 if not os.path.exists(pickle_path):
319 raise TestListPickleException('%s does not exist.' % pickle_path) 267 raise TestListPickleException('%s does not exist.' % pickle_path)
320 if os.path.getmtime(pickle_path) <= os.path.getmtime(jar_path): 268 if os.path.getmtime(pickle_path) <= os.path.getmtime(jar_path):
321 raise TestListPickleException( 269 raise TestListPickleException(
322 '%s newer than %s.' % (jar_path, pickle_path)) 270 '%s newer than %s.' % (jar_path, pickle_path))
323 271
324 with open(pickle_path, 'r') as f: 272 with open(pickle_path, 'r') as f:
325 pickle_data = pickle.load(f) 273 pickle_data = pickle.load(f)
326 jar_md5 = md5sum.CalculateHostMd5Sums(jar_path)[jar_path] 274 jar_md5 = md5sum.CalculateHostMd5Sums(jar_path)[jar_path]
327 275
328 if pickle_data['VERSION'] != _PICKLE_FORMAT_VERSION: 276 if pickle_data['VERSION'] != _PICKLE_FORMAT_VERSION:
329 raise TestListPickleException('PICKLE_FORMAT_VERSION has changed.') 277 raise TestListPickleException('PICKLE_FORMAT_VERSION has changed.')
330 if pickle_data['JAR_MD5SUM'] != jar_md5: 278 if pickle_data['JAR_MD5SUM'] != jar_md5:
331 raise TestListPickleException('JAR file MD5 sum differs.') 279 raise TestListPickleException('JAR file MD5 sum differs.')
332 return pickle_data['TEST_METHODS'] 280 return pickle_data['TEST_METHODS']
333 281
334 282
283 # TODO(yolandyan): remove this once the test listing from java runner lands
335 def _GetTestsFromProguard(jar_path): 284 def _GetTestsFromProguard(jar_path):
336 p = proguard.Dump(jar_path) 285 p = proguard.Dump(jar_path)
337 class_lookup = dict((c['class'], c) for c in p['classes']) 286 class_lookup = dict((c['class'], c) for c in p['classes'])
338 287
339 def is_test_class(c): 288 def is_test_class(c):
340 return c['class'].endswith('Test') 289 return c['class'].endswith('Test')
341 290
342 def is_test_method(m): 291 def is_test_method(m):
343 return m['method'].startswith('test') 292 return m['method'].startswith('test')
344 293
(...skipping 91 matching lines...) Expand 10 before | Expand all | Expand 10 after
436 This will include text to disambiguate between tests for which GetTestName 385 This will include text to disambiguate between tests for which GetTestName
437 would return the same name. 386 would return the same name.
438 387
439 Args: 388 Args:
440 test: the instrumentation test dict. 389 test: the instrumentation test dict.
441 sep: the character(s) that should join the class name and the method name. 390 sep: the character(s) that should join the class name and the method name.
442 Returns: 391 Returns:
443 The unique test name as a string. 392 The unique test name as a string.
444 """ 393 """
445 display_name = GetTestName(test, sep=sep) 394 display_name = GetTestName(test, sep=sep)
446 if 'flags' in test: 395 if test.get('flags', [None])[0]:
447 flags = test['flags'] 396 display_name = '%s with %s' % (display_name, ' '.join(test['flags']))
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 397 return display_name
453 398
454 399
455 class InstrumentationTestInstance(test_instance.TestInstance): 400 class InstrumentationTestInstance(test_instance.TestInstance):
456 401
457 def __init__(self, args, data_deps_delegate, error_func): 402 def __init__(self, args, data_deps_delegate, error_func):
458 super(InstrumentationTestInstance, self).__init__() 403 super(InstrumentationTestInstance, self).__init__()
459 404
460 self._additional_apks = [] 405 self._additional_apks = []
461 self._apk_under_test = None 406 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) 512 error_func('Unable to find test APK: %s' % self._test_apk.path)
568 if not self._test_jar: 513 if not self._test_jar:
569 logging.warning('Test jar not specified. Test runner will not have ' 514 logging.warning('Test jar not specified. Test runner will not have '
570 'Java annotation info available. May not handle test ' 515 'Java annotation info available. May not handle test '
571 'timeouts correctly.') 516 'timeouts correctly.')
572 elif not os.path.exists(self._test_jar): 517 elif not os.path.exists(self._test_jar):
573 error_func('Unable to find test JAR: %s' % self._test_jar) 518 error_func('Unable to find test JAR: %s' % self._test_jar)
574 519
575 self._test_package = self._test_apk.GetPackageName() 520 self._test_package = self._test_apk.GetPackageName()
576 all_instrumentations = self._test_apk.GetAllInstrumentations() 521 all_instrumentations = self._test_apk.GetAllInstrumentations()
577 junit3_runners = [ 522 test_runners = [
578 x for x in all_instrumentations if ('true' not in x.get( 523 x for x in all_instrumentations if ('true' not in x.get(
579 'chromium-junit4', ''))] 524 'chromium-junit4', ''))]
580 junit4_runners = [ 525 test_runners_junit4 = [
581 x for x in all_instrumentations if ('true' in x.get( 526 x for x in all_instrumentations if ('true' in x.get(
582 'chromium-junit4', ''))] 527 'chromium-junit4', ''))]
583 528
584 if len(junit3_runners) > 1: 529 if len(test_runners) > 1:
585 logging.warning('This test apk has more than one JUnit3 instrumentation') 530 logging.warning('This test apk has more than one JUnit3 instrumentation')
586 if len(junit4_runners) > 1: 531 if len(test_runners_junit4) > 1:
587 logging.warning('This test apk has more than one JUnit4 instrumentation') 532 logging.warning('This test apk has more than one JUnit4 instrumentation')
588 533
589 self._test_runner = ( 534 self._test_runner = (
590 junit3_runners[0]['android:name'] if junit3_runners else 535 test_runners[0]['android:name'] if test_runners else
591 self.test_apk.GetInstrumentationName()) 536 self.test_apk.GetInstrumentationName())
592 self._test_runner_junit4 = ( 537 self._test_runner_junit4 = (
593 junit4_runners[0]['android:name'] if junit4_runners else None) 538 test_runners_junit4[0]['android:name'] if test_runners_junit4 else None)
594 539
595 self._package_info = None 540 self._package_info = None
596 if self._apk_under_test: 541 if self._apk_under_test:
597 package_under_test = self._apk_under_test.GetPackageName() 542 package_under_test = self._apk_under_test.GetPackageName()
598 for package_info in constants.PACKAGE_INFO.itervalues(): 543 for package_info in constants.PACKAGE_INFO.itervalues():
599 if package_under_test == package_info.package: 544 if package_under_test == package_info.package:
600 self._package_info = package_info 545 self._package_info = package_info
601 break 546 break
602 if not self._package_info: 547 if not self._package_info:
603 logging.warning('Unable to find package info for %s', self._test_package) 548 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)) 766 self._data_deps_delegate(self._runtime_deps_path))
822 767
823 def GetDataDependencies(self): 768 def GetDataDependencies(self):
824 return self._data_deps 769 return self._data_deps
825 770
826 def GetTests(self): 771 def GetTests(self):
827 if self.test_jar: 772 if self.test_jar:
828 tests = GetAllTestsFromJar(self.test_jar) 773 tests = GetAllTestsFromJar(self.test_jar)
829 else: 774 else:
830 tests = GetAllTestsFromApk(self.test_apk.path) 775 tests = GetAllTestsFromApk(self.test_apk.path)
831 inflated_tests = self._ParametrizeTestsWithFlags(self._InflateTests(tests)) 776 inflated_tests = self._ParameterizeTestsWithFlags(self._InflateTests(tests))
832 if self._test_runner_junit4 is None and any( 777 if self._test_runner_junit4 is None and any(
833 t['is_junit4'] for t in inflated_tests): 778 t['is_junit4'] for t in inflated_tests):
834 raise MissingJUnit4RunnerException() 779 raise MissingJUnit4RunnerException()
835 filtered_tests = FilterTests( 780 filtered_tests = FilterTests(
836 inflated_tests, self._test_filter, self._annotations, 781 inflated_tests, self._test_filter, self._annotations,
837 self._excluded_annotations) 782 self._excluded_annotations)
838 if self._test_filter and not filtered_tests: 783 if self._test_filter and not filtered_tests:
839 for t in inflated_tests: 784 for t in inflated_tests:
840 logging.debug(' %s', GetUniqueTestName(t)) 785 logging.debug(' %s', GetUniqueTestName(t))
841 raise UnmatchedFilterException(self._test_filter) 786 raise UnmatchedFilterException(self._test_filter)
842 return filtered_tests 787 return filtered_tests
843 788
844 # pylint: disable=no-self-use 789 # pylint: disable=no-self-use
845 def _InflateTests(self, tests): 790 def _InflateTests(self, tests):
846 inflated_tests = [] 791 inflated_tests = []
847 for c in tests: 792 for c in tests:
848 for m in c['methods']: 793 for m in c['methods']:
849 a = dict(c['annotations']) 794 a = dict(c['annotations'])
850 a.update(m['annotations']) 795 a.update(m['annotations'])
851 inflated_tests.append({ 796 inflated_tests.append({
852 'class': c['class'], 797 'class': c['class'],
853 'method': m['method'], 798 'method': m['method'],
854 'annotations': a, 799 'annotations': a,
855 'is_junit4': c['superclass'] == 'java.lang.Object' 800 'is_junit4': c['superclass'] == 'java.lang.Object'
856 }) 801 })
857 return inflated_tests 802 return inflated_tests
858 803
859 def _ParametrizeTestsWithFlags(self, tests): 804 def _ParameterizeTestsWithFlags(self, tests):
860 new_tests = [] 805 new_tests = []
861 for t in tests: 806 for t in tests:
862 parameters = ParseCommandLineFlagParameters(t['annotations']) 807 annotations = t['annotations']
808 parameters = None
809 if (annotations.get(_COMMANDLINE_PARAMETERIZATION)
810 and _SKIP_PARAMETERIZATION not in annotations):
811 parameters = annotations[_COMMANDLINE_PARAMETERIZATION]['value']
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