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 static com.android.tradefed.targetprep.UserHelper.RUN_TESTS_AS_USER_KEY; 20 21 import com.android.annotations.VisibleForTesting; 22 import com.android.tradefed.config.Option; 23 import com.android.tradefed.config.OptionClass; 24 import com.android.tradefed.device.BackgroundDeviceAction; 25 import com.android.tradefed.device.CollectingOutputReceiver; 26 import com.android.tradefed.device.DeviceNotAvailableException; 27 import com.android.tradefed.device.ITestDevice; 28 import com.android.tradefed.invoker.TestInformation; 29 import com.android.tradefed.log.LogUtil.CLog; 30 import com.android.tradefed.result.error.DeviceErrorIdentifier; 31 import com.android.tradefed.util.CommandResult; 32 import com.android.tradefed.util.CommandStatus; 33 import com.android.tradefed.util.RunUtil; 34 35 import java.util.ArrayList; 36 import java.util.HashMap; 37 import java.util.List; 38 import java.util.Map; 39 import java.util.concurrent.TimeUnit; 40 41 @OptionClass(alias = "run-command") 42 public class RunCommandTargetPreparer extends BaseTargetPreparer { 43 44 @Option(name = "run-command", description = "adb shell command to run") 45 private List<String> mCommands = new ArrayList<String>(); 46 47 @Option(name = "run-bg-command", description = "Command to run repeatedly in the" 48 + " device background. Can be repeated to run multiple commands" 49 + " in the background.") 50 private List<String> mBgCommands = new ArrayList<String>(); 51 52 @Option(name = "hide-bg-output", description = "if true, don't log background command output") 53 private boolean mHideBgOutput = false; 54 55 @Option(name = "teardown-command", description = "adb shell command to run at teardown time") 56 private List<String> mTeardownCommands = new ArrayList<String>(); 57 58 @Option(name = "delay-after-commands", 59 description = "Time to delay after running commands, in msecs") 60 private long mDelayMsecs = 0; 61 62 @Option(name = "run-command-timeout", 63 description = "Timeout for execute shell command", 64 isTimeVal = true) 65 private long mRunCmdTimeout = 0; 66 67 @Option( 68 name = "teardown-command-timeout", 69 description = "Timeout for execute shell teardown command", 70 isTimeVal = true) 71 private long mTeardownCmdTimeout = 0; 72 73 @Option(name = "throw-if-cmd-fail", description = "Whether or not to throw if a command fails") 74 private boolean mThrowIfFailed = false; 75 76 @Option( 77 name = "log-command-output", 78 description = "Whether or not to always log the commands output") 79 private boolean mLogOutput = false; 80 81 @Option( 82 name = "test-user-token", 83 description = 84 "When set, that token will be replaced by the id of the user running the test." 85 + " For example, if it's set as %TEST_USER%, a command that uses it could" 86 + " be written as something like 'cmd self-destruct --user %TEST_USER%'.") 87 private String mTestUserToken = ""; 88 89 private Map<BackgroundDeviceAction, CollectingOutputReceiver> mBgDeviceActionsMap = 90 new HashMap<>(); 91 92 private final List<String> mExecutedCommands = new ArrayList<>(); 93 94 private String mTestUser; 95 96 /** {@inheritDoc} */ 97 @Override setUp(TestInformation testInfo)98 public void setUp(TestInformation testInfo) 99 throws TargetSetupError, DeviceNotAvailableException { 100 ITestDevice device = getDevice(testInfo); 101 102 if (!mTestUserToken.isEmpty()) { 103 mTestUser = testInfo.properties().get(RUN_TESTS_AS_USER_KEY); 104 if (mTestUser == null || mTestUser.isEmpty()) { 105 // Ideally it should be just "cur", but not all commands support that 106 mTestUser = String.valueOf(device.getCurrentUser()); 107 } 108 CLog.d("Will replace '%s' by '%s' on all commands", mTestUserToken, mTestUser); 109 } 110 111 for (String rawCmd : mBgCommands) { 112 String bgCmd = resolveCommand(rawCmd); 113 mExecutedCommands.add(bgCmd); 114 CollectingOutputReceiver receiver = new CollectingOutputReceiver(); 115 BackgroundDeviceAction mBgDeviceAction = 116 new BackgroundDeviceAction(bgCmd, bgCmd, device, receiver, 0); 117 mBgDeviceAction.start(); 118 mBgDeviceActionsMap.put(mBgDeviceAction, receiver); 119 } 120 121 for (String rawCmd : mCommands) { 122 String cmd = resolveCommand(rawCmd); 123 mExecutedCommands.add(cmd); 124 CommandResult result; 125 // Shell v2 with command status checks 126 if (mRunCmdTimeout > 0) { 127 result = 128 device.executeShellV2Command(cmd, mRunCmdTimeout, TimeUnit.MILLISECONDS, 0); 129 } else { 130 result = device.executeShellV2Command(cmd); 131 } 132 // Ensure the command ran successfully. 133 if (!CommandStatus.SUCCESS.equals(result.getStatus())) { 134 if (mThrowIfFailed) { 135 throw new TargetSetupError( 136 String.format( 137 "Failed to run '%s' without error. stdout: '%s'\nstderr: '%s'", 138 cmd, result.getStdout(), result.getStderr()), 139 device.getDeviceDescriptor(), 140 DeviceErrorIdentifier.SHELL_COMMAND_ERROR); 141 } else { 142 CLog.d( 143 "cmd: '%s' failed, returned:\nstdout:%s\nstderr:%s", 144 cmd, result.getStdout(), result.getStderr()); 145 } 146 } else if (mLogOutput) { 147 CLog.d( 148 "cmd: '%s', returned:\nstdout:%s\nstderr:%s", 149 cmd, result.getStdout(), result.getStderr()); 150 } 151 } 152 153 if (mDelayMsecs > 0) { 154 CLog.d("Sleeping %d msecs on device %s", mDelayMsecs, device.getSerialNumber()); 155 RunUtil.getDefault().sleep(mDelayMsecs); 156 } 157 } 158 159 @Override tearDown(TestInformation testInfo, Throwable e)160 public void tearDown(TestInformation testInfo, Throwable e) throws DeviceNotAvailableException { 161 for (Map.Entry<BackgroundDeviceAction, CollectingOutputReceiver> bgAction : 162 mBgDeviceActionsMap.entrySet()) { 163 if (!mHideBgOutput) { 164 CLog.d("Background command output : %s", bgAction.getValue().getOutput()); 165 } 166 bgAction.getKey().cancel(); 167 } 168 if (e instanceof DeviceNotAvailableException) { 169 CLog.e("Skipping command teardown since exception was DeviceNotAvailable"); 170 return; 171 } 172 for (String rawCmd : mTeardownCommands) { 173 String cmd = resolveCommand(rawCmd); 174 try { 175 CommandResult result; 176 if (mTeardownCmdTimeout > 0) { 177 result = 178 getDevice(testInfo) 179 .executeShellV2Command( 180 cmd, mTeardownCmdTimeout, TimeUnit.MILLISECONDS, 0); 181 } else { 182 result = getDevice(testInfo).executeShellV2Command(cmd); 183 } 184 if (!CommandStatus.SUCCESS.equals(result.getStatus())) { 185 CLog.d( 186 "tearDown cmd: '%s' failed, returned:\nstdout:%s\nstderr:%s", 187 cmd, result.getStdout(), result.getStderr()); 188 } else if (mLogOutput) { 189 CLog.d( 190 "tearDown cmd: '%s', returned:\nstdout:%s\nstderr:%s", 191 cmd, result.getStdout(), result.getStderr()); 192 } 193 } catch (TargetSetupError tse) { 194 CLog.e(tse); 195 } 196 } 197 } 198 199 /** Add a command that will be run by the preparer. */ addRunCommand(String cmd)200 public final void addRunCommand(String cmd) { 201 mCommands.add(cmd); 202 } 203 204 @VisibleForTesting getCommands()205 public List<String> getCommands() { 206 return mCommands; 207 } 208 209 @VisibleForTesting getExecutedCommands()210 public List<String> getExecutedCommands() { 211 return mExecutedCommands; 212 } 213 214 /** 215 * Returns the device to apply the preparer on. 216 * 217 * @param testInfo 218 * @return The device to apply the preparer on. 219 * @throws TargetSetupError 220 */ getDevice(TestInformation testInfo)221 protected ITestDevice getDevice(TestInformation testInfo) throws TargetSetupError { 222 return testInfo.getDevice(); 223 } 224 resolveCommand(String rawCmd)225 private String resolveCommand(String rawCmd) { 226 return mTestUser == null ? rawCmd : rawCmd.replace(mTestUserToken, mTestUser); 227 } 228 } 229 230