blob: 3f73585fc046319b4f38a7404a1eb55db9288e6a [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.fastboot.tests;
import com.android.tradefed.build.IBuildInfo;
import com.android.tradefed.config.ConfigurationException;
import com.android.tradefed.config.Option;
import com.android.tradefed.config.OptionSetter;
import com.android.tradefed.device.DeviceDisconnectedException;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.IManagedTestDevice;
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.device.ITestDevice.RecoveryMode;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
import com.android.tradefed.result.ITestInvocationListener;
import com.android.tradefed.result.TestDescription;
import com.android.tradefed.targetprep.BuildError;
import com.android.tradefed.targetprep.DeviceFlashPreparer;
import com.android.tradefed.targetprep.IDeviceFlasher.UserDataFlashOption;
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.util.FileUtil;
import com.android.tradefed.util.proto.TfMetricProtoUtil;
import java.io.File;
import java.util.HashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
/**
* Flashes the device as part of the test, report device post-flash status as test results. TODO:
* Add more fastboot test validation step.
*/
public class FastbootTest implements IRemoteTest, IDeviceTest, IBuildReceiver {
private static final String FASTBOOT_TEST = FastbootTest.class.getName();
/** the desired recentness of battery level * */
private static final long BATTERY_FRESHNESS_MS = 30 * 1000;
private static final long INVALID_TIME_DURATION = -1;
private static final String INITIAL_BOOT_TIME = "initial-boot";
private static final String ONLINE_TIME = "online";
@Option(
name = "device-boot-time",
description = "Max time in ms to wait for" + " device to boot.",
isTimeVal = true)
private long mDeviceBootTimeMs = 5 * 60 * 1000;
@Option(name = "userdata-flash", description = "Specify handling of userdata partition.")
private UserDataFlashOption mUserDataFlashOption = UserDataFlashOption.WIPE;
@Option(
name = "concurrent-flasher-limit",
description =
"The maximum number of concurrent"
+ " flashers (may be useful to avoid memory constraints)")
private Integer mConcurrentFlasherLimit = null;
@Option(
name = "skip-battery-check",
description = "If true, the battery reading test will" + " be skipped.")
private boolean mSkipBatteryCheck = false;
@Option(
name = "flasher-class",
description =
"The Flasher class (implementing "
+ "DeviceFlashPreparer) to be used for the fastboot test",
mandatory = true)
private String mFlasherClass;
private IBuildInfo mBuildInfo;
private ITestDevice mDevice;
/** {@inheritDoc} */
@Override
public void setBuild(IBuildInfo buildInfo) {
mBuildInfo = buildInfo;
}
/** {@inheritDoc} */
@Override
public void setDevice(ITestDevice device) {
mDevice = device;
}
/** {@inheritDoc} */
@Override
public ITestDevice getDevice() {
return mDevice;
}
/** {@inheritDoc} */
@Override
public void run(ITestInvocationListener listener) throws DeviceNotAvailableException {
long start = System.currentTimeMillis();
listener.testRunStarted(FASTBOOT_TEST, 1);
String originalFastbootpath = ((IManagedTestDevice) mDevice).getFastbootPath();
try {
testFastboot(listener);
} finally {
// reset fastboot path
((IManagedTestDevice) mDevice).setFastbootPath(originalFastbootpath);
listener.testRunEnded(
System.currentTimeMillis() - start, new HashMap<String, Metric>());
}
}
/**
* Flash the device and calculate the time to bring the device online and first boot.
*
* @param listener
* @throws DeviceNotAvailableException
*/
private void testFastboot(ITestInvocationListener listener) throws DeviceNotAvailableException {
HashMap<String, Metric> result = new HashMap<>();
TestDescription firstBootTestId =
new TestDescription(
String.format("%s.%s", FASTBOOT_TEST, FASTBOOT_TEST), FASTBOOT_TEST);
listener.testStarted(firstBootTestId);
DeviceFlashPreparer flasher = loadFlashPreparerClass();
long bootStart = INVALID_TIME_DURATION;
long onlineTime = INVALID_TIME_DURATION;
long bootTime = INVALID_TIME_DURATION;
try {
if (flasher == null) {
throw new RuntimeException(
String.format("Could not find flasher %s", mFlasherClass));
}
try {
OptionSetter setter = new OptionSetter(flasher);
// duping and passing parameters down to flasher
setter.setOptionValue("device-boot-time", Long.toString(mDeviceBootTimeMs));
setter.setOptionValue("userdata-flash", mUserDataFlashOption.toString());
if (mConcurrentFlasherLimit != null) {
setter.setOptionValue(
"concurrent-flasher-limit", mConcurrentFlasherLimit.toString());
}
// always to skip because the test needs to detect device online
// and available state individually
setter.setOptionValue("skip-post-flashing-setup", "true");
setter.setOptionValue("force-system-flash", "true");
} catch (ConfigurationException ce) {
// this really shouldn't happen, but if it does, it'll indicate a setup problem
// so this should be exposed, even at the expense of categorizing the build as
// having a critical failure
throw new RuntimeException("failed to set options for flasher", ce);
}
File fastboot = getFastbootFile(mBuildInfo);
if (fastboot == null) {
listener.testFailed(
firstBootTestId, "Couldn't find the fastboot binary in build info.");
return;
}
// Set the fastboot path for device
((IManagedTestDevice) mDevice).setFastbootPath(fastboot.getAbsolutePath());
// flash it!
CLog.v("Flashing device %s", mDevice.getSerialNumber());
try {
flasher.setUp(mDevice, mBuildInfo);
// we are skipping post boot setup so this is the start of boot process
bootStart = System.currentTimeMillis();
} catch (TargetSetupError | BuildError e) {
// setUp() may throw DeviceNotAvailableException, TargetSetupError and BuildError.
// DNAE is allowed to get thrown here so that a tool failure is triggered and build
// maybe retried; the other 2x types are also rethrown as RuntimeException's for
// the same purpose. In general, these exceptions reflect flashing or infra related
// flakiness, so retrying is a reasonable mitigation.
throw new RuntimeException("Exception during device flashing", e);
}
// check if device is online after flash, i.e. if adb is broken
CLog.v("Waiting for device %s online", mDevice.getSerialNumber());
mDevice.setRecoveryMode(RecoveryMode.ONLINE);
try {
mDevice.waitForDeviceOnline();
onlineTime = System.currentTimeMillis() - bootStart;
} catch (DeviceNotAvailableException dnae) {
CLog.e("Device not online after flashing");
CLog.e(dnae);
listener.testRunFailed("Device not online after flashing");
throw new DeviceDisconnectedException(
"Device not online after flashing", mDevice.getSerialNumber());
}
// check if device can be fully booted, i.e. if application framework won't boot
CLog.v("Waiting for device %s boot complete", mDevice.getSerialNumber());
mDevice.setRecoveryMode(RecoveryMode.AVAILABLE);
try {
mDevice.waitForDeviceAvailable(mDeviceBootTimeMs);
bootTime = System.currentTimeMillis() - bootStart;
} catch (DeviceNotAvailableException dnae) {
CLog.e("Device %s not available after flashing", mDevice.getSerialNumber());
CLog.e(dnae);
// only report as test failure, not test run failure because we were able to run the
// test until the end, despite the failure verdict, and the device is returned to
// the pool in a useable state
listener.testFailed(firstBootTestId, "Device not available after flashing");
return;
}
CLog.v("Device %s boot complete", mDevice.getSerialNumber());
if (mSkipBatteryCheck) {
// If we skip the battery Check, we can return directly after boot complete.
return;
}
// We check if battery level are readable as non root to ensure that device is usable.
mDevice.disableAdbRoot();
try {
Future<Integer> batteryFuture =
mDevice.getIDevice()
.getBattery(BATTERY_FRESHNESS_MS, TimeUnit.MILLISECONDS);
// get cached value or wait up to 500ms for battery level query
Integer level = batteryFuture.get(500, TimeUnit.MILLISECONDS);
CLog.d("Battery level value reading is: '%s'", level);
if (level == null) {
listener.testFailed(firstBootTestId, "Reading of battery level is wrong.");
return;
}
} catch (InterruptedException
| ExecutionException
| java.util.concurrent.TimeoutException e) {
CLog.e("Failed to query battery level for %s", mDevice.getSerialNumber());
CLog.e(e);
listener.testFailed(firstBootTestId, "Failed to query battery level.");
return;
} finally {
mDevice.enableAdbRoot();
}
} finally {
CLog.d("Device online time: %dms, initial boot time: %dms", onlineTime, bootTime);
if (onlineTime != INVALID_TIME_DURATION) {
result.put(
ONLINE_TIME, TfMetricProtoUtil.stringToMetric(Long.toString(onlineTime)));
}
if (bootTime != INVALID_TIME_DURATION) {
result.put(
INITIAL_BOOT_TIME,
TfMetricProtoUtil.stringToMetric(Long.toString(bootTime)));
}
listener.testEnded(firstBootTestId, result);
}
}
/**
* Attempt to load the class implementing {@link DeviceFlashPreparer} based on the option
* flasher-class, allows anybody to tests fastboot using their own implementation of it.
*/
private DeviceFlashPreparer loadFlashPreparerClass() {
try {
Class<?> flasherClass = Class.forName(mFlasherClass);
Object flasherObject = flasherClass.newInstance();
if (flasherObject instanceof DeviceFlashPreparer) {
return (DeviceFlashPreparer) flasherObject;
} else {
CLog.e(
"Loaded class '%s' is not an instance of DeviceFlashPreparer.",
flasherObject);
return null;
}
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
CLog.e(e);
return null;
}
}
/** Helper to find the fastboot file as part of the buildinfo file list. */
private File getFastbootFile(IBuildInfo buildInfo) {
File fastboot = buildInfo.getFile("fastboot");
if (fastboot == null) {
return null;
}
FileUtil.chmodGroupRWX(fastboot);
return fastboot;
}
}