Skip to content

Commit a4384fd

Browse files
committed
Merge tests defined in extensions
Merge the XCTests and swift-testing tests defined in extensions into their parent TestItems. This is done as another pass after the TestScanner visitors have walked the tree. Fixes #1218
1 parent e71aa5d commit a4384fd

File tree

3 files changed

+135
-7
lines changed

3 files changed

+135
-7
lines changed

Sources/SourceKitLSP/Swift/SwiftTestingScanner.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ final class SyntacticSwiftTestingTestScanner: SyntaxVisitor {
201201
let syntaxTree = await syntaxTreeManager.syntaxTree(for: snapshot)
202202
let visitor = SyntacticSwiftTestingTestScanner(snapshot: snapshot, allTestsDisabled: false, parentTypeNames: [])
203203
visitor.walk(syntaxTree)
204-
return visitor.result
204+
return visitor.result.mergeTestsInExtensions()
205205
}
206206

207207
/// Visit a class/struct/... or extension declaration.

Sources/SourceKitLSP/TestDiscovery.swift

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -331,7 +331,7 @@ final class SyntacticSwiftXCTestScanner: SyntaxVisitor {
331331
let syntaxTree = await syntaxTreeManager.syntaxTree(for: snapshot)
332332
let visitor = SyntacticSwiftXCTestScanner(snapshot: snapshot)
333333
visitor.walk(syntaxTree)
334-
return visitor.result
334+
return visitor.result.mergeTestsInExtensions()
335335
}
336336

337337
private func findTestMethods(in members: MemberBlockItemListSyntax, containerName: String) -> [TestItem] {
@@ -436,6 +436,45 @@ extension TestItem {
436436
}
437437
}
438438

439+
extension Collection where Element == TestItem {
440+
/// Walks the TestItem tree of each item in the collection and merges orphaned leaf nodes
441+
/// into their parent, if a parent exists.
442+
///
443+
/// A node's parent is identified by the node's ID with the last component dropped.
444+
func mergeTestsInExtensions() -> [TestItem] {
445+
var itemDict: [String: TestItem] = [:]
446+
for item in self {
447+
if var existingItem = itemDict[item.id] {
448+
existingItem.children = (existingItem.children + item.children)
449+
itemDict[item.id] = existingItem
450+
} else {
451+
itemDict[item.id] = item
452+
}
453+
}
454+
455+
for item in self {
456+
let parentID = item.id.components(separatedBy: "/").dropLast().joined(separator: "/")
457+
// If the parent exists, add the current item to its children and remove it from the root
458+
if var parent = itemDict[parentID] {
459+
parent.children.append(item)
460+
itemDict[parent.id] = parent
461+
itemDict[item.id] = nil
462+
}
463+
}
464+
465+
// Filter out the items that have been merged into their parents, sorting the tests by location
466+
var reorganizedItems = itemDict.values.compactMap { $0 }.sorted { $0.location < $1.location }
467+
468+
reorganizedItems = reorganizedItems.map({
469+
var newItem = $0
470+
newItem.children = $0.children.mergeTestsInExtensions()
471+
return newItem
472+
})
473+
474+
return reorganizedItems
475+
}
476+
}
477+
439478
extension SwiftLanguageService {
440479
public func syntacticDocumentTests(for uri: DocumentURI, in workspace: Workspace) async throws -> [TestItem] {
441480
let snapshot = try documentManager.latestSnapshot(uri)

Tests/SourceKitLSPTests/DocumentTestDiscoveryTests.swift

Lines changed: 94 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -827,29 +827,118 @@ final class DocumentTestDiscoveryTests: XCTestCase {
827827
location: Location(uri: uri, range: positions["2️⃣"]..<positions["3️⃣"]),
828828
children: [],
829829
tags: []
830-
)
830+
),
831+
TestItem(
832+
id: "MyTests/twoIsThree()",
833+
label: "twoIsThree()",
834+
disabled: false,
835+
style: TestStyle.swiftTesting,
836+
location: Location(uri: uri, range: positions["6️⃣"]..<positions["7️⃣"]),
837+
children: [],
838+
tags: []
839+
),
831840
],
832841
tags: []
833-
),
842+
)
843+
]
844+
)
845+
}
846+
847+
func testSwiftTestingTestSuitesWithExtension() async throws {
848+
let testClient = try await TestSourceKitLSPClient()
849+
let uri = DocumentURI.for(.swift)
850+
851+
let positions = testClient.openDocument(
852+
"""
853+
import XCTest
854+
855+
1️⃣@Suite struct MyTests {
856+
2️⃣@Test func oneIsTwo() {}3️⃣
857+
}4️⃣
858+
859+
extension MyTests {
860+
5️⃣@Test func twoIsThree() {}6️⃣
861+
}
862+
""",
863+
uri: uri
864+
)
865+
866+
let tests = try await testClient.send(DocumentTestsRequest(textDocument: TextDocumentIdentifier(uri)))
867+
XCTAssertEqual(
868+
tests,
869+
[
834870
TestItem(
835871
id: "MyTests",
836872
label: "MyTests",
837873
disabled: false,
838874
style: TestStyle.swiftTesting,
839-
location: Location(uri: uri, range: positions["5️⃣"]..<positions["8️⃣"]),
875+
location: Location(uri: uri, range: positions["1️⃣"]..<positions["4️⃣"]),
840876
children: [
877+
TestItem(
878+
id: "MyTests/oneIsTwo()",
879+
label: "oneIsTwo()",
880+
disabled: false,
881+
style: TestStyle.swiftTesting,
882+
location: Location(uri: uri, range: positions["2️⃣"]..<positions["3️⃣"]),
883+
children: [],
884+
tags: []
885+
),
841886
TestItem(
842887
id: "MyTests/twoIsThree()",
843888
label: "twoIsThree()",
844889
disabled: false,
845890
style: TestStyle.swiftTesting,
846-
location: Location(uri: uri, range: positions["6️⃣"]..<positions["7️⃣"]),
891+
location: Location(uri: uri, range: positions["5️⃣"]..<positions["6️⃣"]),
892+
children: [],
893+
tags: []
894+
),
895+
],
896+
tags: []
897+
)
898+
]
899+
)
900+
}
901+
902+
func testXCTestTestsWithExtension() async throws {
903+
let testClient = try await TestSourceKitLSPClient()
904+
let uri = DocumentURI.for(.swift)
905+
906+
let positions = testClient.openDocument(
907+
"""
908+
import XCTest
909+
910+
1️⃣final class MyTests: XCTestCase {}4️⃣
911+
912+
extension MyTests {
913+
2️⃣func testOneIsTwo() {}3️⃣
914+
}
915+
""",
916+
uri: uri
917+
)
918+
919+
let tests = try await testClient.send(DocumentTestsRequest(textDocument: TextDocumentIdentifier(uri)))
920+
XCTAssertEqual(
921+
tests,
922+
[
923+
TestItem(
924+
id: "MyTests",
925+
label: "MyTests",
926+
disabled: false,
927+
style: TestStyle.xcTest,
928+
location: Location(uri: uri, range: positions["1️⃣"]..<positions["4️⃣"]),
929+
children: [
930+
TestItem(
931+
id: "MyTests/testOneIsTwo()",
932+
label: "testOneIsTwo()",
933+
disabled: false,
934+
style: TestStyle.xcTest,
935+
location: Location(uri: uri, range: positions["2️⃣"]..<positions["3️⃣"]),
847936
children: [],
848937
tags: []
849938
)
850939
],
851940
tags: []
852-
),
941+
)
853942
]
854943
)
855944
}

0 commit comments

Comments
 (0)