Merge "Adevice update integration test; keep symlinks" into main
diff --git a/adevice/integration_tests/build_adevice_integration_tests.sh b/adevice/integration_tests/build_adevice_integration_tests.sh
new file mode 100755
index 0000000..d706f97
--- /dev/null
+++ b/adevice/integration_tests/build_adevice_integration_tests.sh
@@ -0,0 +1,101 @@
+#!/usr/bin/env bash
+
+# Copyright (C) 2024 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.
+
+# This script is dedicated to build the atest integration test in build server.
+# To run the test locally, it's recommended to invoke the test via
+# `atest atest_integration_tests` or `python atest_integration_tests.py`.
+# For usage examples please run `python atest_integration_tests.py --help`.
+
+set -eo pipefail
+set -x
+
+# Legacy support for the deprecated argument --artifacts_dir and directory name
+for ((i=1; i<=$#; i++)); do
+ arg="${@:$i:1}"
+ case "$arg" in
+ --artifacts_dir)
+ export SNAPSHOT_STORAGE_TAR_PATH="${@:$i+1:1}"/adevice_integration_tests.tar
+ i=$((i+1))
+ ;;
+ *)
+ filtered_args+=("$arg")
+ ;;
+ esac
+done
+
+if [ -n "${DIST_DIR}" ] ; then
+ export SNAPSHOT_STORAGE_TAR_PATH=${DIST_DIR}/adevice_integration_tests.tar
+fi
+
+function get_build_var()
+{
+ (${PWD}/build/soong/soong_ui.bash --dumpvar-mode --abs $1)
+}
+
+if [ ! -n "${ANDROID_BUILD_TOP}" ] ; then
+ export ANDROID_BUILD_TOP=${PWD}
+fi
+
+# Uncomment the following if verifying locally without running envsetup
+# if [ ! -n "${TARGET_PRODUCT}" ] || [ ! -n "${TARGET_BUILD_VARIANT}" ] ; then
+# export \
+# TARGET_PRODUCT=aosp_x86_64 \
+# TARGET_BUILD_VARIANT=userdebug \
+# TARGET_RELEASE="trunk_staging"
+# fi
+
+# ANDROID_BUILD_TOP is deprecated, so don't use it throughout the script.
+# But if someone sets it, we'll respect it.
+cd ${ANDROID_BUILD_TOP:-.}
+
+if [ ! -n "${ANDROID_PRODUCT_OUT}" ] ; then
+ export ANDROID_PRODUCT_OUT=$(get_build_var PRODUCT_OUT)
+fi
+
+if [ ! -n "${OUT}" ] ; then
+ export OUT=$ANDROID_PRODUCT_OUT
+fi
+
+if [ ! -n "${ANDROID_HOST_OUT}" ] ; then
+ export ANDROID_HOST_OUT=$(get_build_var HOST_OUT)
+fi
+
+if [ ! -n "${ANDROID_TARGET_OUT_TESTCASES}" ] ; then
+ export ANDROID_TARGET_OUT_TESTCASES=$(get_build_var TARGET_OUT_TESTCASES)
+fi
+
+if [ ! -n "${HOST_OUT_TESTCASES}" ] ; then
+ export HOST_OUT_TESTCASES=$(get_build_var HOST_OUT_TESTCASES)
+ export ANDROID_HOST_OUT_TESTCASES=$HOST_OUT_TESTCASES
+fi
+
+if [ ! -n "${ANDROID_JAVA_HOME}" ] ; then
+ export ANDROID_JAVA_HOME=$(get_build_var ANDROID_JAVA_HOME)
+ export JAVA_HOME=$(get_build_var JAVA_HOME)
+fi
+
+export REMOTE_AVD=true
+
+# Use the versioned Python binaries in prebuilts/ for a reproducible
+# build with minimal reliance on host tools. Add build/bazel/bin to PATH since
+# atest needs 'b'
+export PATH=${PWD}/prebuilts/build-tools/path/linux-x86:${PWD}/build/bazel/bin:${PWD}/out/host/linux-x86/bin/:${PATH}
+
+# Use the versioned Java binaries in prebuilds/ for a reproducible
+# build with minimal reliance on host tools.
+export PATH=${ANDROID_JAVA_HOME}/bin:${PATH}
+
+python3 tools/asuite/atest/integration_tests/adevice_integration_tests.py "${filtered_args[@]}" --build --tar_snapshot
diff --git a/atest/integration_tests/Android.bp b/atest/integration_tests/Android.bp
index 2b90e87..08bf8ce 100644
--- a/atest/integration_tests/Android.bp
+++ b/atest/integration_tests/Android.bp
@@ -103,6 +103,21 @@
}
python_test_host {
+ name: "adevice_integration_tests",
+ srcs: [
+ "adevice_integration_tests.py",
+ "adevice_command_success_tests.py",
+ ],
+ test_config_template: ":atest_integration_test_config_template",
+ test_options: {
+ unit_test: false,
+ },
+ defaults: [
+ "atest_integration_test_defaults",
+ ],
+}
+
+python_test_host {
name: "atest_command_success_tests",
srcs: [
"atest_command_success_tests.py",
@@ -117,6 +132,20 @@
}
python_test_host {
+ name: "adevice_command_success_tests",
+ srcs: [
+ "adevice_command_success_tests.py",
+ ],
+ test_config_template: "split_build_test_script_config.xml",
+ test_options: {
+ unit_test: false,
+ },
+ defaults: [
+ "atest_integration_test_defaults",
+ ],
+}
+
+python_test_host {
name: "atest_command_verification_tests",
srcs: [
"atest_command_verification_tests.py",
diff --git a/atest/integration_tests/adevice_command_success_tests.py b/atest/integration_tests/adevice_command_success_tests.py
new file mode 100644
index 0000000..f7dd9ca
--- /dev/null
+++ b/atest/integration_tests/adevice_command_success_tests.py
@@ -0,0 +1,77 @@
+#!/usr/bin/env python3
+#
+# Copyright 2024, 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.
+
+"""Tests to check if adevice commands were executed with success exit codes."""
+
+import atest_integration_test
+
+
+class AdeviceCommandSuccessTests(atest_integration_test.AtestTestCase):
+ """Test whether the adevice commands run with success exit codes."""
+
+ def setUp(self):
+ super().setUp()
+ self._default_snapshot_include_paths += [
+ '$OUT_DIR/combined-*.ninja',
+ '$OUT_DIR/build-*.ninja',
+ '$OUT_DIR/soong/*.ninja',
+ '$OUT_DIR/target/',
+ ]
+
+ self._default_snapshot_env_keys += ['TARGET_PRODUCT', 'ANDROID_BUILD_TOP']
+ self._default_snapshot_exclude_paths = []
+
+ def test_status(self):
+ """Test if status command runs successfully across periodic repo syncs."""
+ self._verify_adevice_command_success('adevice status')
+
+ def test_update(self):
+ """Test if update command runs successfully across periodic repo syncs."""
+ self._verify_adevice_command_success(
+ 'adevice update --max-allowed-changes=6000'
+ )
+
+ def _verify_adevice_command_success(self, test_cmd: str):
+ """Verifies whether an adevice command run completed with exit code 0."""
+ script = self.create_atest_script()
+
+ def build_step(
+ step_in: atest_integration_test.StepInput,
+ ) -> atest_integration_test.StepOutput:
+ self._run_shell_command(
+ 'build/soong/soong_ui.bash --make-mode'.split(),
+ env=step_in.get_env(),
+ cwd=step_in.get_repo_root(),
+ print_output=True,
+ ).check_returncode()
+ return self.create_step_output()
+
+ def test_step(step_in: atest_integration_test.StepInput) -> None:
+ self._run_shell_command(
+ test_cmd.split(),
+ env=step_in.get_env(),
+ cwd=step_in.get_repo_root(),
+ print_output=True,
+ ).check_returncode()
+ print(step_in.get_env())
+
+ script.add_build_step(build_step)
+ script.add_test_step(test_step)
+ script.run()
+
+
+if __name__ == '__main__':
+ atest_integration_test.main()
diff --git a/atest/integration_tests/adevice_integration_tests.py b/atest/integration_tests/adevice_integration_tests.py
new file mode 100644
index 0000000..ad5a69b
--- /dev/null
+++ b/atest/integration_tests/adevice_integration_tests.py
@@ -0,0 +1,25 @@
+#!/usr/bin/env python3
+#
+# Copyright 2024, 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.
+
+"""A collection of all integration test cases for adevice."""
+
+# pylint: disable=wildcard-import
+from adevice_command_success_tests import *
+import atest_integration_test
+
+
+if __name__ == '__main__':
+ atest_integration_test.main()
diff --git a/atest/integration_tests/snapshot.py b/atest/integration_tests/snapshot.py
index 55cedac..53d164f 100644
--- a/atest/integration_tests/snapshot.py
+++ b/atest/integration_tests/snapshot.py
@@ -230,6 +230,7 @@
permissions: int,
symlink_target: str,
is_directory: bool,
+ is_target_in_workspace: bool = False,
):
self.path = path
self.timestamp = timestamp
@@ -237,6 +238,7 @@
self.permissions = permissions
self.symlink_target = symlink_target
self.is_directory = is_directory
+ self.is_target_in_workspace = is_target_in_workspace
class _BlobStore:
@@ -429,16 +431,19 @@
)
def process_link(path: pathlib.Path) -> None:
- symlink_target = path.resolve()
+ relative_path = path.relative_to(root_path).as_posix()
+ symlink_target = path.readlink()
+ is_target_in_workspace = False
if symlink_target.is_relative_to(root_path):
symlink_target = symlink_target.relative_to(root_path)
- relative_path = path.relative_to(root_path).as_posix()
+ is_target_in_workspace = True
file_infos[relative_path] = _FileInfo(
relative_path,
timestamp=None,
content_hash=None,
permissions=None,
symlink_target=symlink_target.as_posix(),
+ is_target_in_workspace=is_target_in_workspace,
is_directory=False,
)
@@ -569,11 +574,10 @@
if self._is_excluded(file_path.as_posix(), exclude_paths):
continue
if file_info.symlink_target:
- target = (
- file_info.symlink_target
- if os.path.isabs(file_info.symlink_target)
- else pathlib.Path(root_path).joinpath(file_info.symlink_target)
- )
+ file_path.parent.mkdir(parents=True, exist_ok=True)
+ target = file_info.symlink_target
+ if bool(file_info.is_target_in_workspace):
+ target = pathlib.Path(root_path).joinpath(target)
file_path.parent.mkdir(parents=True, exist_ok=True)
file_path.symlink_to(target)
continue
diff --git a/atest/integration_tests/snapshot_unittest.py b/atest/integration_tests/snapshot_unittest.py
index 75e0479..1c9a4db 100644
--- a/atest/integration_tests/snapshot_unittest.py
+++ b/atest/integration_tests/snapshot_unittest.py
@@ -199,15 +199,15 @@
snapshot_name = 'a_snapshot_name'
restore_dir = self.temp_dir / 'restore'
link_file_name = 'link'
- target_file_name = 'non-existent-path'
+ target_file_name = pathlib.Path('non-existent-path')
workspace.joinpath(link_file_name).symlink_to(target_file_name)
snapshot.take_snapshot(snapshot_name, workspace, ['*'])
snapshot.restore_snapshot(snapshot_name, restore_dir)
self.assertEqual(
- restore_dir.joinpath(link_file_name).resolve(),
- restore_dir / target_file_name,
+ restore_dir.joinpath(link_file_name).readlink(),
+ target_file_name,
)
def test_restore_snapshot_preserve_dangling_absolute_links(self):