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.config.IConfiguration; 21 import com.android.tradefed.config.Option; 22 import com.android.tradefed.config.Option.Importance; 23 import com.android.tradefed.config.OptionClass; 24 import com.android.tradefed.device.DeviceNotAvailableException; 25 import com.android.tradefed.log.LogUtil.CLog; 26 import com.android.tradefed.result.FileInputStreamSource; 27 import com.android.tradefed.result.LogDataType; 28 import com.android.tradefed.testtype.IAbi; 29 import com.android.tradefed.testtype.IRemoteTest; 30 import com.android.tradefed.testtype.suite.params.IModuleParameter; 31 import com.android.tradefed.testtype.suite.params.ModuleParameters; 32 import com.android.tradefed.testtype.suite.params.ModuleParametersHelper; 33 import com.android.tradefed.testtype.suite.params.NegativeHandler; 34 import com.android.tradefed.util.ArrayUtil; 35 import com.android.tradefed.util.FileUtil; 36 37 import com.google.common.annotations.VisibleForTesting; 38 39 import java.io.File; 40 import java.io.FileNotFoundException; 41 import java.io.IOException; 42 import java.util.ArrayList; 43 import java.util.HashMap; 44 import java.util.HashSet; 45 import java.util.LinkedHashMap; 46 import java.util.List; 47 import java.util.Map; 48 import java.util.Set; 49 50 /** A Test for running Compatibility Test Suite with new suite system. */ 51 @OptionClass(alias = "base-suite") 52 public class BaseTestSuite extends ITestSuite { 53 54 public static final String INCLUDE_FILTER_OPTION = "include-filter"; 55 public static final String EXCLUDE_FILTER_OPTION = "exclude-filter"; 56 public static final String MODULE_OPTION = "module"; 57 public static final char MODULE_OPTION_SHORT_NAME = 'm'; 58 public static final String TEST_ARG_OPTION = "test-arg"; 59 public static final String TEST_OPTION = "test"; 60 public static final char TEST_OPTION_SHORT_NAME = 't'; 61 public static final String CONFIG_PATTERNS_OPTION = "config-patterns"; 62 private static final String MODULE_ARG_OPTION = "module-arg"; 63 private static final int MAX_FILTER_DISPLAY = 20; 64 65 @Option( 66 name = INCLUDE_FILTER_OPTION, 67 description = 68 "the include module filters to apply. Format: '[abi] <module-name> [test]'." 69 + " See documentation:" 70 + "https://source.android.com/devices/tech/test_infra/tradefed/testing/through-suite/option-passing", 71 importance = Importance.ALWAYS) 72 private Set<String> mIncludeFilters = new HashSet<>(); 73 74 @Option( 75 name = EXCLUDE_FILTER_OPTION, 76 description = 77 "the exclude module filters to apply. Format: '[abi] <module-name> [test]'." 78 + " See documentation:" 79 + "https://source.android.com/devices/tech/test_infra/tradefed/testing/through-suite/option-passing", 80 importance = Importance.ALWAYS) 81 private Set<String> mExcludeFilters = new HashSet<>(); 82 83 @Option( 84 name = MODULE_OPTION, 85 shortName = MODULE_OPTION_SHORT_NAME, 86 description = "the test module to run. Only works for configuration in the tests dir.", 87 importance = Importance.IF_UNSET 88 ) 89 private String mModuleName = null; 90 91 @Option( 92 name = TEST_OPTION, 93 shortName = TEST_OPTION_SHORT_NAME, 94 description = "the test to run.", 95 importance = Importance.IF_UNSET 96 ) 97 private String mTestName = null; 98 99 @Option( 100 name = MODULE_ARG_OPTION, 101 description = 102 "the arguments to pass to a module. The expected format is" 103 + "\"<module-name>:[{alias}]<arg-name>:[<arg-key>:=]<arg-value>\"", 104 importance = Importance.ALWAYS 105 ) 106 private List<String> mModuleArgs = new ArrayList<>(); 107 108 @Option( 109 name = TEST_ARG_OPTION, 110 description = 111 "The arguments to pass to a test or its preparers. The expected format is" 112 + "\"<test-class>:<arg-name>:[<arg-key>:=]<arg-value>\"", 113 importance = Importance.ALWAYS) 114 private List<String> mTestArgs = new ArrayList<>(); 115 116 @Option( 117 name = "run-suite-tag", 118 description = 119 "The tag that must be run. If specified, only configurations containing the " 120 + "matching suite tag will be able to run." 121 ) 122 private String mSuiteTag = null; 123 124 @Option( 125 name = "prioritize-host-config", 126 description = 127 "If there are duplicate test configs for host/target, prioritize the host config, " 128 + "otherwise use the target config." 129 ) 130 private boolean mPrioritizeHostConfig = false; 131 132 @Option( 133 name = "suite-config-prefix", 134 description = "Search only configs with given prefix for suite tags." 135 ) 136 private String mSuitePrefix = null; 137 138 @Option( 139 name = "skip-loading-config-jar", 140 description = "Whether or not to skip loading configurations from the JAR on the classpath." 141 ) 142 private boolean mSkipJarLoading = false; 143 144 @Option( 145 name = CONFIG_PATTERNS_OPTION, 146 description = 147 "The pattern(s) of the configurations that should be loaded from a directory." 148 + " If none is explicitly specified, .*.xml and .*.config will be used." 149 + " Can be repeated." 150 ) 151 private List<String> mConfigPatterns = new ArrayList<>(); 152 153 @Option( 154 name = "enable-parameterized-modules", 155 description = 156 "Whether or not to enable parameterized modules. This is a feature flag for work " 157 + "in development." 158 ) 159 private boolean mEnableParameter = false; 160 161 @Option( 162 name = "enable-mainline-parameterized-modules", 163 description = 164 "Whether or not to enable mainline parameterized modules. This is a feature flag " 165 + "for work in development." 166 ) 167 private boolean mEnableMainlineParameter = false; 168 169 @Option( 170 name = "enable-optional-parameterization", 171 description = 172 "Whether or not to enable optional parameters. Optional parameters are " 173 + "parameters not usually used by default." 174 ) 175 private boolean mEnableOptionalParameter = false; 176 177 @Option( 178 name = "module-parameter", 179 description = 180 "Allows to run only one module parameter type instead of all the combinations. " 181 + "For example: 'instant_app' would only run the instant_app version of " 182 + "modules" 183 ) 184 private ModuleParameters mForceParameter = null; 185 186 @Option( 187 name = "exclude-module-parameters", 188 description = 189 "Exclude some modules parameter from being evaluated in the run combinations." 190 + "For example: 'instant_app' would exclude all the instant_app version of " 191 + "modules." 192 ) 193 private Set<ModuleParameters> mExcludedModuleParameters = new HashSet<>(); 194 195 @Option( 196 name = "fail-on-everything-filtered", 197 description = 198 "Whether or not to fail the invocation in case test filter returns" 199 + " an empty result.") 200 private boolean mFailOnEverythingFiltered = false; 201 202 private SuiteModuleLoader mModuleRepo; 203 private Map<String, List<SuiteTestFilter>> mIncludeFiltersParsed = new HashMap<>(); 204 private Map<String, List<SuiteTestFilter>> mExcludeFiltersParsed = new HashMap<>(); 205 private List<File> mConfigPaths = new ArrayList<>(); 206 207 /** {@inheritDoc} */ 208 @Override loadTests()209 public LinkedHashMap<String, IConfiguration> loadTests() { 210 try { 211 File testsDir = getTestsDir(); 212 setupFilters(testsDir); 213 Set<IAbi> abis = getAbis(getDevice()); 214 215 // Create and populate the filters here 216 SuiteModuleLoader.addFilters(mIncludeFilters, mIncludeFiltersParsed, abis); 217 SuiteModuleLoader.addFilters(mExcludeFilters, mExcludeFiltersParsed, abis); 218 219 String includeFilter = mIncludeFiltersParsed.toString(); 220 if (mIncludeFiltersParsed.size() > MAX_FILTER_DISPLAY) { 221 if (isSplitting()) { 222 includeFilter = includeFilter.substring(0, 100) + "..."; 223 } else { 224 File suiteIncludeFilters = null; 225 try { 226 suiteIncludeFilters = 227 FileUtil.createTempFile("suite-include-filters", ".txt"); 228 FileUtil.writeToFile(mIncludeFiltersParsed.toString(), suiteIncludeFilters); 229 logFilterFile( 230 suiteIncludeFilters, 231 suiteIncludeFilters.getName(), 232 LogDataType.TEXT); 233 includeFilter = String.format("See %s", suiteIncludeFilters.getName()); 234 } catch (IOException e) { 235 CLog.e(e); 236 } finally { 237 FileUtil.deleteFile(suiteIncludeFilters); 238 } 239 } 240 } 241 242 String excludeFilter = mExcludeFiltersParsed.toString(); 243 if (mExcludeFiltersParsed.size() > MAX_FILTER_DISPLAY) { 244 if (isSplitting()) { 245 excludeFilter = excludeFilter.substring(0, 100) + "..."; 246 } else { 247 File suiteExcludeFilters = null; 248 try { 249 suiteExcludeFilters = 250 FileUtil.createTempFile("suite-exclude-filters", ".txt"); 251 FileUtil.writeToFile(mExcludeFiltersParsed.toString(), suiteExcludeFilters); 252 logFilterFile( 253 suiteExcludeFilters, 254 suiteExcludeFilters.getName(), 255 LogDataType.TEXT); 256 excludeFilter = String.format("See %s", suiteExcludeFilters.getName()); 257 } catch (IOException e) { 258 CLog.e(e); 259 } finally { 260 FileUtil.deleteFile(suiteExcludeFilters); 261 } 262 } 263 } 264 265 CLog.d( 266 "Initializing ModuleRepo\nABIs:%s\n" 267 + "Test Args:%s\nModule Args:%s\nIncludes:%s\nExcludes:%s", 268 abis, mTestArgs, mModuleArgs, includeFilter, excludeFilter); 269 270 mModuleRepo = 271 createModuleLoader( 272 mIncludeFiltersParsed, mExcludeFiltersParsed, mTestArgs, mModuleArgs); 273 if (mForceParameter != null && !mEnableParameter) { 274 throw new IllegalArgumentException( 275 "'module-parameter' option was specified without " 276 + "'enable-parameterized-modules'"); 277 } 278 if (mEnableOptionalParameter && !mEnableParameter) { 279 throw new IllegalArgumentException( 280 "'enable-optional-parameterization' option was specified without " 281 + "'enable-parameterized-modules'"); 282 } 283 284 if (mEnableMainlineParameter) { 285 mModuleRepo.setMainlineParameterizedModules(mEnableMainlineParameter); 286 mModuleRepo.setInvocationContext(getInvocationContext()); 287 } 288 289 mModuleRepo.setParameterizedModules(mEnableParameter); 290 mModuleRepo.setOptionalParameterizedModules(mEnableOptionalParameter); 291 mModuleRepo.setModuleParameter(mForceParameter); 292 mModuleRepo.setExcludedModuleParameters(mExcludedModuleParameters); 293 294 List<File> testsDirectories = new ArrayList<>(); 295 296 // Include host or target first in the search if it exists, we have to this in 297 // BaseTestSuite because it's the only one with the BuildInfo knowledge of linked files 298 if (mPrioritizeHostConfig) { 299 File hostSubDir = getBuildInfo().getFile(BuildInfoFileKey.HOST_LINKED_DIR); 300 if (hostSubDir != null && hostSubDir.exists()) { 301 testsDirectories.add(hostSubDir); 302 } 303 } else { 304 File targetSubDir = getBuildInfo().getFile(BuildInfoFileKey.TARGET_LINKED_DIR); 305 if (targetSubDir != null && targetSubDir.exists()) { 306 testsDirectories.add(targetSubDir); 307 } 308 } 309 310 // Finally add the full test cases directory in case there is no special sub-dir. 311 testsDirectories.add(testsDir); 312 // Actual loading of the configurations. 313 LinkedHashMap<String, IConfiguration> loadedTests = 314 loadingStrategy(abis, testsDirectories, mSuitePrefix, mSuiteTag); 315 316 if (mFailOnEverythingFiltered 317 && loadedTests.isEmpty() 318 && !mIncludeFiltersParsed.isEmpty()) { 319 throw new IllegalStateException( 320 String.format( 321 "Include filter '%s' was specified" 322 + " but resulted in an empty test set.", 323 includeFilter)); 324 } 325 return loadedTests; 326 } catch (DeviceNotAvailableException | FileNotFoundException e) { 327 throw new RuntimeException(e); 328 } 329 } 330 331 /** 332 * Default loading strategy will load from the resources and the tests directory. Can be 333 * extended or replaced. 334 * 335 * @param abis The set of abis to run against. 336 * @param testsDirs The tests directory. 337 * @param suitePrefix A prefix to filter the resource directory. 338 * @param suiteTag The suite tag a module should have to be included. Can be null. 339 * @return A list of loaded configuration for the suite. 340 */ loadingStrategy( Set<IAbi> abis, List<File> testsDirs, String suitePrefix, String suiteTag)341 public LinkedHashMap<String, IConfiguration> loadingStrategy( 342 Set<IAbi> abis, List<File> testsDirs, String suitePrefix, String suiteTag) { 343 LinkedHashMap<String, IConfiguration> loadedConfigs = new LinkedHashMap<>(); 344 // Load and return directly the specific config files. 345 if (!mConfigPaths.isEmpty()) { 346 CLog.d( 347 "Loading the specified configs path '%s' and skip loading from the resources.", 348 mConfigPaths); 349 return getModuleLoader().loadConfigsFromSpecifiedPaths(mConfigPaths, abis, suiteTag); 350 } 351 352 // Load configs that are part of the resources 353 if (!mSkipJarLoading) { 354 loadedConfigs.putAll( 355 getModuleLoader().loadConfigsFromJars(abis, suitePrefix, suiteTag)); 356 } 357 358 // Load the configs that are part of the tests dir 359 if (mConfigPatterns.isEmpty()) { 360 // If no special pattern was configured, use the default configuration patterns we know 361 mConfigPatterns.add(".*\\.config$"); 362 mConfigPatterns.add(".*\\.xml$"); 363 } 364 365 loadedConfigs.putAll( 366 getModuleLoader() 367 .loadConfigsFromDirectory( 368 testsDirs, abis, suitePrefix, suiteTag, mConfigPatterns)); 369 return loadedConfigs; 370 } 371 372 /** {@inheritDoc} */ 373 @Override setBuild(IBuildInfo buildInfo)374 public void setBuild(IBuildInfo buildInfo) { 375 super.setBuild(buildInfo); 376 } 377 378 /** Sets include-filters for the compatibility test */ setIncludeFilter(Set<String> includeFilters)379 public void setIncludeFilter(Set<String> includeFilters) { 380 mIncludeFilters.addAll(includeFilters); 381 } 382 383 /** Gets a copy of include-filters for the compatibility test */ getIncludeFilter()384 protected Set<String> getIncludeFilter() { 385 return new HashSet<String>(mIncludeFilters); 386 } 387 388 /** Sets exclude-filters for the compatibility test */ setExcludeFilter(Set<String> excludeFilters)389 public void setExcludeFilter(Set<String> excludeFilters) { 390 mExcludeFilters.addAll(excludeFilters); 391 } 392 393 /** Gets a copy of exclude-filters for the compatibility test */ getExcludeFilter()394 protected Set<String> getExcludeFilter() { 395 return new HashSet<String>(mExcludeFilters); 396 } 397 398 /** Returns the current {@link SuiteModuleLoader}. */ getModuleLoader()399 public SuiteModuleLoader getModuleLoader() { 400 return mModuleRepo; 401 } 402 403 /** Adds module args */ addModuleArgs(Set<String> moduleArgs)404 public void addModuleArgs(Set<String> moduleArgs) { 405 mModuleArgs.addAll(moduleArgs); 406 } 407 408 /** Clear the stored module args out */ clearModuleArgs()409 void clearModuleArgs() { 410 mModuleArgs.clear(); 411 } 412 413 /** Add config patterns */ addConfigPatterns(List<String> patterns)414 public void addConfigPatterns(List<String> patterns) { 415 mConfigPatterns.addAll(patterns); 416 } 417 418 /** Set whether or not parameterized modules are enabled or not. */ setEnableParameterizedModules(boolean enableParameter)419 public void setEnableParameterizedModules(boolean enableParameter) { 420 mEnableParameter = enableParameter; 421 } 422 423 /** Set whether or not optional parameterized modules are enabled or not. */ setEnableOptionalParameterizedModules(boolean enableOptionalParameter)424 public void setEnableOptionalParameterizedModules(boolean enableOptionalParameter) { 425 mEnableOptionalParameter = enableOptionalParameter; 426 } 427 setModuleParameter(ModuleParameters forceParameter)428 public void setModuleParameter(ModuleParameters forceParameter) { 429 mForceParameter = forceParameter; 430 } 431 432 /** 433 * Create the {@link SuiteModuleLoader} responsible to load the {@link IConfiguration} and 434 * assign them some of the options. 435 * 436 * @param includeFiltersFormatted The formatted and parsed include filters. 437 * @param excludeFiltersFormatted The formatted and parsed exclude filters. 438 * @param testArgs the list of test ({@link IRemoteTest}) arguments. 439 * @param moduleArgs the list of module arguments. 440 * @return the created {@link SuiteModuleLoader}. 441 */ createModuleLoader( Map<String, List<SuiteTestFilter>> includeFiltersFormatted, Map<String, List<SuiteTestFilter>> excludeFiltersFormatted, List<String> testArgs, List<String> moduleArgs)442 public SuiteModuleLoader createModuleLoader( 443 Map<String, List<SuiteTestFilter>> includeFiltersFormatted, 444 Map<String, List<SuiteTestFilter>> excludeFiltersFormatted, 445 List<String> testArgs, 446 List<String> moduleArgs) { 447 return new SuiteModuleLoader( 448 includeFiltersFormatted, excludeFiltersFormatted, testArgs, moduleArgs); 449 } 450 451 /** 452 * Sets the include/exclude filters up based on if a module name was given. 453 * 454 * @throws FileNotFoundException if any file is not found. 455 */ setupFilters(File testsDir)456 protected void setupFilters(File testsDir) throws FileNotFoundException { 457 if (mModuleName == null) { 458 if (mTestName != null) { 459 throw new IllegalArgumentException( 460 "Test name given without module name. Add --module <module-name>"); 461 } 462 return; 463 } 464 // If this option (-m / --module) is set only the matching unique module should run. 465 Set<File> modules = 466 SuiteModuleLoader.getModuleNamesMatching( 467 testsDir, mSuitePrefix, String.format(".*%s.*.config", mModuleName)); 468 // If multiple modules match, do exact match. 469 if (modules.size() > 1) { 470 Set<File> newModules = new HashSet<>(); 471 String exactModuleName = String.format("%s.config", mModuleName); 472 for (File module : modules) { 473 if (module.getName().equals(exactModuleName)) { 474 newModules.add(module); 475 modules = newModules; 476 break; 477 } 478 } 479 } 480 if (modules.size() == 0) { 481 throw new IllegalArgumentException( 482 String.format("No modules found matching %s", mModuleName)); 483 } else if (modules.size() > 1) { 484 throw new IllegalArgumentException( 485 String.format( 486 "Multiple modules found matching %s:\n%s\nWhich one did you " 487 + "mean?\n", 488 mModuleName, ArrayUtil.join("\n", modules))); 489 } else { 490 File mod = modules.iterator().next(); 491 String moduleName = mod.getName().replace(".config", ""); 492 checkFilters(mIncludeFilters, moduleName); 493 checkFilters(mExcludeFilters, moduleName); 494 mIncludeFilters.add( 495 new SuiteTestFilter(getRequestedAbi(), moduleName, mTestName).toString()); 496 // Create the matching filters for the parameterized version of it if needed. 497 if (mEnableParameter) { 498 for (ModuleParameters param : ModuleParameters.values()) { 499 IModuleParameter moduleParam = 500 ModuleParametersHelper.getParameterHandler( 501 param, mEnableOptionalParameter); 502 if (moduleParam == null) { 503 continue; 504 } 505 if (moduleParam instanceof NegativeHandler) { 506 continue; 507 } 508 String paramModuleName = 509 String.format( 510 "%s[%s]", moduleName, moduleParam.getParameterIdentifier()); 511 mIncludeFilters.add( 512 new SuiteTestFilter(getRequestedAbi(), paramModuleName, mTestName) 513 .toString()); 514 } 515 } 516 } 517 } 518 519 @Override cleanUpSuiteSetup()520 void cleanUpSuiteSetup() { 521 super.cleanUpSuiteSetup(); 522 // Clean the filters because at that point they have been applied to the runners. 523 // This can save several GB of memories during sharding. 524 mIncludeFilters.clear(); 525 mExcludeFilters.clear(); 526 mIncludeFiltersParsed.clear(); 527 mExcludeFiltersParsed.clear(); 528 } 529 530 /** 531 * Add the config path for {@link SuiteModuleLoader} to limit the search loading 532 * configurations. 533 * 534 * @param configPath A {@code File} with the absolute path of the configuration. 535 */ addConfigPaths(File configPath)536 void addConfigPaths(File configPath) { 537 mConfigPaths.add(configPath); 538 } 539 540 /** Clear the stored config paths out. */ clearConfigPaths()541 void clearConfigPaths() { 542 mConfigPaths.clear(); 543 } 544 545 /* Helper method designed to remove filters in a list not applicable to the given module */ checkFilters(Set<String> filters, String moduleName)546 private static void checkFilters(Set<String> filters, String moduleName) { 547 Set<String> cleanedFilters = new HashSet<String>(); 548 for (String filter : filters) { 549 SuiteTestFilter filterObject = SuiteTestFilter.createFrom(filter); 550 String filterName = filterObject.getName(); 551 String filterBaseName = filterObject.getBaseName(); 552 if (moduleName.equals(filterName) || moduleName.equals(filterBaseName)) { 553 cleanedFilters.add(filter); // Module name matches, filter passes 554 } 555 } 556 filters.clear(); 557 filters.addAll(cleanedFilters); 558 } 559 560 /* Return a {@link boolean} for the setting of prioritize-host-config.*/ getPrioritizeHostConfig()561 boolean getPrioritizeHostConfig() { 562 return mPrioritizeHostConfig; 563 } 564 565 /** 566 * Set option prioritize-host-config. 567 * 568 * @param prioritizeHostConfig true to prioritize host config, i.e., run host test if possible. 569 */ 570 @VisibleForTesting setPrioritizeHostConfig(boolean prioritizeHostConfig)571 protected void setPrioritizeHostConfig(boolean prioritizeHostConfig) { 572 mPrioritizeHostConfig = prioritizeHostConfig; 573 } 574 575 /** Log a file directly to the result reporter. */ logFilterFile(File filterFile, String dataName, LogDataType type)576 private void logFilterFile(File filterFile, String dataName, LogDataType type) { 577 if (getCurrentTestLogger() == null) { 578 return; 579 } 580 try (FileInputStreamSource source = new FileInputStreamSource(filterFile)) { 581 getCurrentTestLogger().testLog(dataName, type, source); 582 } 583 } 584 } 585