From af389d78ced03d560b5030fb36b673406b6d5adf Mon Sep 17 00:00:00 2001 From: Pierre Mardon Date: Sun, 16 Jun 2024 21:56:10 +0200 Subject: [PATCH 01/10] Swift 6 mode (#26) * Swift 6 mode, refs #25 * Update workflow --- .github/workflows/swift.yml | 15 ++++++------- Package.swift | 3 ++- .../ConstantDelayRetryPolicy.swift | 4 ++-- .../ExponentialBackoffRetryPolicy.swift | 17 ++++++++------- Sources/SwiftRetrier/Core/Model/Job.swift | 2 +- .../Core/Model/Policies/AttemptFailure.swift | 2 +- .../Core/Model/Policies/RetryDecision.swift | 2 +- .../Core/Model/Policies/RetryPolicy.swift | 4 ++-- .../Core/Model/Retriers/Retrier.swift | 4 ++-- .../GiveUpOnPolicyWrapper.swift | 11 ++++++---- .../PolicyBuilding/RetryPolicy+GiveUpOn.swift | 4 ++-- .../Core/Retriers/ConditionalRetrier.swift | 2 +- .../SwiftRetrier/Core/Retriers/Repeater.swift | 2 +- .../Core/Retriers/SimpleRetrier.swift | 8 ++++--- Sources/SwiftRetrier/DSL/ColdRepeater.swift | 4 ++-- Sources/SwiftRetrier/DSL/ColdRetrier.swift | 8 +++---- .../SwiftRetrier/TypeErasing/AnyRetrier.swift | 2 +- .../TypeErasing/AnySingleOutputRetrier.swift | 4 ++-- Sources/SwiftRetrier/Util/OnMain.swift | 2 +- .../Retriers/ConditionalRetrierTests.swift | 11 +++++----- .../FallibleRetrierTests.swift | 5 ++++- .../ProtocolBasedTests/RetrierTests.swift | 16 +++++++++----- .../SingleOutputConditionalRetrierTests.swift | 21 +++++++------------ .../SingleOutputFallibleRetrierTests.swift | 2 ++ .../SingleOutputRetrierTests.swift | 8 +++++-- .../Retriers/RepeaterTests.swift | 3 ++- 26 files changed, 93 insertions(+), 73 deletions(-) diff --git a/.github/workflows/swift.yml b/.github/workflows/swift.yml index feac9ad..ce3d300 100644 --- a/.github/workflows/swift.yml +++ b/.github/workflows/swift.yml @@ -6,15 +6,16 @@ on: jobs: build: - runs-on: macos-13 + runs-on: macos-14 steps: - - uses: maxim-lobanov/setup-xcode@v1 - with: - xcode-version: latest-stable - - name: Check XCode Version + - uses: actions/checkout@v4 + - name: List available Xcode versions + run: ls /Applications | grep Xcode + - name: Set up Xcode version + run: sudo xcode-select -s /Applications/Xcode_16.0.app/Contents/Developer + - name: Show current version of Xcode run: xcodebuild -version - - uses: actions/checkout@v3 - name: Build run: xcodebuild -scheme SwiftRetrier build -destination "platform=OS X" - name: Run tests @@ -22,7 +23,7 @@ jobs: lint: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v4 - name: SwiftLint uses: raphaelbussa/swiftlint-action@main with: diff --git a/Package.swift b/Package.swift index fcf16f0..e165fe4 100644 --- a/Package.swift +++ b/Package.swift @@ -35,5 +35,6 @@ let package = Package( dependencies: ["SwiftRetrier"], plugins: [] + extraPlugins ) - ] + ], + swiftLanguageVersions: [.version("6")] ) diff --git a/Sources/SwiftRetrier/Core/ConstantDelayPolicy/ConstantDelayRetryPolicy.swift b/Sources/SwiftRetrier/Core/ConstantDelayPolicy/ConstantDelayRetryPolicy.swift index 0ae77ad..885885c 100644 --- a/Sources/SwiftRetrier/Core/ConstantDelayPolicy/ConstantDelayRetryPolicy.swift +++ b/Sources/SwiftRetrier/Core/ConstantDelayPolicy/ConstantDelayRetryPolicy.swift @@ -1,6 +1,6 @@ import Foundation -open class ConstantDelayRetryPolicy: RetryPolicy { +public struct ConstantDelayRetryPolicy: RetryPolicy { public let delay: TimeInterval @@ -16,7 +16,7 @@ open class ConstantDelayRetryPolicy: RetryPolicy { .retry(delay: retryDelay(for: attemptFailure)) } - public func freshCopy() -> RetryPolicy { + public func policyAfter(attemptFailure: AttemptFailure, delay: TimeInterval) -> any RetryPolicy { self } } diff --git a/Sources/SwiftRetrier/Core/ExponentialBackoffPolicy/ExponentialBackoffRetryPolicy.swift b/Sources/SwiftRetrier/Core/ExponentialBackoffPolicy/ExponentialBackoffRetryPolicy.swift index 03382ae..fe79baf 100644 --- a/Sources/SwiftRetrier/Core/ExponentialBackoffPolicy/ExponentialBackoffRetryPolicy.swift +++ b/Sources/SwiftRetrier/Core/ExponentialBackoffPolicy/ExponentialBackoffRetryPolicy.swift @@ -1,8 +1,8 @@ import Foundation -open class ExponentialBackoffRetryPolicy: RetryPolicy { +public struct ExponentialBackoffRetryPolicy: RetryPolicy { - public enum Jitter { + public enum Jitter: Sendable { case none case full case decorrelated(growthFactor: Double = ExponentialBackoffConstants.defaultDecorrelatedJitterGrowthFactor) @@ -11,14 +11,16 @@ open class ExponentialBackoffRetryPolicy: RetryPolicy { public let timeSlot: TimeInterval public let maxDelay: TimeInterval public let jitter: Jitter - private var previousDelay: TimeInterval? + private let previousDelay: TimeInterval? public init(timeSlot: TimeInterval = ExponentialBackoffConstants.defaultTimeSlot, maxDelay: TimeInterval = ExponentialBackoffConstants.defaultMaxDelay, - jitter: Jitter = ExponentialBackoffConstants.defaultJitter) { + jitter: Jitter = ExponentialBackoffConstants.defaultJitter, + previousDelay: TimeInterval? = nil) { self.timeSlot = timeSlot self.maxDelay = maxDelay self.jitter = jitter + self.previousDelay = previousDelay } public func exponentiationBySquaring(_ base: T, _ multiplier: T, _ exponent: T) -> T { @@ -57,7 +59,6 @@ open class ExponentialBackoffRetryPolicy: RetryPolicy { } else { delay = fullJitterDelay(attemptIndex: attemptIndex) } - previousDelay = delay return delay } @@ -72,7 +73,7 @@ open class ExponentialBackoffRetryPolicy: RetryPolicy { } } - open func retryDelay(for attemptFailure: AttemptFailure) -> TimeInterval { + public func retryDelay(for attemptFailure: AttemptFailure) -> TimeInterval { min(maxDelay, uncappedDelay(attemptIndex: attemptFailure.index)) } @@ -80,7 +81,7 @@ open class ExponentialBackoffRetryPolicy: RetryPolicy { .retry(delay: retryDelay(for: attemptFailure)) } - public func freshCopy() -> RetryPolicy { - ExponentialBackoffRetryPolicy(timeSlot: timeSlot, maxDelay: maxDelay, jitter: jitter) + public func policyAfter(attemptFailure: AttemptFailure, delay: TimeInterval) -> any RetryPolicy { + ExponentialBackoffRetryPolicy(timeSlot: timeSlot, maxDelay: maxDelay, jitter: jitter, previousDelay: delay) } } diff --git a/Sources/SwiftRetrier/Core/Model/Job.swift b/Sources/SwiftRetrier/Core/Model/Job.swift index 7aaab44..30e0ad3 100644 --- a/Sources/SwiftRetrier/Core/Model/Job.swift +++ b/Sources/SwiftRetrier/Core/Model/Job.swift @@ -1 +1 @@ -public typealias Job = () async throws -> Value +public typealias Job = @Sendable () async throws -> Value diff --git a/Sources/SwiftRetrier/Core/Model/Policies/AttemptFailure.swift b/Sources/SwiftRetrier/Core/Model/Policies/AttemptFailure.swift index 59dfecc..f118367 100644 --- a/Sources/SwiftRetrier/Core/Model/Policies/AttemptFailure.swift +++ b/Sources/SwiftRetrier/Core/Model/Policies/AttemptFailure.swift @@ -1,6 +1,6 @@ import Foundation -public struct AttemptFailure { +public struct AttemptFailure: Sendable { public let trialStart: Date public let index: UInt public let error: Error diff --git a/Sources/SwiftRetrier/Core/Model/Policies/RetryDecision.swift b/Sources/SwiftRetrier/Core/Model/Policies/RetryDecision.swift index 32fcda4..c3e9204 100644 --- a/Sources/SwiftRetrier/Core/Model/Policies/RetryDecision.swift +++ b/Sources/SwiftRetrier/Core/Model/Policies/RetryDecision.swift @@ -1,6 +1,6 @@ import Foundation -public enum RetryDecision { +public enum RetryDecision: Sendable { case giveUp case retry(delay: TimeInterval) } diff --git a/Sources/SwiftRetrier/Core/Model/Policies/RetryPolicy.swift b/Sources/SwiftRetrier/Core/Model/Policies/RetryPolicy.swift index 889c9ee..21d20c7 100644 --- a/Sources/SwiftRetrier/Core/Model/Policies/RetryPolicy.swift +++ b/Sources/SwiftRetrier/Core/Model/Policies/RetryPolicy.swift @@ -1,7 +1,7 @@ import Foundation -public protocol RetryPolicy { +public protocol RetryPolicy: Sendable { func shouldRetry(on attemptFailure: AttemptFailure) -> RetryDecision func retryDelay(for attemptFailure: AttemptFailure) -> TimeInterval - func freshCopy() -> RetryPolicy + func policyAfter(attemptFailure: AttemptFailure, delay: TimeInterval) -> any RetryPolicy } diff --git a/Sources/SwiftRetrier/Core/Model/Retriers/Retrier.swift b/Sources/SwiftRetrier/Core/Model/Retriers/Retrier.swift index c0ca8ce..1151170 100644 --- a/Sources/SwiftRetrier/Core/Model/Retriers/Retrier.swift +++ b/Sources/SwiftRetrier/Core/Model/Retriers/Retrier.swift @@ -1,8 +1,8 @@ import Foundation import Combine -public protocol Retrier: Cancellable, AnyObject { - associatedtype Output +public protocol Retrier: Cancellable, AnyObject, Sendable { + associatedtype Output: Sendable func publisher() -> AnyPublisher, Never> } diff --git a/Sources/SwiftRetrier/Core/PolicyBuilding/GiveUpOnPolicyWrapper.swift b/Sources/SwiftRetrier/Core/PolicyBuilding/GiveUpOnPolicyWrapper.swift index 6204e83..cc73128 100644 --- a/Sources/SwiftRetrier/Core/PolicyBuilding/GiveUpOnPolicyWrapper.swift +++ b/Sources/SwiftRetrier/Core/PolicyBuilding/GiveUpOnPolicyWrapper.swift @@ -3,9 +3,9 @@ import Foundation public struct GiveUpOnPolicyWrapper: RetryPolicy { private let wrapped: RetryPolicy - private let giveUpCriterium: (AttemptFailure) -> Bool + private let giveUpCriterium: @Sendable (AttemptFailure) -> Bool - public init(wrapped: RetryPolicy, giveUpCriterium: @escaping (AttemptFailure) -> Bool) { + public init(wrapped: RetryPolicy, giveUpCriterium: @escaping @Sendable (AttemptFailure) -> Bool) { self.wrapped = wrapped self.giveUpCriterium = giveUpCriterium } @@ -21,7 +21,10 @@ public struct GiveUpOnPolicyWrapper: RetryPolicy { return wrapped.shouldRetry(on: attemptFailure) } - public func freshCopy() -> RetryPolicy { - GiveUpOnPolicyWrapper(wrapped: wrapped.freshCopy(), giveUpCriterium: giveUpCriterium) + public func policyAfter(attemptFailure: AttemptFailure, delay: TimeInterval) -> any RetryPolicy { + GiveUpOnPolicyWrapper( + wrapped: wrapped.policyAfter(attemptFailure: attemptFailure, delay: delay), + giveUpCriterium: giveUpCriterium + ) } } diff --git a/Sources/SwiftRetrier/Core/PolicyBuilding/RetryPolicy+GiveUpOn.swift b/Sources/SwiftRetrier/Core/PolicyBuilding/RetryPolicy+GiveUpOn.swift index 8413fec..70e80d2 100644 --- a/Sources/SwiftRetrier/Core/PolicyBuilding/RetryPolicy+GiveUpOn.swift +++ b/Sources/SwiftRetrier/Core/PolicyBuilding/RetryPolicy+GiveUpOn.swift @@ -2,7 +2,7 @@ import Foundation public extension RetryPolicy { - func giveUp(on giveUpCriterium: @escaping (AttemptFailure) -> Bool) -> RetryPolicy { + func giveUp(on giveUpCriterium: @escaping @Sendable (AttemptFailure) -> Bool) -> RetryPolicy { GiveUpOnPolicyWrapper(wrapped: self, giveUpCriterium: giveUpCriterium) } @@ -17,7 +17,7 @@ public extension RetryPolicy { }) } - func giveUpOnErrors(matching finalErrorCriterium: @escaping (Error) -> Bool) -> RetryPolicy { + func giveUpOnErrors(matching finalErrorCriterium: @escaping @Sendable (Error) -> Bool) -> RetryPolicy { GiveUpOnPolicyWrapper(wrapped: self, giveUpCriterium: { finalErrorCriterium($0.error) }) } } diff --git a/Sources/SwiftRetrier/Core/Retriers/ConditionalRetrier.swift b/Sources/SwiftRetrier/Core/Retriers/ConditionalRetrier.swift index 175d589..a96ac25 100644 --- a/Sources/SwiftRetrier/Core/Retriers/ConditionalRetrier.swift +++ b/Sources/SwiftRetrier/Core/Retriers/ConditionalRetrier.swift @@ -20,7 +20,7 @@ import Combine /// /// If the condition publisher completes and it had not emitted any value or the last value it emitted was `false` /// then the retrier emits a completion embedding `RetryError.conditionPublisherCompleted` and finishes. -public class ConditionalRetrier: SingleOutputRetrier { +public class ConditionalRetrier: SingleOutputRetrier, @unchecked Sendable { private let policy: RetryPolicy private let job: Job diff --git a/Sources/SwiftRetrier/Core/Retriers/Repeater.swift b/Sources/SwiftRetrier/Core/Retriers/Repeater.swift index 65ef724..e7e2ad7 100644 --- a/Sources/SwiftRetrier/Core/Retriers/Repeater.swift +++ b/Sources/SwiftRetrier/Core/Retriers/Repeater.swift @@ -22,7 +22,7 @@ import Combine /// ``` /// /// On cancellation, the publisher emits a completion embedding a `CancellationError`then finishes. -public class Repeater: Retrier { +public class Repeater: Retrier, @unchecked Sendable { private let retrierBuilder: () -> AnySingleOutputRetrier private var retrier: AnySingleOutputRetrier? diff --git a/Sources/SwiftRetrier/Core/Retriers/SimpleRetrier.swift b/Sources/SwiftRetrier/Core/Retriers/SimpleRetrier.swift index 59e9d1d..d9dcbef 100644 --- a/Sources/SwiftRetrier/Core/Retriers/SimpleRetrier.swift +++ b/Sources/SwiftRetrier/Core/Retriers/SimpleRetrier.swift @@ -10,14 +10,14 @@ import Combine /// the last attempt error, the publisher emits the attempt failure then a completion embedding the attempt error. /// - **the retrier is canceled:** any awaiting on the `value` property will throw a `CancellationError`, the publisher /// emits a completion embedding the same error then finishes. -public class SimpleRetrier: SingleOutputRetrier { +public class SimpleRetrier: SingleOutputRetrier, @unchecked Sendable { public let trialStart = Date() private let subject = PassthroughSubject, Never>() private var task: Task! public init(policy: RetryPolicy, job: @escaping Job) { - self.task = createTask(policy: policy.freshCopy(), job: job) + self.task = createTask(policy: policy, job: job) } @MainActor @@ -49,6 +49,7 @@ public class SimpleRetrier: SingleOutputRetrier { // Ensure we don't start before any ongoing business on main actor is finished await MainActor.run {} do { + var policy = policy var attemptIndex: UInt = 0 while true { try Task.checkCancellation() @@ -60,7 +61,7 @@ public class SimpleRetrier: SingleOutputRetrier { let attemptFailure = AttemptFailure(trialStart: trialStart, index: attemptIndex, error: error) await sendAttemptFailure(attemptFailure) try Task.checkCancellation() - let retryDecision = await MainActor.run { [attemptIndex] in + let retryDecision = await MainActor.run { [policy, attemptIndex] in policy.shouldRetry(on: AttemptFailure(trialStart: trialStart, index: attemptIndex, error: error)) @@ -70,6 +71,7 @@ public class SimpleRetrier: SingleOutputRetrier { throw error case .retry(delay: let delay): try await Task.sleep(nanoseconds: nanoseconds(delay)) + policy = policy.policyAfter(attemptFailure: attemptFailure, delay: delay) attemptIndex += 1 } } diff --git a/Sources/SwiftRetrier/DSL/ColdRepeater.swift b/Sources/SwiftRetrier/DSL/ColdRepeater.swift index 23f1618..eedd4ee 100644 --- a/Sources/SwiftRetrier/DSL/ColdRepeater.swift +++ b/Sources/SwiftRetrier/DSL/ColdRepeater.swift @@ -9,7 +9,7 @@ public struct ColdRepeater { public extension ColdRepeater { - func giveUp(on giveUpCriterium: @escaping (AttemptFailure) -> Bool) -> ColdRepeater { + func giveUp(on giveUpCriterium: @escaping @Sendable (AttemptFailure) -> Bool) -> ColdRepeater { let policy = policy.giveUp(on: giveUpCriterium) return ColdRepeater(policy: policy, repeatDelay: repeatDelay, conditionPublisher: conditionPublisher) } @@ -24,7 +24,7 @@ public extension ColdRepeater { return ColdRepeater(policy: policy, repeatDelay: repeatDelay, conditionPublisher: conditionPublisher) } - func giveUpOnErrors(matching finalErrorCriterium: @escaping (Error) -> Bool) -> ColdRepeater { + func giveUpOnErrors(matching finalErrorCriterium: @escaping @Sendable (Error) -> Bool) -> ColdRepeater { let policy = policy.giveUpOnErrors(matching: finalErrorCriterium) return ColdRepeater(policy: policy, repeatDelay: repeatDelay, conditionPublisher: conditionPublisher) } diff --git a/Sources/SwiftRetrier/DSL/ColdRetrier.swift b/Sources/SwiftRetrier/DSL/ColdRetrier.swift index 65d677c..7246fdc 100644 --- a/Sources/SwiftRetrier/DSL/ColdRetrier.swift +++ b/Sources/SwiftRetrier/DSL/ColdRetrier.swift @@ -8,7 +8,7 @@ public struct ColdRetrier { public extension ColdRetrier { - func giveUp(on giveUpCriterium: @escaping (AttemptFailure) -> Bool) -> ColdRetrier { + func giveUp(on giveUpCriterium: @escaping @Sendable (AttemptFailure) -> Bool) -> ColdRetrier { let policy = policy.giveUp(on: giveUpCriterium) return ColdRetrier(policy: policy, conditionPublisher: conditionPublisher) } @@ -23,7 +23,7 @@ public extension ColdRetrier { return ColdRetrier(policy: policy, conditionPublisher: conditionPublisher) } - func giveUpOnErrors(matching finalErrorCriterium: @escaping (Error) -> Bool) -> ColdRetrier { + func giveUpOnErrors(matching finalErrorCriterium: @escaping @Sendable (Error) -> Bool) -> ColdRetrier { let policy = policy.giveUpOnErrors(matching: finalErrorCriterium) return ColdRetrier(policy: policy, conditionPublisher: conditionPublisher) } @@ -40,7 +40,7 @@ public extension ColdRetrier { } @discardableResult - func execute(_ job: @escaping Job) -> AnySingleOutputRetrier { + func execute(_ job: @escaping Job) -> AnySingleOutputRetrier { if let conditionPublisher { return ConditionalRetrier(policy: policy, conditionPublisher: conditionPublisher, job: job) .eraseToAnySingleOutputRetrier() @@ -49,7 +49,7 @@ public extension ColdRetrier { } @discardableResult - func callAsFunction(_ job: @escaping Job) -> AnySingleOutputRetrier { + func callAsFunction(_ job: @escaping Job) -> AnySingleOutputRetrier { execute(job) } } diff --git a/Sources/SwiftRetrier/TypeErasing/AnyRetrier.swift b/Sources/SwiftRetrier/TypeErasing/AnyRetrier.swift index 8c51990..6d934ca 100644 --- a/Sources/SwiftRetrier/TypeErasing/AnyRetrier.swift +++ b/Sources/SwiftRetrier/TypeErasing/AnyRetrier.swift @@ -1,7 +1,7 @@ import Foundation import Combine -public class AnyRetrier: Retrier { +public class AnyRetrier: Retrier, @unchecked Sendable { public let publisherBlock: () -> AnyPublisher, Never> private let cancelBlock: () -> Void diff --git a/Sources/SwiftRetrier/TypeErasing/AnySingleOutputRetrier.swift b/Sources/SwiftRetrier/TypeErasing/AnySingleOutputRetrier.swift index 1e7054a..4bbd7a6 100644 --- a/Sources/SwiftRetrier/TypeErasing/AnySingleOutputRetrier.swift +++ b/Sources/SwiftRetrier/TypeErasing/AnySingleOutputRetrier.swift @@ -1,9 +1,9 @@ import Foundation import Combine -public class AnySingleOutputRetrier: AnyRetrier, SingleOutputRetrier { +public class AnySingleOutputRetrier: AnyRetrier, SingleOutputRetrier, @unchecked Sendable { - private let outputBlock: () async throws -> Output + private let outputBlock: @Sendable () async throws -> Output public init(_ retrier: R) where R: SingleOutputRetrier, R.Output == Value { self.outputBlock = { try await retrier.value } diff --git a/Sources/SwiftRetrier/Util/OnMain.swift b/Sources/SwiftRetrier/Util/OnMain.swift index 31250e0..6abeca8 100644 --- a/Sources/SwiftRetrier/Util/OnMain.swift +++ b/Sources/SwiftRetrier/Util/OnMain.swift @@ -1,6 +1,6 @@ import Foundation -func onMain(_ block: @escaping () -> Void) { +func onMain(_ block: @escaping @Sendable () -> Void) { if Thread.isMainThread { block() } else { diff --git a/Tests/SwiftRetrierTests/Retriers/ConditionalRetrierTests.swift b/Tests/SwiftRetrierTests/Retriers/ConditionalRetrierTests.swift index 09a6dde..b845b3b 100644 --- a/Tests/SwiftRetrierTests/Retriers/ConditionalRetrierTests.swift +++ b/Tests/SwiftRetrierTests/Retriers/ConditionalRetrierTests.swift @@ -82,6 +82,7 @@ class ConditionalRetrierSpecificTests: XCTestCase { try await taskWait() } + @MainActor func test_execution_when_condition_true() { let condition = Just(true) .eraseToAnyPublisher() @@ -123,20 +124,20 @@ class ConditionalRetrierSpecificTests: XCTestCase { cancellable.cancel() } + @MainActor func test_attempt_on_failure_propagated_during_second_trial() { var failedOnce = false var jobExecutionCount = 0 - let job = { + let expectationOwnErrorPropagated = expectation(description: "Attempt own error propagated") + + let retrier = buildRetrier(trueFalseTruePublisher(), { @MainActor in jobExecutionCount += 1 try await taskWait() if !failedOnce { failedOnce = true throw defaultError } - } - let expectationOwnErrorPropagated = expectation(description: "Attempt own error propagated") - - let retrier = buildRetrier(trueFalseTruePublisher(), job) + }) let cancellable = retrier.publisher() .sink(receiveCompletion: { _ in }, receiveValue: { diff --git a/Tests/SwiftRetrierTests/Retriers/ProtocolBasedTests/FallibleRetrierTests.swift b/Tests/SwiftRetrierTests/Retriers/ProtocolBasedTests/FallibleRetrierTests.swift index 6385c30..1d5c6f6 100644 --- a/Tests/SwiftRetrierTests/Retriers/ProtocolBasedTests/FallibleRetrierTests.swift +++ b/Tests/SwiftRetrierTests/Retriers/ProtocolBasedTests/FallibleRetrierTests.swift @@ -20,6 +20,7 @@ class FallibleRetrierTests: XCTestCase { super.tearDown() } + @MainActor func test_Should_PublishCompletionEventWithError_When_JobFailsAndPolicyGivesUp() { let retrier = buildRetrier(Policy.testDefault(maxAttempts: 1), immediateFailureJob) let expectation = expectation(description: "Failure completion received") @@ -34,6 +35,7 @@ class FallibleRetrierTests: XCTestCase { cancellable.cancel() } + @MainActor func test_Should_CompleteSuccessPublisher_When_JobFailsAndPolicyGivesUp() { let retrier = buildRetrier(Policy.testDefault(maxAttempts: 1), immediateFailureJob) let expectation = expectation(description: "Completion received") @@ -46,10 +48,11 @@ class FallibleRetrierTests: XCTestCase { cancellable.cancel() } + @MainActor func test_Should_ThrowErrorInJob_When_RetrierIsCancelled() { let cancellationExpectation = expectation(description: "Cancellation catched") var fulfilled = false - let retrier = buildRetrier(Policy.testDefault(maxAttempts: 1), { + let retrier = buildRetrier(Policy.testDefault(maxAttempts: 1), { @MainActor in do { try await taskWait(defaultJobDuration) } catch { diff --git a/Tests/SwiftRetrierTests/Retriers/ProtocolBasedTests/RetrierTests.swift b/Tests/SwiftRetrierTests/Retriers/ProtocolBasedTests/RetrierTests.swift index 441c9d2..86b3e99 100644 --- a/Tests/SwiftRetrierTests/Retriers/ProtocolBasedTests/RetrierTests.swift +++ b/Tests/SwiftRetrierTests/Retriers/ProtocolBasedTests/RetrierTests.swift @@ -1,5 +1,6 @@ import Foundation import XCTest +import Combine @testable import SwiftRetrier class RetrierTests: XCTestCase { @@ -20,6 +21,7 @@ class RetrierTests: XCTestCase { super.tearDown() } + @MainActor func test_Should_PublishAttemptFailureWithJobError_When_JobFails() { let retrier = buildRetrier({ throw defaultError }) let expectation = expectation(description: "Failure received") @@ -34,6 +36,7 @@ class RetrierTests: XCTestCase { cancellable.cancel() } + @MainActor func test_Should_PublishAttemptSuccess_When_JobSucceeds() { let retrier = buildRetrier(immediateSuccessJob) let expectation = expectation(description: "Success received") @@ -48,9 +51,10 @@ class RetrierTests: XCTestCase { cancellable.cancel() } + @MainActor func test_Should_PublishAttemptFailureThenAttemptSuccess_When_JobFailsThenSucceeds() { var calledOnce = false - let retrier = buildRetrier({ + let retrier = buildRetrier({ @MainActor in if !calledOnce { calledOnce = true throw defaultError @@ -108,6 +112,7 @@ class RetrierTests: XCTestCase { cancellable.cancel() } + @MainActor func test_Should_CompletePublisherWithFinished_When_RetrierIsCancelled() { let failureExpectation = expectation(description: "Failure received") let retrier = buildRetrier(immediateSuccessJob) @@ -133,16 +138,17 @@ class RetrierTests: XCTestCase { @MainActor func test_Should_StillRetry_When_RetrierNotFinishedAndNotRetained() async throws { - var shouldSignalExecution = false + // Just working around capture issue + let shouldSignalExecution = CurrentValueSubject(false) var executed = false - weak var retrier = retrier { - if shouldSignalExecution { + weak var retrier = retrier { @MainActor in + if shouldSignalExecution.value { executed = true } throw defaultError } try await taskWait() - shouldSignalExecution = true + shouldSignalExecution.value = true try await taskWait(defaultSequenceWaitingTime / 2) XCTAssertTrue(executed) retrier?.cancel() diff --git a/Tests/SwiftRetrierTests/Retriers/ProtocolBasedTests/SingleOutputConditionalRetrierTests.swift b/Tests/SwiftRetrierTests/Retriers/ProtocolBasedTests/SingleOutputConditionalRetrierTests.swift index 32e6a0f..2c27ef4 100644 --- a/Tests/SwiftRetrierTests/Retriers/ProtocolBasedTests/SingleOutputConditionalRetrierTests.swift +++ b/Tests/SwiftRetrierTests/Retriers/ProtocolBasedTests/SingleOutputConditionalRetrierTests.swift @@ -41,11 +41,9 @@ class SingleOutputConditionalRetrierTests: XCTestCase { } catch {} } + @MainActor func test_Should_SuccessfullyAwaitValue_When_ConditionPublisherCausesASecondTrialThatSucceeds() { - let job = { - try await taskWait() - } - let retrier = buildRetrier(trueFalseTruePublisher(), job) + let retrier = buildRetrier(trueFalseTruePublisher(), { try await taskWait() }) let expectation = expectation(description: "Receive async output") Task { _ = try await retrier.value @@ -56,12 +54,9 @@ class SingleOutputConditionalRetrierTests: XCTestCase { @MainActor func test_Should_CompletePublisherWithFinished_When_ConditionPublisherCausesASecondTrialThatSucceeds() async { - let job = { - try await taskWait() - } let expectation = expectation(description: "Finished") - let retrier = buildRetrier(trueFalseTruePublisher(), job) + let retrier = buildRetrier(trueFalseTruePublisher(), { try await taskWait() }) let cancellable = retrier.publisher() .sink(receiveCompletion: { if case .finished = $0 { @@ -72,20 +67,20 @@ class SingleOutputConditionalRetrierTests: XCTestCase { cancellable.cancel() } + @MainActor func test_Should_ExecuteJobTheRightNumberOfTimes_When_ConditionPublisherInterruptsATrial() { var failedOnce = false var jobExecutionCount = 0 - let job = { + let completionReceived = expectation(description: "Completion received") + + let retrier = buildRetrier(trueFalseTruePublisher(), { @MainActor in jobExecutionCount += 1 try await taskWait() if !failedOnce { failedOnce = true throw defaultError } - } - let completionReceived = expectation(description: "Completion received") - - let retrier = buildRetrier(trueFalseTruePublisher(), job) + }) let cancellable = retrier.publisher() .sink(receiveCompletion: { _ in completionReceived.fulfill() diff --git a/Tests/SwiftRetrierTests/Retriers/ProtocolBasedTests/SingleOutputFallibleRetrierTests.swift b/Tests/SwiftRetrierTests/Retriers/ProtocolBasedTests/SingleOutputFallibleRetrierTests.swift index a2ab9e1..ebf4fed 100644 --- a/Tests/SwiftRetrierTests/Retriers/ProtocolBasedTests/SingleOutputFallibleRetrierTests.swift +++ b/Tests/SwiftRetrierTests/Retriers/ProtocolBasedTests/SingleOutputFallibleRetrierTests.swift @@ -28,6 +28,7 @@ class SingleOutputFallibleRetrierTests: XCTestCase { } catch {} } + @MainActor func test_Should_ReceiveFinishedCompletionOnFailurePublisher_When_RetrierSucceedsOnLastAttempt() { let retrier = buildRetrier(Policy.testDefault(maxAttempts: 1), immediateSuccessJob) let expectation = expectation(description: "Finished received") @@ -42,6 +43,7 @@ class SingleOutputFallibleRetrierTests: XCTestCase { cancellable.cancel() } + @MainActor func test_Should_ReceiveAttemptFailureOnFailurePublisher_When_JobFailsOnLastAttempt() { let retrier = buildRetrier(Policy.testDefault(maxAttempts: 1), immediateFailureJob) let expectation = expectation(description: "Attempt failure received") diff --git a/Tests/SwiftRetrierTests/Retriers/ProtocolBasedTests/SingleOutputRetrierTests.swift b/Tests/SwiftRetrierTests/Retriers/ProtocolBasedTests/SingleOutputRetrierTests.swift index cb406ce..45d2aba 100644 --- a/Tests/SwiftRetrierTests/Retriers/ProtocolBasedTests/SingleOutputRetrierTests.swift +++ b/Tests/SwiftRetrierTests/Retriers/ProtocolBasedTests/SingleOutputRetrierTests.swift @@ -19,6 +19,7 @@ class SingleOutputRetrierTests: XCTestCase { super.tearDown() } + @MainActor func test_Should_CompletePublisherWithFinished_When_JobSucceeds() { let retrier = buildRetrier(immediateSuccessJob) let expectation = expectation(description: "Finished received") @@ -33,6 +34,7 @@ class SingleOutputRetrierTests: XCTestCase { cancellable.cancel() } + @MainActor func test_Should_CompleteSuccessPublisherWithFinished_When_JobSucceeds() { let retrier = buildRetrier(immediateSuccessJob) let expectation = expectation(description: "Finished received") @@ -47,9 +49,10 @@ class SingleOutputRetrierTests: XCTestCase { cancellable.cancel() } + @MainActor func test_Should_CompletePublisherWithFinished_When_JobSucceedsAfterOneRetry() { var calledOnce = false - let retrier = buildRetrier({ + let retrier = buildRetrier({ @MainActor in if !calledOnce { calledOnce = true throw defaultError @@ -66,9 +69,10 @@ class SingleOutputRetrierTests: XCTestCase { cancellable.cancel() } + @MainActor func test_Should_ReceiveAsyncValue_When_JobSucceedsAfterOneRetry() { var calledOnce = false - let retrier = buildRetrier({ + let retrier = buildRetrier({ @MainActor in if !calledOnce { calledOnce = true throw defaultError diff --git a/Tests/SwiftRetrierTests/Retriers/RepeaterTests.swift b/Tests/SwiftRetrierTests/Retriers/RepeaterTests.swift index 4c77e45..394f236 100644 --- a/Tests/SwiftRetrierTests/Retriers/RepeaterTests.swift +++ b/Tests/SwiftRetrierTests/Retriers/RepeaterTests.swift @@ -36,10 +36,11 @@ class RepeaterTests: XCTestCase { super.tearDown() } + @MainActor func test_repeats() { var count = 0 let expectation = expectation(description: "Should repeat") - _ = buildRetrier(repeatDelay, { + _ = buildRetrier(repeatDelay, { @MainActor in count += 1 if count == 3 { expectation.fulfill() From acf7e395c854f87c6472ac49618b726203c7057c Mon Sep 17 00:00:00 2001 From: Pierre Mardon Date: Sun, 16 Jun 2024 22:00:06 +0200 Subject: [PATCH 02/10] Enhance RetryPolicy API, refs #27 (#28) --- Package.swift | 4 +- .../Core/Model/Policies/RetryPolicy.swift | 1 - .../GiveUpCriteriaPolicyWrapper.swift | 37 +++++++++++++++++++ .../GiveUpOnPolicyWrapper.swift | 30 --------------- .../PolicyBuilding/RetryPolicy+GiveUpOn.swift | 22 ++++++----- Sources/SwiftRetrier/DSL/ColdRepeater.swift | 8 ++-- Sources/SwiftRetrier/DSL/ColdRetrier.swift | 8 ++-- 7 files changed, 60 insertions(+), 50 deletions(-) create mode 100644 Sources/SwiftRetrier/Core/PolicyBuilding/GiveUpCriteriaPolicyWrapper.swift delete mode 100644 Sources/SwiftRetrier/Core/PolicyBuilding/GiveUpOnPolicyWrapper.swift diff --git a/Package.swift b/Package.swift index e165fe4..e7c1c9a 100644 --- a/Package.swift +++ b/Package.swift @@ -8,8 +8,8 @@ let lint = false var extraDependencies: [Package.Dependency] = [] var extraPlugins: [Target.PluginUsage] = [] if lint { - extraDependencies = [.package(url: "https://github.com/realm/SwiftLint", exact: "0.52.4")] - extraPlugins = [.plugin(name: "SwiftLintPlugin", package: "SwiftLint")] + extraDependencies = [.package(url: "https://github.com/realm/SwiftLint.git", from: "0.55.1")] + extraPlugins = [.plugin(name: "SwiftLintBuildToolPlugin", package: "SwiftLint")] } let package = Package( diff --git a/Sources/SwiftRetrier/Core/Model/Policies/RetryPolicy.swift b/Sources/SwiftRetrier/Core/Model/Policies/RetryPolicy.swift index 21d20c7..a45a000 100644 --- a/Sources/SwiftRetrier/Core/Model/Policies/RetryPolicy.swift +++ b/Sources/SwiftRetrier/Core/Model/Policies/RetryPolicy.swift @@ -2,6 +2,5 @@ import Foundation public protocol RetryPolicy: Sendable { func shouldRetry(on attemptFailure: AttemptFailure) -> RetryDecision - func retryDelay(for attemptFailure: AttemptFailure) -> TimeInterval func policyAfter(attemptFailure: AttemptFailure, delay: TimeInterval) -> any RetryPolicy } diff --git a/Sources/SwiftRetrier/Core/PolicyBuilding/GiveUpCriteriaPolicyWrapper.swift b/Sources/SwiftRetrier/Core/PolicyBuilding/GiveUpCriteriaPolicyWrapper.swift new file mode 100644 index 0000000..f458077 --- /dev/null +++ b/Sources/SwiftRetrier/Core/PolicyBuilding/GiveUpCriteriaPolicyWrapper.swift @@ -0,0 +1,37 @@ +import Foundation + +public typealias GiveUpCriteria = @Sendable ( + _ attemptFailure: AttemptFailure, + _ nestedPolicyDelay: TimeInterval +) -> Bool + +public struct GiveUpCriteriaPolicyWrapper: RetryPolicy { + + private let wrapped: RetryPolicy + private let giveUpCriteria: GiveUpCriteria + + public init(wrapped: RetryPolicy, giveUpCriteria: @escaping GiveUpCriteria) { + self.wrapped = wrapped + self.giveUpCriteria = giveUpCriteria + } + + public func shouldRetry(on attemptFailure: AttemptFailure) -> RetryDecision { + return switch wrapped.shouldRetry(on: attemptFailure) { + case .giveUp: + .giveUp + case .retry(let delay): + if giveUpCriteria(attemptFailure, delay) { + .giveUp + } else { + .retry(delay: delay) + } + } + } + + public func policyAfter(attemptFailure: AttemptFailure, delay: TimeInterval) -> any RetryPolicy { + GiveUpCriteriaPolicyWrapper( + wrapped: wrapped.policyAfter(attemptFailure: attemptFailure, delay: delay), + giveUpCriteria: giveUpCriteria + ) + } +} diff --git a/Sources/SwiftRetrier/Core/PolicyBuilding/GiveUpOnPolicyWrapper.swift b/Sources/SwiftRetrier/Core/PolicyBuilding/GiveUpOnPolicyWrapper.swift deleted file mode 100644 index cc73128..0000000 --- a/Sources/SwiftRetrier/Core/PolicyBuilding/GiveUpOnPolicyWrapper.swift +++ /dev/null @@ -1,30 +0,0 @@ -import Foundation - -public struct GiveUpOnPolicyWrapper: RetryPolicy { - - private let wrapped: RetryPolicy - private let giveUpCriterium: @Sendable (AttemptFailure) -> Bool - - public init(wrapped: RetryPolicy, giveUpCriterium: @escaping @Sendable (AttemptFailure) -> Bool) { - self.wrapped = wrapped - self.giveUpCriterium = giveUpCriterium - } - - public func retryDelay(for attemptFailure: AttemptFailure) -> TimeInterval { - self.wrapped.retryDelay(for: attemptFailure) - } - - public func shouldRetry(on attemptFailure: AttemptFailure) -> RetryDecision { - guard !giveUpCriterium(attemptFailure) else { - return .giveUp - } - return wrapped.shouldRetry(on: attemptFailure) - } - - public func policyAfter(attemptFailure: AttemptFailure, delay: TimeInterval) -> any RetryPolicy { - GiveUpOnPolicyWrapper( - wrapped: wrapped.policyAfter(attemptFailure: attemptFailure, delay: delay), - giveUpCriterium: giveUpCriterium - ) - } -} diff --git a/Sources/SwiftRetrier/Core/PolicyBuilding/RetryPolicy+GiveUpOn.swift b/Sources/SwiftRetrier/Core/PolicyBuilding/RetryPolicy+GiveUpOn.swift index 70e80d2..1d38962 100644 --- a/Sources/SwiftRetrier/Core/PolicyBuilding/RetryPolicy+GiveUpOn.swift +++ b/Sources/SwiftRetrier/Core/PolicyBuilding/RetryPolicy+GiveUpOn.swift @@ -2,22 +2,26 @@ import Foundation public extension RetryPolicy { - func giveUp(on giveUpCriterium: @escaping @Sendable (AttemptFailure) -> Bool) -> RetryPolicy { - GiveUpOnPolicyWrapper(wrapped: self, giveUpCriterium: giveUpCriterium) + func giveUp(on giveUpCriteria: @escaping GiveUpCriteria) -> RetryPolicy { + GiveUpCriteriaPolicyWrapper(wrapped: self, giveUpCriteria: giveUpCriteria) } func giveUpAfter(maxAttempts: UInt) -> RetryPolicy { - GiveUpOnPolicyWrapper(wrapped: self, giveUpCriterium: { $0.index >= maxAttempts - 1}) + GiveUpCriteriaPolicyWrapper(wrapped: self) { attempt, _ in + attempt.index >= maxAttempts - 1 + } } func giveUpAfter(timeout: TimeInterval) -> RetryPolicy { - GiveUpOnPolicyWrapper(wrapped: self, giveUpCriterium: { - let nextAttemptStart = Date().addingTimeInterval(retryDelay(for: $0)) - return nextAttemptStart >= $0.trialStart.addingTimeInterval(timeout) - }) + GiveUpCriteriaPolicyWrapper(wrapped: self) { attempt, wrappedDelay in + let nextAttemptStart = Date().addingTimeInterval(wrappedDelay) + return nextAttemptStart >= attempt.trialStart.addingTimeInterval(timeout) + } } - func giveUpOnErrors(matching finalErrorCriterium: @escaping @Sendable (Error) -> Bool) -> RetryPolicy { - GiveUpOnPolicyWrapper(wrapped: self, giveUpCriterium: { finalErrorCriterium($0.error) }) + func giveUpOnErrors(matching finalErrorCriteria: @escaping @Sendable (Error) -> Bool) -> RetryPolicy { + GiveUpCriteriaPolicyWrapper(wrapped: self) { attempt, _ in + finalErrorCriteria(attempt.error) + } } } diff --git a/Sources/SwiftRetrier/DSL/ColdRepeater.swift b/Sources/SwiftRetrier/DSL/ColdRepeater.swift index eedd4ee..78e9595 100644 --- a/Sources/SwiftRetrier/DSL/ColdRepeater.swift +++ b/Sources/SwiftRetrier/DSL/ColdRepeater.swift @@ -9,8 +9,8 @@ public struct ColdRepeater { public extension ColdRepeater { - func giveUp(on giveUpCriterium: @escaping @Sendable (AttemptFailure) -> Bool) -> ColdRepeater { - let policy = policy.giveUp(on: giveUpCriterium) + func giveUp(on giveUpCriteria: @escaping GiveUpCriteria) -> ColdRepeater { + let policy = policy.giveUp(on: giveUpCriteria) return ColdRepeater(policy: policy, repeatDelay: repeatDelay, conditionPublisher: conditionPublisher) } @@ -24,8 +24,8 @@ public extension ColdRepeater { return ColdRepeater(policy: policy, repeatDelay: repeatDelay, conditionPublisher: conditionPublisher) } - func giveUpOnErrors(matching finalErrorCriterium: @escaping @Sendable (Error) -> Bool) -> ColdRepeater { - let policy = policy.giveUpOnErrors(matching: finalErrorCriterium) + func giveUpOnErrors(matching finalErrorCriteria: @escaping @Sendable (Error) -> Bool) -> ColdRepeater { + let policy = policy.giveUpOnErrors(matching: finalErrorCriteria) return ColdRepeater(policy: policy, repeatDelay: repeatDelay, conditionPublisher: conditionPublisher) } diff --git a/Sources/SwiftRetrier/DSL/ColdRetrier.swift b/Sources/SwiftRetrier/DSL/ColdRetrier.swift index 7246fdc..2f34075 100644 --- a/Sources/SwiftRetrier/DSL/ColdRetrier.swift +++ b/Sources/SwiftRetrier/DSL/ColdRetrier.swift @@ -8,8 +8,8 @@ public struct ColdRetrier { public extension ColdRetrier { - func giveUp(on giveUpCriterium: @escaping @Sendable (AttemptFailure) -> Bool) -> ColdRetrier { - let policy = policy.giveUp(on: giveUpCriterium) + func giveUp(on giveUpCriteria: @escaping GiveUpCriteria) -> ColdRetrier { + let policy = policy.giveUp(on: giveUpCriteria) return ColdRetrier(policy: policy, conditionPublisher: conditionPublisher) } @@ -23,8 +23,8 @@ public extension ColdRetrier { return ColdRetrier(policy: policy, conditionPublisher: conditionPublisher) } - func giveUpOnErrors(matching finalErrorCriterium: @escaping @Sendable (Error) -> Bool) -> ColdRetrier { - let policy = policy.giveUpOnErrors(matching: finalErrorCriterium) + func giveUpOnErrors(matching finalErrorCriteria: @escaping @Sendable (Error) -> Bool) -> ColdRetrier { + let policy = policy.giveUpOnErrors(matching: finalErrorCriteria) return ColdRetrier(policy: policy, conditionPublisher: conditionPublisher) } From fd3178c69fa4fac61161d425cad0e8dcbe9a4cd7 Mon Sep 17 00:00:00 2001 From: Pierre Mardon Date: Mon, 17 Jun 2024 22:20:57 +0200 Subject: [PATCH 03/10] Revert Swift 6 upgrade, activate concurrency checks, closes #31 (#32) --- Package.swift | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index e7c1c9a..4231288 100644 --- a/Package.swift +++ b/Package.swift @@ -28,13 +28,19 @@ let package = Package( .target( name: "SwiftRetrier", dependencies: [], + swiftSettings: [ + .enableExperimentalFeature("StrictConcurrency=complete") + ], plugins: [] + extraPlugins ), .testTarget( name: "SwiftRetrierTests", dependencies: ["SwiftRetrier"], + swiftSettings: [ + .enableExperimentalFeature("StrictConcurrency=complete") + ], plugins: [] + extraPlugins ) ], - swiftLanguageVersions: [.version("6")] + swiftLanguageVersions: [.version("5.8")] ) From 16d810a8b9b895fa6ea40c7c5f32949b4b1f2381 Mon Sep 17 00:00:00 2001 From: Pierre Mardon Date: Mon, 17 Jun 2024 22:37:25 +0200 Subject: [PATCH 04/10] Enhance README regarding stateless nature of retry policies, refs #33 (#34) --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 20f3544..280b732 100644 --- a/README.md +++ b/README.md @@ -135,6 +135,11 @@ You can add failure conditions using `giveUp*()` functions. You can create your own policies that conform `RetryPolicy` and they will benefit from the same modifiers. Have a look at `ConstantDelayRetryPolicy.swift` for a basic example. +⚠️ Policies should be stateless. To ensure that, I recommend implementing them with `struct` types. + +If a policy needs to know about attempts history, ensure you propagate what's needed when implementing +`policyAfter(attemptFailure:, delay:) -> any RetryPolicy`. + To create a DSL entry point using your policy: ```swift From 963d8a994e6488b2755bb8e886b10d944a4ef68a Mon Sep 17 00:00:00 2001 From: Pierre Mardon Date: Sun, 16 Jun 2024 22:15:21 +0200 Subject: [PATCH 05/10] Use swift instead of xcodebuild in CI, refs #2 --- .github/workflows/swift.yml | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/.github/workflows/swift.yml b/.github/workflows/swift.yml index ce3d300..5a08eea 100644 --- a/.github/workflows/swift.yml +++ b/.github/workflows/swift.yml @@ -9,17 +9,14 @@ jobs: runs-on: macos-14 steps: + - uses: swift-actions/setup-swift@v1 + with: + swift-version: '6.0' - uses: actions/checkout@v4 - - name: List available Xcode versions - run: ls /Applications | grep Xcode - - name: Set up Xcode version - run: sudo xcode-select -s /Applications/Xcode_16.0.app/Contents/Developer - - name: Show current version of Xcode - run: xcodebuild -version - name: Build - run: xcodebuild -scheme SwiftRetrier build -destination "platform=OS X" + run: swift build -v - name: Run tests - run: xcodebuild -scheme SwiftRetrier test -destination "platform=OS X" + run: swift test -v lint: runs-on: ubuntu-latest steps: @@ -27,4 +24,4 @@ jobs: - name: SwiftLint uses: raphaelbussa/swiftlint-action@main with: - subcommand: lint --strict + subcommand: lint --strict \ No newline at end of file From 18b787f2a85f8a1826533e5ce45edd405302da21 Mon Sep 17 00:00:00 2001 From: Pierre Mardon Date: Tue, 18 Jun 2024 08:54:54 +0200 Subject: [PATCH 06/10] Change CI Swift version to 5 --- .github/workflows/swift.yml | 2 +- Package.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/swift.yml b/.github/workflows/swift.yml index 5a08eea..0f1fe11 100644 --- a/.github/workflows/swift.yml +++ b/.github/workflows/swift.yml @@ -11,7 +11,7 @@ jobs: steps: - uses: swift-actions/setup-swift@v1 with: - swift-version: '6.0' + swift-version: '5.9' - uses: actions/checkout@v4 - name: Build run: swift build -v diff --git a/Package.swift b/Package.swift index 4231288..cd24bc2 100644 --- a/Package.swift +++ b/Package.swift @@ -42,5 +42,5 @@ let package = Package( plugins: [] + extraPlugins ) ], - swiftLanguageVersions: [.version("5.8")] + swiftLanguageVersions: [.version("5")] ) From b41b57e4d66865b8acc51977f4f9e87a48335a42 Mon Sep 17 00:00:00 2001 From: Pierre Mardon Date: Tue, 18 Jun 2024 09:29:24 +0200 Subject: [PATCH 07/10] Update setup-swift action to v2 --- .github/workflows/swift.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/swift.yml b/.github/workflows/swift.yml index 0f1fe11..cb08743 100644 --- a/.github/workflows/swift.yml +++ b/.github/workflows/swift.yml @@ -9,7 +9,7 @@ jobs: runs-on: macos-14 steps: - - uses: swift-actions/setup-swift@v1 + - uses: swift-actions/setup-swift@v2 with: swift-version: '5.9' - uses: actions/checkout@v4 From 8d643f605d557dd0ef7899f5c06e66f856e6c408 Mon Sep 17 00:00:00 2001 From: Pierre Mardon Date: Tue, 18 Jun 2024 09:33:01 +0200 Subject: [PATCH 08/10] Try Swift 5.10 --- .github/workflows/swift.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/swift.yml b/.github/workflows/swift.yml index cb08743..2e50f32 100644 --- a/.github/workflows/swift.yml +++ b/.github/workflows/swift.yml @@ -11,7 +11,7 @@ jobs: steps: - uses: swift-actions/setup-swift@v2 with: - swift-version: '5.9' + swift-version: '5.10' - uses: actions/checkout@v4 - name: Build run: swift build -v From f5edd17dcb114609efd564173ebb34248903f739 Mon Sep 17 00:00:00 2001 From: Pierre Mardon Date: Tue, 18 Jun 2024 09:36:10 +0200 Subject: [PATCH 09/10] Print swift version --- .github/workflows/swift.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/swift.yml b/.github/workflows/swift.yml index 2e50f32..0f49d53 100644 --- a/.github/workflows/swift.yml +++ b/.github/workflows/swift.yml @@ -12,6 +12,8 @@ jobs: - uses: swift-actions/setup-swift@v2 with: swift-version: '5.10' + - name: Get swift version + run: swift --version - uses: actions/checkout@v4 - name: Build run: swift build -v From 784e993bcdd14c1b37b81b9fd90290dc49e17b01 Mon Sep 17 00:00:00 2001 From: Pierre Mardon Date: Tue, 18 Jun 2024 10:06:06 +0200 Subject: [PATCH 10/10] Attempt to fix CI error --- .../SingleOutputConditionalRetrierTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/SwiftRetrierTests/Retriers/ProtocolBasedTests/SingleOutputConditionalRetrierTests.swift b/Tests/SwiftRetrierTests/Retriers/ProtocolBasedTests/SingleOutputConditionalRetrierTests.swift index 2c27ef4..0162d7b 100644 --- a/Tests/SwiftRetrierTests/Retriers/ProtocolBasedTests/SingleOutputConditionalRetrierTests.swift +++ b/Tests/SwiftRetrierTests/Retriers/ProtocolBasedTests/SingleOutputConditionalRetrierTests.swift @@ -1,4 +1,4 @@ -import XCTest +@preconcurrency import XCTest @testable import SwiftRetrier import Combine