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.annotations.VisibleForTesting; 19 import com.android.tradefed.config.ConfigurationDescriptor; 20 import com.android.tradefed.config.ConfigurationException; 21 import com.android.tradefed.config.ConfigurationFactory; 22 import com.android.tradefed.config.ConfigurationUtil; 23 import com.android.tradefed.config.IConfiguration; 24 import com.android.tradefed.config.IConfigurationFactory; 25 import com.android.tradefed.config.IDeviceConfiguration; 26 import com.android.tradefed.config.OptionDef; 27 import com.android.tradefed.config.OptionSetter; 28 import com.android.tradefed.device.DeviceFoldableState; 29 import com.android.tradefed.error.HarnessRuntimeException; 30 import com.android.tradefed.invoker.IInvocationContext; 31 import com.android.tradefed.log.LogUtil.CLog; 32 import com.android.tradefed.result.error.InfraErrorIdentifier; 33 import com.android.tradefed.targetprep.ITargetPreparer; 34 import com.android.tradefed.testtype.IAbi; 35 import com.android.tradefed.testtype.IAbiReceiver; 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.MainlineModuleHandler; 42 import com.android.tradefed.testtype.suite.params.ModuleParameters; 43 import com.android.tradefed.testtype.suite.params.ModuleParametersHelper; 44 import com.android.tradefed.testtype.suite.params.NegativeHandler; 45 import com.android.tradefed.testtype.suite.params.NotMultiAbiHandler; 46 import com.android.tradefed.util.AbiUtils; 47 import com.android.tradefed.util.FileUtil; 48 import com.android.tradefed.util.StreamUtil; 49 50 import com.google.common.base.Strings; 51 import com.google.common.net.UrlEscapers; 52 53 import java.io.File; 54 import java.io.FilenameFilter; 55 import java.io.IOException; 56 import java.io.PrintWriter; 57 import java.util.ArrayList; 58 import java.util.Arrays; 59 import java.util.Collection; 60 import java.util.Collections; 61 import java.util.HashMap; 62 import java.util.HashSet; 63 import java.util.LinkedHashMap; 64 import java.util.LinkedHashSet; 65 import java.util.List; 66 import java.util.Map; 67 import java.util.Map.Entry; 68 import java.util.Set; 69 import java.util.regex.Matcher; 70 import java.util.regex.Pattern; 71 import java.util.stream.Collectors; 72 73 /** 74 * Retrieves Compatibility test module definitions from the repository. TODO: Add the expansion of 75 * suite when loading a module. 76 */ 77 public class SuiteModuleLoader { 78 79 public static final String CONFIG_EXT = ".config"; 80 private Map<String, List<OptionDef>> mTestOrPreparerOptions = new HashMap<>(); 81 private Map<String, List<OptionDef>> mModuleOptions = new HashMap<>(); 82 private boolean mIncludeAll; 83 private Map<String, LinkedHashSet<SuiteTestFilter>> mIncludeFilters = new HashMap<>(); 84 private Map<String, LinkedHashSet<SuiteTestFilter>> mExcludeFilters = new HashMap<>(); 85 private IConfigurationFactory mConfigFactory = ConfigurationFactory.getInstance(); 86 private IInvocationContext mContext; 87 88 private boolean mAllowParameterizedModules = false; 89 private boolean mAllowMainlineParameterizedModules = false; 90 private boolean mOptimizeMainlineTest = false; 91 private boolean mIgnoreNonPreloadedMainlineModule = false; 92 private boolean mAllowOptionalParameterizedModules = false; 93 private boolean mLoadConfigsWithIncludeFilters = false; 94 private ModuleParameters mForcedModuleParameter = null; 95 private Set<ModuleParameters> mExcludedModuleParameters = new HashSet<>(); 96 private Set<DeviceFoldableState> mFoldableStates = new LinkedHashSet<>(); 97 // Check the mainline parameter configured in a test config must end with .apk, .apks, or .apex. 98 private static final Set<String> MAINLINE_PARAMETERS_TO_VALIDATE = 99 new HashSet<>(Arrays.asList(".apk", ".apks", ".apex")); 100 101 /** 102 * Ctor for the SuiteModuleLoader. 103 * 104 * @param includeFilters The formatted and parsed include filters. 105 * @param excludeFilters The formatted and parsed exclude filters. 106 * @param testArgs the list of test ({@link IRemoteTest}) arguments. 107 * @param moduleArgs the list of module arguments. 108 */ SuiteModuleLoader( Map<String, LinkedHashSet<SuiteTestFilter>> includeFilters, Map<String, LinkedHashSet<SuiteTestFilter>> excludeFilters, List<String> testArgs, List<String> moduleArgs)109 public SuiteModuleLoader( 110 Map<String, LinkedHashSet<SuiteTestFilter>> includeFilters, 111 Map<String, LinkedHashSet<SuiteTestFilter>> excludeFilters, 112 List<String> testArgs, 113 List<String> moduleArgs) { 114 mIncludeAll = includeFilters.isEmpty(); 115 mIncludeFilters = includeFilters; 116 mExcludeFilters = excludeFilters; 117 118 parseArgs(testArgs, mTestOrPreparerOptions); 119 parseArgs(moduleArgs, mModuleOptions); 120 } 121 setInvocationContext(IInvocationContext context)122 public final void setInvocationContext(IInvocationContext context) { 123 mContext = context; 124 } 125 126 /** Sets whether or not to allow parameterized modules. */ setParameterizedModules(boolean allowed)127 public final void setParameterizedModules(boolean allowed) { 128 mAllowParameterizedModules = allowed; 129 } 130 131 /** Sets whether or not to allow parameterized mainline modules. */ setMainlineParameterizedModules(boolean allowed)132 public final void setMainlineParameterizedModules(boolean allowed) { 133 mAllowMainlineParameterizedModules = allowed; 134 } 135 136 /** Sets whether or not to optimize mainline test. */ setOptimizeMainlineTest(boolean allowed)137 public final void setOptimizeMainlineTest(boolean allowed) { 138 mOptimizeMainlineTest = allowed; 139 } 140 141 /** Sets whether or not to ignore installing the module if it is not preloaded. */ setIgnoreNonPreloadedMainlineModule(boolean ignore)142 public final void setIgnoreNonPreloadedMainlineModule(boolean ignore) { 143 mIgnoreNonPreloadedMainlineModule = ignore; 144 } 145 146 /** Sets whether or not to allow optional parameterized modules. */ setOptionalParameterizedModules(boolean allowed)147 public final void setOptionalParameterizedModules(boolean allowed) { 148 mAllowOptionalParameterizedModules = allowed; 149 } 150 151 /** Sets whether or not to load test config based on the given include-filter. */ setLoadConfigsWithIncludeFilters(boolean allowed)152 public final void setLoadConfigsWithIncludeFilters(boolean allowed) { 153 mLoadConfigsWithIncludeFilters = allowed; 154 } 155 156 /** Sets the only {@link ModuleParameters} type that should be run. */ setModuleParameter(ModuleParameters param)157 public final void setModuleParameter(ModuleParameters param) { 158 mForcedModuleParameter = param; 159 } 160 161 /** Sets the set of {@link ModuleParameters} that should not be considered at all. */ setExcludedModuleParameters(Set<ModuleParameters> excludedParams)162 public final void setExcludedModuleParameters(Set<ModuleParameters> excludedParams) { 163 mExcludedModuleParameters = excludedParams; 164 } 165 166 /** Sets the set of {@link DeviceFoldableState} that should be run. */ setFoldableStates(Set<DeviceFoldableState> foldableStates)167 public final void setFoldableStates(Set<DeviceFoldableState> foldableStates) { 168 mFoldableStates = foldableStates; 169 } 170 171 /** Main loading of configurations, looking into the specified files */ loadConfigsFromSpecifiedPaths( List<File> listConfigFiles, Set<IAbi> abis, String suiteTag)172 public LinkedHashMap<String, IConfiguration> loadConfigsFromSpecifiedPaths( 173 List<File> listConfigFiles, Set<IAbi> abis, String suiteTag) { 174 LinkedHashMap<String, IConfiguration> toRun = new LinkedHashMap<>(); 175 for (File configFile : listConfigFiles) { 176 toRun.putAll( 177 loadOneConfig( 178 configFile.getName(), configFile.getAbsolutePath(), abis, suiteTag)); 179 } 180 return toRun; 181 } 182 183 /** Main loading of configurations, looking into a folder */ loadConfigsFromDirectory( List<File> testsDirs, Set<IAbi> abis, String suitePrefix, String suiteTag, List<String> patterns)184 public LinkedHashMap<String, IConfiguration> loadConfigsFromDirectory( 185 List<File> testsDirs, 186 Set<IAbi> abis, 187 String suitePrefix, 188 String suiteTag, 189 List<String> patterns) { 190 LinkedHashMap<String, IConfiguration> toRun = new LinkedHashMap<>(); 191 List<File> listConfigFiles = new ArrayList<>(); 192 listConfigFiles.addAll( 193 ConfigurationUtil.getConfigNamesFileFromDirs(suitePrefix, testsDirs, patterns)); 194 if (mLoadConfigsWithIncludeFilters && !mIncludeFilters.isEmpty()) { 195 CLog.i("Loading test configs based on the given include-filter."); 196 Set<String> filteredConfigNames = new HashSet<>(); 197 for (LinkedHashSet<SuiteTestFilter> entry : mIncludeFilters.values()) { 198 for (SuiteTestFilter file : entry) { 199 // Collect the test config name based on the given include filter. 200 filteredConfigNames.add(String.format("%s.config", file.getBaseName())); 201 } 202 } 203 // Filter the test configs out based on the collected test config names. 204 List<File> filteredConfigs = 205 listConfigFiles.stream() 206 .filter(f -> filteredConfigNames.contains(f.getName())) 207 .collect(Collectors.toList()); 208 listConfigFiles.clear(); 209 listConfigFiles.addAll(filteredConfigs); 210 } 211 // Ensure stable initial order of configurations. 212 Collections.sort(listConfigFiles); 213 toRun.putAll(loadConfigsFromSpecifiedPaths(listConfigFiles, abis, suiteTag)); 214 return toRun; 215 } 216 217 /** 218 * Main loading of configurations, looking into the resources on the classpath. (TF configs for 219 * example). 220 */ loadConfigsFromJars( Set<IAbi> abis, String suitePrefix, String suiteTag)221 public LinkedHashMap<String, IConfiguration> loadConfigsFromJars( 222 Set<IAbi> abis, String suitePrefix, String suiteTag) { 223 LinkedHashMap<String, IConfiguration> toRun = new LinkedHashMap<>(); 224 225 IConfigurationFactory configFactory = ConfigurationFactory.getInstance(); 226 List<String> configs = configFactory.getConfigList(suitePrefix, false); 227 // Sort configs to ensure they are always evaluated and added in the same order. 228 Collections.sort(configs); 229 toRun.putAll(loadTfConfigsFromSpecifiedPaths(configs, abis, suiteTag)); 230 return toRun; 231 } 232 233 /** Main loading of configurations, looking into the specified resources on the classpath. */ loadTfConfigsFromSpecifiedPaths( List<String> configs, Set<IAbi> abis, String suiteTag)234 public LinkedHashMap<String, IConfiguration> loadTfConfigsFromSpecifiedPaths( 235 List<String> configs, Set<IAbi> abis, String suiteTag) { 236 LinkedHashMap<String, IConfiguration> toRun = new LinkedHashMap<>(); 237 for (String configName : configs) { 238 toRun.putAll(loadOneConfig(configName, configName, abis, suiteTag)); 239 } 240 return toRun; 241 } 242 243 /** 244 * Pass the filters to the {@link IRemoteTest}. Default behavior is to ignore if the IRemoteTest 245 * does not implements {@link ITestFileFilterReceiver}. This can be overriden to create a more 246 * restrictive behavior. 247 * 248 * @param test The {@link IRemoteTest} that is being considered. 249 * @param abi The Abi we are currently working on. 250 * @param moduleId The id of the module (usually abi + module name). 251 * @param includeFilters The formatted and parsed include filters. 252 * @param excludeFilters The formatted and parsed exclude filters. 253 */ addFiltersToTest( IRemoteTest test, IAbi abi, String moduleId, Map<String, LinkedHashSet<SuiteTestFilter>> includeFilters, Map<String, LinkedHashSet<SuiteTestFilter>> excludeFilters)254 public void addFiltersToTest( 255 IRemoteTest test, 256 IAbi abi, 257 String moduleId, 258 Map<String, LinkedHashSet<SuiteTestFilter>> includeFilters, 259 Map<String, LinkedHashSet<SuiteTestFilter>> excludeFilters) { 260 261 if (!(test instanceof ITestFilterReceiver)) { 262 CLog.e( 263 "Test (%s) in module %s does not implement ITestFilterReceiver.", 264 test.getClass().getName(), moduleId); 265 return; 266 } 267 LinkedHashSet<SuiteTestFilter> mdIncludes = getFilterList(includeFilters, moduleId); 268 LinkedHashSet<SuiteTestFilter> mdExcludes = getFilterList(excludeFilters, moduleId); 269 if (!mdIncludes.isEmpty()) { 270 addTestIncludes((ITestFilterReceiver) test, mdIncludes, moduleId); 271 } 272 if (!mdExcludes.isEmpty()) { 273 addTestExcludes((ITestFilterReceiver) test, mdExcludes, moduleId); 274 } 275 } 276 277 /** 278 * Load a single config location (file or on TF classpath). It can results in several {@link 279 * IConfiguration}. If a single configuration get expanded in different ways. 280 * 281 * @param configName The actual config name only. (no path) 282 * @param configFullName The fully qualified config name. (with path, if any). 283 * @param abis The set of all abis that needs to run. 284 * @param suiteTag the Tag of the suite aimed to be run. 285 * @return A map of loaded configuration. 286 */ loadOneConfig( String configName, String configFullName, Set<IAbi> abis, String suiteTag)287 private LinkedHashMap<String, IConfiguration> loadOneConfig( 288 String configName, String configFullName, Set<IAbi> abis, String suiteTag) { 289 LinkedHashMap<String, IConfiguration> toRun = new LinkedHashMap<>(); 290 final String name = configName.replace(CONFIG_EXT, ""); 291 final String[] pathArg = new String[] {configFullName}; 292 try { 293 boolean primaryAbi = true; 294 boolean shouldCreateMultiAbi = true; 295 // If a particular parameter was requested to be run, find it. 296 Set<IModuleParameterHandler> mForcedParameters = null; 297 Set<Class<?>> mForcedParameterClasses = null; 298 if (mForcedModuleParameter != null) { 299 mForcedParameters = new HashSet<>(); 300 Map<ModuleParameters, IModuleParameterHandler> moduleParameters = 301 ModuleParametersHelper.resolveParam( 302 mForcedModuleParameter, mAllowOptionalParameterizedModules); 303 mForcedParameterClasses = new HashSet<>(); 304 for (IModuleParameterHandler parameter : moduleParameters.values()) { 305 if (parameter instanceof FoldableExpandingHandler) { 306 for (IModuleParameterHandler fParam : 307 ((FoldableExpandingHandler) parameter) 308 .expandHandler(mFoldableStates)) { 309 mForcedParameterClasses.add(fParam.getClass()); 310 } 311 } else { 312 mForcedParameterClasses.add(parameter.getClass()); 313 } 314 } 315 } 316 317 // Invokes parser to process the test module config file 318 // Need to generate a different config for each ABI as we cannot guarantee the 319 // configs are idempotent. This however means we parse the same file multiple times 320 for (IAbi abi : abis) { 321 // Filter non-primary abi no matter what if not_multi_abi specified 322 if (!shouldCreateMultiAbi && !primaryAbi) { 323 continue; 324 } 325 String baseId = AbiUtils.createId(abi.getName(), name); 326 IConfiguration config = null; 327 try { 328 config = mConfigFactory.createConfigurationFromArgs(pathArg); 329 } catch (ConfigurationException e) { 330 // If the module should not have been running in the first place, give it a 331 // pass on the configuration failure. 332 if (!shouldRunModule(baseId)) { 333 primaryAbi = false; 334 // If the module should not run tests based on the state of filters, 335 // skip this name/abi combination. 336 continue; 337 } 338 throw e; 339 } 340 341 // If a suiteTag is used, we load with it. 342 if (!Strings.isNullOrEmpty(suiteTag) 343 && !config.getConfigurationDescription() 344 .getSuiteTags() 345 .contains(suiteTag)) { 346 // Do not print here, it could leave several hundred lines of logs. 347 continue; 348 } 349 350 boolean skipCreatingBaseConfig = false; 351 List<IModuleParameterHandler> params = null; 352 List<String> mainlineParams = new ArrayList<>(); 353 try { 354 params = getModuleParameters(name, config); 355 mainlineParams = getMainlineModuleParameters(config); 356 } catch (ConfigurationException e) { 357 // If the module should not have been running in the first place, give it a 358 // pass on the configuration failure. 359 if (!shouldRunModule(baseId)) { 360 primaryAbi = false; 361 // If the module should not run tests based on the state of filters, 362 // skip this name/abi combination. 363 continue; 364 } 365 throw e; 366 } 367 // Use the not_multi_abi metadata even if not in parameterized mode. 368 shouldCreateMultiAbi = shouldCreateMultiAbiForBase(params); 369 // Handle parameterized modules if enabled. 370 if (mAllowParameterizedModules) { 371 372 if (params.isEmpty() 373 && mForcedParameters != null 374 // If we have multiple forced parameters, NegativeHandler isn't a valid 375 // option 376 && !(mForcedParameters.size() != 1 377 || (mForcedParameters.iterator().next() 378 instanceof NegativeHandler))) { 379 // If the AndroidTest.xml doesn't specify any parameter but we forced a 380 // parameter like 'instant' to execute. In this case we don't create the 381 // standard module. 382 continue; 383 } 384 385 // If we find any parameterized combination. 386 for (IModuleParameterHandler param : params) { 387 if (param instanceof NegativeHandler) { 388 if (mForcedParameters != null 389 && !mForcedParameterClasses.contains(param.getClass())) { 390 skipCreatingBaseConfig = true; 391 } 392 continue; 393 } 394 if (mForcedParameters != null) { 395 // When a particular parameter is forced, only create it not the others 396 if (mForcedParameterClasses.contains(param.getClass())) { 397 skipCreatingBaseConfig = true; 398 } else { 399 continue; 400 } 401 } 402 // Only create primary abi of parameterized modules 403 if (!primaryAbi) { 404 continue; 405 } 406 String fullId = 407 String.format("%s[%s]", baseId, param.getParameterIdentifier()); 408 String nameWithParam = 409 String.format("%s[%s]", name, param.getParameterIdentifier()); 410 if (shouldRunParameterized( 411 baseId, fullId, nameWithParam, mForcedParameters)) { 412 IConfiguration paramConfig = 413 mConfigFactory.createConfigurationFromArgs(pathArg); 414 // Mark the parameter in the metadata 415 paramConfig 416 .getConfigurationDescription() 417 .addMetadata( 418 ConfigurationDescriptor.ACTIVE_PARAMETER_KEY, 419 param.getParameterIdentifier()); 420 param.addParameterSpecificConfig(paramConfig); 421 setUpConfig(name, nameWithParam, baseId, fullId, paramConfig, abi); 422 param.applySetup(paramConfig); 423 toRun.put(fullId, paramConfig); 424 } 425 } 426 } 427 428 if (mAllowMainlineParameterizedModules) { 429 // If no options defined in a test config, skip generating. 430 // TODO(easoncylee) This is still under discussion. 431 if (mainlineParams.isEmpty()) { 432 continue; 433 } 434 // If we find any parameterized combination for mainline modules. 435 for (String param : mainlineParams) { 436 String fullId = String.format("%s[%s]", baseId, param); 437 String nameWithParam = String.format("%s[%s]", name, param); 438 if (!shouldRunParameterized(baseId, fullId, nameWithParam, null)) { 439 continue; 440 } 441 // Create mainline handler for each defined mainline parameter. 442 MainlineModuleHandler handler = 443 new MainlineModuleHandler( 444 param, 445 abi, 446 mContext, 447 mOptimizeMainlineTest, 448 mIgnoreNonPreloadedMainlineModule); 449 skipCreatingBaseConfig = true; 450 IConfiguration paramConfig = 451 mConfigFactory.createConfigurationFromArgs(pathArg); 452 paramConfig 453 .getConfigurationDescription() 454 .addMetadata(ITestSuite.ACTIVE_MAINLINE_PARAMETER_KEY, param); 455 setUpConfig(name, nameWithParam, baseId, fullId, paramConfig, abi); 456 handler.applySetup(paramConfig); 457 toRun.put(fullId, paramConfig); 458 } 459 } 460 461 primaryAbi = false; 462 // If a parameterized form of the module was forced, we don't create the standard 463 // version of it. 464 if (skipCreatingBaseConfig) { 465 continue; 466 } 467 if (shouldRunModule(baseId)) { 468 // Always add the base regular configuration to the execution. 469 // Do not pass the nameWithParam in because it would cause the module args be 470 // injected into config twice if we pass nameWithParam using name. 471 setUpConfig(name, null, baseId, baseId, config, abi); 472 toRun.put(baseId, config); 473 } 474 } 475 } catch (ConfigurationException e) { 476 throw new HarnessRuntimeException( 477 String.format( 478 "Error parsing configuration: %s: '%s'", 479 configFullName, e.getMessage()), 480 e); 481 } 482 483 return toRun; 484 } 485 486 /** 487 * @return the {@link Set} of modules whose name contains the given pattern. 488 */ getModuleNamesMatching( File directory, String suitePrefix, String pattern)489 public static Set<File> getModuleNamesMatching( 490 File directory, String suitePrefix, String pattern) { 491 List<File> extraTestCasesDirs = Arrays.asList(directory); 492 List<String> patterns = new ArrayList<>(); 493 patterns.add(pattern); 494 Set<File> modules = 495 ConfigurationUtil.getConfigNamesFileFromDirs( 496 suitePrefix, extraTestCasesDirs, patterns); 497 return modules; 498 } 499 500 /** 501 * Utility method that allows to parse and create a structure with the option filters. 502 * 503 * @param stringFilters The original option filters format. 504 * @param filters The filters parsed from the string format. 505 * @param abis The Abis to consider in the filtering. 506 */ addFilters( Set<String> stringFilters, Map<String, LinkedHashSet<SuiteTestFilter>> filters, Set<IAbi> abis, Set<DeviceFoldableState> foldableStates)507 public static void addFilters( 508 Set<String> stringFilters, 509 Map<String, LinkedHashSet<SuiteTestFilter>> filters, 510 Set<IAbi> abis, 511 Set<DeviceFoldableState> foldableStates) { 512 for (String filterString : stringFilters) { 513 SuiteTestFilter parentFilter = SuiteTestFilter.createFrom(filterString); 514 List<SuiteTestFilter> expanded = expandFoldableFilters(parentFilter, foldableStates); 515 for (SuiteTestFilter filter : expanded) { 516 String abi = filter.getAbi(); 517 if (abi == null) { 518 for (IAbi a : abis) { 519 addFilter(a.getName(), filter, filters); 520 } 521 } else { 522 addFilter(abi, filter, filters); 523 } 524 } 525 } 526 } 527 expandFoldableFilters( SuiteTestFilter filter, Set<DeviceFoldableState> foldableStates)528 private static List<SuiteTestFilter> expandFoldableFilters( 529 SuiteTestFilter filter, Set<DeviceFoldableState> foldableStates) { 530 List<SuiteTestFilter> expandedFilters = new ArrayList<>(); 531 if (foldableStates == null || foldableStates.isEmpty()) { 532 expandedFilters.add(filter); 533 return expandedFilters; 534 } 535 if (!ModuleParameters.ALL_FOLDABLE_STATES.toString().equals(filter.getParameterName())) { 536 expandedFilters.add(filter); 537 return expandedFilters; 538 } 539 for (DeviceFoldableState state : foldableStates) { 540 String name = filter.getBaseName() + "[" + state.toString() + "]"; 541 expandedFilters.add( 542 new SuiteTestFilter( 543 filter.getShardIndex(), filter.getAbi(), name, filter.getTest())); 544 } 545 return expandedFilters; 546 } 547 addFilter( String abi, SuiteTestFilter filter, Map<String, LinkedHashSet<SuiteTestFilter>> filters)548 private static void addFilter( 549 String abi, 550 SuiteTestFilter filter, 551 Map<String, LinkedHashSet<SuiteTestFilter>> filters) { 552 getFilterList(filters, AbiUtils.createId(abi, filter.getName())).add(filter); 553 } 554 getFilterList( Map<String, LinkedHashSet<SuiteTestFilter>> filters, String id)555 private static LinkedHashSet<SuiteTestFilter> getFilterList( 556 Map<String, LinkedHashSet<SuiteTestFilter>> filters, String id) { 557 LinkedHashSet<SuiteTestFilter> fs = filters.get(id); 558 if (fs == null) { 559 fs = new LinkedHashSet<>(); 560 filters.put(id, fs); 561 } 562 return fs; 563 } 564 shouldRunModule(String moduleId)565 private boolean shouldRunModule(String moduleId) { 566 LinkedHashSet<SuiteTestFilter> mdIncludes = getFilterList(mIncludeFilters, moduleId); 567 LinkedHashSet<SuiteTestFilter> mdExcludes = getFilterList(mExcludeFilters, moduleId); 568 // if including all modules or includes exist for this module, and there are not excludes 569 // for the entire module, this module should be run. 570 return (mIncludeAll || !mdIncludes.isEmpty()) && !containsModuleExclude(mdExcludes); 571 } 572 573 /** 574 * Except if the parameterized module is explicitly excluded, including the base module result 575 * in including its parameterization variant. 576 */ shouldRunParameterized( String baseModuleId, String parameterModuleId, String nameWithParam, Set<IModuleParameterHandler> forcedModuleParameters)577 private boolean shouldRunParameterized( 578 String baseModuleId, 579 String parameterModuleId, 580 String nameWithParam, 581 Set<IModuleParameterHandler> forcedModuleParameters) { 582 // Explicitly excluded 583 LinkedHashSet<SuiteTestFilter> excluded = getFilterList(mExcludeFilters, parameterModuleId); 584 LinkedHashSet<SuiteTestFilter> excludedParam = 585 getFilterList(mExcludeFilters, nameWithParam); 586 if (containsModuleExclude(excluded) || containsModuleExclude(excludedParam)) { 587 return false; 588 } 589 590 // Implicitly included due to forced parameter 591 if (forcedModuleParameters != null) { 592 LinkedHashSet<SuiteTestFilter> baseInclude = 593 getFilterList(mIncludeFilters, baseModuleId); 594 if (!baseInclude.isEmpty()) { 595 return true; 596 } 597 } 598 // Explicitly included 599 LinkedHashSet<SuiteTestFilter> included = getFilterList(mIncludeFilters, parameterModuleId); 600 LinkedHashSet<SuiteTestFilter> includedParam = 601 getFilterList(mIncludeFilters, nameWithParam); 602 if (mIncludeAll || !included.isEmpty() || !includedParam.isEmpty()) { 603 return true; 604 } 605 return false; 606 } 607 addTestIncludes( ITestFilterReceiver test, Collection<SuiteTestFilter> includes, String moduleId)608 private void addTestIncludes( 609 ITestFilterReceiver test, Collection<SuiteTestFilter> includes, String moduleId) { 610 if (test instanceof ITestFileFilterReceiver) { 611 String escapedFileName = escapeFilterFileName(moduleId); 612 File includeFile = createFilterFile(escapedFileName, ".include", includes); 613 ((ITestFileFilterReceiver) test).setIncludeTestFile(includeFile); 614 } else { 615 // add test includes one at a time 616 for (SuiteTestFilter include : includes) { 617 String filterTestName = include.getTest(); 618 if (filterTestName != null) { 619 test.addIncludeFilter(filterTestName); 620 } 621 } 622 } 623 } 624 addTestExcludes( ITestFilterReceiver test, Collection<SuiteTestFilter> excludes, String moduleId)625 private void addTestExcludes( 626 ITestFilterReceiver test, Collection<SuiteTestFilter> excludes, String moduleId) { 627 if (test instanceof ITestFileFilterReceiver) { 628 String escapedFileName = escapeFilterFileName(moduleId); 629 File excludeFile = createFilterFile(escapedFileName, ".exclude", excludes); 630 ((ITestFileFilterReceiver) test).setExcludeTestFile(excludeFile); 631 } else { 632 // add test excludes one at a time 633 for (SuiteTestFilter exclude : excludes) { 634 test.addExcludeFilter(exclude.getTest()); 635 } 636 } 637 } 638 639 /** module id can contain special characters, avoid them for file names. */ escapeFilterFileName(String moduleId)640 private String escapeFilterFileName(String moduleId) { 641 String escaped = UrlEscapers.urlPathSegmentEscaper().escape(moduleId); 642 return escaped; 643 } 644 createFilterFile( String prefix, String suffix, Collection<SuiteTestFilter> filters)645 private File createFilterFile( 646 String prefix, String suffix, Collection<SuiteTestFilter> filters) { 647 File filterFile = null; 648 PrintWriter out = null; 649 try { 650 filterFile = FileUtil.createTempFile(prefix, suffix); 651 out = new PrintWriter(filterFile); 652 for (SuiteTestFilter filter : filters) { 653 String filterTest = filter.getTest(); 654 if (filterTest != null) { 655 out.println(filterTest); 656 } 657 } 658 out.flush(); 659 } catch (IOException e) { 660 throw new HarnessRuntimeException( 661 "Failed to create filter file", e, InfraErrorIdentifier.FAIL_TO_CREATE_FILE); 662 } finally { 663 StreamUtil.close(out); 664 } 665 filterFile.deleteOnExit(); 666 return filterFile; 667 } 668 669 /** Returns true iff one or more test filters in excludes apply to the entire module. */ containsModuleExclude(Collection<SuiteTestFilter> excludes)670 private boolean containsModuleExclude(Collection<SuiteTestFilter> excludes) { 671 for (SuiteTestFilter exclude : excludes) { 672 if (exclude.getTest() == null) { 673 return true; 674 } 675 } 676 return false; 677 } 678 679 /** A {@link FilenameFilter} to find all the config files in a directory. */ 680 public static class ConfigFilter implements FilenameFilter { 681 682 /** {@inheritDoc} */ 683 @Override accept(File dir, String name)684 public boolean accept(File dir, String name) { 685 return name.endsWith(CONFIG_EXT); 686 } 687 } 688 689 /** 690 * Parse a list of args formatted as expected into {@link OptionDef} to be injected to module 691 * configurations. 692 * 693 * <p>Format: <module name / module id / class runner>:<option name>:[<arg-key>:=]<arg-value> 694 */ parseArgs(List<String> args, Map<String, List<OptionDef>> moduleOptions)695 private void parseArgs(List<String> args, Map<String, List<OptionDef>> moduleOptions) { 696 for (String arg : args) { 697 int moduleSep = arg.indexOf(":"); 698 if (moduleSep == -1) { 699 throw new RuntimeException("Expected delimiter ':' for module or class."); 700 } 701 String moduleName = arg.substring(0, moduleSep); 702 String remainder = arg.substring(moduleSep + 1); 703 List<OptionDef> listOption = moduleOptions.get(moduleName); 704 if (listOption == null) { 705 listOption = new ArrayList<>(); 706 moduleOptions.put(moduleName, listOption); 707 } 708 int optionNameSep = remainder.indexOf(":"); 709 if (optionNameSep == -1) { 710 throw new RuntimeException( 711 "Expected delimiter ':' between option name and values."); 712 } 713 String optionName = remainder.substring(0, optionNameSep); 714 Pattern pattern = Pattern.compile("\\{(.*)\\}(.*)"); 715 Matcher match = pattern.matcher(optionName); 716 if (match.find()) { 717 String alias = match.group(1); 718 String name = match.group(2); 719 optionName = alias + ":" + name; 720 } 721 String optionValueString = remainder.substring(optionNameSep + 1); 722 // TODO: See if QuotationTokenizer can be improved for multi-character delimiter. 723 // or change the delimiter to a single char. 724 String[] tokens = optionValueString.split(":=", 2); 725 OptionDef option = null; 726 if (tokens.length == 1) { 727 option = new OptionDef(optionName, tokens[0], moduleName); 728 } else if (tokens.length == 2) { 729 option = new OptionDef(optionName, tokens[0], tokens[1], moduleName); 730 } 731 listOption.add(option); 732 } 733 } 734 735 /** Gets the list of {@link IModuleParameterHandler}s associated with a module. */ getModuleParameters( String moduleName, IConfiguration config)736 private List<IModuleParameterHandler> getModuleParameters( 737 String moduleName, IConfiguration config) throws ConfigurationException { 738 List<IModuleParameterHandler> params = new ArrayList<>(); 739 Set<String> processedParameterArgs = new HashSet<>(); 740 // Track family of the parameters to make sure we have no duplicate. 741 Map<String, ModuleParameters> duplicateModule = new LinkedHashMap<>(); 742 743 List<String> parameters = 744 config.getConfigurationDescription().getMetaData(ITestSuite.PARAMETER_KEY); 745 if (parameters == null || parameters.isEmpty()) { 746 return params; 747 } 748 749 Set<ModuleParameters> expandedExcludedModuleParameters = new HashSet<>(); 750 for (ModuleParameters moduleParameters : mExcludedModuleParameters) { 751 expandedExcludedModuleParameters.addAll( 752 ModuleParametersHelper.resolveParam( 753 moduleParameters, mAllowOptionalParameterizedModules) 754 .keySet()); 755 } 756 757 for (String p : parameters) { 758 if (!processedParameterArgs.add(p)) { 759 // Avoid processing the same parameter twice 760 continue; 761 } 762 Map<ModuleParameters, IModuleParameterHandler> suiteParams = 763 ModuleParametersHelper.resolveParam( 764 ModuleParameters.valueOf(p.toUpperCase()), 765 mAllowOptionalParameterizedModules); 766 for (Entry<ModuleParameters, IModuleParameterHandler> suiteParamEntry : 767 suiteParams.entrySet()) { 768 ModuleParameters suiteParam = suiteParamEntry.getKey(); 769 String family = suiteParam.getFamily(); 770 if (duplicateModule.containsKey(family)) { 771 // Duplicate family members are not accepted. 772 throw new ConfigurationException( 773 String.format( 774 "Module %s is declaring parameter: " 775 + "%s and %s when only one expected.", 776 moduleName, suiteParam, duplicateModule.get(family))); 777 } else { 778 duplicateModule.put(suiteParam.getFamily(), suiteParam); 779 } 780 // Do not consider the excluded parameterization dimension 781 782 if (expandedExcludedModuleParameters.contains(suiteParam)) { 783 continue; 784 } 785 786 if (suiteParamEntry.getValue() instanceof FoldableExpandingHandler) { 787 List<IModuleParameterHandler> foldableHandlers = 788 ((FoldableExpandingHandler) suiteParamEntry.getValue()) 789 .expandHandler(mFoldableStates); 790 params.addAll(foldableHandlers); 791 } else { 792 params.add(suiteParamEntry.getValue()); 793 } 794 } 795 } 796 return params; 797 } 798 799 /** Gets the list of parameterized mainline modules associated with a module. */ 800 @VisibleForTesting getMainlineModuleParameters(IConfiguration config)801 List<String> getMainlineModuleParameters(IConfiguration config) throws ConfigurationException { 802 List<String> params = new ArrayList<>(); 803 804 List<String> parameters = 805 config.getConfigurationDescription().getMetaData(ITestSuite.MAINLINE_PARAMETER_KEY); 806 if (parameters == null || parameters.isEmpty()) { 807 return params; 808 } 809 810 return new ArrayList<>(dedupMainlineParameters(parameters, config.getName())); 811 } 812 813 /** 814 * De-duplicate the given mainline parameters. 815 * 816 * @param parameters The list of given mainline parameters. 817 * @param configName The test configuration name. 818 * @return The de-duplicated mainline modules list. 819 */ 820 @VisibleForTesting dedupMainlineParameters(List<String> parameters, String configName)821 Set<String> dedupMainlineParameters(List<String> parameters, String configName) 822 throws ConfigurationException { 823 Set<String> results = new HashSet<>(); 824 for (String param : parameters) { 825 if (!isValidMainlineParam(param)) { 826 throw new ConfigurationException( 827 String.format( 828 "Illegal mainline module parameter: \"%s\" configured in the test" 829 + " config: %s. Parameter must end with .apk/.apex/.apks and" 830 + " have no any spaces configured.", 831 param, configName)); 832 } 833 if (!isInAlphabeticalOrder(param)) { 834 throw new ConfigurationException( 835 String.format( 836 "Illegal mainline module parameter: \"%s\" configured in the test" 837 + " config: %s. Parameter must be configured in alphabetical" 838 + " order or with no duplicated modules.", 839 param, configName)); 840 } 841 results.add(param); 842 } 843 return results; 844 } 845 846 /** Whether a mainline parameter configured in a test config is in alphabetical order or not. */ 847 @VisibleForTesting isInAlphabeticalOrder(String param)848 boolean isInAlphabeticalOrder(String param) { 849 String previousString = ""; 850 for (String currentString : param.split(String.format("\\+"))) { 851 // This is to check if the parameter is in alphabetical order or duplicated. 852 if (currentString.compareTo(previousString) <= 0) { 853 return false; 854 } 855 previousString = currentString; 856 } 857 return true; 858 } 859 860 /** Whether the mainline parameter configured in the test config is valid or not. */ 861 @VisibleForTesting isValidMainlineParam(String param)862 boolean isValidMainlineParam(String param) { 863 if (param.contains(" ")) { 864 return false; 865 } 866 for (String m : param.split(String.format("\\+"))) { 867 if (!MAINLINE_PARAMETERS_TO_VALIDATE.stream().anyMatch(entry -> m.endsWith(entry))) { 868 return false; 869 } 870 } 871 return true; 872 } 873 874 /** 875 * Setup the options for the module configuration. 876 * 877 * @param name The base name of the module 878 * @param nameWithParam The id of the parameterized mainline module (module name + parameters) 879 * @param id The base id name of the module. 880 * @param fullId The full id of the module (usually abi + module name + parameters) 881 * @param config The module configuration. 882 * @param abi The abi of the module. 883 * @throws ConfigurationException 884 */ setUpConfig( String name, String nameWithParam, String id, String fullId, IConfiguration config, IAbi abi)885 private void setUpConfig( 886 String name, 887 String nameWithParam, 888 String id, 889 String fullId, 890 IConfiguration config, 891 IAbi abi) 892 throws ConfigurationException { 893 List<OptionDef> optionsToInject = new ArrayList<>(); 894 if (mModuleOptions.containsKey(name)) { 895 optionsToInject.addAll(mModuleOptions.get(name)); 896 } 897 if (nameWithParam != null && mModuleOptions.containsKey(nameWithParam)) { 898 optionsToInject.addAll(mModuleOptions.get(nameWithParam)); 899 } 900 if (mModuleOptions.containsKey(id)) { 901 optionsToInject.addAll(mModuleOptions.get(id)); 902 } 903 if (mModuleOptions.containsKey(fullId)) { 904 optionsToInject.addAll(mModuleOptions.get(fullId)); 905 } 906 config.injectOptionValues(optionsToInject); 907 908 // Set target preparers 909 for (IDeviceConfiguration holder : config.getDeviceConfig()) { 910 for (ITargetPreparer preparer : holder.getTargetPreparers()) { 911 String className = preparer.getClass().getName(); 912 if (mTestOrPreparerOptions.containsKey(className)) { 913 OptionSetter preparerSetter = new OptionSetter(preparer); 914 for (OptionDef def : mTestOrPreparerOptions.get(className)) { 915 preparerSetter.setOptionValue(def.name, def.key, def.value); 916 } 917 } 918 if (preparer instanceof IAbiReceiver) { 919 ((IAbiReceiver) preparer).setAbi(abi); 920 } 921 } 922 } 923 924 // Set IRemoteTests 925 List<IRemoteTest> tests = config.getTests(); 926 for (IRemoteTest test : tests) { 927 String className = test.getClass().getName(); 928 if (mTestOrPreparerOptions.containsKey(className)) { 929 OptionSetter preparerSetter = new OptionSetter(test); 930 for (OptionDef def : mTestOrPreparerOptions.get(className)) { 931 preparerSetter.setOptionValue(def.name, def.key, def.value); 932 } 933 } 934 addFiltersToTest(test, abi, fullId, mIncludeFilters, mExcludeFilters); 935 if (test instanceof IAbiReceiver) { 936 ((IAbiReceiver) test).setAbi(abi); 937 } 938 } 939 940 // add the abi and module name to the description 941 config.getConfigurationDescription().setAbi(abi); 942 config.getConfigurationDescription().setModuleName(name); 943 944 config.validateOptions(); 945 } 946 947 /** Whether or not the base configuration should be created for all abis or not. */ shouldCreateMultiAbiForBase(List<IModuleParameterHandler> params)948 private boolean shouldCreateMultiAbiForBase(List<IModuleParameterHandler> params) { 949 for (IModuleParameterHandler param : params) { 950 if (param instanceof NotMultiAbiHandler) { 951 return false; 952 } 953 } 954 return true; 955 } 956 } 957