Implemented Comparison. Use ReduceProducer.

This commit is contained in:
Sergej Jaskiewicz
2019-10-15 20:51:44 +03:00
committed by Sergej Jaskiewicz
parent bd0b69d7cb
commit 3a88dfd76b
3 changed files with 613 additions and 105 deletions
-105
View File
@@ -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
}
}