Skip to content

Commit 3f6b63b

Browse files
authored
Adapt tests to work better in Swift CI (#45)
* Add utility to create temp directories in unit tests * Create new target for test utilities * Pass the temporary directory to ConvertAction * Re-enable one skipped test * Update new tests to use `createTemporaryDirectory` helper * Remove `createDirectoryForLastPathComponent` argument in test helper * Update test helper documentation * Prefer `FileManager.temporaryDirectory` property in ConvertAction * Move `Files` and `Folder` types into test utility target * Add additional safety checks when creating and removing temporary directories * Replace `TempFolder` test class with `createTempFolder` test function * Use XCTUnwrap instead of force unwrap in test helper * Also shadow `FileManager.temporaryDirectory` in tests * Add temp directory test helper variant with "named" argument This is clearer at the call site when only a single path component is specified. * Fix test helper syntax in disabled preview server test * Re-enable one file monitoring test
1 parent c166e29 commit 3f6b63b

File tree

53 files changed

+669
-825
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

53 files changed

+669
-825
lines changed

Package.swift

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,10 @@ let package = Package(
4444
]),
4545
.testTarget(
4646
name: "SwiftDocCTests",
47-
dependencies: ["SwiftDocC"],
47+
dependencies: [
48+
"SwiftDocC",
49+
"SwiftDocCTestUtilities",
50+
],
4851
resources: [
4952
.copy("Test Resources"),
5053
.copy("Test Bundles"),
@@ -66,11 +69,17 @@ let package = Package(
6669
dependencies: [
6770
"SwiftDocCUtilities",
6871
"SwiftDocC",
72+
"SwiftDocCTestUtilities",
6973
],
7074
resources: [
7175
.copy("Test Resources"),
7276
.copy("Test Bundles"),
7377
]),
78+
79+
// Test utility library
80+
.target(
81+
name: "SwiftDocCTestUtilities",
82+
dependencies: []),
7483

7584
// Command-line tool
7685
.executableTarget(

Sources/SwiftDocC/Utility/DataStructures/File.swift renamed to Sources/SwiftDocCTestUtilities/FilesAndFolders.swift

Lines changed: 73 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,23 @@
99
*/
1010

1111
import Foundation
12+
import XCTest
1213

1314
/*
1415
This file contains API for working with folder hierarchies, and is extensible to allow for testing
1516
for hierarchies as well.
1617
*/
1718

1819
/// An abstract representation of a file (or folder).
19-
protocol File {
20+
public protocol File {
2021
/// The name of the file.
2122
var name: String { get }
2223

2324
/// Writes the file to a given URL.
2425
func write(to url: URL) throws
2526
}
2627

27-
extension File {
28+
public extension File {
2829
/// Writes the file inside of a folder and returns the URL that it was written to.
2930
@discardableResult
3031
func write(inside url: URL) throws -> URL {
@@ -35,12 +36,12 @@ extension File {
3536
}
3637

3738
/// An item which provides data.
38-
protocol DataRepresentable {
39+
public protocol DataRepresentable {
3940
func data() throws -> Data
4041
}
4142

4243
/// `DataRepresentable` can automatically write itself to disk via `Data.write(to:)`
43-
extension DataRepresentable {
44+
public extension DataRepresentable {
4445
func write(to url: URL) throws {
4546
try data().write(to: url)
4647
}
@@ -49,17 +50,22 @@ extension DataRepresentable {
4950
// MARK: -
5051

5152
/// An abstract representation of a folder, containing some files or folders.
52-
struct Folder: File {
53-
let name: String
53+
public struct Folder: File {
54+
public init(name: String, content: [File]) {
55+
self.name = name
56+
self.content = content
57+
}
58+
59+
public let name: String
5460

5561
/// The files and sub folders that this folder contains.
56-
let content: [File]
62+
public let content: [File]
5763

58-
func appendingFile(_ newFile: File) -> Folder {
64+
public func appendingFile(_ newFile: File) -> Folder {
5965
return Folder(name: name, content: content + [newFile])
6066
}
6167

62-
func write(to url: URL) throws {
68+
public func write(to url: URL) throws {
6369
try FileManager.default.createDirectory(at: url, withIntermediateDirectories: false, attributes: nil)
6470
for file in content {
6571
try file.write(inside: url)
@@ -69,7 +75,7 @@ struct Folder: File {
6975

7076
extension Folder {
7177
/// Returns a flat list of a folder's recursive listing for testing purposes.
72-
var recursiveContent: [File] {
78+
public var recursiveContent: [File] {
7379
var result = content
7480
for file in content {
7581
if let content = (file as? Folder)?.recursiveContent {
@@ -81,13 +87,13 @@ extension Folder {
8187
}
8288

8389
/// A representation of an Info.plist file.
84-
struct InfoPlist: File, DataRepresentable {
85-
let name = "Info.plist"
90+
public struct InfoPlist: File, DataRepresentable {
91+
public let name = "Info.plist"
8692

8793
/// The information that the Into.plist file contains.
88-
let content: Content
94+
public let content: Content
8995

90-
init(displayName: String, identifier: String, versionString: String = "1.0", developmentRegion: String = "en") {
96+
public init(displayName: String, identifier: String, versionString: String = "1.0", developmentRegion: String = "en") {
9197
self.content = Content(
9298
displayName: displayName,
9399
identifier: identifier,
@@ -96,11 +102,11 @@ struct InfoPlist: File, DataRepresentable {
96102
)
97103
}
98104

99-
struct Content: Codable, Equatable {
100-
let displayName: String
101-
let identifier: String
102-
let versionString: String
103-
let developmentRegion: String
105+
public struct Content: Codable, Equatable {
106+
public let displayName: String
107+
public let identifier: String
108+
public let versionString: String
109+
public let developmentRegion: String
104110

105111
fileprivate init(displayName: String, identifier: String, versionString: String, developmentRegion: String) {
106112
self.displayName = displayName
@@ -117,7 +123,7 @@ struct InfoPlist: File, DataRepresentable {
117123
}
118124
}
119125

120-
func data() throws -> Data {
126+
public func data() throws -> Data {
121127
// TODO: Replace this with PropertListEncoder (see below) when it's available in swift-corelibs-foundation
122128
// https://github.com/apple/swift-corelibs-foundation/commit/d2d72f88d93f7645b94c21af88a7c9f69c979e4f
123129
let infoPlist = [
@@ -136,74 +142,85 @@ struct InfoPlist: File, DataRepresentable {
136142
}
137143

138144
/// A representation of a text file with some UTF-8 content.
139-
struct TextFile: File, DataRepresentable {
140-
let name: String
145+
public struct TextFile: File, DataRepresentable {
146+
public init(name: String, utf8Content: String) {
147+
self.name = name
148+
self.utf8Content = utf8Content
149+
}
150+
151+
public let name: String
141152

142153
/// The UTF8 content of the file.
143-
let utf8Content: String
154+
public let utf8Content: String
144155

145-
func data() throws -> Data {
156+
public func data() throws -> Data {
146157
return utf8Content.data(using: .utf8)!
147158
}
148159
}
149160

150161
/// A representation of a text file with some UTF-8 content.
151-
struct JSONFile<Content: Codable>: File, DataRepresentable {
152-
let name: String
162+
public struct JSONFile<Content: Codable>: File, DataRepresentable {
163+
public init(name: String, content: Content) {
164+
self.name = name
165+
self.content = content
166+
}
167+
168+
public let name: String
153169

154170
/// The UTF8 content of the file.
155-
let content: Content
171+
public let content: Content
156172

157-
func data() throws -> Data {
173+
public func data() throws -> Data {
158174
return try JSONEncoder().encode(content)
159175
}
160176
}
161177

162178
/// A copy of another file on disk somewhere.
163-
struct CopyOfFile: File, DataRepresentable {
164-
enum Error: DescribedError {
179+
public struct CopyOfFile: File, DataRepresentable {
180+
enum Error: LocalizedError {
165181
case notAFile(URL)
166182
var errorDescription: String {
167183
switch self {
168-
case .notAFile(let url): return "Original url is not a file: \(url.path.singleQuoted)"
184+
case .notAFile(let url): return "Original url is not a file: '\(url.path)'"
169185
}
170186
}
171187
}
172188

173189
/// The original file.
174-
let original: URL
175-
let name: String
190+
public let original: URL
191+
public let name: String
176192

177-
init(original: URL, newName: String? = nil) {
193+
public init(original: URL, newName: String? = nil) {
178194
self.original = original
179195
self.name = newName ?? original.lastPathComponent
180196
}
181197

182-
func data() throws -> Data {
198+
public func data() throws -> Data {
183199
// Note that `CopyOfFile` always reads a file from disk and so it's okay
184200
// to use `FileManager.default` directly here instead of `FileManagerProtocol`.
185-
guard !FileManager.default.directoryExists(atPath: original.path) else { throw Error.notAFile(original) }
201+
var isDirectory: ObjCBool = false
202+
guard FileManager.default.fileExists(atPath: original.path, isDirectory: &isDirectory), !isDirectory.boolValue else { throw Error.notAFile(original) }
186203
return try Data(contentsOf: original)
187204
}
188205

189-
func write(to url: URL) throws {
206+
public func write(to url: URL) throws {
190207
try FileManager.default.copyItem(at: original, to: url)
191208
}
192209
}
193210

194-
struct CopyOfFolder: File {
211+
public struct CopyOfFolder: File {
195212
/// The original file.
196213
let original: URL
197-
let name: String
214+
public let name: String
198215
let shouldCopyFile: (URL) -> Bool
199216

200-
init(original: URL, newName: String? = nil, filter shouldCopyFile: @escaping (URL) -> Bool = { _ in true }) {
217+
public init(original: URL, newName: String? = nil, filter shouldCopyFile: @escaping (URL) -> Bool = { _ in true }) {
201218
self.original = original
202219
self.name = newName ?? original.lastPathComponent
203220
self.shouldCopyFile = shouldCopyFile
204221
}
205222

206-
func write(to url: URL) throws {
223+
public func write(to url: URL) throws {
207224
try FileManager.default.createDirectory(at: url, withIntermediateDirectories: false, attributes: nil)
208225
for filePath in try FileManager.default.contentsOfDirectory(atPath: original.path) {
209226
// `contentsOfDirectory(atPath)` includes hidden files, skipHiddenFiles option doesn't help on Linux.
@@ -217,46 +234,30 @@ struct CopyOfFolder: File {
217234
}
218235

219236
/// A file backed by `Data`.
220-
struct DataFile: File, DataRepresentable {
221-
var name: String
237+
public struct DataFile: File, DataRepresentable {
238+
public var name: String
222239
var _data: Data
223240

224-
init(name: String, data: Data) {
241+
public init(name: String, data: Data) {
225242
self.name = name
226243
self._data = data
227244
}
228245

229-
func data() throws -> Data {
246+
public func data() throws -> Data {
230247
return _data
231248
}
232249
}
233250

234-
/// A temporary folder which can write files to a temporary location on disk and
235-
/// will delete itself when its instance is released from memory.
236-
class TempFolder: File {
237-
let name: String
238-
let url: URL
239-
240-
/// The files and sub folders that this folder contains.
241-
let content: [File]
242-
243-
func write(to url: URL) throws {
244-
try FileManager.default.createDirectory(at: url, withIntermediateDirectories: false, attributes: nil)
245-
for file in content {
246-
try file.write(inside: url)
247-
}
248-
}
249-
250-
init(content: [File]) throws {
251-
self.content = content
252-
253-
url = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(UUID().uuidString)
254-
name = url.absoluteString
255-
256-
try write(to: url)
257-
}
258-
259-
deinit {
260-
try? FileManager.default.removeItem(at: url)
251+
extension XCTestCase {
252+
/// Creates a ``Folder`` and writes its content to a temporary location on disk.
253+
///
254+
/// - Parameters:
255+
/// - content: The files and subfolders to write to a temporary location
256+
/// - Returns: The temporary location where the temporary folder was written.
257+
public func createTempFolder(content: [File]) throws -> URL {
258+
let temporaryDirectory = try createTemporaryDirectory().appendingPathComponent("TempDirectory-\(ProcessInfo.processInfo.globallyUniqueString)")
259+
let folder = Folder(name: temporaryDirectory.lastPathComponent, content: content)
260+
try folder.write(to: temporaryDirectory)
261+
return temporaryDirectory
261262
}
262263
}

0 commit comments

Comments
 (0)