Fix iOS crash and workaround debug hang #1512
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Run tests | |
| env: | |
| # The name of the main module repository | |
| main_project_module: app | |
| # The name in the Play Store | |
| playstore_name: Soundscape | |
| on: | |
| pull_request: | |
| branches: [ "main" ] | |
| # Allows you to run this workflow manually from the Actions tab | |
| workflow_dispatch: | |
| jobs: | |
| lint: | |
| runs-on: ubuntu-latest | |
| environment: development | |
| steps: | |
| - uses: actions/checkout@v7 | |
| with: | |
| fetch-depth: 0 | |
| # Oboe is vendored as prebuilts rather than resolved by Gradle (so that prefab | |
| # can stay off - see app/src/main/cpp/CMakeLists.txt), which means nothing | |
| # normally verifies that the committed binaries still match the version pinned | |
| # in the catalogue. This does. Needs no JDK, so it runs before the setup step. | |
| - name: Check vendored Oboe matches the pinned version | |
| run: ./scripts/update-oboe.sh --check | |
| - name: Setup JDK + Gradle | |
| uses: ./.github/actions/setup-jdk-gradle | |
| - name: Run lint over the repo | |
| run: ./gradlew lint | |
| # The unit tests and the instrumentation tests are independent of each other, | |
| # so they run as separate parallel jobs rather than one long serial job. Both | |
| # need the offline map fixtures (the unit tests read them out of the test | |
| # resources, the instrumentation job pushes one onto the emulator), but that | |
| # step is cached so fetching it twice costs little. | |
| unit-test: | |
| name: Run unit tests | |
| runs-on: ubuntu-latest | |
| environment: development | |
| steps: | |
| - uses: actions/checkout@v7 | |
| with: | |
| fetch-depth: 0 | |
| - name: Setup offline maps for tests | |
| uses: ./.github/actions/setup-offline-maps | |
| with: | |
| output_dir: ${{ env.main_project_module }}/src/test/res/org/scottishtecharmy/soundscape | |
| - name: Setup JDK + Gradle | |
| uses: ./.github/actions/setup-jdk-gradle | |
| # Setting up the tile and search providers from secrets won't work for runners which are | |
| # triggered from forks e.g. to test a pull request. The tests that require tiles check if the | |
| # tile provider exists, so we leave in this code so that if it's triggered by "Run workflow" | |
| # the tile provider will be available. | |
| - name: Setup tile and search providers | |
| uses: ./.github/actions/write-provider-config | |
| with: | |
| platform: android | |
| tile_provider_url: ${{ secrets.TILE_PROVIDER_URL }} | |
| tile_provider_api_key: ${{ secrets.TILE_PROVIDER_API_KEY }} | |
| search_provider_url: ${{ secrets.SEARCH_PROVIDER_URL }} | |
| search_provider_api_key: ${{ secrets.SEARCH_PROVIDER_API_KEY }} | |
| extract_provider_url: ${{ secrets.EXTRACT_PROVIDER_URL }} | |
| - name: Run tests | |
| run: ./gradlew test :shared:testAndroidHostTest | |
| instrumentation-test: | |
| name: Run instrumentation tests | |
| runs-on: ubuntu-latest | |
| environment: development | |
| steps: | |
| - uses: actions/checkout@v7 | |
| with: | |
| fetch-depth: 0 | |
| - name: Setup offline maps for tests | |
| uses: ./.github/actions/setup-offline-maps | |
| with: | |
| output_dir: ${{ env.main_project_module }}/src/test/res/org/scottishtecharmy/soundscape | |
| - name: Setup JDK + Gradle | |
| uses: ./.github/actions/setup-jdk-gradle | |
| # See the note in the unit-test job: provider secrets are unavailable to | |
| # fork-triggered runs, and the tests that need them check at runtime. | |
| - name: Setup tile and search providers | |
| uses: ./.github/actions/write-provider-config | |
| with: | |
| platform: android | |
| tile_provider_url: ${{ secrets.TILE_PROVIDER_URL }} | |
| tile_provider_api_key: ${{ secrets.TILE_PROVIDER_API_KEY }} | |
| search_provider_url: ${{ secrets.SEARCH_PROVIDER_URL }} | |
| search_provider_api_key: ${{ secrets.SEARCH_PROVIDER_API_KEY }} | |
| extract_provider_url: ${{ secrets.EXTRACT_PROVIDER_URL }} | |
| # Build the APK before the emulator boots so the slow Gradle work doesn't | |
| # happen with an emulator sitting open waiting on it. | |
| - name: Build debug version | |
| run: ./gradlew assembleDebug | |
| - name: Enable KVM | |
| run: | | |
| echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules | |
| sudo udevadm control --reload-rules | |
| sudo udevadm trigger --name-match=kvm | |
| - name: Instrumentation Tests | |
| id: instrumentation-tests | |
| uses: reactivecircus/android-emulator-runner@v2 | |
| continue-on-error: true # IMPORTANT: allow pipeline to continue to Android Test Report step | |
| with: | |
| api-level: 35 | |
| target: google_apis | |
| arch: x86_64 | |
| script: | | |
| adb root | |
| adb shell mkdir -p /storage/emulated/0/Android/data/org.scottishtecharmy.soundscape/files/Download | |
| adb push ${{ env.main_project_module }}/src/test/res/org/scottishtecharmy/soundscape/glasgow-gb.pmtiles /storage/emulated/0/Android/data/org.scottishtecharmy.soundscape/files/Download/ | |
| adb logcat -c # clear logs | |
| touch app/emulator.log # create log file | |
| chmod 777 app/emulator.log # allow writing to log file | |
| adb logcat >> app/emulator.log & # pipe all logcat messages into log file as a background process | |
| # NightlyOnly-annotated tests (real-time audio soak tests, live-network map fetches) | |
| # are excluded here and run separately in nightly.yaml instead. | |
| ./gradlew connectedCheck -Pandroid.testInstrumentationRunnerArguments.notAnnotation=org.scottishtecharmy.soundscape.NightlyOnly | |
| - name: Upload Failing Test Report Log | |
| if: steps.instrumentation-tests.outcome != 'success' # upload the generated log on failure of the tests job | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: logs | |
| path: app/emulator.log # path to where the log is | |
| - name: Raise error on test fail # set a red tick on the workflow run if the tests failed | |
| if: steps.instrumentation-tests.outcome != 'success' | |
| run: exit 1 | |
| ios-test: | |
| name: Build and test iOS app | |
| runs-on: [self-hosted, macOS, ARM64] | |
| environment: development | |
| steps: | |
| - uses: actions/checkout@v7 | |
| with: | |
| fetch-depth: 0 | |
| - name: Setup iOS toolchain | |
| uses: ./.github/actions/setup-ios-toolchain | |
| # As with Android, secrets won't be available on fork-triggered PRs. | |
| # iOS tests that require provider URLs check at runtime, so an empty | |
| # Local.xcconfig is acceptable. | |
| - name: Setup tile and search providers | |
| uses: ./.github/actions/write-provider-config | |
| with: | |
| platform: ios | |
| tile_provider_url: ${{ secrets.TILE_PROVIDER_URL }} | |
| search_provider_url: ${{ secrets.SEARCH_PROVIDER_URL }} | |
| extract_provider_url: ${{ secrets.EXTRACT_PROVIDER_URL }} | |
| - name: Generate Xcode project | |
| working-directory: iosApp | |
| run: xcodegen generate | |
| # We can't link the iOS test binary because maplibre-compose's published | |
| # .klib has linker flags pointing at MapLibre.framework that our build | |
| # has no way to provide outside the Xcode/SPM toolchain. Compile the | |
| # test sources to catch Kotlin/iOS errors; the XCTest step below | |
| # exercises the linked code via xcodebuild. | |
| - name: Compile shared Kotlin/Native tests for iOS simulator | |
| run: ./gradlew --configure-on-demand :shared:compileTestKotlinIosSimulatorArm64 -PincludeIos=true | |
| # The runner image does not always pre-register a concrete simulator | |
| # device (xcodebuild only sees the "Any iOS Simulator" placeholder), so | |
| # pin the destination to a real UDID at runtime. | |
| - name: Pick iOS simulator | |
| run: | | |
| xcrun simctl list runtimes | |
| xcrun simctl list devices available | |
| UDID=$(xcrun simctl list devices available --json \ | |
| | jq -r '[.devices | to_entries[] | select(.key | test("iOS")) | .value[] | select(.isAvailable and (.name | startswith("iPhone")))][0].udid // empty') | |
| if [ -z "$UDID" ]; then | |
| echo "No iPhone simulator pre-registered; creating one." | |
| RUNTIME=$(xcrun simctl list runtimes available -j \ | |
| | jq -r '[.runtimes[] | select(.isAvailable and .platform == "iOS")] | sort_by(.version) | reverse | .[0].identifier') | |
| DEVICETYPE=$(xcrun simctl list devicetypes -j \ | |
| | jq -r '[.devicetypes[] | select(.productFamily == "iPhone")] | sort_by(.name) | reverse | .[0].identifier') | |
| UDID=$(xcrun simctl create "CI iPhone" "$DEVICETYPE" "$RUNTIME") | |
| fi | |
| echo "Using simulator $UDID" | |
| echo "SIMULATOR_DESTINATION=platform=iOS Simulator,id=$UDID" >> "$GITHUB_ENV" | |
| - name: Build iOS app for simulator | |
| run: | | |
| xcodebuild build-for-testing \ | |
| -project iosApp/iosApp.xcodeproj \ | |
| -scheme iosApp \ | |
| -configuration Debug \ | |
| -sdk iphonesimulator \ | |
| -destination "$SIMULATOR_DESTINATION" \ | |
| -derivedDataPath iosApp/build/DerivedData \ | |
| CODE_SIGNING_ALLOWED=NO | |
| - name: Run XCTest suite | |
| id: xctest | |
| continue-on-error: true | |
| run: | | |
| xcodebuild test-without-building \ | |
| -project iosApp/iosApp.xcodeproj \ | |
| -scheme iosApp \ | |
| -configuration Debug \ | |
| -sdk iphonesimulator \ | |
| -destination "$SIMULATOR_DESTINATION" \ | |
| -derivedDataPath iosApp/build/DerivedData \ | |
| CODE_SIGNING_ALLOWED=NO | |
| - name: Upload xcresult bundle | |
| if: steps.xctest.outcome != 'success' | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: ios-xcresult | |
| path: iosApp/build/DerivedData/Logs/Test/*.xcresult | |
| - name: Raise error on test fail | |
| if: steps.xctest.outcome != 'success' | |
| run: exit 1 |