|
11 | 11 | //===----------------------------------------------------------------------===//
|
12 | 12 |
|
13 | 13 | import BuildSystemIntegration
|
14 |
| -import Foundation |
15 | 14 | import LanguageServerProtocol
|
16 | 15 | import SKTestSupport
|
| 16 | +import SwiftExtensions |
17 | 17 | import TSCBasic
|
| 18 | +import TSCExtensions |
18 | 19 | import ToolchainRegistry
|
19 | 20 | import XCTest
|
20 | 21 |
|
@@ -238,6 +239,121 @@ final class CompilationDatabaseTests: XCTestCase {
|
238 | 239 | assertContains(error.message, "No language service")
|
239 | 240 | }
|
240 | 241 | }
|
| 242 | + |
| 243 | + func testLookThroughSwiftly() async throws { |
| 244 | + try await withTestScratchDir { scratchDirectory in |
| 245 | + let defaultToolchain = try await unwrap(ToolchainRegistry.forTesting.default) |
| 246 | + |
| 247 | + // We create a toolchain registry with the default toolchain, which is able to provide semantic functionality and |
| 248 | + // a dummy toolchain that can't provide semantic functionality. |
| 249 | + let fakeToolchainURL = scratchDirectory.appending(components: "fakeToolchain") |
| 250 | + let fakeToolchain = Toolchain( |
| 251 | + identifier: "fake", |
| 252 | + displayName: "fake", |
| 253 | + path: fakeToolchainURL, |
| 254 | + clang: nil, |
| 255 | + swift: fakeToolchainURL.appending(components: "usr", "bin", "swift"), |
| 256 | + swiftc: fakeToolchainURL.appending(components: "usr", "bin", "swiftc"), |
| 257 | + swiftFormat: nil, |
| 258 | + clangd: nil, |
| 259 | + sourcekitd: fakeToolchainURL.appending(components: "usr", "lib", "sourcekitd.framework", "sourcekitd"), |
| 260 | + libIndexStore: nil |
| 261 | + ) |
| 262 | + let toolchainRegistry = ToolchainRegistry(toolchains: [ |
| 263 | + try await unwrap(ToolchainRegistry.forTesting.default), fakeToolchain, |
| 264 | + ]) |
| 265 | + |
| 266 | + // We need to create a file for the swift executable because `SwiftlyResolver` checks for its presence. |
| 267 | + try FileManager.default.createDirectory( |
| 268 | + at: XCTUnwrap(fakeToolchain.swift).deletingLastPathComponent(), |
| 269 | + withIntermediateDirectories: true |
| 270 | + ) |
| 271 | + try await "".writeWithRetry(to: XCTUnwrap(fakeToolchain.swift)) |
| 272 | + |
| 273 | + // Create a dummy swiftly executable that picks the default toolchain for all file unless `fakeToolchain` is in |
| 274 | + // the source file's path. |
| 275 | + let dummySwiftlyExecutableUrl = scratchDirectory.appendingPathComponent("swiftly") |
| 276 | + let dummySwiftExecutableUrl = scratchDirectory.appendingPathComponent("swift") |
| 277 | + try FileManager.default.createSymbolicLink( |
| 278 | + at: dummySwiftExecutableUrl, |
| 279 | + withDestinationURL: dummySwiftlyExecutableUrl |
| 280 | + ) |
| 281 | + try await createBinary( |
| 282 | + """ |
| 283 | + import Foundation |
| 284 | +
|
| 285 | + if FileManager.default.currentDirectoryPath.contains("fakeToolchain") { |
| 286 | + print(#"\(fakeToolchain.path.filePath)"#) |
| 287 | + } else { |
| 288 | + print(#"\(defaultToolchain.path.filePath)"#) |
| 289 | + } |
| 290 | + """, |
| 291 | + at: dummySwiftlyExecutableUrl |
| 292 | + ) |
| 293 | + |
| 294 | + // Now create a project in which we have one file in a `realToolchain` directory for which our fake swiftly will |
| 295 | + // pick the default toolchain and one in `fakeToolchain` for which swiftly will pick the fake toolchain. We should |
| 296 | + // be able to get semantic functionality for the file in `realToolchain` but not for `fakeToolchain` because |
| 297 | + // sourcekitd can't be launched for that toolchain (since it doesn't exist). |
| 298 | + let dummySwiftExecutablePathForJSON = try dummySwiftExecutableUrl.filePath.replacing(#"\"#, with: #"\\"#) |
| 299 | + |
| 300 | + let project = try await MultiFileTestProject( |
| 301 | + files: [ |
| 302 | + "realToolchain/realToolchain.swift": """ |
| 303 | + #warning("Test warning") |
| 304 | + """, |
| 305 | + "fakeToolchain/fakeToolchain.swift": """ |
| 306 | + #warning("Test warning") |
| 307 | + """, |
| 308 | + "compile_commands.json": """ |
| 309 | + [ |
| 310 | + { |
| 311 | + "directory": "$TEST_DIR_BACKSLASH_ESCAPED/realToolchain", |
| 312 | + "arguments": [ |
| 313 | + "\(dummySwiftExecutablePathForJSON)", |
| 314 | + "$TEST_DIR_BACKSLASH_ESCAPED/realToolchain/realToolchain.swift", |
| 315 | + \(defaultSDKArgs) |
| 316 | + ], |
| 317 | + "file": "realToolchain.swift", |
| 318 | + "output": "$TEST_DIR_BACKSLASH_ESCAPED/realToolchain/test.swift.o" |
| 319 | + }, |
| 320 | + { |
| 321 | + "directory": "$TEST_DIR_BACKSLASH_ESCAPED/fakeToolchain", |
| 322 | + "arguments": [ |
| 323 | + "\(dummySwiftExecutablePathForJSON)", |
| 324 | + "$TEST_DIR_BACKSLASH_ESCAPED/fakeToolchain/fakeToolchain.swift", |
| 325 | + \(defaultSDKArgs) |
| 326 | + ], |
| 327 | + "file": "fakeToolchain.swift", |
| 328 | + "output": "$TEST_DIR_BACKSLASH_ESCAPED/fakeToolchain/test.swift.o" |
| 329 | + } |
| 330 | + ] |
| 331 | + """, |
| 332 | + ], |
| 333 | + toolchainRegistry: toolchainRegistry |
| 334 | + ) |
| 335 | + |
| 336 | + let (forRealToolchainUri, _) = try project.openDocument("realToolchain.swift") |
| 337 | + let diagnostics = try await project.testClient.send( |
| 338 | + DocumentDiagnosticsRequest(textDocument: TextDocumentIdentifier(forRealToolchainUri)) |
| 339 | + ) |
| 340 | + XCTAssertEqual(diagnostics.fullReport?.items.map(\.message), ["Test warning"]) |
| 341 | + |
| 342 | + let (forDummyToolchainUri, _) = try project.openDocument("fakeToolchain.swift") |
| 343 | + await assertThrowsError( |
| 344 | + try await project.testClient.send( |
| 345 | + DocumentDiagnosticsRequest(textDocument: TextDocumentIdentifier(forDummyToolchainUri)) |
| 346 | + ) |
| 347 | + ) { error in |
| 348 | + guard let error = error as? ResponseError else { |
| 349 | + XCTFail("Expected ResponseError, got \(error)") |
| 350 | + return |
| 351 | + } |
| 352 | + // The actual error message here doesn't matter too much, we just need to check that we don't get diagnostics. |
| 353 | + assertContains(error.message, "No language service") |
| 354 | + } |
| 355 | + } |
| 356 | + } |
241 | 357 | }
|
242 | 358 |
|
243 | 359 | fileprivate let defaultSDKArgs: String = {
|
|
0 commit comments