Add --apk flag to android test command to package the tests into an APK for full Android API access#199
Conversation
…for full Android API access
This was referenced Mar 1, 2026
|
👏 |
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR adds
--apkmode toskip android test, providing two distinct ways to run Swift tests on Android. The default mode pushes a test executable to the device and runs it viaadb shell. The new--apkmode packages the tests into a real Android APK with a NativeActivity harness, giving them access to the full Android framework through JNI.APK mode:
skip android test --apkPackages the tests into a real Android APK and runs them as an installed app. Steps:
swift build --build-tests --swift-sdk <triple> -Xlinker -shared -Xlinker -no-piecross-compiles the test target as a shared library (.so) instead of an executable.sodependencies (same three sources as default mode).so(renamed tolib<Package>Test.so) and all dependency.sofiles into a local staginglib/<abi>/directorytest_harness.c): implementsANativeActivity_onCreatewhich stores the activity pointer and spawns atest_runnerpthread. The runner callsredirect_stdio()to pipe stdout/stderr through reader threads that forward each line to logcat via__android_log_print. It then callsdlopenon the test library, resolvesswt_abiv0_getEntryPointviadlsym, calls the getter to obtain the entry point, opens the event stream file if configured, and calls the Swiftrun_swift_tests()function. Ahandle_test_record()function writes JSON records to both stdout (which goes to logcat) and the event stream fd.TestRunner.swift): exports@_cdecl("run_swift_tests")whichunsafeBitCasts the raw pointer to the ST-0002EntryPointtype, creates a record handler closure that delegates to the Chandle_test_record, and calls the entry point in an async Taskswift build --swift-sdk <triple> --package-path <harness>cross-compiles the harness, producinglibtest_harness.solibtest_harness.sointo the APK'slib/<abi>/alongside the test libraryAndroidManifest.xmldeclaring aNativeActivitywithandroid.app.lib_nameset totest_harnessaapt2 linkcreates the unsigned APK from the manifest andandroid.jarzip -r -0adds thelib/tree (all native libraries) into the APKzipalignaligns the APK for efficient memory mappingkeytool -genkeypairgenerates~/.android/debug.keystoreif it doesn't already existapksigner signsigns the APK with the debug keyadb uninstallremoves any previous version of the test packageadb install -tinstalls the signed APKadb logcat -cclears logcatadb shell am start -n <package>/android.app.NativeActivitylaunches the test activityadb logcat -s SwiftTest:I -v rawstreams test output, forwarding each line to the host's stdout and watching for theSWIFT_TEST_EXIT_CODE=<n>sentinel--event-stream-output-pathwas specified:adb pullcopies/data/local/tmp/swift-test-events.jsonlto the host, thenadb shell rm -fcleans it upadb uninstallremoves the test APK (unless--no-cleanup)Because the tests run inside a real Android app process with NativeActivity, they get a full JNI environment with access to the entire Android framework (Context, AssetManager, content providers, system services, etc.). The tradeoff is that resource bundles don't work: the test
.sois loaded from the APK'slib/directory by the Android runtime, and Foundation has no support for resolvingBundle.moduleresources from an APK's native library path. Tests that load bundled resources at runtime will fail to find them.Default mode:
skip android testAs a refresher, the pre-existing default test mode will compile the test target as an XCTest CLI executable and runs it directly on a shell on the Android emulator or device with the following steps:
swift build --build-tests --swift-sdk <triple>cross-compiles the test target as an executable (<Package>PackageTests.xctest).sodependencies from three sources: build output artifacts, Swift runtime libraries from the SDK's dynamic lib path, andlibc++_shared.sofrom the NDK sysroot.resourcessidecar directories in the build output (e.g.Module_TestModule.resources)adb shell mkdir -p /data/local/tmp/swift-android/<package>-<uuid>/creates a staging directoryadb pushcopies the executable, all.sofiles,.resourcesdirectories, and any--copyfiles to the staging directoryadb shell cd '<staging>' && ./<Package>PackageTests.xctestruns the tests. If the ELF binary's needed section includeslibTesting.so, the executable is run a second time with--testing-library swift-testing, and exit code 69 (EX_UNAVAILABLE= no tests found) is treated as successadb shell rm -rcleans up the staging directory (unless--no-cleanup)Because the executable runs from a flat directory on the filesystem,
Bundle.moduleresource lookup works normally: the.resourcesbundles are right there alongside the binary. The tradeoff is that there is no Android application context, no JVM, and no JNI environment, so tests that need Android framework APIs will not work in this mode.Comparison
--apk)adb shellcommand line.resourcesdirs pushed alongside)