| Index: build/android/pylib/instrumentation/instrumentation_test_instance.py
|
| diff --git a/build/android/pylib/instrumentation/instrumentation_test_instance.py b/build/android/pylib/instrumentation/instrumentation_test_instance.py
|
| index 12fe4309f298615ab0bfbec3e25358609875ac48..4d1f6bceeeccfab7563e19cf19b2e87a8939cef9 100644
|
| --- a/build/android/pylib/instrumentation/instrumentation_test_instance.py
|
| +++ b/build/android/pylib/instrumentation/instrumentation_test_instance.py
|
| @@ -59,6 +59,10 @@ class MissingSizeAnnotationError(Exception):
|
| ', '.join('@' + a for a in _VALID_ANNOTATIONS))
|
|
|
|
|
| +class ProguardPickleException(Exception):
|
| + pass
|
| +
|
| +
|
| # TODO(jbudorick): Make these private class methods of
|
| # InstrumentationTestInstance once the instrumentation test_runner is
|
| # deprecated.
|
| @@ -198,6 +202,154 @@ def ParseCommandLineFlagParameters(annotations):
|
| return result if result else None
|
|
|
|
|
| +def FilterTests(tests, test_filter=None, annotations=None,
|
| + excluded_annotations=None):
|
| + """Filter a list of tests
|
| +
|
| + Args:
|
| + tests: a list of tests. e.g. [
|
| + {'annotations": {}, 'class': 'com.example.TestA', 'methods':[]},
|
| + {'annotations": {}, 'class': 'com.example.TestB', 'methods':[]}]
|
| + test_filter: googletest-style filter string.
|
| + annotations: a dict of wanted annotations for test methods.
|
| + exclude_annotations: a dict of annotations to exclude.
|
| +
|
| + Return:
|
| + A list of filtered tests
|
| + """
|
| + def gtest_filter(c, m):
|
| + if not test_filter:
|
| + return True
|
| + # Allow fully-qualified name as well as an omitted package.
|
| + names = ['%s.%s' % (c['class'], m['method']),
|
| + '%s.%s' % (c['class'].split('.')[-1], m['method'])]
|
| + return unittest_util.FilterTestNames(names, test_filter)
|
| +
|
| + def annotation_filter(all_annotations):
|
| + if not annotations:
|
| + return True
|
| + return any_annotation_matches(annotations, all_annotations)
|
| +
|
| + def excluded_annotation_filter(all_annotations):
|
| + if not excluded_annotations:
|
| + return True
|
| + return not any_annotation_matches(excluded_annotations,
|
| + all_annotations)
|
| +
|
| + def any_annotation_matches(filter_annotations, all_annotations):
|
| + return any(
|
| + ak in all_annotations
|
| + and annotation_value_matches(av, all_annotations[ak])
|
| + for ak, av in filter_annotations.iteritems())
|
| +
|
| + def annotation_value_matches(filter_av, av):
|
| + if filter_av is None:
|
| + return True
|
| + elif isinstance(av, dict):
|
| + return filter_av in av['value']
|
| + elif isinstance(av, list):
|
| + return filter_av in av
|
| + return filter_av == av
|
| +
|
| + filtered_classes = []
|
| + for c in tests:
|
| + filtered_methods = []
|
| + for m in c['methods']:
|
| + # Gtest filtering
|
| + if not gtest_filter(c, m):
|
| + continue
|
| +
|
| + all_annotations = dict(c['annotations'])
|
| + all_annotations.update(m['annotations'])
|
| +
|
| + # Enforce that all tests declare their size.
|
| + if not any(a in _VALID_ANNOTATIONS for a in all_annotations):
|
| + raise MissingSizeAnnotationError('%s.%s' % (c['class'], m['method']))
|
| +
|
| + if (not annotation_filter(all_annotations)
|
| + or not excluded_annotation_filter(all_annotations)):
|
| + continue
|
| +
|
| + filtered_methods.append(m)
|
| +
|
| + if filtered_methods:
|
| + filtered_class = dict(c)
|
| + filtered_class['methods'] = filtered_methods
|
| + filtered_classes.append(filtered_class)
|
| +
|
| + return filtered_classes
|
| +
|
| +def GetAllTests(test_jar):
|
| + pickle_path = '%s-proguard.pickle' % test_jar
|
| + try:
|
| + tests = _GetTestsFromPickle(pickle_path, test_jar)
|
| + except ProguardPickleException as e:
|
| + logging.info('Could not get tests from pickle: %s', e)
|
| + logging.info('Getting tests from JAR via proguard.')
|
| + tests = _GetTestsFromProguard(test_jar)
|
| + _SaveTestsToPickle(pickle_path, test_jar, tests)
|
| + return tests
|
| +
|
| +
|
| +def _GetTestsFromPickle(pickle_path, jar_path):
|
| + if not os.path.exists(pickle_path):
|
| + raise ProguardPickleException('%s does not exist.' % pickle_path)
|
| + if os.path.getmtime(pickle_path) <= os.path.getmtime(jar_path):
|
| + raise ProguardPickleException(
|
| + '%s newer than %s.' % (jar_path, pickle_path))
|
| +
|
| + with open(pickle_path, 'r') as pickle_file:
|
| + pickle_data = pickle.loads(pickle_file.read())
|
| + jar_md5 = md5sum.CalculateHostMd5Sums(jar_path)[jar_path]
|
| +
|
| + if pickle_data['VERSION'] != _PICKLE_FORMAT_VERSION:
|
| + raise ProguardPickleException('PICKLE_FORMAT_VERSION has changed.')
|
| + if pickle_data['JAR_MD5SUM'] != jar_md5:
|
| + raise ProguardPickleException('JAR file MD5 sum differs.')
|
| + return pickle_data['TEST_METHODS']
|
| +
|
| +
|
| +def _GetTestsFromProguard(jar_path):
|
| + p = proguard.Dump(jar_path)
|
| + class_lookup = dict((c['class'], c) for c in p['classes'])
|
| +
|
| + def is_test_class(c):
|
| + return c['class'].endswith('Test')
|
| +
|
| + def is_test_method(m):
|
| + return m['method'].startswith('test')
|
| +
|
| + def recursive_class_annotations(c):
|
| + s = c['superclass']
|
| + if s in class_lookup:
|
| + a = recursive_class_annotations(class_lookup[s])
|
| + else:
|
| + a = {}
|
| + a.update(c['annotations'])
|
| + return a
|
| +
|
| + def stripped_test_class(c):
|
| + return {
|
| + 'class': c['class'],
|
| + 'annotations': recursive_class_annotations(c),
|
| + 'methods': [m for m in c['methods'] if is_test_method(m)],
|
| + }
|
| +
|
| + return [stripped_test_class(c) for c in p['classes']
|
| + if is_test_class(c)]
|
| +
|
| +
|
| +def _SaveTestsToPickle(pickle_path, jar_path, tests):
|
| + jar_md5 = md5sum.CalculateHostMd5Sums(jar_path)[jar_path]
|
| + pickle_data = {
|
| + 'VERSION': _PICKLE_FORMAT_VERSION,
|
| + 'JAR_MD5SUM': jar_md5,
|
| + 'TEST_METHODS': tests,
|
| + }
|
| + with open(pickle_path, 'w') as pickle_file:
|
| + pickle.dump(pickle_data, pickle_file)
|
| +
|
| +
|
| class InstrumentationTestInstance(test_instance.TestInstance):
|
|
|
| def __init__(self, args, isolate_delegate, error_func):
|
| @@ -294,6 +446,7 @@ class InstrumentationTestInstance(test_instance.TestInstance):
|
| for package_info in constants.PACKAGE_INFO.itervalues():
|
| if package_under_test == package_info.package:
|
| self._package_info = package_info
|
| + break
|
| if not self._package_info:
|
| logging.warning('Unable to find package info for %s', self._test_package)
|
|
|
| @@ -493,144 +646,12 @@ class InstrumentationTestInstance(test_instance.TestInstance):
|
| return self._data_deps
|
|
|
| def GetTests(self):
|
| - pickle_path = '%s-proguard.pickle' % self.test_jar
|
| - try:
|
| - tests = self._GetTestsFromPickle(pickle_path, self.test_jar)
|
| - except self.ProguardPickleException as e:
|
| - logging.info('Getting tests from JAR via proguard. (%s)', str(e))
|
| - tests = self._GetTestsFromProguard(self.test_jar)
|
| - self._SaveTestsToPickle(pickle_path, self.test_jar, tests)
|
| - return self._ParametrizeTestsWithFlags(
|
| - self._InflateTests(self._FilterTests(tests)))
|
| -
|
| - class ProguardPickleException(Exception):
|
| - pass
|
| -
|
| - def _GetTestsFromPickle(self, pickle_path, jar_path):
|
| - if not os.path.exists(pickle_path):
|
| - raise self.ProguardPickleException('%s does not exist.' % pickle_path)
|
| - if os.path.getmtime(pickle_path) <= os.path.getmtime(jar_path):
|
| - raise self.ProguardPickleException(
|
| - '%s newer than %s.' % (jar_path, pickle_path))
|
| -
|
| - with open(pickle_path, 'r') as pickle_file:
|
| - pickle_data = pickle.loads(pickle_file.read())
|
| - jar_md5 = md5sum.CalculateHostMd5Sums(jar_path)[jar_path]
|
| -
|
| - try:
|
| - if pickle_data['VERSION'] != _PICKLE_FORMAT_VERSION:
|
| - raise self.ProguardPickleException('PICKLE_FORMAT_VERSION has changed.')
|
| - if pickle_data['JAR_MD5SUM'] != jar_md5:
|
| - raise self.ProguardPickleException('JAR file MD5 sum differs.')
|
| - return pickle_data['TEST_METHODS']
|
| - except TypeError as e:
|
| - logging.error(pickle_data)
|
| - raise self.ProguardPickleException(str(e))
|
| + tests = GetAllTests(self.test_jar)
|
| + filtered_tests = FilterTests(
|
| + tests, self._test_filter, self._annotations, self._excluded_annotations)
|
| + return self._ParametrizeTestsWithFlags(self._InflateTests(filtered_tests))
|
|
|
| # pylint: disable=no-self-use
|
| - def _GetTestsFromProguard(self, jar_path):
|
| - p = proguard.Dump(jar_path)
|
| -
|
| - def is_test_class(c):
|
| - return c['class'].endswith('Test')
|
| -
|
| - def is_test_method(m):
|
| - return m['method'].startswith('test')
|
| -
|
| - class_lookup = dict((c['class'], c) for c in p['classes'])
|
| - def recursive_get_class_annotations(c):
|
| - s = c['superclass']
|
| - if s in class_lookup:
|
| - a = recursive_get_class_annotations(class_lookup[s])
|
| - else:
|
| - a = {}
|
| - a.update(c['annotations'])
|
| - return a
|
| -
|
| - def stripped_test_class(c):
|
| - return {
|
| - 'class': c['class'],
|
| - 'annotations': recursive_get_class_annotations(c),
|
| - 'methods': [m for m in c['methods'] if is_test_method(m)],
|
| - }
|
| -
|
| - return [stripped_test_class(c) for c in p['classes']
|
| - if is_test_class(c)]
|
| -
|
| - def _SaveTestsToPickle(self, pickle_path, jar_path, tests):
|
| - jar_md5 = md5sum.CalculateHostMd5Sums(jar_path)[jar_path]
|
| - pickle_data = {
|
| - 'VERSION': _PICKLE_FORMAT_VERSION,
|
| - 'JAR_MD5SUM': jar_md5,
|
| - 'TEST_METHODS': tests,
|
| - }
|
| - with open(pickle_path, 'w') as pickle_file:
|
| - pickle.dump(pickle_data, pickle_file)
|
| -
|
| - def _FilterTests(self, tests):
|
| -
|
| - def gtest_filter(c, m):
|
| - if not self._test_filter:
|
| - return True
|
| - # Allow fully-qualified name as well as an omitted package.
|
| - names = ['%s.%s' % (c['class'], m['method']),
|
| - '%s.%s' % (c['class'].split('.')[-1], m['method'])]
|
| - return unittest_util.FilterTestNames(names, self._test_filter)
|
| -
|
| - def annotation_filter(all_annotations):
|
| - if not self._annotations:
|
| - return True
|
| - return any_annotation_matches(self._annotations, all_annotations)
|
| -
|
| - def excluded_annotation_filter(all_annotations):
|
| - if not self._excluded_annotations:
|
| - return True
|
| - return not any_annotation_matches(self._excluded_annotations,
|
| - all_annotations)
|
| -
|
| - def any_annotation_matches(filter_annotations, all_annotations):
|
| - return any(
|
| - ak in all_annotations
|
| - and annotation_value_matches(av, all_annotations[ak])
|
| - for ak, av in filter_annotations.iteritems())
|
| -
|
| - def annotation_value_matches(filter_av, av):
|
| - if filter_av is None:
|
| - return True
|
| - elif isinstance(av, dict):
|
| - return filter_av in av['value']
|
| - elif isinstance(av, list):
|
| - return filter_av in av
|
| - return filter_av == av
|
| -
|
| - filtered_classes = []
|
| - for c in tests:
|
| - filtered_methods = []
|
| - for m in c['methods']:
|
| - # Gtest filtering
|
| - if not gtest_filter(c, m):
|
| - continue
|
| -
|
| - all_annotations = dict(c['annotations'])
|
| - all_annotations.update(m['annotations'])
|
| -
|
| - # Enforce that all tests declare their size.
|
| - if not any(a in _VALID_ANNOTATIONS for a in all_annotations):
|
| - raise MissingSizeAnnotationError('%s.%s' % (c['class'], m['method']))
|
| -
|
| - if (not annotation_filter(all_annotations)
|
| - or not excluded_annotation_filter(all_annotations)):
|
| - continue
|
| -
|
| - filtered_methods.append(m)
|
| -
|
| - if filtered_methods:
|
| - filtered_class = dict(c)
|
| - filtered_class['methods'] = filtered_methods
|
| - filtered_classes.append(filtered_class)
|
| -
|
| - return filtered_classes
|
| -
|
| def _InflateTests(self, tests):
|
| inflated_tests = []
|
| for c in tests:
|
| @@ -687,4 +708,3 @@ class InstrumentationTestInstance(test_instance.TestInstance):
|
| def TearDown(self):
|
| if self._isolate_delegate:
|
| self._isolate_delegate.Clear()
|
| -
|
|
|