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.DeviceFoldableState; 25 import com.android.tradefed.device.DeviceNotAvailableException; 26 import com.android.tradefed.device.ITestDevice; 27 import com.android.tradefed.device.StubDevice; 28 import com.android.tradefed.error.HarnessRuntimeException; 29 import com.android.tradefed.invoker.logger.InvocationMetricLogger; 30 import com.android.tradefed.invoker.logger.InvocationMetricLogger.InvocationMetricKey; 31 import com.android.tradefed.log.LogUtil.CLog; 32 import com.android.tradefed.result.FileInputStreamSource; 33 import com.android.tradefed.result.LogDataType; 34 import com.android.tradefed.result.error.InfraErrorIdentifier; 35 import com.android.tradefed.testtype.IAbi; 36 import com.android.tradefed.testtype.IRemoteTest; 37 import com.android.tradefed.testtype.ITestFileFilterReceiver; 38 import com.android.tradefed.testtype.ITestFilterReceiver; 39 import com.android.tradefed.testtype.suite.params.FoldableExpandingHandler; 40 import com.android.tradefed.testtype.suite.params.IModuleParameterHandler; 41 import com.android.tradefed.testtype.suite.params.ModuleParameters; 42 import com.android.tradefed.testtype.suite.params.ModuleParametersHelper; 43 import com.android.tradefed.testtype.suite.params.NegativeHandler; 44 import com.android.tradefed.util.ArrayUtil; 45 import com.android.tradefed.util.FileUtil; 46 47 import java.io.File; 48 import java.io.FileNotFoundException; 49 import java.io.IOException; 50 import java.util.ArrayList; 51 import java.util.HashSet; 52 import java.util.LinkedHashMap; 53 import java.util.LinkedHashSet; 54 import java.util.List; 55 import java.util.Map; 56 import java.util.Map.Entry; 57 import java.util.Set; 58 import java.util.stream.Collectors; 59 60 /** A Test for running Compatibility Test Suite with new suite system. */ 61 @OptionClass(alias = "base-suite") 62 public class BaseTestSuite extends ITestSuite { 63 64 public static final String INCLUDE_FILTER_OPTION = "include-filter"; 65 public static final String EXCLUDE_FILTER_OPTION = "exclude-filter"; 66 public static final String MODULE_OPTION = "module"; 67 public static final char MODULE_OPTION_SHORT_NAME = 'm'; 68 public static final String TEST_ARG_OPTION = "test-arg"; 69 public static final String TEST_OPTION = "test"; 70 public static final char TEST_OPTION_SHORT_NAME = 't'; 71 public static final String CONFIG_PATTERNS_OPTION = "config-patterns"; 72 private static final String MODULE_ARG_OPTION = "module-arg"; 73 private static final String REVERSE_EXCLUDE_FILTERS = "reverse-exclude-filters"; 74 private static final int MAX_FILTER_DISPLAY = 20; 75 76 @Option( 77 name = INCLUDE_FILTER_OPTION, 78 description = 79 "the include module filters to apply. Format: '[abi] <module-name> [test]'. See" 80 + " documentation:" 81 + "https://source.android.com/docs/core/tests/tradefed/testing/through-suite/option-passing", 82 importance = Importance.ALWAYS) 83 private Set<String> mIncludeFilters = new LinkedHashSet<>(); 84 85 @Option( 86 name = EXCLUDE_FILTER_OPTION, 87 description = 88 "the exclude module filters to apply. Format: '[abi] <module-name> [test]'. See" 89 + " documentation:" 90 + "https://source.android.com/docs/core/tests/tradefed/testing/through-suite/option-passing", 91 importance = Importance.ALWAYS) 92 private Set<String> mExcludeFilters = new LinkedHashSet<>(); 93 94 @Option( 95 name = REVERSE_EXCLUDE_FILTERS, 96 description = 97 "Flip exclude-filters into include-filters, in order to run only the excluded " 98 + "set.") 99 private boolean mReverseExcludeFilters = false; 100 101 @Option( 102 name = MODULE_OPTION, 103 shortName = MODULE_OPTION_SHORT_NAME, 104 description = "the test module to run. Only works for configuration in the tests dir.", 105 importance = Importance.IF_UNSET) 106 private String mModuleName = null; 107 108 @Option( 109 name = TEST_OPTION, 110 shortName = TEST_OPTION_SHORT_NAME, 111 description = "the test to run.", 112 importance = Importance.IF_UNSET) 113 private String mTestName = null; 114 115 @Option( 116 name = MODULE_ARG_OPTION, 117 description = 118 "the arguments to pass to a module. The expected format is" 119 + "\"<module-name>:[{alias}]<arg-name>:[<arg-key>:=]<arg-value>\"", 120 importance = Importance.ALWAYS) 121 private List<String> mModuleArgs = new ArrayList<>(); 122 123 @Option( 124 name = TEST_ARG_OPTION, 125 description = 126 "The arguments to pass to a test or its preparers. The expected format is" 127 + "\"<test-class>:<arg-name>:[<arg-key>:=]<arg-value>\"", 128 importance = Importance.ALWAYS) 129 private List<String> mTestArgs = new ArrayList<>(); 130 131 @Option( 132 name = "run-suite-tag", 133 description = 134 "The tag that must be run. If specified, only configurations containing the " 135 + "matching suite tag will be able to run.") 136 private String mSuiteTag = null; 137 138 @Option( 139 name = "suite-config-prefix", 140 description = "Search only configs with given prefix for suite tags.") 141 private String mSuitePrefix = null; 142 143 @Option( 144 name = "skip-loading-config-jar", 145 description = 146 "Whether or not to skip loading configurations from the JAR on the classpath.") 147 private boolean mSkipJarLoading = false; 148 149 @Option( 150 name = CONFIG_PATTERNS_OPTION, 151 description = 152 "The pattern(s) of the configurations that should be loaded from a directory." 153 + " If none is explicitly specified, .*.xml and .*.config will be used." 154 + " Can be repeated.") 155 private List<String> mConfigPatterns = new ArrayList<>(); 156 157 @Option( 158 name = "enable-parameterized-modules", 159 description = 160 "Whether or not to enable parameterized modules. This is a feature flag for" 161 + " work in development.") 162 private boolean mEnableParameter = false; 163 164 @Option( 165 name = "enable-mainline-parameterized-modules", 166 description = 167 "Whether or not to enable mainline parameterized modules. This is a feature" 168 + " flag for work in development.") 169 private boolean mEnableMainlineParameter = false; 170 171 @Option( 172 name = "enable-optional-parameterization", 173 description = 174 "Whether or not to enable optional parameters. Optional parameters are " 175 + "parameters not usually used by default.") 176 private boolean mEnableOptionalParameter = false; 177 178 @Option( 179 name = "module-parameter", 180 description = 181 "Allows to run only one module parameter type instead of all the combinations." 182 + " For example: 'instant_app' would only run the instant_app version of " 183 + "modules") 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" 190 + " combinations.For example: 'instant_app' would exclude all the" 191 + " instant_app version of modules.") 192 private Set<ModuleParameters> mExcludedModuleParameters = new HashSet<>(); 193 194 @Option( 195 name = "fail-on-everything-filtered", 196 description = 197 "Whether or not to fail the invocation in case test filter returns" 198 + " an empty result.") 199 private boolean mFailOnEverythingFiltered = false; 200 201 @Option( 202 name = "ignore-non-preloaded-mainline-module", 203 description = 204 "Skip installing the module(s) when the module(s) that are not" 205 + "preloaded on device. Otherwise an exception will be thrown.") 206 private boolean mIgnoreNonPreloadedMainlineModule = false; 207 208 @Option( 209 name = "load-configs-with-include-filters", 210 description = 211 "An experimental flag to improve the performance of loading test configs with " 212 + "given module defined in include-filter.") 213 private boolean mLoadConfigsWithIncludeFilters = false; 214 215 private SuiteModuleLoader mModuleRepo; 216 private Map<String, LinkedHashSet<SuiteTestFilter>> mIncludeFiltersParsed = 217 new LinkedHashMap<>(); 218 private Map<String, LinkedHashSet<SuiteTestFilter>> mExcludeFiltersParsed = 219 new LinkedHashMap<>(); 220 private List<File> mConfigPaths = new ArrayList<>(); 221 private Set<IAbi> mAbis = new LinkedHashSet<>(); 222 private Set<DeviceFoldableState> mFoldableStates = new LinkedHashSet<>(); 223 setSkipjarLoading(boolean skipJarLoading)224 public void setSkipjarLoading(boolean skipJarLoading) { 225 mSkipJarLoading = skipJarLoading; 226 } 227 228 /** {@inheritDoc} */ 229 @Override loadTests()230 public LinkedHashMap<String, IConfiguration> loadTests() { 231 try { 232 File testsDir = getTestsDir(); 233 try { 234 mFoldableStates = getFoldableStates(getDevice()); 235 } catch (UnsupportedOperationException e) { 236 // Foldable state isn't always supported 237 CLog.e(e); 238 } 239 setupFilters(testsDir); 240 mAbis = getAbis(getDevice()); 241 242 if (mReverseExcludeFilters) { 243 if (mExcludeFilters.isEmpty()) { 244 return new LinkedHashMap<String, IConfiguration>(); 245 } 246 mIncludeFilters.clear(); 247 mIncludeFilters.addAll(mExcludeFilters); 248 mExcludeFilters.clear(); 249 } 250 251 // Create and populate the filters here 252 SuiteModuleLoader.addFilters( 253 mIncludeFilters, mIncludeFiltersParsed, mAbis, mFoldableStates); 254 SuiteModuleLoader.addFilters( 255 mExcludeFilters, mExcludeFiltersParsed, mAbis, mFoldableStates); 256 257 String includeFilters = ""; 258 if (mIncludeFiltersParsed.size() > MAX_FILTER_DISPLAY) { 259 if (isSplitting()) { 260 includeFilters = "Includes: <too long to display>"; 261 } else { 262 File suiteIncludeFilters = null; 263 try { 264 suiteIncludeFilters = 265 FileUtil.createTempFile("suite-include-filters", ".txt"); 266 FileUtil.writeToFile(mIncludeFiltersParsed.toString(), suiteIncludeFilters); 267 logFilterFile( 268 suiteIncludeFilters, 269 suiteIncludeFilters.getName(), 270 LogDataType.TEXT); 271 includeFilters = 272 String.format("Includes: See %s", suiteIncludeFilters.getName()); 273 } catch (IOException e) { 274 CLog.e(e); 275 } finally { 276 FileUtil.deleteFile(suiteIncludeFilters); 277 } 278 } 279 } else if (mIncludeFiltersParsed.size() > 0) { 280 includeFilters = String.format("Includes: %s", mIncludeFiltersParsed.toString()); 281 } 282 283 String excludeFilters = ""; 284 if (mExcludeFiltersParsed.size() > MAX_FILTER_DISPLAY) { 285 if (isSplitting()) { 286 excludeFilters = "Excludes: <too long to display>"; 287 } else { 288 File suiteExcludeFilters = null; 289 try { 290 suiteExcludeFilters = 291 FileUtil.createTempFile("suite-exclude-filters", ".txt"); 292 FileUtil.writeToFile(mExcludeFiltersParsed.toString(), suiteExcludeFilters); 293 logFilterFile( 294 suiteExcludeFilters, 295 suiteExcludeFilters.getName(), 296 LogDataType.TEXT); 297 excludeFilters = 298 String.format("Excludes: See %s", suiteExcludeFilters.getName()); 299 } catch (IOException e) { 300 CLog.e(e); 301 } finally { 302 FileUtil.deleteFile(suiteExcludeFilters); 303 } 304 } 305 } else if (mExcludeFiltersParsed.size() > 0) { 306 excludeFilters = String.format("Excludes: %s", mExcludeFiltersParsed.toString()); 307 } 308 309 CLog.d( 310 "Initializing ModuleRepo\nABIs:%s\n" + "Test Args:%s\nModule Args:%s\n%s\n%s", 311 mAbis, mTestArgs, mModuleArgs, includeFilters, excludeFilters); 312 if (!mFoldableStates.isEmpty()) { 313 CLog.d("Foldable states: %s", mFoldableStates); 314 } 315 316 mModuleRepo = 317 createModuleLoader( 318 mIncludeFiltersParsed, mExcludeFiltersParsed, mTestArgs, mModuleArgs); 319 if (mForceParameter != null && !mEnableParameter) { 320 throw new IllegalArgumentException( 321 "'module-parameter' option was specified without " 322 + "'enable-parameterized-modules'"); 323 } 324 if (mEnableOptionalParameter && !mEnableParameter) { 325 throw new IllegalArgumentException( 326 "'enable-optional-parameterization' option was specified without " 327 + "'enable-parameterized-modules'"); 328 } 329 330 if (mEnableMainlineParameter) { 331 mModuleRepo.setMainlineParameterizedModules(mEnableMainlineParameter); 332 mModuleRepo.setInvocationContext(getInvocationContext()); 333 mModuleRepo.setOptimizeMainlineTest( 334 getConfiguration().getCommandOptions().getOptimizeMainlineTest()); 335 mModuleRepo.setIgnoreNonPreloadedMainlineModule(mIgnoreNonPreloadedMainlineModule); 336 } 337 338 mModuleRepo.setParameterizedModules(mEnableParameter); 339 mModuleRepo.setOptionalParameterizedModules(mEnableOptionalParameter); 340 mModuleRepo.setModuleParameter(mForceParameter); 341 mModuleRepo.setExcludedModuleParameters(mExcludedModuleParameters); 342 mModuleRepo.setFoldableStates(mFoldableStates); 343 mModuleRepo.setLoadConfigsWithIncludeFilters(mLoadConfigsWithIncludeFilters); 344 345 List<File> testsDirectories = new ArrayList<>(); 346 347 // Include host or target first in the search if it exists, we have to this in 348 // BaseTestSuite because it's the only one with the BuildInfo knowledge of linked files 349 if (getPrioritizeHostConfig()) { 350 File hostSubDir = getBuildInfo().getFile(BuildInfoFileKey.HOST_LINKED_DIR); 351 if (hostSubDir != null && hostSubDir.exists()) { 352 testsDirectories.add(hostSubDir); 353 } 354 } else { 355 File targetSubDir = getBuildInfo().getFile(BuildInfoFileKey.TARGET_LINKED_DIR); 356 if (targetSubDir != null && targetSubDir.exists()) { 357 testsDirectories.add(targetSubDir); 358 } 359 } 360 361 // Finally add the full test cases directory in case there is no special sub-dir. 362 testsDirectories.add(testsDir); 363 // Actual loading of the configurations. 364 long start = System.currentTimeMillis(); 365 CLog.d("Loading tests from %s", testsDirectories); 366 LinkedHashMap<String, IConfiguration> loadedTests = 367 loadingStrategy(mAbis, testsDirectories, mSuitePrefix, mSuiteTag); 368 long duration = System.currentTimeMillis() - start; 369 InvocationMetricLogger.addInvocationMetrics( 370 InvocationMetricKey.LOAD_TEST_CONFIGS_TIME, duration); 371 if (mFailOnEverythingFiltered 372 && loadedTests.isEmpty() 373 && !mIncludeFiltersParsed.isEmpty()) { 374 // remove modules with empty filters from the message 375 Map<String, LinkedHashSet<SuiteTestFilter>> includeFiltersCleaned = 376 mIncludeFiltersParsed.entrySet().stream() 377 .filter( 378 entry -> 379 entry.getValue() != null 380 && !entry.getValue().isEmpty()) 381 .collect( 382 Collectors.toMap( 383 Map.Entry::getKey, 384 Map.Entry::getValue, 385 (x, y) -> y, 386 LinkedHashMap::new)); 387 String errorMessage = 388 String.format( 389 "Include filter '%s' was specified but resulted in an empty test" 390 + " set.", 391 includeFiltersCleaned.toString()); 392 if (errorMessage.length() > 1000) { 393 CLog.e(errorMessage); 394 errorMessage = 395 String.format( 396 "Include filter was specified for %d modules but resulted in an" 397 + " empty test set. Check host log for complete list of" 398 + " include filters.", 399 includeFiltersCleaned.size()); 400 } 401 throw new HarnessRuntimeException( 402 errorMessage, InfraErrorIdentifier.OPTION_CONFIGURATION_ERROR); 403 } 404 return loadedTests; 405 } catch (DeviceNotAvailableException e) { 406 throw new HarnessRuntimeException(e.getMessage(), e); 407 } catch (FileNotFoundException fnfe) { 408 throw new HarnessRuntimeException( 409 fnfe.getMessage(), fnfe, InfraErrorIdentifier.ARTIFACT_NOT_FOUND); 410 } 411 } 412 413 /** 414 * Default loading strategy will load from the resources and the tests directory. Can be 415 * extended or replaced. 416 * 417 * @param abis The set of abis to run against. 418 * @param testsDirs The tests directory. 419 * @param suitePrefix A prefix to filter the resource directory. 420 * @param suiteTag The suite tag a module should have to be included. Can be null. 421 * @return A list of loaded configuration for the suite. 422 */ loadingStrategy( Set<IAbi> abis, List<File> testsDirs, String suitePrefix, String suiteTag)423 public LinkedHashMap<String, IConfiguration> loadingStrategy( 424 Set<IAbi> abis, List<File> testsDirs, String suitePrefix, String suiteTag) { 425 LinkedHashMap<String, IConfiguration> loadedConfigs = new LinkedHashMap<>(); 426 // Load and return directly the specific config files. 427 if (!mConfigPaths.isEmpty()) { 428 CLog.d( 429 "Loading the specified configs path '%s' and skip loading from the resources.", 430 mConfigPaths); 431 return getModuleLoader().loadConfigsFromSpecifiedPaths(mConfigPaths, abis, suiteTag); 432 } 433 434 // Load configs that are part of the resources 435 if (!mSkipJarLoading) { 436 loadedConfigs.putAll( 437 getModuleLoader().loadConfigsFromJars(abis, suitePrefix, suiteTag)); 438 } 439 440 // Load the configs that are part of the tests dir 441 if (mConfigPatterns.isEmpty()) { 442 // If no special pattern was configured, use the default configuration patterns we know 443 mConfigPatterns.add(".*\\.config$"); 444 mConfigPatterns.add(".*\\.xml$"); 445 } 446 447 loadedConfigs.putAll( 448 getModuleLoader() 449 .loadConfigsFromDirectory( 450 testsDirs, abis, suitePrefix, suiteTag, mConfigPatterns)); 451 return loadedConfigs; 452 } 453 454 /** {@inheritDoc} */ 455 @Override setBuild(IBuildInfo buildInfo)456 public void setBuild(IBuildInfo buildInfo) { 457 super.setBuild(buildInfo); 458 } 459 460 /** Sets include-filters for the compatibility test */ setIncludeFilter(Set<String> includeFilters)461 public void setIncludeFilter(Set<String> includeFilters) { 462 mIncludeFilters.addAll(includeFilters); 463 } 464 465 /** Gets a copy of include-filters for the compatibility test */ getIncludeFilter()466 public Set<String> getIncludeFilter() { 467 return new LinkedHashSet<String>(mIncludeFilters); 468 } 469 clearIncludeFilter()470 public void clearIncludeFilter() { 471 mIncludeFilters.clear(); 472 } 473 474 /** Sets exclude-filters for the compatibility test */ setExcludeFilter(Set<String> excludeFilters)475 public void setExcludeFilter(Set<String> excludeFilters) { 476 mExcludeFilters.addAll(excludeFilters); 477 } 478 clearExcludeFilter()479 public void clearExcludeFilter() { 480 mExcludeFilters.clear(); 481 } 482 483 /** Gets a copy of exclude-filters for the compatibility test */ getExcludeFilter()484 public Set<String> getExcludeFilter() { 485 return new HashSet<String>(mExcludeFilters); 486 } 487 488 /** Returns the current {@link SuiteModuleLoader}. */ getModuleLoader()489 public SuiteModuleLoader getModuleLoader() { 490 return mModuleRepo; 491 } 492 reevaluateFilters()493 public void reevaluateFilters() { 494 SuiteModuleLoader.addFilters( 495 mIncludeFilters, mIncludeFiltersParsed, mAbis, mFoldableStates); 496 SuiteModuleLoader.addFilters( 497 mExcludeFilters, mExcludeFiltersParsed, mAbis, mFoldableStates); 498 if (getDirectModule() != null) { 499 // Remove all entries for unrelated modules 500 mExcludeFiltersParsed.keySet().removeIf(key -> !key.equals(getDirectModule().getId())); 501 // Also clean exclude filters left over 502 for (String filterString : new HashSet<>(mExcludeFilters)) { 503 SuiteTestFilter parentFilter = SuiteTestFilter.createFrom(filterString); 504 if (!parentFilter.getModuleId().equals(getDirectModule().getId())) { 505 mExcludeFilters.remove(filterString); 506 } 507 } 508 } 509 } 510 511 /** Adds module args */ addModuleArgs(Set<String> moduleArgs)512 public void addModuleArgs(Set<String> moduleArgs) { 513 mModuleArgs.addAll(moduleArgs); 514 } 515 516 /** Clear the stored module args out */ clearModuleArgs()517 void clearModuleArgs() { 518 mModuleArgs.clear(); 519 } 520 521 /** Add config patterns */ addConfigPatterns(List<String> patterns)522 public void addConfigPatterns(List<String> patterns) { 523 mConfigPatterns.addAll(patterns); 524 } 525 526 /** Set whether or not parameterized modules are enabled or not. */ setEnableParameterizedModules(boolean enableParameter)527 public void setEnableParameterizedModules(boolean enableParameter) { 528 mEnableParameter = enableParameter; 529 } 530 531 /** Set whether or not optional parameterized modules are enabled or not. */ setEnableOptionalParameterizedModules(boolean enableOptionalParameter)532 public void setEnableOptionalParameterizedModules(boolean enableOptionalParameter) { 533 mEnableOptionalParameter = enableOptionalParameter; 534 } 535 setModuleParameter(ModuleParameters forceParameter)536 public void setModuleParameter(ModuleParameters forceParameter) { 537 mForceParameter = forceParameter; 538 } 539 540 /** 541 * Create the {@link SuiteModuleLoader} responsible to load the {@link IConfiguration} and 542 * assign them some of the options. 543 * 544 * @param includeFiltersFormatted The formatted and parsed include filters. 545 * @param excludeFiltersFormatted The formatted and parsed exclude filters. 546 * @param testArgs the list of test ({@link IRemoteTest}) arguments. 547 * @param moduleArgs the list of module arguments. 548 * @return the created {@link SuiteModuleLoader}. 549 */ createModuleLoader( Map<String, LinkedHashSet<SuiteTestFilter>> includeFiltersFormatted, Map<String, LinkedHashSet<SuiteTestFilter>> excludeFiltersFormatted, List<String> testArgs, List<String> moduleArgs)550 public SuiteModuleLoader createModuleLoader( 551 Map<String, LinkedHashSet<SuiteTestFilter>> includeFiltersFormatted, 552 Map<String, LinkedHashSet<SuiteTestFilter>> excludeFiltersFormatted, 553 List<String> testArgs, 554 List<String> moduleArgs) { 555 return new SuiteModuleLoader( 556 includeFiltersFormatted, excludeFiltersFormatted, testArgs, moduleArgs); 557 } 558 559 /** 560 * Sets the include/exclude filters up based on if a module name was given. 561 * 562 * @throws FileNotFoundException if any file is not found. 563 */ setupFilters(File testsDir)564 protected void setupFilters(File testsDir) throws FileNotFoundException { 565 if (mModuleName == null) { 566 if (mTestName != null) { 567 throw new IllegalArgumentException( 568 "Test name given without module name. Add --module <module-name>"); 569 } 570 return; 571 } 572 // If this option (-m / --module) is set only the matching unique module should run. 573 Set<File> modules = 574 SuiteModuleLoader.getModuleNamesMatching( 575 testsDir, mSuitePrefix, String.format(".*%s.*.config", mModuleName)); 576 // If multiple modules match, do exact match. 577 if (modules.size() > 1) { 578 Set<File> newModules = new HashSet<>(); 579 String exactModuleName = String.format("%s.config", mModuleName); 580 for (File module : modules) { 581 if (module.getName().equals(exactModuleName)) { 582 newModules.add(module); 583 modules = newModules; 584 break; 585 } 586 } 587 } 588 if (modules.size() == 0) { 589 throw new HarnessRuntimeException( 590 String.format("No modules found matching %s", mModuleName), 591 InfraErrorIdentifier.OPTION_CONFIGURATION_ERROR); 592 } else if (modules.size() > 1) { 593 throw new HarnessRuntimeException( 594 String.format( 595 "Multiple modules found matching %s:\n%s\nWhich one did you " 596 + "mean?\n", 597 mModuleName, ArrayUtil.join("\n", modules)), 598 InfraErrorIdentifier.OPTION_CONFIGURATION_ERROR); 599 } else { 600 File mod = modules.iterator().next(); 601 String moduleName = mod.getName().replace(".config", ""); 602 checkFilters(mIncludeFilters, moduleName); 603 checkFilters(mExcludeFilters, moduleName); 604 mIncludeFilters.add( 605 new SuiteTestFilter(getRequestedAbi(), moduleName, mTestName).toString()); 606 // Create the matching filters for the parameterized version of it if needed. 607 if (mEnableParameter) { 608 for (ModuleParameters param : ModuleParameters.values()) { 609 Map<ModuleParameters, IModuleParameterHandler> moduleParamExpanded = 610 ModuleParametersHelper.resolveParam(param, mEnableOptionalParameter); 611 if (moduleParamExpanded == null) { 612 continue; 613 } 614 for (Entry<ModuleParameters, IModuleParameterHandler> moduleParam : 615 moduleParamExpanded.entrySet()) { 616 if (moduleParam.getValue() instanceof NegativeHandler) { 617 continue; 618 } 619 if (moduleParam.getValue() instanceof FoldableExpandingHandler) { 620 List<IModuleParameterHandler> foldableHandlers = 621 ((FoldableExpandingHandler) moduleParam.getValue()) 622 .expandHandler(mFoldableStates); 623 for (IModuleParameterHandler foldableHandler : foldableHandlers) { 624 String paramModuleName = 625 String.format( 626 "%s[%s]", 627 moduleName, 628 foldableHandler.getParameterIdentifier()); 629 mIncludeFilters.add( 630 new SuiteTestFilter( 631 getRequestedAbi(), 632 paramModuleName, 633 mTestName) 634 .toString()); 635 } 636 continue; 637 } 638 String paramModuleName = 639 String.format( 640 "%s[%s]", 641 moduleName, 642 moduleParam.getValue().getParameterIdentifier()); 643 mIncludeFilters.add( 644 new SuiteTestFilter(getRequestedAbi(), paramModuleName, mTestName) 645 .toString()); 646 } 647 } 648 } 649 } 650 } 651 652 @Override cleanUpSuiteSetup()653 public void cleanUpSuiteSetup() { 654 super.cleanUpSuiteSetup(); 655 // Clean the filters because at that point they have been applied to the runners. 656 // This can save several GB of memories during sharding. 657 mIncludeFilters.clear(); 658 mExcludeFilters.clear(); 659 mIncludeFiltersParsed.clear(); 660 mExcludeFiltersParsed.clear(); 661 } 662 663 /** 664 * Add the config path for {@link SuiteModuleLoader} to limit the search loading configurations. 665 * 666 * @param configPath A {@code File} with the absolute path of the configuration. 667 */ addConfigPaths(File configPath)668 void addConfigPaths(File configPath) { 669 mConfigPaths.add(configPath); 670 } 671 672 /** Clear the stored config paths out. */ clearConfigPaths()673 void clearConfigPaths() { 674 mConfigPaths.clear(); 675 } 676 677 /* Helper method designed to remove filters in a list not applicable to the given module */ checkFilters(Set<String> filters, String moduleName)678 private static void checkFilters(Set<String> filters, String moduleName) { 679 Set<String> cleanedFilters = new HashSet<String>(); 680 for (String filter : filters) { 681 SuiteTestFilter filterObject = SuiteTestFilter.createFrom(filter); 682 String filterName = filterObject.getName(); 683 String filterBaseName = filterObject.getBaseName(); 684 if (moduleName.equals(filterName) || moduleName.equals(filterBaseName)) { 685 cleanedFilters.add(filter); // Module name matches, filter passes 686 } 687 } 688 filters.clear(); 689 filters.addAll(cleanedFilters); 690 } 691 692 /** Log a file directly to the result reporter. */ logFilterFile(File filterFile, String dataName, LogDataType type)693 private void logFilterFile(File filterFile, String dataName, LogDataType type) { 694 if (getCurrentTestLogger() == null) { 695 return; 696 } 697 try (FileInputStreamSource source = new FileInputStreamSource(filterFile)) { 698 getCurrentTestLogger().testLog(dataName, type, source); 699 } 700 } 701 702 @Override shouldModuleRun(ModuleDefinition module)703 protected boolean shouldModuleRun(ModuleDefinition module) { 704 String moduleId = module.getId(); 705 LinkedHashSet<SuiteTestFilter> excludeFilters = mExcludeFiltersParsed.get(moduleId); 706 CLog.d("Filters for '%s': %s", moduleId, excludeFilters); 707 if (excludeFilters == null || excludeFilters.isEmpty()) { 708 return true; 709 } 710 for (SuiteTestFilter filter : excludeFilters) { 711 if (filter.getTest() == null) { 712 CLog.d("Skipping %s, it previously passed.", moduleId); 713 return false; 714 } 715 for (IRemoteTest test : module.getTests()) { 716 if (test instanceof ITestFileFilterReceiver) { 717 File excludeFilterFile = ((ITestFileFilterReceiver) test).getExcludeTestFile(); 718 if (excludeFilterFile == null) { 719 try { 720 excludeFilterFile = FileUtil.createTempFile("exclude-filter", ".txt"); 721 } catch (IOException e) { 722 throw new HarnessRuntimeException( 723 e.getMessage(), e, InfraErrorIdentifier.FAIL_TO_CREATE_FILE); 724 } 725 ((ITestFileFilterReceiver) test).setExcludeTestFile(excludeFilterFile); 726 } 727 try { 728 FileUtil.writeToFile(filter.getTest() + "\n", excludeFilterFile, true); 729 } catch (IOException e) { 730 CLog.e(e); 731 continue; 732 } 733 } else if (test instanceof ITestFilterReceiver) { 734 ((ITestFilterReceiver) test).addExcludeFilter(filter.getTest()); 735 } 736 } 737 } 738 return true; 739 } 740 getFoldableStates(ITestDevice device)741 protected Set<DeviceFoldableState> getFoldableStates(ITestDevice device) 742 throws DeviceNotAvailableException { 743 if (device == null || device.getIDevice() instanceof StubDevice) { 744 return mFoldableStates; 745 } 746 if (!mFoldableStates.isEmpty()) { 747 return mFoldableStates; 748 } 749 mFoldableStates = device.getFoldableStates(); 750 return mFoldableStates; 751 } 752 getRunSuiteTag()753 public String getRunSuiteTag() { 754 return mSuiteTag; 755 } 756 reverseExcludeFilters()757 public boolean reverseExcludeFilters() { 758 return mReverseExcludeFilters; 759 } 760 } 761