skiptools / skiptools/skip

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

Open
#727 0 comments 0 reactions 0 assignees View on GitHub

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_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.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.