1# 2# Copyright 2016 - 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 16import logging 17import subprocess 18import threading 19 20from vts.runners.host import utils 21 22STDOUT = 'stdouts' 23STDERR = 'stderrs' 24EXIT_CODE = 'return_codes' 25 26# Exit code returned from the sub-process when timed out on Linux systems. 27EXIT_CODE_TIMEOUT_ON_LINUX = -15 28 29# Same as EXIT_CODE_TIMEOUT_ON_LINUX but on Windows systems. 30EXIT_CODE_TIMEOUT_ON_WINDOWS = -1073741510 31 32 33def _ExecuteOneShellCommandWithTimeout(cmd, 34 timeout, 35 callback_on_timeout=None, 36 *args): 37 """Executes a command with timeout. 38 39 If the process times out, this function terminates it and continues 40 waiting. 41 42 Args: 43 proc: Popen object, the process to wait for. 44 timeout: float, timeout in seconds. 45 callback_on_timeout: callable, callback function for the case 46 when the command times out. 47 args: arguments for the callback_on_timeout. 48 49 Returns: 50 tuple(string, string, int) which are stdout, stderr and return code. 51 """ 52 # On Windows, subprocess.Popen(shell=True) starts two processes, cmd.exe 53 # and the command. The Popen object represents the cmd.exe process, so 54 # calling Popen.kill() does not terminate the command. 55 # This function uses process group to ensure command termination. 56 proc = utils.start_standing_subprocess(cmd) 57 result = [] 58 59 def WaitForProcess(): 60 out, err = proc.communicate() 61 result.append((out, err, proc.returncode)) 62 63 wait_thread = threading.Thread(target=WaitForProcess) 64 wait_thread.daemon = True 65 wait_thread.start() 66 try: 67 wait_thread.join(timeout) 68 finally: 69 if proc.poll() is None: 70 utils.kill_process_group(proc) 71 if callback_on_timeout is not None: 72 if ((utils.is_on_windows() 73 and proc.returncode == EXIT_CODE_TIMEOUT_ON_WINDOWS) 74 or proc.returncode == EXIT_CODE_TIMEOUT_ON_LINUX): 75 callback_on_timeout(*args) 76 wait_thread.join() 77 78 if len(result) != 1: 79 logging.error("Unexpected command result: %s", result) 80 return "", "", proc.returncode 81 return result[0] 82 83 84def RunCommand(command): 85 """Runs a unix command and stashes the result. 86 87 Args: 88 command: the command to run. 89 90 Returns: 91 code of the subprocess. 92 """ 93 proc = subprocess.Popen( 94 command, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 95 (stdout, stderr) = proc.communicate() 96 if proc.returncode != 0: 97 logging.error('Fail to execute command: %s ' 98 '(stdout: %s\n stderr: %s\n)' % (command, stdout, 99 stderr)) 100 return proc.returncode 101 102 103def ExecuteOneShellCommand(cmd, timeout=None, callback_on_timeout=None, *args): 104 """Executes one shell command and returns (stdout, stderr, exit_code). 105 106 Args: 107 cmd: string, a shell command. 108 timeout: float, timeout in seconds. 109 callback_on_timeout: callable, callback function for the case 110 when the command times out. 111 args: arguments for the callback_on_timeout. 112 113 Returns: 114 tuple(string, string, int), containing stdout, stderr, exit_code of 115 the shell command. 116 If timeout, exit_code is -15 on Unix; -1073741510 on Windows. 117 """ 118 if timeout is None: 119 p = subprocess.Popen( 120 str(cmd), 121 shell=True, 122 stdout=subprocess.PIPE, 123 stderr=subprocess.PIPE) 124 stdout, stderr = p.communicate() 125 return (stdout, stderr, p.returncode) 126 else: 127 return _ExecuteOneShellCommandWithTimeout( 128 str(cmd), timeout, callback_on_timeout, *args) 129 130 131def ExecuteShellCommand(cmd): 132 """Execute one shell cmd or a list of shell commands. 133 134 Args: 135 cmd: string or a list of strings, shell command(s) 136 137 Returns: 138 dict{int->string}, containing stdout, stderr, exit_code of the shell command(s) 139 """ 140 if not isinstance(cmd, list): 141 cmd = [cmd] 142 143 results = [ExecuteOneShellCommand(command) for command in cmd] 144 stdout, stderr, exit_code = zip(*results) 145 return {STDOUT: stdout, STDERR: stderr, EXIT_CODE: exit_code} 146