Skip to content

Commit a7b7f21

Browse files
authored
Merge pull request #2035 from ahoppen/build-server-tests
Add a new test project type that uses a custom build server
2 parents f92fa53 + 5c60d1d commit a7b7f21

File tree

11 files changed

+292
-194
lines changed

11 files changed

+292
-194
lines changed

Sources/BuildSystemIntegration/BuildSystemManager.swift

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -179,9 +179,9 @@ private extension BuildSystemSpec {
179179
_ createBuildSystem: @Sendable (_ connectionToSourceKitLSP: any Connection) async throws -> BuiltInBuildSystem?
180180
) async -> BuildSystemAdapter? {
181181
let connectionToSourceKitLSP = LocalConnection(
182-
receiverName: "BuildSystemManager for \(projectRoot.lastPathComponent)"
182+
receiverName: "BuildSystemManager for \(projectRoot.lastPathComponent)",
183+
handler: messagesToSourceKitLSPHandler
183184
)
184-
connectionToSourceKitLSP.start(handler: messagesToSourceKitLSPHandler)
185185

186186
let buildSystem = await orLog("Creating build system") {
187187
try await createBuildSystem(connectionToSourceKitLSP)
@@ -197,9 +197,9 @@ private extension BuildSystemSpec {
197197
buildSystemHooks: buildSystemHooks
198198
)
199199
let connectionToBuildSystem = LocalConnection(
200-
receiverName: "\(type(of: buildSystem)) for \(projectRoot.lastPathComponent)"
200+
receiverName: "\(type(of: buildSystem)) for \(projectRoot.lastPathComponent)",
201+
handler: buildSystemAdapter
201202
)
202-
connectionToBuildSystem.start(handler: buildSystemAdapter)
203203
return .builtIn(buildSystemAdapter, connectionToBuildSystem: connectionToBuildSystem)
204204
}
205205

@@ -266,9 +266,9 @@ private extension BuildSystemSpec {
266266
#endif
267267
case .injected(let injector):
268268
let connectionToSourceKitLSP = LocalConnection(
269-
receiverName: "BuildSystemManager for \(projectRoot.lastPathComponent)"
269+
receiverName: "BuildSystemManager for \(projectRoot.lastPathComponent)",
270+
handler: messagesToSourceKitLSPHandler
270271
)
271-
connectionToSourceKitLSP.start(handler: messagesToSourceKitLSPHandler)
272272
return .injected(
273273
await injector(projectRoot, connectionToSourceKitLSP)
274274
)
@@ -510,8 +510,7 @@ package actor BuildSystemManager: QueueBasedMessageHandler {
510510
connectionToSourceKitLSP: legacyBuildServer.connectionToSourceKitLSP,
511511
buildSystemHooks: buildSystemHooks
512512
)
513-
let connectionToBuildSystem = LocalConnection(receiverName: "Legacy BSP server")
514-
connectionToBuildSystem.start(handler: adapter)
513+
let connectionToBuildSystem = LocalConnection(receiverName: "Legacy BSP server", handler: adapter)
515514
self.buildSystemAdapter = .builtIn(adapter, connectionToBuildSystem: connectionToBuildSystem)
516515
}
517516
Task {

Sources/BuildSystemIntegration/CMakeLists.txt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,7 @@ add_library(BuildSystemIntegration STATIC
1919
LegacyBuildServerBuildSystem.swift
2020
MainFilesProvider.swift
2121
SplitShellCommand.swift
22-
SwiftPMBuildSystem.swift
23-
TestBuildSystem.swift)
22+
SwiftPMBuildSystem.swift)
2423
set_target_properties(BuildSystemIntegration PROPERTIES
2524
INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_Swift_MODULE_DIRECTORY})
2625
target_link_libraries(BuildSystemIntegration PUBLIC

Sources/BuildSystemIntegration/LegacyBuildServerBuildSystem.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,10 @@ actor LegacyBuildServerBuildSystem: MessageHandler, BuiltInBuildSystem {
7373
self.configPath = configPath
7474
self.indexDatabasePath = nil
7575
self.indexStorePath = nil
76-
self.connectionToSourceKitLSP = LocalConnection(receiverName: "BuildSystemManager")
77-
await self.connectionToSourceKitLSP.start(handler: externalBuildSystemAdapter.messagesToSourceKitLSPHandler)
76+
self.connectionToSourceKitLSP = LocalConnection(
77+
receiverName: "BuildSystemManager",
78+
handler: await externalBuildSystemAdapter.messagesToSourceKitLSPHandler
79+
)
7880
await externalBuildSystemAdapter.changeMessageToSourceKitLSPHandler(to: self)
7981
self.buildServer = await externalBuildSystemAdapter.connectionToBuildServer
8082
}

Sources/LanguageServerProtocolExtensions/LocalConnection.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,11 @@ package final class LocalConnection: Connection, Sendable {
5656
self.name = receiverName
5757
}
5858

59+
package convenience init(receiverName: String, handler: MessageHandler) {
60+
self.init(receiverName: receiverName)
61+
self.start(handler: handler)
62+
}
63+
5964
deinit {
6065
queue.sync {
6166
if state != .closed {

Sources/BuildSystemIntegration/TestBuildSystem.swift renamed to Sources/SKTestSupport/CustomBuildServerTestProject.swift

Lines changed: 97 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -2,53 +2,61 @@
22
//
33
// This source file is part of the Swift.org open source project
44
//
5-
// Copyright (c) 2014 - 2020 Apple Inc. and the Swift project authors
5+
// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors
66
// Licensed under Apache License v2.0 with Runtime Library Exception
77
//
88
// See https://swift.org/LICENSE.txt for license information
99
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
1010
//
1111
//===----------------------------------------------------------------------===//
1212

13-
import Foundation
13+
import BuildSystemIntegration
1414
import LanguageServerProtocolExtensions
1515
import SKLogging
16-
import SKOptions
16+
import SourceKitLSP
17+
import SwiftExtensions
1718
import ToolchainRegistry
19+
import XCTest
1820

1921
#if compiler(>=6)
2022
package import BuildServerProtocol
23+
package import Foundation
2124
package import LanguageServerProtocol
25+
package import SKOptions
2226
#else
2327
import BuildServerProtocol
28+
import Foundation
2429
import LanguageServerProtocol
30+
import SKOptions
2531
#endif
2632

27-
/// Build system to be used for testing BuildSystem and BuildSystemDelegate functionality with SourceKitLSPServer
28-
/// and other components.
29-
package actor TestBuildSystem: MessageHandler {
30-
private let connectionToSourceKitLSP: any Connection
31-
32-
/// Build settings by file.
33-
private var buildSettingsByFile: [DocumentURI: TextDocumentSourceKitOptionsResponse] = [:]
34-
35-
package func setBuildSettings(for uri: DocumentURI, to buildSettings: TextDocumentSourceKitOptionsResponse?) {
36-
buildSettingsByFile[uri] = buildSettings
37-
connectionToSourceKitLSP.send(OnBuildTargetDidChangeNotification(changes: nil))
38-
}
33+
// MARK: - CustomBuildServer
3934

40-
private let initializeData: SourceKitInitializeBuildResponseData
35+
/// A build server that can be injected into `CustomBuildServerTestProject`.
36+
package protocol CustomBuildServer: MessageHandler {
37+
init(projectRoot: URL, connectionToSourceKitLSP: any Connection)
4138

42-
package init(
43-
initializeData: SourceKitInitializeBuildResponseData = SourceKitInitializeBuildResponseData(
44-
sourceKitOptionsProvider: true
45-
),
46-
connectionToSourceKitLSP: any Connection
47-
) {
48-
self.initializeData = initializeData
49-
self.connectionToSourceKitLSP = connectionToSourceKitLSP
50-
}
39+
func initializeBuildRequest(_ request: InitializeBuildRequest) async throws -> InitializeBuildResponse
40+
func onBuildInitialized(_ notification: OnBuildInitializedNotification) throws
41+
func buildShutdown(_ request: BuildShutdownRequest) async throws -> VoidResponse
42+
func onBuildExit(_ notification: OnBuildExitNotification) throws
43+
func workspaceBuildTargetsRequest(
44+
_ request: WorkspaceBuildTargetsRequest
45+
) async throws -> WorkspaceBuildTargetsResponse
46+
func buildTargetSourcesRequest(_ request: BuildTargetSourcesRequest) async throws -> BuildTargetSourcesResponse
47+
func textDocumentSourceKitOptionsRequest(
48+
_ request: TextDocumentSourceKitOptionsRequest
49+
) async throws -> TextDocumentSourceKitOptionsResponse?
50+
func prepareTarget(_ request: BuildTargetPrepareRequest) async throws -> VoidResponse
51+
func waitForBuildSystemUpdates(request: WorkspaceWaitForBuildSystemUpdatesRequest) async -> VoidResponse
52+
nonisolated func onWatchedFilesDidChange(_ notification: OnWatchedFilesDidChangeNotification) throws
53+
func workspaceWaitForBuildSystemUpdatesRequest(
54+
_ request: WorkspaceWaitForBuildSystemUpdatesRequest
55+
) async throws -> VoidResponse
56+
nonisolated func cancelRequest(_ notification: CancelRequestNotification) throws
57+
}
5158

59+
extension CustomBuildServer {
5260
package nonisolated func handle(_ notification: some NotificationType) {
5361
do {
5462
switch notification {
@@ -64,7 +72,7 @@ package actor TestBuildSystem: MessageHandler {
6472
throw ResponseError.methodNotFound(type(of: notification).method)
6573
}
6674
} catch {
67-
logger.error("Error while handling BSP notification")
75+
logger.error("Error while handling BSP notification: \(error.forLogging)")
6876
}
6977
}
7078

@@ -102,10 +110,18 @@ package actor TestBuildSystem: MessageHandler {
102110
reply(.failure(ResponseError.methodNotFound(type(of: request).method)))
103111
}
104112
}
113+
}
105114

106-
func initializeBuildRequest(_ request: InitializeBuildRequest) async throws -> InitializeBuildResponse {
107-
return InitializeBuildResponse(
108-
displayName: "TestBuildSystem",
115+
package extension CustomBuildServer {
116+
// MARK: Helper functions for the implementation of BSP methods
117+
118+
func initializationResponse(
119+
initializeData: SourceKitInitializeBuildResponseData = SourceKitInitializeBuildResponseData(
120+
sourceKitOptionsProvider: true
121+
)
122+
) -> InitializeBuildResponse {
123+
InitializeBuildResponse(
124+
displayName: "\(type(of: self))",
109125
version: "",
110126
bspVersion: "2.2.0",
111127
capabilities: BuildServerCapabilities(),
@@ -114,17 +130,25 @@ package actor TestBuildSystem: MessageHandler {
114130
)
115131
}
116132

117-
nonisolated func onBuildInitialized(_ notification: OnBuildInitializedNotification) throws {
118-
// Nothing to do
133+
func dummyTargetSourcesResponse(_ files: some Sequence<DocumentURI>) -> BuildTargetSourcesResponse {
134+
return BuildTargetSourcesResponse(items: [
135+
SourcesItem(target: .dummy, sources: files.map { SourceItem(uri: $0, kind: .file, generated: false) })
136+
])
119137
}
120138

139+
// MARK: Default implementation for all build server methods that usually don't need customization.
140+
141+
func initializeBuildRequest(_ request: InitializeBuildRequest) async throws -> InitializeBuildResponse {
142+
return initializationResponse()
143+
}
144+
145+
nonisolated func onBuildInitialized(_ notification: OnBuildInitializedNotification) throws {}
146+
121147
func buildShutdown(_ request: BuildShutdownRequest) async throws -> VoidResponse {
122148
return VoidResponse()
123149
}
124150

125-
nonisolated func onBuildExit(_ notification: OnBuildExitNotification) throws {
126-
// Nothing to do
127-
}
151+
nonisolated func onBuildExit(_ notification: OnBuildExitNotification) throws {}
128152

129153
func workspaceBuildTargetsRequest(
130154
_ request: WorkspaceBuildTargetsRequest
@@ -142,32 +166,15 @@ package actor TestBuildSystem: MessageHandler {
142166
])
143167
}
144168

145-
func buildTargetSourcesRequest(_ request: BuildTargetSourcesRequest) async throws -> BuildTargetSourcesResponse {
146-
return BuildTargetSourcesResponse(items: [
147-
SourcesItem(
148-
target: .dummy,
149-
sources: buildSettingsByFile.keys.map { SourceItem(uri: $0, kind: .file, generated: false) }
150-
)
151-
])
152-
}
153-
154-
func textDocumentSourceKitOptionsRequest(
155-
_ request: TextDocumentSourceKitOptionsRequest
156-
) async throws -> TextDocumentSourceKitOptionsResponse? {
157-
return buildSettingsByFile[request.textDocument.uri]
158-
}
159-
160169
func prepareTarget(_ request: BuildTargetPrepareRequest) async throws -> VoidResponse {
161170
return VoidResponse()
162171
}
163172

164-
package func waitForBuildSystemUpdates(request: WorkspaceWaitForBuildSystemUpdatesRequest) async -> VoidResponse {
173+
func waitForBuildSystemUpdates(request: WorkspaceWaitForBuildSystemUpdatesRequest) async -> VoidResponse {
165174
return VoidResponse()
166175
}
167176

168-
nonisolated func onWatchedFilesDidChange(_ notification: OnWatchedFilesDidChangeNotification) throws {
169-
// Not watching any files
170-
}
177+
nonisolated func onWatchedFilesDidChange(_ notification: OnWatchedFilesDidChangeNotification) throws {}
171178

172179
func workspaceWaitForBuildSystemUpdatesRequest(
173180
_ request: WorkspaceWaitForBuildSystemUpdatesRequest
@@ -177,3 +184,40 @@ package actor TestBuildSystem: MessageHandler {
177184

178185
nonisolated func cancelRequest(_ notification: CancelRequestNotification) throws {}
179186
}
187+
188+
// MARK: - CustomBuildServerTestProject
189+
190+
/// A test project that launches a custom build server in-process.
191+
///
192+
/// In contrast to `ExternalBuildServerTestProject`, the custom build system runs in-process and is implemented in
193+
/// Swift.
194+
package final class CustomBuildServerTestProject<BuildServer: CustomBuildServer>: MultiFileTestProject {
195+
private let buildServerBox = ThreadSafeBox<BuildServer?>(initialValue: nil)
196+
197+
package init(
198+
files: [RelativeFileLocation: String],
199+
buildServer buildServerType: BuildServer.Type,
200+
options: SourceKitLSPOptions? = nil,
201+
enableBackgroundIndexing: Bool = false,
202+
testName: String = #function
203+
) async throws {
204+
let hooks: Hooks = Hooks(
205+
buildSystemHooks: BuildSystemHooks(injectBuildServer: { [buildServerBox] projectRoot, connectionToSourceKitLSP in
206+
let buildServer = BuildServer(projectRoot: projectRoot, connectionToSourceKitLSP: connectionToSourceKitLSP)
207+
buildServerBox.value = buildServer
208+
return LocalConnection(receiverName: "TestBuildSystem", handler: buildServer)
209+
})
210+
)
211+
try await super.init(
212+
files: files,
213+
options: options,
214+
hooks: hooks,
215+
enableBackgroundIndexing: enableBackgroundIndexing,
216+
testName: testName
217+
)
218+
}
219+
220+
package func buildServer(file: StaticString = #filePath, line: UInt = #line) throws -> BuildServer {
221+
try XCTUnwrap(buildServerBox.value, "Accessing build server before it has been created", file: file, line: line)
222+
}
223+
}

Sources/SKTestSupport/BuildServerTestProject.swift renamed to Sources/SKTestSupport/ExternalBuildServerTestProject.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ private let skTestSupportInputsDirectory: URL = {
5959
///
6060
/// The build server can contain `$SDK_ARGS`, which will replaced by `"-sdk", "/path/to/sdk"` on macOS and by an empty
6161
/// string on all other platforms.
62-
package class BuildServerTestProject: MultiFileTestProject {
62+
package class ExternalBuildServerTestProject: MultiFileTestProject {
6363
package init(
6464
files: [RelativeFileLocation: String],
6565
buildServerConfigLocation: RelativeFileLocation = ".bsp/sourcekit-lsp.json",

0 commit comments

Comments
 (0)