From 5e3a18d8c76e10b94de954b4e7238c808dfc7562 Mon Sep 17 00:00:00 2001 From: Sergej Jaskiewicz Date: Thu, 11 Jul 2019 00:54:07 +0300 Subject: [PATCH] Implement Publishers.SetFailureType (#28) --- RemainingCombineInterface.swift | 55 ------ Sources/OpenCombine/AnyPublisher.swift | 4 +- Sources/OpenCombine/AnySubject.swift | 4 +- .../Publishers/Publishers.Count.swift | 2 +- .../Publishers/Publishers.Decode.swift | 2 +- .../Publishers/Publishers.DropWhile.swift | 4 +- .../Publishers/Publishers.Encode.swift | 2 +- .../Publishers/Publishers.Map.swift | 16 +- .../Publishers/Publishers.MapError.swift | 2 +- .../Publishers/Publishers.Print.swift | 2 +- .../Publishers.SetFailureType.swift | 86 +++++++++ Sources/OpenCombine/Subscriptions.swift | 4 +- .../OpenCombineTests/AnySubscriberTests.swift | 2 +- .../Helpers/CustomPublisher.swift | 17 +- .../Helpers/TrackingSubscriber.swift | 26 +-- .../PublisherTests/DecodeTests.swift | 2 +- .../PublisherTests/EncodeTests.swift | 4 +- .../PublisherTests/MapTests.swift | 14 +- .../PublisherTests/SetFailureTypeTests.swift | 172 ++++++++++++++++++ Tests/OpenCombineTests/XCTestManifests.swift | 1 + 20 files changed, 319 insertions(+), 102 deletions(-) create mode 100644 Sources/OpenCombine/Publishers/Publishers.SetFailureType.swift create mode 100644 Tests/OpenCombineTests/PublisherTests/SetFailureTypeTests.swift diff --git a/RemainingCombineInterface.swift b/RemainingCombineInterface.swift index 7331e7d..42d5cfd 100644 --- a/RemainingCombineInterface.swift +++ b/RemainingCombineInterface.swift @@ -742,48 +742,6 @@ final public class Future : Publisher where Failure : Error { final public func receive(subscriber: S) where Output == S.Input, Failure == S.Failure, S : Subscriber } - -extension Publishers { - - /// A publisher that appears to send a specified failure type. - /// - /// The publisher cannot actually fail with the specified type and instead just finishes normally. Use this publisher type when you need to match the error types for two mismatched publishers. - public struct SetFailureType : Publisher where Upstream : Publisher, Failure : Error, Upstream.Failure == Never { - - /// The kind of values published by this publisher. - public typealias Output = Upstream.Output - - /// The publisher from which this publisher receives elements. - public let upstream: Upstream - - /// Creates a publisher that appears to send a specified failure type. - /// - /// - Parameter upstream: The publisher from which this publisher receives elements. - public init(upstream: Upstream) - - /// This function is called to attach the specified `Subscriber` to this `Publisher` by `subscribe(_:)` - /// - /// - SeeAlso: `subscribe(_:)` - /// - Parameters: - /// - subscriber: The subscriber to attach to this `Publisher`. - /// once attached it can begin to receive values. - public func receive(subscriber: S) where Failure == S.Failure, S : Subscriber, Upstream.Output == S.Input - - public func setFailureType(to failure: E.Type) -> Publishers.SetFailureType where E : Error - } -} - -extension Publisher where Self.Failure == Never { - - /// Changes the failure type declared by the upstream publisher. - /// - /// The publisher returned by this method cannot actually fail with the specified type and instead just finishes normally. Instead, you use this method when you need to match the error types of two mismatched publishers. - /// - /// - Parameter failureType: The `Failure` type presented by this publisher. - /// - Returns: A publisher that appears to send the specified failure type. - public func setFailureType(to failureType: E.Type) -> Publishers.SetFailureType where E : Error -} - extension Publishers { /// A publisher that emits a Boolean value upon receiving an element that satisfies the predicate closure. @@ -3301,19 +3259,6 @@ extension Publishers.CombineLatest4 : Equatable where A : Equatable, B : Equatab public static func == (lhs: Publishers.CombineLatest4, rhs: Publishers.CombineLatest4) -> Bool } -extension Publishers.SetFailureType : Equatable where Upstream : Equatable { - - /// Returns a Boolean value indicating whether two values are equal. - /// - /// Equality is the inverse of inequality. For any values `a` and `b`, - /// `a == b` implies that `a != b` is `false`. - /// - /// - Parameters: - /// - lhs: A value to compare. - /// - rhs: Another value to compare. - public static func == (lhs: Publishers.SetFailureType, rhs: Publishers.SetFailureType) -> Bool -} - extension Publishers.Collect : Equatable where Upstream : Equatable { /// Returns a Boolean value indicating whether two values are equal. diff --git a/Sources/OpenCombine/AnyPublisher.swift b/Sources/OpenCombine/AnyPublisher.swift index a8ef4fc..a641b20 100644 --- a/Sources/OpenCombine/AnyPublisher.swift +++ b/Sources/OpenCombine/AnyPublisher.swift @@ -39,7 +39,7 @@ extension AnyPublisher: Publisher { public func receive(subscriber: SubscriberType) where Output == SubscriberType.Input, Failure == SubscriberType.Failure { - box.receive(subscriber: subscriber) + box.subscribe(subscriber) } } @@ -77,6 +77,6 @@ internal final class PublisherBox override internal func receive(subscriber: SubscriberType) where Failure == SubscriberType.Failure, Output == SubscriberType.Input { - base.receive(subscriber: subscriber) + base.subscribe(subscriber) } } diff --git a/Sources/OpenCombine/AnySubject.swift b/Sources/OpenCombine/AnySubject.swift index 81ffbdd..e05409c 100644 --- a/Sources/OpenCombine/AnySubject.swift +++ b/Sources/OpenCombine/AnySubject.swift @@ -26,7 +26,7 @@ public final class AnySubject: Subject { public func receive(subscriber: SubscriberType) where Output == SubscriberType.Input, Failure == SubscriberType.Failure { - _box.receive(subscriber: subscriber) + _box.subscribe(subscriber) } public func send(_ value: Output) { @@ -78,7 +78,7 @@ private final class SubjectBox override func receive(subscriber: SubscriberType) where Failure == SubscriberType.Failure, Output == SubscriberType.Input { - base.receive(subscriber: subscriber) + base.subscribe(subscriber) } } diff --git a/Sources/OpenCombine/Publishers/Publishers.Count.swift b/Sources/OpenCombine/Publishers/Publishers.Count.swift index f544d87..f3928fa 100644 --- a/Sources/OpenCombine/Publishers/Publishers.Count.swift +++ b/Sources/OpenCombine/Publishers/Publishers.Count.swift @@ -40,7 +40,7 @@ extension Publishers { SubscriberType.Input == Output { let count = _Count(downstream: subscriber) - upstream.receive(subscriber: count) + upstream.subscribe(count) } } } diff --git a/Sources/OpenCombine/Publishers/Publishers.Decode.swift b/Sources/OpenCombine/Publishers/Publishers.Decode.swift index 8a95c9b..3f61cf8 100644 --- a/Sources/OpenCombine/Publishers/Publishers.Decode.swift +++ b/Sources/OpenCombine/Publishers/Publishers.Decode.swift @@ -42,7 +42,7 @@ extension Publishers { downstream: subscriber, decoder: _decoder ) - upstream.receive(subscriber: decodeSubscriber) + upstream.subscribe(decodeSubscriber) } } } diff --git a/Sources/OpenCombine/Publishers/Publishers.DropWhile.swift b/Sources/OpenCombine/Publishers/Publishers.DropWhile.swift index eb2a0b8..b457bf3 100644 --- a/Sources/OpenCombine/Publishers/Publishers.DropWhile.swift +++ b/Sources/OpenCombine/Publishers/Publishers.DropWhile.swift @@ -30,7 +30,7 @@ extension Publishers { where Failure == SubscriberType.Failure, Output == SubscriberType.Input { let inner = Inner(downstream: subscriber, predicate: catching(predicate)) - upstream.receive(subscriber: inner) + upstream.subscribe(inner) } } @@ -57,7 +57,7 @@ extension Publishers { where Output == SubscriberType.Input, SubscriberType.Failure == Error { let inner = Inner(downstream: subscriber, predicate: catching(predicate)) - upstream.receive(subscriber: inner) + upstream.subscribe(inner) } } } diff --git a/Sources/OpenCombine/Publishers/Publishers.Encode.swift b/Sources/OpenCombine/Publishers/Publishers.Encode.swift index defbce7..be26a90 100644 --- a/Sources/OpenCombine/Publishers/Publishers.Encode.swift +++ b/Sources/OpenCombine/Publishers/Publishers.Encode.swift @@ -44,7 +44,7 @@ extension Publishers { downstream: subscriber, encoder: encoder ) - upstream.receive(subscriber: encodeSubscriber) + upstream.subscribe(encodeSubscriber) } } } diff --git a/Sources/OpenCombine/Publishers/Publishers.Map.swift b/Sources/OpenCombine/Publishers/Publishers.Map.swift index 9f74551..12d50d1 100644 --- a/Sources/OpenCombine/Publishers/Publishers.Map.swift +++ b/Sources/OpenCombine/Publishers/Publishers.Map.swift @@ -48,6 +48,12 @@ extension Publishers { /// The closure that transforms elements from the upstream publisher. public let transform: (Upstream.Output) -> Output + + public init(upstream: Upstream, + transform: @escaping (Upstream.Output) -> Output) { + self.upstream = upstream + self.transform = transform + } } /// A publisher that transforms all elements from the upstream publisher @@ -62,6 +68,12 @@ extension Publishers { /// The error-throwing closure that transforms elements from /// the upstream publisher. public let transform: (Upstream.Output) throws -> Output + + public init(upstream: Upstream, + transform: @escaping (Upstream.Output) throws -> Output) { + self.upstream = upstream + self.transform = transform + } } } @@ -70,7 +82,7 @@ extension Publishers.Map { where Output == Downstream.Input, Downstream.Failure == Upstream.Failure { let inner = Inner(downstream: subscriber, transform: catching(transform)) - upstream.receive(subscriber: inner) + upstream.subscribe(inner) } public func map( @@ -92,7 +104,7 @@ extension Publishers.TryMap { where Output == Downstream.Input, Downstream.Failure == Error { let inner = Inner(downstream: subscriber, transform: catching(transform)) - upstream.receive(subscriber: inner) + upstream.subscribe(inner) } public func map( diff --git a/Sources/OpenCombine/Publishers/Publishers.MapError.swift b/Sources/OpenCombine/Publishers/Publishers.MapError.swift index fbf35ec..917d7db 100644 --- a/Sources/OpenCombine/Publishers/Publishers.MapError.swift +++ b/Sources/OpenCombine/Publishers/Publishers.MapError.swift @@ -42,7 +42,7 @@ extension Publishers { downstream: subscriber, transform: transform ) - upstream.receive(subscriber: mapErrorSubscriber) + upstream.subscribe(mapErrorSubscriber) } } } diff --git a/Sources/OpenCombine/Publishers/Publishers.Print.swift b/Sources/OpenCombine/Publishers/Publishers.Print.swift index 5e9d690..7c36c43 100644 --- a/Sources/OpenCombine/Publishers/Publishers.Print.swift +++ b/Sources/OpenCombine/Publishers/Publishers.Print.swift @@ -47,7 +47,7 @@ extension Publishers { where Failure == SubscriberType.Failure, Output == SubscriberType.Input { let inner = Inner(downstream: subscriber, prefix: prefix, stream: stream) - upstream.receive(subscriber: inner) + upstream.subscribe(inner) } } } diff --git a/Sources/OpenCombine/Publishers/Publishers.SetFailureType.swift b/Sources/OpenCombine/Publishers/Publishers.SetFailureType.swift new file mode 100644 index 0000000..ee9f063 --- /dev/null +++ b/Sources/OpenCombine/Publishers/Publishers.SetFailureType.swift @@ -0,0 +1,86 @@ +// +// Publishers.SetFailureType.swift +// +// +// Created by Sergej Jaskiewicz on 08.07.2019. +// + +extension Publishers { + + /// A publisher that appears to send a specified failure type. + /// + /// The publisher cannot actually fail with the specified type and instead + /// just finishes normally. Use this publisher type when you need to match + /// the error types for two mismatched publishers. + public struct SetFailureType: Publisher + where Upstream.Failure == Never + { + public typealias Output = Upstream.Output + + /// The publisher from which this publisher receives elements. + public let upstream: Upstream + + /// Creates a publisher that appears to send a specified failure type. + /// + /// - Parameter upstream: The publisher from which this publisher receives + /// elements. + public init(upstream: Upstream) { + self.upstream = upstream + } + + public func receive(subscriber: Downstream) + where Downstream.Failure == Failure, Downstream.Input == Output + { + let inner = Inner(downstream: subscriber) + upstream.subscribe(inner) + } + + public func setFailureType( + to failure: NewFailure.Type + ) -> Publishers.SetFailureType { + return .init(upstream: upstream) + } + } +} + +extension Publishers.SetFailureType: Equatable where Upstream: Equatable {} + +extension Publisher where Failure == Never { + + /// Changes the failure type declared by the upstream publisher. + /// + /// The publisher returned by this method cannot actually fail + /// with the specified type and instead just finishes normally. Instead, you use + /// this method when you need to match the error types of two mismatched publishers. + /// + /// - Parameter failureType: The `Failure` type presented by this publisher. + /// - Returns: A publisher that appears to send the specified failure type. + public func setFailureType( + to failureType: NewFailure.Type + ) -> Publishers.SetFailureType { + return .init(upstream: self) + } +} + +extension Publishers.SetFailureType { + private final class Inner + : OperatorSubscription, + Subscriber, + CustomStringConvertible + where Upstream.Output == Downstream.Input + { + func receive(subscription: Subscription) { + downstream.receive(subscription: subscription) + } + + func receive(_ input: Upstream.Output) -> Subscribers.Demand { + return downstream.receive(input) + } + + func receive(completion: Subscribers.Completion) { + downstream.receive(completion: .finished) + } + + var description: String { return "SetFailureType" } + } +} diff --git a/Sources/OpenCombine/Subscriptions.swift b/Sources/OpenCombine/Subscriptions.swift index 4ca9beb..fc0780e 100644 --- a/Sources/OpenCombine/Subscriptions.swift +++ b/Sources/OpenCombine/Subscriptions.swift @@ -24,9 +24,7 @@ private final class Empty: Subscription, CustomStringConvertible, CustomReflecta func cancel() {} - var combineIdentifier: CombineIdentifier { return CombineIdentifier() } - - static let shared = Empty() + fileprivate static let shared = Empty() var description: String { return "Empty" } diff --git a/Tests/OpenCombineTests/AnySubscriberTests.swift b/Tests/OpenCombineTests/AnySubscriberTests.swift index e6ece72..672266f 100644 --- a/Tests/OpenCombineTests/AnySubscriberTests.swift +++ b/Tests/OpenCombineTests/AnySubscriberTests.swift @@ -136,7 +136,7 @@ final class AnySubscriberTests: XCTestCase { publishEvents(events, erased) - let expectedEvents: [TrackingSubject.Event] = + let expectedEvents: [TrackingSubject.Event] = events.compactMap(subscriberEventToSubjectEvent) XCTAssertEqual(subject.history, expectedEvents) diff --git a/Tests/OpenCombineTests/Helpers/CustomPublisher.swift b/Tests/OpenCombineTests/Helpers/CustomPublisher.swift index 7662b2d..13107f8 100644 --- a/Tests/OpenCombineTests/Helpers/CustomPublisher.swift +++ b/Tests/OpenCombineTests/Helpers/CustomPublisher.swift @@ -31,15 +31,12 @@ import OpenCombine /// /// assert(subscription.history == [.requested(.max(42)), .cancelled]) @available(macOS 10.15, *) -typealias CustomPublisher = CustomPublisherBase +typealias CustomPublisher = CustomPublisherBase @available(macOS 10.15, *) -final class CustomPublisherBase: Publisher { +final class CustomPublisherBase: Publisher { - typealias Output = Value - typealias Failure = TestingError - - private(set) var subscriber: AnySubscriber? + private(set) var subscriber: AnySubscriber? private(set) var erasedSubscriber: Any? private let subscription: Subscription? @@ -47,19 +44,19 @@ final class CustomPublisherBase: Publisher { self.subscription = subscription } - func receive(subscriber: SubscriberType) - where Failure == SubscriberType.Failure, Output == SubscriberType.Input + func receive(subscriber: Downstream) + where Failure == Downstream.Failure, Output == Downstream.Input { self.subscriber = AnySubscriber(subscriber) erasedSubscriber = subscriber subscription.map(subscriber.receive(subscription:)) } - func send(_ value: Value) -> Subscribers.Demand { + func send(_ value: Output) -> Subscribers.Demand { return subscriber?.receive(value) ?? .none } - func send(completion: Subscribers.Completion) { + func send(completion: Subscribers.Completion) { subscriber!.receive(completion: completion) } } diff --git a/Tests/OpenCombineTests/Helpers/TrackingSubscriber.swift b/Tests/OpenCombineTests/Helpers/TrackingSubscriber.swift index 2dc7989..c95abf8 100644 --- a/Tests/OpenCombineTests/Helpers/TrackingSubscriber.swift +++ b/Tests/OpenCombineTests/Helpers/TrackingSubscriber.swift @@ -37,8 +37,7 @@ typealias TrackingSubscriber = TrackingSubscriberBase /// is considered equal to any other subscription no matter what the subscription object /// actually is. @available(macOS 10.15, *) -final class TrackingSubscriberBase +final class TrackingSubscriberBase : Subscriber, CustomStringConvertible { @@ -168,16 +167,17 @@ final class TrackingSubscriberBase: Subject, CustomStringConvertible { - - typealias Failure = TestingError - - typealias Output = Value +typealias TrackingSubject = TrackingSubjectBase +@available(macOS 10.15, *) +final class TrackingSubjectBase + : Subject, + CustomStringConvertible +{ enum Event: Equatable, CustomStringConvertible { case subscriber - case value(Value) - case completion(Subscribers.Completion) + case value(Output) + case completion(Subscribers.Completion) static func == (lhs: Event, rhs: Event) -> Bool { switch (lhs, rhs) { @@ -190,7 +190,7 @@ final class TrackingSubject: Subject, CustomStringConvertible case (.finished, .finished): return true case let (.failure(lhs), .failure(rhs)): - return lhs == rhs + return (lhs as? TestingError) == (rhs as? TestingError) default: return false } @@ -213,7 +213,7 @@ final class TrackingSubject: Subject, CustomStringConvertible } } - private let _passthrough = PassthroughSubject() + private let _passthrough = PassthroughSubject() private(set) var history: [Event] = [] private let _receiveSubscriber: ((CustomCombineIdentifierConvertible) -> Void)? private let _onDeinit: (() -> Void)? @@ -228,12 +228,12 @@ final class TrackingSubject: Subject, CustomStringConvertible _onDeinit?() } - func send(_ value: Value) { + func send(_ value: Output) { history.append(.value(value)) _passthrough.send(value) } - func send(completion: Subscribers.Completion) { + func send(completion: Subscribers.Completion) { history.append(.completion(completion)) _passthrough.send(completion: completion) } diff --git a/Tests/OpenCombineTests/PublisherTests/DecodeTests.swift b/Tests/OpenCombineTests/PublisherTests/DecodeTests.swift index e02e358..556a092 100644 --- a/Tests/OpenCombineTests/PublisherTests/DecodeTests.swift +++ b/Tests/OpenCombineTests/PublisherTests/DecodeTests.swift @@ -79,7 +79,7 @@ final class DecodeTests: XCTestCase { // `CustomPublisher` sends the subscription object it has been initialized with // to whoever subscribed to the `CustomPublisher`. - let publisher = CustomPublisherBase(subscription: subscription) + let publisher = CustomPublisherBase(subscription: subscription) // `_Decode` helper will receive the `CustomSubscription ` let decode = publisher.decode(type: [String : String].self, diff --git a/Tests/OpenCombineTests/PublisherTests/EncodeTests.swift b/Tests/OpenCombineTests/PublisherTests/EncodeTests.swift index e6cab5d..7018b10 100644 --- a/Tests/OpenCombineTests/PublisherTests/EncodeTests.swift +++ b/Tests/OpenCombineTests/PublisherTests/EncodeTests.swift @@ -76,7 +76,9 @@ final class EncodeTests: XCTestCase { // `CustomPublisher` sends the subscription object it has been initialized with // to whoever subscribed to the `CustomPublisher`. - let publisher = CustomPublisherBase<[String: String]>(subscription: subscription) + let publisher = CustomPublisherBase<[String: String], TestingError>( + subscription: subscription + ) // `_Encode` helper will receive the `CustomSubscription ` let encode = publisher.encode(encoder: encoder) diff --git a/Tests/OpenCombineTests/PublisherTests/MapTests.swift b/Tests/OpenCombineTests/PublisherTests/MapTests.swift index f438cd9..531ce9b 100644 --- a/Tests/OpenCombineTests/PublisherTests/MapTests.swift +++ b/Tests/OpenCombineTests/PublisherTests/MapTests.swift @@ -169,18 +169,22 @@ final class MapTests: XCTestCase { } func testDemandSend() { - // Given - let expectedReceiveValueDemand = 4 + var expectedReceiveValueDemand = 4 let subscription = CustomSubscription() let publisher = CustomPublisher(subscription: subscription) let map = publisher.map { $0 * 2 } let tracking = TrackingSubscriber( + receiveSubscription: { $0.request(.unlimited) }, receiveValue: { _ in .max(expectedReceiveValueDemand) } ) - // When + map.subscribe(tracking) - // Then - XCTAssertEqual(publisher.send(0), .max(expectedReceiveValueDemand)) + + XCTAssertEqual(publisher.send(0), .max(4)) + + expectedReceiveValueDemand = 120 + + XCTAssertEqual(publisher.send(0), .max(120)) } func testCompletion() { diff --git a/Tests/OpenCombineTests/PublisherTests/SetFailureTypeTests.swift b/Tests/OpenCombineTests/PublisherTests/SetFailureTypeTests.swift new file mode 100644 index 0000000..83a6a92 --- /dev/null +++ b/Tests/OpenCombineTests/PublisherTests/SetFailureTypeTests.swift @@ -0,0 +1,172 @@ +// +// SetFailureTypeTests.swift +// +// +// Created by Sergej Jaskiewicz on 10.07.2019. +// + +import XCTest + +#if OPENCOMBINE_COMPATIBILITY_TEST +import Combine +#else +import OpenCombine +#endif + +@available(macOS 10.15, *) +final class SetFailureTypeTests: XCTestCase { + + static let allTests = [ + ("testEmpty", testEmpty), + ("testForwardingValues", testForwardingValues), + ("testNoDemand", testNoDemand), + ("testDemandSubscribe", testDemandSubscribe), + ("testDemandSend", testDemandSend), + ("testCompletion", testCompletion), + ("testCancel", testCancel), + ("testCancelAlreadyCancelled", testCancelAlreadyCancelled), + ] + + func testEmpty() { + let tracking = TrackingSubscriberBase( + receiveSubscription: { $0.request(.unlimited) } + ) + let publisher = TrackingSubjectBase( + receiveSubscriber: { + XCTAssertEqual(String(describing: $0), "SetFailureType") + } + ) + + publisher + .setFailureType(to: Never.self) + .setFailureType(to: TestingError.self) + .subscribe(tracking) + + XCTAssertEqual(tracking.history, [.subscription("PassthroughSubject")]) + } + + func testForwardingValues() { + let publisher = PassthroughSubject() + let sft = publisher.setFailureType(to: TestingError.self) + let tracking = TrackingSubscriber(receiveSubscription: { $0.request(.unlimited) }) + + publisher.send(1) + sft.subscribe(tracking) + publisher.send(2) + publisher.send(3) + publisher.send(completion: .finished) + publisher.send(5) + + XCTAssertEqual(tracking.history, [ + .subscription("PassthroughSubject"), + .value(2), + .value(3), + .completion(.finished) + ]) + } + + func testNoDemand() { + let subscription = CustomSubscription() + let publisher = CustomPublisherBase(subscription: subscription) + let sft = publisher.setFailureType(to: TestingError.self) + let tracking = TrackingSubscriber() + + sft.subscribe(tracking) + + XCTAssert(subscription.history.isEmpty) + } + + func testDemandSubscribe() { + let expectedSubscribeDemand = 42 + let subscription = CustomSubscription() + let publisher = CustomPublisherBase(subscription: subscription) + let sft = publisher.setFailureType(to: TestingError.self) + + let tracking = TrackingSubscriber( + receiveSubscription: { $0.request(.max(expectedSubscribeDemand)) } + ) + + sft.subscribe(tracking) + + XCTAssertEqual(subscription.history, [.requested(.max(expectedSubscribeDemand))]) + } + + func testDemandSend() { + + var expectedReceiveValueDemand = 4 + let subscription = CustomSubscription() + let publisher = CustomPublisherBase(subscription: subscription) + let sft = publisher.setFailureType(to: TestingError.self) + let tracking = TrackingSubscriber( + receiveSubscription: { $0.request(.unlimited) }, + receiveValue: { _ in .max(expectedReceiveValueDemand) } + ) + + sft.subscribe(tracking) + XCTAssertEqual(publisher.send(0), .max(4)) + + expectedReceiveValueDemand = 120 + + XCTAssertEqual(publisher.send(0), .max(120)) + } + + func testCompletion() { + + let subscription = CustomSubscription() + let publisher = CustomPublisherBase(subscription: subscription) + let sft = publisher.setFailureType(to: TestingError.self) + let tracking = TrackingSubscriber(receiveSubscription: { $0.request(.unlimited) }) + + sft.subscribe(tracking) + publisher.send(completion: .finished) + + XCTAssertEqual(subscription.history, [.requested(.unlimited)]) + XCTAssertEqual( + tracking.history, + [.subscription("CustomSubscription"), .completion(.finished)] + ) + } + + func testCancel() throws { + + let subscription = CustomSubscription() + let publisher = CustomPublisherBase(subscription: subscription) + let sft = publisher.setFailureType(to: TestingError.self) + + var downstreamSubscription: Subscription? + + let tracking = TrackingSubscriber(receiveSubscription: { + $0.request(.unlimited) + downstreamSubscription = $0 + }) + + sft.subscribe(tracking) + try XCTUnwrap(downstreamSubscription).cancel() + XCTAssertEqual(publisher.send(1), .none) + publisher.send(completion: .finished) + + XCTAssertEqual(subscription.history, [.requested(.unlimited), .cancelled]) + } + + func testCancelAlreadyCancelled() throws { + + let subscription = CustomSubscription() + let publisher = CustomPublisherBase(subscription: subscription) + let sft = publisher.setFailureType(to: TestingError.self) + var downstreamSubscription: Subscription? + let tracking = TrackingSubscriber(receiveSubscription: { + $0.request(.unlimited) + downstreamSubscription = $0 + }) + + sft.subscribe(tracking) + try XCTUnwrap(downstreamSubscription).cancel() + downstreamSubscription?.request(.unlimited) + try XCTUnwrap(downstreamSubscription).cancel() + + XCTAssertEqual(subscription.history, [.requested(.unlimited), + .cancelled, + .requested(.unlimited), + .cancelled]) + } +} diff --git a/Tests/OpenCombineTests/XCTestManifests.swift b/Tests/OpenCombineTests/XCTestManifests.swift index 5e3cabc..94a226a 100644 --- a/Tests/OpenCombineTests/XCTestManifests.swift +++ b/Tests/OpenCombineTests/XCTestManifests.swift @@ -34,6 +34,7 @@ public func allTests() -> [XCTestCaseEntry] { testCase(PrintTests.allTests), testCase(PublisherTests.allTests), testCase(ReplaceNilTests.allTests), + testCase(SetFailureTypeTests.allTests), testCase(SequenceTests.allTests), testCase(SinkTests.allTests), testCase(SubscribersDemandTests.allTests),