1 /* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.fastboot.tests; 17 18 import com.android.tradefed.build.IBuildInfo; 19 import com.android.tradefed.config.ConfigurationException; 20 import com.android.tradefed.config.Option; 21 import com.android.tradefed.config.OptionSetter; 22 import com.android.tradefed.device.DeviceDisconnectedException; 23 import com.android.tradefed.device.DeviceNotAvailableException; 24 import com.android.tradefed.device.IManagedTestDevice; 25 import com.android.tradefed.device.ITestDevice; 26 import com.android.tradefed.device.ITestDevice.RecoveryMode; 27 import com.android.tradefed.log.LogUtil.CLog; 28 import com.android.tradefed.metrics.proto.MetricMeasurement.Metric; 29 import com.android.tradefed.result.ITestInvocationListener; 30 import com.android.tradefed.result.TestDescription; 31 import com.android.tradefed.targetprep.BuildError; 32 import com.android.tradefed.targetprep.DeviceFlashPreparer; 33 import com.android.tradefed.targetprep.IDeviceFlasher.UserDataFlashOption; 34 import com.android.tradefed.targetprep.TargetSetupError; 35 import com.android.tradefed.testtype.IBuildReceiver; 36 import com.android.tradefed.testtype.IDeviceTest; 37 import com.android.tradefed.testtype.IRemoteTest; 38 import com.android.tradefed.util.FileUtil; 39 import com.android.tradefed.util.proto.TfMetricProtoUtil; 40 41 import java.io.File; 42 import java.util.HashMap; 43 import java.util.concurrent.ExecutionException; 44 import java.util.concurrent.Future; 45 import java.util.concurrent.TimeUnit; 46 47 /** 48 * Flashes the device as part of the test, report device post-flash status as test results. TODO: 49 * Add more fastboot test validation step. 50 */ 51 public class FastbootTest implements IRemoteTest, IDeviceTest, IBuildReceiver { 52 53 private static final String FASTBOOT_TEST = FastbootTest.class.getName(); 54 /** the desired recentness of battery level * */ 55 private static final long BATTERY_FRESHNESS_MS = 30 * 1000; 56 57 private static final long INVALID_TIME_DURATION = -1; 58 private static final String INITIAL_BOOT_TIME = "initial-boot"; 59 private static final String ONLINE_TIME = "online"; 60 61 @Option( 62 name = "device-boot-time", 63 description = "Max time in ms to wait for" + " device to boot.", 64 isTimeVal = true) 65 private long mDeviceBootTimeMs = 5 * 60 * 1000; 66 67 @Option(name = "userdata-flash", description = "Specify handling of userdata partition.") 68 private UserDataFlashOption mUserDataFlashOption = UserDataFlashOption.WIPE; 69 70 @Option( 71 name = "concurrent-flasher-limit", 72 description = 73 "The maximum number of concurrent" 74 + " flashers (may be useful to avoid memory constraints)") 75 private Integer mConcurrentFlasherLimit = null; 76 77 @Option( 78 name = "skip-battery-check", 79 description = "If true, the battery reading test will" + " be skipped.") 80 private boolean mSkipBatteryCheck = false; 81 82 @Option( 83 name = "flasher-class", 84 description = 85 "The Flasher class (implementing " 86 + "DeviceFlashPreparer) to be used for the fastboot test", 87 mandatory = true) 88 private String mFlasherClass; 89 90 private IBuildInfo mBuildInfo; 91 private ITestDevice mDevice; 92 93 /** {@inheritDoc} */ 94 @Override setBuild(IBuildInfo buildInfo)95 public void setBuild(IBuildInfo buildInfo) { 96 mBuildInfo = buildInfo; 97 } 98 99 /** {@inheritDoc} */ 100 @Override setDevice(ITestDevice device)101 public void setDevice(ITestDevice device) { 102 mDevice = device; 103 } 104 105 /** {@inheritDoc} */ 106 @Override getDevice()107 public ITestDevice getDevice() { 108 return mDevice; 109 } 110 111 /** {@inheritDoc} */ 112 @Override run(ITestInvocationListener listener)113 public void run(ITestInvocationListener listener) throws DeviceNotAvailableException { 114 long start = System.currentTimeMillis(); 115 listener.testRunStarted(FASTBOOT_TEST, 1); 116 String originalFastbootpath = ((IManagedTestDevice) mDevice).getFastbootPath(); 117 try { 118 testFastboot(listener); 119 } finally { 120 // reset fastboot path 121 ((IManagedTestDevice) mDevice).setFastbootPath(originalFastbootpath); 122 listener.testRunEnded( 123 System.currentTimeMillis() - start, new HashMap<String, Metric>()); 124 } 125 } 126 127 /** 128 * Flash the device and calculate the time to bring the device online and first boot. 129 * 130 * @param listener 131 * @throws DeviceNotAvailableException 132 */ testFastboot(ITestInvocationListener listener)133 private void testFastboot(ITestInvocationListener listener) throws DeviceNotAvailableException { 134 HashMap<String, Metric> result = new HashMap<>(); 135 TestDescription firstBootTestId = 136 new TestDescription( 137 String.format("%s.%s", FASTBOOT_TEST, FASTBOOT_TEST), FASTBOOT_TEST); 138 listener.testStarted(firstBootTestId); 139 DeviceFlashPreparer flasher = loadFlashPreparerClass(); 140 long bootStart = INVALID_TIME_DURATION; 141 long onlineTime = INVALID_TIME_DURATION; 142 long bootTime = INVALID_TIME_DURATION; 143 try { 144 if (flasher == null) { 145 throw new RuntimeException( 146 String.format("Could not find flasher %s", mFlasherClass)); 147 } 148 try { 149 OptionSetter setter = new OptionSetter(flasher); 150 // duping and passing parameters down to flasher 151 setter.setOptionValue("device-boot-time", Long.toString(mDeviceBootTimeMs)); 152 setter.setOptionValue("userdata-flash", mUserDataFlashOption.toString()); 153 if (mConcurrentFlasherLimit != null) { 154 setter.setOptionValue( 155 "concurrent-flasher-limit", mConcurrentFlasherLimit.toString()); 156 } 157 // always to skip because the test needs to detect device online 158 // and available state individually 159 setter.setOptionValue("skip-post-flashing-setup", "true"); 160 setter.setOptionValue("force-system-flash", "true"); 161 } catch (ConfigurationException ce) { 162 // this really shouldn't happen, but if it does, it'll indicate a setup problem 163 // so this should be exposed, even at the expense of categorizing the build as 164 // having a critical failure 165 throw new RuntimeException("failed to set options for flasher", ce); 166 } 167 168 File fastboot = getFastbootFile(mBuildInfo); 169 if (fastboot == null) { 170 listener.testFailed( 171 firstBootTestId, "Couldn't find the fastboot binary in build info."); 172 return; 173 } 174 // Set the fastboot path for device 175 ((IManagedTestDevice) mDevice).setFastbootPath(fastboot.getAbsolutePath()); 176 177 // flash it! 178 CLog.v("Flashing device %s", mDevice.getSerialNumber()); 179 try { 180 flasher.setUp(mDevice, mBuildInfo); 181 // we are skipping post boot setup so this is the start of boot process 182 bootStart = System.currentTimeMillis(); 183 } catch (TargetSetupError | BuildError e) { 184 // setUp() may throw DeviceNotAvailableException, TargetSetupError and BuildError. 185 // DNAE is allowed to get thrown here so that a tool failure is triggered and build 186 // maybe retried; the other 2x types are also rethrown as RuntimeException's for 187 // the same purpose. In general, these exceptions reflect flashing or infra related 188 // flakiness, so retrying is a reasonable mitigation. 189 throw new RuntimeException("Exception during device flashing", e); 190 } 191 // check if device is online after flash, i.e. if adb is broken 192 CLog.v("Waiting for device %s online", mDevice.getSerialNumber()); 193 mDevice.setRecoveryMode(RecoveryMode.ONLINE); 194 try { 195 mDevice.waitForDeviceOnline(); 196 onlineTime = System.currentTimeMillis() - bootStart; 197 } catch (DeviceNotAvailableException dnae) { 198 CLog.e("Device not online after flashing"); 199 CLog.e(dnae); 200 listener.testRunFailed("Device not online after flashing"); 201 throw new DeviceDisconnectedException( 202 "Device not online after flashing", mDevice.getSerialNumber()); 203 } 204 // check if device can be fully booted, i.e. if application framework won't boot 205 CLog.v("Waiting for device %s boot complete", mDevice.getSerialNumber()); 206 mDevice.setRecoveryMode(RecoveryMode.AVAILABLE); 207 try { 208 mDevice.waitForDeviceAvailable(mDeviceBootTimeMs); 209 bootTime = System.currentTimeMillis() - bootStart; 210 } catch (DeviceNotAvailableException dnae) { 211 CLog.e("Device %s not available after flashing", mDevice.getSerialNumber()); 212 CLog.e(dnae); 213 // only report as test failure, not test run failure because we were able to run the 214 // test until the end, despite the failure verdict, and the device is returned to 215 // the pool in a useable state 216 listener.testFailed(firstBootTestId, "Device not available after flashing"); 217 return; 218 } 219 CLog.v("Device %s boot complete", mDevice.getSerialNumber()); 220 if (mSkipBatteryCheck) { 221 // If we skip the battery Check, we can return directly after boot complete. 222 return; 223 } 224 // We check if battery level are readable as non root to ensure that device is usable. 225 mDevice.disableAdbRoot(); 226 try { 227 Future<Integer> batteryFuture = 228 mDevice.getIDevice() 229 .getBattery(BATTERY_FRESHNESS_MS, TimeUnit.MILLISECONDS); 230 // get cached value or wait up to 500ms for battery level query 231 Integer level = batteryFuture.get(500, TimeUnit.MILLISECONDS); 232 CLog.d("Battery level value reading is: '%s'", level); 233 if (level == null) { 234 listener.testFailed(firstBootTestId, "Reading of battery level is wrong."); 235 return; 236 } 237 } catch (InterruptedException 238 | ExecutionException 239 | java.util.concurrent.TimeoutException e) { 240 CLog.e("Failed to query battery level for %s", mDevice.getSerialNumber()); 241 CLog.e(e); 242 listener.testFailed(firstBootTestId, "Failed to query battery level."); 243 return; 244 } finally { 245 mDevice.enableAdbRoot(); 246 } 247 } finally { 248 CLog.d("Device online time: %dms, initial boot time: %dms", onlineTime, bootTime); 249 if (onlineTime != INVALID_TIME_DURATION) { 250 result.put( 251 ONLINE_TIME, TfMetricProtoUtil.stringToMetric(Long.toString(onlineTime))); 252 } 253 if (bootTime != INVALID_TIME_DURATION) { 254 result.put( 255 INITIAL_BOOT_TIME, 256 TfMetricProtoUtil.stringToMetric(Long.toString(bootTime))); 257 } 258 listener.testEnded(firstBootTestId, result); 259 } 260 } 261 262 /** 263 * Attempt to load the class implementing {@link DeviceFlashPreparer} based on the option 264 * flasher-class, allows anybody to tests fastboot using their own implementation of it. 265 */ loadFlashPreparerClass()266 private DeviceFlashPreparer loadFlashPreparerClass() { 267 try { 268 Class<?> flasherClass = Class.forName(mFlasherClass); 269 Object flasherObject = flasherClass.newInstance(); 270 if (flasherObject instanceof DeviceFlashPreparer) { 271 return (DeviceFlashPreparer) flasherObject; 272 } else { 273 CLog.e( 274 "Loaded class '%s' is not an instance of DeviceFlashPreparer.", 275 flasherObject); 276 return null; 277 } 278 } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) { 279 CLog.e(e); 280 return null; 281 } 282 } 283 284 /** Helper to find the fastboot file as part of the buildinfo file list. */ getFastbootFile(IBuildInfo buildInfo)285 private File getFastbootFile(IBuildInfo buildInfo) { 286 File fastboot = buildInfo.getFile("fastboot"); 287 if (fastboot == null) { 288 return null; 289 } 290 FileUtil.chmodGroupRWX(fastboot); 291 return fastboot; 292 } 293 } 294