Improve android test filtering
If you look at these two patchsets the only difference is this change https://crrev.com/c/3089797/16 https://crrev.com/c/3089797/17 However 17 fails without it due to a timeout Bug: 1241651 Change-Id: I95b27b8fe4831e170ee7c9f5ae22435b2425c885 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3111092 Commit-Queue: Struan Shrimpton <sshrimp@google.com> Reviewed-by: Gregory Guterman <guterman@google.com> Reviewed-by: Dirk Pranke <dpranke@google.com> Cr-Commit-Position: refs/heads/main@{#914856}
This commit is contained in:

committed by
Chromium LUCI CQ

parent
3e236a2fed
commit
19d16a4bc9
build/android/pylib/instrumentation
@ -242,39 +242,147 @@ def FilterTests(tests, filter_str=None, annotations=None,
|
||||
{'annotations": {}, 'class': 'com.example.TestB', 'method':'test2'}]
|
||||
filter_str: googletest-style filter string.
|
||||
annotations: a dict of wanted annotations for test methods.
|
||||
exclude_annotations: a dict of annotations to exclude.
|
||||
excluded_annotations: a dict of annotations to exclude.
|
||||
|
||||
Return:
|
||||
A list of filtered tests
|
||||
"""
|
||||
def gtest_filter(t):
|
||||
if not filter_str:
|
||||
return True
|
||||
|
||||
def test_names_from_pattern(combined_pattern, test_names):
|
||||
patterns = combined_pattern.split(':')
|
||||
|
||||
hashable_patterns = set()
|
||||
filename_patterns = []
|
||||
for pattern in patterns:
|
||||
if ('*' in pattern or '?' in pattern or '[' in pattern):
|
||||
filename_patterns.append(pattern)
|
||||
else:
|
||||
hashable_patterns.add(pattern)
|
||||
|
||||
filter_test_names = set(
|
||||
unittest_util.FilterTestNames(test_names, ':'.join(
|
||||
filename_patterns))) if len(filename_patterns) > 0 else set()
|
||||
|
||||
for test_name in test_names:
|
||||
if test_name in hashable_patterns:
|
||||
filter_test_names.add(test_name)
|
||||
|
||||
return filter_test_names
|
||||
|
||||
def get_test_names(test):
|
||||
test_names = set()
|
||||
# Allow fully-qualified name as well as an omitted package.
|
||||
unqualified_class_test = {
|
||||
'class': t['class'].split('.')[-1],
|
||||
'method': t['method']
|
||||
'class': test['class'].split('.')[-1],
|
||||
'method': test['method']
|
||||
}
|
||||
names = [
|
||||
GetTestName(t, sep='.'),
|
||||
GetTestName(unqualified_class_test, sep='.'),
|
||||
GetUniqueTestName(t, sep='.')
|
||||
]
|
||||
|
||||
if t['is_junit4']:
|
||||
names += [
|
||||
GetTestNameWithoutParameterPostfix(t, sep='.'),
|
||||
GetTestNameWithoutParameterPostfix(unqualified_class_test, sep='.')
|
||||
]
|
||||
test_name = GetTestName(test, sep='.')
|
||||
test_names.add(test_name)
|
||||
|
||||
unqualified_class_test_name = GetTestName(unqualified_class_test, sep='.')
|
||||
test_names.add(unqualified_class_test_name)
|
||||
|
||||
unique_test_name = GetUniqueTestName(test, sep='.')
|
||||
test_names.add(unique_test_name)
|
||||
|
||||
if test['is_junit4']:
|
||||
junit4_test_name = GetTestNameWithoutParameterPostfix(test, sep='.')
|
||||
test_names.add(junit4_test_name)
|
||||
|
||||
unqualified_junit4_test_name = \
|
||||
GetTestNameWithoutParameterPostfix(unqualified_class_test, sep='.')
|
||||
test_names.add(unqualified_junit4_test_name)
|
||||
return test_names
|
||||
|
||||
def get_tests_from_names(tests, test_names, tests_to_names):
|
||||
''' Returns the tests for which the given names apply
|
||||
|
||||
Args:
|
||||
tests: a list of tests. e.g. [
|
||||
{'annotations": {}, 'class': 'com.example.TestA', 'method':'test1'},
|
||||
{'annotations": {}, 'class': 'com.example.TestB', 'method':'test2'}]
|
||||
test_names: a collection of names determining tests to return.
|
||||
|
||||
Return:
|
||||
A list of tests that match the given test names
|
||||
'''
|
||||
filtered_tests = []
|
||||
for t in tests:
|
||||
current_test_names = tests_to_names[id(t)]
|
||||
|
||||
for current_test_name in current_test_names:
|
||||
if current_test_name in test_names:
|
||||
filtered_tests.append(t)
|
||||
break
|
||||
|
||||
return filtered_tests
|
||||
|
||||
def remove_tests_from_names(tests, remove_test_names, tests_to_names):
|
||||
''' Returns the tests from the given list with given names removed
|
||||
|
||||
Args:
|
||||
tests: a list of tests. e.g. [
|
||||
{'annotations": {}, 'class': 'com.example.TestA', 'method':'test1'},
|
||||
{'annotations": {}, 'class': 'com.example.TestB', 'method':'test2'}]
|
||||
remove_test_names: a collection of names determining tests to remove.
|
||||
tests_to_names: a dcitionary of test ids to a collection of applicable
|
||||
names for that test
|
||||
|
||||
Return:
|
||||
A list of tests that don't match the given test names
|
||||
'''
|
||||
filtered_tests = []
|
||||
|
||||
for t in tests:
|
||||
for name in tests_to_names[id(t)]:
|
||||
if name in remove_test_names:
|
||||
break
|
||||
else:
|
||||
filtered_tests.append(t)
|
||||
return filtered_tests
|
||||
|
||||
def gtests_filter(tests, combined_filter):
|
||||
''' Returns the tests after the filter_str has been applied
|
||||
|
||||
Args:
|
||||
tests: a list of tests. e.g. [
|
||||
{'annotations": {}, 'class': 'com.example.TestA', 'method':'test1'},
|
||||
{'annotations": {}, 'class': 'com.example.TestB', 'method':'test2'}]
|
||||
combined_filter: the filter string representing tests to exclude
|
||||
|
||||
Return:
|
||||
A list of tests that should still be included after the filter_str is
|
||||
applied to their names
|
||||
'''
|
||||
|
||||
if not combined_filter:
|
||||
return tests
|
||||
|
||||
# Collect all test names
|
||||
all_test_names = set()
|
||||
tests_to_names = {}
|
||||
for t in tests:
|
||||
tests_to_names[id(t)] = get_test_names(t)
|
||||
for name in tests_to_names[id(t)]:
|
||||
all_test_names.add(name)
|
||||
|
||||
pattern_groups = filter_str.split('-')
|
||||
if len(pattern_groups) > 1:
|
||||
negative_filter = pattern_groups[1]
|
||||
if unittest_util.FilterTestNames(names, negative_filter):
|
||||
return []
|
||||
negative_pattern = pattern_groups[1] if len(pattern_groups) > 1 else None
|
||||
positive_pattern = pattern_groups[0]
|
||||
|
||||
positive_filter = pattern_groups[0]
|
||||
return unittest_util.FilterTestNames(names, positive_filter)
|
||||
if positive_pattern:
|
||||
# Only use the test names that match the positive pattern
|
||||
positive_test_names = test_names_from_pattern(positive_pattern,
|
||||
all_test_names)
|
||||
tests = get_tests_from_names(tests, positive_test_names, tests_to_names)
|
||||
|
||||
if negative_pattern:
|
||||
# Remove any test the negative filter matches
|
||||
remove_names = test_names_from_pattern(negative_pattern, all_test_names)
|
||||
tests = remove_tests_from_names(tests, remove_names, tests_to_names)
|
||||
|
||||
return tests
|
||||
|
||||
def annotation_filter(all_annotations):
|
||||
if not annotations:
|
||||
@ -308,12 +416,8 @@ def FilterTests(tests, filter_str=None, annotations=None,
|
||||
return filter_av in av
|
||||
return filter_av == av
|
||||
|
||||
filtered_tests = []
|
||||
for t in tests:
|
||||
# Gtest filtering
|
||||
if not gtest_filter(t):
|
||||
continue
|
||||
|
||||
return_tests = []
|
||||
for t in gtests_filter(tests, filter_str):
|
||||
# Enforce that all tests declare their size.
|
||||
if not any(a in _VALID_ANNOTATIONS for a in t['annotations']):
|
||||
raise MissingSizeAnnotationError(GetTestName(t))
|
||||
@ -321,11 +425,9 @@ def FilterTests(tests, filter_str=None, annotations=None,
|
||||
if (not annotation_filter(t['annotations'])
|
||||
or not excluded_annotation_filter(t['annotations'])):
|
||||
continue
|
||||
return_tests.append(t)
|
||||
|
||||
filtered_tests.append(t)
|
||||
|
||||
return filtered_tests
|
||||
|
||||
return return_tests
|
||||
|
||||
# TODO(yolandyan): remove this once the tests are converted to junit4
|
||||
def GetAllTestsFromJar(test_jar):
|
||||
|
@ -198,6 +198,72 @@ class InstrumentationTestInstanceTest(unittest.TestCase):
|
||||
|
||||
self.assertEqual(actual_tests, expected_tests)
|
||||
|
||||
def testGetTests_simpleGtestPositiveAndNegativeFilter(self):
|
||||
o = self.createTestInstance()
|
||||
raw_tests = [{
|
||||
'annotations': {
|
||||
'Feature': {
|
||||
'value': ['Foo']
|
||||
}
|
||||
},
|
||||
'class':
|
||||
'org.chromium.test.SampleTest',
|
||||
'superclass':
|
||||
'java.lang.Object',
|
||||
'methods': [
|
||||
{
|
||||
'annotations': {
|
||||
'SmallTest': None
|
||||
},
|
||||
'method': 'testMethod1',
|
||||
},
|
||||
{
|
||||
'annotations': {
|
||||
'MediumTest': None
|
||||
},
|
||||
'method': 'testMethod2',
|
||||
},
|
||||
],
|
||||
}, {
|
||||
'annotations': {
|
||||
'Feature': {
|
||||
'value': ['Foo']
|
||||
}
|
||||
},
|
||||
'class':
|
||||
'org.chromium.test.SampleTest2',
|
||||
'superclass':
|
||||
'java.lang.Object',
|
||||
'methods': [{
|
||||
'annotations': {
|
||||
'SmallTest': None
|
||||
},
|
||||
'method': 'testMethod1',
|
||||
}],
|
||||
}]
|
||||
|
||||
expected_tests = [
|
||||
{
|
||||
'annotations': {
|
||||
'Feature': {
|
||||
'value': ['Foo']
|
||||
},
|
||||
'SmallTest': None,
|
||||
},
|
||||
'class': 'org.chromium.test.SampleTest',
|
||||
'is_junit4': True,
|
||||
'method': 'testMethod1',
|
||||
},
|
||||
]
|
||||
|
||||
o._test_filter = \
|
||||
'org.chromium.test.SampleTest.*-org.chromium.test.SampleTest.testMethod2'
|
||||
o._test_jar = 'path/to/test.jar'
|
||||
o._junit4_runner_class = 'J4Runner'
|
||||
actual_tests = o.ProcessRawTests(raw_tests)
|
||||
|
||||
self.assertEqual(actual_tests, expected_tests)
|
||||
|
||||
def testGetTests_simpleGtestUnqualifiedNameFilter(self):
|
||||
o = self.createTestInstance()
|
||||
raw_tests = [
|
||||
|
Reference in New Issue
Block a user