Skip to content

Commit f88c241

Browse files
committed
Refactor multi-file rename tests to specify expected renamed source instead of asserting on edits
1 parent bda1387 commit f88c241

File tree

2 files changed

+164
-118
lines changed

2 files changed

+164
-118
lines changed

Sources/SKTestSupport/MultiFileTestWorkspace.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,10 @@ import SKCore
1717
/// The location of a test file within test workspace.
1818
public struct RelativeFileLocation: Hashable, ExpressibleByStringLiteral {
1919
/// The subdirectories in which the file is located.
20-
let directories: [String]
20+
public let directories: [String]
2121

2222
/// The file's name.
23-
let fileName: String
23+
public let fileName: String
2424

2525
public init(directories: [String] = [], _ fileName: String) {
2626
self.directories = directories

Tests/SourceKitLSPTests/RenameTests.swift

Lines changed: 162 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,12 @@ private func assertSingleFileRename(
3838
_ markedSource: String,
3939
newName: String,
4040
expected: String,
41+
testName: String = #function,
4142
file: StaticString = #file,
4243
line: UInt = #line
4344
) async throws {
4445
let testClient = try await TestSourceKitLSPClient()
45-
let uri = DocumentURI.for(.swift)
46+
let uri = DocumentURI.for(.swift, testName: testName)
4647
let positions = testClient.openDocument(markedSource, uri: uri)
4748
for marker in positions.allMarkers {
4849
let response = try await testClient.send(
@@ -59,6 +60,90 @@ private func assertSingleFileRename(
5960
}
6061
}
6162

63+
/// Assert that applying changes to `originalFiles` results in `expected`.
64+
///
65+
/// Upon failure, `message` is added to the XCTest failure messages to provide context which rename failed.
66+
private func assertRenamedSourceMatches(
67+
originalFiles: [RelativeFileLocation: String],
68+
changes: [DocumentURI: [TextEdit]],
69+
expected: [RelativeFileLocation: String],
70+
in ws: MultiFileTestWorkspace,
71+
message: String,
72+
testName: String = #function,
73+
file: StaticString = #file,
74+
line: UInt = #line
75+
) throws {
76+
for (expectedFileLocation, expectedRenamed) in expected {
77+
let originalMarkedSource = try XCTUnwrap(
78+
originalFiles[expectedFileLocation],
79+
"No original source for \(expectedFileLocation.fileName) specified; \(message)",
80+
file: file,
81+
line: line
82+
)
83+
let originalSource = extractMarkers(originalMarkedSource).textWithoutMarkers
84+
let edits = changes[try ws.uri(for: expectedFileLocation.fileName)] ?? []
85+
let renamed = apply(edits: edits, to: originalSource)
86+
XCTAssertEqual(
87+
renamed,
88+
expectedRenamed,
89+
"applying edits did not match expected renamed source for \(expectedFileLocation.fileName); \(message)",
90+
file: file,
91+
line: line
92+
)
93+
}
94+
}
95+
96+
/// Perform a rename request at every location marker except 0️⃣ in `files`, renaming it to `newName`. The location
97+
/// marker 0️⃣ is intended to be used as an anchor for `preRenameActions`.
98+
///
99+
/// Test that applying the edits returned from the requests always result in `expected`.
100+
///
101+
/// `preRenameActions` is executed after opening the workspace but before performing the rename. This allows a workspace
102+
/// to be placed in a state where there are in-memory changes that haven't been written to disk yet.
103+
private func assertMultiFileRename(
104+
files: [RelativeFileLocation: String],
105+
newName: String,
106+
expected: [RelativeFileLocation: String],
107+
manifest: String = SwiftPMTestWorkspace.defaultPackageManifest,
108+
preRenameActions: (SwiftPMTestWorkspace) throws -> Void = { _ in },
109+
testName: String = #function,
110+
file: StaticString = #file,
111+
line: UInt = #line
112+
) async throws {
113+
let ws = try await SwiftPMTestWorkspace(
114+
files: files,
115+
manifest: manifest,
116+
build: true,
117+
testName: testName
118+
)
119+
try preRenameActions(ws)
120+
for (fileLocation, markedSource) in files.sorted(by: { $0.key.fileName < $1.key.fileName }) {
121+
let markers = extractMarkers(markedSource).markers.keys.sorted().filter { $0 != "0️⃣" }
122+
if markers.isEmpty {
123+
continue
124+
}
125+
let (uri, positions) = try ws.openDocument(fileLocation.fileName)
126+
defer {
127+
ws.testClient.send(DidCloseTextDocumentNotification(textDocument: TextDocumentIdentifier(uri)))
128+
}
129+
for marker in markers {
130+
let response = try await ws.testClient.send(
131+
RenameRequest(textDocument: TextDocumentIdentifier(uri), position: positions[marker], newName: newName)
132+
)
133+
let changes = try XCTUnwrap(response?.changes)
134+
try assertRenamedSourceMatches(
135+
originalFiles: files,
136+
changes: changes,
137+
expected: expected,
138+
in: ws,
139+
message: "while performing rename at \(marker)",
140+
file: file,
141+
line: line
142+
)
143+
}
144+
}
145+
}
146+
62147
final class RenameTests: XCTestCase {
63148
func testRenameVariableBaseName() async throws {
64149
try await assertSingleFileRename(
@@ -464,46 +549,53 @@ final class RenameTests: XCTestCase {
464549
}
465550

466551
func testCrossFileSwiftRename() async throws {
467-
let ws = try await SwiftPMTestWorkspace(
552+
try await assertMultiFileRename(
468553
files: [
469554
"a.swift": """
470-
func 1️⃣foo2️⃣() {}
555+
func 1️⃣foo() {}
471556
""",
472557
"b.swift": """
473558
func test() {
474-
3️⃣foo4️⃣()
559+
2️⃣foo()
475560
}
476561
""",
477562
],
478-
build: true
479-
)
480-
481-
let (aUri, aPositions) = try ws.openDocument("a.swift")
482-
let response = try await ws.testClient.send(
483-
RenameRequest(textDocument: TextDocumentIdentifier(aUri), position: aPositions["1️⃣"], newName: "bar")
484-
)
485-
let changes = try XCTUnwrap(response?.changes)
486-
XCTAssertEqual(
487-
changes,
488-
[
489-
aUri: [TextEdit(range: aPositions["1️⃣"]..<aPositions["2️⃣"], newText: "bar")],
490-
try ws.uri(for: "b.swift"): [
491-
TextEdit(range: try ws.position(of: "3️⃣", in: "b.swift")..<ws.position(of: "4️⃣", in: "b.swift"), newText: "bar")
492-
],
563+
newName: "bar",
564+
expected: [
565+
"a.swift": """
566+
func bar() {}
567+
""",
568+
"b.swift": """
569+
func test() {
570+
bar()
571+
}
572+
""",
493573
]
494574
)
495575
}
496576

497577
func testSwiftCrossModuleRename() async throws {
498-
let ws = try await SwiftPMTestWorkspace(
578+
try await assertMultiFileRename(
499579
files: [
500580
"LibA/LibA.swift": """
501-
public func 1️⃣foo2️⃣(3️⃣argLabel4️⃣: Int) {}
581+
public func 1️⃣foo(argLabel: Int) {}
502582
""",
503583
"LibB/LibB.swift": """
504584
import LibA
505585
public func test() {
506-
5️⃣foo6️⃣(7️⃣argLabel8️⃣: 1)
586+
5️⃣foo(argLabel: 1)
587+
}
588+
""",
589+
],
590+
newName: "bar(new:)",
591+
expected: [
592+
"LibA/LibA.swift": """
593+
public func bar(new: Int) {}
594+
""",
595+
"LibB/LibB.swift": """
596+
import LibA
597+
public func test() {
598+
bar(new: 1)
507599
}
508600
""",
509601
],
@@ -519,125 +611,79 @@ final class RenameTests: XCTestCase {
519611
.target(name: "LibB", dependencies: ["LibA"]),
520612
]
521613
)
522-
""",
523-
build: true
524-
)
525-
526-
let expectedChanges = [
527-
try ws.uri(for: "LibA.swift"): [
528-
TextEdit(
529-
range: try ws.position(of: "1️⃣", in: "LibA.swift")..<ws.position(of: "2️⃣", in: "LibA.swift"),
530-
newText: "bar"
531-
),
532-
TextEdit(
533-
range: try ws.position(of: "3️⃣", in: "LibA.swift")..<ws.position(of: "4️⃣", in: "LibA.swift"),
534-
newText: "new"
535-
),
536-
],
537-
try ws.uri(for: "LibB.swift"): [
538-
TextEdit(
539-
range: try ws.position(of: "5️⃣", in: "LibB.swift")..<ws.position(of: "6️⃣", in: "LibB.swift"),
540-
newText: "bar"
541-
),
542-
TextEdit(
543-
range: try ws.position(of: "7️⃣", in: "LibB.swift")..<ws.position(of: "8️⃣", in: "LibB.swift"),
544-
newText: "new"
545-
),
546-
],
547-
]
548-
549-
let (aUri, aPositions) = try ws.openDocument("LibA.swift")
550-
551-
let definitionResponse = try await ws.testClient.send(
552-
RenameRequest(textDocument: TextDocumentIdentifier(aUri), position: aPositions["1️⃣"], newName: "bar(new:)")
553-
)
554-
XCTAssertEqual(try XCTUnwrap(definitionResponse?.changes), expectedChanges)
555-
556-
let (bUri, bPositions) = try ws.openDocument("LibB.swift")
557-
558-
let callResponse = try await ws.testClient.send(
559-
RenameRequest(textDocument: TextDocumentIdentifier(bUri), position: bPositions["5️⃣"], newName: "bar(new:)")
614+
"""
560615
)
561-
XCTAssertEqual(try XCTUnwrap(callResponse?.changes), expectedChanges)
562616
}
563617

564618
func testTryIndexLocationsDontMatchInMemoryLocations() async throws {
565-
let ws = try await SwiftPMTestWorkspace(
619+
try await assertMultiFileRename(
566620
files: [
567621
"a.swift": """
568-
func 1️⃣foo2️⃣() {}
622+
func 1️⃣foo() {}
569623
""",
570624
"b.swift": """
571625
0️⃣func test() {
572626
foo()
573627
}
574628
""",
575629
],
576-
build: true
577-
)
578-
579-
// Modify b.swift so that the locations from the index no longer match the in-memory document.
580-
let (bUri, bPositions) = try ws.openDocument("b.swift")
581-
ws.testClient.send(
582-
DidChangeTextDocumentNotification(
583-
textDocument: VersionedTextDocumentIdentifier(bUri, version: 1),
584-
contentChanges: [TextDocumentContentChangeEvent(range: Range(bPositions["0️⃣"]), text: "\n")]
585-
)
586-
)
587-
588-
// We should notice that the locations from the index don't match the current state of b.swift and not include any
589-
// edits in b.swift
590-
let (aUri, aPositions) = try ws.openDocument("a.swift")
591-
let response = try await ws.testClient.send(
592-
RenameRequest(textDocument: TextDocumentIdentifier(aUri), position: aPositions["1️⃣"], newName: "bar")
593-
)
594-
let changes = try XCTUnwrap(response?.changes)
595-
XCTAssertEqual(
596-
changes,
597-
[aUri: [TextEdit(range: aPositions["1️⃣"]..<aPositions["2️⃣"], newText: "bar")]]
630+
newName: "bar",
631+
expected: [
632+
"a.swift": """
633+
func bar() {}
634+
""",
635+
"b.swift": """
636+
func test() {
637+
foo()
638+
}
639+
""",
640+
],
641+
preRenameActions: { ws in
642+
let (bUri, bPositions) = try ws.openDocument("b.swift")
643+
ws.testClient.send(
644+
DidChangeTextDocumentNotification(
645+
textDocument: VersionedTextDocumentIdentifier(bUri, version: 1),
646+
contentChanges: [TextDocumentContentChangeEvent(range: Range(bPositions["0️⃣"]), text: "\n")]
647+
)
648+
)
649+
}
598650
)
599651
}
600652

601653
func testTryIndexLocationsDontMatchInMemoryLocationsByLineColumnButNotOffset() async throws {
602-
let ws = try await SwiftPMTestWorkspace(
654+
try await assertMultiFileRename(
603655
files: [
604656
"a.swift": """
605-
func 1️⃣foo2️⃣() {}
657+
func 1️⃣foo() {}
606658
""",
607659
"b.swift": """
608660
0️⃣func test() {
609-
3️⃣foo4️⃣()
661+
foo()
610662
}
611663
""",
612664
],
613-
build: true
614-
)
615-
616-
// Modify b.swift so that the locations from the index no longer match the in-memory document based on offsets but
617-
// without introducing new lines so that line/column references are still correct
618-
let (bUri, bPositions) = try ws.openDocument("b.swift")
619-
ws.testClient.send(
620-
DidChangeTextDocumentNotification(
621-
textDocument: VersionedTextDocumentIdentifier(bUri, version: 1),
622-
contentChanges: [
623-
TextDocumentContentChangeEvent(range: Range(bPositions["0️⃣"]), text: "/* this is just a comment */")
624-
]
625-
)
626-
)
627-
628-
// Index and find-syntactic-rename ranges work based on line/column so we should still be able to match the location
629-
// of `foo` after the edit.
630-
let (aUri, aPositions) = try ws.openDocument("a.swift")
631-
let response = try await ws.testClient.send(
632-
RenameRequest(textDocument: TextDocumentIdentifier(aUri), position: aPositions["1️⃣"], newName: "bar")
633-
)
634-
let changes = try XCTUnwrap(response?.changes)
635-
XCTAssertEqual(
636-
changes,
637-
[
638-
aUri: [TextEdit(range: aPositions["1️⃣"]..<aPositions["2️⃣"], newText: "bar")],
639-
bUri: [TextEdit(range: bPositions["3️⃣"]..<bPositions["4️⃣"], newText: "bar")],
640-
]
665+
newName: "bar",
666+
expected: [
667+
"a.swift": """
668+
func bar() {}
669+
""",
670+
"b.swift": """
671+
func test() {
672+
bar()
673+
}
674+
""",
675+
],
676+
preRenameActions: { ws in
677+
let (bUri, bPositions) = try ws.openDocument("b.swift")
678+
ws.testClient.send(
679+
DidChangeTextDocumentNotification(
680+
textDocument: VersionedTextDocumentIdentifier(bUri, version: 1),
681+
contentChanges: [
682+
TextDocumentContentChangeEvent(range: Range(bPositions["0️⃣"]), text: "/* this is just a comment */")
683+
]
684+
)
685+
)
686+
}
641687
)
642688
}
643689
}

0 commit comments

Comments
 (0)