/* * 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.invoker.TestInformation; 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(TestInformation testInfo, ITestInvocationListener listener) throws DeviceNotAvailableException { long start = System.currentTimeMillis(); listener.testRunStarted(FASTBOOT_TEST, 1); String originalFastbootpath = ((IManagedTestDevice) mDevice).getFastbootPath(); try { testFastboot(testInfo, listener); } finally { // reset fastboot path ((IManagedTestDevice) mDevice).setFastbootPath(originalFastbootpath); listener.testRunEnded( System.currentTimeMillis() - start, new HashMap()); } } /** * Flash the device and calculate the time to bring the device online and first boot. * * @param listener * @throws DeviceNotAvailableException */ private void testFastboot(TestInformation testInfo, ITestInvocationListener listener) throws DeviceNotAvailableException { HashMap 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(testInfo); // 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 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; } }