1 /* 2 * Copyright (C) 2018 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.testtype.suite; 17 18 import com.android.tradefed.build.BuildInfoKey.BuildInfoFileKey; 19 import com.android.tradefed.build.IBuildInfo; 20 import com.android.tradefed.build.IDeviceBuildInfo; 21 import com.android.tradefed.config.IConfiguration; 22 import com.android.tradefed.config.Option; 23 import com.android.tradefed.config.Option.Importance; 24 import com.android.tradefed.config.OptionClass; 25 import com.android.tradefed.device.DeviceNotAvailableException; 26 import com.android.tradefed.log.LogUtil.CLog; 27 import com.android.tradefed.testtype.IAbi; 28 import com.android.tradefed.testtype.IRemoteTest; 29 import com.android.tradefed.testtype.suite.params.ModuleParameters; 30 import com.android.tradefed.util.ArrayUtil; 31 32 import com.google.common.annotations.VisibleForTesting; 33 34 import java.io.File; 35 import java.io.FileNotFoundException; 36 import java.util.ArrayList; 37 import java.util.HashMap; 38 import java.util.HashSet; 39 import java.util.LinkedHashMap; 40 import java.util.List; 41 import java.util.Map; 42 import java.util.Set; 43 44 /** A Test for running Compatibility Test Suite with new suite system. */ 45 @OptionClass(alias = "base-suite") 46 public class BaseTestSuite extends ITestSuite { 47 48 public static final String INCLUDE_FILTER_OPTION = "include-filter"; 49 public static final String EXCLUDE_FILTER_OPTION = "exclude-filter"; 50 public static final String MODULE_OPTION = "module"; 51 public static final String TEST_ARG_OPTION = "test-arg"; 52 public static final String TEST_OPTION = "test"; 53 public static final char TEST_OPTION_SHORT_NAME = 't'; 54 public static final String CONFIG_PATTERNS_OPTION = "config-patterns"; 55 private static final String MODULE_ARG_OPTION = "module-arg"; 56 57 @Option( 58 name = INCLUDE_FILTER_OPTION, 59 description = "the include module filters to apply.", 60 importance = Importance.ALWAYS 61 ) 62 private Set<String> mIncludeFilters = new HashSet<>(); 63 64 @Option( 65 name = EXCLUDE_FILTER_OPTION, 66 description = "the exclude module filters to apply.", 67 importance = Importance.ALWAYS 68 ) 69 private Set<String> mExcludeFilters = new HashSet<>(); 70 71 @Option( 72 name = MODULE_OPTION, 73 shortName = 'm', 74 description = "the test module to run. Only works for configuration in the tests dir.", 75 importance = Importance.IF_UNSET 76 ) 77 private String mModuleName = null; 78 79 @Option( 80 name = TEST_OPTION, 81 shortName = TEST_OPTION_SHORT_NAME, 82 description = "the test to run.", 83 importance = Importance.IF_UNSET 84 ) 85 private String mTestName = null; 86 87 @Option( 88 name = MODULE_ARG_OPTION, 89 description = 90 "the arguments to pass to a module. The expected format is" 91 + "\"<module-name>:[{alias}]<arg-name>:[<arg-key>:=]<arg-value>\"", 92 importance = Importance.ALWAYS 93 ) 94 private List<String> mModuleArgs = new ArrayList<>(); 95 96 @Option( 97 name = TEST_ARG_OPTION, 98 description = 99 "the arguments to pass to a test. The expected format is" 100 + "\"<test-class>:<arg-name>:[<arg-key>:=]<arg-value>\"", 101 importance = Importance.ALWAYS 102 ) 103 private List<String> mTestArgs = new ArrayList<>(); 104 105 @Option( 106 name = "run-suite-tag", 107 description = 108 "The tag that must be run. If specified, only configurations containing the " 109 + "matching suite tag will be able to run." 110 ) 111 private String mSuiteTag = null; 112 113 @Option( 114 name = "prioritize-host-config", 115 description = 116 "If there are duplicate test configs for host/target, prioritize the host config, " 117 + "otherwise use the target config." 118 ) 119 private boolean mPrioritizeHostConfig = false; 120 121 @Option( 122 name = "suite-config-prefix", 123 description = "Search only configs with given prefix for suite tags." 124 ) 125 private String mSuitePrefix = null; 126 127 @Option( 128 name = "skip-loading-config-jar", 129 description = "Whether or not to skip loading configurations from the JAR on the classpath." 130 ) 131 private boolean mSkipJarLoading = false; 132 133 @Option( 134 name = CONFIG_PATTERNS_OPTION, 135 description = 136 "The pattern(s) of the configurations that should be loaded from a directory." 137 + " If none is explicitly specified, .*.xml and .*.config will be used." 138 + " Can be repeated." 139 ) 140 private List<String> mConfigPatterns = new ArrayList<>(); 141 142 @Option( 143 name = "enable-parameterized-modules", 144 description = 145 "Whether or not to enable parameterized modules. This is a feature flag for work " 146 + "in development." 147 ) 148 private boolean mEnableParameter = false; 149 150 @Option( 151 name = "module-parameter", 152 description = 153 "Allows to run only one module parameter type instead of all the combinations. " 154 + "For example: 'instant_app' would only run the instant_app version of " 155 + "modules" 156 ) 157 private ModuleParameters mForceParameter = null; 158 159 @Option( 160 name = "exclude-module-parameters", 161 description = 162 "Exclude some modules parameter from being evaluated in the run combinations." 163 + "For example: 'instant_app' would exclude all the instant_app version of " 164 + "modules." 165 ) 166 private Set<ModuleParameters> mExcludedModuleParameters = new HashSet<>(); 167 168 private SuiteModuleLoader mModuleRepo; 169 private Map<String, List<SuiteTestFilter>> mIncludeFiltersParsed = new HashMap<>(); 170 private Map<String, List<SuiteTestFilter>> mExcludeFiltersParsed = new HashMap<>(); 171 172 /** {@inheritDoc} */ 173 @Override loadTests()174 public LinkedHashMap<String, IConfiguration> loadTests() { 175 try { 176 File testsDir = getTestsDir(); 177 setupFilters(testsDir); 178 Set<IAbi> abis = getAbis(getDevice()); 179 180 // Create and populate the filters here 181 SuiteModuleLoader.addFilters(mIncludeFilters, mIncludeFiltersParsed, abis); 182 SuiteModuleLoader.addFilters(mExcludeFilters, mExcludeFiltersParsed, abis); 183 184 CLog.d( 185 "Initializing ModuleRepo\nABIs:%s\n" 186 + "Test Args:%s\nModule Args:%s\nIncludes:%s\nExcludes:%s", 187 abis, mTestArgs, mModuleArgs, mIncludeFiltersParsed, mExcludeFiltersParsed); 188 mModuleRepo = 189 createModuleLoader( 190 mIncludeFiltersParsed, mExcludeFiltersParsed, mTestArgs, mModuleArgs); 191 mModuleRepo.setParameterizedModules(mEnableParameter); 192 mModuleRepo.setModuleParameter(mForceParameter); 193 mModuleRepo.setExcludedModuleParameters(mExcludedModuleParameters); 194 195 List<File> testsDirectories = new ArrayList<>(); 196 197 // Include host or target first in the search if it exists, we have to this in 198 // BaseTestSuite because it's the only one with the BuildInfo knowledge of linked files 199 if (mPrioritizeHostConfig) { 200 File hostSubDir = getBuildInfo().getFile(BuildInfoFileKey.HOST_LINKED_DIR); 201 if (hostSubDir != null && hostSubDir.exists()) { 202 testsDirectories.add(hostSubDir); 203 } 204 } else { 205 File targetSubDir = getBuildInfo().getFile(BuildInfoFileKey.TARGET_LINKED_DIR); 206 if (targetSubDir != null && targetSubDir.exists()) { 207 testsDirectories.add(targetSubDir); 208 } 209 } 210 211 // Finally add the full test cases directory in case there is no special sub-dir. 212 testsDirectories.add(testsDir); 213 // Actual loading of the configurations. 214 return loadingStrategy(abis, testsDirectories, mSuitePrefix, mSuiteTag); 215 } catch (DeviceNotAvailableException | FileNotFoundException e) { 216 throw new RuntimeException(e); 217 } 218 } 219 220 /** 221 * Default loading strategy will load from the resources and the tests directory. Can be 222 * extended or replaced. 223 * 224 * @param abis The set of abis to run against. 225 * @param testsDirs The tests directory. 226 * @param suitePrefix A prefix to filter the resource directory. 227 * @param suiteTag The suite tag a module should have to be included. Can be null. 228 * @return A list of loaded configuration for the suite. 229 */ loadingStrategy( Set<IAbi> abis, List<File> testsDirs, String suitePrefix, String suiteTag)230 public LinkedHashMap<String, IConfiguration> loadingStrategy( 231 Set<IAbi> abis, List<File> testsDirs, String suitePrefix, String suiteTag) { 232 LinkedHashMap<String, IConfiguration> loadedConfigs = new LinkedHashMap<>(); 233 // Load configs that are part of the resources 234 if (!mSkipJarLoading) { 235 loadedConfigs.putAll( 236 getModuleLoader().loadConfigsFromJars(abis, suitePrefix, suiteTag)); 237 } 238 239 // Load the configs that are part of the tests dir 240 if (mConfigPatterns.isEmpty()) { 241 // If no special pattern was configured, use the default configuration patterns we know 242 mConfigPatterns.add(".*\\.config$"); 243 mConfigPatterns.add(".*\\.xml$"); 244 } 245 246 loadedConfigs.putAll( 247 getModuleLoader() 248 .loadConfigsFromDirectory( 249 testsDirs, abis, suitePrefix, suiteTag, mConfigPatterns)); 250 return loadedConfigs; 251 } 252 getTestsDir()253 public File getTestsDir() throws FileNotFoundException { 254 IBuildInfo build = getBuildInfo(); 255 if (build instanceof IDeviceBuildInfo) { 256 return ((IDeviceBuildInfo) build).getTestsDir(); 257 } 258 // TODO: handle multi build? 259 throw new FileNotFoundException("Could not found a tests dir folder."); 260 } 261 262 /** {@inheritDoc} */ 263 @Override setBuild(IBuildInfo buildInfo)264 public void setBuild(IBuildInfo buildInfo) { 265 super.setBuild(buildInfo); 266 } 267 268 /** Sets include-filters for the compatibility test */ setIncludeFilter(Set<String> includeFilters)269 public void setIncludeFilter(Set<String> includeFilters) { 270 mIncludeFilters.addAll(includeFilters); 271 } 272 273 /** Gets a copy of include-filters for the compatibility test */ getIncludeFilter()274 protected Set<String> getIncludeFilter() { 275 return new HashSet<String>(mIncludeFilters); 276 } 277 278 /** Sets exclude-filters for the compatibility test */ setExcludeFilter(Set<String> excludeFilters)279 public void setExcludeFilter(Set<String> excludeFilters) { 280 mExcludeFilters.addAll(excludeFilters); 281 } 282 283 /** Gets a copy of exclude-filters for the compatibility test */ getExcludeFilter()284 protected Set<String> getExcludeFilter() { 285 return new HashSet<String>(mExcludeFilters); 286 } 287 288 /** Returns the current {@link SuiteModuleLoader}. */ getModuleLoader()289 public SuiteModuleLoader getModuleLoader() { 290 return mModuleRepo; 291 } 292 293 /** Adds module args */ addModuleArgs(Set<String> moduleArgs)294 public void addModuleArgs(Set<String> moduleArgs) { 295 mModuleArgs.addAll(moduleArgs); 296 } 297 298 /** Add config patterns */ addConfigPatterns(List<String> patterns)299 public void addConfigPatterns(List<String> patterns) { 300 mConfigPatterns.addAll(patterns); 301 } 302 303 /** 304 * Create the {@link SuiteModuleLoader} responsible to load the {@link IConfiguration} and 305 * assign them some of the options. 306 * 307 * @param includeFiltersFormatted The formatted and parsed include filters. 308 * @param excludeFiltersFormatted The formatted and parsed exclude filters. 309 * @param testArgs the list of test ({@link IRemoteTest}) arguments. 310 * @param moduleArgs the list of module arguments. 311 * @return the created {@link SuiteModuleLoader}. 312 */ createModuleLoader( Map<String, List<SuiteTestFilter>> includeFiltersFormatted, Map<String, List<SuiteTestFilter>> excludeFiltersFormatted, List<String> testArgs, List<String> moduleArgs)313 public SuiteModuleLoader createModuleLoader( 314 Map<String, List<SuiteTestFilter>> includeFiltersFormatted, 315 Map<String, List<SuiteTestFilter>> excludeFiltersFormatted, 316 List<String> testArgs, 317 List<String> moduleArgs) { 318 return new SuiteModuleLoader( 319 includeFiltersFormatted, excludeFiltersFormatted, testArgs, moduleArgs); 320 } 321 322 /** 323 * Sets the include/exclude filters up based on if a module name was given. 324 * 325 * @throws FileNotFoundException if any file is not found. 326 */ setupFilters(File testsDir)327 protected void setupFilters(File testsDir) throws FileNotFoundException { 328 if (mModuleName != null) { 329 // If this option (-m / --module) is set only the matching unique module should run. 330 Set<File> modules = 331 SuiteModuleLoader.getModuleNamesMatching( 332 testsDir, mSuitePrefix, String.format(".*%s.*.config", mModuleName)); 333 // If multiple modules match, do exact match. 334 if (modules.size() > 1) { 335 Set<File> newModules = new HashSet<>(); 336 String exactModuleName = String.format("%s.config", mModuleName); 337 for (File module : modules) { 338 if (module.getName().equals(exactModuleName)) { 339 newModules.add(module); 340 modules = newModules; 341 break; 342 } 343 } 344 } 345 if (modules.size() == 0) { 346 throw new IllegalArgumentException( 347 String.format("No modules found matching %s", mModuleName)); 348 } else if (modules.size() > 1) { 349 throw new IllegalArgumentException( 350 String.format( 351 "Multiple modules found matching %s:\n%s\nWhich one did you " 352 + "mean?\n", 353 mModuleName, ArrayUtil.join("\n", modules))); 354 } else { 355 File mod = modules.iterator().next(); 356 String moduleName = mod.getName().replace(".config", ""); 357 checkFilters(mIncludeFilters, moduleName); 358 checkFilters(mExcludeFilters, moduleName); 359 mIncludeFilters.add( 360 new SuiteTestFilter(getRequestedAbi(), moduleName, mTestName).toString()); 361 } 362 } else if (mTestName != null) { 363 throw new IllegalArgumentException( 364 "Test name given without module name. Add --module <module-name>"); 365 } 366 } 367 368 @Override cleanUpSuiteSetup()369 void cleanUpSuiteSetup() { 370 super.cleanUpSuiteSetup(); 371 // Clean the filters because at that point they have been applied to the runners. 372 // This can save several GB of memories during sharding. 373 mIncludeFilters.clear(); 374 mExcludeFilters.clear(); 375 mIncludeFiltersParsed.clear(); 376 mExcludeFiltersParsed.clear(); 377 } 378 379 /* Helper method designed to remove filters in a list not applicable to the given module */ checkFilters(Set<String> filters, String moduleName)380 private static void checkFilters(Set<String> filters, String moduleName) { 381 Set<String> cleanedFilters = new HashSet<String>(); 382 for (String filter : filters) { 383 SuiteTestFilter filterObject = SuiteTestFilter.createFrom(filter); 384 String filterName = filterObject.getName(); 385 String filterBaseName = filterObject.getBaseName(); 386 if (moduleName.equals(filterName) || moduleName.equals(filterBaseName)) { 387 cleanedFilters.add(filter); // Module name matches, filter passes 388 } 389 } 390 filters.clear(); 391 filters.addAll(cleanedFilters); 392 } 393 394 /* Return a {@link boolean} for the setting of prioritize-host-config.*/ getPrioritizeHostConfig()395 boolean getPrioritizeHostConfig() { 396 return mPrioritizeHostConfig; 397 } 398 399 /** 400 * Set option prioritize-host-config. 401 * 402 * @param prioritizeHostConfig true to prioritize host config, i.e., run host test if possible. 403 */ 404 @VisibleForTesting setPrioritizeHostConfig(boolean prioritizeHostConfig)405 protected void setPrioritizeHostConfig(boolean prioritizeHostConfig) { 406 mPrioritizeHostConfig = prioritizeHostConfig; 407 } 408 } 409