blob: 2532a0aa131eda5dfcf9495430997722a80001b9 [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.stability.tests;
import com.android.ddmlib.Log.LogLevel;
import com.android.tradefed.config.Option;
import com.android.tradefed.config.Option.Importance;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.result.ByteArrayInputStreamSource;
import com.android.tradefed.result.ITestInvocationListener;
import com.android.tradefed.result.LogDataType;
import com.android.tradefed.testtype.IDeviceTest;
import com.android.tradefed.testtype.IRemoteTest;
import com.android.tradefed.testtype.IShardableTest;
import com.android.tradefed.util.CommandResult;
import com.android.tradefed.util.CommandStatus;
import com.android.tradefed.util.RunUtil;
import com.android.tradefed.util.proto.TfMetricProtoUtil;
import org.junit.Assert;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
/**
* A test that reboots device many times, and reports successful iteration count.
*/
public class RebootStressTest implements IRemoteTest, IDeviceTest, IShardableTest {
private static final String[] LAST_KMSG_PATHS = {
"/sys/fs/pstore/console-ramoops-0",
"/sys/fs/pstore/console-ramoops",
"/proc/last_kmsg",
};
// max number of ms to allowed for the post-boot waitForDeviceAvailable check
private static final long DEVICE_AVAIL_TIME = 3 * 1000;
@Option(name = "iterations", description = "number of reboot iterations to perform")
private int mIterations = 1;
@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 = "run-name", description =
"The test run name used to report metrics.")
private String mRunName = "reboot-stress";
@Option(name = "post-boot-wait-time", description =
"Total number of seconds to wait between reboot attempts")
private int mWaitTime = 10;
@Option(name = "post-boot-poll-time", description =
"Number of seconds to wait between device health checks")
private int mPollSleepTime = 1;
@Option(name = "wipe-data", description =
"Flag to set if userdata should be wiped between reboots")
private boolean mWipeUserData = false;
@Option(name = "wipe-data-cmd", description =
"the fastboot command(s) to use to wipe data and reboot device.")
private Collection<String> mFastbootWipeCmds = new ArrayList<String>();
@Option(name = "boot-into-bootloader", description =
"Flag to set if reboot should enter bootloader")
private boolean mBootIntoBootloader = true;
private ITestDevice mDevice;
private String mLastKmsg;
private String mDmesg;
/**
* Set the run name
*/
void setRunName(String runName) {
mRunName = runName;
}
/**
* Return the number of iterations.
* <p/>
* 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 void setDevice(ITestDevice device) {
mDevice = device;
}
/**
* {@inheritDoc}
*/
@Override
public ITestDevice getDevice() {
return mDevice;
}
void setWaitTime(int waitTimeSec) {
mWaitTime = waitTimeSec;
}
void setPollTime(int pollTime) {
mPollSleepTime = pollTime;
}
void setWipeData(boolean wipeData) {
mWipeUserData = wipeData;
}
void setFastbootWipeDataCmds(Collection<String> cmds) {
mFastbootWipeCmds.addAll(cmds);
}
/**
* {@inheritDoc}
*/
@Override
public Collection<IRemoteTest> split() {
if (mShards == null || mShards <= 1) {
return null;
}
Collection<IRemoteTest> shards = new ArrayList<IRemoteTest>(mShards);
int remainingIterations = mIterations;
for (int i = mShards; i > 0; i--) {
RebootStressTest testShard = new RebootStressTest();
// device will be set by test invoker
testShard.setRunName(mRunName);
testShard.setWaitTime(mWaitTime);
testShard.setWipeData(mWipeUserData);
testShard.setFastbootWipeDataCmds(mFastbootWipeCmds);
testShard.setPollTime(mPollSleepTime);
// attempt to divide iterations evenly among shards with no remainder
int iterationsForShard = Math.round(remainingIterations/i);
if (iterationsForShard > 0) {
testShard.setIterations(iterationsForShard);
remainingIterations -= iterationsForShard;
shards.add(testShard);
}
}
return shards;
}
/**
* {@inheritDoc}
*/
@Override
public void run(ITestInvocationListener listener) throws DeviceNotAvailableException {
Assert.assertNotNull(getDevice());
listener.testRunStarted(mRunName, 0);
long startTime = System.currentTimeMillis();
int actualIterations = 0;
try {
for (actualIterations = 0; actualIterations < mIterations; actualIterations++) {
CLog.i("Reboot attempt %d of %d", actualIterations+1, mIterations);
if (mBootIntoBootloader){
getDevice().rebootIntoBootloader();
if (mWipeUserData) {
executeFastbootWipeCommand();
} else {
getDevice().executeFastbootCommand("reboot");
}
} else {
getDevice().reboot();
}
getDevice().waitForDeviceAvailable();
doWaitAndCheck();
CLog.logAndDisplay(LogLevel.INFO, "Device %s completed %d of %d iterations",
getDevice().getSerialNumber(), actualIterations+1, mIterations);
}
} finally {
Map<String, String> metrics = new HashMap<String, String>(1);
long durationMs = System.currentTimeMillis() - startTime;
metrics.put("iterations", Integer.toString(actualIterations));
metrics.put("shards", "1");
listener.testRunEnded(durationMs, TfMetricProtoUtil.upgradeConvert(metrics));
if (mLastKmsg != null) {
listener.testLog(String.format("last_kmsg_%s", getDevice().getSerialNumber()),
LogDataType.KERNEL_LOG,
new ByteArrayInputStreamSource(mLastKmsg.getBytes()));
}
if (mDmesg != null) {
listener.testLog(String.format("dmesg_%s", getDevice().getSerialNumber()),
LogDataType.KERNEL_LOG, new ByteArrayInputStreamSource(mDmesg.getBytes()));
}
CLog.logAndDisplay(LogLevel.INFO, "Device %s completed %d of %d iterations",
getDevice().getSerialNumber(), actualIterations, mIterations);
}
}
private void executeFastbootWipeCommand() throws DeviceNotAvailableException {
if (mFastbootWipeCmds.isEmpty()) {
// default to use fastboot -w reboot
mFastbootWipeCmds.add("-w reboot");
}
for (String fastbootCmd : mFastbootWipeCmds) {
CLog.i("Running '%s'", fastbootCmd);
CommandResult result = getDevice().executeFastbootCommand(fastbootCmd.split(" "));
Assert.assertEquals(result.getStderr(), CommandStatus.SUCCESS, result.getStatus());
}
}
/**
* Perform wait between reboots. Perform periodic checks on device to ensure is still available.
*
* @throws DeviceNotAvailableException
*/
private void doWaitAndCheck() throws DeviceNotAvailableException {
long waitTimeMs = mWaitTime * 1000;
long elapsedTime = 0;
while (elapsedTime < waitTimeMs) {
long startTime = System.currentTimeMillis();
// ensure device is still up
getDevice().waitForDeviceAvailable(DEVICE_AVAIL_TIME);
checkForUserDataFailure();
RunUtil.getDefault().sleep(mPollSleepTime * 1000);
elapsedTime += System.currentTimeMillis() - startTime;
}
}
/**
* Check logs for userdata formatting/mounting issues.
*
* @throws DeviceNotAvailableException
*/
private void checkForUserDataFailure() throws DeviceNotAvailableException {
for (String path : LAST_KMSG_PATHS) {
if (getDevice().doesFileExist(path)) {
mLastKmsg = getDevice().executeShellCommand("cat " + path);
Assert.assertFalse(String.format("Last kmsg log showed a kernel panic on %s",
getDevice().getSerialNumber()), mLastKmsg.contains("Kernel panic"));
break;
}
}
mDmesg = getDevice().executeShellCommand("dmesg");
Assert.assertFalse(String.format("Read only mount of userdata detected on %s",
getDevice().getSerialNumber()),
mDmesg.contains("Remounting filesystem read-only"));
}
}