Skip to content

Generated test harness TestRunner.swift fails to compile under Swift 6 strict concurrency #727

Description

@SeraphimSakuja

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_SERIALconnectedDebugAndroidTest)
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-module aborts 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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions