/* * Copyright (C) 2011 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.ota.tests; import com.android.ddmlib.Log; import com.android.tradefed.build.IBuildInfo; import com.android.tradefed.config.ConfigurationException; import com.android.tradefed.config.IConfiguration; import com.android.tradefed.config.IConfigurationReceiver; 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.device.ITestDevice; import com.android.tradefed.log.LogUtil.CLog; import com.android.tradefed.metrics.proto.MetricMeasurement.Metric; import com.android.tradefed.result.FileInputStreamSource; import com.android.tradefed.result.ITestInvocationListener; import com.android.tradefed.result.InputStreamSource; import com.android.tradefed.result.LogDataType; import com.android.tradefed.targetprep.BuildError; import com.android.tradefed.targetprep.ITargetPreparer; import com.android.tradefed.targetprep.TargetSetupError; import com.android.tradefed.testtype.IBuildReceiver; import com.android.tradefed.testtype.IDeviceTest; import com.android.tradefed.testtype.IRemoteTest; import com.android.tradefed.testtype.IResumableTest; import com.android.tradefed.testtype.IShardableTest; import com.android.tradefed.util.FileUtil; import com.android.tradefed.util.IRunUtil; import com.android.tradefed.util.RunUtil; import com.android.tradefed.util.StreamUtil; import com.android.tradefed.util.proto.TfMetricProtoUtil; import org.junit.Assert; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; /** * A test that will flash a build on a device, wait for the device to be OTA-ed to another build, * and then repeat N times. * *
Note: this test assumes that the {@link ITargetPreparer}s included in this test's {@link * IConfiguration} will flash the device back to a baseline build, and prepare the device to receive * the OTA to a new build. */ @OptionClass(alias = "ota-stability") public class OtaStabilityTest implements IDeviceTest, IBuildReceiver, IConfigurationReceiver, IShardableTest, IResumableTest { private static final String LOG_TAG = "OtaStabilityTest"; private IBuildInfo mDeviceBuild; private IConfiguration mConfiguration; private ITestDevice mDevice; @Option( name = "run-name", description = "The name of the ota stability test run. Used to report metrics.") private String mRunName = "ota-stability"; @Option( name = "iterations", description = "Number of ota stability 'flash + wait for ota' iterations to run.") private int mIterations = 20; @Option( name = "wait-recovery-time", description = "Number of minutes to wait for device to begin installing ota.") private int mWaitRecoveryTime = 15; @Option( name = "wait-install-time", description = "Number of minutes to wait for device to be online after beginning ota installation.") private int mWaitInstallTime = 10; @Option( name = "shards", description = "Optional number of shards to split test into. " + "Iterations will be split evenly among shards.", importance = Importance.IF_UNSET) private Integer mShards = null; @Option( name = "resume", description = "Resume the ota test run if an device setup error " + "stopped the previous test run.") private boolean mResumeMode = false; /** controls if this test should be resumed. Only used if mResumeMode is enabled */ private boolean mResumable = true; /** {@inheritDoc} */ @Override public void setConfiguration(IConfiguration configuration) { mConfiguration = configuration; } /** {@inheritDoc} */ @Override public void setBuild(IBuildInfo buildInfo) { mDeviceBuild = buildInfo; } /** {@inheritDoc} */ @Override public void setDevice(ITestDevice device) { mDevice = device; } /** {@inheritDoc} */ @Override public ITestDevice getDevice() { return mDevice; } /** Set the wait recovery time */ void setWaitRecoveryTime(int waitRecoveryTime) { mWaitRecoveryTime = waitRecoveryTime; } /** Set the wait install time */ void setWaitInstallTime(int waitInstallTime) { mWaitInstallTime = waitInstallTime; } /** Set the run name */ void setRunName(String runName) { mRunName = runName; } /** * Return the number of iterations. * *
Exposed for unit testing
*/
public int getIterations() {
return mIterations;
}
/** Set the iterations */
void setIterations(int iterations) {
mIterations = iterations;
}
/** Set the number of shards */
void setShards(int shards) {
mShards = shards;
}
/** {@inheritDoc} */
@Override
public Collection Currently does this by re-running {@link ITargetPreparer#setUp(ITestDevice, IBuildInfo)}
*
* @throws DeviceNotAvailableException
* @throws BuildError
* @throws TargetSetupError
* @throws ConfigurationException
*/
private void flashDevice()
throws TargetSetupError, BuildError, DeviceNotAvailableException,
ConfigurationException {
// assume the target preparers will flash the device back to device build
for (ITargetPreparer preparer : mConfiguration.getTargetPreparers()) {
preparer.setUp(mDevice, mDeviceBuild);
}
}
/**
* Get the {@link IRunUtil} instance to use.
*
* Exposed so unit tests can mock.
*/
IRunUtil getRunUtil() {
return RunUtil.getDefault();
}
/**
* Blocks and waits for OTA package to be installed.
*
* @param listener the {@link ITestInvocationListener}
* @return the build id the device ota-ed to
* @throws DeviceNotAvailableException
* @throws AssertionError
*/
private String waitForOta(ITestInvocationListener listener)
throws DeviceNotAvailableException, AssertionError {
String currentBuildId = mDevice.getBuildId();
Assert.assertEquals(
String.format(
"device %s does not have expected build id on boot.",
mDevice.getSerialNumber()),
currentBuildId,
mDeviceBuild.getBuildId());
// give some time for device to settle
getRunUtil().sleep(5 * 1000);
// force a checkin so device downloads OTA immediately
mDevice.executeShellCommand(
"am broadcast -a android.server.checkin.CHECKIN com.google.android.gms");
Assert.assertTrue(
String.format(
"Device %s did not enter recovery after %d min.",
mDevice.getSerialNumber(), mWaitRecoveryTime),
mDevice.waitForDeviceInRecovery(mWaitRecoveryTime * 60 * 1000));
try {
mDevice.waitForDeviceOnline(mWaitInstallTime * 60 * 1000);
} catch (DeviceNotAvailableException e) {
Log.e(
LOG_TAG,
String.format(
"Device %s did not come back online after leaving recovery",
mDevice.getSerialNumber()));
sendRecoveryLog(listener);
throw e;
}
try {
mDevice.waitForDeviceAvailable();
} catch (DeviceNotAvailableException e) {
Log.e(
LOG_TAG,
String.format(
"Device %s did not boot up successfully after leaving recovery/installing OTA",
mDevice.getSerialNumber()));
throw e;
}
currentBuildId = mDevice.getBuildId();
// TODO: should exact expected build id be checked?
Assert.assertNotEquals(
String.format(
"Device %s build id did not change after leaving recovery",
mDevice.getSerialNumber()),
currentBuildId,
mDeviceBuild.getBuildId());
return currentBuildId;
}
private void sendRecoveryLog(ITestInvocationListener listener)
throws DeviceNotAvailableException {
File destFile = null;
InputStreamSource destSource = null;
try {
// get recovery log
destFile = FileUtil.createTempFile("recovery", "log");
boolean gotFile = mDevice.pullFile("/tmp/recovery.log", destFile);
if (gotFile) {
destSource = new FileInputStreamSource(destFile);
listener.testLog("recovery_log", LogDataType.TEXT, destSource);
}
} catch (IOException e) {
Log.e(
LOG_TAG,
String.format(
"Failed to get recovery log from device %s",
mDevice.getSerialNumber()));
Log.e(LOG_TAG, e);
} finally {
FileUtil.deleteFile(destFile);
StreamUtil.cancel(destSource);
}
}
private void checkFields() {
if (mDevice == null) {
throw new IllegalArgumentException("missing device");
}
if (mConfiguration == null) {
throw new IllegalArgumentException("missing configuration");
}
if (mDeviceBuild == null) {
throw new IllegalArgumentException("missing build info");
}
}
/** {@inheritDoc} */
@Override
public boolean isResumable() {
return mResumeMode && mResumable;
}
}