Files
sourcekit-lsp/Tests/SourceKitLSPTests/SourceKitTests.swift
Max Desiatov f5a30d9351 Fix uses of deprecated non-throwing AbsolutePath.init (#709)
These warnings appeared after an update to TSC deprecated this initializer.
2023-02-09 14:04:57 +00:00

397 lines
16 KiB
Swift

//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2018 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
import ISDBTestSupport
import LanguageServerProtocol
import LSPTestSupport
import SKCore
import SKTestSupport
import TSCBasic
import XCTest
public typealias URL = Foundation.URL
final class SKTests: XCTestCase {
func testInitLocal() {
let c = TestSourceKitServer()
defer { withExtendedLifetime(c) {} } // Keep connection alive for callbacks.
let sk = c.client
let initResult = try! sk.sendSync(InitializeRequest(
processId: nil,
rootPath: nil,
rootURI: nil,
initializationOptions: nil,
capabilities: ClientCapabilities(workspace: nil, textDocument: nil),
trace: .off,
workspaceFolders: nil))
guard case .options(let syncOptions) = initResult.capabilities.textDocumentSync else {
XCTFail("Unexpected textDocumentSync property")
return
}
XCTAssertEqual(syncOptions.openClose, true)
XCTAssertNotNil(initResult.capabilities.completionProvider)
}
func testInitJSON() {
let c = TestSourceKitServer(connectionKind: .jsonrpc)
defer { withExtendedLifetime(c) {} } // Keep connection alive for callbacks.
let sk = c.client
let initResult = try! sk.sendSync(InitializeRequest(
processId: nil,
rootPath: nil,
rootURI: nil,
initializationOptions: nil,
capabilities: ClientCapabilities(workspace: nil, textDocument: nil),
trace: .off,
workspaceFolders: nil))
guard case .options(let syncOptions) = initResult.capabilities.textDocumentSync else {
XCTFail("Unexpected textDocumentSync property")
return
}
XCTAssertEqual(syncOptions.openClose, true)
XCTAssertNotNil(initResult.capabilities.completionProvider)
}
func testIndexSwiftModules() throws {
guard let ws = try staticSourceKitTibsWorkspace(name: "SwiftModules") else { return }
try ws.buildAndIndex()
defer { withExtendedLifetime(ws) {} } // Keep workspace alive for callbacks.
let locDef = ws.testLoc("aaa:def")
let locRef = ws.testLoc("aaa:call:c")
try ws.openDocument(locDef.url, language: .swift)
try ws.openDocument(locRef.url, language: .swift)
// MARK: Jump to definition
let response = try ws.sk.sendSync(DefinitionRequest(
textDocument: locRef.docIdentifier,
position: locRef.position))
guard case .locations(let jump) = response else {
XCTFail("Response is not locations")
return
}
XCTAssertEqual(jump.count, 1)
XCTAssertEqual(jump.first?.uri, try locDef.docUri.nativeURI)
XCTAssertEqual(jump.first?.range.lowerBound, locDef.position)
// MARK: Find references
let refs = try ws.sk.sendSync(ReferencesRequest(
textDocument: locDef.docIdentifier,
position: locDef.position,
context: ReferencesContext(includeDeclaration: true)))
let call = ws.testLoc("aaa:call")
XCTAssertEqual(Set(refs), [
Location(TestLocation(url: URL(fileURLWithPath: try resolveSymlinks(AbsolutePath(validating: locDef.url.path)).pathString), line: locDef.line, utf8Column: locDef.utf8Column, utf16Column: locDef.utf16Column)),
Location(TestLocation(url: URL(fileURLWithPath: try resolveSymlinks(AbsolutePath(validating: locRef.url.path)).pathString), line: locRef.line, utf8Column: locRef.utf8Column, utf16Column: locRef.utf16Column)),
Location(TestLocation(url: URL(fileURLWithPath: try resolveSymlinks(AbsolutePath(validating: call.url.path)).pathString), line: call.line, utf8Column: call.utf8Column, utf16Column: call.utf16Column)),
])
}
func testIndexShutdown() throws {
let tmpDir = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true)
.appendingPathComponent("sk-test-data/\(testDirectoryName)", isDirectory: true)
func listdir(_ url: URL) throws -> [URL] {
try FileManager.default.contentsOfDirectory(at: url, includingPropertiesForKeys: nil)
}
func checkRunningIndex(build: Bool) throws -> URL? {
guard let ws = try staticSourceKitTibsWorkspace(
name: "SwiftModules", tmpDir: tmpDir, removeTmpDir: false)
else {
return nil
}
if build {
try ws.buildAndIndex()
}
let locDef = ws.testLoc("aaa:def")
let locRef = ws.testLoc("aaa:call:c")
try ws.openDocument(locRef.url, language: .swift)
let response = try ws.sk.sendSync(DefinitionRequest(
textDocument: locRef.docIdentifier,
position: locRef.position))
guard case .locations(let jump) = response else {
XCTFail("Response is not locations")
return nil
}
XCTAssertEqual(jump.count, 1)
XCTAssertEqual(jump.first?.uri, try locDef.docUri.nativeURI)
XCTAssertEqual(jump.first?.range.lowerBound, locDef.position)
let tmpContents = try listdir(tmpDir)
guard let versionedPath = tmpContents.filter({ $0.lastPathComponent.starts(with: "v") }).spm_only else {
XCTFail("expected one version path 'v[0-9]*', found \(tmpContents)")
return nil
}
let versionContentsBefore = try listdir(versionedPath)
XCTAssertEqual(versionContentsBefore.count, 1)
XCTAssert(versionContentsBefore.first?.lastPathComponent.starts(with: "p") ?? false)
try withExtendedLifetime(ws) {
_ = try ws.sk.sendSync(ShutdownRequest())
}
return versionedPath
}
guard let versionedPath = try checkRunningIndex(build: true) else { return }
let versionContentsAfter = try listdir(versionedPath)
XCTAssertEqual(versionContentsAfter.count, 1)
XCTAssertEqual(versionContentsAfter.first?.lastPathComponent, "saved")
_ = try checkRunningIndex(build: true)
try FileManager.default.removeItem(atPath: tmpDir.path)
}
func testCodeCompleteSwiftTibs() throws {
guard let ws = try staticSourceKitTibsWorkspace(name: "CodeCompleteSingleModule") else { return }
let loc = ws.testLoc("cc:A")
try ws.openDocument(loc.url, language: .swift)
let results = try withExtendedLifetime(ws) { try ws.sk.sendSync(
CompletionRequest(textDocument: loc.docIdentifier, position: loc.position))
}
XCTAssertEqual(results.items, [
CompletionItem(
label: "method(a: Int)",
kind: .method,
detail: "Void",
deprecated: false, sortText: nil,
filterText: "method(a:)",
insertText: "method(a: )",
insertTextFormat: .plain,
textEdit: .textEdit(TextEdit(range: Position(line: 1, utf16index: 14)..<Position(line: 1, utf16index: 14), newText: "method(a: )"))),
CompletionItem(
label: "self",
kind: .keyword,
detail: "A",
deprecated: false, sortText: nil,
filterText: "self",
insertText: "self",
insertTextFormat: .plain,
textEdit: .textEdit(TextEdit(range: Position(line: 1, utf16index: 14)..<Position(line: 1, utf16index: 14), newText: "self"))),
])
}
func testDependenciesUpdatedSwiftTibs() throws {
guard let ws = try mutableSourceKitTibsTestWorkspace(name: "SwiftModules") else { return }
defer { withExtendedLifetime(ws) {} } // Keep workspace alive for callbacks.
guard let server = ws.testServer.server else {
XCTFail("Unable to fetch SourceKitServer to notify for build system events.")
return
}
let moduleRef = ws.testLoc("aaa:call:c")
let startExpectation = XCTestExpectation(description: "initial diagnostics")
startExpectation.expectedFulfillmentCount = 2
ws.sk.handleNextNotification { (note: Notification<PublishDiagnosticsNotification>) in
// Semantic analysis: no errors expected here.
XCTAssertEqual(note.params.diagnostics.count, 0)
startExpectation.fulfill()
}
ws.sk.appendOneShotNotificationHandler { (note: Notification<PublishDiagnosticsNotification>) in
// Semantic analysis: expect module import error.
XCTAssertEqual(note.params.diagnostics.count, 1)
if let diagnostic = note.params.diagnostics.first {
XCTAssert(diagnostic.message.contains("no such module"),
"expected module import error but found \"\(diagnostic.message)\"")
}
startExpectation.fulfill()
}
try ws.openDocument(moduleRef.url, language: .swift)
let started = XCTWaiter.wait(for: [startExpectation], timeout: defaultTimeout)
if started != .completed {
fatalError("error \(started) waiting for initial diagnostics notification")
}
try ws.buildAndIndex()
let finishExpectation = XCTestExpectation(description: "post-build diagnostics")
finishExpectation.expectedFulfillmentCount = 2
ws.sk.handleNextNotification { (note: Notification<PublishDiagnosticsNotification>) in
// Semantic analysis - SourceKit currently caches diagnostics so we still see an error.
XCTAssertEqual(note.params.diagnostics.count, 1)
finishExpectation.fulfill()
}
ws.sk.appendOneShotNotificationHandler { (note: Notification<PublishDiagnosticsNotification>) in
// Semantic analysis: no more errors expected, import should resolve since we built.
XCTAssertEqual(note.params.diagnostics.count, 0)
finishExpectation.fulfill()
}
server.filesDependenciesUpdated([DocumentURI(moduleRef.url)])
let finished = XCTWaiter.wait(for: [finishExpectation], timeout: defaultTimeout)
if finished != .completed {
fatalError("error \(finished) waiting for post-build diagnostics notification")
}
}
func testDependenciesUpdatedCXXTibs() throws {
guard let ws = try mutableSourceKitTibsTestWorkspace(name: "GeneratedHeader") else { return }
defer { withExtendedLifetime(ws) {} } // Keep workspace alive for callbacks.
guard let server = ws.testServer.server else {
XCTFail("Unable to fetch SourceKitServer to notify for build system events.")
return
}
let moduleRef = ws.testLoc("libX:call:main")
let startExpectation = XCTestExpectation(description: "initial diagnostics")
ws.sk.handleNextNotification { (note: Notification<PublishDiagnosticsNotification>) in
// Expect one error:
// - Implicit declaration of function invalid
XCTAssertEqual(note.params.diagnostics.count, 1)
startExpectation.fulfill()
}
let generatedHeaderURL = moduleRef.url.deletingLastPathComponent()
.appendingPathComponent("lib-generated.h", isDirectory: false)
// Write an empty header file first since clangd doesn't handle missing header
// files without a recently upstreamed extension.
try "".write(to: generatedHeaderURL, atomically: true, encoding: .utf8)
try ws.openDocument(moduleRef.url, language: .c)
let started = XCTWaiter.wait(for: [startExpectation], timeout: defaultTimeout)
guard started == .completed else {
XCTFail("error \(started) waiting for initial diagnostics notification")
return
}
// Update the header file to have the proper contents for our code to build.
let contents = "int libX(int value);"
try contents.write(to: generatedHeaderURL, atomically: true, encoding: .utf8)
try ws.buildAndIndex()
let finishExpectation = XCTestExpectation(description: "post-build diagnostics")
ws.sk.handleNextNotification { (note: Notification<PublishDiagnosticsNotification>) in
// No more errors expected, import should resolve since we the generated header file
// now has the proper contents.
XCTAssertEqual(note.params.diagnostics.count, 0)
finishExpectation.fulfill()
}
server.filesDependenciesUpdated([DocumentURI(moduleRef.url)])
let finished = XCTWaiter.wait(for: [finishExpectation], timeout: defaultTimeout)
guard finished == .completed else {
XCTFail("error \(finished) waiting for post-build diagnostics notification")
return
}
}
func testClangdGoToInclude() throws {
guard let ws = try staticSourceKitTibsWorkspace(name: "BasicCXX") else { return }
guard ToolchainRegistry.shared.default?.clangd != nil else { return }
let mainLoc = ws.testLoc("Object:include:main")
let expectedDoc = ws.testLoc("Object").docIdentifier.uri
let includePosition =
Position(line: mainLoc.position.line, utf16index: mainLoc.utf16Column + 2)
try ws.openDocument(mainLoc.url, language: .c)
let goToInclude = DefinitionRequest(
textDocument: mainLoc.docIdentifier, position: includePosition)
let resp = try withExtendedLifetime(ws) { try ws.sk.sendSync(goToInclude) }
let locationsOrLinks = try XCTUnwrap(resp, "No response for go-to-#include")
switch locationsOrLinks {
case .locations(let locations):
XCTAssert(!locations.isEmpty, "Found no locations for go-to-#include")
if let loc = locations.first {
XCTAssertEqual(loc.uri, expectedDoc)
}
case .locationLinks(let locationLinks):
XCTAssert(!locationLinks.isEmpty, "Found no location links for go-to-#include")
if let link = locationLinks.first {
XCTAssertEqual(link.targetUri, expectedDoc)
}
}
}
func testClangdGoToDefinitionWithoutIndex() throws {
guard let ws = try staticSourceKitTibsWorkspace(name: "BasicCXX") else { return }
guard ToolchainRegistry.shared.default?.clangd != nil else { return }
let refLoc = ws.testLoc("Object:ref:main")
let expectedDoc = try ws.testLoc("Object").docIdentifier.uri.nativeURI
let refPos = Position(line: refLoc.position.line, utf16index: refLoc.utf16Column + 2)
try ws.openDocument(refLoc.url, language: .c)
let goToDefinition = DefinitionRequest(
textDocument: refLoc.docIdentifier, position: refPos)
let resp = try withExtendedLifetime(ws) { try ws.sk.sendSync(goToDefinition) }
let locationsOrLinks = try XCTUnwrap(resp, "No response for go-to-definition")
switch locationsOrLinks {
case .locations(let locations):
XCTAssert(!locations.isEmpty, "Found no locations for go-to-definition")
if let loc = locations.first {
XCTAssertEqual(loc.uri, expectedDoc)
}
case .locationLinks(let locationLinks):
XCTAssert(!locationLinks.isEmpty, "Found no location links for go-to-definition")
if let link = locationLinks.first {
XCTAssertEqual(link.targetUri, expectedDoc)
}
}
}
func testClangdGoToDeclaration() throws {
guard let ws = try staticSourceKitTibsWorkspace(name: "BasicCXX") else { return }
guard ToolchainRegistry.shared.default?.clangd != nil else { return }
let mainLoc = ws.testLoc("Object:ref:newObject")
let expectedDoc = ws.testLoc("Object:decl:newObject").docIdentifier.uri
let includePosition =
Position(line: mainLoc.position.line, utf16index: mainLoc.utf16Column + 2)
try ws.openDocument(mainLoc.url, language: .c)
let goToInclude = DeclarationRequest(
textDocument: mainLoc.docIdentifier, position: includePosition)
let resp = try! ws.sk.sendSync(goToInclude)
let locationsOrLinks = try XCTUnwrap(resp, "No response for go-to-declaration")
switch locationsOrLinks {
case .locations(let locations):
XCTAssert(!locations.isEmpty, "Found no locations for go-to-declaration")
if let loc = locations.first {
XCTAssertEqual(loc.uri, expectedDoc)
}
case .locationLinks(let locationLinks):
XCTAssert(!locationLinks.isEmpty, "Found no location links for go-to-declaration")
if let link = locationLinks.first {
XCTAssertEqual(link.targetUri, expectedDoc)
}
}
}
}