Summary
skip android test --apk is the only mode with a real Android application Context
(per its own --help text: "required for tests that need an Android application
context"), but its SwiftTestRunner/SwiftTest harness only ever runs Swift
Testing, never XCTest, regardless of --testing-library. And any JNI call made
from inside a @Test body under that harness either hangs indefinitely or crashes,
depending on how it's structured — never succeeds.
Minimal repro
import Testing
import SkipAndroidBridge
@Test func contextLookup() throws {
let context = jniContext { ProcessInfo.processInfo.dynamicAndroidContext() }
}
Run with:
skip android test --android-serial <emulator-or-device> --testing-library all --apk
against any Fuse (skip init --native-app) package with this test in its SPM test
target.
What happens
Attempt 1 — plain call, wrapped in jniContext { } as required.
Hangs indefinitely. Confirmed via adb shell ps that the process stays alive
(state S, sleeping) rather than crashing or completing — a genuine deadlock, not
an error. testStarted is logged; no testEnded, no crash, no further log output
observed for 20+ minutes before external kill.
Attempt 2 — bootstrap first.
Added try? AndroidBridgeBootstrap.initAndroidBridge(filesDir:cacheDir:) (with
placeholder paths — it only needs real ones for the FileManager/XDG env-var
setup) as the first statement in the test, before any context access, mirroring
skip-android-bridge's own AndroidBridgeTests.swift pattern.
Result: crashes immediately —
SwiftJNI/SwiftJNI.swift:166: Fatal error: SwiftJNI: you must perform JNI operations within a jniContext { ... } block — but this time the crash is inside
initAndroidBridge's own internals (AndroidBootstrap.setupCACerts() and/or
AndroidLooper.setupMainLooper()), which don't wrap their own JNI calls in
jniContext { }.
Attempt 3 — @MainActor, both structural forms.
Tried both a bare @Test @MainActor func and a @Suite @MainActor struct wrapping
the same call. Both crash identically, with this backtrace:
#00 _dispatch_main_queue_callback_4CF
#01 TestHarness.drainMainQueueCallback(...)
#02 (JNI trampoline back into drainMainQueueCallback)
#03 android::Looper::pollOnce
#04 android::android_os_MessageQueue_nativePollOnce
#05 android.os.MessageQueue.next
#06 android.os.Looper.loopOnce
#07 android.os.Looper.loop
#08 android.app.ActivityThread.main
SwiftTest's own harness (libtest_harness.so) already owns the main thread and
drains libdispatch's main queue itself via drainMainQueueCallback, invoked
reentrantly from inside Looper.pollOnce. Scheduling @MainActor-isolated test
code onto the main dispatch queue makes it run inside that same reentrant call,
colliding with the harness's own control flow. The @Suite-level variant crashes
even before testStarted is logged — during Swift Testing's own suite-dispatch
machinery, before the test body runs at all.
Attempt 4 — @Suite(.serialized).
Confirmed via adb shell ps: reproduces attempt 1's hang exactly (process alive,
state S, no crash, no completion). Avoids attempt 3's crash simply by staying on
the worker thread — which is exactly where attempt 1's wedge happens.
Expected
Some documented, working way to make a JNI/Context-touching call from a Swift
Testing @Test body under --apk mode — or confirmation this is a known
limitation and XCTest-under---apk (or some other path) is the intended way to get
Context-dependent Android tests running.
Environment
- Skip 1.9.5
- Swift 6.3.3 (Android SDK
swift-6.3.3-RELEASE_android)
- Android API 34, x86_64 emulator (
Pixel_API_34)
- macOS 26.5, Intel (x86_64) host
skip checkup clean
Why this matters
Any test needing a real Android Context — credential storage via
EncryptedSharedPreferences, TLS/TrustManager work, anything touching
android.content.Context-scoped APIs — currently has no working path to on-device
automated verification under Fuse. Happy to provide the full local package/test
setup if useful; kept this report to the minimal repro rather than an app-specific
one.
Summary
skip android test --apkis the only mode with a real Android application Context(per its own
--helptext: "required for tests that need an Android applicationcontext"), but its
SwiftTestRunner/SwiftTestharness only ever runs SwiftTesting, never XCTest, regardless of
--testing-library. And any JNI call madefrom inside a
@Testbody under that harness either hangs indefinitely or crashes,depending on how it's structured — never succeeds.
Minimal repro
Run with:
against any Fuse (
skip init --native-app) package with this test in its SPM testtarget.
What happens
Attempt 1 — plain call, wrapped in
jniContext { }as required.Hangs indefinitely. Confirmed via
adb shell psthat the process stays alive(state
S, sleeping) rather than crashing or completing — a genuine deadlock, notan error.
testStartedis logged; notestEnded, no crash, no further log outputobserved for 20+ minutes before external kill.
Attempt 2 — bootstrap first.
Added
try? AndroidBridgeBootstrap.initAndroidBridge(filesDir:cacheDir:)(withplaceholder paths — it only needs real ones for the
FileManager/XDG env-varsetup) as the first statement in the test, before any context access, mirroring
skip-android-bridge's ownAndroidBridgeTests.swiftpattern.Result: crashes immediately —
SwiftJNI/SwiftJNI.swift:166: Fatal error: SwiftJNI: you must perform JNI operations within a jniContext { ... } block— but this time the crash is insideinitAndroidBridge's own internals (AndroidBootstrap.setupCACerts()and/orAndroidLooper.setupMainLooper()), which don't wrap their own JNI calls injniContext { }.Attempt 3 —
@MainActor, both structural forms.Tried both a bare
@Test @MainActor funcand a@Suite @MainActor structwrappingthe same call. Both crash identically, with this backtrace:
SwiftTest's own harness (libtest_harness.so) already owns the main thread anddrains libdispatch's main queue itself via
drainMainQueueCallback, invokedreentrantly from inside
Looper.pollOnce. Scheduling@MainActor-isolated testcode onto the main dispatch queue makes it run inside that same reentrant call,
colliding with the harness's own control flow. The
@Suite-level variant crasheseven before
testStartedis logged — during Swift Testing's own suite-dispatchmachinery, before the test body runs at all.
Attempt 4 —
@Suite(.serialized).Confirmed via
adb shell ps: reproduces attempt 1's hang exactly (process alive,state
S, no crash, no completion). Avoids attempt 3's crash simply by staying onthe worker thread — which is exactly where attempt 1's wedge happens.
Expected
Some documented, working way to make a JNI/Context-touching call from a Swift
Testing
@Testbody under--apkmode — or confirmation this is a knownlimitation and XCTest-under-
--apk(or some other path) is the intended way to getContext-dependent Android tests running.
Environment
swift-6.3.3-RELEASE_android)Pixel_API_34)skip checkupcleanWhy this matters
Any test needing a real Android Context — credential storage via
EncryptedSharedPreferences, TLS/TrustManagerwork, anything touchingandroid.content.Context-scoped APIs — currently has no working path to on-deviceautomated verification under Fuse. Happy to provide the full local package/test
setup if useful; kept this report to the minimal repro rather than an app-specific
one.