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.compatibility.common.tradefed.build.CompatibilityBuildHelper; 20 import com.android.tradefed.build.IBuildInfo; 21 import com.android.tradefed.log.LogUtil.CLog; 22 import com.android.tradefed.util.CommandResult; 23 import com.android.tradefed.util.CommandStatus; 24 import com.android.tradefed.util.IRunUtil; 25 import com.android.tradefed.util.ProcessHelper; 26 import com.android.tradefed.util.RunInterruptedException; 27 import com.android.tradefed.util.RunUtil; 28 import java.io.File; 29 import java.io.FileNotFoundException; 30 import java.io.IOException; 31 32 /** 33 * A helper class for executing VTS python scripts. 34 */ 35 public class VtsPythonRunnerHelper { 36 static final String OS_NAME = "os.name"; 37 static final String WINDOWS = "Windows"; // Not officially supported OS. 38 static final String LINUX = "Linux"; 39 static final String PYTHONPATH = "PYTHONPATH"; 40 static final String VIRTUAL_ENV_PATH = "VIRTUALENVPATH"; 41 static final String VTS = "vts"; 42 static final String ANDROID_BUILD_TOP = "ANDROID_BUILD_TOP"; 43 static final long TEST_ABORT_TIMEOUT_MSECS = 1000 * 15; 44 45 private IBuildInfo mBuildInfo = null; 46 private String mPythonVersion = ""; 47 private IRunUtil mRunUtil = null; 48 private String mPythonPath = null; 49 50 public VtsPythonRunnerHelper(IBuildInfo buildInfo) { 51 mBuildInfo = buildInfo; 52 mPythonPath = buildPythonPath(); 53 mRunUtil = new RunUtil(); 54 mRunUtil.setEnvVariable(PYTHONPATH, mPythonPath); 55 mRunUtil.setEnvVariable("VTS", "1"); 56 } 57 58 /** 59 * Create a {@link ProcessHelper} from mRunUtil. 60 * 61 * @param cmd the command to run. 62 * @throws IOException if fails to start Process. 63 */ 64 protected ProcessHelper createProcessHelper(String[] cmd) throws IOException { 65 return new ProcessHelper(mRunUtil.runCmdInBackground(cmd)); 66 } 67 68 /** 69 * Run VTS Python runner and handle interrupt from TradeFed. 70 * 71 * @param cmd: the command to start VTS Python runner. 72 * @param commandResult: the object containing the command result. 73 * @param timeout: command timeout value. 74 * @return null if the command terminates or times out; a message string if the command is 75 * interrupted by TradeFed. 76 */ 77 public String runPythonRunner(String[] cmd, CommandResult commandResult, long timeout) { 78 ProcessHelper process; 79 try { 80 process = createProcessHelper(cmd); 81 } catch (IOException e) { 82 CLog.e(e); 83 commandResult.setStatus(CommandStatus.EXCEPTION); 84 commandResult.setStdout(""); 85 commandResult.setStderr(""); 86 return null; 87 } 88 89 String interruptMessage; 90 try { 91 CommandStatus commandStatus; 92 try { 93 commandStatus = process.waitForProcess(timeout); 94 interruptMessage = null; 95 } catch (RunInterruptedException e) { 96 CLog.e("Python process is interrupted."); 97 commandStatus = CommandStatus.TIMED_OUT; 98 interruptMessage = (e.getMessage() != null ? e.getMessage() : ""); 99 } 100 if (process.isRunning()) { 101 CLog.e("Cancel Python process and wait %d seconds.", 102 TEST_ABORT_TIMEOUT_MSECS / 1000); 103 try { 104 process.closeStdin(); 105 // Wait for the process to clean up and ignore the CommandStatus. 106 // Propagate RunInterruptedException if this is interrupted again. 107 process.waitForProcess(TEST_ABORT_TIMEOUT_MSECS); 108 } catch (IOException e) { 109 CLog.e("Fail to cancel Python process."); 110 } 111 } 112 commandResult.setStatus(commandStatus); 113 } finally { 114 process.cleanUp(); 115 } 116 commandResult.setStdout(process.getStdout()); 117 commandResult.setStderr(process.getStderr()); 118 return interruptMessage; 119 } 120 121 /** 122 * This method returns whether the OS is Windows. 123 */ 124 private static boolean isOnWindows() { 125 return System.getProperty(OS_NAME).contains(WINDOWS); 126 } 127 128 /** 129 * This method builds the python path based on the following values: 130 * 1) System environment $PYTHONPATH. 131 * 2) VTS testcase root directory (e.g. android-vts/testcases/). 132 * 3) Value passed in buildInfo attribute PYTHONPATH (for tests started 133 * using PythonVirtualenvPreparer). 134 * 4) System environment $ANDROID_BUILD_TOP/test. 135 * 136 * @throws RuntimeException. 137 */ 138 private String buildPythonPath() throws RuntimeException { 139 StringBuilder sb = new StringBuilder(); 140 String separator = File.pathSeparator; 141 if (System.getenv(PYTHONPATH) != null) { 142 sb.append(separator); 143 sb.append(System.getenv(PYTHONPATH)); 144 } 145 146 // to get the path for android-vts/testcases/ which keeps the VTS python code under vts. 147 if (mBuildInfo != null) { 148 CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mBuildInfo); 149 150 File testDir = null; 151 try { 152 testDir = buildHelper.getTestsDir(); 153 } catch (FileNotFoundException e) { 154 /* pass */ 155 } 156 if (testDir != null) { 157 sb.append(separator); 158 String testCaseDataDir = testDir.getAbsolutePath(); 159 sb.append(testCaseDataDir); 160 } else if (mBuildInfo.getFile(VTS) != null) { 161 sb.append(separator); 162 sb.append(mBuildInfo.getFile(VTS).getAbsolutePath()).append("/.."); 163 } 164 165 // for when one uses PythonVirtualenvPreparer. 166 if (mBuildInfo.getFile(PYTHONPATH) != null) { 167 sb.append(separator); 168 sb.append(mBuildInfo.getFile(PYTHONPATH).getAbsolutePath()); 169 } 170 } 171 172 if (System.getenv(ANDROID_BUILD_TOP) != null) { 173 sb.append(separator); 174 sb.append(System.getenv(ANDROID_BUILD_TOP)).append("/test"); 175 } 176 if (sb.length() == 0) { 177 throw new RuntimeException("Could not find python path on host machine"); 178 } 179 return sb.substring(1); 180 } 181 182 /** 183 * This method gets the python binary. 184 */ 185 public String getPythonBinary() { 186 boolean isWindows = isOnWindows(); 187 String python = (isWindows ? "python.exe" : "python" + mPythonVersion); 188 if (mBuildInfo != null) { 189 File venvDir = mBuildInfo.getFile(VIRTUAL_ENV_PATH); 190 if (venvDir != null) { 191 String binDir = (isWindows ? "Scripts" : "bin"); 192 File pythonBinaryFile = 193 new File(venvDir.getAbsolutePath(), binDir + File.separator + python); 194 String pythonBinPath = pythonBinaryFile.getAbsolutePath(); 195 if (pythonBinaryFile.exists()) { 196 CLog.i("Python path " + pythonBinPath + ".\n"); 197 return pythonBinPath; 198 } 199 CLog.e(python + " doesn't exist under the " 200 + "created virtualenv dir (" + pythonBinPath + ").\n"); 201 } else { 202 CLog.e(VIRTUAL_ENV_PATH + " not available in BuildInfo. " 203 + "Please use VtsPythonVirtualenvPreparer tartget preparer.\n"); 204 } 205 } 206 207 CommandResult c = mRunUtil.runTimedCmd(1000, (isWindows ? "where" : "which"), python); 208 String pythonBin = c.getStdout().trim(); 209 if (pythonBin.length() == 0) { 210 throw new RuntimeException("Could not find python binary on host " 211 + "machine"); 212 } 213 return pythonBin; 214 } 215 216 /** 217 * Gets mPythonPath. 218 */ 219 public String getPythonPath() { 220 return mPythonPath; 221 } 222 223 /** 224 * Sets mPythonVersion. 225 */ 226 public void setPythonVersion(String pythonVersion) { 227 mPythonVersion = pythonVersion; 228 } 229 230 /** 231 * Sets mRunUtil, should only be used in tests. 232 */ 233 public void setRunUtil(IRunUtil runUtil) { 234 mRunUtil = runUtil; 235 } 236 } 237