Skip to content

Commit a799da3

Browse files
committed
Implement a syntactic workspace-wide test index
This workspace-wide syntactic test index is used for two purposes: - It is used for XCTests instead of the semantic index for files that have on-disk or in-memory modifications to files - It is uses for swift-testing tests, which are only discovered syntactically. rdar://119191037
1 parent 17c0a44 commit a799da3

25 files changed

+1320
-124
lines changed

Sources/SKCore/BuildServerBuildSystem.swift

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,17 @@ extension BuildServerBuildSystem: BuildSystem {
316316

317317
return .unhandled
318318
}
319+
320+
public func testFiles() async -> [DocumentURI] {
321+
// BuildServerBuildSystem does not support syntactic test discovery
322+
// (https://github.com/apple/sourcekit-lsp/issues/1173).
323+
return []
324+
}
325+
326+
public func addTestFilesDidChangeCallback(_ callback: @escaping () async -> Void) {
327+
// BuildServerBuildSystem does not support syntactic test discovery
328+
// (https://github.com/apple/sourcekit-lsp/issues/1173).
329+
}
319330
}
320331

321332
private func loadBuildServerConfig(path: AbsolutePath, fileSystem: FileSystem) throws -> BuildServerConfig {

Sources/SKCore/BuildSystem.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,18 @@ public protocol BuildSystem: AnyObject, Sendable {
8787
func filesDidChange(_ events: [FileEvent]) async
8888

8989
func fileHandlingCapability(for uri: DocumentURI) async -> FileHandlingCapability
90+
91+
/// Returns the list of files that might contain test cases.
92+
///
93+
/// The returned file list is an over-approximation. It might contain tests from non-test targets or files that don't
94+
/// actually contain any tests. Keeping this list as minimal as possible helps reduce the amount of work that the
95+
/// syntactic test indexer needs to perform.
96+
func testFiles() async -> [DocumentURI]
97+
98+
/// Adds a callback that should be called when the value returned by `testFiles()` changes.
99+
///
100+
/// The callback might also be called without an actual change to `testFiles`.
101+
func addTestFilesDidChangeCallback(_ callback: @Sendable @escaping () async -> Void) async
90102
}
91103

92104
public let buildTargetsNotSupported =

Sources/SKCore/BuildSystemManager.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,10 @@ extension BuildSystemManager {
176176
fallbackBuildSystem != nil ? .fallback : .unhandled
177177
)
178178
}
179+
180+
public func testFiles() async -> [DocumentURI] {
181+
return await buildSystem?.testFiles() ?? []
182+
}
179183
}
180184

181185
extension BuildSystemManager: BuildSystemDelegate {

Sources/SKCore/CompilationDatabaseBuildSystem.swift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ public actor CompilationDatabaseBuildSystem {
3939
/// Delegate to handle any build system events.
4040
public weak var delegate: BuildSystemDelegate? = nil
4141

42+
/// Callbacks that should be called if the list of possible test files has changed.
43+
public var testFilesDidChangeCallbacks: [() async -> Void] = []
44+
4245
public func setDelegate(_ delegate: BuildSystemDelegate?) async {
4346
self.delegate = delegate
4447
}
@@ -167,6 +170,9 @@ extension CompilationDatabaseBuildSystem: BuildSystem {
167170
if let delegate = self.delegate {
168171
await delegate.fileBuildSettingsChanged(self.watchedFiles)
169172
}
173+
for testFilesDidChangeCallback in testFilesDidChangeCallbacks {
174+
await testFilesDidChangeCallback()
175+
}
170176
}
171177

172178
public func filesDidChange(_ events: [FileEvent]) async {
@@ -185,4 +191,12 @@ extension CompilationDatabaseBuildSystem: BuildSystem {
185191
return .unhandled
186192
}
187193
}
194+
195+
public func testFiles() async -> [DocumentURI] {
196+
return compdb?.allCommands.map { DocumentURI($0.url) } ?? []
197+
}
198+
199+
public func addTestFilesDidChangeCallback(_ callback: @escaping () async -> Void) async {
200+
testFilesDidChangeCallbacks.append(callback)
201+
}
188202
}

Sources/SKSupport/AsyncQueue.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import Foundation
1616
/// array.
1717
private protocol AnyTask: Sendable {
1818
func waitForCompletion() async
19+
20+
func cancel()
1921
}
2022

2123
extension Task: AnyTask {
@@ -89,6 +91,16 @@ public final class AsyncQueue<TaskMetadata: DependencyTracker>: Sendable {
8991

9092
public init() {}
9193

94+
public func cancelTasks(where filter: (TaskMetadata) -> Bool) {
95+
pendingTasks.withLock { pendingTasks in
96+
for task in pendingTasks {
97+
if filter(task.metadata) {
98+
task.task.cancel()
99+
}
100+
}
101+
}
102+
}
103+
92104
/// Schedule a new closure to be executed on the queue.
93105
///
94106
/// If this is a serial queue, all previously added tasks are guaranteed to

Sources/SKSwiftPMWorkspace/SwiftPMBuildSystem.swift

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,9 @@ public actor SwiftPMBuildSystem {
8181
self.delegate = delegate
8282
}
8383

84+
/// Callbacks that should be called if the list of possible test files has changed.
85+
public var testFilesDidChangeCallbacks: [() async -> Void] = []
86+
8487
let workspacePath: TSCAbsolutePath
8588
/// The directory containing `Package.swift`.
8689
public var projectRoot: TSCAbsolutePath
@@ -267,6 +270,9 @@ extension SwiftPMBuildSystem {
267270
}
268271
await delegate.fileBuildSettingsChanged(self.watchedFiles)
269272
await delegate.fileHandlingCapabilityChanged()
273+
for testFilesDidChangeCallback in testFilesDidChangeCallbacks {
274+
await testFilesDidChangeCallback()
275+
}
270276
}
271277
}
272278

@@ -380,6 +386,15 @@ extension SwiftPMBuildSystem: SKCore.BuildSystem {
380386
return .unhandled
381387
}
382388
}
389+
390+
public func testFiles() -> [DocumentURI] {
391+
// We should only include source files from test targets (https://github.com/apple/sourcekit-lsp/issues/1174).
392+
return fileToTarget.map { DocumentURI($0.key.asURL) }
393+
}
394+
395+
public func addTestFilesDidChangeCallback(_ callback: @Sendable @escaping () async -> Void) async {
396+
testFilesDidChangeCallbacks.append(callback)
397+
}
383398
}
384399

385400
extension SwiftPMBuildSystem {

Sources/SKTestSupport/IndexedSingleSwiftFileTestProject.swift

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,25 @@ public struct IndexedSingleSwiftFileTestProject {
6868

6969
if let sdk = TibsBuilder.defaultSDKPath {
7070
compilerArguments += ["-sdk", sdk]
71+
72+
// The following are needed so we can import XCTest
73+
let sdkUrl = URL(fileURLWithPath: sdk)
74+
let usrLibDir =
75+
sdkUrl
76+
.deletingLastPathComponent()
77+
.deletingLastPathComponent()
78+
.appendingPathComponent("usr")
79+
.appendingPathComponent("lib")
80+
let frameworksDir =
81+
sdkUrl
82+
.deletingLastPathComponent()
83+
.deletingLastPathComponent()
84+
.appendingPathComponent("Library")
85+
.appendingPathComponent("Frameworks")
86+
compilerArguments += [
87+
"-I", usrLibDir.path,
88+
"-F", frameworksDir.path,
89+
]
7190
}
7291

7392
let compilationDatabase = JSONCompilationDatabase(

Sources/SKTestSupport/MultiFileTestProject.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,4 +142,13 @@ public class MultiFileTestProject {
142142
}
143143
return DocumentPositions(markedText: fileData.markedText)[marker]
144144
}
145+
146+
public func range(from fromMarker: String, to toMarker: String, in fileName: String) throws -> Range<Position> {
147+
return try position(of: fromMarker, in: fileName)..<position(of: toMarker, in: fileName)
148+
}
149+
150+
public func location(from fromMarker: String, to toMarker: String, in fileName: String) throws -> Location {
151+
let range = try self.range(from: fromMarker, to: toMarker, in: fileName)
152+
return Location(uri: try self.uri(for: fileName), range: range)
153+
}
145154
}

Sources/SKTestSupport/SwiftPMTestProject.swift

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ public class SwiftPMTestProject: MultiFileTestProject {
4040
manifest: String = SwiftPMTestProject.defaultPackageManifest,
4141
workspaces: (URL) -> [WorkspaceFolder] = { [WorkspaceFolder(uri: DocumentURI($0))] },
4242
build: Bool = false,
43+
allowBuildFailure: Bool = false,
4344
usePullDiagnostics: Bool = true,
4445
testName: String = #function
4546
) async throws {
@@ -66,7 +67,11 @@ public class SwiftPMTestProject: MultiFileTestProject {
6667
)
6768

6869
if build {
69-
try await Self.build(at: self.scratchDirectory)
70+
if allowBuildFailure {
71+
try? await Self.build(at: self.scratchDirectory)
72+
} else {
73+
try await Self.build(at: self.scratchDirectory)
74+
}
7075
}
7176
// Wait for the indexstore-db to finish indexing
7277
_ = try await testClient.send(PollIndexRequest())

Sources/SKTestSupport/TestSourceKitLSPClient.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -362,7 +362,7 @@ public struct DocumentPositions {
362362
}
363363
}
364364

365-
init(markedText: String) {
365+
public init(markedText: String) {
366366
let (markers, textWithoutMarker) = extractMarkers(markedText)
367367
self.init(markers: markers, textWithoutMarkers: textWithoutMarker)
368368
}

0 commit comments

Comments
 (0)