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.tradefed.build.IBuildInfo; 20 import com.android.tradefed.log.LogUtil.CLog; 21 import com.android.tradefed.targetprep.VtsPythonVirtualenvPreparer; 22 23 import java.io.File; 24 import java.io.IOException; 25 26 /** 27 * A helper class for executing VTS python scripts. 28 */ 29 public class VtsPythonRunnerHelper { 30 // The timeout for the runner's teardown prodedure. 31 public static final long TEST_ABORT_TIMEOUT_MSECS = 1000 * 40; 32 33 static final String PATH = "PATH"; 34 static final String PYTHONHOME = "PYTHONHOME"; 35 static final String VTS = "vts"; 36 37 // Python virtual environment root path 38 private File mVirtualenvPath; 39 protected IRunUtil mRunUtil; 40 VtsPythonRunnerHelper(IBuildInfo buildInfo, File workingDir)41 public VtsPythonRunnerHelper(IBuildInfo buildInfo, File workingDir) { 42 this(buildInfo.getBuildAttributes().get(VtsPythonVirtualenvPreparer.VIRTUAL_ENV), 43 workingDir); 44 } 45 VtsPythonRunnerHelper(String virtualEnvPath, File workingDir)46 public VtsPythonRunnerHelper(String virtualEnvPath, File workingDir) { 47 this(virtualEnvPath == null ? null : new File(virtualEnvPath), workingDir); 48 } 49 VtsPythonRunnerHelper(File virtualEnvPath, File workingDir)50 public VtsPythonRunnerHelper(File virtualEnvPath, File workingDir) { 51 mVirtualenvPath = virtualEnvPath; 52 mRunUtil = new RunUtil(); 53 activateVirtualenv(mRunUtil, getPythonVirtualEnv()); 54 mRunUtil.setWorkingDir(workingDir); 55 } 56 57 /** 58 * Create a {@link ProcessHelper} from mRunUtil. 59 * 60 * @param cmd the command to run. 61 * @throws IOException if fails to start Process. 62 */ createProcessHelper(String[] cmd)63 protected ProcessHelper createProcessHelper(String[] cmd) throws IOException { 64 return new ProcessHelper(mRunUtil.runCmdInBackground(cmd)); 65 } 66 67 /** 68 * Run VTS Python runner and handle interrupt from TradeFed. 69 * 70 * @param cmd the command to start VTS Python runner. 71 * @param commandResult the object containing the command result. 72 * @param timeout command timeout value. 73 * @return null if the command terminates or times out; a message string if the command is 74 * interrupted by TradeFed. 75 */ runPythonRunner(String[] cmd, CommandResult commandResult, long timeout)76 public String runPythonRunner(String[] cmd, CommandResult commandResult, long timeout) { 77 ProcessHelper process; 78 try { 79 process = createProcessHelper(cmd); 80 } catch (IOException e) { 81 CLog.e(e); 82 commandResult.setStatus(CommandStatus.EXCEPTION); 83 commandResult.setStdout(""); 84 commandResult.setStderr(""); 85 return null; 86 } 87 88 String interruptMessage; 89 try { 90 CommandStatus commandStatus; 91 try { 92 commandStatus = process.waitForProcess(timeout); 93 interruptMessage = null; 94 } catch (RunInterruptedException e) { 95 CLog.e("Python process is interrupted."); 96 commandStatus = CommandStatus.TIMED_OUT; 97 interruptMessage = (e.getMessage() != null ? e.getMessage() : ""); 98 } 99 if (process.isRunning()) { 100 CLog.e("Cancel Python process and wait %d seconds.", 101 TEST_ABORT_TIMEOUT_MSECS / 1000); 102 try { 103 process.closeStdin(); 104 // Wait for the process to clean up and ignore the CommandStatus. 105 // Propagate RunInterruptedException if this is interrupted again. 106 process.waitForProcess(TEST_ABORT_TIMEOUT_MSECS); 107 } catch (IOException e) { 108 CLog.e("Fail to cancel Python process."); 109 } 110 } 111 commandResult.setStatus(commandStatus); 112 } finally { 113 process.cleanUp(); 114 } 115 commandResult.setStdout(process.getStdout()); 116 commandResult.setStderr(process.getStderr()); 117 return interruptMessage; 118 } 119 120 /** 121 * Gets python bin directory path. 122 * 123 * This method will check the directory existence. 124 * 125 * @return python bin directory; null if not exist. 126 */ getPythonBinDir(String virtualenvPath)127 public static String getPythonBinDir(String virtualenvPath) { 128 if (virtualenvPath == null) { 129 return null; 130 } 131 String binDirName = EnvUtil.isOnWindows() ? "Scripts" : "bin"; 132 File res = new File(virtualenvPath, binDirName); 133 if (!res.exists()) { 134 return null; 135 } 136 return res.getAbsolutePath(); 137 } 138 139 /** 140 * Get python virtualenv path 141 * @return virutalenv path. null if doesn't exist 142 */ getPythonVirtualEnv()143 public String getPythonVirtualEnv() { 144 if (mVirtualenvPath == null) { 145 return null; 146 } 147 return mVirtualenvPath.getAbsolutePath(); 148 } 149 150 /** 151 * Activate virtualenv for a RunUtil. 152 * 153 * This method will check for python bin directory existence 154 * 155 * @param runUtil 156 * @param virtualenvPath 157 */ activateVirtualenv(IRunUtil runUtil, String virtualenvPath)158 public static void activateVirtualenv(IRunUtil runUtil, String virtualenvPath) { 159 String pythonBinDir = getPythonBinDir(virtualenvPath); 160 if (pythonBinDir == null || !new File(pythonBinDir).exists()) { 161 CLog.e("Invalid python virtualenv path. Using python from system path."); 162 } else { 163 String separater = EnvUtil.isOnWindows() ? ";" : ":"; 164 runUtil.setEnvVariable(PATH, pythonBinDir + separater + System.getenv().get(PATH)); 165 runUtil.setEnvVariable(VtsPythonVirtualenvPreparer.VIRTUAL_ENV, virtualenvPath); 166 runUtil.unsetEnvVariable(PYTHONHOME); 167 } 168 } 169 } 170