#!/bin/sh # Run scrcpy-android tests inside the project-local Docker image. # # ./test unit tests (JVM only, no KVM) # ./test unit same # ./test e2e real two-emulator pairing/mirroring E2E (needs /dev/kvm) # ./test all unit + e2e # ./test screenshots # boot emulator, capture the fastlane store screenshots # into fastlane/metadata/.../phoneScreenshots (needs /dev/kvm) # ./test apk build a signed release APK with a throwaway keystore # (exercises scripts/build-apk end-to-end, no KVM) # ./test server build and verify the pinned scrcpy server source # # The image is built on first run and cached as $IMG. # /dev/kvm must be readable by the invoking user for e2e and screenshots. set -eu ROOT="$(cd "$(dirname "$0")" && pwd)" cmd="${1:-unit}" mkdir -p "$ROOT/.tools" exec 9>"$ROOT/.tools/test.lock" if ! flock -n 9; then echo "test: another repository test is running" >&2 exit 3 fi target=unit case "$cmd" in e2e|all|screenshots) target=e2e ;; esac # The image tag is a hash of the rig inputs, so editing the Dockerfile # or the SDK package list automatically invalidates the cached image. IMG="scrcpy-android-test-$target:$(cat "$ROOT/test-rig/Dockerfile" \ "$ROOT/test-rig/sdk-packages.txt" "$ROOT/test-rig/sdk-packages-e2e.txt" \ | sha256sum | cut -c1-12)" case "$cmd" in unit|server|e2e|all|screenshots|apk) ;; -h|--help) # Usage is the header comment block: print from line 2 down to # the first non-comment line. awk 'NR > 1 && /^#/ { sub(/^# ?/, ""); print; next } NR > 1 { exit }' "$0" >&2 exit 0 ;; *) echo "usage: $0 [unit|server|e2e|all|screenshots|apk]" >&2 exit 2 ;; esac # Cheap supply-chain check: the gradle wrapper jar is a small binary # we check into the tree; verify its sha before letting it run. "$ROOT/scripts/check-wrapper" "$ROOT/scripts/check-server" # Build the image if needed. --network=host so RUN apt/curl can reach # the outside on hosts where the default docker bridge has no DNS. if ! docker image inspect "$IMG" >/dev/null 2>&1; then echo "test: building $IMG (first run, ~10 min)" >&2 # Cheap pre-flight: warn the user up-front if the host can't resolve # the docker registry, so they don't watch an apt step time out. if ! getent hosts registry-1.docker.io >/dev/null 2>&1; then echo "test: WARNING - registry-1.docker.io did not resolve via /etc/resolv.conf." >&2 echo " Build will likely fail. Fix the host DNS (e.g." >&2 echo " append 'nameserver 1.1.1.1' to /etc/resolv.conf) and retry." >&2 fi docker build --network=host --target "$target" -t "$IMG" "$ROOT/test-rig" fi # KVM check for tiers that boot the emulator. needs_kvm= KVM_GID= case "$cmd" in e2e|all|screenshots) needs_kvm=1 ;; esac if [ -n "$needs_kvm" ]; then if [ ! -e /dev/kvm ]; then echo "test: /dev/kvm not present on this host - e2e cannot run." >&2 exit 3 fi if [ ! -r /dev/kvm ] || [ ! -w /dev/kvm ]; then echo "test: /dev/kvm exists but is not rw for uid $(id -u)." >&2 echo " one-time fix:" >&2 echo " sudo usermod -aG kvm $(id -un)" >&2 echo " then log out and back in" >&2 exit 3 fi # Pass through /dev/kvm's owning gid (numeric - group names from the # host don't exist inside the container) so the container user # (mapped to host uid:gid) can actually open it. KVM_GID=$(stat -c %g /dev/kvm) fi # Forward Ctrl-C cleanly, run as the host user so files in .tools/ stay # owned by the invoker (avoids root-owned gradle cache on the host). # --network=host so the gradle wrapper can fetch the distribution and # maven deps in environments where the docker bridge DNS misbehaves. set -- docker run --rm -i --network=host if [ -n "$needs_kvm" ]; then set -- "$@" --device /dev/kvm --group-add "$KVM_GID" \ --tmpfs /work/.tools/avd:rw,size=10g,mode=1777 fi exec "$@" \ -u "$(id -u):$(id -g)" \ -v "$ROOT":/work \ -w /work \ -e ANDROID_USER_HOME=/work/.tools/android-home \ "$IMG" \ /work/test-rig/run.sh "$cmd"