blob: 71de26e6fd6e922c28d20e84944a06b02c64eb2d [file] [log] [blame]
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.tradefed.testtype.suite;
import com.android.tradefed.build.BuildInfoKey.BuildInfoFileKey;
import com.android.tradefed.build.IBuildInfo;
import com.android.tradefed.build.IDeviceBuildInfo;
import com.android.tradefed.config.IConfiguration;
import com.android.tradefed.config.Option;
import com.android.tradefed.config.Option.Importance;
import com.android.tradefed.config.OptionClass;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.testtype.IAbi;
import com.android.tradefed.testtype.IRemoteTest;
import com.android.tradefed.testtype.suite.params.ModuleParameters;
import com.android.tradefed.util.ArrayUtil;
import com.google.common.annotations.VisibleForTesting;
import java.io.File;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
/** A Test for running Compatibility Test Suite with new suite system. */
@OptionClass(alias = "base-suite")
public class BaseTestSuite extends ITestSuite {
public static final String INCLUDE_FILTER_OPTION = "include-filter";
public static final String EXCLUDE_FILTER_OPTION = "exclude-filter";
public static final String MODULE_OPTION = "module";
public static final String TEST_ARG_OPTION = "test-arg";
public static final String TEST_OPTION = "test";
public static final char TEST_OPTION_SHORT_NAME = 't';
public static final String CONFIG_PATTERNS_OPTION = "config-patterns";
private static final String MODULE_ARG_OPTION = "module-arg";
@Option(
name = INCLUDE_FILTER_OPTION,
description = "the include module filters to apply.",
importance = Importance.ALWAYS
)
private Set<String> mIncludeFilters = new HashSet<>();
@Option(
name = EXCLUDE_FILTER_OPTION,
description = "the exclude module filters to apply.",
importance = Importance.ALWAYS
)
private Set<String> mExcludeFilters = new HashSet<>();
@Option(
name = MODULE_OPTION,
shortName = 'm',
description = "the test module to run. Only works for configuration in the tests dir.",
importance = Importance.IF_UNSET
)
private String mModuleName = null;
@Option(
name = TEST_OPTION,
shortName = TEST_OPTION_SHORT_NAME,
description = "the test to run.",
importance = Importance.IF_UNSET
)
private String mTestName = null;
@Option(
name = MODULE_ARG_OPTION,
description =
"the arguments to pass to a module. The expected format is"
+ "\"<module-name>:[{alias}]<arg-name>:[<arg-key>:=]<arg-value>\"",
importance = Importance.ALWAYS
)
private List<String> mModuleArgs = new ArrayList<>();
@Option(
name = TEST_ARG_OPTION,
description =
"the arguments to pass to a test. The expected format is"
+ "\"<test-class>:<arg-name>:[<arg-key>:=]<arg-value>\"",
importance = Importance.ALWAYS
)
private List<String> mTestArgs = new ArrayList<>();
@Option(
name = "run-suite-tag",
description =
"The tag that must be run. If specified, only configurations containing the "
+ "matching suite tag will be able to run."
)
private String mSuiteTag = null;
@Option(
name = "prioritize-host-config",
description =
"If there are duplicate test configs for host/target, prioritize the host config, "
+ "otherwise use the target config."
)
private boolean mPrioritizeHostConfig = false;
@Option(
name = "suite-config-prefix",
description = "Search only configs with given prefix for suite tags."
)
private String mSuitePrefix = null;
@Option(
name = "skip-loading-config-jar",
description = "Whether or not to skip loading configurations from the JAR on the classpath."
)
private boolean mSkipJarLoading = false;
@Option(
name = CONFIG_PATTERNS_OPTION,
description =
"The pattern(s) of the configurations that should be loaded from a directory."
+ " If none is explicitly specified, .*.xml and .*.config will be used."
+ " Can be repeated."
)
private List<String> mConfigPatterns = new ArrayList<>();
@Option(
name = "enable-parameterized-modules",
description =
"Whether or not to enable parameterized modules. This is a feature flag for work "
+ "in development."
)
private boolean mEnableParameter = false;
@Option(
name = "module-parameter",
description =
"Allows to run only one module parameter type instead of all the combinations. "
+ "For example: 'instant_app' would only run the instant_app version of "
+ "modules"
)
private ModuleParameters mForceParameter = null;
@Option(
name = "exclude-module-parameters",
description =
"Exclude some modules parameter from being evaluated in the run combinations."
+ "For example: 'instant_app' would exclude all the instant_app version of "
+ "modules."
)
private Set<ModuleParameters> mExcludedModuleParameters = new HashSet<>();
private SuiteModuleLoader mModuleRepo;
private Map<String, List<SuiteTestFilter>> mIncludeFiltersParsed = new HashMap<>();
private Map<String, List<SuiteTestFilter>> mExcludeFiltersParsed = new HashMap<>();
/** {@inheritDoc} */
@Override
public LinkedHashMap<String, IConfiguration> loadTests() {
try {
File testsDir = getTestsDir();
setupFilters(testsDir);
Set<IAbi> abis = getAbis(getDevice());
// Create and populate the filters here
SuiteModuleLoader.addFilters(mIncludeFilters, mIncludeFiltersParsed, abis);
SuiteModuleLoader.addFilters(mExcludeFilters, mExcludeFiltersParsed, abis);
CLog.d(
"Initializing ModuleRepo\nABIs:%s\n"
+ "Test Args:%s\nModule Args:%s\nIncludes:%s\nExcludes:%s",
abis, mTestArgs, mModuleArgs, mIncludeFiltersParsed, mExcludeFiltersParsed);
mModuleRepo =
createModuleLoader(
mIncludeFiltersParsed, mExcludeFiltersParsed, mTestArgs, mModuleArgs);
mModuleRepo.setParameterizedModules(mEnableParameter);
mModuleRepo.setModuleParameter(mForceParameter);
mModuleRepo.setExcludedModuleParameters(mExcludedModuleParameters);
List<File> testsDirectories = new ArrayList<>();
// Include host or target first in the search if it exists, we have to this in
// BaseTestSuite because it's the only one with the BuildInfo knowledge of linked files
if (mPrioritizeHostConfig) {
File hostSubDir = getBuildInfo().getFile(BuildInfoFileKey.HOST_LINKED_DIR);
if (hostSubDir != null && hostSubDir.exists()) {
testsDirectories.add(hostSubDir);
}
} else {
File targetSubDir = getBuildInfo().getFile(BuildInfoFileKey.TARGET_LINKED_DIR);
if (targetSubDir != null && targetSubDir.exists()) {
testsDirectories.add(targetSubDir);
}
}
// Finally add the full test cases directory in case there is no special sub-dir.
testsDirectories.add(testsDir);
// Actual loading of the configurations.
return loadingStrategy(abis, testsDirectories, mSuitePrefix, mSuiteTag);
} catch (DeviceNotAvailableException | FileNotFoundException e) {
throw new RuntimeException(e);
}
}
/**
* Default loading strategy will load from the resources and the tests directory. Can be
* extended or replaced.
*
* @param abis The set of abis to run against.
* @param testsDirs The tests directory.
* @param suitePrefix A prefix to filter the resource directory.
* @param suiteTag The suite tag a module should have to be included. Can be null.
* @return A list of loaded configuration for the suite.
*/
public LinkedHashMap<String, IConfiguration> loadingStrategy(
Set<IAbi> abis, List<File> testsDirs, String suitePrefix, String suiteTag) {
LinkedHashMap<String, IConfiguration> loadedConfigs = new LinkedHashMap<>();
// Load configs that are part of the resources
if (!mSkipJarLoading) {
loadedConfigs.putAll(
getModuleLoader().loadConfigsFromJars(abis, suitePrefix, suiteTag));
}
// Load the configs that are part of the tests dir
if (mConfigPatterns.isEmpty()) {
// If no special pattern was configured, use the default configuration patterns we know
mConfigPatterns.add(".*\\.config$");
mConfigPatterns.add(".*\\.xml$");
}
loadedConfigs.putAll(
getModuleLoader()
.loadConfigsFromDirectory(
testsDirs, abis, suitePrefix, suiteTag, mConfigPatterns));
return loadedConfigs;
}
public File getTestsDir() throws FileNotFoundException {
IBuildInfo build = getBuildInfo();
if (build instanceof IDeviceBuildInfo) {
return ((IDeviceBuildInfo) build).getTestsDir();
}
// TODO: handle multi build?
throw new FileNotFoundException("Could not found a tests dir folder.");
}
/** {@inheritDoc} */
@Override
public void setBuild(IBuildInfo buildInfo) {
super.setBuild(buildInfo);
}
/** Sets include-filters for the compatibility test */
public void setIncludeFilter(Set<String> includeFilters) {
mIncludeFilters.addAll(includeFilters);
}
/** Gets a copy of include-filters for the compatibility test */
protected Set<String> getIncludeFilter() {
return new HashSet<String>(mIncludeFilters);
}
/** Sets exclude-filters for the compatibility test */
public void setExcludeFilter(Set<String> excludeFilters) {
mExcludeFilters.addAll(excludeFilters);
}
/** Gets a copy of exclude-filters for the compatibility test */
protected Set<String> getExcludeFilter() {
return new HashSet<String>(mExcludeFilters);
}
/** Returns the current {@link SuiteModuleLoader}. */
public SuiteModuleLoader getModuleLoader() {
return mModuleRepo;
}
/** Adds module args */
public void addModuleArgs(Set<String> moduleArgs) {
mModuleArgs.addAll(moduleArgs);
}
/** Add config patterns */
public void addConfigPatterns(List<String> patterns) {
mConfigPatterns.addAll(patterns);
}
/**
* Create the {@link SuiteModuleLoader} responsible to load the {@link IConfiguration} and
* assign them some of the options.
*
* @param includeFiltersFormatted The formatted and parsed include filters.
* @param excludeFiltersFormatted The formatted and parsed exclude filters.
* @param testArgs the list of test ({@link IRemoteTest}) arguments.
* @param moduleArgs the list of module arguments.
* @return the created {@link SuiteModuleLoader}.
*/
public SuiteModuleLoader createModuleLoader(
Map<String, List<SuiteTestFilter>> includeFiltersFormatted,
Map<String, List<SuiteTestFilter>> excludeFiltersFormatted,
List<String> testArgs,
List<String> moduleArgs) {
return new SuiteModuleLoader(
includeFiltersFormatted, excludeFiltersFormatted, testArgs, moduleArgs);
}
/**
* Sets the include/exclude filters up based on if a module name was given.
*
* @throws FileNotFoundException if any file is not found.
*/
protected void setupFilters(File testsDir) throws FileNotFoundException {
if (mModuleName != null) {
// If this option (-m / --module) is set only the matching unique module should run.
Set<File> modules =
SuiteModuleLoader.getModuleNamesMatching(
testsDir, mSuitePrefix, String.format(".*%s.*.config", mModuleName));
// If multiple modules match, do exact match.
if (modules.size() > 1) {
Set<File> newModules = new HashSet<>();
String exactModuleName = String.format("%s.config", mModuleName);
for (File module : modules) {
if (module.getName().equals(exactModuleName)) {
newModules.add(module);
modules = newModules;
break;
}
}
}
if (modules.size() == 0) {
throw new IllegalArgumentException(
String.format("No modules found matching %s", mModuleName));
} else if (modules.size() > 1) {
throw new IllegalArgumentException(
String.format(
"Multiple modules found matching %s:\n%s\nWhich one did you "
+ "mean?\n",
mModuleName, ArrayUtil.join("\n", modules)));
} else {
File mod = modules.iterator().next();
String moduleName = mod.getName().replace(".config", "");
checkFilters(mIncludeFilters, moduleName);
checkFilters(mExcludeFilters, moduleName);
mIncludeFilters.add(
new SuiteTestFilter(getRequestedAbi(), moduleName, mTestName).toString());
}
} else if (mTestName != null) {
throw new IllegalArgumentException(
"Test name given without module name. Add --module <module-name>");
}
}
@Override
void cleanUpSuiteSetup() {
super.cleanUpSuiteSetup();
// Clean the filters because at that point they have been applied to the runners.
// This can save several GB of memories during sharding.
mIncludeFilters.clear();
mExcludeFilters.clear();
mIncludeFiltersParsed.clear();
mExcludeFiltersParsed.clear();
}
/* Helper method designed to remove filters in a list not applicable to the given module */
private static void checkFilters(Set<String> filters, String moduleName) {
Set<String> cleanedFilters = new HashSet<String>();
for (String filter : filters) {
if (moduleName.equals(SuiteTestFilter.createFrom(filter).getName())) {
cleanedFilters.add(filter); // Module name matches, filter passes
}
}
filters.clear();
filters.addAll(cleanedFilters);
}
/* Return a {@link boolean} for the setting of prioritize-host-config.*/
boolean getPrioritizeHostConfig() {
return mPrioritizeHostConfig;
}
/**
* Set option prioritize-host-config.
*
* @param prioritizeHostConfig true to prioritize host config, i.e., run host test if possible.
*/
@VisibleForTesting
protected void setPrioritizeHostConfig(boolean prioritizeHostConfig) {
mPrioritizeHostConfig = prioritizeHostConfig;
}
}