2 # ===----------------------------------------------------------------------===##
4 # Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
5 # See https://llvm.org/LICENSE.txt for license information.
6 # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
8 # ===----------------------------------------------------------------------===##
10 """adb_run.py is a utility for running a libc++ test program via adb.
21 from typing
import List
, Tuple
24 # Sync a host file /path/to/dir/file to ${REMOTE_BASE_DIR}/run-${HASH}/dir/file.
25 REMOTE_BASE_DIR
= "/data/local/tmp/adb_run"
27 g_job_limit_socket
= None
31 def run_adb_sync_command(command
: List
[str]) -> None:
32 """Run an adb command and discard the output, unless the command fails. If
33 the command fails, dump the output instead, and exit the script with
37 sys
.stderr
.write(f
"running: {shlex.join(command)}\n")
38 proc
= subprocess
.run(command
, universal_newlines
=True,
39 stdin
=subprocess
.DEVNULL
, stdout
=subprocess
.PIPE
,
40 stderr
=subprocess
.STDOUT
, encoding
="utf-8")
41 if proc
.returncode
!= 0:
42 # adb's stdout (e.g. for adb push) should normally be discarded, but
43 # on failure, it should be shown. Print it to stderr because it's
44 # unrelated to the test program's stdout output. A common error caught
45 # here is "No space left on device".
46 sys
.stderr
.write(f
"{proc.stdout}\n"
47 f
"error: adb command exited with {proc.returncode}: "
48 f
"{shlex.join(command)}\n")
49 sys
.exit(proc
.returncode
)
52 def sync_test_dir(local_dir
: str, remote_dir
: str) -> None:
53 """Sync the libc++ test directory on the host to the remote device."""
55 # Optimization: The typical libc++ test directory has only a single
56 # *.tmp.exe file in it. In that case, skip the `mkdir` command, which is
57 # normally necessary because we don't know if the target directory already
58 # exists on the device.
59 local_files
= os
.listdir(local_dir
)
60 if len(local_files
) == 1:
61 local_file
= os
.path
.join(local_dir
, local_files
[0])
62 remote_file
= os
.path
.join(remote_dir
, local_files
[0])
63 if not os
.path
.islink(local_file
) and os
.path
.isfile(local_file
):
64 run_adb_sync_command(["adb", "push", "--sync", local_file
,
68 assert os
.path
.basename(local_dir
) == os
.path
.basename(remote_dir
)
69 run_adb_sync_command(["adb", "shell", "mkdir", "-p", remote_dir
])
70 run_adb_sync_command(["adb", "push", "--sync", local_dir
,
71 os
.path
.dirname(remote_dir
)])
74 def build_env_arg(env_args
: List
[str], prepend_path_args
: List
[Tuple
[str, str]]) -> str:
77 k
, v
= arg
.split("=", 1)
78 components
.append(f
"export {k}={shlex.quote(v)}; ")
79 for k
, v
in prepend_path_args
:
80 components
.append(f
"export {k}={shlex.quote(v)}${{{k}:+:${k}}}; ")
81 return "".join(components
)
84 def run_command(args
: argparse
.Namespace
) -> int:
85 local_dir
= args
.execdir
86 assert local_dir
.startswith("/")
87 assert not local_dir
.endswith("/")
89 # Copy each execdir to a subdir of REMOTE_BASE_DIR. Name the directory using
90 # a hash of local_dir so that concurrent adb_run invocations don't create
91 # the same intermediate parent directory. At least `adb push` has trouble
92 # with concurrent mkdir syscalls on common parent directories. (Somehow
93 # mkdir fails with EAGAIN/EWOULDBLOCK, see internal Google bug,
95 local_dir_hash
= hashlib
.sha1(local_dir
.encode()).hexdigest()
96 remote_dir
= f
"{REMOTE_BASE_DIR}/run-{local_dir_hash}/{os.path.basename(local_dir)}"
97 sync_test_dir(local_dir
, remote_dir
)
100 # Set the environment early so that PATH can be overridden. Overriding
101 # PATH is useful for:
102 # - Replacing older shell utilities with toybox (e.g. on old devices).
103 # - Adding a `bash` command that delegates to `sh` (mksh).
104 f
"{build_env_arg(args.env, args.prepend_path_env)}"
106 # Set a high oom_score_adj so that, if the test program uses too much
107 # memory, it is killed before anything else on the device. The default
108 # oom_score_adj is -1000, so a test using too much memory typically
109 # crashes the device.
110 "echo 1000 >/proc/self/oom_score_adj; "
112 # If we're running as root, switch to the shell user. The libc++
113 # filesystem tests require running without root permissions. Some x86
114 # emulator devices (before Android N) do not have a working `adb unroot`
115 # and always run as root. Non-debug builds typically lack `su` and only
116 # run as the shell user.
118 # Some libc++ tests create temporary files in the working directory,
119 # which might be owned by root. Before switching to shell, make the
120 # cwd writable (and readable+executable) to every user.
123 # - Avoid "id -u" because it wasn't supported until Android M.
124 # - The `env` and `which` commands were also added in Android M.
125 # - Starting in Android M, su from root->shell resets PATH, so we need
126 # to modify it again in the new environment.
127 # - Avoid chmod's "a+rwx" syntax because it's not supported until
129 # - Defining this function allows specifying the arguments to the test
130 # program (i.e. "$@") only once.
131 "run_without_root() {"
134 " *\"uid=0(root)\"*)"
135 " if command -v env >/dev/null; then"
136 " su shell \"$(command -v env)\" PATH=\"$PATH\" \"$@\";"
145 # Older versions of Bionic limit the length of argv[0] to 127 bytes
146 # (SOINFO_NAME_LEN-1), and the path to libc++ tests tend to exceed this
147 # limit. Changing the working directory works around this limit. The limit
148 # is increased to 4095 (PATH_MAX-1) in Android M (API 23).
149 command_line
= [arg
.replace(local_dir
+ "/", "./") for arg
in args
.command
]
151 # Prior to the adb feature "shell_v2" (added in Android N), `adb shell`
152 # always created a pty:
153 # - This merged stdout and stderr together.
154 # - The pty converts LF to CRLF.
155 # - The exit code of the shell command wasn't propagated.
156 # Work around all three limitations, unless "shell_v2" is present.
157 proc
= subprocess
.run(["adb", "features"], check
=True,
158 stdin
=subprocess
.DEVNULL
, stdout
=subprocess
.PIPE
,
160 adb_features
= set(proc
.stdout
.strip().split())
161 has_shell_v2
= "shell_v2" in adb_features
163 adb_shell_command
+= (
164 f
"cd {remote_dir} && run_without_root {shlex.join(command_line)}"
167 adb_shell_command
+= (
170 f
" cd {remote_dir} && run_without_root {shlex.join(command_line)};"
171 f
" echo -n __libcxx_adb_exit__=$?"
174 f
"echo -n __libcxx_adb_stdout__\"$stdout\""
177 adb_command_line
= ["adb", "shell", adb_shell_command
]
179 sys
.stderr
.write(f
"running: {shlex.join(adb_command_line)}\n")
182 proc
= subprocess
.run(adb_command_line
, shell
=False, check
=False,
184 return proc
.returncode
186 proc
= subprocess
.run(adb_command_line
, shell
=False, check
=False,
187 stdout
=subprocess
.PIPE
, stderr
=subprocess
.STDOUT
,
189 # The old `adb shell` mode used a pty, which converted LF to CRLF.
191 output
= proc
.stdout
.replace("\r\n", "\n")
194 sys
.stderr
.write(f
"error: adb failed:\n"
195 f
" command: {shlex.join(adb_command_line)}\n"
196 f
" output: {output}\n")
197 return proc
.returncode
199 match
= re
.match(r
"(.*)__libcxx_adb_stdout__(.*)__libcxx_adb_exit__=(\d+)$",
202 sys
.stderr
.write(f
"error: could not parse adb output:\n"
203 f
" command: {shlex.join(adb_command_line)}\n"
204 f
" output: {output}\n")
207 sys
.stderr
.write(match
.group(1))
208 sys
.stdout
.write(match
.group(2))
209 return int(match
.group(3))
212 def connect_to_job_limiter_server(sock_addr
: str) -> None:
213 sock
= socket
.socket(socket
.AF_UNIX
, socket
.SOCK_STREAM
)
216 sock
.connect(sock_addr
)
217 except (FileNotFoundError
, ConnectionRefusedError
) as e
:
218 # Copying-and-pasting an adb_run.py command-line from a lit test failure
219 # is likely to fail because the socket no longer exists (or is
220 # inactive), so just give a warning.
221 sys
.stderr
.write(f
"warning: could not connect to {sock_addr}: {e}\n")
224 # The connect call can succeed before the server has called accept, because
225 # of the listen backlog, so wait for the server to send a byte.
228 # Keep the socket open until this process ends, then let the OS close the
229 # connection automatically.
230 global g_job_limit_socket
231 g_job_limit_socket
= sock
235 """Main function (pylint wants this docstring)."""
236 parser
= argparse
.ArgumentParser()
237 parser
.add_argument("--execdir", type=str, required
=True)
238 parser
.add_argument("--env", type=str, required
=False, action
="append",
239 default
=[], metavar
="NAME=VALUE")
240 parser
.add_argument("--prepend-path-env", type=str, nargs
=2, required
=False,
241 action
="append", default
=[],
242 metavar
=("NAME", "PATH"))
243 parser
.add_argument("--job-limit-socket")
244 parser
.add_argument("--verbose", "-v", default
=False, action
="store_true")
245 parser
.add_argument("command", nargs
=argparse
.ONE_OR_MORE
)
246 args
= parser
.parse_args()
249 g_verbose
= args
.verbose
250 if args
.job_limit_socket
is not None:
251 connect_to_job_limiter_server(args
.job_limit_socket
)
252 return run_command(args
)
255 if __name__
== '__main__':