diff options
author | 2023-01-19 18:26:43 +0000 | |
---|---|---|
committer | 2023-01-30 10:27:37 +0000 | |
commit | 513801a31b69c4cf9397a85afd570a4c02b1f914 (patch) | |
tree | 8acc80bbfc629f7ef285fbb2a419ff9889814d5a | |
parent | 3633fe4e7dc8a4acaea0d9911c86683b51c1db2b (diff) |
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
-rw-r--r-- | test/README.chroot_vm.md | 74 | ||||
-rwxr-xr-x | test/default_run.py | 61 | ||||
-rwxr-xr-x | test/run-test | 55 | ||||
-rw-r--r-- | test/testrunner/env.py | 3 | ||||
-rwxr-xr-x | test/testrunner/testrunner.py | 16 | ||||
-rwxr-xr-x | tools/buildbot-build.sh | 30 | ||||
-rwxr-xr-x | tools/buildbot-cleanup-device.sh | 17 | ||||
-rwxr-xr-x | tools/buildbot-setup-device.sh | 33 | ||||
-rwxr-xr-x | tools/buildbot-sync.sh | 111 | ||||
-rwxr-xr-x | tools/buildbot-teardown-device.sh | 2 | ||||
-rwxr-xr-x | tools/buildbot-utils.sh | 36 | ||||
-rwxr-xr-x | tools/buildbot-vm.sh | 128 | ||||
-rwxr-xr-x | tools/run-gtests.sh | 12 |
13 files changed, 486 insertions, 92 deletions
diff --git a/test/README.chroot_vm.md b/test/README.chroot_vm.md new file mode 100644 index 0000000000..2f163deaf5 --- /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 f457956fa3..e102311ce7 100755 --- a/test/default_run.py +++ b/test/default_run.py @@ -213,6 +213,8 @@ def default_run(ctx, args, **kwargs): 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 @@ def default_run(ctx, args, **kwargs): 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 @@ def default_run(ctx, args, **kwargs): 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 @@ def default_run(ctx, args, **kwargs): 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 @@ def default_run(ctx, args, **kwargs): # 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 @@ def default_run(ctx, args, **kwargs): {DEBUGGER_OPTS} \ {QUOTED_DALVIKVM_BOOT_OPT} \ {TMP_DIR_OPTION} \ + {dalvikvm_logger} \ -XX:DumpNativeStackOnSigQuit:false \ -cp {DALVIKVM_CLASSPATH} {MAIN} {ARGS}" @@ -1008,6 +1015,26 @@ def default_run(ctx, args, **kwargs): 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 @@ def default_run(ctx, args, **kwargs): 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 @@ def default_run(ctx, args, **kwargs): # 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 @@ def default_run(ctx, args, **kwargs): 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 @@ def default_run(ctx, args, **kwargs): 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 e283079a11..f2be1d8b8c 100755 --- a/test/run-test +++ b/test/run-test @@ -113,6 +113,15 @@ if True: 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 @@ if True: 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 @@ if True: 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 True: 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 True: 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 d5e0543721..44d0db67ec 100644 --- a/test/testrunner/env.py +++ b/test/testrunner/env.py @@ -146,3 +146,6 @@ DIST_DIR = _get_build_var('DIST_DIR') 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 80b98feb80..4499ffd6e1 100755 --- a/test/testrunner/testrunner.py +++ b/test/testrunner/testrunner.py @@ -354,8 +354,13 @@ def find_extra_device_arguments(target): 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 @@ def run_test(command, test, test_variant, test_name): 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 parse_test_name(test_name): 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 449ea37807..e55eb3560b 100755 --- a/tools/buildbot-build.sh +++ b/tools/buildbot-build.sh @@ -25,8 +25,6 @@ if [ ! -d art ]; then 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 @@ if [[ $build_target == "yes" ]]; then 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 7dee149129..7fd57b413e 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 811ba8005f..90d680b7d2 100755 --- a/tools/buildbot-setup-device.sh +++ b/tools/buildbot-setup-device.sh @@ -25,7 +25,38 @@ else 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 ba49c6164e..ef9ec8b3a0 100755 --- a/tools/buildbot-sync.sh +++ b/tools/buildbot-sync.sh @@ -20,27 +20,21 @@ set -e . "$(dirname $0)/buildbot-utils.sh" -# Setup as root, as some actions performed here require it. -adb root -adb wait-for-device - -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 +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 "$ART_TEST_CHROOT" ]]; then - msgerror 'ART_TEST_CHROOT environment variable is empty; ' \ +if [[ -z "$ANDROID_BUILD_TOP" ]]; then + 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 @@ fi 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 @@ activate_apex() { 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.tzdata 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 156b4f1176..e71dcbef0b 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 32ed234d90..5bc58af39d 100755 --- a/tools/buildbot-utils.sh +++ b/tools/buildbot-utils.sh @@ -53,7 +53,43 @@ function msgerror() { 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 0000000000..524a57b42a --- /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 21064c1739..0f333f6e2d 100755 --- a/tools/run-gtests.sh +++ b/tools/run-gtests.sh @@ -59,9 +59,17 @@ done 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 @@ failing_tests=() 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" \ |