Skip to content

Commit

Permalink
Resolve warnings for Xcode 14+
Browse files Browse the repository at this point in the history
  • Loading branch information
surpher committed Aug 5, 2023
1 parent b2998d4 commit cff1761
Show file tree
Hide file tree
Showing 13 changed files with 296 additions and 241 deletions.
13 changes: 9 additions & 4 deletions .swiftlint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ disabled_rules:
- identifier_name
- large_tuple
- operator_whitespace
- unused_declaration
- unused_import

opt_in_rules:
- closure_body_length
- closure_end_indentation
Expand Down Expand Up @@ -46,13 +49,12 @@ opt_in_rules:
- toggle_bool
- trailing_whitespace
- unneeded_parentheses_in_closure_argument
- unused_declaration
- unused_import
- yoda_condition
- xct_specific_matcher

attributes:
always_on_same_line: ["@IBAction", "@objc"]
analyzer_rules:
- unused_declaration
- unused_import

line_length:
warning: 180
Expand All @@ -65,6 +67,9 @@ modifier_order:
nesting:
type_level: 2

included:
- Sources

excluded:
- Carthage
- Tests
Expand Down
2 changes: 1 addition & 1 deletion Configurations/Target-macOS-Tests-Shared.xcconfig
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//:configuration = Debug
SDKROOT = macosx
MACOSX_DEPLOYMENT_TARGET = 10.15
MACOSX_DEPLOYMENT_TARGET = 11.0

CODE_SIGN_STYLE = Automatic
COMBINE_HIDPI_IMAGES = YES
Expand Down
2 changes: 2 additions & 0 deletions PactSwift.xcfilelist
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
$(SRCROOT)/Sources/MockService+Concurrency.swift
$(SRCROOT)/Sources/MockService+Extension.swift
$(SRCROOT)/Sources/Extensions/Bundle+PactSwift.swift
$(SRCROOT)/Sources/Extensions/Task+Timeout.swift
$(SRCROOT)/Sources/Extensions/Date+PactSwift.swift
Expand Down
20 changes: 12 additions & 8 deletions PactSwift.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,10 @@
AD78FB49264FD21900765BD3 /* PactContractTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD78FB47264FD21900765BD3 /* PactContractTests.swift */; };
AD8546F42513601800211E28 /* RandomDateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD8546F32513601800211E28 /* RandomDateTests.swift */; };
AD8546F52513601800211E28 /* RandomDateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD8546F32513601800211E28 /* RandomDateTests.swift */; };
AD854D882A7E75080005C502 /* MockService+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD854D872A7E75080005C502 /* MockService+Extension.swift */; };
AD854D892A7E75080005C502 /* MockService+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD854D872A7E75080005C502 /* MockService+Extension.swift */; };
AD854D8B2A7E75E20005C502 /* MockService+Concurrency.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD854D8A2A7E75E20005C502 /* MockService+Concurrency.swift */; };
AD854D8C2A7E75E20005C502 /* MockService+Concurrency.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD854D8A2A7E75E20005C502 /* MockService+Concurrency.swift */; };
AD879B9D258242AC00F85B0B /* PactSwiftVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD879B9C258242AC00F85B0B /* PactSwiftVersion.swift */; };
AD879B9E258242AC00F85B0B /* PactSwiftVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD879B9C258242AC00F85B0B /* PactSwiftVersion.swift */; };
AD881808242C715B00BF510D /* PactSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AD8817FE242C715A00BF510D /* PactSwift.framework */; };
Expand Down Expand Up @@ -311,6 +315,8 @@
AD78FB47264FD21900765BD3 /* PactContractTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PactContractTests.swift; sourceTree = "<group>"; };
AD8546F025135F4200211E28 /* RandomDate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RandomDate.swift; sourceTree = "<group>"; };
AD8546F32513601800211E28 /* RandomDateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RandomDateTests.swift; sourceTree = "<group>"; };
AD854D872A7E75080005C502 /* MockService+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MockService+Extension.swift"; sourceTree = "<group>"; };
AD854D8A2A7E75E20005C502 /* MockService+Concurrency.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MockService+Concurrency.swift"; sourceTree = "<group>"; };
AD879B9C258242AC00F85B0B /* PactSwiftVersion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PactSwiftVersion.swift; sourceTree = "<group>"; };
AD8817FE242C715A00BF510D /* PactSwift.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = PactSwift.framework; sourceTree = BUILT_PRODUCTS_DIR; };
AD881801242C715A00BF510D /* PactSwift.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PactSwift.h; sourceTree = "<group>"; };
Expand Down Expand Up @@ -460,13 +466,6 @@
path = Matchers;
sourceTree = "<group>";
};
AD1AE4BA26BBC956001E09E2 /* Frameworks */ = {
isa = PBXGroup;
children = (
);
name = Frameworks;
sourceTree = "<group>";
};
AD641A362434354C00785CE1 /* Model */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -546,7 +545,6 @@
ADD6993D242C861600C5C2C2 /* .swiftlint.yml */,
ADCB2562242C83E20048BD18 /* Configurations */,
ADC6FAE024302BA800026714 /* Documentation */,
AD1AE4BA26BBC956001E09E2 /* Frameworks */,
ADF2E582267462090029507D /* Package.swift */,
AD8817FF242C715A00BF510D /* Products */,
AD8A32D6242CCF5600283080 /* Scripts */,
Expand Down Expand Up @@ -633,6 +631,8 @@
ADCB2563242C844B0048BD18 /* Headers */,
AD01C9392432A31F00B75C9D /* Matchers */,
AD10361324468AB3002C97CA /* MockService.swift */,
AD854D8A2A7E75E20005C502 /* MockService+Concurrency.swift */,
AD854D872A7E75080005C502 /* MockService+Extension.swift */,
ADB7C167243327E300A16CDE /* Model */,
AD8DF434243C53750062CB1A /* PactBuilder.swift */,
ADF17CB926B5019B008E7ECD /* PFMockService.swift */,
Expand Down Expand Up @@ -1087,6 +1087,7 @@
buildActionMask = 2147483647;
files = (
AD54435E27D32DCA00D4C464 /* DateTimeExpression.swift in Sources */,
AD854D8B2A7E75E20005C502 /* MockService+Concurrency.swift in Sources */,
AD641A3C24344ED400785CE1 /* Pacticipant.swift in Sources */,
ADA17E41251377A4004F1A82 /* Date+PactSwift.swift in Sources */,
AD879B9D258242AC00F85B0B /* PactSwiftVersion.swift in Sources */,
Expand Down Expand Up @@ -1133,6 +1134,7 @@
AD4B97102513A3DB00C37560 /* RandomString.swift in Sources */,
AD8DF439243EEBDB0062CB1A /* SomethingLike.swift in Sources */,
AD9D7D9026C8B3DA00FE4137 /* FromProviderState.swift in Sources */,
AD854D882A7E75080005C502 /* MockService+Extension.swift in Sources */,
ADD3C27424416CDF002E73B9 /* EqualTo.swift in Sources */,
ADA4B1E426D31EDA00A5AE88 /* VersionSelector.swift in Sources */,
ADDE4704244D101700E4F7EE /* ErrorReportable.swift in Sources */,
Expand Down Expand Up @@ -1215,6 +1217,7 @@
buildActionMask = 2147483647;
files = (
AD54435F27D32DCA00D4C464 /* DateTimeExpression.swift in Sources */,
AD854D8C2A7E75E20005C502 /* MockService+Concurrency.swift in Sources */,
AD8FC7E42463BB9F00361854 /* Interaction.swift in Sources */,
ADA17E42251377A4004F1A82 /* Date+PactSwift.swift in Sources */,
AD879B9E258242AC00F85B0B /* PactSwiftVersion.swift in Sources */,
Expand Down Expand Up @@ -1261,6 +1264,7 @@
AD4B97112513A3DB00C37560 /* RandomString.swift in Sources */,
AD8FC7ED2463BB9F00361854 /* Response.swift in Sources */,
AD9D7D9126C8B3DA00FE4137 /* FromProviderState.swift in Sources */,
AD854D892A7E75080005C502 /* MockService+Extension.swift in Sources */,
AD8FC7F22463BBB800361854 /* DecimalLike.swift in Sources */,
ADA4B1E526D31EDA00A5AE88 /* VersionSelector.swift in Sources */,
AD8FC7EB2463BB9F00361854 /* ProviderState.swift in Sources */,
Expand Down
2 changes: 1 addition & 1 deletion Scripts/build_file_list_and_swiftlint
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ if which swiftlint >/dev/null; then

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

# Output an empty derived file
touch $DERIVED_FILE_DIR/swiftlint.txt
Expand Down
141 changes: 141 additions & 0 deletions Sources/MockService+Concurrency.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
//
// Created by Marko Justinek on 5/8/2023.
// Copyright © 2023 Marko Justinek. All rights reserved.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
// IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
//

import Foundation

#if canImport(_Concurrency) && compiler(>=5.7)
@_implementationOnly import PactSwiftMockServer

public extension MockService {

/// Runs the Pact test against the code making the API request
///
/// - Parameters:
/// - file: The file to report the failing test in
/// - line: The line on which to report the failing test
/// - verify: An array of specific `Interaction`s to verify. If none provided, the latest defined interaction is used
/// - timeout: Time before the test times out. Default is 10 seconds
/// - testFunction: Your async code making the API request
///
/// The `testFunction` closure is passed a `String` representing the url of the active Mock Server.
///
/// ```
/// try await mockService.run { baseURL in
/// // async code making the request with provided `baseURL`
/// // assert response can be processed
/// }
/// ```
///
@available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)
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 {
// Use the provided set or if not provided only the current interaction
pact.interactions = interactions ?? [currentInteraction]

if checkForInvalidInteractions(pact.interactions, file: file, line: line) {
// Remove interactions with errors
pact.interactions.removeAll { $0.encodingErrors.isEmpty == false }
self.interactions.removeAll()
} else {
// Prepare a brand spanking new MockServer (Mock Provider) on its own port
let mockServer = MockServer()

// Set the expectations so we don't wait for this async magic indefinitely
try await setupPactInteraction(timeout: timeout ?? Constants.kTimeout, file: file, line: line, mockServer: mockServer, testFunction: testFunction)

// At the same time start listening to verification that Mock Server received the expected request
try await verifyPactInteraction(timeout: timeout ?? Constants.kTimeout, file: file, line: line, mockServer: mockServer)
}
}
}

// MARK: - Internal

extension MockService {

@available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)
func setupPactInteraction(timeout: TimeInterval, file: FileString?, line: UInt?, mockServer: MockServer, testFunction: @escaping @Sendable (String) async throws -> Void) async throws {
Logger.log(message: "Setting up pact test", data: pact.data)
do {
// Set up a Mock Server with Pact data and on desired http protocol
try await mockServer.setup(pact: pact.data!, protocol: transferProtocolScheme)

// If Mock Server spun up, run the test function
let task = Task(timeout: timeout) {
try await testFunction(mockServer.baseUrl)
}
// await task completion (value is Void)
try await task.value
} catch {
// Failed to spin up a Mock Server. This could be due to bad Pact data. Most likely to Pact data.
failWith((error as? MockServerError)?.description ?? error.localizedDescription, file: file, line: line)
throw error
}
}

@available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)
func verifyPactInteraction(timeout: TimeInterval, file: FileString?, line: UInt?, mockServer: MockServer) async throws {
do {
let task = Task(timeout: timeout) {
try await mockServer.verify()
}
// await task completion (value is discarded)
_ = try await task.value

// If the comsumer (in testFunction:) made the promised request to Mock Server, go and finalize the test.
// Only finalize when running in simulator or macOS. Running on a physical iOS device makes little sense due to
// writing a pact file to device's disk. `libpact_ffi` does the actual file writing it writes it onto the
// disk of the device it is being run on.
#if targetEnvironment(simulator) || os(macOS)
let message = try await finalize(file: file, line: line)
Logger.log(message: message, data: self.pact.data)
#else
print("[INFO]: Running on an iOS device. Writing Pact interaction into a contract skipped.")
#endif
} catch {
failWith((error as? MockServerError)?.description ?? error.localizedDescription, file: file, line: line)
throw error
}
}

/// Writes a Pact contract file in JSON format
///
/// By default Pact contracts are written to `/tmp/pacts` folder.
/// Set `PACT_OUTPUT_DIR` to `$(PATH)/to/desired/dir/` in `Build` phase of your `Scheme` to change the location.
///
@available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)
func finalize(file: FileString? = nil, line: UInt? = nil) async throws -> String {
// Spin up a fresh Mock Server with a directory to write to
let mockServer = MockServer(directory: pactsDirectory, merge: self.merge)

// Gather all the interactions this MockService has received to set up and prepare Pact data with them all
pact.interactions = interactions.filter { $0.encodingErrors.isEmpty }

// Validate the Pact `Data` is hunky dory
guard let pactData = pact.data else {
throw MockServerError.nullPointer
}

// Ask Mock Server to do the actual Pact file writing to disk
do {
return try await mockServer.finalize(pact: pactData)
} catch {
failWith((error as? MockServerError)?.description ?? error.localizedDescription, file: file, line: line)
throw error
}
}
}
#endif
96 changes: 96 additions & 0 deletions Sources/MockService+Extension.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
//
// Created by Marko Justinek on 5/8/2023.
// Copyright © 2023 Marko Justinek. All rights reserved.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
// IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
//

import Foundation
import XCTest

#if compiler(>=5.5)
@_implementationOnly import PactSwiftMockServer
#else
import PactSwiftMockServer
#endif

extension MockService {

/// Check there are no invalid interactions
func checkForInvalidInteractions(_ interactions: [Interaction], file: FileString? = nil, line: UInt? = nil) -> Bool {
let errors = interactions.flatMap(\.encodingErrors)
for error in errors {
failWith(error.localizedDescription, file: file, line: line)
}
return errors.isEmpty == false
}

/// Writes a Pact contract file in JSON format
///
/// - parameter completion: Result of the writing the Pact contract to JSON
///
/// By default Pact contracts are written to `/tmp/pacts` folder.
/// Set `PACT_OUTPUT_DIR` to `$(PATH)/to/desired/dir/` in `Build` phase of your `Scheme` to change the location.
///
func finalize(file: FileString? = nil, line: UInt? = nil, completion: ((Result<String, MockServerError>) -> Void)? = nil) {
// Spin up a fresh Mock Server with a directory to write to
let mockServer = MockServer(directory: pactsDirectory, merge: self.merge)

// Gather all the interactions this MockService has received to set up and prepare Pact data with them all
pact.interactions = interactions.filter { $0.encodingErrors.isEmpty }

// Validate the Pact `Data` is hunky dory
guard let pactData = pact.data else {
completion?(.failure(.nullPointer))
return
}

// Ask Mock Server to do the actual Pact file writing to disk
mockServer.finalize(pact: pactData) { [unowned self] in
switch $0 {
case .success(let message):
completion?(.success(message))
case .failure(let error):
failWith(error.description)
completion?(.failure(error))
}
}
}

/// Waits for test to be completed and fails if timed out
func waitForPactTestWith(timeout: TimeInterval, file: FileString?, line: UInt?, action: (@escaping () -> Void) -> Void) {
let expectation = XCTestExpectation(description: "waitForPactTest")
action {
expectation.fulfill()
}

let result = XCTWaiter().wait(for: [expectation], timeout: timeout)
if result != .completed {
let message = "Test did not complete within \(timeout) second timeout! Did you run testCompleted() block?"
if let file = file, let line = line {
errorReporter.reportFailure(message, file: file, line: line)
} else {
errorReporter.reportFailure(message)
}
}
}

/// Fail the test and raise the failure in `file` at `line`
func failWith(_ message: String, file: FileString? = nil, line: UInt? = nil) {
if let file = file, let line = line {
errorReporter.reportFailure(message, file: file, line: line)
} else {
errorReporter.reportFailure(message)
}
}
}
Loading

0 comments on commit cff1761

Please sign in to comment.