Skip to content

Commit afd0a24

Browse files
authored
Support using the test file system in SwiftDocC tests (#756)
* Move FileManagerProtocol to a common target * Move TestFileSystem into test utilities target * Make TestFileSystem provide bundles like LocalFileSystemDataProvider * Use FileManagerProtocol to read file contents in diagnostic formatter #663 * Remove print statements in tests * Remove unintended printing in tests * Update InitActionTest to use _Common.FileManagerProtocol * Avoid adding FileManagerProtocol in public API * Move FileManagerProtocol to SwiftDocC as SPI to avoid adding new target
1 parent 69aec44 commit afd0a24

28 files changed

+356
-293
lines changed

Package.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
/*
33
This source file is part of the Swift.org open source project
44

5-
Copyright (c) 2021 Apple Inc. and the Swift project authors
5+
Copyright (c) 2021-2023 Apple Inc. and the Swift project authors
66
Licensed under Apache License v2.0 with Runtime Library Exception
77

88
See https://swift.org/LICENSE.txt for license information
@@ -90,6 +90,7 @@ let package = Package(
9090
.target(
9191
name: "SwiftDocCTestUtilities",
9292
dependencies: [
93+
.target(name: "SwiftDocC"),
9394
.product(name: "SymbolKit", package: "swift-docc-symbolkit"),
9495
],
9596
swiftSettings: swiftSettings

Sources/SwiftDocC/Infrastructure/Diagnostics/DiagnosticConsoleWriter.swift

Lines changed: 43 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/*
22
This source file is part of the Swift.org open source project
33

4-
Copyright (c) 2021-2023 Apple Inc. and the Swift project authors
4+
Copyright (c) 2021-2024 Apple Inc. and the Swift project authors
55
Licensed under Apache License v2.0 with Runtime Library Exception
66

77
See https://swift.org/LICENSE.txt for license information
@@ -27,18 +27,30 @@ public final class DiagnosticConsoleWriter: DiagnosticFormattingConsumer {
2727
/// - formattingOptions: The formatting options for the diagnostics.
2828
/// - baseUrl: A url to be used as a base url when formatting diagnostic source path.
2929
/// - highlight: Whether or not to highlight the default diagnostic formatting output.
30-
public init(
30+
public convenience init(
3131
_ stream: TextOutputStream = LogHandle.standardError,
3232
formattingOptions options: DiagnosticFormattingOptions = [],
3333
baseURL: URL? = nil,
3434
highlight: Bool? = nil
35+
) {
36+
self.init(stream, formattingOptions: options, baseURL: baseURL, highlight: highlight, fileManager: FileManager.default)
37+
}
38+
39+
@_spi(FileManagerProtocol)
40+
public init(
41+
_ stream: TextOutputStream = LogHandle.standardError,
42+
formattingOptions options: DiagnosticFormattingOptions = [],
43+
baseURL: URL? = nil,
44+
highlight: Bool? = nil,
45+
fileManager: FileManagerProtocol = FileManager.default
3546
) {
3647
outputStream = stream
3748
formattingOptions = options
3849
diagnosticFormatter = Self.makeDiagnosticFormatter(
3950
options,
4051
baseURL: baseURL,
41-
highlight: highlight ?? TerminalHelper.isConnectedToTerminal
52+
highlight: highlight ?? TerminalHelper.isConnectedToTerminal,
53+
fileManager: fileManager
4254
)
4355
}
4456

@@ -72,31 +84,43 @@ public final class DiagnosticConsoleWriter: DiagnosticFormattingConsumer {
7284
private static func makeDiagnosticFormatter(
7385
_ options: DiagnosticFormattingOptions,
7486
baseURL: URL?,
75-
highlight: Bool
87+
highlight: Bool,
88+
fileManager: FileManagerProtocol
7689
) -> DiagnosticConsoleFormatter {
7790
if options.contains(.formatConsoleOutputForTools) {
7891
return IDEDiagnosticConsoleFormatter(options: options)
7992
} else {
80-
return DefaultDiagnosticConsoleFormatter(baseUrl: baseURL, highlight: highlight, options: options)
93+
return DefaultDiagnosticConsoleFormatter(baseUrl: baseURL, highlight: highlight, options: options, fileManager: fileManager)
8194
}
8295
}
8396
}
8497

8598
// MARK: Formatted descriptions
8699

87100
extension DiagnosticConsoleWriter {
88-
89101
public static func formattedDescription<Problems>(for problems: Problems, options: DiagnosticFormattingOptions = []) -> String where Problems: Sequence, Problems.Element == Problem {
90-
return problems.map { formattedDescription(for: $0, options: options) }.joined(separator: "\n")
102+
formattedDescription(for: problems, options: options, fileManager: FileManager.default)
103+
}
104+
@_spi(FileManagerProtocol)
105+
public static func formattedDescription<Problems>(for problems: Problems, options: DiagnosticFormattingOptions = [], fileManager: FileManagerProtocol) -> String where Problems: Sequence, Problems.Element == Problem {
106+
return problems.map { formattedDescription(for: $0, options: options, fileManager: fileManager) }.joined(separator: "\n")
91107
}
92108

93109
public static func formattedDescription(for problem: Problem, options: DiagnosticFormattingOptions = []) -> String {
94-
let diagnosticFormatter = makeDiagnosticFormatter(options, baseURL: nil, highlight: TerminalHelper.isConnectedToTerminal)
110+
formattedDescription(for: problem, options: options, fileManager: FileManager.default)
111+
}
112+
@_spi(FileManagerProtocol)
113+
public static func formattedDescription(for problem: Problem, options: DiagnosticFormattingOptions = [], fileManager: FileManagerProtocol = FileManager.default) -> String {
114+
let diagnosticFormatter = makeDiagnosticFormatter(options, baseURL: nil, highlight: TerminalHelper.isConnectedToTerminal, fileManager: fileManager)
95115
return diagnosticFormatter.formattedDescription(for: problem)
96116
}
97117

98118
public static func formattedDescription(for diagnostic: Diagnostic, options: DiagnosticFormattingOptions = []) -> String {
99-
let diagnosticFormatter = makeDiagnosticFormatter(options, baseURL: nil, highlight: TerminalHelper.isConnectedToTerminal)
119+
formattedDescription(for: diagnostic, options: options, fileManager: FileManager.default)
120+
}
121+
@_spi(FileManagerProtocol)
122+
public static func formattedDescription(for diagnostic: Diagnostic, options: DiagnosticFormattingOptions = [], fileManager: FileManagerProtocol) -> String {
123+
let diagnosticFormatter = makeDiagnosticFormatter(options, baseURL: nil, highlight: TerminalHelper.isConnectedToTerminal, fileManager: fileManager)
100124
return diagnosticFormatter.formattedDescription(for: diagnostic)
101125
}
102126
}
@@ -205,18 +229,21 @@ final class DefaultDiagnosticConsoleFormatter: DiagnosticConsoleFormatter {
205229
private let baseUrl: URL?
206230
private let highlight: Bool
207231
private var sourceLines: [URL: [String]] = [:]
232+
private var fileManager: FileManagerProtocol
208233

209234
/// The number of additional lines from the source file that should be displayed both before and after the diagnostic source line.
210235
private static let contextSize = 2
211236

212237
init(
213238
baseUrl: URL?,
214239
highlight: Bool,
215-
options: DiagnosticFormattingOptions
240+
options: DiagnosticFormattingOptions,
241+
fileManager: FileManagerProtocol
216242
) {
217243
self.baseUrl = baseUrl
218244
self.highlight = highlight
219245
self.options = options
246+
self.fileManager = fileManager
220247
}
221248

222249
func formattedDescription<Problems>(for problems: Problems) -> String where Problems: Sequence, Problems.Element == Problem {
@@ -360,7 +387,7 @@ extension DefaultDiagnosticConsoleFormatter {
360387
// Example:
361388
// 9 | A line outside the diagnostic range.
362389
// 10 + A line inside the diagnostic range.
363-
result.append("\n\(linePrefix) \(separator) \(highlightedSource)")
390+
result.append("\n\(linePrefix) \(separator) \(highlightedSource)".removingTrailingWhitespace())
364391

365392
var suggestionsPerColumn = [Int: [String]]()
366393

@@ -466,8 +493,11 @@ extension DefaultDiagnosticConsoleFormatter {
466493
}
467494

468495
// TODO: Add support for also getting the source lines from the symbol graph files.
469-
guard let content = try? String(contentsOf: url)
470-
else { return [] }
496+
guard let data = fileManager.contents(atPath: url.path),
497+
let content = String(data: data, encoding: .utf8)
498+
else {
499+
return []
500+
}
471501

472502
let lines = content.splitByNewlines
473503
sourceLines[url] = lines

Sources/SwiftDocC/Infrastructure/Diagnostics/DiagnosticEngine.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,15 @@ public final class DiagnosticEngine {
3333
}
3434
}
3535

36+
/// Returns a Boolean value indicating whether the engine contains a consumer that satisfies the given predicate.
37+
/// - Parameter predicate: A closure that takes one of the engine's consumers as its argument and returns a Boolean value that indicates whether the passed consumer represents a match.
38+
/// - Returns: `true` if the engine contains a consumer that satisfies predicate; otherwise, `false`.
39+
public func hasConsumer(matching predicate: (DiagnosticConsumer) throws -> Bool) rethrows -> Bool {
40+
try consumers.sync {
41+
try $0.values.contains(where: predicate)
42+
}
43+
}
44+
3645
/// Determines whether warnings will be treated as errors.
3746
private let treatWarningsAsErrors: Bool
3847

Sources/SwiftDocCUtilities/Utility/FileManagerProtocol.swift renamed to Sources/SwiftDocC/Utility/FileManagerProtocol.swift

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/*
22
This source file is part of the Swift.org open source project
33

4-
Copyright (c) 2021 Apple Inc. and the Swift project authors
4+
Copyright (c) 2021-2024 Apple Inc. and the Swift project authors
55
Licensed under Apache License v2.0 with Runtime Library Exception
66

77
See https://swift.org/LICENSE.txt for license information
@@ -22,7 +22,8 @@ import Foundation
2222
/// Should you need a file system with a different storage, create your own
2323
/// protocol implementations to manage files in memory,
2424
/// on a network, in a database, or elsewhere.
25-
protocol FileManagerProtocol {
25+
@_spi(FileManagerProtocol)
26+
public protocol FileManagerProtocol {
2627

2728
/// Returns the data content of a file at the given path, if it exists.
2829
func contents(atPath: String) -> Data?
@@ -73,9 +74,10 @@ protocol FileManagerProtocol {
7374
func createFile(at location: URL, contents: Data, options writingOptions: NSData.WritingOptions?) throws
7475
}
7576

77+
@_spi(FileManagerProtocol)
7678
extension FileManagerProtocol {
7779
/// Returns a Boolean value that indicates whether a directory exists at a specified path.
78-
func directoryExists(atPath path: String) -> Bool {
80+
public func directoryExists(atPath path: String) -> Bool {
7981
var isDirectory = ObjCBool(booleanLiteral: false)
8082
let fileExistsAtPath = fileExists(atPath: path, isDirectory: &isDirectory)
8183
return fileExistsAtPath && isDirectory.boolValue
@@ -84,14 +86,15 @@ extension FileManagerProtocol {
8486

8587
/// Add compliance to `FileManagerProtocol` to `FileManager`,
8688
/// most of the methods are already implemented in Foundation.
89+
@_spi(FileManagerProtocol)
8790
extension FileManager: FileManagerProtocol {
8891

8992
// This method doesn't exist on `FileManager`. There is a similar looking method but it doesn't provide information about potential errors.
90-
func createFile(at location: URL, contents: Data) throws {
93+
public func createFile(at location: URL, contents: Data) throws {
9194
try contents.write(to: location, options: .atomic)
9295
}
9396

94-
func createFile(at location: URL, contents: Data, options writingOptions: NSData.WritingOptions?) throws {
97+
public func createFile(at location: URL, contents: Data, options writingOptions: NSData.WritingOptions?) throws {
9598
if let writingOptions = writingOptions {
9699
try contents.write(to: location, options: writingOptions)
97100
} else {

Sources/SwiftDocC/Utility/FoundationExtensions/FileManager+directoryExists.swift

Lines changed: 0 additions & 20 deletions
This file was deleted.

Sources/SwiftDocCTestUtilities/FilesAndFolders.swift

Lines changed: 10 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -93,51 +93,41 @@ public struct InfoPlist: File, DataRepresentable {
9393
/// The information that the Into.plist file contains.
9494
public let content: Content
9595

96-
public init(displayName: String, identifier: String, versionString: String = "1.0", developmentRegion: String = "en") {
96+
public init(displayName: String, identifier: String? = nil, versionString: String = "1.0") {
9797
self.content = Content(
9898
displayName: displayName,
9999
identifier: identifier,
100-
versionString: versionString,
101-
developmentRegion: developmentRegion
100+
versionString: versionString
102101
)
103102
}
104103

105104
public struct Content: Codable, Equatable {
106105
public let displayName: String
107-
public let identifier: String
108-
public let versionString: String
109-
public let developmentRegion: String
106+
public let identifier: String?
107+
public let versionString: String?
110108

111-
fileprivate init(displayName: String, identifier: String, versionString: String, developmentRegion: String) {
109+
fileprivate init(displayName: String, identifier: String?, versionString: String) {
112110
self.displayName = displayName
113111
self.identifier = identifier
114112
self.versionString = versionString
115-
self.developmentRegion = developmentRegion
116113
}
117114

118115
enum CodingKeys: String, CodingKey {
119116
case displayName = "CFBundleDisplayName"
120117
case identifier = "CFBundleIdentifier"
121118
case versionString = "CFBundleVersion"
122-
case developmentRegion = "CFBundleDevelopmentRegion"
123119
}
124120
}
125121

126122
public func data() throws -> Data {
127-
// TODO: Replace this with PropertListEncoder (see below) when it's available in swift-corelibs-foundation
128-
// https://github.com/apple/swift-corelibs-foundation/commit/d2d72f88d93f7645b94c21af88a7c9f69c979e4f
129-
let infoPlist = [
123+
let encoder = PropertyListEncoder()
124+
encoder.outputFormat = .xml
125+
126+
return try encoder.encode([
130127
Content.CodingKeys.displayName.rawValue: content.displayName,
131128
Content.CodingKeys.identifier.rawValue: content.identifier,
132129
Content.CodingKeys.versionString.rawValue: content.versionString,
133-
Content.CodingKeys.developmentRegion.rawValue: content.developmentRegion,
134-
]
135-
136-
return try PropertyListSerialization.data(
137-
fromPropertyList: infoPlist,
138-
format: .xml,
139-
options: 0
140-
)
130+
])
141131
}
142132
}
143133

0 commit comments

Comments
 (0)