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