|
| 1 | +name: ci-android |
| 2 | + |
| 3 | +on: |
| 4 | + pull_request: |
| 5 | + workflow_dispatch: |
| 6 | + |
| 7 | +jobs: |
| 8 | + ci-android: |
| 9 | + name: ci-android-${{ matrix.abi }}${{ matrix.abi == 'x86_64' && '+test' || '' }} |
| 10 | + runs-on: ubuntu-latest |
| 11 | + timeout-minutes: 70 |
| 12 | + strategy: |
| 13 | + fail-fast: false |
| 14 | + matrix: |
| 15 | + abi: [arm64-v8a, armeabi-v7a, x86, x86_64] |
| 16 | + env: |
| 17 | + TEST_FILTER: "~[benchmark] ~[bench] ~[semaphore] ~[io_scheduler] ~[ring_buffer] ~[thread_pool] ~[tcp_server] ~[tls_server] ~[dns] ~*net::* ~*udp* ~*ip_address* ~*wait_for* ~*wait_until*" |
| 18 | + TEST_TIMEOUT: "600" |
| 19 | + steps: |
| 20 | + - name: Checkout |
| 21 | + uses: actions/checkout@v4 |
| 22 | + with: |
| 23 | + submodules: recursive |
| 24 | + |
| 25 | + - name: Set up JDK |
| 26 | + uses: actions/setup-java@v4 |
| 27 | + with: |
| 28 | + distribution: temurin |
| 29 | + java-version: '17' |
| 30 | + |
| 31 | + - name: Set up Android SDK tools |
| 32 | + uses: android-actions/setup-android@v3 |
| 33 | + |
| 34 | + - name: Cache Gradle & native build |
| 35 | + uses: actions/cache@v4 |
| 36 | + with: |
| 37 | + path: | |
| 38 | + ~/.gradle/caches |
| 39 | + ~/.gradle/wrapper |
| 40 | + ~/.android/build-cache |
| 41 | + test/android/.cxx |
| 42 | + test/android/.gradle |
| 43 | + test/android/build-${{ matrix.abi }} |
| 44 | + key: gradle-${{ runner.os }}-${{ matrix.abi }}-${{ hashFiles('test/android/**/*.gradle*','test/android/gradle.properties') }} |
| 45 | + restore-keys: | |
| 46 | + gradle-${{ runner.os }}-${{ matrix.abi }}- |
| 47 | + gradle-${{ runner.os }}- |
| 48 | +
|
| 49 | + - name: Accept Android licenses |
| 50 | + run: | |
| 51 | + yes | sdkmanager --licenses > /dev/null || true |
| 52 | +
|
| 53 | + - name: Install CMake (required for externalNativeBuild) |
| 54 | + run: | |
| 55 | + sdkmanager --install "cmake;3.22.1" > /dev/null |
| 56 | +
|
| 57 | + - name: Build OpenSSL (if missing for ABI) |
| 58 | + working-directory: test/android |
| 59 | + run: | |
| 60 | + ABI="${{ matrix.abi }}" |
| 61 | + ROOT="$PWD" |
| 62 | + OUT_DIR="external/openssl/$ABI/lib" |
| 63 | + if [ -f "$OUT_DIR/libssl.a" ] && [ -f "$OUT_DIR/libcrypto.a" ]; then |
| 64 | + echo "OpenSSL already present for $ABI"; exit 0; fi |
| 65 | + echo "Building OpenSSL for $ABI"; |
| 66 | + bash scripts/build_openssl.sh --abis "$ABI" --api 24 |
| 67 | +
|
| 68 | + - name: Build debug APK (single ABI) |
| 69 | + working-directory: test/android |
| 70 | + env: |
| 71 | + ANDROID_MATRIX_ABI: ${{ matrix.abi }} |
| 72 | + run: | |
| 73 | + echo "Building for ABI: ${{ matrix.abi }}" |
| 74 | + export GRADLE_USER_HOME="$PWD/.gradle" |
| 75 | + BUILD_DIR="build-${{ matrix.abi }}" |
| 76 | + gradle clean assembleDebug --stacktrace --no-daemon -PciAbi='${{ matrix.abi }}' -PcustomBuildDir=$BUILD_DIR |
| 77 | + ls -la "$BUILD_DIR/outputs/apk/debug" || true |
| 78 | + echo "Verify native lib for ABI present" || true |
| 79 | + find "$BUILD_DIR" -type f -path "*${{ matrix.abi }}*" -name "*.so" | head -n 20 || true |
| 80 | +
|
| 81 | + - name: Upload APK artifact (per ABI) |
| 82 | + if: always() |
| 83 | + uses: actions/upload-artifact@v4 |
| 84 | + with: |
| 85 | + name: apk-${{ matrix.abi }} |
| 86 | + path: | |
| 87 | + test/android/build-${{ matrix.abi }}/outputs/apk/debug/*.apk |
| 88 | + test/android/build/outputs/apk/debug/*.apk |
| 89 | +
|
| 90 | + - name: Install emulator runtime dependencies |
| 91 | + if: matrix.abi == 'x86_64' |
| 92 | + run: | |
| 93 | + sudo apt-get update |
| 94 | + sudo apt-get install -y \ |
| 95 | + libpulse0 libnss3 libxcomposite1 libxcursor1 libxdamage1 \ |
| 96 | + libxi6 libxrandr2 libxtst6 libasound2 libx11-6 libx11-xcb1 \ |
| 97 | + libxcb1 libxss1 libglu1-mesa libdbus-1-3 ca-certificates \ |
| 98 | + fonts-liberation libwayland-client0 libwayland-cursor0 || true |
| 99 | +
|
| 100 | + - name: Create AVD |
| 101 | + if: matrix.abi == 'x86_64' |
| 102 | + run: | |
| 103 | + set -euo pipefail |
| 104 | + export ANDROID_AVD_HOME="$HOME/.android/avd" |
| 105 | + export ANDROID_SDK_ROOT="${ANDROID_SDK_ROOT:-$ANDROID_HOME}" |
| 106 | + export EMU_BIN="$ANDROID_SDK_ROOT/emulator/emulator" |
| 107 | + # Avoid -p: create AVD home only if missing |
| 108 | + [ -d "$ANDROID_AVD_HOME" ] || mkdir "$ANDROID_AVD_HOME" |
| 109 | + sdkmanager --channel=0 --install "emulator" "platform-tools" > /dev/null || true |
| 110 | + sdkmanager --channel=0 --install "system-images;android-30;default;x86_64" > /dev/null |
| 111 | + avdmanager delete avd -n test || true |
| 112 | + echo "no" | avdmanager create avd -n test -k "system-images;android-30;default;x86_64" --force |
| 113 | + "$EMU_BIN" -list-avds || true |
| 114 | + if ! "$EMU_BIN" -list-avds | grep -q '^test$'; then |
| 115 | + echo "AVD 'test' creation failed" >&2; exit 1; fi |
| 116 | +
|
| 117 | + - name: Launch emulator |
| 118 | + if: matrix.abi == 'x86_64' |
| 119 | + run: | |
| 120 | + set -euo pipefail |
| 121 | + export ADB="${ANDROID_SDK_ROOT:-$ANDROID_HOME}/platform-tools/adb" |
| 122 | + export EMU_BIN="${ANDROID_SDK_ROOT:-$ANDROID_HOME}/emulator/emulator" |
| 123 | + export ANDROID_AVD_HOME="$HOME/.android/avd" |
| 124 | + export QT_QPA_PLATFORM=offscreen |
| 125 | + export ANDROID_EMULATOR_USE_SYSTEM_LIBS=1 |
| 126 | + "$ADB" kill-server || true |
| 127 | + "$ADB" start-server |
| 128 | + LOG_FILE=emulator_stdout.log |
| 129 | + nohup "$EMU_BIN" -avd test -no-window -no-audio -no-boot-anim -accel off -gpu swiftshader_indirect -no-snapshot -wipe-data -netfast > "$LOG_FILE" 2>&1 & |
| 130 | + EMU_PID=$! |
| 131 | + sleep 2 |
| 132 | + kill -0 "$EMU_PID" || { echo "Emulator exited early" >&2; tail -n 100 "$LOG_FILE" || true; exit 1; } |
| 133 | + echo "Waiting for emulator device..." |
| 134 | + for i in $(seq 1 150); do |
| 135 | + DEV=$("$ADB" devices | awk '/emulator-/{print $1; exit}') |
| 136 | + [ -n "$DEV" ] && break |
| 137 | + sleep 2 |
| 138 | + done |
| 139 | + [ -n "$DEV" ] || { echo "No emulator device" >&2; tail -n 120 "$LOG_FILE" || true; exit 1; } |
| 140 | + "$ADB" -s "$DEV" wait-for-device |
| 141 | + echo "Waiting boot..." |
| 142 | + for i in $(seq 1 150); do |
| 143 | + BOOTED=$("$ADB" -s "$DEV" shell getprop sys.boot_completed 2>/dev/null | tr -d '\r') |
| 144 | + BOOTANIM=$("$ADB" -s "$DEV" shell getprop service.bootanim.exit 2>/dev/null | tr -d '\r') |
| 145 | + [ "$BOOTED" = "1" ] && [ "$BOOTANIM" = "1" ] && break |
| 146 | + sleep 2 |
| 147 | + done |
| 148 | + "$ADB" -s "$DEV" shell settings put global window_animation_scale 0 || true |
| 149 | + "$ADB" -s "$DEV" shell settings put global transition_animation_scale 0 || true |
| 150 | + "$ADB" -s "$DEV" shell settings put global animator_duration_scale 0 || true |
| 151 | + echo "Waiting for package manager..."; for i in $(seq 1 120); do "$ADB" -s "$DEV" shell pm list packages >/dev/null 2>&1 && break; sleep 2; done |
| 152 | +
|
| 153 | + - name: Install & run tests (x86_64) |
| 154 | + if: matrix.abi == 'x86_64' |
| 155 | + run: | |
| 156 | + set -euo pipefail |
| 157 | + ADB="${ANDROID_SDK_ROOT:-$ANDROID_HOME}/platform-tools/adb" |
| 158 | + DEV=$("$ADB" devices | awk '/emulator-/{print $1; exit}') |
| 159 | + APK=$(ls test/android/build-${{ matrix.abi }}/outputs/apk/debug/*.apk 2>/dev/null | head -n1 || ls test/android/build/outputs/apk/debug/*.apk 2>/dev/null | head -n1 || true) |
| 160 | + [ -n "$DEV" ] || { echo "No device" >&2; exit 1; } |
| 161 | + [ -n "$APK" ] || { echo "APK missing" >&2; exit 1; } |
| 162 | + echo "Install attempts..." |
| 163 | + for i in $(seq 1 5); do |
| 164 | + OUT=$("$ADB" -s "$DEV" install -r "$APK" 2>&1) || true |
| 165 | + echo "$OUT"; echo "$OUT" | grep -q "Success" && break |
| 166 | + sleep 5 |
| 167 | + done |
| 168 | + echo "Waiting for package manager (pm) to be responsive..." |
| 169 | + PM_READY=0 |
| 170 | + for i in $(seq 1 120); do |
| 171 | + "$ADB" -s "$DEV" shell pm list packages >/dev/null 2>&1 && { PM_READY=1; break; } |
| 172 | + sleep 2 |
| 173 | + done |
| 174 | + [ "$PM_READY" -eq 1 ] || { echo "Package manager not ready" >&2; exit 1; } |
| 175 | + echo "Probing storage readiness (/sdcard)..." |
| 176 | + set +e |
| 177 | + STORAGE_READY=0 |
| 178 | + for i in $(seq 1 90); do |
| 179 | + "$ADB" -s "$DEV" shell 'echo 42 > /sdcard/ci_probe 2>/dev/null' >/dev/null 2>&1 |
| 180 | + "$ADB" -s "$DEV" shell 'cat /sdcard/ci_probe' 2>/dev/null | grep -q '^42$' |
| 181 | + if [ $? -eq 0 ]; then STORAGE_READY=1; break; fi |
| 182 | + sleep 2 |
| 183 | + done |
| 184 | + set -e |
| 185 | + if [ "$STORAGE_READY" -ne 1 ]; then |
| 186 | + echo "Storage not fully ready (continuing)" >&2 |
| 187 | + "$ADB" -s "$DEV" shell ls -ld /sdcard 2>/dev/null || true |
| 188 | + "$ADB" -s "$DEV" shell df -h /sdcard 2>/dev/null || true |
| 189 | + fi |
| 190 | + PKG=com.example.libcorotest |
| 191 | + echo "Initial launch to create internal storage dir..." |
| 192 | + "$ADB" -s "$DEV" shell am start -n $PKG/.MainActivity >/dev/null 2>&1 || true |
| 193 | + sleep 5 |
| 194 | + echo "Prepare test config" |
| 195 | + { |
| 196 | + [ -n "${TEST_FILTER}" ] && printf 'filter=%s\n' "${TEST_FILTER}" || true |
| 197 | + printf 'timeout=%s\n' "${TEST_TIMEOUT}"; |
| 198 | + } > coro_test_config.properties |
| 199 | + "$ADB" -s "$DEV" push coro_test_config.properties /data/local/tmp/coro_test_config.properties >/dev/null |
| 200 | + echo "Copy config into app sandbox (best-effort)" |
| 201 | + # Make config copy non-fatal: fallback to defaults if copy fails |
| 202 | + COPY_OK=0 |
| 203 | + # First attempt: try copying through run-as with absolute paths |
| 204 | + PKG_DIR="/data/data/$PKG" |
| 205 | + if "$ADB" -s "$DEV" shell "run-as $PKG test -d . && run-as $PKG test -w ." 2>/dev/null; then |
| 206 | + echo "App sandbox writable, attempting config copy..." |
| 207 | + "$ADB" -s "$DEV" shell run-as $PKG sh -c "test -d files || mkdir files" 2>/dev/null || true |
| 208 | + if "$ADB" -s "$DEV" shell run-as $PKG cp /data/local/tmp/coro_test_config.properties files/coro_test_config.properties 2>/dev/null; then |
| 209 | + COPY_OK=1 |
| 210 | + echo "Config copied successfully" |
| 211 | + "$ADB" -s "$DEV" shell run-as $PKG sh -c 'ls -l files/coro_test_config.properties; head -n 3 files/coro_test_config.properties' || true |
| 212 | + fi |
| 213 | + fi |
| 214 | + if [ $COPY_OK -ne 1 ]; then |
| 215 | + echo "Config copy failed or sandbox not writable - using default test settings" >&2 |
| 216 | + echo "Tests will run with: filter=\"${TEST_FILTER:-*}\", timeout=${TEST_TIMEOUT}s" |
| 217 | + fi |
| 218 | + echo "Force-stop and relaunch for tests" |
| 219 | + "$ADB" -s "$DEV" shell am force-stop $PKG || true |
| 220 | + "$ADB" -s "$DEV" logcat -c || true |
| 221 | + "$ADB" -s "$DEV" shell am start -n $PKG/.MainActivity |
| 222 | + TIMEOUT=3600 |
| 223 | + while [ $TIMEOUT -gt 0 ]; do |
| 224 | + amstack=$("$ADB" -s "$DEV" shell dumpsys activity activities | grep -E "Activities=.*com.example.libcorotest" || true) |
| 225 | + [ -z "$amstack" ] && break |
| 226 | + sleep 2; TIMEOUT=$((TIMEOUT-2)) |
| 227 | + done |
| 228 | + "$ADB" -s "$DEV" logcat -v time -d -s coroTest:I > emulator.log || true |
| 229 | + tail -n 200 emulator.log || true |
| 230 | +
|
| 231 | + - name: Assert success (x86_64) |
| 232 | + if: matrix.abi == 'x86_64' |
| 233 | + run: | |
| 234 | + grep -q "Exit code: 0" emulator.log || { echo "Tests did not report success" >&2; exit 1; } |
| 235 | + grep -q "No tests ran" emulator.log && { echo "No tests executed" >&2; exit 1; } || true |
| 236 | +
|
| 237 | + - name: Extract test log |
| 238 | + if: always() && matrix.abi == 'x86_64' |
| 239 | + run: | |
| 240 | + set -e |
| 241 | + ADB="${ANDROID_SDK_ROOT:-$ANDROID_HOME}/platform-tools/adb" |
| 242 | + DEV=$("$ADB" devices | awk '/emulator-/{print $1; exit}') |
| 243 | + if [ -n "$DEV" ]; then |
| 244 | + "$ADB" -s "$DEV" shell run-as com.example.libcorotest cat files/libcoro-tests.log > libcoro-tests.log 2>/dev/null || echo "libcoro-tests.log not found" >&2 |
| 245 | + fi |
| 246 | + [ -f libcoro-tests.log ] && tail -n 40 libcoro-tests.log || true |
| 247 | +
|
| 248 | + - name: Upload logs |
| 249 | + if: always() && matrix.abi == 'x86_64' |
| 250 | + uses: actions/upload-artifact@v4 |
| 251 | + with: |
| 252 | + name: emulator-logs |
| 253 | + path: | |
| 254 | + emulator.log |
| 255 | + test/android/build-${{ matrix.abi }}/outputs/apk/debug/*.apk |
| 256 | + test/android/build/outputs/apk/debug/*.apk |
| 257 | + libcoro-tests.log |
0 commit comments