Files
sourcekit-lsp/Tests/SourceKitLSPTests/MainFilesProviderTests.swift
Alex Hoppen ce75053555 Handle file did change notifications as freestanding messages
Technically, the watched files notification can change the response of any other request (eg. because a target needs to be re-prepared). But treating it as a `globalConfiguration` inserts a lot of barriers in request  handling and significantly prevents parallelism. Since many editors batch file change notifications already, they might have delayed the file change notification even more, which is equivalent to handling the  notification a little later inside SourceKit-LSP. Thus, treating it as `freestanding` should be acceptable.

Also, simplify the logic needed in tests to write modified files to disk because we now need to run a barrier request in tests to ensure that the watched file notification has been handled.
2025-03-10 21:44:35 -07:00

213 lines
7.7 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 LanguageServerProtocol
import LanguageServerProtocolJSONRPC
import SKLogging
import SKTestSupport
import SourceKitLSP
import SwiftExtensions
import XCTest
final class MainFilesProviderTests: XCTestCase {
func testMainFileForHeaderInPackageTarget() async throws {
let project = try await SwiftPMTestProject(
files: [
"MyLibrary/include/MyLibrary.h": """
void bridging(void) {
int VARIABLE_NAME = 1;
}
""",
"MyLibrary/MyLibrary.c": """
#include "shared.h"
""",
],
manifest: """
let package = Package(
name: "MyLibrary",
targets: [
.target(
name: "MyLibrary",
cSettings: [.define("VARIABLE_NAME", to: "fromMyLibrary"), .unsafeFlags(["-Wunused-variable"])]
)
]
)
""",
usePullDiagnostics: false
)
// Use the definition of `VARIABLE_NAME` together with `-Wunused-variable` to check that we are getting compiler
// arguments from the target.
_ = try project.openDocument("MyLibrary.h", language: .c)
let diags = try await project.testClient.nextDiagnosticsNotification()
XCTAssertEqual(diags.diagnostics.count, 1)
let diag = try XCTUnwrap(diags.diagnostics.first)
XCTAssertEqual(diag.message, "Unused variable 'fromMyLibrary'")
}
func testMainFileForHeaderOutsideOfTarget() async throws {
let project = try await SwiftPMTestProject(
files: [
"Sources/shared.h": """
void bridging(void) {
int VARIABLE_NAME = 1;
}
""",
"Sources/MyLibrary/include/dummy.h": "",
"Sources/MyLibrary/MyLibrary.c": """
#include "$TEST_DIR/Sources/shared.h"
""",
],
manifest: """
let package = Package(
name: "MyLibrary",
targets: [
.target(
name: "MyLibrary",
cSettings: [.define("VARIABLE_NAME", to: "fromMyLibrary"), .unsafeFlags(["-Wunused-variable"])]
)
]
)
""",
usePullDiagnostics: false
)
_ = try project.openDocument("shared.h", language: .c)
// Before we build, we shouldn't have an index and thus we don't infer the build setting for 'shared.h' form
// 'MyLibrary.c'. Hence, we don't have the '-Wunused-variable' build setting and thus no diagnostics.
let preBuildDiags = try await project.testClient.nextDiagnosticsNotification()
XCTAssertEqual(preBuildDiags.diagnostics.count, 0)
try await SwiftPMTestProject.build(at: project.scratchDirectory)
// After building we know that 'shared.h' is included from 'MyLibrary.c' and thus we use its build settings,
// defining `VARIABLE_NAME` to `fromMyLibrary`.
let postBuildDiags = try await project.testClient.nextDiagnosticsNotification()
XCTAssertEqual(postBuildDiags.diagnostics.count, 1)
let diag = try XCTUnwrap(postBuildDiags.diagnostics.first)
XCTAssertEqual(diag.message, "Unused variable 'fromMyLibrary'")
}
func testMainFileForSharedHeaderOutsideOfTarget() async throws {
let project = try await SwiftPMTestProject(
files: [
"Sources/shared.h": """
void bridging(void) {
int VARIABLE_NAME = 1;
}
""",
"Sources/MyLibrary/include/dummy.h": "",
"Sources/MyLibrary/MyLibrary.c": """
#include "$TEST_DIR/Sources/shared.h"
""",
"Sources/MyFancyLibrary/include/dummy.h": "",
"Sources/MyFancyLibrary/MyFancyLibrary.c": """
#include "$TEST_DIR/Sources/shared.h"
""",
],
manifest: """
let package = Package(
name: "MyLibrary",
targets: [
.target(
name: "MyLibrary",
cSettings: [.define("VARIABLE_NAME", to: "fromMyLibrary"), .unsafeFlags(["-Wunused-variable"])]
),
.target(
name: "MyFancyLibrary",
cSettings: [.define("VARIABLE_NAME", to: "fromMyFancyLibrary"), .unsafeFlags(["-Wunused-variable"])]
)
]
)
""",
enableBackgroundIndexing: true,
usePullDiagnostics: false
)
_ = try project.openDocument("shared.h", language: .c)
// We could pick build settings from either 'MyLibrary.c' or 'MyFancyLibrary.c'. We currently pick the
// lexicographically first to be deterministic, which is 'MyFancyLibrary'. Thus `VARIABLE_NAME` is set to
// `fromMyFancyLibrary`.
let diags = try await project.testClient.nextDiagnosticsNotification()
XCTAssertEqual(diags.diagnostics.count, 1)
let diag = try XCTUnwrap(diags.diagnostics.first)
XCTAssertEqual(diag.message, "Unused variable 'fromMyFancyLibrary'")
}
func testMainFileChangesIfIncludeIsAdded() async throws {
let project = try await SwiftPMTestProject(
files: [
"Sources/shared.h": """
void bridging(void) {
int VARIABLE_NAME = 1;
}
""",
"Sources/MyLibrary/include/dummy.h": "",
"Sources/MyLibrary/MyLibrary.c": """
#include "$TEST_DIR/Sources/shared.h"
""",
"Sources/MyFancyLibrary/include/dummy.h": "",
"Sources/MyFancyLibrary/MyFancyLibrary.c": "",
],
manifest: """
let package = Package(
name: "MyLibrary",
targets: [
.target(
name: "MyLibrary",
cSettings: [.define("VARIABLE_NAME", to: "fromMyLibrary"), .unsafeFlags(["-Wunused-variable"])]
),
.target(
name: "MyFancyLibrary",
cSettings: [.define("VARIABLE_NAME", to: "fromMyFancyLibrary"), .unsafeFlags(["-Wunused-variable"])]
)
]
)
""",
enableBackgroundIndexing: true,
usePullDiagnostics: false
)
_ = try project.openDocument("shared.h", language: .c)
// 'MyLibrary.c' is the only file that includes 'shared.h' at first. So we use build settings from MyLibrary and
// define `VARIABLE_NAME` to `fromMyLibrary`.
let preEditDiags = try await project.testClient.nextDiagnosticsNotification()
XCTAssertEqual(preEditDiags.diagnostics.count, 1)
let preEditDiag = try XCTUnwrap(preEditDiags.diagnostics.first)
XCTAssertEqual(preEditDiag.message, "Unused variable 'fromMyLibrary'")
try await project.changeFileOnDisk(
"MyFancyLibrary.c",
newMarkedContents: """
#include "\(try project.scratchDirectory.filePath)/Sources/shared.h"
"""
)
// 'MyFancyLibrary.c' now also includes 'shared.h'. Since it lexicographically precedes MyLibrary, we should use its
// build settings.
// `clangd` may return diagnostics from the old build settings sometimes (I believe when it's still building the
// preamble for shared.h when the new build settings come in). Check that it eventually returns the correct
// diagnostics.
try await repeatUntilExpectedResult {
let refreshedDiags = try? await project.testClient.nextDiagnosticsNotification(timeout: .seconds(1))
guard refreshedDiags?.diagnostics.only?.message == "Unused variable 'fromMyFancyLibrary'" else {
logger.debug("Received unexpected diagnostics: \(refreshedDiags?.forLogging)")
return false
}
return true
}
}
}