Add support for running ART tests on a Linux virtual machine.
Test: setup a local VM and run ART tests on it:
lunch aosp_arm64-userdebug
export ART_TEST_SSH_USER=ubuntu
export ART_TEST_SSH_HOST=localhost
export ART_TEST_SSH_PORT=10001
export ART_TEST_ON_VM=true
. art/tools/buildbot-utils.sh
art/tools/buildbot-build.sh --target
# Create, boot and configure the VM.
art/tools/buildbot-vm.sh create
art/tools/buildbot-vm.sh boot
art/tools/buildbot-vm.sh setup-ssh # password: 'ubuntu'
art/tools/buildbot-cleanup-device.sh
art/tools/buildbot-setup-device.sh
art/tools/buildbot-sync.sh
art/test.py --target -r 001-HelloWorld
art/tools/run-gtests.sh
Test: check that non-VM testing still works:
lunch aosp_arm32-userdebug
. art/tools/buildbot-utils.sh
art/tools/buildbot-build.sh --target
export ART_TEST_CHROOT=/data/local/art-test-chroot
art/tools/buildbot-cleanup-device.sh
art/tools/buildbot-setup-device.sh
art/tools/buildbot-sync.sh
art/test.py --target -r --32 001-HelloWorld
Change-Id: I1393f8132b0b5d6ad10e291504550a5e7751f8e2
diff --git a/test/README.chroot_vm.md b/test/README.chroot_vm.md
new file mode 100644
index 0000000..2f163de
--- /dev/null
+++ b/test/README.chroot_vm.md
@@ -0,0 +1,74 @@
+# ART chroot-based testing on a Linux VM
+
+This doc describes how to set up a Linux VM and how to run ART tests on it.
+
+## Set up the VM
+
+Use script art/build/buildbot-vm.sh. It has various commands (actions) described
+below. First, set up some environment variables used by the script (change as
+you see fit):
+```
+export ART_TEST_SSH_USER=ubuntu
+export ART_TEST_SSH_HOST=localhost
+export ART_TEST_SSH_PORT=10001
+```
+Create the VM (download it and do some initial setup):
+```
+art/tools/buildbot-vm.sh create
+```
+Boot the VM (login is `$ART_TEST_SSH_USER`, password is `ubuntu`):
+```
+art/tools/buildbot-vm.sh boot
+```
+Configure SSH (enter `yes` to add VM to `known_hosts` and then the password):
+```
+art/tools/buildbot-vm.sh setup-ssh
+```
+Now you have the shell (no need to enter password every time):
+```
+art/tools/buildbot-vm.sh connect
+```
+To power off the VM, do:
+```
+art/tools/buildbot-vm.sh quit
+```
+To speed up SSH access, set `UseDNS no` in /etc/ssh/sshd_config on the VM (and
+apply other tweaks described in https://jrs-s.net/2017/07/01/slow-ssh-logins).
+
+# Run ART tests
+```
+This is done in the same way as you would run tests in chroot on device (except
+for a few extra environment variables):
+
+export ANDROID_SERIAL=nonexistent
+export ART_TEST_SSH_USER=ubuntu
+export ART_TEST_SSH_HOST=localhost
+export ART_TEST_SSH_PORT=10001
+export ART_TEST_ON_VM=true
+
+. ./build/envsetup.sh
+lunch armv8-eng # or aosp_riscv64-userdebug, etc.
+art/tools/buildbot-build.sh --target # --installclean
+
+art/tools/buildbot-cleanup-device.sh
+
+# The following two steps can be skipped for faster iteration, but it doesn't
+# always track and update dependencies correctly (e.g. if only an assembly file
+# has been modified).
+art/tools/buildbot-setup-device.sh
+art/tools/buildbot-sync.sh
+
+art/test/run-test --chroot $ART_TEST_CHROOT --64 --interpreter -O 001-HelloWorld
+art/test.py --target -r --ndebug --no-image --64 --interpreter # specify tests
+art/tools/run-gtests.sh
+
+art/tools/buildbot-cleanup-device.sh
+```
+Both test.py and run-test scripts can be used. Tweak options as necessary.
+
+# Limitations
+
+Limitations are mostly related to the absence of system properties on the Linux.
+They are not really needed for ART tests, but they are used for test-related
+things, e.g. to find out if the tests should run in debug configuration (option
+`ro.debuggable`). Therefore debug configuration is currently broken.
diff --git a/test/default_run.py b/test/default_run.py
index f457956..e102311 100755
--- a/test/default_run.py
+++ b/test/default_run.py
@@ -213,6 +213,8 @@
else:
setattr(args, name, new_value)
+ ON_VM = os.environ.get("ART_TEST_ON_VM")
+
# Store copy of stdout&stderr of command in files so that we can diff them later.
# This may run under 'adb shell' so we are limited only to 'sh' shell feature set.
def tee(cmd: str):
@@ -244,7 +246,7 @@
ANDROID_I18N_ROOT = args.android_i18n_root
ANDROID_TZDATA_ROOT = args.android_tzdata_root
ARCHITECTURES_32 = "(arm|x86|none)"
- ARCHITECTURES_64 = "(arm64|x86_64|none)"
+ ARCHITECTURES_64 = "(arm64|x86_64|riscv64|none)"
ARCHITECTURES_PATTERN = ARCHITECTURES_32
GET_DEVICE_ISA_BITNESS_FLAG = "--32"
BOOT_IMAGE = args.boot
@@ -455,7 +457,7 @@
VDEX_ARGS += f" {arg}"
# HACK: Force the use of `signal_dumper` on host.
- if HOST:
+ if HOST or ON_VM:
TIME_OUT = "timeout"
# If you change this, update the timeout in testrunner.py as well.
@@ -683,7 +685,7 @@
else:
FLAGS += " -Xnorelocate"
- if BIONIC:
+ if BIONIC and not ON_VM:
# This is the location that soong drops linux_bionic builds. Despite being
# called linux_bionic-x86 the build is actually amd64 (x86_64) only.
assert path.exists(f"{OUT_DIR}/soong/host/linux_bionic-x86"), (
@@ -961,6 +963,10 @@
# b/27185632
# b/24664297
+ dalvikvm_logger = ""
+ if ON_VM:
+ dalvikvm_logger = "-Xuse-stderr-logger"
+
dalvikvm_cmdline = f"{INVOKE_WITH} {GDB} {ANDROID_ART_BIN_DIR}/{DALVIKVM} \
{GDB_ARGS} \
{FLAGS} \
@@ -974,6 +980,7 @@
{DEBUGGER_OPTS} \
{QUOTED_DALVIKVM_BOOT_OPT} \
{TMP_DIR_OPTION} \
+ {dalvikvm_logger} \
-XX:DumpNativeStackOnSigQuit:false \
-cp {DALVIKVM_CLASSPATH} {MAIN} {ARGS}"
@@ -1008,6 +1015,26 @@
ANDROID_LOG_TAGS = args.android_log_tags
+ def filter_output():
+ # Remove unwanted log messages from stderr before diffing with the expected output.
+ # NB: The unwanted log line can be interleaved in the middle of wanted stderr printf.
+ # In particular, unhandled exception is printed using several unterminated printfs.
+ ALL_LOG_TAGS = ["V", "D", "I", "W", "E", "F", "S"]
+ skip_tag_set = "|".join(ALL_LOG_TAGS[:ALL_LOG_TAGS.index(args.diff_min_log_tag.upper())])
+ skip_reg_exp = fr'[[:alnum:]]+ ({skip_tag_set}) #-# #:#:# [^\n]*\n'.replace('#', '[0-9]+')
+ ctx.run(fr"sed -i -z -E 's/{skip_reg_exp}//g' '{args.stderr_file}'")
+ if not HAVE_IMAGE:
+ message = "(Unable to open file|Could not create image space)"
+ ctx.run(fr"sed -i -E '/^dalvikvm(|32|64) E .* {message}/d' '{args.stderr_file}'")
+ if ANDROID_LOG_TAGS != "*:i" and "D" in skip_tag_set:
+ ctx.run(fr"sed -i -E '/^(Time zone|I18n) APEX ICU file found/d' '{args.stderr_file}'")
+ if ON_VM:
+ messages = "|".join([
+ "failed to connect to tombstoned",
+ "Failed to write stack traces to tombstoned",
+ "Failed to setpriority to :0"])
+ ctx.run(fr"sed -i -E '/({messages})/d' '{args.stderr_file}'")
+
if not HOST:
# Populate LD_LIBRARY_PATH.
LD_LIBRARY_PATH = ""
@@ -1033,8 +1060,9 @@
dlib = ""
art_test_internal_libraries = []
- # Needed to access the test's Odex files.
- LD_LIBRARY_PATH = f"{DEX_LOCATION}/oat/{ISA}:{LD_LIBRARY_PATH}"
+ if not ON_VM:
+ # Needed to access the test's Odex files.
+ LD_LIBRARY_PATH = f"{DEX_LOCATION}/oat/{ISA}:{LD_LIBRARY_PATH}"
# Needed to access the test's native libraries (see e.g. 674-hiddenapi,
# which generates `libhiddenapitest_*.so` libraries in `{DEX_LOCATION}`).
LD_LIBRARY_PATH = f"{DEX_LOCATION}:{LD_LIBRARY_PATH}"
@@ -1060,7 +1088,10 @@
# dumping do not lead to a deadlock, we also use the "-k" option to definitely kill the
# child.
# Note: Using "--foreground" to not propagate the signal to children, i.e., the runtime.
- timeout_prefix = f"timeout --foreground -k 120s {TIME_OUT_VALUE}s {timeout_dumper_cmd}"
+ if ON_VM:
+ timeout_prefix = f"timeout -k 120s {TIME_OUT_VALUE}s"
+ else:
+ timeout_prefix = f"timeout --foreground -k 120s {TIME_OUT_VALUE}s {timeout_dumper_cmd}"
ctx.export(
ASAN_OPTIONS = RUN_TEST_ASAN_OPTIONS,
@@ -1088,6 +1119,10 @@
ctx.run(f"{sync_cmdline}")
ctx.run(tee(f"{timeout_prefix} {dalvikvm_cmdline}"),
expected_exit_code=args.expected_exit_code, desc="DalvikVM")
+
+ if ON_VM:
+ filter_output()
+
else:
# Host run.
if USE_ZIPAPEX or USE_EXRACTED_ZIPAPEX:
@@ -1168,16 +1203,4 @@
ctx.run(cmdline)
else:
ctx.run(tee(cmdline), expected_exit_code=args.expected_exit_code, desc="DalvikVM")
-
- # Remove unwanted log messages from stderr before diffing with the expected output.
- # NB: The unwanted log line can be interleaved in the middle of wanted stderr printf.
- # In particular, unhandled exception is printed using several unterminated printfs.
- ALL_LOG_TAGS = ["V", "D", "I", "W", "E", "F", "S"]
- skip_tag_set = "|".join(ALL_LOG_TAGS[:ALL_LOG_TAGS.index(args.diff_min_log_tag.upper())])
- skip_reg_exp = fr'[[:alnum:]]+ ({skip_tag_set}) #-# #:#:# [^\n]*\n'.replace('#', '[0-9]+')
- ctx.run(fr"sed -i -z -E 's/{skip_reg_exp}//g' '{args.stderr_file}'")
- if not HAVE_IMAGE:
- message = "(Unable to open file|Could not create image space)"
- ctx.run(fr"sed -i -E '/^dalvikvm(|32|64) E .* {message}/d' '{args.stderr_file}'")
- if ANDROID_LOG_TAGS != "*:i" and "D" in skip_tag_set:
- ctx.run(fr"sed -i -E '/^(Time zone|I18n) APEX ICU file found/d' '{args.stderr_file}'")
+ filter_output()
diff --git a/test/run-test b/test/run-test
index e283079..f2be1d8 100755
--- a/test/run-test
+++ b/test/run-test
@@ -113,6 +113,15 @@
tmp_dir = f"{TMPDIR}/{test_dir}"
checker = f"{progdir}/../tools/checker/checker.py"
+ ON_VM = os.environ.get("ART_TEST_ON_VM")
+ SSH_USER = os.environ.get("ART_TEST_SSH_USER")
+ SSH_HOST = os.environ.get("ART_TEST_SSH_HOST")
+ SSH_PORT = os.environ.get("ART_TEST_SSH_PORT")
+ SSH_CMD = os.environ.get("ART_SSH_CMD")
+ SCP_CMD = os.environ.get("ART_SCP_CMD")
+ CHROOT = os.environ.get("ART_TEST_CHROOT")
+ CHROOT_CMD = os.environ.get("ART_CHROOT_CMD")
+
def fail(message: str, caller:Optional[FrameInfo]=None):
caller = caller or getframeinfo(currentframe().f_back) # type: ignore
assert caller
@@ -939,7 +948,10 @@
os.chdir(oldwd)
shutil.rmtree(tmp_dir)
if target_mode == "yes":
- run(f"adb shell rm -rf {chroot_dex_location}")
+ if ON_VM:
+ run(f"{SSH_CMD} \"rm -rf {chroot_dex_location}\"")
+ else:
+ run(f"adb shell rm -rf {chroot_dex_location}")
print(f"{TEST_NAME} files deleted from host" +
(" and from target" if target_mode == "yes" else ""))
else:
@@ -994,22 +1006,37 @@
print(f"{test_dir}: Run...")
if target_mode == "yes":
# Prepare the on-device test directory
- run("adb root")
- run("adb wait-for-device")
- run(f"adb shell 'rm -rf {chroot_dex_location} && mkdir -p {chroot_dex_location}'")
+ if ON_VM:
+ run(f"{SSH_CMD} 'rm -rf {chroot_dex_location} && mkdir -p {chroot_dex_location}'")
+ else:
+ run("adb root")
+ run("adb wait-for-device")
+ run(f"adb shell 'rm -rf {chroot_dex_location} && mkdir -p {chroot_dex_location}'")
push_files = [Path(runner.name)]
push_files += list(Path(".").glob(f"{TEST_NAME}*.jar"))
push_files += list(Path(".").glob(f"expected-*.txt"))
push_files += [p for p in [Path("profile"), Path("res")] if p.exists()]
- run("adb push {} {}".format(" ".join(map(str, push_files)), chroot_dex_location))
+ push_files = " ".join(map(str, push_files))
+ if ON_VM:
+ run(f"{SCP_CMD} {push_files} {SSH_USER}@{SSH_HOST}:{chroot_dex_location}")
+ else:
+ run("adb push {} {}".format(push_files, chroot_dex_location))
- chroot_prefix = f"chroot {chroot}" if chroot else ""
- run(f"adb shell {chroot_prefix} sh {DEX_LOCATION}/run.sh",
- fail_message=f"Runner {chroot_dex_location}/run.sh failed")
+ if ON_VM:
+ run(f"{SSH_CMD} {CHROOT_CMD} bash {DEX_LOCATION}/run.sh",
+ fail_message=f"Runner {chroot_dex_location}/run.sh failed")
+ else:
+ chroot_prefix = f"chroot {chroot}" if chroot else ""
+ run(f"adb shell {chroot_prefix} sh {DEX_LOCATION}/run.sh",
+ fail_message=f"Runner {chroot_dex_location}/run.sh failed")
# Copy the on-device stdout/stderr to host.
pull_files = [test_stdout, test_stderr, "expected-stdout.txt", "expected-stderr.txt"]
- run("adb pull {} .".format(" ".join(f"{chroot_dex_location}/{f}" for f in pull_files)))
+ if ON_VM:
+ srcs = " ".join(f"{SSH_USER}@{SSH_HOST}:{chroot_dex_location}/{f}" for f in pull_files)
+ run(f"{SCP_CMD} {srcs} .")
+ else:
+ run("adb pull {} .".format(" ".join(f"{chroot_dex_location}/{f}" for f in pull_files)))
else:
run(str(runner), fail_message=f"Runner {str(runner)} failed")
@@ -1049,7 +1076,10 @@
if run_checker == "yes":
if target_mode == "yes":
- run(f'adb pull "{chroot}/{cfg_output_dir}/{cfg_output}"')
+ if ON_VM:
+ run(f'{SCP_CMD} "{SSH_USER}@${SSH_HOST}:{CHROOT}/{cfg_output_dir}/{cfg_output}"')
+ else:
+ run(f'adb pull "{chroot}/{cfg_output_dir}/{cfg_output}"')
run(f'"{checker}" -q {checker_args} "{cfg_output}" "{tmp_dir}"',
fail_message="CFG checker failed")
@@ -1057,7 +1087,10 @@
if dump_cfg == "true":
assert run_optimizing == "true", "The CFG can be dumped only in optimizing mode"
if target_mode == "yes":
- run(f"adb pull {chroot}/{cfg_output_dir}/{cfg_output} {dump_cfg_path}")
+ if ON_VM:
+ run(f'{SCP_CMD} "{SSH_USER}@${SSH_HOST}:{CHROOT}/{cfg_output_dir}/{cfg_output} {dump_cfg_output}"')
+ else:
+ run(f"adb pull {chroot}/{cfg_output_dir}/{cfg_output} {dump_cfg_path}")
else:
run(f"cp {cfg_output_dir}/{cfg_output} {dump_cfg_path}")
diff --git a/test/testrunner/env.py b/test/testrunner/env.py
index d5e0543..44d0db6 100644
--- a/test/testrunner/env.py
+++ b/test/testrunner/env.py
@@ -146,3 +146,6 @@
SOONG_OUT_DIR = _get_build_var('SOONG_OUT_DIR')
ART_TEST_RUN_ON_ARM_FVP = _getEnvBoolean('ART_TEST_RUN_ON_ARM_FVP', False)
+
+ART_TEST_ON_VM = _env.get('ART_TEST_ON_VM')
+ART_SSH_CMD = _env.get('ART_SSH_CMD')
diff --git a/test/testrunner/testrunner.py b/test/testrunner/testrunner.py
index 80b98fe..4499ffd 100755
--- a/test/testrunner/testrunner.py
+++ b/test/testrunner/testrunner.py
@@ -354,8 +354,13 @@
def get_device_name():
"""
- Gets the value of ro.product.name from remote device.
+ Gets the value of ro.product.name from remote device (unless running on a VM).
"""
+ if env.ART_TEST_ON_VM:
+ return subprocess.Popen(f"{env.ART_SSH_CMD} uname -a".split(),
+ stdout = subprocess.PIPE,
+ universal_newlines=True).stdout.read().strip()
+
proc = subprocess.Popen(['adb', 'shell', 'getprop', 'ro.product.name'],
stderr=subprocess.STDOUT,
stdout = subprocess.PIPE,
@@ -710,7 +715,7 @@
failed_tests.append((test_name, 'Timed out in %d seconds' % timeout))
# HACK(b/142039427): Print extra backtraces on timeout.
- if "-target-" in test_name:
+ if "-target-" in test_name and not env.ART_TEST_ON_VM:
for i in range(8):
proc_name = "dalvikvm" + test_name[-2:]
pidof = subprocess.run(["adb", "shell", "pidof", proc_name], stdout=subprocess.PIPE)
@@ -1070,8 +1075,11 @@
def get_target_cpu_count():
- adb_command = 'adb shell cat /sys/devices/system/cpu/present'
- cpu_info_proc = subprocess.Popen(adb_command.split(), stdout=subprocess.PIPE)
+ if env.ART_TEST_ON_VM:
+ command = f"{env.ART_SSH_CMD} cat /sys/devices/system/cpu/present"
+ else:
+ command = 'adb shell cat /sys/devices/system/cpu/present'
+ cpu_info_proc = subprocess.Popen(command.split(), stdout=subprocess.PIPE)
cpu_info = cpu_info_proc.stdout.read()
if type(cpu_info) is bytes:
cpu_info = cpu_info.decode('utf-8')
diff --git a/tools/buildbot-build.sh b/tools/buildbot-build.sh
index 449ea37..e55eb35 100755
--- a/tools/buildbot-build.sh
+++ b/tools/buildbot-build.sh
@@ -25,8 +25,6 @@
exit 1
fi
-TARGET_ARCH=$(build/soong/soong_ui.bash --dumpvar-mode TARGET_ARCH)
-
# Logic for setting out_dir from build/make/core/envsetup.mk:
if [[ -z $OUT_DIR ]]; then
if [[ -z $OUT_DIR_COMMON_BASE ]]; then
@@ -210,18 +208,22 @@
arch32=x86
arch64=x86_64
fi
- for so in ${implementation_libs[@]}; do
- if [ -d "$ANDROID_PRODUCT_OUT/system/lib" ]; then
- cmd="cp -p prebuilts/runtime/mainline/platform/impl/$arch32/$so $ANDROID_PRODUCT_OUT/system/lib/$so"
- msginfo "Executing" "$cmd"
- eval "$cmd"
- fi
- if [ -d "$ANDROID_PRODUCT_OUT/system/lib64" ]; then
- cmd="cp -p prebuilts/runtime/mainline/platform/impl/$arch64/$so $ANDROID_PRODUCT_OUT/system/lib64/$so"
- msginfo "Executing" "$cmd"
- eval "$cmd"
- fi
- done
+ if [ "$TARGET_ARCH" = riscv64 ]; then
+ true # no 32-bit arch for RISC-V
+ else
+ for so in ${implementation_libs[@]}; do
+ if [ -d "$ANDROID_PRODUCT_OUT/system/lib" ]; then
+ cmd="cp -p prebuilts/runtime/mainline/platform/impl/$arch32/$so $ANDROID_PRODUCT_OUT/system/lib/$so"
+ msginfo "Executing" "$cmd"
+ eval "$cmd"
+ fi
+ if [ -d "$ANDROID_PRODUCT_OUT/system/lib64" ]; then
+ cmd="cp -p prebuilts/runtime/mainline/platform/impl/$arch64/$so $ANDROID_PRODUCT_OUT/system/lib64/$so"
+ msginfo "Executing" "$cmd"
+ eval "$cmd"
+ fi
+ done
+ fi
fi
# Create canonical name -> file name symlink in the symbol directory for the
diff --git a/tools/buildbot-cleanup-device.sh b/tools/buildbot-cleanup-device.sh
index 7dee149..7fd57b4 100755
--- a/tools/buildbot-cleanup-device.sh
+++ b/tools/buildbot-cleanup-device.sh
@@ -16,7 +16,22 @@
. "$(dirname $0)/buildbot-utils.sh"
-# Setup as root, as device cleanup requires it.
+# Testing on a Linux VM requires special cleanup.
+if [[ -n "$ART_TEST_ON_VM" ]]; then
+ [[ -d "$ART_TEST_VM_DIR" ]] || { msgfatal "no VM found in $ART_TEST_VM_DIR"; }
+ $ART_SSH_CMD "true" || { msgfatal "VM not responding (tried \"$ART_SSH_CMD true\""; }
+ $ART_SSH_CMD "
+ sudo umount $ART_TEST_CHROOT/proc
+ sudo umount $ART_TEST_CHROOT/sys
+ sudo umount $ART_TEST_CHROOT/dev
+ sudo umount $ART_TEST_CHROOT/bin
+ sudo umount $ART_TEST_CHROOT/lib
+ rm -rf $ART_TEST_CHROOT
+ "
+ exit 0
+fi
+
+# Regular Android device. Setup as root, as device cleanup requires it.
adb root
adb wait-for-device
diff --git a/tools/buildbot-setup-device.sh b/tools/buildbot-setup-device.sh
index 811ba80..90d680b 100755
--- a/tools/buildbot-setup-device.sh
+++ b/tools/buildbot-setup-device.sh
@@ -25,7 +25,38 @@
verbose=false
fi
-# Setup as root, as some actions performed here require it.
+# Testing on a Linux VM requires special setup.
+if [[ -n "$ART_TEST_ON_VM" ]]; then
+ [[ -d "$ART_TEST_VM_DIR" ]] || { msgfatal "no VM found in $ART_TEST_VM_DIR"; }
+ $ART_SSH_CMD "true" || { msgerror "no VM (tried \"$ART_SSH_CMD true\""; }
+ $ART_SSH_CMD "
+ mkdir $ART_TEST_CHROOT
+
+ mkdir $ART_TEST_CHROOT/apex
+ mkdir $ART_TEST_CHROOT/bin
+ mkdir $ART_TEST_CHROOT/data
+ mkdir $ART_TEST_CHROOT/data/local
+ mkdir $ART_TEST_CHROOT/data/local/tmp
+ mkdir $ART_TEST_CHROOT/dev
+ mkdir $ART_TEST_CHROOT/etc
+ mkdir $ART_TEST_CHROOT/lib
+ mkdir $ART_TEST_CHROOT/linkerconfig
+ mkdir $ART_TEST_CHROOT/proc
+ mkdir $ART_TEST_CHROOT/sys
+ mkdir $ART_TEST_CHROOT/system
+ mkdir $ART_TEST_CHROOT/tmp
+
+ sudo mount -t proc /proc art-test-chroot/proc
+ sudo mount -t sysfs /sys art-test-chroot/sys
+ sudo mount --bind /dev art-test-chroot/dev
+ sudo mount --bind /bin art-test-chroot/bin
+ sudo mount --bind /lib art-test-chroot/lib
+ $ART_CHROOT_CMD echo \"Hello from chroot! I am \$(uname -a).\"
+ "
+ exit 0
+fi
+
+# Regular Android device. Setup as root, as some actions performed here require it.
adb version
adb root
adb wait-for-device
diff --git a/tools/buildbot-sync.sh b/tools/buildbot-sync.sh
index ba49c61..ef9ec8b 100755
--- a/tools/buildbot-sync.sh
+++ b/tools/buildbot-sync.sh
@@ -20,27 +20,21 @@
. "$(dirname $0)/buildbot-utils.sh"
-# Setup as root, as some actions performed here require it.
-adb root
-adb wait-for-device
+if [[ -z "$ART_TEST_ON_VM" ]]; then
+ # Setup as root, as some actions performed here require it.
+ adb root
+ adb wait-for-device
+fi
if [[ -z "$ANDROID_BUILD_TOP" ]]; then
- msgerror 'ANDROID_BUILD_TOP environment variable is empty; did you forget to run `lunch`?'
- exit 1
-fi
-
-if [[ -z "$ANDROID_PRODUCT_OUT" ]]; then
- msgerror 'ANDROID_PRODUCT_OUT environment variable is empty; did you forget to run `lunch`?'
- exit 1
-fi
-
-if [[ -z "$ART_TEST_CHROOT" ]]; then
- msgerror 'ART_TEST_CHROOT environment variable is empty; ' \
+ msgfatal 'ANDROID_BUILD_TOP environment variable is empty; did you forget to run `lunch`?'
+elif [[ -z "$ANDROID_PRODUCT_OUT" ]]; then
+ msgfatal 'ANDROID_PRODUCT_OUT environment variable is empty; did you forget to run `lunch`?'
+elif [[ -z "$ART_TEST_CHROOT" ]]; then
+ msgfatal 'ART_TEST_CHROOT environment variable is empty; ' \
'please set it before running this script.'
- exit 1
fi
-
# Sync relevant product directories
# ---------------------------------
@@ -53,23 +47,40 @@
continue
fi
msginfo "Syncing $dir directory..."
- adb shell mkdir -p "$ART_TEST_CHROOT/$dir"
- adb push $dir "$ART_TEST_CHROOT/$(dirname $dir)"
+ if [[ -n "$ART_TEST_ON_VM" ]]; then
+ $ART_RSYNC_CMD -R $dir "$ART_TEST_SSH_USER@$ART_TEST_SSH_HOST:$ART_TEST_CHROOT"
+ else
+ adb shell mkdir -p "$ART_TEST_CHROOT/$dir"
+ adb push $dir "$ART_TEST_CHROOT/$(dirname $dir)"
+ fi
done
)
# Overwrite the default public.libraries.txt file with a smaller one that
# contains only the public libraries pushed to the chroot directory.
-adb push "$ANDROID_BUILD_TOP/art/tools/public.libraries.buildbot.txt" \
- "$ART_TEST_CHROOT/system/etc/public.libraries.txt"
+if [[ -n "$ART_TEST_ON_VM" ]]; then
+ $ART_RSYNC_CMD "$ANDROID_BUILD_TOP/art/tools/public.libraries.buildbot.txt" \
+ "$ART_TEST_SSH_USER@$ART_TEST_SSH_HOST:$ART_TEST_CHROOT/system/etc/public.libraries.txt"
+else
+ adb push "$ANDROID_BUILD_TOP/art/tools/public.libraries.buildbot.txt" \
+ "$ART_TEST_CHROOT/system/etc/public.libraries.txt"
+fi
# Create the framework directory if it doesn't exist. Some gtests need it.
-adb shell mkdir -p "$ART_TEST_CHROOT/system/framework"
+if [[ -n "$ART_TEST_ON_VM" ]]; then
+ $ART_SSH_CMD "$ART_CHROOT_CMD mkdir -p $ART_TEST_CHROOT/system/framework"
+else
+ adb shell mkdir -p "$ART_TEST_CHROOT/system/framework"
+fi
# APEX packages activation.
# -------------------------
-adb shell mkdir -p "$ART_TEST_CHROOT/apex"
+if [[ -n "$ART_TEST_ON_VM" ]]; then
+ $ART_SSH_CMD "$ART_CHROOT_CMD mkdir -p $ART_TEST_CHROOT/apex"
+else
+ adb shell mkdir -p "$ART_TEST_CHROOT/apex"
+fi
# Manually "activate" the flattened APEX $1 by syncing it to /apex/$2 in the
# chroot. $2 defaults to $1.
@@ -101,8 +112,13 @@
fi
msginfo "Activating APEX ${src_apex} as ${dst_apex}..."
- adb shell rm -rf "$ART_TEST_CHROOT/apex/${dst_apex}"
- adb push $src_apex_path "$ART_TEST_CHROOT/apex/${dst_apex}"
+ if [[ -n "$ART_TEST_ON_VM" ]]; then
+ $ART_RSYNC_CMD $src_apex_path/* \
+ "$ART_TEST_SSH_USER@$ART_TEST_SSH_HOST:$ART_TEST_CHROOT/apex/${dst_apex}"
+ else
+ adb shell rm -rf "$ART_TEST_CHROOT/apex/${dst_apex}"
+ adb push $src_apex_path "$ART_TEST_CHROOT/apex/${dst_apex}"
+ fi
}
# "Activate" the required APEX modules.
@@ -113,19 +129,34 @@
activate_apex com.android.conscrypt
activate_apex com.android.os.statsd
-# Generate primary boot images on device for testing.
-for b in {32,64}; do
- basename="generate-boot-image$b"
- bin_on_host="$ANDROID_PRODUCT_OUT/system/bin/$basename"
- bin_on_device="/data/local/tmp/$basename"
- output_dir="/system/framework/art_boot_images"
- if [ -f $bin_on_host ]; then
- msginfo "Generating the primary boot image ($b-bit)..."
- adb push "$bin_on_host" "$ART_TEST_CHROOT$bin_on_device"
- adb shell mkdir -p "$ART_TEST_CHROOT$output_dir"
- # `compiler-filter=speed-profile` is required because OatDumpTest checks the compiled code in
- # the boot image.
- adb shell chroot "$ART_TEST_CHROOT" \
- "$bin_on_device" --output-dir=$output_dir --compiler-filter=speed-profile
- fi
-done
+if [[ "$TARGET_ARCH" = "riscv64" ]]; then
+ true # Skip boot image generation for RISC-V; it's not supported.
+else
+ # Generate primary boot images on device for testing.
+ for b in {32,64}; do
+ basename="generate-boot-image$b"
+ bin_on_host="$ANDROID_PRODUCT_OUT/system/bin/$basename"
+ bin_on_device="/data/local/tmp/$basename"
+ output_dir="/system/framework/art_boot_images"
+ if [ -f $bin_on_host ]; then
+ msginfo "Generating the primary boot image ($b-bit)..."
+ if [[ -n "$ART_TEST_ON_VM" ]]; then
+ $ART_RSYNC_CMD "$bin_on_host" \
+ "$ART_TEST_SSH_USER@$ART_TEST_SSH_HOST:$ART_TEST_CHROOT$bin_on_device"
+ $ART_SSH_CMD "mkdir -p $ART_TEST_CHROOT$output_dir"
+ else
+ adb push "$bin_on_host" "$ART_TEST_CHROOT$bin_on_device"
+ adb shell mkdir -p "$ART_TEST_CHROOT$output_dir"
+ fi
+ # `compiler-filter=speed-profile` is required because OatDumpTest checks the compiled code in
+ # the boot image.
+ if [[ -n "$ART_TEST_ON_VM" ]]; then
+ $ART_SSH_CMD \
+ "$ART_CHROOT_CMD $bin_on_device --output-dir=$output_dir --compiler-filter=speed-profile"
+ else
+ adb shell chroot "$ART_TEST_CHROOT" \
+ "$bin_on_device" --output-dir=$output_dir --compiler-filter=speed-profile
+ fi
+ fi
+ done
+fi
diff --git a/tools/buildbot-teardown-device.sh b/tools/buildbot-teardown-device.sh
index 156b4f1..e71dcbe 100755
--- a/tools/buildbot-teardown-device.sh
+++ b/tools/buildbot-teardown-device.sh
@@ -19,6 +19,8 @@
. "$(dirname $0)/buildbot-utils.sh"
+[[ -n "$ART_TEST_ON_VM" ]] && exit 0
+
# Setup as root, as some actions performed here require it.
adb root
adb wait-for-device
diff --git a/tools/buildbot-utils.sh b/tools/buildbot-utils.sh
index 32ed234..5bc58af 100755
--- a/tools/buildbot-utils.sh
+++ b/tools/buildbot-utils.sh
@@ -53,7 +53,43 @@
echo -e "${boldred}Error: ${nc}${message}"
}
+function msgfatal() {
+ local message="$*"
+ echo -e "${boldred}Fatal: ${nc}${message}"
+ exit 1
+}
+
function msgnote() {
local message="$*"
echo -e "${boldcyan}Note: ${nc}${message}"
}
+
+export TARGET_ARCH=$(build/soong/soong_ui.bash --dumpvar-mode TARGET_ARCH)
+
+# Do some checks and prepare environment for tests that run on Linux (not on Android).
+if [[ -n "$ART_TEST_ON_VM" ]]; then
+ if [[ -z $ANDROID_BUILD_TOP ]]; then
+ msgfatal "ANDROID_BUILD_TOP is not set"
+ elif [[ -z "$ART_TEST_SSH_USER" ]]; then
+ msgfatal "ART_TEST_SSH_USER not set"
+ elif [[ -z "$ART_TEST_SSH_HOST" ]]; then
+ msgfatal "ART_TEST_SSH_HOST not set"
+ elif [[ -z "$ART_TEST_SSH_PORT" ]]; then
+ msgfatal "ART_TEST_SSH_PORT not set"
+ fi
+
+ export ART_TEST_CHROOT="/home/$ART_TEST_SSH_USER/art-test-chroot"
+ export ART_CHROOT_CMD="unshare --user --map-root-user chroot art-test-chroot"
+ export ART_SSH_CMD="ssh -q -p $ART_TEST_SSH_PORT $ART_TEST_SSH_USER@$ART_TEST_SSH_HOST"
+ export ART_SCP_CMD="scp -P $ART_TEST_SSH_PORT -p -r"
+ export ART_RSYNC_CMD="rsync -az"
+ export RSYNC_RSH="ssh -p $ART_TEST_SSH_PORT" # don't prefix with "ART_", rsync expects this name
+
+ if [[ "$TARGET_ARCH" =~ ^(arm64|riscv64)$ ]]; then
+ export ART_TEST_VM_IMG="ubuntu-22.04-server-cloudimg-$TARGET_ARCH.img"
+ export ART_TEST_VM_DIR="$ANDROID_BUILD_TOP/vm/$TARGET_ARCH"
+ export ART_TEST_VM="$ART_TEST_VM_DIR/$ART_TEST_VM_IMG"
+ else
+ msgfatal "unexpected TARGET_ARCH=$TARGET_ARCH; expected one of {arm64,riscv64}"
+ fi
+fi
diff --git a/tools/buildbot-vm.sh b/tools/buildbot-vm.sh
new file mode 100755
index 0000000..524a57b
--- /dev/null
+++ b/tools/buildbot-vm.sh
@@ -0,0 +1,128 @@
+#! /bin/bash
+#
+# Copyright (C) 2023 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+set -e
+
+ART_TEST_ON_VM=true . "$(dirname $0)/buildbot-utils.sh"
+
+known_actions="create|boot|setup-ssh|connect|quit"
+
+if [[ -z $ANDROID_BUILD_TOP ]]; then
+ msgfatal "ANDROID_BUILD_TOP is not set"
+elif [[ ( $# -ne 1 ) || ! ( "$1" =~ ^($known_actions)$ ) ]]; then
+ msgfatal "usage: $0 <$known_actions>"
+fi
+
+action="$1"
+
+get_stable_binary() {
+ mkdir tmp && cd tmp
+ wget "http://security.ubuntu.com/ubuntu/pool/main/$1"
+ 7z x "$(basename $1)" && zstd -d data.tar.zst && tar -xf data.tar
+ mv "$2" ..
+ cd .. && rm -rf tmp
+}
+
+if [[ $action = create ]]; then
+(
+ rm -rf "$ART_TEST_VM_DIR"
+ mkdir -p "$ART_TEST_VM_DIR"
+ cd "$ART_TEST_VM_DIR"
+
+ # sudo apt install qemu-system-<arch> qemu-efi cloud-image-utils
+
+ # Get the cloud image for Ubunty 22.04 (Jammy)
+ wget "http://cloud-images.ubuntu.com/releases/22.04/release/$ART_TEST_VM_IMG"
+
+ if [[ "$TARGET_ARCH" = "riscv64" ]]; then
+ # Get U-Boot for Ubuntu 22.04 (Jammy)
+ get_stable_binary \
+ u/u-boot/u-boot-qemu_2022.01+dfsg-2ubuntu2.3_all.deb \
+ usr/lib/u-boot/qemu-riscv64_smode/uboot.elf
+
+ # Get OpenSBI for Ubuntu 22.04 (Jammy)
+ get_stable_binary \
+ o/opensbi/opensbi_1.1-0ubuntu0.22.04.1_all.deb \
+ usr/lib/riscv64-linux-gnu/opensbi/generic/fw_jump.elf
+
+ elif [[ "$TARGET_ARCH" = "arm64" ]]; then
+ # Get EFI (ARM64) for Ubuntu 22.04 (Jammy)
+ get_stable_binary \
+ e/edk2/qemu-efi-aarch64_2022.02-3ubuntu0.22.04.1_all.deb \
+ usr/share/qemu-efi-aarch64/QEMU_EFI.fd
+
+ dd if=/dev/zero of=flash0.img bs=1M count=64
+ dd if=QEMU_EFI.fd of=flash0.img conv=notrunc
+ dd if=/dev/zero of=flash1.img bs=1M count=64
+ fi
+
+ qemu-img resize "$ART_TEST_VM_IMG" +128G
+
+ # https://help.ubuntu.com/community/CloudInit
+ cat >user-data <<EOF
+#cloud-config
+ssh_pwauth: true
+chpasswd:
+ expire: false
+ list:
+ - $ART_TEST_SSH_USER:ubuntu
+EOF
+ cloud-localds user-data.img user-data
+)
+elif [[ $action = boot ]]; then
+(
+ cd "$ART_TEST_VM_DIR"
+ if [[ "$TARGET_ARCH" = "riscv64" ]]; then
+ qemu-system-riscv64 \
+ -m 16G \
+ -smp 8 \
+ -M virt \
+ -nographic \
+ -bios fw_jump.elf \
+ -kernel uboot.elf \
+ -drive file="$ART_TEST_VM_IMG",if=virtio \
+ -drive file=user-data.img,format=raw,if=virtio \
+ -device virtio-net-device,netdev=usernet \
+ -netdev user,id=usernet,hostfwd=tcp::$ART_TEST_SSH_PORT-:22
+ elif [[ "$TARGET_ARCH" = "arm64" ]]; then
+ qemu-system-aarch64 \
+ -m 16G \
+ -smp 8 \
+ -cpu cortex-a57 \
+ -M virt \
+ -nographic \
+ -drive if=none,file="$ART_TEST_VM_IMG",id=hd0 \
+ -pflash flash0.img \
+ -pflash flash1.img \
+ -drive file=user-data.img,format=raw,id=cloud \
+ -device virtio-blk-device,drive=hd0 \
+ -device virtio-net-device,netdev=usernet \
+ -netdev user,id=usernet,hostfwd=tcp::$ART_TEST_SSH_PORT-:22
+ fi
+
+)
+elif [[ $action = setup-ssh ]]; then
+ # Clean up mentions of this VM from known_hosts
+ sed -i -E "/\[$ART_TEST_SSH_HOST.*\]:$ART_TEST_SSH_PORT .*/d" $HOME/.ssh/known_hosts
+ ssh-copy-id -p "$ART_TEST_SSH_PORT" "$ART_TEST_SSH_USER@$ART_TEST_SSH_HOST"
+
+elif [[ $action = connect ]]; then
+ ssh -p "$ART_TEST_SSH_PORT" "$ART_TEST_SSH_USER@$ART_TEST_SSH_HOST"
+
+elif [[ $action = quit ]]; then
+ ssh -p "$ART_TEST_SSH_PORT" "$ART_TEST_SSH_USER@$ART_TEST_SSH_HOST" "sudo poweroff"
+
+fi
diff --git a/tools/run-gtests.sh b/tools/run-gtests.sh
index 21064c1..0f333f6 100755
--- a/tools/run-gtests.sh
+++ b/tools/run-gtests.sh
@@ -59,9 +59,17 @@
options="$@"
+run_in_chroot() {
+ if [ -n $ART_TEST_ON_VM ]; then
+ $ART_SSH_CMD $ART_CHROOT_CMD $@
+ else
+ "$adb" shell chroot "$ART_TEST_CHROOT" $@
+ fi
+}
+
if [[ ${#tests[@]} -eq 0 ]]; then
# Search for executables under the `bin/art` directory of the ART APEX.
- readarray -t tests <<<$("$adb" shell chroot "$ART_TEST_CHROOT" \
+ readarray -t tests <<<$(run_in_chroot \
find "$android_art_root/bin/art" -type f -perm /ugo+x | sort)
fi
@@ -69,7 +77,7 @@
for t in ${tests[@]}; do
echo "$t"
- "$adb" shell chroot "$ART_TEST_CHROOT" \
+ run_in_chroot \
env ANDROID_ART_ROOT="$android_art_root" \
ANDROID_I18N_ROOT="$android_i18n_root" \
ANDROID_TZDATA_ROOT="$android_tzdata_root" \