Skip to content

Commit

Permalink
Add Retry implementation & tests
Browse files Browse the repository at this point in the history
  • Loading branch information
finestructure committed Jul 18, 2023
1 parent 40b7efe commit 8d46e7e
Show file tree
Hide file tree
Showing 2 changed files with 94 additions and 7 deletions.
40 changes: 38 additions & 2 deletions Sources/Retry/Retry.swift
Original file line number Diff line number Diff line change
@@ -1,2 +1,38 @@
// The Swift Programming Language
// https://docs.swift.org/swift-book
import Foundation


enum Retry {
public enum Error: Swift.Error, Equatable {
case retryLimitExceeded(lastError: String?)
}

public static func backedOffDelay(baseDelay: Double, attempt: Int) -> UInt32 {
(pow(2, max(0, attempt - 1)) * Decimal(baseDelay) as NSDecimalNumber).uint32Value
}

public static func attempt<T>(_ label: String,
delay: Double = 5,
retries: Int = 5,
_ block: () throws -> T) throws -> T {
var retriesLeft = retries
var currentTry = 1
var lastError: String?
while true {
if currentTry > 1 {
print("\(label) (attempt \(currentTry))")
}
do {
return try block()
} catch {
lastError = "\(error)"
guard retriesLeft > 0 else { break }
let delay = backedOffDelay(baseDelay: delay, attempt: currentTry)
print("Retrying in \(delay) seconds ...")
sleep(delay)
currentTry += 1
retriesLeft -= 1
}
}
throw Error.retryLimitExceeded(lastError: lastError)
}
}
61 changes: 56 additions & 5 deletions Tests/RetryTests/RetryTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,62 @@ import XCTest
@testable import Retry

final class RetryTests: XCTestCase {
func testExample() throws {
// XCTest Documentation
// https://developer.apple.com/documentation/xctest

// Defining Test Cases and Test Methods
// https://developer.apple.com/documentation/xctest/defining_test_cases_and_test_methods
func test_backedOffDelay() throws {
XCTAssertEqual(Retry.backedOffDelay(baseDelay: 1, attempt: 0), 1)
XCTAssertEqual(Retry.backedOffDelay(baseDelay: 1, attempt: 1), 1)
XCTAssertEqual(Retry.backedOffDelay(baseDelay: 1, attempt: 2), 2)
XCTAssertEqual(Retry.backedOffDelay(baseDelay: 1, attempt: 3), 4)
XCTAssertEqual(Retry.backedOffDelay(baseDelay: 1, attempt: 4), 8)
}

func test_attempt_immediate_success() throws {
var called = 0

// MUT
try Retry.attempt("", delay: 0, retries: 3) {
called += 1
}

// validation
XCTAssertEqual(called, 1)
}

func test_attempt_success_after_retry() throws {
var called = 0
struct Error: Swift.Error { }

// MUT
try Retry.attempt("", delay: 0, retries: 3) {
called += 1
if called < 3 {
throw Error()
}
}

// validation
XCTAssertEqual(called, 3)
}

func test_attempt_retryLimitExceeded() throws {
var called = 0
struct Error: Swift.Error, CustomStringConvertible {
var description: String { "test error" }
}

// MUT
XCTAssertThrowsError(
try Retry.attempt("", delay: 0, retries: 3) {
called += 1
throw Error()
}

) { error in
XCTAssertEqual(error as? Retry.Error, .retryLimitExceeded(lastError: "test error"))
}

// validation
XCTAssertEqual(called, 4)
}

}

0 comments on commit 8d46e7e

Please sign in to comment.