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