Home | History | Annotate | Download | only in util
      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