Skip to content

Commit 387ce43

Browse files
authored
Merge pull request #1294 from ahoppen/build-tests-in-swift-6-mode
Make tests build in Swift 6 mode
2 parents fff4dc4 + 2270128 commit 387ce43

23 files changed

+243
-161
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ default.profraw
33
Package.resolved
44
/.build
55
/.index-build
6+
/.linux-build
67
/Packages
78
/*.xcodeproj
89
/*.sublime-project

Sources/LSPTestSupport/TestJSONRPCConnection.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import XCTest
1717

1818
import class Foundation.Pipe
1919

20-
public final class TestJSONRPCConnection {
20+
public final class TestJSONRPCConnection: Sendable {
2121
public let clientToServer: Pipe = Pipe()
2222
public let serverToClient: Pipe = Pipe()
2323

@@ -151,7 +151,7 @@ public actor TestClient: MessageHandler {
151151
/// Send a request to the LSP server and (asynchronously) receive a reply.
152152
public nonisolated func send<Request: RequestType>(
153153
_ request: Request,
154-
reply: @escaping (LSPResult<Request.Response>) -> Void
154+
reply: @Sendable @escaping (LSPResult<Request.Response>) -> Void
155155
) -> RequestID {
156156
return connectionToServer.send(request, reply: reply)
157157
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import class TSCBasic.Process
14+
15+
#if !os(macOS)
16+
import Foundation
17+
#endif
18+
19+
private func xcrunMacOSSDKPath() -> String? {
20+
guard var path = try? Process.checkNonZeroExit(arguments: ["/usr/bin/xcrun", "--show-sdk-path", "--sdk", "macosx"])
21+
else {
22+
return nil
23+
}
24+
if path.last == "\n" {
25+
path = String(path.dropLast())
26+
}
27+
return path
28+
}
29+
30+
/// The default sdk path to use.
31+
public let defaultSDKPath: String? = {
32+
#if os(macOS)
33+
return xcrunMacOSSDKPath()
34+
#else
35+
return ProcessInfo.processInfo.environment["SDKROOT"]
36+
#endif
37+
}()

Sources/SKTestSupport/IndexedSingleSwiftFileTestProject.swift

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
//===----------------------------------------------------------------------===//
1212

1313
import Foundation
14-
import ISDBTibs
1514
import LanguageServerProtocol
1615
@_spi(Testing) import SKCore
1716
import SourceKitLSP
@@ -66,7 +65,7 @@ public struct IndexedSingleSwiftFileTestProject {
6665
compilerArguments.append("-index-ignore-system-modules")
6766
}
6867

69-
if let sdk = TibsBuilder.defaultSDKPath {
68+
if let sdk = defaultSDKPath {
7069
compilerArguments += ["-sdk", sdk]
7170

7271
// The following are needed so we can import XCTest

Sources/SKTestSupport/SkipUnless.swift

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,16 @@ import enum TSCBasic.ProcessEnv
2727
// MARK: - Skip checks
2828

2929
/// Namespace for functions that are used to skip unsupported tests.
30-
public enum SkipUnless {
30+
public actor SkipUnless {
3131
private enum FeatureCheckResult {
3232
case featureSupported
3333
case featureUnsupported(skipMessage: String)
3434
}
3535

36+
private static let shared = SkipUnless()
37+
3638
/// For any feature that has already been evaluated, the result of whether or not it should be skipped.
37-
private static var checkCache: [String: FeatureCheckResult] = [:]
39+
private var checkCache: [String: FeatureCheckResult] = [:]
3840

3941
/// Throw an `XCTSkip` if any of the following conditions hold
4042
/// - The Swift version of the toolchain used for testing (`ToolchainRegistry.forTesting.default`) is older than
@@ -49,7 +51,7 @@ public enum SkipUnless {
4951
///
5052
/// Independently of these checks, the tests are never skipped in Swift CI (identified by the presence of the `SWIFTCI_USE_LOCAL_DEPS` environment). Swift CI is assumed to always build its own toolchain, which is thus
5153
/// guaranteed to be up-to-date.
52-
private static func skipUnlessSupportedByToolchain(
54+
private func skipUnlessSupportedByToolchain(
5355
swiftVersion: SwiftVersion,
5456
featureName: String = #function,
5557
file: StaticString,
@@ -96,10 +98,10 @@ public enum SkipUnless {
9698
}
9799

98100
public static func sourcekitdHasSemanticTokensRequest(
99-
file: StaticString = #file,
101+
file: StaticString = #filePath,
100102
line: UInt = #line
101103
) async throws {
102-
try await skipUnlessSupportedByToolchain(swiftVersion: SwiftVersion(5, 11), file: file, line: line) {
104+
try await shared.skipUnlessSupportedByToolchain(swiftVersion: SwiftVersion(5, 11), file: file, line: line) {
103105
let testClient = try await TestSourceKitLSPClient()
104106
let uri = DocumentURI.for(.swift)
105107
testClient.openDocument("0.bitPattern", uri: uri)
@@ -127,10 +129,10 @@ public enum SkipUnless {
127129
}
128130

129131
public static func sourcekitdSupportsRename(
130-
file: StaticString = #file,
132+
file: StaticString = #filePath,
131133
line: UInt = #line
132134
) async throws {
133-
try await skipUnlessSupportedByToolchain(swiftVersion: SwiftVersion(5, 11), file: file, line: line) {
135+
try await shared.skipUnlessSupportedByToolchain(swiftVersion: SwiftVersion(5, 11), file: file, line: line) {
134136
let testClient = try await TestSourceKitLSPClient()
135137
let uri = DocumentURI.for(.swift)
136138
let positions = testClient.openDocument("func 1️⃣test() {}", uri: uri)
@@ -147,10 +149,10 @@ public enum SkipUnless {
147149

148150
/// Whether clangd has support for the `workspace/indexedRename` request.
149151
public static func clangdSupportsIndexBasedRename(
150-
file: StaticString = #file,
152+
file: StaticString = #filePath,
151153
line: UInt = #line
152154
) async throws {
153-
try await skipUnlessSupportedByToolchain(swiftVersion: SwiftVersion(5, 11), file: file, line: line) {
155+
try await shared.skipUnlessSupportedByToolchain(swiftVersion: SwiftVersion(5, 11), file: file, line: line) {
154156
let testClient = try await TestSourceKitLSPClient()
155157
let uri = DocumentURI.for(.c)
156158
let positions = testClient.openDocument("void 1️⃣test() {}", uri: uri)
@@ -177,10 +179,10 @@ public enum SkipUnless {
177179
/// toolchain’s SwiftPM stores the Swift modules on the top level but we synthesize compiler arguments expecting the
178180
/// modules to be in a `Modules` subdirectory.
179181
public static func swiftpmStoresModulesInSubdirectory(
180-
file: StaticString = #file,
182+
file: StaticString = #filePath,
181183
line: UInt = #line
182184
) async throws {
183-
try await skipUnlessSupportedByToolchain(swiftVersion: SwiftVersion(5, 11), file: file, line: line) {
185+
try await shared.skipUnlessSupportedByToolchain(swiftVersion: SwiftVersion(5, 11), file: file, line: line) {
184186
let workspace = try await SwiftPMTestProject(
185187
files: ["test.swift": ""],
186188
build: true
@@ -195,21 +197,21 @@ public enum SkipUnless {
195197
}
196198

197199
public static func toolchainContainsSwiftFormat(
198-
file: StaticString = #file,
200+
file: StaticString = #filePath,
199201
line: UInt = #line
200202
) async throws {
201-
try await skipUnlessSupportedByToolchain(swiftVersion: SwiftVersion(5, 11), file: file, line: line) {
203+
try await shared.skipUnlessSupportedByToolchain(swiftVersion: SwiftVersion(5, 11), file: file, line: line) {
202204
return await ToolchainRegistry.forTesting.default?.swiftFormat != nil
203205
}
204206
}
205207

206208
public static func sourcekitdReturnsRawDocumentationResponse(
207-
file: StaticString = #file,
209+
file: StaticString = #filePath,
208210
line: UInt = #line
209211
) async throws {
210212
struct ExpectedMarkdownContentsError: Error {}
211213

212-
return try await skipUnlessSupportedByToolchain(swiftVersion: SwiftVersion(6, 0), file: file, line: line) {
214+
return try await shared.skipUnlessSupportedByToolchain(swiftVersion: SwiftVersion(6, 0), file: file, line: line) {
213215
// The XML-based doc comment conversion did not preserve `Precondition`.
214216
let testClient = try await TestSourceKitLSPClient()
215217
let uri = DocumentURI.for(.swift)
@@ -235,10 +237,10 @@ public enum SkipUnless {
235237
/// Checks whether the index contains a fix that prevents it from adding relations to non-indexed locals
236238
/// (https://github.com/apple/swift/pull/72930).
237239
public static func indexOnlyHasContainedByRelationsToIndexedDecls(
238-
file: StaticString = #file,
240+
file: StaticString = #filePath,
239241
line: UInt = #line
240242
) async throws {
241-
return try await skipUnlessSupportedByToolchain(swiftVersion: SwiftVersion(6, 0), file: file, line: line) {
243+
return try await shared.skipUnlessSupportedByToolchain(swiftVersion: SwiftVersion(6, 0), file: file, line: line) {
242244
let project = try await IndexedSingleSwiftFileTestProject(
243245
"""
244246
func foo() {}

Sources/SKTestSupport/TestSourceKitLSPClient.swift

Lines changed: 28 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
//
1111
//===----------------------------------------------------------------------===//
1212

13+
import CAtomics
1314
import Foundation
1415
import LSPTestSupport
1516
import LanguageServerProtocol
@@ -22,7 +23,7 @@ import XCTest
2223

2324
extension SourceKitLSPServer.Options {
2425
/// The default SourceKitLSPServer options for testing.
25-
public static var testDefault = Self(swiftPublishDiagnosticsDebounceDuration: 0)
26+
public static let testDefault = Self(swiftPublishDiagnosticsDebounceDuration: 0)
2627
}
2728

2829
/// A mock SourceKit-LSP client (aka. a mock editor) that behaves like an editor
@@ -35,7 +36,8 @@ public final class TestSourceKitLSPClient: MessageHandler {
3536
public typealias RequestHandler<Request: RequestType> = (Request) -> Request.Response
3637

3738
/// The ID that should be assigned to the next request sent to the `server`.
38-
private var nextRequestID: Int = 0
39+
/// `nonisolated(unsafe)` is fine because `nextRequestID` is atomic.
40+
private nonisolated(unsafe) var nextRequestID = AtomicUInt32(initialValue: 0)
3941

4042
/// If the server is not using the global module cache, the path of the local
4143
/// module cache.
@@ -66,12 +68,12 @@ public final class TestSourceKitLSPClient: MessageHandler {
6668
///
6769
/// Conceptually, this is an array of `RequestHandler<any RequestType>` but
6870
/// since we can't express this in the Swift type system, we use `[Any]`.
69-
private var requestHandlers: [Any] = []
71+
private nonisolated(unsafe) var requestHandlers = ThreadSafeBox<[Any]>(initialValue: [])
7072

7173
/// A closure that is called when the `TestSourceKitLSPClient` is destructed.
7274
///
7375
/// This allows e.g. a `IndexedSingleSwiftFileTestProject` to delete its temporary files when they are no longer needed.
74-
private let cleanUp: () -> Void
76+
private let cleanUp: @Sendable () -> Void
7577

7678
/// - Parameters:
7779
/// - serverOptions: The equivalent of the command line options with which sourcekit-lsp should be started
@@ -97,7 +99,7 @@ public final class TestSourceKitLSPClient: MessageHandler {
9799
usePullDiagnostics: Bool = true,
98100
workspaceFolders: [WorkspaceFolder]? = nil,
99101
preInitialization: ((TestSourceKitLSPClient) -> Void)? = nil,
100-
cleanUp: @escaping () -> Void = {}
102+
cleanUp: @Sendable @escaping () -> Void = {}
101103
) async throws {
102104
if !useGlobalModuleCache {
103105
moduleCache = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(UUID().uuidString)
@@ -169,8 +171,7 @@ public final class TestSourceKitLSPClient: MessageHandler {
169171
// It's really unfortunate that there are no async deinits. If we had async
170172
// deinits, we could await the sending of a ShutdownRequest.
171173
let sema = DispatchSemaphore(value: 0)
172-
nextRequestID += 1
173-
server.handle(ShutdownRequest(), id: .number(nextRequestID)) { result in
174+
server.handle(ShutdownRequest(), id: .number(Int(nextRequestID.fetchAndIncrement()))) { result in
174175
sema.signal()
175176
}
176177
sema.wait()
@@ -186,9 +187,8 @@ public final class TestSourceKitLSPClient: MessageHandler {
186187

187188
/// Send the request to `server` and return the request result.
188189
public func send<R: RequestType>(_ request: R) async throws -> R.Response {
189-
nextRequestID += 1
190190
return try await withCheckedThrowingContinuation { continuation in
191-
server.handle(request, id: .number(self.nextRequestID)) { result in
191+
server.handle(request, id: .number(Int(nextRequestID.fetchAndIncrement()))) { result in
192192
continuation.resume(with: result)
193193
}
194194
}
@@ -273,7 +273,7 @@ public final class TestSourceKitLSPClient: MessageHandler {
273273
/// If the next request that is sent to the client is of a different kind than
274274
/// the given handler, `TestSourceKitLSPClient` will emit an `XCTFail`.
275275
public func handleNextRequest<R: RequestType>(_ requestHandler: @escaping RequestHandler<R>) {
276-
requestHandlers.append(requestHandler)
276+
requestHandlers.value.append(requestHandler)
277277
}
278278

279279
// MARK: - Conformance to MessageHandler
@@ -290,19 +290,21 @@ public final class TestSourceKitLSPClient: MessageHandler {
290290
id: LanguageServerProtocol.RequestID,
291291
reply: @escaping (LSPResult<Request.Response>) -> Void
292292
) {
293-
let requestHandlerAndIndex = requestHandlers.enumerated().compactMap {
294-
(index, handler) -> (RequestHandler<Request>, Int)? in
295-
guard let handler = handler as? RequestHandler<Request> else {
296-
return nil
293+
requestHandlers.withLock { requestHandlers in
294+
let requestHandlerAndIndex = requestHandlers.enumerated().compactMap {
295+
(index, handler) -> (RequestHandler<Request>, Int)? in
296+
guard let handler = handler as? RequestHandler<Request> else {
297+
return nil
298+
}
299+
return (handler, index)
300+
}.first
301+
guard let (requestHandler, index) = requestHandlerAndIndex else {
302+
reply(.failure(.methodNotFound(Request.method)))
303+
return
297304
}
298-
return (handler, index)
299-
}.first
300-
guard let (requestHandler, index) = requestHandlerAndIndex else {
301-
reply(.failure(.methodNotFound(Request.method)))
302-
return
305+
reply(.success(requestHandler(params)))
306+
requestHandlers.remove(at: index)
303307
}
304-
reply(.success(requestHandler(params)))
305-
requestHandlers.remove(at: index)
306308
}
307309

308310
// MARK: - Convenience functions
@@ -396,8 +398,10 @@ public struct DocumentPositions {
396398
///
397399
/// This allows us to set the ``TestSourceKitLSPClient`` as the message handler of
398400
/// `SourceKitLSPServer` without retaining it.
399-
private class WeakMessageHandler: MessageHandler {
400-
private weak var handler: (any MessageHandler)?
401+
private final class WeakMessageHandler: MessageHandler, Sendable {
402+
// `nonisolated(unsafe)` is fine because `handler` is never modified, only if the weak reference is deallocated, which
403+
// is atomic.
404+
private nonisolated(unsafe) weak var handler: (any MessageHandler)?
401405

402406
init(_ handler: any MessageHandler) {
403407
self.handler = handler
@@ -410,7 +414,7 @@ private class WeakMessageHandler: MessageHandler {
410414
func handle<Request: RequestType>(
411415
_ params: Request,
412416
id: LanguageServerProtocol.RequestID,
413-
reply: @escaping (LanguageServerProtocol.LSPResult<Request.Response>) -> Void
417+
reply: @Sendable @escaping (LanguageServerProtocol.LSPResult<Request.Response>) -> Void
414418
) {
415419
guard let handler = handler else {
416420
reply(.failure(.unknown("Handler has been deallocated")))

Sources/SKTestSupport/Utils.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ public func testScratchDir(testName: String = #function) throws -> URL {
7070
/// The temporary directory will be deleted at the end of `directory` unless the
7171
/// `SOURCEKITLSP_KEEP_TEST_SCRATCH_DIR` environment variable is set.
7272
public func withTestScratchDir<T>(
73-
_ body: (AbsolutePath) async throws -> T,
73+
@_inheritActorContext _ body: @Sendable (AbsolutePath) async throws -> T,
7474
testName: String = #function
7575
) async throws -> T {
7676
let scratchDirectory = try testScratchDir(testName: testName)

Sources/SKTestSupport/WrappedSemaphore.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ import Dispatch
1717
///
1818
/// This should only be used for tests that test priority escalation and thus cannot await a `Task` (which would cause
1919
/// priority elevations).
20-
public struct WrappedSemaphore {
21-
let semaphore = DispatchSemaphore(value: 0)
20+
public struct WrappedSemaphore: Sendable {
21+
private let semaphore = DispatchSemaphore(value: 0)
2222

2323
public init() {}
2424

0 commit comments

Comments
 (0)