• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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