Implemented Comparison. Use ReduceProducer.
This commit is contained in:
committed by
Sergej Jaskiewicz
parent
bd0b69d7cb
commit
3a88dfd76b
@@ -1328,111 +1328,6 @@ extension Publisher {
|
||||
public func throttle<S>(for interval: S.SchedulerTimeType.Stride, scheduler: S, latest: Bool) -> Publishers.Throttle<Self, S> where S : Scheduler
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher that republishes items from another publisher only if each new item is in increasing order from the previously-published item.
|
||||
public struct Comparison<Upstream> : Publisher where Upstream : Publisher {
|
||||
|
||||
/// The kind of values published by this publisher.
|
||||
public typealias Output = Upstream.Output
|
||||
|
||||
/// The kind of errors this publisher might publish.
|
||||
///
|
||||
/// Use `Never` if this `Publisher` does not publish errors.
|
||||
public typealias Failure = Upstream.Failure
|
||||
|
||||
/// The publisher that this publisher receives elements from.
|
||||
public let upstream: Upstream
|
||||
|
||||
/// A closure that receives two elements and returns `true` if they are in increasing order.
|
||||
public let areInIncreasingOrder: (Upstream.Output, Upstream.Output) -> Bool
|
||||
|
||||
public init(upstream: Upstream, areInIncreasingOrder: @escaping (Upstream.Output, Upstream.Output) -> Bool)
|
||||
|
||||
/// 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, Upstream.Output == S.Input
|
||||
}
|
||||
|
||||
/// A publisher that republishes items from another publisher only if each new item is in increasing order from the previously-published item, and fails if the ordering logic throws an error.
|
||||
public struct TryComparison<Upstream> : Publisher where Upstream : Publisher {
|
||||
|
||||
/// The kind of values published by this publisher.
|
||||
public typealias Output = Upstream.Output
|
||||
|
||||
/// The kind of errors this publisher might publish.
|
||||
///
|
||||
/// Use `Never` if this `Publisher` does not publish errors.
|
||||
public typealias Failure = Error
|
||||
|
||||
/// The publisher that this publisher receives elements from.
|
||||
public let upstream: Upstream
|
||||
|
||||
/// A closure that receives two elements and returns `true` if they are in increasing order.
|
||||
public let areInIncreasingOrder: (Upstream.Output, Upstream.Output) throws -> Bool
|
||||
|
||||
public init(upstream: Upstream, areInIncreasingOrder: @escaping (Upstream.Output, Upstream.Output) throws -> Bool)
|
||||
|
||||
/// 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.Output == S.Input, S.Failure == Publishers.TryComparison<Upstream>.Failure
|
||||
}
|
||||
}
|
||||
|
||||
extension Publisher where Self.Output : Comparable {
|
||||
|
||||
/// Publishes the minimum value received from the upstream publisher, after it finishes.
|
||||
///
|
||||
/// After this publisher receives a request for more than 0 items, it requests unlimited items from its upstream publisher.
|
||||
/// - Returns: A publisher that publishes the minimum value received from the upstream publisher, after the upstream publisher finishes.
|
||||
public func min() -> Publishers.Comparison<Self>
|
||||
|
||||
/// Publishes the maximum value received from the upstream publisher, after it finishes.
|
||||
///
|
||||
/// After this publisher receives a request for more than 0 items, it requests unlimited items from its upstream publisher.
|
||||
/// - Returns: A publisher that publishes the maximum value received from the upstream publisher, after the upstream publisher finishes.
|
||||
public func max() -> Publishers.Comparison<Self>
|
||||
}
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Publishes the minimum value received from the upstream publisher, after it finishes.
|
||||
///
|
||||
/// After this publisher receives a request for more than 0 items, it requests unlimited items from its upstream publisher.
|
||||
/// - Parameter areInIncreasingOrder: A closure that receives two elements and returns `true` if they are in increasing order.
|
||||
/// - Returns: A publisher that publishes the minimum value received from the upstream publisher, after the upstream publisher finishes.
|
||||
public func min(by areInIncreasingOrder: @escaping (Self.Output, Self.Output) -> Bool) -> Publishers.Comparison<Self>
|
||||
|
||||
/// Publishes the minimum value received from the upstream publisher, using the provided error-throwing closure to order the items.
|
||||
///
|
||||
/// After this publisher receives a request for more than 0 items, it requests unlimited items from its upstream publisher.
|
||||
/// - Parameter areInIncreasingOrder: A throwing closure that receives two elements and returns `true` if they are in increasing order. If this closure throws, the publisher terminates with a `Failure`.
|
||||
/// - Returns: A publisher that publishes the minimum value received from the upstream publisher, after the upstream publisher finishes.
|
||||
public func tryMin(by areInIncreasingOrder: @escaping (Self.Output, Self.Output) throws -> Bool) -> Publishers.TryComparison<Self>
|
||||
|
||||
/// Publishes the maximum value received from the upstream publisher, using the provided ordering closure.
|
||||
///
|
||||
/// After this publisher receives a request for more than 0 items, it requests unlimited items from its upstream publisher.
|
||||
/// - Parameter areInIncreasingOrder: A closure that receives two elements and returns `true` if they are in increasing order.
|
||||
/// - Returns: A publisher that publishes the maximum value received from the upstream publisher, after the upstream publisher finishes.
|
||||
public func max(by areInIncreasingOrder: @escaping (Self.Output, Self.Output) -> Bool) -> Publishers.Comparison<Self>
|
||||
|
||||
/// Publishes the maximum value received from the upstream publisher, using the provided error-throwing closure to order the items.
|
||||
///
|
||||
/// After this publisher receives a request for more than 0 items, it requests unlimited items from its upstream publisher.
|
||||
/// - Parameter areInIncreasingOrder: A throwing closure that receives two elements and returns `true` if they are in increasing order. If this closure throws, the publisher terminates with a `Failure`.
|
||||
/// - Returns: A publisher that publishes the maximum value received from the upstream publisher, after the upstream publisher finishes.
|
||||
public func tryMax(by areInIncreasingOrder: @escaping (Self.Output, Self.Output) throws -> Bool) -> Publishers.TryComparison<Self>
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher that replaces an empty stream with a provided element.
|
||||
|
||||
@@ -0,0 +1,246 @@
|
||||
//
|
||||
// Publishers.Comparison.swift
|
||||
// OpenCombine
|
||||
//
|
||||
// Created by Ilija Puaca on 22/7/19.
|
||||
//
|
||||
|
||||
extension Publisher where Output: Comparable {
|
||||
|
||||
/// Publishes the minimum value received from the upstream publisher, after it
|
||||
/// finishes.
|
||||
///
|
||||
/// After this publisher receives a request for more than 0 items, it requests
|
||||
/// unlimited items from its upstream publisher.
|
||||
///
|
||||
/// - Returns: A publisher that publishes the minimum value received from the upstream
|
||||
/// publisher, after the upstream publisher finishes.
|
||||
public func min() -> Publishers.Comparison<Self> {
|
||||
return max(by: >)
|
||||
}
|
||||
|
||||
/// Publishes the maximum value received from the upstream publisher, after it
|
||||
/// finishes.
|
||||
///
|
||||
/// After this publisher receives a request for more than 0 items, it requests
|
||||
/// unlimited items from its upstream publisher.
|
||||
///
|
||||
/// - Returns: A publisher that publishes the maximum value received from the upstream
|
||||
/// publisher, after the upstream publisher finishes.
|
||||
public func max() -> Publishers.Comparison<Self> {
|
||||
return max(by: <)
|
||||
}
|
||||
}
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Publishes the minimum value received from the upstream publisher, after it
|
||||
/// finishes.
|
||||
///
|
||||
/// After this publisher receives a request for more than 0 items, it requests
|
||||
/// unlimited items from its upstream publisher.
|
||||
///
|
||||
/// - Parameter areInIncreasingOrder: A closure that receives two elements and returns
|
||||
/// `true` if they are in increasing order.
|
||||
/// - Returns: A publisher that publishes the minimum value received from the upstream
|
||||
/// publisher, after the upstream publisher finishes.
|
||||
public func min(
|
||||
by areInIncreasingOrder: @escaping (Output, Output) -> Bool
|
||||
) -> Publishers.Comparison<Self> {
|
||||
return max(by: { areInIncreasingOrder($1, $0) })
|
||||
}
|
||||
|
||||
/// Publishes the minimum value received from the upstream publisher, using the
|
||||
/// provided error-throwing closure to order the items.
|
||||
///
|
||||
/// After this publisher receives a request for more than 0 items, it requests
|
||||
/// unlimited items from its upstream publisher.
|
||||
///
|
||||
/// - Parameter areInIncreasingOrder: A throwing closure that receives two elements
|
||||
/// and returns `true` if they are in increasing order. If this closure throws, the
|
||||
/// publisher terminates with a `Failure`.
|
||||
/// - Returns: A publisher that publishes the minimum value received from the upstream
|
||||
/// publisher, after the upstream publisher finishes.
|
||||
public func tryMin(
|
||||
by areInIncreasingOrder: @escaping (Output, Output) throws -> Bool
|
||||
) -> Publishers.TryComparison<Self> {
|
||||
return tryMax(by: { try areInIncreasingOrder($1, $0) })
|
||||
}
|
||||
|
||||
/// Publishes the maximum value received from the upstream publisher, using the
|
||||
/// provided ordering closure.
|
||||
///
|
||||
/// After this publisher receives a request for more than 0 items, it requests
|
||||
/// unlimited items from its upstream publisher.
|
||||
///
|
||||
/// - Parameter areInIncreasingOrder: A closure that receives two elements and returns
|
||||
/// `true` if they are in increasing order.
|
||||
/// - Returns: A publisher that publishes the maximum value received from the upstream
|
||||
/// publisher, after the upstream publisher finishes.
|
||||
public func max(
|
||||
by areInIncreasingOrder: @escaping (Output, Output) -> Bool
|
||||
) -> Publishers.Comparison<Self> {
|
||||
return .init(upstream: self, areInIncreasingOrder: areInIncreasingOrder)
|
||||
}
|
||||
|
||||
/// Publishes the maximum value received from the upstream publisher, using the
|
||||
/// provided error-throwing closure to order the items.
|
||||
///
|
||||
/// After this publisher receives a request for more than 0 items, it requests
|
||||
/// unlimited items from its upstream publisher.
|
||||
/// - Parameter areInIncreasingOrder: A throwing closure that receives two elements
|
||||
/// and returns `true` if they are in increasing order. If this closure throws, the
|
||||
/// publisher terminates with a `Failure`.
|
||||
/// - Returns: A publisher that publishes the maximum value received from the upstream
|
||||
/// publisher, after the upstream publisher finishes.
|
||||
public func tryMax(
|
||||
by areInIncreasingOrder: @escaping (Self.Output, Self.Output) throws -> Bool
|
||||
) -> Publishers.TryComparison<Self> {
|
||||
return .init(upstream: self, areInIncreasingOrder: areInIncreasingOrder)
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher that republishes items from another publisher only if each new item is
|
||||
/// in increasing order from the previously-published item.
|
||||
public struct Comparison<Upstream: Publisher>: Publisher {
|
||||
|
||||
public typealias Output = Upstream.Output
|
||||
|
||||
public typealias Failure = Upstream.Failure
|
||||
|
||||
/// The publisher that this publisher receives elements from.
|
||||
public let upstream: Upstream
|
||||
|
||||
/// A closure that receives two elements and returns `true` if they are in
|
||||
/// increasing order.
|
||||
public let areInIncreasingOrder: (Upstream.Output, Upstream.Output) -> Bool
|
||||
|
||||
public init(
|
||||
upstream: Upstream,
|
||||
areInIncreasingOrder: @escaping (Upstream.Output, Upstream.Output) -> Bool
|
||||
) {
|
||||
self.upstream = upstream
|
||||
self.areInIncreasingOrder = areInIncreasingOrder
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Upstream.Failure == Downstream.Failure,
|
||||
Upstream.Output == Downstream.Input
|
||||
{
|
||||
let inner = Inner(downstream: subscriber,
|
||||
areInIncreasingOrder: areInIncreasingOrder)
|
||||
upstream.subscribe(inner)
|
||||
}
|
||||
}
|
||||
|
||||
/// A publisher that republishes items from another publisher only if each new item is
|
||||
/// in increasing order from the previously-published item, and fails if the ordering
|
||||
/// logic throws an error.
|
||||
public struct TryComparison<Upstream: Publisher>: Publisher {
|
||||
|
||||
public typealias Output = Upstream.Output
|
||||
|
||||
public typealias Failure = Error
|
||||
|
||||
/// The publisher that this publisher receives elements from.
|
||||
public let upstream: Upstream
|
||||
|
||||
/// A closure that receives two elements and returns `true` if they are in
|
||||
/// increasing order.
|
||||
public let areInIncreasingOrder: (Upstream.Output, Upstream.Output) throws -> Bool
|
||||
|
||||
public init(
|
||||
upstream: Upstream,
|
||||
areInIncreasingOrder:
|
||||
@escaping (Upstream.Output, Upstream.Output) throws -> Bool
|
||||
) {
|
||||
self.upstream = upstream
|
||||
self.areInIncreasingOrder = areInIncreasingOrder
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Upstream.Output == Downstream.Input, Downstream.Failure == Error
|
||||
{
|
||||
let inner = Inner(downstream: subscriber,
|
||||
areInIncreasingOrder: areInIncreasingOrder)
|
||||
upstream.subscribe(inner)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.Comparison {
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: ReduceProducer<Downstream,
|
||||
Upstream.Output,
|
||||
Upstream.Output,
|
||||
Upstream.Failure,
|
||||
(Upstream.Output, Upstream.Output) -> Bool>
|
||||
where Downstream.Input == Upstream.Output, Downstream.Failure == Upstream.Failure
|
||||
{
|
||||
fileprivate init(
|
||||
downstream: Downstream,
|
||||
areInIncreasingOrder: @escaping (Upstream.Output, Upstream.Output) -> Bool
|
||||
) {
|
||||
super.init(downstream: downstream, initial: nil, reduce: areInIncreasingOrder)
|
||||
}
|
||||
|
||||
override func receive(
|
||||
newValue: Upstream.Output
|
||||
) -> PartialCompletion<Void, Downstream.Failure> {
|
||||
if let result = self.result {
|
||||
if reduce(result, newValue) {
|
||||
self.result = newValue
|
||||
}
|
||||
} else {
|
||||
self.result = newValue
|
||||
}
|
||||
return .continue
|
||||
}
|
||||
|
||||
override var description: String {
|
||||
return "Comparison"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.TryComparison {
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: ReduceProducer<Downstream,
|
||||
Upstream.Output,
|
||||
Upstream.Output,
|
||||
Upstream.Failure,
|
||||
(Upstream.Output, Upstream.Output) throws -> Bool>
|
||||
where Downstream.Input == Upstream.Output, Downstream.Failure == Error
|
||||
{
|
||||
fileprivate init(
|
||||
downstream: Downstream,
|
||||
areInIncreasingOrder:
|
||||
@escaping (Upstream.Output, Upstream.Output) throws -> Bool
|
||||
) {
|
||||
super.init(downstream: downstream, initial: nil, reduce: areInIncreasingOrder)
|
||||
}
|
||||
|
||||
override func receive(
|
||||
newValue: Upstream.Output
|
||||
) -> PartialCompletion<Void, Downstream.Failure> {
|
||||
do {
|
||||
if let result = self.result {
|
||||
if try reduce(result, newValue) {
|
||||
self.result = newValue
|
||||
}
|
||||
} else {
|
||||
self.result = newValue
|
||||
}
|
||||
return .continue
|
||||
} catch {
|
||||
return .failure(error)
|
||||
}
|
||||
}
|
||||
|
||||
override var description: String {
|
||||
return "TryComparison"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,367 @@
|
||||
//
|
||||
// ComparisonTests.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 15.10.2019.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
|
||||
#if OPENCOMBINE_COMPATIBILITY_TEST
|
||||
import Combine
|
||||
#else
|
||||
import OpenCombine
|
||||
#endif
|
||||
|
||||
@available(macOS 10.15, iOS 13.0, *)
|
||||
final class ComparisonTests: XCTestCase {
|
||||
|
||||
// MARK: - Comparison
|
||||
|
||||
func testComparisonBasicBehavior() {
|
||||
ComparisonTests.testBasicBehavior(
|
||||
expectedSubscription: "Comparison",
|
||||
expectedResult: 15,
|
||||
semantics: .max,
|
||||
countComparatorCalls: false,
|
||||
{ upstream, _ in upstream.max() }
|
||||
)
|
||||
|
||||
ComparisonTests.testBasicBehavior(
|
||||
expectedSubscription: "Comparison",
|
||||
expectedResult: 1,
|
||||
semantics: .min,
|
||||
countComparatorCalls: false,
|
||||
{ upstream, _ in upstream.min() }
|
||||
)
|
||||
|
||||
ComparisonTests.testBasicBehavior(
|
||||
expectedSubscription: "Comparison",
|
||||
expectedResult: 8,
|
||||
semantics: .max,
|
||||
{ upstream, comparator in upstream.max(by: comparator) }
|
||||
)
|
||||
|
||||
ComparisonTests.testBasicBehavior(
|
||||
expectedSubscription: "Comparison",
|
||||
expectedResult: 1,
|
||||
semantics: .min,
|
||||
{ upstream, comparator in upstream.min(by: comparator) }
|
||||
)
|
||||
}
|
||||
|
||||
func testComparisonUpstreamFinishesWithError() {
|
||||
ReduceTests.testUpstreamFinishesWithError(expectedSubscription: "Comparison",
|
||||
{ $0.max() })
|
||||
|
||||
ReduceTests.testUpstreamFinishesWithError(expectedSubscription: "Comparison",
|
||||
{ $0.min() })
|
||||
|
||||
ReduceTests.testUpstreamFinishesWithError(expectedSubscription: "Comparison",
|
||||
{ $0.max(by: shouldNotBeCalled()) })
|
||||
|
||||
ReduceTests.testUpstreamFinishesWithError(expectedSubscription: "Comparison",
|
||||
{ $0.min(by: shouldNotBeCalled()) })
|
||||
}
|
||||
|
||||
func testComparisonUpstreamFinishesImmediately() {
|
||||
ReduceTests.testUpstreamFinishesImmediately(expectedSubscription: "Comparison",
|
||||
expectedResult: nil,
|
||||
{ $0.max() })
|
||||
|
||||
ReduceTests.testUpstreamFinishesImmediately(expectedSubscription: "Comparison",
|
||||
expectedResult: nil,
|
||||
{ $0.min() })
|
||||
|
||||
ReduceTests.testUpstreamFinishesImmediately(expectedSubscription: "Comparison",
|
||||
expectedResult: nil,
|
||||
{ $0.max(by: shouldNotBeCalled()) })
|
||||
|
||||
ReduceTests.testUpstreamFinishesImmediately(expectedSubscription: "Comparison",
|
||||
expectedResult: nil,
|
||||
{ $0.min(by: shouldNotBeCalled()) })
|
||||
}
|
||||
|
||||
func testComparisonCancelAlreadyCancelled() throws {
|
||||
try ReduceTests.testCancelAlreadyCancelled { $0.max() }
|
||||
try ReduceTests.testCancelAlreadyCancelled { $0.min() }
|
||||
try ReduceTests.testCancelAlreadyCancelled { $0.max(by: shouldNotBeCalled()) }
|
||||
try ReduceTests.testCancelAlreadyCancelled { $0.min(by: shouldNotBeCalled()) }
|
||||
}
|
||||
|
||||
func testComparisonRequestsUnlimitedThenSendsSubscription() {
|
||||
ReduceTests.testRequestsUnlimitedThenSendsSubscription { $0.max() }
|
||||
|
||||
ReduceTests.testRequestsUnlimitedThenSendsSubscription { $0.min() }
|
||||
|
||||
ReduceTests.testRequestsUnlimitedThenSendsSubscription {
|
||||
$0.max(by: shouldNotBeCalled())
|
||||
}
|
||||
|
||||
ReduceTests.testRequestsUnlimitedThenSendsSubscription {
|
||||
$0.min(by: shouldNotBeCalled())
|
||||
}
|
||||
}
|
||||
|
||||
func testComparisonReceiveSubscriptionTwice() throws {
|
||||
try ReduceTests.testReceiveSubscriptionTwice(expectedSubscription: "Comparison",
|
||||
expectedResult: .normalCompletion(0),
|
||||
{ $0.max() })
|
||||
|
||||
try ReduceTests.testReceiveSubscriptionTwice(expectedSubscription: "Comparison",
|
||||
expectedResult: .normalCompletion(0),
|
||||
{ $0.min() })
|
||||
|
||||
try ReduceTests.testReceiveSubscriptionTwice(expectedSubscription: "Comparison",
|
||||
expectedResult: .normalCompletion(0),
|
||||
{ $0.max(by: shouldNotBeCalled()) })
|
||||
|
||||
try ReduceTests.testReceiveSubscriptionTwice(expectedSubscription: "Comparison",
|
||||
expectedResult: .normalCompletion(0),
|
||||
{ $0.min(by: shouldNotBeCalled()) })
|
||||
}
|
||||
|
||||
func testComparisonLifecycle() throws {
|
||||
try testLifecycle(sendValue: 31,
|
||||
cancellingSubscriptionReleasesSubscriber: false,
|
||||
{ $0.min(by: >) })
|
||||
|
||||
try testLifecycle(sendValue: 31,
|
||||
cancellingSubscriptionReleasesSubscriber: false,
|
||||
{ $0.max(by: >) })
|
||||
}
|
||||
|
||||
func testComparisonReflection() throws {
|
||||
try testReflection(parentInput: Int.self,
|
||||
parentFailure: Error.self,
|
||||
description: "Comparison",
|
||||
customMirror: reduceLikeOperatorMirror(),
|
||||
playgroundDescription: "Comparison",
|
||||
{ $0.min(by: shouldNotBeCalled()) })
|
||||
|
||||
try testReflection(parentInput: Int.self,
|
||||
parentFailure: Error.self,
|
||||
description: "Comparison",
|
||||
customMirror: reduceLikeOperatorMirror(),
|
||||
playgroundDescription: "Comparison",
|
||||
{ $0.max(by: shouldNotBeCalled()) })
|
||||
}
|
||||
|
||||
// MARK: - TryComparison
|
||||
|
||||
func testTryComparisonBasicBehavior() {
|
||||
|
||||
ComparisonTests.testBasicBehavior(
|
||||
expectedSubscription: "TryComparison",
|
||||
expectedResult: 8,
|
||||
semantics: .max,
|
||||
{ upstream, comparator in upstream.tryMax(by: comparator) }
|
||||
)
|
||||
|
||||
ComparisonTests.testBasicBehavior(
|
||||
expectedSubscription: "TryComparison",
|
||||
expectedResult: 1,
|
||||
semantics: .min,
|
||||
{ upstream, comparator in upstream.tryMin(by: comparator) }
|
||||
)
|
||||
}
|
||||
|
||||
func testTryComparisonFailureBecauseOfThrow() throws {
|
||||
|
||||
func comparator(_ lhs: Int, _ rhs: Int) throws -> Bool {
|
||||
if lhs == 3 {
|
||||
throw TestingError.oops
|
||||
}
|
||||
return lhs < rhs
|
||||
}
|
||||
|
||||
try ReduceTests.testFailureBecauseOfThrow(expectedSubscription: "TryComparison",
|
||||
expectedFailure: TestingError.oops,
|
||||
{ $0.tryMax(by: comparator) })
|
||||
|
||||
try ReduceTests.testFailureBecauseOfThrow(expectedSubscription: "TryComparison",
|
||||
expectedFailure: TestingError.oops,
|
||||
{ $0.tryMin(by: comparator) })
|
||||
}
|
||||
|
||||
func testTryComparisonUpstreamFinishesWithError() {
|
||||
ReduceTests.testUpstreamFinishesWithError(expectedSubscription: "TryComparison",
|
||||
{ $0.tryMax(by: >) })
|
||||
|
||||
ReduceTests.testUpstreamFinishesWithError(expectedSubscription: "TryComparison",
|
||||
{ $0.tryMin(by: >) })
|
||||
}
|
||||
|
||||
func testTryComparisonUpstreamFinishesImmediately() {
|
||||
ReduceTests.testUpstreamFinishesImmediately(expectedSubscription: "TryComparison",
|
||||
expectedResult: nil,
|
||||
{ $0.tryMax(by: >) })
|
||||
|
||||
ReduceTests.testUpstreamFinishesImmediately(expectedSubscription: "TryComparison",
|
||||
expectedResult: nil,
|
||||
{ $0.tryMin(by: >) })
|
||||
}
|
||||
|
||||
func testTryComparisonCancelAlreadyCancelled() throws {
|
||||
try ReduceTests.testCancelAlreadyCancelled { $0.tryMax(by: shouldNotBeCalled()) }
|
||||
try ReduceTests.testCancelAlreadyCancelled { $0.tryMin(by: shouldNotBeCalled()) }
|
||||
}
|
||||
|
||||
func testTryComparisonRequestsUnlimitedThenSendsSubscription() {
|
||||
ReduceTests.testRequestsUnlimitedThenSendsSubscription {
|
||||
$0.tryMax(by: shouldNotBeCalled())
|
||||
}
|
||||
|
||||
ReduceTests.testRequestsUnlimitedThenSendsSubscription {
|
||||
$0.tryMin(by: shouldNotBeCalled())
|
||||
}
|
||||
}
|
||||
|
||||
func testTryComparisonReceiveSubscriptionTwice() throws {
|
||||
try ReduceTests.testReceiveSubscriptionTwice(
|
||||
expectedSubscription: "TryComparison",
|
||||
expectedResult: .normalCompletion(0),
|
||||
{ $0.tryMax(by: shouldNotBeCalled()) }
|
||||
)
|
||||
|
||||
try ReduceTests.testReceiveSubscriptionTwice(
|
||||
expectedSubscription: "TryComparison",
|
||||
expectedResult: .normalCompletion(0),
|
||||
{ $0.tryMin(by: shouldNotBeCalled()) }
|
||||
)
|
||||
}
|
||||
|
||||
func testTryComparisonLifecycle() throws {
|
||||
try testLifecycle(sendValue: 31,
|
||||
cancellingSubscriptionReleasesSubscriber: false,
|
||||
{ $0.tryMin(by: >) })
|
||||
|
||||
try testLifecycle(sendValue: 31,
|
||||
cancellingSubscriptionReleasesSubscriber: false,
|
||||
{ $0.tryMax(by: >) })
|
||||
}
|
||||
|
||||
func testTryComparisonReflection() throws {
|
||||
try testReflection(parentInput: Int.self,
|
||||
parentFailure: Error.self,
|
||||
description: "TryComparison",
|
||||
customMirror: reduceLikeOperatorMirror(),
|
||||
playgroundDescription: "TryComparison",
|
||||
{ $0.tryMin(by: shouldNotBeCalled()) })
|
||||
|
||||
try testReflection(parentInput: Int.self,
|
||||
parentFailure: Error.self,
|
||||
description: "TryComparison",
|
||||
customMirror: reduceLikeOperatorMirror(),
|
||||
playgroundDescription: "TryComparison",
|
||||
{ $0.tryMax(by: shouldNotBeCalled()) })
|
||||
}
|
||||
|
||||
// MARK: - Generic tests
|
||||
|
||||
private enum ComparisonSemantics {
|
||||
case min
|
||||
case max
|
||||
}
|
||||
|
||||
private struct ComparisonHistoryElement: Equatable, CustomStringConvertible {
|
||||
let lhs: Int
|
||||
let rhs: Int
|
||||
|
||||
init(_ lhs: Int, _ rhs: Int) {
|
||||
self.lhs = lhs
|
||||
self.rhs = rhs
|
||||
}
|
||||
|
||||
var description: String { return "(\(lhs), \(rhs))" }
|
||||
}
|
||||
|
||||
/// Publishes 2, 1, 4, 6, 15, 8, `.finished`, 7, 32.
|
||||
/// Uses `Int.trailingZeroBitCount` for comparing values.
|
||||
/// Therefore, for the passed comparator 8 is max, 1 is min.
|
||||
private static func testBasicBehavior<Operator: Publisher>(
|
||||
expectedSubscription: StringSubscription,
|
||||
expectedResult: Int,
|
||||
semantics: ComparisonSemantics,
|
||||
countComparatorCalls: Bool = true,
|
||||
_ makeOperator: (CustomPublisher, @escaping (Int, Int) -> Bool) -> Operator
|
||||
) where Operator.Output == Int {
|
||||
|
||||
var comparisonHistory = [ComparisonHistoryElement]()
|
||||
|
||||
func comparator(_ lhs: Int, _ rhs: Int) -> Bool {
|
||||
comparisonHistory.append(.init(lhs, rhs))
|
||||
|
||||
// Some custom logic to make sure the publisher doesn't use '<'.
|
||||
return lhs.trailingZeroBitCount < rhs.trailingZeroBitCount
|
||||
}
|
||||
|
||||
let helper = OperatorTestHelper(
|
||||
publisherType: CustomPublisher.self,
|
||||
initialDemand: .max(1),
|
||||
receiveValueDemand: .none,
|
||||
createSut: { makeOperator($0, comparator) }
|
||||
)
|
||||
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited)])
|
||||
XCTAssertEqual(helper.tracking.history, [.subscription(expectedSubscription)])
|
||||
|
||||
XCTAssertEqual(helper.publisher.send(2), .none) // trailingZeroBitCount = 1
|
||||
XCTAssertEqual(helper.publisher.send(1), .none) // trailingZeroBitCount = 0
|
||||
XCTAssertEqual(helper.publisher.send(4), .none) // trailingZeroBitCount = 2
|
||||
XCTAssertEqual(helper.publisher.send(6), .none) // trailingZeroBitCount = 1
|
||||
XCTAssertEqual(helper.publisher.send(15), .none) // trailingZeroBitCount = 0
|
||||
XCTAssertEqual(helper.publisher.send(8), .none) // trailingZeroBitCount = 3
|
||||
XCTAssertEqual(helper.publisher.send(12), .none) // trailingZeroBitCount = 2
|
||||
|
||||
if countComparatorCalls {
|
||||
switch semantics {
|
||||
case .max:
|
||||
XCTAssertEqual(comparisonHistory, [.init(2, 1),
|
||||
.init(2, 4),
|
||||
.init(4, 6),
|
||||
.init(4, 15),
|
||||
.init(4, 8),
|
||||
.init(8, 12)])
|
||||
case .min:
|
||||
XCTAssertEqual(comparisonHistory, [.init(1, 2),
|
||||
.init(4, 1),
|
||||
.init(6, 1),
|
||||
.init(15, 1),
|
||||
.init(8, 1),
|
||||
.init(12, 1)])
|
||||
}
|
||||
}
|
||||
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited)])
|
||||
XCTAssertEqual(helper.tracking.history, [.subscription(expectedSubscription)])
|
||||
|
||||
helper.publisher.send(completion: .finished)
|
||||
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited)])
|
||||
XCTAssertEqual(helper.tracking.history, [.subscription(expectedSubscription),
|
||||
.value(expectedResult),
|
||||
.completion(.finished)])
|
||||
|
||||
XCTAssertEqual(helper.publisher.send(7), .none) // trailingZeroBitCount = 0
|
||||
XCTAssertEqual(helper.publisher.send(32), .none) // trailingZeroBitCount = 5
|
||||
|
||||
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited)])
|
||||
XCTAssertEqual(helper.tracking.history, [.subscription(expectedSubscription),
|
||||
.value(expectedResult),
|
||||
.completion(.finished)])
|
||||
|
||||
if countComparatorCalls {
|
||||
XCTAssertEqual(comparisonHistory.count, 6)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func shouldNotBeCalled(
|
||||
file: StaticString = #file, line: UInt = #line
|
||||
) -> (Int, Int) -> Bool {
|
||||
return { _, _ in
|
||||
XCTFail("Should not be called", file: file, line: line)
|
||||
return true
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user