1 /* 2 * Copyright (C) 2021 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.tradefed.config.filter; 17 18 import com.android.tradefed.config.IConfiguration; 19 import com.android.tradefed.config.Option; 20 import com.android.tradefed.config.OptionClass; 21 import com.android.tradefed.log.LogUtil.CLog; 22 import com.android.tradefed.service.TradefedFeatureClient; 23 import com.android.tradefed.testtype.IRemoteTest; 24 import com.android.tradefed.testtype.ITestFilterReceiver; 25 import com.android.tradefed.testtype.suite.BaseTestSuite; 26 import com.android.tradefed.testtype.suite.SuiteTestFilter; 27 import com.android.tradefed.testtype.suite.TestMappingSuiteRunner; 28 29 import com.google.common.annotations.VisibleForTesting; 30 import com.google.common.base.Strings; 31 import com.proto.tradefed.feature.FeatureResponse; 32 import com.proto.tradefed.feature.PartResponse; 33 34 import java.util.ArrayList; 35 import java.util.Arrays; 36 import java.util.HashMap; 37 import java.util.LinkedHashSet; 38 import java.util.List; 39 import java.util.Set; 40 41 /** Filter options applied to the invocation. */ 42 @OptionClass(alias = "global-filters") 43 public final class GlobalTestFilter { 44 45 public static final String INCLUDE_FILTER_OPTION = "include-filter"; 46 public static final String EXCLUDE_FILTER_OPTION = "exclude-filter"; 47 public static final String STRICT_INCLUDE_FILTER_OPTION = "strict-include-filter"; 48 public static final String DELIMITER_NAME = "delimiter"; 49 50 @Option( 51 name = INCLUDE_FILTER_OPTION, 52 description = 53 "Filters applied to the invocation. Format: [abi] [module-name]" 54 + " [test-class][#method-name]") 55 private Set<String> mIncludeFilters = new LinkedHashSet<>(); 56 57 @Option( 58 name = EXCLUDE_FILTER_OPTION, 59 description = 60 "Filters applied to the invocation. Format: [abi] [module-name]" 61 + " [test-class][#method-name]") 62 private Set<String> mExcludeFilters = new LinkedHashSet<>(); 63 64 @Option( 65 name = STRICT_INCLUDE_FILTER_OPTION, 66 description = 67 "Filters applied to the invocation. Format: [abi] [module-name]" 68 + " [test-class][#method-name]. All other filters " 69 + "will be ignored to strictly run this set." 70 + "This is still best-effort as not all runners " 71 + "support filtering equally.") 72 private Set<String> mStrictIncludeFilters = new LinkedHashSet<>(); 73 74 @Option( 75 name = "disable-global-filters", 76 description = "Feature flag to enable the global filters") 77 private boolean mDisable = false; 78 79 private TradefedFeatureClient mClient; 80 private boolean mSetupDone = false; 81 GlobalTestFilter()82 public GlobalTestFilter() {} 83 84 @VisibleForTesting GlobalTestFilter(TradefedFeatureClient client)85 GlobalTestFilter(TradefedFeatureClient client) { 86 mClient = client; 87 } 88 89 /** Returns the Set of global include filters. */ getIncludeFilters()90 public Set<String> getIncludeFilters() { 91 return new LinkedHashSet<>(mIncludeFilters); 92 } 93 94 /** Returns the Set of global exclude filters. */ getExcludeFilters()95 public Set<String> getExcludeFilters() { 96 return new LinkedHashSet<>(mExcludeFilters); 97 } 98 99 /** Returns the Set of global strict include filters. */ getStrictIncludeFilters()100 public Set<String> getStrictIncludeFilters() { 101 return new LinkedHashSet<>(mStrictIncludeFilters); 102 } 103 addPreviousPassedTests(Set<String> previousPassed)104 public void addPreviousPassedTests(Set<String> previousPassed) { 105 if (!previousPassed.isEmpty()) { 106 CLog.d("Adding following exclusion to GlobalTestFilter: %s", previousPassed); 107 } 108 mExcludeFilters.addAll(previousPassed); 109 } 110 111 /** Initialize the global filters by passing them to the tests. */ setUpFilters(IConfiguration config, Set<String> demotedList)112 public void setUpFilters(IConfiguration config, Set<String> demotedList) { 113 if (mDisable) { 114 CLog.d("Global filters are disabled."); 115 return; 116 } 117 if (mSetupDone) { 118 CLog.d("Global filters already set."); 119 return; 120 } 121 CLog.d("Setting up global filters"); 122 // If it's a subprocess, fetch filters 123 if (config.getCommandOptions().getInvocationData().containsKey("subprocess")) { 124 populateGlobalFilters(); 125 } else { 126 if (!demotedList.isEmpty()) { 127 CLog.d("Adding demoted list to global filters: %s", demotedList); 128 mExcludeFilters.addAll(demotedList); 129 } 130 } 131 // Apply filters 132 if (mStrictIncludeFilters.isEmpty()) { 133 for (IRemoteTest test : config.getTests()) { 134 if (test instanceof BaseTestSuite) { 135 ((BaseTestSuite) test).setIncludeFilter(mIncludeFilters); 136 ((BaseTestSuite) test).setExcludeFilter(mExcludeFilters); 137 } else if (test instanceof ITestFilterReceiver) { 138 ITestFilterReceiver filterableTest = (ITestFilterReceiver) test; 139 applyFiltersToTest(filterableTest); 140 } 141 } 142 } else { 143 CLog.d("Strict include filters specified: %s", mStrictIncludeFilters); 144 for (IRemoteTest test : config.getTests()) { 145 if (test instanceof BaseTestSuite) { 146 applyGlobalStrictFilters((BaseTestSuite) test, mStrictIncludeFilters); 147 } else if (test instanceof ITestFilterReceiver) { 148 ITestFilterReceiver filterableTest = (ITestFilterReceiver) test; 149 applyFiltersToTest(filterableTest); 150 } 151 } 152 } 153 mSetupDone = true; 154 } 155 applyGlobalStrictFilters(BaseTestSuite test, Set<String> strictFilters)156 public static void applyGlobalStrictFilters(BaseTestSuite test, Set<String> strictFilters) { 157 ((BaseTestSuite) test).clearExcludeFilter(); 158 ((BaseTestSuite) test).clearIncludeFilter(); 159 ((BaseTestSuite) test).setIncludeFilter(strictFilters); 160 if (test instanceof TestMappingSuiteRunner) { 161 ((TestMappingSuiteRunner) test).clearTestGroup(); 162 ((TestMappingSuiteRunner) test).clearKeywords(); 163 ((TestMappingSuiteRunner) test).clearTestMappingPaths(); 164 } 165 } 166 167 /** Apply the global filters to the test. */ applyFiltersToTest(ITestFilterReceiver filterableTest)168 public void applyFiltersToTest(ITestFilterReceiver filterableTest) { 169 if (mStrictIncludeFilters.isEmpty()) { 170 Set<String> includeFilters = new LinkedHashSet<>(filterableTest.getIncludeFilters()); 171 includeFilters.addAll(filtersFromGlobal(mIncludeFilters)); 172 filterableTest.clearIncludeFilters(); 173 filterableTest.addAllIncludeFilters(includeFilters); 174 175 Set<String> excludeFilters = new LinkedHashSet<>(filterableTest.getExcludeFilters()); 176 excludeFilters.addAll(filtersFromGlobal(mExcludeFilters)); 177 filterableTest.clearExcludeFilters(); 178 filterableTest.addAllExcludeFilters(excludeFilters); 179 } else { 180 filterableTest.clearExcludeFilters(); 181 filterableTest.clearIncludeFilters(); 182 filterableTest.addAllIncludeFilters(filtersFromGlobal(mStrictIncludeFilters)); 183 } 184 } 185 186 /** Apply global filters to the suite */ applyFiltersToTest(BaseTestSuite suite)187 public void applyFiltersToTest(BaseTestSuite suite) { 188 if (mStrictIncludeFilters.isEmpty()) { 189 suite.setIncludeFilter(mIncludeFilters); 190 suite.setExcludeFilter(mExcludeFilters); 191 } else { 192 CLog.d("Applying strict filters to suite."); 193 suite.clearExcludeFilter(); 194 suite.clearIncludeFilter(); 195 suite.setIncludeFilter(mStrictIncludeFilters); 196 if (suite instanceof TestMappingSuiteRunner) { 197 ((TestMappingSuiteRunner) suite).clearTestGroup(); 198 ((TestMappingSuiteRunner) suite).clearKeywords(); 199 ((TestMappingSuiteRunner) suite).clearTestMappingPaths(); 200 } 201 } 202 suite.reevaluateFilters(); 203 } 204 205 /** Fetch and populate global filters if needed. */ populateGlobalFilters()206 private void populateGlobalFilters() { 207 if (mClient == null) { 208 mClient = new TradefedFeatureClient(); 209 } 210 try { 211 FeatureResponse globalFilters = 212 mClient.triggerFeature( 213 GlobalFilterGetter.GLOBAL_FILTER_GETTER, new HashMap<>()); 214 if (globalFilters.hasMultiPartResponse()) { 215 String delimiter = ","; 216 for (PartResponse rep : 217 globalFilters.getMultiPartResponse().getResponsePartList()) { 218 if (rep.getKey().equals(DELIMITER_NAME)) { 219 delimiter = rep.getValue().trim(); 220 } 221 } 222 223 for (PartResponse rep : 224 globalFilters.getMultiPartResponse().getResponsePartList()) { 225 if (rep.getKey().equals(INCLUDE_FILTER_OPTION)) { 226 mIncludeFilters.addAll(splitStringFilters(delimiter, rep.getValue())); 227 } else if (rep.getKey().equals(EXCLUDE_FILTER_OPTION)) { 228 mExcludeFilters.addAll(splitStringFilters(delimiter, rep.getValue())); 229 } else if (rep.getKey().equals(STRICT_INCLUDE_FILTER_OPTION)) { 230 mStrictIncludeFilters.addAll(splitStringFilters(delimiter, rep.getValue())); 231 } else if (rep.getKey().equals(DELIMITER_NAME)) { 232 // Ignore 233 } else { 234 CLog.w("Unexpected response key '%s' for global filters", rep.getKey()); 235 } 236 } 237 } else { 238 CLog.w("Unexpected response for global filters: %s", globalFilters); 239 } 240 } finally { 241 mClient.close(); 242 } 243 } 244 splitStringFilters(String delimiter, String value)245 private List<String> splitStringFilters(String delimiter, String value) { 246 if (Strings.isNullOrEmpty(value)) { 247 return new ArrayList<String>(); 248 } 249 return Arrays.asList(value.split(delimiter)); 250 } 251 filtersFromGlobal(Set<String> filters)252 private Set<String> filtersFromGlobal(Set<String> filters) { 253 Set<String> globalFilters = new LinkedHashSet<>(); 254 filters.forEach( 255 f -> { 256 SuiteTestFilter suiteFilter = SuiteTestFilter.createFrom(f); 257 if (!Strings.isNullOrEmpty(suiteFilter.getTest())) { 258 globalFilters.add(suiteFilter.getTest()); 259 } else if (!Strings.isNullOrEmpty(suiteFilter.getName())) { 260 // For non-suite, if the test isn't present due to no module, fallback to 261 // name 262 globalFilters.add(suiteFilter.getName()); 263 } 264 }); 265 return globalFilters; 266 } 267 } 268