1 /* 2 * Copyright (C) 2015 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.compatibility.common.tradefed.testtype; 17 18 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper; 19 import com.android.compatibility.common.tradefed.result.TestRunHandler; 20 import com.android.compatibility.common.tradefed.util.LinearPartition; 21 import com.android.compatibility.common.tradefed.util.UniqueModuleCountUtil; 22 import com.android.compatibility.common.util.TestFilter; 23 import com.android.ddmlib.Log.LogLevel; 24 import com.android.tradefed.build.IBuildInfo; 25 import com.android.tradefed.config.ConfigurationException; 26 import com.android.tradefed.config.ConfigurationFactory; 27 import com.android.tradefed.config.IConfiguration; 28 import com.android.tradefed.config.IConfigurationFactory; 29 import com.android.tradefed.log.LogUtil.CLog; 30 import com.android.tradefed.testtype.IAbi; 31 import com.android.tradefed.testtype.IRemoteTest; 32 import com.android.tradefed.testtype.ITestFileFilterReceiver; 33 import com.android.tradefed.testtype.ITestFilterReceiver; 34 import com.android.tradefed.util.AbiUtils; 35 import com.android.tradefed.util.FileUtil; 36 import com.android.tradefed.util.MultiMap; 37 import com.android.tradefed.util.TimeUtil; 38 39 import com.google.common.annotations.VisibleForTesting; 40 41 import java.io.File; 42 import java.io.FilenameFilter; 43 import java.io.IOException; 44 import java.io.PrintWriter; 45 import java.util.ArrayList; 46 import java.util.Collection; 47 import java.util.Collections; 48 import java.util.Comparator; 49 import java.util.HashMap; 50 import java.util.HashSet; 51 import java.util.LinkedList; 52 import java.util.List; 53 import java.util.Map; 54 import java.util.Map.Entry; 55 import java.util.Set; 56 57 /** 58 * Retrieves Compatibility test module definitions from the repository. 59 */ 60 public class ModuleRepo implements IModuleRepo { 61 62 private static final String CONFIG_EXT = ".config"; 63 private static final Map<String, Integer> ENDING_MODULES = new HashMap<>(); 64 static { 65 // b/62732298 put testFullDisk in the end to accommodate CTSMediaStressTest temporally 66 ENDING_MODULES.put("CtsAppSecurityHostTestCases", 1); 67 ENDING_MODULES.put("CtsMonkeyTestCases", 2); 68 } 69 // Synchronization objects for Token Modules. 70 private int mInitCount = 0; 71 private Set<IModuleDef> mTokenModuleScheduled; 72 private static Object lock = new Object(); 73 74 private int mTotalShards; 75 private Integer mShardIndex; 76 77 private Map<String, Set<String>> mDeviceTokens = new HashMap<>(); 78 private Map<String, Map<String, List<String>>> mTestArgs = new HashMap<>(); 79 private Map<String, Map<String, List<String>>> mModuleArgs = new HashMap<>(); 80 private boolean mIncludeAll; 81 private Map<String, List<TestFilter>> mIncludeFilters = new HashMap<>(); 82 private Map<String, List<TestFilter>> mExcludeFilters = new HashMap<>(); 83 private IConfigurationFactory mConfigFactory = ConfigurationFactory.getInstance(); 84 85 private volatile boolean mInitialized = false; 86 87 // Holds all the tests with tokens waiting to be run. Meaning the DUT must have a specific token. 88 private List<IModuleDef> mTokenModules = new ArrayList<>(); 89 private List<IModuleDef> mNonTokenModules = new ArrayList<>(); 90 91 /** 92 * {@inheritDoc} 93 */ 94 @Override getNumberOfShards()95 public int getNumberOfShards() { 96 return mTotalShards; 97 } 98 99 /** 100 * Returns the device tokens of this module repo. Exposed for testing. 101 */ getDeviceTokens()102 protected Map<String, Set<String>> getDeviceTokens() { 103 return mDeviceTokens; 104 } 105 106 /** 107 * A {@link FilenameFilter} to find all modules in a directory who match the given pattern. 108 */ 109 public static class NameFilter implements FilenameFilter { 110 111 private String mPattern; 112 NameFilter(String pattern)113 public NameFilter(String pattern) { 114 mPattern = pattern; 115 } 116 117 /** 118 * {@inheritDoc} 119 */ 120 @Override accept(File dir, String name)121 public boolean accept(File dir, String name) { 122 return name.contains(mPattern) && name.endsWith(CONFIG_EXT); 123 } 124 } 125 126 /** 127 * {@inheritDoc} 128 */ 129 @Override getNonTokenModules()130 public List<IModuleDef> getNonTokenModules() { 131 return mNonTokenModules; 132 } 133 134 /** 135 * {@inheritDoc} 136 */ 137 @Override getTokenModules()138 public List<IModuleDef> getTokenModules() { 139 return mTokenModules; 140 } 141 142 /** 143 * {@inheritDoc} 144 */ 145 @Override getModuleIds()146 public String[] getModuleIds() { 147 Set<String> moduleIdSet = new HashSet<>(); 148 for (IModuleDef moduleDef : mNonTokenModules) { 149 moduleIdSet.add(moduleDef.getId()); 150 } 151 for (IModuleDef moduleDef : mTokenModules) { 152 moduleIdSet.add(moduleDef.getId()); 153 } 154 return moduleIdSet.toArray(new String[moduleIdSet.size()]); 155 } 156 157 /** 158 * {@inheritDoc} 159 */ 160 @Override isInitialized()161 public boolean isInitialized() { 162 return mInitialized; 163 } 164 165 /** 166 * {@inheritDoc} 167 */ 168 @Override initialize(int totalShards, Integer shardIndex, File testsDir, Set<IAbi> abis, List<String> deviceTokens, List<String> testArgs, List<String> moduleArgs, Set<String> includeFilters, Set<String> excludeFilters, MultiMap<String, String> metadataIncludeFilters, MultiMap<String, String> metadataExcludeFilters, IBuildInfo buildInfo)169 public void initialize(int totalShards, Integer shardIndex, File testsDir, Set<IAbi> abis, 170 List<String> deviceTokens, List<String> testArgs, List<String> moduleArgs, 171 Set<String> includeFilters, Set<String> excludeFilters, 172 MultiMap<String, String> metadataIncludeFilters, 173 MultiMap<String, String> metadataExcludeFilters, 174 IBuildInfo buildInfo) { 175 CLog.d("Initializing ModuleRepo\nShards:%d\nTests Dir:%s\nABIs:%s\nDevice Tokens:%s\n" + 176 "Test Args:%s\nModule Args:%s\nIncludes:%s\nExcludes:%s", 177 totalShards, testsDir.getAbsolutePath(), abis, deviceTokens, testArgs, moduleArgs, 178 includeFilters, excludeFilters); 179 mInitialized = true; 180 mTotalShards = totalShards; 181 mShardIndex = shardIndex; 182 synchronized (lock) { 183 if (mTokenModuleScheduled == null) { 184 mTokenModuleScheduled = new HashSet<>(); 185 } 186 } 187 188 for (String line : deviceTokens) { 189 String[] parts = line.split(":"); 190 if (parts.length == 2) { 191 String key = parts[0]; 192 String value = parts[1]; 193 Set<String> list = mDeviceTokens.get(key); 194 if (list == null) { 195 list = new HashSet<>(); 196 mDeviceTokens.put(key, list); 197 } 198 list.add(value); 199 } else { 200 throw new IllegalArgumentException( 201 String.format("Could not parse device token: %s", line)); 202 } 203 } 204 putArgs(testArgs, mTestArgs); 205 putArgs(moduleArgs, mModuleArgs); 206 mIncludeAll = includeFilters.isEmpty(); 207 // Include all the inclusions 208 addFilters(includeFilters, mIncludeFilters, abis); 209 // Exclude all the exclusions 210 addFilters(excludeFilters, mExcludeFilters, abis); 211 212 File[] configFiles = testsDir.listFiles(new ConfigFilter()); 213 if (configFiles.length == 0) { 214 throw new IllegalArgumentException( 215 String.format("No config files found in %s", testsDir.getAbsolutePath())); 216 } 217 Map<String, Integer> shardedTestCounts = new HashMap<>(); 218 for (File configFile : configFiles) { 219 final String name = configFile.getName().replace(CONFIG_EXT, ""); 220 final String[] pathArg = new String[] { configFile.getAbsolutePath() }; 221 try { 222 // Invokes parser to process the test module config file 223 // Need to generate a different config for each ABI as we cannot guarantee the 224 // configs are idempotent. This however means we parse the same file multiple times 225 for (IAbi abi : abis) { 226 String id = AbiUtils.createId(abi.getName(), name); 227 if (!shouldRunModule(id)) { 228 // If the module should not run tests based on the state of filters, 229 // skip this name/abi combination. 230 continue; 231 } 232 233 IConfiguration config = mConfigFactory.createConfigurationFromArgs(pathArg); 234 if (!filterByConfigMetadata(config, 235 metadataIncludeFilters, metadataExcludeFilters)) { 236 // if the module config did not pass the metadata filters, it's excluded 237 // from execution 238 continue; 239 } 240 Map<String, List<String>> args = new HashMap<>(); 241 if (mModuleArgs.containsKey(name)) { 242 args.putAll(mModuleArgs.get(name)); 243 } 244 if (mModuleArgs.containsKey(id)) { 245 args.putAll(mModuleArgs.get(id)); 246 } 247 injectOptionsToConfig(args, config); 248 249 List<IRemoteTest> tests = config.getTests(); 250 for (IRemoteTest test : tests) { 251 prepareTestClass(name, abi, config, test); 252 } 253 List<IRemoteTest> shardedTests = tests; 254 if (shardedTests.size() > 1) { 255 shardedTestCounts.put(id, shardedTests.size()); 256 } 257 for (IRemoteTest test : shardedTests) { 258 addModuleDef(name, abi, test, pathArg); 259 } 260 } 261 } catch (ConfigurationException e) { 262 throw new RuntimeException(String.format("error parsing config file: %s", 263 configFile.getName()), e); 264 } 265 } 266 mExcludeFilters.clear(); 267 TestRunHandler.setTestRuns(new CompatibilityBuildHelper(buildInfo), shardedTestCounts); 268 } 269 270 /** 271 * Prepare to run test classes. 272 * 273 * @param name module name 274 * @param abi IAbi object that contains abi information 275 * @param config IConfiguration object created from config file 276 * @param test test class 277 * @throws ConfigurationException 278 */ prepareTestClass(final String name, IAbi abi, IConfiguration config, IRemoteTest test)279 protected void prepareTestClass(final String name, IAbi abi, IConfiguration config, 280 IRemoteTest test) throws ConfigurationException { 281 String className = test.getClass().getName(); 282 Map<String, List<String>> testArgsMap = new HashMap<>(); 283 if (mTestArgs.containsKey(className)) { 284 testArgsMap.putAll(mTestArgs.get(className)); 285 } 286 injectOptionsToConfig(testArgsMap, config); 287 addFiltersToTest(test, abi, name); 288 } 289 290 /** 291 * Helper to inject options to a config. 292 */ 293 @VisibleForTesting injectOptionsToConfig(Map<String, List<String>> optionMap, IConfiguration config)294 void injectOptionsToConfig(Map<String, List<String>> optionMap, IConfiguration config) 295 throws ConfigurationException{ 296 for (Entry<String, List<String>> entry : optionMap.entrySet()) { 297 for (String entryValue : entry.getValue()) { 298 String entryName = entry.getKey(); 299 if (entryValue.contains(":=")) { 300 // entryValue is key-value pair 301 String key = entryValue.substring(0, entryValue.indexOf(":=")); 302 String value = entryValue.substring(entryValue.indexOf(":=") + 2); 303 config.injectOptionValue(entryName, key, value); 304 } else { 305 // entryValue is just the argument value 306 config.injectOptionValue(entryName, entryValue); 307 } 308 } 309 } 310 } 311 addFilters(Set<String> stringFilters, Map<String, List<TestFilter>> filters, Set<IAbi> abis)312 private void addFilters(Set<String> stringFilters, 313 Map<String, List<TestFilter>> filters, Set<IAbi> abis) { 314 for (String filterString : stringFilters) { 315 TestFilter filter = TestFilter.createFrom(filterString); 316 String abi = filter.getAbi(); 317 if (abi == null) { 318 for (IAbi a : abis) { 319 addFilter(a.getName(), filter, filters); 320 } 321 } else { 322 addFilter(abi, filter, filters); 323 } 324 } 325 } 326 addFilter(String abi, TestFilter filter, Map<String, List<TestFilter>> filters)327 private void addFilter(String abi, TestFilter filter, 328 Map<String, List<TestFilter>> filters) { 329 getFilter(filters, AbiUtils.createId(abi, filter.getName())).add(filter); 330 } 331 getFilter(Map<String, List<TestFilter>> filters, String id)332 private List<TestFilter> getFilter(Map<String, List<TestFilter>> filters, String id) { 333 List<TestFilter> fs = filters.get(id); 334 if (fs == null) { 335 fs = new ArrayList<>(); 336 filters.put(id, fs); 337 } 338 return fs; 339 } 340 addModuleDef(String name, IAbi abi, IRemoteTest test, String[] configPaths)341 protected void addModuleDef(String name, IAbi abi, IRemoteTest test, String[] configPaths) 342 throws ConfigurationException { 343 // Invokes parser to process the test module config file 344 IConfiguration config = mConfigFactory.createConfigurationFromArgs(configPaths); 345 addModuleDef(new ModuleDef(name, abi, test, config.getTargetPreparers(), 346 config.getConfigurationDescription())); 347 } 348 addModuleDef(IModuleDef moduleDef)349 protected void addModuleDef(IModuleDef moduleDef) { 350 Set<String> tokens = moduleDef.getTokens(); 351 if (tokens != null && !tokens.isEmpty()) { 352 mTokenModules.add(moduleDef); 353 } else { 354 mNonTokenModules.add(moduleDef); 355 } 356 } 357 addFiltersToTest(IRemoteTest test, IAbi abi, String name)358 private void addFiltersToTest(IRemoteTest test, IAbi abi, String name) { 359 String moduleId = AbiUtils.createId(abi.getName(), name); 360 if (!(test instanceof ITestFilterReceiver)) { 361 throw new IllegalArgumentException(String.format( 362 "Test in module %s must implement ITestFilterReceiver.", moduleId)); 363 } 364 List<TestFilter> mdIncludes = getFilter(mIncludeFilters, moduleId); 365 List<TestFilter> mdExcludes = getFilter(mExcludeFilters, moduleId); 366 if (!mdIncludes.isEmpty()) { 367 addTestIncludes((ITestFilterReceiver) test, mdIncludes, name); 368 } 369 if (!mdExcludes.isEmpty()) { 370 addTestExcludes((ITestFilterReceiver) test, mdExcludes, name); 371 } 372 } 373 374 @VisibleForTesting filterByConfigMetadata(IConfiguration config, MultiMap<String, String> include, MultiMap<String, String> exclude)375 protected boolean filterByConfigMetadata(IConfiguration config, 376 MultiMap<String, String> include, MultiMap<String, String> exclude) { 377 MultiMap<String, String> metadata = config.getConfigurationDescription().getAllMetaData(); 378 boolean shouldInclude = false; 379 for (String key : include.keySet()) { 380 Set<String> filters = new HashSet<>(include.get(key)); 381 if (metadata.containsKey(key)) { 382 filters.retainAll(metadata.get(key)); 383 if (!filters.isEmpty()) { 384 // inclusion filter is not empty and there's at least one matching inclusion 385 // rule so there's no need to match other inclusion rules 386 shouldInclude = true; 387 break; 388 } 389 } 390 } 391 if (!include.isEmpty() && !shouldInclude) { 392 // if inclusion filter is not empty and we didn't find a match, the module will not be 393 // included 394 return false; 395 } 396 // Now evaluate exclusion rules, this ordering also means that exclusion rules may override 397 // inclusion rules: a config already matched for inclusion may still be excluded if matching 398 // rules exist 399 for (String key : exclude.keySet()) { 400 Set<String> filters = new HashSet<>(exclude.get(key)); 401 if (metadata.containsKey(key)) { 402 filters.retainAll(metadata.get(key)); 403 if (!filters.isEmpty()) { 404 // we found at least one matching exclusion rules, so we are excluding this 405 // this module 406 return false; 407 } 408 } 409 } 410 // we've matched at least one inclusion rule (if there's any) AND we didn't match any of the 411 // exclusion rules (if there's any) 412 return true; 413 } 414 shouldRunModule(String moduleId)415 private boolean shouldRunModule(String moduleId) { 416 List<TestFilter> mdIncludes = getFilter(mIncludeFilters, moduleId); 417 List<TestFilter> mdExcludes = getFilter(mExcludeFilters, moduleId); 418 // if including all modules or includes exist for this module, and there are not excludes 419 // for the entire module, this module should be run. 420 return (mIncludeAll || !mdIncludes.isEmpty()) && !containsModuleExclude(mdExcludes); 421 } 422 addTestIncludes(ITestFilterReceiver test, List<TestFilter> includes, String name)423 private void addTestIncludes(ITestFilterReceiver test, List<TestFilter> includes, 424 String name) { 425 if (test instanceof ITestFileFilterReceiver) { 426 File includeFile = createFilterFile(name, ".include", includes); 427 ((ITestFileFilterReceiver)test).setIncludeTestFile(includeFile); 428 } else { 429 // add test includes one at a time 430 for (TestFilter include : includes) { 431 String filterTestName = include.getTest(); 432 if (filterTestName != null) { 433 test.addIncludeFilter(filterTestName); 434 } 435 } 436 } 437 } 438 addTestExcludes(ITestFilterReceiver test, List<TestFilter> excludes, String name)439 private void addTestExcludes(ITestFilterReceiver test, List<TestFilter> excludes, 440 String name) { 441 if (test instanceof ITestFileFilterReceiver) { 442 File excludeFile = createFilterFile(name, ".exclude", excludes); 443 ((ITestFileFilterReceiver)test).setExcludeTestFile(excludeFile); 444 } else { 445 // add test excludes one at a time 446 for (TestFilter exclude : excludes) { 447 test.addExcludeFilter(exclude.getTest()); 448 } 449 } 450 } 451 createFilterFile(String prefix, String suffix, List<TestFilter> filters)452 private File createFilterFile(String prefix, String suffix, List<TestFilter> filters) { 453 File filterFile = null; 454 PrintWriter out = null; 455 try { 456 filterFile = FileUtil.createTempFile(prefix, suffix); 457 out = new PrintWriter(filterFile); 458 for (TestFilter filter : filters) { 459 String filterTest = filter.getTest(); 460 if (filterTest != null) { 461 out.println(filterTest); 462 } 463 } 464 out.flush(); 465 } catch (IOException e) { 466 throw new RuntimeException("Failed to create filter file"); 467 } finally { 468 if (out != null) { 469 out.close(); 470 } 471 } 472 filterFile.deleteOnExit(); 473 return filterFile; 474 } 475 476 /* 477 * Returns true iff one or more test filters in excludes apply to the entire module. 478 */ containsModuleExclude(Collection<TestFilter> excludes)479 private boolean containsModuleExclude(Collection<TestFilter> excludes) { 480 for (TestFilter exclude : excludes) { 481 if (exclude.getTest() == null) { 482 return true; 483 } 484 } 485 return false; 486 } 487 488 /** 489 * A {@link FilenameFilter} to find all the config files in a directory. 490 */ 491 public static class ConfigFilter implements FilenameFilter { 492 493 /** 494 * {@inheritDoc} 495 */ 496 @Override accept(File dir, String name)497 public boolean accept(File dir, String name) { 498 CLog.d("%s/%s", dir.getAbsolutePath(), name); 499 return name.endsWith(CONFIG_EXT); 500 } 501 } 502 503 /** 504 * {@inheritDoc} 505 */ 506 @Override getModules(String serial, int shardIndex)507 public LinkedList<IModuleDef> getModules(String serial, int shardIndex) { 508 Collections.sort(mNonTokenModules, new ExecutionOrderComparator()); 509 List<IModuleDef> modules = getShard(mNonTokenModules, shardIndex, mTotalShards); 510 if (modules == null) { 511 modules = new LinkedList<IModuleDef>(); 512 } 513 long estimatedTime = 0; 514 for (IModuleDef def : modules) { 515 estimatedTime += def.getRuntimeHint(); 516 } 517 518 // FIXME: Token Modules are the only last part that is not deterministic. 519 synchronized (lock) { 520 // Get tokens from the device 521 Set<String> tokens = mDeviceTokens.get(serial); 522 if (tokens != null && !tokens.isEmpty()) { 523 // if it matches any of the token modules, add them 524 for (IModuleDef def : mTokenModules) { 525 if (!mTokenModuleScheduled.contains(def)) { 526 if (tokens.equals(def.getTokens())) { 527 modules.add(def); 528 CLog.d("Adding %s to scheduled token", def); 529 mTokenModuleScheduled.add(def); 530 } 531 } 532 } 533 } 534 // the last shard going through may add everything remaining. 535 if (mInitCount == (mTotalShards - 1) && 536 mTokenModuleScheduled.size() != mTokenModules.size()) { 537 mTokenModules.removeAll(mTokenModuleScheduled); 538 if (mTotalShards != 1) { 539 // Only print the warnings if we are sharding. 540 CLog.e("Could not find any token for %s. Adding to last shard.", mTokenModules); 541 } 542 modules.addAll(mTokenModules); 543 } 544 mInitCount++; 545 } 546 Collections.sort(modules, new ExecutionOrderComparator()); 547 int uniqueCount = UniqueModuleCountUtil.countUniqueModules(modules); 548 CLog.logAndDisplay(LogLevel.INFO, "%s running %s test sub-modules, expected to complete " 549 + "in %s.", serial, uniqueCount, TimeUtil.formatElapsedTime(estimatedTime)); 550 CLog.d("module list for this shard: %s", modules); 551 LinkedList<IModuleDef> tests = new LinkedList<>(); 552 tests.addAll(modules); 553 return tests; 554 } 555 556 /** 557 * Helper to linearly split the list into shards with balanced runtimeHint. 558 * Exposed for testing. 559 */ getShard(List<IModuleDef> fullList, int shardIndex, int totalShard)560 protected List<IModuleDef> getShard(List<IModuleDef> fullList, int shardIndex, int totalShard) { 561 List<List<IModuleDef>> res = LinearPartition.split(fullList, totalShard); 562 if (res.isEmpty()) { 563 return null; 564 } 565 if (shardIndex >= res.size()) { 566 // If we could not shard up to expectation 567 return null; 568 } 569 return res.get(shardIndex); 570 } 571 572 /** 573 * @return the {@link List} of modules whose name contains the given pattern. 574 */ getModuleNamesMatching(File directory, String pattern)575 public static List<String> getModuleNamesMatching(File directory, String pattern) { 576 String[] names = directory.list(new NameFilter(pattern)); 577 List<String> modules = new ArrayList<String>(names.length); 578 for (String name : names) { 579 int index = name.indexOf(CONFIG_EXT); 580 if (index > 0) { 581 String module = name.substring(0, index); 582 if (module.equals(pattern)) { 583 // Pattern represents a single module, just return a single-item list 584 modules = new ArrayList<>(1); 585 modules.add(module); 586 return modules; 587 } 588 modules.add(module); 589 } 590 } 591 return modules; 592 } 593 putArgs(List<String> args, Map<String, Map<String, List<String>>> argsMap)594 private static void putArgs(List<String> args, 595 Map<String, Map<String, List<String>>> argsMap) { 596 for (String arg : args) { 597 String[] parts = arg.split(":"); 598 String target = parts[0]; 599 String name = parts[1]; 600 String value; 601 if (parts.length == 4) { 602 // key and value given, keep the pair delimited by ':' and stored as value 603 value = String.format("%s:%s", parts[2], parts[3]); 604 } else { 605 value = parts[2]; 606 } 607 Map<String, List<String>> map = argsMap.get(target); 608 if (map == null) { 609 map = new HashMap<>(); 610 argsMap.put(target, map); 611 } 612 List<String> valueList = map.get(name); 613 if (valueList == null) { 614 valueList = new ArrayList<>(); 615 map.put(name, valueList); 616 } 617 valueList.add(value); 618 } 619 } 620 621 /** 622 * Sort by name and use runtimeHint for separation, shortest test first. 623 */ 624 private static class ExecutionOrderComparator implements Comparator<IModuleDef> { 625 @Override compare(IModuleDef def1, IModuleDef def2)626 public int compare(IModuleDef def1, IModuleDef def2) { 627 int value1 = 0; 628 int value2 = 0; 629 if (ENDING_MODULES.containsKey(def1.getName())) { 630 value1 = ENDING_MODULES.get(def1.getName()); 631 } 632 if (ENDING_MODULES.containsKey(def2.getName())) { 633 value2 = ENDING_MODULES.get(def2.getName()); 634 } 635 if (value1 == 0 && value2 == 0) { 636 int time = (int) Math.signum(def1.getRuntimeHint() - def2.getRuntimeHint()); 637 if (time == 0) { 638 return def1.getName().compareTo(def2.getName()); 639 } 640 return time; 641 } 642 return (int) Math.signum(value1 - value2); 643 } 644 } 645 646 /** 647 * {@inheritDoc} 648 */ 649 @Override tearDown()650 public void tearDown() { 651 mNonTokenModules.clear(); 652 mTokenModules.clear(); 653 mIncludeFilters.clear(); 654 mExcludeFilters.clear(); 655 mTestArgs.clear(); 656 mModuleArgs.clear(); 657 } 658 659 /** 660 * @return the mConfigFactory 661 */ getConfigFactory()662 protected IConfigurationFactory getConfigFactory() { 663 return mConfigFactory; 664 } 665 } 666