Skip to content

Commit 092f80d

Browse files
authored
Generated code for XCTest on non-Darwin needs to be actor-isolated. (#7566)
On Linux, Windows, etc. (anywhere that uses swift-corelibs-xctest instead of XCTest.framework), SwiftPM is responsible for generating an entry point function that passes in all tests discovered at compile time. The compiler cannot tell whether the generated code is concurrency-safe. This PR modifies the generated code and makes it main-actor-isolated. Since it's only ever used in the program's main function, this is safe. Resolves #7556.
1 parent 3793551 commit 092f80d

File tree

2 files changed

+17
-7
lines changed

2 files changed

+17
-7
lines changed

Sources/Build/BuildOperationBuildSystemDelegateHandler.swift

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ final class TestDiscoveryCommand: CustomLLBuildCommand, TestBuildCommand {
8787
8888
fileprivate extension \#(className) {
8989
@available(*, deprecated, message: "Not actually deprecated. Marked as deprecated to allow inclusion of deprecated tests (which test deprecated functionality) without warnings")
90+
@MainActor
9091
static let __allTests__\#(className) = [
9192
\#(testMethods.map { $0.allTestsEntry }.joined(separator: ",\n "))
9293
]
@@ -98,6 +99,7 @@ final class TestDiscoveryCommand: CustomLLBuildCommand, TestBuildCommand {
9899
content +=
99100
#"""
100101
@available(*, deprecated, message: "Not actually deprecated. Marked as deprecated to allow inclusion of deprecated tests (which test deprecated functionality) without warnings")
102+
@MainActor
101103
func __\#(module)__allTests() -> [XCTestCaseEntry] {
102104
return [
103105
\#(testsByClassNames.map { "testCase(\($0.key).__allTests__\($0.key))" }
@@ -166,6 +168,7 @@ final class TestDiscoveryCommand: CustomLLBuildCommand, TestBuildCommand {
166168
import XCTest
167169
168170
@available(*, deprecated, message: "Not actually deprecated. Marked as deprecated to allow inclusion of deprecated tests (which test deprecated functionality) without warnings")
171+
@MainActor
169172
public func __allDiscoveredTests() -> [XCTestCaseEntry] {
170173
\#(testsKeyword) tests = [XCTestCaseEntry]()
171174
@@ -264,18 +267,15 @@ final class TestEntryPointCommand: CustomLLBuildCommand, TestBuildCommand {
264267
@main
265268
@available(*, deprecated, message: "Not actually deprecated. Marked as deprecated to allow inclusion of deprecated tests (which test deprecated functionality) without warnings")
266269
struct Runner {
267-
#if os(WASI)
268-
/// On WASI, we can't block the main thread, so XCTestMain is defined as async.
269270
static func main() async {
270271
\#(testObservabilitySetup)
272+
#if os(WASI)
273+
/// On WASI, we can't block the main thread, so XCTestMain is defined as async.
271274
await XCTMain(__allDiscoveredTests()) as Never
272-
}
273-
#else
274-
static func main() {
275-
\#(testObservabilitySetup)
275+
#else
276276
XCTMain(__allDiscoveredTests()) as Never
277+
#endif
277278
}
278-
#endif
279279
}
280280
"""#
281281
)

Tests/CommandsTests/TestCommandTests.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,4 +293,14 @@ final class TestCommandTests: CommandsTestCase {
293293
}
294294
}
295295
}
296+
297+
#if !canImport(Darwin)
298+
func testGeneratedMainIsConcurrencySafe_XCTest() throws {
299+
let strictConcurrencyFlags = ["-Xswiftc", "-strict-concurrency=complete"]
300+
try fixture(name: "Miscellaneous/TestDiscovery/Simple") { fixturePath in
301+
let (_, stderr) = try SwiftPM.Test.execute(strictConcurrencyFlags, packagePath: fixturePath)
302+
XCTAssertNoMatch(stderr, .contains("is not concurrency-safe"))
303+
}
304+
}
305+
#endif
296306
}

0 commit comments

Comments
 (0)