• 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.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