Skip to content

Add Android CI that runs the tests on an emulator - #30

Merged
Kyle-Ye merged 3 commits into
mainfrom
ci/android
Sep 28, 2026
Merged

Kyle-Ye merged 3 commits into
mainfrom
ci/android

Conversation

@Kyle-Ye

@Kyle-Ye Kyle-Ye commented Sep 28, 2026 •

Copy link
Copy Markdown
Member

Summary

  • New Android workflow: cross-compiles the test suite with the official Swift SDK for Android (Swift 6.3.3) and runs it on an x86_64 API 28 emulator.
  • utils/android_test.sh (also make test-android): the same build → stage → run steps for local use against any emulator or device reachable via adb. build and run can be invoked separately, and run accepts XCTest filters.
  • COpenCombineHelpers links libc++_shared on Android. SwiftPM adds -lstdc++ for C++ targets, but Android's libstdc++ only provides operator new/delete. Without this, std::system_error, std::terminate and the exception runtime the helpers use would be left undefined.
  • Test fix: ExecuteOnBackgroundThread.swift imports Android, because the Swift SDK for Android has no Glibc module. It also binds the pthread_create start-routine argument as an optional: the argument is _Nonnull in the NDK r27 headers but _Nullable from r28 on and with Glibc.
  • README CI badge and a "Testing on Android" section in CONTRIBUTING.

Workflow details

  • Host toolchain: swift.org tarball for Ubuntu 24.04, GPG-verified against the swift.org keys. The keys are fetched with curl --compressed | zcat -f because the CDN can serve them compressed. The "Swift 6.x Release Signing Key" expired on 2026-09-16, but gpg still accepts the 6.3.3 signature, which was made before that.
  • Swift SDK for Android: swift sdk install with the checksum from swift.org's release API, the same source as the Wasm SDK checksum in wasm.yml.
  • NDK: the runner's preinstalled r27d ($ANDROID_NDK_HOME), linked into the SDK by the script through the bundle's setup-android-sdk.sh.
  • Emulator: reactivecircus/android-emulator-runner@v2, API 28, arch: x86_64 (the action defaults to 32-bit x86), with KVM enabled via udev. Unused runner toolchains are removed first to leave room for the emulator, as swiftlang's Android job does.
  • Tests run from /data/local/tmp/OpenCombineTests, next to the SDK's Swift runtime libraries and the NDK's libc++_shared.so. The XCTest exit code decides the job result.

Testing

  • Android: 1566 tests, 0 failures on the x86_64 API 28 emulator. That includes the assertCrashes checks, which re-launch the test binary and expect it to crash.
  • All other jobs (macOS, compatibility, Ubuntu, Windows, WASM, SwiftLint) pass, and Codecov reports unchanged coverage.
  • shellcheck and actionlint are clean.

Cross-compile the test suite with the official Swift SDK for Android
(Swift 6.3.3, checksum from swift.org) and run it on an x86_64 API 28
emulator via reactivecircus/android-emulator-runner. The host toolchain
is downloaded from swift.org and its signature verified, and the NDK
preinstalled on the runner (r27d) is linked into the SDK.

utils/android_test.sh does the building, staging and running, so the
same steps work locally against any emulator or device reachable via
adb (make test-android). It stages the test runner with the Swift
runtime libraries from the SDK and libc++_shared.so from the NDK,
pushes them to /data/local/tmp and propagates the XCTest exit code.

ExecuteOnBackgroundThread.swift now imports the Android module: the
Swift SDK for Android exposes bionic as `Android`, not `Glibc`, so the
test helper hit its #error on Android.
@codecov

codecov Bot commented Sep 28, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 96.32%. Comparing base (fa6ba53) to head (b584daa).

Additional details and impacted files
@@           Coverage Diff           @@
##             main      #30   +/-   ##
=======================================
  Coverage   96.32%   96.32%           
=======================================
  Files         108      108           
  Lines        8100     8100           
=======================================
  Hits         7802     7802           
  Misses        298      298           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@Kyle-Ye Kyle-Ye linked an issue Sep 28, 2026 that may be closed by this pull request
The first run failed with "gpg: no valid OpenPGP data found": the
signing keys arrived compressed. Let curl decode any declared content
encoding and pass the result through zcat -f for gzip bytes served
without one, as swiftlang/github-workflows does.

Also name the job explicitly so the SDK checksum from the matrix no
longer shows up in the check name.
The Android job failed to compile ExecuteOnBackgroundThread.swift:
pthread_create's start routine takes `void* _Nonnull` in the NDK r27
headers but `void* _Nullable` from r28 on, and on Glibc. Bind the
argument as an optional so the closure compiles with either.

Link libc++_shared for COpenCombineHelpers on Android. SwiftPM adds
-lstdc++ for C++ targets there, but Android's libstdc++ only provides
operator new/delete, so std::system_error, std::terminate and the
exception runtime the helpers use would be left undefined.
@Kyle-Ye Kyle-Ye mentioned this pull request Sep 28, 2026
@Kyle-Ye
Kyle-Ye merged commit 6fd3c58 into main Sep 28, 2026
10 of 11 checks passed
@Kyle-Ye
Kyle-Ye deleted the ci/android branch September 28, 2026 16:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add Android support

1 participant