blob: 6071f145fe1d6ee1e85b80b050f57b82fbc8dd84 [file] [log] [blame]
Yan Wangfb9bdd82019-06-20 15:16:22 -07001#!/usr/bin/env python3
2#
3# Copyright 2019, The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17"""Helper util libraries for command line operations."""
18
19import asyncio
20import sys
21import time
22from typing import Tuple, Optional, List
23
24import lib.print_utils as print_utils
25
26TIMEOUT = 50
27SIMULATE = False
28
Yan Wangc50d0fc2019-07-19 11:30:23 -070029def run_command_nofail(cmd: List[str], **kwargs) -> None:
30 """Runs cmd list with default timeout.
31
32 Throws exception if the execution fails.
33 """
34 my_kwargs = {"timeout": TIMEOUT, "shell": False, "simulate": False}
35 my_kwargs.update(kwargs)
36 passed, out = execute_arbitrary_command(cmd, **my_kwargs)
37 if not passed:
38 raise RuntimeError(
39 "Failed to execute %s (kwargs=%s), output=%s" % (cmd, kwargs, out))
40
Yan Wangfb9bdd82019-06-20 15:16:22 -070041def run_adb_shell_command(cmd: str) -> Tuple[bool, str]:
42 """Runs command using adb shell.
43
44 Returns:
45 A tuple of running status (True=succeeded, False=failed or timed out) and
46 std output (string contents of stdout with trailing whitespace removed).
47 """
48 return run_shell_command('adb shell "{}"'.format(cmd))
49
50def run_shell_func(script_path: str,
51 func: str,
52 args: List[str]) -> Tuple[bool, str]:
53 """Runs shell function with default timeout.
54
55 Returns:
56 A tuple of running status (True=succeeded, False=failed or timed out) and
57 std output (string contents of stdout with trailing whitespace removed) .
58 """
Yan Wang90bc5ac2019-06-28 14:56:41 -070059 if args:
60 cmd = 'bash -c "source {script_path}; {func} {args}"'.format(
61 script_path=script_path,
62 func=func,
63 args=' '.join("'{}'".format(arg) for arg in args))
64 else:
65 cmd = 'bash -c "source {script_path}; {func}"'.format(
66 script_path=script_path,
67 func=func)
68
Yan Wangfb9bdd82019-06-20 15:16:22 -070069 print_utils.debug_print(cmd)
70 return run_shell_command(cmd)
71
72def run_shell_command(cmd: str) -> Tuple[bool, str]:
73 """Runs shell command with default timeout.
74
75 Returns:
76 A tuple of running status (True=succeeded, False=failed or timed out) and
77 std output (string contents of stdout with trailing whitespace removed) .
78 """
79 return execute_arbitrary_command([cmd],
80 TIMEOUT,
81 shell=True,
82 simulate=SIMULATE)
83
84def execute_arbitrary_command(cmd: List[str],
85 timeout: int,
86 shell: bool,
87 simulate: bool) -> Tuple[bool, str]:
88 """Run arbitrary shell command with default timeout.
89
90 Mostly copy from
91 frameworks/base/startop/scripts/app_startup/app_startup_runner.py.
92
93 Args:
94 cmd: list of cmd strings.
95 timeout: the time limit of running cmd.
96 shell: indicate if the cmd is a shell command.
97 simulate: if it's true, do not run the command and assume the running is
98 successful.
99
100 Returns:
101 A tuple of running status (True=succeeded, False=failed or timed out) and
102 std output (string contents of stdout with trailing whitespace removed) .
103 """
104 if simulate:
105 print(cmd)
106 return True, ''
107
108 print_utils.debug_print('[EXECUTE]', cmd)
109 # block until either command finishes or the timeout occurs.
110 loop = asyncio.get_event_loop()
111
112 (return_code, script_output) = loop.run_until_complete(
113 _run_command(*cmd, shell=shell, timeout=timeout))
114
115 script_output = script_output.decode() # convert bytes to str
116
117 passed = (return_code == 0)
118 print_utils.debug_print('[$?]', return_code)
119 if not passed:
120 print('[FAILED, code:%s]' % (return_code), script_output, file=sys.stderr)
121
122 return passed, script_output.rstrip()
123
124async def _run_command(*args: List[str],
125 shell: bool = False,
126 timeout: Optional[int] = None) -> Tuple[int, bytes]:
127 if shell:
128 process = await asyncio.create_subprocess_shell(
129 *args, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.STDOUT)
130 else:
131 process = await asyncio.create_subprocess_exec(
132 *args, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.STDOUT)
133
134 script_output = b''
135
136 print_utils.debug_print('[PID]', process.pid)
137
138 timeout_remaining = timeout
139 time_started = time.time()
140
141 # read line (sequence of bytes ending with b'\n') asynchronously
142 while True:
143 try:
144 line = await asyncio.wait_for(process.stdout.readline(),
145 timeout_remaining)
146 print_utils.debug_print('[STDOUT]', line)
147 script_output += line
148
149 if timeout_remaining:
150 time_elapsed = time.time() - time_started
151 timeout_remaining = timeout - time_elapsed
152 except asyncio.TimeoutError:
153 print_utils.debug_print('[TIMEDOUT] Process ', process.pid)
154
155 print_utils.debug_print('[TIMEDOUT] Sending SIGTERM.')
156 process.terminate()
157
158 # 5 second timeout for process to handle SIGTERM nicely.
159 try:
160 (remaining_stdout,
161 remaining_stderr) = await asyncio.wait_for(process.communicate(), 5)
162 script_output += remaining_stdout
163 except asyncio.TimeoutError:
164 print_utils.debug_print('[TIMEDOUT] Sending SIGKILL.')
165 process.kill()
166
167 # 5 second timeout to finish with SIGKILL.
168 try:
169 (remaining_stdout,
170 remaining_stderr) = await asyncio.wait_for(process.communicate(), 5)
171 script_output += remaining_stdout
172 except asyncio.TimeoutError:
173 # give up, this will leave a zombie process.
174 print_utils.debug_print('[TIMEDOUT] SIGKILL failed for process ',
175 process.pid)
176 time.sleep(100)
177
178 return -1, script_output
179 else:
180 if not line: # EOF
181 break
182
183 code = await process.wait() # wait for child process to exit
184 return code, script_output