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 if (shouldRunParameterized(baseId, fullId, mForcedParameter)) { 345 IConfiguration paramConfig = 346 mConfigFactory.createConfigurationFromArgs(pathArg); 347 // Mark the parameter in the metadata 348 paramConfig 349 .getConfigurationDescription() 350 .addMetadata( 351 ConfigurationDescriptor.ACTIVE_PARAMETER_KEY, 352 param.getParameterIdentifier()); 353 param.addParameterSpecificConfig(paramConfig); 354 setUpConfig(name, baseId, fullId, paramConfig, abi); 355 param.applySetup(paramConfig); 356 toRun.put(fullId, paramConfig); 357 } 358 } 359 } 360 361 if (mAllowMainlineParameterizedModules) { 362 // If no options defined in a test config, skip generating. 363 // TODO(easoncylee) This is still under discussion. 364 if (mainlineParams.isEmpty()) { 365 continue; 366 } 367 // If we find any parameterized combination for mainline modules. 368 for (String param : mainlineParams) { 369 String fullId = String.format("%s[%s]", baseId, param); 370 if (!shouldRunParameterized(baseId, fullId, null)) { 371 continue; 372 } 373 // Create mainline handler for each defined mainline parameter. 374 MainlineModuleHandler handler = 375 new MainlineModuleHandler( 376 param, 377 abi, 378 mContext 379 ); 380 skipCreatingBaseConfig = true; 381 IConfiguration paramConfig = 382 mConfigFactory.createConfigurationFromArgs(pathArg); 383 paramConfig 384 .getConfigurationDescription() 385 .addMetadata( 386 ITestSuite.ACTIVE_MAINLINE_PARAMETER_KEY, 387 param); 388 setUpConfig(name, baseId, fullId, paramConfig, abi); 389 handler.applySetup(paramConfig); 390 toRun.put(fullId, paramConfig); 391 } 392 } 393 394 primaryAbi = false; 395 // If a parameterized form of the module was forced, we don't create the standard 396 // version of it. 397 if (skipCreatingBaseConfig) { 398 continue; 399 } 400 if (shouldRunModule(baseId)) { 401 // Always add the base regular configuration to the execution. 402 setUpConfig(name, baseId, baseId, config, abi); 403 toRun.put(baseId, config); 404 } 405 } 406 } catch (ConfigurationException e) { 407 throw new RuntimeException( 408 String.format( 409 "Error parsing configuration: %s: '%s'", 410 configFullName, e.getMessage()), 411 e); 412 } 413 414 return toRun; 415 } 416 417 /** @return the {@link Set} of modules whose name contains the given pattern. */ getModuleNamesMatching( File directory, String suitePrefix, String pattern)418 public static Set<File> getModuleNamesMatching( 419 File directory, String suitePrefix, String pattern) { 420 List<File> extraTestCasesDirs = Arrays.asList(directory); 421 List<String> patterns = new ArrayList<>(); 422 patterns.add(pattern); 423 Set<File> modules = 424 ConfigurationUtil.getConfigNamesFileFromDirs( 425 suitePrefix, extraTestCasesDirs, patterns); 426 return modules; 427 } 428 429 /** 430 * Utility method that allows to parse and create a structure with the option filters. 431 * 432 * @param stringFilters The original option filters format. 433 * @param filters The filters parsed from the string format. 434 * @param abis The Abis to consider in the filtering. 435 */ addFilters( Set<String> stringFilters, Map<String, List<SuiteTestFilter>> filters, Set<IAbi> abis)436 public static void addFilters( 437 Set<String> stringFilters, Map<String, List<SuiteTestFilter>> filters, Set<IAbi> abis) { 438 for (String filterString : stringFilters) { 439 SuiteTestFilter filter = SuiteTestFilter.createFrom(filterString); 440 String abi = filter.getAbi(); 441 if (abi == null) { 442 for (IAbi a : abis) { 443 addFilter(a.getName(), filter, filters); 444 } 445 } else { 446 addFilter(abi, filter, filters); 447 } 448 } 449 } 450 addFilter( String abi, SuiteTestFilter filter, Map<String, List<SuiteTestFilter>> filters)451 private static void addFilter( 452 String abi, SuiteTestFilter filter, Map<String, List<SuiteTestFilter>> filters) { 453 getFilterList(filters, AbiUtils.createId(abi, filter.getName())).add(filter); 454 } 455 getFilterList( Map<String, List<SuiteTestFilter>> filters, String id)456 private static List<SuiteTestFilter> getFilterList( 457 Map<String, List<SuiteTestFilter>> filters, String id) { 458 List<SuiteTestFilter> fs = filters.get(id); 459 if (fs == null) { 460 fs = new ArrayList<>(); 461 filters.put(id, fs); 462 } 463 return fs; 464 } 465 shouldRunModule(String moduleId)466 private boolean shouldRunModule(String moduleId) { 467 List<SuiteTestFilter> mdIncludes = getFilterList(mIncludeFilters, moduleId); 468 List<SuiteTestFilter> mdExcludes = getFilterList(mExcludeFilters, moduleId); 469 // if including all modules or includes exist for this module, and there are not excludes 470 // for the entire module, this module should be run. 471 return (mIncludeAll || !mdIncludes.isEmpty()) && !containsModuleExclude(mdExcludes); 472 } 473 474 /** 475 * Except if the parameterized module is explicitly excluded, including the base module result 476 * in including its parameterization variant. 477 */ shouldRunParameterized( String baseModuleId, String parameterModuleId, IModuleParameter forcedModuleParameter)478 private boolean shouldRunParameterized( 479 String baseModuleId, String parameterModuleId, IModuleParameter forcedModuleParameter) { 480 // Explicitly excluded 481 List<SuiteTestFilter> excluded = getFilterList(mExcludeFilters, parameterModuleId); 482 if (containsModuleExclude(excluded)) { 483 return false; 484 } 485 486 // Implicitly included due to forced parameter 487 if (forcedModuleParameter != null) { 488 List<SuiteTestFilter> baseInclude = getFilterList(mIncludeFilters, baseModuleId); 489 if (!baseInclude.isEmpty()) { 490 return true; 491 } 492 } 493 // Explicitly included 494 List<SuiteTestFilter> included = getFilterList(mIncludeFilters, parameterModuleId); 495 if (mIncludeAll || !included.isEmpty()) { 496 return true; 497 } 498 return false; 499 } 500 addTestIncludes( ITestFilterReceiver test, List<SuiteTestFilter> includes, String moduleId)501 private void addTestIncludes( 502 ITestFilterReceiver test, List<SuiteTestFilter> includes, String moduleId) { 503 if (test instanceof ITestFileFilterReceiver) { 504 String escapedFileName = escapeFilterFileName(moduleId); 505 File includeFile = createFilterFile(escapedFileName, ".include", includes); 506 ((ITestFileFilterReceiver) test).setIncludeTestFile(includeFile); 507 } else { 508 // add test includes one at a time 509 for (SuiteTestFilter include : includes) { 510 String filterTestName = include.getTest(); 511 if (filterTestName != null) { 512 test.addIncludeFilter(filterTestName); 513 } 514 } 515 } 516 } 517 addTestExcludes( ITestFilterReceiver test, List<SuiteTestFilter> excludes, String moduleId)518 private void addTestExcludes( 519 ITestFilterReceiver test, List<SuiteTestFilter> excludes, String moduleId) { 520 if (test instanceof ITestFileFilterReceiver) { 521 String escapedFileName = escapeFilterFileName(moduleId); 522 File excludeFile = createFilterFile(escapedFileName, ".exclude", excludes); 523 ((ITestFileFilterReceiver) test).setExcludeTestFile(excludeFile); 524 } else { 525 // add test excludes one at a time 526 for (SuiteTestFilter exclude : excludes) { 527 test.addExcludeFilter(exclude.getTest()); 528 } 529 } 530 } 531 532 /** module id can contain special characters, avoid them for file names. */ escapeFilterFileName(String moduleId)533 private String escapeFilterFileName(String moduleId) { 534 String escaped = UrlEscapers.urlPathSegmentEscaper().escape(moduleId); 535 return escaped; 536 } 537 createFilterFile(String prefix, String suffix, List<SuiteTestFilter> filters)538 private File createFilterFile(String prefix, String suffix, List<SuiteTestFilter> filters) { 539 File filterFile = null; 540 PrintWriter out = null; 541 try { 542 filterFile = FileUtil.createTempFile(prefix, suffix); 543 out = new PrintWriter(filterFile); 544 for (SuiteTestFilter filter : filters) { 545 String filterTest = filter.getTest(); 546 if (filterTest != null) { 547 out.println(filterTest); 548 } 549 } 550 out.flush(); 551 } catch (IOException e) { 552 throw new RuntimeException("Failed to create filter file"); 553 } finally { 554 StreamUtil.close(out); 555 } 556 filterFile.deleteOnExit(); 557 return filterFile; 558 } 559 560 /** Returns true iff one or more test filters in excludes apply to the entire module. */ containsModuleExclude(Collection<SuiteTestFilter> excludes)561 private boolean containsModuleExclude(Collection<SuiteTestFilter> excludes) { 562 for (SuiteTestFilter exclude : excludes) { 563 if (exclude.getTest() == null) { 564 return true; 565 } 566 } 567 return false; 568 } 569 570 /** A {@link FilenameFilter} to find all the config files in a directory. */ 571 public static class ConfigFilter implements FilenameFilter { 572 573 /** {@inheritDoc} */ 574 @Override accept(File dir, String name)575 public boolean accept(File dir, String name) { 576 return name.endsWith(CONFIG_EXT); 577 } 578 } 579 580 /** 581 * Parse a list of args formatted as expected into {@link OptionDef} to be injected to module 582 * configurations. 583 * 584 * <p>Format: <module name / module id / class runner>:<option name>:[<arg-key>:=]<arg-value> 585 */ parseArgs(List<String> args, Map<String, List<OptionDef>> moduleOptions)586 private void parseArgs(List<String> args, Map<String, List<OptionDef>> moduleOptions) { 587 for (String arg : args) { 588 int moduleSep = arg.indexOf(":"); 589 if (moduleSep == -1) { 590 throw new RuntimeException("Expected delimiter ':' for module or class."); 591 } 592 String moduleName = arg.substring(0, moduleSep); 593 String remainder = arg.substring(moduleSep + 1); 594 List<OptionDef> listOption = moduleOptions.get(moduleName); 595 if (listOption == null) { 596 listOption = new ArrayList<>(); 597 moduleOptions.put(moduleName, listOption); 598 } 599 int optionNameSep = remainder.indexOf(":"); 600 if (optionNameSep == -1) { 601 throw new RuntimeException( 602 "Expected delimiter ':' between option name and values."); 603 } 604 String optionName = remainder.substring(0, optionNameSep); 605 Pattern pattern = Pattern.compile("\\{(.*)\\}(.*)"); 606 Matcher match = pattern.matcher(optionName); 607 if (match.find()) { 608 String alias = match.group(1); 609 String name = match.group(2); 610 optionName = alias + ":" + name; 611 } 612 String optionValueString = remainder.substring(optionNameSep + 1); 613 // TODO: See if QuotationTokenizer can be improved for multi-character delimiter. 614 // or change the delimiter to a single char. 615 String[] tokens = optionValueString.split(":=", 2); 616 OptionDef option = null; 617 if (tokens.length == 1) { 618 option = new OptionDef(optionName, tokens[0], moduleName); 619 } else if (tokens.length == 2) { 620 option = new OptionDef(optionName, tokens[0], tokens[1], moduleName); 621 } 622 listOption.add(option); 623 } 624 } 625 626 /** Gets the list of {@link IModuleParameter}s associated with a module. */ getModuleParameters(String moduleName, IConfiguration config)627 private List<IModuleParameter> getModuleParameters(String moduleName, IConfiguration config) 628 throws ConfigurationException { 629 List<IModuleParameter> params = new ArrayList<>(); 630 Set<String> processedParameterArgs = new HashSet<>(); 631 // Track family of the parameters to make sure we have no duplicate. 632 Map<String, ModuleParameters> duplicateModule = new LinkedHashMap<>(); 633 634 List<String> parameters = 635 config.getConfigurationDescription().getMetaData(ITestSuite.PARAMETER_KEY); 636 if (parameters == null || parameters.isEmpty()) { 637 return params; 638 } 639 for (String p : parameters) { 640 if (!processedParameterArgs.add(p)) { 641 // Avoid processing the same parameter twice 642 continue; 643 } 644 ModuleParameters suiteParam = ModuleParameters.valueOf(p.toUpperCase()); 645 String family = suiteParam.getFamily(); 646 if (duplicateModule.containsKey(family)) { 647 // Duplicate family members are not accepted. 648 throw new ConfigurationException( 649 String.format( 650 "Module %s is declaring parameter: " 651 + "%s and %s when only one expected.", 652 moduleName, suiteParam, duplicateModule.get(family))); 653 } else { 654 duplicateModule.put(suiteParam.getFamily(), suiteParam); 655 } 656 // Do not consider the excluded parameterization dimension 657 if (mExcludedModuleParameters.contains(suiteParam)) { 658 CLog.d("'%s' was excluded via exclude-module-parameters.", moduleName); 659 continue; 660 } 661 IModuleParameter handler = 662 ModuleParametersHelper.getParameterHandler( 663 suiteParam, /* optionalParams */ mAllowOptionalParameterizedModules); 664 if (handler != null) { 665 params.add(handler); 666 } 667 } 668 return params; 669 } 670 671 /** Gets the list of parameterized mainline modules associated with a module. */ 672 @VisibleForTesting getMainlineModuleParameters(IConfiguration config)673 List<String> getMainlineModuleParameters(IConfiguration config) 674 throws ConfigurationException { 675 List<String> params = new ArrayList<>(); 676 677 List<String> parameters = 678 config.getConfigurationDescription().getMetaData(ITestSuite.MAINLINE_PARAMETER_KEY); 679 if (parameters == null || parameters.isEmpty()) { 680 return params; 681 } 682 683 return new ArrayList<>(dedupMainlineParameters(parameters, config.getName())); 684 } 685 686 /** 687 * De-duplicate the given mainline parameters. 688 * 689 * @param parameters The list of given mainline parameters. 690 * @param configName The test configuration name. 691 * @return The de-duplicated mainline modules list. 692 */ 693 @VisibleForTesting dedupMainlineParameters(List<String> parameters, String configName)694 Set<String> dedupMainlineParameters(List<String> parameters, String configName) 695 throws ConfigurationException { 696 Set<String> results = new HashSet<>(); 697 for (String param : parameters) { 698 if (!isValidMainlineParam(param)) { 699 throw new ConfigurationException( 700 String.format( 701 "Illegal mainline module parameter: \"%s\" configured in the " + 702 "test config: %s. Parameter must end with .apk/.apex/.apks and " + 703 "have no any spaces configured.", param, configName) 704 ); 705 } 706 if (!isInAlphabeticalOrder(param)) { 707 throw new ConfigurationException( 708 String.format( 709 "Illegal mainline module parameter: \"%s\" configured in the " + 710 "test config: %s. Parameter must be configured in alphabetical " + 711 "order or with no duplicated modules.", param, configName) 712 ); 713 } 714 results.add(param); 715 } 716 return results; 717 } 718 719 /** Whether a mainline parameter configured in a test config is in alphabetical order or not. */ 720 @VisibleForTesting isInAlphabeticalOrder(String param)721 boolean isInAlphabeticalOrder(String param) { 722 String previousString = ""; 723 for (String currentString : param.split(String.format("\\+"))) { 724 // This is to check if the parameter is in alphabetical order or duplicated. 725 if (currentString.compareTo(previousString) <= 0) { 726 return false; 727 } 728 previousString = currentString; 729 } 730 return true; 731 } 732 733 /** Whether the mainline parameter configured in the test config is valid or not. */ 734 @VisibleForTesting isValidMainlineParam(String param)735 boolean isValidMainlineParam(String param) { 736 if (param.contains(" ")) { 737 return false; 738 } 739 for (String m : param.split(String.format("\\+"))) { 740 if (!MAINLINE_PARAMETERS_TO_VALIDATE.stream().anyMatch(entry -> m.endsWith(entry))) { 741 return false; 742 } 743 } 744 return true; 745 } 746 747 /** 748 * Setup the options for the module configuration. 749 * 750 * @param name The base name of the module 751 * @param id The base id name of the module. 752 * @param fullId The full id of the module (usually abi + module name + parameters) 753 * @param config The module configuration. 754 * @param abi The abi of the module. 755 * @throws ConfigurationException 756 */ setUpConfig(String name, String id, String fullId, IConfiguration config, IAbi abi)757 private void setUpConfig(String name, String id, String fullId, IConfiguration config, IAbi abi) 758 throws ConfigurationException { 759 List<OptionDef> optionsToInject = new ArrayList<>(); 760 if (mModuleOptions.containsKey(name)) { 761 optionsToInject.addAll(mModuleOptions.get(name)); 762 } 763 if (mModuleOptions.containsKey(id)) { 764 optionsToInject.addAll(mModuleOptions.get(id)); 765 } 766 if (mModuleOptions.containsKey(fullId)) { 767 optionsToInject.addAll(mModuleOptions.get(fullId)); 768 } 769 config.injectOptionValues(optionsToInject); 770 771 // Set target preparers 772 for (IDeviceConfiguration holder : config.getDeviceConfig()) { 773 for (ITargetPreparer preparer : holder.getTargetPreparers()) { 774 String className = preparer.getClass().getName(); 775 if (mTestOrPreparerOptions.containsKey(className)) { 776 config.injectOptionValues(mTestOrPreparerOptions.get(className)); 777 } 778 if (preparer instanceof IAbiReceiver) { 779 ((IAbiReceiver) preparer).setAbi(abi); 780 } 781 } 782 } 783 784 // Set IRemoteTests 785 List<IRemoteTest> tests = config.getTests(); 786 for (IRemoteTest test : tests) { 787 String className = test.getClass().getName(); 788 if (mTestOrPreparerOptions.containsKey(className)) { 789 config.injectOptionValues(mTestOrPreparerOptions.get(className)); 790 } 791 addFiltersToTest(test, abi, fullId, mIncludeFilters, mExcludeFilters); 792 if (test instanceof IAbiReceiver) { 793 ((IAbiReceiver) test).setAbi(abi); 794 } 795 } 796 797 // add the abi and module name to the description 798 config.getConfigurationDescription().setAbi(abi); 799 config.getConfigurationDescription().setModuleName(name); 800 801 config.validateOptions(); 802 } 803 804 /** Whether or not the base configuration should be created for all abis or not. */ shouldCreateMultiAbiForBase(List<IModuleParameter> params)805 private boolean shouldCreateMultiAbiForBase(List<IModuleParameter> params) { 806 for (IModuleParameter param : params) { 807 if (param instanceof NotMultiAbiHandler) { 808 return false; 809 } 810 } 811 return true; 812 } 813 } 814