|
| 1 | +//===----------------------------------------------------------------------===// |
| 2 | +// |
| 3 | +// This source file is part of the Swift.org open source project |
| 4 | +// |
| 5 | +// Copyright (c) 2014 - 2019 Apple Inc. and the Swift project authors |
| 6 | +// Licensed under Apache License v2.0 with Runtime Library Exception |
| 7 | +// |
| 8 | +// See https://swift.org/LICENSE.txt for license information |
| 9 | +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors |
| 10 | +// |
| 11 | +//===----------------------------------------------------------------------===// |
| 12 | + |
| 13 | +import SourceKit |
| 14 | +import SKSwiftPMWorkspace |
| 15 | +import LanguageServerProtocol |
| 16 | +import SKCore |
| 17 | +import IndexStoreDB |
| 18 | +import ISDBTibs |
| 19 | +import ISDBTestSupport |
| 20 | +import Basic |
| 21 | +import SPMUtility |
| 22 | +import XCTest |
| 23 | +import Foundation |
| 24 | + |
| 25 | +public final class SKSwiftPMTestWorkspace { |
| 26 | + |
| 27 | + /// The directory containing the original, unmodified project. |
| 28 | + public let projectDir: URL |
| 29 | + |
| 30 | + /// A test-specific directory that we can put temporary files into. |
| 31 | + public let tmpDir: URL |
| 32 | + |
| 33 | + /// The build directory. |
| 34 | + public let buildDir: URL |
| 35 | + |
| 36 | + /// The source files used by the test. |
| 37 | + public let sources: TestSources |
| 38 | + |
| 39 | + /// The source code index. |
| 40 | + public var index: IndexStoreDB |
| 41 | + |
| 42 | + /// The toolchain. |
| 43 | + public let toolchain: Toolchain |
| 44 | + |
| 45 | + /// Connection to the language server. |
| 46 | + public let testServer: TestSourceKitServer = TestSourceKitServer(connectionKind: .local) |
| 47 | + |
| 48 | + public var sk: TestClient { testServer.client } |
| 49 | + |
| 50 | + public init(projectDir: URL, tmpDir: URL, toolchain: Toolchain) throws { |
| 51 | + self.projectDir = projectDir |
| 52 | + self.tmpDir = tmpDir |
| 53 | + self.toolchain = toolchain |
| 54 | + |
| 55 | + let fm = FileManager.default |
| 56 | + _ = try? fm.removeItem(at: tmpDir) |
| 57 | + |
| 58 | + buildDir = tmpDir.appendingPathComponent("build", isDirectory: true) |
| 59 | + try fm.createDirectory(at: buildDir, withIntermediateDirectories: true, attributes: nil) |
| 60 | + let sourceDir = tmpDir.appendingPathComponent("src", isDirectory: true) |
| 61 | + try fm.copyItem(at: projectDir, to: sourceDir) |
| 62 | + let databaseDir = tmpDir.appendingPathComponent("db", isDirectory: true) |
| 63 | + try fm.createDirectory(at: databaseDir, withIntermediateDirectories: true, attributes: nil) |
| 64 | + |
| 65 | + self.sources = try TestSources(rootDirectory: sourceDir) |
| 66 | + |
| 67 | + let sourcePath = AbsolutePath(sources.rootDirectory.path) |
| 68 | + let buildPath = AbsolutePath(buildDir.path) |
| 69 | + let buildSetup = BuildSetup(configuration: .debug, path: buildPath, flags: BuildFlags()) |
| 70 | + |
| 71 | + let swiftpm = try SwiftPMWorkspace( |
| 72 | + workspacePath: sourcePath, |
| 73 | + toolchainRegistry: ToolchainRegistry.shared, |
| 74 | + buildSetup: buildSetup) |
| 75 | + |
| 76 | + let libIndexStore = try IndexStoreLibrary(dylibPath: toolchain.libIndexStore!.pathString) |
| 77 | + |
| 78 | + try fm.createDirectory(atPath: swiftpm.indexStorePath!.pathString, withIntermediateDirectories: true) |
| 79 | + |
| 80 | + self.index = try IndexStoreDB( |
| 81 | + storePath: swiftpm.indexStorePath!.pathString, |
| 82 | + databasePath: tmpDir.path, |
| 83 | + library: libIndexStore, |
| 84 | + listenToUnitEvents: false) |
| 85 | + |
| 86 | + sk.allowUnexpectedNotification = true |
| 87 | + |
| 88 | + testServer.server!.workspace = Workspace( |
| 89 | + rootPath: sourcePath, |
| 90 | + clientCapabilities: ClientCapabilities(), |
| 91 | + buildSettings: swiftpm, |
| 92 | + index: index, |
| 93 | + buildSetup: buildSetup) |
| 94 | + } |
| 95 | + |
| 96 | + deinit { |
| 97 | + _ = try? FileManager.default.removeItem(atPath: tmpDir.path) |
| 98 | + } |
| 99 | +} |
| 100 | + |
| 101 | +extension SKSwiftPMTestWorkspace { |
| 102 | + |
| 103 | + public func testLoc(_ name: String) -> TestLocation { sources.locations[name]! } |
| 104 | + |
| 105 | + public func buildAndIndex() throws { |
| 106 | + try build() |
| 107 | + index.pollForUnitChangesAndWait() |
| 108 | + } |
| 109 | + |
| 110 | + public enum Error: Swift.Error { |
| 111 | + case buildFailure(Foundation.Process.TerminationReason, exitCode: Int32) |
| 112 | + } |
| 113 | + |
| 114 | + func build() throws { |
| 115 | + let p = Process() |
| 116 | + p.launchPath = String(toolchain.swiftc!.pathString.dropLast()) |
| 117 | + p.arguments = [ |
| 118 | + "build", |
| 119 | + "--package-path", sources.rootDirectory.path, |
| 120 | + "--build-path", buildDir.path, |
| 121 | + "-Xswiftc", "-index-ignore-system-modules", |
| 122 | + "-Xcc", "-index-ignore-system-symbols", |
| 123 | + ] |
| 124 | + |
| 125 | + p.launch() |
| 126 | + p.waitUntilExit() |
| 127 | + if p.terminationReason != .exit || p.terminationStatus != 0 { |
| 128 | + throw Error.buildFailure(p.terminationReason, exitCode: p.terminationStatus) |
| 129 | + } |
| 130 | + } |
| 131 | +} |
| 132 | + |
| 133 | +extension SKSwiftPMTestWorkspace { |
| 134 | + public func openDocument(_ url: URL, language: Language) throws { |
| 135 | + sk.send(DidOpenTextDocument(textDocument: TextDocumentItem( |
| 136 | + url: url, |
| 137 | + language: language, |
| 138 | + version: 1, |
| 139 | + text: try sources.sourceCache.get(url)))) |
| 140 | + } |
| 141 | +} |
| 142 | + |
| 143 | +extension XCTestCase { |
| 144 | + |
| 145 | + public func staticSourceKitSwiftPMWorkspace(name: String, testFile: String = #file) throws -> SKSwiftPMTestWorkspace? { |
| 146 | + let testDirName = testDirectoryName |
| 147 | + let toolchain = ToolchainRegistry.shared.default! |
| 148 | + let workspace = try SKSwiftPMTestWorkspace( |
| 149 | + projectDir: inputsDirectory(testFile: testFile) |
| 150 | + .appendingPathComponent(name, isDirectory: true), |
| 151 | + tmpDir: URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true) |
| 152 | + .appendingPathComponent("sk-test-data/\(testDirName)", isDirectory: true), |
| 153 | + toolchain: toolchain) |
| 154 | + |
| 155 | + let hasClangFile: Bool = workspace.sources.locations.contains { _, loc in |
| 156 | + loc.url.pathExtension != "swift" |
| 157 | + } |
| 158 | + |
| 159 | + if hasClangFile { |
| 160 | + if toolchain.libIndexStore == nil { |
| 161 | + fputs("warning: skipping test because libIndexStore is missing;" + |
| 162 | + "install Clang's IndexStore component\n", stderr) |
| 163 | + return nil |
| 164 | + } |
| 165 | + if !TibsToolchain(toolchain).clangHasIndexSupport { |
| 166 | + fputs("warning: skipping test because '\(toolchain.clang!)' does not " + |
| 167 | + "have indexstore support; use swift-clang\n", stderr) |
| 168 | + return nil |
| 169 | + } |
| 170 | + } |
| 171 | + |
| 172 | + return workspace |
| 173 | + } |
| 174 | +} |
0 commit comments