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.tradefed.build.BuildInfoKey.BuildInfoFileKey;
19 import com.android.tradefed.build.IBuildInfo;
20 import com.android.tradefed.config.IConfiguration;
21 import com.android.tradefed.config.Option;
22 import com.android.tradefed.config.Option.Importance;
23 import com.android.tradefed.config.OptionClass;
24 import com.android.tradefed.device.DeviceFoldableState;
25 import com.android.tradefed.device.DeviceNotAvailableException;
26 import com.android.tradefed.device.ITestDevice;
27 import com.android.tradefed.device.StubDevice;
28 import com.android.tradefed.error.HarnessRuntimeException;
29 import com.android.tradefed.invoker.logger.InvocationMetricLogger;
30 import com.android.tradefed.invoker.logger.InvocationMetricLogger.InvocationMetricKey;
31 import com.android.tradefed.log.LogUtil.CLog;
32 import com.android.tradefed.result.FileInputStreamSource;
33 import com.android.tradefed.result.LogDataType;
34 import com.android.tradefed.result.error.InfraErrorIdentifier;
35 import com.android.tradefed.testtype.IAbi;
36 import com.android.tradefed.testtype.IRemoteTest;
37 import com.android.tradefed.testtype.ITestFileFilterReceiver;
38 import com.android.tradefed.testtype.ITestFilterReceiver;
39 import com.android.tradefed.testtype.suite.params.FoldableExpandingHandler;
40 import com.android.tradefed.testtype.suite.params.IModuleParameterHandler;
41 import com.android.tradefed.testtype.suite.params.ModuleParameters;
42 import com.android.tradefed.testtype.suite.params.ModuleParametersHelper;
43 import com.android.tradefed.testtype.suite.params.NegativeHandler;
44 import com.android.tradefed.util.ArrayUtil;
45 import com.android.tradefed.util.FileUtil;
46 
47 import com.google.common.annotations.VisibleForTesting;
48 
49 import java.io.File;
50 import java.io.FileNotFoundException;
51 import java.io.IOException;
52 import java.util.ArrayList;
53 import java.util.HashSet;
54 import java.util.LinkedHashMap;
55 import java.util.LinkedHashSet;
56 import java.util.List;
57 import java.util.Map;
58 import java.util.Map.Entry;
59 import java.util.Set;
60 import java.util.stream.Collectors;
61 
62 /** A Test for running Compatibility Test Suite with new suite system. */
63 @OptionClass(alias = "base-suite")
64 public class BaseTestSuite extends ITestSuite {
65 
66     public static final String INCLUDE_FILTER_OPTION = "include-filter";
67     public static final String EXCLUDE_FILTER_OPTION = "exclude-filter";
68     public static final String MODULE_OPTION = "module";
69     public static final char MODULE_OPTION_SHORT_NAME = 'm';
70     public static final String TEST_ARG_OPTION = "test-arg";
71     public static final String TEST_OPTION = "test";
72     public static final char TEST_OPTION_SHORT_NAME = 't';
73     public static final String CONFIG_PATTERNS_OPTION = "config-patterns";
74     private static final String MODULE_ARG_OPTION = "module-arg";
75     private static final String REVERSE_EXCLUDE_FILTERS = "reverse-exclude-filters";
76     private static final int MAX_FILTER_DISPLAY = 20;
77 
78     @Option(
79             name = INCLUDE_FILTER_OPTION,
80             description =
81                     "the include module filters to apply. Format: '[abi] <module-name> [test]'. See"
82                         + " documentation:"
83                         + "https://source.android.com/docs/core/tests/tradefed/testing/through-suite/option-passing",
84             importance = Importance.ALWAYS)
85     private Set<String> mIncludeFilters = new LinkedHashSet<>();
86 
87     @Option(
88             name = EXCLUDE_FILTER_OPTION,
89             description =
90                     "the exclude module filters to apply. Format: '[abi] <module-name> [test]'. See"
91                         + " documentation:"
92                         + "https://source.android.com/docs/core/tests/tradefed/testing/through-suite/option-passing",
93             importance = Importance.ALWAYS)
94     private Set<String> mExcludeFilters = new LinkedHashSet<>();
95 
96     @Option(
97             name = REVERSE_EXCLUDE_FILTERS,
98             description =
99                     "Flip exclude-filters into include-filters, in order to run only the excluded "
100                             + "set.")
101     private boolean mReverseExcludeFilters = false;
102 
103     @Option(
104             name = MODULE_OPTION,
105             shortName = MODULE_OPTION_SHORT_NAME,
106             description = "the test module to run. Only works for configuration in the tests dir.",
107             importance = Importance.IF_UNSET)
108     private String mModuleName = null;
109 
110     @Option(
111             name = TEST_OPTION,
112             shortName = TEST_OPTION_SHORT_NAME,
113             description = "the test to run.",
114             importance = Importance.IF_UNSET)
115     private String mTestName = null;
116 
117     @Option(
118             name = MODULE_ARG_OPTION,
119             description =
120                     "the arguments to pass to a module. The expected format is"
121                             + "\"<module-name>:[{alias}]<arg-name>:[<arg-key>:=]<arg-value>\"",
122             importance = Importance.ALWAYS)
123     private List<String> mModuleArgs = new ArrayList<>();
124 
125     @Option(
126             name = TEST_ARG_OPTION,
127             description =
128                     "The arguments to pass to a test or its preparers. The expected format is"
129                             + "\"<test-class>:<arg-name>:[<arg-key>:=]<arg-value>\"",
130             importance = Importance.ALWAYS)
131     private List<String> mTestArgs = new ArrayList<>();
132 
133     @Option(
134             name = "run-suite-tag",
135             description =
136                     "The tag that must be run. If specified, only configurations containing the "
137                             + "matching suite tag will be able to run.")
138     private String mSuiteTag = null;
139 
140     @Option(
141             name = "prioritize-host-config",
142             description =
143                     "If there are duplicate test configs for host/target, prioritize the host"
144                             + " config, otherwise use the target config.")
145     private boolean mPrioritizeHostConfig = false;
146 
147     @Option(
148             name = "suite-config-prefix",
149             description = "Search only configs with given prefix for suite tags.")
150     private String mSuitePrefix = null;
151 
152     @Option(
153             name = "skip-loading-config-jar",
154             description =
155                     "Whether or not to skip loading configurations from the JAR on the classpath.")
156     private boolean mSkipJarLoading = false;
157 
158     @Option(
159             name = CONFIG_PATTERNS_OPTION,
160             description =
161                     "The pattern(s) of the configurations that should be loaded from a directory."
162                             + " If none is explicitly specified, .*.xml and .*.config will be used."
163                             + " Can be repeated.")
164     private List<String> mConfigPatterns = new ArrayList<>();
165 
166     @Option(
167             name = "enable-parameterized-modules",
168             description =
169                     "Whether or not to enable parameterized modules. This is a feature flag for"
170                             + " work in development.")
171     private boolean mEnableParameter = false;
172 
173     @Option(
174             name = "enable-mainline-parameterized-modules",
175             description =
176                     "Whether or not to enable mainline parameterized modules. This is a feature"
177                             + " flag for work in development.")
178     private boolean mEnableMainlineParameter = false;
179 
180     @Option(
181             name = "enable-optional-parameterization",
182             description =
183                     "Whether or not to enable optional parameters. Optional parameters are "
184                             + "parameters not usually used by default.")
185     private boolean mEnableOptionalParameter = false;
186 
187     @Option(
188             name = "module-parameter",
189             description =
190                     "Allows to run only one module parameter type instead of all the combinations."
191                         + " For example: 'instant_app' would only run the instant_app version of "
192                         + "modules")
193     private ModuleParameters mForceParameter = null;
194 
195     @Option(
196             name = "exclude-module-parameters",
197             description =
198                     "Exclude some modules parameter from being evaluated in the run"
199                             + " combinations.For example: 'instant_app' would exclude all the"
200                             + " instant_app version of modules.")
201     private Set<ModuleParameters> mExcludedModuleParameters = new HashSet<>();
202 
203     @Option(
204             name = "fail-on-everything-filtered",
205             description =
206                     "Whether or not to fail the invocation in case test filter returns"
207                             + " an empty result.")
208     private boolean mFailOnEverythingFiltered = false;
209 
210     @Option(
211             name = "ignore-non-preloaded-mainline-module",
212             description =
213                     "Skip installing the module(s) when the module(s) that are not"
214                             + "preloaded on device. Otherwise an exception will be thrown.")
215     private boolean mIgnoreNonPreloadedMainlineModule = false;
216 
217     @Option(
218             name = "load-configs-with-include-filters",
219             description =
220                     "An experimental flag to improve the performance of loading test configs with "
221                             + "given module defined in include-filter.")
222     private boolean mLoadConfigsWithIncludeFilters = false;
223 
224     private SuiteModuleLoader mModuleRepo;
225     private Map<String, LinkedHashSet<SuiteTestFilter>> mIncludeFiltersParsed =
226             new LinkedHashMap<>();
227     private Map<String, LinkedHashSet<SuiteTestFilter>> mExcludeFiltersParsed =
228             new LinkedHashMap<>();
229     private List<File> mConfigPaths = new ArrayList<>();
230     private Set<IAbi> mAbis = new LinkedHashSet<>();
231     private Set<DeviceFoldableState> mFoldableStates = new LinkedHashSet<>();
232 
setSkipjarLoading(boolean skipJarLoading)233     public void setSkipjarLoading(boolean skipJarLoading) {
234         mSkipJarLoading = skipJarLoading;
235     }
236 
237     /** {@inheritDoc} */
238     @Override
loadTests()239     public LinkedHashMap<String, IConfiguration> loadTests() {
240         try {
241             File testsDir = getTestsDir();
242             try {
243                 mFoldableStates = getFoldableStates(getDevice());
244             } catch (UnsupportedOperationException e) {
245                 // Foldable state isn't always supported
246                 CLog.e(e);
247             }
248             setupFilters(testsDir);
249             mAbis = getAbis(getDevice());
250 
251             if (mReverseExcludeFilters) {
252                 if (mExcludeFilters.isEmpty()) {
253                     return new LinkedHashMap<String, IConfiguration>();
254                 }
255                 mIncludeFilters.clear();
256                 mIncludeFilters.addAll(mExcludeFilters);
257                 mExcludeFilters.clear();
258             }
259 
260             // Create and populate the filters here
261             SuiteModuleLoader.addFilters(
262                     mIncludeFilters, mIncludeFiltersParsed, mAbis, mFoldableStates);
263             SuiteModuleLoader.addFilters(
264                     mExcludeFilters, mExcludeFiltersParsed, mAbis, mFoldableStates);
265 
266             String includeFilters = "";
267             if (mIncludeFiltersParsed.size() > MAX_FILTER_DISPLAY) {
268                 if (isSplitting()) {
269                     includeFilters = "Includes: <too long to display>";
270                 } else {
271                     File suiteIncludeFilters = null;
272                     try {
273                         suiteIncludeFilters =
274                                 FileUtil.createTempFile("suite-include-filters", ".txt");
275                         FileUtil.writeToFile(mIncludeFiltersParsed.toString(), suiteIncludeFilters);
276                         logFilterFile(
277                                 suiteIncludeFilters,
278                                 suiteIncludeFilters.getName(),
279                                 LogDataType.TEXT);
280                         includeFilters =
281                                 String.format("Includes: See %s", suiteIncludeFilters.getName());
282                     } catch (IOException e) {
283                         CLog.e(e);
284                     } finally {
285                         FileUtil.deleteFile(suiteIncludeFilters);
286                     }
287                 }
288             } else if (mIncludeFiltersParsed.size() > 0) {
289                 includeFilters = String.format("Includes: %s", mIncludeFiltersParsed.toString());
290             }
291 
292             String excludeFilters = "";
293             if (mExcludeFiltersParsed.size() > MAX_FILTER_DISPLAY) {
294                 if (isSplitting()) {
295                     excludeFilters = "Excludes: <too long to display>";
296                 } else {
297                     File suiteExcludeFilters = null;
298                     try {
299                         suiteExcludeFilters =
300                                 FileUtil.createTempFile("suite-exclude-filters", ".txt");
301                         FileUtil.writeToFile(mExcludeFiltersParsed.toString(), suiteExcludeFilters);
302                         logFilterFile(
303                                 suiteExcludeFilters,
304                                 suiteExcludeFilters.getName(),
305                                 LogDataType.TEXT);
306                         excludeFilters =
307                                 String.format("Excludes: See %s", suiteExcludeFilters.getName());
308                     } catch (IOException e) {
309                         CLog.e(e);
310                     } finally {
311                         FileUtil.deleteFile(suiteExcludeFilters);
312                     }
313                 }
314             } else if (mExcludeFiltersParsed.size() > 0) {
315                 excludeFilters = String.format("Excludes: %s", mExcludeFiltersParsed.toString());
316             }
317 
318             CLog.d(
319                     "Initializing ModuleRepo\nABIs:%s\n" + "Test Args:%s\nModule Args:%s\n%s\n%s",
320                     mAbis, mTestArgs, mModuleArgs, includeFilters, excludeFilters);
321             if (!mFoldableStates.isEmpty()) {
322                 CLog.d("Foldable states: %s", mFoldableStates);
323             }
324 
325             mModuleRepo =
326                     createModuleLoader(
327                             mIncludeFiltersParsed, mExcludeFiltersParsed, mTestArgs, mModuleArgs);
328             if (mForceParameter != null && !mEnableParameter) {
329                 throw new IllegalArgumentException(
330                         "'module-parameter' option was specified without "
331                                 + "'enable-parameterized-modules'");
332             }
333             if (mEnableOptionalParameter && !mEnableParameter) {
334                 throw new IllegalArgumentException(
335                         "'enable-optional-parameterization' option was specified without "
336                                 + "'enable-parameterized-modules'");
337             }
338 
339             if (mEnableMainlineParameter) {
340                 mModuleRepo.setMainlineParameterizedModules(mEnableMainlineParameter);
341                 mModuleRepo.setInvocationContext(getInvocationContext());
342                 mModuleRepo.setOptimizeMainlineTest(
343                         getConfiguration().getCommandOptions().getOptimizeMainlineTest());
344                 mModuleRepo.setIgnoreNonPreloadedMainlineModule(mIgnoreNonPreloadedMainlineModule);
345             }
346 
347             mModuleRepo.setParameterizedModules(mEnableParameter);
348             mModuleRepo.setOptionalParameterizedModules(mEnableOptionalParameter);
349             mModuleRepo.setModuleParameter(mForceParameter);
350             mModuleRepo.setExcludedModuleParameters(mExcludedModuleParameters);
351             mModuleRepo.setFoldableStates(mFoldableStates);
352             mModuleRepo.setLoadConfigsWithIncludeFilters(mLoadConfigsWithIncludeFilters);
353 
354             List<File> testsDirectories = new ArrayList<>();
355 
356             // Include host or target first in the search if it exists, we have to this in
357             // BaseTestSuite because it's the only one with the BuildInfo knowledge of linked files
358             if (mPrioritizeHostConfig) {
359                 File hostSubDir = getBuildInfo().getFile(BuildInfoFileKey.HOST_LINKED_DIR);
360                 if (hostSubDir != null && hostSubDir.exists()) {
361                     testsDirectories.add(hostSubDir);
362                 }
363             } else {
364                 File targetSubDir = getBuildInfo().getFile(BuildInfoFileKey.TARGET_LINKED_DIR);
365                 if (targetSubDir != null && targetSubDir.exists()) {
366                     testsDirectories.add(targetSubDir);
367                 }
368             }
369 
370             // Finally add the full test cases directory in case there is no special sub-dir.
371             testsDirectories.add(testsDir);
372             // Actual loading of the configurations.
373             long start = System.currentTimeMillis();
374             LinkedHashMap<String, IConfiguration> loadedTests =
375                     loadingStrategy(mAbis, testsDirectories, mSuitePrefix, mSuiteTag);
376             long duration = System.currentTimeMillis() - start;
377             InvocationMetricLogger.addInvocationMetrics(
378                     InvocationMetricKey.LOAD_TEST_CONFIGS_TIME, duration);
379             if (mFailOnEverythingFiltered
380                     && loadedTests.isEmpty()
381                     && !mIncludeFiltersParsed.isEmpty()) {
382                 // remove modules with empty filters from the message
383                 Map<String, LinkedHashSet<SuiteTestFilter>> includeFiltersCleaned =
384                         mIncludeFiltersParsed.entrySet().stream()
385                                 .filter(
386                                         entry ->
387                                                 entry.getValue() != null
388                                                         && !entry.getValue().isEmpty())
389                                 .collect(
390                                         Collectors.toMap(
391                                                 Map.Entry::getKey,
392                                                 Map.Entry::getValue,
393                                                 (x, y) -> y,
394                                                 LinkedHashMap::new));
395                 String errorMessage =
396                         String.format(
397                                 "Include filter '%s' was specified but resulted in an empty test"
398                                         + " set.",
399                                 includeFiltersCleaned.toString());
400                 if (errorMessage.length() > 1000) {
401                     CLog.e(errorMessage);
402                     errorMessage =
403                             String.format(
404                                     "Include filter was specified for %d modules but resulted in an"
405                                             + " empty test set. Check host log for complete list of"
406                                             + " include filters.",
407                                     includeFiltersCleaned.size());
408                 }
409                 throw new HarnessRuntimeException(
410                         errorMessage, InfraErrorIdentifier.OPTION_CONFIGURATION_ERROR);
411             }
412             return loadedTests;
413         } catch (DeviceNotAvailableException e) {
414             throw new HarnessRuntimeException(e.getMessage(), e);
415         } catch (FileNotFoundException fnfe) {
416             throw new HarnessRuntimeException(
417                     fnfe.getMessage(), fnfe, InfraErrorIdentifier.ARTIFACT_NOT_FOUND);
418         }
419     }
420 
421     /**
422      * Default loading strategy will load from the resources and the tests directory. Can be
423      * extended or replaced.
424      *
425      * @param abis The set of abis to run against.
426      * @param testsDirs The tests directory.
427      * @param suitePrefix A prefix to filter the resource directory.
428      * @param suiteTag The suite tag a module should have to be included. Can be null.
429      * @return A list of loaded configuration for the suite.
430      */
loadingStrategy( Set<IAbi> abis, List<File> testsDirs, String suitePrefix, String suiteTag)431     public LinkedHashMap<String, IConfiguration> loadingStrategy(
432             Set<IAbi> abis, List<File> testsDirs, String suitePrefix, String suiteTag) {
433         LinkedHashMap<String, IConfiguration> loadedConfigs = new LinkedHashMap<>();
434         // Load and return directly the specific config files.
435         if (!mConfigPaths.isEmpty()) {
436             CLog.d(
437                     "Loading the specified configs path '%s' and skip loading from the resources.",
438                     mConfigPaths);
439             return getModuleLoader().loadConfigsFromSpecifiedPaths(mConfigPaths, abis, suiteTag);
440         }
441 
442         // Load configs that are part of the resources
443         if (!mSkipJarLoading) {
444             loadedConfigs.putAll(
445                     getModuleLoader().loadConfigsFromJars(abis, suitePrefix, suiteTag));
446         }
447 
448         // Load the configs that are part of the tests dir
449         if (mConfigPatterns.isEmpty()) {
450             // If no special pattern was configured, use the default configuration patterns we know
451             mConfigPatterns.add(".*\\.config$");
452             mConfigPatterns.add(".*\\.xml$");
453         }
454 
455         loadedConfigs.putAll(
456                 getModuleLoader()
457                         .loadConfigsFromDirectory(
458                                 testsDirs, abis, suitePrefix, suiteTag, mConfigPatterns));
459         return loadedConfigs;
460     }
461 
462     /** {@inheritDoc} */
463     @Override
setBuild(IBuildInfo buildInfo)464     public void setBuild(IBuildInfo buildInfo) {
465         super.setBuild(buildInfo);
466     }
467 
468     /** Sets include-filters for the compatibility test */
setIncludeFilter(Set<String> includeFilters)469     public void setIncludeFilter(Set<String> includeFilters) {
470         mIncludeFilters.addAll(includeFilters);
471     }
472 
473     /** Gets a copy of include-filters for the compatibility test */
getIncludeFilter()474     public Set<String> getIncludeFilter() {
475         return new LinkedHashSet<String>(mIncludeFilters);
476     }
477 
clearIncludeFilter()478     public void clearIncludeFilter() {
479         mIncludeFilters.clear();
480     }
481 
482     /** Sets exclude-filters for the compatibility test */
setExcludeFilter(Set<String> excludeFilters)483     public void setExcludeFilter(Set<String> excludeFilters) {
484         mExcludeFilters.addAll(excludeFilters);
485     }
486 
clearExcludeFilter()487     public void clearExcludeFilter() {
488         mExcludeFilters.clear();
489     }
490 
491     /** Gets a copy of exclude-filters for the compatibility test */
getExcludeFilter()492     public Set<String> getExcludeFilter() {
493         return new HashSet<String>(mExcludeFilters);
494     }
495 
496     /** Returns the current {@link SuiteModuleLoader}. */
getModuleLoader()497     public SuiteModuleLoader getModuleLoader() {
498         return mModuleRepo;
499     }
500 
reevaluateFilters()501     public void reevaluateFilters() {
502         SuiteModuleLoader.addFilters(
503                 mIncludeFilters, mIncludeFiltersParsed, mAbis, mFoldableStates);
504         SuiteModuleLoader.addFilters(
505                 mExcludeFilters, mExcludeFiltersParsed, mAbis, mFoldableStates);
506         if (getDirectModule() != null) {
507             // Remove all entries for unrelated modules
508             mExcludeFiltersParsed.keySet().removeIf(key -> !key.equals(getDirectModule().getId()));
509             // Also clean exclude filters left over
510             for (String filterString : new HashSet<>(mExcludeFilters)) {
511                 SuiteTestFilter parentFilter = SuiteTestFilter.createFrom(filterString);
512                 if (!parentFilter.getModuleId().equals(getDirectModule().getId())) {
513                     mExcludeFilters.remove(filterString);
514                 }
515             }
516         }
517     }
518 
519     /** Adds module args */
addModuleArgs(Set<String> moduleArgs)520     public void addModuleArgs(Set<String> moduleArgs) {
521         mModuleArgs.addAll(moduleArgs);
522     }
523 
524     /** Clear the stored module args out */
clearModuleArgs()525     void clearModuleArgs() {
526         mModuleArgs.clear();
527     }
528 
529     /** Add config patterns */
addConfigPatterns(List<String> patterns)530     public void addConfigPatterns(List<String> patterns) {
531         mConfigPatterns.addAll(patterns);
532     }
533 
534     /** Set whether or not parameterized modules are enabled or not. */
setEnableParameterizedModules(boolean enableParameter)535     public void setEnableParameterizedModules(boolean enableParameter) {
536         mEnableParameter = enableParameter;
537     }
538 
539     /** Set whether or not optional parameterized modules are enabled or not. */
setEnableOptionalParameterizedModules(boolean enableOptionalParameter)540     public void setEnableOptionalParameterizedModules(boolean enableOptionalParameter) {
541         mEnableOptionalParameter = enableOptionalParameter;
542     }
543 
setModuleParameter(ModuleParameters forceParameter)544     public void setModuleParameter(ModuleParameters forceParameter) {
545         mForceParameter = forceParameter;
546     }
547 
548     /**
549      * Create the {@link SuiteModuleLoader} responsible to load the {@link IConfiguration} and
550      * assign them some of the options.
551      *
552      * @param includeFiltersFormatted The formatted and parsed include filters.
553      * @param excludeFiltersFormatted The formatted and parsed exclude filters.
554      * @param testArgs the list of test ({@link IRemoteTest}) arguments.
555      * @param moduleArgs the list of module arguments.
556      * @return the created {@link SuiteModuleLoader}.
557      */
createModuleLoader( Map<String, LinkedHashSet<SuiteTestFilter>> includeFiltersFormatted, Map<String, LinkedHashSet<SuiteTestFilter>> excludeFiltersFormatted, List<String> testArgs, List<String> moduleArgs)558     public SuiteModuleLoader createModuleLoader(
559             Map<String, LinkedHashSet<SuiteTestFilter>> includeFiltersFormatted,
560             Map<String, LinkedHashSet<SuiteTestFilter>> excludeFiltersFormatted,
561             List<String> testArgs,
562             List<String> moduleArgs) {
563         return new SuiteModuleLoader(
564                 includeFiltersFormatted, excludeFiltersFormatted, testArgs, moduleArgs);
565     }
566 
567     /**
568      * Sets the include/exclude filters up based on if a module name was given.
569      *
570      * @throws FileNotFoundException if any file is not found.
571      */
setupFilters(File testsDir)572     protected void setupFilters(File testsDir) throws FileNotFoundException {
573         if (mModuleName == null) {
574             if (mTestName != null) {
575                 throw new IllegalArgumentException(
576                         "Test name given without module name. Add --module <module-name>");
577             }
578             return;
579         }
580         // If this option (-m / --module) is set only the matching unique module should run.
581         Set<File> modules =
582                 SuiteModuleLoader.getModuleNamesMatching(
583                         testsDir, mSuitePrefix, String.format(".*%s.*.config", mModuleName));
584         // If multiple modules match, do exact match.
585         if (modules.size() > 1) {
586             Set<File> newModules = new HashSet<>();
587             String exactModuleName = String.format("%s.config", mModuleName);
588             for (File module : modules) {
589                 if (module.getName().equals(exactModuleName)) {
590                     newModules.add(module);
591                     modules = newModules;
592                     break;
593                 }
594             }
595         }
596         if (modules.size() == 0) {
597             throw new HarnessRuntimeException(
598                     String.format("No modules found matching %s", mModuleName),
599                     InfraErrorIdentifier.OPTION_CONFIGURATION_ERROR);
600         } else if (modules.size() > 1) {
601             throw new HarnessRuntimeException(
602                     String.format(
603                             "Multiple modules found matching %s:\n%s\nWhich one did you "
604                                     + "mean?\n",
605                             mModuleName, ArrayUtil.join("\n", modules)),
606                     InfraErrorIdentifier.OPTION_CONFIGURATION_ERROR);
607         } else {
608             File mod = modules.iterator().next();
609             String moduleName = mod.getName().replace(".config", "");
610             checkFilters(mIncludeFilters, moduleName);
611             checkFilters(mExcludeFilters, moduleName);
612             mIncludeFilters.add(
613                     new SuiteTestFilter(getRequestedAbi(), moduleName, mTestName).toString());
614             // Create the matching filters for the parameterized version of it if needed.
615             if (mEnableParameter) {
616                 for (ModuleParameters param : ModuleParameters.values()) {
617                     Map<ModuleParameters, IModuleParameterHandler> moduleParamExpanded =
618                             ModuleParametersHelper.resolveParam(param, mEnableOptionalParameter);
619                     if (moduleParamExpanded == null) {
620                         continue;
621                     }
622                     for (Entry<ModuleParameters, IModuleParameterHandler> moduleParam :
623                             moduleParamExpanded.entrySet()) {
624                         if (moduleParam.getValue() instanceof NegativeHandler) {
625                             continue;
626                         }
627                         if (moduleParam.getValue() instanceof FoldableExpandingHandler) {
628                             List<IModuleParameterHandler> foldableHandlers =
629                                     ((FoldableExpandingHandler) moduleParam.getValue())
630                                             .expandHandler(mFoldableStates);
631                             for (IModuleParameterHandler foldableHandler : foldableHandlers) {
632                                 String paramModuleName =
633                                         String.format(
634                                                 "%s[%s]",
635                                                 moduleName,
636                                                 foldableHandler.getParameterIdentifier());
637                                 mIncludeFilters.add(
638                                         new SuiteTestFilter(
639                                                         getRequestedAbi(),
640                                                         paramModuleName,
641                                                         mTestName)
642                                                 .toString());
643                             }
644                             continue;
645                         }
646                         String paramModuleName =
647                                 String.format(
648                                         "%s[%s]",
649                                         moduleName,
650                                         moduleParam.getValue().getParameterIdentifier());
651                         mIncludeFilters.add(
652                                 new SuiteTestFilter(getRequestedAbi(), paramModuleName, mTestName)
653                                         .toString());
654                     }
655                 }
656             }
657         }
658     }
659 
660     @Override
cleanUpSuiteSetup()661     public void cleanUpSuiteSetup() {
662         super.cleanUpSuiteSetup();
663         // Clean the filters because at that point they have been applied to the runners.
664         // This can save several GB of memories during sharding.
665         mIncludeFilters.clear();
666         mExcludeFilters.clear();
667         mIncludeFiltersParsed.clear();
668         mExcludeFiltersParsed.clear();
669     }
670 
671     /**
672      * Add the config path for {@link SuiteModuleLoader} to limit the search loading configurations.
673      *
674      * @param configPath A {@code File} with the absolute path of the configuration.
675      */
addConfigPaths(File configPath)676     void addConfigPaths(File configPath) {
677         mConfigPaths.add(configPath);
678     }
679 
680     /** Clear the stored config paths out. */
clearConfigPaths()681     void clearConfigPaths() {
682         mConfigPaths.clear();
683     }
684 
685     /* Helper method designed to remove filters in a list not applicable to the given module */
checkFilters(Set<String> filters, String moduleName)686     private static void checkFilters(Set<String> filters, String moduleName) {
687         Set<String> cleanedFilters = new HashSet<String>();
688         for (String filter : filters) {
689             SuiteTestFilter filterObject = SuiteTestFilter.createFrom(filter);
690             String filterName = filterObject.getName();
691             String filterBaseName = filterObject.getBaseName();
692             if (moduleName.equals(filterName) || moduleName.equals(filterBaseName)) {
693                 cleanedFilters.add(filter); // Module name matches, filter passes
694             }
695         }
696         filters.clear();
697         filters.addAll(cleanedFilters);
698     }
699 
700     /* Return a {@link boolean} for the setting of prioritize-host-config.*/
getPrioritizeHostConfig()701     boolean getPrioritizeHostConfig() {
702         return mPrioritizeHostConfig;
703     }
704 
705     /**
706      * Set option prioritize-host-config.
707      *
708      * @param prioritizeHostConfig true to prioritize host config, i.e., run host test if possible.
709      */
710     @VisibleForTesting
setPrioritizeHostConfig(boolean prioritizeHostConfig)711     protected void setPrioritizeHostConfig(boolean prioritizeHostConfig) {
712         mPrioritizeHostConfig = prioritizeHostConfig;
713     }
714 
715     /** Log a file directly to the result reporter. */
logFilterFile(File filterFile, String dataName, LogDataType type)716     private void logFilterFile(File filterFile, String dataName, LogDataType type) {
717         if (getCurrentTestLogger() == null) {
718             return;
719         }
720         try (FileInputStreamSource source = new FileInputStreamSource(filterFile)) {
721             getCurrentTestLogger().testLog(dataName, type, source);
722         }
723     }
724 
725     @Override
shouldModuleRun(ModuleDefinition module)726     protected boolean shouldModuleRun(ModuleDefinition module) {
727         String moduleId = module.getId();
728         LinkedHashSet<SuiteTestFilter> excludeFilters = mExcludeFiltersParsed.get(moduleId);
729         CLog.d("Filters for '%s': %s", moduleId, excludeFilters);
730         if (excludeFilters == null || excludeFilters.isEmpty()) {
731             return true;
732         }
733         for (SuiteTestFilter filter : excludeFilters) {
734             if (filter.getTest() == null) {
735                 CLog.d("Skipping %s, it previously passed.", moduleId);
736                 return false;
737             }
738             for (IRemoteTest test : module.getTests()) {
739                 if (test instanceof ITestFileFilterReceiver) {
740                     File excludeFilterFile = ((ITestFileFilterReceiver) test).getExcludeTestFile();
741                     if (excludeFilterFile == null) {
742                         try {
743                             excludeFilterFile = FileUtil.createTempFile("exclude-filter", ".txt");
744                         } catch (IOException e) {
745                             throw new HarnessRuntimeException(
746                                     e.getMessage(), e, InfraErrorIdentifier.FAIL_TO_CREATE_FILE);
747                         }
748                         ((ITestFileFilterReceiver) test).setExcludeTestFile(excludeFilterFile);
749                     }
750                     try {
751                         FileUtil.writeToFile(filter.getTest() + "\n", excludeFilterFile, true);
752                     } catch (IOException e) {
753                         CLog.e(e);
754                         continue;
755                     }
756                 } else if (test instanceof ITestFilterReceiver) {
757                     ((ITestFilterReceiver) test).addExcludeFilter(filter.getTest());
758                 }
759             }
760         }
761         return true;
762     }
763 
getFoldableStates(ITestDevice device)764     protected Set<DeviceFoldableState> getFoldableStates(ITestDevice device)
765             throws DeviceNotAvailableException {
766         if (device == null || device.getIDevice() instanceof StubDevice) {
767             return mFoldableStates;
768         }
769         if (!mFoldableStates.isEmpty()) {
770             return mFoldableStates;
771         }
772         mFoldableStates = device.getFoldableStates();
773         return mFoldableStates;
774     }
775 
getRunSuiteTag()776     public String getRunSuiteTag() {
777         return mSuiteTag;
778     }
779 
reverseExcludeFilters()780     public boolean reverseExcludeFilters() {
781         return mReverseExcludeFilters;
782     }
783 }
784