JNI calls hang or crash when made from a Swift Testing @Test body under --apk instrumented mode
Nobody has claimed this yet.
- Dominant language
- Swift
- Stars
- 3.2k
- Forks
- 106
- Avg merge
- 6d 13h
- Merged PRs (30d)
- 1
Description
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 checkupclean
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.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with SwiftTestRunner and SwiftTest handling of --apk and --testing-library, then inspect SwiftJNI's jniContext requirement and AndroidBridgeBootstrap's setupCACerts and setupMainLooper paths. Reproduce the minimal contextLookup @Test with --testing-library all --apk and verify whether it completes without hanging or crashing; done is a documented working path or confirmed limitation.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- android, swift
- Domain
- mobile-dev, testing-qa
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 38/100