22 Commits

Author SHA1 Message Date
Eric Patey d57c878651 FlatMap (#45)
Implement FlatMap
2019-09-13 10:57:17 -04:00
Eric Patey 7fa91778c2 Fix failing tests caused by Apple changes in GM Seed. (#56)
Update tests and implementation to match Apple changes in gm seed.
2019-09-13 10:50:38 -04:00
Sergej Jaskiewicz d15e604764 Remove mention of XCTestManifests from the Dangerfile 2019-09-13 14:45:28 +03:00
Evgeniy 07c7a98d72 @propertyWrapper Published (#52) 2019-09-07 18:10:53 +03:00
Sergej Jaskiewicz 01ef05be1f Pass --enable-index-store flag when testing in release mode 2019-09-07 16:32:34 +03:00
Sergej Jaskiewicz beee9d0d51 Remove allTests properties in test classes 2019-09-07 16:32:34 +03:00
Sergej Jaskiewicz aacd1a326c Remove LinuxMain.swift 2019-09-07 16:32:34 +03:00
Sergej Jaskiewicz 5528adcc67 Enable test discovery on Linux 2019-09-07 16:32:34 +03:00
Sergej Jaskiewicz 1b810d0536 Update Swift version on Linux 2019-09-07 16:32:34 +03:00
Bogdan Vlad 8b25238154 ReplaceError implementation (#50) 2019-09-01 22:05:44 +03:00
Sergej Jaskiewicz 9b9915bde7 Fix Optional.Publisher.collect() operator specialization
Update for Xcode 11.0 beta 6
2019-08-26 03:51:12 +03:00
Eric Patey 27f01e5f21 Implement IgnoreOutput (#44) 2019-08-20 17:32:33 +03:00
Sergej Jaskiewicz 739eb47409 Update for Xcode 11 beta 6
Yes, it's the only change.
2019-08-20 10:54:17 +03:00
Sergej Jaskiewicz 14d5a90e89 Add Slack badge 2019-08-05 19:57:58 +03:00
Sergej Jaskiewicz 0e869bc861 Implement CompactMap (#32) 2019-08-02 18:59:53 +03:00
Joe Spadafora 2f38069166 First where (#29) 2019-08-02 18:55:51 +03:00
Sergej Jaskiewicz 97d07d0a14 Better linting for inheritance clauses and dictionary literals 2019-08-02 14:18:17 +03:00
Joe Spadafora d3888a3808 Implement Filter/TryFilter (#22)
* Adds filter and try filter implementations

* Implement Filter

* Remove @testable declaration

* Fix linting

* Updates tests and creates testing helper

* Fix allTests to include all tests

* Renames TestHelper to OperatorTestHelper and adds documentation

* Adds more test coverage

* Updates to use subclasses for filter / tryfilter

* Adds subscription test

* Fix subscriber demand to be lazy

* Fix CustomPublisherBase changes from master

* Fix iOS availability on test helper

* Updates availability for test functions

* Simplify Filter implementation, add more tests

* Ensure test suite consistency on Darwin and Linux

* Add missing tests to XCTestManifests.swift
2019-08-02 00:20:35 +03:00
Franz Busch d2b8709afb Store newly send value in internal variable inside CurrentValueObject (#39) 2019-08-01 23:34:27 +03:00
Sergej Jaskiewicz a28177e9c5 Cache homebrew artifacts 2019-08-01 15:49:00 +03:00
Sergej Jaskiewicz cef19fce4b Use Danger CI 2019-08-01 15:49:00 +03:00
Sergej Jaskiewicz 7f6bba62de Add .hound.yml 2019-08-01 11:52:46 +03:00
63 changed files with 4186 additions and 957 deletions
+32
View File
@@ -75,3 +75,35 @@ generic_type_name:
attributes:
always_on_line_above:
- "@usableFromInline"
custom_rules:
no_foundation_dependency:
included: Sources/OpenCombine
name: "No Foundation Dependency"
regex: "^import.*Foundation.*$"
message: "We don't want to depend on Foundation"
severity: error
no_dispatch_dependency:
included: Sources/OpenCombine
name: "No Dispatch Dependency"
regex: "^import.*Dispatch.*$"
message: "We don't want to depend on Dispatch"
severity: error
inheritance_colon:
name: "Inheritance Colon"
regex: '\s[A-Z_]\w*(<[\w\s:\.,]+>)?(?: +:\s*|:(?:\s{0}|\s{2,}))([\[|\(]*\S)'
message: "Colons should be next to the identifier of the inheriting type"
severity: warning
match_kinds:
- identifier
- typeidentifier
dictionary_type_colon:
name: "Dictionary Type Colon"
regex: '\[\w+(?:(?:\s{0}|\s{2,}):| :(?:\s{0}|\s{2,})\w)'
message: "Colon should be surrounded by a single whitespace in a dictionary literal"
severity: warning
match_kinds:
- typeidentifier
+37 -8
View File
@@ -2,17 +2,27 @@ language: generic
addons:
homebrew:
taps:
- danger/tap
packages:
- swiftlint
- danger-swift
update: true
cache:
directories:
- .build
- ~/.danger-swift
- ~/.swiftenv
- ~/Library/Caches/Homebrew
matrix:
include:
- name: "Ubuntu 16.04 | Swift 5.1 | Tests"
os: linux
dist: xenial
sudo: required
env: SWIFT_VERSION="swift-5.1-DEVELOPMENT-SNAPSHOT-2019-06-16-a" OPENCOMBINE_TEST="YES"
env: SWIFT_VERSION="swift-5.1-DEVELOPMENT-SNAPSHOT-2019-09-05-a" OPENCOMBINE_TEST="YES"
- name: "macOS 10.14 | Swift 5.0 | Tests"
os: osx
osx_image: xcode10.2
@@ -21,10 +31,14 @@ matrix:
# os: osx
# osx_image: xcode11
# env: SWIFT_VERSION="5.1" OPENCOMBINE_COMPATIBILITY_TEST="YES"
- name: "macOS 10.14 | Swift 5.0 | SwiftLint"
- name: "macOS 10.14 | Swift 5.0 | Code Quality"
os: osx
osx_image: xcode10.2
env: SWIFT_VERSION="5.0" SWIFT_LINT="YES"
env: SWIFT_VERSION="5.0" RUN_DANGER="YES" SWIFT_LINT="YES"
before_cache:
- brew cleanup
before_install:
- if [[ $TRAVIS_OS_NAME == "linux" ]]; then
cat /proc/cpuinfo;
@@ -38,21 +52,36 @@ install:
fi
script:
- if [[ $OPENCOMBINE_TEST == "YES" ]]; then
swift test -c debug --enable-code-coverage --sanitize thread;
if [[ $TRAVIS_OS_NAME == "linux" ]]; then
make SWIFT_TEST_FLAGS="--enable-test-discovery --enable-index-store" test-debug;
else
make test-debug;
fi
fi
- if [[ $OPENCOMBINE_TEST == "YES" ]]; then
swift test -c release;
if [[ $TRAVIS_OS_NAME == "linux" ]]; then
make SWIFT_TEST_FLAGS="--enable-test-discovery --enable-index-store" test-release;
else
make test-release;
fi
fi
- if [[ $OPENCOMBINE_COMPATIBILITY_TEST == "YES" ]]; then
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;
make generate-compatibility-xcodeproj;
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";
fi
- if [[ $RUN_DANGER == "YES" ]]; then
danger-swift ci;
fi
after_success:
- if [[ $CODE_COVERAGE == "YES" ]]; then
swift package generate-xcodeproj --enable-code-coverage;
make generate-xcodeproj;
xcodebuild -scheme OpenCombine-Package build test | xcpretty;
bash <(curl -s https://codecov.io/bash);
fi
+12
View File
@@ -0,0 +1,12 @@
import Danger
let danger = Danger()
SwiftLint.lint(inline: true,
configFile: ".swiftlint.yml",
strict: true,
lintAllFiles: true)
if danger.warnings.isEmpty, danger.fails.isEmpty {
markdown("LGTM")
}
+38
View File
@@ -0,0 +1,38 @@
SWIFT_TEST_FLAGS=
debug:
swift build -c debug
release:
swift build -c release
test-debug:
swift test -c debug --sanitize thread $(SWIFT_TEST_FLAGS)
test-release:
swift test -c release $(SWIFT_TEST_FLAGS)
swift-version:
swift -version
test-compatibility:
swift test -Xswiftc -DOPENCOMBINE_COMPATIBILITY_TEST
generate-compatibility-xcodeproj:
swift package generate-xcodeproj --xcconfig-overrides Combine-Compatibility.xcconfig; \
open OpenCombine.xcodeproj
generate-xcodeproj:
swift package generate-xcodeproj --enable-code-coverage
clean:
rm -rf .build
.PHONY: debug release \
test-debug \
test-release \
swift-version \
test-compatibility-debug \
generate-compatibility-xcodeproj \
generate-xcodeproj \
clean
+2 -1
View File
@@ -13,6 +13,7 @@ let package = Package(
targets: [
.target(name: "OpenCombine"),
.testTarget(name: "OpenCombineTests",
dependencies: ["OpenCombine", "GottaGoFast"])
dependencies: ["OpenCombine", "GottaGoFast"],
swiftSettings: [.unsafeFlags(["-enable-testing"])])
]
)
+1
View File
@@ -3,6 +3,7 @@
[![codecov](https://codecov.io/gh/broadwaylamb/OpenCombine/branch/master/graph/badge.svg)](https://codecov.io/gh/broadwaylamb/OpenCombine)
![Language](https://img.shields.io/badge/Swift-5.0-orange.svg)
![Platform](https://img.shields.io/badge/platform-Linux%20%7C%20macOS%20%7C%20iOS%20%7C%20watchOS%20%7C%20tvOS-lightgrey.svg)
[<img src="https://img.shields.io/badge/slack-OpenCombine-yellow.svg?logo=slack">](https://join.slack.com/t/opencombine/shared_invite/enQtNzE2MjE5NzkxODI0LTYxMjkzNDUxZWViZWI1Njc2YjBhODgxNjRjOTdkZTcxOGU2ZjJjZjYxMGI3NWZkN2RkNGFmZTUzNmU3MGE2ZWM)
Open-source implementation of Apple's [Combine](https://developer.apple.com/documentation/combine) framework for processing values over time.
-442
View File
@@ -115,82 +115,6 @@ extension Publisher {
public func measureInterval<S>(using scheduler: S, options: S.SchedulerOptions? = nil) -> Publishers.MeasureInterval<Self, S> where S : Scheduler
}
extension Publishers {
/// A publisher that republishes all elements that match a provided closure.
public struct Filter<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 from which this publisher receives elements.
public let upstream: Upstream
/// A closure that indicates whether to republish an element.
public let isIncluded: (Upstream.Output) -> Bool
public init(upstream: Upstream, isIncluded: @escaping (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 all elements that match a provided error-throwing closure.
public struct TryFilter<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 from which this publisher receives elements.
public let upstream: Upstream
/// A error-throwing closure that indicates whether to republish an element.
public let isIncluded: (Upstream.Output) throws -> Bool
public init(upstream: Upstream, isIncluded: @escaping (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.TryFilter<Upstream>.Failure
}
}
extension Publisher {
/// Republishes all elements that match a provided closure.
///
/// - Parameter isIncluded: A closure that takes one element and returns a Boolean value indicating whether to republish the element.
/// - Returns: A publisher that republishes all elements that satisfy the closure.
public func filter(_ isIncluded: @escaping (Self.Output) -> Bool) -> Publishers.Filter<Self>
/// Republishes all elements that match a provided error-throwing closure.
///
/// If the `isIncluded` closure throws an error, the publisher fails with that error.
///
/// - Parameter isIncluded: A closure that takes one element and returns a Boolean value indicating whether to republish the element.
/// - Returns: A publisher that republishes all elements that satisfy the closure.
public func tryFilter(_ isIncluded: @escaping (Self.Output) throws -> Bool) -> Publishers.TryFilter<Self>
}
extension Publishers {
/// A publisher that raises a debugger signal when a provided closure needs to stop the process in the debugger.
@@ -1164,77 +1088,6 @@ extension Publisher {
public func tryReduce<T>(_ initialResult: T, _ nextPartialResult: @escaping (T, Self.Output) throws -> T) -> Publishers.TryReduce<Self, T>
}
extension Publishers {
/// A publisher that republishes all non-`nil` results of calling a closure with each received element.
public struct CompactMap<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
/// A closure that receives values from the upstream publisher and returns optional values.
public let transform: (Upstream.Output) -> Output?
public init(upstream: Upstream, transform: @escaping (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 republishes all non-`nil` results of calling an error-throwing closure with each received element.
public struct TryCompactMap<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 = Error
/// The publisher from which this publisher receives elements.
public let upstream: Upstream
/// An error-throwing closure that receives values from the upstream publisher and returns optional values.
///
/// If this closure throws an error, the publisher fails.
public let transform: (Upstream.Output) throws -> Output?
public init(upstream: Upstream, transform: @escaping (Upstream.Output) throws -> 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, S.Failure == Publishers.TryCompactMap<Upstream, Output>.Failure
}
}
extension Publisher {
/// Calls a closure with each received element and publishes any returned optional that has a value.
///
/// - Parameter transform: A closure that receives a value and returns an optional value.
/// - Returns: A publisher that republishes all non-`nil` results of calling the transform closure.
public func compactMap<T>(_ transform: @escaping (Self.Output) -> T?) -> Publishers.CompactMap<Self, T>
/// Calls an error-throwing closure with each received element and publishes any returned optional that has a value.
///
/// If the closure throws an error, the publisher cancels the upstream and sends the thrown error to the downstream receiver as a `Failure`.
/// - Parameter transform: an error-throwing closure that receives a value and returns an optional value.
/// - Returns: A publisher that republishes all non-`nil` results of calling the transform closure.
public func tryCompactMap<T>(_ transform: @escaping (Self.Output) throws -> T?) -> Publishers.TryCompactMap<Self, T>
}
extension Publishers {
/// A publisher created by applying the merge function to two upstream publishers.
@@ -1777,43 +1630,6 @@ extension Publisher {
public func tryLast(where predicate: @escaping (Self.Output) throws -> Bool) -> Publishers.TryLastWhere<Self>
}
extension Publishers {
/// A publisher that ignores all upstream elements, but passes along a completion state (finish or failed).
public struct IgnoreOutput<Upstream> : Publisher where Upstream : Publisher {
/// The kind of values published by this publisher.
public typealias Output = Never
/// 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.IgnoreOutput<Upstream>.Output
}
}
extension Publisher {
/// Ingores all upstream elements, but passes along a completion state (finished or failed).
///
/// The output type of this publisher is `Never`.
/// - Returns: A publisher that ignores all upstream elements.
public func ignoreOutput() -> Publishers.IgnoreOutput<Self>
}
extension Publishers {
/// A publisher that flattens nested publishers.
@@ -2147,45 +1963,10 @@ extension Publishers {
/// 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 replaces any errors in the stream with a provided element.
public struct ReplaceError<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 = Never
/// The element with which to replace errors from the upstream publisher.
public let output: Upstream.Output
/// The publisher from which this publisher receives elements.
public let upstream: Upstream
public init(upstream: Upstream, output: Publishers.ReplaceError<Upstream>.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 S : Subscriber, Upstream.Output == S.Input, S.Failure == Publishers.ReplaceError<Upstream>.Failure
}
}
extension Publisher {
/// Replaces any errors in the stream with the provided element.
///
/// If the upstream publisher fails with an error, this publisher emits the provided element, then finishes normally.
/// - Parameter output: An element to emit when the upstream publisher fails.
/// - Returns: A publisher that replaces an error from the upstream publisher with the provided output element.
public func replaceError(with output: Self.Output) -> Publishers.ReplaceError<Self>
/// Replaces an empty stream with the provided element.
///
/// If the upstream publisher finishes without producing any elements, this publisher emits the provided element, then finishes normally.
@@ -2952,51 +2733,6 @@ extension Publisher {
public func tryCatch<P>(_ handler: @escaping (Self.Failure) throws -> P) -> Publishers.TryCatch<Self, P> where P : Publisher, Self.Output == P.Output
}
extension Publishers {
public struct FlatMap<NewPublisher, Upstream> : Publisher where NewPublisher : Publisher, Upstream : Publisher, NewPublisher.Failure == Upstream.Failure {
/// The kind of values published by this publisher.
public typealias Output = NewPublisher.Output
/// The kind of errors this publisher might publish.
///
/// Use `Never` if this `Publisher` does not publish errors.
public typealias Failure = Upstream.Failure
public let upstream: Upstream
public let maxPublishers: Subscribers.Demand
public let transform: (Upstream.Output) -> NewPublisher
public init(upstream: Upstream, maxPublishers: Subscribers.Demand, transform: @escaping (Upstream.Output) -> NewPublisher)
/// 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, NewPublisher.Output == S.Input, Upstream.Failure == S.Failure
}
}
extension Publisher {
/// Transforms all elements from an upstream publisher into a new or existing publisher.
///
/// `flatMap` merges the output from all returned publishers into a single stream of output.
///
/// - Parameters:
/// - maxPublishers: The maximum number of publishers produced by this method.
/// - transform: A closure that takes an element as a parameter and returns a publisher
/// that produces elements of that type.
/// - Returns: A publisher that transforms elements from an upstream publisher into
/// a publisher of that elements type.
public func flatMap<T, P>(maxPublishers: Subscribers.Demand = .unlimited, _ transform: @escaping (Self.Output) -> P) -> Publishers.FlatMap<P, Self> where T == P.Output, P : Publisher, Self.Failure == P.Failure
}
extension Publishers {
/// A publisher that delays delivery of elements and completion to the downstream receiver.
@@ -3089,127 +2825,6 @@ extension Publisher {
public func dropFirst(_ count: Int = 1) -> Publishers.Drop<Self>
}
extension Publishers {
/// A publisher that publishes the first element of a stream, then finishes.
public struct First<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 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, Upstream.Output == S.Input
}
/// A publisher that only publishes the first element of a stream to satisfy a predicate closure.
public struct FirstWhere<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 from which this publisher receives elements.
public let upstream: Upstream
/// The closure that determines whether to publish an element.
public let predicate: (Upstream.Output) -> Bool
public init(upstream: Upstream, predicate: @escaping (Publishers.FirstWhere<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 only publishes the first element of a stream to satisfy a throwing predicate closure.
public struct TryFirstWhere<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 from which this publisher receives elements.
public let upstream: Upstream
/// The error-throwing closure that determines whether to publish an element.
public let predicate: (Upstream.Output) throws -> Bool
public init(upstream: Upstream, predicate: @escaping (Publishers.TryFirstWhere<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.TryFirstWhere<Upstream>.Failure
}
}
extension Publisher {
/// Publishes the first element of a stream, then finishes.
///
/// If this publisher doesnt receive any elements, it finishes without publishing.
/// - Returns: A publisher that only publishes the first element of a stream.
public func first() -> Publishers.First<Self>
/// Publishes the first element of a stream to satisfy a predicate closure, then finishes.
///
/// The publisher ignores all elements after the first. If this publisher doesnt receive any elements, it finishes without publishing.
/// - Parameter predicate: A closure that takes an element as a parameter and returns a Boolean value that indicates whether to publish the element.
/// - Returns: A publisher that only publishes the first element of a stream that satifies the predicate.
public func first(where predicate: @escaping (Self.Output) -> Bool) -> Publishers.FirstWhere<Self>
/// Publishes the first element of a stream to satisfy a throwing predicate closure, then finishes.
///
/// The publisher ignores all elements after the first. If this publisher doesnt receive any elements, it finishes without publishing. If the predicate closure throws, the publisher fails with an error.
/// - Parameter predicate: A closure that takes an element as a parameter and returns a Boolean value that indicates whether to publish the element.
/// - Returns: A publisher that only publishes the first element of a stream that satifies the predicate.
public func tryFirst(where predicate: @escaping (Self.Output) throws -> Bool) -> Publishers.TryFirstWhere<Self>
}
extension Publishers.Filter {
public func filter(_ isIncluded: @escaping (Publishers.Filter<Upstream>.Output) -> Bool) -> Publishers.Filter<Upstream>
public func tryFilter(_ isIncluded: @escaping (Publishers.Filter<Upstream>.Output) throws -> Bool) -> Publishers.TryFilter<Upstream>
}
extension Publishers.TryFilter {
public func filter(_ isIncluded: @escaping (Publishers.TryFilter<Upstream>.Output) -> Bool) -> Publishers.TryFilter<Upstream>
public func tryFilter(_ isIncluded: @escaping (Publishers.TryFilter<Upstream>.Output) throws -> Bool) -> Publishers.TryFilter<Upstream>
}
extension Just {
public func prepend(_ elements: Output...) -> Publishers.Sequence<[Output], Just<Output>.Failure>
@@ -3295,18 +2910,6 @@ extension Publishers.CollectByCount : Equatable where Upstream : Equatable {
public static func == (lhs: Publishers.CollectByCount<Upstream>, rhs: Publishers.CollectByCount<Upstream>) -> Bool
}
extension Publishers.CompactMap {
public func compactMap<T>(_ transform: @escaping (Output) -> T?) -> Publishers.CompactMap<Upstream, T>
public func map<T>(_ transform: @escaping (Output) -> T) -> Publishers.CompactMap<Upstream, T>
}
extension Publishers.TryCompactMap {
public func compactMap<T>(_ transform: @escaping (Output) throws -> T?) -> Publishers.TryCompactMap<Upstream, T>
}
extension Publishers.Merge : Equatable where A : Equatable, B : Equatable {
/// Returns a Boolean value that indicates whether two publishers are equivalent.
@@ -3410,17 +3013,6 @@ extension Publishers.Count : Equatable where Upstream : Equatable {
public static func == (lhs: Publishers.Count<Upstream>, rhs: Publishers.Count<Upstream>) -> Bool
}
extension Publishers.IgnoreOutput : Equatable where Upstream : Equatable {
/// Returns a Boolean value that indicates whether two publishers are equivalent.
///
/// - Parameters:
/// - lhs: An ignore output publisher to compare for equality.
/// - rhs: Another ignore output publisher to compare for equality.
/// - Returns: `true` if the two publishers have equal upstream publishers, `false` otherwise.
public static func == (lhs: Publishers.IgnoreOutput<Upstream>, rhs: Publishers.IgnoreOutput<Upstream>) -> Bool
}
extension Publishers.Retry : Equatable where Upstream : Equatable {
/// Returns a Boolean value indicating whether two values are equal.
@@ -3575,40 +3167,6 @@ extension Publishers.First : Equatable where Upstream : Equatable {
public static func == (lhs: Publishers.First<Upstream>, rhs: Publishers.First<Upstream>) -> Bool
}
/// 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)
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 {
public typealias Output = Value
public typealias Failure = Never
/// 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 Value == S.Input, S : Subscriber, S.Failure == Published<Value>.Publisher.Failure
}
/// 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`
+1
View File
@@ -249,6 +249,7 @@ internal final class SubjectSubscriber<Downstream: Subject>
internal func receive(completion: Subscribers.Completion<Downstream.Failure>) {
downstreamSubject?.send(completion: completion)
downstreamSubject = nil
}
internal var description: String { return "Subject" }
@@ -14,6 +14,8 @@ public final class CurrentValueSubject<Output, Failure: Error>: Subject {
// TODO: Combine uses bag data structure
private var _subscriptions: [Conduit] = []
private var _value: Output
private var _completion: Subscribers.Completion<Failure>?
internal var upstreamSubscriptions: [Subscription] = []
@@ -22,8 +24,11 @@ public final class CurrentValueSubject<Output, Failure: Error>: Subject {
/// The value wrapped by this subject, published as a new element whenever it changes.
public var value: Output {
didSet {
send(value)
get {
return _value
}
set {
send(newValue)
}
}
@@ -31,7 +36,7 @@ public final class CurrentValueSubject<Output, Failure: Error>: Subject {
///
/// - Parameter value: The initial value to publish.
public init(_ value: Output) {
self.value = value
self._value = value
}
deinit {
@@ -68,6 +73,7 @@ public final class CurrentValueSubject<Output, Failure: Error>: Subject {
public func send(_ input: Output) {
_lock.do {
_value = input
for subscription in _subscriptions where !subscription.isCompleted {
if subscription._demand > 0 {
subscription._offer(input)
+3 -6
View File
@@ -139,9 +139,7 @@ public struct ImmediateScheduler: Scheduler {
tolerance: SchedulerTimeType.Stride,
options: SchedulerOptions?,
_ action: @escaping () -> Void) {
fatalError(
"Attempt to schedule something in the future on the immediate scheduler"
)
action()
}
/// Performs the action at some time after the specified date, at the specified
@@ -151,8 +149,7 @@ public struct ImmediateScheduler: Scheduler {
tolerance: SchedulerTimeType.Stride,
options: SchedulerOptions?,
_ action: @escaping () -> Void) -> Cancellable {
fatalError(
"Attempt to schedule something in the future on the immediate scheduler"
)
action()
return Subscriptions.empty
}
}
+97
View File
@@ -0,0 +1,97 @@
//
// Published.swift
// OpenCombine
//
// Created by Евгений Богомолов on 01/09/2019.
//
#if swift(>=5.1)
/// 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.
@propertyWrapper public struct Published<Value> {
/// Initialize the storage of the Published
/// property as well as the corresponding `Publisher`.
public init(initialValue: Value) {
value = initialValue
}
@available(*, unavailable)
public init(wrappedValue: Value) {
value = wrappedValue
}
public struct Publisher: OpenCombine.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(_:)`
///
/// - SeeAlso: `subscribe(_:)`
/// - Parameters:
/// - subscriber: The subscriber to attach to this `Publisher`.
/// once attached it can begin to receive values.
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Downstream.Input == Value, Downstream.Failure == Never
{
subject.subscribe(subscriber)
}
fileprivate let subject: OpenCombine.CurrentValueSubject<Value, Never>
fileprivate init(_ output: Output) {
subject = .init(output)
}
}
private var value: Value
/// The property that can be accessed with the
/// `$` syntax and allows access to the `Publisher`
public var projectedValue: Publisher {
mutating get {
if let publisher = publisher {
return publisher
}
let publisher = Publisher(value)
self.publisher = publisher
return publisher
}
}
@available(*, unavailable, message:
"@Published is only available on properties of classes")
public var wrappedValue: Value {
get { value }
set {
value = newValue
publisher?.subject.value = newValue
}
}
private var publisher: Publisher?
@available(*, unavailable, message:
"This subscript is unavailable in OpenCombine yet")
public static subscript<EnclosingSelf: AnyObject>(
_enclosingInstance object: EnclosingSelf,
wrapped wrappedKeyPath: ReferenceWritableKeyPath<EnclosingSelf, Value>,
storage storageKeyPath: ReferenceWritableKeyPath<EnclosingSelf, Published<Value>>
) -> Value {
get { fatalError() }
set { fatalError() }
}
}
#endif
@@ -138,7 +138,7 @@ extension Optional.OCombine.Publisher {
}
public func collect() -> Optional<[Output]>.OCombine.Publisher {
return .init(self.output.map { [$0] })
return .init(self.output.map { [$0] } ?? [])
}
public func compactMap<ElementOfResult>(
@@ -0,0 +1,211 @@
//
// Publishers.CompactMap.swift
//
//
// Created by Sergej Jaskiewicz on 11.07.2019.
//
extension Publishers {
/// A publisher that republishes all non-`nil` results of calling a closure
/// with each received element.
public struct CompactMap<Upstream: Publisher, Output>: Publisher {
public typealias Failure = Upstream.Failure
/// The publisher from which this publisher receives elements.
public let upstream: Upstream
/// A closure that receives values from the upstream publisher
/// and returns optional values.
public let transform: (Upstream.Output) -> Output?
public init(upstream: Upstream,
transform: @escaping (Upstream.Output) -> Output?) {
self.upstream = upstream
self.transform = transform
}
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Downstream.Input == Output, Downstream.Failure == Failure
{
let inner = Inner(downstream: subscriber, transform: catching(transform))
upstream.subscribe(inner)
}
}
/// A publisher that republishes all non-`nil` results of calling an error-throwing
/// closure with each received element.
public struct TryCompactMap<Upstream: Publisher, Output>: Publisher {
public typealias Failure = Error
/// The publisher from which this publisher receives elements.
public let upstream: Upstream
/// An error-throwing closure that receives values from the upstream publisher
/// and returns optional values.
///
/// If this closure throws an error, the publisher fails.
public let transform: (Upstream.Output) throws -> Output?
public init(upstream: Upstream,
transform: @escaping (Upstream.Output) throws -> Output?) {
self.upstream = upstream
self.transform = transform
}
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Downstream.Input == Output, Downstream.Failure == Failure
{
let inner = Inner(downstream: subscriber, transform: catching(transform))
upstream.subscribe(inner)
}
}
}
extension Publisher {
/// Calls a closure with each received element and publishes any returned
/// optional that has a value.
///
/// - Parameter transform: A closure that receives a value and returns
/// an optional value.
/// - Returns: A publisher that republishes all non-`nil` results of calling
/// the transform closure.
public func compactMap<ElementOfResult>(
_ transform: @escaping (Output) -> ElementOfResult?
) -> Publishers.CompactMap<Self, ElementOfResult> {
return .init(upstream: self, transform: transform)
}
/// Calls an error-throwing closure with each received element and publishes
/// any returned optional that has a value.
///
/// If the closure throws an error, the publisher cancels the upstream and sends
/// the thrown error to the downstream receiver as a `Failure`.
///
/// - Parameter transform: an error-throwing closure that receives a value
/// and returns an optional value.
/// - Returns: A publisher that republishes all non-`nil` results of calling
/// the `transform` closure.
public func tryCompactMap<ElementOfResult>(
_ transform: @escaping (Output) throws -> ElementOfResult?
) -> Publishers.TryCompactMap<Self, ElementOfResult> {
return .init(upstream: self, transform: transform)
}
}
extension Publishers.CompactMap {
public func compactMap<ElementOfResult>(
_ transform: @escaping (Output) -> ElementOfResult?
) -> Publishers.CompactMap<Upstream, ElementOfResult> {
return .init(upstream: upstream,
transform: { self.transform($0).flatMap(transform) })
}
public func map<ElementOfResult>(
_ transform: @escaping (Output) -> ElementOfResult
) -> Publishers.CompactMap<Upstream, ElementOfResult> {
return .init(upstream: upstream,
transform: { self.transform($0).map(transform) })
}
}
extension Publishers.TryCompactMap {
public func compactMap<ElementOfResult>(
_ transform: @escaping (Output) throws -> ElementOfResult?
) -> Publishers.TryCompactMap<Upstream, ElementOfResult> {
return .init(upstream: upstream,
transform: { try self.transform($0).flatMap(transform) })
}
}
private class _CompactMap<Upstream: Publisher, Downstream: Subscriber>
: OperatorSubscription<Downstream>,
Subscription
{
typealias Input = Upstream.Output
typealias Failure = Upstream.Failure
typealias Transform = (Input) -> Result<Downstream.Input?, Downstream.Failure>
fileprivate var _transform: Transform?
var _isCompleted: Bool {
return _transform == nil
}
init(downstream: Downstream, transform: @escaping Transform) {
_transform = transform
super.init(downstream: downstream)
}
func receive(subscription: Subscription) {
upstreamSubscription = subscription
downstream.receive(subscription: self)
}
func receive(_ input: Input) -> Subscribers.Demand {
guard let transform = _transform else { return .none }
switch transform(input) {
case .success(let output?):
return downstream.receive(output)
case .success(nil):
return .max(1)
case .failure(let error):
downstream.receive(completion: .failure(error))
_transform = nil
return .none
}
}
func request(_ demand: Subscribers.Demand) {
guard !_isCompleted else { return }
upstreamSubscription?.request(demand)
}
override func cancel() {
_transform = nil
upstreamSubscription?.cancel()
upstreamSubscription = nil
}
}
extension Publishers.CompactMap {
private final class Inner<Downstream: Subscriber>
: _CompactMap<Upstream, Downstream>,
Subscriber,
CustomStringConvertible
where Downstream.Failure == Upstream.Failure
{
var description: String { return "CompactMap" }
func receive(completion: Subscribers.Completion<Upstream.Failure>) {
if !_isCompleted {
_transform = nil
downstream.receive(completion: completion)
}
}
}
}
extension Publishers.TryCompactMap {
private final class Inner<Downstream: Subscriber>
: _CompactMap<Upstream, Downstream>,
Subscriber,
CustomStringConvertible
where Downstream.Failure == Error
{
var description: String { return "TryCompactMap" }
func receive(completion: Subscribers.Completion<Upstream.Failure>) {
if !_isCompleted {
_transform = nil
downstream.receive(completion: completion.eraseError())
}
}
}
}
@@ -127,6 +127,7 @@ private class _DropWhile<Upstream: Publisher, Downstream: Subscriber>
override func cancel() {
upstreamSubscription?.cancel()
upstreamSubscription = nil
isCompleted = true
// Don't zero out downstream, that's what Combine does (probably a bug)
}
}
@@ -142,11 +143,9 @@ extension Publishers.DropWhile {
var description: String { return "DropWhile" }
func receive(completion: Subscribers.Completion<Failure>) {
guard !isCompleted else {
assertionFailure("unreachable")
return
}
guard !isCompleted else { return }
downstream.receive(completion: completion)
isCompleted = true
}
}
}
@@ -164,6 +163,7 @@ extension Publishers.TryDropWhile {
func receive(completion: Subscribers.Completion<Failure>) {
guard !isCompleted else { return }
downstream.receive(completion: completion.eraseError())
isCompleted = true
}
}
}
@@ -0,0 +1,228 @@
//
// Publishers.Filter.swift
//
//
// Created by Joseph Spadafora on 7/3/19.
//
extension Publisher {
/// Republishes all elements that match a provided closure.
///
/// - Parameter isIncluded: A closure that takes one element and returns
/// a Boolean value indicating whether to republish the element.
/// - Returns: A publisher that republishes all elements that satisfy the closure.
public func filter(
_ isIncluded: @escaping (Output) -> Bool
) -> Publishers.Filter<Self> {
return Publishers.Filter(upstream: self, isIncluded: isIncluded)
}
/// Republishes all elements that match a provided error-throwing closure.
///
/// If the `isIncluded` closure throws an error, the publisher fails with that error.
///
/// - Parameter isIncluded: A closure that takes one element and returns a
/// Boolean value indicating whether to republish the element.
/// - Returns: A publisher that republishes all elements that satisfy the closure.
public func tryFilter(
_ isIncluded: @escaping (Output) throws -> Bool
) -> Publishers.TryFilter<Self> {
return Publishers.TryFilter(upstream: self, isIncluded: isIncluded)
}
}
extension Publishers.Filter {
public func filter(
_ isIncluded: @escaping (Output) -> Bool
) -> Publishers.Filter<Upstream> {
return .init(upstream: upstream) { self.isIncluded($0) && isIncluded($0) }
}
public func tryFilter(
_ isIncluded: @escaping (Output) throws -> Bool
) -> Publishers.TryFilter<Upstream> {
return .init(upstream: upstream) { try self.isIncluded($0) && isIncluded($0) }
}
}
extension Publishers.TryFilter {
public func filter(
_ isIncluded: @escaping (Output) -> Bool
) -> Publishers.TryFilter<Upstream> {
return .init(upstream: upstream) { try self.isIncluded($0) && isIncluded($0) }
}
public func tryFilter(
_ isIncluded: @escaping (Output) throws -> Bool
) -> Publishers.TryFilter<Upstream> {
return .init(upstream: upstream) { try self.isIncluded($0) && isIncluded($0) }
}
}
extension Publishers {
/// A publisher that republishes all elements that match a provided closure.
public struct Filter<Upstream: Publisher>: 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 from which this publisher receives elements.
public let upstream: Upstream
/// A closure that indicates whether to republish an element.
public let isIncluded: (Upstream.Output) -> Bool
public init(upstream: Upstream, isIncluded: @escaping (Output) -> Bool) {
self.upstream = upstream
self.isIncluded = isIncluded
}
/// 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,
Upstream.Output == SubscriberType.Input
{
let filter = Inner(downstream: subscriber, isIncluded: catching(isIncluded))
upstream.receive(subscriber: filter)
}
}
/// A publisher that republishes all elements that match
/// a provided error-throwing closure.
public struct TryFilter<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 from which this publisher receives elements.
public let upstream: Upstream
/// A error-throwing closure that indicates whether to republish an element.
public let isIncluded: (Upstream.Output) throws -> Bool
public init(upstream: Upstream,
isIncluded: @escaping (Upstream.Output) throws -> Bool) {
self.upstream = upstream
self.isIncluded = isIncluded
}
/// 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<Downstream: Subscriber>(subscriber: Downstream)
where Upstream.Output == Downstream.Input,
Downstream.Failure == Failure
{
let filter = Inner(downstream: subscriber, isIncluded: catching(isIncluded))
upstream.receive(subscriber: filter)
}
}
}
private class _Filter<Upstream: Publisher, Downstream: Subscriber>
: OperatorSubscription<Downstream>,
Subscription
where Upstream.Output == Downstream.Input
{
typealias Input = Upstream.Output
typealias Failure = Upstream.Failure
typealias Predicate = (Input) -> Result<Bool, Downstream.Failure>
private var _isIncluded: Predicate?
var isFinished: Bool {
return _isIncluded == nil
}
init(downstream: Downstream, isIncluded: @escaping Predicate) {
_isIncluded = isIncluded
super.init(downstream: downstream)
}
func receive(subscription: Subscription) {
upstreamSubscription = subscription
downstream.receive(subscription: self)
}
func receive(_ input: Input) -> Subscribers.Demand {
guard let isIncluded = _isIncluded else { return .none }
switch isIncluded(input) {
case .success(let isIncluded):
return isIncluded ? downstream.receive(input) : .max(1)
case .failure(let error):
downstream.receive(completion: .failure(error))
cancel()
return .none
}
}
func request(_ demand: Subscribers.Demand) {
guard !isFinished else { return }
upstreamSubscription?.request(demand)
}
override func cancel() {
_isIncluded = nil
upstreamSubscription?.cancel()
upstreamSubscription = nil
}
}
extension Publishers.Filter {
private final class Inner<Downstream: Subscriber>
: _Filter<Upstream, Downstream>,
Subscriber,
CustomStringConvertible
where Upstream.Output == Downstream.Input,
Upstream.Failure == Downstream.Failure {
var description: String { return "Filter" }
func receive(completion: Subscribers.Completion<Failure>) {
guard !isFinished else { return }
downstream.receive(completion: completion)
}
}
}
extension Publishers.TryFilter {
private final class Inner<Downstream: Subscriber>
: _Filter<Upstream, Downstream>,
Subscriber,
CustomStringConvertible
where Upstream.Output == Downstream.Input, Downstream.Failure == Error {
var description: String { return "TryFilter" }
func receive(completion: Subscribers.Completion<Failure>) {
guard !isFinished else { return }
downstream.receive(completion: completion.eraseError())
}
}
}
@@ -0,0 +1,293 @@
//
// Publishers.First.swift
//
//
// Created by Joseph Spadafora on 7/8/19.
//
extension Publisher {
/// Publishes the first element of a stream, then finishes.
///
/// If this publisher doesnt receive any elements, it finishes without publishing.
/// - Returns: A publisher that only publishes the first element of a stream.
public func first() -> Publishers.First<Self> {
return .init(upstream: self)
}
/// Publishes the first element of a stream to
/// satisfy a predicate closure, then finishes.
///
/// The publisher ignores all elements after the first.
/// If this publisher doesnt receive any elements,
/// it finishes without publishing.
/// - Parameter predicate: A closure that takes an element as a parameter and
/// returns a Boolean value that indicates whether to publish the element.
/// - Returns: A publisher that only publishes the first element of a stream
/// that satifies the predicate.
public func first(
where predicate: @escaping (Output) -> Bool
) -> Publishers.FirstWhere<Self> {
return .init(upstream: self, predicate: predicate)
}
/// Publishes the first element of a stream to satisfy a
/// throwing predicate closure, then finishes.
///
/// The publisher ignores all elements after the first. If this publisher
/// doesnt receive any elements, it finishes without publishing. If the
/// predicate closure throws, the publisher fails with an error.
/// - Parameter predicate: A closure that takes an element as a parameter and
/// returns a Boolean value that indicates whether to publish the element.
/// - Returns: A publisher that only publishes the first element of a stream
/// that satifies the predicate.
public func tryFirst(
where predicate: @escaping (Output) throws -> Bool
) -> Publishers.TryFirstWhere<Self> {
return .init(upstream: self, predicate: predicate)
}
}
extension Publishers {
/// A publisher that publishes the first element of a stream, then finishes.
public struct First<Upstream: Publisher>: Publisher {
public typealias Output = Upstream.Output
public typealias Failure = Upstream.Failure
/// The publisher from which this publisher receives elements.
public let upstream: Upstream
public init(upstream: Upstream) {
self.upstream = upstream
}
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Failure == Downstream.Failure,
Output == Downstream.Input
{
let inner = Inner(downstream: subscriber, predicate: { _ in .success(true) })
upstream.receive(subscriber: inner)
}
}
/// A publisher that only publishes the first element of a
/// stream to satisfy a predicate closure.
public struct FirstWhere<Upstream: Publisher>: Publisher {
public typealias Output = Upstream.Output
public typealias Failure = Upstream.Failure
/// The publisher from which this publisher receives elements.
public let upstream: Upstream
/// The closure that determines whether to publish an element.
public let predicate: (Output) -> Bool
public init(upstream: Upstream, predicate: @escaping (Output) -> Bool) {
self.upstream = upstream
self.predicate = predicate
}
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Failure == Downstream.Failure,
Output == Downstream.Input
{
let inner = Inner(downstream: subscriber, predicate: catching(predicate))
upstream.receive(subscriber: inner)
}
}
/// A publisher that only publishes the first element of a stream
/// to satisfy a throwing predicate closure.
public struct TryFirstWhere<Upstream: Publisher>: Publisher {
public typealias Output = Upstream.Output
public typealias Failure = Error
/// The publisher from which this publisher receives elements.
public let upstream: Upstream
/// The error-throwing closure that determines whether to publish an element.
public let predicate: (Output) throws -> Bool
public init(upstream: Upstream, predicate: @escaping (Output) throws -> Bool) {
self.upstream = upstream
self.predicate = predicate
}
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Failure == Downstream.Failure,
Output == Downstream.Input
{
let inner = Inner(downstream: subscriber, predicate: catching(predicate))
upstream.receive(subscriber: inner)
}
}
}
private class _FirstWhere<Upstream: Publisher, Downstream: Subscriber>
: OperatorSubscription<Downstream>,
Subscription
where Downstream.Input == Upstream.Output
{
typealias Input = Upstream.Output
typealias Failure = Upstream.Failure
typealias Predicate = (Input) -> Result<Bool, Downstream.Failure>
//
// .pending(input)
//
// receive(input) request(demand)
//
//
//
//
// .waitingForDemand .finished
//
//
//
//
// request(demand) receive(input)
//
// .downstreamHasRequested
//
enum State {
case waitingForDemand
case pending(Input)
case downstreamHasRequested
case finished
}
var predicate: Predicate?
private var _state: State = .waitingForDemand
var isCompleted: Bool {
return predicate == nil
}
init(downstream: Downstream, predicate: @escaping Predicate) {
self.predicate = predicate
super.init(downstream: downstream)
}
func receive(subscription: Subscription) {
upstreamSubscription = subscription
subscription.request(.unlimited)
downstream.receive(subscription: self)
}
func receive(_ input: Input) -> Subscribers.Demand {
switch _state {
case .pending, .finished:
break
case .downstreamHasRequested:
_ifSatisfiesPredicate(input) {
_state = .finished
_sendDownstream(input)
}
case .waitingForDemand:
_ifSatisfiesPredicate(input) {
_state = .pending(input)
}
}
return .none
}
private func _ifSatisfiesPredicate(_ input: Input, _ onSuccess: () -> Void) {
guard let predicate = self.predicate else { return }
switch predicate(input) {
case .success(true):
onSuccess()
case .success(false):
return
case .failure(let error):
cancel()
downstream.receive(completion: .failure(error))
return
}
}
private func _sendDownstream(_ input: Input) {
_ = downstream.receive(input)
cancel()
downstream.receive(completion: .finished)
}
func request(_ demand: Subscribers.Demand) {
precondition(demand > 0, "demand must not be zero")
switch _state {
case .waitingForDemand:
_state = .downstreamHasRequested
case .pending(let input):
_state = .finished
_sendDownstream(input)
case .finished, .downstreamHasRequested:
break
}
}
override func cancel() {
predicate = nil
upstreamSubscription?.cancel()
upstreamSubscription = nil
}
}
extension Publishers.First {
private final class Inner<Downstream: Subscriber>
: _FirstWhere<Upstream, Downstream>,
Subscriber,
CustomStringConvertible
where Upstream.Output == Downstream.Input,
Upstream.Failure == Downstream.Failure
{
var description: String { return "First" }
func receive(completion: Subscribers.Completion<Failure>) {
guard !isCompleted else { return }
predicate = nil
downstream.receive(completion: completion)
}
}
}
extension Publishers.FirstWhere {
private final class Inner<Downstream: Subscriber>
: _FirstWhere<Upstream, Downstream>,
Subscriber,
CustomStringConvertible
where Upstream.Output == Downstream.Input,
Upstream.Failure == Downstream.Failure
{
var description: String { return "TryFirst" }
func receive(completion: Subscribers.Completion<Failure>) {
guard !isCompleted else { return }
predicate = nil
downstream.receive(completion: completion)
}
}
}
extension Publishers.TryFirstWhere {
private final class Inner<Downstream: Subscriber>
: _FirstWhere<Upstream, Downstream>,
Subscriber,
CustomStringConvertible
where Upstream.Output == Downstream.Input,
Downstream.Failure == Error
{
var description: String { return "TryFirstWhere" }
func receive(completion: Subscribers.Completion<Failure>) {
guard !isCompleted else { return }
predicate = nil
downstream.receive(completion: completion.eraseError())
}
}
}
@@ -0,0 +1,404 @@
//
// Publishers.FlatMap.swift
//
// Created by Eric Patey on 16.08.2019.
//
extension Publisher {
/// Transforms all elements from an upstream publisher into a new or existing
/// publisher.
///
/// `flatMap` merges the output from all returned publishers into a single stream of
/// output.
///
/// - Parameters:
/// - maxPublishers: The maximum number of publishers produced by this method.
/// - transform: A closure that takes an element as a parameter and returns a
/// publisher that produces elements of that type.
/// - Returns: A publisher that transforms elements from an upstream publisher into
/// a publisher of that elements type.
public func flatMap<Result, Child>(maxPublishers: Subscribers.Demand = .unlimited,
_ transform: @escaping (Self.Output) -> Child)
-> Publishers.FlatMap<Child, Self>
where Result == Child.Output, Child: Publisher, Self.Failure == Child.Failure {
return Publishers.FlatMap(upstream: self,
maxPublishers: maxPublishers,
transform: transform)
}
}
extension Publishers {
public struct FlatMap<Child, Upstream>: Publisher
where Child: Publisher, Upstream: Publisher, Child.Failure == Upstream.Failure {
/// The kind of values published by this publisher.
public typealias Output = Child.Output
/// The kind of errors this publisher might publish.
///
/// Use `Never` if this `Publisher` does not publish errors.
public typealias Failure = Upstream.Failure
public let upstream: Upstream
public let maxPublishers: Subscribers.Demand
public let transform: (Upstream.Output) -> Child
public init(upstream: Upstream, maxPublishers: Subscribers.Demand,
transform: @escaping (Upstream.Output) -> Child) {
self.upstream = upstream
self.maxPublishers = maxPublishers
self.transform = transform
}
/// 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<Downstream>(subscriber: Downstream)
where Downstream: Subscriber,
Child.Output == Downstream.Input,
Upstream.Failure == Downstream.Failure {
let inner = Inner(downstream: subscriber,
maxPublishers: maxPublishers,
transform: transform)
upstream.subscribe(inner)
}
}
}
extension Publishers.FlatMap {
fileprivate final class Inner<Downstream: Subscriber>
: CustomStringConvertible,
Cancellable
where Downstream.Input == Child.Output, Downstream.Failure == Upstream.Failure {
typealias Input = Upstream.Output
typealias Failure = Upstream.Failure
private typealias PendingValue = (
value: Downstream.Input,
// If the value was buffered at the time it became available, and the child's
// demand was left at `.none` we keep track of the child in `pausedChild` so
// that we can demand some more of it after sending this value.
pausedChild: ChildSubscriber?)
private let lock = Lock(recursive: false)
private let maxPublishers: Subscribers.Demand
private let transform: (Upstream.Output) -> Child
// Locking rules for this class.
// - All mutable state must only be accessed while `lock` is held.
// - In order to avoid any deadlock potential, it is absolutely forbidden to have
// any sort of call out from this class while the lock is held. This is why
// the draining of the work queue uses a relatively complex pattern.
private var downstream: Downstream?
private var childSubscribers = Set<ChildSubscriber>()
private var downstreamDemand = Subscribers.Demand.unlimited
private var valuesToSend = [PendingValue]()
private var queueIsBeingProcessed = false
private var sendFinishedAfterDrainingQueue = false
private var upstreamSubscription: Subscription?
var description: String { return "FlatMap" }
init(downstream: Downstream,
maxPublishers: Subscribers.Demand,
transform: @escaping (Upstream.Output) -> Child) {
self.downstream = downstream
self.maxPublishers = maxPublishers
self.transform = transform
}
final func cancel() {
let (upstreamToCancel, childrenToCancel) = lock.do { ()
-> (Subscription?, Set<ChildSubscriber>) in
let upstreamToCancel = upstreamSubscription
upstreamSubscription = nil
return (upstreamToCancel, lockedDeactivateAndReturnChildToCancel())
}
upstreamToCancel?.cancel()
cancelChildren(childrenToCancel)
}
}
}
// Private implementation
extension Publishers.FlatMap.Inner {
private func deactivate() {
cancelChildren(lock.do(lockedDeactivateAndReturnChildToCancel))
}
// Must be called with lock held.
private func lockedDeactivateAndReturnChildToCancel() -> Set<ChildSubscriber> {
downstream = nil
downstreamDemand = .none
let result = childSubscribers
childSubscribers.removeAll()
upstreamSubscription = nil
return result
}
private func cancelChildren(_ childrenToCancel: Set<ChildSubscriber>) {
childrenToCancel.forEach { $0.cancel() }
}
/// In a thread-safe way, this function performs the passed in work with the lock held
/// and then checks to see if either upstream or any of the child subscriptions remain
/// active. If there are no remaining active subscriptions, it enqueues the sending
/// of `.finished` downstream using the processing queue.
/// - Parameter lockedWork: block to be formed with the lock held.
private final func maybeSendFinishedAfterExecutingWork(lockedWork: () -> Void) {
let shouldProcessQueue: Bool = lock.do {
lockedWork()
if childSubscribers.isEmpty && upstreamSubscription == nil {
sendFinishedAfterDrainingQueue = true
if !queueIsBeingProcessed {
queueIsBeingProcessed = true
return true
}
}
return false
}
if shouldProcessQueue {
processQueue()
}
}
private func receivedCompletion(_ completion: Subscribers.Completion<Failure>,
fromChild child: ChildSubscriber) {
switch completion {
case .finished:
removeActiveSubscription(forChild: child)
case .failure:
downstream?.receive(completion: completion)
deactivate()
}
}
private func removeActiveSubscription(forChild child: ChildSubscriber) {
maybeSendFinishedAfterExecutingWork { childSubscribers.remove(child) }
}
private func receivedValue(_ value: Child.Output,
fromChild child: ChildSubscriber) -> Subscribers.Demand {
// When receiving a value from a child, we need to determine what additional
// demand to return to the child. Apple's logic for this determination is as
// follows:
// - If we are in `.unlimited` mode, we always request `.none` additional
// else
// - If there is a surplus relative to the demand, we request `.none`
// else
// - There is not yet a surplus, so request `.max(1)` more from the child
let (surplusAvailable, processTheQueue): (Bool, Bool) = lock.do {
// If we already have enough values to satisfy the demand, we "buffer" this
// child value establishing a surplus.
if downstreamDemand <= valuesToSend.count {
valuesToSend.append((value, child))
return (surplusAvailable: true, processTheQueue: false)
} else {
valuesToSend.append((value, nil))
if queueIsBeingProcessed {
return (surplusAvailable: false, processTheQueue: false)
}
queueIsBeingProcessed = true
return (surplusAvailable: false, processTheQueue: true)
}
}
let demandResult = surplusAvailable || demandForChild() == .unlimited
? Subscribers.Demand.none : Subscribers.Demand.max(1)
if processTheQueue {
processQueue()
}
return demandResult
}
private func demandForChild() -> Subscribers.Demand {
return self.downstreamDemand == .unlimited ? .unlimited : .max(1)
}
private enum QueueWorkStatus {
case noWork
case sendFinish
case sendValues(values: ArraySlice<PendingValue>)
}
private func processQueue() {
assert(queueIsBeingProcessed)
// We loop processing the queue in case somebody put stuff on the queue while we
// were sending values with the lock unlocked.
while true {
let work: QueueWorkStatus = lock.do {
if downstreamDemand == .none || valuesToSend.isEmpty {
if sendFinishedAfterDrainingQueue && valuesToSend.isEmpty {
return .sendFinish
} else {
queueIsBeingProcessed = false
return .noWork
}
}
let countToSend = min(valuesToSend.count, downstreamDemand.max ?? .max)
let result = valuesToSend[0..<countToSend]
// TODO: Consider an alternative storage to avoid O(n) removeFirst
valuesToSend.removeFirst(countToSend)
downstreamDemand -= countToSend
return .sendValues(values: result)
}
guard let downstream = downstream else { return }
switch work {
case .noWork:
return
case .sendFinish:
downstream.receive(completion: .finished)
deactivate()
return
case .sendValues(let values):
var newDemand = Subscribers.Demand.none
values.forEach {
newDemand += downstream.receive($0.value)
// pausedChild is present only if the value was buffered and the
// child's demand was left at `.none`. In that case, once we send the
// buffered value, we need to tell the child to get another value.
$0.pausedChild?.request(.max(1))
}
if newDemand != .none {
lock.do { downstreamDemand += newDemand }
}
}
}
}
}
// This `Subscriber` implementation is for `FlatMap`'s upstream subscription
extension Publishers.FlatMap.Inner: Subscriber {
// FlatMap received the upstream subscription
fileprivate func receive(subscription: Subscription) {
upstreamSubscription = subscription
downstream?.receive(subscription: self)
subscription.request(maxPublishers)
}
/// Receive a new value from the upstream subscription. A new child subscription
/// will be made on the `Child` that the input value is transformed into.
/// - Parameter input: a value to be transformed by `transform`
fileprivate func receive(_ input: Input) -> Subscribers.Demand {
let newChildSubscriber = ChildSubscriber(parent: self)
lock.do { _ = childSubscribers.insert(newChildSubscriber) }
self.transform(input).subscribe(newChildSubscriber)
return .none
}
// Upstream subscription completed
fileprivate func receive(completion: Subscribers.Completion<Failure>) {
switch completion {
case .finished:
maybeSendFinishedAfterExecutingWork { upstreamSubscription = nil }
case .failure:
downstream?.receive(completion: completion)
deactivate()
}
}
}
// Inner is the `Subscription` for `Downstream`
extension Publishers.FlatMap.Inner: Subscription {
fileprivate func request(_ demand: Subscribers.Demand) {
let (drainTheQueue, becameUnlimited) = lock.do { () -> (Bool, Bool) in
let becameUnlimited =
(demand == .unlimited) && (downstreamDemand != .unlimited)
downstreamDemand = demand
defer { queueIsBeingProcessed = true }
return (!queueIsBeingProcessed, becameUnlimited)
}
if becameUnlimited {
// TODO: This code isn't yet thread safe. The correct change is to do this
// through the queue just like sending values and finished. Finished is
// done through the queue as a bit of a hack. The right design is to have
// an enum of actions on the queue. That enum will include (send value,
// send finished, set child demand).
let newChildDemand = demandForChild()
childSubscribers.forEach { $0.request(newChildDemand) }
}
if drainTheQueue {
processQueue()
}
}
}
extension Publishers.FlatMap.Inner {
/// ChildSubscriber is needed to help implement the backpressure/demand strategy.
/// Specifically, a custom subscriber is needed to manage the demand of the child
/// subscription:
/// - Send .max(1) request when the subscription is received
/// - Send .max(1) request when downstream subscriber demands more and a previously
/// buffered value from the child was sent. (When the value was buffered, the
/// child's demand reached .none - effectively pausing the child.)
fileprivate final class ChildSubscriber: Hashable {
internal typealias Input = Downstream.Input
internal typealias Failure = Downstream.Failure
private var _upstreamSubscription: Subscription?
private unowned let _parent: Publishers.FlatMap<Child, Upstream>.Inner<Downstream>
init(parent: Publishers.FlatMap<Child, Upstream>.Inner<Downstream>) {
_parent = parent
}
fileprivate func request(_ demand: Subscribers.Demand) {
_upstreamSubscription?.request(demand)
}
public static func == (lhs: ChildSubscriber, rhs: ChildSubscriber) -> Bool {
return ObjectIdentifier(lhs) == ObjectIdentifier(rhs)
}
public func hash(into hasher: inout Hasher) {
hasher.combine(ObjectIdentifier(self))
}
}
}
extension Publishers.FlatMap.Inner.ChildSubscriber: Cancellable {
internal func cancel() {
_upstreamSubscription?.cancel()
_upstreamSubscription = nil
}
}
extension Publishers.FlatMap.Inner.ChildSubscriber: Subscriber {
internal func receive(subscription: Subscription) {
if _upstreamSubscription == nil {
_upstreamSubscription = subscription
subscription.request(_parent.demandForChild())
} else {
assertionFailure()
subscription.cancel()
}
}
fileprivate func receive(_ input: Input) -> Subscribers.Demand {
return _parent.receivedValue(input, fromChild: self)
}
fileprivate func receive(completion: Subscribers.Completion<Failure>) {
_parent.receivedCompletion(completion, fromChild: self)
}
}
@@ -0,0 +1,91 @@
//
// Publishers.IgnoreOutput.swift
//
// Created by Eric Patey on 16.08.2019.
//
extension Publisher {
/// Ingores all upstream elements, but passes along a completion
/// state (finished or failed).
///
/// The output type of this publisher is `Never`.
/// - Returns: A publisher that ignores all upstream elements.
public func ignoreOutput() -> Publishers.IgnoreOutput<Self> {
return Publishers.IgnoreOutput(upstream: self)
}
}
extension Publishers {
/// A publisher that ignores all upstream elements, but passes along a completion
/// state (finish or failed).
public struct IgnoreOutput<Upstream: Publisher>: Publisher {
/// The kind of values published by this publisher.
public typealias Output = Never
/// 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<Downstream: Subscriber>(subscriber: Downstream)
where Downstream.Failure == Upstream.Failure, Downstream.Input == Never {
let inner = Inner<Downstream>(downstream: subscriber)
upstream.subscribe(inner)
}
}
}
extension Publishers.IgnoreOutput {
private final class Inner<Downstream: Subscriber>
: OperatorSubscription<Downstream>,
Subscriber,
CustomStringConvertible,
Subscription
where Downstream.Input == Never,
Downstream.Failure == Upstream.Failure
{
typealias Input = Upstream.Output
typealias Output = Never
typealias Failure = Upstream.Failure
var description: String { return "IgnoreOutput" }
func receive(subscription: Subscription) {
upstreamSubscription = subscription
downstream.receive(subscription: self)
subscription.request(.unlimited)
}
func receive(_ input: Input) -> Subscribers.Demand {
return .none
}
func receive(completion: Subscribers.Completion<Failure>) {
downstream.receive(completion: completion)
}
func request(_ demand: Subscribers.Demand) {
// ignore and requests from downstream since we'll never send
// any values
}
}
}
extension Publishers.IgnoreOutput: Equatable where Upstream: Equatable {}
@@ -39,7 +39,7 @@ extension Publisher {
extension Publishers {
/// A publisher that transforms all elements from the upstream publisher with
/// a provided closure.
public struct Map<Upstream: Publisher, Output> : Publisher {
public struct Map<Upstream: Publisher, Output>: Publisher {
public typealias Failure = Upstream.Failure
@@ -0,0 +1,132 @@
//
// Publishers.ReplaceError.swift
// OpenCombine
//
// Created by Bogdan Vlad on 8/29/19.
//
extension Publishers {
/// A publisher that replaces any errors in the stream with a provided element.
public struct ReplaceError<Upstream: Publisher>: 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 = Never
/// The element with which to replace errors from the upstream publisher.
public let output: Upstream.Output
/// The publisher from which this publisher receives elements.
public let upstream: Upstream
public init(upstream: Upstream,
output: Output) {
self.upstream = upstream
self.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<SubscriberType: Subscriber>(subscriber: SubscriberType)
where Upstream.Output == SubscriberType.Input,
SubscriberType.Failure == Failure
{
let replaceErrorSubscriber = _ReplaceError<Upstream, SubscriberType>(
downstream: subscriber,
output: output
)
upstream.subscribe(replaceErrorSubscriber)
}
}
}
extension Publisher {
/// Replaces any errors in the stream with the provided element.
///
/// If the upstream publisher fails with an error, this publisher emits the provided
/// element, then finishes normally.
/// - Parameter output: An element to emit when the upstream publisher fails.
/// - Returns: A publisher that replaces an error from the upstream publisher with
/// the provided output element.
public func replaceError(with output: Output) -> Publishers.ReplaceError<Self> {
return Publishers.ReplaceError(upstream: self, output: output)
}
}
private final class _ReplaceError<Upstream: Publisher, Downstream: Subscriber>
: OperatorSubscription<Downstream>,
Subscriber,
CustomStringConvertible,
Subscription
where Upstream.Output == Downstream.Input
{
typealias Input = Upstream.Output
typealias Failure = Upstream.Failure
var downstreamDemandCounter: Subscribers.Demand = .none
var hasFailed: Bool = false
var description: String { return "ReplaceError" }
private let output: Downstream.Input
init(downstream: Downstream,
output: Downstream.Input) {
self.output = output
super.init(downstream: downstream)
}
func request(_ demand: Subscribers.Demand) {
if hasFailed {
_ = downstream.receive(output)
downstream?.receive(completion: .finished)
} else {
downstreamDemandCounter += demand
upstreamSubscription?.request(demand)
}
}
func receive(subscription: Subscription) {
upstreamSubscription = subscription
downstream.receive(subscription: self)
}
func receive(_ input: Upstream.Output) -> Subscribers.Demand {
downstreamDemandCounter -= 1
let demand = downstream.receive(input)
downstreamDemandCounter += demand
return demand
}
func receive(completion: Subscribers.Completion<Upstream.Failure>) {
switch completion {
case .finished:
downstream.receive(completion: .finished)
case .failure:
hasFailed = true
// If there was no demand from downstream,
// ReplaceError does not forward the value that
// replaces the error until it is requested.
if downstreamDemandCounter > 0 {
_ = downstream.receive(output)
downstream?.receive(completion: .finished)
}
}
}
override func cancel() {
upstreamSubscription?.cancel()
upstreamSubscription = nil
}
}
+1 -1
View File
@@ -57,6 +57,6 @@ internal func catching<Input, Output, Failure: Error>(
/// an error but returns `Result`.
internal func catching<Input, Output>(
_ transform: @escaping (Input) throws -> Output
) -> (Input) -> Result<Output, Error> {
) -> (Input) -> Result<Output, Error> {
return { input in Result { try transform(input) } }
}
@@ -27,7 +27,7 @@ extension Subscribers {
let children: [(label: String?, value: Any)] = [
(label: "object", value: object as Any),
(label: "keyPath", value: keyPath),
(label: "upstreamSubscription", value: _upstreamSubscription as Any)
(label: "status", value: _upstreamSubscription as Any)
]
return Mirror(self, children: children)
}
-15
View File
@@ -1,15 +0,0 @@
//
// Subscribers.Demand.swift
//
//
// Created by Sergej Jaskiewicz on 10.06.2019.
//
import XCTest
import OpenCombineTests
var tests = [XCTestCaseEntry]()
tests += OpenCombineTests.allTests()
XCTMain(tests)
@@ -16,15 +16,6 @@ import OpenCombine
@available(macOS 10.15, iOS 13.0, *)
final class AnyCancellableTests: XCTestCase {
static let allTests = [
("testClosureInitialized", testClosureInitialized),
("testCancelableInitialized", testCancelableInitialized),
("testCancelTwice", testCancelTwice),
("testStoreInArbitraryCollection", testStoreInArbitraryCollection),
("testStoreInSet", testStoreInSet),
("testIndirectCancellation", testIndirectCancellation),
]
func testClosureInitialized() {
var fired = false
@@ -16,11 +16,6 @@ import OpenCombine
@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() {
+19 -19
View File
@@ -19,16 +19,6 @@ private typealias Sut = AnySubscriber<Int, TestingError>
@available(macOS 10.15, iOS 13.0, *)
final class AnySubscriberTests: XCTestCase {
static let allTests = [
("testCombineIdentifier", testCombineIdentifier),
("testDescription", testDescription),
("testReflection", testReflection),
("testErasingSubscriber", testErasingSubscriber),
("testErasingSubscriberSubscription", testErasingSubscriberSubscription),
("testErasingSubject", testErasingSubject),
("testErasingSubjectSubscription", testErasingSubjectSubscription),
]
func testCombineIdentifier() {
let empty = Sut()
@@ -148,18 +138,10 @@ final class AnySubscriberTests: XCTestCase {
let expectedEvents: [TrackingSubject<Int>.Event] =
[.subscription("Subject")] + events.compactMap(subscriberEventToSubjectEvent)
.throughFirstCompletion()
XCTAssertEqual(subject.history, expectedEvents)
let shuffledEvents = events.shuffled()
publishEvents(shuffledEvents, erased)
let expectedShuffledEvents =
shuffledEvents.compactMap(subscriberEventToSubjectEvent)
XCTAssertEqual(subject.history, expectedEvents + expectedShuffledEvents)
let demand = erased.receive(0)
XCTAssertEqual(demand, .none)
@@ -240,3 +222,21 @@ private func subscriberEventToSubjectEvent(
return .completion(c)
}
}
@available(macOS 10.15, iOS 13.0, *)
extension Array {
func throughFirstCompletion<SubjectOutput>() -> Array
where Element == TrackingSubject<SubjectOutput>.Event
{
var encounteredFirstCompletion = false
return self.prefix {
if encounteredFirstCompletion {
return false
}
if case .completion = $0 {
encounteredFirstCompletion = true
}
return true
}
}
}
@@ -17,12 +17,6 @@ import OpenCombine
@available(macOS 10.15, iOS 13.0, *)
final class CombineIdentifierTests: PerformanceTestCase {
static let allTests = [
("testDefaultInitialized", testDefaultInitialized),
("testAnyObject", testAnyObject),
("testDefaultInitializedPerformance", testDefaultInitializedPerformance),
]
func testDefaultInitialized() {
let id1 = CombineIdentifier()
let id2 = CombineIdentifier()
@@ -13,24 +13,9 @@ import Combine
import OpenCombine
#endif
// swiftlint:disable explicit_top_level_acl
@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),
]
private typealias Sut = CurrentValueSubject<Int, TestingError>
// Reactive Streams Spec: Rules #1, #2, #9
@@ -322,6 +307,39 @@ final class CurrentValueSubjectTests: XCTestCase {
.completion(.finished)])
}
func testSubscriptionAfterSend() {
// Given
let passthrough = Sut(0)
let subscriber = TrackingSubscriber(
receiveSubscription: { subscription in
subscription.request(.unlimited)
})
// When
passthrough.send(2)
passthrough.subscribe(subscriber)
// Then
XCTAssertEqual(subscriber.history, [.subscription("CurrentValueSubject"),
.value(2)])
}
func testSubscriptionAfterSet() {
// Given
let passthrough = Sut(0)
let subscriber = TrackingSubscriber(receiveSubscription: { subscription in
subscription.request(.unlimited)
})
// When
passthrough.value = 3
passthrough.subscribe(subscriber)
// Then
XCTAssertEqual(subscriber.history, [.subscription("CurrentValueSubject"),
.value(3)])
}
func testSendSubscription() {
let subscription1 = CustomSubscription()
let cvs = Sut(1)
@@ -441,15 +459,15 @@ final class CurrentValueSubjectTests: XCTestCase {
race(
{
cvs.value += 1
cvs.value = 42
},
{
cvs.value -= 1
cvs.value = 42
}
)
XCTAssertEqual(inputs.value.count, 40200)
XCTAssertEqual(cvs.value, 112)
XCTAssertEqual(cvs.value, 42)
race(
{
@@ -5,9 +5,7 @@
// Created by Joseph Spadafora on 6/29/19.
//
#if OPENCOMBINE_COMPATIBILITY_TEST
import Combine
#else
#if !OPENCOMBINE_COMPATIBILITY_TEST
import Foundation
import OpenCombine
@@ -0,0 +1,71 @@
//
// OperatorTestHelper.swift
//
//
// Created by Joseph Spadafora on 7/6/19.
//
import XCTest
#if OPENCOMBINE_COMPATIBILITY_TEST
import Combine
#else
import OpenCombine
#endif
/// `OperatorTestHelper` is an abstraction that helps avoid a lot of boilerplate when
/// testing an operator. It is initialized with a publisher type and creates a
/// `CustomSubscription`, `CustomPublisherBase` and `TrackingSubscriberBase`.
@available(macOS 10.15, iOS 13.0, *)
class OperatorTestHelper<SourceValue: Equatable,
SourcePublisher,
Sut: Publisher>
where Sut.Output: Equatable,
SourcePublisher: CustomPublisherBase<SourceValue, TestingError>
{
typealias Value = Sut.Output
typealias Failure = Sut.Failure
let subscription: CustomSubscription
let publisher: SourcePublisher
let tracking: TrackingSubscriberBase<Value, Failure>
private(set) var sut: Sut
var downstreamSubscription: Subscription?
/// This initializes the `OperatorTestHelper`. In most cases,
/// you can just pass a `publisherType` and closure
/// for `createSut` to get all the setup that you'll need for a test.
/// - Parameter publisherType: This should be filled in with the
/// type of `CustomPublisherBase` that you would like the
/// operator you are testing to be built from.
/// - Parameter initialDemand: This is the demand that the
/// created `TrackingSubscriber` should return upon receiving a subscription.
/// - Parameter receiveValueDemand: This is the demand that the
/// created `TrackingSubscriber should return upon receiving a value.
/// - Parameter customSubscription: This parameter defaults to `CustomSubscription()`,
/// but can be replaced with your own instance if you want to override
/// any of the default `CustomSubscription` initializer closures.
/// - Parameter createSut: This closure takes a new concrete instance
/// of the `publisherType` as an input to the closure and creates an
/// instance of the operator that you are trying to test.
init(publisherType: SourcePublisher.Type,
initialDemand: Subscribers.Demand?,
receiveValueDemand: Subscribers.Demand,
customSubscription: CustomSubscription = CustomSubscription(),
createSut: (SourcePublisher) -> Sut)
{
self.subscription = customSubscription
let createdPublisher = publisherType.init(subscription: customSubscription)
self.publisher = createdPublisher
self.sut = createSut(createdPublisher)
self.tracking = TrackingSubscriberBase<Value, Failure>(
receiveSubscription: {
initialDemand.map($0.request)
},
receiveValue: { _ in receiveValueDemand }
)
tracking.onSubscribe = { self.downstreamSubscription = $0 }
sut.subscribe(tracking)
}
}
@@ -86,6 +86,12 @@ final class TrackingSubscriberBase<Value: Equatable, Failure: Error>
private let _receiveCompletion: ((Subscribers.Completion<Failure>) -> Void)?
private let _onDeinit: (() -> Void)?
var onSubscribe: ((Subscription) -> Void)?
var onValue: ((Input) -> Void)?
var onFinish: (() -> Void)?
var onFailure: ((Failure) -> Void)?
var onDeinit: (() -> Void)?
/// The history of subscriptions, inputs and completions of this subscriber
private(set) var history: [Event] = []
@@ -144,16 +150,24 @@ final class TrackingSubscriberBase<Value: Equatable, Failure: Error>
func receive(subscription: Subscription) {
history.append(.subscription(.init(subscription)))
onSubscribe?(subscription)
_receiveSubscription?(subscription)
}
func receive(_ input: Value) -> Subscribers.Demand {
history.append(.value(input))
onValue?(input)
return _receiveValue?(input) ?? .none
}
func receive(completion: Subscribers.Completion<Failure>) {
history.append(.completion(completion))
switch completion {
case .failure(let error):
onFailure?(error)
case .finished:
onFinish?()
}
_receiveCompletion?(completion)
}
@@ -162,6 +176,7 @@ final class TrackingSubscriberBase<Value: Equatable, Failure: Error>
}
deinit {
onDeinit?()
_onDeinit?()
}
}
@@ -16,11 +16,6 @@ import OpenCombine
@available(macOS 10.15, iOS 13.0, *)
final class ImmediateSchedulerTests: XCTestCase {
static let allTests = [
("testStride", testSchedulerTimeType),
("testActions", testActions),
]
func testSchedulerTimeType() throws {
typealias Stride = ImmediateScheduler.SchedulerTimeType.Stride
@@ -80,5 +75,22 @@ final class ImmediateSchedulerTests: XCTestCase {
}
XCTAssertTrue(fired)
fired = false
ImmediateScheduler.shared.schedule(after: ImmediateScheduler.shared.now) {
fired = true
}
XCTAssertTrue(fired)
fired = false
let cancellable = ImmediateScheduler
.shared
.schedule(after: ImmediateScheduler.shared.now, interval: 10) {
fired = true
}
XCTAssertTrue(fired)
XCTAssertEqual(String(describing: cancellable), "Empty")
}
}
@@ -16,19 +16,6 @@ import OpenCombine
@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),
]
private typealias Sut = PassthroughSubject<Int, TestingError>
// Reactive Streams Spec: Rules #1, #2, #9
@@ -16,12 +16,6 @@ import OpenCombine
@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 {
@@ -0,0 +1,466 @@
//
// CompactMapTests.swift
//
//
// Created by Sergej Jaskiewicz on 11.07.2019.
//
import XCTest
#if OPENCOMBINE_COMPATIBILITY_TEST
import Combine
#else
import OpenCombine
#endif
@available(macOS 10.15, iOS 13.0, *)
final class CompactMapTests: XCTestCase {
func testEmpty() {
let tracking = TrackingSubscriberBase<Int, TestingError>(
receiveSubscription: { $0.request(.unlimited) }
)
let publisher = TrackingSubject<String>(
receiveSubscriber: {
XCTAssertEqual(String(describing: $0), "CompactMap")
}
)
publisher.compactMap(Int.init).subscribe(tracking)
XCTAssertEqual(tracking.history, [.subscription("CompactMap")])
}
func testError() {
let expectedError = TestingError.oops
let tracking = TrackingSubscriberBase<Int, TestingError>(
receiveSubscription: { $0.request(.unlimited) }
)
let publisher =
CustomPublisherBase<String, TestingError>(subscription: CustomSubscription())
publisher.compactMap(Int.init).subscribe(tracking)
publisher.send(completion: .failure(expectedError))
publisher.send(completion: .failure(expectedError))
XCTAssertEqual(tracking.history, [.subscription("CompactMap"),
.completion(.failure(.oops))])
}
func testTryMapFailureBecauseOfThrow() {
var counter = 0 // How many times the transform is called?
let publisher = PassthroughSubject<String, Error>()
let compactMap = publisher.tryCompactMap { value -> Int? in
counter += 1
if value == "throw" {
throw "too much" as TestingError
}
return Int(value)
}
let tracking = TrackingSubscriberBase<Int, Error>(
receiveSubscription: { $0.request(.unlimited) }
)
publisher.send("1")
compactMap.subscribe(tracking)
publisher.send("2")
publisher.send("3")
publisher.send("throw")
publisher.send("9")
publisher.send(completion: .finished)
XCTAssertEqual(tracking.history,
[.subscription("TryCompactMap"),
.value(2),
.value(3),
.completion(.failure("too much" as TestingError))])
XCTAssertEqual(counter, 3)
}
func testTryMapFailureOnCompletion() {
let publisher = PassthroughSubject<String, Error>()
let compactMap = publisher.tryCompactMap(Int.init)
let tracking = TrackingSubscriberBase<Int, Error>()
publisher.send("1")
compactMap.subscribe(tracking)
publisher.send(completion: .failure(TestingError.oops))
publisher.send("2")
XCTAssertEqual(tracking.history,
[.subscription("TryCompactMap"),
.completion(.failure(TestingError.oops))])
}
func testRange() {
let publisher = PassthroughSubject<String, TestingError>()
let compactMap = publisher.compactMap(Int.init)
let tracking = TrackingSubscriber(receiveSubscription: { $0.request(.unlimited) })
publisher.send("1")
compactMap.subscribe(tracking)
publisher.send("2")
publisher.send("a")
publisher.send("b")
publisher.send("a")
publisher.send("3")
publisher.send("4")
publisher.send("5")
publisher.send("!")
publisher.send(completion: .finished)
publisher.send("6")
XCTAssertEqual(tracking.history, [.subscription("CompactMap"),
.value(2),
.value(3),
.value(4),
.value(5),
.completion(.finished)])
}
func testNoDemand() {
let subscription = CustomSubscription()
let publisher =
CustomPublisherBase<String, TestingError>(subscription: subscription)
let compactMap = publisher.compactMap(Int.init)
let tracking = TrackingSubscriber()
compactMap.subscribe(tracking)
XCTAssertTrue(subscription.history.isEmpty)
}
func testDemandOnSubscribe() {
let subscription = CustomSubscription()
let publisher =
CustomPublisherBase<String, TestingError>(subscription: subscription)
let compactMap = publisher.compactMap(Int.init)
let tracking = TrackingSubscriber(
receiveSubscription: { $0.request(.max(42)) }
)
compactMap.subscribe(tracking)
XCTAssertEqual(subscription.history, [.requested(.max(42))])
}
func testDemand() {
let subscription = CustomSubscription()
let publisher =
CustomPublisherBase<String, TestingError>(subscription: subscription)
let compactMap = publisher.compactMap(Int.init)
var downstreamSubscription: Subscription?
var demandOnReceiveValue = Subscribers.Demand.max(3)
let tracking = TrackingSubscriber(
receiveSubscription: {
$0.request(.max(5))
downstreamSubscription = $0
},
receiveValue: { _ in demandOnReceiveValue }
)
compactMap.subscribe(tracking)
XCTAssertNotNil(downstreamSubscription)
XCTAssertEqual(subscription.history, [.requested(.max(5))])
// unsatisfied demand = 5
XCTAssertEqual(publisher.send("a"), .max(1))
XCTAssertEqual(subscription.history, [.requested(.max(5))])
// unsatisfied demand = 5
XCTAssertEqual(publisher.send("1"), .max(3))
XCTAssertEqual(subscription.history, [.requested(.max(5))])
// unsatisfied demand = 5 - 1 + 3 = 7
demandOnReceiveValue = .max(2)
XCTAssertEqual(publisher.send("2"), demandOnReceiveValue)
XCTAssertEqual(subscription.history, [.requested(.max(5))])
// unsatisfied demand = 7 - 1 + 2 = 8
demandOnReceiveValue = .max(1)
XCTAssertEqual(publisher.send("3"), demandOnReceiveValue)
XCTAssertEqual(subscription.history, [.requested(.max(5))])
// unsatisfied demand = 8 - 1 + 1 = 8
XCTAssertEqual(publisher.send("b"), .max(1))
XCTAssertEqual(subscription.history, [.requested(.max(5))])
// unsatisfied demand = 8
downstreamSubscription?.request(.max(15))
downstreamSubscription?.request(.max(5))
XCTAssertEqual(subscription.history, [.requested(.max(5)),
.requested(.max(15)),
.requested(.max(5))])
// unsatisfied demand = 8 + 15 + 5 = 28
demandOnReceiveValue = .none
XCTAssertEqual(publisher.send("4"), demandOnReceiveValue)
XCTAssertEqual(subscription.history, [.requested(.max(5)),
.requested(.max(15)),
.requested(.max(5))])
// unsatisfied demand = 28 - 1 + 0 = 27
downstreamSubscription?.request(.max(121))
XCTAssertEqual(subscription.history, [.requested(.max(5)),
.requested(.max(15)),
.requested(.max(5)),
.requested(.max(121))])
// unsatisfied demand = 27 + 121 = 148
XCTAssertEqual(publisher.send("c"), .max(1))
XCTAssertEqual(subscription.history, [.requested(.max(5)),
.requested(.max(15)),
.requested(.max(5)),
.requested(.max(121))])
// unsatisfied demand = 148
downstreamSubscription?.cancel()
downstreamSubscription?.cancel()
XCTAssertEqual(subscription.history, [.requested(.max(5)),
.requested(.max(15)),
.requested(.max(5)),
.requested(.max(121)),
.cancelled])
downstreamSubscription?.request(.max(3))
XCTAssertEqual(subscription.history, [.requested(.max(5)),
.requested(.max(15)),
.requested(.max(5)),
.requested(.max(121)),
.cancelled])
demandOnReceiveValue = .max(80)
XCTAssertEqual(publisher.send("8"), .none)
}
func testCompletion() {
let subscription = CustomSubscription()
let publisher =
CustomPublisherBase<String, TestingError>(subscription: subscription)
let compactMap = publisher.compactMap(Int.init)
let tracking = TrackingSubscriber(receiveSubscription: { $0.request(.unlimited) })
compactMap.subscribe(tracking)
publisher.send(completion: .finished)
XCTAssertEqual(subscription.history, [.requested(.unlimited)])
XCTAssertEqual(tracking.history, [.subscription("CompactMap"),
.completion(.finished)])
}
func testCompactMapCancel() throws {
let subscription = CustomSubscription()
let publisher =
CustomPublisherBase<String, TestingError>(subscription: subscription)
let compactMap = publisher.compactMap(Int.init)
var downstreamSubscription: Subscription?
let tracking = TrackingSubscriber(
receiveSubscription: {
$0.request(.unlimited)
downstreamSubscription = $0
}
)
compactMap.subscribe(tracking)
try XCTUnwrap(downstreamSubscription).cancel()
XCTAssertEqual(publisher.send("1"), .none)
publisher.send(completion: .finished)
XCTAssertEqual(subscription.history, [.requested(.unlimited), .cancelled])
}
func testTryCompactMapCancel() throws {
let subscription = CustomSubscription()
let publisher =
CustomPublisherBase<String, TestingError>(subscription: subscription)
let tryCompactMap = publisher.tryCompactMap(Int.init)
var downstreamSubscription: Subscription?
let tracking = TrackingSubscriberBase<Int, Error>(
receiveSubscription: {
$0.request(.unlimited)
downstreamSubscription = $0
}
)
tryCompactMap.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<String, TestingError>(subscription: subscription)
let compactMap = publisher.compactMap(Int.init)
var downstreamSubscription: Subscription?
let tracking = TrackingSubscriber(
receiveSubscription: {
$0.request(.unlimited)
downstreamSubscription = $0
}
)
compactMap.subscribe(tracking)
try XCTUnwrap(downstreamSubscription).cancel()
downstreamSubscription?.request(.unlimited)
try XCTUnwrap(downstreamSubscription).cancel()
XCTAssertEqual(subscription.history, [.requested(.unlimited),
.cancelled])
}
func testLifecycle() throws {
var deinitCounter = 0
let onDeinit = { deinitCounter += 1 }
do {
let passthrough = PassthroughSubject<String, TestingError>()
let compactMap = passthrough.compactMap(Int.init)
let emptySubscriber = TrackingSubscriber(onDeinit: onDeinit)
XCTAssertTrue(emptySubscriber.history.isEmpty)
compactMap.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<String, TestingError>()
let compactMap = passthrough.compactMap(Int.init)
let emptySubscriber = TrackingSubscriber(onDeinit: onDeinit)
XCTAssertTrue(emptySubscriber.history.isEmpty)
compactMap.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<String, TestingError>()
let compactMap = passthrough.compactMap(Int.init)
let emptySubscriber = TrackingSubscriber(
receiveSubscription: { subscription = $0; $0.request(.unlimited) },
onDeinit: onDeinit
)
XCTAssertTrue(emptySubscriber.history.isEmpty)
compactMap.subscribe(emptySubscriber)
XCTAssertEqual(emptySubscriber.subscriptions.count, 1)
passthrough.send("31")
XCTAssertEqual(emptySubscriber.inputs.count, 1)
XCTAssertEqual(emptySubscriber.completions.count, 0)
XCTAssertNotNil(subscription)
}
XCTAssertEqual(deinitCounter, 0)
try XCTUnwrap(subscription).cancel()
XCTAssertEqual(deinitCounter, 0)
}
func testCompactMapOperatorSpecializationForCompactMap() {
let tracking = TrackingSubscriber(
receiveSubscription: { $0.request(.unlimited) }
)
let publisher = PassthroughSubject<String, TestingError>()
let compactMap1 = publisher.compactMap(Int.init)
let compactMap2 = compactMap1.compactMap { $0.isMultiple(of: 2) ? $0 / 2 : nil }
compactMap2.subscribe(tracking)
publisher.send("0")
publisher.send("3")
publisher.send("a")
publisher.send("12")
publisher.send("11")
publisher.send("20")
publisher.send("b")
publisher.send(completion: .finished)
XCTAssert(compactMap1.upstream === compactMap2.upstream)
XCTAssertEqual(tracking.history, [.subscription("CompactMap"),
.value(0),
.value(6),
.value(10),
.completion(.finished)])
}
func testMapOperatorSpecializationForCompactMap() {
let tracking = TrackingSubscriber(
receiveSubscription: { $0.request(.unlimited) }
)
let publisher = PassthroughSubject<String, TestingError>()
let compactMap1 = publisher.compactMap(Int.init)
let compactMap2 = compactMap1.map { $0 + 1 }
compactMap2.subscribe(tracking)
publisher.send("0")
publisher.send("3")
publisher.send("a")
publisher.send("12")
publisher.send("11")
publisher.send("20")
publisher.send("b")
publisher.send(completion: .finished)
XCTAssert(compactMap1.upstream === compactMap2.upstream)
XCTAssertEqual(tracking.history, [.subscription("CompactMap"),
.value(1),
.value(4),
.value(13),
.value(12),
.value(21),
.completion(.finished)])
}
func testCompactMapOperatorSpecializationForTryCompactMap() {
let tracking = TrackingSubscriberBase<Int, Error>(
receiveSubscription: { $0.request(.unlimited) }
)
let publisher = PassthroughSubject<String, Never>()
let tryCompactMap1 = publisher.tryCompactMap { input -> Int? in
if input == "throw" { throw TestingError.oops }
return Int(input)
}
let tryCompactMap2 = tryCompactMap1
.compactMap { $0.isMultiple(of: 2) ? $0 / 2 : nil }
tryCompactMap2.subscribe(tracking)
publisher.send("0")
publisher.send("3")
publisher.send("a")
publisher.send("12")
publisher.send("11")
publisher.send("20")
publisher.send("b")
XCTAssert(tryCompactMap1.upstream === tryCompactMap2.upstream)
XCTAssertEqual(tracking.history, [.subscription("TryCompactMap"),
.value(0),
.value(6),
.value(10)])
publisher.send("throw")
XCTAssertEqual(tracking.history, [.subscription("TryCompactMap"),
.value(0),
.value(6),
.value(10),
.completion(.failure(TestingError.oops))])
}
}
@@ -16,15 +16,6 @@ import OpenCombine
@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)
@@ -15,11 +15,6 @@ import OpenCombine
@available(macOS 10.15, iOS 13.0, *)
final class DecodeTests: XCTestCase {
static let allTests = [
("testDecodingSuccess", testDecodingSuccess),
("testDecodingFailure", testDecodingFailure),
("testDemand", testDemand)
]
var jsonEncoder: TestEncoder = TestEncoder()
var jsonDecoder: TestDecoder = TestDecoder()
@@ -34,7 +29,7 @@ final class DecodeTests: XCTestCase {
let data = 78
let subject = PassthroughSubject<Int, Error>()
let publisher = subject.decode(type: [String : String].self, decoder: jsonDecoder)
let subscriber = TrackingSubscriberBase<[String: String], Error>(
let subscriber = TrackingSubscriberBase<[String : String], Error>(
receiveSubscription: { $0.request(.unlimited) }
)
jsonDecoder.handleDecode = { decodeData in
@@ -57,7 +52,7 @@ final class DecodeTests: XCTestCase {
let failData = 95
let subject = PassthroughSubject<Int, Error>()
let publisher = subject.decode(type: [String : String].self, decoder: jsonDecoder)
let subscriber = TrackingSubscriberBase<[String: String], Error>(
let subscriber = TrackingSubscriberBase<[String : String], Error>(
receiveSubscription: { $0.request(.unlimited) }
)
@@ -17,11 +17,6 @@ import OpenCombine
@available(macOS 10.15, iOS 13.0, *)
final class DeferredTests: XCTestCase {
static let allTests = [
("testDeferredCreatedAfterSubscription",
testDeferredCreatedAfterSubscription)
]
func testDeferredCreatedAfterSubscription() {
var deferredPublisherCreatedCount = 0
@@ -16,18 +16,6 @@ import OpenCombine
@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),
("testDropWhileCompletion",
testDropWhileCompletion),
]
func testDropWhile() {
var counter = 0 // How many times the predicate is called?
@@ -237,16 +225,12 @@ final class DropWhileTests: XCTestCase {
publisher.send(completion: .finished)
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))])
.completion(.finished)])
}
func testCancelAlreadyCancelled() throws {
@@ -271,9 +255,7 @@ final class DropWhileTests: XCTestCase {
publisher.send(completion: .finished)
XCTAssertEqual(subscription.history, [.requested(.unlimited), .cancelled])
XCTAssertEqual(tracking.history, [.subscription("DropWhile"),
.completion(.failure(.oops)),
.completion(.finished)])
XCTAssertEqual(tracking.history, [.subscription("DropWhile")])
}
func testLifecycle() throws {
@@ -16,12 +16,6 @@ import OpenCombine
@available(macOS 10.15, iOS 13.0, *)
final class EmptyTests: XCTestCase {
static let allTests = [
("testEmpty", testEmpty),
("testImmediatelyCancel", testImmediatelyCancel),
("testEquatable", testEquatable),
]
func testEmpty() {
let completesImmediately = Empty(completeImmediately: true,
@@ -16,13 +16,6 @@ import OpenCombine
@available(macOS 10.15, iOS 13.0, *)
final class EncodeTests: XCTestCase {
static let allTests = [
("testEncodingSuccess", testEncodingSuccess),
("testEncodingFailure", testEncodingFailure),
("testDemand", testDemand),
("testEncodeSuccessHistory", testEncodeSuccessHistory),
]
private var encoder = TestEncoder()
private var decoder = TestDecoder()
@@ -16,10 +16,6 @@ import OpenCombine
@available(macOS 10.15, iOS 13.0, *)
final class FailTests: XCTestCase {
static let allTests = [
("testSubscription", testSubscription),
]
private typealias Sut = Fail<Int, TestingError>
func testSubscription() {
@@ -0,0 +1,389 @@
//
// FilterTests.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 FilterTests: XCTestCase {
func testFilterRemovesElements() {
// Given
let helper = OperatorTestHelper(publisherType: CustomPublisher.self,
initialDemand: .max(2),
receiveValueDemand: .none) {
$0.filter { $0.isMultiple(of: 2) }
}
// When
for i in 1...5 {
XCTAssertEqual(helper.publisher.send(i),
helper.sut.isIncluded(i) ? .none : .max(1))
}
// Then
XCTAssertEqual(helper.tracking.history, [.subscription("Filter"),
.value(2),
.value(4)])
}
func testTryFilterWorks() {
// Given
let helper = OperatorTestHelper(publisherType: CustomPublisher.self,
initialDemand: .max(2),
receiveValueDemand: .none) {
$0.tryFilter {
try $0.isMultiple(of: 2) && nonthrowingReturn($0)
}
}
// When
for i in 1...5 {
XCTAssertEqual(helper.publisher.send(i),
try helper.sut.isIncluded(i) ? .none : .max(1))
}
// Then
XCTAssertEqual(helper.tracking.history, [.subscription("TryFilter"),
.value(2),
.value(4)])
}
func testTryFilterCompletesWithErrorWhenThrown() {
// Given
let helper = OperatorTestHelper(publisherType: CustomPublisher.self,
initialDemand: .unlimited,
receiveValueDemand: .none) {
$0.tryFilter {
try failOnFive(value: $0)
}
}
// When
for i in 1...5 {
_ = helper.publisher.send(i)
}
helper.publisher.send(completion: .finished)
// Then
XCTAssertEqual(helper.tracking.history, [.subscription("TryFilter"),
.value(1),
.value(2),
.value(3),
.value(4),
.completion(.failure(TestingError.oops))
])
}
func testCanCompleteWithFinished() {
// Given
let helper = OperatorTestHelper(publisherType: CustomPublisher.self,
initialDemand: .unlimited,
receiveValueDemand: .none) {
$0.filter { _ in true }
}
// When
XCTAssertEqual(helper.publisher.send(1), .none)
helper.publisher.send(completion: .finished)
// Then
XCTAssertEqual(helper.tracking.history, [.subscription("Filter"),
.value(1),
.completion(.finished)])
}
func testFilterCanCompleteWithError() {
// Given
let helper = OperatorTestHelper(publisherType: CustomPublisher.self,
initialDemand: .unlimited,
receiveValueDemand: .none) {
$0.filter { _ in true }
}
// When
XCTAssertEqual(helper.publisher.send(1), .none)
helper.publisher.send(completion: .failure(.oops))
// Then
XCTAssertEqual(helper.tracking.history, [.subscription("Filter"),
.value(1),
.completion(.failure(.oops))])
}
func testTryFilterCanCompleteWithError() {
// Given
let helper = OperatorTestHelper(
publisherType: CustomPublisher.self,
initialDemand: .unlimited,
receiveValueDemand: .none,
createSut: {
$0.tryFilter { _ in true }
}
)
// When
XCTAssertEqual(helper.publisher.send(1), .none)
helper.publisher.send(completion: .failure(.oops))
// Then
XCTAssertEqual(helper.tracking.history,
[.subscription("TryFilter"),
.value(1),
.completion(.failure(TestingError.oops))])
}
func testFilterSubscriptionDemand() {
let helper = OperatorTestHelper(
publisherType: CustomPublisher.self,
initialDemand: .max(3),
receiveValueDemand: .none,
createSut: {
$0.filter { $0.isMultiple(of: 2) }
}
)
XCTAssertEqual(helper.publisher.send(1), .max(1))
XCTAssertEqual(helper.publisher.send(2), .max(0))
XCTAssertEqual(helper.publisher.send(3), .max(1))
XCTAssertEqual(helper.publisher.send(4), .max(0))
XCTAssertEqual(helper.publisher.send(5), .max(1))
XCTAssertEqual(helper.publisher.send(6), .max(0))
XCTAssertEqual(helper.publisher.send(7), .max(1))
XCTAssertEqual(helper.publisher.send(8), .max(0))
XCTAssertEqual(helper.subscription.history, [.requested(.max(3))])
}
func testTryFilterSubscriptionDemand() {
let helper = OperatorTestHelper(publisherType: CustomPublisher.self,
initialDemand: .max(3),
receiveValueDemand: .none) {
$0.tryFilter { $0.isMultiple(of: 2) }
}
XCTAssertEqual(helper.publisher.send(1), .max(1))
XCTAssertEqual(helper.publisher.send(2), .max(0))
XCTAssertEqual(helper.publisher.send(3), .max(1))
XCTAssertEqual(helper.publisher.send(4), .max(0))
XCTAssertEqual(helper.publisher.send(5), .max(1))
XCTAssertEqual(helper.publisher.send(6), .max(0))
XCTAssertEqual(helper.publisher.send(7), .max(1))
XCTAssertEqual(helper.publisher.send(8), .max(0))
}
func testFilterCancel() throws {
let helper = OperatorTestHelper(publisherType: CustomPublisher.self,
initialDemand: .unlimited,
receiveValueDemand: .none,
createSut: { $0.filter { $0.isMultiple(of: 2) } })
try XCTUnwrap(helper.downstreamSubscription).cancel()
XCTAssertEqual(helper.publisher.send(2), .none)
helper.publisher.send(completion: .finished)
XCTAssertEqual(helper.publisher.send(4), .none)
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited), .cancelled])
XCTAssertEqual(helper.tracking.history, [.subscription("Filter")])
}
func testTryFilterCancel() throws {
let helper = OperatorTestHelper(
publisherType: CustomPublisher.self,
initialDemand: .unlimited,
receiveValueDemand: .none,
createSut: {
$0.tryFilter { try failOnFive(value: $0) && $0.isMultiple(of: 2) }
}
)
try XCTUnwrap(helper.downstreamSubscription).cancel()
XCTAssertEqual(helper.publisher.send(2), .none)
helper.publisher.send(completion: .finished)
XCTAssertEqual(helper.publisher.send(4), .none)
XCTAssertEqual(helper.publisher.send(5), .none)
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited), .cancelled])
XCTAssertEqual(helper.tracking.history, [.subscription("TryFilter")])
}
func testCancelAlreadyCancelled() throws {
let helper = OperatorTestHelper(publisherType: CustomPublisher.self,
initialDemand: .unlimited,
receiveValueDemand: .none,
createSut: { $0.filter { $0.isMultiple(of: 2) } })
try XCTUnwrap(helper.downstreamSubscription).cancel()
try XCTUnwrap(helper.downstreamSubscription).request(.unlimited)
try XCTUnwrap(helper.downstreamSubscription).cancel()
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited), .cancelled])
}
func testLifecycle() throws {
var deinitCounter = 0
let onDeinit = { deinitCounter += 1 }
do {
let passthrough = PassthroughSubject<Int, TestingError>()
let filter = passthrough.filter { $0.isMultiple(of: 2) }
let emptySubscriber = TrackingSubscriber(onDeinit: onDeinit)
XCTAssertTrue(emptySubscriber.history.isEmpty)
filter.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 filter = passthrough.filter { $0.isMultiple(of: 2) }
let emptySubscriber = TrackingSubscriber(onDeinit: onDeinit)
XCTAssertTrue(emptySubscriber.history.isEmpty)
filter.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 filter = passthrough.filter { $0.isMultiple(of: 2) }
let emptySubscriber = TrackingSubscriber(
receiveSubscription: { subscription = $0; $0.request(.unlimited) },
onDeinit: onDeinit
)
XCTAssertTrue(emptySubscriber.history.isEmpty)
filter.subscribe(emptySubscriber)
XCTAssertEqual(emptySubscriber.subscriptions.count, 1)
passthrough.send(32)
XCTAssertEqual(emptySubscriber.inputs.count, 1)
XCTAssertEqual(emptySubscriber.completions.count, 0)
XCTAssertNotNil(subscription)
}
XCTAssertEqual(deinitCounter, 0)
try XCTUnwrap(subscription).cancel()
XCTAssertEqual(deinitCounter, 0)
}
func testFilterOperatorSpecializationForFilter() {
// Given
let helper = OperatorTestHelper(publisherType: CustomPublisher.self,
initialDemand: .max(1),
receiveValueDemand: .none) {
$0.filter {
$0.isMultiple(of: 3)
}.filter {
$0.isMultiple(of: 5)
}
}
// When
for i in 1...20 {
XCTAssertEqual(helper.publisher.send(i),
helper.sut.isIncluded(i) ? .none : .max(1))
}
// Then
XCTAssertEqual(helper.tracking.history, [.subscription("Filter"), .value(15)])
}
func testTryFilterOperatorSpecializationForFilter() {
// Given
let helper = OperatorTestHelper(publisherType: CustomPublisher.self,
initialDemand: .max(1),
receiveValueDemand: .none) {
$0.filter {
$0.isMultiple(of: 3)
}.tryFilter {
$0.isMultiple(of: 5)
}
}
// When
for i in 1...20 {
XCTAssertEqual(helper.publisher.send(i),
try helper.sut.isIncluded(i) ? .none : .max(1))
}
// Then
XCTAssertEqual(helper.tracking.history, [.subscription("TryFilter"), .value(15)])
}
func testFilterOperatorSpecializationForTryFilter() {
// Given
let helper = OperatorTestHelper(publisherType: CustomPublisher.self,
initialDemand: .max(1),
receiveValueDemand: .none) {
$0.tryFilter {
$0.isMultiple(of: 3)
}.filter {
$0.isMultiple(of: 5)
}
}
// When
for i in 1...20 {
XCTAssertEqual(helper.publisher.send(i),
try helper.sut.isIncluded(i) ? .none : .max(1))
}
// Then
XCTAssertEqual(helper.tracking.history, [.subscription("TryFilter"), .value(15)])
}
func testTryFilterOperatorSpecializationForTryFilter() {
// Given
let helper = OperatorTestHelper(publisherType: CustomPublisher.self,
initialDemand: .max(3),
receiveValueDemand: .none) {
$0.tryFilter {
$0.isMultiple(of: 3)
}.tryFilter {
$0.isMultiple(of: 5)
}
}
// When
for i in 1...20 {
XCTAssertEqual(helper.publisher.send(i),
try helper.sut.isIncluded(i) ? .none : .max(1))
}
// Then
XCTAssertEqual(helper.tracking.history, [.subscription("TryFilter"),
.value(15)])
}
}
private func nonthrowingReturn(_ value: Int) throws -> Bool {
return true
}
private func failOnFive(value: Int) throws -> Bool {
if value == 5 {
throw TestingError.oops
}
return true
}
@@ -0,0 +1,606 @@
//
// FirstTests.swift
//
//
// Created by Joseph Spadafora on 7/9/19.
//
import XCTest
#if OPENCOMBINE_COMPATIBILITY_TEST
import Combine
#else
import OpenCombine
#endif
@available(macOS 10.15, iOS 13.0, *)
final class FirstTests: XCTestCase {
func testFirstDemand() throws {
let helper = OperatorTestHelper(publisherType: CustomPublisher.self,
initialDemand: nil,
receiveValueDemand: .none,
createSut: { $0.first() })
XCTAssertEqual(helper.tracking.history, [.subscription("First")])
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited)])
XCTAssertEqual(helper.publisher.send(1), .none)
XCTAssertEqual(helper.publisher.send(2), .none)
XCTAssertEqual(helper.tracking.history, [.subscription("First")])
try XCTUnwrap(helper.downstreamSubscription).request(.unlimited)
try XCTUnwrap(helper.downstreamSubscription).request(.max(1))
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited), .cancelled])
XCTAssertEqual(helper.tracking.history, [.subscription("First"),
.value(1),
.completion(.finished)])
try XCTUnwrap(helper.downstreamSubscription).cancel()
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited), .cancelled])
}
func testFirstFinishesAndReturnsFirstItem() {
let helper = OperatorTestHelper(publisherType: CustomPublisher.self,
initialDemand: .max(3),
receiveValueDemand: .max(1),
createSut: { $0.first() })
XCTAssertEqual(helper.tracking.history, [.subscription("First")])
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited)])
XCTAssertEqual(helper.publisher.send(25), .none)
XCTAssertEqual(helper.tracking.history, [.subscription("First"),
.value(25),
.completion(.finished)])
helper.publisher.send(completion: .finished)
XCTAssertEqual(helper.tracking.history, [.subscription("First"),
.value(25),
.completion(.finished)])
XCTAssertEqual(helper.publisher.send(73), .none)
XCTAssertEqual(helper.tracking.history, [.subscription("First"),
.value(25),
.completion(.finished)])
}
func testFirstFinishesWithError() {
let helper = OperatorTestHelper(publisherType: CustomPublisher.self,
initialDemand: .max(3),
receiveValueDemand: .max(1),
createSut: { $0.first() })
XCTAssertEqual(helper.tracking.history, [.subscription("First")])
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited)])
helper.publisher.send(completion: .failure(.oops))
XCTAssertEqual(helper.tracking.history, [.subscription("First"),
.completion(.failure(.oops))])
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited)])
helper.publisher.send(completion: .failure(.oops))
XCTAssertEqual(helper.tracking.history, [.subscription("First"),
.completion(.failure(.oops))])
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited)])
XCTAssertEqual(helper.publisher.send(73), .none)
XCTAssertEqual(helper.tracking.history, [.subscription("First"),
.completion(.failure(.oops))])
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited)])
}
func testFirstFinishesImmediately() {
let helper = OperatorTestHelper(publisherType: CustomPublisher.self,
initialDemand: .max(3),
receiveValueDemand: .max(1),
createSut: { $0.first() })
XCTAssertEqual(helper.tracking.history, [.subscription("First")])
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited)])
helper.publisher.send(completion: .finished)
XCTAssertEqual(helper.tracking.history, [.subscription("First"),
.completion(.finished)])
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited)])
helper.publisher.send(completion: .failure(.oops))
XCTAssertEqual(helper.tracking.history, [.subscription("First"),
.completion(.finished)])
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited)])
XCTAssertEqual(helper.publisher.send(73), .none)
XCTAssertEqual(helper.tracking.history, [.subscription("First"),
.completion(.finished)])
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited)])
}
func testFirstLifecycle() throws {
var deinitCounter = 0
let onDeinit = { deinitCounter += 1 }
do {
let passthrough = PassthroughSubject<Int, TestingError>()
let first = passthrough.first()
let emptySubscriber = TrackingSubscriber(onDeinit: onDeinit)
XCTAssertTrue(emptySubscriber.history.isEmpty)
first.subscribe(emptySubscriber)
XCTAssertEqual(emptySubscriber.subscriptions.count, 1)
passthrough.send(31)
XCTAssertEqual(emptySubscriber.inputs.count, 0)
XCTAssertEqual(emptySubscriber.completions.count, 0)
}
XCTAssertEqual(deinitCounter, 0)
do {
let passthrough = PassthroughSubject<Int, TestingError>()
let first = passthrough.first()
let emptySubscriber = TrackingSubscriber(onDeinit: onDeinit)
XCTAssertTrue(emptySubscriber.history.isEmpty)
first.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 first = passthrough.first()
let emptySubscriber = TrackingSubscriber(
receiveSubscription: { subscription = $0; $0.request(.unlimited) },
onDeinit: onDeinit
)
XCTAssertTrue(emptySubscriber.history.isEmpty)
first.subscribe(emptySubscriber)
XCTAssertEqual(emptySubscriber.subscriptions.count, 1)
passthrough.send(32)
XCTAssertEqual(emptySubscriber.inputs.count, 1)
XCTAssertEqual(emptySubscriber.completions.count, 1)
XCTAssertNotNil(subscription)
}
XCTAssertEqual(deinitCounter, 0)
try XCTUnwrap(subscription).cancel()
XCTAssertEqual(deinitCounter, 0)
}
func testFirstWhereDemand() throws {
var firedCounter = 0
let helper = OperatorTestHelper(
publisherType: CustomPublisher.self,
initialDemand: nil,
receiveValueDemand: .none,
createSut: {
$0.first {
firedCounter += 1
return $0 > 1
}
}
)
XCTAssertEqual(helper.tracking.history, [.subscription("TryFirst")])
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited)])
XCTAssertEqual(helper.publisher.send(0), .none)
XCTAssertEqual(helper.publisher.send(1), .none)
XCTAssertEqual(helper.publisher.send(2), .none)
XCTAssertEqual(helper.publisher.send(3), .none)
XCTAssertEqual(helper.tracking.history, [.subscription("TryFirst")])
XCTAssertEqual(firedCounter, 3)
try XCTUnwrap(helper.downstreamSubscription).request(.unlimited)
try XCTUnwrap(helper.downstreamSubscription).request(.max(1))
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited), .cancelled])
XCTAssertEqual(helper.tracking.history, [.subscription("TryFirst"),
.value(2),
.completion(.finished)])
try XCTUnwrap(helper.downstreamSubscription).cancel()
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited), .cancelled])
}
func testFirstWhereFinishesAndReturnsFirstMatchingItem() {
let helper = OperatorTestHelper(
publisherType: CustomPublisher.self,
initialDemand: .max(5),
receiveValueDemand: .max(1),
createSut: { $0.first(where: { $0 > 2 }) }
)
XCTAssertEqual(helper.tracking.history, [.subscription("TryFirst")])
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited)])
XCTAssertEqual(helper.publisher.send(1), .none)
XCTAssertEqual(helper.tracking.history, [.subscription("TryFirst")])
XCTAssertEqual(helper.publisher.send(2), .none)
XCTAssertEqual(helper.tracking.history, [.subscription("TryFirst")])
XCTAssertEqual(helper.publisher.send(3), .none)
XCTAssertEqual(helper.tracking.history, [.subscription("TryFirst"),
.value(3),
.completion(.finished)])
helper.publisher.send(completion: .finished)
XCTAssertEqual(helper.tracking.history, [.subscription("TryFirst"),
.value(3),
.completion(.finished)])
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited), .cancelled])
XCTAssertEqual(helper.publisher.send(4), .none)
XCTAssertEqual(helper.tracking.history, [.subscription("TryFirst"),
.value(3),
.completion(.finished)])
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited), .cancelled])
}
func testFirstWhereFinishesWithError() {
let helper = OperatorTestHelper(
publisherType: CustomPublisher.self,
initialDemand: .max(5),
receiveValueDemand: .max(1),
createSut: { $0.first(where: { $0 > 2 }) }
)
XCTAssertEqual(helper.tracking.history, [.subscription("TryFirst")])
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited)])
helper.publisher.send(completion: .failure(.oops))
XCTAssertEqual(helper.tracking.history, [.subscription("TryFirst"),
.completion(.failure(.oops))])
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited)])
helper.publisher.send(completion: .failure(.oops))
XCTAssertEqual(helper.tracking.history, [.subscription("TryFirst"),
.completion(.failure(.oops))])
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited)])
XCTAssertEqual(helper.publisher.send(73), .none)
XCTAssertEqual(helper.tracking.history, [.subscription("TryFirst"),
.completion(.failure(.oops))])
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited)])
}
func testFirstWhereLifecycle() throws {
var deinitCounter = 0
let onDeinit = { deinitCounter += 1 }
do {
let passthrough = PassthroughSubject<Int, TestingError>()
let firstWhere = passthrough.first { $0 > 1 }
let emptySubscriber = TrackingSubscriber(onDeinit: onDeinit)
XCTAssertTrue(emptySubscriber.history.isEmpty)
firstWhere.subscribe(emptySubscriber)
XCTAssertEqual(emptySubscriber.subscriptions.count, 1)
passthrough.send(31)
XCTAssertEqual(emptySubscriber.inputs.count, 0)
XCTAssertEqual(emptySubscriber.completions.count, 0)
}
XCTAssertEqual(deinitCounter, 0)
do {
let passthrough = PassthroughSubject<Int, TestingError>()
let firstWhere = passthrough.first { $0 > 1 }
let emptySubscriber = TrackingSubscriber(onDeinit: onDeinit)
XCTAssertTrue(emptySubscriber.history.isEmpty)
firstWhere.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 firstWhere = passthrough.first { $0 > 1 }
let emptySubscriber = TrackingSubscriber(
receiveSubscription: { subscription = $0; $0.request(.unlimited) },
onDeinit: onDeinit
)
XCTAssertTrue(emptySubscriber.history.isEmpty)
firstWhere.subscribe(emptySubscriber)
XCTAssertEqual(emptySubscriber.subscriptions.count, 1)
passthrough.send(32)
XCTAssertEqual(emptySubscriber.inputs.count, 1)
XCTAssertEqual(emptySubscriber.completions.count, 1)
XCTAssertNotNil(subscription)
}
XCTAssertEqual(deinitCounter, 0)
try XCTUnwrap(subscription).cancel()
XCTAssertEqual(deinitCounter, 0)
var predicateDeinitCounter = 0
let onPredicateDeinit = {
predicateDeinitCounter += 1
}
do {
let passthrough = PassthroughSubject<Int, TestingError>()
let firstWhere = passthrough.first { _ in
_ = TrackingSubscriber(onDeinit: onPredicateDeinit)
return true
}
XCTAssertEqual(predicateDeinitCounter, 0)
let subscriber =
TrackingSubscriber(receiveSubscription: { $0.request(.max(1)) })
XCTAssertTrue(subscriber.history.isEmpty)
firstWhere.subscribe(subscriber)
XCTAssertEqual(subscriber.subscriptions.count, 1)
passthrough.send(31)
XCTAssertEqual(subscriber.inputs.count, 1)
XCTAssertEqual(subscriber.completions.count, 1)
XCTAssertEqual(predicateDeinitCounter, 1)
}
}
func testTryFirstWhereDemand() throws {
var firedCounter = 0
let helper = OperatorTestHelper(
publisherType: CustomPublisher.self,
initialDemand: nil,
receiveValueDemand: .none,
createSut: {
$0.tryFirst {
firedCounter += 1
return $0 > 1
}
}
)
XCTAssertEqual(helper.tracking.history, [.subscription("TryFirstWhere")])
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited)])
XCTAssertEqual(helper.publisher.send(0), .none)
XCTAssertEqual(helper.publisher.send(1), .none)
XCTAssertEqual(helper.publisher.send(2), .none)
XCTAssertEqual(helper.publisher.send(3), .none)
XCTAssertEqual(helper.tracking.history, [.subscription("TryFirstWhere")])
XCTAssertEqual(firedCounter, 3)
try XCTUnwrap(helper.downstreamSubscription).request(.unlimited)
try XCTUnwrap(helper.downstreamSubscription).request(.max(1))
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited), .cancelled])
XCTAssertEqual(helper.tracking.history, [.subscription("TryFirstWhere"),
.value(2),
.completion(.finished)])
try XCTUnwrap(helper.downstreamSubscription).cancel()
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited), .cancelled])
}
func testTryFirstWhereReturnsFirstMatchingElement() {
let helper = OperatorTestHelper(
publisherType: CustomPublisher.self,
initialDemand: .max(5),
receiveValueDemand: .max(1),
createSut: { $0.tryFirst(where: { $0 > 6 }) }
)
XCTAssertEqual(helper.tracking.history, [.subscription("TryFirstWhere")])
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited)])
for number in 1...6 {
XCTAssertEqual(helper.publisher.send(number), .none)
XCTAssertEqual(helper.tracking.history, [.subscription("TryFirstWhere")])
}
XCTAssertEqual(helper.publisher.send(7), .none)
XCTAssertEqual(helper.tracking.history, [.subscription("TryFirstWhere"),
.value(7),
.completion(.finished)])
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited), .cancelled])
XCTAssertEqual(helper.publisher.send(8), .none)
XCTAssertEqual(helper.tracking.history, [.subscription("TryFirstWhere"),
.value(7),
.completion(.finished)])
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited), .cancelled])
}
func testTryFirstWhereFinishesWithError() {
let helper = OperatorTestHelper(
publisherType: CustomPublisher.self,
initialDemand: .max(5),
receiveValueDemand: .max(1),
createSut: { $0.tryFirst(where: { $0 > 6 }) }
)
XCTAssertEqual(helper.tracking.history, [.subscription("TryFirstWhere")])
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited)])
helper.publisher.send(completion: .failure(.oops))
XCTAssertEqual(helper.tracking.history,
[.subscription("TryFirstWhere"),
.completion(.failure(TestingError.oops))])
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited)])
helper.publisher.send(completion: .failure(.oops))
XCTAssertEqual(helper.tracking.history,
[.subscription("TryFirstWhere"),
.completion(.failure(TestingError.oops))])
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited)])
XCTAssertEqual(helper.publisher.send(73), .none)
XCTAssertEqual(helper.tracking.history,
[.subscription("TryFirstWhere"),
.completion(.failure(TestingError.oops))])
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited)])
}
func testTryFirstWhereFinishesWhenErrorThrown() {
let helper = OperatorTestHelper(
publisherType: CustomPublisher.self,
initialDemand: .max(5),
receiveValueDemand: .max(1),
createSut: {
$0.tryFirst(where: {
if $0 == 3 {
throw TestingError.oops
}
return $0 > 3
})
}
)
XCTAssertEqual(helper.tracking.history, [.subscription("TryFirstWhere")])
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited)])
XCTAssertEqual(helper.publisher.send(2), .none)
XCTAssertEqual(helper.tracking.history, [.subscription("TryFirstWhere")])
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited)])
XCTAssertEqual(helper.publisher.send(3), .none)
XCTAssertEqual(helper.tracking.history,
[.subscription("TryFirstWhere"),
.completion(.failure(TestingError.oops))])
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited), .cancelled])
XCTAssertEqual(helper.publisher.send(4), .none)
XCTAssertEqual(helper.tracking.history,
[.subscription("TryFirstWhere"),
.completion(.failure(TestingError.oops))])
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited), .cancelled])
}
func testTryFirstWhereLifecycle() throws {
var deinitCounter = 0
let onDeinit = { deinitCounter += 1 }
do {
let passthrough = PassthroughSubject<Int, TestingError>()
let tryFirstWhere = passthrough.tryFirst { $0 > 1 }
let emptySubscriber = TrackingSubscriberBase<Int, Error>(onDeinit: onDeinit)
XCTAssertTrue(emptySubscriber.history.isEmpty)
tryFirstWhere.subscribe(emptySubscriber)
XCTAssertEqual(emptySubscriber.subscriptions.count, 1)
passthrough.send(31)
XCTAssertEqual(emptySubscriber.inputs.count, 0)
XCTAssertEqual(emptySubscriber.completions.count, 0)
}
XCTAssertEqual(deinitCounter, 0)
do {
let passthrough = PassthroughSubject<Int, TestingError>()
let tryFirstWhere = passthrough.tryFirst { $0 > 1 }
let emptySubscriber = TrackingSubscriberBase<Int, Error>(onDeinit: onDeinit)
XCTAssertTrue(emptySubscriber.history.isEmpty)
tryFirstWhere.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 tryFirstWhere = passthrough.tryFirst { $0 > 1 }
let emptySubscriber = TrackingSubscriberBase<Int, Error>(
receiveSubscription: { subscription = $0; $0.request(.unlimited) },
onDeinit: onDeinit
)
XCTAssertTrue(emptySubscriber.history.isEmpty)
tryFirstWhere.subscribe(emptySubscriber)
XCTAssertEqual(emptySubscriber.subscriptions.count, 1)
passthrough.send(32)
XCTAssertEqual(emptySubscriber.inputs.count, 1)
XCTAssertEqual(emptySubscriber.completions.count, 1)
XCTAssertNotNil(subscription)
}
XCTAssertEqual(deinitCounter, 0)
try XCTUnwrap(subscription).cancel()
XCTAssertEqual(deinitCounter, 0)
var predicateDeinitCounter = 0
let onPredicateDeinit = {
predicateDeinitCounter += 1
}
do {
let passthrough = PassthroughSubject<Int, TestingError>()
let tryFirstWhere = passthrough.tryFirst { _ in
_ = TrackingSubscriber(onDeinit: onPredicateDeinit)
return true
}
XCTAssertEqual(predicateDeinitCounter, 0)
let subscriber = TrackingSubscriberBase<Int, Error>(
receiveSubscription: { $0.request(.max(1)) }
)
XCTAssertTrue(subscriber.history.isEmpty)
tryFirstWhere.subscribe(subscriber)
XCTAssertEqual(subscriber.subscriptions.count, 1)
passthrough.send(31)
XCTAssertEqual(subscriber.inputs.count, 1)
XCTAssertEqual(subscriber.completions.count, 1)
XCTAssertEqual(predicateDeinitCounter, 1)
}
do {
let passthrough = PassthroughSubject<Int, TestingError>()
let tryFirstWhere = passthrough.tryFirst { _ in
_ = TrackingSubscriber(onDeinit: onPredicateDeinit)
throw TestingError.oops
}
XCTAssertEqual(predicateDeinitCounter, 1)
let subscriber = TrackingSubscriberBase<Int, Error>(
receiveSubscription: { $0.request(.max(1)) }
)
XCTAssertTrue(subscriber.history.isEmpty)
tryFirstWhere.subscribe(subscriber)
XCTAssertEqual(subscriber.subscriptions.count, 1)
passthrough.send(31)
XCTAssertEqual(subscriber.inputs.count, 0)
XCTAssertEqual(subscriber.completions.count, 1)
XCTAssertEqual(predicateDeinitCounter, 2)
}
}
func testCancelAlreadyCancelled() throws {
let helper = OperatorTestHelper(publisherType: CustomPublisher.self,
initialDemand: .unlimited,
receiveValueDemand: .none,
createSut: { $0.first() })
try XCTUnwrap(helper.downstreamSubscription).cancel()
try XCTUnwrap(helper.downstreamSubscription).request(.unlimited)
try XCTUnwrap(helper.downstreamSubscription).cancel()
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited), .cancelled])
}
}
@@ -0,0 +1,625 @@
//
// FlatMapTests.swift
//
// Created by Eric Patey on 17.08.2019.
//
import XCTest
#if OPENCOMBINE_COMPATIBILITY_TEST
import Combine
#else
import OpenCombine
#endif
/// Helper function for predictably testing concurrency/race scenarios.
/// - Parameter block: block to execute concurrently
///
/// Apple, surprisingly, calls out to subscribers with a lock held. This will absolutely
/// block children who send values concurrently until the current downstream value
/// delivery has been completed.
///
/// This means that, without a timeout, the code below is guaranteed to deadlock. Because
/// of this we need to choose a timeout that is low enough to not materially slow down the
/// tests, but long enough to ensure that we are effectively testing the desired race
/// conditions. It needs to be a long enough timeout to allow the caller's block to begin
/// executing.
///
/// I understand that timeouts like this are a smell. I'd be happy to entertain other ways
/// to deterministically test concurrency/race conditions.
func performConcurrentBlock(_ block: @escaping () -> Void) {
let sem = DispatchSemaphore(value: 0)
DispatchQueue.global(qos: .background).async {
block()
sem.signal()
}
#if OPENCOMBINE_COMPATIBILITY_TEST
// If running in compatibility mode, assert that we got a timeout. If not, Apple
// changed their implementation to not call out with a lock held.
XCTAssertEqual(sem.wait(timeout: DispatchTime.now() + 0.01), .timedOut)
#else
sem.wait()
#endif
}
@available(macOS 10.15, iOS 13.0, *)
final class FlatMapTests: XCTestCase {
static let allTests = [
("testSendsChildValues", testSendsChildValues),
("testChildSubscribeDeadlock", testChildSubscribeDeadlock),
("testCancelCancels", testCancelCancels),
("testUpstreamDemandWithMaxPublishers", testUpstreamDemandWithMaxPublishers),
("testUpstreamDemandWithNoMaxPublishers", testUpstreamDemandWithNoMaxPublishers),
("testChildDemandWhenUnlimited", testChildDemandWhenUnlimited),
("testChildDemandWhenLimited", testChildDemandWhenLimited),
("testDemandFromLimitedtoUnlimited", testDemandFromLimitedToUnlimited),
("testChildValueReceivedWhileSendingValue",
testChildValueReceivedWhileSendingValue),
("testCompletesProperlyWhenChildrenOutliveUpstream",
testCompletesProperlyWhenChildrenOutliveUpstream),
("testCompletesProperlyWhenUpstreamOutlivesChildren",
testCompletesProperlyWhenUpstreamOutlivesChildren),
("testDoesNotCompleteWithBufferedValues", testDoesNotCompleteWithBufferedValues),
("testFailsIfUpstreamFails", testFailsIfUpstreamFails),
("testFailsIfChildFails", testFailsIfChildFails),
("testFailsWithoutSendingBufferedValues", testFailsWithoutSendingBufferedValues),
("testAllSubscriptionsReleasedOnUpstreamFailure",
testAllSubscriptionsReleasedOnUpstreamFailure),
("testAllSubscriptionsReleasedOnChildFailure",
testAllSubscriptionsReleasedOnChildFailure),
("testSendsSubcriptionDownstreamBeforeDemandUpstream",
testSendsSubcriptionDownstreamBeforeDemandUpstream),
("testTestSuiteIncludesAllTests", testTestSuiteIncludesAllTests)
]
func testSendsChildValues() {
let upstreamPublisher = PassthroughSubject<
PassthroughSubject<Int, TestingError>,
TestingError>()
let childPublisher1 = PassthroughSubject<Int, TestingError>()
let childPublisher2 = PassthroughSubject<Int, TestingError>()
let flatMap = upstreamPublisher.flatMap { $0 }
let downstreamSubscriber = TrackingSubscriber(receiveSubscription: {
$0.request(.unlimited)
})
flatMap.subscribe(downstreamSubscriber)
upstreamPublisher.send(childPublisher1)
upstreamPublisher.send(childPublisher2)
childPublisher1.send(666)
childPublisher2.send(777)
XCTAssertEqual(downstreamSubscriber.history, [.subscription("FlatMap"),
.value(666),
.value(777)])
}
// This test ensures that the code can properly re-enter when synchronously receiving
// a value during subscription (which Just(_) does).
// 1. FlatMap.Inner.receive(_ input:)
// 2. Publisher.subscribe
// ...
// 3. FlatMap.Inner.ChildSubscriber.recive(subscription:)
// 4. subscription.request()
// 5. Just.Inner.request()
// 6. FlatMap.Inner.child(_:receivedValue)
// 7. lock
//
// At one point, I had a bug where the lock was taken by #1 before calling #2
// This broke the rules of calling out with a lock held, and lead to a deadlock
// at #7.
//
// Also, in my opinion, working around the issue with recursive locks is a smell.
func testChildSubscribeDeadlock() {
let upstreamSubscription = CustomSubscription()
let upstreamPublisher = CustomPublisherBase<Int, Never>(
subscription: upstreamSubscription)
let flatMap = upstreamPublisher.flatMap(maxPublishers: .max(2)) { Just($0) }
let downstreamSubscriber = TrackingSubscriberBase<Int, Never>(
receiveSubscription: { $0.request(.unlimited) })
flatMap.subscribe(downstreamSubscriber)
XCTAssertEqual(upstreamPublisher.send(666), .none)
// Simply making it here shows that there's no dealock
}
func testCancelCancels() {
let upstreamSubscription = CustomSubscription()
let upstreamPublisher = CustomPublisherBase<Int, Never>(
subscription: upstreamSubscription)
let childSubscription = CustomSubscription()
let childPublisher = CustomPublisherBase<Int, Never>(
subscription: childSubscription)
let flatMap = upstreamPublisher.flatMap { _ in childPublisher }
var downstreamSubscription: Subscription?
let downstreamSubscriber = TrackingSubscriberBase<Int, Never>(receiveSubscription:
{
downstreamSubscription = $0
$0.request(.unlimited)
})
flatMap.subscribe(downstreamSubscriber)
XCTAssertEqual(upstreamPublisher.send(1), .none)
downstreamSubscription?.cancel()
XCTAssertEqual(upstreamSubscription.history.last, .cancelled)
XCTAssertEqual(childSubscription.history.last, .cancelled)
}
func testUpstreamDemandWithMaxPublishers() {
var upstreamDemand = Subscribers.Demand.none
let upstreamSubscription = CustomSubscription(onRequest: { upstreamDemand += $0 })
let upstreamPublisher = CustomPublisherBase<Int, Never>(
subscription: upstreamSubscription)
let flatMap = upstreamPublisher.flatMap(maxPublishers: .max(2)) { Just($0) }
let downstreamSubscriber = TrackingSubscriberBase<Int, Never>(
receiveSubscription: { $0.request(.unlimited) })
flatMap.subscribe(downstreamSubscriber)
XCTAssertEqual(upstreamDemand, .max(2))
}
func testUpstreamDemandWithNoMaxPublishers() {
var upstreamDemand = Subscribers.Demand.none
let upstreamSubscription = CustomSubscription(onRequest: { upstreamDemand += $0 })
let upstreamPublisher = CustomPublisherBase<Int, Never>(
subscription: upstreamSubscription)
let flatMap = upstreamPublisher.flatMap { Just($0) }
let downstreamSubscriber = TrackingSubscriberBase<Int, Never>(
receiveSubscription: { $0.request(.unlimited) })
flatMap.subscribe(downstreamSubscriber)
XCTAssertEqual(upstreamDemand, .unlimited)
}
func testChildDemandWhenUnlimited() throws {
let upstreamPublisher = PassthroughSubject<Void, Never>()
var childDemand = Subscribers.Demand.none
let childSubscription = CustomSubscription(onRequest: { childDemand += $0 })
let childPublisher = CustomPublisherBase<Int, Never>(
subscription: childSubscription)
let flatMap = upstreamPublisher.flatMap { _ in childPublisher }
let downstreamSubscriber = TrackingSubscriberBase<Int, Never>(receiveSubscription:
{
$0.request(.unlimited)
})
flatMap.subscribe(downstreamSubscriber)
upstreamPublisher.send()
XCTAssertEqual(childDemand, .unlimited)
XCTAssertEqual(childPublisher.send(666), .none)
}
func testChildDemandWhenLimited() throws {
let upstreamPublisher = PassthroughSubject<AnyPublisher<Int, Never>, Never>()
var child1Demand = Subscribers.Demand.none
let child1Subscription = CustomSubscription(onRequest: { child1Demand += $0 })
let child1Publisher = CustomPublisherBase<Int, Never>(
subscription: child1Subscription)
var child2Demand = Subscribers.Demand.none
let child2Subscription = CustomSubscription(onRequest: { child2Demand += $0 })
let child2Publisher = CustomPublisherBase<Int, Never>(
subscription: child2Subscription)
let flatMap = upstreamPublisher.flatMap { $0 }
var downstreamSubscription: Subscription?
let downstreamSubscriber = TrackingSubscriberBase<Int, Never>(receiveSubscription:
{
downstreamSubscription = $0
$0.request(.max(2))
})
flatMap.subscribe(downstreamSubscriber)
upstreamPublisher.send(AnyPublisher(child1Publisher))
upstreamPublisher.send(AnyPublisher(child2Publisher))
// Apple starts out the children with a demand of 1. On receipt of a child value,
// 1 more is demanded until it has one extra/buffered value after the downstream
// demand is satisfied.
XCTAssertEqual(child1Demand, .max(1))
XCTAssertEqual(child2Demand, .max(1))
// Downstream demand is 2, so:
// - this value gets sent
// - downstream demand goes down to 1
// - child is asked for 1 more
XCTAssertEqual(child1Publisher.send(666), .max(1))
XCTAssertEqual(downstreamSubscriber.history, [.subscription("FlatMap"),
.value(666)])
// Downstream demand is 1, so:
// - this value gets sent
// - downstream demand goes down to 0, but still need a buffered value
// - child is asked for 1 more
XCTAssertEqual(child1Publisher.send(777), .max(1))
XCTAssertEqual(downstreamSubscriber.history, [.subscription("FlatMap"),
.value(666),
.value(777)])
// Downstream demand is 0, so:
// - this value is buffered and NOT sent
// - downstream demand is 0 and there's a buffered value
// - child is asked for 0 more
XCTAssertEqual(child1Publisher.send(888), .none)
XCTAssertEqual(downstreamSubscriber.history, [.subscription("FlatMap"),
.value(666),
.value(777)])
XCTAssertEqual(child1Publisher.send(999), .none)
XCTAssertEqual(downstreamSubscriber.history, [.subscription("FlatMap"),
.value(666),
.value(777)])
// Downstream demands more, so:
// - the buffered value gets sent
// - child is asked for 1 more
XCTAssertEqual(child1Demand, .max(1))
XCTAssertEqual(child2Demand, .max(1))
try XCTUnwrap(downstreamSubscription).request(.max(10))
// This is a little odd, but rather than re-establishing a demand of 1 on the
// child like it did initially, Apple appears to demand 1 of the child for every
// buffered value that was sent. In this case, child1 is asked for 2 more.
XCTAssertEqual(child1Demand, .max(3))
XCTAssertEqual(child2Demand, .max(1))
XCTAssertEqual(downstreamSubscriber.history, [.subscription("FlatMap"),
.value(666),
.value(777),
.value(888),
.value(999)])
}
func testDemandFromLimitedToUnlimited() {
let upstreamPublisher = PassthroughSubject<Void, Never>()
var childDemand = Subscribers.Demand.none
let childSubscription = CustomSubscription(onRequest: { childDemand += $0 })
let childPublisher = CustomPublisherBase<Int, Never>(
subscription: childSubscription)
let flatMap = upstreamPublisher.flatMap { _ in childPublisher }
var downstreamSubscription: Subscription?
let downstreamSubscriber = TrackingSubscriberBase<Int, Never>(receiveSubscription:
{
downstreamSubscription = $0
$0.request(.max(3))
})
flatMap.subscribe(downstreamSubscriber)
upstreamPublisher.send()
XCTAssertEqual(childDemand, .max(1))
downstreamSubscription?.request(.unlimited)
XCTAssertEqual(childDemand, .unlimited)
}
func testChildValueReceivedWhileSendingValue() throws {
let upstreamPublisher = PassthroughSubject<AnyPublisher<Int, TestingError>,
TestingError>()
let child1Publisher = CustomPublisher(subscription: CustomSubscription())
let child2Publisher = CustomPublisher(subscription: CustomSubscription())
let flatMap = upstreamPublisher.flatMap { $0 }
let received777Sem = DispatchSemaphore(value: 0)
let downstreamSubscriber = TrackingSubscriber(
receiveSubscription: { $0.request(.max(2)) },
receiveValue: {
if $0 == 666 {
performConcurrentBlock {
XCTAssertEqual(child2Publisher.send(777), .max(1))
}
} else if $0 == 777 {
received777Sem.signal()
}
return .none
}
)
flatMap.subscribe(downstreamSubscriber)
upstreamPublisher.send(AnyPublisher(child1Publisher))
upstreamPublisher.send(AnyPublisher(child2Publisher))
XCTAssertEqual(child1Publisher.send(666), .max(1))
received777Sem.wait()
XCTAssertEqual(downstreamSubscriber.history, [.subscription("FlatMap"),
.value(666),
.value(777)])
}
func testCompletesProperlyWhenUpstreamOutlivesChildren() {
let upstreamPublisher = PassthroughSubject<AnyPublisher<Int, Never>, Never>()
let child1 = PassthroughSubject<Int, Never>()
let child2 = PassthroughSubject<Int, Never>()
let flatMap = upstreamPublisher.flatMap { $0 }
let downstreamSubscriber = TrackingSubscriberBase<Int, Never>(
receiveSubscription: { $0.request(.unlimited) })
flatMap.subscribe(downstreamSubscriber)
upstreamPublisher.send(AnyPublisher(child1))
upstreamPublisher.send(AnyPublisher(child2))
XCTAssertEqual(downstreamSubscriber.history, [.subscription("FlatMap")])
child1.send(666)
child1.send(completion: .finished)
// Better stay alive even after upstream and one child finished
XCTAssertEqual(downstreamSubscriber.history, [.subscription("FlatMap"),
.value(666)])
child2.send(777)
child2.send(completion: .finished)
// Better stay alive even after all children finished
XCTAssertEqual(downstreamSubscriber.history, [.subscription("FlatMap"),
.value(666),
.value(777)])
upstreamPublisher.send(completion: .finished)
// Better complete when upstream and all children finished
XCTAssertEqual(downstreamSubscriber.history, [.subscription("FlatMap"),
.value(666),
.value(777),
.completion(.finished)])
}
func testCompletesProperlyWhenChildrenOutliveUpstream() {
let upstreamPublisher = PassthroughSubject<AnyPublisher<Int, Never>, Never>()
let child1 = PassthroughSubject<Int, Never>()
let child2 = PassthroughSubject<Int, Never>()
let flatMap = upstreamPublisher.flatMap { $0 }
let downstreamSubscriber = TrackingSubscriberBase<Int, Never>(
receiveSubscription: { $0.request(.unlimited) })
flatMap.subscribe(downstreamSubscriber)
upstreamPublisher.send(AnyPublisher(child1))
upstreamPublisher.send(AnyPublisher(child2))
XCTAssertEqual(downstreamSubscriber.history, [.subscription("FlatMap")])
upstreamPublisher.send(completion: .finished)
// Better stay alive even after upstream finished
XCTAssertEqual(downstreamSubscriber.history, [.subscription("FlatMap")])
child1.send(666)
child1.send(completion: .finished)
// Better stay alive even after upstream and one child finished
XCTAssertEqual(downstreamSubscriber.history, [.subscription("FlatMap"),
.value(666)])
child2.send(777)
child2.send(completion: .finished)
// Better complete when upstream and all children finished
XCTAssertEqual(downstreamSubscriber.history, [.subscription("FlatMap"),
.value(666),
.value(777),
.completion(.finished)])
}
func testDoesNotCompleteWithBufferedValues() {
let upstreamPublisher = PassthroughSubject<Void, Never>()
let childSubscription = CustomSubscription()
let childPublisher = CustomPublisherBase<Int, Never>(
subscription: childSubscription)
let flatMap = upstreamPublisher.flatMap { _ in childPublisher }
var downstreamSubscription: Subscription?
let downstreamSubscriber = TrackingSubscriberBase<Int, Never>(receiveSubscription:
{
downstreamSubscription = $0
$0.request(.max(1))
})
flatMap.subscribe(downstreamSubscriber)
upstreamPublisher.send()
XCTAssertEqual(childPublisher.send(666), .max(1))
XCTAssertEqual(childPublisher.send(777), .none)
XCTAssertEqual(downstreamSubscriber.history, [.subscription("FlatMap"),
.value(666)])
upstreamPublisher.send(completion: .finished)
childPublisher.send(completion: .finished)
XCTAssertEqual(downstreamSubscriber.history, [.subscription("FlatMap"),
.value(666)])
downstreamSubscription?.request(.unlimited)
XCTAssertEqual(downstreamSubscriber.history, [.subscription("FlatMap"),
.value(666),
.value(777),
.completion(.finished)])
}
func testFailsIfUpstreamFails() {
let upstreamPublisher = PassthroughSubject<
AnyPublisher<Int, TestingError>,
TestingError>()
let flatMap = upstreamPublisher.flatMap { $0 }
let downstreamSubscriber = TrackingSubscriberBase<Int, TestingError>(
receiveSubscription: { $0.request(.unlimited) })
flatMap.subscribe(downstreamSubscriber)
upstreamPublisher.send(completion: .failure(TestingError.oops))
XCTAssertEqual(downstreamSubscriber.history, [.subscription("FlatMap"),
.completion(.failure(TestingError.oops))])
}
func testFailsIfChildFails() {
let upstream = PassthroughSubject<AnyPublisher<Int, TestingError>, TestingError>()
let child = PassthroughSubject<Int, TestingError>()
let flatMap = upstream.flatMap { $0 }
let tracking = TrackingSubscriberBase<Int, TestingError>(
receiveSubscription: { $0.request(.unlimited) })
flatMap.subscribe(tracking)
upstream.send(AnyPublisher(child))
child.send(completion: .failure(TestingError.oops))
XCTAssertEqual(tracking.history, [.subscription("FlatMap"),
.completion(.failure(TestingError.oops))])
}
func testFailsWithoutSendingBufferedValues() {
let upstreamPublisher = PassthroughSubject<
PassthroughSubject<Int, TestingError>,
TestingError>()
let childPublisher = PassthroughSubject<Int, TestingError>()
let flatMap = upstreamPublisher.flatMap { $0 }
let downstreamSubscriber = TrackingSubscriber(receiveSubscription: {
$0.request(.max(1))
})
flatMap.subscribe(downstreamSubscriber)
upstreamPublisher.send(childPublisher)
// Send a value
childPublisher.send(666)
XCTAssertEqual(downstreamSubscriber.history, [.subscription("FlatMap"),
.value(666)])
// Buffer a value
childPublisher.send(777)
XCTAssertEqual(downstreamSubscriber.history, [.subscription("FlatMap"),
.value(666)])
// Fail
let error = TestingError.oops
upstreamPublisher.send(completion: .failure(error))
XCTAssertEqual(downstreamSubscriber.history, [.subscription("FlatMap"),
.value(666),
.completion(.failure(error))])
}
func testAllSubscriptionsReleasedOnUpstreamFailure() {
let upstreamSubscription = CustomSubscription()
let upstreamPublisher = CustomPublisherBase<Int, TestingError>(
subscription: upstreamSubscription)
let downstreamSubscriber = TrackingSubscriberBase<Int, TestingError>(
receiveSubscription: { $0.request(.unlimited) })
let childSubscription = CustomSubscription()
let child = CustomPublisher(subscription: childSubscription)
let flatMap = upstreamPublisher.flatMap { _ in child }
flatMap.subscribe(downstreamSubscriber)
XCTAssertEqual(upstreamPublisher.send(1), .none)
XCTAssertEqual(child.send(666), .none)
upstreamPublisher.send(completion: .failure(TestingError.oops))
XCTAssertEqual(childSubscription.history, [.requested(.unlimited),
.cancelled])
XCTAssertEqual(upstreamSubscription.history, [.requested(.unlimited)])
}
func testAllSubscriptionsReleasedOnChildFailure() {
let upstreamSubscription = CustomSubscription()
let upstreamPublisher = CustomPublisherBase<Int, TestingError>(
subscription: upstreamSubscription)
let downstreamSubscriber = TrackingSubscriberBase<Int, TestingError>(
receiveSubscription: { $0.request(.unlimited) })
let child1 = PassthroughSubject<Int, TestingError>()
let child2Subscription = CustomSubscription()
let child2 = CustomPublisher(subscription: child2Subscription)
let children = [AnyPublisher(child1), AnyPublisher(child2)]
let flatMap = upstreamPublisher.flatMap { children[$0] }
flatMap.subscribe(downstreamSubscriber)
XCTAssertEqual(upstreamPublisher.send(0), .none)
XCTAssertEqual(upstreamPublisher.send(1), .none)
child1.send(666)
XCTAssertEqual(child2.send(777), .none)
child1.send(completion: .failure(TestingError.oops))
XCTAssertEqual(child2Subscription.history, [.requested(.unlimited),
.cancelled])
XCTAssertEqual(upstreamSubscription.history, [.requested(.unlimited)])
}
func testSendsSubcriptionDownstreamBeforeDemandUpstream() {
let sentDemandRequestUpstream = "Sent demand request upstream"
let sentSubscriptionDownstream = "Sent subcription downstream"
var receiveOrder: [String] = []
let upstreamSubscription = CustomSubscription(onRequest: { _ in
receiveOrder.append(sentDemandRequestUpstream) })
let upstreamPublisher = CustomPublisherBase<Int, Never>(
subscription: upstreamSubscription)
let flatMapPublisher = upstreamPublisher.flatMap { Just($0) }
let downstreamSubscriber = TrackingSubscriberBase<Int, Never>(
receiveSubscription: { _ in receiveOrder.append(sentSubscriptionDownstream) })
flatMapPublisher.subscribe(downstreamSubscriber)
XCTAssertEqual(receiveOrder, [sentSubscriptionDownstream,
sentDemandRequestUpstream])
}
// MARK: -
func testTestSuiteIncludesAllTests() {
// https://oleb.net/blog/2017/03/keeping-xctest-in-sync/
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
let thisClass = type(of: self)
let allTestsCount = thisClass.allTests.count
let darwinCount = thisClass.defaultTestSuite.testCaseCount
XCTAssertEqual(allTestsCount,
darwinCount,
"\(darwinCount - allTestsCount) tests are missing from allTests")
#endif
}
}
@@ -0,0 +1,138 @@
//
// IgnoreOutputTests.swift
//
// Created by Eric Patey on 16.08.20019.
//
import XCTest
#if OPENCOMBINE_COMPATIBILITY_TEST
import Combine
#else
import OpenCombine
#endif
@available(macOS 10.15, iOS 13.0, *)
final class IgnoreOutputTests: XCTestCase {
func testCompletionWithEmpty() {
let subscription = CustomSubscription()
let publisher = CustomPublisher(subscription: subscription)
let ignoreOutputPublisher = publisher.ignoreOutput()
let tracking = TrackingSubscriberBase<Never, TestingError>(
receiveSubscription: { $0.request(.max(42)) }
)
XCTAssertEqual(tracking.history, [])
ignoreOutputPublisher.subscribe(tracking)
XCTAssertEqual(tracking.history, [.subscription("IgnoreOutput")])
publisher.send(completion: .finished)
XCTAssertEqual(tracking.history, [.subscription("IgnoreOutput"),
.completion(.finished)])
}
func testCompletionWithValues() {
let upstreamSubscription = CustomSubscription()
let upstreamPublisher = CustomPublisher(subscription: upstreamSubscription)
let ignoreOutputPublisher = upstreamPublisher.ignoreOutput()
let tracking = TrackingSubscriberBase<Never, TestingError>(
receiveSubscription: { $0.request(.max(42)) }
)
XCTAssertEqual(tracking.history, [])
ignoreOutputPublisher.subscribe(tracking)
XCTAssertEqual(tracking.history, [.subscription("IgnoreOutput")])
XCTAssertEqual(upstreamPublisher.send(666), .none)
XCTAssertEqual(tracking.history, [.subscription("IgnoreOutput")])
upstreamPublisher.send(completion: .finished)
XCTAssertEqual(tracking.history, [.subscription("IgnoreOutput"),
.completion(.finished)])
}
func testCompletionWithError() {
let subscription = CustomSubscription()
let publisher = CustomPublisher(subscription: subscription)
let ignoreOutputPublisher = publisher.ignoreOutput()
let tracking = TrackingSubscriberBase<Never, TestingError>(
receiveSubscription: { $0.request(.max(42)) }
)
XCTAssertEqual(tracking.history, [])
ignoreOutputPublisher.subscribe(tracking)
XCTAssertEqual(tracking.history, [.subscription("IgnoreOutput")])
XCTAssertEqual(publisher.send(666), .none)
XCTAssertEqual(tracking.history, [.subscription("IgnoreOutput")])
publisher.send(completion: .failure(.oops))
XCTAssertEqual(tracking.history, [.subscription("IgnoreOutput"),
.completion(.failure(.oops))])
}
func testDemand() {
// demand from downstream is ignored since no values are ever
// sent. upstream demand is set to unlimited and left alone.
let subscription = CustomSubscription()
let publisher = CustomPublisher(subscription: subscription)
let ignoreOutput = publisher.ignoreOutput()
var downstreamSubscription: Subscription?
let tracking = TrackingSubscriberBase<Never, TestingError>(
receiveSubscription: {
$0.request(.max(42))
downstreamSubscription = $0
})
ignoreOutput.subscribe(tracking)
XCTAssertNotNil(downstreamSubscription)
XCTAssertEqual(subscription.history, [.requested(.unlimited)])
XCTAssertEqual(publisher.send(0), .none)
XCTAssertEqual(subscription.history, [.requested(.unlimited)])
XCTAssertEqual(publisher.send(2), .none)
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 testSendsSubcriptionDownstreamBeforeDemandUpstream() {
let sentDemandRequestUpstream = "Sent demand request upstream"
let sentSubscriptionDownstream = "Sent subcription downstream"
var receiveOrder: [String] = []
let subscription = CustomSubscription(onRequest: { _ in
receiveOrder.append(sentDemandRequestUpstream)
})
let publisher = CustomPublisher(subscription: subscription)
let ignoreOutputPublisher = publisher.ignoreOutput()
let tracking =
TrackingSubscriberBase<Never, TestingError>(receiveSubscription: { _ in
receiveOrder.append(sentSubscriptionDownstream)
})
ignoreOutputPublisher.subscribe(tracking)
XCTAssertEqual(receiveOrder, [sentSubscriptionDownstream,
sentDemandRequestUpstream])
}
}
@@ -16,57 +16,6 @@ import OpenCombine
@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),
("testMaxOperatorSpecialization", testMaxOperatorSpecialization),
("testContainsOperatorSpecialization", testContainsOperatorSpecialization),
("testTryContainsOperatorSpecialization", testTryContainsOperatorSpecialization),
("testRemoveDuplicatesOperatorSpecialization",
testRemoveDuplicatesOperatorSpecialization),
("testTryRemoveDuplicatesOperatorSpecialization",
testTryRemoveDuplicatesOperatorSpecialization),
("testAllSatisfyOperatorSpecialization", testAllSatisfyOperatorSpecialization),
("testTryAllSatisfyOperatorSpecialization",
testTryAllSatisfyOperatorSpecialization),
("testCollectOperatorSpecialization", testCollectOperatorSpecialization),
("testCountOperatorSpecialization", testCountOperatorSpecialization),
("testDropFirstOperatorSpecialization", testDropFirstOperatorSpecialization),
("testDropWhileOperatorSpecialization", testDropWhileOperatorSpecialization),
("testFirstOperatorSpecialization", testFirstOperatorSpecialization),
("testFirstWhereOperatorSpecializtion", testFirstWhereOperatorSpecializtion),
("testLastOperatorSpecialization", testLastOperatorSpecialization),
("testLastWhereOperatorSpecializtion", testLastWhereOperatorSpecializtion),
("testIgnoreOutputOperatorSpecialization",
testIgnoreOutputOperatorSpecialization),
("testMapOperatorSpecialization", testMapOperatorSpecialization),
("testTryMapOperatorSpecialization", testTryMapOperatorSpecialization),
("testCompactMapOperatorSpecialization", testCompactMapOperatorSpecialization),
("testFilterOperatorSpecialization", testFilterOperatorSpecialization),
("testMapErrorOperatorSpecialization", testMapErrorOperatorSpecialization),
("testReplaceErrorOperatorSpecialization",
testReplaceErrorOperatorSpecialization),
("testReplaceEmptyOperatorSpecialization",
testReplaceEmptyOperatorSpecialization),
("testRetryOperatorSpecialization", testRetryOperatorSpecialization),
("testReduceOperatorSpecialization", testReduceOperatorSpecialization),
("testTryReduceOperatorSpecialization", testTryReduceOperatorSpecialization),
("testScanOperatorSpecialization", testScanOperatorSpecialization),
("testTryScanOperatorSpecialization", testTryScanOperatorSpecialization),
("testOutputAtIndexOperatorSpecialization",
testOutputAtIndexOperatorSpecialization),
("testOutputInRangeOperatorSpecialization",
testOutputInRangeOperatorSpecialization),
("testPrefixOperatorSpecialization", testPrefixOperatorSpecialization),
("testPrefixWhileOperatorSpecialization", testPrefixWhileOperatorSpecialization),
("testSetFailureTypeOperatorSpecialization",
testSetFailureTypeOperatorSpecialization),
]
private typealias Sut = Just
func testJustNoInitialDemand() {
@@ -15,18 +15,6 @@ import OpenCombine
@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
@@ -15,29 +15,6 @@ import OpenCombine
@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),
("testMapCancel", testMapCancel),
("testTryMapCancel", testTryMapCancel),
("testCancelAlreadyCancelled", testCancelAlreadyCancelled),
("testLifecycle", testLifecycle),
("testMapOperatorSpecializationForMap", testMapOperatorSpecializationForMap),
("testTryMapOperatorSpecializationForMap",
testTryMapOperatorSpecializationForMap),
("testMapOperatorSpecializationForTryMap",
testMapOperatorSpecializationForTryMap),
("testTryMapOperatorSpecializationForTryMap",
testTryMapOperatorSpecializationForTryMap)
]
func testEmpty() {
// Given
@@ -234,7 +211,7 @@ final class MapTests: XCTestCase {
// When
map.subscribe(tracking)
try XCTUnwrap(downstreamSubscription).cancel()
_ = publisher.send(1)
XCTAssertEqual(publisher.send(1), .none)
publisher.send(completion: .finished)
// Then
XCTAssertEqual(subscription.history, [.requested(.unlimited), .cancelled])
@@ -253,7 +230,7 @@ final class MapTests: XCTestCase {
// When
map.subscribe(tracking)
try XCTUnwrap(downstreamSubscription).cancel()
_ = publisher.send(1)
XCTAssertEqual(publisher.send(1), .none)
publisher.send(completion: .finished)
// Then
XCTAssertEqual(subscription.history, [.requested(.unlimited), .cancelled])
@@ -352,12 +329,14 @@ final class MapTests: XCTestCase {
publisher.send(2)
publisher.send(3)
publisher.send(5)
publisher.send(completion: .finished)
XCTAssert(map1.upstream === map2.upstream)
XCTAssertEqual(tracking.history, [.subscription("PassthroughSubject"),
.value(5),
.value(7),
.value(11)])
.value(11),
.completion(.finished)])
}
func testTryMapOperatorSpecializationForMap() {
@@ -16,14 +16,6 @@ import OpenCombine
@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 = CustomPublisher(subscription: CustomSubscription())
@@ -16,46 +16,6 @@ import OpenCombine
@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
@@ -194,7 +154,7 @@ final class OptionalPublisherTests: XCTestCase {
func testCollectOperatorSpecialization() {
XCTAssertEqual(Sut<Int>(13).collect(), Sut([13]))
XCTAssertEqual(Sut<Int>(nil).collect(), Sut(nil))
XCTAssertEqual(Sut<Int>(nil).collect(), Sut([]))
}
func testCountOperatorSpecialization() {
@@ -16,12 +16,6 @@ import OpenCombine
@available(macOS 10.15, iOS 13.0, *)
final class PrintTests: XCTestCase {
static let allTests = [
("testPrintWithoutPrefix", testPrintWithoutPrefix),
("testPrintWithPrefix", testPrintWithPrefix),
("testSynchronization", testSynchronization),
]
func testPrintWithoutPrefix() {
let stream = StringStream()
@@ -0,0 +1,190 @@
//
// ReplaceErrorTests.swift
// OpenCombineTests
//
// Created by Bogdan Vlad on 8/29/19.
//
import XCTest
#if OPENCOMBINE_COMPATIBILITY_TEST
import Combine
#else
import OpenCombine
#endif
@available(macOS 10.15, iOS 13.0, *)
final class ReplaceErrorTests: XCTestCase {
func testEmpty() {
let helper = OperatorTestHelper(publisherType: CustomPublisher.self,
initialDemand: nil,
receiveValueDemand: .none,
createSut: { $0.replaceError(with: 42) })
XCTAssertEqual(helper.tracking.history, [.subscription("ReplaceError")])
}
func testError() {
let helper = OperatorTestHelper(publisherType: CustomPublisher.self,
initialDemand: .max(1),
receiveValueDemand: .none,
createSut: { $0.replaceError(with: 42) })
helper.publisher.send(completion: .failure(TestingError.oops))
XCTAssertEqual(helper.tracking.history, [.subscription("ReplaceError"),
.value(42),
.completion(.finished)
])
}
func testWithoutError() {
let helper = OperatorTestHelper(publisherType: CustomPublisher.self,
initialDemand: .max(1),
receiveValueDemand: .none,
createSut: { $0.replaceError(with: 40) })
XCTAssertEqual(helper.publisher.send(42), .none)
helper.publisher.send(completion: .finished)
XCTAssertEqual(helper.tracking.history, [.subscription("ReplaceError"),
.value(42),
.completion(.finished)])
}
func testSendingValueAndThenError() {
let helper = OperatorTestHelper(publisherType: CustomPublisher.self,
initialDemand: .max(1),
receiveValueDemand: .max(1),
createSut: { $0.replaceError(with: 42) })
XCTAssertEqual(helper.publisher.send(41), .max(1))
helper.publisher.send(completion: .failure(TestingError.oops))
XCTAssertEqual(helper.tracking.history, [.subscription("ReplaceError"),
.value(41),
.value(42),
.completion(.finished)])
}
func testFailingBeforeDemanding() {
let helper = OperatorTestHelper(publisherType: CustomPublisher.self,
initialDemand: nil,
receiveValueDemand: .max(1),
createSut: { $0.replaceError(with: 42) })
helper.publisher.send(completion: .failure(TestingError.oops))
XCTAssertEqual(helper.tracking.history, [.subscription("ReplaceError")])
helper.downstreamSubscription?.request(.unlimited)
XCTAssertEqual(helper.tracking.history, [.subscription("ReplaceError"),
.value(42),
.completion(.finished)])
}
func testLifecycle() throws {
var deinitCounter = 0
let onDeinit = { deinitCounter += 1 }
do {
let passthrough = PassthroughSubject<Int, TestingError>()
let replaceError = passthrough.replaceError(with: 10)
let emptySubscriber = TrackingSubscriberBase<Int, Never>(
onDeinit: onDeinit
)
XCTAssertTrue(emptySubscriber.history.isEmpty)
replaceError.print("test").subscribe(emptySubscriber)
XCTAssertEqual(emptySubscriber.subscriptions.count, 1)
passthrough.send(31)
XCTAssertEqual(emptySubscriber.inputs.count, 0)
passthrough.send(completion: .failure("failure"))
XCTAssertEqual(emptySubscriber.completions.count, 0)
}
XCTAssertEqual(deinitCounter, 0)
do {
let passthrough = PassthroughSubject<Int, TestingError>()
let replaceError = passthrough.replaceError(with: 10)
let emptySubscriber = TrackingSubscriberBase<Int, Never>(
onDeinit: onDeinit
)
XCTAssertTrue(emptySubscriber.history.isEmpty)
replaceError.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 replaceError = passthrough.replaceError(with: 10)
let emptySubscriber = TrackingSubscriberBase<Int, Never>(
receiveSubscription: { subscription = $0; $0.request(.unlimited) },
onDeinit: onDeinit
)
XCTAssertTrue(emptySubscriber.history.isEmpty)
replaceError.subscribe(emptySubscriber)
XCTAssertEqual(emptySubscriber.subscriptions.count, 1)
passthrough.send(31)
XCTAssertEqual(emptySubscriber.inputs.count, 1)
XCTAssertEqual(emptySubscriber.completions.count, 0)
XCTAssertNotNil(subscription)
}
XCTAssertEqual(deinitCounter, 0)
try XCTUnwrap(subscription).cancel()
XCTAssertEqual(deinitCounter, 0)
}
func testCancelAlreadyCancelled() throws {
let helper = OperatorTestHelper(publisherType: CustomPublisher.self,
initialDemand: .unlimited,
receiveValueDemand: .max(1),
createSut: { $0.replaceError(with: 42) })
helper.downstreamSubscription?.cancel()
try XCTUnwrap(helper.downstreamSubscription).cancel()
helper.downstreamSubscription?.request(.unlimited)
try XCTUnwrap(helper.downstreamSubscription).cancel()
XCTAssertEqual(helper.subscription.history, [.requested(.unlimited),
.cancelled])
}
func testErrorWhileDownstreamDemandIsZero() {
let helper = OperatorTestHelper(publisherType: CustomPublisher.self,
initialDemand: .max(1),
receiveValueDemand: .none,
createSut: { $0.replaceError(with: 42) })
// Send demanded value
_ = helper.publisher.send(9)
XCTAssertEqual(helper.tracking.history, [.subscription("ReplaceError"),
.value(9)])
helper.publisher.send(completion: .failure(TestingError.oops))
XCTAssertEqual(helper.tracking.history, [.subscription("ReplaceError"),
.value(9)])
helper.downstreamSubscription?.request(.max(1))
XCTAssertEqual(helper.tracking.history, [.subscription("ReplaceError"),
.value(9),
.value(42),
.completion(.finished)])
}
}
private struct OtherError: Error {
let original: Error
init(_ original: Error) {
self.original = original
}
}
@@ -15,11 +15,6 @@ import OpenCombine
@available(macOS 10.15, iOS 13.0, *)
final class ReplaceNilTests: XCTestCase {
static let allTests = [
("testReplacesNilElement", testReplacesNilElement),
("testExistingElementIsPreserved", testExistingElementIsPreserved),
("testMultipleReplacements", testMultipleReplacements)
]
func testReplacesNilElement() {
// Given
@@ -16,49 +16,6 @@ import OpenCombine
@available(macOS 10.15, iOS 13.0, *)
final class ResultPublisherTests: XCTestCase {
static let allTests = [
("testOnceSuccessNoInitialDemand", testOnceSuccessNoInitialDemand),
("testOnceSuccessWithInitialDemand", testOnceSuccessWithInitialDemand),
("testSuccessCancelOnSubscription", testSuccessCancelOnSubscription),
("testOnceFailure", testOnceFailure),
("testFailureCancelOnSubscription", testFailureCancelOnSubscription),
("testLifecycle", testLifecycle),
("testCustomMirror", testCustomMirror),
("testMinOperatorSpecialization", testMinOperatorSpecialization),
("testTryMinOperatorSpecialization", testTryMinOperatorSpecialization),
("testMaxOperatorSpecialization", testMaxOperatorSpecialization),
("testTryMaxOperatorSpecialization", testTryMaxOperatorSpecialization),
("testContainsOperatorSpecialization", testContainsOperatorSpecialization),
("testTryContainsOperatorSpecialization", testTryContainsOperatorSpecialization),
("testRemoveDuplicatesOperatorSpecialization",
testRemoveDuplicatesOperatorSpecialization),
("testTryRemoveDuplicatesOperatorSpecialization",
testTryRemoveDuplicatesOperatorSpecialization),
("testAllSatifyOperatorSpecialization", testAllSatifyOperatorSpecialization),
("testTryAllSatifyOperatorSpecialization",
testTryAllSatifyOperatorSpecialization),
("testCollectOperatorSpecialization", testCollectOperatorSpecialization),
("testCountOperatorSpecialization", testCountOperatorSpecialization),
("testFirstOperatorSpecialization", testFirstOperatorSpecialization),
("testLastOperatorSpecialization", testLastOperatorSpecialization),
("testIgnoreOutputOperatorSpecialization",
testIgnoreOutputOperatorSpecialization),
("testMapOperatorSpecialization", testMapOperatorSpecialization),
("testTryMapOperatorSpecialization", testTryMapOperatorSpecialization),
("testMapErrorOperatorSpecialization", testMapErrorOperatorSpecialization),
("testReplaceErrorOperatorSpecialization",
testReplaceErrorOperatorSpecialization),
("testReplaceEmptyOperatorSpecialization",
testReplaceEmptyOperatorSpecialization),
("testRetryOperatorSpecialization", testRetryOperatorSpecialization),
("testReduceOperatorSpecialization", testReduceOperatorSpecialization),
("testTryReduceOperatorSpecialization", testTryReduceOperatorSpecialization),
("testScanOperatorSpecialization", testScanOperatorSpecialization),
("testTryScanOperatorSpecialization", testTryScanOperatorSpecialization),
("testSetFailureTypeOperatorSpecialization",
testSetFailureTypeOperatorSpecialization),
]
#if OPENCOMBINE_COMPATIBILITY_TEST || !canImport(Combine)
private typealias ResultPublisher<Output, Failure: Error> =
Result<Output, Failure>.Publisher
@@ -16,60 +16,6 @@ import OpenCombine
@available(macOS 10.15, iOS 13.0, *)
final class SequenceTests: XCTestCase {
static let allTests = [
("testEmptySequence", testEmptySequence),
("testSequenceNoInitialDemand", testSequenceNoInitialDemand),
("testSequenceInitialDemand", testSequenceInitialDemand),
("testCancelOnSubscription", testCancelOnSubscription),
("testLifecycle", testLifecycle),
("testAllSatisfyOperatorSpecialization", testAllSatisfyOperatorSpecialization),
("testTryAllSatisfyOperatorSpecialization",
testTryAllSatisfyOperatorSpecialization),
("testCollectOperatorSpecialization", testCollectOperatorSpecialization),
("testCompactMapOperatorSpecialization", testCompactMapOperatorSpecialization),
("testMinOperatorSpecialization", testMinOperatorSpecialization),
("testMaxOperatorSpecialization", testMaxOperatorSpecialization),
("testContainsOperatorSpecialization", testContainsOperatorSpecialization),
("testTryContainsOperatorSpecialization", testTryContainsOperatorSpecialization),
("testDropWhileOperatorSpecialization", testDropWhileOperatorSpecialization),
("testDropFirstOperatorSpecialization", testDropFirstOperatorSpecialization),
("testFirstWhereOperatorSpecializtion", testFirstWhereOperatorSpecializtion),
("testFilterOperatorSpecialization", testFilterOperatorSpecialization),
("testIgnoreOutputOperatorSpecialization",
testIgnoreOutputOperatorSpecialization),
("testMapOperatorSpecialization", testMapOperatorSpecialization),
("testPrefixOperatorSpecialization", testPrefixOperatorSpecialization),
("testPrefixWhileOperatorSpecialization", testPrefixWhileOperatorSpecialization),
("testReduceOperatorSpecialization", testReduceOperatorSpecialization),
("testTryReduceOperatorSpecialization", testTryReduceOperatorSpecialization),
("testReplaceNilOperatorSpecialization", testReplaceNilOperatorSpecialization),
("testScanOperatorSpecialization", testScanOperatorSpecialization),
("testSetFailureTypeOperatorSpecialization",
testSetFailureTypeOperatorSpecialization),
("testRemoveDuplicatesOperatorSpecialization",
testRemoveDuplicatesOperatorSpecialization),
("testFirstOperatorSpecialization", testFirstOperatorSpecialization),
("testCountOperatorSpecialization", testCountOperatorSpecialization),
("testOutputAtIndexOperatorSpecialization",
testOutputAtIndexOperatorSpecialization),
("testOutputInRangeOperatorSpecialization",
testOutputInRangeOperatorSpecialization),
("testLastOperatorSpecialization", testLastOperatorSpecialization),
("testLastWhereOperatorSpecializtion", testLastWhereOperatorSpecializtion),
("testPrependVariadicOperatorSpezialization",
testPrependVariadicOperatorSpezialization),
("testPrependSequenceOperatorSpecialization",
testPrependSequenceOperatorSpecialization),
("testPrependPublisherOperatorSpecialization",
testPrependPublisherOperatorSpecialization),
("testAppendVariadicOperatorSpezialization",
testAppendVariadicOperatorSpezialization),
("testAppendSequenceOperatorSpecialization",
testAppendSequenceOperatorSpecialization),
("testAppendPublisherOperatorSpecialization",
testAppendPublisherOperatorSpecialization),
]
#if OPENCOMBINE_COMPATIBILITY_TEST || !canImport(Combine)
private typealias ResultPublisher<Output, Failure: Error> =
Result<Output, Failure>.Publisher
@@ -16,17 +16,6 @@ import OpenCombine
@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) }
@@ -16,14 +16,6 @@ import OpenCombine
@available(macOS 10.15, iOS 13.0, *)
final class AssignTests: XCTestCase {
static let allTests = [
("testDescription", testDescription),
("testReflection", testReflection),
("testSubscription", testSubscription),
("testReceiveValue", testReceiveValue),
("testPublisherOperator", testPublisherOperator),
]
private typealias Sut<Root> = Subscribers.Assign<Root, Int>
private final class TestObject {
@@ -58,7 +50,7 @@ final class AssignTests: XCTestCase {
XCTAssertEqual(children[1].value as? ReferenceWritableKeyPath<TestObject, Int>,
\.value)
XCTAssertEqual(children[2].label, "upstreamSubscription")
XCTAssertEqual(children[2].label, "status")
XCTAssertNotNil(children[2].value)
}
@@ -18,11 +18,7 @@ import Foundation
@available(macOS 10.15, iOS 13.0, *)
final class CompletionTests: XCTestCase {
static let allTests = [
("testEncodingDecoding", testEncodingDecoding)
]
typealias Sut = Subscribers.Completion<TestingError>
private typealias Sut = Subscribers.Completion<TestingError>
let encoder: JSONEncoder = {
let encoder = JSONEncoder()
@@ -16,14 +16,6 @@ import OpenCombine
@available(macOS 10.15, iOS 13.0, *)
final class SinkTests: XCTestCase {
static let allTests = [
("testDescription", testDescription),
("testReflection", testReflection),
("testSubscription", testSubscription),
("testReceiveValue", testReceiveValue),
("testPublisherOperator", testPublisherOperator),
]
private typealias Sut = Subscribers.Sink<Int, Never>
func testDescription() {
@@ -18,18 +18,6 @@ import OpenCombine
@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),
("testEncodeDecodeJSON", testEncodeDecodeJSON),
("testEncodeDecodePlist", testEncodeDecodePlist),
]
func testCrashesOnNegativeValue() {
assertCrashes {
_ = Subscribers.Demand.max(-1)
@@ -108,6 +96,7 @@ final class SubscribersDemandTests: XCTestCase {
}
func testComparison() {
// swiftlint:disable no_space_in_method_call
XCTAssertFalse(Subscribers.Demand.unlimited < .unlimited)
XCTAssertFalse(Subscribers.Demand.unlimited > .unlimited)
XCTAssertFalse(Subscribers.Demand.unlimited != .unlimited)
@@ -197,6 +186,7 @@ final class SubscribersDemandTests: XCTestCase {
XCTAssertTrue (100 == Subscribers.Demand.max(100))
XCTAssertTrue (100 <= Subscribers.Demand.max(100))
XCTAssertTrue (100 >= Subscribers.Demand.max(100))
// swiftlint:enable no_space_in_method_call
}
func testMax() {
@@ -1,41 +0,0 @@
//
// Subscribers.Demand.swift
//
//
// Created by Sergej Jaskiewicz on 10.06.2019.
//
import XCTest
#if !canImport(ObjectiveC)
public func allTests() -> [XCTestCaseEntry] {
return [
testCase(AnyCancellableTests.allTests),
testCase(AnyPublisherTests.allTests),
testCase(AssignTests.allTests),
testCase(CombineIdentifierTests.allTests),
testCase(CompletionTests.allTests),
testCase(CurrentValueSubjectTests.allTests),
testCase(DecodeTests.allTests),
testCase(DropWhileTests.allTests),
testCase(EmptyTests.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