• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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