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