/* * 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 junit.framework.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 { // 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(listener); 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, metrics); if (mLastKmsg != null) { listener.testLog(String.format("last_kmsg_%s", getDevice().getSerialNumber()), LogDataType.TEXT, new ByteArrayInputStreamSource(mLastKmsg.getBytes())); } if (mDmesg != null) { listener.testLog(String.format("dmesg_%s", getDevice().getSerialNumber()), LogDataType.TEXT, 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(ITestInvocationListener listener) 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(listener); RunUtil.getDefault().sleep(mPollSleepTime * 1000); elapsedTime += System.currentTimeMillis() - startTime; } } /** * Check logs for userdata formatting/mounting issues. * * @throws DeviceNotAvailableException */ private void checkForUserDataFailure(ITestInvocationListener listener) throws DeviceNotAvailableException { mLastKmsg = getDevice().executeShellCommand("cat /proc/last_kmsg"); mDmesg = getDevice().executeShellCommand("dmesg"); Assert.assertFalse(String.format("Read only mount of userdata detected on %s", getDevice().getSerialNumber()), mDmesg.contains("Remounting filesystem read-only")); Assert.assertFalse(String.format("Last kmsg log showed a kernel panic on %s", getDevice().getSerialNumber()), mLastKmsg.contains("Kernel panic")); } }