14 Commits

Author SHA1 Message Date
Sergej Jaskiewicz 26faf90356 Fix assertCrashes(within:) for Linux 2019-07-31 23:49:17 +03:00
Sergej Jaskiewicz 511d676c30 Test send(subscription:) for PassthroughSubject & CurrentValueSubject 2019-07-31 23:49:17 +03:00
Sergej Jaskiewicz f6ecc28d25 Update for Xcode 11.0 beta 5 2019-07-31 23:49:17 +03:00
Sergej Jaskiewicz 03fe398395 Update for Xcode 11.0 beta 4 (#36)
* Update for Xcode 11.0 beta 4

* Temporarily disable compatibility tests on Travis CI

* Fix SwiftLint warnings

* More test coverage
2019-07-26 16:50:40 +03:00
Sergej Jaskiewicz 47bbd8d81a Update .travis.yml for Xcode 11 (#33)
* Update .travis.yml for Xcode 11

* Enable compatibility testing on iOS
2019-07-11 05:58:53 +03:00
Sergej Jaskiewicz 5e3a18d8c7 Implement Publishers.SetFailureType (#28) 2019-07-11 00:54:07 +03:00
Sergej Jaskiewicz 74e1c1ae32 Actualize XCTestManifests.swift 2019-07-08 18:20:23 +03:00
Sergej Jaskiewicz 4dbc8cc09b Implement Publisher.subscribe<S: Subject>(S) (#27) 2019-07-08 18:07:47 +03:00
Joe Spadafora 2849b1c195 Adds Publishers.Deferred and tests (#26) 2019-07-08 17:30:29 +03:00
Joe Spadafora c3e6905c68 Adds count implementation and tests (#20) 2019-07-08 13:05:16 +03:00
Joe Spadafora e75ad6b0de Implements replaceNil function (#24) 2019-07-05 23:28:04 +03:00
Sergej Jaskiewicz c790e5f708 Fix Multicast semantics and lots of other stuff (#25)
* Fix Multicast semantics and lots of other stuff

* Fix MapError subscription semantics

* Fix subscriptions semantics for exhausted CurrentValueSubject
2019-07-05 08:27:25 +03:00
Joe Spadafora 065b981934 Add map error (#23) 2019-07-05 07:28:37 +03:00
Sergej Jaskiewicz fbd1fd7014 Don't use CODECOV_TOKEN 2019-07-04 20:56:34 +03:00
82 changed files with 4870 additions and 3509 deletions
-6
View File
@@ -72,12 +72,6 @@ line_length:
generic_type_name:
min_length: 3
file_types_order:
order:
- main_type
- extension
- supporting_type
attributes:
always_on_line_above:
- "@usableFromInline"
+11 -14
View File
@@ -12,19 +12,15 @@ matrix:
os: linux
dist: xenial
sudo: required
env: SWIFT_VERSION="swift-5.1-DEVELOPMENT-SNAPSHOT-2019-06-16-a"
env: SWIFT_VERSION="swift-5.1-DEVELOPMENT-SNAPSHOT-2019-06-16-a" OPENCOMBINE_TEST="YES"
- name: "macOS 10.14 | Swift 5.0 | Tests"
os: osx
osx_image: xcode10.2
env: SWIFT_VERSION="5.0" CODE_COVERAGE="YES"
# - name: "macOS 10.14 | Swift 5.1"
# os: osx
# osx_image: xcode10.2
# env: SWIFT_VERSION="swift-5.1-DEVELOPMENT-SNAPSHOT-2019-06-16-a"
# - name: "macOS 10.15 | Swift 5.1"
# os: osx
# osx_image: xcode11.0
# env: SWIFT_VERSION="swift-5.1-DEVELOPMENT-SNAPSHOT-2019-06-16-a" OPENCOMBINE_COMPATIBILITY_TEST="YES"
env: SWIFT_VERSION="5.0" CODE_COVERAGE="YES" OPENCOMBINE_TEST="YES"
# - name: "iOS 13.0 | Swift 5.1 | Compatibility Tests"
# os: osx
# osx_image: xcode11
# env: SWIFT_VERSION="5.1" OPENCOMBINE_COMPATIBILITY_TEST="YES"
- name: "macOS 10.14 | Swift 5.0 | SwiftLint"
os: osx
osx_image: xcode10.2
@@ -41,14 +37,15 @@ install:
gem install xcpretty;
fi
script:
- if [[ $SWIFT_LINT != "YES" ]]; then
- if [[ $OPENCOMBINE_TEST == "YES" ]]; then
swift test -c debug --enable-code-coverage --sanitize thread;
fi
- if [[ $SWIFT_LINT != "YES" ]]; then
- if [[ $OPENCOMBINE_TEST == "YES" ]]; then
swift test -c release;
fi
- if [[ $OPENCOMBINE_COMPATIBILITY_TEST == "YES" ]]; then
swift test -c release -Xswiftc -DOPENCOMBINE_COMPATIBILITY_TEST;
swift package generate-xcodeproj --xcconfig-overrides iOS-Combine-Compatibility.xcconfig;
set -o pipefail && xcodebuild -scheme OpenCombine-Package -sdk iphonesimulator13.0 -destination "platform=iOS Simulator,name=iPhone Xs,OS=13.0" build test | xcpretty;
fi
- if [[ $SWIFT_LINT == "YES" ]]; then
swiftlint lint --strict --reporter "emoji";
@@ -57,5 +54,5 @@ after_success:
- if [[ $CODE_COVERAGE == "YES" ]]; then
swift package generate-xcodeproj --enable-code-coverage;
xcodebuild -scheme OpenCombine-Package build test | xcpretty;
bash <(curl -s https://codecov.io/bash) -t $CODECOV_TOKEN;
bash <(curl -s https://codecov.io/bash);
fi
+306 -198
View File
@@ -260,39 +260,6 @@ extension Publisher {
public func breakpointOnError() -> Publishers.Breakpoint<Self>
}
extension Publishers {
/// A publisher that awaits subscription before running the supplied closure to create a publisher for the new subscriber.
public struct Deferred<DeferredPublisher> : Publisher where DeferredPublisher : Publisher {
/// The kind of values published by this publisher.
public typealias Output = DeferredPublisher.Output
/// The kind of errors this publisher might publish.
///
/// Use `Never` if this `Publisher` does not publish errors.
public typealias Failure = DeferredPublisher.Failure
/// The closure to execute when it receives a subscription.
///
/// The publisher returned by this closure immediately receives the incoming subscription.
public let createPublisher: () -> DeferredPublisher
/// Creates a deferred publisher.
///
/// - Parameter createPublisher: The closure to execute when calling `subscribe(_:)`.
public init(createPublisher: @escaping () -> DeferredPublisher)
/// 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<S>(subscriber: S) where S : Subscriber, DeferredPublisher.Failure == S.Failure, DeferredPublisher.Output == S.Input
}
}
extension Publishers {
/// A publisher that publishes a single Boolean value that indicates whether all received elements pass a given predicate.
@@ -760,6 +727,7 @@ extension Publisher {
public func tryPrefix(while predicate: @escaping (Self.Output) throws -> Bool) -> Publishers.TryPrefixWhile<Self>
}
/// A publisher that eventually produces one value and then finishes or fails.
final public class Future<Output, Failure> : Publisher where Failure : Error {
public typealias Promise = (Result<Output, Failure>) -> Void
@@ -775,48 +743,6 @@ final public class Future<Output, Failure> : Publisher where Failure : Error {
final public func receive<S>(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<Upstream, Failure> : 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<S>(subscriber: S) where Failure == S.Failure, S : Subscriber, Upstream.Output == S.Input
public func setFailureType<E>(to failure: E.Type) -> Publishers.SetFailureType<Upstream, E> 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<E>(to failureType: E.Type) -> Publishers.SetFailureType<Self, E> where E : Error
}
extension Publishers {
/// A publisher that emits a Boolean value upon receiving an element that satisfies the predicate closure.
@@ -1157,11 +1083,6 @@ extension Publisher {
public func prefix<P>(untilOutputFrom publisher: P) -> Publishers.PrefixUntilOutput<Self, P> where P : Publisher
}
extension Publisher {
public func subscribe<S>(_ subject: S) -> AnyCancellable where S : Subject, Self.Failure == S.Failure, Self.Output == S.Output
}
extension Publishers {
/// A publisher that applies a closure to all received elements and produces an accumulated value when the upstream publisher finishes.
@@ -1760,7 +1681,7 @@ extension Publisher {
/// Transforms elements from the upstream publisher by providing the current element to a closure along with the last value returned by the closure.
///
/// let pub = (0...5)
/// .publisher()
/// .publisher
/// .scan(0, { return $0 + $1 })
/// .sink(receiveValue: { print ("\($0)", terminator: " ") })
/// // Prints "0 1 3 6 10 15 ".
@@ -1782,43 +1703,6 @@ extension Publisher {
public func tryScan<T>(_ initialResult: T, _ nextPartialResult: @escaping (T, Self.Output) throws -> T) -> Publishers.TryScan<Self, T>
}
extension Publishers {
/// A publisher that publishes the number of elements received from the upstream publisher.
public struct Count<Upstream> : Publisher where Upstream : Publisher {
/// The kind of values published by this publisher.
public typealias Output = Int
/// The kind of errors this publisher might publish.
///
/// Use `Never` if this `Publisher` does not publish errors.
public typealias Failure = Upstream.Failure
/// The publisher from which this publisher receives elements.
public let upstream: Upstream
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<S>(subscriber: S) where S : Subscriber, Upstream.Failure == S.Failure, S.Input == Publishers.Count<Upstream>.Output
}
}
extension Publisher {
/// Publishes the number of elements received from the upstream publisher.
///
/// - Returns: A publisher that consumes all elements until the upstream publisher finishes, then emits a single
/// value with the total number of elements received.
public func count() -> Publishers.Count<Self>
}
extension Publishers {
/// A publisher that only publishes the last element of a stream that satisfies a predicate closure, once the stream finishes.
@@ -2031,45 +1915,6 @@ extension Publisher {
public func retry(_ retries: Int) -> Publishers.Retry<Self>
}
extension Publishers {
/// A publisher that converts any failure from the upstream publisher into a new error.
public struct MapError<Upstream, Failure> : Publisher where Upstream : Publisher, Failure : Error {
/// 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
/// The closure that converts the upstream failure into a new error.
public let transform: (Upstream.Failure) -> Failure
public init(upstream: Upstream, transform: @escaping (Upstream.Failure) -> Failure)
public init(upstream: Upstream, _ map: @escaping (Upstream.Failure) -> Failure)
/// 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<S>(subscriber: S) where Failure == S.Failure, S : Subscriber, Upstream.Output == S.Input
}
}
extension Publisher {
/// Converts any failure from the upstream publisher into a new error.
///
/// Until the upstream publisher finishes normally or fails with an error, the returned publisher republishes all the elements it receives.
///
/// - Parameter transform: A closure that takes the upstream failure as a parameter and returns a new error for the publisher to terminate with.
/// - Returns: A publisher that replaces any upstream failure with a new error produced by the `transform` closure.
public func mapError<E>(_ transform: @escaping (Self.Failure) -> E) -> Publishers.MapError<Self, E> where E : Error
}
extension Publishers {
/// A publisher that publishes either the most-recent or first element published by the upstream publisher in a specified time interval.
@@ -2332,15 +2177,6 @@ extension Publishers {
}
}
extension Publisher {
/// Replaces nil elements in the stream with the proviced element.
///
/// - Parameter output: The element to use when replacing `nil`.
/// - Returns: A publisher that replaces `nil` elements from the upstream publisher with the provided element.
public func replaceNil<T>(with output: T) -> Publishers.Map<Self, T> where Self.Output == T?
}
extension Publisher {
/// Replaces any errors in the stream with the provided element.
@@ -2794,6 +2630,12 @@ extension Publishers {
}
}
extension Publishers.PrefetchStrategy : Equatable {
}
extension Publishers.PrefetchStrategy : Hashable {
}
extension Publisher {
public func buffer(size: Int, prefetch: Publishers.PrefetchStrategy, whenFull: Publishers.BufferingStrategy<Self.Failure>) -> Publishers.Buffer<Self>
@@ -3086,7 +2928,7 @@ extension Publisher {
/// The following example replaces any error from the upstream publisher and replaces the upstream with a `Just` publisher. This continues the stream by publishing a single value and completing normally.
/// ```
/// enum SimpleError: Error { case error }
/// let errorPublisher = (0..<10).publisher().tryMap { v -> Int in
/// let errorPublisher = (0..<10).publisher.tryMap { v -> Int in
/// if v < 5 {
/// return v
/// } else {
@@ -3180,7 +3022,9 @@ extension Publishers {
/// The scheduler to deliver the delayed events.
public let scheduler: Context
public init(upstream: Upstream, interval: Context.SchedulerTimeType.Stride, tolerance: Context.SchedulerTimeType.Stride, scheduler: Context)
public let options: Context.SchedulerOptions?
public init(upstream: Upstream, interval: Context.SchedulerTimeType.Stride, tolerance: Context.SchedulerTimeType.Stride, scheduler: Context, options: Context.SchedulerOptions? = nil)
/// This function is called to attach the specified `Subscriber` to this `Publisher` by `subscribe(_:)`
///
@@ -3192,16 +3036,17 @@ extension Publishers {
}
}
/// Delays delivery of all output to the downstream receiver by a specified amount of time on a particular scheduler.
///
/// The delay affects the delivery of elements and completion, but not of the original subscription.
/// - Parameters:
/// - interval: The amount of time to delay.
/// - tolerance: The allowed tolerance in firing delayed events.
/// - scheduler: The scheduler to deliver the delayed events.
/// - options: Options relevant to the schedulers behavior.
/// - Returns: A publisher that delays delivery of elements and completion to the downstream receiver.
public func delay<S>(for interval: S.SchedulerTimeType.Stride, tolerance: S.SchedulerTimeType.Stride? = nil, scheduler: S, options: S.SchedulerOptions? = nil) -> Publishers.Delay<Self, S> where S : Scheduler
extension Publisher {
/// Delays delivery of all output to the downstream receiver by a specified amount of time on a particular scheduler.
///
/// The delay affects the delivery of elements and completion, but not of the original subscription.
/// - Parameters:
/// - interval: The amount of time to delay.
/// - tolerance: The allowed tolerance in firing delayed events.
/// - scheduler: The scheduler to deliver the delayed events.
/// - Returns: A publisher that delays delivery of elements and completion to the downstream receiver.
public func delay<S>(for interval: S.SchedulerTimeType.Stride, tolerance: S.SchedulerTimeType.Stride? = nil, scheduler: S, options: S.SchedulerOptions? = nil) -> Publishers.Delay<Self, S> where S : Scheduler
}
extension Publishers {
@@ -3424,19 +3269,6 @@ extension Publishers.CombineLatest4 : Equatable where A : Equatable, B : Equatab
public static func == (lhs: Publishers.CombineLatest4<A, B, C, D>, rhs: Publishers.CombineLatest4<A, B, C, D>) -> 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<Upstream, Failure>, rhs: Publishers.SetFailureType<Upstream, Failure>) -> Bool
}
extension Publishers.Collect : Equatable where Upstream : Equatable {
/// Returns a Boolean value indicating whether two values are equal.
@@ -3746,22 +3578,22 @@ extension Publishers.First : Equatable where Upstream : Equatable {
/// Adds a `Publisher` to a property.
///
/// Properties annotated with `@Published` contain both the stored value and a publisher which sends any new values after the property value has been sent. New subscribers will receive the current value of the property first.
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
@propertyWrapper public struct Published<Value> {
/// Initialize the storage of the Published property as well as the corresponding `Publisher`.
public init(initialValue: Value)
/// The current value of the property.
public var value: Value
public static subscript<EnclosingSelf: AnyObject>(
_enclosingInstance object: EnclosingSelf,
wrapped wrappedKeyPath: ReferenceWritableKeyPath<EnclosingSelf, Value>,
storage storageKeyPath: ReferenceWritableKeyPath<EnclosingSelf, Published<Value>>
) -> Value { get set }
public class Publisher : Publisher {
/// The kind of values published by this publisher.
public typealias Output = Value
/// The kind of errors this publisher might publish.
///
/// Use `Never` if this `Publisher` does not publish errors.
public typealias Failure = Never
/// This function is called to attach the specified `Subscriber` to this `Publisher` by `subscribe(_:)`
@@ -3773,5 +3605,281 @@ extension Publishers.First : Equatable where Upstream : Equatable {
public func receive<S>(subscriber: S) where Value == S.Input, S : Subscriber, S.Failure == Published<Value>.Publisher.Failure
}
public var delegateValue: Published<Value>.Publisher { mutating get }
/// The property that can be accessed with the `$` syntax and allows access to the `Publisher`
public var projectedValue: Published<Value>.Publisher { mutating get }
}
/// A type of object with a publisher that emits before the object has changed.
///
/// By default an `ObservableObject` will synthesize an `objectWillChange`
/// publisher that emits before any of its `@Published` properties changes:
///
/// class Contact : ObservableObject {
/// @Published var name: String
/// @Published var age: Int
///
/// init(name: String, age: Int) {
/// self.name = name
/// self.age = age
/// }
///
/// func haveBirthday() -> Int {
/// age += 1
/// }
/// }
///
/// let john = Contact(name: "John Appleseed", age: 24)
/// john.objectWillChange.sink { _ in print("will change") }
/// print(john.haveBirthday)
/// // Prints "will change"
/// // Prints "25"
///
public protocol ObservableObject : AnyObject {
/// The type of publisher that emits before the object has changed.
associatedtype ObjectWillChangePublisher : Publisher = ObservableObjectPublisher where Self.ObjectWillChangePublisher.Failure == Never
/// A publisher that emits before the object has changed.
var objectWillChange: Self.ObjectWillChangePublisher { get }
}
extension ObservableObject where Self.ObjectWillChangePublisher == ObservableObjectPublisher {
/// A publisher that emits before the object has changed.
public var objectWillChange: ObservableObjectPublisher { get }
}
/// The default publisher of an `ObservableObject`.
final public class ObservableObjectPublisher : Publisher {
/// The kind of values published by this publisher.
public typealias Output = Void
/// The kind of errors this publisher might publish.
///
/// Use `Never` if this `Publisher` does not publish errors.
public typealias Failure = Never
public init()
/// 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.
final public func receive<S>(subscriber: S) where S : Subscriber, S.Failure == ObservableObjectPublisher.Failure, S.Input == ObservableObjectPublisher.Output
final public func send()
}
/// A publisher that allows for recording a series of inputs and a completion for later playback to each subscriber.
public struct Record<Output, Failure> : Publisher where Failure : Error {
/// The recorded output and completion.
public let recording: Record<Output, Failure>.Recording
/// Interactively record a series of outputs and a completion.
public init(record: (inout Record<Output, Failure>.Recording) -> Void)
/// Initialize with a recording.
public init(recording: Record<Output, Failure>.Recording)
/// Set up a complete recording with the specified output and completion.
public init(output: [Output], completion: Subscribers.Completion<Failure>)
/// 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<S>(subscriber: S) where Output == S.Input, Failure == S.Failure, S : Subscriber
/// A recorded set of `Output` and a `Subscribers.Completion`.
public struct Recording {
public typealias Input = Output
/// The output which will be sent to a `Subscriber`.
public var output: [Output] { get }
/// The completion which will be sent to a `Subscriber`.
public var completion: Subscribers.Completion<Failure> { get }
/// Set up a recording in a state ready to receive output.
public init()
/// Set up a complete recording with the specified output and completion.
public init(output: [Output], completion: Subscribers.Completion<Failure> = .finished)
/// Add an output to the recording.
///
/// A `fatalError` will be raised if output is added after adding completion.
public mutating func receive(_ input: Record<Output, Failure>.Recording.Input)
/// Add a completion to the recording.
///
/// A `fatalError` will be raised if more than one completion is added.
public mutating func receive(completion: Subscribers.Completion<Failure>)
}
}
extension Record : Codable where Output : Decodable, Output : Encodable, Failure : Decodable, Failure : Encodable {
/// Creates a new instance by decoding from the given decoder.
///
/// This initializer throws an error if reading from the decoder fails, or
/// if the data read is corrupted or otherwise invalid.
///
/// - Parameter decoder: The decoder to read data from.
public init(from decoder: Decoder) throws
/// Encodes this value into the given encoder.
///
/// If the value fails to encode anything, `encoder` will encode an empty
/// keyed container in its place.
///
/// This function throws an error if any values are invalid for the given
/// encoder's format.
///
/// - Parameter encoder: The encoder to write data to.
public func encode(to encoder: Encoder) throws
}
extension Record.Recording : Codable where Output : Decodable, Output : Encodable, Failure : Decodable, Failure : Encodable {
/// Creates a new instance by decoding from the given decoder.
///
/// This initializer throws an error if reading from the decoder fails, or
/// if the data read is corrupted or otherwise invalid.
///
/// - Parameter decoder: The decoder to read data from.
public init(from decoder: Decoder) throws
public func encode(into encoder: Encoder) throws
/// Encodes this value into the given encoder.
///
/// If the value fails to encode anything, `encoder` will encode an empty
/// keyed container in its place.
///
/// This function throws an error if any values are invalid for the given
/// encoder's format.
///
/// - Parameter encoder: The encoder to write data to.
public func encode(to encoder: Encoder) throws
}
extension Publishers {
/// A publisher that publishes the value of a key path.
public struct MapKeyPath<Upstream, Output> : Publisher where Upstream : Publisher {
/// The kind of errors this publisher might publish.
///
/// Use `Never` if this `Publisher` does not publish errors.
public typealias Failure = Upstream.Failure
/// The publisher from which this publisher receives elements.
public let upstream: Upstream
/// The key path of a property to publish.
public let keyPath: KeyPath<Upstream.Output, Output>
/// 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<S>(subscriber: S) where Output == S.Input, S : Subscriber, Upstream.Failure == S.Failure
}
/// A publisher that publishes the values of two key paths as a tuple.
public struct MapKeyPath2<Upstream, Output0, Output1> : Publisher where Upstream : Publisher {
/// The kind of values published by this publisher.
public typealias Output = (Output0, Output1)
/// The kind of errors this publisher might publish.
///
/// Use `Never` if this `Publisher` does not publish errors.
public typealias Failure = Upstream.Failure
/// The publisher from which this publisher receives elements.
public let upstream: Upstream
/// The key path of a property to publish.
public let keyPath0: KeyPath<Upstream.Output, Output0>
/// The key path of a second property to publish.
public let keyPath1: KeyPath<Upstream.Output, Output1>
/// 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<S>(subscriber: S) where S : Subscriber, Upstream.Failure == S.Failure, S.Input == (Output0, Output1)
}
/// A publisher that publishes the values of three key paths as a tuple.
public struct MapKeyPath3<Upstream, Output0, Output1, Output2> : Publisher where Upstream : Publisher {
/// The kind of values published by this publisher.
public typealias Output = (Output0, Output1, Output2)
/// The kind of errors this publisher might publish.
///
/// Use `Never` if this `Publisher` does not publish errors.
public typealias Failure = Upstream.Failure
/// The publisher from which this publisher receives elements.
public let upstream: Upstream
/// The key path of a property to publish.
public let keyPath0: KeyPath<Upstream.Output, Output0>
/// The key path of a second property to publish.
public let keyPath1: KeyPath<Upstream.Output, Output1>
/// The key path of a third property to publish.
public let keyPath2: KeyPath<Upstream.Output, Output2>
/// 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<S>(subscriber: S) where S : Subscriber, Upstream.Failure == S.Failure, S.Input == (Output0, Output1, Output2)
}
}
extension Publisher {
/// Returns a publisher that publishes the value of a key path.
///
/// - Parameter keyPath: The key path of a property on `Output`
/// - Returns: A publisher that publishes the value of the key path.
public func map<T>(_ keyPath: KeyPath<Self.Output, T>) -> Publishers.MapKeyPath<Self, T>
/// Returns a publisher that publishes the values of two key paths as a tuple.
///
/// - Parameters:
/// - keyPath0: The key path of a property on `Output`
/// - keyPath1: The key path of another property on `Output`
/// - Returns: A publisher that publishes the values of two key paths as a tuple.
public func map<T0, T1>(_ keyPath0: KeyPath<Self.Output, T0>, _ keyPath1: KeyPath<Self.Output, T1>) -> Publishers.MapKeyPath2<Self, T0, T1>
/// Returns a publisher that publishes the values of three key paths as a tuple.
///
/// - Parameters:
/// - keyPath0: The key path of a property on `Output`
/// - keyPath1: The key path of another property on `Output`
/// - keyPath2: The key path of a third property on `Output`
/// - Returns: A publisher that publishes the values of three key paths as a tuple.
public func map<T0, T1, T2>(_ keyPath0: KeyPath<Self.Output, T0>, _ keyPath1: KeyPath<Self.Output, T1>, _ keyPath2: KeyPath<Self.Output, T2>) -> Publishers.MapKeyPath3<Self, T0, T1, T2>
}
+2 -2
View File
@@ -21,7 +21,7 @@ public final class AnyCancellable: Cancellable, Hashable {
_cancel = cancel
}
public init<CancellableType: Cancellable>(_ canceller: CancellableType) {
public init<OtherCancellable: Cancellable>(_ canceller: OtherCancellable) {
_cancel = canceller.cancel
}
@@ -39,7 +39,7 @@ public final class AnyCancellable: Cancellable, Hashable {
}
deinit {
cancel()
_cancel?()
}
}
+16 -6
View File
@@ -9,7 +9,10 @@
///
/// Use `AnyPublisher` to wrap a publisher whose type has details you dont want to expose
/// to subscribers or other publishers.
public struct AnyPublisher<Output, Failure: Error> {
public struct AnyPublisher<Output, Failure: Error>
: CustomStringConvertible,
CustomPlaygroundDisplayConvertible
{
@usableFromInline
internal let box: PublisherBoxBase<Output, Failure>
@@ -24,6 +27,14 @@ public struct AnyPublisher<Output, Failure: Error> {
{
box = PublisherBox(base: publisher)
}
public var description: String {
return "AnyPublisher"
}
public var playgroundDescription: Any {
return description
}
}
extension AnyPublisher: Publisher {
@@ -39,7 +50,7 @@ extension AnyPublisher: Publisher {
public func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
where Output == SubscriberType.Input, Failure == SubscriberType.Failure
{
box.receive(subscriber: subscriber)
box.subscribe(subscriber)
}
}
@@ -61,9 +72,8 @@ internal class PublisherBoxBase<Output, Failure: Error>: Publisher {
@usableFromInline
internal final class PublisherBox<PublisherType: Publisher>
: PublisherBoxBase<PublisherType.Output,
PublisherType.Failure> {
: PublisherBoxBase<PublisherType.Output, PublisherType.Failure>
{
@usableFromInline
internal let base: PublisherType
@@ -77,6 +87,6 @@ internal final class PublisherBox<PublisherType: Publisher>
override internal func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
where Failure == SubscriberType.Failure, Output == SubscriberType.Input
{
base.receive(subscriber: subscriber)
base.subscribe(subscriber)
}
}
-116
View File
@@ -1,116 +0,0 @@
//
// AnySubject.swift
//
//
// Created by Sergej Jaskiewicz on 10.06.2019.
//
public final class AnySubject<Output, Failure: Error>: Subject {
private let _box: SubjectBoxBase<Output, Failure>
public init<SubjectType: Subject>(_ subject: SubjectType)
where Output == SubjectType.Output, Failure == SubjectType.Failure
{
_box = SubjectBox(base: subject)
}
public init(
_ subscribe: @escaping (AnySubscriber<Output, Failure>) -> Void,
_ send: @escaping (Output) -> Void,
_ sendCompletion: @escaping (Subscribers.Completion<Failure>) -> Void
) {
_box = ClosureBasedSubject(subscribe, send, sendCompletion)
}
public func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
where Output == SubscriberType.Input, Failure == SubscriberType.Failure
{
_box.receive(subscriber: subscriber)
}
public func send(_ value: Output) {
_box.send(value)
}
public func send(completion: Subscribers.Completion<Failure>) {
_box.send(completion: completion)
}
}
/// A type-erasing base class. Its concrete subclass is generic over the underlying
/// publisher.
private class SubjectBoxBase<Output, Failure: Error>: Subject {
func send(_ value: Output) {
fatalError()
}
func send(completion: Subscribers.Completion<Failure>) {
fatalError()
}
func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
where Failure == SubscriberType.Failure, Output == SubscriberType.Input
{
fatalError()
}
}
private final class SubjectBox<SubjectType: Subject>
: SubjectBoxBase<SubjectType.Output,
SubjectType.Failure> {
private let base: SubjectType
init(base: SubjectType) {
self.base = base
}
override func send(_ value: Output) {
base.send(value)
}
override func send(completion: Subscribers.Completion<Failure>) {
base.send(completion: completion)
}
override func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
where Failure == SubscriberType.Failure, Output == SubscriberType.Input
{
base.receive(subscriber: subscriber)
}
}
private final class ClosureBasedSubject<Output, Failure: Error>
: SubjectBoxBase<Output, Failure>
{
private let _subscribe: (AnySubscriber<Output, Failure>) -> Void
private let _receive: (Output) -> Void
private let _receiveCompletion: (Subscribers.Completion<Failure>) -> Void
init(
_ subscribe: @escaping (AnySubscriber<Output, Failure>) -> Void,
_ receive: @escaping (Output) -> Void,
_ receiveCompletion: @escaping (Subscribers.Completion<Failure>) -> Void
) {
_subscribe = subscribe
_receive = receive
_receiveCompletion = receiveCompletion
}
override func send(_ value: Output) {
_receive(value)
}
override func send(completion: Subscribers.Completion<Failure>) {
_receiveCompletion(completion)
}
override func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
where Failure == SubscriberType.Failure, Output == SubscriberType.Input
{
_subscribe(AnySubscriber(subscriber))
}
}
+148 -66
View File
@@ -15,32 +15,65 @@ public struct AnySubscriber<Input, Failure: Error>: Subscriber,
CustomReflectable,
CustomPlaygroundDisplayConvertible
{
private let _box: SubscriberBoxBase<Input, Failure>
@usableFromInline
internal let box: AnySubscriberBase<Input, Failure>
@usableFromInline
internal let descriptionThunk: () -> String
@usableFromInline
internal let customMirrorThunk: () -> Mirror
@usableFromInline
internal let playgroundDescriptionThunk: () -> Any
public let combineIdentifier: CombineIdentifier
public var description: String { return _box.description }
public var description: String { return descriptionThunk() }
public var customMirror: Mirror { return _box.customMirror }
public var customMirror: Mirror { return customMirrorThunk() }
/// A custom playground description for this instance.
public var playgroundDescription: Any { return description }
public var playgroundDescription: Any { return playgroundDescriptionThunk() }
/// Creates a type-erasing subscriber to wrap an existing subscriber.
///
/// - Parameter s: The subscriber to type-erase.
public init<SubscriberType: Subscriber>(_ subscriber: SubscriberType)
where Input == SubscriberType.Input, Failure == SubscriberType.Failure
@inline(__always)
@inlinable
public init<Subscriber: OpenCombine.Subscriber>(_ subscriber: Subscriber)
where Input == Subscriber.Input, Failure == Subscriber.Failure
{
_box = SubscriberBox(base: subscriber)
combineIdentifier = subscriber.combineIdentifier
box = AnySubscriberBox(subscriber)
if let description = subscriber as? CustomStringConvertible {
descriptionThunk = { description.description }
} else {
let fixedDescription = String(describing: type(of: subscriber))
descriptionThunk = { fixedDescription }
}
customMirrorThunk = {
(subscriber as? CustomReflectable)?.customMirror
?? Mirror(subscriber, children: EmptyCollection())
}
if let playgroundDescription = subscriber as? CustomPlaygroundDisplayConvertible {
playgroundDescriptionThunk = { playgroundDescription.playgroundDescription }
} else if let desccription = subscriber as? CustomStringConvertible {
playgroundDescriptionThunk = { desccription.description }
} else {
let fixedDescription = String(describing: type(of: subscriber))
playgroundDescriptionThunk = { fixedDescription }
}
}
public init<SubjectType: Subject>(_ subject: SubjectType)
where Input == SubjectType.Output, Failure == SubjectType.Failure
public init<Subject: OpenCombine.Subject>(_ subject: Subject)
where Input == Subject.Output, Failure == Subject.Failure
{
_box = SubjectSubscriber(subject)
combineIdentifier = CombineIdentifier(_box)
self.init(SubjectSubscriber(subject))
}
/// Creates a type-erasing subscriber that executes the provided closures.
@@ -52,140 +85,189 @@ public struct AnySubscriber<Input, Failure: Error>: Subscriber,
/// the publisher.
/// - receiveCompletion: A closure to execute when the subscriber receives
/// a completion callback from the publisher.
@inline(__always)
@inlinable
public init(receiveSubscription: ((Subscription) -> Void)? = nil,
receiveValue: ((Input) -> Subscribers.Demand)? = nil,
receiveCompletion: ((Subscribers.Completion<Failure>) -> Void)? = nil) {
_box = ClosureBasedSubscriber(receiveSubscription: receiveSubscription,
receiveValue: receiveValue,
receiveCompletion: receiveCompletion)
box = ClosureBasedAnySubscriber(
receiveSubscription ?? { _ in },
receiveValue ?? { _ in .none },
receiveCompletion ?? { _ in }
)
combineIdentifier = CombineIdentifier()
descriptionThunk = { "Anonymous AnySubscriber" }
customMirrorThunk = { Mirror(reflecting: "Anonymous AnySubscriber") }
playgroundDescriptionThunk = { "Anonymous AnySubscriber" }
}
@inline(__always)
@inlinable
public func receive(subscription: Subscription) {
_box.receive(subscription: subscription)
box.receive(subscription: subscription)
}
@inline(__always)
@inlinable
public func receive(_ value: Input) -> Subscribers.Demand {
return _box.receive(value)
return box.receive(value)
}
@inline(__always)
@inlinable
public func receive(completion: Subscribers.Completion<Failure>) {
_box.receive(completion: completion)
box.receive(completion: completion)
}
}
/// A type-erasing base class. Its concrete subclass is generic over the underlying
/// publisher.
internal class SubscriberBoxBase<Input, Failure: Error>: Subscriber,
CustomStringConvertible,
CustomReflectable {
/// subscriber.
@usableFromInline
internal class AnySubscriberBase<Input, Failure: Error>: Subscriber {
@inline(__always)
@inlinable
internal init() {}
@inline(__always)
@inlinable
deinit {}
@usableFromInline
internal func receive(subscription: Subscription) {
fatalError()
}
@usableFromInline
internal func receive(_ input: Input) -> Subscribers.Demand {
fatalError()
}
@usableFromInline
internal func receive(completion: Subscribers.Completion<Failure>) {
fatalError()
}
internal var description: String { return "AnySubscriber" }
internal var customMirror: Mirror {
return Mirror(combineIdentifier, children: EmptyCollection())
}
}
internal final class SubscriberBox<SubscriberType: Subscriber>
: SubscriberBoxBase<SubscriberType.Input, SubscriberType.Failure>
@usableFromInline
internal final class AnySubscriberBox<Base: Subscriber>
: AnySubscriberBase<Base.Input, Base.Failure>
{
@usableFromInline
internal let base: Base
private let base: SubscriberType
internal init(base: SubscriberType) {
@inlinable
internal init(_ base: Base) {
self.base = base
}
@inlinable
deinit {}
@inlinable
override internal func receive(subscription: Subscription) {
base.receive(subscription: subscription)
}
override internal func receive(_ input: Input) -> Subscribers.Demand {
@inlinable
override internal func receive(_ input: Base.Input) -> Subscribers.Demand {
return base.receive(input)
}
override internal func receive(completion: Subscribers.Completion<Failure>) {
@inlinable
override internal func receive(completion: Subscribers.Completion<Base.Failure>) {
base.receive(completion: completion)
}
override internal var customMirror: Mirror { return Mirror(reflecting: base) }
override internal var description: String { return String(describing: base) }
}
internal final class ClosureBasedSubscriber<Input, Failure: Error>
: SubscriberBoxBase<Input, Failure>
@usableFromInline
internal final class ClosureBasedAnySubscriber<Input, Failure: Error>
: AnySubscriberBase<Input, Failure>
{
@usableFromInline
internal let receiveSubscriptionThunk: (Subscription) -> Void
private let _receiveSubscription: ((Subscription) -> Void)?
private let _receiveValue: ((Input) -> Subscribers.Demand)?
private let _receiveCompletion: ((Subscribers.Completion<Failure>) -> Void)?
@usableFromInline
internal let receiveValueThunk: (Input) -> Subscribers.Demand
internal init(receiveSubscription: ((Subscription) -> Void)? = nil,
receiveValue: ((Input) -> Subscribers.Demand)? = nil,
receiveCompletion: ((Subscribers.Completion<Failure>) -> Void)? = nil) {
_receiveSubscription = receiveSubscription
_receiveValue = receiveValue
_receiveCompletion = receiveCompletion
@usableFromInline
internal let receiveCompletionThunk: (Subscribers.Completion<Failure>) -> Void
@inlinable
internal init(_ rcvSubscription: @escaping (Subscription) -> Void,
_ rcvValue: @escaping (Input) -> Subscribers.Demand,
_ rcvCompletion: @escaping (Subscribers.Completion<Failure>) -> Void) {
receiveSubscriptionThunk = rcvSubscription
receiveValueThunk = rcvValue
receiveCompletionThunk = rcvCompletion
}
@inlinable
deinit {}
@inlinable
override internal func receive(subscription: Subscription) {
_receiveSubscription?(subscription)
receiveSubscriptionThunk(subscription)
}
@inlinable
override internal func receive(_ input: Input) -> Subscribers.Demand {
return _receiveValue?(input) ?? .none
return receiveValueThunk(input)
}
@inlinable
override internal func receive(completion: Subscribers.Completion<Failure>) {
_receiveCompletion?(completion)
receiveCompletionThunk(completion)
}
}
internal final class SubjectSubscriber<SubjectType: Subject>
: SubscriberBoxBase<SubjectType.Output, SubjectType.Failure>
internal final class SubjectSubscriber<Downstream: Subject>
: Subscriber,
CustomStringConvertible,
CustomReflectable,
Subscription
{
internal var parent: SubjectType?
internal var downstreamSubject: Downstream?
internal var upstreamSubscription: Subscription?
internal init(_ parent: SubjectType) {
self.parent = parent
internal init(_ parent: Downstream) {
self.downstreamSubject = parent
}
override internal func receive(subscription: Subscription) {
internal func receive(subscription: Subscription) {
guard upstreamSubscription == nil else { return }
upstreamSubscription = subscription
subscription.request(.unlimited)
downstreamSubject?.send(subscription: self)
}
override internal func receive(_ input: Input) -> Subscribers.Demand {
parent?.send(input)
internal func receive(_ input: Downstream.Output) -> Subscribers.Demand {
downstreamSubject?.send(input)
return .none
}
override internal func receive(completion: Subscribers.Completion<Failure>) {
parent?.send(completion: completion)
internal func receive(completion: Subscribers.Completion<Downstream.Failure>) {
downstreamSubject?.send(completion: completion)
}
override internal var description: String { return "Subject" }
internal var description: String { return "Subject" }
override internal var customMirror: Mirror {
internal var customMirror: Mirror {
let children: [(label: String?, value: Any)] = [
(label: "parent", value: parent as Any),
(label: "downstreamSubject", value: downstreamSubject as Any),
(label: "upstreamSubscription", value: upstreamSubscription as Any)
]
return Mirror(self, children: children)
}
internal func request(_ demand: Subscribers.Demand) {
upstreamSubscription?.request(demand)
}
internal func cancel() {
upstreamSubscription?.cancel()
upstreamSubscription = nil
downstreamSubject = nil
}
}
-16
View File
@@ -1,16 +0,0 @@
//
// Cancelable.swift
//
//
// Created by Sergej Jaskiewicz on 10.06.2019.
//
/// A protocol indicating that an activity or action may be canceled.
///
/// Calling `cancel()` frees up any allocated resources. It also stops side effects such
/// as timers, network access, or disk I/O.
public protocol Cancellable {
/// Cancel the activity.
func cancel()
}
+35
View File
@@ -0,0 +1,35 @@
//
// Cancellable.swift
//
//
// Created by Sergej Jaskiewicz on 10.06.2019.
//
/// A protocol indicating that an activity or action may be canceled.
///
/// Calling `cancel()` frees up any allocated resources. It also stops side effects such
/// as timers, network access, or disk I/O.
public protocol Cancellable {
/// Cancel the activity.
func cancel()
}
extension Cancellable {
/// Stores this Cancellable in the specified collection.
/// Parameters:
/// - collection: The collection to store this Cancellable.
public func store<Cancellables: RangeReplaceableCollection>(
in collection: inout Cancellables
) where Cancellables.Element == AnyCancellable {
AnyCancellable(self).store(in: &collection)
}
/// Stores this Cancellable in the specified set.
/// Parameters:
/// - collection: The set to store this Cancellable.
public func store(in set: inout Set<AnyCancellable>) {
AnyCancellable(self).store(in: &set)
}
}
+38 -9
View File
@@ -16,6 +16,10 @@ public final class CurrentValueSubject<Output, Failure: Error>: Subject {
private var _completion: Subscribers.Completion<Failure>?
internal var upstreamSubscriptions: [Subscription] = []
internal var hasAnyDownstreamDemand = false
/// The value wrapped by this subject, published as a new element whenever it changes.
public var value: Output {
didSet {
@@ -30,16 +34,36 @@ public final class CurrentValueSubject<Output, Failure: Error>: Subject {
self.value = value
}
public func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
where Output == SubscriberType.Input, Failure == SubscriberType.Failure
{
let subscription = Conduit(parent: self, downstream: AnySubscriber(subscriber))
_lock.do {
_subscriptions.append(subscription)
deinit {
for subscription in _subscriptions {
subscription._downstream = nil
}
}
subscriber.receive(subscription: subscription)
public func send(subscription: Subscription) {
_lock.do {
upstreamSubscriptions.append(subscription)
subscription.request(.unlimited)
}
}
public func receive<Subscriber: OpenCombine.Subscriber>(subscriber: Subscriber)
where Output == Subscriber.Input, Failure == Subscriber.Failure
{
_lock.do {
if let completion = _completion {
subscriber.receive(subscription: Subscriptions.empty)
subscriber.receive(completion: completion)
return
} else {
let subscription = Conduit(parent: self,
downstream: AnySubscriber(subscriber))
_subscriptions.append(subscription)
subscriber.receive(subscription: subscription)
}
}
}
public func send(_ input: Output) {
@@ -102,7 +126,7 @@ extension CurrentValueSubject {
}
func request(_ demand: Subscribers.Demand) {
guard demand > 0 else { return }
precondition(demand > 0)
_parent?._lock.do {
if !_delivered, let value = _parent?.value {
_offer(value)
@@ -111,6 +135,7 @@ extension CurrentValueSubject {
} else {
_demand = demand
}
_parent?.hasAnyDownstreamDemand = true
}
}
@@ -119,3 +144,7 @@ extension CurrentValueSubject {
}
}
}
extension CurrentValueSubject.Conduit: CustomStringConvertible {
fileprivate var description: String { return "CurrentValueSubject" }
}
+52 -11
View File
@@ -18,24 +18,49 @@ public final class PassthroughSubject<Output, Failure: Error>: Subject {
// TODO: Combine uses bag data structure
private var _subscriptions: [Conduit] = []
internal var upstreamSubscriptions: [Subscription] = []
internal var hasAnyDownstreamDemand = false
public init() {}
deinit {
for subscription in _subscriptions {
subscription._downstream = nil
}
}
public func send(subscription: Subscription) {
_lock.do {
upstreamSubscriptions.append(subscription)
if hasAnyDownstreamDemand {
subscription.request(.unlimited)
}
}
}
public func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
where Output == SubscriberType.Input, Failure == SubscriberType.Failure
{
let subscription = Conduit(parent: self, downstream: AnySubscriber(subscriber))
_lock.do {
_subscriptions.append(subscription)
}
if let completion = _completion {
subscriber.receive(subscription: Subscriptions.empty)
subscriber.receive(completion: completion)
return
} else {
let subscription = Conduit(parent: self,
downstream: AnySubscriber(subscriber))
subscriber.receive(subscription: subscription)
_subscriptions.append(subscription)
subscriber.receive(subscription: subscription)
}
}
}
public func send(_ input: Output) {
_lock.do {
for subscription in _subscriptions
where !subscription.isCompleted && subscription._demand > 0
where !subscription._isCompleted && subscription._demand > 0
{
let newDemand = subscription._downstream?.receive(input) ?? .none
subscription._demand += newDemand
@@ -45,13 +70,23 @@ public final class PassthroughSubject<Output, Failure: Error>: Subject {
}
public func send(completion: Subscribers.Completion<Failure>) {
_completion = completion
_lock.do {
_completion = completion
for subscriber in _subscriptions {
subscriber._receive(completion: completion)
}
}
}
private func _acknowledgeDownstreamDemand() {
_lock.do {
guard !hasAnyDownstreamDemand else { return }
hasAnyDownstreamDemand = true
for subscription in upstreamSubscriptions {
subscription.request(.unlimited)
}
}
}
}
extension PassthroughSubject {
@@ -64,7 +99,7 @@ extension PassthroughSubject {
fileprivate var _demand: Subscribers.Demand = .none
var isCompleted: Bool {
fileprivate var _isCompleted: Bool {
return _parent == nil
}
@@ -75,20 +110,26 @@ extension PassthroughSubject {
}
fileprivate func _receive(completion: Subscribers.Completion<Failure>) {
if !isCompleted {
if !_isCompleted {
_parent = nil
_downstream?.receive(completion: completion)
}
}
func request(_ demand: Subscribers.Demand) {
fileprivate func request(_ demand: Subscribers.Demand) {
precondition(demand > 0, "demand must not be zero")
_parent?._lock.do {
_demand = demand
}
_parent?._acknowledgeDownstreamDemand()
}
func cancel() {
fileprivate func cancel() {
_parent = nil
}
}
}
extension PassthroughSubject.Conduit: CustomStringConvertible {
fileprivate var description: String { return "PassthroughSubject" }
}
+14 -4
View File
@@ -36,8 +36,8 @@ public protocol Publisher {
/// - Parameters:
/// - subscriber: The subscriber to attach to this `Publisher`.
/// once attached it can begin to receive values.
func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
where Failure == SubscriberType.Failure, Output == SubscriberType.Input
func receive<Subscriber: OpenCombine.Subscriber>(subscriber: Subscriber)
where Failure == Subscriber.Failure, Output == Subscriber.Input
}
extension Publisher {
@@ -52,9 +52,19 @@ extension Publisher {
/// - Parameters:
/// - subscriber: The subscriber to attach to this `Publisher`. After attaching,
/// the subscriber can start to receive values.
public func subscribe<SubscriberType: Subscriber>(_ subscriber: SubscriberType)
where Failure == SubscriberType.Failure, Output == SubscriberType.Input
public func subscribe<Subscriber: OpenCombine.Subscriber>(_ subscriber: Subscriber)
where Failure == Subscriber.Failure, Output == Subscriber.Input
{
receive(subscriber: subscriber)
}
public func subscribe<Subject: OpenCombine.Subject>(
_ subject: Subject
) -> AnyCancellable
where Failure == Subject.Failure, Output == Subject.Output
{
let subscriber = SubjectSubscriber(subject)
self.subscribe(subscriber)
return AnyCancellable(subscriber)
}
}
@@ -0,0 +1,48 @@
//
// Deferred.swift
//
//
// Created by Joseph Spadafora on 7/7/19.
//
/// A publisher that awaits subscription before running the supplied closure
/// to create a publisher for the new subscriber.
public struct Deferred<DeferredPublisher: Publisher>: Publisher {
/// The kind of values published by this publisher.
public typealias Output = DeferredPublisher.Output
/// The kind of errors this publisher might publish.
///
/// Use `Never` if this `Publisher` does not publish errors.
public typealias Failure = DeferredPublisher.Failure
/// The closure to execute when it receives a subscription.
///
/// The publisher returned by this closure immediately
/// receives the incoming subscription.
public let createPublisher: () -> DeferredPublisher
/// Creates a deferred publisher.
///
/// - Parameter createPublisher: The closure to execute
/// when calling `subscribe(_:)`.
public init(createPublisher: @escaping () -> DeferredPublisher) {
self.createPublisher = createPublisher
}
/// 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<SubscriberType: Subscriber>(subscriber: SubscriberType)
where Failure == SubscriberType.Failure,
Output == SubscriberType.Input
{
let deferredPublisher = createPublisher()
deferredPublisher.subscribe(subscriber)
}
}
@@ -0,0 +1,53 @@
//
// Empty.swift
//
//
// Created by Sergej Jaskiewicz on 16.06.2019.
//
/// A publisher that never publishes any values, and optionally finishes immediately.
///
/// You can create a Never publisher one which never sends values and never
/// finishes or fails with the initializer `Empty(completeImmediately: false)`.
public struct Empty<Output, Failure: Error>: Publisher, Equatable {
/// Creates an empty publisher.
///
/// - Parameter completeImmediately: A Boolean value that indicates whether
/// the publisher should immediately finish.
public init(completeImmediately: Bool = true) {
self.completeImmediately = completeImmediately
}
/// Creates an empty publisher with the given completion behavior and output and
/// failure types.
///
/// Use this initializer to connect the empty publisher to subscribers or other
/// publishers that have specific output and failure types.
/// - Parameters:
/// - completeImmediately: A Boolean value that indicates whether the publisher
/// should immediately finish.
/// - outputType: The output type exposed by this publisher.
/// - failureType: The failure type exposed by this publisher.
public init(completeImmediately: Bool = true,
outputType: Output.Type,
failureType: Failure.Type) {
self.init(completeImmediately: completeImmediately)
}
/// A Boolean value that indicates whether the publisher immediately sends
/// a completion.
///
/// If `true`, the publisher finishes immediately after sending a subscription
/// to the subscriber. If `false`, it never completes.
public let completeImmediately: Bool
public func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
where Output == SubscriberType.Input, Failure == SubscriberType.Failure
{
subscriber.receive(subscription: Subscriptions.empty)
if completeImmediately {
subscriber.receive(completion: .finished)
}
}
}
+41
View File
@@ -0,0 +1,41 @@
//
// Fail.swift
//
//
// Created by Sergej Jaskiewicz on 19.06.2019.
//
/// A publisher that immediately terminates with the specified error.
public struct Fail<Output, Failure: Error>: Publisher {
/// Creates a publisher that immediately terminates with the specified failure.
///
/// - Parameter error: The failure to send when terminating the publisher.
public init(error: Failure) {
self.error = error
}
/// Creates publisher with the given output type, that immediately terminates with
/// the specified failure.
///
/// Use this initializer to create a `Fail` publisher that can work with
/// subscribers or publishers that expect a given output type.
/// - Parameters:
/// - outputType: The output type exposed by this publisher.
/// - failure: The failure to send when terminating the publisher.
public init(outputType: Output.Type, failure: Failure) {
self.error = failure
}
/// The failure to send when terminating the publisher.
public let error: Failure
public func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
where Output == SubscriberType.Input, Failure == SubscriberType.Failure
{
subscriber.receive(subscription: Subscriptions.empty)
subscriber.receive(completion: .failure(error))
}
}
extension Fail: Equatable where Failure: Equatable {}
+45 -102
View File
@@ -11,8 +11,6 @@
/// is also useful when replacing a value with `Catch`.
///
/// In contrast with `Publishers.Once`, a `Just` publisher cannot fail with an error.
/// In contrast with `Publishers.Optional`, a `Just` publisher always produces
/// a value.
public struct Just<Output>: Publisher {
public typealias Failure = Never
@@ -66,8 +64,8 @@ extension Just {
public func tryAllSatisfy(
_ predicate: (Output) throws -> Bool
) -> Publishers.Once<Bool, Error> {
return Publishers.Once(Result { try predicate(output) })
) -> Result<Bool, Error>.OCombine.Publisher {
return .init(Result { try predicate(output) })
}
public func contains(where predicate: (Output) -> Bool) -> Just<Bool> {
@@ -76,8 +74,8 @@ extension Just {
public func tryContains(
where predicate: (Output) throws -> Bool
) -> Publishers.Once<Bool, Error> {
return Publishers.Once(Result { try predicate(output) })
) -> Result<Bool, Error>.OCombine.Publisher {
return .init(Result { try predicate(output) })
}
public func collect() -> Just<[Output]> {
@@ -90,45 +88,25 @@ extension Just {
return self
}
public func tryMin(
by areInIncreasingOrder: (Output, Output) throws -> Bool
) -> Publishers.Optional<Bool, Error> {
return Publishers.Optional(Result { try areInIncreasingOrder(output, output) })
}
public func max(
by areInIncreasingOrder: (Output, Output) -> Bool
) -> Just<Output> {
return self
}
public func tryMax(
by areInIncreasingOrder: (Output, Output) throws -> Bool
) -> Publishers.Optional<Bool, Error> {
return Publishers.Optional(Result { try areInIncreasingOrder(output, output) })
}
public func count() -> Just<Int> {
return .init(1)
}
public func dropFirst(
_ count: Int = 1
) -> Publishers.Optional<Output, Never> {
public func dropFirst(_ count: Int = 1) -> Optional<Output>.OCombine.Publisher {
precondition(count >= 0, "count must not be negative")
return Publishers.Optional(count > 0 ? nil : output)
return .init(count > 0 ? nil : self.output)
}
public func drop(
while predicate: (Output) -> Bool
) -> Publishers.Optional<Output, Never> {
return Publishers.Optional(predicate(output) ? nil : output)
}
public func tryDrop(
while predicate: (Output) throws -> Bool
) -> Publishers.Optional<Output, Error> {
return Publishers.Optional(Result { try predicate(output) ? nil : output })
) -> Optional<Output>.OCombine.Publisher {
return .init(predicate(output) ? nil : output)
}
public func first() -> Just<Output> {
@@ -137,14 +115,8 @@ extension Just {
public func first(
where predicate: (Output) -> Bool
) -> Publishers.Optional<Output, Never> {
return Publishers.Optional(predicate(output) ? output : nil)
}
public func tryFirst(
where predicate: (Output) throws -> Bool
) -> Publishers.Optional<Output, Error> {
return Publishers.Optional(Result { try predicate(output) ? output : nil })
) -> Optional<Output>.OCombine.Publisher {
return .init(predicate(output) ? output : nil)
}
public func last() -> Just<Output> {
@@ -153,18 +125,12 @@ extension Just {
public func last(
where predicate: (Output) -> Bool
) -> Publishers.Optional<Output, Never> {
return Publishers.Optional(predicate(output) ? output : nil)
) -> Optional<Output>.OCombine.Publisher {
return .init(predicate(output) ? output : nil)
}
public func tryLast(
where predicate: (Output) throws -> Bool
) -> Publishers.Optional<Output, Error> {
return Publishers.Optional(Result { try predicate(output) ? output : nil })
}
public func ignoreOutput() -> Publishers.Empty<Output, Never> {
return Publishers.Empty()
public func ignoreOutput() -> Empty<Output, Never> {
return .init()
}
public func map<ElementOfResult>(
@@ -175,79 +141,61 @@ extension Just {
public func tryMap<ElementOfResult>(
_ transform: (Output) throws -> ElementOfResult
) -> Publishers.Once<ElementOfResult, Error> {
return Publishers.Once(Result { try transform(output) })
) -> Result<ElementOfResult, Error>.OCombine.Publisher {
return .init(Result { try transform(output) })
}
public func compactMap<ElementOfResult>(
_ transform: (Output) -> ElementOfResult?
) -> Publishers.Optional<ElementOfResult, Never> {
return Publishers.Optional(transform(output))
}
public func tryCompactMap<ElementOfResult>(
_ transform: (Output) throws -> ElementOfResult?
) -> Publishers.Optional<ElementOfResult, Error> {
return Publishers.Optional(Result { try transform(output) })
) -> Optional<ElementOfResult>.OCombine.Publisher {
return .init(transform(output))
}
public func filter(
_ isIncluded: (Output) -> Bool
) -> Publishers.Optional<Output, Never> {
return Publishers.Optional(isIncluded(output) ? output : nil)
) -> Optional<Output>.OCombine.Publisher {
return .init(isIncluded(output) ? output : nil)
}
public func tryFilter(
_ isIncluded: (Output) throws -> Bool
) -> Publishers.Optional<Output, Error> {
return Publishers.Optional(Result { try isIncluded(output) ? output : nil })
}
public func output(at index: Int) -> Publishers.Optional<Output, Never> {
public func output(at index: Int) -> Optional<Output>.OCombine.Publisher {
precondition(index >= 0, "index must not be negative")
return Publishers.Optional(index == 0 ? output : nil)
return .init(index == 0 ? output : nil)
}
public func output<RangeExpr: RangeExpression>(
in range: RangeExpr
) -> Publishers.Optional<Output, Never> where RangeExpr.Bound == Int {
public func output<RangeExpression: Swift.RangeExpression>(
in range: RangeExpression
) -> Optional<Output>.OCombine.Publisher where RangeExpression.Bound == Int {
// TODO: Broken in Apple's Combine? (FB6169621)
// Empty range should result in a nil
let range = range.relative(to: 0..<Int.max)
return Publishers.Optional(range.lowerBound == 0 ? output : nil)
return .init(range.lowerBound == 0 ? output : nil)
// The above implementation is used for compatibility.
//
// It actually probably should be just this:
// return Publishers.Optional(range.contains(0) ? output : nil)
// return .init(range.contains(0) ? output : nil)
}
public func prefix(_ maxLength: Int) -> Publishers.Optional<Output, Never> {
public func prefix(_ maxLength: Int) -> Optional<Output>.OCombine.Publisher {
precondition(maxLength >= 0, "maxLength must not be negative")
return Publishers.Optional(maxLength > 0 ? output : nil)
return .init(maxLength > 0 ? output : nil)
}
public func prefix(
while predicate: (Output) -> Bool
) -> Publishers.Optional<Output, Never> {
return Publishers.Optional(predicate(output) ? output : nil)
}
public func tryPrefix(
while predicate: (Output) throws -> Bool
) -> Publishers.Optional<Output, Error> {
return Publishers.Optional(Result { try predicate(output) ? output : nil })
) -> Optional<Output>.OCombine.Publisher {
return .init(predicate(output) ? output : nil)
}
public func setFailureType<Failure: Error>(
to failureType: Failure.Type
) -> Publishers.Once<Output, Failure> {
return Publishers.Once(output)
) -> Result<Output, Failure>.OCombine.Publisher {
return .init(output)
}
public func mapError<Failure: Error>(
_ transform: (Never) -> Failure
) -> Publishers.Once<Output, Failure> {
return Publishers.Once(output)
) -> Result<Output, Failure>.OCombine.Publisher {
return .init(output)
}
public func removeDuplicates(
@@ -258,9 +206,8 @@ extension Just {
public func tryRemoveDuplicates(
by predicate: (Output, Output) throws -> Bool
) -> Publishers.Once<Output, Error> {
return Publishers
.Once(Result { try _ = predicate(output, output); return output })
) -> Result<Output, Error>.OCombine.Publisher {
return .init(Result { try _ = predicate(output, output); return output })
}
public func replaceError(with output: Output) -> Just<Output> {
@@ -275,36 +222,32 @@ extension Just {
return self
}
public func retry() -> Just<Output> {
return self
}
public func reduce<Accumulator>(
_ initialResult: Accumulator,
_ nextPartialResult: (Accumulator, Output) -> Accumulator
) -> Publishers.Once<Accumulator, Never> {
return Publishers.Once(nextPartialResult(initialResult, output))
) -> Result<Accumulator, Never>.OCombine.Publisher {
return .init(nextPartialResult(initialResult, output))
}
public func tryReduce<Accumulator>(
_ initialResult: Accumulator,
_ nextPartialResult: (Accumulator, Output) throws -> Accumulator
) -> Publishers.Once<Accumulator, Error> {
return Publishers.Once(Result { try nextPartialResult(initialResult, output) })
) -> Result<Accumulator, Error>.OCombine.Publisher {
return .init(Result { try nextPartialResult(initialResult, output) })
}
public func scan<ElementOfResult>(
_ initialResult: ElementOfResult,
_ nextPartialResult: (ElementOfResult, Output) -> ElementOfResult
) -> Publishers.Once<ElementOfResult, Never> {
return Publishers.Once(nextPartialResult(initialResult, output))
) -> Result<ElementOfResult, Never>.OCombine.Publisher {
return .init(nextPartialResult(initialResult, output))
}
public func tryScan<ElementOfResult>(
_ initialResult: ElementOfResult,
_ nextPartialResult: (ElementOfResult, Output) throws -> ElementOfResult
) -> Publishers.Once<ElementOfResult, Error> {
return Publishers.Once(Result { try nextPartialResult(initialResult, output) })
) -> Result<ElementOfResult, Error>.OCombine.Publisher {
return .init(Result { try nextPartialResult(initialResult, output) })
}
}
@@ -6,7 +6,7 @@
//
internal class OperatorSubscription<Downstream: Subscriber>: CustomReflectable {
internal var downstream: Downstream
internal var downstream: Downstream?
internal var upstreamSubscription: Subscription?
internal var customMirror: Mirror {
@@ -20,5 +20,6 @@ internal class OperatorSubscription<Downstream: Subscriber>: CustomReflectable {
internal func cancel() {
upstreamSubscription?.cancel()
upstreamSubscription = nil
downstream = nil
}
}
@@ -0,0 +1,277 @@
//
// Optional.Publisher.swift
//
//
// Created by Sergej Jaskiewicz on 17.06.2019.
//
extension Optional {
/// A namespace for disambiguation when both OpenCombine and Combine are imported.
///
/// Combine extends `Optional` with a nested type `Publisher`.
/// If you import both OpenCombine and Combine (either explicitly or implicitly,
/// e. g. when importing Foundation), you will not be able to write
/// `Optional<Int>.Publisher`, because Swift is unable to understand
/// which `Publisher` you're referring to.
///
/// So you have to write `Optional<Int>.OCombine.Publisher`.
///
/// This bug is tracked [here](https://bugs.swift.org/browse/SR-11183).
///
/// You can omit this whenever Combine is not available (e. g. on Linux).
public struct OCombine {
fileprivate let optional: Optional
fileprivate init(_ optional: Optional) {
self.optional = optional
}
/// A publisher that publishes an optional value to each subscriber
/// exactly once, if the optional has a value.
///
/// In contrast with `Just`, an `Optional` publisher may send
/// no value before completion.
public struct Publisher: OpenCombine.Publisher {
public typealias Output = Wrapped
public typealias Failure = Never
/// The result to deliver to each subscriber.
public let output: Wrapped?
/// Creates a publisher to emit the optional value of a successful result,
/// or fail with an error.
///
/// - Parameter result: The result to deliver to each subscriber.
public init(_ output: Output?) {
self.output = output
}
public func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
where Output == SubscriberType.Input, Failure == SubscriberType.Failure
{
if let output = output {
subscriber.receive(subscription: Inner(value: output,
downstream: subscriber))
} else {
subscriber.receive(subscription: Subscriptions.empty)
subscriber.receive(completion: .finished)
}
}
}
}
#if !canImport(Combine)
/// A publisher that publishes an optional value to each subscriber
/// exactly once, if the optional has a value.
///
/// In contrast with `Just`, an `Optional` publisher may send
/// no value before completion.
public typealias Publisher = OCombine.Publisher
#endif
}
private final class Inner<SubscriberType: Subscriber>: Subscription,
CustomStringConvertible,
CustomReflectable
{
private let _output: SubscriberType.Input
private var _downstream: SubscriberType?
init(value: SubscriberType.Input, downstream: SubscriberType) {
_output = value
_downstream = downstream
}
func request(_ demand: Subscribers.Demand) {
if let downstream = _downstream, demand > 0 {
_ = downstream.receive(_output)
downstream.receive(completion: .finished)
_downstream = nil
}
}
func cancel() {
_downstream = nil
}
var description: String { return "Optional" }
var customMirror: Mirror {
return Mirror(self, unlabeledChildren: CollectionOfOne(_output))
}
}
extension Optional.OCombine.Publisher: Equatable where Wrapped: Equatable {}
extension Optional.OCombine.Publisher where Wrapped: Equatable {
public func contains(_ output: Output) -> Optional<Bool>.OCombine.Publisher {
return .init(self.output.map { $0 == output })
}
public func removeDuplicates() -> Optional<Wrapped>.OCombine.Publisher {
return self
}
}
extension Optional.OCombine.Publisher where Wrapped: Comparable {
public func min() -> Optional<Wrapped>.OCombine.Publisher {
return self
}
public func max() -> Optional<Wrapped>.OCombine.Publisher {
return self
}
}
extension Optional.OCombine.Publisher {
public func allSatisfy(
_ predicate: (Output) -> Bool
) -> Optional<Bool>.OCombine.Publisher {
return .init(self.output.map(predicate))
}
public func collect() -> Optional<[Output]>.OCombine.Publisher {
return .init(self.output.map { [$0] })
}
public func compactMap<ElementOfResult>(
_ transform: (Output) -> ElementOfResult?
) -> Optional<ElementOfResult>.OCombine.Publisher {
return .init(self.output.flatMap(transform))
}
public func min(
by areInIncreasingOrder: (Output, Output) -> Bool
) -> Optional<Output>.OCombine.Publisher {
return self
}
public func max(
by areInIncreasingOrder: (Output, Output) -> Bool
) -> Optional<Output>.OCombine.Publisher {
return self
}
public func contains(
where predicate: (Output) -> Bool
) -> Optional<Bool>.OCombine.Publisher {
return .init(self.output.map(predicate))
}
public func count() -> Optional<Int>.OCombine.Publisher {
return .init(self.output.map { _ in 1 })
}
public func dropFirst(_ count: Int = 1) -> Optional<Output>.OCombine.Publisher {
precondition(count >= 0, "count must not be negative")
return .init(self.output.flatMap { count == 0 ? $0 : nil })
}
public func drop(
while predicate: (Output) -> Bool
) -> Optional<Output>.OCombine.Publisher {
return .init(self.output.flatMap { predicate($0) ? nil : $0 })
}
public func first() -> Optional<Output>.OCombine.Publisher {
return self
}
public func first(
where predicate: (Output) -> Bool
) -> Optional<Output>.OCombine.Publisher {
return .init(output.flatMap { predicate($0) ? $0 : nil })
}
public func last() -> Optional<Output>.OCombine.Publisher {
return self
}
public func last(
where predicate: (Output) -> Bool
) -> Optional<Output>.OCombine.Publisher {
return .init(output.flatMap { predicate($0) ? $0 : nil })
}
public func filter(
_ isIncluded: (Output) -> Bool
) -> Optional<Output>.OCombine.Publisher {
return .init(output.flatMap { isIncluded($0) ? $0 : nil })
}
public func ignoreOutput() -> Empty<Output, Failure> {
return .init()
}
public func map<ElementOfResult>(
_ transform: (Output) -> ElementOfResult
) -> Optional<ElementOfResult>.OCombine.Publisher {
return .init(output.map(transform))
}
public func output(at index: Int) -> Optional<Output>.OCombine.Publisher {
precondition(index >= 0, "index must not be negative")
return .init(output.flatMap { index == 0 ? $0 : nil })
}
public func output<RangeExpression: Swift.RangeExpression>(
in range: RangeExpression
) -> Optional<Output>.OCombine.Publisher where RangeExpression.Bound == Int {
let range = range.relative(to: 0 ..< Int.max)
precondition(range.lowerBound >= 0, "lowerBould must not be negative")
// I don't know why, but Combine has this precondition
precondition(range.upperBound < .max - 1)
return .init(output.flatMap { range.contains(0) ? $0 : nil })
}
public func prefix(_ maxLength: Int) -> Optional<Output>.OCombine.Publisher {
precondition(maxLength >= 0, "maxLength must not be negative")
return .init(output.flatMap { maxLength > 0 ? $0 : nil })
}
public func prefix(
while predicate: (Output) -> Bool
) -> Optional<Output>.OCombine.Publisher {
return .init(output.flatMap { predicate($0) ? $0 : nil })
}
public func reduce<Accumulator>(
_ initialResult: Accumulator,
_ nextPartialResult: (Accumulator, Output) -> Accumulator
) -> Optional<Accumulator>.OCombine.Publisher {
return .init(output.map { nextPartialResult(initialResult, $0) })
}
public func scan<ElementOfResult>(
_ initialResult: ElementOfResult,
_ nextPartialResult: (ElementOfResult, Output) -> ElementOfResult
) -> Optional<ElementOfResult>.OCombine.Publisher {
return .init(output.map { nextPartialResult(initialResult, $0) })
}
public func removeDuplicates(
by predicate: (Output, Output) -> Bool
) -> Optional<Output>.OCombine.Publisher {
return self
}
public func replaceError(with output: Output) -> Optional<Output>.OCombine.Publisher {
return self
}
public func replaceEmpty(with output: Output) -> Just<Output> {
return .init(self.output ?? output)
}
public func retry(_ times: Int) -> Optional<Output>.OCombine.Publisher {
return self
}
}
@@ -0,0 +1,94 @@
//
// Publishers.Count.swift
//
//
// Created by Joseph Spadafora on 6/25/19.
//
extension Publishers {
/// A publisher that publishes the number of elements received
/// from the upstream publisher.
public struct Count<Upstream: Publisher>: Publisher {
/// The kind of values published by this publisher.
public typealias Output = Int
/// The kind of errors this publisher might publish.
///
/// Use `Never` if this `Publisher` does not publish errors.
public typealias Failure = Upstream.Failure
/// The publisher from which this publisher receives elements.
public let upstream: Upstream
public init(upstream: Upstream) {
self.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<SubscriberType: Subscriber>(subscriber: SubscriberType)
where Upstream.Failure == SubscriberType.Failure,
SubscriberType.Input == Output
{
let count = _Count<Upstream, SubscriberType>(downstream: subscriber)
upstream.subscribe(count)
}
}
}
extension Publisher {
/// Publishes the number of elements received from the upstream publisher.
///
/// - Returns: A publisher that consumes all elements until the upstream publisher
/// finishes, then emits a single value with the total number of elements received.
public func count() -> Publishers.Count<Self> {
return Publishers.Count(upstream: self)
}
}
private final class _Count<Upstream: Publisher, Downstream: Subscriber>
: OperatorSubscription<Downstream>,
Subscriber,
CustomStringConvertible,
Subscription
where Downstream.Input == Int,
Upstream.Failure == Downstream.Failure
{
typealias Input = Upstream.Output
typealias Output = Int
typealias Failure = Downstream.Failure
private var _count = 0
var description: String { return "Count" }
func receive(subscription: Subscription) {
upstreamSubscription = subscription
downstream.receive(subscription: self)
upstreamSubscription?.request(.unlimited)
}
func receive(_ input: Input) -> Subscribers.Demand {
_count += 1
return .none
}
func receive(completion: Subscribers.Completion<Upstream.Failure>) {
if case .finished = completion {
_ = downstream.receive(_count)
}
downstream.receive(completion: completion)
}
func request(_ demand: Subscribers.Demand) {
}
}
@@ -42,7 +42,7 @@ extension Publishers {
downstream: subscriber,
decoder: _decoder
)
upstream.receive(subscriber: decodeSubscriber)
upstream.subscribe(decodeSubscriber)
}
}
}
@@ -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,14 +57,13 @@ extension Publishers {
where Output == SubscriberType.Input, SubscriberType.Failure == Error
{
let inner = Inner(downstream: subscriber, predicate: catching(predicate))
upstream.receive(subscriber: inner)
upstream.subscribe(inner)
}
}
}
private class _DropWhile<Upstream: Publisher, Downstream: Subscriber>
: OperatorSubscription<Downstream>,
CustomStringConvertible,
Subscription
where Upstream.Output == Downstream.Input
{
@@ -74,22 +73,30 @@ private class _DropWhile<Upstream: Publisher, Downstream: Subscriber>
/// The predicate is reset to `nil` as soon as it returns `false`.
var predicate: Predicate?
var demand: Subscribers.Demand = .none
var isCompleted = false
init(downstream: Downstream, predicate: @escaping Predicate) {
self.predicate = predicate
super.init(downstream: downstream)
}
var description: String { return "DropWhile" }
func receive(subscription: Subscription) {
upstreamSubscription = subscription
downstream.receive(subscription: self)
}
func receive(_ input: Input) -> Subscribers.Demand {
guard upstreamSubscription != nil else {
return .none
}
guard let predicate = self.predicate else {
return downstream.receive(input)
}
// NOTE: until the predicate returns false, we will ask the upstream publisher
// for elements one by one, no matter how much elements the downstream subscriber
// requests.
// for elements one by one.
//
// However, IF the downstream requests anything, we accumulate this demand in the
// `demand` property so that later we can provide the downstream with the correct
@@ -97,49 +104,30 @@ private class _DropWhile<Upstream: Publisher, Downstream: Subscriber>
//
// As soon as the predicate returns false, we switch to the mode where
// we just forward all the requests from the downstream to the upstream.
subscription.request(.max(1))
downstream.receive(subscription: self)
}
func receive(_ input: Input) -> Subscribers.Demand {
guard let predicate = self.predicate else {
return downstream.receive(input)
}
switch predicate(input) {
case .success(true):
// See the NOTE above to understand why we return .max(1)
return .max(1)
case .success(false):
// Okay, we hit the first element not satisfying the predicate,
// from now on we just republish the values to the downstream.
self.predicate = nil
// The demand that the downstream has requested has been accumulated in the
// `demand` property. Now it's time to pay the debt.
//
// Subtracting 1 for the current value.
return demand + downstream.receive(input) - 1
return downstream.receive(input)
case .failure(let error):
downstream.receive(completion: .failure(error))
isCompleted = true
cancel()
return .none
}
}
func request(_ demand: Subscribers.Demand) {
if predicate == nil {
// If predicate is nil, that means that we have already received a value
// that doesn't satisfy the predicate, hence we're in the state where we
// just forward each request to the upstream.
upstreamSubscription?.request(demand)
} else {
// Otherwise, as mentioned in the NOTE above, we accumulate all the demand
// requested by the downstream until the predicate returns false.
self.demand += demand
}
upstreamSubscription?.request(demand)
}
override func cancel() {
upstreamSubscription?.cancel()
upstreamSubscription = nil
// Don't zero out downstream, that's what Combine does (probably a bug)
}
}
@@ -147,10 +135,17 @@ extension Publishers.DropWhile {
private final class Inner<Downstream: Subscriber>
: _DropWhile<Upstream, Downstream>,
CustomStringConvertible,
Subscriber
where Upstream.Output == Downstream.Input, Downstream.Failure == Upstream.Failure
{
var description: String { return "DropWhile" }
func receive(completion: Subscribers.Completion<Failure>) {
guard !isCompleted else {
assertionFailure("unreachable")
return
}
downstream.receive(completion: completion)
}
}
@@ -160,10 +155,14 @@ extension Publishers.TryDropWhile {
private final class Inner<Downstream: Subscriber>
: _DropWhile<Upstream, Downstream>,
Subscriber
CustomStringConvertible,
Subscriber
where Upstream.Output == Downstream.Input, Downstream.Failure == Error
{
var description: String { return "TryDropWhile" }
func receive(completion: Subscribers.Completion<Failure>) {
guard !isCompleted else { return }
downstream.receive(completion: completion.eraseError())
}
}
@@ -1,56 +0,0 @@
//
// Publishers.Empty.swift
//
//
// Created by Sergej Jaskiewicz on 16.06.2019.
//
extension Publishers {
/// A publisher that never publishes any values, and optionally finishes immediately.
///
/// You can create a Never publisher one which never sends values and never
/// finishes or fails with the initializer `Empty(completeImmediately: false)`.
public struct Empty<Output, Failure: Error>: Publisher, Equatable {
/// Creates an empty publisher.
///
/// - Parameter completeImmediately: A Boolean value that indicates whether
/// the publisher should immediately finish.
public init(completeImmediately: Bool = true) {
self.completeImmediately = completeImmediately
}
/// Creates an empty publisher with the given completion behavior and output and
/// failure types.
///
/// Use this initializer to connect the empty publisher to subscribers or other
/// publishers that have specific output and failure types.
/// - Parameters:
/// - completeImmediately: A Boolean value that indicates whether the publisher
/// should immediately finish.
/// - outputType: The output type exposed by this publisher.
/// - failureType: The failure type exposed by this publisher.
public init(completeImmediately: Bool = true,
outputType: Output.Type,
failureType: Failure.Type) {
self.init(completeImmediately: completeImmediately)
}
/// A Boolean value that indicates whether the publisher immediately sends
/// a completion.
///
/// If `true`, the publisher finishes immediately after sending a subscription
/// to the subscriber. If `false`, it never completes.
public let completeImmediately: Bool
public func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
where Output == SubscriberType.Input, Failure == SubscriberType.Failure
{
subscriber.receive(subscription: Subscriptions.empty)
if completeImmediately {
subscriber.receive(completion: .finished)
}
}
}
}
@@ -44,7 +44,7 @@ extension Publishers {
downstream: subscriber,
encoder: encoder
)
upstream.receive(subscriber: encodeSubscriber)
upstream.subscribe(encodeSubscriber)
}
}
}
@@ -1,44 +0,0 @@
//
// Publishers.Fail.swift
//
//
// Created by Sergej Jaskiewicz on 19.06.2019.
//
extension Publishers {
/// A publisher that immediately terminates with the specified error.
public struct Fail<Output, Failure: Error>: Publisher {
/// Creates a publisher that immediately terminates with the specified failure.
///
/// - Parameter error: The failure to send when terminating the publisher.
public init(error: Failure) {
self.error = error
}
/// Creates publisher with the given output type, that immediately terminates with
/// the specified failure.
///
/// Use this initializer to create a `Fail` publisher that can work with
/// subscribers or publishers that expect a given output type.
/// - Parameters:
/// - outputType: The output type exposed by this publisher.
/// - failure: The failure to send when terminating the publisher.
public init(outputType: Output.Type, failure: Failure) {
self.error = failure
}
/// The failure to send when terminating the publisher.
public let error: Failure
public func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
where Output == SubscriberType.Input, Failure == SubscriberType.Failure
{
subscriber.receive(subscription: Subscriptions.empty)
subscriber.receive(completion: .failure(error))
}
}
}
extension Publishers.Fail: Equatable where Failure: Equatable {}
@@ -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<Result>(
@@ -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<Result>(
@@ -109,9 +121,7 @@ extension Publishers.TryMap {
}
private class _Map<Upstream: Publisher, Downstream: Subscriber>
: OperatorSubscription<Downstream>,
CustomStringConvertible,
Subscription
: OperatorSubscription<Downstream>
{
typealias Input = Upstream.Output
typealias Failure = Upstream.Failure
@@ -128,13 +138,6 @@ private class _Map<Upstream: Publisher, Downstream: Subscriber>
super.init(downstream: downstream)
}
var description: String { return "Map" }
func receive(subscription: Subscription) {
upstreamSubscription = subscription
downstream.receive(subscription: self)
}
func receive(_ input: Input) -> Subscribers.Demand {
switch _transform?(input) {
case .success(let output)?:
@@ -147,30 +150,26 @@ private class _Map<Upstream: Publisher, Downstream: Subscriber>
return .none
}
}
func request(_ demand: Subscribers.Demand) {
upstreamSubscription?.request(demand)
}
override func cancel() {
_transform = nil
upstreamSubscription?.cancel()
}
}
extension Publishers.Map {
private final class Inner<Downstream: Subscriber>
: _Map<Upstream, Downstream>,
CustomStringConvertible,
Subscriber
where Downstream.Failure == Upstream.Failure
{
func receive(completion: Subscribers.Completion<Failure>) {
if !isCompleted {
_transform = nil
downstream.receive(completion: completion)
}
func receive(subscription: Subscription) {
downstream.receive(subscription: subscription)
}
func receive(completion: Subscribers.Completion<Failure>) {
downstream.receive(completion: completion)
}
var description: String { return "Map" }
}
}
@@ -178,14 +177,32 @@ extension Publishers.TryMap {
private final class Inner<Downstream: Subscriber>
: _Map<Upstream, Downstream>,
Subscription,
CustomStringConvertible,
Subscriber
where Downstream.Failure == Error
{
func receive(subscription: Subscription) {
upstreamSubscription = subscription
downstream.receive(subscription: self)
}
func receive(completion: Subscribers.Completion<Failure>) {
if !isCompleted {
_transform = nil
downstream.receive(completion: completion.eraseError())
}
}
func request(_ demand: Subscribers.Demand) {
upstreamSubscription?.request(demand)
}
override func cancel() {
_transform = nil
super.cancel()
}
var description: String { return "TryMap" }
}
}
@@ -0,0 +1,104 @@
//
// Publishers.MapError.swift
//
//
// Created by Joseph Spadafora on 7/4/19.
//
extension Publishers {
/// A publisher that converts any failure from the
/// upstream publisher into a new error.
public struct MapError<Upstream: Publisher, Failure: Error>: Publisher {
/// 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
/// The closure that converts the upstream failure into a new error.
public let transform: (Upstream.Failure) -> Failure
public init(upstream: Upstream, _ map: @escaping (Upstream.Failure) -> Failure) {
self.upstream = upstream
self.transform = map
}
/// 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<SubscriberType: Subscriber>(subscriber: SubscriberType)
where Failure == SubscriberType.Failure,
Upstream.Output == SubscriberType.Input
{
let mapErrorSubscriber = _MapError<Upstream, SubscriberType>(
downstream: subscriber,
transform: transform
)
upstream.subscribe(mapErrorSubscriber)
}
}
}
extension Publisher {
/// Converts any failure from the upstream publisher into a new error.
///
/// Until the upstream publisher finishes normally or fails with an error,
/// the returned publisher republishes all the elements it receives.
///
/// - Parameter transform: A closure that takes the upstream failure as a
/// parameter and returns a new error for the publisher to terminate with.
/// - Returns: A publisher that replaces any upstream failure with a
/// new error produced by the `transform` closure.
public func mapError<NewFailure: Error>(
_ transform: @escaping (Self.Failure) -> NewFailure
) -> Publishers.MapError<Self, NewFailure>
{
return Publishers.MapError(upstream: self, transform)
}
}
private final class _MapError<Upstream: Publisher, Downstream: Subscriber>
: OperatorSubscription<Downstream>,
Subscriber,
CustomStringConvertible
where Upstream.Output == Downstream.Input
{
typealias Input = Upstream.Output
typealias Failure = Upstream.Failure
typealias Output = Downstream.Input
private let _transform: (Upstream.Failure) -> Downstream.Failure
var description: String { return "MapError" }
init(downstream: Downstream,
transform: @escaping (Upstream.Failure) -> Downstream.Failure) {
self._transform = transform
super.init(downstream: downstream)
}
func receive(subscription: Subscription) {
upstreamSubscription = subscription
downstream.receive(subscription: subscription)
}
func receive(_ input: Input) -> Subscribers.Demand {
return downstream.receive(input)
}
func receive(completion: Subscribers.Completion<Failure>) {
switch completion {
case .finished:
downstream.receive(completion: .finished)
case .failure(let error):
downstream.receive(completion: .failure(_transform(error)))
}
}
}
@@ -5,6 +5,25 @@
// Created by Sergej Jaskiewicz on 14.06.2019.
//
extension Publisher {
public func multicast<SubjectType: Subject>(
_ createSubject: @escaping () -> SubjectType
) -> Publishers.Multicast<Self, SubjectType>
where Failure == SubjectType.Failure, Output == SubjectType.Output
{
return Publishers.Multicast(upstream: self, createSubject: createSubject)
}
public func multicast<SubjectType: Subject>(
subject: SubjectType
) -> Publishers.Multicast<Self, SubjectType>
where Failure == SubjectType.Failure, Output == SubjectType.Output
{
return multicast { subject }
}
}
extension Publishers {
public final class Multicast<Upstream: Publisher, SubjectType: Subject>
@@ -28,11 +47,11 @@ extension Publishers {
self.createSubject = createSubject
}
public func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
where SubjectType.Failure == SubscriberType.Failure,
SubjectType.Output == SubscriberType.Input
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where SubjectType.Failure == Downstream.Failure,
SubjectType.Output == Downstream.Input
{
_subject.subscribe(subscriber)
_subject.subscribe(Inner(downstream: subscriber))
}
public func connect() -> Cancellable {
@@ -42,27 +61,41 @@ extension Publishers {
upstream.subscribe(subscriber)
return AnyCancellable {
subscriber.parent = nil
subscriber.downstreamSubject = nil
}
}
}
}
extension Publisher {
extension Publishers.Multicast {
public func multicast<SubjectType: Subject>(
_ createSubject: @escaping () -> SubjectType
) -> Publishers.Multicast<Self, SubjectType>
where Failure == SubjectType.Failure, Output == SubjectType.Output
private final class Inner<Downstream: Subscriber>
: OperatorSubscription<Downstream>,
Subscriber,
CustomStringConvertible,
Subscription
where Upstream.Output == Downstream.Input, Upstream.Failure == Downstream.Failure
{
return Publishers.Multicast(upstream: self, createSubject: createSubject)
}
typealias Input = Upstream.Output
typealias Failure = Upstream.Failure
public func multicast<SubjectType: Subject>(
subject: SubjectType
) -> Publishers.Multicast<Self, SubjectType>
where Failure == SubjectType.Failure, Output == SubjectType.Output
{
return multicast { subject }
var description: String { return "Multicast" }
func receive(subscription: Subscription) {
upstreamSubscription = subscription
downstream.receive(subscription: self)
}
func receive(_ input: Input) -> Subscribers.Demand {
return downstream.receive(input)
}
func receive(completion: Subscribers.Completion<Failure>) {
downstream.receive(completion: completion)
}
func request(_ demand: Subscribers.Demand) {
upstreamSubscription?.request(demand)
}
}
}
@@ -1,387 +0,0 @@
//
// Publishers.Once.swift
//
//
// Created by Sergej Jaskiewicz on 17.06.2019.
//
extension Publishers {
/// A publisher that publishes an output to each subscriber exactly once then
/// finishes, or fails immediately without producing any elements.
///
/// If `result` is `.success`, then `Once` waits until it receives a request for
/// at least 1 value before sending the output. If `result` is `.failure`, then `Once`
/// sends the failure immediately upon subscription.
///
/// In contrast with `Just`, a `Once` publisher can terminate with an error instead of
/// sending a value. In contrast with `Optional`, a `Once` publisher always sends one
/// value (unless it terminates with an error).
public struct Once<Output, Failure: Error>: Publisher {
/// The result to deliver to each subscriber.
public let result: Result<Output, Failure>
/// Creates a publisher that delivers the specified result.
///
/// If the result is `.success`, the `Once` publisher sends the specified output
/// to all subscribers and finishes normally. If the result is `.failure`, then
/// the publisher fails immediately with the specified error.
///
/// - Parameter result: The result to deliver to each subscriber.
public init(_ result: Result<Output, Failure>) {
self.result = result
}
/// Creates a publisher that sends the specified output to all subscribers and
/// finishes normally.
///
/// - Parameter output: The output to deliver to each subscriber.
public init(_ output: Output) {
self.init(.success(output))
}
/// Creates a publisher that immediately terminates upon subscription with
/// the given failure.
///
/// - Parameter failure: The failure to send when terminating.
public init(_ failure: Failure) {
self.init(.failure(failure))
}
public func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
where SubscriberType.Input == Output, SubscriberType.Failure == Failure
{
switch result {
case .success(let value):
subscriber.receive(subscription: Inner(value: value,
downstream: subscriber))
case .failure(let failure):
subscriber.receive(subscription: Subscriptions.empty)
subscriber.receive(completion: .failure(failure))
}
}
}
}
private final class Inner<SubscriberType: Subscriber>: Subscription,
CustomStringConvertible,
CustomReflectable
{
private let _output: SubscriberType.Input
private var _downstream: SubscriberType?
init(value: SubscriberType.Input, downstream: SubscriberType) {
_output = value
_downstream = downstream
}
func request(_ demand: Subscribers.Demand) {
if let downstream = _downstream, demand > 0 {
_ = downstream.receive(_output)
downstream.receive(completion: .finished)
_downstream = nil
}
}
func cancel() {
_downstream = nil
}
var description: String { return "Once" }
var customMirror: Mirror {
return Mirror(self, unlabeledChildren: CollectionOfOne(_output))
}
}
extension Publishers.Once: Equatable where Output: Equatable, Failure: Equatable {}
extension Publishers.Once where Output: Equatable {
public func contains(_ output: Output) -> Publishers.Once<Bool, Failure> {
return Publishers.Once(result.map { $0 == output })
}
public func removeDuplicates() -> Publishers.Once<Output, Failure> {
return self
}
}
extension Publishers.Once where Output: Comparable {
public func min() -> Publishers.Once<Output, Failure> {
return self
}
public func max() -> Publishers.Once<Output, Failure> {
return self
}
}
extension Publishers.Once {
public func allSatisfy(
_ predicate: (Output) -> Bool
) -> Publishers.Once<Bool, Failure> {
return Publishers.Once(result.map(predicate))
}
public func tryAllSatisfy(
_ predicate: (Output) throws -> Bool
) -> Publishers.Once<Bool, Error> {
return Publishers.Once(result.tryMap(predicate))
}
public func contains(
where predicate: (Output) -> Bool
) -> Publishers.Once<Bool, Failure> {
return Publishers.Once(result.map(predicate))
}
public func tryContains(
where predicate: (Output) throws -> Bool
) -> Publishers.Once<Bool, Error> {
return Publishers.Once(result.tryMap(predicate))
}
public func collect() -> Publishers.Once<[Output], Failure> {
return Publishers.Once(result.map { [$0] })
}
public func min(
by areInIncreasingOrder: (Output, Output) -> Bool
) -> Publishers.Once<Output, Failure> {
return self
}
public func tryMin(
by areInIncreasingOrder: (Output, Output) throws -> Bool
) -> Publishers.Once<Output, Failure> {
return self
}
public func max(
by areInIncreasingOrder: (Output, Output
) -> Bool) -> Publishers.Once<Output, Failure> {
return self
}
public func tryMax(
by areInIncreasingOrder: (Output, Output) throws -> Bool
) -> Publishers.Once<Output, Failure> {
return self
}
public func count() -> Publishers.Once<Int, Failure> {
return Publishers.Once(result.map { _ in 1 })
}
public func dropFirst(_ count: Int = 1) -> Publishers.Optional<Output, Failure> {
precondition(count >= 0, "count must not be negative")
return Publishers.Optional((try? result.get()).flatMap { count == 0 ? $0 : nil })
}
public func drop(
while predicate: (Output) -> Bool
) -> Publishers.Optional<Output, Failure> {
return Publishers.Optional(result.map { predicate($0) ? nil : $0 })
}
public func tryDrop(
while predicate: (Output) throws -> Bool
) -> Publishers.Optional<Output, Error> {
return Publishers.Optional(result.tryMap { try predicate($0) ? nil : $0 })
}
public func first() -> Publishers.Once<Output, Failure> {
return self
}
public func first(
where predicate: (Output) -> Bool
) -> Publishers.Optional<Output, Failure> {
return Publishers.Optional(result.map { predicate($0) ? $0 : nil })
}
public func tryFirst(
where predicate: (Output) throws -> Bool
) -> Publishers.Optional<Output, Error> {
return Publishers.Optional(result.tryMap { try predicate($0) ? $0 : nil })
}
public func last() -> Publishers.Once<Output, Failure> {
return self
}
public func last(
where predicate: (Output) -> Bool
) -> Publishers.Optional<Output, Failure> {
return Publishers.Optional(result.map { predicate($0) ? $0 : nil })
}
public func tryLast(
where predicate: (Output) throws -> Bool
) -> Publishers.Optional<Output, Error> {
return Publishers.Optional(result.tryMap { try predicate($0) ? $0 : nil })
}
public func filter(
_ isIncluded: (Output) -> Bool
) -> Publishers.Optional<Output, Failure> {
return Publishers.Optional(result.map { isIncluded($0) ? $0 : nil })
}
public func tryFilter(
_ isIncluded: (Output) throws -> Bool
) -> Publishers.Optional<Output, Error> {
return Publishers.Optional(result.tryMap { try isIncluded($0) ? $0 : nil })
}
public func ignoreOutput() -> Publishers.Empty<Output, Failure> {
return Publishers.Empty()
}
public func map<ElementOfResult>(
_ transform: (Output) -> ElementOfResult
) -> Publishers.Once<ElementOfResult, Failure> {
return Publishers.Once(result.map(transform))
}
public func tryMap<ElementOfResult>(
_ transform: (Output) throws -> ElementOfResult
) -> Publishers.Once<ElementOfResult, Error> {
return Publishers.Once(result.tryMap(transform))
}
public func compactMap<ElementOfResult>(
_ transform: (Output) -> ElementOfResult?
) -> Publishers.Optional<ElementOfResult, Failure> {
return Publishers.Optional(result.map(transform))
}
public func tryCompactMap<ElementOfResult>(
_ transform: (Output) throws -> ElementOfResult?
) -> Publishers.Optional<ElementOfResult, Error> {
return Publishers.Optional(result.tryMap(transform))
}
public func mapError<TransformedFailure: Error>(
_ transform: (Failure) -> TransformedFailure
) -> Publishers.Once<Output, TransformedFailure> {
return Publishers.Once(result.mapError(transform))
}
public func output(at index: Int) -> Publishers.Optional<Output, Failure> {
precondition(index >= 0, "index must not be negative")
return Publishers.Optional(result.map { index == 0 ? $0 : nil })
}
public func output<RangeExpr: RangeExpression>(
in range: RangeExpr
) -> Publishers.Optional<Output, Failure> where RangeExpr.Bound == Int {
// TODO: Broken in Apple's Combine? (FB6169621)
// Empty range should result in a nil
let range = range.relative(to: 0..<Int.max)
return Publishers.Optional(
result.map { range.lowerBound == 0 ? $0 : nil }
)
// The above implementation is used for compatibility.
//
// It actually probably should be just this:
// return Publishers.Optional(
// result.map { range.contains(0) ? $0 : nil }
// )
}
public func prefix(_ maxLength: Int) -> Publishers.Optional<Output, Failure> {
precondition(maxLength >= 0, "maxLength must not be negative")
// TODO: Seems broken in Apple's Combine (FB6168300)
return Publishers.Optional(
result.map { maxLength == 0 ? $0 : nil }
)
// The above implementation is used for compatibility.
//
// It actually should be the following:
// return Publishers.Optional(
// result.map { $0.flatMap { maxLength > 0 ? $0 : nil } }
// )
}
public func prefix(
while predicate: (Output) -> Bool
) -> Publishers.Optional<Output, Failure> {
return Publishers.Optional(result.map { predicate($0) ? $0 : nil })
}
public func tryPrefix(
while predicate: (Output) throws -> Bool
) -> Publishers.Optional<Output, Error> {
return Publishers.Optional(result.tryMap { try predicate($0) ? $0 : nil })
}
public func removeDuplicates(
by predicate: (Output, Output) -> Bool
) -> Publishers.Once<Output, Failure> {
return self
}
public func tryRemoveDuplicates(
by predicate: (Output, Output) throws -> Bool
) -> Publishers.Once<Output, Error> {
return Publishers.Once(result.mapError { $0 })
}
public func replaceError(with output: Output) -> Publishers.Once<Output, Never> {
return Publishers.Once(.success(result.unwrapOr(output)))
}
public func replaceEmpty(with output: Output) -> Publishers.Once<Output, Failure> {
return self
}
public func retry(_ times: Int) -> Publishers.Once<Output, Failure> {
return self
}
public func retry() -> Publishers.Once<Output, Failure> {
return self
}
public func reduce<Accumulator>(
_ initialResult: Accumulator,
_ nextPartialResult: (Accumulator, Output) -> Accumulator
) -> Publishers.Once<Accumulator, Failure> {
return Publishers.Once(result.map { nextPartialResult(initialResult, $0) })
}
public func tryReduce<Accumulator>(
_ initialResult: Accumulator,
_ nextPartialResult: (Accumulator, Output) throws -> Accumulator
) -> Publishers.Once<Accumulator, Error> {
return Publishers.Once(result.tryMap { try nextPartialResult(initialResult, $0) })
}
public func scan<ElementOfResult>(
_ initialResult: ElementOfResult,
_ nextPartialResult: (ElementOfResult, Output) -> ElementOfResult
) -> Publishers.Once<ElementOfResult, Failure> {
return Publishers.Once(result.map { nextPartialResult(initialResult, $0) })
}
public func tryScan<ElementOfResult>(
_ initialResult: ElementOfResult,
_ nextPartialResult: (ElementOfResult, Output) throws -> ElementOfResult
) -> Publishers.Once<ElementOfResult, Error> {
return Publishers.Once(result.tryMap { try nextPartialResult(initialResult, $0) })
}
}
extension Publishers.Once where Failure == Never {
public func setFailureType<Failure: Error>(
to failureType: Failure.Type
) -> Publishers.Once<Output, Failure> {
return Publishers.Once(result.success)
}
}
@@ -1,406 +0,0 @@
//
// Publishers.Optional.swift
//
//
// Created by Sergej Jaskiewicz on 17.06.2019.
//
extension Publishers {
/// A publisher that publishes an optional value to each subscriber exactly once, if
/// the optional has a value.
///
/// If `result` is `.success`, and the value is non-nil, then `Optional` waits until
/// receiving a request for at least 1 value before sending the output. If `result` is
/// `.failure`, then `Optional` sends the failure immediately upon subscription.
/// If `result` is `.success` and the value is nil, then `Optional` sends `.finished`
/// immediately upon subscription.
///
/// In contrast with `Just`, an `Optional` publisher can send an error.
/// In contrast with `Once`, an `Optional` publisher can send zero values and finish
/// normally, or send zero values and fail with an error.
public struct Optional<Output, Failure: Error>: Publisher {
// swiftlint:disable:previous syntactic_sugar
/// The result to deliver to each subscriber.
public let result: Result<Output?, Failure>
/// Creates a publisher to emit the optional value of a successful result, or fail
/// with an error.
///
/// - Parameter result: The result to deliver to each subscriber.
public init(_ result: Result<Output?, Failure>) {
self.result = result
}
public init(_ output: Output?) {
self.init(.success(output))
}
public init(_ failure: Failure) {
self.init(.failure(failure))
}
public func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
where Output == SubscriberType.Input, Failure == SubscriberType.Failure
{
switch result {
case .success(let value?):
subscriber.receive(subscription: Inner(value: value,
downstream: subscriber))
case .success(nil):
subscriber.receive(subscription: Subscriptions.empty)
subscriber.receive(completion: .finished)
case .failure(let failure):
subscriber.receive(subscription: Subscriptions.empty)
subscriber.receive(completion: .failure(failure))
}
}
}
}
private final class Inner<SubscriberType: Subscriber>: Subscription,
CustomStringConvertible,
CustomReflectable
{
private let _output: SubscriberType.Input
private var _downstream: SubscriberType?
init(value: SubscriberType.Input, downstream: SubscriberType) {
_output = value
_downstream = downstream
}
func request(_ demand: Subscribers.Demand) {
if let downstream = _downstream, demand > 0 {
_ = downstream.receive(_output)
downstream.receive(completion: .finished)
_downstream = nil
}
}
func cancel() {
_downstream = nil
}
var description: String { return "Optional" }
var customMirror: Mirror {
return Mirror(self, unlabeledChildren: CollectionOfOne(_output))
}
}
extension Publishers.Optional: Equatable where Output: Equatable, Failure: Equatable {}
extension Publishers.Optional where Output: Equatable {
public func contains(_ output: Output) -> Publishers.Optional<Bool, Failure> {
return Publishers.Optional(result.map { $0 == output })
}
public func removeDuplicates() -> Publishers.Optional<Output, Failure> {
return self
}
}
extension Publishers.Optional where Output: Comparable {
public func min() -> Publishers.Optional<Output, Failure> {
return self
}
public func max() -> Publishers.Optional<Output, Failure> {
return self
}
}
extension Publishers.Optional {
public func allSatisfy(
_ predicate: (Output) -> Bool
) -> Publishers.Optional<Bool, Failure> {
return Publishers.Optional(result.map { $0.map(predicate) })
}
public func tryAllSatisfy(
_ predicate: (Output) throws -> Bool
) -> Publishers.Optional<Bool, Error> {
return Publishers.Optional(result.tryMap { try $0.map(predicate) })
}
public func collect() -> Publishers.Optional<[Output], Failure> {
return Publishers.Optional(result.map { $0.map { [$0] } })
}
public func compactMap<ElementOfResult>(
_ transform: (Output) -> ElementOfResult?
) -> Publishers.Optional<ElementOfResult, Failure> {
return Publishers.Optional(result.map { $0.flatMap(transform) })
}
public func tryCompactMap<ElementOfResult>(
_ transform: (Output) throws -> ElementOfResult?
) -> Publishers.Optional<ElementOfResult, Error> {
return Publishers.Optional(result.tryMap { try $0.flatMap(transform) })
}
public func min(
by areInIncreasingOrder: (Output, Output) -> Bool
) -> Publishers.Optional<Output, Failure> {
return self
}
public func tryMin(
by areInIncreasingOrder: (Output, Output) throws -> Bool
) -> Publishers.Optional<Output, Failure> {
return self
}
public func max(
by areInIncreasingOrder: (Output, Output) -> Bool
) -> Publishers.Optional<Output, Failure> {
return self
}
public func tryMax(
by areInIncreasingOrder: (Output, Output) throws -> Bool
) -> Publishers.Optional<Output, Failure> {
return self
}
public func contains(
where predicate: (Output) -> Bool
) -> Publishers.Optional<Bool, Failure> {
return Publishers.Optional(result.map { $0.map(predicate) })
}
public func tryContains(
where predicate: (Output) throws -> Bool
) -> Publishers.Optional<Bool, Error> {
return Publishers.Optional(result.tryMap { try $0.map(predicate) })
}
public func count() -> Publishers.Optional<Int, Failure> {
return Publishers.Optional(result.map { _ in 1 })
}
public func dropFirst(_ count: Int = 1) -> Publishers.Optional<Output, Failure> {
precondition(count >= 0, "count must not be negative")
return Publishers.Optional(try? result.get().flatMap { count == 0 ? $0 : nil })
}
public func drop(
while predicate: (Output) -> Bool
) -> Publishers.Optional<Output, Failure> {
return Publishers.Optional(result.map { $0.flatMap { predicate($0) ? nil : $0 } })
}
public func tryDrop(
while predicate: (Output) throws -> Bool
) -> Publishers.Optional<Output, Error> {
return Publishers.Optional(
result.tryMap { try $0.flatMap { try predicate($0) ? nil : $0 } }
)
}
public func first() -> Publishers.Optional<Output, Failure> {
return self
}
public func first(
where predicate: (Output) -> Bool
) -> Publishers.Optional<Output, Failure> {
return Publishers.Optional(result.map { $0.flatMap { predicate($0) ? $0 : nil } })
}
public func tryFirst(
where predicate: (Output) throws -> Bool
) -> Publishers.Optional<Output, Error> {
return Publishers.Optional(
result.tryMap { try $0.flatMap { try predicate($0) ? $0 : nil } }
)
}
public func last() -> Publishers.Optional<Output, Failure> {
return self
}
public func last(
where predicate: (Output) -> Bool
) -> Publishers.Optional<Output, Failure> {
return Publishers.Optional(result.map { $0.flatMap { predicate($0) ? $0 : nil } })
}
public func tryLast(
where predicate: (Output) throws -> Bool
) -> Publishers.Optional<Output, Error> {
return Publishers.Optional(
result.tryMap { try $0.flatMap { try predicate($0) ? $0 : nil } }
)
}
public func filter(
_ isIncluded: (Output) -> Bool
) -> Publishers.Optional<Output, Failure> {
return Publishers.Optional(
result.map { $0.flatMap { isIncluded($0) ? $0 : nil } }
)
}
public func tryFilter(
_ isIncluded: (Output) throws -> Bool
) -> Publishers.Optional<Output, Error> {
return Publishers.Optional(
result.tryMap { try $0.flatMap { try isIncluded($0) ? $0 : nil } }
)
}
public func ignoreOutput() -> Publishers.Empty<Output, Failure> {
return Publishers.Empty()
}
public func map<ElementOfResult>(
_ transform: (Output) -> ElementOfResult
) -> Publishers.Optional<ElementOfResult, Failure> {
return Publishers.Optional(result.map { $0.map(transform) })
}
public func tryMap<ElementOfResult>(
_ transform: (Output) throws -> ElementOfResult
) -> Publishers.Optional<ElementOfResult, Error> {
return Publishers.Optional(result.tryMap { try $0.map(transform) })
}
public func mapError<TransformedFailure: Error>(
_ transform: (Failure) -> TransformedFailure
) -> Publishers.Optional<Output, TransformedFailure> {
return Publishers.Optional(result.mapError(transform))
}
public func output(at index: Int) -> Publishers.Optional<Output, Failure> {
precondition(index >= 0, "index must not be negative")
return Publishers.Optional(result.map { $0.flatMap { index == 0 ? $0 : nil } })
}
public func output<RangeExpr: RangeExpression>(
in range: RangeExpr
) -> Publishers.Optional<Output, Failure> where RangeExpr.Bound == Int {
// TODO: Broken in Apple's Combine? (FB6169621)
// Empty range should result in a nil
let range = range.relative(to: 0..<Int.max)
return Publishers.Optional(
result.map { $0.flatMap { range.lowerBound == 0 ? $0 : nil } }
)
// The above implementation is used for compatibility.
//
// It actually probably should be just this:
// return Publishers.Optional(
// result.map { $0.flatMap { range.contains(0) ? $0 : nil } }
// )
}
public func prefix(_ maxLength: Int) -> Publishers.Optional<Output, Failure> {
precondition(maxLength >= 0, "maxLength must not be negative")
// TODO: Seems broken in Apple's Combine (FB6168300)
return Publishers.Optional(
result.map { $0.flatMap { maxLength == 0 ? $0 : nil } }
)
// The above implementation is used for compatibility.
//
// It actually should be the following:
// return Publishers.Optional(
// result.map { $0.flatMap { maxLength > 0 ? $0 : nil } }
// )
}
public func prefix(
while predicate: (Output) -> Bool
) -> Publishers.Optional<Output, Failure> {
return Publishers.Optional(
result.map { $0.flatMap { predicate($0) ? $0 : nil } }
)
}
public func tryPrefix(
while predicate: (Output) throws -> Bool
) -> Publishers.Optional<Output, Error> {
return Publishers.Optional(
result.tryMap { try $0.flatMap { try predicate($0) ? $0 : nil } }
)
}
public func reduce<Accumulator>(
_ initialResult: Accumulator,
_ nextPartialResult: (Accumulator, Output) -> Accumulator
) -> Publishers.Optional<Accumulator, Failure> {
return Publishers.Optional(
result.map { $0.map { nextPartialResult(initialResult, $0) } }
)
}
public func tryReduce<Accumulator>(
_ initialResult: Accumulator,
_ nextPartialResult: (Accumulator, Output) throws -> Accumulator
) -> Publishers.Optional<Accumulator, Error> {
return Publishers.Optional(
result.tryMap { try $0.map { try nextPartialResult(initialResult, $0) } }
)
}
public func scan<ElementOfResult>(
_ initialResult: ElementOfResult,
_ nextPartialResult: (ElementOfResult, Output) -> ElementOfResult
) -> Publishers.Optional<ElementOfResult, Failure> {
return Publishers.Optional(
result.map { $0.map { nextPartialResult(initialResult, $0) } }
)
}
public func tryScan<ElementOfResult>(
_ initialResult: ElementOfResult,
_ nextPartialResult: (ElementOfResult, Output) throws -> ElementOfResult
) -> Publishers.Optional<ElementOfResult, Error> {
return Publishers.Optional(
result.tryMap { try $0.map { try nextPartialResult(initialResult, $0) } }
)
}
public func removeDuplicates(
by predicate: (Output, Output) -> Bool
) -> Publishers.Optional<Output, Failure> {
return self
}
public func tryRemoveDuplicates(
by predicate: (Output, Output) throws -> Bool
) -> Publishers.Optional<Output, Error> {
return Publishers.Optional(result.mapError { $0 })
}
public func replaceError(with output: Output) -> Publishers.Optional<Output, Never> {
return Publishers.Optional(.success(result.unwrapOr(output)))
}
public func replaceEmpty(
with output: Output
) -> Publishers.Optional<Output, Failure> {
return self
}
public func retry(_ times: Int) -> Publishers.Optional<Output, Failure> {
return self
}
public func retry() -> Publishers.Optional<Output, Failure> {
return self
}
}
extension Publishers.Optional where Failure == Never {
public func setFailureType<Failure: Error>(
to failureType: Failure.Type
) -> Publishers.Optional<Output, Failure> {
return Publishers.Optional(result.success)
}
}
@@ -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)
}
}
}
@@ -0,0 +1,22 @@
//
// Publishers.ReplaceNil.swift
//
//
// Created by Joseph Spadafora on 7/4/19.
//
extension Publisher {
/// Replaces nil elements in the stream with the proviced element.
///
/// - Parameter output: The element to use when replacing `nil`.
/// - Returns: A publisher that replaces `nil` elements from
/// the upstream publisher with the provided element.
public func replaceNil<ElementOfResult>(
with output: ElementOfResult
) -> Publishers.Map<Self, ElementOfResult>
where Output == ElementOfResult?
{
return Publishers.Map(upstream: self) { $0 ?? output }
}
}
@@ -110,21 +110,42 @@ extension Publishers.Sequence {
extension Publishers.Sequence: Equatable where Elements: Equatable {}
extension Publishers.Sequence where Failure == Never {
public func min(
by areInIncreasingOrder: (Elements.Element, Elements.Element) -> Bool
) -> Optional<Elements.Element>.OCombine.Publisher {
return .init(sequence.min(by: areInIncreasingOrder))
}
public func max(
by areInIncreasingOrder: (Elements.Element, Elements.Element) -> Bool
) -> Optional<Elements.Element>.OCombine.Publisher {
return .init(sequence.max(by: areInIncreasingOrder))
}
public func first(
where predicate: (Elements.Element) -> Bool
) -> Optional<Elements.Element>.OCombine.Publisher {
return .init(sequence.first(where: predicate))
}
}
extension Publishers.Sequence {
public func allSatisfy(
_ predicate: (Elements.Element) -> Bool
) -> Publishers.Once<Bool, Failure> {
) -> Result<Bool, Failure>.OCombine.Publisher {
return .init(sequence.allSatisfy(predicate))
}
public func tryAllSatisfy(
_ predicate: (Elements.Element) throws -> Bool
) -> Publishers.Once<Bool, Error> {
) -> Result<Bool, Error>.OCombine.Publisher {
return .init(Result { try sequence.allSatisfy(predicate) })
}
public func collect() -> Publishers.Once<[Elements.Element], Failure> {
public func collect() -> Result<[Elements.Element], Failure>.OCombine.Publisher {
return .init(Array(sequence))
}
@@ -134,39 +155,15 @@ extension Publishers.Sequence {
return .init(sequence: sequence.compactMap(transform))
}
public func min(
by areInIncreasingOrder: (Elements.Element, Elements.Element) -> Bool
) -> Publishers.Optional<Elements.Element, Failure> {
return .init(sequence.min(by: areInIncreasingOrder))
}
public func tryMin(
by areInIncreasingOrder: (Elements.Element, Elements.Element) throws -> Bool
) -> Publishers.Optional<Elements.Element, Error> {
return .init(Result { try sequence.min(by: areInIncreasingOrder) })
}
public func max(
by areInIncreasingOrder: (Elements.Element, Elements.Element) -> Bool
) -> Publishers.Optional<Elements.Element, Failure> {
return .init(sequence.max(by: areInIncreasingOrder))
}
public func tryMax(
by areInIncreasingOrder: (Elements.Element, Elements.Element) throws -> Bool
) -> Publishers.Optional<Elements.Element, Error> {
return .init(Result { try sequence.max(by: areInIncreasingOrder) })
}
public func contains(
where predicate: (Elements.Element) -> Bool
) -> Publishers.Once<Bool, Failure> {
) -> Result<Bool, Failure>.OCombine.Publisher {
return .init(sequence.contains(where: predicate))
}
public func tryContains(
where predicate: (Elements.Element) throws -> Bool
) -> Publishers.Once<Bool, Error> {
) -> Result<Bool, Error>.OCombine.Publisher {
return .init(Result { try sequence.contains(where: predicate) })
}
@@ -182,26 +179,14 @@ extension Publishers.Sequence {
return .init(sequence: sequence.dropFirst(count))
}
public func first(
where predicate: (Elements.Element) -> Bool
) -> Publishers.Optional<Elements.Element, Failure> {
return .init(sequence.first(where: predicate))
}
public func tryFirst(
where predicate: (Elements.Element) throws -> Bool
) -> Publishers.Optional<Elements.Element, Error> {
return .init(Result { try sequence.first(where: predicate) })
}
public func filter(
_ isIncluded: (Elements.Element) -> Bool
) -> Publishers.Sequence<[Elements.Element], Failure> {
return .init(sequence: sequence.filter(isIncluded))
}
public func ignoreOutput() -> Publishers.Empty<Elements.Element, Failure> {
return .init(completeImmediately: true)
public func ignoreOutput() -> Empty<Elements.Element, Failure> {
return .init()
}
public func map<ElementOfResult>(
@@ -225,7 +210,7 @@ extension Publishers.Sequence {
public func reduce<Accumulator>(
_ initialResult: Accumulator,
_ nextPartialResult: @escaping (Accumulator, Elements.Element) -> Accumulator
) -> Publishers.Once<Accumulator, Failure> {
) -> Result<Accumulator, Failure>.OCombine.Publisher {
return .init(sequence.reduce(initialResult, nextPartialResult))
}
@@ -233,7 +218,7 @@ extension Publishers.Sequence {
_ initialResult: Accumulator,
_ nextPartialResult:
@escaping (Accumulator, Elements.Element) throws -> Accumulator
) -> Publishers.Once<Accumulator, Error> {
) -> Result<Accumulator, Error>.OCombine.Publisher {
return .init(Result { try sequence.reduce(initialResult, nextPartialResult) })
}
@@ -276,38 +261,43 @@ extension Publishers.Sequence where Elements.Element: Equatable {
return .init(sequence: result)
}
public func contains(_ output: Elements.Element) -> Publishers.Once<Bool, Failure> {
public func contains(
_ output: Elements.Element
) -> Result<Bool, Failure>.OCombine.Publisher {
return .init(sequence.contains(output))
}
}
extension Publishers.Sequence where Elements.Element: Comparable {
extension Publishers.Sequence where Failure == Never, Elements.Element: Comparable {
public func min() -> Publishers.Optional<Elements.Element, Failure> {
public func min() -> Optional<Elements.Element>.OCombine.Publisher {
return .init(sequence.min())
}
public func max() -> Publishers.Optional<Elements.Element, Failure> {
public func max() -> Optional<Elements.Element>.OCombine.Publisher {
return .init(sequence.max())
}
}
extension Publishers.Sequence where Elements: Collection, Failure == Never {
public func first() -> Optional<Elements.Element>.OCombine.Publisher {
return .init(sequence.first)
}
public func output(
at index: Elements.Index
) -> Optional<Elements.Element>.OCombine.Publisher {
return .init(sequence.indices.contains(index) ? sequence[index] : nil)
}
}
extension Publishers.Sequence where Elements: Collection {
public func first() -> Publishers.Optional<Elements.Element, Failure> {
return .init(sequence.first)
}
public func count() -> Publishers.Once<Int, Failure> {
public func count() -> Result<Int, Failure>.OCombine.Publisher {
return .init(sequence.count)
}
public func output(
at index: Elements.Index
) -> Publishers.Optional<Elements.Element, Failure> {
return .init(sequence.indices.contains(index) ? sequence[index] : nil)
}
public func output(
in range: Range<Elements.Index>
) -> Publishers.Sequence<[Elements.Element], Failure> {
@@ -315,40 +305,41 @@ extension Publishers.Sequence where Elements: Collection {
}
}
extension Publishers.Sequence where Elements: BidirectionalCollection {
extension Publishers.Sequence where Elements: BidirectionalCollection, Failure == Never {
public func last() -> Publishers.Optional<Elements.Element, Failure> {
public func last() -> Optional<Elements.Element>.OCombine.Publisher {
return .init(sequence.last)
}
public func last(
where predicate: (Elements.Element) -> Bool
) -> Publishers.Optional<Elements.Element, Failure> {
) -> Optional<Elements.Element>.OCombine.Publisher {
return .init(sequence.last(where: predicate))
}
}
public func tryLast(
where predicate: (Elements.Element) throws -> Bool
) -> Publishers.Optional<Elements.Element, Error> {
return .init(Result { try sequence.last(where: predicate) })
extension Publishers.Sequence where Elements: RandomAccessCollection, Failure == Never {
public func output(
at index: Elements.Index
) -> Optional<Elements.Element>.OCombine.Publisher {
return .init(sequence.indices.contains(index) ? sequence[index] : nil)
}
public func count() -> Just<Int> {
return .init(sequence.count)
}
}
extension Publishers.Sequence where Elements: RandomAccessCollection {
public func output(
at index: Elements.Index
) -> Publishers.Optional<Elements.Element, Failure> {
return .init(sequence.indices.contains(index) ? sequence[index] : nil)
}
public func output(
in range: Range<Elements.Index>
) -> Publishers.Sequence<[Elements.Element], Failure> {
return .init(sequence: Array(sequence[range]))
}
public func count() -> Publishers.Optional<Int, Failure> {
public func count() -> Result<Int, Failure>.OCombine.Publisher {
return .init(sequence.count)
}
}
@@ -408,7 +399,7 @@ extension Publishers.Sequence where Elements: RangeReplaceableCollection {
extension Sequence {
public func publisher() -> Publishers.Sequence<Self, Never> {
return Publishers.Sequence(sequence: self)
public var publisher: Publishers.Sequence<Self, Never> {
return .init(sequence: self)
}
}
@@ -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<Upstream: Publisher, Failure: Error>: 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<Downstream: Subscriber>(subscriber: Downstream)
where Downstream.Failure == Failure, Downstream.Input == Output
{
let inner = Inner(downstream: subscriber)
upstream.subscribe(inner)
}
public func setFailureType<NewFailure: Error>(
to failure: NewFailure.Type
) -> Publishers.SetFailureType<Upstream, NewFailure> {
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<NewFailure: Error>(
to failureType: NewFailure.Type
) -> Publishers.SetFailureType<Self, NewFailure> {
return .init(upstream: self)
}
}
extension Publishers.SetFailureType {
private final class Inner<Downstream: Subscriber>
: OperatorSubscription<Downstream>,
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<Never>) {
downstream.receive(completion: .finished)
}
var description: String { return "SetFailureType" }
}
}
@@ -0,0 +1,328 @@
//
// Result.Publisher.swift
//
//
// Created by Sergej Jaskiewicz on 17.06.2019.
//
extension Result {
/// A namespace for disambiguation when both OpenCombine and Combine are imported.
///
/// Combine extends `Result` with a nested type `Publisher`.
/// If you import both OpenCombine and Combine (either explicitly or implicitly,
/// e. g. when importing Foundation), you will not be able
/// to write `Result<Int, Error>.Publisher`,
/// because Swift is unable to understand which `Publisher` you're referring to.
///
/// So you have to write `Result<Int, Error>.OCombine.Publisher`.
///
/// This bug is tracked [here](https://bugs.swift.org/browse/SR-11183).
///
/// You can omit this whenever Combine is not available (e. g. on Linux).
public struct OCombine {
fileprivate let result: Result
fileprivate init(_ result: Result) {
self.result = result
}
public var publisher: Publisher {
return Publisher(result)
}
/// A publisher that publishes an output to each subscriber exactly once then
/// finishes, or fails immediately without producing any elements.
///
/// If `result` is `.success`, then `Once` waits until it receives a request for
/// at least 1 value before sending the output. If `result` is `.failure`,
/// then `Once` sends the failure immediately upon subscription.
///
/// In contrast with `Just`, a `Once` publisher can terminate with an error
/// instead of sending a value. In contrast with `Optional`, a `Once` publisher
/// always sends one value (unless it terminates with an error).
public struct Publisher: OpenCombine.Publisher {
public typealias Output = Success
/// The result to deliver to each subscriber.
public let result: Result
/// Creates a publisher that delivers the specified result.
///
/// If the result is `.success`, the `Once` publisher sends the specified
/// output to all subscribers and finishes normally. If the result is
/// `.failure`, then the publisher fails immediately with the specified
/// error.
///
/// - Parameter result: The result to deliver to each subscriber.
public init(_ result: Result) {
self.result = result
}
/// Creates a publisher that sends the specified output to all subscribers and
/// finishes normally.
///
/// - Parameter output: The output to deliver to each subscriber.
public init(_ output: Output) {
self.init(.success(output))
}
/// Creates a publisher that immediately terminates upon subscription with
/// the given failure.
///
/// - Parameter failure: The failure to send when terminating.
public init(_ failure: Failure) {
self.init(.failure(failure))
}
public func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
where SubscriberType.Input == Success, SubscriberType.Failure == Failure
{
switch result {
case .success(let value):
subscriber.receive(subscription: Inner(value: value,
downstream: subscriber))
case .failure(let failure):
subscriber.receive(subscription: Subscriptions.empty)
subscriber.receive(completion: .failure(failure))
}
}
}
}
public var ocombine: OCombine {
return OCombine(self)
}
#if !canImport(Combine)
/// A publisher that publishes an output to each subscriber exactly once then
/// finishes, or fails immediately without producing any elements.
///
/// If `result` is `.success`, then `Once` waits until it receives a request for
/// at least 1 value before sending the output. If `result` is `.failure`, then `Once`
/// sends the failure immediately upon subscription.
///
/// In contrast with `Just`, a `Once` publisher can terminate with an error instead of
/// sending a value. In contrast with `Optional`, a `Once` publisher always sends one
/// value (unless it terminates with an error).
public typealias Publisher = OCombine.Publisher
public var publisher: Publisher {
return Publisher(self)
}
#endif
}
private final class Inner<SubscriberType: Subscriber>: Subscription,
CustomStringConvertible,
CustomReflectable
{
private let _output: SubscriberType.Input
private var _downstream: SubscriberType?
init(value: SubscriberType.Input, downstream: SubscriberType) {
_output = value
_downstream = downstream
}
func request(_ demand: Subscribers.Demand) {
if let downstream = _downstream, demand > 0 {
_ = downstream.receive(_output)
downstream.receive(completion: .finished)
_downstream = nil
}
}
func cancel() {
_downstream = nil
}
var description: String { return "Once" }
var customMirror: Mirror {
return Mirror(self, unlabeledChildren: CollectionOfOne(_output))
}
}
extension Result.OCombine.Publisher: Equatable
where Output: Equatable, Failure: Equatable
{
}
extension Result.OCombine.Publisher where Output: Equatable {
public func contains(_ output: Output) -> Result<Bool, Failure>.OCombine.Publisher {
return .init(result.map { $0 == output })
}
public func removeDuplicates() -> Result<Output, Failure>.OCombine.Publisher {
return self
}
}
extension Result.OCombine.Publisher where Output: Comparable {
public func min() -> Result<Output, Failure>.OCombine.Publisher {
return self
}
public func max() -> Result<Output, Failure>.OCombine.Publisher {
return self
}
}
extension Result.OCombine.Publisher {
public func allSatisfy(
_ predicate: (Output) -> Bool
) -> Result<Bool, Failure>.OCombine.Publisher {
return .init(result.map(predicate))
}
public func tryAllSatisfy(
_ predicate: (Output) throws -> Bool
) -> Result<Bool, Error>.OCombine.Publisher {
return .init(result.tryMap(predicate))
}
public func contains(
where predicate: (Output) -> Bool
) -> Result<Bool, Failure>.OCombine.Publisher {
return .init(result.map(predicate))
}
public func tryContains(
where predicate: (Output) throws -> Bool
) -> Result<Bool, Error>.OCombine.Publisher {
return .init(result.tryMap(predicate))
}
public func collect() -> Result<[Output], Failure>.OCombine.Publisher {
return .init(result.map { [$0] })
}
public func min(
by areInIncreasingOrder: (Output, Output) -> Bool
) -> Result<Output, Failure>.OCombine.Publisher {
return self
}
public func tryMin(
by areInIncreasingOrder: (Output, Output) throws -> Bool
) -> Result<Output, Error>.OCombine.Publisher {
return .init(result.tryMap { _ = try areInIncreasingOrder($0, $0); return $0 })
}
public func max(
by areInIncreasingOrder: (Output, Output
) -> Bool) -> Result<Output, Failure>.OCombine.Publisher {
return self
}
public func tryMax(
by areInIncreasingOrder: (Output, Output) throws -> Bool
) -> Result<Output, Error>.OCombine.Publisher {
return .init(result.tryMap { _ = try areInIncreasingOrder($0, $0); return $0 })
}
public func count() -> Result<Int, Failure>.OCombine.Publisher {
return .init(result.map { _ in 1 })
}
public func first() -> Result<Output, Failure>.OCombine.Publisher {
return self
}
public func last() -> Result<Output, Failure>.OCombine.Publisher {
return self
}
public func ignoreOutput() -> Empty<Output, Failure> {
return .init()
}
public func map<ElementOfResult>(
_ transform: (Output) -> ElementOfResult
) -> Result<ElementOfResult, Failure>.OCombine.Publisher {
return .init(result.map(transform))
}
public func tryMap<ElementOfResult>(
_ transform: (Output) throws -> ElementOfResult
) -> Result<ElementOfResult, Error>.OCombine.Publisher {
return .init(result.tryMap(transform))
}
public func mapError<TransformedFailure: Error>(
_ transform: (Failure) -> TransformedFailure
) -> Result<Output, TransformedFailure>.OCombine.Publisher {
return .init(result.mapError(transform))
}
public func removeDuplicates(
by predicate: (Output, Output) -> Bool
) -> Result<Output, Failure>.OCombine.Publisher {
return self
}
public func tryRemoveDuplicates(
by predicate: (Output, Output) throws -> Bool
) -> Result<Output, Error>.OCombine.Publisher {
return .init(result.tryMap { _ = try predicate($0, $0); return $0 })
}
public func replaceError(
with output: Output
) -> Result<Output, Never>.OCombine.Publisher {
return .init(.success(result.unwrapOr(output)))
}
public func replaceEmpty(
with output: Output
) -> Result<Output, Failure>.OCombine.Publisher {
return self
}
public func retry(_ times: Int) -> Result<Output, Failure>.OCombine.Publisher {
return self
}
public func reduce<Accumulator>(
_ initialResult: Accumulator,
_ nextPartialResult: (Accumulator, Output) -> Accumulator
) -> Result<Accumulator, Failure>.OCombine.Publisher {
return .init(result.map { nextPartialResult(initialResult, $0) })
}
public func tryReduce<Accumulator>(
_ initialResult: Accumulator,
_ nextPartialResult: (Accumulator, Output) throws -> Accumulator
) -> Result<Accumulator, Error>.OCombine.Publisher{
return .init(result.tryMap { try nextPartialResult(initialResult, $0) })
}
public func scan<ElementOfResult>(
_ initialResult: ElementOfResult,
_ nextPartialResult: (ElementOfResult, Output) -> ElementOfResult
) -> Result<ElementOfResult, Failure>.OCombine.Publisher {
return .init(result.map { nextPartialResult(initialResult, $0) })
}
public func tryScan<ElementOfResult>(
_ initialResult: ElementOfResult,
_ nextPartialResult: (ElementOfResult, Output) throws -> ElementOfResult
) -> Result<ElementOfResult, Error>.OCombine.Publisher {
return .init(result.tryMap { try nextPartialResult(initialResult, $0) })
}
}
extension Result.OCombine.Publisher where Failure == Never {
public func setFailureType<Failure: Error>(
to failureType: Failure.Type
) -> Result<Output, Failure>.OCombine.Publisher {
return .init(result.success)
}
}
+4
View File
@@ -22,6 +22,10 @@ public protocol Subject: AnyObject, Publisher {
/// - Parameter completion: A `Completion` instance which indicates whether publishing
/// has finished normally or failed with an error.
func send(completion: Subscribers.Completion<Failure>)
/// Provides this Subject an opportunity to establish demand for any new upstream
/// subscriptions (say, via `Publisher.subscribe<S: Subject>(_: Subject)`)
func send(subscription: Subscription)
}
extension Subject where Output == Void {
+15
View File
@@ -45,3 +45,18 @@ extension Subscriber where Input == Void {
return receive(())
}
}
extension Optional where Wrapped: Subscriber {
internal func receive(subscription: Subscription) {
self?.receive(subscription: subscription)
}
internal func receive(_ input: Wrapped.Input) -> Subscribers.Demand {
return self?.receive(input) ?? .none
}
internal func receive(completion: Subscribers.Completion<Wrapped.Failure>) {
self?.receive(completion: completion)
}
}
@@ -69,13 +69,14 @@ extension Subscribers {
extension Publisher where Self.Failure == Never {
/// Assigns the value of a KVO-compliant property from a publisher.
/// Assigns each element from a Publisher to a property on an object.
///
/// - Parameters:
/// - keyPath: The key path of the property to assign.
/// - object: The object on which to assign the value.
/// - Returns: A cancellable instance; used when you end KVO-based assignment of
/// the key paths value.
/// - Returns: A cancellable instance; used when you end assignment
/// of the received value. Deallocation of the result will tear down
/// the subscription stream.
public func assign<Root>(to keyPath: ReferenceWritableKeyPath<Root, Output>,
on object: Root) -> AnyCancellable {
let subscriber = Subscribers.Assign(object: object, keyPath: keyPath)
@@ -24,23 +24,14 @@ extension Subscribers.Completion: Equatable where Failure: Equatable {}
extension Subscribers.Completion: Hashable where Failure: Hashable {}
extension Subscribers.Completion: Codable where Failure: Decodable, Failure: Encodable {
extension Subscribers.Completion {
private enum CodingKeys: String, CodingKey {
case success = "success"
case error = "error"
}
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let success = try container.decode(Bool.self, forKey: .success)
if success {
self = .finished
} else {
let error = try container.decode(Failure.self, forKey: .error)
self = .failure(error)
}
}
extension Subscribers.Completion: Encodable where Failure: Encodable {
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
@@ -54,6 +45,19 @@ extension Subscribers.Completion: Codable where Failure: Decodable, Failure: Enc
}
}
extension Subscribers.Completion: Decodable where Failure: Decodable {
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let success = try container.decode(Bool.self, forKey: .success)
if success {
self = .finished
} else {
let error = try container.decode(Failure.self, forKey: .error)
self = .failure(error)
}
}
}
extension Subscribers.Completion {
/// Erases the `Failure` type to `Swift.Error`. This function exists
@@ -20,28 +20,31 @@ extension Subscribers {
Codable,
CustomStringConvertible
{
private var rawValue: UInt
@usableFromInline
internal let rawValue: UInt
private static let _rawValueUnlimited = UInt(Int.max) + 1
private init<Integer: BinaryInteger>(_ rawValue: Integer) {
if rawValue < 0 {
self.rawValue = 0
} else if rawValue > Demand._rawValueUnlimited {
self.rawValue = Demand._rawValueUnlimited
} else {
self.rawValue = UInt(rawValue)
}
@inline(__always)
@inlinable
internal init(rawValue: UInt) {
self.rawValue = min(UInt(Int.max) + 1, rawValue)
}
/// Requests as many values as the `Publisher` can produce.
public static let unlimited = Subscribers.Demand(_rawValueUnlimited)
public static let unlimited = Demand(rawValue: .max)
/// A demand for no items.
///
/// This is equivalent to `Demand.max(0)`.
public static let none = Demand.max(0)
/// Limits the maximum number of values.
/// The `Publisher` may send fewer than the requested number.
/// Negative values will result in a `fatalError`.
public static func max(_ value: Int) -> Subscribers.Demand {
return .init(UInt(value))
@inline(__always)
@inlinable
public static func max(_ value: Int) -> Demand {
precondition(value >= 0, "demand cannot be negative")
return Demand(rawValue: UInt(value))
}
public var description: String {
@@ -53,6 +56,8 @@ extension Subscribers {
}
/// When adding any value to `.unlimited`, the result is `.unlimited`.
@inline(__always)
@inlinable
public static func + (lhs: Demand, rhs: Demand) -> Demand {
switch (lhs, rhs) {
case (.unlimited, _):
@@ -60,31 +65,34 @@ extension Subscribers {
case (_, .unlimited):
return .unlimited
default:
let (sum, isOverflow) = lhs.rawValue.addingReportingOverflow(rhs.rawValue)
return isOverflow ? .unlimited : .init(sum)
let (sum, isOverflow) = Int(lhs.rawValue)
.addingReportingOverflow(Int(rhs.rawValue))
return isOverflow ? .unlimited : .max(sum)
}
}
/// A demand for no items.
///
/// This is equivalent to `Demand.max(0)`.
public static let none: Demand = .max(0)
/// When adding any value to `.unlimited`, the result is `.unlimited`.
@inline(__always)
@inlinable
public static func += (lhs: inout Demand, rhs: Demand) {
if lhs == .unlimited { return }
lhs = lhs + rhs
}
/// When adding any value to` .unlimited`, the result is `.unlimited`.
@inline(__always)
@inlinable
public static func + (lhs: Demand, rhs: Int) -> Demand {
if lhs == .unlimited {
return .unlimited
}
return Demand(lhs.rawValue.advanced(by: rhs))
let (sum, isOverflow) = Int(lhs.rawValue).addingReportingOverflow(rhs)
return isOverflow ? .unlimited : .max(sum)
}
/// When adding any value to `.unlimited`, the result is `.unlimited`.
@inline(__always)
@inlinable
public static func += (lhs: inout Demand, rhs: Int) {
lhs = lhs + rhs
}
@@ -93,21 +101,23 @@ extension Subscribers {
if lhs == .unlimited {
return .unlimited
}
let (product, isOverflow) =
lhs.rawValue.multipliedReportingOverflow(by: UInt(rhs))
return isOverflow ? .unlimited : .init(product)
let (product, isOverflow) = Int(lhs.rawValue)
.multipliedReportingOverflow(by: rhs)
return isOverflow ? .unlimited : .max(product)
}
@inline(__always)
@inlinable
public static func *= (lhs: inout Demand, rhs: Int) {
lhs = lhs * rhs
}
/// When subtracting any value (including .unlimited) from .unlimited,
/// the result is still .unlimited. Subtracting unlimited from any value
/// (except unlimited) results in .max(0). A negative demand is not possible;
/// any operation that would result in a negative value is clamped to .max(0).
/// When subtracting any value (including `.unlimited`) from `.unlimited`,
/// the result is still `.unlimited`. Subtracting `.unlimited` from any value
/// (except `.unlimited`) results in `.max(0)`. A negative demand is not possible;
/// any operation that would result in a negative value is clamped to `.max(0)`.
@inline(__always)
@inlinable
public static func - (lhs: Demand, rhs: Demand) -> Demand {
switch (lhs, rhs) {
case (.unlimited, _):
@@ -115,110 +125,231 @@ extension Subscribers {
case (_, .unlimited):
return .none
default:
let (difference, isOverflow) =
lhs.rawValue.subtractingReportingOverflow(rhs.rawValue)
return isOverflow ? .none : .init(difference)
let (difference, isOverflow) = Int(lhs.rawValue)
.subtractingReportingOverflow(Int(rhs.rawValue))
return isOverflow ? .none : .max(difference)
}
}
/// When subtracting any value (including .unlimited) from .unlimited,
/// the result is still .unlimited. Subtracting unlimited from any value
/// (except unlimited) results in .max(0). A negative demand is not possible;
/// any operation that would result in a negative value is clamped to .max(0).
/// When subtracting any value (including `.unlimited`) from `.unlimited`,
/// the result is still `.unlimited`. Subtracting unlimited from any value
/// (except `.unlimited`) results in `.max(0)`. A negative demand is not possible;
/// any operation that would result in a negative value is clamped to `.max(0)`.
/// but be aware that it is not usable when requesting values in a subscription.
@inline(__always)
@inlinable
public static func -= (lhs: inout Demand, rhs: Demand) {
lhs = lhs - rhs
}
/// When subtracting any value from .unlimited, the result is still .unlimited.
/// When subtracting any value from `.unlimited`, the result is still
/// `.unlimited`.
/// A negative demand is not possible; any operation that would result in
/// a negative value is clamped to .max(0)
/// a negative value is clamped to `.max(0)`
@inline(__always)
@inlinable
public static func - (lhs: Demand, rhs: Int) -> Demand {
if lhs == .unlimited {
return .unlimited
}
let (difference, isOverflow) =
Int(lhs.rawValue).subtractingReportingOverflow(rhs)
return isOverflow ? .none : .init(difference)
let (difference, isOverflow) = Int(lhs.rawValue)
.subtractingReportingOverflow(rhs)
return isOverflow ? .none : .max(difference)
}
/// When subtracting any value from .unlimited, the result is still .unlimited.
/// When subtracting any value from `.unlimited,` the result is still
/// `.unlimited`.
/// A negative demand is not possible; any operation that would result in
/// a negative value is clamped to .max(0)
/// a negative value is clamped to `.max(0)`
@inline(__always)
@inlinable
public static func -= (lhs: inout Demand, rhs: Int) {
if lhs == .unlimited { return }
lhs = lhs - rhs
}
@inline(__always)
@inlinable
public static func > (lhs: Demand, rhs: Int) -> Bool {
return lhs > .max(rhs)
if lhs == .unlimited {
return true
} else {
return Int(lhs.rawValue) > rhs
}
}
@inline(__always)
@inlinable
public static func >= (lhs: Demand, rhs: Int) -> Bool {
return lhs >= .max(rhs)
if lhs == .unlimited {
return true
} else {
return Int(lhs.rawValue) >= rhs
}
}
@inline(__always)
@inlinable
public static func > (lhs: Int, rhs: Demand) -> Bool {
return .max(lhs) > rhs
if rhs == .unlimited {
return false
} else {
return lhs > Int(rhs.rawValue)
}
}
@inline(__always)
@inlinable
public static func >= (lhs: Int, rhs: Demand) -> Bool {
return .max(lhs) >= rhs
if rhs == .unlimited {
return false
} else {
return lhs >= Int(rhs.rawValue)
}
}
@inline(__always)
@inlinable
public static func < (lhs: Demand, rhs: Int) -> Bool {
return lhs < .max(rhs)
if lhs == .unlimited {
return false
} else {
return Int(lhs.rawValue) < rhs
}
}
@inline(__always)
@inlinable
public static func < (lhs: Int, rhs: Demand) -> Bool {
return .max(lhs) < rhs
if rhs == .unlimited {
return true
} else {
return lhs < Int(rhs.rawValue)
}
}
@inline(__always)
@inlinable
public static func <= (lhs: Demand, rhs: Int) -> Bool {
return lhs <= .max(rhs)
if lhs == .unlimited {
return false
} else {
return Int(lhs.rawValue) <= rhs
}
}
@inline(__always)
@inlinable
public static func <= (lhs: Int, rhs: Demand) -> Bool {
return .max(lhs) <= rhs
if rhs == .unlimited {
return true
} else {
return lhs <= Int(rhs.rawValue)
}
}
/// If `lhs` is `.unlimited`, then the result is always `false`.
/// If `rhs` is `.unlimited` then the result is `false` iff `lhs` is `.unlimited`
/// Otherwise, the two `.max` values are compared.
@inline(__always)
@inlinable
public static func < (lhs: Demand, rhs: Demand) -> Bool {
if lhs == .unlimited {
switch (lhs, rhs) {
case (.unlimited, _):
return false
}
if rhs == .unlimited {
case (_, .unlimited):
return true
default:
return lhs.rawValue < rhs.rawValue
}
}
return lhs.rawValue < rhs.rawValue
@inline(__always)
@inlinable
public static func <= (lhs: Demand, rhs: Demand) -> Bool {
switch (lhs, rhs) {
case (.unlimited, .unlimited):
return true
case (.unlimited, _):
return false
case (_, .unlimited):
return true
default:
return lhs.rawValue <= rhs.rawValue
}
}
@inline(__always)
@inlinable
public static func >= (lhs: Demand, rhs: Demand) -> Bool {
switch (lhs, rhs) {
case (.unlimited, .unlimited):
return true
case (.unlimited, _):
return true
case (_, .unlimited):
return false
default:
return lhs.rawValue >= rhs.rawValue
}
}
@inline(__always)
@inlinable
public static func > (lhs: Demand, rhs: Demand) -> Bool {
switch (lhs, rhs) {
case (.unlimited, .unlimited):
return false
case (.unlimited, _):
return true
case (_, .unlimited):
return false
default:
return lhs.rawValue > rhs.rawValue
}
}
/// Returns `true` if `lhs` and `rhs` are equal. `.unlimited` is not equal to any
/// integer.
@inline(__always)
@inlinable
public static func == (lhs: Demand, rhs: Int) -> Bool {
return lhs == .max(rhs)
if lhs == .unlimited {
return false
} else {
return Int(lhs.rawValue) == rhs
}
}
/// Returns `true` if `lhs` and `rhs` are not equal. `.unlimited` is not equal to
/// any integer.
public static func != (lhs: Demand, rhs: Int) -> Bool {
return lhs != .max(rhs)
if lhs == .unlimited {
return true
} else {
return Int(lhs.rawValue) != rhs
}
}
/// Returns `true` if `lhs` and `rhs` are equal. `.unlimited` is not equal to any
/// integer.
public static func == (lhs: Int, rhs: Demand) -> Bool {
return .max(lhs) == rhs
if rhs == .unlimited {
return false
} else {
return rhs.rawValue == lhs
}
}
/// Returns `true` if `lhs` and `rhs` are not equal. `.unlimited` is not equal to
/// any integer.
public static func != (lhs: Int, rhs: Demand) -> Bool {
return .max(lhs) != rhs
if rhs == .unlimited {
return true
} else {
return Int(rhs.rawValue) != lhs
}
}
/// Returns the number of requested values, or `nil` if `.unlimited`.
@@ -229,5 +360,14 @@ extension Subscribers {
return Int(rawValue)
}
}
public init(from decoder: Decoder) throws {
try self.init(rawValue: decoder.singleValueContainer().decode(UInt.self))
}
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(rawValue)
}
}
}
@@ -34,13 +34,13 @@ extension Subscribers {
/// Initializes a sink with the provided closures.
///
/// - Parameters:
/// - receiveValue: The closure to execute on receipt of a value. If `nil`,
/// the sink uses an empty closure.
/// - receiveCompletion: The closure to execute on completion. If `nil`,
/// the sink uses an empty closure.
public init(receiveCompletion: ((Subscribers.Completion<Failure>) -> Void)? = nil,
receiveValue: @escaping ((Input) -> Void)) {
self.receiveCompletion = receiveCompletion ?? { _ in }
/// - receiveCompletion: The closure to execute on completion.
/// - receiveValue: The closure to execute on receipt of a value.
public init(
receiveCompletion: @escaping (Subscribers.Completion<Failure>) -> Void,
receiveValue: @escaping ((Input) -> Void)
) {
self.receiveCompletion = receiveCompletion
self.receiveValue = receiveValue
}
@@ -75,21 +75,44 @@ extension Publisher {
///
/// This method creates the subscriber and immediately requests an unlimited number
/// of values, prior to returning the subscriber.
/// - parameter receiveValue: The closure to execute on receipt of a value. If `nil`,
/// the sink uses an empty closure.
/// - parameter receiveComplete: The closure to execute on completion. If `nil`,
/// the sink uses an empty closure.
/// - Returns: A subscriber that performs the provided closures upon receiving values
/// or completion.
///
/// - parameter receiveComplete: The closure to execute on completion.
/// - parameter receiveValue: The closure to execute on receipt of a value.
/// - Returns: A cancellable instance; used when you end assignment of
/// the received value. Deallocation of the result will tear down
/// the subscription stream.
public func sink(
receiveCompletion: ((Subscribers.Completion<Failure>) -> Void)? = nil,
receiveCompletion: @escaping (Subscribers.Completion<Failure>) -> Void,
receiveValue: @escaping ((Output) -> Void)
) -> Subscribers.Sink<Output, Failure> {
) -> AnyCancellable {
let subscriber = Subscribers.Sink<Output, Failure>(
receiveCompletion: receiveCompletion,
receiveValue: receiveValue
)
subscribe(subscriber)
return subscriber
return AnyCancellable(subscriber)
}
}
extension Publisher where Failure == Never {
/// Attaches a subscriber with closure-based behavior.
///
/// This method creates the subscriber and immediately requests an unlimited number
/// of values, prior to returning the subscriber.
///
/// - parameter receiveValue: The closure to execute on receipt of a value.
/// - Returns: A cancellable instance; used when you end assignment of
/// the received value. Deallocation of the result will tear down
/// the subscription stream.
public func sink(
receiveValue: @escaping (Output) -> Void
) -> AnyCancellable {
let subscriber = Subscribers.Sink<Output, Failure>(
receiveCompletion: { _ in },
receiveValue: receiveValue
)
subscribe(subscriber)
return AnyCancellable(subscriber)
}
}
+6 -6
View File
@@ -13,20 +13,20 @@ extension Subscriptions {
///
/// Use the empty subscription when you need a `Subscription` that ignores requests
/// and cancellation.
public static var empty: Subscription { return Empty.shared }
public static var empty: Subscription { return EmptySubscription.shared }
}
private final class Empty: Subscription, CustomStringConvertible, CustomReflectable {
private final class EmptySubscription: Subscription,
CustomStringConvertible,
CustomReflectable
{
private init() {}
func request(_ demand: Subscribers.Demand) {}
func cancel() {}
var combineIdentifier: CombineIdentifier { return CombineIdentifier() }
static let shared = Empty()
fileprivate static let shared = EmptySubscription()
var description: String { return "Empty" }
+1
View File
@@ -11,4 +11,5 @@ import OpenCombineTests
var tests = [XCTestCaseEntry]()
tests += OpenCombineTests.allTests()
XCTMain(tests)
@@ -13,7 +13,7 @@ import Combine
import OpenCombine
#endif
@available(macOS 10.15, *)
@available(macOS 10.15, iOS 13.0, *)
final class AnyCancellableTests: XCTestCase {
static let allTests = [
@@ -21,6 +21,8 @@ final class AnyCancellableTests: XCTestCase {
("testCancelableInitialized", testCancelableInitialized),
("testCancelTwice", testCancelTwice),
("testStoreInArbitraryCollection", testStoreInArbitraryCollection),
("testStoreInSet", testStoreInSet),
("testIndirectCancellation", testIndirectCancellation),
]
func testClosureInitialized() {
@@ -97,11 +99,56 @@ final class AnyCancellableTests: XCTestCase {
XCTAssertEqual(disposeBag.history, [.emptyInit, .append])
let cancellable2 = AnyCancellable({})
let cancellable2 = AnyCancellable(cancellable1)
cancellable2.store(in: &disposeBag)
XCTAssertEqual(disposeBag.history, [.emptyInit, .append, .append])
XCTAssertEqual(disposeBag.storage, [cancellable1, cancellable2])
let cancellable2Abstracted: Cancellable = cancellable2
cancellable2Abstracted.store(in: &disposeBag)
XCTAssertEqual(disposeBag.history, [.emptyInit, .append, .append, .append])
XCTAssertEqual(disposeBag.storage.count, 3)
if disposeBag.storage.count == 3 {
XCTAssertNotEqual(disposeBag.storage[2], cancellable2)
}
}
func testStoreInSet() {
var disposeBag = Set<AnyCancellable>()
let cancellable1 = AnyCancellable({})
cancellable1.store(in: &disposeBag)
XCTAssertEqual(disposeBag, [cancellable1])
let cancellable2 = AnyCancellable(cancellable1)
cancellable2.store(in: &disposeBag)
XCTAssertEqual(disposeBag, [cancellable1, cancellable2])
cancellable2.store(in: &disposeBag)
XCTAssertEqual(disposeBag, [cancellable1, cancellable2])
let cancellable2Abstracted: Cancellable = cancellable2
cancellable2Abstracted.store(in: &disposeBag)
XCTAssertEqual(disposeBag.count, 3)
}
func testIndirectCancellation() {
let subscription = CustomSubscription()
let cancellable1 = AnyCancellable(subscription)
let cancellable2 = AnyCancellable(cancellable1)
XCTAssert(subscription.history.isEmpty)
cancellable2.cancel()
XCTAssertEqual(subscription.history, [.cancelled])
cancellable1.cancel()
XCTAssertEqual(subscription.history, [.cancelled])
}
}
+16 -5
View File
@@ -13,22 +13,33 @@ import Combine
import OpenCombine
#endif
@available(macOS 10.15, *)
@available(macOS 10.15, iOS 13.0, *)
final class AnyPublisherTests: XCTestCase {
static let allTests = [
("testErasePublisher", testErasePublisher),
("testDescription", testDescription),
]
private typealias Sut = AnyPublisher<Int, TestingError>
func testErasePublisher() {
let publisher = TrackingSubject<Int>()
let erased = AnyPublisher(publisher)
let subscriber = TrackingSubscriber()
let publisher = TrackingSubject<Int>(
receiveSubscriber: {
XCTAssertEqual($0.combineIdentifier, subscriber.combineIdentifier)
}
)
let erased = AnyPublisher(publisher)
erased.receive(subscriber: subscriber)
XCTAssertEqual(publisher.history, [.subscriber(subscriber.combineIdentifier)])
erased.subscribe(subscriber)
XCTAssertEqual(publisher.history, [.subscriber])
}
func testDescription() {
let erased = AnyPublisher(TrackingSubject<Int>())
XCTAssertEqual(erased.description, "AnyPublisher")
XCTAssertEqual(erased.description, erased.playgroundDescription as? String)
}
}
@@ -1,72 +0,0 @@
//
// AnySubjectTests.swift
//
//
// Created by Sergej Jaskiewicz on 16.06.2019.
//
import XCTest
#if OPENCOMBINE_COMPATIBILITY_TEST
import Combine
#else
import OpenCombine
#endif
@available(macOS 10.15, *)
final class AnySubjectTests: XCTestCase {
static let allTests = [
("testEraseSubject", testEraseSubject),
("testClosureBasedSubject", testClosureBasedSubject),
]
private typealias Sut = AnyPublisher<Int, TestingError>
func testEraseSubject() {
let subject = TrackingSubject<Int>()
let erased = AnySubject(subject)
let subscriber = TrackingSubscriber()
erased.receive(subscriber: subscriber)
erased.send(42)
erased.send(completion: .finished)
erased.send(completion: .failure("f"))
erased.send(12)
erased.receive(subscriber: subscriber)
XCTAssertEqual(subject.history, [.subscriber(subscriber.combineIdentifier),
.value(42),
.completion(.finished),
.completion(.failure("f")),
.value(12),
.subscriber(subscriber.combineIdentifier)])
}
func testClosureBasedSubject() {
var events: [TrackingSubject<Int>.Event] = []
let erased = AnySubject<Int, TestingError>(
{ events.append(.subscriber($0.combineIdentifier)) },
{ events.append(.value($0)) },
{ events.append(.completion($0)) }
)
let subscriber = TrackingSubscriber()
erased.receive(subscriber: subscriber)
erased.send(42)
erased.send(completion: .finished)
erased.send(completion: .failure("f"))
erased.send(12)
erased.receive(subscriber: subscriber)
XCTAssertEqual(events, [.subscriber(subscriber.combineIdentifier),
.value(42),
.completion(.finished),
.completion(.failure("f")),
.value(12),
.subscriber(subscriber.combineIdentifier)])
}
}
+33 -26
View File
@@ -13,10 +13,10 @@ import Combine
import OpenCombine
#endif
@available(macOS 10.15, *)
@available(macOS 10.15, iOS 13.0, *)
private typealias Sut = AnySubscriber<Int, TestingError>
@available(macOS 10.15, *)
@available(macOS 10.15, iOS 13.0, *)
final class AnySubscriberTests: XCTestCase {
static let allTests = [
@@ -38,18 +38,28 @@ final class AnySubscriberTests: XCTestCase {
let subscriber1 = TrackingSubscriber()
let subscriber2 = TrackingSubscriber()
XCTAssertNotEqual(Sut(subscriber1).combineIdentifier,
Sut(subscriber2).combineIdentifier)
XCTAssertEqual(subscriber1.combineIdentifier,
Sut(subscriber1).combineIdentifier)
XCTAssertEqual(subscriber2.combineIdentifier,
Sut(subscriber2).combineIdentifier)
do {
let erased1 = Sut(subscriber1)
let erased2 = Sut(subscriber2)
XCTAssertNotEqual(erased1.combineIdentifier, erased2.combineIdentifier)
}
do {
let subject = Sut(subscriber1)
XCTAssertEqual(subscriber1.combineIdentifier, subject.combineIdentifier)
}
do {
let subject = Sut(subscriber2)
XCTAssertEqual(subscriber2.combineIdentifier, subject.combineIdentifier)
}
}
func testDescription() {
let empty = Sut()
XCTAssertEqual(empty.description, "AnySubscriber")
XCTAssertEqual(empty.description, "Anonymous AnySubscriber")
XCTAssertEqual(empty.description, empty.playgroundDescription as? String)
let subject = PassthroughSubject<Int, TestingError>()
@@ -71,7 +81,7 @@ final class AnySubscriberTests: XCTestCase {
let empty = Sut()
XCTAssertEqual(
String(describing: Mirror(reflecting: empty).subjectType),
"CombineIdentifier"
"String"
)
XCTAssert(Mirror(reflecting: empty).children.isEmpty)
@@ -123,7 +133,7 @@ final class AnySubscriberTests: XCTestCase {
publishEvents(events, publisher)
XCTAssertEqual(subscriber.history, [.subscription(Subscriptions.empty),
XCTAssertEqual(subscriber.history, [.subscription("PassthroughSubject"),
.completion(.finished)])
}
@@ -136,8 +146,8 @@ final class AnySubscriberTests: XCTestCase {
publishEvents(events, erased)
let expectedEvents: [TrackingSubject.Event] =
events.compactMap(subscriberEventToSubjectEvent)
let expectedEvents: [TrackingSubject<Int>.Event] =
[.subscription("Subject")] + events.compactMap(subscriberEventToSubjectEvent)
XCTAssertEqual(subject.history, expectedEvents)
@@ -167,19 +177,16 @@ final class AnySubscriberTests: XCTestCase {
publishEvents(events, publisher)
XCTAssertEqual(subject.history, [.value(31),
.value(42),
.value(-1),
.value(141241241),
XCTAssertEqual(subject.history, [.subscription("Subject"),
.completion(.finished)])
}
}
@available(OSX 10.15, *)
@available(macOS 10.15, iOS 13.0, *)
private let events: [TrackingSubscriber.Event] = [
.subscription(Subscriptions.empty),
.subscription(Subscriptions.empty),
.subscription(Subscriptions.empty),
.subscription("1"),
.subscription("2"),
.subscription("3"),
.value(31),
.value(42),
.value(-1),
@@ -189,7 +196,7 @@ private let events: [TrackingSubscriber.Event] = [
.completion(.failure("failure"))
]
@available(OSX 10.15, *)
@available(macOS 10.15, iOS 13.0, *)
private func publishEvents(_ events: [TrackingSubscriber.Event], _ erased: Sut) {
for event in events {
switch event {
@@ -203,15 +210,15 @@ private func publishEvents(_ events: [TrackingSubscriber.Event], _ erased: Sut)
}
}
@available(OSX 10.15, *)
@available(macOS 10.15, iOS 13.0, *)
private func publishEvents(
_ events: [TrackingSubscriber.Event],
_ publisher: PassthroughSubject<Int, TestingError>
) {
for event in events {
switch event {
case .subscription:
break
case .subscription(let s):
publisher.send(subscription: s)
case .value(let v):
publisher.send(v)
case .completion(let c):
@@ -220,7 +227,7 @@ private func publishEvents(
}
}
@available(OSX 10.15, *)
@available(macOS 10.15, iOS 13.0, *)
private func subscriberEventToSubjectEvent(
_ from: TrackingSubscriber.Event
) -> TrackingSubject<Int>.Event? {
@@ -14,7 +14,7 @@ import Combine
import OpenCombine
#endif
@available(macOS 10.15, *)
@available(macOS 10.15, iOS 13.0, *)
final class CombineIdentifierTests: PerformanceTestCase {
static let allTests = [
@@ -15,15 +15,18 @@ import OpenCombine
// swiftlint:disable explicit_top_level_acl
@available(macOS 10.15, *)
@available(macOS 10.15, iOS 13.0, *)
final class CurrentValueSubjectTests: XCTestCase {
static let allTests = [
("testRequestingDemand", testRequestingDemand),
("testCrashOnZeroInitialDemand", testCrashOnZeroInitialDemand),
("testSendFailureCompletion", testSendFailureCompletion),
("testMultipleSubscriptions", testMultipleSubscriptions),
("testMultipleCompletions", testMultipleCompletions),
("testValuesAfterCompletion", testValuesAfterCompletion),
("testSubscriptionAfterCompletion", testSubscriptionAfterCompletion),
("testSendSubscription", testSendSubscription),
("testLifecycle", testLifecycle),
("testSynchronization", testSynchronization),
]
@@ -114,6 +117,16 @@ final class CurrentValueSubjectTests: XCTestCase {
XCTAssertEqual(numberOfInputsHistory, expectedNumberOfInputsHistory)
}
func testCrashOnZeroInitialDemand() {
assertCrashes {
let subscriber = TrackingSubscriber(
receiveSubscription: { $0.request(.none) }
)
Sut(1).subscribe(subscriber)
}
}
func testSendFailureCompletion() {
let cvs = Sut(0)
let subscriber = TrackingSubscriber(
@@ -124,18 +137,18 @@ final class CurrentValueSubjectTests: XCTestCase {
cvs.subscribe(subscriber)
XCTAssertEqual(subscriber.history, [.subscription(Subscriptions.empty),
XCTAssertEqual(subscriber.history, [.subscription("CurrentValueSubject"),
.value(0)])
cvs.value += 3
XCTAssertEqual(subscriber.history, [.subscription(Subscriptions.empty),
XCTAssertEqual(subscriber.history, [.subscription("CurrentValueSubject"),
.value(0),
.value(3)])
cvs.send(completion: .failure(.oops))
XCTAssertEqual(subscriber.history, [.subscription(Subscriptions.empty),
XCTAssertEqual(subscriber.history, [.subscription("CurrentValueSubject"),
.value(0),
.value(3),
.completion(.failure(.oops))])
@@ -179,51 +192,53 @@ final class CurrentValueSubjectTests: XCTestCase {
cvs.subscribe(subscriber)
XCTAssertEqual(subscriber.tracking.history, [.value(112),
.subscription(Subscriptions.empty),
.value(112),
.subscription(Subscriptions.empty),
.value(112),
.subscription(Subscriptions.empty),
.value(112),
.subscription(Subscriptions.empty),
.value(112),
.subscription(Subscriptions.empty),
.value(112),
.subscription(Subscriptions.empty),
.value(112),
.subscription(Subscriptions.empty),
.value(112),
.subscription(Subscriptions.empty),
.value(112),
.subscription(Subscriptions.empty),
.value(112),
.subscription(Subscriptions.empty)])
XCTAssertEqual(subscriber.tracking.history,
[.value(112),
.subscription("CurrentValueSubject"),
.value(112),
.subscription("CurrentValueSubject"),
.value(112),
.subscription("CurrentValueSubject"),
.value(112),
.subscription("CurrentValueSubject"),
.value(112),
.subscription("CurrentValueSubject"),
.value(112),
.subscription("CurrentValueSubject"),
.value(112),
.subscription("CurrentValueSubject"),
.value(112),
.subscription("CurrentValueSubject"),
.value(112),
.subscription("CurrentValueSubject"),
.value(112),
.subscription("CurrentValueSubject")])
cvs.subscribe(subscriber)
XCTAssertEqual(subscriber.tracking.history, [.value(112),
.subscription(Subscriptions.empty),
.value(112),
.subscription(Subscriptions.empty),
.value(112),
.subscription(Subscriptions.empty),
.value(112),
.subscription(Subscriptions.empty),
.value(112),
.subscription(Subscriptions.empty),
.value(112),
.subscription(Subscriptions.empty),
.value(112),
.subscription(Subscriptions.empty),
.value(112),
.subscription(Subscriptions.empty),
.value(112),
.subscription(Subscriptions.empty),
.value(112),
.subscription(Subscriptions.empty),
.value(112),
.subscription(Subscriptions.empty)])
XCTAssertEqual(subscriber.tracking.history,
[.value(112),
.subscription("CurrentValueSubject"),
.value(112),
.subscription("CurrentValueSubject"),
.value(112),
.subscription("CurrentValueSubject"),
.value(112),
.subscription("CurrentValueSubject"),
.value(112),
.subscription("CurrentValueSubject"),
.value(112),
.subscription("CurrentValueSubject"),
.value(112),
.subscription("CurrentValueSubject"),
.value(112),
.subscription("CurrentValueSubject"),
.value(112),
.subscription("CurrentValueSubject"),
.value(112),
.subscription("CurrentValueSubject"),
.value(112),
.subscription("CurrentValueSubject")])
}
// Reactive Streams Spec: Rule #6
@@ -241,24 +256,24 @@ final class CurrentValueSubjectTests: XCTestCase {
cvs.subscribe(subscriber)
cvs.value = 42
XCTAssertEqual(subscriber.history, [.subscription(Subscriptions.empty),
XCTAssertEqual(subscriber.history, [.subscription("CurrentValueSubject"),
.value(112),
.value(42)])
cvs.send(completion: .finished)
XCTAssertEqual(subscriber.history, [.subscription(Subscriptions.empty),
XCTAssertEqual(subscriber.history, [.subscription("CurrentValueSubject"),
.value(112),
.value(42),
.completion(.finished)])
cvs.send(completion: .finished)
XCTAssertEqual(subscriber.history, [.subscription(Subscriptions.empty),
XCTAssertEqual(subscriber.history, [.subscription("CurrentValueSubject"),
.value(112),
.value(42),
.completion(.finished)])
cvs.send(completion: .failure("oops"))
XCTAssertEqual(subscriber.history, [.subscription(Subscriptions.empty),
XCTAssertEqual(subscriber.history, [.subscription("CurrentValueSubject"),
.value(112),
.value(42),
.completion(.finished)])
@@ -279,23 +294,69 @@ final class CurrentValueSubjectTests: XCTestCase {
cvs.subscribe(subscriber)
cvs.value = 44
XCTAssertEqual(subscriber.history, [.subscription(Subscriptions.empty),
XCTAssertEqual(subscriber.history, [.subscription("CurrentValueSubject"),
.value(112),
.value(44)])
cvs.send(completion: .finished)
XCTAssertEqual(subscriber.history, [.subscription(Subscriptions.empty),
XCTAssertEqual(subscriber.history, [.subscription("CurrentValueSubject"),
.value(112),
.value(44),
.completion(.finished)])
cvs.value = 1201
XCTAssertEqual(subscriber.history, [.subscription(Subscriptions.empty),
XCTAssertEqual(subscriber.history, [.subscription("CurrentValueSubject"),
.value(112),
.value(44),
.completion(.finished)])
}
func testSubscriptionAfterCompletion() {
let passthrough = Sut(0)
passthrough.send(completion: .finished)
let subscriber = TrackingSubscriber()
passthrough.subscribe(subscriber)
XCTAssertEqual(subscriber.history, [.subscription("Empty"),
.completion(.finished)])
}
func testSendSubscription() {
let subscription1 = CustomSubscription()
let cvs = Sut(1)
cvs.send(subscription: subscription1)
XCTAssertEqual(subscription1.history, [.requested(.unlimited)])
let subscriber1 = TrackingSubscriber(receiveSubscription: { $0.request(.max(1)) })
cvs.subscribe(subscriber1)
XCTAssertEqual(subscription1.history, [.requested(.unlimited)])
XCTAssertEqual(subscriber1.history, [.subscription("CurrentValueSubject"),
.value(1)])
let subscriber2 = TrackingSubscriber(receiveSubscription: { $0.request(.max(2)) })
cvs.subscribe(subscriber2)
XCTAssertEqual(subscription1.history, [.requested(.unlimited)])
XCTAssertEqual(subscriber1.history, [.subscription("CurrentValueSubject"),
.value(1)])
XCTAssertEqual(subscriber2.history, [.subscription("CurrentValueSubject"),
.value(1)])
cvs.send(subscription: subscription1)
XCTAssertEqual(subscription1.history, [.requested(.unlimited),
.requested(.unlimited)])
cvs.send(0)
cvs.send(0)
let subscription2 = CustomSubscription()
cvs.send(subscription: subscription2)
XCTAssertEqual(subscription2.history, [.requested(.unlimited)])
}
func testLifecycle() throws {
var deinitCounter = 0
@@ -310,17 +371,17 @@ final class CurrentValueSubjectTests: XCTestCase {
XCTAssertTrue(subscriber.history.isEmpty)
cvs.subscribe(subscriber)
XCTAssertEqual(subscriber.history, [.subscription(Subscriptions.empty)])
XCTAssertEqual(subscriber.history, [.subscription("CurrentValueSubject")])
cvs.value += 1
XCTAssertEqual(subscriber.history, [.subscription(Subscriptions.empty)])
XCTAssertEqual(subscriber.history, [.subscription("CurrentValueSubject")])
cvs.send(completion: .failure(.oops))
XCTAssertEqual(subscriber.history, [.subscription(Subscriptions.empty),
XCTAssertEqual(subscriber.history, [.subscription("CurrentValueSubject"),
.completion(.failure(.oops))])
}
XCTAssertEqual(deinitCounter, 0)
XCTAssertEqual(deinitCounter, 1)
var subscription: Subscription?
@@ -332,18 +393,18 @@ final class CurrentValueSubjectTests: XCTestCase {
)
XCTAssertTrue(subscriber.history.isEmpty)
cvs.subscribe(subscriber)
XCTAssertEqual(subscriber.history, [.subscription(Subscriptions.empty),
XCTAssertEqual(subscriber.history, [.subscription("CurrentValueSubject"),
.value(0)])
cvs.send(31)
XCTAssertEqual(subscriber.history, [.subscription(Subscriptions.empty),
XCTAssertEqual(subscriber.history, [.subscription("CurrentValueSubject"),
.value(0),
.value(31)])
XCTAssertNotNil(subscription)
}
XCTAssertEqual(deinitCounter, 0)
XCTAssertEqual(deinitCounter, 1)
try XCTUnwrap(subscription).cancel()
XCTAssertEqual(deinitCounter, 0)
XCTAssertEqual(deinitCounter, 2)
}
func testSynchronization() {
@@ -13,4 +13,7 @@ import OpenCombine
extension JSONDecoder: TopLevelDecoder {}
extension JSONEncoder: TopLevelEncoder {}
extension PropertyListDecoder: TopLevelDecoder {}
extension PropertyListEncoder: TopLevelEncoder {}
#endif
@@ -0,0 +1,87 @@
//
// AssertCrashes.swift
//
//
// Created by Sergej Jaskiewicz on 31.07.2019.
//
import Foundation
import XCTest
extension XCTest {
var testcaseName: String {
return String(describing: type(of: self))
}
var testName: String {
// Since on Apple platforms `self.name` has
// format `-[XCTestCaseSubclassName testMethodName]`,
// and on other platforms the format is
// `XCTestCaseSubclassName.testMethodName`
// we have this workaround in order to unify the names
return name
.components(separatedBy: testcaseName)
.last!
.trimmingCharacters(in: CharacterSet.alphanumerics.inverted)
}
// Taken from swift-corelibs-foundation and slightly modified for OpenCombine
@available(macOS 10.13, iOS 8.0, *)
func assertCrashes(within block: () throws -> Void) rethrows {
#if !Xcode && !os(iOS) && !os(watchOS) && !os(tvOS)
let childProcessEnvVariable = "OPENCOMBINE_TEST_PERFORM_ASSERT_CRASHES_BLOCKS"
let childProcessEnvVariableOnValue = "YES"
let isChildProcess = ProcessInfo
.processInfo
.environment[childProcessEnvVariable] == childProcessEnvVariableOnValue
if isChildProcess {
try block()
} else {
var arguments = ProcessInfo.processInfo.arguments
let xctestUtilityPath = URL(fileURLWithPath: arguments[0])
let childProcess = Process()
childProcess.executableURL = xctestUtilityPath
arguments.removeFirst()
arguments.removeAll { $0.hasPrefix("OpenCombineTests.") || $0 == "-XCTest" }
arguments.insert("OpenCombineTests.\(testcaseName)/\(testName)", at: 0)
#if os(macOS)
arguments.insert("-XCTest", at: 0)
#endif
childProcess.arguments = arguments
var environment = ProcessInfo.processInfo.environment
environment[childProcessEnvVariable] = childProcessEnvVariableOnValue
childProcess.environment = environment
func printDiagostics() {
print("Parent process invocation:")
print(ProcessInfo.processInfo.arguments.joined(separator: " "))
print("Child process invocation:")
print(
([ProcessInfo.processInfo.arguments[0]] + arguments)
.joined(separator: " ")
)
}
do {
try childProcess.run()
childProcess.waitUntilExit()
if childProcess.terminationReason != .uncaughtSignal {
XCTFail("Child process should have crashed: \(childProcess)")
printDiagostics()
}
} catch {
XCTFail("""
Couldn't start child process for testing crash: \(childProcess) - \(error)
""")
printDiagostics()
}
}
#endif
}
}
@@ -30,34 +30,33 @@ import OpenCombine
/// publisher.subscribe(subscriber)
///
/// assert(subscription.history == [.requested(.max(42)), .cancelled])
@available(macOS 10.15, *)
typealias CustomPublisher = CustomPublisherBase<Int>
@available(macOS 10.15, iOS 13.0, *)
typealias CustomPublisher = CustomPublisherBase<Int, TestingError>
@available(macOS 10.15, *)
final class CustomPublisherBase<Value: Equatable>: Publisher {
@available(macOS 10.15, iOS 13.0, *)
final class CustomPublisherBase<Output: Equatable, Failure: Error>: Publisher {
typealias Output = Value
typealias Failure = TestingError
private var subscriber: AnySubscriber<Value, TestingError>?
private(set) var subscriber: AnySubscriber<Output, Failure>?
private(set) var erasedSubscriber: Any?
private let subscription: Subscription?
init(subscription: Subscription?) {
self.subscription = subscription
}
func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
where Failure == SubscriberType.Failure, Output == SubscriberType.Input
func receive<Downstream: Subscriber>(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 {
return subscriber!.receive(value)
func send(_ value: Output) -> Subscribers.Demand {
return subscriber?.receive(value) ?? .none
}
func send(completion: Subscribers.Completion<TestingError>) {
func send(completion: Subscribers.Completion<Failure>) {
subscriber!.receive(completion: completion)
}
}
@@ -16,8 +16,8 @@ import OpenCombine
///
/// In order to inject `CustomSubscription` into the chain of subscriptions,
/// use the `CustomSubscriber` class.
@available(macOS 10.15, *)
final class CustomSubscription: Subscription {
@available(macOS 10.15, iOS 13.0, *)
final class CustomSubscription: Subscription, CustomStringConvertible {
enum Event: Equatable, CustomStringConvertible {
case requested(Subscribers.Demand)
@@ -26,9 +26,9 @@ final class CustomSubscription: Subscription {
var description: String {
switch self {
case .requested(let demand):
return "requested(\(demand))"
return ".requested(.\(demand))"
case .cancelled:
return "cancelled"
return ".cancelled"
}
}
}
@@ -37,12 +37,12 @@ final class CustomSubscription: Subscription {
private(set) var history: [Event] = []
private let _requested: ((Subscribers.Demand) -> Void)?
private let _canceled: (() -> Void)?
private let _cancelled: (() -> Void)?
init(onRequest: ((Subscribers.Demand) -> Void)? = nil,
onCancel: (() -> Void)? = nil) {
_requested = onRequest
_canceled = onCancel
_cancelled = onCancel
}
var lastRequested: Subscribers.Demand? {
@@ -56,7 +56,7 @@ final class CustomSubscription: Subscription {
}.last
}
var canceled = false
var cancelled = false
func request(_ demand: Subscribers.Demand) {
history.append(.requested(demand))
@@ -65,7 +65,9 @@ final class CustomSubscription: Subscription {
func cancel() {
history.append(.cancelled)
canceled = true
_canceled?()
cancelled = true
_cancelled?()
}
var description: String { return "CustomSubscription" }
}
+11 -12
View File
@@ -11,18 +11,18 @@ import Combine
import OpenCombine
#endif
class TestEncoder: TopLevelEncoder {
final class TestEncoder: TopLevelEncoder {
typealias Output = Int
private var nextEncoded = 1
var encoded: [Int: Any] = [:]
var encoded: [Int : Any] = [:]
var handleEncode: ((Any) -> Int?)?
var handleEncode: ((Any) throws -> Int?)?
func encode<EncodableType: Encodable>(_ value: EncodableType) throws -> Int {
var keyNumber = nextEncoded
if let number = handleEncode?(value) {
if let number = try handleEncode?(value) {
keyNumber = number
} else {
nextEncoded += 1
@@ -32,19 +32,18 @@ class TestEncoder: TopLevelEncoder {
}
}
class TestDecoder: TopLevelDecoder {
final class TestDecoder: TopLevelDecoder {
typealias Input = Int
static let error = "Could not decode" as TestingError
var handleDecode: ((Int) -> Any?)?
var handleDecode: ((Int) throws -> Any?)?
func decode<DecodablyType: Decodable>(
_ type: DecodablyType.Type,
from data: Int)
throws -> DecodablyType
{
if let value = handleDecode?(data), let mappedValue = value as? DecodablyType {
func decode<Decodable: Swift.Decodable>(
_ type: Decodable.Type,
from data: Int
) throws -> Decodable {
if let value = try handleDecode?(data), let mappedValue = value as? Decodable {
return mappedValue
}
throw TestDecoder.error
@@ -11,7 +11,7 @@ import Combine
import OpenCombine
#endif
@available(macOS 10.15, *)
@available(macOS 10.15, iOS 13.0, *)
typealias DisposeBag = TrackingCollection<AnyCancellable>
final class TrackingCollection<Element> {
@@ -224,3 +224,34 @@ extension TrackingCollection: RangeReplaceableCollection {
try storage.removeAll(where: shouldBeRemoved)
}
}
final class TrackingRangeExpression<RangeExpression: Swift.RangeExpression>
: Swift.RangeExpression
where RangeExpression.Bound == Int
{
enum Event: Equatable {
case contains(Int)
case relativeTo(Range<Int>?)
}
typealias Bound = Int
private let underlying: RangeExpression
private(set) var history: [Event] = []
init(_ underlying: RangeExpression) {
self.underlying = underlying
}
func relative<Elements: Collection>(
to collection: Elements
) -> Range<Int> where Elements.Index == Int {
history.append(.relativeTo(collection as? Range<Int>))
return underlying.relative(to: collection)
}
func contains(_ element: Int) -> Bool {
history.append(.contains(element))
return underlying.contains(element)
}
}
@@ -22,7 +22,7 @@ import OpenCombine
/// `TrackingSubscriber.Event.subscription(Subscription.empty)`
/// is considered equal to any other subscription no matter what the subscription object
/// actually is.
@available(macOS 10.15, *)
@available(macOS 10.15, iOS 13.0, *)
typealias TrackingSubscriber = TrackingSubscriberBase<Int, TestingError>
/// `TrackingSubscriber` records every event like "receiveSubscription",
@@ -36,22 +36,21 @@ typealias TrackingSubscriber = TrackingSubscriberBase<Int, TestingError>
/// `TrackingSubscriber.Event.subscription(Subscription.empty)`
/// is considered equal to any other subscription no matter what the subscription object
/// actually is.
@available(macOS 10.15, *)
final class TrackingSubscriberBase<Value: Equatable,
Failure: Error>
@available(macOS 10.15, iOS 13.0, *)
final class TrackingSubscriberBase<Value: Equatable, Failure: Error>
: Subscriber,
CustomStringConvertible
{
enum Event: Equatable, CustomStringConvertible {
case subscription(Subscription)
case subscription(StringSubscription)
case value(Value)
case completion(Subscribers.Completion<Failure>)
static func == (lhs: Event, rhs: Event) -> Bool {
switch (lhs, rhs) {
case (.subscription, .subscription):
return true
case let (.subscription(lhs), .subscription(rhs)):
return lhs == rhs
case let (.value(lhs), .value(rhs)):
return lhs == rhs
case let (.completion(lhs), .completion(rhs)):
@@ -70,14 +69,14 @@ final class TrackingSubscriberBase<Value: Equatable,
var description: String {
switch self {
case .subscription:
return "subscription"
case .subscription(let subscription):
return ".subscription(\"\(subscription)\")"
case .value(let value):
return "value(\(value))"
return ".value(\(value))"
case .completion(.finished):
return "finished"
return ".completion(.finished)"
case .completion(.failure(let error)):
return "failure(\(error))"
return ".completion(.failure(\(error)))"
}
}
}
@@ -92,7 +91,8 @@ final class TrackingSubscriberBase<Value: Equatable,
/// A lazy view on `history` with all events except subscriptions filtered out
var subscriptions: LazyMapSequence<
LazyFilterSequence<LazyMapSequence<[Event], Subscription?>>, Subscription
LazyFilterSequence<LazyMapSequence<[Event], StringSubscription?>>,
StringSubscription
> {
return history.lazy.compactMap {
if case .subscription(let s) = $0 {
@@ -143,7 +143,7 @@ final class TrackingSubscriberBase<Value: Equatable,
}
func receive(subscription: Subscription) {
history.append(.subscription(subscription))
history.append(.subscription(.init(subscription)))
_receiveSubscription?(subscription)
}
@@ -166,21 +166,25 @@ final class TrackingSubscriberBase<Value: Equatable,
}
}
@available(macOS 10.15, *)
final class TrackingSubject<Value: Equatable>: Subject {
@available(macOS 10.15, iOS 13.0, *)
typealias TrackingSubject<Output: Equatable> = TrackingSubjectBase<Output, TestingError>
typealias Failure = TestingError
typealias Output = Value
enum Event: Equatable {
case subscriber(CombineIdentifier)
case value(Value)
case completion(Subscribers.Completion<TestingError>)
@available(macOS 10.15, iOS 13.0, *)
final class TrackingSubjectBase<Output: Equatable, Failure: Error>
: Subject,
CustomStringConvertible
{
enum Event: Equatable, CustomStringConvertible {
case subscriber
case subscription(StringSubscription)
case value(Output)
case completion(Subscribers.Completion<Failure>)
static func == (lhs: Event, rhs: Event) -> Bool {
switch (lhs, rhs) {
case let (.subscriber(lhs), .subscriber(rhs)):
case (.subscriber, .subscriber):
return true
case let (.subscription(lhs), .subscription(rhs)):
return lhs == rhs
case let (.value(lhs), .value(rhs)):
return lhs == rhs
@@ -189,7 +193,7 @@ final class TrackingSubject<Value: Equatable>: Subject {
case (.finished, .finished):
return true
case let (.failure(lhs), .failure(rhs)):
return lhs == rhs
return (lhs as? TestingError) == (rhs as? TestingError)
default:
return false
}
@@ -197,21 +201,119 @@ final class TrackingSubject<Value: Equatable>: Subject {
return false
}
}
var description: String {
switch self {
case .subscriber:
return ".subscriber"
case .subscription(let description):
return ".subscription(\"\(description)\")"
case .value(let value):
return ".value(\(value))"
case .completion(.finished):
return ".completion(.finished)"
case .completion(.failure(let error)):
return ".completion(.failure(\(error))"
}
}
}
private let _passthrough = PassthroughSubject<Output, Failure>()
private(set) var history: [Event] = []
private let _receiveSubscriber: ((CustomCombineIdentifierConvertible) -> Void)?
private let _onDeinit: (() -> Void)?
func send(_ value: Value) {
history.append(.value(value))
init(receiveSubscriber: ((CustomCombineIdentifierConvertible) -> Void)? = nil,
onDeinit: (() -> Void)? = nil) {
_receiveSubscriber = receiveSubscriber
_onDeinit = onDeinit
}
func send(completion: Subscribers.Completion<TestingError>) {
deinit {
_onDeinit?()
}
func send(subscription: Subscription) {
history.append(.subscription(.subscription(subscription)))
_passthrough.send(subscription: subscription)
}
func send(_ value: Output) {
history.append(.value(value))
_passthrough.send(value)
}
func send(completion: Subscribers.Completion<Failure>) {
history.append(.completion(completion))
_passthrough.send(completion: completion)
}
func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
where Failure == SubscriberType.Failure, Output == SubscriberType.Input
{
history.append(.subscriber(subscriber.combineIdentifier))
_receiveSubscriber?(subscriber)
history.append(.subscriber)
_passthrough.subscribe(subscriber)
}
var description: String { return "TrackingSubject" }
}
@available(macOS 10.15, iOS 13.0, *)
enum StringSubscription: Subscription,
CustomStringConvertible,
ExpressibleByStringLiteral {
case string(String)
case subscription(Subscription)
init(_ subscription: Subscription) {
self = .subscription(subscription)
}
init(stringLiteral value: String) {
self = .string(value)
}
var description: String {
switch self {
case .string(let string):
return string
case .subscription(let subscription):
return String(describing: subscription)
}
}
func request(_ demand: Subscribers.Demand) {
underlying?.request(demand)
}
var combineIdentifier: CombineIdentifier {
switch self {
case .subscription(let subscription):
return subscription.combineIdentifier
case .string:
fatalError("String has no combineIdentifier")
}
}
func cancel() {
underlying?.cancel()
}
var underlying: Subscription? {
switch self {
case .string:
return nil
case .subscription(let underlying):
return underlying
}
}
}
@available(macOS 10.15, iOS 13.0, *)
extension StringSubscription: Equatable {
static func == (lhs: StringSubscription, rhs: StringSubscription) -> Bool {
return lhs.description == rhs.description
}
}
@@ -13,7 +13,7 @@ import Combine
import OpenCombine
#endif
@available(macOS 10.15, *)
@available(macOS 10.15, iOS 13.0, *)
final class ImmediateSchedulerTests: XCTestCase {
static let allTests = [
@@ -13,15 +13,18 @@ import Combine
import OpenCombine
#endif
@available(macOS 10.15, *)
@available(macOS 10.15, iOS 13.0, *)
final class PassthroughSubjectTests: XCTestCase {
static let allTests = [
("testRequestingDemand", testRequestingDemand),
("testCrashOnZeroInitialDemand", testCrashOnZeroInitialDemand),
("testSendFailureCompletion", testSendFailureCompletion),
("testMultipleSubscriptions", testMultipleSubscriptions),
("testMultipleCompletions", testMultipleCompletions),
("testValuesAfterCompletion", testValuesAfterCompletion),
("testSubscriptionAfterCompletion", testSubscriptionAfterCompletion),
("testSendSubscription", testSendSubscription),
("testLifecycle", testLifecycle),
("testSynchronization", testSynchronization),
]
@@ -31,8 +34,8 @@ final class PassthroughSubjectTests: XCTestCase {
// Reactive Streams Spec: Rules #1, #2, #9
func testRequestingDemand() {
let initialDemands: [Subscribers.Demand] = [
.max(0),
let initialDemands: [Subscribers.Demand?] = [
nil,
.max(1),
.max(2),
.max(10),
@@ -66,7 +69,7 @@ final class PassthroughSubjectTests: XCTestCase {
let subscriber = AnySubscriber<Int, TestingError>(
receiveSubscription: { subscription in
subscriptions.append(subscription)
subscription.request(initialDemand)
initialDemand.map(subscription.request)
},
receiveValue: { value in
defer { i += 1 }
@@ -104,6 +107,16 @@ final class PassthroughSubjectTests: XCTestCase {
XCTAssertEqual(numberOfInputsHistory, expectedNumberOfInputsHistory)
}
func testCrashOnZeroInitialDemand() {
assertCrashes {
let subscriber = TrackingSubscriber(
receiveSubscription: { $0.request(.none) }
)
Sut().subscribe(subscriber)
}
}
func testSendFailureCompletion() {
let cvs = Sut()
let subscriber = TrackingSubscriber(
@@ -114,11 +127,11 @@ final class PassthroughSubjectTests: XCTestCase {
cvs.subscribe(subscriber)
XCTAssertEqual(subscriber.history, [.subscription(Subscriptions.empty)])
XCTAssertEqual(subscriber.history, [.subscription("PassthroughSubject")])
cvs.send(completion: .failure(.oops))
XCTAssertEqual(subscriber.history, [.subscription(Subscriptions.empty),
XCTAssertEqual(subscriber.history, [.subscription("PassthroughSubject"),
.completion(.failure(.oops))])
}
@@ -267,6 +280,49 @@ final class PassthroughSubjectTests: XCTestCase {
XCTAssertEqual(completions.count, 1)
}
func testSubscriptionAfterCompletion() {
let passthrough = Sut()
passthrough.send(completion: .finished)
let subscriber = TrackingSubscriber()
passthrough.subscribe(subscriber)
XCTAssertEqual(subscriber.history, [.subscription("Empty"),
.completion(.finished)])
}
func testSendSubscription() {
let subscription1 = CustomSubscription()
let passthrough = Sut()
passthrough.send(subscription: subscription1)
XCTAssertEqual(subscription1.history, [])
let subscriber1 = TrackingSubscriber(receiveSubscription: { $0.request(.max(1)) })
passthrough.subscribe(subscriber1)
XCTAssertEqual(subscription1.history, [.requested(.unlimited)])
XCTAssertEqual(subscriber1.history, [.subscription("PassthroughSubject")])
let subscriber2 = TrackingSubscriber(receiveSubscription: { $0.request(.max(2)) })
passthrough.subscribe(subscriber2)
XCTAssertEqual(subscription1.history, [.requested(.unlimited)])
XCTAssertEqual(subscriber1.history, [.subscription("PassthroughSubject")])
XCTAssertEqual(subscriber2.history, [.subscription("PassthroughSubject")])
passthrough.send(subscription: subscription1)
XCTAssertEqual(subscription1.history, [.requested(.unlimited),
.requested(.unlimited)])
passthrough.send(0)
passthrough.send(0)
let subscription2 = CustomSubscription()
passthrough.send(subscription: subscription2)
XCTAssertEqual(subscription2.history, [.requested(.unlimited)])
}
func testLifecycle() throws {
var deinitCounter = 0
@@ -285,7 +341,7 @@ final class PassthroughSubjectTests: XCTestCase {
XCTAssertEqual(emptySubscriber.completions.count, 1)
}
XCTAssertEqual(deinitCounter, 0)
XCTAssertEqual(deinitCounter, 1)
do {
let passthrough = Sut()
@@ -297,7 +353,7 @@ final class PassthroughSubjectTests: XCTestCase {
XCTAssertEqual(emptySubscriber.completions.count, 0)
}
XCTAssertEqual(deinitCounter, 0)
XCTAssertEqual(deinitCounter, 1)
var subscription: Subscription?
@@ -316,9 +372,9 @@ final class PassthroughSubjectTests: XCTestCase {
XCTAssertNotNil(subscription)
}
XCTAssertEqual(deinitCounter, 0)
XCTAssertEqual(deinitCounter, 1)
try XCTUnwrap(subscription).cancel()
XCTAssertEqual(deinitCounter, 0)
XCTAssertEqual(deinitCounter, 2)
}
func testSynchronization() {
+108
View File
@@ -0,0 +1,108 @@
//
// PublisherTests.swift
//
//
// Created by Sergej Jaskiewicz on 08.07.2019.
//
import XCTest
#if OPENCOMBINE_COMPATIBILITY_TEST
import Combine
#else
import OpenCombine
#endif
@available(macOS 10.15, iOS 13.0, *)
final class PublisherTests: XCTestCase {
static let allTests = [
("testSubscribeSubscriber", testSubscribeSubscriber),
("testSubscribeSubject", testSubscribeSubject),
("testSubjectSubscriber", testSubjectSubscriber),
]
func testSubscribeSubscriber() {
final class TrivialPublisher: Publisher {
typealias Output = Int
typealias Failure = TestingError
private(set) var counter = 0
func receive<SomeSubscriber: Subscriber>(
subscriber: SomeSubscriber
) where Failure == SomeSubscriber.Failure, Output == SomeSubscriber.Input {
counter += 1
}
}
let publisher = TrivialPublisher()
let subscriber = TrackingSubscriber()
publisher.subscribe(subscriber)
XCTAssertEqual(publisher.counter, 1)
XCTAssert(subscriber.history.isEmpty)
}
func testSubscribeSubject() {
let subscription = CustomSubscription()
let publisher = CustomPublisher(subscription: subscription)
let subject = TrackingSubject<Int>()
let cancellable = publisher.subscribe(subject)
XCTAssertEqual(subscription.history, [])
XCTAssertEqual(subject.history, [.subscription("Subject")])
XCTAssertEqual(publisher.send(0), .none)
XCTAssertEqual(publisher.send(1), .none)
XCTAssertEqual(subscription.history, [])
XCTAssertEqual(subject.history, [.subscription("Subject"),
.value(0),
.value(1)])
cancellable.cancel()
XCTAssertEqual(publisher.send(2), .none)
XCTAssertEqual(subscription.history, [.cancelled])
XCTAssertEqual(subject.history, [.subscription("Subject"), .value(0), .value(1)])
}
func testSubjectSubscriber() throws {
let subscription = CustomSubscription()
let publisher = CustomPublisher(subscription: subscription)
var subjectDestroyed = false
do {
let subject = TrackingSubject<Int>(onDeinit: { subjectDestroyed = true })
let cancellable = publisher.subscribe(subject)
try withExtendedLifetime(cancellable) {
let subjectSubscription =
try XCTUnwrap(publisher.erasedSubscriber as? Subscription)
XCTAssertEqual(String(describing: subjectSubscription), "Subject")
subjectSubscription.request(.max(42))
XCTAssertEqual(subscription.history, [.requested(.max(42))])
subjectSubscription.cancel()
subjectSubscription.cancel()
XCTAssertEqual(subscription.history, [.requested(.max(42)), .cancelled])
subjectSubscription.request(.max(37))
XCTAssertEqual(subscription.history, [.requested(.max(42)), .cancelled])
}
}
XCTAssert(subjectDestroyed)
}
}
@@ -0,0 +1,150 @@
//
// CountTests.swift
//
//
// Created by Joseph Spadafora on 6/25/19.
//
import XCTest
#if OPENCOMBINE_COMPATIBILITY_TEST
import Combine
#else
import OpenCombine
#endif
@available(macOS 10.15, iOS 13.0, *)
final class CountTests: XCTestCase {
static let allTests = [
("testSendsCorrectCount", testSendsCorrectCount),
("testCountWaitsUntilFinishedToSend", testCountWaitsUntilFinishedToSend),
("testAddingSubscriberRequestsUnlimitedDemand",
testAddingSubscriberRequestsUnlimitedDemand),
("testReceivesSubscriptionBeforeRequestingUpstream",
testReceivesSubscriptionBeforeRequestingUpstream)
]
func testSendsCorrectCount() {
let subscription = CustomSubscription()
let publisher = CustomPublisher(subscription: subscription)
let countPublisher = publisher.count()
let tracking = TrackingSubscriber(
receiveSubscription: { $0.request(.max(42)) }
)
XCTAssertEqual(tracking.history, [])
countPublisher.subscribe(tracking)
XCTAssertEqual(tracking.history, [.subscription("Count")])
let sendAmount = Int.random(in: 1...1000)
for _ in 0..<sendAmount {
_ = publisher.send(3)
}
XCTAssertEqual(tracking.history, [.subscription("Count")])
publisher.send(completion: .finished)
XCTAssertEqual(tracking.history, [.subscription("Count"),
.value(sendAmount),
.completion(.finished)])
}
func testCountWaitsUntilFinishedToSend() {
let subscription = CustomSubscription()
let publisher = CustomPublisher(subscription: subscription)
let countPublisher = publisher.count()
let tracking = TrackingSubscriber(
receiveSubscription: { $0.request(.max(42)) }
)
countPublisher.subscribe(tracking)
_ = publisher.send(1)
XCTAssertEqual(tracking.history, [.subscription("Count")])
_ = publisher.send(2)
XCTAssertEqual(tracking.history, [.subscription("Count")])
_ = publisher.send(0)
XCTAssertEqual(tracking.history, [.subscription("Count")])
publisher.send(completion: .finished)
XCTAssertEqual(tracking.history, [.subscription("Count"),
.value(3),
.completion(.finished)])
}
func testDemand() {
let subscription = CustomSubscription()
let publisher = CustomPublisher(subscription: subscription)
let countPublisher = publisher.count()
var downstreamSubscription: Subscription?
let tracking = TrackingSubscriber(
receiveSubscription: {
$0.request(.max(42))
downstreamSubscription = $0
},
receiveValue: { _ in .max(4) }
)
countPublisher.subscribe(tracking)
XCTAssertNotNil(downstreamSubscription)
XCTAssertEqual(subscription.history, [.requested(.unlimited)])
XCTAssertEqual(publisher.send(0), .max(0))
XCTAssertEqual(subscription.history, [.requested(.unlimited)])
XCTAssertEqual(publisher.send(2), .max(0))
XCTAssertEqual(subscription.history, [.requested(.unlimited)])
downstreamSubscription?.request(.max(95))
downstreamSubscription?.request(.max(5))
XCTAssertEqual(subscription.history, [.requested(.unlimited)])
downstreamSubscription?.cancel()
downstreamSubscription?.cancel()
XCTAssertEqual(subscription.history, [.requested(.unlimited),
.cancelled])
downstreamSubscription?.request(.max(50))
XCTAssertEqual(subscription.history, [.requested(.unlimited),
.cancelled])
}
func testAddingSubscriberRequestsUnlimitedDemand() {
// When
let subscription = CustomSubscription()
let publisher = CustomPublisher(subscription: subscription)
let countPublisher = publisher.count()
let tracking = TrackingSubscriber()
// Given
XCTAssertEqual(subscription.history, [])
countPublisher.subscribe(tracking)
// Then
XCTAssertEqual(subscription.history, [.requested(.unlimited)])
}
func testReceivesSubscriptionBeforeRequestingUpstream() {
let upstreamRequest = "Requested upstream subscription"
let receiveDownstream = "Receive downstream"
var receiveOrder: [String] = []
let subscription = CustomSubscription(onRequest: { _ in
receiveOrder.append(upstreamRequest)
})
let publisher = CustomPublisher(subscription: subscription)
let countPublisher = publisher.count()
let tracking = TrackingSubscriber(receiveSubscription: { _ in
receiveOrder.append(receiveDownstream)
})
countPublisher.subscribe(tracking)
XCTAssertEqual(receiveOrder, [receiveDownstream, upstreamRequest])
}
}
@@ -13,11 +13,11 @@ import Combine
import OpenCombine
#endif
@available(macOS 10.15, *)
@available(macOS 10.15, iOS 13.0, *)
final class DecodeTests: XCTestCase {
static let allTests = [
("testDecodeWorks", testDecodeWorks),
("testDownstraemReceivesFailure", testDownstreamReceivesFailure),
("testDecodingSuccess", testDecodingSuccess),
("testDecodingFailure", testDecodingFailure),
("testDemand", testDemand)
]
@@ -30,11 +30,10 @@ final class DecodeTests: XCTestCase {
jsonDecoder = TestDecoder()
}
func testDecodeWorks() throws {
// Given
let data = 78 // Represents decodable data
func testDecodingSuccess() throws {
let data = 78
let subject = PassthroughSubject<Int, Error>()
let publisher = subject.decode(type: [String: String].self, decoder: jsonDecoder)
let publisher = subject.decode(type: [String : String].self, decoder: jsonDecoder)
let subscriber = TrackingSubscriberBase<[String: String], Error>(
receiveSubscription: { $0.request(.unlimited) }
)
@@ -45,53 +44,41 @@ final class DecodeTests: XCTestCase {
return nil
}
// When
publisher.subscribe(subscriber)
subject.send(data)
subject.send(completion: .finished)
// Then
XCTAssertEqual(subscriber.history, [.subscription(Subscriptions.empty),
.value(testValue)])
XCTAssertEqual(subscriber.history, [.subscription("Decode"),
.value(testValue),
.completion(.finished)])
}
func testDownstreamReceivesFailure() {
// Given
func testDecodingFailure() {
let failData = 95
let subject = PassthroughSubject<Int, Error>()
let publisher = subject.decode(type: [String: String].self, decoder: jsonDecoder)
let publisher = subject.decode(type: [String : String].self, decoder: jsonDecoder)
let subscriber = TrackingSubscriberBase<[String: String], Error>(
receiveSubscription: { $0.request(.unlimited) }
)
// When
publisher.subscribe(subscriber)
subject.send(failData)
// Then
XCTAssertEqual(subscriber.history, [.subscription(Subscriptions.empty),
XCTAssertEqual(subscriber.history, [.subscription("Decode"),
.completion(.failure(TestDecoder.error))])
}
func testDemand() {
// `CustomSubscription` tracks all the requests and cancellations
// in its `history` property
let subscription = CustomSubscription()
// `CustomPublisher` sends the subscription object it has been initialized with
// to whoever subscribed to the `CustomPublisher`.
let publisher = CustomPublisherBase<Int>(subscription: subscription)
let publisher = CustomPublisherBase<Int, TestingError>(subscription: subscription)
// `_Decode` helper will receive the `CustomSubscription `
let decode = publisher.decode(type: [String : String].self,
decoder: jsonDecoder)
// This is actually `_Decode`
var downstreamSubscription: Subscription?
// `TrackingSubscriber` records every event like "receiveSubscription",
// "receiveValue" and "receiveCompletion" into its `history` property,
// optionally executing the provided callbacks.
let tracking = TrackingSubscriberBase<[String: String], Error>(
let tracking = TrackingSubscriberBase<[String : String], Error>(
receiveSubscription: {
$0.request(.max(42))
downstreamSubscription = $0
@@ -100,8 +87,10 @@ final class DecodeTests: XCTestCase {
)
decode.subscribe(tracking)
XCTAssertNotNil(downstreamSubscription) // Removes unused variable warning
XCTAssertEqual(subscription.history, [.requested(.max(42))])
XCTAssertNotNil(downstreamSubscription)
XCTAssertEqual(publisher.send(10), .none)
XCTAssertEqual(subscription.history, [.requested(.max(42)), .cancelled])
}
}
@@ -0,0 +1,54 @@
//
// DeferredTests.swift
//
//
// Created by Joseph Spadafora on 7/7/19.
//
import XCTest
//import Combine
#if OPENCOMBINE_COMPATIBILITY_TEST
import Combine
#else
import OpenCombine
#endif
@available(macOS 10.15, iOS 13.0, *)
final class DeferredTests: XCTestCase {
static let allTests = [
("testDeferredCreatedAfterSubscription",
testDeferredCreatedAfterSubscription)
]
func testDeferredCreatedAfterSubscription() {
var deferredPublisherCreatedCount = 0
let subscription = CustomSubscription()
let publisher = CustomPublisher(subscription: subscription)
let deferred = Deferred { () -> CustomPublisher in
deferredPublisherCreatedCount += 1
return publisher
}
let tracking = TrackingSubscriber()
XCTAssertEqual(deferredPublisherCreatedCount, 0)
XCTAssertEqual(tracking.history, [])
deferred.subscribe(tracking)
XCTAssertEqual(tracking.history, [.subscription("CustomSubscription")])
XCTAssertEqual(deferredPublisherCreatedCount, 1)
deferred.subscribe(tracking)
XCTAssertEqual(deferredPublisherCreatedCount, 2)
subscription.request(.unlimited)
XCTAssertEqual(tracking.history, [.subscription("CustomSubscription"),
.subscription("CustomSubscription")])
}
}
@@ -13,15 +13,14 @@ import Combine
import OpenCombine
#endif
// TODO: add tests from https://github.com/ReactiveX/RxJava/blob/83f2bd771ee172a2154e0fb30c5ffcaf8f71433c/src/test/java/io/reactivex/internal/operators/observable/ObservableSkipWhileTest.java
@available(macOS 10.15, *)
@available(macOS 10.15, iOS 13.0, *)
final class DropWhileTests: XCTestCase {
static let allTests = [
("testDropWhile", testDropWhile),
("testTryDropWhileFailureBecauseOfThrow", testTryDropWhileFailureBecauseOfThrow),
("testTryDropWhileFailureOnCompletion", testTryDropWhileFailureOnCompletion),
("testTryDropWhileSuccess", testTryDropWhileSuccess),
("testDemand", testDemand),
("testTryDropWhileCancelsUpstreamOnThrow",
testTryDropWhileCancelsUpstreamOnThrow),
@@ -48,7 +47,7 @@ final class DropWhileTests: XCTestCase {
publisher.send(completion: .finished)
publisher.send(10)
XCTAssertEqual(tracking.history, [.subscription(Subscriptions.empty),
XCTAssertEqual(tracking.history, [.subscription("DropWhile"),
.value(7),
.value(8),
.value(9),
@@ -82,7 +81,7 @@ final class DropWhileTests: XCTestCase {
publisher.send(completion: .finished)
XCTAssertEqual(tracking.history,
[.subscription(Subscriptions.empty),
[.subscription("TryDropWhile"),
.completion(.failure("too much" as TestingError))])
XCTAssertEqual(counter, 3)
@@ -101,10 +100,36 @@ final class DropWhileTests: XCTestCase {
publisher.send(2)
XCTAssertEqual(tracking.history,
[.subscription(Subscriptions.empty),
[.subscription("TryDropWhile"),
.completion(.failure(TestingError.oops))])
}
func testTryDropWhileSuccess() {
let publisher = PassthroughSubject<Int, Error>()
let drop = publisher.tryDrop { $0.isMultiple(of: 2) }
let tracking = TrackingSubscriberBase<Int, Error>(
receiveSubscription: { $0.request(.max(2)) }
)
publisher.send(1)
drop.subscribe(tracking)
publisher.send(0)
publisher.send(2)
publisher.send(3)
publisher.send(4)
publisher.send(5)
publisher.send(completion: .finished)
publisher.send(8)
XCTAssertEqual(tracking.history,
[.subscription("TryDropWhile"),
.value(3),
.value(4),
.completion(.finished)])
}
func testDemand() {
let subscription = CustomSubscription()
@@ -122,40 +147,54 @@ final class DropWhileTests: XCTestCase {
drop.subscribe(tracking)
XCTAssertNotNil(downstreamSubscription)
dump(type(of: downstreamSubscription!))
XCTAssertEqual(subscription.history, [.requested(.max(1))])
XCTAssertEqual(subscription.history, [.requested(.max(42))])
XCTAssertEqual(publisher.send(0), .max(1))
XCTAssertEqual(subscription.history, [.requested(.max(1))])
XCTAssertEqual(subscription.history, [.requested(.max(42))])
XCTAssertEqual(publisher.send(2), .max(1))
XCTAssertEqual(subscription.history, [.requested(.max(1))])
XCTAssertEqual(subscription.history, [.requested(.max(42))])
downstreamSubscription?.request(.max(95))
downstreamSubscription?.request(.max(5))
XCTAssertEqual(subscription.history, [.requested(.max(1))])
XCTAssertEqual(subscription.history, [.requested(.max(42)),
.requested(.max(95)),
.requested(.max(5))])
XCTAssertEqual(publisher.send(3), .max(145)) // 145 = 42 + 95 + 5 + 3
XCTAssertEqual(subscription.history, [.requested(.max(1))])
XCTAssertEqual(publisher.send(3), .max(4))
XCTAssertEqual(subscription.history, [.requested(.max(42)),
.requested(.max(95)),
.requested(.max(5))])
downstreamSubscription?.request(.max(121))
XCTAssertEqual(subscription.history, [.requested(.max(1)), .requested(.max(121))])
XCTAssertEqual(subscription.history, [.requested(.max(42)),
.requested(.max(95)),
.requested(.max(5)),
.requested(.max(121))])
XCTAssertEqual(publisher.send(7), .max(4))
XCTAssertEqual(subscription.history, [.requested(.max(1)), .requested(.max(121))])
XCTAssertEqual(subscription.history, [.requested(.max(42)),
.requested(.max(95)),
.requested(.max(5)),
.requested(.max(121))])
downstreamSubscription?.cancel()
downstreamSubscription?.cancel()
XCTAssertEqual(subscription.history, [.requested(.max(1)),
XCTAssertEqual(subscription.history, [.requested(.max(42)),
.requested(.max(95)),
.requested(.max(5)),
.requested(.max(121)),
.cancelled])
downstreamSubscription?.request(.max(50))
XCTAssertEqual(subscription.history, [.requested(.max(1)),
XCTAssertEqual(subscription.history, [.requested(.max(42)),
.requested(.max(95)),
.requested(.max(5)),
.requested(.max(121)),
.cancelled])
XCTAssertEqual(publisher.send(8), .max(4))
XCTAssertEqual(publisher.send(8), .none)
}
func testTryDropWhileCancelsUpstreamOnThrow() {
@@ -169,15 +208,18 @@ final class DropWhileTests: XCTestCase {
)
drop.subscribe(tracking)
XCTAssertEqual(subscription.history, [.requested(.max(1))])
XCTAssertEqual(subscription.history, [.requested(.unlimited)])
XCTAssertEqual(publisher.send(100), .none)
XCTAssertEqual(subscription.history, [.requested(.max(1)), .cancelled])
XCTAssertEqual(subscription.history, [.requested(.unlimited), .cancelled])
publisher.send(completion: .finished)
XCTAssertEqual(subscription.history, [.requested(.max(1)), .cancelled])
XCTAssertEqual(subscription.history, [.requested(.unlimited), .cancelled])
XCTAssertEqual(tracking.history,
[.subscription(Subscriptions.empty),
.completion(.failure("too much" as TestingError)),
.completion(.finished)])
[.subscription("TryDropWhile"),
.completion(.failure("too much" as TestingError))])
XCTAssertEqual(publisher.send(12), .none)
XCTAssertEqual(tracking.history,
[.subscription("TryDropWhile"),
.completion(.failure("too much" as TestingError))])
}
func testDropWhileCompletion() {
@@ -190,12 +232,103 @@ final class DropWhileTests: XCTestCase {
)
drop.subscribe(tracking)
XCTAssertEqual(subscription.history, [.requested(.max(1))])
XCTAssertEqual(subscription.history, [.requested(.unlimited)])
publisher.send(completion: .finished)
publisher.send(completion: .finished)
XCTAssertEqual(subscription.history, [.requested(.max(1))])
XCTAssertEqual(tracking.history, [.subscription(Subscriptions.empty),
XCTAssertEqual(subscription.history, [.requested(.unlimited)])
XCTAssertEqual(tracking.history, [.subscription("DropWhile"),
.completion(.finished),
.completion(.finished)])
publisher.send(completion: .failure(.oops))
publisher.send(completion: .failure(.oops))
XCTAssertEqual(tracking.history, [.subscription("DropWhile"),
.completion(.finished),
.completion(.finished),
.completion(.failure(.oops)),
.completion(.failure(.oops))])
}
func testCancelAlreadyCancelled() throws {
// Given
let subscription = CustomSubscription()
let publisher = CustomPublisher(subscription: subscription)
let dropWhile = publisher.drop(while: { _ in true })
var downstreamSubscription: Subscription?
let tracking = TrackingSubscriber(receiveSubscription: {
$0.request(.unlimited)
downstreamSubscription = $0
})
dropWhile.subscribe(tracking)
try XCTUnwrap(downstreamSubscription).cancel()
downstreamSubscription?.request(.unlimited)
try XCTUnwrap(downstreamSubscription).cancel()
XCTAssertEqual(subscription.history, [.requested(.unlimited), .cancelled])
publisher.send(completion: .failure(.oops))
publisher.send(completion: .finished)
XCTAssertEqual(subscription.history, [.requested(.unlimited), .cancelled])
XCTAssertEqual(tracking.history, [.subscription("DropWhile"),
.completion(.failure(.oops)),
.completion(.finished)])
}
func testLifecycle() throws {
var deinitCounter = 0
let onDeinit = { deinitCounter += 1 }
do {
let passthrough = PassthroughSubject<Int, TestingError>()
let dropWhile = passthrough.drop(while: { _ in true })
let emptySubscriber = TrackingSubscriber(onDeinit: onDeinit)
XCTAssertTrue(emptySubscriber.history.isEmpty)
dropWhile.subscribe(emptySubscriber)
XCTAssertEqual(emptySubscriber.subscriptions.count, 1)
passthrough.send(31)
XCTAssertEqual(emptySubscriber.inputs.count, 0)
passthrough.send(completion: .failure("failure"))
XCTAssertEqual(emptySubscriber.completions.count, 1)
}
XCTAssertEqual(deinitCounter, 0)
do {
let passthrough = PassthroughSubject<Int, TestingError>()
let dropWhile = passthrough.drop(while: { _ in true })
let emptySubscriber = TrackingSubscriber(onDeinit: onDeinit)
XCTAssertTrue(emptySubscriber.history.isEmpty)
dropWhile.subscribe(emptySubscriber)
XCTAssertEqual(emptySubscriber.subscriptions.count, 1)
XCTAssertEqual(emptySubscriber.inputs.count, 0)
XCTAssertEqual(emptySubscriber.completions.count, 0)
}
XCTAssertEqual(deinitCounter, 0)
var subscription: Subscription?
do {
let passthrough = PassthroughSubject<Int, TestingError>()
let dropWhile = passthrough.drop(while: { _ in true })
let emptySubscriber = TrackingSubscriber(
receiveSubscription: { subscription = $0; $0.request(.unlimited) },
onDeinit: onDeinit
)
XCTAssertTrue(emptySubscriber.history.isEmpty)
dropWhile.subscribe(emptySubscriber)
XCTAssertEqual(emptySubscriber.subscriptions.count, 1)
passthrough.send(31)
XCTAssertEqual(emptySubscriber.inputs.count, 0)
XCTAssertEqual(emptySubscriber.completions.count, 0)
}
XCTAssertEqual(deinitCounter, 0)
try XCTUnwrap(subscription).cancel()
XCTAssertEqual(deinitCounter, 0)
}
}
@@ -13,44 +13,66 @@ import Combine
import OpenCombine
#endif
@available(macOS 10.15, *)
@available(macOS 10.15, iOS 13.0, *)
final class EmptyTests: XCTestCase {
static let allTests = [
("testEmpty", testEmpty),
("testImmediatelyCancel", testImmediatelyCancel),
("testEquatable", testEquatable),
]
func testEmpty() {
let completesImmediately = Publishers.Empty(completeImmediately: true,
outputType: Int.self,
failureType: TestingError.self)
let completesImmediately = Empty(completeImmediately: true,
outputType: Int.self,
failureType: TestingError.self)
let subscriber = TrackingSubscriber()
completesImmediately.subscribe(subscriber)
XCTAssertEqual(subscriber.history, [.subscription(Subscriptions.empty),
XCTAssertEqual(subscriber.history, [.subscription("Empty"),
.completion(.finished)])
let doesNotComplete = Publishers.Empty(completeImmediately: false,
outputType: Int.self,
failureType: TestingError.self)
let doesNotComplete = Empty(completeImmediately: false,
outputType: Int.self,
failureType: TestingError.self)
doesNotComplete.subscribe(subscriber)
XCTAssertEqual(subscriber.history, [.subscription(Subscriptions.empty),
XCTAssertEqual(subscriber.history, [.subscription("Empty"),
.completion(.finished),
.subscription(Subscriptions.empty)])
.subscription("Empty")])
}
func testImmediatelyCancel() {
let completesImmediately = Publishers.Empty(outputType: Int.self,
failureType: TestingError.self)
let completesImmediately = Empty(outputType: Int.self,
failureType: TestingError.self)
let subscriber = TrackingSubscriber(receiveSubscription: { $0.cancel() })
completesImmediately.subscribe(subscriber)
XCTAssertEqual(subscriber.history, [.subscription(Subscriptions.empty),
XCTAssertEqual(subscriber.history, [.subscription("Empty"),
.completion(.finished)])
}
func testEquatable() {
XCTAssertEqual(Empty(completeImmediately: true,
outputType: Int.self,
failureType: Error.self),
Empty(completeImmediately: true,
outputType: Int.self,
failureType: Error.self))
XCTAssertEqual(Empty(completeImmediately: false,
outputType: Int.self,
failureType: Error.self),
Empty(completeImmediately: false,
outputType: Int.self,
failureType: Error.self))
XCTAssertNotEqual(Empty(completeImmediately: true,
outputType: Int.self,
failureType: Error.self),
Empty(completeImmediately: false,
outputType: Int.self,
failureType: Error.self))
}
}
@@ -13,12 +13,14 @@ import Combine
import OpenCombine
#endif
@available(macOS 10.15, *)
@available(macOS 10.15, iOS 13.0, *)
final class EncodeTests: XCTestCase {
static let allTests = [
("testEncodeWorks", testEncodeWorks),
("testEncodingSuccess", testEncodingSuccess),
("testEncodingFailure", testEncodingFailure),
("testDemand", testDemand),
("testEncodeSuccessHistory", testEncodeSuccessHistory)
("testEncodeSuccessHistory", testEncodeSuccessHistory),
]
private var encoder = TestEncoder()
@@ -30,63 +32,68 @@ final class EncodeTests: XCTestCase {
decoder = TestDecoder()
}
func testEncodeWorks() throws {
// Given
let testValue = ["test": "TestDecodable"]
let subject = PassthroughSubject<[String: String], Error>()
func testEncodingSuccess() throws {
let testValue = ["test" : "TestDecodable"]
let subject = PassthroughSubject<[String : String], Error>()
let publisher = subject.encode(encoder: encoder)
let subscriber = TrackingSubscriberBase<Int, Error>(
receiveSubscription: { $0.request(.unlimited) }
)
// When
publisher.subscribe(subscriber)
subject.send(testValue)
// Then
XCTAssert(encoder.encoded.first?.value as? [String: String] == testValue)
XCTAssertEqual(encoder.encoded.first?.value as? [String : String], testValue)
}
func testEncodingFailure() throws {
let testValue = ["test" : "TestDecodable"]
let subject = PassthroughSubject<[String : String], Error>()
encoder.handleEncode = { _ in throw TestingError.oops }
let publisher = subject.encode(encoder: encoder)
let subscriber = TrackingSubscriberBase<Int, Error>(
receiveSubscription: { $0.request(.unlimited) }
)
publisher.subscribe(subscriber)
subject.send(testValue)
XCTAssertEqual(subscriber.history, [.subscription("Encode"),
.completion(.failure(TestingError.oops))])
}
func testEncodeSuccessHistory() throws {
// Given
let testValue = ["test": "TestDecodable"]
let subject = PassthroughSubject<[String: String], Error>()
let testValue = ["test" : "TestDecodable"]
let subject = PassthroughSubject<[String : String], Error>()
let publisher = subject.encode(encoder: encoder)
let subscriber = TrackingSubscriberBase<Int, Error>(
receiveSubscription: { $0.request(.unlimited) }
)
// When
publisher.subscribe(subscriber)
subject.send(testValue)
// Then
guard let testKey = encoder.encoded.first?.key, encoder.encoded.count == 1 else {
XCTFail("Could not get testing data from encoding")
return
}
XCTAssertEqual(subscriber.history, [.subscription(Subscriptions.empty),
XCTAssertEqual(subscriber.history, [.subscription("Encode"),
.value(testKey)])
}
func testDemand() {
// `CustomSubscription` tracks all the requests and cancellations
// in its `history` property
let subscription = CustomSubscription()
// `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)
// This is actually `_Decode`
var downstreamSubscription: Subscription?
// `TrackingSubscriber` records every event like "receiveSubscription",
// "receiveValue" and "receiveCompletion" into its `history` property,
// optionally executing the provided callbacks.
let tracking = TrackingSubscriberBase<Int, Error>(
receiveSubscription: {
$0.request(.max(37))
@@ -96,7 +103,9 @@ final class EncodeTests: XCTestCase {
)
encode.subscribe(tracking)
XCTAssertNotNil(downstreamSubscription) // Removes unused variable warning
XCTAssertNotNil(downstreamSubscription)
XCTAssertEqual(publisher.send(["test" : "TestDecodable"]), .max(2))
XCTAssertEqual(subscription.history, [.requested(.max(37))])
}
}
@@ -13,21 +13,21 @@ import Combine
import OpenCombine
#endif
@available(macOS 10.15, *)
@available(macOS 10.15, iOS 13.0, *)
final class FailTests: XCTestCase {
static let allTests = [
("testSubscription", testSubscription),
]
private typealias Sut = Publishers.Fail<Int, TestingError>
private typealias Sut = Fail<Int, TestingError>
func testSubscription() {
let just = Sut(error: .oops)
let tracking = TrackingSubscriber()
just.subscribe(tracking)
XCTAssertEqual(tracking.history, [.subscription(Subscriptions.empty),
XCTAssertEqual(tracking.history, [.subscription("Empty"),
.completion(.failure(.oops))])
}
}
@@ -13,18 +13,17 @@ import Combine
import OpenCombine
#endif
@available(macOS 10.15, *)
@available(macOS 10.15, iOS 13.0, *)
final class JustTests: XCTestCase {
static let allTests = [
("testJustNoInitialDemand", testJustNoInitialDemand),
("testCustomMirror", testCustomMirror),
("testJustWithInitialDemand", testJustWithInitialDemand),
("testLifecycle", testLifecycle),
("testCancelOnSubscription", testCancelOnSubscription),
("testMinOperatorSpecialization", testMinOperatorSpecialization),
("testTryMinOperatorSpecialization", testTryMinOperatorSpecialization),
("testMaxOperatorSpecialization", testMaxOperatorSpecialization),
("testTryMaxOperatorSpecialization", testTryMaxOperatorSpecialization),
("testContainsOperatorSpecialization", testContainsOperatorSpecialization),
("testTryContainsOperatorSpecialization", testTryContainsOperatorSpecialization),
("testRemoveDuplicatesOperatorSpecialization",
@@ -38,24 +37,16 @@ final class JustTests: XCTestCase {
("testCountOperatorSpecialization", testCountOperatorSpecialization),
("testDropFirstOperatorSpecialization", testDropFirstOperatorSpecialization),
("testDropWhileOperatorSpecialization", testDropWhileOperatorSpecialization),
("testTryDropWhileOperatorSpecialization",
testTryDropWhileOperatorSpecialization),
("testFirstOperatorSpecialization", testFirstOperatorSpecialization),
("testFirstWhereOperatorSpecializtion", testFirstWhereOperatorSpecializtion),
("testTryFirstWhereOperatorSpecializtion",
testTryFirstWhereOperatorSpecializtion),
("testLastOperatorSpecialization", testLastOperatorSpecialization),
("testLastWhereOperatorSpecializtion", testLastWhereOperatorSpecializtion),
("testTryLastWhereOperatorSpecializtion", testTryLastWhereOperatorSpecializtion),
("testIgnoreOutputOperatorSpecialization",
testIgnoreOutputOperatorSpecialization),
("testMapOperatorSpecialization", testMapOperatorSpecialization),
("testTryMapOperatorSpecialization", testTryMapOperatorSpecialization),
("testCompactMapOperatorSpecialization", testCompactMapOperatorSpecialization),
("testTryCompactMapOperatorSpecialization",
testTryCompactMapOperatorSpecialization),
("testFilterOperatorSpecialization", testFilterOperatorSpecialization),
("testTryFilterOperatorSpecialization", testTryFilterOperatorSpecialization),
("testMapErrorOperatorSpecialization", testMapErrorOperatorSpecialization),
("testReplaceErrorOperatorSpecialization",
testReplaceErrorOperatorSpecialization),
@@ -72,8 +63,6 @@ final class JustTests: XCTestCase {
testOutputInRangeOperatorSpecialization),
("testPrefixOperatorSpecialization", testPrefixOperatorSpecialization),
("testPrefixWhileOperatorSpecialization", testPrefixWhileOperatorSpecialization),
("testTryPrefixWhileOperatorSpecialization",
testTryPrefixWhileOperatorSpecialization),
("testSetFailureTypeOperatorSpecialization",
testSetFailureTypeOperatorSpecialization),
]
@@ -85,16 +74,34 @@ final class JustTests: XCTestCase {
let tracking = TrackingSubscriberBase<Int, Never>()
just.subscribe(tracking)
XCTAssertEqual(tracking.history, [.subscription(Subscriptions.empty)])
XCTAssertEqual(tracking.history, [.subscription("Just")])
tracking.subscriptions.first?.request(.max(100))
tracking.subscriptions.first?.request(.max(1))
XCTAssertEqual(tracking.history, [.subscription(Subscriptions.empty),
XCTAssertEqual(tracking.history, [.subscription("Just"),
.value(42),
.completion(.finished)])
}
func testCustomMirror() throws {
let just = Sut(42)
var downstreamSubscription: Subscription?
let tracking = TrackingSubscriberBase<Int, Never>(
receiveSubscription: { downstreamSubscription = $0 }
)
just.subscribe(tracking)
var reflected = ""
try dump(XCTUnwrap(downstreamSubscription), to: &reflected)
XCTAssertEqual(reflected, """
Just #0
- 42
""")
}
func testJustWithInitialDemand() {
let just = Sut(42)
let tracking =
@@ -103,7 +110,7 @@ final class JustTests: XCTestCase {
})
just.subscribe(tracking)
XCTAssertEqual(tracking.history, [.subscription(Subscriptions.empty),
XCTAssertEqual(tracking.history, [.subscription("Just"),
.value(42),
.completion(.finished)])
}
@@ -115,7 +122,7 @@ final class JustTests: XCTestCase {
)
just.subscribe(tracking)
XCTAssertEqual(tracking.history, [.subscription(Subscriptions.empty),
XCTAssertEqual(tracking.history, [.subscription("Just"),
.value(42),
.completion(.finished)])
}
@@ -145,22 +152,6 @@ final class JustTests: XCTestCase {
XCTAssertEqual(count, 0, "comparator should not be called for min(by:)")
}
func testTryMinOperatorSpecialization() {
var count = 0
let comparator1: (Int, Int) -> Bool = { count += 1; return $0 == $1 }
let comparator2: (Int, Int) -> Bool = { count += 1; return $0 != $1 }
let throwingComparator: (Int, Int) throws -> Bool = { _, _ in
count += 1
throw TestingError.oops
}
XCTAssertEqual(try Sut(1).tryMin(by: comparator1).result.get(), true)
XCTAssertEqual(try Sut(1).tryMin(by: comparator2).result.get(), false)
assertThrowsError(try Sut(1).tryMin(by: throwingComparator).result.get(), .oops)
XCTAssertEqual(count, 3)
}
func testMaxOperatorSpecialization() {
XCTAssertEqual(Sut(341).max(), Sut(341))
@@ -171,22 +162,6 @@ final class JustTests: XCTestCase {
XCTAssertEqual(count, 0, "comparator should not be called for max(by:)")
}
func testTryMaxOperatorSpecialization() {
var count = 0
let comparator1: (Int, Int) -> Bool = { count += 1; return $0 == $1 }
let comparator2: (Int, Int) -> Bool = { count += 1; return $0 != $1 }
let throwingComparator: (Int, Int) throws -> Bool = { _, _ in
count += 1
throw TestingError.oops
}
XCTAssertEqual(try Sut(1).tryMax(by: comparator1).result.get(), true)
XCTAssertEqual(try Sut(1).tryMax(by: comparator2).result.get(), false)
assertThrowsError(try Sut(1).tryMax(by: throwingComparator).result.get(), .oops)
XCTAssertEqual(count, 3)
}
func testContainsOperatorSpecialization() {
XCTAssertEqual(Sut(10).contains(12), Sut(false))
XCTAssertEqual(Sut(10).contains(10), Sut(true))
@@ -250,22 +225,16 @@ final class JustTests: XCTestCase {
}
func testDropFirstOperatorSpecialization() {
XCTAssertEqual(Sut<Int>(10000).dropFirst().result, .success(nil))
XCTAssertEqual(Sut<Int>(10000).dropFirst(100).result, .success(nil))
XCTAssertEqual(Sut<Int>(10000).dropFirst(0).result, .success(10000))
XCTAssertEqual(Sut<Int>(10000).dropFirst(), .init(nil))
XCTAssertEqual(Sut<Int>(10000).dropFirst(100), .init(nil))
XCTAssertEqual(Sut<Int>(10000).dropFirst(0), .init(10000))
}
func testDropWhileOperatorSpecialization() {
XCTAssertEqual(Sut<Int>(42).drop { $0 != 42 }.result, .success(42))
XCTAssertEqual(Sut<Int>(-13).drop { $0 != 42 }.result, .success(nil))
XCTAssertEqual(Sut<Int>(1).drop { $0 < 0 }.result, .success(1))
XCTAssertEqual(Sut<Int>(1).drop { $0 > 0 }.result, .success(nil))
}
func testTryDropWhileOperatorSpecialization() {
XCTAssertEqual(try Sut<Int>(42).tryDrop { $0 != 42 }.result.get(), 42)
XCTAssertNil(try Sut<Int>(-13).tryDrop { $0 != 42 }.result.get())
assertThrowsError(try Sut<Int>(-13).tryDrop(while: throwing).result.get(), .oops)
XCTAssertEqual(Sut<Int>(42).drop { $0 != 42 }, .init(42))
XCTAssertEqual(Sut<Int>(-13).drop { $0 != 42 }, .init(nil))
XCTAssertEqual(Sut<Int>(1).drop { $0 < 0 }, .init(1))
XCTAssertEqual(Sut<Int>(1).drop { $0 > 0 }, .init(nil))
}
func testFirstOperatorSpecialization() {
@@ -273,16 +242,10 @@ final class JustTests: XCTestCase {
}
func testFirstWhereOperatorSpecializtion() {
XCTAssertEqual(Sut<Int>(42).first { $0 != 42 }.result, .success(nil))
XCTAssertEqual(Sut<Int>(-13).first { $0 != 42 }.result, .success(-13))
XCTAssertEqual(Sut<Int>(1).first { $0 < 0 }.result, .success(nil))
XCTAssertEqual(Sut<Int>(1).first { $0 > 0 }.result, .success(1))
}
func testTryFirstWhereOperatorSpecializtion() {
XCTAssertEqual(try Sut<Int>(-13).tryFirst { $0 != 42 }.result.get(), -13)
XCTAssertNil(try Sut<Int>(42).tryFirst { $0 != 42 }.result.get())
assertThrowsError(try Sut<Int>(-13).tryFirst(where: throwing).result.get(), .oops)
XCTAssertEqual(Sut<Int>(42).first { $0 != 42 }, .init(nil))
XCTAssertEqual(Sut<Int>(-13).first { $0 != 42 }, .init(-13))
XCTAssertEqual(Sut<Int>(1).first { $0 < 0 }, .init(nil))
XCTAssertEqual(Sut<Int>(1).first { $0 > 0 }, .init(1))
}
func testLastOperatorSpecialization() {
@@ -290,16 +253,10 @@ final class JustTests: XCTestCase {
}
func testLastWhereOperatorSpecializtion() {
XCTAssertEqual(Sut<Int>(42).last { $0 != 42 }.result, .success(nil))
XCTAssertEqual(Sut<Int>(-13).last { $0 != 42 }.result, .success(-13))
XCTAssertEqual(Sut<Int>(1).last { $0 < 0 }.result, .success(nil))
XCTAssertEqual(Sut<Int>(1).last { $0 > 0 }.result, .success(1))
}
func testTryLastWhereOperatorSpecializtion() {
XCTAssertEqual(try Sut<Int>(-13).tryLast { $0 != 42 }.result.get(), -13)
XCTAssertNil(try Sut<Int>(42).tryLast { $0 != 42 }.result.get())
assertThrowsError(try Sut<Int>(-13).tryLast(where: throwing).result.get(), .oops)
XCTAssertEqual(Sut<Int>(42).last { $0 != 42 }, .init(nil))
XCTAssertEqual(Sut<Int>(-13).last { $0 != 42 }, .init(-13))
XCTAssertEqual(Sut<Int>(1).last { $0 < 0 }, .init(nil))
XCTAssertEqual(Sut<Int>(1).last { $0 > 0 }, .init(1))
}
func testIgnoreOutputOperatorSpecialization() {
@@ -318,30 +275,15 @@ final class JustTests: XCTestCase {
func testCompactMapOperatorSpecialization() {
let transform: (Int) -> String? = { $0 == 42 ? String($0) : nil }
XCTAssertEqual(Sut<Int>(42).compactMap(transform).result, .success("42"))
XCTAssertEqual(Sut<Int>(100).compactMap(transform).result, .success(nil))
}
func testTryCompactMapOperatorSpecialization() {
let transform: (Int) -> String? = { $0 == 42 ? String($0) : nil }
XCTAssertEqual(try Sut<Int>(42).tryCompactMap(transform).result.get(), "42")
XCTAssertNil(try Sut<Int>(100).tryCompactMap(transform).result.get())
assertThrowsError(try Sut<Int>(42).tryMap(throwing).result.get() as Int?, "oops")
XCTAssertEqual(Sut<Int>(42).compactMap(transform), .init("42"))
XCTAssertEqual(Sut<Int>(100).compactMap(transform), .init(nil))
}
func testFilterOperatorSpecialization() {
XCTAssertEqual(Sut<Int>(42).filter { $0 != 42 }.result, .success(nil))
XCTAssertEqual(Sut<Int>(-13).filter { $0 != 42 }.result, .success(-13))
XCTAssertEqual(Sut<Int>(1).filter { $0 < 0 }.result, .success(nil))
XCTAssertEqual(Sut<Int>(1).filter { $0 > 0 }.result, .success(1))
}
func testTryFilterOperatorSpecialization() {
XCTAssertEqual(try Sut<Int>(-13).tryFilter { $0 != 42 }.result.get(), -13)
XCTAssertNil(try Sut<Int>(42).tryFilter { $0 != 42 }.result.get())
assertThrowsError(try Sut<Int>(-13).tryFilter(throwing).result.get() as Int?,
.oops)
XCTAssertEqual(Sut<Int>(42).filter { $0 != 42 }, .init(nil))
XCTAssertEqual(Sut<Int>(-13).filter { $0 != 42 }, .init(-13))
XCTAssertEqual(Sut<Int>(1).filter { $0 < 0 }, .init(nil))
XCTAssertEqual(Sut<Int>(1).filter { $0 > 0 }, .init(1))
}
func testMapErrorOperatorSpecialization() {
@@ -357,7 +299,6 @@ final class JustTests: XCTestCase {
}
func testRetryOperatorSpecialization() {
XCTAssertEqual(Sut(1).retry(), Sut(1))
XCTAssertEqual(Sut(1).retry(Int.max), Sut(1))
}
@@ -380,9 +321,9 @@ final class JustTests: XCTestCase {
}
func testOutputAtIndexOperatorSpecialization() {
XCTAssertEqual(Sut<Int>(12).output(at: 0).result, .success(12))
XCTAssertEqual(Sut<Int>(12).output(at: 1).result, .success(nil))
XCTAssertEqual(Sut<Int>(12).output(at: 42).result, .success(nil))
XCTAssertEqual(Sut<Int>(12).output(at: 0), .init(12))
XCTAssertEqual(Sut<Int>(12).output(at: 1), .init(nil))
XCTAssertEqual(Sut<Int>(12).output(at: 42), .init(nil))
}
func testOutputInRangeOperatorSpecialization() {
@@ -390,34 +331,27 @@ final class JustTests: XCTestCase {
// Empty range should result in a nil
// If this is fixed, this test will fail in compatibility mode so we will need
// to change our implementation to the correct one.
XCTAssertEqual(Sut<Int>(12).output(in: 0 ..< 10).result, .success(12))
XCTAssertEqual(Sut<Int>(12).output(in: -10 ..< 10).result, .success(nil))
XCTAssertEqual(Sut<Int>(12).output(in: (-10)...).result, .success(nil))
XCTAssertEqual(Sut<Int>(12).output(in: -10 ... 10).result, .success(nil))
XCTAssertEqual(Sut<Int>(12).output(in: -10 ..< -5).result, .success(nil))
XCTAssertEqual(Sut<Int>(12).output(in: 0 ..< 0).result, .success(12))
XCTAssertEqual(Sut<Int>(12).output(in: 0 ... 0).result, .success(12))
XCTAssertEqual(Sut<Int>(12).output(in: 1 ..< 10).result, .success(nil))
XCTAssertEqual(Sut<Int>(12).output(in: 0 ..< 10), .init(12))
XCTAssertEqual(Sut<Int>(12).output(in: -10 ..< 10), .init(nil))
XCTAssertEqual(Sut<Int>(12).output(in: (-10)...), .init(nil))
XCTAssertEqual(Sut<Int>(12).output(in: -10 ... 10), .init(nil))
XCTAssertEqual(Sut<Int>(12).output(in: -10 ..< -5), .init(nil))
XCTAssertEqual(Sut<Int>(12).output(in: 0 ..< 0), .init(12))
XCTAssertEqual(Sut<Int>(12).output(in: 0 ... 0), .init(12))
XCTAssertEqual(Sut<Int>(12).output(in: 1 ..< 10), .init(nil))
}
func testPrefixOperatorSpecialization() {
XCTAssertEqual(Sut<Int>(98).prefix(0).result, .success(nil))
XCTAssertEqual(Sut<Int>(98).prefix(1).result, .success(98))
XCTAssertEqual(Sut<Int>(98).prefix(1000).result, .success(98))
XCTAssertEqual(Sut<Int>(98).prefix(0), .init(nil))
XCTAssertEqual(Sut<Int>(98).prefix(1), .init(98))
XCTAssertEqual(Sut<Int>(98).prefix(1000), .init(98))
}
func testPrefixWhileOperatorSpecialization() {
XCTAssertEqual(Sut<Int>(42).prefix { $0 != 42 }.result, .success(nil))
XCTAssertEqual(Sut<Int>(-13).prefix { $0 != 42 }.result, .success(-13))
XCTAssertEqual(Sut<Int>(1).prefix { $0 < 0 }.result, .success(nil))
XCTAssertEqual(Sut<Int>(1).prefix { $0 > 0 }.result, .success(1))
}
func testTryPrefixWhileOperatorSpecialization() {
XCTAssertEqual(try Sut<Int>(-13).tryPrefix { $0 != 42 }.result.get(), -13)
XCTAssertNil(try Sut<Int>(42).tryPrefix { $0 != 42 }.result.get())
assertThrowsError(try Sut<Int>(-13).tryPrefix(while: throwing).result.get(),
.oops)
XCTAssertEqual(Sut<Int>(42).prefix { $0 != 42 }, .init(nil))
XCTAssertEqual(Sut<Int>(-13).prefix { $0 != 42 }, .init(-13))
XCTAssertEqual(Sut<Int>(1).prefix { $0 < 0 }, .init(nil))
XCTAssertEqual(Sut<Int>(1).prefix { $0 > 0 }, .init(1))
}
func testSetFailureTypeOperatorSpecialization() {
@@ -0,0 +1,262 @@
//
// MapErrorTests.swift
//
//
// Created by Joseph Spadafora on 7/4/19.
//
import XCTest
#if OPENCOMBINE_COMPATIBILITY_TEST
import Combine
#else
import OpenCombine
#endif
@available(macOS 10.15, iOS 13.0, *)
final class MapErrorTests: XCTestCase {
static let allTests = [
("testEmpty", testEmpty),
("testError", testError),
("testRange", testRange),
("testNoDemand", testNoDemand),
("testDemandSubscribe", testDemandSubscribe),
("testDemandSend", testDemandSend),
("testCompletion", testCompletion),
("testCancel", testCancel),
("testCancelAlreadyCancelled", testCancelAlreadyCancelled),
("testLifecycle", testLifecycle),
]
func testEmpty() {
// Given
let tracking = TrackingSubscriberBase<Int, OtherError>(
receiveSubscription: { $0.request(.unlimited) }
)
let publisher = TrackingSubject<Int>(
receiveSubscriber: {
XCTAssertEqual(String(describing: $0), "MapError")
}
)
// When
publisher.mapError(OtherError.init).subscribe(tracking)
// Then
XCTAssertEqual(tracking.history, [.subscription("PassthroughSubject")])
}
func testError() {
// Given
let expectedError = TestingError.oops
let tracking = TrackingSubscriberBase<Int, OtherError>(
receiveSubscription: { $0.request(.unlimited) }
)
let subscription = CustomSubscription()
let publisher = CustomPublisher(subscription: subscription)
// When
publisher.mapError(OtherError.init).subscribe(tracking)
publisher.send(completion: .failure(expectedError))
publisher.send(completion: .failure(expectedError))
// Then
XCTAssertEqual(tracking.history, [
.subscription("CustomSubscription"),
.completion(.failure(OtherError(expectedError))),
.completion(.failure(OtherError(expectedError)))
])
}
func testRange() {
// Given
let publisher = PassthroughSubject<Int, TestingError>()
let mapError = publisher.mapError(OtherError.init)
let tracking = TrackingSubscriberBase<Int, OtherError>(
receiveSubscription: { $0.request(.unlimited) }
)
// When
publisher.send(1)
mapError.subscribe(tracking)
publisher.send(2)
publisher.send(3)
publisher.send(completion: .finished)
publisher.send(5)
// Then
XCTAssertEqual(tracking.history, [
.subscription("PassthroughSubject"),
.value(2),
.value(3),
.completion(.finished)
])
}
func testNoDemand() {
// Given
let subscription = CustomSubscription()
let publisher = CustomPublisher(subscription: subscription)
let mapError = publisher.mapError(OtherError.init)
let tracking = TrackingSubscriberBase<Int, OtherError>()
// When
mapError.subscribe(tracking)
// Then
XCTAssertTrue(subscription.history.isEmpty)
}
func testDemandSubscribe() {
// Given
let expectedSubscribeDemand = 42
let subscription = CustomSubscription()
let publisher = CustomPublisher(subscription: subscription)
let mapError = publisher.mapError(OtherError.init)
let tracking = TrackingSubscriberBase<Int, OtherError>(
receiveSubscription: { $0.request(.max(expectedSubscribeDemand)) }
)
// When
mapError.subscribe(tracking)
// Then
XCTAssertEqual(subscription.history, [.requested(.max(expectedSubscribeDemand))])
}
func testDemandSend() {
// Given
let expectedReceiveValueDemand = 4
let subscription = CustomSubscription()
let publisher = CustomPublisher(subscription: subscription)
let mapError = publisher.mapError(OtherError.init)
let tracking = TrackingSubscriberBase<Int, OtherError>(
receiveValue: { _ in .max(expectedReceiveValueDemand) }
)
// When
mapError.subscribe(tracking)
// Then
XCTAssertEqual(publisher.send(0), .max(expectedReceiveValueDemand))
}
func testCompletion() {
// Given
let subscription = CustomSubscription()
let publisher = CustomPublisher(subscription: subscription)
let mapError = publisher.mapError(OtherError.init)
let tracking = TrackingSubscriberBase<Int, OtherError>(
receiveSubscription: { $0.request(.unlimited) }
)
// When
mapError.subscribe(tracking)
publisher.send(completion: .finished)
// Then
XCTAssertEqual(subscription.history, [.requested(.unlimited)])
XCTAssertEqual(
tracking.history,
[.subscription("CustomSubscription"), .completion(.finished)]
)
}
func testCancel() throws {
// Given
let subscription = CustomSubscription()
let publisher = CustomPublisher(subscription: subscription)
let mapError = publisher.mapError(OtherError.init)
var downstreamSubscription: Subscription?
let tracking = TrackingSubscriberBase<Int, OtherError>(
receiveSubscription: {
$0.request(.unlimited)
downstreamSubscription = $0
}
)
// When
mapError.subscribe(tracking)
try XCTUnwrap(downstreamSubscription).cancel()
// Then
XCTAssertEqual(subscription.history, [.requested(.unlimited), .cancelled])
}
func testCancelAlreadyCancelled() throws {
// Given
let subscription = CustomSubscription()
let publisher = CustomPublisher(subscription: subscription)
let mapError = publisher.mapError(OtherError.init)
var downstreamSubscription: Subscription?
let tracking = TrackingSubscriberBase<Int, OtherError>(
receiveSubscription: {
$0.request(.unlimited)
downstreamSubscription = $0
}
)
// When
mapError.subscribe(tracking)
try XCTUnwrap(downstreamSubscription).cancel()
downstreamSubscription?.request(.unlimited)
try XCTUnwrap(downstreamSubscription).cancel()
// Then
XCTAssertEqual(subscription.history, [.requested(.unlimited),
.cancelled,
.requested(.unlimited),
.cancelled])
}
func testLifecycle() throws {
var deinitCounter = 0
let onDeinit = { deinitCounter += 1 }
do {
let passthrough = PassthroughSubject<Int, TestingError>()
let mapError = passthrough.mapError(OtherError.init)
let emptySubscriber = TrackingSubscriberBase<Int, OtherError>(
onDeinit: onDeinit
)
XCTAssertTrue(emptySubscriber.history.isEmpty)
mapError.subscribe(emptySubscriber)
XCTAssertEqual(emptySubscriber.subscriptions.count, 1)
passthrough.send(31)
XCTAssertEqual(emptySubscriber.inputs.count, 0)
passthrough.send(completion: .failure("failure"))
XCTAssertEqual(emptySubscriber.completions.count, 1)
}
XCTAssertEqual(deinitCounter, 1)
do {
let passthrough = PassthroughSubject<Int, TestingError>()
let mapError = passthrough.mapError(OtherError.init)
let emptySubscriber = TrackingSubscriberBase<Int, OtherError>(
onDeinit: onDeinit
)
XCTAssertTrue(emptySubscriber.history.isEmpty)
mapError.subscribe(emptySubscriber)
XCTAssertEqual(emptySubscriber.subscriptions.count, 1)
XCTAssertEqual(emptySubscriber.inputs.count, 0)
XCTAssertEqual(emptySubscriber.completions.count, 0)
}
XCTAssertEqual(deinitCounter, 1)
var subscription: Subscription?
do {
let passthrough = PassthroughSubject<Int, TestingError>()
let mapError = passthrough.mapError(OtherError.init)
let emptySubscriber = TrackingSubscriberBase<Int, OtherError>(
receiveSubscription: { subscription = $0; $0.request(.unlimited) },
onDeinit: onDeinit
)
XCTAssertTrue(emptySubscriber.history.isEmpty)
mapError.subscribe(emptySubscriber)
XCTAssertEqual(emptySubscriber.subscriptions.count, 1)
passthrough.send(31)
XCTAssertEqual(emptySubscriber.inputs.count, 1)
XCTAssertEqual(emptySubscriber.completions.count, 0)
XCTAssertNotNil(subscription)
}
XCTAssertEqual(deinitCounter, 1)
try XCTUnwrap(subscription).cancel()
XCTAssertEqual(deinitCounter, 2)
}
}
private struct OtherError: Error {
let original: Error
init(_ original: Error) {
self.original = original
}
}
@@ -13,19 +13,21 @@ import Combine
import OpenCombine
#endif
@available(macOS 10.15, *)
@available(macOS 10.15, iOS 13.0, *)
final class MapTests: XCTestCase {
static let allTests = [
("testEmpty", testEmpty),
("testError", testError),
("testTryMapFailureBecauseOfThrow", testTryMapFailureBecauseOfThrow),
("testTryMapFailureOnCompletion", testTryMapFailureOnCompletion),
("testTryMapSuccess", testTryMapSuccess),
("testRange", testRange),
("testNoDemand", testNoDemand),
("testDemandSubscribe", testDemandSubscribe),
("testDemandSend", testDemandSend),
("testCompletion", testCompletion),
("testCancel", testCancel),
("testMapCancel", testMapCancel),
("testTryMapCancel", testTryMapCancel),
("testCancelAlreadyCancelled", testCancelAlreadyCancelled),
("testLifecycle", testLifecycle),
("testMapOperatorSpecializationForMap", testMapOperatorSpecializationForMap),
@@ -39,28 +41,34 @@ final class MapTests: XCTestCase {
func testEmpty() {
// Given
let tracking = TrackingSubscriberBase<Int, Never>(
let tracking = TrackingSubscriberBase<String, TestingError>(
receiveSubscription: { $0.request(.unlimited) }
)
let publisher = PassthroughSubject<Int, Never>()
let publisher = TrackingSubject<Int>(
receiveSubscriber: {
XCTAssertEqual(String(describing: $0), "Map")
}
)
// When
publisher.map { $0 * 2 }.subscribe(tracking)
publisher.map(String.init).subscribe(tracking)
// Then
XCTAssertEqual(tracking.history, [.subscription(Subscriptions.empty)])
XCTAssertEqual(tracking.history, [.subscription("PassthroughSubject")])
}
func testError() {
// Given
let expectedError = TestingError.oops
let tracking = TrackingSubscriber(receiveSubscription: { $0.request(.unlimited) })
let publisher = PassthroughSubject<Int, TestingError>()
let publisher = CustomPublisher(subscription: CustomSubscription())
// When
publisher.map { $0 * 2 }.subscribe(tracking)
publisher.send(completion: .failure(expectedError))
publisher.send(completion: .failure(expectedError))
// Then
XCTAssertEqual(tracking.history, [
.subscription(Subscriptions.empty),
.completion(Subscribers.Completion<TestingError>.failure(expectedError))
.subscription("CustomSubscription"),
.completion(.failure(expectedError)),
.completion(.failure(expectedError))
])
}
@@ -88,7 +96,7 @@ final class MapTests: XCTestCase {
publisher.send(completion: .finished)
XCTAssertEqual(tracking.history,
[.subscription(Subscriptions.empty),
[.subscription("TryMap"),
.value(4),
.value(6),
.completion(.failure("too much" as TestingError))])
@@ -109,10 +117,26 @@ final class MapTests: XCTestCase {
publisher.send(2)
XCTAssertEqual(tracking.history,
[.subscription(Subscriptions.empty),
[.subscription("TryMap"),
.completion(.failure(TestingError.oops))])
}
func testTryMapSuccess() {
let publisher = PassthroughSubject<Int, Error>()
let map = publisher.tryMap { $0 * 2 }
let tracking = TrackingSubscriberBase<Int, Error>()
publisher.send(1)
map.subscribe(tracking)
publisher.send(completion: .finished)
publisher.send(2)
XCTAssertEqual(tracking.history,
[.subscription("TryMap"),
.completion(.finished)])
}
func testRange() {
// Given
let publisher = PassthroughSubject<Int, TestingError>()
@@ -127,7 +151,7 @@ final class MapTests: XCTestCase {
publisher.send(5)
// Then
XCTAssertEqual(tracking.history, [
.subscription(Subscriptions.empty),
.subscription("PassthroughSubject"),
.value(4),
.value(6),
.completion(.finished)
@@ -162,18 +186,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() {
@@ -189,11 +217,11 @@ final class MapTests: XCTestCase {
XCTAssertEqual(subscription.history, [.requested(.unlimited)])
XCTAssertEqual(
tracking.history,
[.subscription(Subscriptions.empty), .completion(.finished)]
[.subscription("CustomSubscription"), .completion(.finished)]
)
}
func testCancel() throws {
func testMapCancel() throws {
// Given
let subscription = CustomSubscription()
let publisher = CustomPublisher(subscription: subscription)
@@ -206,6 +234,27 @@ final class MapTests: XCTestCase {
// When
map.subscribe(tracking)
try XCTUnwrap(downstreamSubscription).cancel()
_ = publisher.send(1)
publisher.send(completion: .finished)
// Then
XCTAssertEqual(subscription.history, [.requested(.unlimited), .cancelled])
}
func testTryMapCancel() throws {
// Given
let subscription = CustomSubscription()
let publisher = CustomPublisher(subscription: subscription)
let map = publisher.tryMap { $0 * 2 }
var downstreamSubscription: Subscription?
let tracking = TrackingSubscriberBase<Int, Error>(receiveSubscription: {
$0.request(.unlimited)
downstreamSubscription = $0
})
// When
map.subscribe(tracking)
try XCTUnwrap(downstreamSubscription).cancel()
_ = publisher.send(1)
publisher.send(completion: .finished)
// Then
XCTAssertEqual(subscription.history, [.requested(.unlimited), .cancelled])
}
@@ -251,7 +300,7 @@ final class MapTests: XCTestCase {
XCTAssertEqual(emptySubscriber.completions.count, 1)
}
XCTAssertEqual(deinitCounter, 0)
XCTAssertEqual(deinitCounter, 1)
do {
let passthrough = PassthroughSubject<Int, TestingError>()
@@ -264,7 +313,7 @@ final class MapTests: XCTestCase {
XCTAssertEqual(emptySubscriber.completions.count, 0)
}
XCTAssertEqual(deinitCounter, 0)
XCTAssertEqual(deinitCounter, 1)
var subscription: Subscription?
@@ -284,9 +333,9 @@ final class MapTests: XCTestCase {
XCTAssertNotNil(subscription)
}
XCTAssertEqual(deinitCounter, 0)
XCTAssertEqual(deinitCounter, 1)
try XCTUnwrap(subscription).cancel()
XCTAssertEqual(deinitCounter, 0)
XCTAssertEqual(deinitCounter, 2)
}
func testMapOperatorSpecializationForMap() {
@@ -305,7 +354,7 @@ final class MapTests: XCTestCase {
publisher.send(5)
XCTAssert(map1.upstream === map2.upstream)
XCTAssertEqual(tracking.history, [.subscription(Subscriptions.empty),
XCTAssertEqual(tracking.history, [.subscription("PassthroughSubject"),
.value(5),
.value(7),
.value(11)])
@@ -330,14 +379,14 @@ final class MapTests: XCTestCase {
publisher.send(5)
XCTAssert(map1.upstream === tryMap2.upstream)
XCTAssertEqual(tracking.history, [.subscription(Subscriptions.empty),
XCTAssertEqual(tracking.history, [.subscription("TryMap"),
.value(5),
.value(7),
.value(11)])
publisher.send(6)
XCTAssertEqual(tracking.history, [.subscription(Subscriptions.empty),
XCTAssertEqual(tracking.history, [.subscription("TryMap"),
.value(5),
.value(7),
.value(11),
@@ -363,14 +412,14 @@ final class MapTests: XCTestCase {
publisher.send(5)
XCTAssert(tryMap1.upstream === tryMap2.upstream)
XCTAssertEqual(tracking.history, [.subscription(Subscriptions.empty),
XCTAssertEqual(tracking.history, [.subscription("TryMap"),
.value(5),
.value(7),
.value(11)])
publisher.send(6)
XCTAssertEqual(tracking.history, [.subscription(Subscriptions.empty),
XCTAssertEqual(tracking.history, [.subscription("TryMap"),
.value(5),
.value(7),
.value(11),
@@ -396,14 +445,14 @@ final class MapTests: XCTestCase {
publisher.send(5)
XCTAssert(tryMap1.upstream === tryMap2.upstream)
XCTAssertEqual(tracking.history, [.subscription(Subscriptions.empty),
XCTAssertEqual(tracking.history, [.subscription("TryMap"),
.value(5),
.value(7),
.value(11)])
publisher.send(6)
XCTAssertEqual(tracking.history, [.subscription(Subscriptions.empty),
XCTAssertEqual(tracking.history, [.subscription("TryMap"),
.value(5),
.value(7),
.value(11),
@@ -13,45 +13,48 @@ import Combine
import OpenCombine
#endif
@available(macOS 10.15, *)
@available(macOS 10.15, iOS 13.0, *)
final class MulticastTests: XCTestCase {
static let allTests = [
("testMulticast", testMulticast),
("testMulticastConnectTwice", testMulticastConnectTwice),
("testMulticastDisconnect", testMulticastDisconnect),
("testLateSubscriber", testLateSubscriber),
("testSubscribeAfterCompletion", testSubscribeAfterCompletion),
]
func testMulticast() throws {
let publisher = PassthroughSubject<Int, TestingError>()
let publisher = CustomPublisher(subscription: CustomSubscription())
let multicast = publisher.multicast(PassthroughSubject.init)
let tracking = TrackingSubscriber(receiveSubscription: { $0.request(.unlimited) })
multicast.subscribe(tracking)
publisher.send(0)
publisher.send(12)
XCTAssertEqual(publisher.send(0), .none)
XCTAssertEqual(publisher.send(12), .none)
XCTAssertEqual(tracking.history, [.subscription(Subscriptions.empty)])
XCTAssertEqual(tracking.history, [.subscription("Multicast")])
var connection = multicast.connect()
publisher.send(-1)
publisher.send(42)
XCTAssertEqual(publisher.send(-1), .none)
XCTAssertEqual(publisher.send(42), .none)
connection.cancel()
publisher.send(14)
XCTAssertEqual(publisher.send(14), .none)
connection = multicast.connect()
publisher.send(15)
XCTAssertEqual(publisher.send(15), .none)
publisher.send(completion: .finished)
publisher.send(completion: .finished)
connection.cancel()
XCTAssertEqual(tracking.history, [.subscription(Subscriptions.empty),
XCTAssertEqual(tracking.history, [.subscription("Multicast"),
.value(-1),
.value(42),
.value(15),
@@ -60,9 +63,12 @@ final class MulticastTests: XCTestCase {
func testMulticastConnectTwice() {
let publisher = PassthroughSubject<Int, TestingError>()
let multicast = publisher.multicast(PassthroughSubject.init)
let tracking = TrackingSubscriber(receiveSubscription: { $0.request(.unlimited) })
let publisher = TrackingSubject<Int>()
let multicastSubject = TrackingSubject<Int>()
let multicast = publisher.multicast(subject: multicastSubject)
let tracking = TrackingSubscriber(
receiveSubscription: { $0.request(.max(10)) }
)
multicast.subscribe(tracking)
@@ -74,7 +80,7 @@ final class MulticastTests: XCTestCase {
publisher.send(42)
publisher.send(completion: .finished)
XCTAssertEqual(tracking.history, [.subscription(Subscriptions.empty),
XCTAssertEqual(tracking.history, [.subscription("Multicast"),
.value(42),
.value(42),
.completion(.finished)])
@@ -104,9 +110,9 @@ final class MulticastTests: XCTestCase {
publisher.send(2)
publisher.send(completion: .finished)
XCTAssertEqual(tracking.history, [.subscription(Subscriptions.empty),
XCTAssertEqual(tracking.history, [.subscription("Multicast"),
.value(42),
.subscription(Subscriptions.empty),
.subscription("Multicast"),
.value(2),
.value(2),
.completion(.finished),
@@ -114,4 +120,169 @@ final class MulticastTests: XCTestCase {
connection.cancel()
}
func testLateSubscriber() {
let subscription = CustomSubscription()
let publisher = CustomPublisher(subscription: subscription)
let subject = TrackingSubject<Int>()
let multicast = publisher.multicast(subject: subject)
XCTAssert(subject.history.isEmpty)
let earlySubscriber = TrackingSubscriber(
receiveSubscription: {
$0.request(.max(3))
}
)
multicast.subscribe(earlySubscriber)
XCTAssertNil(publisher.subscriber)
XCTAssertEqual(earlySubscriber.history, [.subscription("Multicast")])
XCTAssertEqual(subject.history, [.subscriber])
let connection = multicast.connect()
XCTAssert(connection is AnyCancellable)
XCTAssertNotNil(publisher.subscriber)
XCTAssertEqual(subscription.history, [.requested(.unlimited)])
XCTAssertEqual(earlySubscriber.history, [.subscription("Multicast")])
XCTAssertEqual(subject.history, [.subscriber, .subscription("Subject")])
XCTAssertEqual(publisher.send(1), .none)
XCTAssertEqual(publisher.send(2), .none)
XCTAssertEqual(publisher.send(3), .none)
XCTAssertEqual(publisher.send(4), .none)
XCTAssertEqual(subscription.history, [.requested(.unlimited)])
XCTAssertEqual(earlySubscriber.history, [.subscription("Multicast"),
.value(1),
.value(2),
.value(3)])
XCTAssertEqual(subject.history, [.subscriber,
.subscription("Subject"),
.value(1),
.value(2),
.value(3),
.value(4)])
let lateSubscriber = TrackingSubscriber(
receiveSubscription: {
$0.request(.max(2))
}
)
multicast.subscribe(lateSubscriber)
XCTAssertEqual(publisher.send(5), .none)
XCTAssertEqual(publisher.send(6), .none)
XCTAssertEqual(publisher.send(7), .none)
XCTAssertEqual(subscription.history, [.requested(.unlimited)])
XCTAssertEqual(earlySubscriber.history, [.subscription("Multicast"),
.value(1),
.value(2),
.value(3)])
XCTAssertEqual(lateSubscriber.history, [.subscription("Multicast"),
.value(5),
.value(6)])
XCTAssertEqual(subject.history, [.subscriber,
.subscription("Subject"),
.value(1),
.value(2),
.value(3),
.value(4),
.subscriber,
.value(5),
.value(6),
.value(7)])
publisher.send(completion: .finished)
let latestSubscriber = TrackingSubscriber(
receiveSubscription: {
$0.request(.none)
}
)
multicast.subscribe(latestSubscriber)
XCTAssertEqual(subscription.history, [.requested(.unlimited)])
XCTAssertEqual(earlySubscriber.history, [.subscription("Multicast"),
.value(1),
.value(2),
.value(3),
.completion(.finished)])
XCTAssertEqual(lateSubscriber.history, [.subscription("Multicast"),
.value(5),
.value(6),
.completion(.finished)])
XCTAssertEqual(latestSubscriber.history, [.subscription("Multicast"),
.completion(.finished)])
XCTAssertEqual(subject.history, [.subscriber,
.subscription("Subject"),
.value(1),
.value(2),
.value(3),
.value(4),
.subscriber,
.value(5),
.value(6),
.value(7),
.completion(.finished),
.subscriber])
}
func testSubscribeAfterCompletion() {
final class Subj: Subject {
typealias Output = Int
typealias Failure = TestingError
var subscriber: AnySubscriber<Int, TestingError>?
func receive<Downstream: Subscriber>(subscriber: Downstream)
where Downstream.Failure == TestingError, Downstream.Input == Int
{
subscriber.receive(subscription: CustomSubscription())
self.subscriber = AnySubscriber(subscriber)
}
func send(subscription: Subscription) {
subscriber?.receive(subscription: subscription)
}
func send(_ value: Int) {
_ = subscriber?.receive(value)
}
func send(completion: Subscribers.Completion<TestingError>) {
subscriber?.receive(completion: completion)
}
}
let publisher = CustomPublisher(subscription: CustomSubscription())
let subject = Subj()
let multicast = publisher.multicast(subject: subject)
_ = multicast.connect()
publisher.send(completion: .finished)
let lateSubscriber = TrackingSubscriber(
receiveSubscription: { $0.request(.unlimited) }
)
multicast.subscribe(lateSubscriber)
XCTAssertEqual(lateSubscriber.history, [.subscription("Multicast")])
}
}
@@ -0,0 +1,370 @@
//
// OptionalPublisherTests.swift
//
//
// Created by Sergej Jaskiewicz on 18.06.2019.
//
import XCTest
#if OPENCOMBINE_COMPATIBILITY_TEST
import Combine
#else
import OpenCombine
#endif
@available(macOS 10.15, iOS 13.0, *)
final class OptionalPublisherTests: XCTestCase {
static let allTests = [
("testSuccessNoInitialDemand", testSuccessNoInitialDemand),
("testSuccessWithInitialDemand", testSuccessWithInitialDemand),
("testSuccessCancelOnSubscription", testSuccessCancelOnSubscription),
("testNil", testNil),
("testLifecycle", testLifecycle),
("testMinOperatorSpecialization", testMinOperatorSpecialization),
("testMaxOperatorSpecialization", testMaxOperatorSpecialization),
("testContainsOperatorSpecialization", testContainsOperatorSpecialization),
("testRemoveDuplicatesOperatorSpecialization",
testRemoveDuplicatesOperatorSpecialization),
("testAllSatifyOperatorSpecialization", testAllSatifyOperatorSpecialization),
("testCollectOperatorSpecialization", testCollectOperatorSpecialization),
("testCountOperatorSpecialization", testCountOperatorSpecialization),
("testDropFirstOperatorSpecialization", testDropFirstOperatorSpecialization),
("testDropWhileOperatorSpecialization", testDropWhileOperatorSpecialization),
("testFirstOperatorSpecialization", testFirstOperatorSpecialization),
("testFirstWhereOperatorSpecializtion", testFirstWhereOperatorSpecializtion),
("testLastOperatorSpecialization", testLastOperatorSpecialization),
("testLastWhereOperatorSpecializtion", testLastWhereOperatorSpecializtion),
("testFilterOperatorSpecialization", testFilterOperatorSpecialization),
("testIgnoreOutputOperatorSpecialization",
testIgnoreOutputOperatorSpecialization),
("testMapOperatorSpecialization", testMapOperatorSpecialization),
("testCompactMapOperatorSpecialization", testCompactMapOperatorSpecialization),
("testReplaceErrorOperatorSpecialization",
testReplaceErrorOperatorSpecialization),
("testReplaceEmptyOperatorSpecialization",
testReplaceEmptyOperatorSpecialization),
("testRetryOperatorSpecialization", testRetryOperatorSpecialization),
("testReduceOperatorSpecialization", testReduceOperatorSpecialization),
("testScanOperatorSpecialization", testScanOperatorSpecialization),
("testOutputAtIndexOperatorSpecialization",
testOutputAtIndexOperatorSpecialization),
("testOutputInRangeOperatorSpecialization",
testOutputInRangeOperatorSpecialization),
("testPrefixOperatorSpecialization", testPrefixOperatorSpecialization),
("testPrefixWhileOperatorSpecialization", testPrefixWhileOperatorSpecialization),
]
#if OPENCOMBINE_COMPATIBILITY_TEST || !canImport(Combine)
private typealias Sut<Output> = Optional<Output>.Publisher
#else
private typealias Sut<Output> = Optional<Output>.OCombine.Publisher
#endif
func testSuccessNoInitialDemand() {
let success = Sut(42)
let tracking = TrackingSubscriberBase<Int, Never>()
success.subscribe(tracking)
XCTAssertEqual(tracking.history, [.subscription("Optional")])
tracking.subscriptions.first?.request(.max(100))
tracking.subscriptions.first?.request(.max(1))
XCTAssertEqual(tracking.history, [.subscription("Optional"),
.value(42),
.completion(.finished)])
}
func testSuccessWithInitialDemand() {
let just = Sut(42)
let tracking = TrackingSubscriberBase<Int, Never>(
receiveSubscription: { $0.request(.unlimited) }
)
just.subscribe(tracking)
XCTAssertEqual(tracking.history, [.subscription("Optional"),
.value(42),
.completion(.finished)])
}
func testSuccessCancelOnSubscription() {
let success = Sut(42)
let tracking = TrackingSubscriberBase<Int, Never>(
receiveSubscription: { $0.request(.max(1)); $0.cancel() }
)
success.subscribe(tracking)
XCTAssertEqual(tracking.history, [.subscription("Optional"),
.value(42),
.completion(.finished)])
}
func testNil() {
let success = Sut<Int>(nil)
let tracking = TrackingSubscriberBase<Int, Never>()
success.subscribe(tracking)
XCTAssertEqual(tracking.history, [.subscription("Empty"),
.completion(.finished)])
}
func testLifecycle() {
var deinitCount = 0
do {
let once = Sut(42)
let tracking = TrackingSubscriberBase<Int, Never>(
onDeinit: { deinitCount += 1 }
)
once.subscribe(tracking)
tracking.subscriptions.first?.cancel()
}
XCTAssertEqual(deinitCount, 1)
}
// MARK: - Operator specializations for Optional
func testMinOperatorSpecialization() {
XCTAssertEqual(Sut<Int>(112).min(), Sut(112))
XCTAssertEqual(Sut<Int>(nil).min(), Sut(nil))
var count = 0
let comparator: (Int, Int) -> Bool = { count += 1; return $0 > $1 }
XCTAssertEqual(Sut<Int>(1).min(by: comparator), Sut(1))
XCTAssertEqual(Sut<Int>(nil).min(by: comparator), Sut(nil))
XCTAssertEqual(count, 0, "comparator should not be called for min(by:)")
}
func testMaxOperatorSpecialization() {
XCTAssertEqual(Sut<Int>(341).max(), Sut(341))
XCTAssertEqual(Sut<Int>(nil).max(), Sut(nil))
var count = 0
let comparator: (Int, Int) -> Bool = { count += 1; return $0 > $1 }
XCTAssertEqual(Sut<Int>(2).max(by: comparator), Sut(2))
XCTAssertEqual(Sut<Int>(nil).max(by: comparator), Sut(nil))
XCTAssertEqual(count, 0, "comparator should not be called for max(by:)")
}
func testContainsOperatorSpecialization() {
XCTAssertEqual(Sut<Int>(10).contains(12), Sut(false))
XCTAssertEqual(Sut<Int>(10).contains(10), Sut(true))
XCTAssertEqual(Sut<Int>(nil).contains(10), Sut(nil))
var count = 0
let predicate: (Int) -> Bool = { count += 1; return $0 < 100 }
XCTAssertEqual(Sut<Int>(64).contains(where: predicate), Sut(true))
XCTAssertEqual(Sut<Int>(112).contains(where: predicate), Sut(false))
XCTAssertEqual(Sut<Int>(nil).contains(where: predicate), Sut(nil))
XCTAssertEqual(count, 2)
}
func testRemoveDuplicatesOperatorSpecialization() {
XCTAssertEqual(Sut<Int>(1000).removeDuplicates(), Sut(1000))
XCTAssertEqual(Sut<Int>(nil).removeDuplicates(), Sut(nil))
var count = 0
let comparator: (Int, Int) -> Bool = { count += 1; return $0 == $1 }
XCTAssertEqual(Sut<Int>(44).removeDuplicates(by: comparator), Sut(44))
XCTAssertEqual(Sut<Int>(nil).removeDuplicates(by: comparator), Sut(nil))
XCTAssertEqual(count,
0,
"comparator should not be called for removeDuplicates(by:)")
}
func testAllSatifyOperatorSpecialization() {
var count = 0
let predicate: (Int) -> Bool = { count += 1; return $0 > 0 }
XCTAssertEqual(Sut<Int>(0).allSatisfy(predicate), Sut(false))
XCTAssertEqual(Sut<Int>(1).allSatisfy(predicate), Sut(true))
XCTAssertEqual(Sut<Int>(nil).allSatisfy(predicate), Sut(nil))
XCTAssertEqual(count, 2)
}
func testCollectOperatorSpecialization() {
XCTAssertEqual(Sut<Int>(13).collect(), Sut([13]))
XCTAssertEqual(Sut<Int>(nil).collect(), Sut(nil))
}
func testCountOperatorSpecialization() {
XCTAssertEqual(Sut<Int>(10000).count(), Sut(1))
XCTAssertEqual(Sut<Int>(nil).count(), Sut(nil))
}
func testDropFirstOperatorSpecialization() {
XCTAssertEqual(Sut<Int>(10000).dropFirst(), Sut(nil))
XCTAssertEqual(Sut<Int>(10000).dropFirst(100), Sut(nil))
XCTAssertEqual(Sut<Int>(10000).dropFirst(0), Sut(10000))
XCTAssertEqual(Sut<Int>(nil).dropFirst(), Sut(nil))
}
func testDropWhileOperatorSpecialization() {
var count = 0
let predicate: (Int) -> Bool = { count += 1; return $0 != 42 }
XCTAssertEqual(Sut<Int>(42).drop(while: predicate), Sut(42))
XCTAssertEqual(Sut<Int>(-13).drop(while: predicate), Sut(nil))
XCTAssertEqual(Sut<Int>(nil).drop(while: predicate), Sut(nil))
XCTAssertEqual(count, 2)
}
func testFirstOperatorSpecialization() {
XCTAssertEqual(Sut<Int>(3).first(), Sut(3))
XCTAssertEqual(Sut<Int>(nil).first(), Sut(nil))
}
func testFirstWhereOperatorSpecializtion() {
var count = 0
let predicate: (Int) -> Bool = { count += 1; return $0 == 42 }
XCTAssertEqual(Sut<Int>(42).first(where: predicate), Sut(42))
XCTAssertEqual(Sut<Int>(-13).first(where: predicate), Sut(nil))
XCTAssertEqual(Sut<Int>(nil).first(where: predicate), Sut(nil))
XCTAssertEqual(count, 2)
}
func testLastOperatorSpecialization() {
XCTAssertEqual(Sut<Int>(4).last(), Sut(4))
XCTAssertEqual(Sut<Int>(nil).last(), Sut(nil))
}
func testLastWhereOperatorSpecializtion() {
var count = 0
let predicate: (Int) -> Bool = { count += 1; return $0 == 42 }
XCTAssertEqual(Sut<Int>(42).last(where: predicate), Sut(42))
XCTAssertEqual(Sut<Int>(-13).last(where: predicate), Sut(nil))
XCTAssertEqual(Sut<Int>(nil).last(where: predicate), Sut(nil))
XCTAssertEqual(count, 2)
}
func testFilterOperatorSpecialization() {
var count = 0
let predicate: (Int) -> Bool = { count += 1; return $0 == 42 }
XCTAssertEqual(Sut<Int>(42).filter(predicate), Sut(42))
XCTAssertEqual(Sut<Int>(-13).filter(predicate), Sut(nil))
XCTAssertEqual(Sut<Int>(nil).filter(predicate), Sut(nil))
XCTAssertEqual(count, 2)
}
func testIgnoreOutputOperatorSpecialization() {
XCTAssertTrue(Sut<Double>(13.0).ignoreOutput().completeImmediately)
}
func testMapOperatorSpecialization() {
var count = 0
let transform: (Int) -> String = { count += 1; return String($0) }
XCTAssertEqual(Sut<Int>(42).map(transform), Sut("42"))
XCTAssertEqual(Sut<Int>(nil).map(transform), Sut(nil))
XCTAssertEqual(count, 1)
}
func testCompactMapOperatorSpecialization() {
var count = 0
let transform: (Int) -> String? = {
count += 1
return $0 == 42 ? String($0) : nil
}
XCTAssertEqual(Sut<Int>(42).compactMap(transform), Sut("42"))
XCTAssertEqual(Sut<Int>(100).compactMap(transform), Sut(nil))
XCTAssertEqual(Sut<Int>(nil).compactMap(transform), Sut(nil))
XCTAssertEqual(count, 2)
}
func testReplaceErrorOperatorSpecialization() {
XCTAssertEqual(Sut<Int>(1).replaceError(with: 100), Sut(1))
XCTAssertEqual(Sut<Int>(nil).replaceError(with: 100), Sut(nil))
}
func testReplaceEmptyOperatorSpecialization() {
XCTAssertEqual(Sut<Int>(1).replaceEmpty(with: 100), Just(1))
XCTAssertEqual(Sut<Int>(nil).replaceEmpty(with: 100), Just(100))
}
func testRetryOperatorSpecialization() {
XCTAssertEqual(Sut<Int>(1).retry(100), Sut(1))
XCTAssertEqual(Sut<Int>(nil).retry(100), Sut(nil))
}
func testReduceOperatorSpecialization() {
var count = 0
let plus: (Int, Int) -> Int = { count += 1; return $0 + $1 }
XCTAssertEqual(Sut<Int>(4).reduce(2, plus), Sut(6))
XCTAssertEqual(Sut<Int>(nil).reduce(2, plus), Sut(nil))
XCTAssertEqual(count, 1)
}
func testScanOperatorSpecialization() {
var count = 0
let plus: (Int, Int) -> Int = { count += 1; return $0 + $1 }
XCTAssertEqual(Sut<Int>(4).scan(2, plus), Sut(6))
XCTAssertEqual(Sut<Int>(nil).scan(2, plus), Sut(nil))
XCTAssertEqual(count, 1)
}
func testOutputAtIndexOperatorSpecialization() {
XCTAssertEqual(Sut<Int>(12).output(at: 0), Sut(12))
XCTAssertEqual(Sut<Int>(nil).output(at: 0), Sut(nil))
XCTAssertEqual(Sut<Int>(12).output(at: 1), Sut(nil))
XCTAssertEqual(Sut<Int>(12).output(at: 42), Sut(nil))
}
func testOutputInRangeOperatorSpecialization() {
XCTAssertEqual(Sut<Int>(12).output(in: 0 ..< 10), Sut(12))
XCTAssertEqual(Sut<Int>(12).output(in: 0 ..< (.max - 2)), Sut(12))
XCTAssertEqual(Sut<Int>(nil).output(in: 0 ..< 10), Sut(nil))
XCTAssertEqual(Sut<Int>(12).output(in: 0 ..< 0), Sut(nil))
XCTAssertEqual(Sut<Int>(12).output(in: 0 ... 0), Sut(12))
XCTAssertEqual(Sut<Int>(12).output(in: 1 ..< 10), Sut(nil))
XCTAssertEqual(Sut<Int>(12).output(in: ...0), Sut(12))
XCTAssertEqual(Sut<Int>(12).output(in: ..<0), Sut(nil))
XCTAssertEqual(Sut<Int>(12).output(in: ..<1), Sut(12))
let trackingRange = TrackingRangeExpression(0 ..< 10)
_ = Sut<Int>(12).output(in: trackingRange)
XCTAssertEqual(trackingRange.history, [.relativeTo(0 ..< .max)])
}
func testPrefixOperatorSpecialization() {
XCTAssertEqual(Sut<Int>(98).prefix(0), Sut(nil))
XCTAssertEqual(Sut<Int>(98).prefix(1), Sut(98))
XCTAssertEqual(Sut<Int>(98).prefix(1000), Sut(98))
XCTAssertEqual(Sut<Int>(nil).prefix(0), Sut(nil))
XCTAssertEqual(Sut<Int>(nil).prefix(1), Sut(nil))
}
func testPrefixWhileOperatorSpecialization() {
var count = 0
let predicate: (Int) -> Bool = { count += 1; return $0.isMultiple(of: 2) }
XCTAssertEqual(Sut<Int>(98).prefix(while: predicate), Sut(98))
XCTAssertEqual(Sut<Int>(99).prefix(while: predicate), Sut(nil))
XCTAssertEqual(Sut<Int>(nil).prefix(while: predicate), Sut(nil))
XCTAssertEqual(count, 2)
}
}
@@ -1,814 +0,0 @@
//
// OptionalTests.swift
//
//
// Created by Sergej Jaskiewicz on 18.06.2019.
//
import XCTest
#if OPENCOMBINE_COMPATIBILITY_TEST
import Combine
#else
import OpenCombine
#endif
@available(macOS 10.15, *)
final class OptionalTests: XCTestCase {
static let allTests = [
("testSuccessNoInitialDemand", testSuccessNoInitialDemand),
("testSuccessWithInitialDemand", testSuccessWithInitialDemand),
("testSuccessCancelOnSubscription", testSuccessCancelOnSubscription),
("testFailure", testFailure),
("testFailureCancelOnSubscription", testFailureCancelOnSubscription),
("testNil", testNil),
("testLifecycle", testLifecycle),
("testMinOperatorSpecialization", testMinOperatorSpecialization),
("testTryMinOperatorSpecialization", testTryMinOperatorSpecialization),
("testMaxOperatorSpecialization", testMaxOperatorSpecialization),
("testTryMaxOperatorSpecialization", testTryMaxOperatorSpecialization),
("testContainsOperatorSpecialization", testContainsOperatorSpecialization),
("testTryContainsOperatorSpecialization", testTryContainsOperatorSpecialization),
("testRemoveDuplicatesOperatorSpecialization",
testRemoveDuplicatesOperatorSpecialization),
("testTryRemoveDuplicatesOperatorSpecialization",
testTryRemoveDuplicatesOperatorSpecialization),
("testAllSatifyOperatorSpecialization", testAllSatifyOperatorSpecialization),
("testTryAllSatifyOperatorSpecialization",
testTryAllSatifyOperatorSpecialization),
("testCollectOperatorSpecialization", testCollectOperatorSpecialization),
("testCountOperatorSpecialization", testCountOperatorSpecialization),
("testDropFirstOperatorSpecialization", testDropFirstOperatorSpecialization),
("testDropWhileOperatorSpecialization", testDropWhileOperatorSpecialization),
("testTryDropWhereOperatorSpecialization",
testTryDropWhileOperatorSpecialization),
("testFirstOperatorSpecialization", testFirstOperatorSpecialization),
("testFirstWhereOperatorSpecializtion", testFirstWhereOperatorSpecializtion),
("testTryFirstWhereOperatorSpecializtion",
testTryFirstWhereOperatorSpecializtion),
("testLastOperatorSpecialization", testLastOperatorSpecialization),
("testLastWhereOperatorSpecializtion", testLastWhereOperatorSpecializtion),
("testTryLastWhereOperatorSpecializtion", testTryLastWhereOperatorSpecializtion),
("testFilterOperatorSpecialization", testFilterOperatorSpecialization),
("testTryFilterOperatorSpecialization", testTryFilterOperatorSpecialization),
("testIgnoreOutputOperatorSpecialization",
testIgnoreOutputOperatorSpecialization),
("testMapOperatorSpecialization", testMapOperatorSpecialization),
("testTryMapOperatorSpecialization", testTryMapOperatorSpecialization),
("testCompactMapOperatorSpecialization", testCompactMapOperatorSpecialization),
("testTryCompactMapOperatorSpecialization",
testTryCompactMapOperatorSpecialization),
("testMapErrorOperatorSpecialization", testMapErrorOperatorSpecialization),
("testReplaceErrorOperatorSpecialization",
testReplaceErrorOperatorSpecialization),
("testReplaceEmptyOperatorSpecialization",
testReplaceEmptyOperatorSpecialization),
("testRetryOperatorSpecialization", testRetryOperatorSpecialization),
("testReduceOperatorSpecialization", testReduceOperatorSpecialization),
("testTryReduceOperatorSpecialization", testTryReduceOperatorSpecialization),
("testScanOperatorSpecialization", testScanOperatorSpecialization),
("testTryScanOperatorSpecialization", testTryScanOperatorSpecialization),
("testOutputAtIndexOperatorSpecialization",
testOutputAtIndexOperatorSpecialization),
("testOutputInRangeOperatorSpecialization",
testOutputInRangeOperatorSpecialization),
("testPrefixOperatorSpecialization", testPrefixOperatorSpecialization),
("testPrefixWhileOperatorSpecialization", testPrefixWhileOperatorSpecialization),
("testTryPrefixWhileOperatorSpecialization",
testTryPrefixWhileOperatorSpecialization),
("testSetFailureTypeOperatorSpecialization",
testSetFailureTypeOperatorSpecialization),
]
private typealias Sut<Output> = Publishers.Optional<Output, TestingError>
func testSuccessNoInitialDemand() {
let success = Sut(42)
let tracking = TrackingSubscriber()
success.subscribe(tracking)
XCTAssertEqual(tracking.history, [.subscription(Subscriptions.empty)])
tracking.subscriptions.first?.request(.max(100))
tracking.subscriptions.first?.request(.max(1))
XCTAssertEqual(tracking.history, [.subscription(Subscriptions.empty),
.value(42),
.completion(.finished)])
}
func testSuccessWithInitialDemand() {
let just = Sut(42)
let tracking = TrackingSubscriber(receiveSubscription: { $0.request(.unlimited) })
just.subscribe(tracking)
XCTAssertEqual(tracking.history, [.subscription(Subscriptions.empty),
.value(42),
.completion(.finished)])
}
func testSuccessCancelOnSubscription() {
let success = Sut(42)
let tracking = TrackingSubscriber(
receiveSubscription: { $0.request(.max(1)); $0.cancel() }
)
success.subscribe(tracking)
XCTAssertEqual(tracking.history, [.subscription(Subscriptions.empty),
.value(42),
.completion(.finished)])
}
func testFailure() {
let failure = Sut<Int>("failure")
let tracking = TrackingSubscriber()
failure.subscribe(tracking)
XCTAssertEqual(tracking.history, [.subscription(Subscriptions.empty),
.completion(.failure("failure"))])
}
func testFailureCancelOnSubscription() {
let failure = Sut<Int>("failure")
let tracking = TrackingSubscriber(
receiveSubscription: { $0.cancel() }
)
failure.subscribe(tracking)
XCTAssertEqual(tracking.history, [.subscription(Subscriptions.empty),
.completion(.failure("failure"))])
}
func testNil() {
let success = Sut<Int>(nil)
let tracking = TrackingSubscriber()
success.subscribe(tracking)
XCTAssertEqual(tracking.history, [.subscription(Subscriptions.empty),
.completion(.finished)])
}
func testLifecycle() {
var deinitCount = 0
do {
let once = Sut(42)
let tracking = TrackingSubscriber(onDeinit: { deinitCount += 1 })
once.subscribe(tracking)
tracking.subscriptions.first?.cancel()
}
XCTAssertEqual(deinitCount, 1)
}
// MARK: - Operator specializations for Optional
func testMinOperatorSpecialization() {
XCTAssertEqual(Sut<Int>(112).min().result, .success(112))
XCTAssertEqual(Sut<Int>(nil).min().result, .success(nil))
XCTAssertEqual(Sut<Int>("error").min().result, .failure("error"))
var count = 0
let comparator: (Int, Int) -> Bool = { count += 1; return $0 > $1 }
XCTAssertEqual(Sut<Int>(1).min(by: comparator).result, .success(1))
XCTAssertEqual(Sut<Int>(nil).min(by: comparator).result, .success(nil))
XCTAssertEqual(Sut<Int>("error").min(by: comparator).result, .failure("error"))
XCTAssertEqual(count, 0, "comparator should not be called for min(by:)")
}
func testTryMinOperatorSpecialization() {
var count = 0
let comparator: (Int, Int) -> Bool = { count += 1; return $0 > $1 }
let throwingComparator: (Int, Int) throws -> Bool = { _, _ in
count += 1
throw TestingError.oops
}
XCTAssertEqual(Sut<Int>(1).tryMin(by: comparator).result, .success(1))
XCTAssertEqual(Sut<Int>(nil).tryMin(by: comparator).result, .success(nil))
XCTAssertEqual(Sut<Int>(1).tryMin(by: throwingComparator).result, .success(1))
XCTAssertEqual(Sut<Int>(nil).tryMin(by: throwingComparator).result, .success(nil))
XCTAssertEqual(Sut<Int>("error").tryMin(by: throwingComparator).result,
.failure("error"))
XCTAssertEqual(count, 0, "comparator should not be called for tryMin(by:)")
}
func testMaxOperatorSpecialization() {
XCTAssertEqual(Sut<Int>(341).max().result, .success(341))
XCTAssertEqual(Sut<Int>(nil).max().result, .success(nil))
XCTAssertEqual(Sut<Int>("error").max().result, .failure("error"))
var count = 0
let comparator: (Int, Int) -> Bool = { count += 1; return $0 > $1 }
XCTAssertEqual(Sut<Int>(2).max(by: comparator).result, .success(2))
XCTAssertEqual(Sut<Int>(nil).max(by: comparator).result, .success(nil))
XCTAssertEqual(Sut<Int>("error").max(by: comparator).result, .failure("error"))
XCTAssertEqual(count, 0, "comparator should not be called for max(by:)")
}
func testTryMaxOperatorSpecialization() {
var count = 0
let comparator: (Int, Int) -> Bool = { count += 1; return $0 > $1 }
let throwingComparator: (Int, Int) throws -> Bool = { _, _ in
count += 1
throw TestingError.oops
}
XCTAssertEqual(Sut<Int>(2).tryMax(by: comparator).result, .success(2))
XCTAssertEqual(Sut<Int>(nil).tryMax(by: comparator).result, .success(nil))
XCTAssertEqual(Sut<Int>(2).tryMax(by: throwingComparator).result, .success(2))
XCTAssertEqual(Sut<Int>(nil).tryMax(by: throwingComparator).result, .success(nil))
XCTAssertEqual(Sut<Int>("error").tryMax(by: throwingComparator).result,
.failure("error"))
XCTAssertEqual(count, 0, "comparator should not be called for tryMax(by:)")
}
func testContainsOperatorSpecialization() {
XCTAssertEqual(Sut<Int>(10).contains(12).result, .success(false))
XCTAssertEqual(Sut<Int>(10).contains(10).result, .success(true))
XCTAssertEqual(Sut<Int>(nil).contains(10).result, .success(false))
XCTAssertEqual(Sut<Int>("error").contains(12).result, .failure("error"))
var count = 0
let predicate: (Int) -> Bool = { count += 1; return $0 < 100 }
XCTAssertEqual(Sut<Int>(64).contains(where: predicate).result, .success(true))
XCTAssertEqual(Sut<Int>(112).contains(where: predicate).result, .success(false))
XCTAssertEqual(Sut<Int>(nil).contains(where: predicate).result, .success(nil))
XCTAssertEqual(Sut<Int>("error").contains(where: predicate).result,
.failure("error"))
XCTAssertEqual(count, 2)
}
func testTryContainsOperatorSpecialization() {
var count = 0
let predicate: (Int) -> Bool = { count += 1; return $0 > 0 }
let throwingPredicate: (Int) throws -> Bool = { _ in
count += 1
throw TestingError.oops
}
XCTAssertEqual(try Sut<Int>(0).tryContains(where: predicate).result.get(), false)
XCTAssertEqual(try Sut<Int>(12).tryContains(where: predicate).result.get(), true)
XCTAssertEqual(try Sut<Int>(1).tryContains(where: predicate).result.get(), true)
XCTAssertEqual(try Sut<Int>(-1).tryContains(where: predicate).result.get(), false)
XCTAssertNil(try Sut<Int>(nil).tryContains(where: predicate).result.get())
XCTAssertNil(try Sut<Int>(nil).tryContains(where: predicate).result.get())
assertThrowsError(
try Sut<Int>(1).tryContains(where: throwingPredicate).result.get(),
.oops
)
XCTAssertNil(try Sut<Int>(nil).tryContains(where: throwingPredicate).result.get())
assertThrowsError(
try Sut<Int>("error").tryContains(where: throwingPredicate).result.get(),
"error"
)
XCTAssertEqual(count, 5)
}
func testRemoveDuplicatesOperatorSpecialization() {
XCTAssertEqual(Sut<Int>(1000).removeDuplicates().result, .success(1000))
XCTAssertEqual(Sut<Int>(nil).removeDuplicates().result, .success(nil))
XCTAssertEqual(Sut<Int>("error").removeDuplicates().result, .failure("error"))
var count = 0
let comparator: (Int, Int) -> Bool = { count += 1; return $0 == $1 }
XCTAssertEqual(Sut<Int>(44).removeDuplicates(by: comparator).result, .success(44))
XCTAssertEqual(Sut<Int>(nil).removeDuplicates(by: comparator).result,
.success(nil))
XCTAssertEqual(Sut<Int>("error").removeDuplicates(by: comparator).result,
.failure("error"))
XCTAssertEqual(count,
0,
"comparator should not be called for removeDuplicates(by:)")
}
func testTryRemoveDuplicatesOperatorSpecialization() {
var count = 0
let comparator: (Int, Int) -> Bool = { count += 1; return $0 > $1 }
let throwingComparator: (Int, Int) throws -> Bool = { _, _ in
count += 1
throw TestingError.oops
}
XCTAssertEqual(try Sut<Int>(44).tryRemoveDuplicates(by: comparator).result.get(),
44)
XCTAssertNil(try Sut<Int>(nil).tryRemoveDuplicates(by: comparator).result.get())
XCTAssertEqual(
try Sut<Int>(44).tryRemoveDuplicates(by: throwingComparator).result.get(),
44
)
XCTAssertNil(
try Sut<Int>(nil).tryRemoveDuplicates(by: throwingComparator).result.get()
)
assertThrowsError(
try Sut<Int>("error")
.tryRemoveDuplicates(by: throwingComparator).result.get(),
"error"
)
XCTAssertEqual(count,
0,
"comparator should not be called for tryRemoveDuplicates(by:)")
}
func testAllSatifyOperatorSpecialization() {
var count = 0
let predicate: (Int) -> Bool = { count += 1; return $0 > 0 }
XCTAssertEqual(Sut<Int>(0).allSatisfy(predicate).result, .success(false))
XCTAssertEqual(Sut<Int>(1).allSatisfy(predicate).result, .success(true))
XCTAssertEqual(Sut<Int>(nil).allSatisfy(predicate).result, .success(nil))
XCTAssertEqual(Sut<Int>("error").allSatisfy(predicate).result, .failure("error"))
XCTAssertEqual(count, 2)
}
func testTryAllSatifyOperatorSpecialization() {
var count = 0
let predicate: (Int) -> Bool = { count += 1; return $0 > 0 }
let throwingPredicate: (Int) throws -> Bool = { _ in
count += 1
throw TestingError.oops
}
XCTAssertEqual(try Sut<Int>(0).tryAllSatisfy(predicate).result.get(), false)
XCTAssertEqual(try Sut<Int>(1).tryAllSatisfy(predicate).result.get(), true)
XCTAssertNil(try Sut<Int>(nil).tryAllSatisfy(predicate).result.get())
assertThrowsError(try Sut<Int>(1).tryAllSatisfy(throwingPredicate).result.get(),
.oops)
XCTAssertNil(try Sut<Int>(nil).tryAllSatisfy(throwingPredicate).result.get())
assertThrowsError(
try Sut<Int>("error").tryAllSatisfy(throwingPredicate).result.get(),
"error"
)
XCTAssertEqual(count, 3)
}
func testCollectOperatorSpecialization() {
XCTAssertEqual(Sut<Int>(13).collect().result, .success([13]))
XCTAssertEqual(Sut<Int>(nil).collect().result, .success(nil))
XCTAssertEqual(Sut<Int>("error").collect().result, .failure("error"))
}
func testCountOperatorSpecialization() {
XCTAssertEqual(Sut<Int>(10000).count().result, .success(1))
XCTAssertEqual(Sut<Int>(nil).count().result, .success(1))
XCTAssertEqual(Sut<Int>("error").count().result, .failure("error"))
}
func testDropFirstOperatorSpecialization() {
XCTAssertEqual(Sut<Int>(10000).dropFirst().result, .success(nil))
XCTAssertEqual(Sut<Int>(10000).dropFirst(100).result, .success(nil))
XCTAssertEqual(Sut<Int>(10000).dropFirst(0).result, .success(10000))
XCTAssertEqual(Sut<Int>(nil).dropFirst().result, .success(nil))
XCTAssertEqual(Sut<Int>("error").dropFirst().result, .success(nil))
}
func testDropWhileOperatorSpecialization() {
var count = 0
let predicate: (Int) -> Bool = { count += 1; return $0 != 42 }
XCTAssertEqual(Sut<Int>(42).drop(while: predicate).result, .success(42))
XCTAssertEqual(Sut<Int>(-13).drop(while: predicate).result, .success(nil))
XCTAssertEqual(Sut<Int>(nil).drop(while: predicate).result, .success(nil))
XCTAssertEqual(Sut<Int>("error").drop(while: predicate).result, .failure("error"))
XCTAssertEqual(count, 2)
}
func testTryDropWhileOperatorSpecialization() {
var count = 0
let predicate: (Int) -> Bool = { count += 1; return $0 != 42 }
let throwingPredicate: (Int) throws -> Bool = { _ in
count += 1
throw TestingError.oops
}
XCTAssertEqual(try Sut<Int>(42).tryDrop(while: predicate).result.get(), 42)
XCTAssertNil(try Sut<Int>(-13).tryDrop(while: predicate).result.get())
XCTAssertNil(try Sut<Int>(nil).tryDrop(while: predicate).result.get())
assertThrowsError(try Sut<Int>("error").tryDrop(while: predicate).result.get(),
"error")
assertThrowsError(
try Sut<Int>(42).tryDrop(while: throwingPredicate).result.get(),
.oops
)
XCTAssertNil(try Sut<Int>(nil).tryDrop(while: throwingPredicate).result.get())
assertThrowsError(
try Sut<Int>("error").tryDrop(while: throwingPredicate).result.get(),
"error"
)
XCTAssertEqual(count, 3)
}
func testFirstOperatorSpecialization() {
XCTAssertEqual(Sut<Int>(3).first().result, .success(3))
XCTAssertEqual(Sut<Int>(nil).first().result, .success(nil))
XCTAssertEqual(Sut<Int>("error").first().result, .failure("error"))
}
func testFirstWhereOperatorSpecializtion() {
var count = 0
let predicate: (Int) -> Bool = { count += 1; return $0 == 42 }
XCTAssertEqual(Sut<Int>(42).first(where: predicate).result, .success(42))
XCTAssertEqual(Sut<Int>(-13).first(where: predicate).result, .success(nil))
XCTAssertEqual(Sut<Int>(nil).first(where: predicate).result, .success(nil))
XCTAssertEqual(Sut<Int>("error").first(where: predicate).result,
.failure("error"))
XCTAssertEqual(count, 2)
}
func testTryFirstWhereOperatorSpecializtion() {
var count = 0
let predicate: (Int) -> Bool = { count += 1; return $0 == 42 }
let throwingPredicate: (Int) throws -> Bool = { _ in
count += 1
throw TestingError.oops
}
XCTAssertEqual(try Sut<Int>(42).tryFirst(where: predicate).result.get(), 42)
XCTAssertNil(try Sut<Int>(-13).tryFirst(where: predicate).result.get())
XCTAssertNil(try Sut<Int>(nil).tryFirst(where: predicate).result.get())
assertThrowsError(try Sut<Int>("error").tryFirst(where: predicate).result.get(),
"error")
assertThrowsError(
try Sut<Int>(42).tryFirst(where: throwingPredicate).result.get(),
.oops
)
XCTAssertNil(try Sut<Int>(nil).tryFirst(where: throwingPredicate).result.get())
assertThrowsError(
try Sut<Int>("error").tryFirst(where: throwingPredicate).result.get(),
"error"
)
XCTAssertEqual(count, 3)
}
func testLastOperatorSpecialization() {
XCTAssertEqual(Sut<Int>(4).last().result, .success(4))
XCTAssertEqual(Sut<Int>(nil).last().result, .success(nil))
XCTAssertEqual(Sut<Int>("error").last().result, .failure("error"))
}
func testLastWhereOperatorSpecializtion() {
var count = 0
let predicate: (Int) -> Bool = { count += 1; return $0 == 42 }
XCTAssertEqual(Sut<Int>(42).last(where: predicate).result, .success(42))
XCTAssertEqual(Sut<Int>(-13).last(where: predicate).result, .success(nil))
XCTAssertEqual(Sut<Int>(nil).last(where: predicate).result, .success(nil))
XCTAssertEqual(Sut<Int>("error").last(where: predicate).result,
.failure("error"))
XCTAssertEqual(count, 2)
}
func testTryLastWhereOperatorSpecializtion() {
var count = 0
let predicate: (Int) -> Bool = { count += 1; return $0 == 42 }
let throwingPredicate: (Int) throws -> Bool = { _ in
count += 1
throw TestingError.oops
}
XCTAssertEqual(try Sut<Int>(42).tryLast(where: predicate).result.get(), 42)
XCTAssertNil(try Sut<Int>(-13).tryLast(where: predicate).result.get())
XCTAssertNil(try Sut<Int>(nil).tryLast(where: predicate).result.get())
assertThrowsError(try Sut<Int>("error").tryLast(where: predicate).result.get(),
"error")
assertThrowsError(
try Sut<Int>(42).tryLast(where: throwingPredicate).result.get(),
.oops
)
XCTAssertNil(try Sut<Int>(nil).tryLast(where: throwingPredicate).result.get())
assertThrowsError(
try Sut<Int>("error").tryLast(where: throwingPredicate).result.get(),
"error"
)
XCTAssertEqual(count, 3)
}
func testFilterOperatorSpecialization() {
var count = 0
let predicate: (Int) -> Bool = { count += 1; return $0 == 42 }
XCTAssertEqual(Sut<Int>(42).filter(predicate).result, .success(42))
XCTAssertEqual(Sut<Int>(-13).filter(predicate).result, .success(nil))
XCTAssertEqual(Sut<Int>(nil).filter(predicate).result, .success(nil))
XCTAssertEqual(Sut<Int>("error").filter(predicate).result,
.failure("error"))
XCTAssertEqual(count, 2)
}
func testTryFilterOperatorSpecialization() {
var count = 0
let predicate: (Int) -> Bool = { count += 1; return $0 == 42 }
let throwingPredicate: (Int) throws -> Bool = { _ in
count += 1
throw TestingError.oops
}
XCTAssertEqual(try Sut<Int>(42).tryFilter(predicate).result.get(), 42)
XCTAssertNil(try Sut<Int>(-13).tryFilter(predicate).result.get())
XCTAssertNil(try Sut<Int>(nil).tryFilter(predicate).result.get())
assertThrowsError(try Sut<Int>("error").tryFilter(predicate).result.get(),
"error")
assertThrowsError(
try Sut<Int>(42).tryFilter(throwingPredicate).result.get(),
.oops
)
XCTAssertNil(try Sut<Int>(nil).tryFilter(throwingPredicate).result.get())
assertThrowsError(
try Sut<Int>("error").tryFilter(throwingPredicate).result.get(),
"error"
)
XCTAssertEqual(count, 3)
}
func testIgnoreOutputOperatorSpecialization() {
XCTAssertTrue(Sut<Double>(13.0).ignoreOutput().completeImmediately)
XCTAssertTrue(Sut<Double>("error").ignoreOutput().completeImmediately)
do {
var completion: Subscribers.Completion<TestingError>?
_ = Sut<Double>("error").ignoreOutput()
.sink(receiveCompletion: { completion = $0 },
receiveValue: { _ in })
switch completion {
case .finished?:
break
default:
XCTFail("ignoreOutput should send 'finished' completion for Optional")
}
}
}
func testMapOperatorSpecialization() {
var count = 0
let transform: (Int) -> String = { count += 1; return String($0) }
XCTAssertEqual(Sut<Int>(42).map(transform).result, .success("42"))
XCTAssertEqual(Sut<Int>(nil).map(transform).result, .success(nil))
XCTAssertEqual(Sut<Int>("error").map(transform).result, .failure("error"))
XCTAssertEqual(count, 1)
}
func testTryMapOperatorSpecialization() {
var count = 0
let transform: (Int) -> String = { count += 1; return String($0) }
let throwingTrasnform: (Int) throws -> String = { _ in
count += 1
throw TestingError.oops
}
XCTAssertEqual(try Sut<Int>(42).tryMap(transform).result.get(), "42")
XCTAssertNil(try Sut<Int>(nil).tryMap(transform).result.get())
assertThrowsError(try Sut<Int>(42).tryMap(throwingTrasnform).result.get(), "oops")
XCTAssertNil(try Sut<Int>(nil).tryMap(throwingTrasnform).result.get())
assertThrowsError(try Sut<Int>("error").tryMap(throwingTrasnform).result.get(),
"error")
XCTAssertEqual(count, 2)
}
func testCompactMapOperatorSpecialization() {
var count = 0
let transform: (Int) -> String? = {
count += 1
return $0 == 42 ? String($0) : nil
}
XCTAssertEqual(Sut<Int>(42).compactMap(transform).result, .success("42"))
XCTAssertEqual(Sut<Int>(100).compactMap(transform).result, .success(nil))
XCTAssertEqual(Sut<Int>(nil).compactMap(transform).result, .success(nil))
XCTAssertEqual(Sut<Int>("error").map(transform).result, .failure("error"))
XCTAssertEqual(count, 2)
}
func testTryCompactMapOperatorSpecialization() {
var count = 0
let transform: (Int) -> String? = {
count += 1
return $0 == 42 ? String($0) : nil
}
let throwingTrasnform: (Int) throws -> String? = { _ in
count += 1
throw TestingError.oops
}
XCTAssertEqual(try Sut<Int>(42).tryCompactMap(transform).result.get(), "42")
XCTAssertNil(try Sut<Int>(100).tryCompactMap(transform).result.get())
XCTAssertNil(try Sut<Int>(nil).tryCompactMap(transform).result.get())
assertThrowsError(try Sut<Int>(42).tryMap(throwingTrasnform).result.get(), .oops)
XCTAssertNil(try Sut<Int>(nil).tryMap(throwingTrasnform).result.get())
assertThrowsError(try Sut<Int>("error").tryMap(throwingTrasnform).result.get(),
"error")
XCTAssertEqual(count, 3)
}
func testMapErrorOperatorSpecialization() {
XCTAssertEqual(Sut<Int>(42).mapError { _ in TestingError.oops }.result,
.success(42))
XCTAssertEqual(Sut<Int>(nil).mapError { _ in TestingError.oops }.result,
.success(nil))
XCTAssertEqual(
Sut<Int>("error")
.mapError { TestingError(description: $0.description.uppercased()) }
.result,
.failure("ERROR")
)
}
func testReplaceErrorOperatorSpecialization() {
XCTAssertEqual(Sut<Int>(1).replaceError(with: 100).result, .success(1))
XCTAssertEqual(Sut<Int>(nil).replaceError(with: 100).result, .success(nil))
XCTAssertEqual(Sut<Int>("error").replaceError(with: 100).result, .success(100))
}
func testReplaceEmptyOperatorSpecialization() {
XCTAssertEqual(Sut<Int>(1).replaceEmpty(with: 100).result, .success(1))
XCTAssertEqual(Sut<Int>(nil).replaceEmpty(with: 100).result, .success(nil))
XCTAssertEqual(Sut<Int>("error").replaceEmpty(with: 100).result,
.failure("error"))
}
func testRetryOperatorSpecialization() {
XCTAssertEqual(Sut<Int>(1).retry().result, .success(1))
XCTAssertEqual(Sut<Int>(nil).retry().result, .success(nil))
XCTAssertEqual(Sut<Int>("error").retry().result, .failure("error"))
XCTAssertEqual(Sut<Int>(1).retry(100).result, .success(1))
XCTAssertEqual(Sut<Int>(nil).retry(100).result, .success(nil))
XCTAssertEqual(Sut<Int>("error").retry(100).result, .failure("error"))
}
func testReduceOperatorSpecialization() {
var count = 0
let plus: (Int, Int) -> Int = { count += 1; return $0 + $1 }
XCTAssertEqual(Sut<Int>(4).reduce(2, plus).result, .success(6))
XCTAssertEqual(Sut<Int>(nil).reduce(2, plus).result, .success(nil))
XCTAssertEqual(Sut<Int>("error").reduce(2, plus).result, .failure("error"))
XCTAssertEqual(count, 1)
}
func testTryReduceOperatorSpecialization() {
var count = 0
let multiply: (Int, Int) -> Int = { count += 1; return $0 * $1 }
let throwing: (Int, Int) throws -> Int = { _, _ in
count += 1
throw TestingError.oops
}
XCTAssertEqual(try Sut<Int>(4).tryReduce(2, multiply).result.get(), 8)
XCTAssertNil(try Sut<Int>(nil).tryReduce(2, multiply).result.get())
assertThrowsError(try Sut<Int>(4).tryReduce(2, throwing).result.get(), "oops")
XCTAssertNil(try Sut<Int>(nil).tryReduce(2, throwing).result.get())
assertThrowsError(try Sut<Int>("error").tryReduce(2, throwing).result.get(),
"error")
XCTAssertEqual(count, 2)
}
func testScanOperatorSpecialization() {
var count = 0
let plus: (Int, Int) -> Int = { count += 1; return $0 + $1 }
XCTAssertEqual(Sut<Int>(4).scan(2, plus).result, .success(6))
XCTAssertEqual(Sut<Int>(nil).scan(2, plus).result, .success(nil))
XCTAssertEqual(Sut<Int>("error").scan(2, plus).result, .failure("error"))
XCTAssertEqual(count, 1)
}
func testTryScanOperatorSpecialization() {
var count = 0
let multiply: (Int, Int) -> Int = { count += 1; return $0 * $1 }
let throwing: (Int, Int) throws -> Int = { _, _ in
count += 1
throw TestingError.oops
}
XCTAssertEqual(try Sut<Int>(4).tryScan(2, multiply).result.get(), 8)
XCTAssertEqual(try Sut<Int>(nil).tryScan(2, multiply).result.get(), nil)
assertThrowsError(try Sut<Int>(4).tryScan(2, throwing).result.get(), "oops")
XCTAssertNil(try Sut<Int>(nil).tryScan(2, throwing).result.get())
assertThrowsError(try Sut<Int>("error").tryScan(2, throwing).result.get(),
"error")
XCTAssertEqual(count, 2)
}
func testOutputAtIndexOperatorSpecialization() {
XCTAssertEqual(Sut<Int>(12).output(at: 0).result, .success(12))
XCTAssertEqual(Sut<Int>(nil).output(at: 0).result, .success(nil))
XCTAssertEqual(Sut<Int>(12).output(at: 1).result, .success(nil))
XCTAssertEqual(Sut<Int>(12).output(at: 42).result, .success(nil))
XCTAssertEqual(Sut<Int>("error").output(at: 42).result, .failure("error"))
}
func testOutputInRangeOperatorSpecialization() {
// TODO: Broken in Apple's Combine? (FB6169621)
// Empty range should result in a nil
// If this is fixed, this test will fail in compatibility mode so we will need
// to change our implementation to the correct one.
XCTAssertEqual(Sut<Int>(12).output(in: 0 ..< 10).result, .success(12))
XCTAssertEqual(Sut<Int>(12).output(in: -10 ..< 10).result, .success(nil))
XCTAssertEqual(Sut<Int>(12).output(in: (-10)...).result, .success(nil))
XCTAssertEqual(Sut<Int>(12).output(in: -10 ... 10).result, .success(nil))
XCTAssertEqual(Sut<Int>(12).output(in: -10 ..< -5).result, .success(nil))
XCTAssertEqual(Sut<Int>(nil).output(in: 0 ..< 10).result, .success(nil))
XCTAssertEqual(Sut<Int>(12).output(in: 0 ..< 0).result, .success(12))
XCTAssertEqual(Sut<Int>(12).output(in: 0 ... 0).result, .success(12))
XCTAssertEqual(Sut<Int>(12).output(in: 1 ..< 10).result, .success(nil))
XCTAssertEqual(Sut<Int>("error").output(in: 0 ..< 10).result, .failure("error"))
}
func testPrefixOperatorSpecialization() {
// TODO: Seems broken in Apple's Combine (FB6168300)
// If this is fixed, this test will fail in compatibility mode so we will need
// to change our implementation to the correct one.
XCTAssertEqual(Sut<Int>(98).prefix(0).result, .success(98))
XCTAssertEqual(Sut<Int>(98).prefix(1).result, .success(nil))
XCTAssertEqual(Sut<Int>(98).prefix(1000).result, .success(nil))
XCTAssertEqual(Sut<Int>(nil).prefix(0).result, .success(nil))
XCTAssertEqual(Sut<Int>(nil).prefix(1).result, .success(nil))
XCTAssertEqual(Sut<Int>("error").prefix(0).result, .failure("error"))
XCTAssertEqual(Sut<Int>("error").prefix(1).result, .failure("error"))
}
func testPrefixWhileOperatorSpecialization() {
var count = 0
let predicate: (Int) -> Bool = { count += 1; return $0.isMultiple(of: 2) }
XCTAssertEqual(Sut<Int>(98).prefix(while: predicate).result, .success(98))
XCTAssertEqual(Sut<Int>(99).prefix(while: predicate).result, .success(nil))
XCTAssertEqual(Sut<Int>(nil).prefix(while: predicate).result, .success(nil))
XCTAssertEqual(Sut<Int>("error").prefix(while: predicate).result,
.failure("error"))
XCTAssertEqual(count, 2)
}
func testTryPrefixWhileOperatorSpecialization() {
var count = 0
let predicate: (Int) -> Bool = { count += 1; return $0.isMultiple(of: 2) }
let throwingPredicate: (Int) throws -> Bool = { _ in
count += 1
throw TestingError.oops
}
XCTAssertEqual(try Sut<Int>(98).tryPrefix(while: predicate).result.get(), 98)
XCTAssertNil(try Sut<Int>(99).tryPrefix(while: predicate).result.get())
XCTAssertNil(try Sut<Int>(nil).tryPrefix(while: predicate).result.get())
assertThrowsError(
try Sut<Int>(98).tryPrefix(while: throwingPredicate).result.get(),
.oops
)
XCTAssertNil( try Sut<Int>(nil).tryPrefix(while: throwingPredicate).result.get())
assertThrowsError(
try Sut<Int>("error").tryPrefix(while: throwingPredicate).result.get(),
"error"
)
}
func testSetFailureTypeOperatorSpecialization() {
XCTAssertEqual(
Publishers.Optional<Int, Never>(73)
.setFailureType(to: TestingError.self)
.result,
.success(73)
)
}
}
@@ -13,7 +13,7 @@ import Combine
import OpenCombine
#endif
@available(macOS 10.15, *)
@available(macOS 10.15, iOS 13.0, *)
final class PrintTests: XCTestCase {
static let allTests = [
@@ -63,7 +63,7 @@ final class PrintTests: XCTestCase {
XCTAssertEqual(publisher.send(10), .unlimited)
downstreamSubscription?.cancel()
XCTAssertEqual(tracking.history, [.subscription(Subscriptions.empty),
XCTAssertEqual(tracking.history, [.subscription("Print"),
.value(1),
.value(2),
.completion(.finished),
@@ -75,7 +75,7 @@ final class PrintTests: XCTestCase {
.cancelled])
let expectedOutput = """
receive subscription: (OpenCombineTests.CustomSubscription)
receive subscription: (CustomSubscription)
callback subscription
request unlimited
callback request demand
@@ -150,7 +150,7 @@ final class PrintTests: XCTestCase {
XCTAssertEqual(publisher.send(10), .unlimited)
downstreamSubscription?.cancel()
XCTAssertEqual(tracking.history, [.subscription(Subscriptions.empty),
XCTAssertEqual(tracking.history, [.subscription("Print"),
.value(1),
.value(2),
.completion(.finished),
@@ -162,7 +162,7 @@ final class PrintTests: XCTestCase {
.cancelled])
let expectedOutput = """
👉: receive subscription: (OpenCombineTests.CustomSubscription)
👉: receive subscription: (CustomSubscription)
callback subscription
👉: request unlimited
callback request demand
@@ -199,7 +199,7 @@ final class PrintTests: XCTestCase {
func testSynchronization() {
let stream = StringStream()
let publisher = CustomPublisher(subscription: nil)
let publisher = CustomPublisherBase<Int, Never>(subscription: nil)
let printer = publisher.print(to: stream)
let counter = Atomic(0)
@@ -0,0 +1,94 @@
//
// ReplaceNilTests.swift
//
//
// Created by Joseph Spadafora on 7/4/19.
//
import XCTest
#if OPENCOMBINE_COMPATIBILITY_TEST
import Combine
#else
import OpenCombine
#endif
@available(macOS 10.15, iOS 13.0, *)
final class ReplaceNilTests: XCTestCase {
static let allTests = [
("testReplacesNilElement", testReplacesNilElement),
("testExistingElementIsPreserved", testExistingElementIsPreserved),
("testMultipleReplacements", testMultipleReplacements)
]
func testReplacesNilElement() {
// Given
let nilPublisher = PassthroughSubject<Int?, Never>()
let output = Int.random(in: 1...100)
let sut = nilPublisher.replaceNil(with: output)
let subscriber = TrackingSubscriberBase<Int, Never>(
receiveSubscription: {
$0.request(.unlimited)
}
)
// When
sut.subscribe(subscriber)
nilPublisher.send(nil)
// Then
XCTAssertEqual(subscriber.history, [.subscription("PassthroughSubject"),
.value(output)])
}
func testExistingElementIsPreserved() {
// Given
let nilPublisher = PassthroughSubject<Int?, Never>()
let output = Int.random(in: 1...100)
let sut = nilPublisher.replaceNil(with: output + 1)
let subscriber = TrackingSubscriberBase<Int, Never>(
receiveSubscription: {
$0.request(.unlimited)
}
)
// When
sut.subscribe(subscriber)
nilPublisher.send(output)
// Then
XCTAssertEqual(subscriber.history, [.subscription("PassthroughSubject"),
.value(output)])
}
func testMultipleReplacements() {
// Given
let passthrough = PassthroughSubject<Int?, Never>()
let sut = passthrough.replaceNil(with: 42)
let subscriber = TrackingSubscriberBase<Int, Never>(
receiveSubscription: {
$0.request(.unlimited)
}
)
// When
sut.subscribe(subscriber)
passthrough.send(1)
passthrough.send(2)
passthrough.send(nil)
passthrough.send(4)
passthrough.send(5)
passthrough.send(nil)
passthrough.send(completion: .finished)
// Then
XCTAssertEqual(subscriber.history, [.subscription("PassthroughSubject"),
.value(1),
.value(2),
.value(42),
.value(4),
.value(5),
.value(42),
.completion(.finished)])
}
}
@@ -1,5 +1,5 @@
//
// OnceTests.swift
// ResultPublisherTests.swift
//
//
// Created by Sergej Jaskiewicz on 17.06.2019.
@@ -13,8 +13,8 @@ import Combine
import OpenCombine
#endif
@available(macOS 10.15, *)
final class OnceTests: XCTestCase {
@available(macOS 10.15, iOS 13.0, *)
final class ResultPublisherTests: XCTestCase {
static let allTests = [
("testOnceSuccessNoInitialDemand", testOnceSuccessNoInitialDemand),
@@ -23,6 +23,7 @@ final class OnceTests: XCTestCase {
("testOnceFailure", testOnceFailure),
("testFailureCancelOnSubscription", testFailureCancelOnSubscription),
("testLifecycle", testLifecycle),
("testCustomMirror", testCustomMirror),
("testMinOperatorSpecialization", testMinOperatorSpecialization),
("testTryMinOperatorSpecialization", testTryMinOperatorSpecialization),
("testMaxOperatorSpecialization", testMaxOperatorSpecialization),
@@ -38,26 +39,12 @@ final class OnceTests: XCTestCase {
testTryAllSatifyOperatorSpecialization),
("testCollectOperatorSpecialization", testCollectOperatorSpecialization),
("testCountOperatorSpecialization", testCountOperatorSpecialization),
("testDropFirstOperatorSpecialization", testDropFirstOperatorSpecialization),
("testDropWhileOperatorSpecialization", testDropWhileOperatorSpecialization),
("testTryDropWhileOperatorSpecialization",
testTryDropWhileOperatorSpecialization),
("testFirstOperatorSpecialization", testFirstOperatorSpecialization),
("testFirstWhereOperatorSpecializtion", testFirstWhereOperatorSpecializtion),
("testTryFirstWhereOperatorSpecializtion",
testTryFirstWhereOperatorSpecializtion),
("testLastOperatorSpecialization", testLastOperatorSpecialization),
("testLastWhereOperatorSpecializtion", testLastWhereOperatorSpecializtion),
("testTryLastWhereOperatorSpecializtion", testTryLastWhereOperatorSpecializtion),
("testFilterOperatorSpecialization", testFilterOperatorSpecialization),
("testTryFilterOperatorSpecialization", testTryFilterOperatorSpecialization),
("testIgnoreOutputOperatorSpecialization",
testIgnoreOutputOperatorSpecialization),
("testMapOperatorSpecialization", testMapOperatorSpecialization),
("testTryMapOperatorSpecialization", testTryMapOperatorSpecialization),
("testCompactMapOperatorSpecialization", testCompactMapOperatorSpecialization),
("testTryCompactMapOperatorSpecialization",
testTryCompactMapOperatorSpecialization),
("testMapErrorOperatorSpecialization", testMapErrorOperatorSpecialization),
("testReplaceErrorOperatorSpecialization",
testReplaceErrorOperatorSpecialization),
@@ -68,53 +55,70 @@ final class OnceTests: XCTestCase {
("testTryReduceOperatorSpecialization", testTryReduceOperatorSpecialization),
("testScanOperatorSpecialization", testScanOperatorSpecialization),
("testTryScanOperatorSpecialization", testTryScanOperatorSpecialization),
("testOutputAtIndexOperatorSpecialization",
testOutputAtIndexOperatorSpecialization),
("testOutputInRangeOperatorSpecialization",
testOutputInRangeOperatorSpecialization),
("testPrefixOperatorSpecialization", testPrefixOperatorSpecialization),
("testPrefixWhileOperatorSpecialization", testPrefixWhileOperatorSpecialization),
("testTryPrefixWhileOperatorSpecialization",
testTryPrefixWhileOperatorSpecialization),
("testSetFailureTypeOperatorSpecialization",
testSetFailureTypeOperatorSpecialization),
]
private typealias Sut<Output> = Publishers.Once<Output, TestingError>
#if OPENCOMBINE_COMPATIBILITY_TEST || !canImport(Combine)
private typealias ResultPublisher<Output, Failure: Error> =
Result<Output, Failure>.Publisher
private func makePublisher<Output, Failure: Error>(
_ result: Result<Output, Failure>
) -> ResultPublisher<Output, Failure> {
return result.publisher
}
#else
private typealias ResultPublisher<Output, Failure: Error> =
Result<Output, Failure>.OCombine.Publisher
private func makePublisher<Output, Failure: Error>(
_ result: Result<Output, Failure>
) -> ResultPublisher<Output, Failure> {
return result.ocombine.publisher
}
#endif
private typealias Sut<Output> = ResultPublisher<Output, TestingError>
private func makePublisher<Output>(
_ output: Output
) -> ResultPublisher<Output, TestingError> {
return makePublisher(.success(output))
}
func testOnceSuccessNoInitialDemand() {
let success = Sut(42)
let success = makePublisher(42)
let tracking = TrackingSubscriber()
success.subscribe(tracking)
XCTAssertEqual(tracking.history, [.subscription(Subscriptions.empty)])
XCTAssertEqual(tracking.history, [.subscription("Once")])
tracking.subscriptions.first?.request(.max(100))
tracking.subscriptions.first?.request(.max(1))
XCTAssertEqual(tracking.history, [.subscription(Subscriptions.empty),
XCTAssertEqual(tracking.history, [.subscription("Once"),
.value(42),
.completion(.finished)])
}
func testOnceSuccessWithInitialDemand() {
let just = Sut(42)
let just = makePublisher(42)
let tracking = TrackingSubscriber(receiveSubscription: { $0.request(.unlimited) })
just.subscribe(tracking)
XCTAssertEqual(tracking.history, [.subscription(Subscriptions.empty),
XCTAssertEqual(tracking.history, [.subscription("Once"),
.value(42),
.completion(.finished)])
}
func testSuccessCancelOnSubscription() {
let success = Sut(42)
let success = makePublisher(42)
let tracking = TrackingSubscriber(
receiveSubscription: { $0.request(.max(1)); $0.cancel() }
)
success.subscribe(tracking)
XCTAssertEqual(tracking.history, [.subscription(Subscriptions.empty),
XCTAssertEqual(tracking.history, [.subscription("Once"),
.value(42),
.completion(.finished)])
}
@@ -124,7 +128,7 @@ final class OnceTests: XCTestCase {
let tracking = TrackingSubscriber()
failure.subscribe(tracking)
XCTAssertEqual(tracking.history, [.subscription(Subscriptions.empty),
XCTAssertEqual(tracking.history, [.subscription("Empty"),
.completion(.failure("failure"))])
}
@@ -135,14 +139,14 @@ final class OnceTests: XCTestCase {
)
failure.subscribe(tracking)
XCTAssertEqual(tracking.history, [.subscription(Subscriptions.empty),
XCTAssertEqual(tracking.history, [.subscription("Empty"),
.completion(.failure("failure"))])
}
func testLifecycle() {
var deinitCount = 0
do {
let once = Sut(42)
let once = makePublisher(42)
let tracking = TrackingSubscriber(onDeinit: { deinitCount += 1 })
once.subscribe(tracking)
tracking.subscriptions.first?.cancel()
@@ -150,6 +154,24 @@ final class OnceTests: XCTestCase {
XCTAssertEqual(deinitCount, 1)
}
func testCustomMirror() throws {
let publisher = makePublisher(42)
var downstreamSubscription: Subscription?
let tracking = TrackingSubscriber(
receiveSubscription: { downstreamSubscription = $0 }
)
publisher.subscribe(tracking)
var reflected = ""
try dump(XCTUnwrap(downstreamSubscription), to: &reflected)
XCTAssertEqual(reflected, """
Once #0
- 42
""")
}
// MARK: - Operator specializations for Once
func testMinOperatorSpecialization() {
@@ -171,12 +193,13 @@ final class OnceTests: XCTestCase {
count += 1
throw TestingError.oops
}
XCTAssertEqual(Sut<Int>(1).tryMin(by: comparator).result, .success(1))
XCTAssertEqual(Sut<Int>(1).tryMin(by: throwingComparator).result, .success(1))
XCTAssertEqual(Sut<Int>("error").tryMin(by: throwingComparator).result,
.failure("error"))
XCTAssertEqual(try Sut<Int>(1).tryMin(by: comparator).result.get(), 1)
assertThrowsError(try Sut<Int>(1).tryMin(by: throwingComparator).result.get(),
.oops)
assertThrowsError(try Sut<Int>(.oops).tryMin(by: throwingComparator).result.get(),
.oops)
XCTAssertEqual(count, 0, "comparator should not be called for tryMin(by:)")
XCTAssertEqual(count, 2)
}
func testMaxOperatorSpecialization() {
@@ -199,12 +222,13 @@ final class OnceTests: XCTestCase {
throw TestingError.oops
}
XCTAssertEqual(Sut<Int>(2).tryMax(by: comparator).result, .success(2))
XCTAssertEqual(Sut<Int>(2).tryMax(by: throwingComparator).result, .success(2))
XCTAssertEqual(Sut<Int>("error").tryMax(by: throwingComparator).result,
.failure("error"))
XCTAssertEqual(try Sut<Int>(2).tryMax(by: comparator).result.get(), 2)
assertThrowsError(try Sut<Int>(2).tryMax(by: throwingComparator).result.get(),
.oops)
assertThrowsError(try Sut<Int>(.oops).tryMax(by: throwingComparator).result.get(),
.oops)
XCTAssertEqual(count, 0, "comparator should not be called for tryMax(by:)")
XCTAssertEqual(count, 2)
}
func testContainsOperatorSpecialization() {
@@ -261,16 +285,17 @@ final class OnceTests: XCTestCase {
func testTryRemoveDuplicatesOperatorSpecialization() {
var count = 0
let comparator: (Int, Int) -> Bool = { count += 1; return $0 > $1 }
let throwingComparator: (Int, Int) throws -> Bool = { _, _ in
let throwingComparator: (Int, Int) throws -> Bool = {
XCTAssertEqual($0, $1)
count += 1
throw TestingError.oops
}
XCTAssertEqual(try Sut<Int>(44).tryRemoveDuplicates(by: comparator).result.get(),
44)
XCTAssertEqual(
assertThrowsError(
try Sut<Int>(44).tryRemoveDuplicates(by: throwingComparator).result.get(),
44
.oops
)
assertThrowsError(
try Sut<Int>("error")
@@ -278,9 +303,7 @@ final class OnceTests: XCTestCase {
"error"
)
XCTAssertEqual(count,
0,
"comparator should not be called for tryRemoveDuplicates(by:)")
XCTAssertEqual(count, 2)
}
func testAllSatifyOperatorSpecialization() {
@@ -325,170 +348,16 @@ final class OnceTests: XCTestCase {
XCTAssertEqual(Sut<Int>("error").count().result, .failure("error"))
}
func testDropFirstOperatorSpecialization() {
XCTAssertEqual(Sut<Int>(10000).dropFirst().result, .success(nil))
XCTAssertEqual(Sut<Int>(10000).dropFirst(100).result, .success(nil))
XCTAssertEqual(Sut<Int>(10000).dropFirst(0).result, .success(10000))
XCTAssertEqual(Sut<Int>("error").dropFirst().result, .success(nil))
}
func testDropWhileOperatorSpecialization() {
var count = 0
let predicate: (Int) -> Bool = { count += 1; return $0 != 42 }
XCTAssertEqual(Sut<Int>(42).drop(while: predicate).result, .success(42))
XCTAssertEqual(Sut<Int>(-13).drop(while: predicate).result, .success(nil))
XCTAssertEqual(Sut<Int>("error").drop(while: predicate).result, .failure("error"))
XCTAssertEqual(count, 2)
}
func testTryDropWhileOperatorSpecialization() {
var count = 0
let predicate: (Int) -> Bool = { count += 1; return $0 != 42 }
let throwingPredicate: (Int) throws -> Bool = { _ in
count += 1
throw TestingError.oops
}
XCTAssertEqual(try Sut<Int>(42).tryDrop(while: predicate).result.get(), 42)
XCTAssertNil(try Sut<Int>(-13).tryDrop(while: predicate).result.get())
assertThrowsError(try Sut<Int>("error").tryDrop(while: predicate).result.get(),
"error")
assertThrowsError(
try Sut<Int>(42).tryDrop(while: throwingPredicate).result.get(),
.oops
)
assertThrowsError(
try Sut<Int>("error").tryDrop(while: throwingPredicate).result.get(),
"error"
)
XCTAssertEqual(count, 3)
}
func testFirstOperatorSpecialization() {
XCTAssertEqual(Sut<Int>(3).first().result, .success(3))
XCTAssertEqual(Sut<Int>("error").first().result, .failure("error"))
}
func testFirstWhereOperatorSpecializtion() {
var count = 0
let predicate: (Int) -> Bool = { count += 1; return $0 == 42 }
XCTAssertEqual(Sut<Int>(42).first(where: predicate).result, .success(42))
XCTAssertEqual(Sut<Int>(-13).first(where: predicate).result, .success(nil))
XCTAssertEqual(Sut<Int>("error").first(where: predicate).result,
.failure("error"))
XCTAssertEqual(count, 2)
}
func testTryFirstWhereOperatorSpecializtion() {
var count = 0
let predicate: (Int) -> Bool = { count += 1; return $0 == 42 }
let throwingPredicate: (Int) throws -> Bool = { _ in
count += 1
throw TestingError.oops
}
XCTAssertEqual(try Sut<Int>(42).tryFirst(where: predicate).result.get(), 42)
XCTAssertNil(try Sut<Int>(-13).tryFirst(where: predicate).result.get())
assertThrowsError(try Sut<Int>("error").tryFirst(where: predicate).result.get(),
"error")
assertThrowsError(
try Sut<Int>(42).tryFirst(where: throwingPredicate).result.get(),
.oops
)
assertThrowsError(
try Sut<Int>("error").tryFirst(where: throwingPredicate).result.get(),
"error"
)
XCTAssertEqual(count, 3)
}
func testLastOperatorSpecialization() {
XCTAssertEqual(Sut<Int>(4).last().result, .success(4))
XCTAssertEqual(Sut<Int>("error").last().result, .failure("error"))
}
func testLastWhereOperatorSpecializtion() {
var count = 0
let predicate: (Int) -> Bool = { count += 1; return $0 == 42 }
XCTAssertEqual(Sut<Int>(42).last(where: predicate).result, .success(42))
XCTAssertEqual(Sut<Int>(-13).last(where: predicate).result, .success(nil))
XCTAssertEqual(Sut<Int>("error").last(where: predicate).result,
.failure("error"))
XCTAssertEqual(count, 2)
}
func testTryLastWhereOperatorSpecializtion() {
var count = 0
let predicate: (Int) -> Bool = { count += 1; return $0 == 42 }
let throwingPredicate: (Int) throws -> Bool = { _ in
count += 1
throw TestingError.oops
}
XCTAssertEqual(try Sut<Int>(42).tryLast(where: predicate).result.get(), 42)
XCTAssertNil(try Sut<Int>(-13).tryLast(where: predicate).result.get())
assertThrowsError(try Sut<Int>("error").tryLast(where: predicate).result.get(),
"error")
assertThrowsError(
try Sut<Int>(42).tryLast(where: throwingPredicate).result.get(),
.oops
)
assertThrowsError(
try Sut<Int>("error").tryLast(where: throwingPredicate).result.get(),
"error"
)
XCTAssertEqual(count, 3)
}
func testFilterOperatorSpecialization() {
var count = 0
let predicate: (Int) -> Bool = { count += 1; return $0 == 42 }
XCTAssertEqual(Sut<Int>(42).filter(predicate).result, .success(42))
XCTAssertEqual(Sut<Int>(-13).filter(predicate).result, .success(nil))
XCTAssertEqual(Sut<Int>("error").filter(predicate).result,
.failure("error"))
XCTAssertEqual(count, 2)
}
func testTryFilterOperatorSpecialization() {
var count = 0
let predicate: (Int) -> Bool = { count += 1; return $0 == 42 }
let throwingPredicate: (Int) throws -> Bool = { _ in
count += 1
throw TestingError.oops
}
XCTAssertEqual(try Sut<Int>(42).tryFilter(predicate).result.get(), 42)
XCTAssertNil(try Sut<Int>(-13).tryFilter(predicate).result.get())
assertThrowsError(try Sut<Int>("error").tryFilter(predicate).result.get(),
"error")
assertThrowsError(
try Sut<Int>(42).tryFilter(throwingPredicate).result.get(),
.oops
)
assertThrowsError(
try Sut<Int>("error").tryFilter(throwingPredicate).result.get(),
"error"
)
XCTAssertEqual(count, 3)
}
func testIgnoreOutputOperatorSpecialization() {
XCTAssertTrue(Sut<Double>(13.0).ignoreOutput().completeImmediately)
XCTAssertTrue(Sut<Double>("error").ignoreOutput().completeImmediately)
@@ -521,40 +390,6 @@ final class OnceTests: XCTestCase {
"error")
}
func testCompactMapOperatorSpecialization() {
var count = 0
let transform: (Int) -> String? = {
count += 1
return $0 == 42 ? String($0) : nil
}
XCTAssertEqual(Sut<Int>(42).compactMap(transform).result, .success("42"))
XCTAssertEqual(Sut<Int>(100).compactMap(transform).result, .success(nil))
XCTAssertEqual(Sut<Int>("error").map(transform).result, .failure("error"))
XCTAssertEqual(count, 2)
}
func testTryCompactMapOperatorSpecialization() {
var count = 0
let transform: (Int) -> String? = {
count += 1
return $0 == 42 ? String($0) : nil
}
let throwingTrasnform: (Int) throws -> String? = { _ in
count += 1
throw TestingError.oops
}
XCTAssertEqual(try Sut<Int>(42).tryCompactMap(transform).result.get(), "42")
XCTAssertNil(try Sut<Int>(100).tryCompactMap(transform).result.get())
assertThrowsError(try Sut<Int>(42).tryMap(throwingTrasnform).result.get(), .oops)
assertThrowsError(try Sut<Int>("error").tryMap(throwingTrasnform).result.get(),
"error")
XCTAssertEqual(count, 3)
}
func testMapErrorOperatorSpecialization() {
XCTAssertEqual(Sut<Int>(42).mapError { _ in TestingError.oops }.result,
.success(42))
@@ -578,9 +413,6 @@ final class OnceTests: XCTestCase {
}
func testRetryOperatorSpecialization() {
XCTAssertEqual(Sut<Int>(1).retry().result, .success(1))
XCTAssertEqual(Sut<Int>("error").retry().result, .failure("error"))
XCTAssertEqual(Sut<Int>(1).retry(100).result, .success(1))
XCTAssertEqual(Sut<Int>("error").retry(100).result, .failure("error"))
}
@@ -609,77 +441,10 @@ final class OnceTests: XCTestCase {
"error")
}
func testOutputAtIndexOperatorSpecialization() {
XCTAssertEqual(Sut<Int>(12).output(at: 0).result, .success(12))
XCTAssertEqual(Sut<Int>(12).output(at: 1).result, .success(nil))
XCTAssertEqual(Sut<Int>(12).output(at: 42).result, .success(nil))
XCTAssertEqual(Sut<Int>("error").output(at: 42).result, .failure("error"))
}
func testOutputInRangeOperatorSpecialization() {
// TODO: Broken in Apple's Combine? (FB6169621)
// Empty range should result in a nil
// If this is fixed, this test will fail in compatibility mode so we will need
// to change our implementation to the correct one.
XCTAssertEqual(Sut<Int>(12).output(in: 0 ..< 10).result, .success(12))
XCTAssertEqual(Sut<Int>(12).output(in: -10 ..< 10).result, .success(nil))
XCTAssertEqual(Sut<Int>(12).output(in: (-10)...).result, .success(nil))
XCTAssertEqual(Sut<Int>(12).output(in: -10 ... 10).result, .success(nil))
XCTAssertEqual(Sut<Int>(12).output(in: -10 ..< -5).result, .success(nil))
XCTAssertEqual(Sut<Int>(12).output(in: 0 ..< 0).result, .success(12))
XCTAssertEqual(Sut<Int>(12).output(in: 0 ... 0).result, .success(12))
XCTAssertEqual(Sut<Int>(12).output(in: 1 ..< 10).result, .success(nil))
XCTAssertEqual(Sut<Int>("error").output(in: 0 ..< 10).result, .failure("error"))
}
func testPrefixOperatorSpecialization() {
// TODO: Seems broken in Apple's Combine (FB6168300)
// If this is fixed, this test will fail in compatibility mode so we will need
// to change our implementation to the correct one.
XCTAssertEqual(Sut<Int>(98).prefix(0).result, .success(98))
XCTAssertEqual(Sut<Int>(98).prefix(1).result, .success(nil))
XCTAssertEqual(Sut<Int>(98).prefix(1000).result, .success(nil))
XCTAssertEqual(Sut<Int>("error").prefix(0).result, .failure("error"))
XCTAssertEqual(Sut<Int>("error").prefix(1).result, .failure("error"))
}
func testPrefixWhileOperatorSpecialization() {
var count = 0
let predicate: (Int) -> Bool = { count += 1; return $0.isMultiple(of: 2) }
XCTAssertEqual(Sut<Int>(98).prefix(while: predicate).result, .success(98))
XCTAssertEqual(Sut<Int>(99).prefix(while: predicate).result, .success(nil))
XCTAssertEqual(Sut<Int>("error").prefix(while: predicate).result,
.failure("error"))
XCTAssertEqual(count, 2)
}
func testTryPrefixWhileOperatorSpecialization() {
var count = 0
let predicate: (Int) -> Bool = { count += 1; return $0.isMultiple(of: 2) }
let throwingPredicate: (Int) throws -> Bool = { _ in
count += 1
throw TestingError.oops
}
XCTAssertEqual(try Sut<Int>(98).tryPrefix(while: predicate).result.get(), 98)
XCTAssertNil(try Sut<Int>(99).tryPrefix(while: predicate).result.get())
assertThrowsError(
try Sut<Int>(98).tryPrefix(while: throwingPredicate).result.get(),
.oops
)
assertThrowsError(
try Sut<Int>("error").tryPrefix(while: throwingPredicate).result.get(),
"error"
)
}
func testSetFailureTypeOperatorSpecialization() {
XCTAssertEqual(
try Publishers.Once<Int, Never>(73)
try ResultPublisher<Int, Never>(73)
.setFailureType(to: TestingError.self)
.result
.get(),
@@ -13,7 +13,7 @@ import Combine
import OpenCombine
#endif
@available(macOS 10.15, *)
@available(macOS 10.15, iOS 13.0, *)
final class SequenceTests: XCTestCase {
static let allTests = [
@@ -28,16 +28,12 @@ final class SequenceTests: XCTestCase {
("testCollectOperatorSpecialization", testCollectOperatorSpecialization),
("testCompactMapOperatorSpecialization", testCompactMapOperatorSpecialization),
("testMinOperatorSpecialization", testMinOperatorSpecialization),
("tesTryMinOperatorSpecialization", testTryMinOperatorSpecialization),
("testMaxOperatorSpecialization", testMaxOperatorSpecialization),
("tesTryMaxOperatorSpecialization", testTryMaxOperatorSpecialization),
("testContainsOperatorSpecialization", testContainsOperatorSpecialization),
("testTryContainsOperatorSpecialization", testTryContainsOperatorSpecialization),
("testDropWhileOperatorSpecialization", testDropWhileOperatorSpecialization),
("testDropFirstOperatorSpecialization", testDropFirstOperatorSpecialization),
("testFirstWhereOperatorSpecializtion", testFirstWhereOperatorSpecializtion),
("testTryFirstWhereOperatorSpecializtion",
testTryFirstWhereOperatorSpecializtion),
("testFilterOperatorSpecialization", testFilterOperatorSpecialization),
("testIgnoreOutputOperatorSpecialization",
testIgnoreOutputOperatorSpecialization),
@@ -60,7 +56,6 @@ final class SequenceTests: XCTestCase {
testOutputInRangeOperatorSpecialization),
("testLastOperatorSpecialization", testLastOperatorSpecialization),
("testLastWhereOperatorSpecializtion", testLastWhereOperatorSpecializtion),
("testTryLastWhereOperatorSpecializtion", testTryLastWhereOperatorSpecializtion),
("testPrependVariadicOperatorSpezialization",
testPrependVariadicOperatorSpezialization),
("testPrependSequenceOperatorSpecialization",
@@ -75,6 +70,14 @@ final class SequenceTests: XCTestCase {
testAppendPublisherOperatorSpecialization),
]
#if OPENCOMBINE_COMPATIBILITY_TEST || !canImport(Combine)
private typealias ResultPublisher<Output, Failure: Error> =
Result<Output, Failure>.Publisher
#else
private typealias ResultPublisher<Output, Failure: Error> =
Result<Output, Failure>.OCombine.Publisher
#endif
func testEmptySequence() {
let emptyCounter = Counter(upperBound: 0)
@@ -89,7 +92,7 @@ final class SequenceTests: XCTestCase {
publisher.subscribe(subscriber)
XCTAssertEqual(subscriber.history, [.subscription(Subscriptions.empty),
XCTAssertEqual(subscriber.history, [.subscription("Empty"),
.completion(.finished)])
XCTAssertEqual(emptyCounter.state, 1)
}
@@ -115,18 +118,18 @@ final class SequenceTests: XCTestCase {
publisher.subscribe(subscriber)
XCTAssertEqual(subscriber.history, [.subscription(Subscriptions.empty)])
XCTAssertEqual(subscriber.history, [.subscription("Counter")])
subscriber.subscriptions.first?.request(.max(3))
XCTAssertEqual(subscriber.history, [.subscription(Subscriptions.empty),
XCTAssertEqual(subscriber.history, [.subscription("Counter"),
.value(1),
.value(2),
.value(3)])
subscriber.subscriptions.first?.request(.max(5))
XCTAssertEqual(subscriber.history, [.subscription(Subscriptions.empty),
XCTAssertEqual(subscriber.history, [.subscription("Counter"),
.value(1),
.value(2),
.value(3),
@@ -138,7 +141,7 @@ final class SequenceTests: XCTestCase {
subscriber.subscriptions.first?.request(.none)
XCTAssertEqual(subscriber.history, [.subscription(Subscriptions.empty),
XCTAssertEqual(subscriber.history, [.subscription("Counter"),
.value(1),
.value(2),
.value(3),
@@ -150,7 +153,7 @@ final class SequenceTests: XCTestCase {
subscriber.subscriptions.first?.request(.max(1))
XCTAssertEqual(subscriber.history, [.subscription(Subscriptions.empty),
XCTAssertEqual(subscriber.history, [.subscription("Sequence"),
.value(1),
.value(2),
.value(3),
@@ -165,8 +168,9 @@ final class SequenceTests: XCTestCase {
var reflected = ""
try dump(XCTUnwrap(subscriber.subscriptions.first), to: &reflected)
XCTAssertEqual(reflected, """
Sequence #0
- sequence: 0 elements
Sequence
subscription: Sequence #0
- sequence: 0 elements
""")
}
@@ -183,12 +187,12 @@ final class SequenceTests: XCTestCase {
publisher.subscribe(subscriber)
XCTAssertEqual(subscriber.history, [.subscription(Subscriptions.empty),
XCTAssertEqual(subscriber.history, [.subscription("Counter"),
.value(1)])
subscriber.subscriptions.first?.request(.max(1))
XCTAssertEqual(subscriber.history, [.subscription(Subscriptions.empty),
XCTAssertEqual(subscriber.history, [.subscription("Counter"),
.value(1),
.value(2),
.value(3),
@@ -207,12 +211,12 @@ final class SequenceTests: XCTestCase {
)
publisher.subscribe(subscriber)
XCTAssertEqual(subscriber.history, [.subscription(Subscriptions.empty),
XCTAssertEqual(subscriber.history, [.subscription("Sequence"),
.value(1)])
subscriber.subscriptions.first?.request(.max(1))
XCTAssertEqual(subscriber.history, [.subscription(Subscriptions.empty),
XCTAssertEqual(subscriber.history, [.subscription("Sequence"),
.value(1)])
}
@@ -231,10 +235,10 @@ final class SequenceTests: XCTestCase {
XCTAssertTrue(subscriber.history.isEmpty)
publisher.subscribe(subscriber)
XCTAssertEqual(subscriber.history, [.subscription(Subscriptions.empty)])
XCTAssertEqual(subscriber.history, [.subscription("Counter")])
subscriber.subscriptions.first?.request(.max(3))
XCTAssertEqual(subscriber.history, [.subscription(Subscriptions.empty),
XCTAssertEqual(subscriber.history, [.subscription("Sequence"),
.value(1),
.value(2),
.completion(.finished)])
@@ -253,7 +257,7 @@ final class SequenceTests: XCTestCase {
)
XCTAssertTrue(subscriber.history.isEmpty)
publisher.subscribe(subscriber)
XCTAssertEqual(subscriber.history, [.subscription(Subscriptions.empty)])
XCTAssertEqual(subscriber.history, [.subscription("Counter")])
XCTAssertNotNil(subscription)
}
@@ -298,34 +302,12 @@ final class SequenceTests: XCTestCase {
XCTAssertEqual(makePublisher([3, 4, 5, -1, 2]).min(by: >), .init(5))
}
func testTryMinOperatorSpecialization() {
XCTAssertEqual(try makePublisher([3, 4, 5, -1, 2]).tryMin(by: <).result.get(), -1)
XCTAssertEqual(try makePublisher([3, 4, 5, -1, 2]).tryMin(by: >).result.get(), 5)
XCTAssertNil(
try makePublisher(EmptyCollection<Int>()).tryMin(by: throwing).result.get()
)
XCTAssertEqual(try makePublisher([10]).tryMin(by: throwing).result.get(), 10)
assertThrowsError(try makePublisher([10, 20]).tryMin(by: throwing).result.get(),
.oops)
}
func testMaxOperatorSpecialization() {
XCTAssertEqual(makePublisher(EmptyCollection<Int>()).max(), .init(nil))
XCTAssertEqual(makePublisher([3, 4, 5, -1, 2]).max(), .init(5))
XCTAssertEqual(makePublisher([3, 4, 5, -1, 2]).max(by: >), .init(-1))
}
func testTryMaxOperatorSpecialization() {
XCTAssertEqual(try makePublisher([3, 4, 5, -1, 2]).tryMax(by: <).result.get(), 5)
XCTAssertEqual(try makePublisher([3, 4, 5, -1, 2]).tryMax(by: >).result.get(), -1)
XCTAssertNil(
try makePublisher(EmptyCollection<Int>()).tryMax(by: throwing).result.get()
)
XCTAssertEqual(try makePublisher([10]).tryMax(by: throwing).result.get(), 10)
assertThrowsError(try makePublisher([10, 20]).tryMax(by: throwing).result.get(),
.oops)
}
func testContainsOperatorSpecialization() {
XCTAssertEqual(makePublisher(EmptyCollection<Int>()).contains(12), .init(false))
XCTAssertEqual(makePublisher(0 ..< 12).contains(12), .init(false))
@@ -374,28 +356,6 @@ final class SequenceTests: XCTestCase {
)
}
func testTryFirstWhereOperatorSpecializtion() {
XCTAssertEqual(
try makePublisher(1 ..< 9).tryFirst { $0.isMultiple(of: 4) }.result.get(),
4
)
XCTAssertNil(
try makePublisher(1 ..< 9).tryFirst { $0.isMultiple(of: 13) }.result.get()
)
XCTAssertNil(
try makePublisher(EmptyCollection<Int>())
.tryFirst { $0.isMultiple(of: 13) }.result.get()
)
XCTAssertNil(
try makePublisher(EmptyCollection<Int>())
.tryFirst(where: throwing).result.get()
)
assertThrowsError(
try makePublisher(0 ..< 9).tryFirst(where: throwing).result.get(),
.oops
)
}
func testFilterOperatorSpecialization() {
XCTAssertEqual(makePublisher(0 ..< 10).filter { $0.isMultiple(of: 3) },
.init(sequence: [0, 3, 6, 9]))
@@ -483,10 +443,21 @@ final class SequenceTests: XCTestCase {
}
func testCountOperatorSpecialization() {
XCTAssertEqual(makePublisher(0 ..< .max).count(), Publishers.Once(.max))
XCTAssertEqual(makePublisher(EmptyCollection<Int>()).count(), Publishers.Once(0))
XCTAssertEqual(makePublisher([1, 1, 1, 1, 1, 1]).count(), Publishers.Optional(6))
XCTAssertEqual(makePublisher([]).count(), Publishers.Optional(0))
XCTAssertEqual(makePublisher(0 ..< .max).count(), Just(.max))
XCTAssertEqual(makePublisher(EmptyCollection<Int>()).count(), Just(0))
XCTAssertEqual(makePublisher([1, 1, 1, 1, 1, 1]).count(), Just(6))
XCTAssertEqual(
makePublisher([1, 1, 1, 1, 1, 1])
.setFailureType(to: TestingError.self)
.count(),
ResultPublisher(.success(6))
)
XCTAssertEqual(
makePublisher([1, 2, 3, 4, 5, 6].lazy.filter { $0.isMultiple(of: 2) })
.count(),
ResultPublisher(.success(3))
)
XCTAssertEqual(makePublisher([]).count(), Just(0))
}
func testOutputAtIndexOperatorSpecialization() {
@@ -554,28 +525,6 @@ final class SequenceTests: XCTestCase {
)
}
func testTryLastWhereOperatorSpecializtion() {
XCTAssertEqual(
try makePublisher(1 ..< 9).tryLast { $0.isMultiple(of: 4) }.result.get(),
8
)
XCTAssertNil(
try makePublisher(1 ..< 9).tryLast { $0.isMultiple(of: 13) }.result.get()
)
XCTAssertNil(
try makePublisher(EmptyCollection<Int>())
.tryLast { $0.isMultiple(of: 13) }.result.get()
)
XCTAssertNil(
try makePublisher(EmptyCollection<Int>())
.tryLast(where: throwing).result.get()
)
assertThrowsError(
try makePublisher(0 ..< 9).tryLast(where: throwing).result.get(),
.oops
)
}
func testPrependVariadicOperatorSpezialization() {
let baseCollection = TrackingCollection<Int>([4, 5, 6, 7])
@@ -746,30 +695,30 @@ private final class Counter: Sequence, IteratorProtocol, CustomStringConvertible
/// If both Foundation and OpenCombine are imported, Apple's Combine
/// extensions leak through Foundation, which results in the following error:
///
/// let publisher = [1, 2, 3, 4].publisher()
/// let publisher = [1, 2, 3, 4].publisher
/// ^
/// error: ambiguous use of 'publisher()'
/// error: ambiguous use of 'publisher'
///
/// This could be fixed by explicitly specifying a type:
///
/// let publisher: OpenCombine.Publishers.Sequence = [1, 2, 3, 4].publisher()
/// let publisher: OpenCombine.Publishers.Sequence = [1, 2, 3, 4].publisher
///
/// But this won't compile when testing compatibility, since compatibility tests
/// don't import OpenCombine. This could be fixed as well like this:
///
/// #if OPENCOMBINE_COMPATIBILITY_TEST
/// let publisher: Combine.Publishers.Sequence = [1, 2, 3, 4].publisher()
/// let publisher: Combine.Publishers.Sequence = [1, 2, 3, 4].publisher
/// #else
/// let publisher: OpenCombine.Publishers.Sequence = [1, 2, 3, 4].publisher()
/// let publisher: OpenCombine.Publishers.Sequence = [1, 2, 3, 4].publisher
/// #endif
///
/// But this is too verbose. This function provides a more concise way:
///
/// let publisher = makePublisher([1, 2, 3, 4])
///
@available(macOS 10.15, *)
@available(macOS 10.15, iOS 13.0, *)
private func makePublisher<Elements: Sequence>(
_ elements: Elements
) -> Publishers.Sequence<Elements, Never> {
return elements.publisher()
return elements.publisher
}
@@ -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, iOS 13.0, *)
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<Int, TestingError>(
receiveSubscription: { $0.request(.unlimited) }
)
let publisher = TrackingSubjectBase<Int, Never>(
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<Int, Never>()
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<Int, Never>(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<Int, Never>(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<Int, Never>(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<Int, Never>(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<Int, Never>(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<Int, Never>(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])
}
}
@@ -13,7 +13,7 @@ import Combine
import OpenCombine
#endif
@available(macOS 10.15, *)
@available(macOS 10.15, iOS 13.0, *)
final class AssignTests: XCTestCase {
static let allTests = [
@@ -70,19 +70,19 @@ final class AssignTests: XCTestCase {
let subscription1 = CustomSubscription()
assign.receive(subscription: subscription1)
XCTAssertEqual(subscription1.lastRequested, .unlimited)
XCTAssertFalse(subscription1.canceled)
XCTAssertFalse(subscription1.cancelled)
let subscription2 = CustomSubscription()
assign.receive(subscription: subscription2)
XCTAssertFalse(subscription1.canceled)
XCTAssertTrue(subscription2.canceled)
XCTAssertFalse(subscription1.cancelled)
XCTAssertTrue(subscription2.cancelled)
assign.receive(subscription: subscription1)
XCTAssertTrue(subscription1.canceled)
XCTAssertTrue(subscription1.cancelled)
subscription1.canceled = false
subscription1.cancelled = false
assign.receive(completion: .finished)
XCTAssertTrue(subscription1.canceled)
XCTAssertTrue(subscription1.cancelled)
}
func testReceiveValue() {
@@ -15,7 +15,7 @@ import OpenCombine
import Foundation
@available(macOS 10.15, *)
@available(macOS 10.15, iOS 13.0, *)
final class CompletionTests: XCTestCase {
static let allTests = [
@@ -13,7 +13,7 @@ import Combine
import OpenCombine
#endif
@available(macOS 10.15, *)
@available(macOS 10.15, iOS 13.0, *)
final class SinkTests: XCTestCase {
static let allTests = [
@@ -27,37 +27,37 @@ final class SinkTests: XCTestCase {
private typealias Sut = Subscribers.Sink<Int, Never>
func testDescription() {
let sink = Sut(receiveValue: { _ in })
let sink = Sut(receiveCompletion: { _ in }, receiveValue: { _ in })
XCTAssertEqual(sink.description, "Sink")
XCTAssertEqual(sink.playgroundDescription as? String, "Sink")
}
func testReflection() {
let sink = Sut(receiveValue: { _ in })
let sink = Sut(receiveCompletion: { _ in }, receiveValue: { _ in })
XCTAssert(sink.customMirror.children.isEmpty)
}
func testSubscription() {
let sink = Sut(receiveValue: { _ in })
let sink = Sut(receiveCompletion: { _ in }, receiveValue: { _ in })
let subscription1 = CustomSubscription()
sink.receive(subscription: subscription1)
XCTAssertEqual(subscription1.lastRequested, .unlimited)
XCTAssertFalse(subscription1.canceled)
XCTAssertFalse(subscription1.cancelled)
let subscription2 = CustomSubscription()
sink.receive(subscription: subscription2)
XCTAssertFalse(subscription1.canceled)
XCTAssertTrue(subscription2.canceled)
XCTAssertFalse(subscription1.cancelled)
XCTAssertTrue(subscription2.cancelled)
sink.receive(subscription: subscription1)
XCTAssertTrue(subscription1.canceled)
XCTAssertTrue(subscription1.cancelled)
subscription1.canceled = false
subscription1.cancelled = false
sink.receive(completion: .finished)
XCTAssertFalse(subscription1.canceled)
XCTAssertFalse(subscription1.cancelled)
}
func testReceiveValue() {
@@ -114,6 +114,6 @@ final class SinkTests: XCTestCase {
}
publisher.send(100)
XCTAssertEqual(value, 100)
XCTAssertEqual(value, 42)
}
}
@@ -15,19 +15,27 @@ import Combine
import OpenCombine
#endif
@available(macOS 10.15, *)
@available(macOS 10.15, iOS 13.0, *)
final class SubscribersDemandTests: XCTestCase {
static let allTests = [
("testCrashesOnNegativeValue", testCrashesOnNegativeValue),
("testAddition", testAddition),
("testSubtraction", testSubtraction),
("testMultiplication", testMultiplication),
("testComparison", testComparison),
("testMax", testMax),
("testDescription", testDescription),
("testEncodeDecode", testEncodeDecode),
("testEncodeDecodeJSON", testEncodeDecodeJSON),
("testEncodeDecodePlist", testEncodeDecodePlist),
]
func testCrashesOnNegativeValue() {
assertCrashes {
_ = Subscribers.Demand.max(-1)
}
}
func testAddition() {
XCTAssertEqual(.max(42) + .unlimited, Subscribers.Demand.unlimited)
@@ -203,35 +211,129 @@ final class SubscribersDemandTests: XCTestCase {
XCTAssertEqual(Subscribers.Demand.unlimited.description, "unlimited")
}
func testEncodeDecode() throws {
func testEncodeDecodeJSON() throws {
try testEncodeDecode(
encoder: JSONEncoder(),
decoder: JSONDecoder(),
expectedEncodedMax: #"{"value":42}"#,
expectedEncodedUnlimited: #"{"value":\#(UInt(Int.max) + 1)}"#,
illFormedNegative: #"{"value":-1}"#,
illFormedTooBig: #"{"value":\#(UInt(Int.max) + 2)}"#,
stringToDecoderInput: { Data($0.utf8) },
encoderOutputToString: { String(decoding: $0, as: UTF8.self) }
)
}
let jsonMax = #"{"rawValue":42}"#
let jsonUnlimited = #"{"rawValue":\#(UInt(Int.max) + 1)}"#
let jsonIllFormedNegative = #"{"rawValue":-1}"#
let jsonIllFormedTooBig = #"{"rawValue":\#(UInt(Int.max) + 2)}"#
func testEncodeDecodePlist() throws {
let encoder = PropertyListEncoder()
encoder.outputFormat = .xml
try testEncodeDecode(
encoder: encoder,
decoder: PropertyListDecoder(),
expectedEncodedMax: """
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" \
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
\t<key>value</key>
\t<integer>42</integer>
</dict>
</plist>
let encodedMax = try JSONEncoder().encode(Subscribers.Demand.max(42))
XCTAssertEqual(String(decoding: encodedMax, as: UTF8.self), jsonMax)
""",
expectedEncodedUnlimited: """
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" \
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
\t<key>value</key>
\t<integer>\(UInt(Int.max) + 1)</integer>
</dict>
</plist>
let encodedUnlimited = try JSONEncoder().encode(Subscribers.Demand.unlimited)
XCTAssertEqual(String(decoding: encodedUnlimited, as: UTF8.self), jsonUnlimited)
""",
illFormedNegative: """
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" \
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
\t<key>value</key>
\t<integer>-1</integer>
</dict>
</plist>
let decodedMax = try JSONDecoder().decode(Subscribers.Demand.self,
from: Data(jsonMax.utf8))
""",
illFormedTooBig: """
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" \
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
\t<key>value</key>
\t<integer>\(UInt(Int.max) + 2)</integer>
</dict>
</plist>
""",
stringToDecoderInput: { Data($0.utf8) },
encoderOutputToString: { String(decoding: $0, as: UTF8.self) }
)
}
private func testEncodeDecode<Encoder: TopLevelEncoder, Decoder: TopLevelDecoder>(
encoder: Encoder,
decoder: Decoder,
expectedEncodedMax: String,
expectedEncodedUnlimited: String,
illFormedNegative: String,
illFormedTooBig: String,
stringToDecoderInput: (String) -> Decoder.Input,
encoderOutputToString: (Encoder.Output) -> String
) throws where Decoder.Input == Encoder.Output {
let encodedMax = try encoderOutputToString(
encoder.encode(DemandKeyedWrapper.max(42))
)
XCTAssertEqual(encodedMax, expectedEncodedMax)
let encodedUnlimited = try encoderOutputToString(
encoder.encode(DemandKeyedWrapper.unlimited)
)
XCTAssertEqual(encodedUnlimited, expectedEncodedUnlimited)
let decodedMax = try decoder
.decode(DemandKeyedWrapper.self,
from: stringToDecoderInput(expectedEncodedMax))
XCTAssertEqual(decodedMax, .max(42))
let decodedUnlimited = try JSONDecoder().decode(Subscribers.Demand.self,
from: Data(jsonUnlimited.utf8))
let decodedUnlimited = try decoder
.decode(DemandKeyedWrapper.self,
from: stringToDecoderInput(expectedEncodedUnlimited))
XCTAssertEqual(decodedUnlimited, .unlimited)
XCTAssertThrowsError(
try JSONDecoder().decode(Subscribers.Demand.self,
from: Data(jsonIllFormedNegative.utf8))
try decoder.decode(DemandKeyedWrapper.self,
from: stringToDecoderInput(illFormedNegative))
)
let decodedIllFormedTooBig = try JSONDecoder()
.decode(Subscribers.Demand.self, from: Data(jsonIllFormedTooBig.utf8))
let decodedIllFormedTooBig = try decoder
.decode(DemandKeyedWrapper.self,
from: stringToDecoderInput(illFormedTooBig))
XCTAssertEqual(decodedIllFormedTooBig.description, "max(\(UInt(Int.max) + 2))")
XCTAssertEqual(decodedIllFormedTooBig.value.description, "unlimited")
}
}
@available(macOS 10.15, iOS 13.0, *)
private struct DemandKeyedWrapper: Codable, Equatable {
let value: Subscribers.Demand
static let unlimited = DemandKeyedWrapper(value: .unlimited)
static func max(_ value: Int) -> DemandKeyedWrapper {
return DemandKeyedWrapper(value: .max(value))
}
}
+19 -17
View File
@@ -10,30 +10,32 @@ import XCTest
#if !canImport(ObjectiveC)
public func allTests() -> [XCTestCaseEntry] {
return [
testCase(SubscribersDemandTests.allTests),
testCase(CompletionTests.allTests),
testCase(PassthroughSubjectTests.allTests),
testCase(CurrentValueSubjectTests.allTests),
testCase(ImmediateSchedulerTests.allTests),
testCase(AnySubscriberTests.allTests),
testCase(AnyPublisherTests.allTests),
testCase(AnySubjectTests.allTests),
testCase(CombineIdentifierTests.allTests),
testCase(AnyCancellableTests.allTests),
testCase(MulticastTests.allTests),
testCase(AnyPublisherTests.allTests),
testCase(AssignTests.allTests),
testCase(SinkTests.allTests),
testCase(DropWhileTests.allTests),
testCase(CombineIdentifierTests.allTests),
testCase(CompletionTests.allTests),
testCase(CurrentValueSubjectTests.allTests),
testCase(DecodeTests.allTests),
testCase(EncodeTests.allTests),
testCase(MapTests.allTests),
testCase(DropWhileTests.allTests),
testCase(EmptyTests.allTests),
testCase(JustTests.allTests),
testCase(OnceTests.allTests),
testCase(OptionalTests.allTests),
testCase(EncodeTests.allTests),
testCase(FailTests.allTests),
testCase(ImmediateSchedulerTests.allTests),
testCase(JustTests.allTests),
testCase(MapErrorTests.allTests),
testCase(MapTests.allTests),
testCase(MulticastTests.allTests),
testCase(ResultPublisherTests.allTests),
testCase(OptionalPublisherTests.allTests),
testCase(PassthroughSubjectTests.allTests),
testCase(PrintTests.allTests),
testCase(PublisherTests.allTests),
testCase(ReplaceNilTests.allTests),
testCase(SetFailureTypeTests.allTests),
testCase(SequenceTests.allTests),
testCase(SinkTests.allTests),
testCase(SubscribersDemandTests.allTests),
]
}
#endif
+1
View File
@@ -0,0 +1 @@
OTHER_SWIFT_FLAGS = $(inherited) -DOPENCOMBINE_COMPATIBILITY_TEST