Files
sourcekit-lsp/Tests/SourceKitLSPTests/MainFilesProviderTests.swift
Alex Hoppen fba2b56b82 Retry writing file contents in tests on Windows
Sometimes file writes fail on Windows because another process (like sourcekitd or clangd) still has exclusive access to the file but releases it soon after. Retry to save the file if this happens. This matches what a user would do.
2025-02-04 19:08:39 -08:00

215 lines
7.9 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'")
let newFancyLibraryContents = """
#include "\(try project.scratchDirectory.filePath)/Sources/shared.h"
"""
let fancyLibraryUri = try project.uri(for: "MyFancyLibrary.c")
try await newFancyLibraryContents.writeWithRetry(to: XCTUnwrap(fancyLibraryUri.fileURL))
project.testClient.send(
DidChangeWatchedFilesNotification(changes: [FileEvent(uri: fancyLibraryUri, type: .changed)])
)
// '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
}
}
}