Skip to content

Commit cff1761

Browse files
committed
Resolve warnings for Xcode 14+
1 parent b2998d4 commit cff1761

13 files changed

+296
-241
lines changed

.swiftlint.yml

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ disabled_rules:
33
- identifier_name
44
- large_tuple
55
- operator_whitespace
6+
- unused_declaration
7+
- unused_import
8+
69
opt_in_rules:
710
- closure_body_length
811
- closure_end_indentation
@@ -46,13 +49,12 @@ opt_in_rules:
4649
- toggle_bool
4750
- trailing_whitespace
4851
- unneeded_parentheses_in_closure_argument
49-
- unused_declaration
50-
- unused_import
5152
- yoda_condition
5253
- xct_specific_matcher
5354

54-
attributes:
55-
always_on_same_line: ["@IBAction", "@objc"]
55+
analyzer_rules:
56+
- unused_declaration
57+
- unused_import
5658

5759
line_length:
5860
warning: 180
@@ -65,6 +67,9 @@ modifier_order:
6567
nesting:
6668
type_level: 2
6769

70+
included:
71+
- Sources
72+
6873
excluded:
6974
- Carthage
7075
- Tests

Configurations/Target-macOS-Tests-Shared.xcconfig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//:configuration = Debug
22
SDKROOT = macosx
3-
MACOSX_DEPLOYMENT_TARGET = 10.15
3+
MACOSX_DEPLOYMENT_TARGET = 11.0
44

55
CODE_SIGN_STYLE = Automatic
66
COMBINE_HIDPI_IMAGES = YES

PactSwift.xcfilelist

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
$(SRCROOT)/Sources/MockService+Concurrency.swift
2+
$(SRCROOT)/Sources/MockService+Extension.swift
13
$(SRCROOT)/Sources/Extensions/Bundle+PactSwift.swift
24
$(SRCROOT)/Sources/Extensions/Task+Timeout.swift
35
$(SRCROOT)/Sources/Extensions/Date+PactSwift.swift

PactSwift.xcodeproj/project.pbxproj

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,10 @@
7070
AD78FB49264FD21900765BD3 /* PactContractTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD78FB47264FD21900765BD3 /* PactContractTests.swift */; };
7171
AD8546F42513601800211E28 /* RandomDateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD8546F32513601800211E28 /* RandomDateTests.swift */; };
7272
AD8546F52513601800211E28 /* RandomDateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD8546F32513601800211E28 /* RandomDateTests.swift */; };
73+
AD854D882A7E75080005C502 /* MockService+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD854D872A7E75080005C502 /* MockService+Extension.swift */; };
74+
AD854D892A7E75080005C502 /* MockService+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD854D872A7E75080005C502 /* MockService+Extension.swift */; };
75+
AD854D8B2A7E75E20005C502 /* MockService+Concurrency.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD854D8A2A7E75E20005C502 /* MockService+Concurrency.swift */; };
76+
AD854D8C2A7E75E20005C502 /* MockService+Concurrency.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD854D8A2A7E75E20005C502 /* MockService+Concurrency.swift */; };
7377
AD879B9D258242AC00F85B0B /* PactSwiftVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD879B9C258242AC00F85B0B /* PactSwiftVersion.swift */; };
7478
AD879B9E258242AC00F85B0B /* PactSwiftVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD879B9C258242AC00F85B0B /* PactSwiftVersion.swift */; };
7579
AD881808242C715B00BF510D /* PactSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AD8817FE242C715A00BF510D /* PactSwift.framework */; };
@@ -311,6 +315,8 @@
311315
AD78FB47264FD21900765BD3 /* PactContractTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PactContractTests.swift; sourceTree = "<group>"; };
312316
AD8546F025135F4200211E28 /* RandomDate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RandomDate.swift; sourceTree = "<group>"; };
313317
AD8546F32513601800211E28 /* RandomDateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RandomDateTests.swift; sourceTree = "<group>"; };
318+
AD854D872A7E75080005C502 /* MockService+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MockService+Extension.swift"; sourceTree = "<group>"; };
319+
AD854D8A2A7E75E20005C502 /* MockService+Concurrency.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MockService+Concurrency.swift"; sourceTree = "<group>"; };
314320
AD879B9C258242AC00F85B0B /* PactSwiftVersion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PactSwiftVersion.swift; sourceTree = "<group>"; };
315321
AD8817FE242C715A00BF510D /* PactSwift.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = PactSwift.framework; sourceTree = BUILT_PRODUCTS_DIR; };
316322
AD881801242C715A00BF510D /* PactSwift.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PactSwift.h; sourceTree = "<group>"; };
@@ -460,13 +466,6 @@
460466
path = Matchers;
461467
sourceTree = "<group>";
462468
};
463-
AD1AE4BA26BBC956001E09E2 /* Frameworks */ = {
464-
isa = PBXGroup;
465-
children = (
466-
);
467-
name = Frameworks;
468-
sourceTree = "<group>";
469-
};
470469
AD641A362434354C00785CE1 /* Model */ = {
471470
isa = PBXGroup;
472471
children = (
@@ -546,7 +545,6 @@
546545
ADD6993D242C861600C5C2C2 /* .swiftlint.yml */,
547546
ADCB2562242C83E20048BD18 /* Configurations */,
548547
ADC6FAE024302BA800026714 /* Documentation */,
549-
AD1AE4BA26BBC956001E09E2 /* Frameworks */,
550548
ADF2E582267462090029507D /* Package.swift */,
551549
AD8817FF242C715A00BF510D /* Products */,
552550
AD8A32D6242CCF5600283080 /* Scripts */,
@@ -633,6 +631,8 @@
633631
ADCB2563242C844B0048BD18 /* Headers */,
634632
AD01C9392432A31F00B75C9D /* Matchers */,
635633
AD10361324468AB3002C97CA /* MockService.swift */,
634+
AD854D8A2A7E75E20005C502 /* MockService+Concurrency.swift */,
635+
AD854D872A7E75080005C502 /* MockService+Extension.swift */,
636636
ADB7C167243327E300A16CDE /* Model */,
637637
AD8DF434243C53750062CB1A /* PactBuilder.swift */,
638638
ADF17CB926B5019B008E7ECD /* PFMockService.swift */,
@@ -1087,6 +1087,7 @@
10871087
buildActionMask = 2147483647;
10881088
files = (
10891089
AD54435E27D32DCA00D4C464 /* DateTimeExpression.swift in Sources */,
1090+
AD854D8B2A7E75E20005C502 /* MockService+Concurrency.swift in Sources */,
10901091
AD641A3C24344ED400785CE1 /* Pacticipant.swift in Sources */,
10911092
ADA17E41251377A4004F1A82 /* Date+PactSwift.swift in Sources */,
10921093
AD879B9D258242AC00F85B0B /* PactSwiftVersion.swift in Sources */,
@@ -1133,6 +1134,7 @@
11331134
AD4B97102513A3DB00C37560 /* RandomString.swift in Sources */,
11341135
AD8DF439243EEBDB0062CB1A /* SomethingLike.swift in Sources */,
11351136
AD9D7D9026C8B3DA00FE4137 /* FromProviderState.swift in Sources */,
1137+
AD854D882A7E75080005C502 /* MockService+Extension.swift in Sources */,
11361138
ADD3C27424416CDF002E73B9 /* EqualTo.swift in Sources */,
11371139
ADA4B1E426D31EDA00A5AE88 /* VersionSelector.swift in Sources */,
11381140
ADDE4704244D101700E4F7EE /* ErrorReportable.swift in Sources */,
@@ -1215,6 +1217,7 @@
12151217
buildActionMask = 2147483647;
12161218
files = (
12171219
AD54435F27D32DCA00D4C464 /* DateTimeExpression.swift in Sources */,
1220+
AD854D8C2A7E75E20005C502 /* MockService+Concurrency.swift in Sources */,
12181221
AD8FC7E42463BB9F00361854 /* Interaction.swift in Sources */,
12191222
ADA17E42251377A4004F1A82 /* Date+PactSwift.swift in Sources */,
12201223
AD879B9E258242AC00F85B0B /* PactSwiftVersion.swift in Sources */,
@@ -1261,6 +1264,7 @@
12611264
AD4B97112513A3DB00C37560 /* RandomString.swift in Sources */,
12621265
AD8FC7ED2463BB9F00361854 /* Response.swift in Sources */,
12631266
AD9D7D9126C8B3DA00FE4137 /* FromProviderState.swift in Sources */,
1267+
AD854D892A7E75080005C502 /* MockService+Extension.swift in Sources */,
12641268
AD8FC7F22463BBB800361854 /* DecimalLike.swift in Sources */,
12651269
ADA4B1E526D31EDA00A5AE88 /* VersionSelector.swift in Sources */,
12661270
AD8FC7EB2463BB9F00361854 /* ProviderState.swift in Sources */,

Scripts/build_file_list_and_swiftlint

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ if which swiftlint >/dev/null; then
4141

4242
# Run swiftlint (TODO: - swiftlint by iterating through the $1.xcfilelist)
4343
# swiftlint --config $2 -- #filename0 #filename1 #filename2 ...
44-
swiftlint --path Sources --config $2
44+
swiftlint --config $2
4545

4646
# Output an empty derived file
4747
touch $DERIVED_FILE_DIR/swiftlint.txt
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
//
2+
// Created by Marko Justinek on 5/8/2023.
3+
// Copyright © 2023 Marko Justinek. All rights reserved.
4+
//
5+
// Permission to use, copy, modify, and/or distribute this software for any
6+
// purpose with or without fee is hereby granted, provided that the above
7+
// copyright notice and this permission notice appear in all copies.
8+
//
9+
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10+
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11+
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
12+
// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13+
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14+
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
15+
// IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16+
//
17+
18+
import Foundation
19+
20+
#if canImport(_Concurrency) && compiler(>=5.7)
21+
@_implementationOnly import PactSwiftMockServer
22+
23+
public extension MockService {
24+
25+
/// Runs the Pact test against the code making the API request
26+
///
27+
/// - Parameters:
28+
/// - file: The file to report the failing test in
29+
/// - line: The line on which to report the failing test
30+
/// - verify: An array of specific `Interaction`s to verify. If none provided, the latest defined interaction is used
31+
/// - timeout: Time before the test times out. Default is 10 seconds
32+
/// - testFunction: Your async code making the API request
33+
///
34+
/// The `testFunction` closure is passed a `String` representing the url of the active Mock Server.
35+
///
36+
/// ```
37+
/// try await mockService.run { baseURL in
38+
/// // async code making the request with provided `baseURL`
39+
/// // assert response can be processed
40+
/// }
41+
/// ```
42+
///
43+
@available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)
44+
func run(_ file: FileString? = #file, line: UInt? = #line, verify interactions: [Interaction]? = nil, timeout: TimeInterval? = nil, testFunction: @escaping @Sendable (_ baseURL: String) async throws -> Void) async throws {
45+
// Use the provided set or if not provided only the current interaction
46+
pact.interactions = interactions ?? [currentInteraction]
47+
48+
if checkForInvalidInteractions(pact.interactions, file: file, line: line) {
49+
// Remove interactions with errors
50+
pact.interactions.removeAll { $0.encodingErrors.isEmpty == false }
51+
self.interactions.removeAll()
52+
} else {
53+
// Prepare a brand spanking new MockServer (Mock Provider) on its own port
54+
let mockServer = MockServer()
55+
56+
// Set the expectations so we don't wait for this async magic indefinitely
57+
try await setupPactInteraction(timeout: timeout ?? Constants.kTimeout, file: file, line: line, mockServer: mockServer, testFunction: testFunction)
58+
59+
// At the same time start listening to verification that Mock Server received the expected request
60+
try await verifyPactInteraction(timeout: timeout ?? Constants.kTimeout, file: file, line: line, mockServer: mockServer)
61+
}
62+
}
63+
}
64+
65+
// MARK: - Internal
66+
67+
extension MockService {
68+
69+
@available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)
70+
func setupPactInteraction(timeout: TimeInterval, file: FileString?, line: UInt?, mockServer: MockServer, testFunction: @escaping @Sendable (String) async throws -> Void) async throws {
71+
Logger.log(message: "Setting up pact test", data: pact.data)
72+
do {
73+
// Set up a Mock Server with Pact data and on desired http protocol
74+
try await mockServer.setup(pact: pact.data!, protocol: transferProtocolScheme)
75+
76+
// If Mock Server spun up, run the test function
77+
let task = Task(timeout: timeout) {
78+
try await testFunction(mockServer.baseUrl)
79+
}
80+
// await task completion (value is Void)
81+
try await task.value
82+
} catch {
83+
// Failed to spin up a Mock Server. This could be due to bad Pact data. Most likely to Pact data.
84+
failWith((error as? MockServerError)?.description ?? error.localizedDescription, file: file, line: line)
85+
throw error
86+
}
87+
}
88+
89+
@available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)
90+
func verifyPactInteraction(timeout: TimeInterval, file: FileString?, line: UInt?, mockServer: MockServer) async throws {
91+
do {
92+
let task = Task(timeout: timeout) {
93+
try await mockServer.verify()
94+
}
95+
// await task completion (value is discarded)
96+
_ = try await task.value
97+
98+
// If the comsumer (in testFunction:) made the promised request to Mock Server, go and finalize the test.
99+
// Only finalize when running in simulator or macOS. Running on a physical iOS device makes little sense due to
100+
// writing a pact file to device's disk. `libpact_ffi` does the actual file writing it writes it onto the
101+
// disk of the device it is being run on.
102+
#if targetEnvironment(simulator) || os(macOS)
103+
let message = try await finalize(file: file, line: line)
104+
Logger.log(message: message, data: self.pact.data)
105+
#else
106+
print("[INFO]: Running on an iOS device. Writing Pact interaction into a contract skipped.")
107+
#endif
108+
} catch {
109+
failWith((error as? MockServerError)?.description ?? error.localizedDescription, file: file, line: line)
110+
throw error
111+
}
112+
}
113+
114+
/// Writes a Pact contract file in JSON format
115+
///
116+
/// By default Pact contracts are written to `/tmp/pacts` folder.
117+
/// Set `PACT_OUTPUT_DIR` to `$(PATH)/to/desired/dir/` in `Build` phase of your `Scheme` to change the location.
118+
///
119+
@available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)
120+
func finalize(file: FileString? = nil, line: UInt? = nil) async throws -> String {
121+
// Spin up a fresh Mock Server with a directory to write to
122+
let mockServer = MockServer(directory: pactsDirectory, merge: self.merge)
123+
124+
// Gather all the interactions this MockService has received to set up and prepare Pact data with them all
125+
pact.interactions = interactions.filter { $0.encodingErrors.isEmpty }
126+
127+
// Validate the Pact `Data` is hunky dory
128+
guard let pactData = pact.data else {
129+
throw MockServerError.nullPointer
130+
}
131+
132+
// Ask Mock Server to do the actual Pact file writing to disk
133+
do {
134+
return try await mockServer.finalize(pact: pactData)
135+
} catch {
136+
failWith((error as? MockServerError)?.description ?? error.localizedDescription, file: file, line: line)
137+
throw error
138+
}
139+
}
140+
}
141+
#endif
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
//
2+
// Created by Marko Justinek on 5/8/2023.
3+
// Copyright © 2023 Marko Justinek. All rights reserved.
4+
//
5+
// Permission to use, copy, modify, and/or distribute this software for any
6+
// purpose with or without fee is hereby granted, provided that the above
7+
// copyright notice and this permission notice appear in all copies.
8+
//
9+
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10+
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11+
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
12+
// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13+
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14+
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
15+
// IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16+
//
17+
18+
import Foundation
19+
import XCTest
20+
21+
#if compiler(>=5.5)
22+
@_implementationOnly import PactSwiftMockServer
23+
#else
24+
import PactSwiftMockServer
25+
#endif
26+
27+
extension MockService {
28+
29+
/// Check there are no invalid interactions
30+
func checkForInvalidInteractions(_ interactions: [Interaction], file: FileString? = nil, line: UInt? = nil) -> Bool {
31+
let errors = interactions.flatMap(\.encodingErrors)
32+
for error in errors {
33+
failWith(error.localizedDescription, file: file, line: line)
34+
}
35+
return errors.isEmpty == false
36+
}
37+
38+
/// Writes a Pact contract file in JSON format
39+
///
40+
/// - parameter completion: Result of the writing the Pact contract to JSON
41+
///
42+
/// By default Pact contracts are written to `/tmp/pacts` folder.
43+
/// Set `PACT_OUTPUT_DIR` to `$(PATH)/to/desired/dir/` in `Build` phase of your `Scheme` to change the location.
44+
///
45+
func finalize(file: FileString? = nil, line: UInt? = nil, completion: ((Result<String, MockServerError>) -> Void)? = nil) {
46+
// Spin up a fresh Mock Server with a directory to write to
47+
let mockServer = MockServer(directory: pactsDirectory, merge: self.merge)
48+
49+
// Gather all the interactions this MockService has received to set up and prepare Pact data with them all
50+
pact.interactions = interactions.filter { $0.encodingErrors.isEmpty }
51+
52+
// Validate the Pact `Data` is hunky dory
53+
guard let pactData = pact.data else {
54+
completion?(.failure(.nullPointer))
55+
return
56+
}
57+
58+
// Ask Mock Server to do the actual Pact file writing to disk
59+
mockServer.finalize(pact: pactData) { [unowned self] in
60+
switch $0 {
61+
case .success(let message):
62+
completion?(.success(message))
63+
case .failure(let error):
64+
failWith(error.description)
65+
completion?(.failure(error))
66+
}
67+
}
68+
}
69+
70+
/// Waits for test to be completed and fails if timed out
71+
func waitForPactTestWith(timeout: TimeInterval, file: FileString?, line: UInt?, action: (@escaping () -> Void) -> Void) {
72+
let expectation = XCTestExpectation(description: "waitForPactTest")
73+
action {
74+
expectation.fulfill()
75+
}
76+
77+
let result = XCTWaiter().wait(for: [expectation], timeout: timeout)
78+
if result != .completed {
79+
let message = "Test did not complete within \(timeout) second timeout! Did you run testCompleted() block?"
80+
if let file = file, let line = line {
81+
errorReporter.reportFailure(message, file: file, line: line)
82+
} else {
83+
errorReporter.reportFailure(message)
84+
}
85+
}
86+
}
87+
88+
/// Fail the test and raise the failure in `file` at `line`
89+
func failWith(_ message: String, file: FileString? = nil, line: UInt? = nil) {
90+
if let file = file, let line = line {
91+
errorReporter.reportFailure(message, file: file, line: line)
92+
} else {
93+
errorReporter.reportFailure(message)
94+
}
95+
}
96+
}

0 commit comments

Comments
 (0)