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