blob: ff282635d2e94ec6f21a02f20ee4102f37c0efdd [file] [log] [blame]
/*
* Copyright (C) 2016 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.result;
import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
import com.android.compatibility.common.tradefed.testtype.CompatibilityTest;
import com.android.compatibility.common.tradefed.testtype.ISubPlan;
import com.android.compatibility.common.tradefed.testtype.SubPlan;
import com.android.compatibility.common.tradefed.util.OptionHelper;
import com.android.compatibility.common.util.ICaseResult;
import com.android.compatibility.common.util.IInvocationResult;
import com.android.compatibility.common.util.IModuleResult;
import com.android.compatibility.common.util.ITestResult;
import com.android.compatibility.common.util.ResultHandler;
import com.android.compatibility.common.util.TestFilter;
import com.android.compatibility.common.util.TestStatus;
import com.android.ddmlib.Log.LogLevel;
import com.android.tradefed.config.ArgsOptionParser;
import com.android.tradefed.config.ConfigurationException;
import com.android.tradefed.config.Option;
import com.android.tradefed.config.Option.Importance;
import com.android.tradefed.log.LogUtil.CLog;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/**
* Class for creating subplans from compatibility result XML.
*/
public class SubPlanCreator {
// result types
public static final String PASSED = "passed";
public static final String FAILED = "failed";
public static final String NOT_EXECUTED = "not_executed";
// static mapping of result types to TestStatuses
private static final Map<String, TestStatus> mStatusMap;
static {
Map<String, TestStatus> statusMap = new HashMap<String, TestStatus>();
statusMap.put(PASSED, TestStatus.PASS);
statusMap.put(FAILED, TestStatus.FAIL);
mStatusMap = Collections.unmodifiableMap(statusMap);
}
@Option (name = "name", shortName = 'n', description = "the name of the subplan to create",
importance=Importance.IF_UNSET)
private String mSubPlanName = null;
@Option (name = "session", description = "the session id to derive from",
importance=Importance.IF_UNSET)
private Integer mSessionId = null;
@Option (name = "result-type",
description = "the result type to include. One of passed, failed, not_executed."
+ " Option may be repeated",
importance=Importance.IF_UNSET)
private Set<String> mResultTypes = new HashSet<String>();
@Option(name = CompatibilityTest.INCLUDE_FILTER_OPTION,
description = "the include module filters to apply.",
importance = Importance.NEVER)
private Set<String> mIncludeFilters = new HashSet<String>();
@Option(name = CompatibilityTest.EXCLUDE_FILTER_OPTION,
description = "the exclude module filters to apply.",
importance = Importance.NEVER)
private Set<String> mExcludeFilters = new HashSet<String>();
@Option(name = CompatibilityTest.MODULE_OPTION, shortName = 'm',
description = "the test module to run.",
importance = Importance.NEVER)
private String mModuleName = null;
@Option(name = CompatibilityTest.TEST_OPTION, shortName = 't',
description = "the test to run.",
importance = Importance.NEVER)
private String mTestName = null;
@Option(name = CompatibilityTest.ABI_OPTION, shortName = 'a',
description = "the abi to test.",
importance = Importance.NEVER)
private String mAbiName = null;
File mSubPlanFile = null;
IInvocationResult mResult = null;
/**
* Create an empty {@link SubPlanCreator}.
* <p/>
* All {@link Option} fields must be populated via
* {@link com.android.tradefed.config.ArgsOptionParser}
*/
public SubPlanCreator() {}
/**
* Create a {@link SubPlanCreator} using the specified option values.
*/
public SubPlanCreator(String name, int session, Collection<String> resultTypes) {
mSubPlanName = name;
mSessionId = session;
mResultTypes.addAll(resultTypes);
}
/**
* Set the result from which to derive the subplan.
* @param result
*/
public void setResult(IInvocationResult result) {
mResult = result;
}
/**
* Add a result type from which to derive the subplan. PASSED, FAILED, or NOT_EXECUTED
* @param resultType
*/
public void addResultType(String resultType) {
mResultTypes.add(resultType);
}
/**
* Create and serialize a subplan derived from a result.
* <p/>
* {@link Option} values must all be set before this is called.
* @return serialized subplan file.
* @throws ConfigurationException
*/
public File createAndSerializeSubPlan(CompatibilityBuildHelper buildHelper)
throws ConfigurationException {
ISubPlan subPlan = createSubPlan(buildHelper);
if (subPlan != null) {
try {
subPlan.serialize(new BufferedOutputStream(new FileOutputStream(mSubPlanFile)));
CLog.logAndDisplay(LogLevel.INFO, "Created subplan \"%s\" at %s",
mSubPlanName, mSubPlanFile.getAbsolutePath());
return mSubPlanFile;
} catch (IOException e) {
CLog.e("Failed to create plan file %s", mSubPlanFile.getAbsolutePath());
CLog.e(e);
}
}
return null;
}
/**
* Create a subplan derived from a result.
* <p/>
* {@link Option} values must be set before this is called.
* @param build
* @return subplan
* @throws ConfigurationException
*/
public ISubPlan createSubPlan(CompatibilityBuildHelper buildHelper)
throws ConfigurationException {
setupFields(buildHelper);
ISubPlan subPlan = new SubPlan();
// add filters from previous session to track which tests must run
subPlan.addAllIncludeFilters(mIncludeFilters);
subPlan.addAllExcludeFilters(mExcludeFilters);
if (mModuleName != null) {
subPlan.addIncludeFilter(new TestFilter(mAbiName, mModuleName, mTestName).toString());
}
for (IModuleResult module : mResult.getModules()) {
if (shouldRunModule(module)) {
Set<TestStatus> statusesToRun = getStatusesToRun();
TestFilter moduleInclude =
new TestFilter(module.getAbi(), module.getName(), null /*test*/);
if (shouldRunEntireModule(module)) {
// include entire module
subPlan.addIncludeFilter(moduleInclude.toString());
} else if (mResultTypes.contains(NOT_EXECUTED) && !module.isDone()) {
// add module include and test excludes
subPlan.addIncludeFilter(moduleInclude.toString());
for (ICaseResult caseResult : module.getResults()) {
for (ITestResult testResult : caseResult.getResults()) {
if (!statusesToRun.contains(testResult.getResultStatus())) {
TestFilter testExclude = new TestFilter(module.getAbi(),
module.getName(), testResult.getFullName());
subPlan.addExcludeFilter(testExclude.toString());
}
}
}
} else {
// Not-executed tests should not be rerun and/or this module is completed
// In any such case, it suffices to add includes for each test to rerun
for (ICaseResult caseResult : module.getResults()) {
for (ITestResult testResult : caseResult.getResults()) {
if (statusesToRun.contains(testResult.getResultStatus())) {
TestFilter testInclude = new TestFilter(module.getAbi(),
module.getName(), testResult.getFullName());
subPlan.addIncludeFilter(testInclude.toString());
}
}
}
}
} else {
// module should not run, exclude entire module
TestFilter moduleExclude =
new TestFilter(module.getAbi(), module.getName(), null /*test*/);
subPlan.addExcludeFilter(moduleExclude.toString());
}
}
return subPlan;
}
/**
* Whether any tests within the given {@link IModuleResult} should be run, based on
* the content of mResultTypes.
* @param module
* @return true if at least one test in the module should run
*/
private boolean shouldRunModule(IModuleResult module) {
if (mResultTypes.contains(NOT_EXECUTED) && !module.isDone()) {
// module has not executed tests that the subplan should run
return true;
}
for (TestStatus status : getStatusesToRun()) {
if (module.countResults(status) > 0) {
return true;
}
}
return false;
}
/**
* Whether all tests within the given {@link IModuleResult} should be run, based on
* the content of mResultTypes.
* @param module
* @return true if every test in the module should run
*/
private boolean shouldRunEntireModule(IModuleResult module) {
if (!mResultTypes.contains(NOT_EXECUTED) && !module.isDone()) {
// module has not executed tests that the subplan should not run
return false;
}
Set<TestStatus> statusesToRun = getStatusesToRun();
for (TestStatus status : TestStatus.values()) {
if (!statusesToRun.contains(status)) {
// status is a TestStatus we don't want to run
if (module.countResults(status) > 0) {
return false;
}
}
}
return true;
}
/**
* Retrieves a {@link Set} of {@link TestStatus}es to run, based on the content of
* mResultTypes. Does not account for result type NOT_EXECUTED, since no such TestStatus
* exists.
* @return set of TestStatuses to run
*/
private Set<TestStatus> getStatusesToRun() {
Set<TestStatus> statusesToRun = new HashSet<TestStatus>();
for (String resultType : mResultTypes) {
// no test status exists for not-executed tests
if (resultType != NOT_EXECUTED) {
statusesToRun.add(mStatusMap.get(resultType));
}
}
return statusesToRun;
}
/**
* Ensure that all {@Option}s and fields are populated with valid values.
* @param buildHelper
* @throws ConfigurationException if any option has an invalid value
*/
private void setupFields(CompatibilityBuildHelper buildHelper) throws ConfigurationException {
if (mResult == null) {
if (mSessionId == null) {
throw new ConfigurationException("Missing --session argument");
}
try {
mResult = ResultHandler.findResult(buildHelper.getResultsDir(), mSessionId);
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
}
if (mResult == null) {
throw new IllegalArgumentException(String.format(
"Could not find session with id %d", mSessionId));
}
}
String retryCommandLineArgs = mResult.getCommandLineArgs();
if (retryCommandLineArgs != null) {
try {
// parse the command-line string from the result file and set options
ArgsOptionParser parser = new ArgsOptionParser(this);
parser.parse(OptionHelper.getValidCliArgs(retryCommandLineArgs, this));
} catch (ConfigurationException e) {
throw new RuntimeException(e);
}
}
if (mResultTypes.isEmpty()) {
// add all valid values, include all tests of all statuses
mResultTypes.addAll(
new HashSet<String>(Arrays.asList(PASSED, FAILED, NOT_EXECUTED)));
}
// validate all test status values
for (String type : mResultTypes) {
if (!type.equals(PASSED) && !type.equals(FAILED) && !type.equals(NOT_EXECUTED)) {
throw new ConfigurationException(String.format("result type %s invalid", type));
}
}
if (mSubPlanName == null) {
mSubPlanName = createPlanName();
}
try {
mSubPlanFile = new File(buildHelper.getSubPlansDir(), mSubPlanName + ".xml");
if (mSubPlanFile.exists()) {
throw new ConfigurationException(String.format("Subplan %s already exists",
mSubPlanName));
}
} catch (IOException e) {
throw new RuntimeException("Could not find subplans directory");
}
}
/**
* Helper to create a plan name if none is explicitly set
*/
private String createPlanName() {
StringBuilder sb = new StringBuilder();
sb.append(String.join("_", mResultTypes));
sb.append("_");
if (mSessionId != null) {
sb.append(Integer.toString(mSessionId));
sb.append("_");
}
// use unique start time for name
sb.append(CompatibilityBuildHelper.getDirSuffix(mResult.getStartTime()));
return sb.toString();
}
}