Skip to content

Commit 0069e8b

Browse files
authored
Refactor CreateAPITests structure and snapshotting (CreateAPI#128)
* Introduce new Snapshotter class as an entry point for running shapshot tests * Remove test support outside of CreateAPITests target * Update AllPackages to support new dir structure * Move Helpers.swift into ./Helpers as TemporaryDirectory.swift * Move SpecFixture type into its own file
1 parent 21109ae commit 0069e8b

File tree

1,720 files changed

+158
-99
lines changed

Some content is hidden

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

1,720 files changed

+158
-99
lines changed

Package.swift

+1-3
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,7 @@ let package = Package(
4141
.testTarget(
4242
name: "create-api-tests",
4343
dependencies: ["create-api"],
44-
path: "Tests/CreateAPITests",
45-
exclude: ["AllPackages"],
46-
resources: [.copy("Expected"), .copy("Specs")]
44+
path: "Tests/CreateAPITests"
4745
),
4846
.testTarget(
4947
name: "CreateOptionsTests",

Scripts/test-generated.sh

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@
22

33
set -e
44

5-
cd ./Tests/CreateAPITests/AllPackages
5+
cd ./Tests/Support/AllPackages
66
swift build

Tests/CreateAPITests/GenerateTestCase.swift

+5-23
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,9 @@
11
import XCTest
22
@testable import create_api
33

4-
class GenerateTestCase: XCTestCase {
5-
struct SpecFixture {
6-
let name: String
7-
let ext: String
8-
9-
static let cookpad = SpecFixture(name: "cookpad", ext: "json")
10-
static let discriminator = SpecFixture(name: "discriminator", ext: "yaml")
11-
static let edgecases = SpecFixture(name: "edgecases", ext: "yaml")
12-
static let github = SpecFixture(name: "github", ext: "yaml")
13-
static let inlining = SpecFixture(name: "inlining", ext: "yaml")
14-
static let petstore = SpecFixture(name: "petstore", ext: "yaml")
15-
static let stripParentNameNestedObjects = SpecFixture(name: "strip-parent-name-nested-objects", ext: "yaml")
16-
static let testQueryParameters = SpecFixture(name: "test-query-parameters", ext: "yaml")
17-
18-
var path: String {
19-
pathForSpec(named: name, ext: ext)
20-
}
21-
}
22-
4+
class GenerateTestCase: XCTestCase {
235
var temp: TemporaryDirectory!
6+
let snapshotter: Snapshotter = .shared
247

258
override func setUp() {
269
super.setUp()
@@ -40,13 +23,12 @@ class GenerateTestCase: XCTestCase {
4023
arguments: [String] = [],
4124
configuration: String? = nil
4225
) throws {
43-
let output = temp.url
26+
let outputURL = temp.url
4427
.appendingPathComponent("Output")
45-
.path
4628

4729
// Append the output, config and spec to the arguments passed
4830
var arguments = arguments
49-
arguments.append(contentsOf: ["--output", output])
31+
arguments.append(contentsOf: ["--output", outputURL.path])
5032
if let configuration = configuration {
5133
arguments.append(contentsOf: ["--config", self.config(configuration, ext: "yml")])
5234
}
@@ -56,7 +38,7 @@ class GenerateTestCase: XCTestCase {
5638
try generate(arguments)
5739

5840
// Then the output should match what was generated
59-
try compare(expected: name, actual: output)
41+
try snapshotter.processSnapshot(at: outputURL, against: name)
6042
}
6143

6244
func generate(_ arguments: [String]) throws {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import Foundation
2+
3+
struct SpecFixture {
4+
let name: String
5+
let ext: String
6+
7+
static let cookpad = SpecFixture(name: "cookpad", ext: "json")
8+
static let discriminator = SpecFixture(name: "discriminator", ext: "yaml")
9+
static let edgecases = SpecFixture(name: "edgecases", ext: "yaml")
10+
static let github = SpecFixture(name: "github", ext: "yaml")
11+
static let inlining = SpecFixture(name: "inlining", ext: "yaml")
12+
static let petstore = SpecFixture(name: "petstore", ext: "yaml")
13+
static let stripParentNameNestedObjects = SpecFixture(name: "strip-parent-name-nested-objects", ext: "yaml")
14+
static let testQueryParameters = SpecFixture(name: "test-query-parameters", ext: "yaml")
15+
16+
var path: String {
17+
URL(fileURLWithPath: #filePath)
18+
.appendingPathComponent("..")
19+
.appendingPathComponent("..")
20+
.appendingPathComponent("..")
21+
.appendingPathComponent("Support")
22+
.appendingPathComponent("Specs")
23+
.appendingPathComponent(name)
24+
.appendingPathExtension(ext)
25+
.resolvingSymlinksInPath()
26+
.path
27+
}
28+
}

Tests/CreateAPITests/Helpers.swift renamed to Tests/CreateAPITests/Helpers/TemporaryDirectory.swift

+1-14
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,4 @@
1-
import XCTest
2-
3-
func file(named name: String, ext: String) -> Data {
4-
let url = Bundle.module.url(forResource: name, withExtension: ext)
5-
return try! Data(contentsOf: url!)
6-
}
7-
8-
func fileExists(named name: String, ext: String) -> Bool {
9-
Bundle.module.url(forResource: name, withExtension: ext) != nil
10-
}
11-
12-
func pathForSpec(named name: String, ext: String) -> String {
13-
Bundle.module.url(forResource: name, withExtension: ext, subdirectory: "Specs")!.path
14-
}
1+
import Foundation
152

163
struct TemporaryDirectory {
174
let url: URL
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import Foundation
2+
3+
struct DirectoryDiffer {
4+
func compare(_ expectedURL: URL, against actualURL: URL) throws {
5+
// TODO: Rewrite differ and leverage https://github.com/pointfreeco/swift-custom-dump?
6+
try diff(expectedURL: expectedURL, actualURL: actualURL)
7+
}
8+
}

Tests/CreateAPITests/Snapshots.swift renamed to Tests/CreateAPITests/Snapshotting/LegacyDiffer.swift

+1-20
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,8 @@
11
import XCTest
22

3-
private let generateSnapshots = false
43
private let openDiff = false
54

6-
func compare(expected: String, actual: String) throws {
7-
let expectedURL = Bundle.module.resourceURL!
8-
.appendingPathComponent("Expected")
9-
.appendingPathComponent(expected)
10-
let actualURL = URL(fileURLWithPath: actual)
11-
12-
if generateSnapshots || !FileManager.default.fileExists(atPath: expectedURL.path) {
13-
let destinationURL = URL(fileURLWithPath: #filePath)
14-
.deletingLastPathComponent()
15-
.appendingPathComponent("Expected")
16-
.appendingPathComponent(actualURL.lastPathComponent)
17-
try? FileManager.default.removeItem(at: destinationURL)
18-
try FileManager.default.copyItem(at: actualURL, to: destinationURL)
19-
} else {
20-
try diff(expectedURL: expectedURL, actualURL: actualURL)
21-
}
22-
}
23-
24-
private func diff(expectedURL: URL, actualURL: URL) throws {
5+
func diff(expectedURL: URL, actualURL: URL) throws {
256
func contents(at url: URL) throws -> [URL] {
267
try FileManager.default.contentsOfDirectory(at: url, includingPropertiesForKeys: [.isDirectoryKey], options: [.skipsHiddenFiles])
278
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import Foundation
2+
3+
private let generateSnapshots = false
4+
5+
class Snapshotter {
6+
enum Behavior {
7+
case assert, record
8+
}
9+
10+
static let shared = Snapshotter(behavior: generateSnapshots ? .record : .assert)
11+
12+
let behavior: Behavior
13+
private(set) var recordedSnapshots: [URL]
14+
private let fileManager: FileManager
15+
16+
private init(behavior: Behavior) {
17+
self.behavior = behavior
18+
self.recordedSnapshots = []
19+
self.fileManager = .default
20+
}
21+
22+
/// Processes a snapshot at the given path by either asserting a match against a stored snapshot, or rewriting it.
23+
func processSnapshot(at generatedURL: URL, against name: String) throws {
24+
let snapshotURL = snapshotLocation(for: name)
25+
26+
switch behavior {
27+
case .assert:
28+
try DirectoryDiffer().compare(snapshotURL, against: generatedURL)
29+
30+
case .record:
31+
try resetAllSnapshotsIfNeeded()
32+
try resetDirectory(at: snapshotURL, create: false)
33+
try fileManager.copyItem(at: generatedURL, to: snapshotURL)
34+
recordedSnapshots.append(snapshotURL)
35+
}
36+
}
37+
38+
/// The location of a generated snapshot stored on disk
39+
private func snapshotLocation(for name: String) -> URL {
40+
snapshotDirectory.appendingPathComponent(name)
41+
}
42+
43+
/// Deletes the contents of the snapshot directory if it was already there in preparation to re-record all outputs
44+
private func resetAllSnapshotsIfNeeded() throws {
45+
guard shouldResetAllSnapshots else { return }
46+
try resetDirectory(at: snapshotDirectory)
47+
}
48+
49+
private var shouldResetAllSnapshots: Bool {
50+
guard recordedSnapshots.isEmpty else { return false }
51+
return true // TODO: Don't reset snapshot directory when only running a subset of tests
52+
}
53+
54+
/// The URL to the snapshot storage directory
55+
var snapshotDirectory: URL {
56+
URL(fileURLWithPath: #filePath)
57+
.appendingPathComponent("..")
58+
.appendingPathComponent("..")
59+
.appendingPathComponent("..")
60+
.appendingPathComponent("Support")
61+
.appendingPathComponent("Snapshots")
62+
.resolvingSymlinksInPath()
63+
}
64+
65+
/// Deletes and recreates the directory at the given location.
66+
private func resetDirectory(at url: URL, create: Bool = true) throws {
67+
if fileManager.fileExists(atPath: url.path) {
68+
try fileManager.removeItem(at: url)
69+
}
70+
71+
if create {
72+
try fileManager.createDirectory(at: url, withIntermediateDirectories: true)
73+
}
74+
}
75+
}

Tests/CreateAPITests/AllPackages/Package.swift renamed to Tests/Support/AllPackages/Package.swift

+38-38
Original file line numberDiff line numberDiff line change
@@ -15,44 +15,44 @@ let package = Package(
1515
.library(name: "AllPackages", targets: ["AllPackages"])
1616
],
1717
dependencies: [
18-
.package(path: "../Expected/cookpad"),
19-
.package(path: "../Expected/discriminator"),
20-
.package(path: "../Expected/edgecases-change-access-control"),
21-
.package(path: "../Expected/edgecases-coding-keys"),
22-
.package(path: "../Expected/edgecases-default"),
23-
.package(path: "../Expected/edgecases-disable-acronyms"),
24-
.package(path: "../Expected/edgecases-disable-enums"),
25-
.package(path: "../Expected/edgecases-indent-with-two-width-spaces"),
26-
.package(path: "../Expected/edgecases-int32-int64"),
27-
.package(path: "../Expected/edgecases-rename"),
28-
.package(path: "../Expected/edgecases-rename-properties"),
29-
.package(path: "../Expected/edgecases-tabs"),
30-
.package(path: "../Expected/edgecases-yaml-config"),
31-
.package(path: "../Expected/inlining-default"),
32-
.package(path: "../Expected/OctoKit"),
33-
.package(path: "../Expected/petstore-base-class"),
34-
.package(path: "../Expected/petstore-change-entityname"),
35-
.package(path: "../Expected/petstore-change-namespace-when-operations-style"),
36-
.package(path: "../Expected/petstore-change-namespace-when-rest-style"),
37-
.package(path: "../Expected/petstore-custom-imports"),
38-
.package(path: "../Expected/petstore-default"),
39-
.package(path: "../Expected/petstore-disable-comments"),
40-
.package(path: "../Expected/petstore-disable-init-with-coder"),
41-
.package(path: "../Expected/petstore-disable-inlining"),
42-
.package(path: "../Expected/petstore-disable-mutable-properties"),
43-
.package(path: "../Expected/petstore-enable-mutable-properties"),
44-
.package(path: "../Expected/petstore-entity-exclude"),
45-
.package(path: "../Expected/petstore-filename-template"),
46-
.package(path: "../Expected/petstore-generate-classes"),
47-
.package(path: "../Expected/petstore-identifiable"),
48-
.package(path: "../Expected/petstore-only-schemas"),
49-
.package(path: "../Expected/petstore-single-threaded"),
50-
.package(path: "../Expected/petstore-some-entities-as-classes"),
51-
.package(path: "../Expected/petstore-some-entities-as-structs"),
52-
.package(path: "../Expected/petstore-merge-sources"),
53-
.package(path: "../Expected/strip-parent-name-nested-objects-default"),
54-
.package(path: "../Expected/strip-parent-name-nested-objects-enabled"),
55-
.package(path: "../Expected/test-query-parameters")
18+
.package(path: "../Snapshots/cookpad"),
19+
.package(path: "../Snapshots/discriminator"),
20+
.package(path: "../Snapshots/edgecases-change-access-control"),
21+
.package(path: "../Snapshots/edgecases-coding-keys"),
22+
.package(path: "../Snapshots/edgecases-default"),
23+
.package(path: "../Snapshots/edgecases-disable-acronyms"),
24+
.package(path: "../Snapshots/edgecases-disable-enums"),
25+
.package(path: "../Snapshots/edgecases-indent-with-two-width-spaces"),
26+
.package(path: "../Snapshots/edgecases-int32-int64"),
27+
.package(path: "../Snapshots/edgecases-rename"),
28+
.package(path: "../Snapshots/edgecases-rename-properties"),
29+
.package(path: "../Snapshots/edgecases-tabs"),
30+
.package(path: "../Snapshots/edgecases-yaml-config"),
31+
.package(path: "../Snapshots/inlining-default"),
32+
.package(path: "../Snapshots/OctoKit"),
33+
.package(path: "../Snapshots/petstore-base-class"),
34+
.package(path: "../Snapshots/petstore-change-entityname"),
35+
.package(path: "../Snapshots/petstore-change-namespace-when-operations-style"),
36+
.package(path: "../Snapshots/petstore-change-namespace-when-rest-style"),
37+
.package(path: "../Snapshots/petstore-custom-imports"),
38+
.package(path: "../Snapshots/petstore-default"),
39+
.package(path: "../Snapshots/petstore-disable-comments"),
40+
.package(path: "../Snapshots/petstore-disable-init-with-coder"),
41+
.package(path: "../Snapshots/petstore-disable-inlining"),
42+
.package(path: "../Snapshots/petstore-disable-mutable-properties"),
43+
.package(path: "../Snapshots/petstore-enable-mutable-properties"),
44+
.package(path: "../Snapshots/petstore-entity-exclude"),
45+
.package(path: "../Snapshots/petstore-filename-template"),
46+
.package(path: "../Snapshots/petstore-generate-classes"),
47+
.package(path: "../Snapshots/petstore-identifiable"),
48+
.package(path: "../Snapshots/petstore-only-schemas"),
49+
.package(path: "../Snapshots/petstore-single-threaded"),
50+
.package(path: "../Snapshots/petstore-some-entities-as-classes"),
51+
.package(path: "../Snapshots/petstore-some-entities-as-structs"),
52+
.package(path: "../Snapshots/petstore-merge-sources"),
53+
.package(path: "../Snapshots/strip-parent-name-nested-objects-default"),
54+
.package(path: "../Snapshots/strip-parent-name-nested-objects-enabled"),
55+
.package(path: "../Snapshots/test-query-parameters")
5656
],
5757
targets: [
5858
.target(

0 commit comments

Comments
 (0)