blob: 69bfb30340ff91b10cd1024e9050cad0075206d3 [file] [log] [blame]
/*
* Copyright (C) 2015 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.compatibility.common.tradefed.command;
import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
import com.android.compatibility.common.tradefed.build.CompatibilityBuildProvider;
import com.android.compatibility.common.tradefed.result.SubPlanHelper;
import com.android.compatibility.common.tradefed.result.suite.CertificationResultXml;
import com.android.compatibility.common.tradefed.testtype.suite.CompatibilityTestSuite;
import com.android.compatibility.common.util.ResultHandler;
import com.android.tradefed.build.BuildRetrievalError;
import com.android.tradefed.build.IBuildInfo;
import com.android.tradefed.command.Console;
import com.android.tradefed.config.ArgsOptionParser;
import com.android.tradefed.config.ConfigurationException;
import com.android.tradefed.config.ConfigurationFactory;
import com.android.tradefed.config.IConfiguration;
import com.android.tradefed.config.IConfigurationFactory;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.result.suite.SuiteResultHolder;
import com.android.tradefed.testtype.Abi;
import com.android.tradefed.testtype.IAbi;
import com.android.tradefed.testtype.IRemoteTest;
import com.android.tradefed.testtype.IRuntimeHintProvider;
import com.android.tradefed.testtype.suite.ITestSuite;
import com.android.tradefed.testtype.suite.SuiteModuleLoader;
import com.android.tradefed.testtype.suite.TestSuiteInfo;
import com.android.tradefed.testtype.suite.params.ModuleParameters;
import com.android.tradefed.util.AbiUtils;
import com.android.tradefed.util.FileUtil;
import com.android.tradefed.util.MultiMap;
import com.android.tradefed.util.Pair;
import com.android.tradefed.util.RegexTrie;
import com.android.tradefed.util.TableFormatter;
import com.android.tradefed.util.TimeUtil;
import com.android.tradefed.util.VersionParser;
import com.google.common.base.Joiner;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* An extension of Tradefed's console which adds features specific to compatibility testing.
*/
public class CompatibilityConsole extends Console {
/**
* Hard coded list of modules to be excluded from manual module sharding
* @see #splitModules(int)
*/
private final static Set<String> MODULE_SPLIT_EXCLUSIONS = new HashSet<>();
static {
MODULE_SPLIT_EXCLUSIONS.add("CtsDeqpTestCases");
}
private final static String ADD_PATTERN = "a(?:dd)?";
private static final String LATEST_RESULT_DIR = "latest";
private CompatibilityBuildHelper mBuildHelper;
private IBuildInfo mBuildInfo;
/**
* {@inheritDoc}
*/
@Override
public void run() {
String buildNumber = TestSuiteInfo.getInstance().getBuildNumber();
String versionFile = VersionParser.fetchVersion();
if (versionFile != null) {
buildNumber = versionFile;
}
printLine(
String.format(
"Android %s %s (%s)",
TestSuiteInfo.getInstance().getFullName(),
TestSuiteInfo.getInstance().getVersion(),
buildNumber));
printLine("Use \"help\" or \"help all\" to get more information on running commands.");
super.run();
}
/**
* Adds the 'list plans', 'list modules' and 'list results' commands
*/
@Override
protected void setCustomCommands(RegexTrie<Runnable> trie, List<String> genericHelp,
Map<String, String> commandHelp) {
genericHelp.add("Enter 'help add' for help with 'add subplan' commands");
genericHelp.add("\t----- " + TestSuiteInfo.getInstance().getFullName() + " usage ----- ");
genericHelp.add("Usage: run <plan> [--module <module name>] [ options ]");
genericHelp.add("Example: run cts --module CtsGestureTestCases --bugreport-on-failure");
genericHelp.add("");
trie.put(new Runnable() {
@Override
public void run() {
listPlans();
}
}, LIST_PATTERN, "p(?:lans)?");
trie.put(
new Runnable() {
@Override
public void run() {
listModules(null);
}
},
LIST_PATTERN,
"m(?:odules)?");
trie.put(
new ArgRunnable<CaptureList>() {
@Override
public void run(CaptureList args) {
String parameter = args.get(2).get(0);
listModules(parameter);
}
},
LIST_PATTERN,
"m(?:odules)?",
"(.*)");
trie.put(new Runnable() {
@Override
public void run() {
listResults();
}
}, LIST_PATTERN, "r(?:esults)?");
trie.put(new Runnable() {
@Override
public void run() {
listSubPlans();
}
}, LIST_PATTERN, "s(?:ubplans)?");
trie.put(new ArgRunnable<CaptureList>() {
@Override
public void run(CaptureList args) {
// Skip 2 tokens to get past split and modules pattern
String arg = args.get(2).get(0);
int shards = Integer.parseInt(arg);
if (shards <= 1) {
printLine("number of shards should be more than 1");
return;
}
splitModules(shards);
}
}, "split", "m(?:odules)?", "(\\d+)");
trie.put(new ArgRunnable<CaptureList>() {
@Override
public void run(CaptureList args) {
// Skip 2 tokens to get past "add" and "subplan"
String[] flatArgs = new String[args.size() - 2];
for (int i = 2; i < args.size(); i++) {
flatArgs[i - 2] = args.get(i).get(0);
}
addSubPlan(flatArgs);
}
}, ADD_PATTERN, "s(?:ubplan)?", null);
trie.put(new ArgRunnable<CaptureList>() {
@Override
public void run(CaptureList args) {
printLine("'add subplan' requires parameters, use 'help add' to get more details");
}
}, ADD_PATTERN, "s(?:ubplan)?");
trie.put(new Runnable() {
@Override
public void run() {
printLine(String.format("Android %s %s (%s)",
TestSuiteInfo.getInstance().getFullName(),
TestSuiteInfo.getInstance().getVersion(),
TestSuiteInfo.getInstance().getBuildNumber()));
}
}, "version"); // override tradefed 'version' command to print test suite name and version
// find existing help for 'LIST_PATTERN' commands, and append these commands help
String listHelp = commandHelp.get(LIST_PATTERN);
if (listHelp == null) {
// no help? Unexpected, but soldier on
listHelp = new String();
}
String combinedHelp =
listHelp
+ LINE_SEPARATOR
+ "\t----- "
+ TestSuiteInfo.getInstance().getFullName()
+ " specific options ----- "
+ LINE_SEPARATOR
+ "\tp[lans] List all plans available"
+ LINE_SEPARATOR
+ "\tm[odules] List all modules available"
+ LINE_SEPARATOR
+ String.format(
"\tm[odules] [module parameter] List all modules matching the "
+ "parameter. (available params: %s)",
Arrays.asList(ModuleParameters.values()))
+ LINE_SEPARATOR
+ "\tr[esults] List all results"
+ LINE_SEPARATOR;
commandHelp.put(LIST_PATTERN, combinedHelp);
// Update existing RUN_PATTERN with CTS specific extra run possibilities.
String runHelp = commandHelp.get(RUN_PATTERN);
if (runHelp == null) {
runHelp = new String();
}
String combinedRunHelp = runHelp +
LINE_SEPARATOR +
"\t----- " + TestSuiteInfo.getInstance().getFullName()
+ " specific options ----- " + LINE_SEPARATOR +
"\t<plan> --module/-m <module> Run a test module" + LINE_SEPARATOR +
"\t<plan> --module/-m <module> --test/-t <test_name> Run a specific test from" +
" the module. Test name can be <package>.<class>, <package>.<class>#<method> or "
+ "<native_binary_name>" + LINE_SEPARATOR +
"\t\tAvailable Options:" + LINE_SEPARATOR +
"\t\t\t--serial/-s <device_id>: The device to run the test on" + LINE_SEPARATOR +
"\t\t\t--abi/-a <abi> : The ABI to run the test against" + LINE_SEPARATOR +
"\t\t\t--logcat-on-failure : Capture logcat when a test fails"
+ LINE_SEPARATOR +
"\t\t\t--bugreport-on-failure : Capture a bugreport when a test fails"
+ LINE_SEPARATOR +
"\t\t\t--screenshot-on-failure: Capture a screenshot when a test fails"
+ LINE_SEPARATOR +
"\t\t\t--shard-count <shards>: Shards a run into the given number of independent " +
"chunks, to run on multiple devices in parallel." + LINE_SEPARATOR +
"\t ----- In order to retry a previous run -----" + LINE_SEPARATOR +
"\tretry --retry <session id to retry> [--retry-type <FAILED | NOT_EXECUTED>]"
+ LINE_SEPARATOR +
"\t\tWithout --retry-type, retry will run both FAIL and NOT_EXECUTED tests"
+ LINE_SEPARATOR;
commandHelp.put(RUN_PATTERN, combinedRunHelp);
commandHelp.put(ADD_PATTERN, String.format(
"%s help:" + LINE_SEPARATOR +
"\tadd s[ubplan]: create a subplan from a previous session" + LINE_SEPARATOR +
"\t\tAvailable Options:" + LINE_SEPARATOR +
"\t\t\t--session <session_id>: The session used to create a subplan"
+ LINE_SEPARATOR +
"\t\t\t--name/-n <subplan_name>: The name of the new subplan" + LINE_SEPARATOR +
"\t\t\t--result-type <status>: Which results to include in the subplan. "
+ "One of passed, failed, not_executed. Repeatable" + LINE_SEPARATOR,
ADD_PATTERN));
}
/**
* {@inheritDoc}
*/
@Override
protected String getConsolePrompt() {
return String.format("%s-tf > ", TestSuiteInfo.getInstance().getName().toLowerCase());
}
/**
* List all the modules available in the suite, if a specific parameter is requested, only
* display that one.
*
* @param moduleParameter The parameter requested to be displayed. Null if all should be shown.
*/
private void listModules(String moduleParameter) {
CompatibilityTestSuite test = new CompatibilityTestSuite();
Set<String> abiStrings = ITestSuite.getAbisForBuildTargetArchFromSuite();
Set<IAbi> abis = new LinkedHashSet<>();
for (String abi : abiStrings) {
if (AbiUtils.isAbiSupportedByCompatibility(abi)) {
abis.add(new Abi(abi, AbiUtils.getBitness(abi)));
}
}
test.setAbis(abis);
if (getBuild() != null) {
test.setEnableParameterizedModules(true);
test.setEnableOptionalParameterizedModules(true);
if (moduleParameter != null) {
test.setModuleParameter(ModuleParameters.valueOf(moduleParameter.toUpperCase()));
}
test.setBuild(getBuild());
LinkedHashMap<String, IConfiguration> configs = test.loadTests();
printLine(String.format("%s", Joiner.on("\n").join(configs.keySet())));
} else {
printLine("Error fetching information about modules.");
}
}
private void listPlans() {
printLine("Available plans include:");
ConfigurationFactory.getInstance().printHelp(System.out);
}
private void splitModules(int shards) {
File[] files = null;
try {
files = getBuildHelper().getTestsDir().listFiles(new SuiteModuleLoader.ConfigFilter());
} catch (FileNotFoundException e) {
printLine(e.getMessage());
e.printStackTrace();
}
// parse through all config files to get runtime hints
if (files != null && files.length > 0) {
IConfigurationFactory configFactory = ConfigurationFactory.getInstance();
List<Pair<String, Long>> moduleRuntime = new ArrayList<>();
// parse through all config files to calculate module execution time
for (File file : files) {
IConfiguration config = null;
String moduleName = file.getName().split("\\.")[0];
if (MODULE_SPLIT_EXCLUSIONS.contains(moduleName)) {
continue;
}
try {
config = configFactory.createConfigurationFromArgs(new String[]{
file.getAbsolutePath(),
});
} catch (ConfigurationException ce) {
printLine("Error loading config file: " + file.getAbsolutePath());
CLog.e(ce);
continue;
}
long runtime = 0;
for (IRemoteTest test : config.getTests()) {
if (test instanceof IRuntimeHintProvider) {
runtime += ((IRuntimeHintProvider) test).getRuntimeHint();
} else {
CLog.w("Using default 1m runtime estimation for test type %s",
test.getClass().getSimpleName());
runtime += 60 * 1000;
}
}
moduleRuntime.add(new Pair<String, Long>(moduleName, runtime));
}
// sort list modules in descending order of runtime hint
Collections.sort(moduleRuntime, new Comparator<Pair<String, Long>>() {
@Override
public int compare(Pair<String, Long> o1, Pair<String, Long> o2) {
return o2.second.compareTo(o1.second);
}
});
// partition list of modules based on the runtime hint
List<List<Pair<String, Long>>> splittedModules = new ArrayList<>();
for (int i = 0; i < shards; i++) {
splittedModules.add(new ArrayList<>());
}
int shardIndex = 0;
int increment = 1;
long[] shardTimes = new long[shards];
// go through the sorted list, distribute modules into shards in zig-zag pattern to get
// an even execution time among shards
for (Pair<String, Long> module : moduleRuntime) {
splittedModules.get(shardIndex).add(module);
// also collect total runtime per shard
shardTimes[shardIndex] += module.second;
shardIndex += increment;
// zig-zagging: first distribute modules from shard 0 to N, then N down to 0, repeat
if (shardIndex == shards) {
increment = -1;
shardIndex = shards - 1;
}
if (shardIndex == -1) {
increment = 1;
shardIndex = 0;
}
}
shardIndex = 0;
// print the final shared lists
for (List<Pair<String, Long>> shardedModules : splittedModules) {
StringBuilder lineBuffer = new StringBuilder();
lineBuffer.append(String.format("shard #%d (%s):",
shardIndex, TimeUtil.formatElapsedTime(shardTimes[shardIndex])));
Iterator<Pair<String, Long>> itr = shardedModules.iterator();
lineBuffer.append(itr.next().first);
while (itr.hasNext()) {
lineBuffer.append(',');
lineBuffer.append(itr.next().first);
}
shardIndex++;
printLine(lineBuffer.toString());
}
} else {
printLine("No modules found");
}
}
private void listResults() {
TableFormatter tableFormatter = new TableFormatter();
List<List<String>> table = new ArrayList<>();
List<File> resultDirs = null;
Map<SuiteResultHolder, File> holders = new LinkedHashMap<>();
try {
resultDirs = getResults(getBuildHelper().getResultsDir());
} catch (FileNotFoundException e) {
throw new RuntimeException("Error while parsing results directory", e);
}
CertificationResultXml xmlParser = new CertificationResultXml();
for (File resultDir : resultDirs) {
if (LATEST_RESULT_DIR.equals(resultDir.getName())) {
continue;
}
try {
holders.put(xmlParser.parseResults(resultDir, true), resultDir);
} catch (IOException e) {
e.printStackTrace();
}
}
if (holders.isEmpty()) {
printLine(String.format("No results found"));
return;
}
int i = 0;
for (SuiteResultHolder holder : holders.keySet()) {
String moduleProgress = String.format("%d of %d",
holder.completeModules, holder.totalModules);
table.add(
Arrays.asList(
Integer.toString(i),
Long.toString(holder.passedTests),
Long.toString(holder.failedTests),
moduleProgress,
holders.get(holder).getName(),
holder.context
.getAttributes()
.get(CertificationResultXml.SUITE_PLAN_ATTR)
.get(0),
Joiner.on(", ").join(holder.context.getShardsSerials().values()),
printAttributes(holder.context.getAttributes(), "build_id"),
printAttributes(holder.context.getAttributes(), "build_product")));
i++;
}
// add the table header to the beginning of the list
table.add(0, Arrays.asList("Session", "Pass", "Fail", "Modules Complete",
"Result Directory", "Test Plan", "Device serial(s)", "Build ID", "Product"));
tableFormatter.displayTable(table, new PrintWriter(System.out, true));
}
private String printAttributes(MultiMap<String, String> map, String key) {
if (map.get(key) == null) {
return "unknown";
}
return map.get(key).get(0);
}
/**
* Returns the list of all results directories.
*/
private List<File> getResults(File resultsDir) {
return ResultHandler.getResultDirectories(resultsDir);
}
private void listSubPlans() {
File[] files = null;
try {
files = getBuildHelper().getSubPlansDir().listFiles();
} catch (FileNotFoundException e) {
printLine(e.getMessage());
e.printStackTrace();
}
if (files != null && files.length > 0) {
List<String> subPlans = new ArrayList<>();
for (File subPlanFile : files) {
subPlans.add(FileUtil.getBaseName(subPlanFile.getName()));
}
Collections.sort(subPlans);
for (String subPlan : subPlans) {
printLine(subPlan);
}
} else {
printLine("No subplans found");
}
}
private void addSubPlan(String[] flatArgs) {
SubPlanHelper creator = new SubPlanHelper();
try {
ArgsOptionParser optionParser = new ArgsOptionParser(creator);
optionParser.parse(Arrays.asList(flatArgs));
creator.createAndSerializeSubPlan(getBuildHelper());
} catch (ConfigurationException e) {
printLine("Error: " + e.getMessage());
printLine(ArgsOptionParser.getOptionHelp(false, creator));
}
}
private CompatibilityBuildHelper getBuildHelper() {
if (mBuildHelper == null) {
IBuildInfo build = getBuild();
if (build == null) {
return null;
}
mBuildHelper = new CompatibilityBuildHelper(build);
}
return mBuildHelper;
}
private IBuildInfo getBuild() {
if (mBuildInfo == null) {
try {
CompatibilityBuildProvider buildProvider = new CompatibilityBuildProvider();
mBuildInfo = buildProvider.getBuild();
} catch (BuildRetrievalError e) {
e.printStackTrace();
}
}
return mBuildInfo;
}
public static void main(String[] args) throws InterruptedException, ConfigurationException {
Console console = new CompatibilityConsole();
Console.startConsole(console, args);
}
}