Skip to content

Commit 281c3e0

Browse files
committed
Prepend module name to TestItem IDs
It is possible to have two identically named suites in two different test targets. These were being erroniously rolled up in to the same parent TestItem. Disambiguate these TestItems by prepending the module name. This has the added benefit of making the TestItem IDs a fully qualified name that can be passed to `swift test`. The module name is pulled from the compiler arguments for the target. If no module name can be found we fall back to the `targetID` for the `ConfiguredTarget`.
1 parent b7b0900 commit 281c3e0

File tree

3 files changed

+207
-204
lines changed

3 files changed

+207
-204
lines changed

Sources/SKCore/BuildSystemManager.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,18 @@ extension BuildSystemManager {
154154
.first
155155
}
156156

157+
/// Returns the target's module name as parsed from the `ConfiguredTarget`'s compiler arguments.
158+
public func moduleName(for document: DocumentURI, in target: ConfiguredTarget) async -> String? {
159+
guard let buildSettings = await buildSettings(for: document, in: target, language: .swift),
160+
let moduleNameFlagIndex = buildSettings.compilerArguments.firstIndex(of: "-module-name")
161+
else {
162+
return nil
163+
}
164+
165+
let moduleNameIndex = buildSettings.compilerArguments.index(after: moduleNameFlagIndex)
166+
return buildSettings.compilerArguments[moduleNameIndex]
167+
}
168+
157169
/// Returns the build settings for `document` from `buildSystem`.
158170
///
159171
/// Implementation detail of `buildSettings(for:language:)`.

Sources/SourceKitLSP/TestDiscovery.swift

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -260,7 +260,7 @@ extension SourceKitLSPServer {
260260

261261
func workspaceTests(_ req: WorkspaceTestsRequest) async throws -> [TestItem] {
262262
return await self.workspaces
263-
.concurrentMap { await self.tests(in: $0) }
263+
.concurrentMap { await self.tests(in: $0).prefixTestsWithModuleName(workspace: $0) }
264264
.flatMap { $0 }
265265
.sorted { $0.testItem.location < $1.testItem.location }
266266
.mergingTestsInExtensions()
@@ -272,6 +272,7 @@ extension SourceKitLSPServer {
272272
languageService: LanguageService
273273
) async throws -> [TestItem] {
274274
return try await documentTestsWithoutMergingExtensions(req, workspace: workspace, languageService: languageService)
275+
.prefixTestsWithModuleName(workspace: workspace)
275276
.mergingTestsInExtensions()
276277
}
277278

@@ -476,6 +477,42 @@ fileprivate extension Array<AnnotatedTestItem> {
476477
}
477478
return result
478479
}
480+
481+
func prefixTestsWithModuleName(workspace: Workspace) async -> Self {
482+
return await self.asyncMap({
483+
// If the module name can't be determined we return the test item without a prefixed id.
484+
guard let moduleName = await self.moduleName(from: workspace, for: $0.testItem.location.uri) else {
485+
return $0
486+
}
487+
var newTest = $0.testItem
488+
newTest.id = "\(moduleName).\(newTest.id)"
489+
newTest.children = await prefixTestsWithModuleName(workspace: workspace, newTest.children)
490+
return AnnotatedTestItem(testItem: newTest, isExtension: $0.isExtension)
491+
})
492+
}
493+
494+
private func prefixTestsWithModuleName(workspace: Workspace, _ tests: [TestItem]) async -> [TestItem] {
495+
return await tests.asyncMap({
496+
guard let moduleName = await self.moduleName(from: workspace, for: $0.location.uri) else {
497+
return $0
498+
}
499+
500+
var newTest = $0
501+
newTest.id = "\(moduleName).\(newTest.id)"
502+
newTest.children = await prefixTestsWithModuleName(workspace: workspace, newTest.children)
503+
return newTest
504+
})
505+
}
506+
507+
private func moduleName(from workspace: Workspace, for uri: DocumentURI) async -> String? {
508+
guard let configuredTarget = await workspace.buildSystemManager.canonicalConfiguredTarget(for: uri) else {
509+
return nil
510+
}
511+
// If for whatever reason we can't get a module name from the build system, fall back
512+
// to using the targetID as this would be used when there is no command line arguments
513+
// to define the module name as something other than the targetID.
514+
return await workspace.buildSystemManager.moduleName(for: uri, in: configuredTarget) ?? configuredTarget.targetID
515+
}
479516
}
480517

481518
extension SwiftLanguageService {

0 commit comments

Comments
 (0)