From 44fa307b7676d87158d9adf9f3465a0639777264 Mon Sep 17 00:00:00 2001 From: Dan Fabulich Date: Wed, 22 Apr 2026 14:41:00 -0700 Subject: [PATCH 1/3] Prepare `XCGradleHarness` for `NonisolatedNonsendingByDefault` Fixes #675 The issue occurred because the `skip` project is built without `NonisolatedNonsendingByDefault`, which means that `runGradleTests` and `invokeGradle` were `@concurrent` by legacy default, forbidden to access `self` or any `task-isolated` members. The generated `XCSkipTests` file in the end-user's project, however, _is_ `NonisolatedNonsendingByDefault` on Swift 6.2, which means that `testSkipModule` method became task-isolated. (I'm a little unclear on how/why the test became task-isolated, but it did.) We could have fixed this by updating `skip` to Swift 6.2+ and enabling `NonisolatedNonsendingByDefault` and other "Approachable Concurrency" features there, but this is easier, and will continue to work if/when we upgrade `skip` in the future. (When `skip` does decide to enable `NonisolatedNonsendingByDefault`, these annotations will simply reiterate the default.) --- Sources/SkipTest/XCGradleHarness.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Sources/SkipTest/XCGradleHarness.swift b/Sources/SkipTest/XCGradleHarness.swift index 6a6b443..ebe5ee8 100644 --- a/Sources/SkipTest/XCGradleHarness.swift +++ b/Sources/SkipTest/XCGradleHarness.swift @@ -24,6 +24,7 @@ extension XCGradleHarness where Self : XCTestCase { /// /// - SeeAlso: https://developer.android.com/studio/test/command-line /// - SeeAlso: https://docs.gradle.org/current/userguide/java_testing.html#test_filtering + nonisolated(nonsending) public func runGradleTests(device: String? = ProcessInfo.processInfo.environment["ANDROID_SERIAL"], file: StaticString = #file, line: UInt = #line) async throws { do { #if DEBUG @@ -50,6 +51,7 @@ extension XCGradleHarness where Self : XCTestCase { /// - moduleSuffix: the expected module name for automatic test determination /// - sourcePath: the full path to the test case call site, which is used to determine the package root @available(macOS 13, macCatalyst 16, iOS 16, tvOS 16, watchOS 8, *) + nonisolated(nonsending) func invokeGradle(actions: [String], arguments: [String] = [], info: Bool = false, deviceID: String? = nil, testFilter: String? = nil, moduleName: String? = nil, maxMemory: UInt64? = ProcessInfo.processInfo.physicalMemory, fromSourceFileRelativeToPackageRoot sourcePath: StaticString? = #file) async throws { // the filters should be passed through to the --tests argument, but they don't seem to work for Android unit tests, neighter for Robolectric nor connected tests From 484c945e71247d3bcfaefd488a35c5c5481aac5a Mon Sep 17 00:00:00 2001 From: Marc Prud'hommeaux Date: Tue, 19 May 2026 12:24:48 -0400 Subject: [PATCH 2/3] Update XCGradleHarness.swift to fix testFilters argument --- Sources/SkipTest/XCGradleHarness.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/SkipTest/XCGradleHarness.swift b/Sources/SkipTest/XCGradleHarness.swift index 31a7095..1c12a48 100644 --- a/Sources/SkipTest/XCGradleHarness.swift +++ b/Sources/SkipTest/XCGradleHarness.swift @@ -61,7 +61,7 @@ extension XCGradleHarness where Self : XCTestCase { /// - sourcePath: the full path to the test case call site, which is used to determine the package root @available(macOS 13, macCatalyst 16, iOS 16, tvOS 16, watchOS 8, *) nonisolated(nonsending) - func invokeGradle(actions: [String], arguments: [String] = [], info: Bool = false, deviceID: String? = nil, testFilter: String? = nil, moduleName: String? = nil, maxMemory: UInt64? = ProcessInfo.processInfo.physicalMemory, fromSourceFileRelativeToPackageRoot sourcePath: StaticString? = #file) async throws { + func invokeGradle(actions: [String], arguments: [String] = [], info: Bool = false, deviceID: String? = nil, testFilters: [String]? = nil, moduleName: String? = nil, maxMemory: UInt64? = ProcessInfo.processInfo.physicalMemory, fromSourceFileRelativeToPackageRoot sourcePath: StaticString? = #file) async throws { var actions = actions let isTestAction = actions.contains(where: { $0.hasPrefix("test") || $0.hasPrefix("connected") }) From 938f2a7bafbc7f180530bae29aeae56a9f7fd647 Mon Sep 17 00:00:00 2001 From: Marc Prud'hommeaux Date: Tue, 19 May 2026 12:25:26 -0400 Subject: [PATCH 3/3] Update XCGradleHarness.swift to fix testFilters --- Sources/SkipTest/XCGradleHarness.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/SkipTest/XCGradleHarness.swift b/Sources/SkipTest/XCGradleHarness.swift index 1c12a48..c6a36d4 100644 --- a/Sources/SkipTest/XCGradleHarness.swift +++ b/Sources/SkipTest/XCGradleHarness.swift @@ -61,7 +61,7 @@ extension XCGradleHarness where Self : XCTestCase { /// - sourcePath: the full path to the test case call site, which is used to determine the package root @available(macOS 13, macCatalyst 16, iOS 16, tvOS 16, watchOS 8, *) nonisolated(nonsending) - func invokeGradle(actions: [String], arguments: [String] = [], info: Bool = false, deviceID: String? = nil, testFilters: [String]? = nil, moduleName: String? = nil, maxMemory: UInt64? = ProcessInfo.processInfo.physicalMemory, fromSourceFileRelativeToPackageRoot sourcePath: StaticString? = #file) async throws { + func invokeGradle(actions: [String], arguments: [String] = [], info: Bool = false, deviceID: String? = nil, testFilters: [String] = [], moduleName: String? = nil, maxMemory: UInt64? = ProcessInfo.processInfo.physicalMemory, fromSourceFileRelativeToPackageRoot sourcePath: StaticString? = #file) async throws { var actions = actions let isTestAction = actions.contains(where: { $0.hasPrefix("test") || $0.hasPrefix("connected") })