1 /*
2  * Copyright (C) 2012 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.targetprep;
18 
19 import com.android.tradefed.config.Option;
20 import com.android.tradefed.config.OptionClass;
21 import com.android.tradefed.device.BackgroundDeviceAction;
22 import com.android.tradefed.device.CollectingOutputReceiver;
23 import com.android.tradefed.device.DeviceNotAvailableException;
24 import com.android.tradefed.device.ITestDevice;
25 import com.android.tradefed.invoker.TestInformation;
26 import com.android.tradefed.log.LogUtil.CLog;
27 import com.android.tradefed.result.error.DeviceErrorIdentifier;
28 import com.android.tradefed.util.CommandResult;
29 import com.android.tradefed.util.CommandStatus;
30 import com.android.tradefed.util.RunUtil;
31 
32 import java.util.ArrayList;
33 import java.util.HashMap;
34 import java.util.List;
35 import java.util.Map;
36 import java.util.concurrent.TimeUnit;
37 
38 @OptionClass(alias = "run-command")
39 public class RunCommandTargetPreparer extends BaseTargetPreparer {
40 
41     @Option(name = "run-command", description = "adb shell command to run")
42     private List<String> mCommands = new ArrayList<String>();
43 
44     @Option(name = "run-bg-command", description = "Command to run repeatedly in the"
45             + " device background. Can be repeated to run multiple commands"
46             + " in the background.")
47     private List<String> mBgCommands = new ArrayList<String>();
48 
49     @Option(name = "hide-bg-output", description = "if true, don't log background command output")
50     private boolean mHideBgOutput = false;
51 
52     @Option(name = "teardown-command", description = "adb shell command to run at teardown time")
53     private List<String> mTeardownCommands = new ArrayList<String>();
54 
55     @Option(name = "delay-after-commands",
56             description = "Time to delay after running commands, in msecs")
57     private long mDelayMsecs = 0;
58 
59     @Option(name = "run-command-timeout",
60             description = "Timeout for execute shell command",
61             isTimeVal = true)
62     private long mRunCmdTimeout = 0;
63 
64     @Option(name = "throw-if-cmd-fail", description = "Whether or not to throw if a command fails")
65     private boolean mThrowIfFailed = false;
66 
67     private Map<BackgroundDeviceAction, CollectingOutputReceiver> mBgDeviceActionsMap =
68             new HashMap<>();
69 
70     /** {@inheritDoc} */
71     @Override
setUp(TestInformation testInfo)72     public void setUp(TestInformation testInfo)
73             throws TargetSetupError, DeviceNotAvailableException {
74         ITestDevice device = getDevice(testInfo);
75         for (String bgCmd : mBgCommands) {
76             CollectingOutputReceiver receiver = new CollectingOutputReceiver();
77             BackgroundDeviceAction mBgDeviceAction =
78                     new BackgroundDeviceAction(bgCmd, bgCmd, device, receiver, 0);
79             mBgDeviceAction.start();
80             mBgDeviceActionsMap.put(mBgDeviceAction, receiver);
81         }
82 
83         for (String cmd : mCommands) {
84             CommandResult result;
85             // Shell v2 with command status checks
86             if (mRunCmdTimeout > 0) {
87                 result =
88                         device.executeShellV2Command(cmd, mRunCmdTimeout, TimeUnit.MILLISECONDS, 0);
89             } else {
90                 result = device.executeShellV2Command(cmd);
91             }
92             // Ensure the command ran successfully.
93             if (!CommandStatus.SUCCESS.equals(result.getStatus())) {
94                 if (mThrowIfFailed) {
95                     throw new TargetSetupError(
96                             String.format(
97                                     "Failed to run '%s' without error. stdout: '%s'\nstderr: '%s'",
98                                     cmd, result.getStdout(), result.getStderr()),
99                             device.getDeviceDescriptor(),
100                             DeviceErrorIdentifier.SHELL_COMMAND_ERROR);
101                 } else {
102                     CLog.d(
103                             "cmd: '%s' failed, returned:\nstdout:%s\nstderr:%s",
104                             cmd, result.getStdout(), result.getStderr());
105                 }
106             }
107         }
108 
109         if (mDelayMsecs > 0) {
110             CLog.d("Sleeping %d msecs on device %s", mDelayMsecs, device.getSerialNumber());
111             RunUtil.getDefault().sleep(mDelayMsecs);
112         }
113     }
114 
115     /** {@inheritDoc} */
116     @Override
tearDown(TestInformation testInfo, Throwable e)117     public void tearDown(TestInformation testInfo, Throwable e) throws DeviceNotAvailableException {
118         for (Map.Entry<BackgroundDeviceAction, CollectingOutputReceiver> bgAction :
119                 mBgDeviceActionsMap.entrySet()) {
120             if (!mHideBgOutput) {
121                 CLog.d("Background command output : %s", bgAction.getValue().getOutput());
122             }
123             bgAction.getKey().cancel();
124         }
125         if (e instanceof DeviceNotAvailableException) {
126             CLog.e("Skipping command teardown since exception was DeviceNotAvailable");
127             return;
128         }
129         for (String cmd : mTeardownCommands) {
130             try {
131                 CommandResult result = getDevice(testInfo).executeShellV2Command(cmd);
132                 if (!CommandStatus.SUCCESS.equals(result.getStatus())) {
133                     CLog.d(
134                             "tearDown cmd: '%s' failed, returned:\nstdout:%s\nstderr:%s",
135                             cmd, result.getStdout(), result.getStderr());
136                 }
137             } catch (TargetSetupError tse) {
138                 CLog.e(tse);
139             }
140         }
141     }
142 
143     /** Add a command that will be run by the preparer. */
addRunCommand(String cmd)144     public final void addRunCommand(String cmd) {
145         mCommands.add(cmd);
146     }
147 
148     /**
149      * Returns the device to apply the preparer on.
150      *
151      * @param testInfo
152      * @return The device to apply the preparer on.
153      * @throws TargetSetupError
154      */
getDevice(TestInformation testInfo)155     protected ITestDevice getDevice(TestInformation testInfo) throws TargetSetupError {
156         return testInfo.getDevice();
157     }
158 }
159 
160