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.annotations.VisibleForTesting;
19 import com.android.tradefed.config.ConfigurationDescriptor;
20 import com.android.tradefed.config.ConfigurationException;
21 import com.android.tradefed.config.ConfigurationFactory;
22 import com.android.tradefed.config.ConfigurationUtil;
23 import com.android.tradefed.config.IConfiguration;
24 import com.android.tradefed.config.IConfigurationFactory;
25 import com.android.tradefed.config.IDeviceConfiguration;
26 import com.android.tradefed.config.OptionDef;
27 import com.android.tradefed.config.OptionSetter;
28 import com.android.tradefed.device.DeviceFoldableState;
29 import com.android.tradefed.error.HarnessRuntimeException;
30 import com.android.tradefed.invoker.IInvocationContext;
31 import com.android.tradefed.log.LogUtil.CLog;
32 import com.android.tradefed.result.error.InfraErrorIdentifier;
33 import com.android.tradefed.targetprep.ITargetPreparer;
34 import com.android.tradefed.testtype.IAbi;
35 import com.android.tradefed.testtype.IAbiReceiver;
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.MainlineModuleHandler;
42 import com.android.tradefed.testtype.suite.params.ModuleParameters;
43 import com.android.tradefed.testtype.suite.params.ModuleParametersHelper;
44 import com.android.tradefed.testtype.suite.params.NegativeHandler;
45 import com.android.tradefed.testtype.suite.params.NotMultiAbiHandler;
46 import com.android.tradefed.util.AbiUtils;
47 import com.android.tradefed.util.FileUtil;
48 import com.android.tradefed.util.StreamUtil;
49 
50 import com.google.common.base.Strings;
51 import com.google.common.net.UrlEscapers;
52 
53 import java.io.File;
54 import java.io.FilenameFilter;
55 import java.io.IOException;
56 import java.io.PrintWriter;
57 import java.util.ArrayList;
58 import java.util.Arrays;
59 import java.util.Collection;
60 import java.util.Collections;
61 import java.util.HashMap;
62 import java.util.HashSet;
63 import java.util.LinkedHashMap;
64 import java.util.LinkedHashSet;
65 import java.util.List;
66 import java.util.Map;
67 import java.util.Map.Entry;
68 import java.util.Set;
69 import java.util.regex.Matcher;
70 import java.util.regex.Pattern;
71 import java.util.stream.Collectors;
72 
73 /**
74  * Retrieves Compatibility test module definitions from the repository. TODO: Add the expansion of
75  * suite when loading a module.
76  */
77 public class SuiteModuleLoader {
78 
79     public static final String CONFIG_EXT = ".config";
80     private Map<String, List<OptionDef>> mTestOrPreparerOptions = new HashMap<>();
81     private Map<String, List<OptionDef>> mModuleOptions = new HashMap<>();
82     private boolean mIncludeAll;
83     private Map<String, LinkedHashSet<SuiteTestFilter>> mIncludeFilters = new HashMap<>();
84     private Map<String, LinkedHashSet<SuiteTestFilter>> mExcludeFilters = new HashMap<>();
85     private IConfigurationFactory mConfigFactory = ConfigurationFactory.getInstance();
86     private IInvocationContext mContext;
87 
88     private boolean mAllowParameterizedModules = false;
89     private boolean mAllowMainlineParameterizedModules = false;
90     private boolean mOptimizeMainlineTest = false;
91     private boolean mIgnoreNonPreloadedMainlineModule = false;
92     private boolean mAllowOptionalParameterizedModules = false;
93     private boolean mLoadConfigsWithIncludeFilters = false;
94     private ModuleParameters mForcedModuleParameter = null;
95     private Set<ModuleParameters> mExcludedModuleParameters = new HashSet<>();
96     private Set<DeviceFoldableState> mFoldableStates = new LinkedHashSet<>();
97     // Check the mainline parameter configured in a test config must end with .apk, .apks, or .apex.
98     private static final Set<String> MAINLINE_PARAMETERS_TO_VALIDATE =
99             new HashSet<>(Arrays.asList(".apk", ".apks", ".apex"));
100 
101     /**
102      * Ctor for the SuiteModuleLoader.
103      *
104      * @param includeFilters The formatted and parsed include filters.
105      * @param excludeFilters The formatted and parsed exclude filters.
106      * @param testArgs the list of test ({@link IRemoteTest}) arguments.
107      * @param moduleArgs the list of module arguments.
108      */
SuiteModuleLoader( Map<String, LinkedHashSet<SuiteTestFilter>> includeFilters, Map<String, LinkedHashSet<SuiteTestFilter>> excludeFilters, List<String> testArgs, List<String> moduleArgs)109     public SuiteModuleLoader(
110             Map<String, LinkedHashSet<SuiteTestFilter>> includeFilters,
111             Map<String, LinkedHashSet<SuiteTestFilter>> excludeFilters,
112             List<String> testArgs,
113             List<String> moduleArgs) {
114         mIncludeAll = includeFilters.isEmpty();
115         mIncludeFilters = includeFilters;
116         mExcludeFilters = excludeFilters;
117 
118         parseArgs(testArgs, mTestOrPreparerOptions);
119         parseArgs(moduleArgs, mModuleOptions);
120     }
121 
setInvocationContext(IInvocationContext context)122     public final void setInvocationContext(IInvocationContext context) {
123         mContext = context;
124     }
125 
126     /** Sets whether or not to allow parameterized modules. */
setParameterizedModules(boolean allowed)127     public final void setParameterizedModules(boolean allowed) {
128         mAllowParameterizedModules = allowed;
129     }
130 
131     /** Sets whether or not to allow parameterized mainline modules. */
setMainlineParameterizedModules(boolean allowed)132     public final void setMainlineParameterizedModules(boolean allowed) {
133         mAllowMainlineParameterizedModules = allowed;
134     }
135 
136     /** Sets whether or not to optimize mainline test. */
setOptimizeMainlineTest(boolean allowed)137     public final void setOptimizeMainlineTest(boolean allowed) {
138         mOptimizeMainlineTest = allowed;
139     }
140 
141     /** Sets whether or not to ignore installing the module if it is not preloaded. */
setIgnoreNonPreloadedMainlineModule(boolean ignore)142     public final void setIgnoreNonPreloadedMainlineModule(boolean ignore) {
143         mIgnoreNonPreloadedMainlineModule = ignore;
144     }
145 
146     /** Sets whether or not to allow optional parameterized modules. */
setOptionalParameterizedModules(boolean allowed)147     public final void setOptionalParameterizedModules(boolean allowed) {
148         mAllowOptionalParameterizedModules = allowed;
149     }
150 
151     /** Sets whether or not to load test config based on the given include-filter. */
setLoadConfigsWithIncludeFilters(boolean allowed)152     public final void setLoadConfigsWithIncludeFilters(boolean allowed) {
153         mLoadConfigsWithIncludeFilters = allowed;
154     }
155 
156     /** Sets the only {@link ModuleParameters} type that should be run. */
setModuleParameter(ModuleParameters param)157     public final void setModuleParameter(ModuleParameters param) {
158         mForcedModuleParameter = param;
159     }
160 
161     /** Sets the set of {@link ModuleParameters} that should not be considered at all. */
setExcludedModuleParameters(Set<ModuleParameters> excludedParams)162     public final void setExcludedModuleParameters(Set<ModuleParameters> excludedParams) {
163         mExcludedModuleParameters = excludedParams;
164     }
165 
166     /** Sets the set of {@link DeviceFoldableState} that should be run. */
setFoldableStates(Set<DeviceFoldableState> foldableStates)167     public final void setFoldableStates(Set<DeviceFoldableState> foldableStates) {
168         mFoldableStates = foldableStates;
169     }
170 
171     /** Main loading of configurations, looking into the specified files */
loadConfigsFromSpecifiedPaths( List<File> listConfigFiles, Set<IAbi> abis, String suiteTag)172     public LinkedHashMap<String, IConfiguration> loadConfigsFromSpecifiedPaths(
173             List<File> listConfigFiles, Set<IAbi> abis, String suiteTag) {
174         LinkedHashMap<String, IConfiguration> toRun = new LinkedHashMap<>();
175         for (File configFile : listConfigFiles) {
176             toRun.putAll(
177                     loadOneConfig(
178                             configFile.getName(), configFile.getAbsolutePath(), abis, suiteTag));
179         }
180         return toRun;
181     }
182 
183     /** Main loading of configurations, looking into a folder */
loadConfigsFromDirectory( List<File> testsDirs, Set<IAbi> abis, String suitePrefix, String suiteTag, List<String> patterns)184     public LinkedHashMap<String, IConfiguration> loadConfigsFromDirectory(
185             List<File> testsDirs,
186             Set<IAbi> abis,
187             String suitePrefix,
188             String suiteTag,
189             List<String> patterns) {
190         LinkedHashMap<String, IConfiguration> toRun = new LinkedHashMap<>();
191         List<File> listConfigFiles = new ArrayList<>();
192         listConfigFiles.addAll(
193                 ConfigurationUtil.getConfigNamesFileFromDirs(suitePrefix, testsDirs, patterns));
194         if (mLoadConfigsWithIncludeFilters && !mIncludeFilters.isEmpty()) {
195             CLog.i("Loading test configs based on the given include-filter.");
196             Set<String> filteredConfigNames = new HashSet<>();
197             for (LinkedHashSet<SuiteTestFilter> entry : mIncludeFilters.values()) {
198                 for (SuiteTestFilter file : entry) {
199                     // Collect the test config name based on the given include filter.
200                     filteredConfigNames.add(String.format("%s.config", file.getBaseName()));
201                 }
202             }
203             // Filter the test configs out based on the collected test config names.
204             List<File> filteredConfigs =
205                     listConfigFiles.stream()
206                             .filter(f -> filteredConfigNames.contains(f.getName()))
207                             .collect(Collectors.toList());
208             listConfigFiles.clear();
209             listConfigFiles.addAll(filteredConfigs);
210         }
211         // Ensure stable initial order of configurations.
212         Collections.sort(listConfigFiles);
213         toRun.putAll(loadConfigsFromSpecifiedPaths(listConfigFiles, abis, suiteTag));
214         return toRun;
215     }
216 
217     /**
218      * Main loading of configurations, looking into the resources on the classpath. (TF configs for
219      * example).
220      */
loadConfigsFromJars( Set<IAbi> abis, String suitePrefix, String suiteTag)221     public LinkedHashMap<String, IConfiguration> loadConfigsFromJars(
222             Set<IAbi> abis, String suitePrefix, String suiteTag) {
223         LinkedHashMap<String, IConfiguration> toRun = new LinkedHashMap<>();
224 
225         IConfigurationFactory configFactory = ConfigurationFactory.getInstance();
226         List<String> configs = configFactory.getConfigList(suitePrefix, false);
227         // Sort configs to ensure they are always evaluated and added in the same order.
228         Collections.sort(configs);
229         toRun.putAll(loadTfConfigsFromSpecifiedPaths(configs, abis, suiteTag));
230         return toRun;
231     }
232 
233     /** Main loading of configurations, looking into the specified resources on the classpath. */
loadTfConfigsFromSpecifiedPaths( List<String> configs, Set<IAbi> abis, String suiteTag)234     public LinkedHashMap<String, IConfiguration> loadTfConfigsFromSpecifiedPaths(
235             List<String> configs, Set<IAbi> abis, String suiteTag) {
236         LinkedHashMap<String, IConfiguration> toRun = new LinkedHashMap<>();
237         for (String configName : configs) {
238             toRun.putAll(loadOneConfig(configName, configName, abis, suiteTag));
239         }
240         return toRun;
241     }
242 
243     /**
244      * Pass the filters to the {@link IRemoteTest}. Default behavior is to ignore if the IRemoteTest
245      * does not implements {@link ITestFileFilterReceiver}. This can be overriden to create a more
246      * restrictive behavior.
247      *
248      * @param test The {@link IRemoteTest} that is being considered.
249      * @param abi The Abi we are currently working on.
250      * @param moduleId The id of the module (usually abi + module name).
251      * @param includeFilters The formatted and parsed include filters.
252      * @param excludeFilters The formatted and parsed exclude filters.
253      */
addFiltersToTest( IRemoteTest test, IAbi abi, String moduleId, Map<String, LinkedHashSet<SuiteTestFilter>> includeFilters, Map<String, LinkedHashSet<SuiteTestFilter>> excludeFilters)254     public void addFiltersToTest(
255             IRemoteTest test,
256             IAbi abi,
257             String moduleId,
258             Map<String, LinkedHashSet<SuiteTestFilter>> includeFilters,
259             Map<String, LinkedHashSet<SuiteTestFilter>> excludeFilters) {
260 
261         if (!(test instanceof ITestFilterReceiver)) {
262             CLog.e(
263                     "Test (%s) in module %s does not implement ITestFilterReceiver.",
264                     test.getClass().getName(), moduleId);
265             return;
266         }
267         LinkedHashSet<SuiteTestFilter> mdIncludes = getFilterList(includeFilters, moduleId);
268         LinkedHashSet<SuiteTestFilter> mdExcludes = getFilterList(excludeFilters, moduleId);
269         if (!mdIncludes.isEmpty()) {
270             addTestIncludes((ITestFilterReceiver) test, mdIncludes, moduleId);
271         }
272         if (!mdExcludes.isEmpty()) {
273             addTestExcludes((ITestFilterReceiver) test, mdExcludes, moduleId);
274         }
275     }
276 
277     /**
278      * Load a single config location (file or on TF classpath). It can results in several {@link
279      * IConfiguration}. If a single configuration get expanded in different ways.
280      *
281      * @param configName The actual config name only. (no path)
282      * @param configFullName The fully qualified config name. (with path, if any).
283      * @param abis The set of all abis that needs to run.
284      * @param suiteTag the Tag of the suite aimed to be run.
285      * @return A map of loaded configuration.
286      */
loadOneConfig( String configName, String configFullName, Set<IAbi> abis, String suiteTag)287     private LinkedHashMap<String, IConfiguration> loadOneConfig(
288             String configName, String configFullName, Set<IAbi> abis, String suiteTag) {
289         LinkedHashMap<String, IConfiguration> toRun = new LinkedHashMap<>();
290         final String name = configName.replace(CONFIG_EXT, "");
291         final String[] pathArg = new String[] {configFullName};
292         try {
293             boolean primaryAbi = true;
294             boolean shouldCreateMultiAbi = true;
295             // If a particular parameter was requested to be run, find it.
296             Set<IModuleParameterHandler> mForcedParameters = null;
297             Set<Class<?>> mForcedParameterClasses = null;
298             if (mForcedModuleParameter != null) {
299                 mForcedParameters = new HashSet<>();
300                 Map<ModuleParameters, IModuleParameterHandler> moduleParameters =
301                         ModuleParametersHelper.resolveParam(
302                                 mForcedModuleParameter, mAllowOptionalParameterizedModules);
303                 mForcedParameterClasses = new HashSet<>();
304                 for (IModuleParameterHandler parameter : moduleParameters.values()) {
305                     if (parameter instanceof FoldableExpandingHandler) {
306                         for (IModuleParameterHandler fParam :
307                                 ((FoldableExpandingHandler) parameter)
308                                         .expandHandler(mFoldableStates)) {
309                             mForcedParameterClasses.add(fParam.getClass());
310                         }
311                     } else {
312                         mForcedParameterClasses.add(parameter.getClass());
313                     }
314                 }
315             }
316 
317             // Invokes parser to process the test module config file
318             // Need to generate a different config for each ABI as we cannot guarantee the
319             // configs are idempotent. This however means we parse the same file multiple times
320             for (IAbi abi : abis) {
321                 // Filter non-primary abi no matter what if not_multi_abi specified
322                 if (!shouldCreateMultiAbi && !primaryAbi) {
323                     continue;
324                 }
325                 String baseId = AbiUtils.createId(abi.getName(), name);
326                 IConfiguration config = null;
327                 try {
328                     config = mConfigFactory.createConfigurationFromArgs(pathArg);
329                 } catch (ConfigurationException e) {
330                     // If the module should not have been running in the first place, give it a
331                     // pass on the configuration failure.
332                     if (!shouldRunModule(baseId)) {
333                         primaryAbi = false;
334                         // If the module should not run tests based on the state of filters,
335                         // skip this name/abi combination.
336                         continue;
337                     }
338                     throw e;
339                 }
340 
341                 // If a suiteTag is used, we load with it.
342                 if (!Strings.isNullOrEmpty(suiteTag)
343                         && !config.getConfigurationDescription()
344                                 .getSuiteTags()
345                                 .contains(suiteTag)) {
346                     // Do not print here, it could leave several hundred lines of logs.
347                     continue;
348                 }
349 
350                 boolean skipCreatingBaseConfig = false;
351                 List<IModuleParameterHandler> params = null;
352                 List<String> mainlineParams = new ArrayList<>();
353                 try {
354                     params = getModuleParameters(name, config);
355                     mainlineParams = getMainlineModuleParameters(config);
356                 } catch (ConfigurationException e) {
357                     // If the module should not have been running in the first place, give it a
358                     // pass on the configuration failure.
359                     if (!shouldRunModule(baseId)) {
360                         primaryAbi = false;
361                         // If the module should not run tests based on the state of filters,
362                         // skip this name/abi combination.
363                         continue;
364                     }
365                     throw e;
366                 }
367                 // Use the not_multi_abi metadata even if not in parameterized mode.
368                 shouldCreateMultiAbi = shouldCreateMultiAbiForBase(params);
369                 // Handle parameterized modules if enabled.
370                 if (mAllowParameterizedModules) {
371 
372                     if (params.isEmpty()
373                             && mForcedParameters != null
374                             // If we have multiple forced parameters, NegativeHandler isn't a valid
375                             // option
376                             && !(mForcedParameters.size() != 1
377                                     || (mForcedParameters.iterator().next()
378                                             instanceof NegativeHandler))) {
379                         // If the AndroidTest.xml doesn't specify any parameter but we forced a
380                         // parameter like 'instant' to execute. In this case we don't create the
381                         // standard module.
382                         continue;
383                     }
384 
385                     // If we find any parameterized combination.
386                     for (IModuleParameterHandler param : params) {
387                         if (param instanceof NegativeHandler) {
388                             if (mForcedParameters != null
389                                     && !mForcedParameterClasses.contains(param.getClass())) {
390                                 skipCreatingBaseConfig = true;
391                             }
392                             continue;
393                         }
394                         if (mForcedParameters != null) {
395                             // When a particular parameter is forced, only create it not the others
396                             if (mForcedParameterClasses.contains(param.getClass())) {
397                                 skipCreatingBaseConfig = true;
398                             } else {
399                                 continue;
400                             }
401                         }
402                         // Only create primary abi of parameterized modules
403                         if (!primaryAbi) {
404                             continue;
405                         }
406                         String fullId =
407                                 String.format("%s[%s]", baseId, param.getParameterIdentifier());
408                         String nameWithParam =
409                                 String.format("%s[%s]", name, param.getParameterIdentifier());
410                         if (shouldRunParameterized(
411                                 baseId, fullId, nameWithParam, mForcedParameters)) {
412                             IConfiguration paramConfig =
413                                     mConfigFactory.createConfigurationFromArgs(pathArg);
414                             // Mark the parameter in the metadata
415                             paramConfig
416                                     .getConfigurationDescription()
417                                     .addMetadata(
418                                             ConfigurationDescriptor.ACTIVE_PARAMETER_KEY,
419                                             param.getParameterIdentifier());
420                             param.addParameterSpecificConfig(paramConfig);
421                             setUpConfig(name, nameWithParam, baseId, fullId, paramConfig, abi);
422                             param.applySetup(paramConfig);
423                             toRun.put(fullId, paramConfig);
424                         }
425                     }
426                 }
427 
428                 if (mAllowMainlineParameterizedModules) {
429                     // If no options defined in a test config, skip generating.
430                     // TODO(easoncylee) This is still under discussion.
431                     if (mainlineParams.isEmpty()) {
432                         continue;
433                     }
434                     // If we find any parameterized combination for mainline modules.
435                     for (String param : mainlineParams) {
436                         String fullId = String.format("%s[%s]", baseId, param);
437                         String nameWithParam = String.format("%s[%s]", name, param);
438                         if (!shouldRunParameterized(baseId, fullId, nameWithParam, null)) {
439                             continue;
440                         }
441                         // Create mainline handler for each defined mainline parameter.
442                         MainlineModuleHandler handler =
443                                 new MainlineModuleHandler(
444                                         param,
445                                         abi,
446                                         mContext,
447                                         mOptimizeMainlineTest,
448                                         mIgnoreNonPreloadedMainlineModule);
449                         skipCreatingBaseConfig = true;
450                         IConfiguration paramConfig =
451                                 mConfigFactory.createConfigurationFromArgs(pathArg);
452                         paramConfig
453                                 .getConfigurationDescription()
454                                 .addMetadata(ITestSuite.ACTIVE_MAINLINE_PARAMETER_KEY, param);
455                         setUpConfig(name, nameWithParam, baseId, fullId, paramConfig, abi);
456                         handler.applySetup(paramConfig);
457                         toRun.put(fullId, paramConfig);
458                     }
459                 }
460 
461                 primaryAbi = false;
462                 // If a parameterized form of the module was forced, we don't create the standard
463                 // version of it.
464                 if (skipCreatingBaseConfig) {
465                     continue;
466                 }
467                 if (shouldRunModule(baseId)) {
468                     // Always add the base regular configuration to the execution.
469                     // Do not pass the nameWithParam in because it would cause the module args be
470                     // injected into config twice if we pass nameWithParam using name.
471                     setUpConfig(name, null, baseId, baseId, config, abi);
472                     toRun.put(baseId, config);
473                 }
474             }
475         } catch (ConfigurationException e) {
476             throw new HarnessRuntimeException(
477                     String.format(
478                             "Error parsing configuration: %s: '%s'",
479                             configFullName, e.getMessage()),
480                     e);
481         }
482 
483         return toRun;
484     }
485 
486     /**
487      * @return the {@link Set} of modules whose name contains the given pattern.
488      */
getModuleNamesMatching( File directory, String suitePrefix, String pattern)489     public static Set<File> getModuleNamesMatching(
490             File directory, String suitePrefix, String pattern) {
491         List<File> extraTestCasesDirs = Arrays.asList(directory);
492         List<String> patterns = new ArrayList<>();
493         patterns.add(pattern);
494         Set<File> modules =
495                 ConfigurationUtil.getConfigNamesFileFromDirs(
496                         suitePrefix, extraTestCasesDirs, patterns);
497         return modules;
498     }
499 
500     /**
501      * Utility method that allows to parse and create a structure with the option filters.
502      *
503      * @param stringFilters The original option filters format.
504      * @param filters The filters parsed from the string format.
505      * @param abis The Abis to consider in the filtering.
506      */
addFilters( Set<String> stringFilters, Map<String, LinkedHashSet<SuiteTestFilter>> filters, Set<IAbi> abis, Set<DeviceFoldableState> foldableStates)507     public static void addFilters(
508             Set<String> stringFilters,
509             Map<String, LinkedHashSet<SuiteTestFilter>> filters,
510             Set<IAbi> abis,
511             Set<DeviceFoldableState> foldableStates) {
512         for (String filterString : stringFilters) {
513             SuiteTestFilter parentFilter = SuiteTestFilter.createFrom(filterString);
514             List<SuiteTestFilter> expanded = expandFoldableFilters(parentFilter, foldableStates);
515             for (SuiteTestFilter filter : expanded) {
516                 String abi = filter.getAbi();
517                 if (abi == null) {
518                     for (IAbi a : abis) {
519                         addFilter(a.getName(), filter, filters);
520                     }
521                 } else {
522                     addFilter(abi, filter, filters);
523                 }
524             }
525         }
526     }
527 
expandFoldableFilters( SuiteTestFilter filter, Set<DeviceFoldableState> foldableStates)528     private static List<SuiteTestFilter> expandFoldableFilters(
529             SuiteTestFilter filter, Set<DeviceFoldableState> foldableStates) {
530         List<SuiteTestFilter> expandedFilters = new ArrayList<>();
531         if (foldableStates == null || foldableStates.isEmpty()) {
532             expandedFilters.add(filter);
533             return expandedFilters;
534         }
535         if (!ModuleParameters.ALL_FOLDABLE_STATES.toString().equals(filter.getParameterName())) {
536             expandedFilters.add(filter);
537             return expandedFilters;
538         }
539         for (DeviceFoldableState state : foldableStates) {
540             String name = filter.getBaseName() + "[" + state.toString() + "]";
541             expandedFilters.add(
542                     new SuiteTestFilter(
543                             filter.getShardIndex(), filter.getAbi(), name, filter.getTest()));
544         }
545         return expandedFilters;
546     }
547 
addFilter( String abi, SuiteTestFilter filter, Map<String, LinkedHashSet<SuiteTestFilter>> filters)548     private static void addFilter(
549             String abi,
550             SuiteTestFilter filter,
551             Map<String, LinkedHashSet<SuiteTestFilter>> filters) {
552         getFilterList(filters, AbiUtils.createId(abi, filter.getName())).add(filter);
553     }
554 
getFilterList( Map<String, LinkedHashSet<SuiteTestFilter>> filters, String id)555     private static LinkedHashSet<SuiteTestFilter> getFilterList(
556             Map<String, LinkedHashSet<SuiteTestFilter>> filters, String id) {
557         LinkedHashSet<SuiteTestFilter> fs = filters.get(id);
558         if (fs == null) {
559             fs = new LinkedHashSet<>();
560             filters.put(id, fs);
561         }
562         return fs;
563     }
564 
shouldRunModule(String moduleId)565     private boolean shouldRunModule(String moduleId) {
566         LinkedHashSet<SuiteTestFilter> mdIncludes = getFilterList(mIncludeFilters, moduleId);
567         LinkedHashSet<SuiteTestFilter> mdExcludes = getFilterList(mExcludeFilters, moduleId);
568         // if including all modules or includes exist for this module, and there are not excludes
569         // for the entire module, this module should be run.
570         return (mIncludeAll || !mdIncludes.isEmpty()) && !containsModuleExclude(mdExcludes);
571     }
572 
573     /**
574      * Except if the parameterized module is explicitly excluded, including the base module result
575      * in including its parameterization variant.
576      */
shouldRunParameterized( String baseModuleId, String parameterModuleId, String nameWithParam, Set<IModuleParameterHandler> forcedModuleParameters)577     private boolean shouldRunParameterized(
578             String baseModuleId,
579             String parameterModuleId,
580             String nameWithParam,
581             Set<IModuleParameterHandler> forcedModuleParameters) {
582         // Explicitly excluded
583         LinkedHashSet<SuiteTestFilter> excluded = getFilterList(mExcludeFilters, parameterModuleId);
584         LinkedHashSet<SuiteTestFilter> excludedParam =
585                 getFilterList(mExcludeFilters, nameWithParam);
586         if (containsModuleExclude(excluded) || containsModuleExclude(excludedParam)) {
587             return false;
588         }
589 
590         // Implicitly included due to forced parameter
591         if (forcedModuleParameters != null) {
592             LinkedHashSet<SuiteTestFilter> baseInclude =
593                     getFilterList(mIncludeFilters, baseModuleId);
594             if (!baseInclude.isEmpty()) {
595                 return true;
596             }
597         }
598         // Explicitly included
599         LinkedHashSet<SuiteTestFilter> included = getFilterList(mIncludeFilters, parameterModuleId);
600         LinkedHashSet<SuiteTestFilter> includedParam =
601                 getFilterList(mIncludeFilters, nameWithParam);
602         if (mIncludeAll || !included.isEmpty() || !includedParam.isEmpty()) {
603             return true;
604         }
605         return false;
606     }
607 
addTestIncludes( ITestFilterReceiver test, Collection<SuiteTestFilter> includes, String moduleId)608     private void addTestIncludes(
609             ITestFilterReceiver test, Collection<SuiteTestFilter> includes, String moduleId) {
610         if (test instanceof ITestFileFilterReceiver) {
611             String escapedFileName = escapeFilterFileName(moduleId);
612             File includeFile = createFilterFile(escapedFileName, ".include", includes);
613             ((ITestFileFilterReceiver) test).setIncludeTestFile(includeFile);
614         } else {
615             // add test includes one at a time
616             for (SuiteTestFilter include : includes) {
617                 String filterTestName = include.getTest();
618                 if (filterTestName != null) {
619                     test.addIncludeFilter(filterTestName);
620                 }
621             }
622         }
623     }
624 
addTestExcludes( ITestFilterReceiver test, Collection<SuiteTestFilter> excludes, String moduleId)625     private void addTestExcludes(
626             ITestFilterReceiver test, Collection<SuiteTestFilter> excludes, String moduleId) {
627         if (test instanceof ITestFileFilterReceiver) {
628             String escapedFileName = escapeFilterFileName(moduleId);
629             File excludeFile = createFilterFile(escapedFileName, ".exclude", excludes);
630             ((ITestFileFilterReceiver) test).setExcludeTestFile(excludeFile);
631         } else {
632             // add test excludes one at a time
633             for (SuiteTestFilter exclude : excludes) {
634                 test.addExcludeFilter(exclude.getTest());
635             }
636         }
637     }
638 
639     /** module id can contain special characters, avoid them for file names. */
escapeFilterFileName(String moduleId)640     private String escapeFilterFileName(String moduleId) {
641         String escaped = UrlEscapers.urlPathSegmentEscaper().escape(moduleId);
642         return escaped;
643     }
644 
createFilterFile( String prefix, String suffix, Collection<SuiteTestFilter> filters)645     private File createFilterFile(
646             String prefix, String suffix, Collection<SuiteTestFilter> filters) {
647         File filterFile = null;
648         PrintWriter out = null;
649         try {
650             filterFile = FileUtil.createTempFile(prefix, suffix);
651             out = new PrintWriter(filterFile);
652             for (SuiteTestFilter filter : filters) {
653                 String filterTest = filter.getTest();
654                 if (filterTest != null) {
655                     out.println(filterTest);
656                 }
657             }
658             out.flush();
659         } catch (IOException e) {
660             throw new HarnessRuntimeException(
661                     "Failed to create filter file", e, InfraErrorIdentifier.FAIL_TO_CREATE_FILE);
662         } finally {
663             StreamUtil.close(out);
664         }
665         filterFile.deleteOnExit();
666         return filterFile;
667     }
668 
669     /** Returns true iff one or more test filters in excludes apply to the entire module. */
containsModuleExclude(Collection<SuiteTestFilter> excludes)670     private boolean containsModuleExclude(Collection<SuiteTestFilter> excludes) {
671         for (SuiteTestFilter exclude : excludes) {
672             if (exclude.getTest() == null) {
673                 return true;
674             }
675         }
676         return false;
677     }
678 
679     /** A {@link FilenameFilter} to find all the config files in a directory. */
680     public static class ConfigFilter implements FilenameFilter {
681 
682         /** {@inheritDoc} */
683         @Override
accept(File dir, String name)684         public boolean accept(File dir, String name) {
685             return name.endsWith(CONFIG_EXT);
686         }
687     }
688 
689     /**
690      * Parse a list of args formatted as expected into {@link OptionDef} to be injected to module
691      * configurations.
692      *
693      * <p>Format: <module name / module id / class runner>:<option name>:[<arg-key>:=]<arg-value>
694      */
parseArgs(List<String> args, Map<String, List<OptionDef>> moduleOptions)695     private void parseArgs(List<String> args, Map<String, List<OptionDef>> moduleOptions) {
696         for (String arg : args) {
697             int moduleSep = arg.indexOf(":");
698             if (moduleSep == -1) {
699                 throw new RuntimeException("Expected delimiter ':' for module or class.");
700             }
701             String moduleName = arg.substring(0, moduleSep);
702             String remainder = arg.substring(moduleSep + 1);
703             List<OptionDef> listOption = moduleOptions.get(moduleName);
704             if (listOption == null) {
705                 listOption = new ArrayList<>();
706                 moduleOptions.put(moduleName, listOption);
707             }
708             int optionNameSep = remainder.indexOf(":");
709             if (optionNameSep == -1) {
710                 throw new RuntimeException(
711                         "Expected delimiter ':' between option name and values.");
712             }
713             String optionName = remainder.substring(0, optionNameSep);
714             Pattern pattern = Pattern.compile("\\{(.*)\\}(.*)");
715             Matcher match = pattern.matcher(optionName);
716             if (match.find()) {
717                 String alias = match.group(1);
718                 String name = match.group(2);
719                 optionName = alias + ":" + name;
720             }
721             String optionValueString = remainder.substring(optionNameSep + 1);
722             // TODO: See if QuotationTokenizer can be improved for multi-character delimiter.
723             // or change the delimiter to a single char.
724             String[] tokens = optionValueString.split(":=", 2);
725             OptionDef option = null;
726             if (tokens.length == 1) {
727                 option = new OptionDef(optionName, tokens[0], moduleName);
728             } else if (tokens.length == 2) {
729                 option = new OptionDef(optionName, tokens[0], tokens[1], moduleName);
730             }
731             listOption.add(option);
732         }
733     }
734 
735     /** Gets the list of {@link IModuleParameterHandler}s associated with a module. */
getModuleParameters( String moduleName, IConfiguration config)736     private List<IModuleParameterHandler> getModuleParameters(
737             String moduleName, IConfiguration config) throws ConfigurationException {
738         List<IModuleParameterHandler> params = new ArrayList<>();
739         Set<String> processedParameterArgs = new HashSet<>();
740         // Track family of the parameters to make sure we have no duplicate.
741         Map<String, ModuleParameters> duplicateModule = new LinkedHashMap<>();
742 
743         List<String> parameters =
744                 config.getConfigurationDescription().getMetaData(ITestSuite.PARAMETER_KEY);
745         if (parameters == null || parameters.isEmpty()) {
746             return params;
747         }
748 
749         Set<ModuleParameters> expandedExcludedModuleParameters = new HashSet<>();
750         for (ModuleParameters moduleParameters : mExcludedModuleParameters) {
751             expandedExcludedModuleParameters.addAll(
752                     ModuleParametersHelper.resolveParam(
753                                     moduleParameters, mAllowOptionalParameterizedModules)
754                             .keySet());
755         }
756 
757         for (String p : parameters) {
758             if (!processedParameterArgs.add(p)) {
759                 // Avoid processing the same parameter twice
760                 continue;
761             }
762             Map<ModuleParameters, IModuleParameterHandler> suiteParams =
763                     ModuleParametersHelper.resolveParam(
764                             ModuleParameters.valueOf(p.toUpperCase()),
765                             mAllowOptionalParameterizedModules);
766             for (Entry<ModuleParameters, IModuleParameterHandler> suiteParamEntry :
767                     suiteParams.entrySet()) {
768                 ModuleParameters suiteParam = suiteParamEntry.getKey();
769                 String family = suiteParam.getFamily();
770                 if (duplicateModule.containsKey(family)) {
771                     // Duplicate family members are not accepted.
772                     throw new ConfigurationException(
773                             String.format(
774                                     "Module %s is declaring parameter: "
775                                             + "%s and %s when only one expected.",
776                                     moduleName, suiteParam, duplicateModule.get(family)));
777                 } else {
778                     duplicateModule.put(suiteParam.getFamily(), suiteParam);
779                 }
780                 // Do not consider the excluded parameterization dimension
781 
782                 if (expandedExcludedModuleParameters.contains(suiteParam)) {
783                     continue;
784                 }
785 
786                 if (suiteParamEntry.getValue() instanceof FoldableExpandingHandler) {
787                     List<IModuleParameterHandler> foldableHandlers =
788                             ((FoldableExpandingHandler) suiteParamEntry.getValue())
789                                     .expandHandler(mFoldableStates);
790                     params.addAll(foldableHandlers);
791                 } else {
792                     params.add(suiteParamEntry.getValue());
793                 }
794             }
795         }
796         return params;
797     }
798 
799     /** Gets the list of parameterized mainline modules associated with a module. */
800     @VisibleForTesting
getMainlineModuleParameters(IConfiguration config)801     List<String> getMainlineModuleParameters(IConfiguration config) throws ConfigurationException {
802         List<String> params = new ArrayList<>();
803 
804         List<String> parameters =
805                 config.getConfigurationDescription().getMetaData(ITestSuite.MAINLINE_PARAMETER_KEY);
806         if (parameters == null || parameters.isEmpty()) {
807             return params;
808         }
809 
810         return new ArrayList<>(dedupMainlineParameters(parameters, config.getName()));
811     }
812 
813     /**
814      * De-duplicate the given mainline parameters.
815      *
816      * @param parameters The list of given mainline parameters.
817      * @param configName The test configuration name.
818      * @return The de-duplicated mainline modules list.
819      */
820     @VisibleForTesting
dedupMainlineParameters(List<String> parameters, String configName)821     Set<String> dedupMainlineParameters(List<String> parameters, String configName)
822             throws ConfigurationException {
823         Set<String> results = new HashSet<>();
824         for (String param : parameters) {
825             if (!isValidMainlineParam(param)) {
826                 throw new ConfigurationException(
827                         String.format(
828                                 "Illegal mainline module parameter: \"%s\" configured in the test"
829                                     + " config: %s. Parameter must end with .apk/.apex/.apks and"
830                                     + " have no any spaces configured.",
831                                 param, configName));
832             }
833             if (!isInAlphabeticalOrder(param)) {
834                 throw new ConfigurationException(
835                         String.format(
836                                 "Illegal mainline module parameter: \"%s\" configured in the test"
837                                     + " config: %s. Parameter must be configured in alphabetical"
838                                     + " order or with no duplicated modules.",
839                                 param, configName));
840             }
841             results.add(param);
842         }
843         return results;
844     }
845 
846     /** Whether a mainline parameter configured in a test config is in alphabetical order or not. */
847     @VisibleForTesting
isInAlphabeticalOrder(String param)848     boolean isInAlphabeticalOrder(String param) {
849         String previousString = "";
850         for (String currentString : param.split(String.format("\\+"))) {
851             // This is to check if the parameter is in alphabetical order or duplicated.
852             if (currentString.compareTo(previousString) <= 0) {
853                 return false;
854             }
855             previousString = currentString;
856         }
857         return true;
858     }
859 
860     /** Whether the mainline parameter configured in the test config is valid or not. */
861     @VisibleForTesting
isValidMainlineParam(String param)862     boolean isValidMainlineParam(String param) {
863         if (param.contains(" ")) {
864             return false;
865         }
866         for (String m : param.split(String.format("\\+"))) {
867             if (!MAINLINE_PARAMETERS_TO_VALIDATE.stream().anyMatch(entry -> m.endsWith(entry))) {
868                 return false;
869             }
870         }
871         return true;
872     }
873 
874     /**
875      * Setup the options for the module configuration.
876      *
877      * @param name The base name of the module
878      * @param nameWithParam The id of the parameterized mainline module (module name + parameters)
879      * @param id The base id name of the module.
880      * @param fullId The full id of the module (usually abi + module name + parameters)
881      * @param config The module configuration.
882      * @param abi The abi of the module.
883      * @throws ConfigurationException
884      */
setUpConfig( String name, String nameWithParam, String id, String fullId, IConfiguration config, IAbi abi)885     private void setUpConfig(
886             String name,
887             String nameWithParam,
888             String id,
889             String fullId,
890             IConfiguration config,
891             IAbi abi)
892             throws ConfigurationException {
893         List<OptionDef> optionsToInject = new ArrayList<>();
894         if (mModuleOptions.containsKey(name)) {
895             optionsToInject.addAll(mModuleOptions.get(name));
896         }
897         if (nameWithParam != null && mModuleOptions.containsKey(nameWithParam)) {
898             optionsToInject.addAll(mModuleOptions.get(nameWithParam));
899         }
900         if (mModuleOptions.containsKey(id)) {
901             optionsToInject.addAll(mModuleOptions.get(id));
902         }
903         if (mModuleOptions.containsKey(fullId)) {
904             optionsToInject.addAll(mModuleOptions.get(fullId));
905         }
906         config.injectOptionValues(optionsToInject);
907 
908         // Set target preparers
909         for (IDeviceConfiguration holder : config.getDeviceConfig()) {
910             for (ITargetPreparer preparer : holder.getTargetPreparers()) {
911                 String className = preparer.getClass().getName();
912                 if (mTestOrPreparerOptions.containsKey(className)) {
913                     OptionSetter preparerSetter = new OptionSetter(preparer);
914                     for (OptionDef def : mTestOrPreparerOptions.get(className)) {
915                         preparerSetter.setOptionValue(def.name, def.key, def.value);
916                     }
917                 }
918                 if (preparer instanceof IAbiReceiver) {
919                     ((IAbiReceiver) preparer).setAbi(abi);
920                 }
921             }
922         }
923 
924         // Set IRemoteTests
925         List<IRemoteTest> tests = config.getTests();
926         for (IRemoteTest test : tests) {
927             String className = test.getClass().getName();
928             if (mTestOrPreparerOptions.containsKey(className)) {
929                 OptionSetter preparerSetter = new OptionSetter(test);
930                 for (OptionDef def : mTestOrPreparerOptions.get(className)) {
931                     preparerSetter.setOptionValue(def.name, def.key, def.value);
932                 }
933             }
934             addFiltersToTest(test, abi, fullId, mIncludeFilters, mExcludeFilters);
935             if (test instanceof IAbiReceiver) {
936                 ((IAbiReceiver) test).setAbi(abi);
937             }
938         }
939 
940         // add the abi and module name to the description
941         config.getConfigurationDescription().setAbi(abi);
942         config.getConfigurationDescription().setModuleName(name);
943 
944         config.validateOptions();
945     }
946 
947     /** Whether or not the base configuration should be created for all abis or not. */
shouldCreateMultiAbiForBase(List<IModuleParameterHandler> params)948     private boolean shouldCreateMultiAbiForBase(List<IModuleParameterHandler> params) {
949         for (IModuleParameterHandler param : params) {
950             if (param instanceof NotMultiAbiHandler) {
951                 return false;
952             }
953         }
954         return true;
955     }
956 }
957