Generated test harness TestRunner.swift fails to compile under Swift 6 strict concurrency
Nobody has claimed this yet.
- Dominant language
- Swift
- Stars
- 3.2k
- Forks
- 106
- Avg merge
- 6d 13h
- Merged PRs (30d)
- 1
Description
Environment
| skip (Skip.git) | 1.9.7 (26808ac3604663a73ad79cbacccb287d6a3957d4) |
| Xcode | 26.4.1 (17E202) |
| Toolchain for the Android SDK build | swift-6.2.3-RELEASE.xctoolchain (chosen by Skip itself, --swift-sdk aarch64-unknown-linux-android28) |
| Affected paths | skip android test --apk and the instrumented XCSkipTests path (ANDROID_SERIAL → connectedDebugAndroidTest) |
| Project | Swift Package Manager multiplatform app using Skip Fuse |
Description
Both test execution paths generate a temporary test harness
(.../T/<uuid>/harness/Sources/TestHarness/TestRunner.swift), whose line 154 fails to compile
under Swift 6 strict concurrency:
152 | let semaphore = DispatchSemaphore(value: 0)
153 | nonisolated(unsafe) var testSuccess = false
154 | Task {
| |- error: sending value of non-Sendable type '() async -> ()' risks causing data races [#SendingRisksDataRace]
| `- note: Passing value of non-Sendable type '() async -> ()' as a 'sending' argument to initializer 'init(name:priority:operation:)' risks causing races in between local and caller code
155 | defer { semaphore.signal() }
156 | do {
:
162 | semaphore.wait()
163 |
164 | let exitCode: Int32 = testSuccess ? 0 : 1
| `- note: access can happen concurrently
165 | return jint(exitCode)
166 | }
TestRunner.swift is generated by Skip itself — there is no way to influence the generated
harness via project or compiler flags. -Xswiftc -swift-version -Xswiftc 5 in our own
Package.swift does not reach this build, because Skip builds the harness in its own temporary
package with its own flags.
Before upgrading to Skip 1.9.7, a separate Kotlin generator defect (Unresolved reference 'Bundle') masked the instrumented path — that defect is fixed as of this upgrade, and
TestRunner.swift:154 is now the same visible blocker on both paths.
Reproduction
1. skip android test --apk log
[0/1] Planning build
[1/1] Compiling plugin Create SkipLink
[2/2] Compiling plugin skipstone
Building for debugging...
[2/16] Write swift-version--35B2706C3EC7C88B.txt
Build complete! (8.89s)
[✓] Check Swift Package (0.64s)
Building for debugging...
[0/5] Write sources
[1/5] Write swift-version--35B2706C3EC7C88B.txt
error: emit-module command failed with exit code 1 (use -v to see invocation)
[3/7] Emitting module TestHarness
/private/var/.../T/7CC6111F-E935-4B29-97B8-E0DC8A7641EC/harness/Sources/TestHarness/TestRunner.swift:154:5: error: sending value of non-Sendable type '() async -> ()' risks causing data races [#SendingRisksDataRace]
152 | let semaphore = DispatchSemaphore(value: 0)
153 | nonisolated(unsafe) var testSuccess = false
154 | Task {
| |- error: sending value of non-Sendable type '() async -> ()' risks causing data races [#SendingRisksDataRace]
| `- note: Passing value of non-Sendable type '() async -> ()' as a 'sending' argument to initializer 'init(name:priority:operation:)' risks causing races in between local and caller code
155 | defer { semaphore.signal() }
156 | do {
:
162 | semaphore.wait()
163 |
164 | let exitCode: Int32 = testSuccess ? 0 : 1
| `- note: access can happen concurrently
165 | return jint(exitCode)
166 | }
[#SendingRisksDataRace]: <https://docs.swift.org/compiler/documentation/diagnostics/sending-risks-data-race>
Error: Error terminated(code: 1) running command: ~/Library/Developer/Toolchains/swift-6.2.3-RELEASE.xctoolchain/usr/bin/swift build --swift-sdk aarch64-unknown-linux-android28 --package-path .../harness --configuration debug
2. Instrumented XCSkipTests / connectedDebugAndroidTest log
[2/2] Compiling plugin skipstone
Building for debugging...
[2/18] Write swift-version--35B2706C3EC7C88B.txt
Build complete! (5.13s)
[✓] Check Swift Package (0.15s)
Building for debugging...
[0/5] Write sources
[1/5] Write swift-version--35B2706C3EC7C88B.txt
error: emit-module .../checkouts/skip/Sources/SkipTest/XCGradleHarness.swift:356: error: -[XCSkipTests testSkipModule] : error: emit-module command failed with exit code 1 (use -v to see invocation)
.../checkouts/skip/Sources/SkipTest/XCGradleHarness.swift:356: error: -[XCSkipTests testSkipModule] : Execution failed for task ':buildAndroidSwiftTestLibs' (registered in build file 'build.gradle.kts').
.../plugins/outputs/.../SkipTestHarness/XCSkipTests.swift:10: error: -[XCSkipTests testSkipModule] : failed - The expected test output folder did not exist, which may indicate that the gradle process encountered a build error or other issue.
Test Case '-[XCSkipTests testSkipModule]' failed (30.856 seconds).
[3/7] Emitting module TestHarness
.../T/80D22003-B19A-4C60-B411-D6A62A705B1A/harness/Sources/TestHarness/TestRunner.swift:154:5: error: sending value of non-Sendable type '() async -> ()' risks causing data races [#SendingRisksDataRace]
152 | let semaphore = DispatchSemaphore(value: 0)
153 | nonisolated(unsafe) var testSuccess = false
154 | Task {
| |- error: sending value of non-Sendable type '() async -> ()' risks causing data races [#SendingRisksDataRace]
| `- note: Passing value of non-Sendable type '() async -> ()' as a 'sending' argument to initializer 'init(name:priority:operation:)' risks causing races in between local and caller code
155 | defer { semaphore.signal() }
156 | do {
:
162 | semaphore.wait()
163 |
164 | let exitCode: Int32 = testSuccess ? 0 : 1
| `- note: access can happen concurrently
165 | return jint(exitCode)
166 | }
[#SendingRisksDataRace]: <https://docs.swift.org/compiler/documentation/diagnostics/sending-risks-data-race>
Error: Error terminated(code: 1) running command: ~/Library/Developer/Toolchains/swift-6.2.3-RELEASE.xctoolchain/usr/bin/swift build --package-path .../harness --configuration debug --swift-sdk aarch64-unknown-linux-android28 --build-system native
FAILURE: Build failed with an exception.
Both logs show the same failing line in the generated TestRunner.swift:154.
Expected vs. Actual
- Expected: The generated test harness compiles under Swift 6 strict concurrency.
- Actual:
emit-moduleaborts with#SendingRisksDataRace, blocking both test paths.
Impact
Without a fix, the instrumented Android test lane (with a real Looper / main-thread
semantics) is unreachable, which blocks automated verification of @MainActor/main-thread
behavior that today can only be checked via manual screenshots. The regular skip android test
lane (non-instrumented) is not affected — it uses a different path.
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 the generated TestRunner.swift around line 154 and trace how Skip produces the temporary TestHarness package. Compare the regular skip android test --apk and instrumented XCSkipTests paths, including XCGradleHarness.swift:356. Done means both harness builds compile under Swift 6.2.3 and the two reported test paths proceed past harness generation.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- android, swift
- Domain
- mobile-dev, testing-qa
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 58/100