Skip to content

Commit

Permalink
Rename DynamicPendable to Pendable
Browse files Browse the repository at this point in the history
  • Loading branch information
younata committed Apr 13, 2024
1 parent 3dc0209 commit 75d1083
Show file tree
Hide file tree
Showing 6 changed files with 56 additions and 56 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,22 @@ protocol ResolvableWithFallback {
func resolveWithFallback()
}

/// DynamicPendable is a safe way to represent the 2 states that an asynchronous call can be in
/// Pendable is a safe way to represent the 2 states that an asynchronous call can be in
///
/// - `pending`, the state while waiting for the call to finish.
/// - `finished`, the state once the call has finished.
///
/// DynamicPendable, as the name suggests, is dynamic - it allows you to finish a pending
/// call after it's been made. This makes DynamicPendable behave very similarly to something like
/// Combine's `Future`.
/// Pendable allows you to finish a pending call after it's been made. This makes Pendable behave very
/// similarly to something like Combine's `Future`.
///
/// - Note: The reason you must provide a fallback value is to prevent deadlock when used in test.
/// Unlike something like Combine's `Future`, it is very often the case that you will write
/// tests which end while the call is in the pending state. If you do this too much, then your
/// entire test suite will deadlock, as Swift Concurrency works under the assumption that
/// blocked tasks of work will always eventually be unblocked. To help prevent this, pending calls
/// are always resolved with the fallback after a given delay. You can also manually force this
/// by calling the ``resolveWithFallback`` method.
public final class DynamicPendable<Value: Sendable>: @unchecked Sendable, ResolvableWithFallback {
/// by calling the ``Pendable\resolveWithFallback()`` method.
public final class Pendable<Value: Sendable>: @unchecked Sendable, ResolvableWithFallback {
private enum State: Sendable {
case pending
case finished(Value)
Expand All @@ -46,15 +45,15 @@ public final class DynamicPendable<Value: Sendable>: @unchecked Sendable, Resolv
resolveWithFallback()
}

/// Initializes a new `DynamicPendable`, in a pending state, with the given fallback value.
/// Initializes a new `Pendable`, in a pending state, with the given fallback value.
public init(fallbackValue: Value) {
self.fallbackValue = fallbackValue
}

/// Gets the value for the `DynamicPendable`, possibly waiting until it's resolved.
/// Gets the value for the `Pendable`, possibly waiting until it's resolved.
///
/// - parameter fallbackDelay: The amount of time (in seconds) to wait until the call returns
/// the fallback value. This is only really used when the `DynamicPendable` is in a pending state.
/// the fallback value. This is only used when the `Pendable` is in a pending state.
public func call(fallbackDelay: TimeInterval = PendableDefaults.delay) async -> Value {
return await withTaskGroup(of: Value.self) { taskGroup in
taskGroup.addTask { await self.handleCall() }
Expand All @@ -69,9 +68,10 @@ public final class DynamicPendable<Value: Sendable>: @unchecked Sendable, Resolv
}
}

/// Resolves the `DynamicPendable` with the fallback value.
/// Resolves the `Pendable` with the fallback value.
///
/// Note: This no-ops if the pendable is already in a resolved state.
/// - Note: This no-ops if the pendable is already in a resolved state.
/// - Note: This is called for when you re-stub a `Spy` in ``Spy/stub(_:)``
public func resolveWithFallback() {
lock.lock()
defer { lock.unlock() }
Expand All @@ -81,7 +81,7 @@ public final class DynamicPendable<Value: Sendable>: @unchecked Sendable, Resolv
}
}

/// Resolves the `DynamicPendable` with the given value.
/// Resolves the `Pendable` with the given value.
///
/// Even if the pendable is already resolves, this resets the resolved value to the given value.
public func resolve(with value: Value) {
Expand All @@ -95,7 +95,7 @@ public final class DynamicPendable<Value: Sendable>: @unchecked Sendable, Resolv

}

/// Resolves any outstanding calls to the `DynamicPendable` with the current value,
/// Resolves any outstanding calls to the `Pendable` with the current value,
/// and resets it back into the pending state.
public func reset() {
lock.lock()
Expand Down Expand Up @@ -131,45 +131,45 @@ public final class DynamicPendable<Value: Sendable>: @unchecked Sendable, Resolv
}
}

public typealias ThrowingDynamicPendable<Success, Failure: Error> = DynamicPendable<Result<Success, Failure>>
public typealias ThrowingDynamicPendable<Success, Failure: Error> = Pendable<Result<Success, Failure>>

extension DynamicPendable {
/// Gets or throws value for the `DynamicPendable`, possibly waiting until it's resolved.
extension Pendable {
/// Gets or throws value for the `Pendable`, possibly waiting until it's resolved.
///
/// - parameter resolveDelay: The amount of time (in seconds) to wait until the call returns
/// the fallback value. This is only really used when the `DynamicPendable` is in a pending state.
/// the fallback value. This is only used when the `Pendable` is in a pending state.
public func call<Success, Failure: Error>(resolveDelay: TimeInterval = PendableDefaults.delay) async throws -> Success where Value == Result<Success, Failure> {

Check warning on line 141 in Sources/Fakes/Pendable/Pendable.swift

View workflow job for this annotation

GitHub Actions / lint

Line Length Violation: Line should be 160 characters or less; currently it has 164 characters (line_length)
try await call(fallbackDelay: resolveDelay).get()
}
}

extension DynamicPendable {
/// Creates a new finished `DynamicPendable` pre-resolved with the given value.
public static func finished(_ value: Value) -> DynamicPendable<Value> {
let pendable = DynamicPendable(fallbackValue: value)
extension Pendable {
/// Creates a new finished `Pendable` pre-resolved with the given value.
public static func finished(_ value: Value) -> Pendable<Value> {
let pendable = Pendable(fallbackValue: value)
pendable.resolve(with: value)
return pendable
}

/// Creates a new finished `DynamicPendable` pre-resolved with Void.
public static func finished() -> DynamicPendable where Value == Void {
return DynamicPendable.finished(())
/// Creates a new finished `Pendable` pre-resolved with Void.
public static func finished() -> Pendable where Value == Void {
return Pendable.finished(())
}
}

extension DynamicPendable {
/// Creates a new pending `DynamicPendable` with the given fallback value.
public static func pending(fallback: Value) -> DynamicPendable<Value> {
return DynamicPendable(fallbackValue: fallback)
extension Pendable {
/// Creates a new pending `Pendable` with the given fallback value.
public static func pending(fallback: Value) -> Pendable<Value> {
return Pendable(fallbackValue: fallback)
}

/// Creates a new pending `DynamicPendable` with a fallback value of Void.
public static func pending() -> DynamicPendable<Value> where Value == Void {
return DynamicPendable(fallbackValue: ())
/// Creates a new pending `Pendable` with a fallback value of Void.
public static func pending() -> Pendable<Value> where Value == Void {
return Pendable(fallbackValue: ())
}

/// Creates a new pending `DynamicPendable` with a fallback value of nil.
public static func pending<Wrapped>() -> DynamicPendable<Value> where Value == Optional<Wrapped> {
return DynamicPendable(fallbackValue: nil)
/// Creates a new pending `Pendable` with a fallback value of nil.
public static func pending<Wrapped>() -> Pendable<Value> where Value == Optional<Wrapped> {

Check warning on line 172 in Sources/Fakes/Pendable/Pendable.swift

View workflow job for this annotation

GitHub Actions / lint

Syntactic Sugar Violation: Shorthand syntactic sugar should be used, i.e. Int? instead of Optional<Int> (syntactic_sugar)
return Pendable(fallbackValue: nil)
}
}
26 changes: 13 additions & 13 deletions Sources/Fakes/Spy/Spy+Pendable.swift
Original file line number Diff line number Diff line change
@@ -1,73 +1,73 @@
import Foundation

public typealias PendableSpy<Arguments, Value> = Spy<Arguments, DynamicPendable<Value>>
public typealias PendableSpy<Arguments, Value> = Spy<Arguments, Pendable<Value>>

extension Spy {
/// Create a pendable Spy that is pre-stubbed to return return a a pending that will block for a bit before returning the fallback value.
public convenience init<Value>(pendingFallback: Value) where Returning == DynamicPendable<Value> {
public convenience init<Value>(pendingFallback: Value) where Returning == Pendable<Value> {
self.init(.pending(fallback: pendingFallback))
}

/// Create a pendable Spy that is pre-stubbed to return return a a pending that will block for a bit before returning Void.
public convenience init() where Returning == DynamicPendable<Void> {
public convenience init() where Returning == Pendable<Void> {
self.init(.pending(fallback: ()))
}

/// Create a pendable Spy that is pre-stubbed to return a finished value.
public convenience init<Value>(finished: Value) where Returning == DynamicPendable<Value> {
public convenience init<Value>(finished: Value) where Returning == Pendable<Value> {
self.init(.finished(finished))
}
}

extension Spy {
/// Update the pendable Spy's stub to be in a pending state.
public func stub<Value>(pendingFallback: Value) where Returning == DynamicPendable<Value> {
public func stub<Value>(pendingFallback: Value) where Returning == Pendable<Value> {
self.stub(.pending(fallback: pendingFallback))
}

/// Update the pendable Spy's stub to be in a pending state.
public func stubPending() where Returning == DynamicPendable<Void> {
public func stubPending() where Returning == Pendable<Void> {
self.stub(.pending(fallback: ()))
}

/// Update the pendable Spy's stub to be in a pending state.
public func stubPending<Wrapped>() where Returning == DynamicPendable<Optional<Wrapped>> {
public func stubPending<Wrapped>() where Returning == Pendable<Optional<Wrapped>> {

Check warning on line 34 in Sources/Fakes/Spy/Spy+Pendable.swift

View workflow job for this annotation

GitHub Actions / lint

Syntactic Sugar Violation: Shorthand syntactic sugar should be used, i.e. Int? instead of Optional<Int> (syntactic_sugar)
self.stub(.pending(fallback: nil))
}

/// Update the pendable Spy's stub to return the given value.
///
/// - parameter finished: The value to return when `callAsFunction` is called.
public func stub<Value>(finished: Value) where Returning == DynamicPendable<Value> {
public func stub<Value>(finished: Value) where Returning == Pendable<Value> {
self.stub(.finished(finished))
}

/// Update the pendable Spy's stub to be in a pending state.
public func stubFinished() where Returning == DynamicPendable<Void> {
public func stubFinished() where Returning == Pendable<Void> {
self.stub(.finished(()))
}
}

extension Spy {
/// Records the arguments and handles the result according to ``Pendable/resolve(delay:)-hvhg``.
/// Records the arguments and handles the result according to ``Pendable/call(fallbackDelay:)``.
///
/// - parameter arguments: The arguments to record.
/// - parameter fallbackDelay: The amount of seconds to delay if the `Pendable` is pending before
/// returning its fallback value. If the `Pendable` is finished, then this value is ignored.
public func callAsFunction<Value>(
_ arguments: Arguments,
fallbackDelay: TimeInterval = PendableDefaults.delay
) async -> Value where Returning == DynamicPendable<Value> {
) async -> Value where Returning == Pendable<Value> {
return await call(arguments).call(fallbackDelay: fallbackDelay)
}

/// Records that a call was made and handles the result according to ``Pendable/resolve(delay:)-hvhg``.
/// Records that a call was made and handles the result according to ``Pendable/call(fallbackDelay:)``.
///
/// - parameter fallbackDelay: The amount of seconds to delay if the `Pendable` is pending before
/// returning its fallback value. If the `Pendable` is finished, then this value is ignored.
public func callAsFunction<Value>(
fallbackDelay: TimeInterval = PendableDefaults.delay
) async -> Value where Arguments == Void, Returning == DynamicPendable<Value> {
) async -> Value where Arguments == Void, Returning == Pendable<Value> {
return await call(()).call(fallbackDelay: fallbackDelay)
}
}
4 changes: 2 additions & 2 deletions Sources/Fakes/Spy/Spy+ThrowingPendable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ extension Spy {

extension Spy {
// Returning == ThrowingPendable
/// Records the arguments and handles the result according to ``Pendable/resolve(delay:)-1bb25``.
/// Records the arguments and handles the result according to ``Pendable/call(fallbackDelay:)``.
/// This call then throws or returns the success, according to `Result.get`.
///
/// - parameter arguments: The arguments to record.
Expand All @@ -70,7 +70,7 @@ extension Spy {
return try await call(arguments).call(fallbackDelay: fallbackDelay).get()
}

/// Records that a call was made and handles the result according to ``Pendable/resolve(delay:)-1bb25``.
/// Records that a call was made and handles the result according to ``Pendable/call(fallbackDelay:)``.
/// This call then throws or returns the success, according to `Result.get`.
///
/// - parameter pendingDelay: The amount of seconds to delay if the `Pendable` is .pending before
Expand Down
2 changes: 1 addition & 1 deletion Sources/Fakes/Spy/Spy.swift
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ extension Spy {
}

extension Spy {
public func resolveStub<Value>(with value: Value) where Returning == DynamicPendable<Value> {
public func resolveStub<Value>(with value: Value) where Returning == Pendable<Value> {
lock.lock()
defer { lock.unlock() }
_stub.resolve(with: value)
Expand Down
6 changes: 3 additions & 3 deletions Tests/FakesTests/DynamicPendableTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import XCTest

final class DynamicPendableTests: XCTestCase {
func testSingleCall() async {
let subject = DynamicPendable<Int>.pending(fallback: 0)
let subject = Pendable<Int>.pending(fallback: 0)

async let result = subject.call()

Expand All @@ -17,7 +17,7 @@ final class DynamicPendableTests: XCTestCase {
}

func testMultipleCalls() async {
let subject = DynamicPendable<Int>.pending(fallback: 0)
let subject = Pendable<Int>.pending(fallback: 0)

async let result = withTaskGroup(of: Int.self, returning: [Int].self) { taskGroup in
for _ in 0..<100 {
Expand All @@ -40,7 +40,7 @@ final class DynamicPendableTests: XCTestCase {
}

func testAutoresolve() async {
let subject = DynamicPendable<Int>.pending(fallback: 3)
let subject = Pendable<Int>.pending(fallback: 3)

let expectation = self.expectation(description: "Autoresolves after the given delay")

Expand Down
6 changes: 3 additions & 3 deletions Tests/FakesTests/SpyTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ final class SpyTests: XCTestCase {
}

func testDynamicPendable() async {
let subject = Spy<Void, DynamicPendable<Void>>()
let subject = Spy<Void, Pendable<Void>>()

let managedTask = await ManagedTask<Void, Never>.running {
await subject()
Expand All @@ -164,15 +164,15 @@ final class SpyTests: XCTestCase {
}

func testDynamicPendableDeinit() async {
let subject = Spy<Void, DynamicPendable<Void>>()
let subject = Spy<Void, Pendable<Void>>()

let managedTask = await ManagedTask<Void, Never>.running {
await subject()
}

await expect { await managedTask.hasStarted }.toEventually(beTrue())

subject.stub(DynamicPendable.pending())
subject.stub(Pendable.pending())
subject.resolveStub(with: ())

await expect { await managedTask.isFinished }.toEventually(beTrue())
Expand Down

0 comments on commit 75d1083

Please sign in to comment.