• 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.build.IDeviceBuildInfo;
21 import com.android.tradefed.config.IConfiguration;
22 import com.android.tradefed.config.Option;
23 import com.android.tradefed.config.Option.Importance;
24 import com.android.tradefed.config.OptionClass;
25 import com.android.tradefed.device.DeviceNotAvailableException;
26 import com.android.tradefed.log.LogUtil.CLog;
27 import com.android.tradefed.testtype.IAbi;
28 import com.android.tradefed.testtype.IRemoteTest;
29 import com.android.tradefed.testtype.suite.params.ModuleParameters;
30 import com.android.tradefed.util.ArrayUtil;
31 
32 import com.google.common.annotations.VisibleForTesting;
33 
34 import java.io.File;
35 import java.io.FileNotFoundException;
36 import java.util.ArrayList;
37 import java.util.HashMap;
38 import java.util.HashSet;
39 import java.util.LinkedHashMap;
40 import java.util.List;
41 import java.util.Map;
42 import java.util.Set;
43 
44 /** A Test for running Compatibility Test Suite with new suite system. */
45 @OptionClass(alias = "base-suite")
46 public class BaseTestSuite extends ITestSuite {
47 
48     public static final String INCLUDE_FILTER_OPTION = "include-filter";
49     public static final String EXCLUDE_FILTER_OPTION = "exclude-filter";
50     public static final String MODULE_OPTION = "module";
51     public static final String TEST_ARG_OPTION = "test-arg";
52     public static final String TEST_OPTION = "test";
53     public static final char TEST_OPTION_SHORT_NAME = 't';
54     public static final String CONFIG_PATTERNS_OPTION = "config-patterns";
55     private static final String MODULE_ARG_OPTION = "module-arg";
56 
57     @Option(
58         name = INCLUDE_FILTER_OPTION,
59         description = "the include module filters to apply.",
60         importance = Importance.ALWAYS
61     )
62     private Set<String> mIncludeFilters = new HashSet<>();
63 
64     @Option(
65         name = EXCLUDE_FILTER_OPTION,
66         description = "the exclude module filters to apply.",
67         importance = Importance.ALWAYS
68     )
69     private Set<String> mExcludeFilters = new HashSet<>();
70 
71     @Option(
72         name = MODULE_OPTION,
73         shortName = 'm',
74         description = "the test module to run. Only works for configuration in the tests dir.",
75         importance = Importance.IF_UNSET
76     )
77     private String mModuleName = null;
78 
79     @Option(
80         name = TEST_OPTION,
81         shortName = TEST_OPTION_SHORT_NAME,
82         description = "the test to run.",
83         importance = Importance.IF_UNSET
84     )
85     private String mTestName = null;
86 
87     @Option(
88         name = MODULE_ARG_OPTION,
89         description =
90                 "the arguments to pass to a module. The expected format is"
91                         + "\"<module-name>:[{alias}]<arg-name>:[<arg-key>:=]<arg-value>\"",
92         importance = Importance.ALWAYS
93     )
94     private List<String> mModuleArgs = new ArrayList<>();
95 
96     @Option(
97         name = TEST_ARG_OPTION,
98         description =
99                 "the arguments to pass to a test. The expected format is"
100                         + "\"<test-class>:<arg-name>:[<arg-key>:=]<arg-value>\"",
101         importance = Importance.ALWAYS
102     )
103     private List<String> mTestArgs = new ArrayList<>();
104 
105     @Option(
106         name = "run-suite-tag",
107         description =
108                 "The tag that must be run. If specified, only configurations containing the "
109                         + "matching suite tag will be able to run."
110     )
111     private String mSuiteTag = null;
112 
113     @Option(
114         name = "prioritize-host-config",
115         description =
116                 "If there are duplicate test configs for host/target, prioritize the host config, "
117                         + "otherwise use the target config."
118     )
119     private boolean mPrioritizeHostConfig = false;
120 
121     @Option(
122         name = "suite-config-prefix",
123         description = "Search only configs with given prefix for suite tags."
124     )
125     private String mSuitePrefix = null;
126 
127     @Option(
128         name = "skip-loading-config-jar",
129         description = "Whether or not to skip loading configurations from the JAR on the classpath."
130     )
131     private boolean mSkipJarLoading = false;
132 
133     @Option(
134         name = CONFIG_PATTERNS_OPTION,
135         description =
136                 "The pattern(s) of the configurations that should be loaded from a directory."
137                         + " If none is explicitly specified, .*.xml and .*.config will be used."
138                         + " Can be repeated."
139     )
140     private List<String> mConfigPatterns = new ArrayList<>();
141 
142     @Option(
143         name = "enable-parameterized-modules",
144         description =
145                 "Whether or not to enable parameterized modules. This is a feature flag for work "
146                         + "in development."
147     )
148     private boolean mEnableParameter = false;
149 
150     @Option(
151         name = "module-parameter",
152         description =
153                 "Allows to run only one module parameter type instead of all the combinations. "
154                         + "For example: 'instant_app' would only run the instant_app version of "
155                         + "modules"
156     )
157     private ModuleParameters mForceParameter = null;
158 
159     @Option(
160         name = "exclude-module-parameters",
161         description =
162                 "Exclude some modules parameter from being evaluated in the run combinations."
163                         + "For example: 'instant_app' would exclude all the instant_app version of "
164                         + "modules."
165     )
166     private Set<ModuleParameters> mExcludedModuleParameters = new HashSet<>();
167 
168     private SuiteModuleLoader mModuleRepo;
169     private Map<String, List<SuiteTestFilter>> mIncludeFiltersParsed = new HashMap<>();
170     private Map<String, List<SuiteTestFilter>> mExcludeFiltersParsed = new HashMap<>();
171 
172     /** {@inheritDoc} */
173     @Override
loadTests()174     public LinkedHashMap<String, IConfiguration> loadTests() {
175         try {
176             File testsDir = getTestsDir();
177             setupFilters(testsDir);
178             Set<IAbi> abis = getAbis(getDevice());
179 
180             // Create and populate the filters here
181             SuiteModuleLoader.addFilters(mIncludeFilters, mIncludeFiltersParsed, abis);
182             SuiteModuleLoader.addFilters(mExcludeFilters, mExcludeFiltersParsed, abis);
183 
184             CLog.d(
185                     "Initializing ModuleRepo\nABIs:%s\n"
186                             + "Test Args:%s\nModule Args:%s\nIncludes:%s\nExcludes:%s",
187                     abis, mTestArgs, mModuleArgs, mIncludeFiltersParsed, mExcludeFiltersParsed);
188             mModuleRepo =
189                     createModuleLoader(
190                             mIncludeFiltersParsed, mExcludeFiltersParsed, mTestArgs, mModuleArgs);
191             mModuleRepo.setParameterizedModules(mEnableParameter);
192             mModuleRepo.setModuleParameter(mForceParameter);
193             mModuleRepo.setExcludedModuleParameters(mExcludedModuleParameters);
194 
195             List<File> testsDirectories = new ArrayList<>();
196 
197             // Include host or target first in the search if it exists, we have to this in
198             // BaseTestSuite because it's the only one with the BuildInfo knowledge of linked files
199             if (mPrioritizeHostConfig) {
200                 File hostSubDir = getBuildInfo().getFile(BuildInfoFileKey.HOST_LINKED_DIR);
201                 if (hostSubDir != null && hostSubDir.exists()) {
202                     testsDirectories.add(hostSubDir);
203                 }
204             } else {
205                 File targetSubDir = getBuildInfo().getFile(BuildInfoFileKey.TARGET_LINKED_DIR);
206                 if (targetSubDir != null && targetSubDir.exists()) {
207                     testsDirectories.add(targetSubDir);
208                 }
209             }
210 
211             // Finally add the full test cases directory in case there is no special sub-dir.
212             testsDirectories.add(testsDir);
213             // Actual loading of the configurations.
214             return loadingStrategy(abis, testsDirectories, mSuitePrefix, mSuiteTag);
215         } catch (DeviceNotAvailableException | FileNotFoundException e) {
216             throw new RuntimeException(e);
217         }
218     }
219 
220     /**
221      * Default loading strategy will load from the resources and the tests directory. Can be
222      * extended or replaced.
223      *
224      * @param abis The set of abis to run against.
225      * @param testsDirs The tests directory.
226      * @param suitePrefix A prefix to filter the resource directory.
227      * @param suiteTag The suite tag a module should have to be included. Can be null.
228      * @return A list of loaded configuration for the suite.
229      */
loadingStrategy( Set<IAbi> abis, List<File> testsDirs, String suitePrefix, String suiteTag)230     public LinkedHashMap<String, IConfiguration> loadingStrategy(
231             Set<IAbi> abis, List<File> testsDirs, String suitePrefix, String suiteTag) {
232         LinkedHashMap<String, IConfiguration> loadedConfigs = new LinkedHashMap<>();
233         // Load configs that are part of the resources
234         if (!mSkipJarLoading) {
235             loadedConfigs.putAll(
236                     getModuleLoader().loadConfigsFromJars(abis, suitePrefix, suiteTag));
237         }
238 
239         // Load the configs that are part of the tests dir
240         if (mConfigPatterns.isEmpty()) {
241             // If no special pattern was configured, use the default configuration patterns we know
242             mConfigPatterns.add(".*\\.config$");
243             mConfigPatterns.add(".*\\.xml$");
244         }
245 
246         loadedConfigs.putAll(
247                 getModuleLoader()
248                         .loadConfigsFromDirectory(
249                                 testsDirs, abis, suitePrefix, suiteTag, mConfigPatterns));
250         return loadedConfigs;
251     }
252 
getTestsDir()253     public File getTestsDir() throws FileNotFoundException {
254         IBuildInfo build = getBuildInfo();
255         if (build instanceof IDeviceBuildInfo) {
256             return ((IDeviceBuildInfo) build).getTestsDir();
257         }
258         // TODO: handle multi build?
259         throw new FileNotFoundException("Could not found a tests dir folder.");
260     }
261 
262     /** {@inheritDoc} */
263     @Override
setBuild(IBuildInfo buildInfo)264     public void setBuild(IBuildInfo buildInfo) {
265         super.setBuild(buildInfo);
266     }
267 
268     /** Sets include-filters for the compatibility test */
setIncludeFilter(Set<String> includeFilters)269     public void setIncludeFilter(Set<String> includeFilters) {
270         mIncludeFilters.addAll(includeFilters);
271     }
272 
273     /** Gets a copy of include-filters for the compatibility test */
getIncludeFilter()274     protected Set<String> getIncludeFilter() {
275         return new HashSet<String>(mIncludeFilters);
276     }
277 
278     /** Sets exclude-filters for the compatibility test */
setExcludeFilter(Set<String> excludeFilters)279     public void setExcludeFilter(Set<String> excludeFilters) {
280         mExcludeFilters.addAll(excludeFilters);
281     }
282 
283     /** Gets a copy of exclude-filters for the compatibility test */
getExcludeFilter()284     protected Set<String> getExcludeFilter() {
285         return new HashSet<String>(mExcludeFilters);
286     }
287 
288     /** Returns the current {@link SuiteModuleLoader}. */
getModuleLoader()289     public SuiteModuleLoader getModuleLoader() {
290         return mModuleRepo;
291     }
292 
293     /** Adds module args */
addModuleArgs(Set<String> moduleArgs)294     public void addModuleArgs(Set<String> moduleArgs) {
295         mModuleArgs.addAll(moduleArgs);
296     }
297 
298     /** Add config patterns */
addConfigPatterns(List<String> patterns)299     public void addConfigPatterns(List<String> patterns) {
300         mConfigPatterns.addAll(patterns);
301     }
302 
303     /**
304      * Create the {@link SuiteModuleLoader} responsible to load the {@link IConfiguration} and
305      * assign them some of the options.
306      *
307      * @param includeFiltersFormatted The formatted and parsed include filters.
308      * @param excludeFiltersFormatted The formatted and parsed exclude filters.
309      * @param testArgs the list of test ({@link IRemoteTest}) arguments.
310      * @param moduleArgs the list of module arguments.
311      * @return the created {@link SuiteModuleLoader}.
312      */
createModuleLoader( Map<String, List<SuiteTestFilter>> includeFiltersFormatted, Map<String, List<SuiteTestFilter>> excludeFiltersFormatted, List<String> testArgs, List<String> moduleArgs)313     public SuiteModuleLoader createModuleLoader(
314             Map<String, List<SuiteTestFilter>> includeFiltersFormatted,
315             Map<String, List<SuiteTestFilter>> excludeFiltersFormatted,
316             List<String> testArgs,
317             List<String> moduleArgs) {
318         return new SuiteModuleLoader(
319                 includeFiltersFormatted, excludeFiltersFormatted, testArgs, moduleArgs);
320     }
321 
322     /**
323      * Sets the include/exclude filters up based on if a module name was given.
324      *
325      * @throws FileNotFoundException if any file is not found.
326      */
setupFilters(File testsDir)327     protected void setupFilters(File testsDir) throws FileNotFoundException {
328         if (mModuleName != null) {
329             // If this option (-m / --module) is set only the matching unique module should run.
330             Set<File> modules =
331                     SuiteModuleLoader.getModuleNamesMatching(
332                             testsDir, mSuitePrefix, String.format(".*%s.*.config", mModuleName));
333             // If multiple modules match, do exact match.
334             if (modules.size() > 1) {
335                 Set<File> newModules = new HashSet<>();
336                 String exactModuleName = String.format("%s.config", mModuleName);
337                 for (File module : modules) {
338                     if (module.getName().equals(exactModuleName)) {
339                         newModules.add(module);
340                         modules = newModules;
341                         break;
342                     }
343                 }
344             }
345             if (modules.size() == 0) {
346                 throw new IllegalArgumentException(
347                         String.format("No modules found matching %s", mModuleName));
348             } else if (modules.size() > 1) {
349                 throw new IllegalArgumentException(
350                         String.format(
351                                 "Multiple modules found matching %s:\n%s\nWhich one did you "
352                                         + "mean?\n",
353                                 mModuleName, ArrayUtil.join("\n", modules)));
354             } else {
355                 File mod = modules.iterator().next();
356                 String moduleName = mod.getName().replace(".config", "");
357                 checkFilters(mIncludeFilters, moduleName);
358                 checkFilters(mExcludeFilters, moduleName);
359                 mIncludeFilters.add(
360                         new SuiteTestFilter(getRequestedAbi(), moduleName, mTestName).toString());
361             }
362         } else if (mTestName != null) {
363             throw new IllegalArgumentException(
364                     "Test name given without module name. Add --module <module-name>");
365         }
366     }
367 
368     @Override
cleanUpSuiteSetup()369     void cleanUpSuiteSetup() {
370         super.cleanUpSuiteSetup();
371         // Clean the filters because at that point they have been applied to the runners.
372         // This can save several GB of memories during sharding.
373         mIncludeFilters.clear();
374         mExcludeFilters.clear();
375         mIncludeFiltersParsed.clear();
376         mExcludeFiltersParsed.clear();
377     }
378 
379     /* Helper method designed to remove filters in a list not applicable to the given module */
checkFilters(Set<String> filters, String moduleName)380     private static void checkFilters(Set<String> filters, String moduleName) {
381         Set<String> cleanedFilters = new HashSet<String>();
382         for (String filter : filters) {
383             SuiteTestFilter filterObject = SuiteTestFilter.createFrom(filter);
384             String filterName = filterObject.getName();
385             String filterBaseName = filterObject.getBaseName();
386             if (moduleName.equals(filterName) || moduleName.equals(filterBaseName)) {
387                 cleanedFilters.add(filter); // Module name matches, filter passes
388             }
389         }
390         filters.clear();
391         filters.addAll(cleanedFilters);
392     }
393 
394     /* Return a {@link boolean} for the setting of prioritize-host-config.*/
getPrioritizeHostConfig()395     boolean getPrioritizeHostConfig() {
396         return mPrioritizeHostConfig;
397     }
398 
399     /**
400      * Set option prioritize-host-config.
401      *
402      * @param prioritizeHostConfig true to prioritize host config, i.e., run host test if possible.
403      */
404     @VisibleForTesting
setPrioritizeHostConfig(boolean prioritizeHostConfig)405     protected void setPrioritizeHostConfig(boolean prioritizeHostConfig) {
406         mPrioritizeHostConfig = prioritizeHostConfig;
407     }
408 }
409