1 /*
2  * Copyright (C) 2018 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 
17 package com.android.tradefed.util;
18 
19 import com.android.annotations.VisibleForTesting;
20 import com.android.ddmlib.CollectingOutputReceiver;
21 import com.android.tradefed.device.DeviceNotAvailableException;
22 import com.android.tradefed.device.ITestDevice;
23 import com.android.tradefed.log.LogUtil.CLog;
24 
25 import java.util.Vector;
26 import java.util.concurrent.TimeUnit;
27 import java.util.function.Predicate;
28 
29 public class CmdUtil {
30     public static final int MAX_RETRY_COUNT = 10;
31     static final int DELAY_BETWEEN_RETRY_IN_SECS = 3;
32     static final long FRAMEWORK_START_TIMEOUT = 1000 * 60 * 2; // 2 minutes.
33     static final String BOOTCOMPLETE_PROP = "dev.bootcomplete";
34 
35     // An interface to wait with delay. Used for testing purpose.
sleep(int seconds)36     public interface ISleeper { public void sleep(int seconds) throws InterruptedException; }
37     private ISleeper mSleeper = null;
38 
39     /**
40      * Helper method to retry given cmd until the expected results are satisfied.
41      * An example usage it to retry 'lshal' until the expected hal service appears.
42      *
43      * @param device testing device.
44      * @param cmd the command string to be executed on device.
45      * @param predicate function that checks the exit condition.
46      * @return true if the exit condition is satisfied, false otherwise.
47      * @throws DeviceNotAvailableException
48      */
waitCmdResultWithDelay(ITestDevice device, String cmd, Predicate<String> predicate)49     public boolean waitCmdResultWithDelay(ITestDevice device, String cmd,
50             Predicate<String> predicate) throws DeviceNotAvailableException {
51         for (int count = 0; count < MAX_RETRY_COUNT; count++) {
52             if (validateCmdSuccess(device, cmd, predicate)) {
53                 return true;
54             }
55         }
56         return false;
57     }
58 
59     /**
60      * Helper method to retry a given set of commands, cmds.
61      *
62      * @param device testing device.
63      * @param cmds a vector of the command strings to be executed on device.
64      * @param validation_cmd the validation command string to be executed on device.
65      * @param predicate function that checks the exit condition.
66      * @return true if the exit condition is satisfied, false otherwise.
67      * @throws DeviceNotAvailableException
68      */
retry(ITestDevice device, Vector<String> cmds, String validation_cmd, Predicate<String> predicate)69     public boolean retry(ITestDevice device, Vector<String> cmds, String validation_cmd,
70             Predicate<String> predicate) throws DeviceNotAvailableException {
71         if (cmds.isEmpty()) {
72             CLog.w("retry() called but cmd is an epmty vector.");
73             return false;
74         }
75         for (int count = 0; count < MAX_RETRY_COUNT; count++) {
76             for (String cmd : cmds) {
77                 CLog.d("Running a command: %s", cmd);
78                 String out = device.executeShellCommand(cmd);
79                 CLog.d("Command output: %s", out);
80             }
81             if (validateCmdSuccess(device, validation_cmd, predicate)) {
82                 return true;
83             }
84         }
85         return false;
86     }
87 
88     /**
89      * Helper method to retry a given cmd.
90      *
91      * @param device testing device.
92      * @param cmd the command string to be executed on device.
93      * @param validation_cmd the validation command string to be executed on device.
94      * @param predicate function that checks the exit condition.
95      * @return true if the exit condition is satisfied, false otherwise.
96      * @throws DeviceNotAvailableException
97      */
retry(ITestDevice device, String cmd, String validation_cmd, Predicate<String> predicate)98     public boolean retry(ITestDevice device, String cmd, String validation_cmd,
99             Predicate<String> predicate) throws DeviceNotAvailableException {
100         return retry(device, cmd, validation_cmd, predicate, MAX_RETRY_COUNT);
101     }
102 
103     /**
104      * Helper method to retry a given cmd.
105      *
106      * @param device testing device.
107      * @param cmd the command string to be executed on device.
108      * @param validation_cmd the validation command string to be executed on device.
109      * @param predicate function that checks the exit condition.
110      * @param retry_count the max number of times to try
111      * @return true if the exit condition is satisfied, false otherwise.
112      * @throws DeviceNotAvailableException
113      */
retry(ITestDevice device, String cmd, String validation_cmd, Predicate<String> predicate, int retry_count)114     public boolean retry(ITestDevice device, String cmd, String validation_cmd,
115             Predicate<String> predicate, int retry_count) throws DeviceNotAvailableException {
116         for (int count = 0; count < retry_count; count++) {
117             CLog.d("Running a command: %s", cmd);
118             device.executeShellCommand(cmd);
119             if (validateCmdSuccess(device, validation_cmd, predicate)) {
120                 return true;
121             }
122         }
123         return false;
124     }
125 
126     /**
127      * Validates the device status and waits if the validation fails.
128      *
129      * @param device testing device.
130      * @param cmd the command string to be executed on device.
131      * @param predicate function that checks the exit condition.
132      * @return true if the exit condition is satisfied, false otherwise.
133      * @throws DeviceNotAvailableException
134      */
validateCmdSuccess(ITestDevice device, String cmd, Predicate<String> predicate)135     protected boolean validateCmdSuccess(ITestDevice device, String cmd,
136             Predicate<String> predicate) throws DeviceNotAvailableException {
137         if (cmd == null) {
138             CLog.w("validateCmdSuccess() called but cmd is null");
139             return false;
140         }
141         String out = device.executeShellCommand(cmd);
142         CLog.d("validating cmd output: %s", out);
143         if (out != null && predicate.test(out)) {
144             CLog.d("Exit condition satisfied.");
145             return true;
146         } else {
147             CLog.d("Exit condition not satisfied. Waiting for %s more seconds.",
148                     DELAY_BETWEEN_RETRY_IN_SECS);
149             try {
150                 if (mSleeper != null) {
151                     mSleeper.sleep(DELAY_BETWEEN_RETRY_IN_SECS);
152                 } else {
153                     TimeUnit.SECONDS.sleep(DELAY_BETWEEN_RETRY_IN_SECS);
154                 }
155             } catch (InterruptedException ex) {
156                 /* pass */
157             }
158         }
159         return false;
160     }
161 
162     /**
163      * Restarts the Andriod framework and waits for the device boot completion.
164      *
165      * @param device the test device instance.
166      * @throws DeviceNotAvailableException
167      */
restartFramework(ITestDevice device)168     public void restartFramework(ITestDevice device) throws DeviceNotAvailableException {
169         device.executeShellCommand("stop");
170         setSystemProperty(device, BOOTCOMPLETE_PROP, "0");
171         device.executeShellCommand("start");
172         device.waitForDeviceAvailable(FRAMEWORK_START_TIMEOUT);
173     }
174 
175     /**
176      * Gets a sysprop from the device.
177      *
178      * @param device the test device instance.
179      * @param name the name of a sysprop.
180      * @return the device sysprop value.
181      * @throws DeviceNotAvailableException
182      */
getSystemProperty(ITestDevice device, String name)183     public String getSystemProperty(ITestDevice device, String name)
184             throws DeviceNotAvailableException {
185         CollectingOutputReceiver receiver = new CollectingOutputReceiver();
186         device.executeShellCommand(String.format("getprop %s", name), receiver);
187         return receiver.getOutput();
188     }
189 
190     /**
191      * Sets a sysprop on the device.
192      *
193      * @param device the test device instance.
194      * @param name the name of a sysprop.
195      * @param value the value of a sysprop.
196      * @throws DeviceNotAvailableException
197      */
setSystemProperty(ITestDevice device, String name, String value)198     public void setSystemProperty(ITestDevice device, String name, String value)
199             throws DeviceNotAvailableException {
200         device.executeShellCommand(String.format("setprop %s %s", name, value));
201     }
202 
203     @VisibleForTesting
setSleeper(ISleeper sleeper)204     void setSleeper(ISleeper sleeper) {
205         mSleeper = sleeper;
206     }
207 }
208