blob: 38c21d4551c2678fce68562e45fb730e31a9601d [file] [log] [blame]
/*
* Copyright (C) 2012 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;
import com.android.tradefed.config.ConfigurationException;
import com.android.tradefed.config.Option;
import com.android.tradefed.config.Option.Importance;
import com.android.tradefed.config.OptionClass;
import com.android.tradefed.config.OptionCopier;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.metrics.proto.MetricMeasurement.Measurements;
import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
import com.android.tradefed.result.BugreportCollector;
import com.android.tradefed.result.ITestInvocationListener;
import com.android.tradefed.testtype.testdefs.XmlDefsTest;
import com.android.tradefed.util.AbiFormatter;
import com.android.tradefed.util.ListInstrumentationParser;
import com.android.tradefed.util.ListInstrumentationParser.InstrumentationTarget;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
/**
* Runs all instrumentation found on current device.
*/
@OptionClass(alias = "installed-instrumentation")
public class InstalledInstrumentationsTest
implements IDeviceTest, IResumableTest, IShardableTest, IStrictShardableTest {
/** the metric key name for the test coverage target value */
// TODO: move this to a more generic location
public static final String COVERAGE_TARGET_KEY = XmlDefsTest.COVERAGE_TARGET_KEY;
private static final String PM_LIST_CMD = "pm list instrumentation";
private static final String LINE_SEPARATOR = "\\r?\\n";
private ITestDevice mDevice;
@Option(name = "shell-timeout",
description="The defined timeout (in milliseconds) is used as a maximum waiting time "
+ "when expecting the command output from the device. At any time, if the "
+ "shell command does not output anything for a period longer than defined "
+ "timeout the TF run terminates. For no timeout, set to 0.")
private long mShellTimeout = 10 * 60 * 1000; // default to 10 minutes
@Option(name = "test-timeout",
description="Sets timeout (in milliseconds) that will be applied to each test. In the "
+ "event of a test timeout it will log the results and proceed with executing "
+ "the next test. For no timeout, set to 0.")
private int mTestTimeout = 5 * 60 * 1000; // default to 5 minutes
@Option(name = "size",
description = "Restrict tests to a specific test size. " +
"One of 'small', 'medium', 'large'",
importance = Importance.IF_UNSET)
private String mTestSize = null;
@Option(name = "runner",
description = "Restrict tests executed to a specific instrumentation class runner. " +
"Installed instrumentations that do not have this runner will be skipped.")
private String mRunner = null;
@Option(name = "rerun",
description = "Rerun unexecuted tests individually on same device if test run " +
"fails to complete.")
private boolean mIsRerunMode = true;
@Option(name = "resume",
description = "Schedule unexecuted tests for resumption on another device " +
"if first device becomes unavailable.")
private boolean mIsResumeMode = false;
@Option(name = "send-coverage",
description = "Send coverage target info to test listeners.")
private boolean mSendCoverage = false;
@Option(name = "bugreport-on-failure", description = "Sets which failed testcase events " +
"cause a bugreport to be collected. a bugreport after failed testcases. Note that " +
"there is _no feedback mechanism_ between the test runner and the bugreport " +
"collector, so use the EACH setting with due caution.")
private BugreportCollector.Freq mBugreportFrequency = null;
@Option(
name = "bugreport-on-run-failure",
description = "Take a bugreport if the instrumentation finish with a run failure"
)
private boolean mBugreportOnRunFailure = false;
@Option(name = "screenshot-on-failure", description = "Take a screenshot on every test failure")
private boolean mScreenshotOnFailure = false;
@Option(name = "logcat-on-failure", description =
"take a logcat snapshot on every test failure.")
private boolean mLogcatOnFailures = false;
@Option(name = "logcat-on-failure-size", description =
"The max number of logcat data in bytes to capture when --logcat-on-failure is on. " +
"Should be an amount that can comfortably fit in memory.")
private int mMaxLogcatBytes = 500 * 1024; // 500K
@Option(name = "class",
description = "Only run tests in specified class")
private String mTestClass = null;
@Option(name = "package",
description =
"Only run tests within this specific java package. Will be ignored if --class is set.")
private String mTestPackageName = null;
@Option(name = "instrumentation-arg",
description = "Additional instrumentation arguments to provide.")
private Map<String, String> mInstrArgMap = new HashMap<String, String>();
@Option(name = "rerun-from-file", description =
"Use test file instead of separate adb commands for each test " +
"when re-running instrumentations for tests that failed to run in previous attempts. ")
private boolean mReRunUsingTestFile = false;
@Option(name = "rerun-from-file-attempts", description =
"Max attempts to rerun tests from file. -1 means rerun from file infinitely.")
private int mReRunUsingTestFileAttempts = -1;
@Option(name = "fallback-to-serial-rerun", description =
"Rerun tests serially after rerun from file failed.")
private boolean mFallbackToSerialRerun = true;
@Option(name = "reboot-before-rerun", description =
"Reboot a device before re-running instrumentations.")
private boolean mRebootBeforeReRun = false;
@Option(name = "shards", description =
"Split test run into this many parallel shards")
private int mShards = 0;
@Option(name = "disable", description =
"Disable the test by setting this flag to true.")
private boolean mDisable = false;
@Option(
name = "coverage",
description =
"Collect code coverage for this test run. Note that the build under test must be a "
+ "coverage build or else this will fail."
)
private boolean mCoverage = false;
@Option(
name = "hidden-api-checks",
description =
"If set to false, the '--no-hidden-api-checks' flag will be passed to the am "
+ "instrument command. Only works for P or later."
)
private boolean mHiddenApiChecks = true;
private int mTotalShards = 0;
private int mShardIndex = 0;
private List<InstrumentationTest> mTests = null;
@Option(name = AbiFormatter.FORCE_ABI_STRING,
description = AbiFormatter.FORCE_ABI_DESCRIPTION,
importance = Importance.IF_UNSET)
private String mForceAbi = null;
/**
* {@inheritDoc}
*/
@Override
public ITestDevice getDevice() {
return mDevice;
}
/**
* {@inheritDoc}
*/
@Override
public void setDevice(ITestDevice device) {
mDevice = device;
}
/**
* Set the send coverage flag.
* <p/>
* Exposed for unit testing.
*/
void setSendCoverage(boolean sendCoverage) {
mSendCoverage = sendCoverage;
}
/**
* Gets the list of {@link InstrumentationTest}s contained within.
* <p/>
* Exposed for unit testing.
*/
List<InstrumentationTest> getTests() {
return mTests;
}
/**
* Sets the number of total shards this test should be split into.
* <p/>
* Exposed for unit testing.
*/
void setTotalShards(int totalShards) {
mTotalShards = totalShards;
}
/**
* Sets the shard index number of this test.
* <p/>
* Exposed for unit testing.
*/
void setShardIndex(int shardIndex) {
mShardIndex = shardIndex;
}
/**
* {@inheritDoc}
*/
@Override
public void run(ITestInvocationListener listener) throws DeviceNotAvailableException {
if (getDevice() == null) {
throw new IllegalArgumentException("Device has not been set");
}
if (mDisable) {
return;
}
buildTests();
doRun(listener);
}
/**
* Build the list of tests to run from the device, if not done already. Note: Can be called
* multiple times in case of resumed runs.
* @throws DeviceNotAvailableException
*/
private void buildTests() throws DeviceNotAvailableException {
if (mTests == null) {
ListInstrumentationParser parser = new ListInstrumentationParser();
String pmListOutput = getDevice().executeShellCommand(PM_LIST_CMD);
String pmListLines[] = pmListOutput.split(LINE_SEPARATOR);
parser.processNewLines(pmListLines);
if (parser.getInstrumentationTargets().isEmpty()) {
throw new IllegalArgumentException(String.format(
"No instrumentations were found on device %s - <%s>", getDevice()
.getSerialNumber(), pmListOutput));
}
int numUnshardedTests = 0;
mTests = new LinkedList<InstrumentationTest>();
for (InstrumentationTarget target : parser.getInstrumentationTargets()) {
if (mRunner == null || mRunner.equals(target.runnerName)) {
// Some older instrumentations are not shardable. As a result, we should try to
// shard these instrumentations by APKs, rather than by test methods.
if (mTotalShards > 0 && !target.isShardable()) {
numUnshardedTests += 1;
if ((numUnshardedTests - 1) % mTotalShards != mShardIndex) {
continue;
}
}
InstrumentationTest t = createInstrumentationTest();
try {
// Copies all current argument values to the new runner that will be
// used to actually run the tests.
OptionCopier.copyOptions(InstalledInstrumentationsTest.this, t);
} catch (ConfigurationException e) {
// Bail out rather than run tests with unexpected options
throw new RuntimeException("failed to copy instrumentation options", e);
}
t.setPackageName(target.packageName);
t.setRunnerName(target.runnerName);
t.setCoverageTarget(target.targetName);
if (mTotalShards > 0 && target.isShardable()) {
t.addInstrumentationArg("shardIndex", Integer.toString(mShardIndex));
t.addInstrumentationArg("numShards", Integer.toString(mTotalShards));
}
mTests.add(t);
}
}
}
}
/**
* Run the previously built tests.
*
* @param listener the {@link ITestInvocationListener}
* @throws DeviceNotAvailableException
*/
private void doRun(ITestInvocationListener listener) throws DeviceNotAvailableException {
while (!mTests.isEmpty()) {
InstrumentationTest test = mTests.get(0);
CLog.d("Running test %s on %s", test.getPackageName(), getDevice().getSerialNumber());
if (mSendCoverage && test.getCoverageTarget() != null) {
sendCoverage(test.getPackageName(), test.getCoverageTarget(), listener);
}
test.setDevice(getDevice());
test.setClassName(mTestClass);
test.setTestPackageName(mTestPackageName);
test.run(listener);
// test completed, remove from list
mTests.remove(0);
}
}
/**
* Forwards the tests coverage target info as a test metric.
*
* @param packageName
* @param coverageTarget
* @param listener
*/
private void sendCoverage(String packageName, String coverageTarget,
ITestInvocationListener listener) {
HashMap<String, Metric> coverageMetric = new HashMap<String, Metric>();
Metric metric =
Metric.newBuilder()
.setMeasurements(
Measurements.newBuilder().setSingleString(coverageTarget).build())
.build();
coverageMetric.put(COVERAGE_TARGET_KEY, metric);
listener.testRunStarted(packageName, 0);
listener.testRunEnded(0, coverageMetric);
}
long getShellTimeout() {
return mShellTimeout;
}
int getTestTimeout() {
return mTestTimeout;
}
String getTestSize() {
return mTestSize;
}
/**
* Creates the {@link InstrumentationTest} to use. Exposed for unit testing.
*/
InstrumentationTest createInstrumentationTest() {
// We do not know what kind of instrumentation we will find, so we don't enforce the ddmlib
// format for AndroidJUnitRunner.
InstrumentationTest test = new InstrumentationTest();
test.setEnforceFormat(false);
return test;
}
/**
* {@inheritDoc}
*/
@Override
public boolean isResumable() {
// hack to not resume if tests were never run
// TODO: fix this properly in TestInvocation
if (mTests == null) {
return false;
}
return mIsResumeMode;
}
/**
* {@inheritDoc}
*/
@Override
public Collection<IRemoteTest> split() {
if (mShards > 1) {
Collection<IRemoteTest> shards = new ArrayList<>(mShards);
for (int index = 0; index < mShards; index++) {
shards.add(getTestShard(mShards, index));
}
return shards;
}
return null;
}
/**
* {@inheritDoc}
*/
@Override
public IRemoteTest getTestShard(int shardCount, int shardIndex) {
InstalledInstrumentationsTest shard = new InstalledInstrumentationsTest();
try {
OptionCopier.copyOptions(this, shard);
} catch (ConfigurationException e) {
CLog.e("failed to copy instrumentation options: %s", e.getMessage());
}
shard.mShards = 0;
shard.mShardIndex = shardIndex;
shard.mTotalShards = shardCount;
return shard;
}
}