Files
sourcekit-lsp/Tests/SourceKitDTests/SourceKitDTests.swift
Alex Hoppen 14cfd50582 If sourcekitd or clangd don’t respond to a request for 5 minutes, terminate them and use crash recovery to restore behavior
This should be a last stop-gap measure in case sourcekitd or clangd get stuck, don’t respond to any requests anymore and don’t honor cancellation either. In that case we can restore SourceKit-LSP behavior by killing them and using the crash recovery logic to restore functionality.

rdar://149492159
2025-05-07 13:43:28 +02:00

110 lines
3.3 KiB
Swift

//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2020 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 Csourcekitd
import Foundation
import LanguageServerProtocol
import LanguageServerProtocolExtensions
import SKTestSupport
import SourceKitD
import SwiftExtensions
import TSCBasic
import ToolchainRegistry
import XCTest
import class TSCBasic.Process
final class SourceKitDTests: XCTestCase {
func testMultipleNotificationHandlers() async throws {
let sourcekitdPath = await ToolchainRegistry.forTesting.default!.sourcekitd!
let sourcekitd = try await SourceKitD.getOrCreate(
dylibPath: sourcekitdPath,
pluginPaths: sourceKitPluginPaths
)
let keys = sourcekitd.keys
let path = DocumentURI(for: .swift).pseudoPath
let isExpectedNotification = { @Sendable (response: SKDResponse) -> Bool in
if let notification: sourcekitd_api_uid_t = response.value?[keys.notification],
let name: String = response.value?[keys.name]
{
return name == path && notification == sourcekitd.values.documentUpdateNotification
}
return false
}
let expectation1 = expectation(description: "handler 1")
let handler1 = ClosureNotificationHandler { response in
if isExpectedNotification(response) {
expectation1.fulfill()
}
}
// SourceKitD weakly references handlers
defer {
_fixLifetime(handler1)
}
await sourcekitd.addNotificationHandler(handler1)
let expectation2 = expectation(description: "handler 2")
let handler2 = ClosureNotificationHandler { response in
if isExpectedNotification(response) {
expectation2.fulfill()
}
}
// SourceKitD weakly references handlers
defer {
_fixLifetime(handler2)
}
await sourcekitd.addNotificationHandler(handler2)
let args = SKDRequestArray(sourcekitd: sourcekitd)
if case .darwin? = Platform.current,
let sdkpath = try? await Process.checkNonZeroExit(args: "/usr/bin/xcrun", "--show-sdk-path", "--sdk", "macosx")
.trimmingCharacters(in: .whitespacesAndNewlines)
{
args += ["-sdk", sdkpath]
}
args.append(path)
let req = sourcekitd.dictionary([
keys.request: sourcekitd.requests.editorOpen,
keys.name: path,
keys.sourceText: """
func foo() {}
""",
keys.compilerArgs: args,
])
_ = try await sourcekitd.send(req, timeout: defaultTimeoutDuration)
try await fulfillmentOfOrThrow(expectation1, expectation2)
let close = sourcekitd.dictionary([
keys.request: sourcekitd.requests.editorClose,
keys.name: path,
])
_ = try await sourcekitd.send(close, timeout: defaultTimeoutDuration)
}
}
private final class ClosureNotificationHandler: SKDNotificationHandler {
let f: @Sendable (SKDResponse) -> Void
init(_ f: @Sendable @escaping (SKDResponse) -> Void) {
self.f = f
}
func notification(_ response: SKDResponse) {
f(response)
}
}