Skip to content

Commit

Permalink
Add tests for Publishers.MapKeyPath
Browse files Browse the repository at this point in the history
  • Loading branch information
broadwaylamb committed Oct 8, 2019
1 parent b7df8a4 commit 80e32c2
Show file tree
Hide file tree
Showing 13 changed files with 680 additions and 725 deletions.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 9 additions & 2 deletions Sources/OpenCombine/Publishers/Publishers.MapKeyPath.swift.gyb
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,10 @@ extension Publisher {
% init_args_joined = ',\n '.join(init_args)
%
% publisher_name = make_publisher_name(arity)
%
% doc_cardinal = 'a keyt path' if arity == 1 else cardinal + ' key paths'

/// Returns a publisher that publishes the values of three key paths as a tuple.
/// Returns a publisher that publishes the values of ${doc_cardinal} as a tuple.
///
/// - Parameters:
% for i in range(arity):
Expand Down Expand Up @@ -159,7 +161,12 @@ extension Publishers.${publisher_name} {
var description: String { return "${inner_description}" }

var customMirror: Mirror {
return Mirror(self, children: EmptyCollection())
let children: [Mirror.Child] = [
% for i in range(arity):
("${key_path_var(i, arity)}", ${key_path_var(i, arity)}),
% end
]
return Mirror(self, children: children)
}

var playgroundDescription: Any { return description }
Expand Down
122 changes: 122 additions & 0 deletions Tests/OpenCombineTests/Helpers/TestLifecycle.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
//
// TestLifecycle.swift
//
//
// Created by Sergej Jaskiewicz on 08.10.2019.
//

import XCTest

#if OPENCOMBINE_COMPATIBILITY_TEST
import Combine
#else
import OpenCombine
#endif

@available(macOS 10.15, iOS 13.0, *)
func testLifecycle<UpstreamOutput, Operator: Publisher>(
file: StaticString = #file,
line: UInt = #line,
sendValue valueToBeSent: UpstreamOutput,
cancellingSubscriptionReleasesSubscriber: Bool,
_ makeOperator: (PassthroughSubject<UpstreamOutput, TestingError>) -> Operator
) throws {
var deinitCounter = 0

let onDeinit = { deinitCounter += 1 }

// Lifecycle test #1
do {
let passthrough = PassthroughSubject<UpstreamOutput, TestingError>()
let operatorPublisher = makeOperator(passthrough)
let emptySubscriber =
TrackingSubscriberBase<Operator.Output, Operator.Failure>(onDeinit: onDeinit)
XCTAssertTrue(emptySubscriber.history.isEmpty,
"Lifecycle test #1: thesubscriber's history should be empty",
file: file,
line: line)
operatorPublisher.subscribe(emptySubscriber)
passthrough.send(valueToBeSent)
passthrough.send(completion: .failure("failure"))
}

if cancellingSubscriptionReleasesSubscriber {
XCTAssertEqual(deinitCounter,
1,
"""
Lifecycle test #1: deinit should be called, because \
the subscription has completed
""",
file: file,
line: line)
} else {
XCTAssertEqual(deinitCounter,
0,
"""
Lifecycle test #1: deinit should not be called
""",
file: file,
line: line)
}

// Lifecycle test #2
do {
let passthrough = PassthroughSubject<UpstreamOutput, TestingError>()
let operatorPublisher = makeOperator(passthrough)
let emptySubscriber =
TrackingSubscriberBase<Operator.Output, Operator.Failure>(onDeinit: onDeinit)
operatorPublisher.subscribe(emptySubscriber)
}

XCTAssertEqual(deinitCounter,
cancellingSubscriptionReleasesSubscriber ? 1 : 0,
"""
Lifecycle test #2: deinit should not be called, \
because the subscription is never cancelled
""",
file: file,
line: line)

// Lifecycle test #3

var subscription: Subscription?

do {
let passthrough = PassthroughSubject<UpstreamOutput, TestingError>()
let operatorPublisher = makeOperator(passthrough)
let emptySubscriber = TrackingSubscriberBase<Operator.Output, Operator.Failure>(
receiveSubscription: { subscription = $0; $0.request(.unlimited) },
onDeinit: onDeinit
)
operatorPublisher.subscribe(emptySubscriber)
passthrough.send(valueToBeSent)
}

XCTAssertEqual(deinitCounter,
cancellingSubscriptionReleasesSubscriber ? 1 : 0,
"""
Lifecycle test #3: deinit should not be called, \
because the subscription is not cancelled yet
""",
file: file,
line: line)

try XCTUnwrap(subscription, file: file, line: line).cancel()

if cancellingSubscriptionReleasesSubscriber {
XCTAssertEqual(deinitCounter,
2,
"""
Lifecycle test #3: deinit should be called, because
the subscription has been cancelled
""",
file: file,
line: line)
} else {
XCTAssertEqual(deinitCounter,
0,
"Lifecycle test #3: deinit should not be called",
file: file,
line: line)
}
}
3 changes: 2 additions & 1 deletion Tests/OpenCombineTests/Helpers/TestReflection.swift
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ internal func testReflection<Output, Failure: Error, Operator: Publisher>(
customMirror customMirrorPredicate: ((Mirror) -> Bool)?,
playgroundDescription: String,
_ makeOperator: (CustomConnectablePublisherBase<Output, Failure>) -> Operator
) throws where Operator.Output: Equatable {
) throws {
let publisher = CustomConnectablePublisherBase<Output, Failure>(subscription: nil)
let operatorPublisher = makeOperator(publisher)
let tracking = TrackingSubscriberBase<Operator.Output, Operator.Failure>()
Expand All @@ -87,6 +87,7 @@ internal func testReflection<Output, Failure: Error, Operator: Publisher>(

if let customMirrorPredicate = customMirrorPredicate {
XCTAssert(customMirrorPredicate(customMirror),
"customMirror doesn't satisfy the predicate",
file: file,
line: line)
}
Expand Down
83 changes: 61 additions & 22 deletions Tests/OpenCombineTests/Helpers/TrackingSubscriber.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
// Created by Sergej Jaskiewicz on 11.06.2019.
//

import XCTest

#if OPENCOMBINE_COMPATIBILITY_TEST
import Combine
#else
Expand Down Expand Up @@ -37,36 +39,16 @@ typealias TrackingSubscriber = TrackingSubscriberBase<Int, TestingError>
/// is considered equal to any other subscription no matter what the subscription object
/// actually is.
@available(macOS 10.15, iOS 13.0, *)
final class TrackingSubscriberBase<Value: Equatable, Failure: Error>
final class TrackingSubscriberBase<Value, Failure: Error>
: Subscriber,
CustomStringConvertible
{

enum Event: Equatable, CustomStringConvertible {
enum Event: CustomStringConvertible {
case subscription(StringSubscription)
case value(Value)
case completion(Subscribers.Completion<Failure>)

static func == (lhs: Event, rhs: Event) -> Bool {
switch (lhs, rhs) {
case let (.subscription(lhs), .subscription(rhs)):
return lhs == rhs
case let (.value(lhs), .value(rhs)):
return lhs == rhs
case let (.completion(lhs), .completion(rhs)):
switch (lhs, rhs) {
case (.finished, .finished):
return true
case let (.failure(lhs), .failure(rhs)):
return (lhs as? TestingError) == (rhs as? TestingError)
default:
return false
}
default:
return false
}
}

var description: String {
switch self {
case .subscription(let subscription):
Expand Down Expand Up @@ -175,12 +157,69 @@ final class TrackingSubscriberBase<Value: Equatable, Failure: Error>
return "\(type(of: self)): \(history)"
}

func assertHistoryEqual(_ expected: [Event],
valueComparator: (Value, Value) -> Bool,
file: StaticString = #file,
line: UInt = #line) {

let equals = history.count == expected.count &&
zip(history, expected)
.allSatisfy { $0.isEqual(to: $1, valueComparator: valueComparator) }

XCTAssert(equals,
"\(history) is not equal to \(expected)",
file: file,
line: line)
}

deinit {
onDeinit?()
_onDeinit?()
}
}

@available(macOS 10.15, iOS 13.0, *)
extension TrackingSubscriberBase where Value: Equatable {
func assertHistoryEqual(_ expected: [Event],
file: StaticString = #file,
line: UInt = #line) {
assertHistoryEqual(expected, valueComparator: ==, file: file, line: line)
}
}

@available(macOS 10.15, iOS 13.0, *)
extension TrackingSubscriberBase.Event {
func isEqual(to other: TrackingSubscriberBase<Value, Failure>.Event,
valueComparator: (Value, Value) -> Bool) -> Bool {
switch (self, other) {
case let (.subscription(lhs), .subscription(rhs)):
return lhs == rhs
case let (.value(lhs), .value(rhs)):
return valueComparator(lhs, rhs)
case let (.completion(lhs), .completion(rhs)):
switch (lhs, rhs) {
case (.finished, .finished):
return true
case let (.failure(lhs), .failure(rhs)):
return (lhs as? TestingError) == (rhs as? TestingError)
default:
return false
}
default:
return false
}
}
}

@available(macOS 10.15, iOS 13.0, *)
extension TrackingSubscriberBase.Event: Equatable where Value: Equatable {

static func == (lhs: TrackingSubscriberBase<Value, Failure>.Event,
rhs: TrackingSubscriberBase<Value, Failure>.Event) -> Bool {
return lhs.isEqual(to: rhs, valueComparator: ==)
}
}

@available(macOS 10.15, iOS 13.0, *)
typealias TrackingSubject<Output: Equatable> = TrackingSubjectBase<Output, TestingError>

Expand Down

0 comments on commit 80e32c2

Please sign in to comment.