Files
sourcekit-lsp/Tests/SourceKitTests/ExecuteCommandTests.swift
Ben Langmuir ddb9a8d964 Make initial workspace load asynchronous
Some editors treat the initialize request as blocking, or otherwise time
out if it takes too long. We have been scraping that edge for swiftpm
projects (time taken to load the manifest and construct a build graph),
and large compilation databases (time taken to parse the json and then
split commands into separate arguments). In practice, a *debug build* of
sourcekit-lsp was failing to open a large compilation database fast
enough for sublime text's lsp plugin (3 second limit).

This change hides the latency for loading the build system and creating
the index by letting it continue beyond the initialize request, blocking
instead the next request. This is not a complete solution. In addition
to hiding latency in between requests, this also is based on editors
being more forgiving about slow requests beyond the first initialize.`

We should also continue to improve the performance of the initial load
of the build system and index. And we should also consider more
prinicipled ways to use asynchronicity, for example by not doing the
initial load on the server's message queue. The main challenge for that
is that we have some assumptions, particularly in tests, that the index
will be created already, and for the index to load we currently block on
the build system load in order to get the index directory.
2020-02-04 09:20:50 -08:00

162 lines
6.2 KiB
Swift

//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2019 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 LanguageServerProtocol
import LSPTestSupport
import SKTestSupport
import SourceKit
import XCTest
final class ExecuteCommandTests: XCTestCase {
/// Connection and lifetime management for the service.
var connection: TestSourceKitServer! = nil
/// The primary interface to make requests to the SourceKitServer.
var sk: TestClient! = nil
override func tearDown() {
sk = nil
connection = nil
}
override func setUp() {
connection = TestSourceKitServer()
sk = connection.client
_ = try! sk.sendSync(InitializeRequest(
processId: nil,
rootPath: nil,
rootURI: nil,
initializationOptions: nil,
capabilities: ClientCapabilities(workspace: nil, textDocument: nil),
trace: .off,
workspaceFolders: nil))
}
func testLocationSemanticRefactoring() throws {
guard let ws = try staticSourceKitTibsWorkspace(name: "SemanticRefactor") else { return }
let loc = ws.testLoc("sr:string")
try ws.openDocument(loc.url, language: .swift)
let textDocument = TextDocumentIdentifier(loc.url)
let args = SemanticRefactorCommand(title: "Localize String",
actionString: "source.refactoring.kind.localize.string",
positionRange: loc.position..<loc.position,
textDocument: textDocument)
let metadata = SourceKitLSPCommandMetadata(textDocument: textDocument)
var command = try args.asCommand()
command.arguments?.append(metadata.encodeToLSPAny())
let request = ExecuteCommandRequest(command: command.command, arguments: command.arguments)
ws.testServer.client.handleNextRequest { (req: Request<ApplyEditRequest>) in
req.reply(ApplyEditResponse(applied: true, failureReason: nil))
}
let result = try ws.sk.sendSync(request)
guard case .dictionary(let resultDict) = result else {
XCTFail("Result is not a dictionary.")
return
}
XCTAssertEqual(WorkspaceEdit(fromLSPDictionary: resultDict), WorkspaceEdit(changes: [
DocumentURI(loc.url): [
TextEdit(range: Position(line: 1, utf16index: 29)..<Position(line: 1, utf16index: 29),
newText: "NSLocalizedString("),
TextEdit(range: Position(line: 1, utf16index: 44)..<Position(line: 1, utf16index: 44),
newText: ", comment: \"\")")
]
]))
}
func testRangeSemanticRefactoring() throws {
guard let ws = try staticSourceKitTibsWorkspace(name: "SemanticRefactor") else { return }
let loc = ws.testLoc("sr:foo")
try ws.openDocument(loc.url, language: .swift)
let textDocument = TextDocumentIdentifier(loc.url)
let startPosition = Position(line: 1, utf16index: 2)
let endPosition = Position(line: 2, utf16index: 10)
let args = SemanticRefactorCommand(title: "Extract Method",
actionString: "source.refactoring.kind.extract.function",
positionRange: startPosition..<endPosition,
textDocument: textDocument)
let metadata = SourceKitLSPCommandMetadata(textDocument: textDocument)
var command = try args.asCommand()
command.arguments?.append(metadata.encodeToLSPAny())
let request = ExecuteCommandRequest(command: command.command, arguments: command.arguments)
ws.testServer.client.handleNextRequest { (req: Request<ApplyEditRequest>) in
req.reply(ApplyEditResponse(applied: true, failureReason: nil))
}
let result = try ws.sk.sendSync(request)
guard case .dictionary(let resultDict) = result else {
XCTFail("Result is not a dictionary.")
return
}
XCTAssertEqual(WorkspaceEdit(fromLSPDictionary: resultDict), WorkspaceEdit(changes: [
DocumentURI(loc.url): [
TextEdit(range: Position(line: 0, utf16index: 0)..<Position(line: 0, utf16index: 0),
newText: "fileprivate func extractedFunc() -> String {\n/*sr:extractStart*/var a = \"/*sr:string*/\"\n return a\n}\n\n"),
TextEdit(range: Position(line: 1, utf16index: 2)..<Position(line: 2, utf16index: 10),
newText: "return extractedFunc()")
]
]))
}
func testLSPCommandMetadataRetrieval() {
var req = ExecuteCommandRequest(command: "", arguments: nil)
XCTAssertNil(req.metadata)
req.arguments = [1, 2, ""]
XCTAssertNil(req.metadata)
let url = URL(fileURLWithPath: "/a.swift")
let textDocument = TextDocumentIdentifier(url)
let metadata = SourceKitLSPCommandMetadata(textDocument: textDocument)
req.arguments = [metadata.encodeToLSPAny(), 1, 2, ""]
XCTAssertNil(req.metadata)
req.arguments = [1, 2, "", [metadata.encodeToLSPAny()]]
XCTAssertNil(req.metadata)
req.arguments = [1, 2, "", metadata.encodeToLSPAny()]
XCTAssertEqual(req.metadata, metadata)
req.arguments = [metadata.encodeToLSPAny()]
XCTAssertEqual(req.metadata, metadata)
}
func testLSPCommandMetadataRemoval() {
var req = ExecuteCommandRequest(command: "", arguments: nil)
XCTAssertNil(req.argumentsWithoutSourceKitMetadata)
req.arguments = [1, 2, ""]
XCTAssertEqual(req.arguments, req.argumentsWithoutSourceKitMetadata)
let url = URL(fileURLWithPath: "/a.swift")
let textDocument = TextDocumentIdentifier(url)
let metadata = SourceKitLSPCommandMetadata(textDocument: textDocument)
req.arguments = [metadata.encodeToLSPAny(), 1, 2, ""]
XCTAssertEqual(req.arguments, req.argumentsWithoutSourceKitMetadata)
req.arguments = [1, 2, "", [metadata.encodeToLSPAny()]]
XCTAssertEqual(req.arguments, req.argumentsWithoutSourceKitMetadata)
req.arguments = [1, 2, "", metadata.encodeToLSPAny()]
XCTAssertEqual([1, 2, ""], req.argumentsWithoutSourceKitMetadata)
}
}