diff --git a/.semaphore/semaphore.yml b/.semaphore/semaphore.yml index f80cd11f2..b7bdd0790 100644 --- a/.semaphore/semaphore.yml +++ b/.semaphore/semaphore.yml @@ -25,47 +25,28 @@ global_job_config: value: /home/semaphore/Android/Sdk prologue: commands: - # fix DNS to ping *.localhost and *.flowcrypt.test - sudo apt-get update - - sudo apt install -y dnsmasq resolvconf - - echo "#added by flowcrypt" | sudo tee -a /etc/dnsmasq.conf - - echo "listen-address=127.0.0.1" | sudo tee -a /etc/dnsmasq.conf - - echo "address=/test/127.0.0.1" | sudo tee -a /etc/dnsmasq.conf - - echo "address=/localhost/127.0.0.1" | sudo tee -a /etc/dnsmasq.conf - - echo "server=8.8.8.8" | sudo tee -a /etc/dnsmasq.conf - # redirect the systemd-resolved to use the localhost as the primary nameserver - - sudo sed -i '1inameserver 127.0.0.1\' /etc/resolv.conf - - sudo systemctl restart dnsmasq - # print some debug info - - ping fel.localhost -c 1 - - ping fel.flowcrypt.test -c 1 # use JAVA 21 by default - sem-version java 21 # general settings - export PATH=${ANDROID_HOME}/emulator:${ANDROID_HOME}/cmdline-tools/latest/bin:${ANDROID_HOME}/platform-tools:${PATH} - sudo rm -rf ~/.rbenv ~/.phpbrew - checkout + # fix DNS to ping *.localhost and *.flowcrypt.test + - ./script/ci-setup-DNS.sh # init environment variables - - export SUM=$(checksum build.gradle.kts)-$(checksum FlowCrypt/build.gradle.kts)-$(checksum ./script/ci-install-android-sdk.sh) - - export APP_GRADLE_CACHE=gradle-cache-$SUM # per conf files hash - - export ANDROID_SDK_CACHE=android-sdk-$SUM # per conf files hash - - export BUILD_CXX_CACHE=build-cxx-cache-$SEMAPHORE_GIT_BRANCH-$SUM # per branch and conf files hash - - export BUILD_NATIVE_CACHE=build-native-cache-$SEMAPHORE_GIT_BRANCH-$SUM # per branch and conf files hash - - export BUILD_CACHE=build-cache-$SEMAPHORE_GIT_BRANCH-$SUM # per branch and conf files hash + - export GRADLE_SUM=$(checksum build.gradle.kts)-$(checksum FlowCrypt/build.gradle.kts) + - export SDK_SUM=$(checksum ./script/ci-install-android-sdk.sh) + - export APP_GRADLE_CACHE=project-gradle-cache-$GRADLE_SUM # project .gradle cache, per Gradle config hash + - export ANDROID_SDK_CACHE=android-sdk-$SDK_SUM # Android SDK cache, per SDK installer hash # restore app caches - cache restore $APP_GRADLE_CACHE - - cache restore $BUILD_CXX_CACHE - - cache restore $BUILD_NATIVE_CACHE - - cache restore $BUILD_CACHE # restore global caches - cache restore $ANDROID_SDK_CACHE - cache restore gradle-wrapper - - cache restore gradle-cache - - cache restore android-build-cache + - cache restore gradle-cache-$GRADLE_SUM # Install Android dependencies if needed - ./script/ci-install-android-sdk.sh - # Install ninja - - sudo apt install -y ninja-build blocks: - name: 'Build' execution_time_limit: @@ -79,27 +60,20 @@ blocks: # print Java version - java -version # compile project - - ./gradlew --console=plain assembleConsumerUiTests + - ./gradlew --console=plain --no-daemon --build-cache assembleConsumerUiTests epilogue: on_pass: commands: # store app cache - echo "Store the app cache" - cache has_key $APP_GRADLE_CACHE || cache store $APP_GRADLE_CACHE .gradle - - cache has_key $BUILD_CXX_CACHE || cache store $BUILD_CXX_CACHE FlowCrypt/.cxx - - cache has_key $BUILD_NATIVE_CACHE || cache store $BUILD_NATIVE_CACHE FlowCrypt/.externalNativeBuild - - cache has_key $BUILD_CACHE || cache store $BUILD_CACHE FlowCrypt/build # clean and store global cache - echo "Store the global cache" - find ~/.gradle/caches/ -name "*.lock" -type f -delete # https://medium.com/cirruslabs/mastering-gradle-caching-and-incremental-builds-37eb1af7fcde - cache has_key $ANDROID_SDK_CACHE || cache store $ANDROID_SDK_CACHE $ANDROID_HOME - - cache delete gradle-wrapper - - cache delete gradle-cache - - cache delete android-build-cache - - cache store gradle-wrapper ~/.gradle/wrapper - - cache store gradle-cache ~/.gradle/caches - - cache store android-build-cache ~/.android/build-cache + - cache has_key gradle-wrapper || cache store gradle-wrapper ~/.gradle/wrapper + - cache has_key gradle-cache-$GRADLE_SUM || cache store gradle-cache-$GRADLE_SUM ~/.gradle/caches - name: 'Testing' task: @@ -188,6 +162,8 @@ blocks: commands: # collect and store debug info as artifacts - ./script/ci-get-and-publish-debug-info-as-artifact.sh + # print debug info + - ./script/ci-after-fail-debug.sh after_pipeline: task: diff --git a/FlowCrypt/build.gradle.kts b/FlowCrypt/build.gradle.kts index e8179f29a..c0e5ea7a3 100644 --- a/FlowCrypt/build.gradle.kts +++ b/FlowCrypt/build.gradle.kts @@ -6,8 +6,6 @@ import com.android.build.api.artifact.SingleArtifact import com.android.build.api.variant.ResValue -import org.gradle.api.GradleException -import java.io.File import com.android.ddmlib.DdmPreferences import java.io.FileInputStream import java.text.SimpleDateFormat diff --git a/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/gmailapi/passwordprotected/ComposeScreenPasswordProtectedDisallowedTermsReFetchConfigurationFlowTest.kt b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/gmailapi/passwordprotected/ComposeScreenPasswordProtectedDisallowedTermsReFetchConfigurationFlowTest.kt index 6da001deb..df6d76464 100644 --- a/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/gmailapi/passwordprotected/ComposeScreenPasswordProtectedDisallowedTermsReFetchConfigurationFlowTest.kt +++ b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/gmailapi/passwordprotected/ComposeScreenPasswordProtectedDisallowedTermsReFetchConfigurationFlowTest.kt @@ -40,6 +40,7 @@ import okhttp3.mockwebserver.Dispatcher import okhttp3.mockwebserver.MockResponse import okhttp3.mockwebserver.RecordedRequest import org.hamcrest.Matchers.allOf +import org.junit.Ignore import org.junit.Rule import org.junit.Test import org.junit.rules.RuleChain @@ -63,6 +64,7 @@ import java.util.concurrent.TimeUnit subject = "", isNew = true ) +@Ignore("temporary disabled") class ComposeScreenPasswordProtectedDisallowedTermsReFetchConfigurationFlowTest : BaseComposeScreenPasswordProtectedDisallowedTermsTest( ACCOUNT_ENTITY_WITH_EXISTING_OPTIONAL_PARAMETERS diff --git a/README.md b/README.md index b1a521b10..e52046fa6 100644 --- a/README.md +++ b/README.md @@ -15,18 +15,20 @@ This guide follows Google's recommendations for [testing apps on Android](https: Please follow these steps to setup your virtual or physical device: - [Set up your test environment](https://developer.android.com/training/testing/espresso/setup#set-up-environment). -- Some of the tests use [MockWebServer](https://github.com/square/okhttp/tree/master/mockwebserver). We run a local mock web server via [FlowCryptMockWebServerRule](https://github.com/FlowCrypt/flowcrypt-android/blob/master/FlowCrypt/src/androidTest/java/com/flowcrypt/email/rules/FlowCryptMockWebServerRule.kt). It uses [TestConstants.MOCK_WEB_SERVER_PORT](https://github.com/FlowCrypt/flowcrypt-android/blob/master/FlowCrypt/src/androidTest/java/com/flowcrypt/email/TestConstants.kt#L19) as a port. Unfortunately, the Android system doesn't allow us to use the `HTTPS 433` port by default to run a web server on the `localhost (127.0.0.1)`. That's why we have to run a mock web server on another port (for example, `1212`) and route all traffic from `127.0.0.1:433` to `127.0.0.1:1212`. For that purpose, you can use the [script/ci-wait-for-emulator.sh](https://github.com/FlowCrypt/flowcrypt-android/blob/master/script/ci-wait-for-emulator.sh#L13) script. +- Some of the tests use [MockWebServer](https://github.com/square/okhttp/tree/master/mockwebserver). We run a local mock web server via [FlowCryptMockWebServerRule](https://github.com/FlowCrypt/flowcrypt-android/blob/master/FlowCrypt/src/androidTest/java/com/flowcrypt/email/rules/FlowCryptMockWebServerRule.kt). It uses [TestConstants.MOCK_WEB_SERVER_PORT](https://github.com/FlowCrypt/flowcrypt-android/blob/master/FlowCrypt/src/androidTest/java/com/flowcrypt/email/TestConstants.kt#L19) as a port. Unfortunately, the Android system doesn't allow us to use the `HTTPS 443` port by default to run a web server on the `localhost (127.0.0.1)`. That's why we have to run a mock web server on another port (for example, `1212`) and route all traffic from `127.0.0.1:443` to `127.0.0.1:1212`. For that purpose, you can use the [script/ci-wait-for-emulator.sh](https://github.com/FlowCrypt/flowcrypt-android/blob/master/script/ci-wait-for-emulator.sh#L13) script. - Additionally, the test environment should handle all requests to `*.flowcrypt.test`. All traffic to `*.flowcrypt.test` should be routed to `localhost (127.0.0.1)`. You can use a DNS server to do this. For example, using these commands: ```bash -1. sudo apt install -y dnsmasq resolvconf -2. echo "#added by flowcrypt" | sudo tee -a /etc/dnsmasq.conf -3. echo "listen-address=127.0.0.1" | sudo tee -a /etc/dnsmasq.conf -4. echo "address=/flowcrypt.test/127.0.0.1" | sudo tee -a /etc/dnsmasq.conf -5. echo "address=/localhost/127.0.0.1" | sudo tee -a /etc/dnsmasq.conf -6. sudo systemctl restart dnsmasq +./script/ci-setup-DNS.sh ``` +The script: +- Installs `dnsmasq` and `dnsutils`. +- Creates `/etc/dnsmasq.d/flowcrypt.conf` with local rules for `*.test` and `*.localhost`. +- Sets `no-resolv` with upstream DNS servers (`8.8.8.8` and `1.1.1.1`). +- Sets `/etc/resolv.conf` to `nameserver 127.0.0.1`. +- Restarts `dnsmasq` and verifies resolution with `dig` and `ping`. + ### ✔️ Test types We have two types of tests: diff --git a/docker/HttpsTestWebServer/Dockerfile b/docker/HttpsTestWebServer/Dockerfile new file mode 100644 index 000000000..469578f35 --- /dev/null +++ b/docker/HttpsTestWebServer/Dockerfile @@ -0,0 +1,17 @@ +FROM nginx:1.27-alpine + +RUN apk add --no-cache dnsmasq bind-tools iputils + +COPY docker/HttpsTestWebServer/nginx.conf /etc/nginx/conf.d/default.conf + +COPY FlowCrypt/src/androidTest/resources/ssl/server_combined.pem \ + /etc/nginx/certs/server_combined.pem + +COPY docker/HttpsTestWebServer/dnsmasq.conf /etc/dnsmasq.d/flowcrypt-test.conf +COPY docker/HttpsTestWebServer/entrypoint.sh /entrypoint.sh + +RUN chmod +x /entrypoint.sh + +EXPOSE 53/udp 53/tcp 443 + +ENTRYPOINT ["/entrypoint.sh"] diff --git a/docker/HttpsTestWebServer/dnsmasq.conf b/docker/HttpsTestWebServer/dnsmasq.conf new file mode 100644 index 000000000..ee40202b0 --- /dev/null +++ b/docker/HttpsTestWebServer/dnsmasq.conf @@ -0,0 +1,17 @@ +bind-interfaces +listen-address=127.0.0.1 +port=53 + +# Do not read /etc/resolv.conf to avoid recursive localhost DNS loops. +no-resolv + +# Upstream DNS for public domains. +server=8.8.8.8 +server=1.1.1.1 + +# Local test domains. +address=/test/127.0.0.1 +address=/localhost/127.0.0.1 + +log-queries +log-facility=- diff --git a/docker/HttpsTestWebServer/entrypoint.sh b/docker/HttpsTestWebServer/entrypoint.sh new file mode 100755 index 000000000..62e0ca088 --- /dev/null +++ b/docker/HttpsTestWebServer/entrypoint.sh @@ -0,0 +1,25 @@ +#!/bin/sh + +# +# © 2016-present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com +# Contributors: denbond7 +# + +set -e + +dnsmasq --no-daemon --conf-file=/etc/dnsmasq.d/flowcrypt-test.conf --log-facility=- & + +echo "Configuring resolver to use dnsmasq..." +printf "nameserver 127.0.0.1\n" > /etc/resolv.conf + +echo "Checking dnsmasq directly..." +dig @127.0.0.1 fel.localhost +short +dig @127.0.0.1 fel.flowcrypt.test +short +dig @127.0.0.1 www.google.com +short + +echo "Checking resolver..." +ping -c 1 fel.localhost +ping -c 1 fel.flowcrypt.test +ping -c 1 www.google.com + +exec nginx -g 'daemon off;' diff --git a/docker/HttpsTestWebServer/nginx.conf b/docker/HttpsTestWebServer/nginx.conf new file mode 100644 index 000000000..ac1b53f3a --- /dev/null +++ b/docker/HttpsTestWebServer/nginx.conf @@ -0,0 +1,15 @@ +server { + listen 443 ssl; + + server_name + flowcrypt.test + *.flowcrypt.test; + + ssl_certificate /etc/nginx/certs/server_combined.pem; + ssl_certificate_key /etc/nginx/certs/server_combined.pem; + + location / { + default_type text/plain; + return 200 "hello from docker https mock server\n"; + } +} \ No newline at end of file diff --git a/docker/HttpsTestWebServer/rebuild.sh b/docker/HttpsTestWebServer/rebuild.sh new file mode 100755 index 000000000..514a0bf74 --- /dev/null +++ b/docker/HttpsTestWebServer/rebuild.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash + +# +# © 2016-present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com +# Contributors: denbond7 +# + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(realpath "$SCRIPT_DIR/../..")" + +docker build \ + -t flowcrypt-https-test-server \ + -f "$SCRIPT_DIR/Dockerfile" \ + "$REPO_ROOT" \ No newline at end of file diff --git a/docker/HttpsTestWebServer/run.sh b/docker/HttpsTestWebServer/run.sh new file mode 100755 index 000000000..d7dc7a14b --- /dev/null +++ b/docker/HttpsTestWebServer/run.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash + +# +# © 2016-present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com +# Contributors: denbond7 +# + +set -euo pipefail + +docker rm -f flowcrypt-https-test-server 2>/dev/null || true + +docker run --rm \ + --name flowcrypt-https-test-server \ + --network host \ + --cap-add NET_ADMIN \ + --cap-add NET_BIND_SERVICE \ + flowcrypt-https-test-server diff --git a/docker/TestEnvironment/Dockerfile b/docker/TestEnvironment/Dockerfile new file mode 100644 index 000000000..4d1182fcf --- /dev/null +++ b/docker/TestEnvironment/Dockerfile @@ -0,0 +1,47 @@ +FROM ubuntu:24.04 + +ARG DEBIAN_FRONTEND=noninteractive +ARG ANDROID_PLATFORM=android-36 +ARG ANDROID_SYSTEM_IMAGE=system-images;android-36;google_apis;x86_64 +ARG ANDROID_BUILD_TOOLS=36.0.0 + +ENV JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64 +ENV ANDROID_HOME=/opt/android-sdk +ENV ANDROID_SDK_ROOT=/opt/android-sdk +ENV ANDROID_AVD_HOME=/opt/android-avd +ENV PATH=/opt/android-sdk/emulator:/opt/android-sdk/platform-tools:/opt/android-sdk/cmdline-tools/latest/bin:/usr/lib/jvm/java-21-openjdk-amd64/bin:${PATH} + +RUN apt-get update && apt-get install -y --no-install-recommends \ + bash \ + ca-certificates \ + curl \ + openjdk-21-jdk \ + unzip \ + wget \ + iputils-ping \ + dnsutils \ + git \ + sudo \ + && rm -rf /var/lib/apt/lists/* + +COPY script/ci-install-android-sdk.sh /tmp/ci-install-android-sdk.sh +RUN chmod +x /tmp/ci-install-android-sdk.sh \ + && mkdir -p "${ANDROID_AVD_HOME}" \ + && ANDROID_PLATFORM="${ANDROID_PLATFORM}" \ + ANDROID_SYSTEM_IMAGE="${ANDROID_SYSTEM_IMAGE}" \ + ANDROID_BUILD_TOOLS="${ANDROID_BUILD_TOOLS}" \ + INSTALL_KVM_DEPS=0 \ + RUN_KVM_CHECK=0 \ + /tmp/ci-install-android-sdk.sh \ + && sdkmanager --list_installed \ + && rm -f /tmp/ci-install-android-sdk.sh + +RUN mkdir -p /opt/flowcrypt/scripts +COPY script/create-avd.sh /opt/flowcrypt/scripts/create-avd.sh +COPY script/run-emulator.sh /opt/flowcrypt/scripts/run-emulator.sh +COPY script/ci-wait-for-emulator.sh /opt/flowcrypt/scripts/ci-wait-for-emulator.sh +RUN chmod +x /opt/flowcrypt/scripts/create-avd.sh /opt/flowcrypt/scripts/run-emulator.sh /opt/flowcrypt/scripts/ci-wait-for-emulator.sh + +WORKDIR /workspace + +CMD ["/bin/bash"] diff --git a/docker/TestEnvironment/README.md b/docker/TestEnvironment/README.md new file mode 100644 index 000000000..2e8f5ba5d --- /dev/null +++ b/docker/TestEnvironment/README.md @@ -0,0 +1,94 @@ +# Test Environment Docker Image + +This directory contains a Docker context for a headless Android test environment based on +`ubuntu:24.04`. + +The image installs: + +- OpenJDK 21 +- Android SDK command-line tools +- `platform-tools` +- Android Emulator +- Android platform `android-36` +- Build tools `36.0.0` +- System image `system-images;android-36;google_apis;x86_64` + +## Build + +Use the helper script: + +```bash +./docker/TestEnvironment/rebuild.sh +``` + +Equivalent manual command: + +```bash +docker build -t flowcrypt/android-test-env -f docker/TestEnvironment/Dockerfile . +``` + +## Create an AVD inside the container + +```bash +docker run --rm -it flowcrypt/android-test-env /opt/flowcrypt/scripts/create-avd.sh +``` + +## Run the emulator inside the container + +The container includes reusable helpers: + +- `/opt/flowcrypt/scripts/create-avd.sh` +- `/opt/flowcrypt/scripts/run-emulator.sh` + +The container includes the emulator binaries, but actual emulator launch usually needs: + +- `/dev/kvm` passed through to the container +- additional Docker flags such as `--device /dev/kvm` + +If you need emulator DNS resolution for `*.localhost` / `*.flowcrypt.test` from +`docker/HttpsTestWebServer`, run that container first so host `dnsmasq` is available on +`127.0.0.1:53`. `run.sh` starts this container with `--network host --dns 127.0.0.1`. + +Because `--network host` is used, `adb` inside container can see host-side emulator devices +through host `adb` server. To avoid mixing devices, stop host emulator(s) before testing or run +container `adb` on another port (for example `export ADB_SERVER_PORT=5038`). + +Use the helper script: + +```bash +./docker/TestEnvironment/run.sh +``` + +You can override DNS forwarded to emulator: + +```bash +EMULATOR_DNS_SERVER=127.0.0.1 ./docker/TestEnvironment/run.sh +``` + +Equivalent manual command: + +```bash +docker run --rm -it \ + --network host \ + --dns 127.0.0.1 \ + -e EMULATOR_DNS_SERVER=127.0.0.1 \ + --device /dev/kvm \ + --name flowcrypt-android-test-env \ + flowcrypt/android-test-env \ + bash +``` + +Then inside the container: + +```bash +/opt/flowcrypt/scripts/create-avd.sh +/opt/flowcrypt/scripts/run-emulator.sh +``` + +DNS checks: + +```bash +dig @127.0.0.1 fel.flowcrypt.test +short +adb shell getprop net.dns1 +adb shell ping -c 1 fel.flowcrypt.test +``` diff --git a/docker/TestEnvironment/rebuild.sh b/docker/TestEnvironment/rebuild.sh new file mode 100755 index 000000000..8cb90277f --- /dev/null +++ b/docker/TestEnvironment/rebuild.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash + +# +# © 2016-present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com +# Contributors: denbond7 +# + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(realpath "$SCRIPT_DIR/../..")" + +docker build \ + -t flowcrypt/android-test-env \ + -f "$SCRIPT_DIR/Dockerfile" \ + "$REPO_ROOT" diff --git a/docker/TestEnvironment/run.sh b/docker/TestEnvironment/run.sh new file mode 100755 index 000000000..55c28cdfd --- /dev/null +++ b/docker/TestEnvironment/run.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash + +# +# © 2016-present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com +# Contributors: denbond7 +# + +set -euo pipefail + +IMAGE_NAME="flowcrypt/android-test-env" +CONTAINER_NAME="flowcrypt-android-test-env" +EMULATOR_DNS_SERVER="${EMULATOR_DNS_SERVER:-127.0.0.1}" + +docker rm -f "$CONTAINER_NAME" 2>/dev/null || true + +docker run --rm -it \ + --name "$CONTAINER_NAME" \ + --network host \ + --dns 127.0.0.1 \ + -e EMULATOR_DNS_SERVER="$EMULATOR_DNS_SERVER" \ + --device /dev/kvm \ + "$IMAGE_NAME" diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index c61a118f7..8dcfaa9ce 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,11 @@ +# +# 2016-present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com +# Contributors: denbond7 +# + distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.1-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/script/ci-after-fail-debug.sh b/script/ci-after-fail-debug.sh new file mode 100755 index 000000000..754e624c5 --- /dev/null +++ b/script/ci-after-fail-debug.sh @@ -0,0 +1,175 @@ +#!/usr/bin/env bash + +# +# © 2016-present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com +# Contributors: denbond7 +# + +set -euo pipefail + +SUMMARY_FAILURES=0 + +print_section() { + local title="$1" + + echo + echo "======================================================================" + echo "${title}" + date -u '+UTC time: %Y-%m-%d %H:%M:%S' + echo "======================================================================" +} + +run_debug_cmd() { + local description="$1" + shift + + echo + echo "[debug] ${description}" + "$@" || true +} + +adb_root_shell() { + local cmd="$1" + + adb shell "su 0 sh -c '${cmd}' 2>/dev/null || sh -c '${cmd}'" +} + +summary_check() { + local label="$1" + shift + + if "$@" >/dev/null 2>&1; then + echo "[summary] PASS: ${label}" + else + echo "[summary] FAIL: ${label}" + SUMMARY_FAILURES=$((SUMMARY_FAILURES + 1)) + fi +} + +collect_network_debug_info() { + print_section "Host-side adb state" + run_debug_cmd "adb devices" adb devices -l + run_debug_cmd "adb server version" adb version + run_debug_cmd "adb forward list" adb forward --list + run_debug_cmd "adb reverse list" adb reverse --list + + print_section "Device network overview" + run_debug_cmd "boot completed" adb shell getprop sys.boot_completed + run_debug_cmd "net/dns props" adb shell "getprop | grep -E 'net\\.|dns|dhcp|private_dns'" + run_debug_cmd "ip addr" adb shell ip addr + run_debug_cmd "ip route" adb shell ip route + run_debug_cmd "connectivity dumpsys" adb shell dumpsys connectivity + run_debug_cmd "private dns mode" adb shell settings get global private_dns_mode + + print_section "DNS and internet checks" + + run_debug_cmd \ + "ping raw IP 8.8.8.8" \ + adb shell ping -c 1 8.8.8.8 + + run_debug_cmd \ + "ping www.google.com" \ + adb shell ping -c 1 www.google.com + + run_debug_cmd \ + "ping emulator host gateway 10.0.2.2" \ + adb shell ping -c 1 10.0.2.2 + + run_debug_cmd \ + "ping fes.flowcrypt.test" \ + adb shell ping -c 1 fes.flowcrypt.test + + print_section "NAT and sockets" + + run_debug_cmd \ + "iptables nat OUTPUT rules" \ + adb_root_shell "iptables -t nat -S OUTPUT" + + run_debug_cmd \ + "iptables nat OUTPUT counters" \ + adb_root_shell "iptables -t nat -L OUTPUT -n -v" + + run_debug_cmd \ + "listeners netstat" \ + adb shell netstat -lntp +} + +print_summary() { + print_section "Network summary" + + summary_check \ + "internet raw IP (8.8.8.8)" \ + adb shell ping -c 1 8.8.8.8 + + summary_check \ + "internet DNS (www.google.com)" \ + adb shell ping -c 1 www.google.com + + summary_check \ + "emulator host gateway 10.0.2.2" \ + adb shell ping -c 1 10.0.2.2 + + summary_check \ + "flowcrypt.test ping" \ + adb shell ping -c 1 fes.flowcrypt.test + + summary_check \ + "iptables OUTPUT redirect 443->1212 present" \ + adb_root_shell \ + "iptables -t nat -S OUTPUT | grep -F -- '--dport 443 -j REDIRECT --to-ports 1212'" + + summary_check \ + "listener on :1212 present" \ + adb shell \ + "netstat -lnt 2>/dev/null | grep -E '(^|[:.])1212([[:space:]]|$)'" + + if [[ "$SUMMARY_FAILURES" -eq 0 ]]; then + echo "[summary] RESULT: PASS" + else + echo "[summary] RESULT: FAIL (${SUMMARY_FAILURES} checks failed)" + fi +} + +check_ping_or_fail() { + local host="$1" + local label="$2" + + for attempt in {1..10}; do + if adb shell "ping -c 1 ${host}"; then + return 0 + fi + + if [[ "$attempt" -eq 10 ]]; then + echo "Failed to ping ${host}: ${label}" + return 1 + fi + + sleep 2 + done +} + +################################################################################################### + +NETWORK_FAILURE=0 + +if ! check_ping_or_fail "www.google.com" "internet connection"; then + NETWORK_FAILURE=1 +fi + +if ! check_ping_or_fail "10.0.2.2" "emulator host gateway"; then + NETWORK_FAILURE=1 +fi + +if ! check_ping_or_fail "fes.flowcrypt.test" "flowcrypt.test DNS/reachability"; then + NETWORK_FAILURE=1 +fi + +if [[ "$NETWORK_FAILURE" -eq 1 ]]; then + print_section "Network failure diagnostics" + collect_network_debug_info + print_summary + exit 1 +fi + +print_section "Network status" +echo "All network checks passed" \ No newline at end of file diff --git a/script/ci-after-success-actions-for-instrumentation-tests.sh b/script/ci-after-success-actions-for-instrumentation-tests.sh index 599b26f38..c1a1552c5 100755 --- a/script/ci-after-success-actions-for-instrumentation-tests.sh +++ b/script/ci-after-success-actions-for-instrumentation-tests.sh @@ -1,18 +1,30 @@ -#!/bin/bash +#!/usr/bin/env bash + +# +# © 2016-present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com +# Contributors: denbond7 +# + +set -euo pipefail if [[ "$SEMAPHORE_JOB_NAME" =~ ^Instrumentation.* ]]; then # print debug info about connected device echo "Print connected devices" adb devices - # store logcat log + if [[ -f "$HOME/logcat_log.txt" ]]; then echo "Store logcat log" - artifact push job ~/logcat_log.txt + artifact push job "$HOME/logcat_log.txt" + else + echo "No logcat_log.txt found, skipping" + fi - # store screenshots - echo "Store screenshots" - adb pull "/sdcard/Pictures" - adb shell ls /sdcard/Pictures + echo "Store screenshots" + if adb shell test -d /sdcard/Pictures; then + rm -rf Pictures + adb pull "/sdcard/Pictures" Pictures artifact push job Pictures + else + echo "No /sdcard/Pictures directory found, skipping" + fi fi - diff --git a/script/ci-get-and-publish-debug-info-as-artifact.sh b/script/ci-get-and-publish-debug-info-as-artifact.sh index bf51181f9..1088399f1 100755 --- a/script/ci-get-and-publish-debug-info-as-artifact.sh +++ b/script/ci-get-and-publish-debug-info-as-artifact.sh @@ -1,23 +1,39 @@ -#!/bin/bash +#!/usr/bin/env bash + +# +# © 2016-present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com +# Contributors: denbond7 +# + +set -euo pipefail if [[ "$SEMAPHORE_JOB_NAME" =~ ^Lint.* ]]; then - # don't do any things for 'Lint(structural quality)' job - exit 0 + # Do nothing for 'Lint(structural quality)' job. + exit 0 fi -echo "Store tests results for $SEMAPHORE_JOB_NAME" -artifact push job FlowCrypt/build/reports/ +reports_dir="FlowCrypt/build/reports/" +if [[ -d "$reports_dir" ]]; then + echo "Store test reports for $SEMAPHORE_JOB_NAME" + artifact push job "$reports_dir" +else + echo "Reports directory does not exist: $reports_dir" +fi if [[ "$SEMAPHORE_JOB_NAME" =~ ^Instrumentation.* ]]; then - # store full logcat log - echo "Collect logcat logs as logcat.txt.gz for $SEMAPHORE_JOB_NAME" - adb logcat -d | gzip > ~/logcat.txt.gz - artifact push job ~/logcat.txt.gz + # store full logcat log + echo "Collect logcat logs as logcat.txt.gz for $SEMAPHORE_JOB_NAME" + adb logcat -d | gzip > "$HOME/logcat.txt.gz" + artifact push job "$HOME/logcat.txt.gz" - # store the device's screenshot. it may help to debug a failure - echo "Store the device's screenshot for $SEMAPHORE_JOB_NAME" - adb shell screencap -p /sdcard/screencap.png - adb pull "/sdcard/screencap.png" - artifact push job screencap.png + echo "Store the device's screenshot for $SEMAPHORE_JOB_NAME" + if adb shell screencap -p /sdcard/screencap.png; then + if adb pull "/sdcard/screencap.png"; then + artifact push job screencap.png + else + echo "Could not pull screencap.png" + fi + else + echo "Could not create screencap.png" + fi fi - diff --git a/script/ci-install-android-sdk.sh b/script/ci-install-android-sdk.sh index 084866507..605cbaddc 100755 --- a/script/ci-install-android-sdk.sh +++ b/script/ci-install-android-sdk.sh @@ -22,11 +22,16 @@ fi # ----------------------------- export ANDROID_HOME="${ANDROID_HOME:-$HOME/Android/Sdk}" export ANDROID_SDK_ROOT="${ANDROID_SDK_ROOT:-$ANDROID_HOME}" +INSTALL_KVM_DEPS="${INSTALL_KVM_DEPS:-1}" +RUN_KVM_CHECK="${RUN_KVM_CHECK:-1}" +ANDROID_PLATFORM="${ANDROID_PLATFORM:-android-36}" +ANDROID_SYSTEM_IMAGE="${ANDROID_SYSTEM_IMAGE:-system-images;android-36;google_apis;x86_64}" +ANDROID_BUILD_TOOLS="${ANDROID_BUILD_TOOLS:-}" # ----------------------------- # Pin cmdline-tools archive here # ----------------------------- -SDK_ARCHIVE="commandlinetools-linux-14742923_latest.zip" +SDK_ARCHIVE="${SDK_ARCHIVE:-commandlinetools-linux-14742923_latest.zip}" # ------------------------------------------------------------ # Check that SDK_ARCHIVE is the latest Android cmdline-tools @@ -86,16 +91,20 @@ check_cmdline_tools_latest_or_fail # ----------------------------- # KVM deps (as in your script) # ----------------------------- -sudo apt-get -qq install qemu-kvm libvirt-daemon-system libvirt-clients bridge-utils > /dev/null -sudo kvm-ok +if [[ "$INSTALL_KVM_DEPS" == "1" ]]; then + sudo apt-get -qq install qemu-kvm libvirt-daemon-system libvirt-clients bridge-utils > /dev/null +fi +if [[ "$RUN_KVM_CHECK" == "1" ]]; then + sudo kvm-ok +fi # ----------------------------- # Install SDK if ~/Android doesn't exist (as in your script) # ----------------------------- -if [[ -d "$HOME/Android" ]]; then - echo "$HOME/Android already exists, skipping installation" +if [[ -x "$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager" ]]; then + echo "$ANDROID_HOME already exists, skipping installation" else - echo "$HOME/Android does not exist, installing" + echo "$ANDROID_HOME does not exist, installing" mkdir -p "$ANDROID_HOME" # download, unpack and remove sdk archive @@ -122,10 +131,13 @@ else # Install Android SDK (echo "yes" | "${ANDROID_HOME}/cmdline-tools/latest/bin/sdkmanager" --licenses > /dev/null | grep -v = || true) - ( sleep 5; echo "y" ) | ("${ANDROID_HOME}/cmdline-tools/latest/bin/sdkmanager" "platforms;android-36" > /dev/null | grep -v = || true) + ( sleep 5; echo "y" ) | ("${ANDROID_HOME}/cmdline-tools/latest/bin/sdkmanager" "platforms;${ANDROID_PLATFORM}" > /dev/null | grep -v = || true) ("${ANDROID_HOME}/cmdline-tools/latest/bin/sdkmanager" "platform-tools" | grep -v = || true) ("${ANDROID_HOME}/cmdline-tools/latest/bin/sdkmanager" "emulator" | grep -v = || true) - (echo "y" | "${ANDROID_HOME}/cmdline-tools/latest/bin/sdkmanager" "system-images;android-36;google_apis;x86_64" > /dev/null | grep -v = || true) + if [[ -n "$ANDROID_BUILD_TOOLS" ]]; then + ("${ANDROID_HOME}/cmdline-tools/latest/bin/sdkmanager" "build-tools;${ANDROID_BUILD_TOOLS}" | grep -v = || true) + fi + (echo "y" | "${ANDROID_HOME}/cmdline-tools/latest/bin/sdkmanager" "${ANDROID_SYSTEM_IMAGE}" > /dev/null | grep -v = || true) fi # Uncomment this for debug diff --git a/script/ci-instrumentation-tests-enterprise.sh b/script/ci-instrumentation-tests-enterprise.sh index b197fcb09..ff179352e 100755 --- a/script/ci-instrumentation-tests-enterprise.sh +++ b/script/ci-instrumentation-tests-enterprise.sh @@ -1,4 +1,11 @@ -#!/bin/bash +#!/usr/bin/env bash -./gradlew --console=plain :FlowCrypt:connectedEnterpriseUiTestsAndroidTest \ +# +# © 2016-present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com +# Contributors: denbond7 +# + +set -euo pipefail + +./gradlew --console=plain --no-daemon --build-cache :FlowCrypt:connectedEnterpriseUiTestsAndroidTest \ -Pandroid.testInstrumentationRunnerArguments.filter=com.flowcrypt.email.junit.filters.EnterpriseTestsFilter diff --git a/script/ci-instrumentation-tests-flaky.sh b/script/ci-instrumentation-tests-flaky.sh index 706d3f24b..26f122293 100755 --- a/script/ci-instrumentation-tests-flaky.sh +++ b/script/ci-instrumentation-tests-flaky.sh @@ -1,4 +1,11 @@ -#!/bin/bash +#!/usr/bin/env bash -./gradlew --console=plain :FlowCrypt:connectedEnterpriseUiTestsAndroidTest \ +# +# © 2016-present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com +# Contributors: denbond7 +# + +set -euo pipefail + +./gradlew --console=plain --no-daemon --build-cache :FlowCrypt:connectedEnterpriseUiTestsAndroidTest \ -Pandroid.testInstrumentationRunnerArguments.filter=com.flowcrypt.email.junit.filters.ReadyForCIAndFlakyFilter diff --git a/script/ci-instrumentation-tests-with-mailserver.sh b/script/ci-instrumentation-tests-with-mailserver.sh index 41da56141..64f0d2998 100755 --- a/script/ci-instrumentation-tests-with-mailserver.sh +++ b/script/ci-instrumentation-tests-with-mailserver.sh @@ -1,4 +1,11 @@ -#!/bin/bash +#!/usr/bin/env bash + +# +# © 2016-present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com +# Contributors: denbond7 +# + +set -euo pipefail #print test names #adb shell am instrument -r -w \ @@ -6,5 +13,5 @@ # -e log true \ # com.flowcrypt.email.debug.test/androidx.test.runner.AndroidJUnitRunner -./gradlew --console=plain :FlowCrypt:connectedConsumerUiTestsAndroidTest \ +./gradlew --console=plain --no-daemon --build-cache :FlowCrypt:connectedConsumerUiTestsAndroidTest \ -Pandroid.testInstrumentationRunnerArguments.filter=com.flowcrypt.email.junit.filters.DependsOnMailServerFilter diff --git a/script/ci-instrumentation-tests-without-mailserver.sh b/script/ci-instrumentation-tests-without-mailserver.sh index 778887d21..b904c77ac 100755 --- a/script/ci-instrumentation-tests-without-mailserver.sh +++ b/script/ci-instrumentation-tests-without-mailserver.sh @@ -1,35 +1,39 @@ -#!/bin/bash +#!/usr/bin/env bash -if [[ -z "$1" ]]; - then +# +# © 2016-present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com +# Contributors: denbond7 +# + +set -euo pipefail + +if [[ -z "${1:-}" ]]; then echo "numShards is unset or set to the empty string" exit 1 - else - varNumShards=$1 fi -if [[ -z "$2" ]]; - then +if [[ -z "${2:-}" ]]; then echo "shardIndex is unset or set to the empty string" exit 1 - else - varShardIndex=$2 fi -if [[ ${varShardIndex} -ge ${varNumShards} ]] - then +numShards="$1" +shardIndex="$2" + +if (( shardIndex >= numShards )); then echo "shardIndex should be lower than numShards" - else - #print test names - #adb shell am instrument -w \ - # -e filter com.flowcrypt.email.junit.filters.DoesNotNeedMailServerFilter \ - # -e numShards 3 \ - # -e shardIndex 1 \ - # -e log true \ - # com.flowcrypt.email.debug.test/androidx.test.runner.AndroidJUnitRunner - - ./gradlew --console=plain :FlowCrypt:connectedConsumerUiTestsAndroidTest \ - -Pandroid.testInstrumentationRunnerArguments.filter=com.flowcrypt.email.junit.filters.DoesNotNeedMailServerFilter \ - -Pandroid.testInstrumentationRunnerArguments.numShards="${varNumShards}" \ - -Pandroid.testInstrumentationRunnerArguments.shardIndex="${varShardIndex}" + exit 1 fi + +#print test names +#adb shell am instrument -w \ +# -e filter com.flowcrypt.email.junit.filters.DoesNotNeedMailServerFilter \ +# -e numShards 3 \ +# -e shardIndex 1 \ +# -e log true \ +# com.flowcrypt.email.debug.test/androidx.test.runner.AndroidJUnitRunner + +./gradlew --console=plain --no-daemon --build-cache :FlowCrypt:connectedConsumerUiTestsAndroidTest \ + -Pandroid.testInstrumentationRunnerArguments.filter=com.flowcrypt.email.junit.filters.DoesNotNeedMailServerFilter \ + -Pandroid.testInstrumentationRunnerArguments.numShards="${numShards}" \ + -Pandroid.testInstrumentationRunnerArguments.shardIndex="${shardIndex}" diff --git a/script/ci-junit-tests.sh b/script/ci-junit-tests.sh index dfdcb6a2e..31b13e0b7 100755 --- a/script/ci-junit-tests.sh +++ b/script/ci-junit-tests.sh @@ -1,3 +1,10 @@ -#!/bin/bash +#!/usr/bin/env bash -./gradlew --console=plain :FlowCrypt:testConsumerUiTestsUnitTest +# +# © 2016-present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com +# Contributors: denbond7 +# + +set -euo pipefail + +./gradlew --console=plain --no-daemon --build-cache :FlowCrypt:testConsumerUiTestsUnitTest diff --git a/script/ci-lint-checks.sh b/script/ci-lint-checks.sh index cdd01071a..dab6d43eb 100755 --- a/script/ci-lint-checks.sh +++ b/script/ci-lint-checks.sh @@ -1,3 +1,10 @@ -#!/bin/bash +#!/usr/bin/env bash -./gradlew --console=plain :FlowCrypt:lintConsumerUiTests +# +# © 2016-present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com +# Contributors: denbond7 +# + +set -euo pipefail + +./gradlew --console=plain --no-daemon --build-cache :FlowCrypt:lintConsumerUiTests diff --git a/script/ci-publish-test-results.sh b/script/ci-publish-test-results.sh index 4c6622657..d5c21df8c 100755 --- a/script/ci-publish-test-results.sh +++ b/script/ci-publish-test-results.sh @@ -1,12 +1,26 @@ -#!/bin/bash +#!/usr/bin/env bash + +# +# © 2016-present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com +# Contributors: denbond7 +# + +set -euo pipefail if [[ "$SEMAPHORE_JOB_NAME" =~ ^Instrumentation.* ]]; then - # publish test results for Instrumentation tests - test-results publish ~/git/flowcrypt-android/FlowCrypt/build/outputs/androidTest-results/connected/ --name "$SEMAPHORE_JOB_NAME" + results_dir="$HOME/git/flowcrypt-android/FlowCrypt/build/outputs/androidTest-results/connected/" + if [[ -d "$results_dir" ]]; then + test-results publish "$results_dir" --name "$SEMAPHORE_JOB_NAME" + else + echo "Instrumentation test results directory does not exist: $results_dir" + fi fi if [[ "$SEMAPHORE_JOB_NAME" =~ ^JUnit.* ]]; then - # publish test results for JUnit tests - test-results publish ~/git/flowcrypt-android/FlowCrypt/build/test-results/ --name "$SEMAPHORE_JOB_NAME" + results_dir="$HOME/git/flowcrypt-android/FlowCrypt/build/test-results/" + if [[ -d "$results_dir" ]]; then + test-results publish "$results_dir" --name "$SEMAPHORE_JOB_NAME" + else + echo "JUnit test results directory does not exist: $results_dir" + fi fi - diff --git a/script/ci-setup-DNS.sh b/script/ci-setup-DNS.sh new file mode 100755 index 000000000..738b16a3b --- /dev/null +++ b/script/ci-setup-DNS.sh @@ -0,0 +1,51 @@ +#!/usr/bin/env bash + +# +# © 2016-present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com +# Contributors: denbond7 +# + +set -euo pipefail + +echo "Installing DNS tools..." +sudo apt install -y dnsmasq dnsutils + +echo "Configuring dnsmasq..." +sudo tee /etc/dnsmasq.d/flowcrypt.conf >/dev/null <<'EOF' +# added by flowcrypt +listen-address=127.0.0.1 +bind-interfaces + +# Do not read /etc/resolv.conf to avoid recursive localhost DNS loops. +no-resolv + +# Upstream DNS for public domains. +server=8.8.8.8 +server=1.1.1.1 + +# Local test domains. +address=/test/127.0.0.1 +address=/localhost/127.0.0.1 +EOF + +echo "Configuring host resolver to use dnsmasq..." +echo "nameserver 127.0.0.1" | sudo tee /etc/resolv.conf >/dev/null + +echo "Restarting dnsmasq..." +sudo systemctl restart dnsmasq + +echo "DNS debug info:" +sudo ss -lunp | grep ':53' || true +cat /etc/resolv.conf + +echo "Checking dnsmasq directly..." +dig @127.0.0.1 fel.localhost +dig @127.0.0.1 fel.flowcrypt.test +dig @127.0.0.1 www.google.com + +echo "Checking host resolver..." +ping fel.localhost -c 1 +ping fel.flowcrypt.test -c 1 +ping www.google.com -c 1 + +echo "DNS setup completed successfully." \ No newline at end of file diff --git a/script/ci-setup-and-run-emulator.sh b/script/ci-setup-and-run-emulator.sh index 6df16d71f..04537a81a 100755 --- a/script/ci-setup-and-run-emulator.sh +++ b/script/ci-setup-and-run-emulator.sh @@ -1,15 +1,14 @@ -#!/bin/bash +#!/usr/bin/env bash # # © 2016-present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com # Contributors: denbond7 # -"$ANDROID_HOME/emulator/emulator" -accel-check -avdmanager list devices #debug -echo -ne '\n' | avdmanager -v create avd --name ci-emulator --package "system-images;android-36;google_apis;x86_64" --device 'pixel_9' --abi 'google_apis/x86_64' -cat ~/.android/avd/ci-emulator.avd/config.ini -# echo "hw.ramSize=3064" >> ~/.android/avd/ci-emulator.avd/config.ini -# cat ~/.android/avd/ci-emulator.avd/config.ini -"$ANDROID_HOME/emulator/emulator" -list-avds #debug -"$ANDROID_HOME/emulator/emulator" -avd ci-emulator -no-snapshot -no-window -no-boot-anim -no-audio -gpu auto -read-only -no-metrics & +set -euo pipefail + +AVD_RAM_SIZE=2048 ./script/create-avd.sh +EMULATOR_GPU_MODE=auto \ +EMULATOR_READ_ONLY=1 \ +EMULATOR_WIPE_DATA=1 \ +./script/run-emulator.sh diff --git a/script/ci-wait-for-emulator.sh b/script/ci-wait-for-emulator.sh index 6e622f643..8dd09e6f8 100755 --- a/script/ci-wait-for-emulator.sh +++ b/script/ci-wait-for-emulator.sh @@ -4,32 +4,182 @@ # Contributors: denbond7 # -set -o xtrace -adb wait-for-device shell 'while [[ -z $(getprop sys.boot_completed) ]]; do sleep 1; done;' +set -euo pipefail + +wait_for_boot_completed() { + adb wait-for-device + + # shellcheck disable=SC2016 + adb shell 'while [[ "$(getprop sys.boot_completed)" != "1" ]]; do sleep 1; done;' +} + +run_adb_root_or_fail() { + for attempt in {1..10}; do + if adb root; then + adb wait-for-device + return 0 + fi + + echo "adb root failed... attempt ${attempt}/10" + sleep 2 + done + + echo "adb root failed after 10 attempts" + exit 1 +} + +wait_for_network_after_adb_root() { + for attempt in {1..30}; do + if adb shell "ping -c 1 10.0.2.2" >/dev/null 2>&1 \ + && adb shell "ping -c 1 8.8.8.8" >/dev/null 2>&1; then + echo "Emulator network is ready after adb root" + return 0 + fi + + echo "Waiting for emulator network after adb root... attempt ${attempt}/30" + sleep 2 + done + + echo "Emulator network did not become ready after adb root" + + print_network_debug "network after adb root" + + exit 1 +} + +check_ping_or_fail() { + local host="$1" + local label="$2" + + for attempt in {1..15}; do + if adb shell "ping -c 1 ${host}" >/dev/null 2>&1; then + echo "PASS: ${label}" + return 0 + fi + + echo "Waiting for ${label}... attempt ${attempt}/15" + + sleep 2 + done + + echo "FAIL: ${label}" + + print_network_debug "${label}" + + exit 1 +} + +print_network_debug() { + local reason="$1" + + set +e + + echo + echo "======================================================================" + echo "Network failure diagnostics: ${reason}" + echo "UTC time: $(date -u '+%Y-%m-%d %H:%M:%S')" + echo "======================================================================" + + echo + echo "[debug] adb devices" + adb devices -l + + echo + echo "[debug] adb forward list" + adb forward --list + + echo + echo "[debug] net/dns props" + adb shell getprop | grep -iE 'dns|net' || true + + echo + echo "[debug] ip addr" + adb shell ip addr || true + + echo + echo "[debug] ip route" + adb shell ip route || true + + echo + echo "[debug] connectivity DNS" + adb shell dumpsys connectivity | grep -iE 'DnsAddresses|ServerAddress|Active default network' || true + + echo + echo "[debug] ping gateway" + adb shell ping -c 1 10.0.2.2 || true + + echo + echo "[debug] ping raw internet IP" + adb shell ping -c 1 8.8.8.8 || true + + echo + echo "[debug] ping public DNS name" + adb shell ping -c 1 www.google.com || true + + echo + echo "[debug] ping flowcrypt test domain" + adb shell ping -c 1 fes.flowcrypt.test || true + + set -e +} + +wait_for_boot_completed + adb shell wm dismiss-keyguard + sleep 1 + adb shell settings put global window_animation_scale 0 adb shell settings put global transition_animation_scale 0 adb shell settings put global animator_duration_scale 0 ################################################################################################### -# to test WKD we need to route all traffic for localhost:443 to localhost:1212 +# To test WKD we need to route all traffic for localhost:443 to localhost:1212 # as we can't use 443 directly for a mock web server. -adb root -# Need wait for the root environment -sleep 20 +################################################################################################### + +echo "[debug] DNS before adb root" + +adb shell dumpsys connectivity | grep -iE 'DnsAddresses|ServerAddress|Active default network' || true +adb shell ping -c 1 www.google.com || true +adb shell ping -c 1 fes.flowcrypt.test || true + +run_adb_root_or_fail + +# adb root restarts adbd, so wait until the device is available again. +wait_for_boot_completed + +# Android networking may take some time to recover after adb root. +wait_for_network_after_adb_root + +echo "[debug] DNS after adb root" + +adb shell dumpsys connectivity | grep -iE 'DnsAddresses|ServerAddress|Active default network' || true +adb shell ping -c 1 www.google.com || true +adb shell ping -c 1 fes.flowcrypt.test || true + adb shell "echo 1 > /proc/sys/net/ipv4/ip_forward" -adb shell "iptables -t nat -A PREROUTING -s 127.0.0.1 -p tcp --dport 443 -j REDIRECT --to 1212" -adb shell "iptables -t nat -A OUTPUT -s 127.0.0.1 -p tcp --dport 443 -j REDIRECT --to 1212" + +adb shell "iptables -t nat -D OUTPUT -p tcp -d 127.0.0.1 --dport 443 -j REDIRECT --to-ports 1212" || true + +adb shell "iptables -t nat -A OUTPUT -p tcp -d 127.0.0.1 --dport 443 -j REDIRECT --to-ports 1212" + +adb shell "iptables -t nat -S OUTPUT" + ################################################################################################### # https://developer.android.com/tools/adb#forwardports -# forwards requests on a specific host port to a different port on a device. -# It can be helpful for debugging a mock web server +# Forwards requests on a specific host port to a different port on a device. +# It can be helpful for debugging a mock web server. adb forward tcp:1212 tcp:1212 -#check the emulator has internet connection -adb shell "ping -c 1 www.google.com" +################################################################################################### +# Final network validation before running tests. +################################################################################################### + +check_ping_or_fail "10.0.2.2" "emulator host gateway" +check_ping_or_fail "8.8.8.8" "internet raw IP connectivity" +check_ping_or_fail "www.google.com" "internet DNS" +check_ping_or_fail "fes.flowcrypt.test" "flowcrypt.test DNS/reachability" echo "Emulator is ready" -set +o xtrace diff --git a/script/create-avd.sh b/script/create-avd.sh new file mode 100755 index 000000000..a15982cad --- /dev/null +++ b/script/create-avd.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash + +# +# © 2016-present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com +# Contributors: denbond7 +# + +set -euo pipefail + +AVD_NAME="${AVD_NAME:-ci-emulator}" +SYSTEM_IMAGE="${SYSTEM_IMAGE:-system-images;android-36;google_apis;x86_64}" +DEVICE_NAME="${DEVICE_NAME:-pixel_9}" +ABI="${ABI:-google_apis/x86_64}" +AVD_DIR="${ANDROID_AVD_HOME:-$HOME/.android/avd}" +AVD_RAM_SIZE="${AVD_RAM_SIZE:-}" + +mkdir -p "$AVD_DIR" "$HOME/.android" +touch "$HOME/.android/repositories.cfg" + +if avdmanager list avd | grep -q "Name: ${AVD_NAME}"; then + echo "Removing existing AVD: ${AVD_NAME}" + avdmanager delete avd --name "$AVD_NAME" || true +fi + +rm -rf \ + "${AVD_DIR}/${AVD_NAME}.avd" \ + "${AVD_DIR}/${AVD_NAME}.ini" + +echo -ne '\n' | avdmanager -v create avd \ + --force \ + --name "$AVD_NAME" \ + --package "$SYSTEM_IMAGE" \ + --device "$DEVICE_NAME" \ + --abi "$ABI" + +if [[ -n "$AVD_RAM_SIZE" ]]; then + echo "hw.ramSize=${AVD_RAM_SIZE}" >> "${AVD_DIR}/${AVD_NAME}.avd/config.ini" +fi + +echo "Created AVD ${AVD_NAME}" diff --git a/script/run-emulator.sh b/script/run-emulator.sh new file mode 100755 index 000000000..f4ecae7a3 --- /dev/null +++ b/script/run-emulator.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash + +# +# © 2016-present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com +# Contributors: denbond7 +# + +set -euo pipefail + +AVD_NAME="${AVD_NAME:-ci-emulator}" +EMULATOR_GPU_MODE="${EMULATOR_GPU_MODE:-swiftshader_indirect}" +EMULATOR_WIPE_DATA="${EMULATOR_WIPE_DATA:-1}" +EMULATOR_DNS_SERVER="${EMULATOR_DNS_SERVER:-127.0.0.1}" + +"$ANDROID_HOME/emulator/emulator" -accel-check + +emulator_args=( + -avd "$AVD_NAME" + -no-window + -no-boot-anim + -no-audio + -no-snapshot + -no-snapshot-load + -no-snapshot-save + -gpu "$EMULATOR_GPU_MODE" + + # This value is used by the emulator process on the host/container. + # Android guest will still see 10.0.2.3 as DNS, but that proxy will forward + # to the DNS server configured here. + -dns-server "$EMULATOR_DNS_SERVER" + + -read-only + -no-metrics +) + +if [[ "$EMULATOR_WIPE_DATA" == "1" ]]; then + emulator_args+=(-wipe-data) +fi + +"$ANDROID_HOME/emulator/emulator" "${emulator_args[@]}" & + +echo "Started emulator with DNS server: $EMULATOR_DNS_SERVER"