Skip to content

Adapt tests to work better in Swift CI #45

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 19 commits into from
Jan 8, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
65fa3d5
Add utility to create temp directories in unit tests
d-ronnqvist Nov 18, 2021
fb6fa6e
Create new target for test utilities
d-ronnqvist Nov 18, 2021
b645298
Pass the temporary directory to ConvertAction
d-ronnqvist Nov 18, 2021
b7e969b
Re-enable one skipped test
d-ronnqvist Nov 18, 2021
777319f
Update new tests to use `createTemporaryDirectory` helper
d-ronnqvist Dec 13, 2021
0240e39
Remove `createDirectoryForLastPathComponent` argument in test helper
d-ronnqvist Dec 13, 2021
af38caa
Update test helper documentation
d-ronnqvist Dec 13, 2021
8c614df
Prefer `FileManager.temporaryDirectory` property in ConvertAction
d-ronnqvist Dec 13, 2021
2566321
Merge branch 'main' into ci-test-separation
d-ronnqvist Dec 21, 2021
5d4ce3e
Move `Files` and `Folder` types into test utility target
d-ronnqvist Dec 21, 2021
db26074
Add additional safety checks when creating and removing temporary dir…
d-ronnqvist Dec 22, 2021
a3035e2
Replace `TempFolder` test class with `createTempFolder` test function
d-ronnqvist Dec 22, 2021
cc3d57d
Use XCTUnwrap instead of force unwrap in test helper
d-ronnqvist Dec 22, 2021
3105c5c
Merge branch 'main' into ci-test-separation
d-ronnqvist Dec 22, 2021
0436ad5
Also shadow `FileManager.temporaryDirectory` in tests
d-ronnqvist Dec 22, 2021
d71cc38
Add temp directory test helper variant with "named" argument
d-ronnqvist Dec 22, 2021
1e5de9f
Fix test helper syntax in disabled preview server test
d-ronnqvist Dec 22, 2021
4dac6a4
Re-enable one file monitoring test
d-ronnqvist Dec 22, 2021
745e09a
Merge branch 'main' into ci-test-separation
d-ronnqvist Jan 8, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,10 @@ let package = Package(
]),
.testTarget(
name: "SwiftDocCTests",
dependencies: ["SwiftDocC"],
dependencies: [
"SwiftDocC",
"SwiftDocCTestUtilities",
],
resources: [
.copy("Test Resources"),
.copy("Test Bundles"),
Expand All @@ -66,11 +69,17 @@ let package = Package(
dependencies: [
"SwiftDocCUtilities",
"SwiftDocC",
"SwiftDocCTestUtilities",
],
resources: [
.copy("Test Resources"),
.copy("Test Bundles"),
]),

// Test utility library
.target(
name: "SwiftDocCTestUtilities",
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right now this target is only used to share one file between the two test targets but there are more code that could be moved out of SwiftDocC itself into this test target instead.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is great! I've been wanting something like this for a while. No need to do it now- but eventually moving all of our test bundles over to this library would have a big impact on our repo size I think.

dependencies: []),

// Command-line tool
.executableTarget(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,23 @@
*/

import Foundation
import XCTest

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

/// An abstract representation of a file (or folder).
protocol File {
public protocol File {
/// The name of the file.
var name: String { get }

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

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

/// An item which provides data.
protocol DataRepresentable {
public protocol DataRepresentable {
func data() throws -> Data
}

/// `DataRepresentable` can automatically write itself to disk via `Data.write(to:)`
extension DataRepresentable {
public extension DataRepresentable {
func write(to url: URL) throws {
try data().write(to: url)
}
Expand All @@ -49,17 +50,22 @@ extension DataRepresentable {
// MARK: -

/// An abstract representation of a folder, containing some files or folders.
struct Folder: File {
let name: String
public struct Folder: File {
public init(name: String, content: [File]) {
self.name = name
self.content = content
}

public let name: String

/// The files and sub folders that this folder contains.
let content: [File]
public let content: [File]

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

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

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

/// A representation of an Info.plist file.
struct InfoPlist: File, DataRepresentable {
let name = "Info.plist"
public struct InfoPlist: File, DataRepresentable {
public let name = "Info.plist"

/// The information that the Into.plist file contains.
let content: Content
public let content: Content

init(displayName: String, identifier: String, versionString: String = "1.0", developmentRegion: String = "en") {
public init(displayName: String, identifier: String, versionString: String = "1.0", developmentRegion: String = "en") {
self.content = Content(
displayName: displayName,
identifier: identifier,
Expand All @@ -96,11 +102,11 @@ struct InfoPlist: File, DataRepresentable {
)
}

struct Content: Codable, Equatable {
let displayName: String
let identifier: String
let versionString: String
let developmentRegion: String
public struct Content: Codable, Equatable {
public let displayName: String
public let identifier: String
public let versionString: String
public let developmentRegion: String

fileprivate init(displayName: String, identifier: String, versionString: String, developmentRegion: String) {
self.displayName = displayName
Expand All @@ -117,7 +123,7 @@ struct InfoPlist: File, DataRepresentable {
}
}

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

/// A representation of a text file with some UTF-8 content.
struct TextFile: File, DataRepresentable {
let name: String
public struct TextFile: File, DataRepresentable {
public init(name: String, utf8Content: String) {
self.name = name
self.utf8Content = utf8Content
}

public let name: String

/// The UTF8 content of the file.
let utf8Content: String
public let utf8Content: String

func data() throws -> Data {
public func data() throws -> Data {
return utf8Content.data(using: .utf8)!
}
}

/// A representation of a text file with some UTF-8 content.
struct JSONFile<Content: Codable>: File, DataRepresentable {
let name: String
public struct JSONFile<Content: Codable>: File, DataRepresentable {
public init(name: String, content: Content) {
self.name = name
self.content = content
}

public let name: String

/// The UTF8 content of the file.
let content: Content
public let content: Content

func data() throws -> Data {
public func data() throws -> Data {
return try JSONEncoder().encode(content)
}
}

/// A copy of another file on disk somewhere.
struct CopyOfFile: File, DataRepresentable {
enum Error: DescribedError {
public struct CopyOfFile: File, DataRepresentable {
enum Error: LocalizedError {
case notAFile(URL)
var errorDescription: String {
switch self {
case .notAFile(let url): return "Original url is not a file: \(url.path.singleQuoted)"
case .notAFile(let url): return "Original url is not a file: '\(url.path)'"
}
}
}

/// The original file.
let original: URL
let name: String
public let original: URL
public let name: String

init(original: URL, newName: String? = nil) {
public init(original: URL, newName: String? = nil) {
self.original = original
self.name = newName ?? original.lastPathComponent
}

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

func write(to url: URL) throws {
public func write(to url: URL) throws {
try FileManager.default.copyItem(at: original, to: url)
}
}

struct CopyOfFolder: File {
public struct CopyOfFolder: File {
/// The original file.
let original: URL
let name: String
public let name: String
let shouldCopyFile: (URL) -> Bool

init(original: URL, newName: String? = nil, filter shouldCopyFile: @escaping (URL) -> Bool = { _ in true }) {
public init(original: URL, newName: String? = nil, filter shouldCopyFile: @escaping (URL) -> Bool = { _ in true }) {
self.original = original
self.name = newName ?? original.lastPathComponent
self.shouldCopyFile = shouldCopyFile
}

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

/// A file backed by `Data`.
struct DataFile: File, DataRepresentable {
var name: String
public struct DataFile: File, DataRepresentable {
public var name: String
var _data: Data

init(name: String, data: Data) {
public init(name: String, data: Data) {
self.name = name
self._data = data
}

func data() throws -> Data {
public func data() throws -> Data {
return _data
}
}

/// A temporary folder which can write files to a temporary location on disk and
/// will delete itself when its instance is released from memory.
class TempFolder: File {
let name: String
let url: URL

/// The files and sub folders that this folder contains.
let content: [File]

func write(to url: URL) throws {
try FileManager.default.createDirectory(at: url, withIntermediateDirectories: false, attributes: nil)
for file in content {
try file.write(inside: url)
}
}

init(content: [File]) throws {
self.content = content

url = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(UUID().uuidString)
name = url.absoluteString

try write(to: url)
}

deinit {
try? FileManager.default.removeItem(at: url)
extension XCTestCase {
/// Creates a ``Folder`` and writes its content to a temporary location on disk.
///
/// - Parameters:
/// - content: The files and subfolders to write to a temporary location
/// - Returns: The temporary location where the temporary folder was written.
public func createTempFolder(content: [File]) throws -> URL {
let temporaryDirectory = try createTemporaryDirectory().appendingPathComponent("TempDirectory-\(ProcessInfo.processInfo.globallyUniqueString)")
let folder = Folder(name: temporaryDirectory.lastPathComponent, content: content)
try folder.write(to: temporaryDirectory)
return temporaryDirectory
}
}
Loading