xref: /aosp_15_r20/art/test/run-test (revision 795d594fd825385562da6b089ea9b2033f3abf5a)
1#!/usr/bin/env python3
2#
3# Copyright (C) 2007 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
17import os, sys, glob, re, shutil, subprocess, shlex, resource, atexit
18import urllib.parse
19
20import default_run as default_run_module
21
22from argparse import ArgumentParser, BooleanOptionalAction
23from default_run import get_target_arch
24from fcntl import lockf, LOCK_EX, LOCK_NB
25from hashlib import sha1
26from importlib.machinery import SourceFileLoader
27from inspect import currentframe, getframeinfo, FrameInfo
28from pathlib import Path
29from pprint import pprint
30from shutil import copyfile, copytree
31from testrunner import env
32from typing import Optional, Dict, List
33from zipfile import ZipFile
34
35COLOR = (os.environ.get("LUCI_CONTEXT") == None)  # Disable colors on LUCI.
36COLOR_BLUE = '\033[94m' if COLOR else ''
37COLOR_GREEN = '\033[92m' if COLOR else ''
38COLOR_NORMAL = '\033[0m' if COLOR else ''
39COLOR_RED = '\033[91m' if COLOR else ''
40
41# Helper class which allows us to access the environment using syntax sugar.
42# E.g. `env.ANDROID_BUILD_TOP` instead of `os.environ["ANDROID_BUILD_TOP"]`.
43class Environment:
44
45  def __getattr__(self, name):
46    return os.environ.get(name)
47
48  def __setattr__(self, name, value):
49    os.environ[name] = str(value)
50
51
52# Context passed to individual tests to let them customize the behaviour.
53class RunTestContext:
54
55  def __init__(self, tmp_dir: Path, target: bool, chroot, dex_location, test_name) -> None:
56    self.env = Environment()
57    self.target = target
58    self.chroot = chroot
59    self.dex_location = dex_location
60    self.test_name = test_name
61
62    # Note: The expected path can be modified by the tests.
63    self.expected_stdout = tmp_dir / "expected-stdout.txt"
64    self.expected_stderr = tmp_dir / "expected-stderr.txt"
65
66    self.runner: List[str] = ["#!/bin/bash"]
67
68  def echo(self, text):
69    self.run(f"echo {text} > {test_stdout}")
70
71  def export(self, **env: str) -> None:
72    self.runner.append("")
73    for name, value in env.items():
74      self.runner.append(f"export {name}={value}")
75
76  # Add "runner" script command. It is not executed now.
77  # All "runner" commands are executed later via single bash call.
78  def run(self, cmd: str, check: bool=True, expected_exit_code: int=0, desc:str = None) -> None:
79    if cmd == "true":
80      return
81    cmd_esc = cmd.replace("'", r"'\''")
82    self.runner.append("")
83    self.runner.append(f"echo '{COLOR_BLUE}$$ {cmd_esc}{COLOR_NORMAL}'")
84    self.runner.append(cmd)
85
86    # Check the exit code.
87    if check:
88      caller = getframeinfo(currentframe().f_back)  # type: ignore
89      source = "{}:{}".format(Path(caller.filename).name, caller.lineno)
90      msg = f"{self.test_name} FAILED: [{source}] "
91      msg += "{} returned exit code ${{exit_code}}.".format(desc or "Command")
92      if expected_exit_code:
93        msg += f" Expected {expected_exit_code}."
94      self.runner.append(
95        f"exit_code=$?; if [ $exit_code -ne {expected_exit_code} ]; then "
96        f"echo {COLOR_RED}{msg}{COLOR_NORMAL}; exit 100; "
97        f"fi; ")
98    else:
99      self.runner.append("true; # Ignore previous exit code")
100
101  # Execute the default runner (possibly with modified arguments).
102  def default_run(self, args, **kwargs):
103    default_run_module.default_run(self, args, **kwargs)
104
105# Make unique temporary directory guarded by lock file.
106# The name is deterministic (appending suffix as needed).
107def make_tmp_dir():
108  parent = Path(os.environ.get("TMPDIR", "/tmp")) / "art" / "test"
109  parent.mkdir(parents=True, exist_ok=True)
110  args = [a for a in sys.argv[1:] if not a.startswith("--create-runner")]
111  hash = sha1((" ".join(args)).encode()).hexdigest()
112  for i in range(100):
113    tmp_dir = parent / (f"{hash[:8]}" + (f"-{i}" if i > 0 else ""))
114    lock = tmp_dir.with_suffix(".lock")  # NB: Next to the directory, not inside.
115    lock_handle = open(lock, "w")
116    try:
117      lockf(lock_handle, LOCK_EX | LOCK_NB)
118      tmp_dir.mkdir(exist_ok=True)
119      return str(tmp_dir), lock, lock_handle
120    except BlockingIOError:
121      continue
122  assert False, "Failed to create test directory"
123
124# TODO: Replace with 'def main():' (which might change variables from globals to locals)
125if True:
126  progdir = os.path.dirname(__file__)
127  oldwd = os.getcwd()
128  os.chdir(progdir)
129  PYTHON3 = os.environ.get("PYTHON3")
130  tmp_dir, tmp_dir_lock, tmp_dir_lock_handle = make_tmp_dir()
131  test_dir = Path(tmp_dir).name
132  checker = f"{progdir}/../tools/checker/checker.py"
133
134  ON_VM = env.ART_TEST_ON_VM
135  SSH_USER = env.ART_TEST_SSH_USER
136  SSH_HOST = env.ART_TEST_SSH_HOST
137  SSH_PORT = env.ART_TEST_SSH_PORT
138  SSH_CMD = env.ART_SSH_CMD
139  SCP_CMD = env.ART_SCP_CMD
140  CHROOT = env.ART_TEST_CHROOT
141  CHROOT_CMD = env.ART_CHROOT_CMD
142
143  def fail(message: str, caller:Optional[FrameInfo]=None):
144    caller = caller or getframeinfo(currentframe().f_back)  # type: ignore
145    assert caller
146    source = "{}:{}".format(Path(caller.filename).name, caller.lineno)
147    print(f"{COLOR_RED}{TEST_NAME} FAILED: [{source}] {message}{COLOR_NORMAL}",
148          file=sys.stderr)
149    sys.exit(1)
150
151  def run(cmdline: str, check=True, fail_message=None) -> subprocess.CompletedProcess:
152    print(f"{COLOR_BLUE}$ {cmdline}{COLOR_NORMAL}", flush=True)
153    proc = subprocess.run([cmdline],
154                          shell=True,
155                          executable="/bin/bash",
156                          stderr=subprocess.STDOUT)
157    if (check and proc.returncode != 0):
158      if fail_message:
159        # If we have custom fail message, exit without printing the full backtrace.
160        fail(fail_message, getframeinfo(currentframe().f_back))  # type: ignore
161      raise Exception(f"Command failed (exit code {proc.returncode})")
162    return proc
163
164  def export(env: str, value: str) -> None:
165    os.environ[env] = value
166    globals()[env] = value
167
168  def error(msg) -> None:
169    print(msg, file=sys.stderr, flush=True)
170
171  # ANDROID_BUILD_TOP is not set in a build environment.
172  ANDROID_BUILD_TOP = os.environ.get("ANDROID_BUILD_TOP")
173  if not ANDROID_BUILD_TOP:
174    export("ANDROID_BUILD_TOP", oldwd)
175
176  export("JAVA", "java")
177  export("JAVAC", "javac -g -Xlint:-options -source 1.8 -target 1.8")
178  export("PYTHON3",
179         f"{ANDROID_BUILD_TOP}/prebuilts/build-tools/path/linux-x86/python3")
180  export("RUN", f"{PYTHON3} {progdir}/etc/run-test-jar")
181  if env.ART_TEST_RUN_FROM_SOONG:
182    export("DEX_LOCATION", f"/data/local/tmp/art/test/{test_dir}")
183  else:
184    export("DEX_LOCATION", f"/data/run-test/{test_dir}")
185
186  # OUT_DIR defaults to out, and may be relative to ANDROID_BUILD_TOP.
187  # Convert it to an absolute path, since we cd into the tmp_dir to run the tests.
188  OUT_DIR = os.environ.get("OUT_DIR", "")
189  export("OUT_DIR", OUT_DIR or "out")
190  if not OUT_DIR.startswith("/"):
191    export("OUT_DIR", f"{ANDROID_BUILD_TOP}/{OUT_DIR}")
192
193# ANDROID_HOST_OUT is not set in a build environment.
194  ANDROID_HOST_OUT = os.environ.get("ANDROID_HOST_OUT")
195  if not ANDROID_HOST_OUT:
196    export("ANDROID_HOST_OUT", f"{OUT_DIR}/host/linux-x86")
197
198  info = "info.txt"
199  run_cmd = "run"
200  test_stdout = "test-stdout.txt"
201  test_stderr = "test-stderr.txt"
202  cfg_output = "graph.cfg"
203  run_checker = False
204  debug_mode = False
205  # To cause tests to fail fast, limit the file sizes created by dx, dex2oat and
206  # ART output to approximately 128MB. This should be more than sufficient
207  # for any test while still catching cases of runaway output.
208  # Set a hard limit to encourage ART developers to increase the ulimit here if
209  # needed to support a test case rather than resetting the limit in the run
210  # script for the particular test in question. Adjust this if needed for
211  # particular configurations.
212  file_ulimit = 128000
213
214  argp, opt_bool = ArgumentParser(), BooleanOptionalAction
215  argp.add_argument("--O", action='store_true',
216                    help="Run non-debug rather than debug build (off by default).")
217  argp.add_argument("--Xcompiler-option", type=str, action='append', default=[],
218                    help="Pass an option to the compiler.")
219  argp.add_argument("--runtime-option", type=str, action='append', default=[],
220                    help="Pass an option to the runtime.")
221  argp.add_argument("--debug", action='store_true',
222                    help="Wait for the default debugger to attach.")
223  argp.add_argument("--debug-agent", type=str,
224                    help="Wait for the given debugger agent to attach. Currently "
225                         "only supported on host.")
226  argp.add_argument("--debug-wrap-agent", action='store_true',
227                    help="use libwrapagentproperties and tools/libjdwp-compat.props "
228                         "to load the debugger agent specified by --debug-agent.")
229  argp.add_argument("--with-agent", type=str, action='append', default=[],
230                    help="Run the test with the given agent loaded with -agentpath:")
231  argp.add_argument("--debuggable", action='store_true',
232                    help="Whether to compile Java code for a debugger.")
233  argp.add_argument("--gdb", action='store_true',
234                    help="Run under gdb; incompatible with some tests.")
235  argp.add_argument("--gdb-dex2oat", action='store_true',
236                    help="Run dex2oat under the prebuilt gdb.")
237  argp.add_argument("--gdbserver", action='store_true',
238                    help="Start gdbserver (defaults to port :5039).")
239  argp.add_argument("--gdbserver-port", type=str,
240                    help="Start gdbserver with the given COMM (see man gdbserver).")
241  argp.add_argument("--gdbserver-bin", type=str,
242                    help="Use the given binary as gdbserver.")
243  argp.add_argument("--gdb-arg", type=str, action='append', default=[],
244                    help="Pass an option to gdb or gdbserver.")
245  argp.add_argument("--gdb-dex2oat-args", type=str,
246                    help="Pass options separated by ';' to gdb for dex2oat.")
247  argp.add_argument("--simpleperf", action='store_true',
248                    help="Wraps the dalvikvm invocation in 'simpleperf record "
249                         "and dumps stats to stdout.")
250  argp.add_argument("--interpreter", action='store_true',
251                    help="Enable interpreter only mode (off by default).")
252  argp.add_argument("--jit", action='store_true',
253                    help="Enable jit (off by default).")
254  argp.add_argument("--optimizing", action='store_true',
255                    help="Enable optimizing compiler (default).")
256  argp.add_argument("--baseline", action='store_true',
257                    help="Enable baseline compiler.")
258  argp.add_argument("--no-verify", action='store_true',
259                    help="Turn off verification (on by default).")
260  argp.add_argument("--verify-soft-fail", action='store_true',
261                    help="Force soft fail verification (off by default). "
262                         "Verification is enabled if neither --no-verify "
263                         "nor --verify-soft-fail is specified.")
264  argp.add_argument("--no-optimize", action='store_true',
265                    help="Turn off optimization (on by default).")
266  argp.add_argument("--no-precise", action='store_true',
267                    help="Turn off precise GC (on by default).")
268  argp.add_argument("--zygote", action='store_true',
269                    help="Spawn the process from the Zygote. "
270                         "If used, then the other runtime options are ignored.")
271  argp.add_argument("--prebuild", action='store_true',
272                    help="Run dex2oat on the files before starting test. (default)")
273  argp.add_argument("--no-prebuild", action='store_true',
274                    help="Do not run dex2oat on the files before starting the test.")
275  argp.add_argument("--strip-dex", action='store_true',
276                    help="Strip the dex files before starting test.")
277  argp.add_argument("--relocate", action='store_true',
278                    help="Force the use of relocating in the test, making "
279                         "the image and oat files be relocated to a random address before running.")
280  argp.add_argument("--no-relocate", action='store_true',
281                    help="Force the use of no relocating in the test. (default)")
282  argp.add_argument("--image", type=str,
283                    help="Run the test using a precompiled boot image. (default)")
284  argp.add_argument("--no-image", action='store_true',
285                    help="Run the test without a precompiled boot image.")
286  argp.add_argument("--host", action='store_true',
287                    help="Use the host-mode virtual machine.")
288  argp.add_argument("--invoke-with", type=str, action='append', default=[],
289                    help="Pass --invoke-with option to runtime.")
290  argp.add_argument("--dalvik", action='store_true',
291                    help="Use Dalvik (off by default).")
292  argp.add_argument("--jvm", action='store_true',
293                    help="Use a host-local RI virtual machine.")
294  argp.add_argument("--use-java-home", action='store_true',
295                    help="Use the JAVA_HOME environment variable to find the java compiler "
296                         "and runtime (if applicable) to run the test with.")
297  argp.add_argument("--64", dest="is64bit", action='store_true',
298                    help="Run the test in 64-bit mode")
299  argp.add_argument("--bionic", action='store_true',
300                    help="Use the (host, 64-bit only) linux_bionic libc runtime")
301  argp.add_argument("--timeout", type=str,
302                    help="Test timeout in seconds")
303  argp.add_argument("--trace", action='store_true',
304                    help="Run with method tracing")
305  argp.add_argument("--strace", action='store_true',
306                    help="Run with syscall tracing from strace.")
307  argp.add_argument("--stream", action='store_true',
308                    help="Run method tracing in streaming mode (requires --trace)")
309  argp.add_argument("--gcstress", action='store_true',
310                    help="Run with gc stress testing")
311  argp.add_argument("--gcverify", action='store_true',
312                    help="Run with gc verification")
313  argp.add_argument("--jvmti-trace-stress", action='store_true',
314                    help="Run with jvmti method tracing stress testing")
315  argp.add_argument("--jvmti-step-stress", action='store_true',
316                    help="Run with jvmti single step stress testing")
317  argp.add_argument("--jvmti-redefine-stress", action='store_true',
318                    help="Run with jvmti method redefinition stress testing")
319  argp.add_argument("--always-clean", action='store_true',
320                    help="Delete the test files even if the test fails.")
321  argp.add_argument("--never-clean", action='store_true',
322                    help="Keep the test files even if the test succeeds.")
323  argp.add_argument("--chroot", type=str,
324                    help="Run with root directory set to newroot.")
325  argp.add_argument("--android-root", type=str,
326                    help="The path on target for the android root. (/system by default).")
327  argp.add_argument("--android-art-root", type=str,
328                    help="The path on target for the ART module root. "
329                         "(/apex/com.android.art by default).")
330  argp.add_argument("--android-tzdata-root", type=str,
331                    help="The path on target for the Android Time Zone Data root. "
332                         "(/apex/com.android.tzdata by default).")
333  argp.add_argument("--android-i18n-root", type=str,
334                    help="The path on target for the i18n module root. "
335                         "(/apex/com.android.i18n by default)."),
336  argp.add_argument("--dex2oat-swap", action='store_true',
337                    help="Use a dex2oat swap file.")
338  argp.add_argument("--instruction-set-features", type=str,
339                    help="Set instruction-set-features for compilation.")
340  argp.add_argument("--quiet", action='store_true',
341                    help="Don't print anything except failure messages")
342  argp.add_argument("--external-log-tags", action='store_true',
343                    help="Deprecated. Use --android-log-tags instead.")
344  argp.add_argument("--android-log-tags", type=str,
345                    help="Custom logging level for a test run.")
346  argp.add_argument("--suspend-timeout", type=str,
347                    help="Change thread suspend timeout ms (default 500000).")
348  argp.add_argument("--switch-interpreter", action='store_true')
349  argp.add_argument("--jvmti-field-stress", action='store_true',
350                    help="Run with jvmti method field stress testing")
351  argp.add_argument("--vdex", action='store_true',
352                    help="Test using vdex as in input to dex2oat. Only works with --prebuild.")
353  argp.add_argument("--dm", action='store_true'),
354  argp.add_argument("--vdex-filter", type=str)
355  argp.add_argument("--random-profile", action='store_true')
356  argp.add_argument("--dex2oat-jobs", type=int,
357                    help="Number of dex2oat jobs.")
358  argp.add_argument("--create-runner", type=Path, metavar='output_dir',
359                    help="Creates a runner script for use with other tools.")
360  argp.add_argument("--dev", action='store_true',
361                    help="Development mode (dumps to stdout).")
362  argp.add_argument("--update", action='store_true',
363                    help="Update mode (replaces expected-stdout.txt and expected-stderr.txt).")
364  argp.add_argument("--dump-cfg", type=str,
365                    help="Dump the CFG to the specified path.")
366  argp.add_argument("--bisection-search", action='store_true',
367                    help="Perform bisection bug search.")
368  argp.add_argument("--temp-path", type=str,
369                    help="Location where to execute the tests.")
370  argp.add_argument("test_name", nargs="?", default='-', type=str,
371                    help="Name of the test to run.")
372  argp.add_argument("test_args", nargs="*", default=None,
373                    help="Arguments to be passed to the test directly.")
374
375  # Python parser requires the format --key=--value, since without the equals symbol
376  # it looks like the required value has been omitted and there is just another flag.
377  # For example, '--Xcompiler-option --debuggable' will become '--Xcompiler-option=--debuggable'
378  # because otherwise the --Xcompiler-option is missing value and --debuggable is unknown argument.
379  argv = list(sys.argv[1:])
380  for i, arg in reversed(list(enumerate(argv))):
381    if arg in ["--runtime-option", "-Xcompiler-option"]:
382      argv[i] += "=" + argv.pop(i + 1)
383
384  # Accept single-dash arguments as if they were double-dash arguments.
385  # For example, '-Xcompiler-option' becomes '--Xcompiler-option'
386  # because single-dash can be used only with single-letter arguments.
387  for i, arg in list(enumerate(argv)):
388    if arg.startswith("-") and not arg.startswith("--"):
389      argv[i] = "-" + arg
390    if arg == "--":
391      break
392
393  args = argp.parse_args(argv)
394
395  if True:
396    run_args = []
397    target_mode = not args.host
398    if not target_mode:
399      DEX_LOCATION = tmp_dir
400      run_args += ["--host"]
401      os.environ["RUN_MODE"] = "host"
402    quiet = args.quiet
403    usage = False
404    if args.use_java_home:
405      JAVA_HOME = os.environ.get("JAVA_HOME")
406      if JAVA_HOME:
407        export("JAVA", f"{JAVA_HOME}/bin/java")
408        export("JAVAC", f"{JAVA_HOME}/bin/javac -g")
409      else:
410        error("Passed --use-java-home without JAVA_HOME variable set!")
411        usage = True
412    prebuild_mode = True
413    runtime = "art"
414    if args.jvm:
415      target_mode = False
416      DEX_LOCATION = tmp_dir
417      runtime = "jvm"
418      prebuild_mode = False
419      run_args += ["--jvm"]
420    lib = "libartd.so"
421    testlib = "arttestd"
422    if args.O:
423      lib = "libart.so"
424      testlib = "arttest"
425      run_args += ["-O"]
426    if args.dalvik:
427      lib = "libdvm.so"
428      runtime = "dalvik"
429    have_image = not args.no_image
430    relocate = args.relocate and not args.no_relocate
431    if args.prebuild:
432      run_args += ["--prebuild"]
433      prebuild_mode = True
434    if args.strip_dex:
435      run_args += ["--strip-dex"]
436    debuggable = args.debuggable
437    if debuggable:
438      run_args += ["-Xcompiler-option --debuggable"]
439    if args.no_prebuild:
440      run_args += ["--no-prebuild"]
441      prebuild_mode = False
442    basic_verify = gc_verify = args.gcverify
443    gc_stress = args.gcstress
444    if gc_stress:
445        basic_verify = True
446
447    jvmti_step_stress = args.jvmti_step_stress
448    jvmti_redefine_stress = args.jvmti_redefine_stress
449    jvmti_field_stress = args.jvmti_field_stress
450    jvmti_trace_stress = args.jvmti_trace_stress
451
452    if jvmti_step_stress:
453      os.environ["JVMTI_STEP_STRESS"] = "true"
454    if jvmti_redefine_stress:
455      os.environ["JVMTI_REDEFINE_STRESS"] = "true"
456    if jvmti_field_stress:
457      os.environ["JVMTI_FIELD_STRESS"] = "true"
458    if jvmti_trace_stress:
459      os.environ["JVMTI_TRACE_STRESS"] = "true"
460    suspend_timeout = args.suspend_timeout or "500000"
461    if args.image:
462      run_args += [f'--image "{args.image}"']
463    run_args.extend([f'-Xcompiler-option "{option}"' for option in args.Xcompiler_option])
464    run_args.extend([f'--runtime-option "{option}"' for option in args.runtime_option])
465    run_args.extend([f'--gdb-arg "{gdb_arg}"' for gdb_arg in args.gdb_arg])
466
467    if args.gdb_dex2oat_args:
468      run_args.append(f'--gdb-dex2oat-args="{args.gdb_dex2oat_args}"')
469    if args.debug:
470      run_args.append("--debug")
471    if args.debug_wrap_agent:
472      run_args.append("--debug-wrap-agent")
473    run_args.extend([f'--with-agent "{arg}"' for arg in args.with_agent])
474
475    if args.debug_agent:
476      run_args.append(f'--debug-agent "{args.debug_agent}"')
477
478    dump_cfg = bool(args.dump_cfg)
479    dump_cfg_path = args.dump_cfg or ""
480
481    dev_mode = args.gdb or args.gdb_dex2oat
482    if args.gdb:
483      run_args.append("--gdb")
484    if args.gdb_dex2oat:
485      run_args.append("--gdb-dex2oat")
486    if args.gdbserver_bin:
487      run_args.append(f'--gdbserver-bin "{args.gdbserver_bin}"')
488    if args.gdbserver_port:
489      run_args.append(f'--gdbserver-port "{args.gdbserver_port}"')
490    if args.gdbserver:
491      run_args.append("--gdbserver")
492      dev_mode = True
493    strace = args.strace
494    strace_output = "strace-output.txt"
495    timeout = ""
496    if strace:
497      run_args += [
498          f'--invoke-with=strace --invoke-with=-o --invoke-with="{tmp_dir}/{strace_output}"'
499      ]
500      timeout = timeout or "1800"
501    if args.zygote:
502      run_args += ["--zygote"]
503    if args.interpreter:
504      run_args += ["--interpreter"]
505    if args.switch_interpreter:
506      run_args += ["--switch-interpreter"]
507    if args.jit:
508      run_args += ["--jit"]
509    if args.baseline:
510      run_args += ["--baseline"]
511    run_optimizing = args.optimizing
512    if args.no_verify:
513      run_args += ["--no-verify"]
514    if args.verify_soft_fail:
515      run_args += ["--verify-soft-fail"]
516      os.environ["VERIFY_SOFT_FAIL"] = "true"
517    if args.no_optimize:
518      run_args += ["--no-optimize"]
519    if args.no_precise:
520      run_args += ["--no-precise"]
521    if args.android_log_tags:
522      run_args += [f"--android-log-tags {args.android_log_tags}"]
523    if args.external_log_tags:
524      run_args += ["--external-log-tags"]
525    if args.invoke_with:
526      run_args += [f'--invoke-with "{what}"' for what in args.invoke_with]
527    create_runner = args.create_runner
528    if create_runner:
529      run_args += ["--create-runner --dry-run"]
530      dev_mode = True
531    dev_mode = dev_mode or args.dev
532    chroot = args.chroot or ""
533    if chroot:
534      run_args += [f'--chroot "{chroot}"']
535    if args.simpleperf:
536      run_args += ["--simpleperf"]
537    android_root = args.android_root or "/system"
538    if args.android_root:
539      run_args += [f'--android-root "{android_root}"']
540    if args.android_art_root:
541      run_args += [f'--android-art-root "{args.android_art_root}"']
542    if args.android_tzdata_root:
543      run_args += [f'--android-tzdata-root "{args.android_tzdata_root}"']
544    update_mode = args.update
545    suffix64 = "64" if args.is64bit else ""
546    if args.is64bit:
547      run_args += ["--64"]
548    host_lib_root = ANDROID_HOST_OUT
549    if args.bionic:
550      # soong linux_bionic builds are 64bit only.
551      run_args += ["--bionic --host --64"]
552      suffix64 = "64"
553      target_mode = False
554      DEX_LOCATION = tmp_dir
555      host_lib_root = f"{OUT_DIR}/soong/host/linux_bionic-x86"
556
557    timeout = args.timeout or timeout
558    trace = args.trace
559    trace_stream = args.stream
560    always_clean = args.always_clean
561    never_clean = create_runner or args.never_clean
562    if args.dex2oat_swap:
563      run_args += ["--dex2oat-swap"]
564    if args.instruction_set_features:
565      run_args += [f'--instruction-set-features "{args.instruction_set_features}"']
566
567    bisection_search = args.bisection_search
568    if args.vdex:
569      run_args += ["--vdex"]
570    if args.dm:
571      run_args += ["--dm"]
572    if args.vdex_filter:
573      run_args += [f'--vdex-filter "{args.vdex_filter}"']
574    if args.random_profile:
575      run_args += ["--random-profile"]
576    if args.dex2oat_jobs:
577      run_args += [f'-Xcompiler-option "-j{str(args.dex2oat_jobs)}"']
578
579  export("DEX_LOCATION", DEX_LOCATION)
580
581# The DEX_LOCATION with the chroot prefix, if any.
582  chroot_dex_location = f"{chroot}{DEX_LOCATION}"
583
584  # tmp_dir may be relative, resolve.
585  os.chdir(oldwd)
586  tmp_dir = os.path.realpath(tmp_dir)
587  os.chdir(progdir)
588  if not tmp_dir:
589    error(f"Failed to resolve {tmp_dir}")
590    sys.exit(1)
591  os.makedirs(tmp_dir, exist_ok=True)
592
593  # Add thread suspend timeout flag
594  if runtime != "jvm":
595    run_args += [
596        f'--runtime-option "-XX:ThreadSuspendTimeout={suspend_timeout}"'
597    ]
598
599  if basic_verify:
600    # Set HspaceCompactForOOMMinIntervalMs to zero to run hspace compaction for OOM more frequently in tests.
601    run_args += [
602        "--runtime-option -Xgc:preverify --runtime-option -Xgc:postverify "
603        "--runtime-option -XX:HspaceCompactForOOMMinIntervalMs=0"
604    ]
605  if gc_verify:
606    run_args += [
607        "--runtime-option -Xgc:preverify_rosalloc --runtime-option "
608        "-Xgc:postverify_rosalloc"
609    ]
610  if gc_stress:
611    run_args += [
612        "--gc-stress --runtime-option -Xgc:gcstress --runtime-option -Xms2m "
613        "--runtime-option -Xmx16m"
614    ]
615  if jvmti_redefine_stress:
616    run_args += ["--no-app-image --jvmti-redefine-stress"]
617  if jvmti_step_stress:
618    run_args += ["--no-app-image --jvmti-step-stress"]
619  if jvmti_field_stress:
620    run_args += ["--no-app-image --jvmti-field-stress"]
621  if jvmti_trace_stress:
622    run_args += ["--no-app-image --jvmti-trace-stress"]
623  if trace:
624    run_args += [
625        "--runtime-option -Xmethod-trace --runtime-option "
626        "-Xmethod-trace-file-size:2000000"
627    ]
628    if trace_stream:
629      # Streaming mode uses the file size as the buffer size. So output gets really large. Drop
630      # the ability to analyze the file and just write to /dev/null.
631      run_args += ["--runtime-option -Xmethod-trace-file:/dev/null"]
632      # Enable streaming mode.
633      run_args += ["--runtime-option -Xmethod-trace-stream"]
634    else:
635      run_args += [
636          f'--runtime-option "-Xmethod-trace-file:{DEX_LOCATION}/trace.bin"'
637      ]
638  elif trace_stream:
639    error("Cannot use --stream without --trace.")
640    sys.exit(1)
641  if timeout:
642    run_args += [f'--timeout "{timeout}"']
643
644# Most interesting target architecture variables are Makefile variables, not environment variables.
645# Try to map the suffix64 flag and what we find in {ANDROID_PRODUCT_OUT}/data/art-test to an architecture name.
646
647  def guess_target_arch_name():
648    return get_target_arch(suffix64 == "64")
649
650  def guess_host_arch_name():
651    if suffix64 == "64":
652      return "x86_64"
653    else:
654      return "x86"
655
656  if not target_mode:
657    if runtime == "jvm":
658      if prebuild_mode:
659        error("--prebuild with --jvm is unsupported")
660        sys.exit(1)
661    else:
662      # ART/Dalvik host mode.
663      if chroot:
664        error("--chroot with --host is unsupported")
665        sys.exit(1)
666
667  if runtime != "jvm":
668    run_args += [f'--lib "{lib}"']
669
670  ANDROID_PRODUCT_OUT = os.environ.get("ANDROID_PRODUCT_OUT")
671  if runtime == "dalvik":
672    if not target_mode:
673      framework = f"{ANDROID_PRODUCT_OUT}/system/framework"
674      bpath = f"{framework}/core-icu4j.jar:{framework}/core-libart.jar:{framework}/core-oj.jar:{framework}/conscrypt.jar:{framework}/okhttp.jar:{framework}/bouncycastle.jar:{framework}/ext.jar"
675      run_args += [f'--boot --runtime-option "-Xbootclasspath:{bpath}"']
676    else:
677      pass  # defaults to using target BOOTCLASSPATH
678  elif runtime == "art":
679    if not target_mode:
680      host_arch_name = guess_host_arch_name()
681      run_args += [
682          f'--boot "{ANDROID_HOST_OUT}/apex/art_boot_images/javalib/boot.art"'
683      ]
684      run_args += [
685          f'--runtime-option "-Djava.library.path={host_lib_root}/lib{suffix64}:{host_lib_root}/nativetest{suffix64}"'
686      ]
687    else:
688      target_arch_name = guess_target_arch_name()
689      # Note that libarttest(d).so and other test libraries that depend on ART
690      # internal libraries must not be in this path for JNI libraries - they
691      # need to be loaded through LD_LIBRARY_PATH and
692      # NATIVELOADER_DEFAULT_NAMESPACE_LIBS instead.
693      run_args += [
694          f'--runtime-option "-Djava.library.path=/data/nativetest{suffix64}/art/{target_arch_name}"'
695      ]
696      if env.ART_TEST_RUN_FROM_SOONG:
697        run_args += ['--boot "/data/local/tmp/art/apex/art_boot_images/boot.art"']
698      else:
699        run_args += ['--boot "/system/framework/art_boot_images/boot.art"']
700    if relocate:
701      run_args += ["--relocate"]
702    else:
703      run_args += ["--no-relocate"]
704  elif runtime == "jvm":
705    # TODO: Detect whether the host is 32-bit or 64-bit.
706    run_args += [
707        f'--runtime-option "-Djava.library.path={ANDROID_HOST_OUT}/lib64:{ANDROID_HOST_OUT}/nativetest64"'
708    ]
709
710  if not have_image:
711    if runtime != "art":
712      error("--no-image is only supported on the art runtime")
713      sys.exit(1)
714    run_args += ["--no-image"]
715
716  if dev_mode and update_mode:
717    error("--dev and --update are mutually exclusive")
718    usage = True
719
720  if dev_mode and quiet:
721    error("--dev and --quiet are mutually exclusive")
722    usage = True
723
724  if bisection_search and prebuild_mode:
725    error("--bisection-search and --prebuild are mutually exclusive")
726    usage = True
727
728# TODO: Chroot-based bisection search is not supported yet (see below); implement it.
729  if bisection_search and chroot:
730    error("--chroot with --bisection-search is unsupported")
731    sys.exit(1)
732
733  if args.test_name == "-":
734    test_dir = os.path.basename(oldwd)
735  else:
736    test_dir = args.test_name
737
738  if not os.path.isdir(test_dir):
739    td2 = glob.glob(f"{test_dir}-*")
740    if len(td2) == 1 and os.path.isdir(td2[0]):
741      test_dir = td2[0]
742    else:
743      error(f"{test_dir}: no such test directory")
744      usage = True
745
746  os.chdir(test_dir)
747  test_dir = os.getcwd()
748
749  TEST_NAME = os.path.basename(test_dir)
750  export("TEST_NAME", TEST_NAME)
751
752  # Tests named '<number>-checker-*' will also have their CFGs verified with
753  # Checker when compiled with Optimizing on host.
754  # Additionally, if the user specifies that the CFG must be dumped, it will
755  # run the checker for any type of test to generate the CFG.
756  if re.match("[0-9]+-checker-", TEST_NAME) or dump_cfg:
757    if runtime == "art" and run_optimizing:
758      # In no-prebuild or no-image mode, the compiler only quickens so disable the checker.
759      if prebuild_mode:
760        run_checker = True
761
762        if not target_mode:
763          cfg_output_dir = tmp_dir
764          checker_args = f"--arch={host_arch_name.upper()}"
765        else:
766          cfg_output_dir = DEX_LOCATION
767          checker_args = f"--arch={target_arch_name.upper()}"
768
769        if debuggable:
770          checker_args += " --debuggable"
771
772        run_args += [
773            f'-Xcompiler-option "--dump-cfg={cfg_output_dir}/{cfg_output}" -Xcompiler-option -j1'
774        ]
775        checker_args = f"{checker_args} --print-cfg"
776
777  run_args += [f'--testlib "{testlib}"']
778
779  resource.setrlimit(resource.RLIMIT_FSIZE, (file_ulimit * 1024, resource.RLIM_INFINITY))
780
781  # Extract run-test data from the zip file.
782  def unzip():
783    if env.ART_TEST_RUN_FROM_SOONG:
784      # We already have the unzipped copy of the data.
785      assert target_mode
786      src = Path(ANDROID_BUILD_TOP) / "out" / "zip" / "target" / TEST_NAME
787      assert src.exists(), src
788      shutil.rmtree(tmp_dir)
789      copytree(src, tmp_dir)
790      os.chdir(tmp_dir)
791      return
792
793    # Clear the contents, but keep the directory just in case it is open in terminal.
794    for file in Path(tmp_dir).iterdir():
795      if file.is_file() or file.is_symlink():
796        file.unlink()
797      else:
798        shutil.rmtree(file)
799    os.makedirs(f"{tmp_dir}/.unzipped")
800    os.chdir(tmp_dir)
801    m = re.match("[0-9]*([0-9][0-9])-.*", TEST_NAME)
802    assert m, "Can not find test number in " + TEST_NAME
803    SHARD = "HiddenApi" if "hiddenapi" in TEST_NAME else m.group(1)
804    zip_dir = f"{ANDROID_HOST_OUT}/etc/art"
805    if target_mode:
806      zip_file = f"{zip_dir}/art-run-test-target-data-shard{SHARD}.zip"
807      zip_entry = f"target/{TEST_NAME}/"
808    elif runtime == "jvm":
809      zip_file = f"{zip_dir}/art-run-test-jvm-data-shard{SHARD}.zip"
810      zip_entry = f"jvm/{TEST_NAME}/"
811    else:
812      zip_file = f"{zip_dir}/art-run-test-host-data-shard{SHARD}.zip"
813      zip_entry = f"host/{TEST_NAME}/"
814    zip = ZipFile(zip_file, "r")
815    zip_entries = [e for e in zip.namelist() if e.startswith(zip_entry)]
816    zip.extractall(Path(tmp_dir) / ".unzipped", members=zip_entries)
817    for entry in (Path(tmp_dir) / ".unzipped" / zip_entry).iterdir():
818      entry.rename(Path(tmp_dir) / entry.name)
819
820  unzip()
821
822  def clean_up(passed: bool):
823    if always_clean or (passed and not never_clean):
824      os.chdir(oldwd)
825      shutil.rmtree(tmp_dir)
826      if target_mode:
827        if ON_VM:
828          run(f"{SSH_CMD} \"rm -rf {chroot_dex_location}\"")
829        else:
830          run(f"adb shell rm -rf {chroot_dex_location}")
831      os.remove(tmp_dir_lock)
832      print(f"{TEST_NAME} files deleted from host" +
833            (" and from target" if target_mode else ""))
834    else:
835      print(f"{TEST_NAME} files left in {tmp_dir} on host" +
836            (f" and in {chroot_dex_location} on target" if target_mode else ""))
837    atexit.unregister(clean_up)
838
839  ctx = RunTestContext(Path(tmp_dir), target_mode, chroot, DEX_LOCATION, TEST_NAME)
840  td_info = f"{test_dir}/{info}"
841  for td_file in [td_info, ctx.expected_stdout, ctx.expected_stderr]:
842    assert os.access(td_file, os.R_OK)
843
844  # Create runner (bash script that executes the whole test)
845  def create_runner_script() -> Path:
846    parsed_args = default_run_module.parse_args(shlex.split(" ".join(run_args + args.test_args)))
847    parsed_args.stdout_file = os.path.join(DEX_LOCATION, test_stdout)
848    parsed_args.stderr_file = os.path.join(DEX_LOCATION, test_stderr)
849
850    ctx.run(f"cd {DEX_LOCATION}")
851    if not target_mode:
852      # Make "out" directory accessible from test directory.
853      ctx.run(f"ln -s -f -t {DEX_LOCATION} {ANDROID_BUILD_TOP}/out")
854    # Clear the stdout/stderr files (create empty files).
855    ctx.run(f"echo -n > {test_stdout} && echo -n > {test_stderr}")
856
857    script = Path(tmp_dir) / "run.py"
858    if script.exists():
859      module = SourceFileLoader("run_" + TEST_NAME, str(script)).load_module()
860      module.run(ctx, parsed_args)
861    else:
862      default_run_module.default_run(ctx, parsed_args)
863
864    runner = Path(tmp_dir) / "run.sh"
865    runner.write_text("\n".join(ctx.runner))
866    runner.chmod(0o777)
867    return runner
868
869  def do_dump_cfg():
870    assert run_optimizing, "The CFG can be dumped only in optimizing mode"
871    if target_mode:
872      if ON_VM:
873        run(f'{SCP_CMD} "{SSH_USER}@${SSH_HOST}:{CHROOT}/{cfg_output_dir}/'
874            f'{cfg_output} {dump_cfg_path}"')
875      else:
876        run(f"adb pull {chroot}/{cfg_output_dir}/{cfg_output} {dump_cfg_path}")
877    else:
878      run(f"cp {cfg_output_dir}/{cfg_output} {dump_cfg_path}")
879
880  # Test might not execute anything but we still expect the output files to exist.
881  Path(test_stdout).touch()
882  Path(test_stderr).touch()
883
884  export("TEST_RUNTIME", runtime)
885
886  print(f"{test_dir}: Create runner script...")
887  runner = create_runner_script()
888
889  if args.create_runner:
890    # TODO: Generate better unique names.
891    dst = args.create_runner / TEST_NAME / f"{Path(tmp_dir).name}.sh"
892    assert not dst.exists(), dst
893    dst.parent.mkdir(parents=True, exist_ok=True)
894    copyfile(runner, dst)
895
896  # Script debugging feature - just export the runner script into a directory,
897  # so that it can be compared before/after runner script refactoring.
898  save_runner_dir = os.environ.get("RUN_TEST_DEBUG__SAVE_RUNNER_DIR")
899  if save_runner_dir:
900    name = [a for a in sys.argv[1:] if not a.startswith("--create-runner")]
901    name = urllib.parse.quote(" ".join(name), safe=' ')
902    dst = Path(save_runner_dir) / TEST_NAME / name
903    os.makedirs(dst.parent, exist_ok=True)
904    txt = runner.read_text()
905    txt = txt.replace(Path(tmp_dir).name, "${TMP_DIR}")  # Make it deterministic.
906    txt = re.sub('\[run-test:\d+\]', '[run-test:(line-number)]', txt)
907    dst.write_text(txt)
908
909  if args.create_runner or save_runner_dir:
910    sys.exit(0)
911
912  # TODO: Run this in global try-finally once the script is more refactored.
913  atexit.register(clean_up, passed=False)
914
915  print(f"{test_dir}: Run...")
916  if target_mode:
917    # Prepare the on-device test directory
918    if ON_VM:
919      run(f"{SSH_CMD} 'rm -rf {chroot_dex_location} && mkdir -p {chroot_dex_location}'")
920    else:
921      run("adb root")
922      run("adb wait-for-device")
923      run(f"adb shell 'rm -rf {chroot_dex_location} && mkdir -p {chroot_dex_location}'")
924    push_files = [Path(runner.name)]
925    push_files += list(Path(".").glob(f"{TEST_NAME}*.jar"))
926    push_files += list(Path(".").glob(f"expected-*.txt"))
927    push_files += [p for p in [Path("profile"), Path("res")] if p.exists()]
928    push_files = " ".join(map(str, push_files))
929    if ON_VM:
930      run(f"{SCP_CMD} {push_files} {SSH_USER}@{SSH_HOST}:{chroot_dex_location}")
931    else:
932      run("adb push {} {}".format(push_files, chroot_dex_location))
933
934    try:
935      if ON_VM:
936        run(f"{SSH_CMD} {CHROOT_CMD} bash {DEX_LOCATION}/run.sh",
937            fail_message=f"Runner {chroot_dex_location}/run.sh failed")
938      else:
939        chroot_prefix = f"chroot {chroot}" if chroot else ""
940        run(f"adb shell {chroot_prefix} sh {DEX_LOCATION}/run.sh",
941            fail_message=f"Runner {chroot_dex_location}/run.sh failed")
942    finally:
943      # Copy the generated CFG to the specified path.
944      if dump_cfg:
945        do_dump_cfg()
946
947    # Copy the on-device stdout/stderr to host.
948    pull_files = [test_stdout, test_stderr, "expected-stdout.txt", "expected-stderr.txt"]
949    if ON_VM:
950      srcs = " ".join(f"{SSH_USER}@{SSH_HOST}:{chroot_dex_location}/{f}" for f in pull_files)
951      run(f"{SCP_CMD} {srcs} .")
952    else:
953      run("adb pull {} .".format(" ".join(f"{chroot_dex_location}/{f}" for f in pull_files)))
954  else:
955    run(str(runner), fail_message=f"Runner {str(runner)} failed")
956
957  # NB: There is no exit code or return value.
958  # Failing tests just raise python exception.
959  os.chdir(tmp_dir)
960  if update_mode:
961    for src, dst in [(test_stdout, os.path.join(test_dir, ctx.expected_stdout.name)),
962                     (test_stderr, os.path.join(test_dir, ctx.expected_stderr.name))]:
963      if "[DO_NOT_UPDATE]" not in open(dst).readline():
964        copyfile(src, dst)
965
966  print("#################### info")
967  run(f'cat "{td_info}" | sed "s/^/# /g"')
968  print("#################### stdout diff")
969  proc_out = run(f'diff --strip-trailing-cr -u '
970                 f'"{ctx.expected_stdout}" "{test_stdout}"', check=False)
971  print("#################### stderr diff")
972  proc_err = run(f'diff --strip-trailing-cr -u '
973                 f'"{ctx.expected_stderr}" "{test_stderr}"', check=False)
974  if strace:
975    print("#################### strace output (trimmed to 3000 lines)")
976    # Some tests do not run dalvikvm, in which case the trace does not exist.
977    run(f'tail -n 3000 "{tmp_dir}/{strace_output}"', check=False)
978  SANITIZE_HOST = os.environ.get("SANITIZE_HOST")
979  if not target_mode and SANITIZE_HOST == "address":
980    # Run the stack script to symbolize any ASAN aborts on the host for SANITIZE_HOST. The
981    # tools used by the given ABI work for both x86 and x86-64.
982    print("#################### symbolizer (trimmed to 3000 lines)")
983    run(f'''echo "ABI: 'x86_64'" | cat - "{test_stdout}" "{test_stderr}"'''
984        f"""| {ANDROID_BUILD_TOP}/development/scripts/stack | tail -n 3000""")
985  print("####################", flush=True)
986
987  try:
988    if proc_out.returncode != 0 or proc_err.returncode != 0:
989      kind = ((["stdout"] if proc_out.returncode != 0 else []) +
990              (["stderr"] if proc_err.returncode != 0 else []))
991      fail("{} did not match the expected file".format(" and ".join(kind)))
992
993    if run_checker:
994      if target_mode:
995        if ON_VM:
996          run(f'{SCP_CMD} "{SSH_USER}@{SSH_HOST}:{CHROOT}/{cfg_output_dir}/'
997              f'{cfg_output}" "{tmp_dir}"')
998        else:
999          run(f'adb pull "{chroot}/{cfg_output_dir}/{cfg_output}"')
1000      run(f'"{checker}" -q {checker_args} "{cfg_output}" "{tmp_dir}"',
1001          fail_message="CFG checker failed")
1002  finally:
1003    # Copy the generated CFG to the specified path.
1004    if dump_cfg:
1005      do_dump_cfg()
1006
1007  clean_up(passed=True)
1008  print(f"{COLOR_GREEN}{test_dir}: PASSED{COLOR_NORMAL}")
1009