blob: 79aad2e0568cedce8a1af4dfcf51d6de2c9dc7dc [file] [log] [blame] [edit]
#!/usr/bin/env bash
# SPDX-License-Identifier: GPL-2.0
#
# setup_and_test.sh
#
# A standalone script to setup an Android device (Virtual or Physical) with a
# specific, fixed build configuration and run tests against it.
#
# --- Configuration Constants ---
readonly -A BUILD_TYPE_MAP=(
["pb"]="PLATFORM_BUILD"
["sb"]="GSI_BUILD"
["kb"]="KERNEL_BUILD"
["vkb"]="VENDOR_KERNEL_BUILD"
["tb"]="TEST_SUITE_BUILD"
)
readonly -a BUILD_SETUP_ORDER=("pb" "sb" "kb" "vkb")
# --- Global Variables ---
ACLOUD_OUTPUT_FILE="/tmp/ACLOUD_OUTPUT.tmp"
FILES_TO_CLEANUP=("$ACLOUD_OUTPUT_FILE")
DEVICE_TYPE=""
PLATFORM_BUILD=""
GSI_BUILD=""
KERNEL_BUILD=""
VENDOR_KERNEL_BUILD=""
SERIAL_NUMBER=""
TEST_NAME=()
TEST_DIR=""
TEST_SUITE_BUILD=""
OUTPUT_DIR=""
SKIP_BUILD=false
RESTORE_GIT_STATE=false
# Mappings for execution
declare -A ID_TYPES
declare -A TREE_PATHS
declare -A PROJECTS
declare -A COMMITS_TO_TEST_MAP
declare -A ORIGINAL_GIT_STATES # Stores original branch/commit before checkout
# --- Library Import ---
SCRIPT_PATH="$(realpath "${BASH_SOURCE[0]}")"
SCRIPT_DIR="$(dirname "${SCRIPT_PATH}")"
LIB_PATH="${SCRIPT_DIR}/common_lib.sh"
DEVICE_UTIL_PATH="${SCRIPT_DIR}/lib/device_util.sh"
if [[ ! -f "$LIB_PATH" ]]; then
echo "FATAL ERROR: Cannot find required library 'common_lib.sh'" >&2
exit 1
fi
if ! . "$LIB_PATH"; then
echo "FATAL ERROR: Failed to source library '$LIB_PATH'" >&2
exit 1
fi
if [[ ! -f "$DEVICE_UTIL_PATH" ]]; then
fail_error "FATAL ERROR: Cannot find required library 'device_util.sh'"
fi
if ! . "$DEVICE_UTIL_PATH"; then
fail_error "FATAL ERROR: Failed to source library '$DEVICE_UTIL_PATH'"
fi
# --- Scripts ---
readonly LAUNCH_CVD_SCRIPT="${SCRIPT_DIR}/launch_cvd.sh"
readonly FLASH_DEVICE_SCRIPT="${SCRIPT_DIR}/flash_device.sh"
readonly RUN_TEST_SCRIPT="${SCRIPT_DIR}/run_test_only.sh"
readonly FETCH_ARTIFACT_SCRIPT="${SCRIPT_DIR}/fetch_artifact.sh"
# --- Helper Functions ---
function print_help() {
echo "Usage: $0 [OPTIONS]"
echo ""
echo "Setup an Android device with specific builds and run tests."
echo "Accepts fixed commits, local paths, or AB URLs. Does NOT support ranges/lists."
echo ""
echo "Exit Codes:"
echo " 0: Success (Tests Passed)"
echo " 1: Test Failure"
echo " 2: Setup/Infrastructure Failure (Build, Flash, Boot)"
echo " 3: Argument/Validation Error"
echo ""
echo "Options:"
echo " -pb, --platform-build <string> Platform build definition."
echo " -kb, --kernel-build, -gki, --gki-build <string>"
echo " GKI build definition."
echo " -sb, --system-build, -gsi, --gsi-build <string>"
echo " GSI build definition."
echo " -vkb, --vendor-kernel-build <string> Vendor kernel build definition."
echo " -tb, --test-suite-build <string> Test suite definition (AB URL, local path, or fixed commit)."
echo " -t, --test <name> [Required] Test name(s) to run."
echo " -s, --serial-number <serial> Physical device serial. Default: Cuttlefish."
echo " -od, --output-dir <path> Directory for logs/artifacts."
echo " --restore Restore git repositories to their original state after testing."
echo " --skip-build Skip the build/flash step, just run tests."
echo " -h, --help Display this message."
echo ""
echo "Examples:"
echo " $0 -pb ab://git_main/target/12345 -kb ~/kernel/common:a1b2c3d -t CtsExampleTest"
}
function fail_error() {
local message="$1"
local exit_code="${2:-3}" # Default to 3 (Validation Error) if not specified
log_error "$message" "$exit_code" 2
exit "$exit_code"
}
function restore_all_git_states() {
if [[ "$RESTORE_GIT_STATE" != "true" ]]; then
return 0
fi
log_info "Restoring all modified git repositories to their original state..."
for type_code in "${!ORIGINAL_GIT_STATES[@]}"; do
local original_state="${ORIGINAL_GIT_STATES[$type_code]}"
if [[ -z "$original_state" ]]; then
continue
fi
local project_path="${TREE_PATHS[$type_code]}/${PROJECTS[$type_code]}"
if [[ -d "$project_path/.git" ]]; then
log_info "Restoring project '$project_path' to '$original_state'..."
# Capture output to avoid spamming unless error
if ! (cd "$project_path" && git checkout "$original_state" &>/dev/null); then
log_error "Failed to restore project '$project_path' to '$original_state'."
fi
fi
done
}
function cleanup() {
restore_all_git_states
# Clean up temp files
for f in "${FILES_TO_CLEANUP[@]}"; do
if [[ -f "$f" ]]; then
rm -f "$f"
fi
done
}
function parse_args() {
while (( $# > 0 )); do
case "$1" in
-h|--help)
print_help
exit 0
;;
-od|--output-dir)
shift
OUTPUT_DIR="$1"
shift
;;
-pb|--platform-build)
shift
PLATFORM_BUILD="$1"
shift
;;
-sb|-gsi|--system-build|--gsi-build)
shift
GSI_BUILD="$1"
shift
;;
-kb|-gki|--kernel-build|--gki-build)
shift
KERNEL_BUILD="$1"
shift
;;
-vkb|--vendor-kernel-build)
shift
VENDOR_KERNEL_BUILD="$1"
shift
;;
-s|--serial-number)
shift
SERIAL_NUMBER="$1"
shift
;;
-t|--test)
shift
TEST_NAME+=("$1")
shift
;;
-td|--test-dir|-tb|--test-suite-build)
shift
TEST_SUITE_BUILD="$1"
shift
;;
--restore)
RESTORE_GIT_STATE=true
shift
;;
--skip-build)
SKIP_BUILD=true
shift
;;
*)
fail_error "Unsupported flag: $1"
;;
esac
done
if (( ${#TEST_NAME[@]} == 0 )); then
fail_error "At least one test must be specified with -t."
fi
}
function parse_change_string() {
local build_str="$1"
local -n tree_path_ref="$2"
local -n project_ref="$3"
local -n commits_ref="$4"
local -n type_ref="$5"
# Check for ab:// URL
if [[ "$build_str" == ab://* ]]; then
type_ref="ab"
commits_ref=("$build_str")
return 0
fi
# Check for local path without project/commit part (fixed local tree)
if [[ ! "$build_str" == *:* ]]; then
if [[ -d "$build_str" ]]; then
type_ref="local"
commits_ref=("$build_str")
return 0
else
fail_error "Build string is not a valid directory and not a known format: $build_str"
fi
fi
# It's a format with a colon: path/to/project:commit
local path_part="${build_str%%:*}"
local commit_part="${build_str#*:}"
# Expand tilde
local expanded_path="${path_part/#\~/$HOME}"
# Resolve to absolute path to verify existence and find root
if [[ ! -d "$expanded_path" ]]; then
fail_error "Path does not exist: $expanded_path"
fi
local abs_path
abs_path=$(cd "$expanded_path" && pwd -P)
# Use find_repo_root to split Tree Root and Project Path
local repo_root
if ! repo_root=$(find_repo_root "$abs_path"); then
fail_error "Could not find Android Tree Root (.repo) for: $abs_path"
fi
tree_path_ref="$repo_root"
# Project path is the remainder (abs_path - repo_root)
# Remove prefix "$repo_root/"
if [[ "$abs_path" == "$repo_root" ]]; then
fail_error "Path cannot be the repo root itself. Must be a project."
fi
project_ref="${abs_path#$repo_root/}"
# STRICT VALIDATION: Fail on range or list
if [[ "$commit_part" == *","* ]]; then
fail_error "Error: List format (comma-separated) is NOT supported in this script. Use find_change_breakage.sh for bisection."
elif [[ "$commit_part" == *"-"* ]]; then
fail_error "Error: Range format (hyphen-separated) is NOT supported in this script. Use find_change_breakage.sh for bisection."
elif [[ -n "$commit_part" ]]; then
type_ref="fixed_commit"
commits_ref=("$commit_part")
else
fail_error "Invalid format. Commit hash missing for: $build_str"
fi
return 0
}
function validate_args() {
for type_code in "${!BUILD_TYPE_MAP[@]}"; do
local var_name="${BUILD_TYPE_MAP[$type_code]}"
local build_value="${!var_name}"
if [[ -z "$build_value" ]]; then
continue
fi
local tree_path project
local -a commits=()
local id_type=""
parse_change_string "$build_value" tree_path project commits id_type
ID_TYPES[$type_code]="$id_type"
if [[ "$id_type" == "fixed_commit" ]]; then
log_info "Validating git path for '$type_code'..."
TREE_PATHS[$type_code]="$tree_path"
PROJECTS[$type_code]="$project"
COMMITS_TO_TEST_MAP[$type_code]="${commits[0]}"
if [[ ! -d "$tree_path" ]]; then
fail_error "[$type_code] Android source tree path does not exist: $tree_path"
fi
local full_project_path="${tree_path}/${project}"
if [[ ! -d "$full_project_path/.git" ]]; then
fail_error "[$type_code] Project path is not a git repository: $full_project_path"
fi
# Verify commit exists
if ! (cd "$full_project_path" && git cat-file -e "${commits[0]}" &>/dev/null); then
fail_error "[$type_code] Commit ID '${commits[0]}' does not exist in project '$project'."
fi
fi
done
}
function determine_device_type() {
local serial="$1"
if [[ -z "$serial" ]]; then
echo "VIRTUAL"
else
echo "PHYSICAL"
fi
}
function lock_configuration() {
# Locks configuration variables to be Read-Only.
readonly DEVICE_TYPE
readonly PLATFORM_BUILD
readonly GSI_BUILD
readonly KERNEL_BUILD
readonly VENDOR_KERNEL_BUILD
readonly SERIAL_NUMBER
readonly TEST_SUITE_BUILD
readonly OUTPUT_DIR
readonly SKIP_BUILD
readonly RESTORE_GIT_STATE
readonly -A ID_TYPES
readonly -A TREE_PATHS
readonly -A PROJECTS
readonly -A COMMITS_TO_TEST_MAP
}
function git_get_current_head() {
local project_path="$1"
(cd "$project_path" && git symbolic-ref --short -q HEAD 2>/dev/null || git rev-parse HEAD)
}
function git_hard_checkout() {
local project_path="$1"
local commit_hash="$2"
log_info "Checking out commit '$commit_hash' in '$project_path'..."
if ! (cd "$project_path" && git checkout "$commit_hash"); then
fail_error "Failed to checkout commit '$commit_hash' in '$project_path'."
fi
}
# --- Main Execution Logic ---
function setup_and_run() {
for type_code in "${!BUILD_TYPE_MAP[@]}"; do
if [[ "${ID_TYPES[$type_code]}" == "fixed_commit" ]]; then
local path="${TREE_PATHS[$type_code]}/${PROJECTS[$type_code]}"
local commit="${COMMITS_TO_TEST_MAP[$type_code]}"
if [[ -z "${ORIGINAL_GIT_STATES[$type_code]}" ]]; then
ORIGINAL_GIT_STATES[$type_code]="$(git_get_current_head "$path")"
log_info "Saved original git state for $type_code: ${ORIGINAL_GIT_STATES[$type_code]}"
fi
git_hard_checkout "$path" "$commit"
fi
done
local -a setup_cmd_array=()
if [[ "$DEVICE_TYPE" == "PHYSICAL" ]]; then
setup_cmd_array=("$FLASH_DEVICE_SCRIPT" "-s" "$SERIAL_NUMBER")
else
setup_cmd_array=("$LAUNCH_CVD_SCRIPT")
fi
# Add build args
for type_code in "${BUILD_SETUP_ORDER[@]}"; do
local arg_val=""
# If it's a git type (fixed_commit), pass the Tree Path
if [[ "${ID_TYPES[$type_code]}" == "fixed_commit" ]]; then
arg_val="${TREE_PATHS[$type_code]}"
else
# ab:// or local path, pass as is
local var_name="${BUILD_TYPE_MAP[$type_code]}"
arg_val="${!var_name}"
fi
if [[ -n "$arg_val" ]]; then
setup_cmd_array+=("-$type_code" "$arg_val")
fi
done
if "$SKIP_BUILD"; then
setup_cmd_array+=("--skip-build")
fi
log_info "Executing device setup: ${setup_cmd_array[*]}"
local setup_status=0
if [[ "$DEVICE_TYPE" == "VIRTUAL" ]]; then
# We need to capture stdout to find the virtual serial
unbuffer "${setup_cmd_array[@]}" | tee "$ACLOUD_OUTPUT_FILE"
setup_status=${PIPESTATUS[0]}
else
"${setup_cmd_array[@]}"
setup_status=$?
fi
if (( setup_status != 0 )); then
fail_error "Device setup failed." 2
fi
# 4. Run Tests
run_tests_on_device
}
function run_tests_on_device() {
local input_serial_to_use="$SERIAL_NUMBER"
if [[ "$DEVICE_TYPE" == "VIRTUAL" ]]; then
if [[ -f "$ACLOUD_OUTPUT_FILE" ]]; then
input_serial_to_use=$(grep -oP "ANDROID_SERIAL=\K[\.0-9:]+" "$ACLOUD_OUTPUT_FILE")
fi
if [[ -z "$input_serial_to_use" ]]; then
fail_error "Could not determine virtual device serial from output." 2
fi
fi
log_info "Initializing device interactions for serial: $input_serial_to_use"
if ! device_util::init "$input_serial_to_use"; then
fail_error "Failed to initialize device utility." 2
fi
if [[ "$DEVICE_TYPE" == "VIRTUAL" ]]; then
device_util::wait_for_boot_complete || fail_error "Device failed to boot." 2
else
device_util::unlock_screen || fail_error "Failed to unlock screen." 2
device_util::skip_setup_wizard || fail_error "Failed to skip setup wizard." 2
fi
local adb_serial
adb_serial=$(device_util::get_adb_serial)
# Ensure test logs dir exists
local -a test_cmd=("$RUN_TEST_SCRIPT" "-td" "$TEST_SUITE_BUILD")
test_cmd+=("-s" "$adb_serial")
if [[ -n "$OUTPUT_DIR" ]]; then
local logs_dir="${OUTPUT_DIR}/test_logs"
test_cmd+=("-tl" "$logs_dir")
fi
for test in "${TEST_NAME[@]}"; do
test_cmd+=("-t" "$test")
done
log_info "Executing test command: ${test_cmd[*]}"
"${test_cmd[@]}"
local test_status=$?
if (( test_status == 0 )); then
log_info "Tests PASSED."
exit 0
else
fail_error "Tests FAILED." 1
fi
}
function main() {
trap cleanup EXIT
check_commands_available "unzip" "git" "repo" "unbuffer" || fail_error "Missing required commands."
parse_args "$@"
validate_args
DEVICE_TYPE=$(determine_device_type "$SERIAL_NUMBER")
lock_configuration
setup_and_run
}
main "$@"