Skip to content

Commit

Permalink
refine TracingSubscriber
Browse files Browse the repository at this point in the history
  • Loading branch information
ddddxxx committed Jan 23, 2020
1 parent c258a69 commit 7968602
Show file tree
Hide file tree
Showing 53 changed files with 329 additions and 294 deletions.
11 changes: 10 additions & 1 deletion Sources/CXTest/CompletionExtensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,19 @@ public extension Subscribers.Completion {

var isFailure: Bool {
switch self {
case .finished:
return false
case .failure:
return true
}
}

var error: Failure? {
switch self {
case .finished:
return false
return nil
case let .failure(e):
return e
}
}
}
114 changes: 90 additions & 24 deletions Sources/CXTest/TracingSubscriber.swift
Original file line number Diff line number Diff line change
@@ -1,59 +1,73 @@
import CXShim
import CXUtility

public class TracingSubscriber<Input, Failure: Error>: Subscriber, CustomStringConvertible, CustomReflectable, CustomPlaygroundDisplayConvertible {
public class TracingSubscriber<Input, Failure: Error>: Subscriber {

public typealias Event = TracingSubscriberEvent<Input, Failure>
public enum Event {
case subscription(CombineIdentifier)
case value(Input)
case completion(Subscribers.Completion<Failure>)
}

private let receiveSubscriptionBody: ((Subscription) -> Void)?
private let receiveValueBody: ((Input) -> Subscribers.Demand)?
private let receiveCompletionBody: ((Subscribers.Completion<Failure>) -> Void)?
private let _rcvSubscription: ((Subscription) -> Void)?
private let _rcvValue: ((Input) -> Subscribers.Demand)?
private let _rcvCompletion: ((Subscribers.Completion<Failure>) -> Void)?
private let _onDeinit: (() -> Void)?

private let lock = Lock()
private let _lock = Lock()
private var _subscription: Subscription?
private var _events: [Event] = []

public var events: [Event] {
return self.lock.withLockGet(self._events)
return self._lock.withLockGet(self._events)
}

public var subscription: Subscription? {
return self.lock.withLockGet(self._subscription)
return self._lock.withLockGet(self._subscription)
}

public init(receiveSubscription: ((Subscription) -> Void)? = nil, receiveValue: ((Input) -> Subscribers.Demand)? = nil, receiveCompletion: ((Subscribers.Completion<Failure>) -> Void)? = nil, onDeinit: (() -> Void)? = nil) {
self._rcvSubscription = receiveSubscription
self._rcvValue = receiveValue
self._rcvCompletion = receiveCompletion
self._onDeinit = onDeinit
}

public init(receiveSubscription: ((Subscription) -> Void)? = nil, receiveValue: ((Input) -> Subscribers.Demand)? = nil, receiveCompletion: ((Subscribers.Completion<Failure>) -> Void)? = nil) {
self.receiveSubscriptionBody = receiveSubscription
self.receiveValueBody = receiveValue
self.receiveCompletionBody = receiveCompletion
deinit {
_onDeinit?()
}

public func receive(subscription: Subscription) {
self.lock.withLock {
self._lock.withLock {
self._events.append(.subscription(subscription.combineIdentifier))
self._subscription = subscription
}
self.receiveSubscriptionBody?(subscription)
self._rcvSubscription?(subscription)
}

public func receive(_ value: Input) -> Subscribers.Demand {
self.lock.withLock {
self._lock.withLock {
self._events.append(.value(value))
}
return self.receiveValueBody?(value) ?? .none
return self._rcvValue?(value) ?? .none
}

public func receive(completion: Subscribers.Completion<Failure>) {
self.lock.withLock {
self._lock.withLock {
self._events.append(.completion(completion))
self._subscription = nil
}
self.receiveCompletionBody?(completion)
self._rcvCompletion?(completion)
}

public func release() {
self.lock.withLock {
public func releaseSubscription() {
self._lock.withLock {
self._subscription = nil
}
}
}

extension TracingSubscriber: CustomStringConvertible, CustomReflectable, CustomPlaygroundDisplayConvertible {

public var description: String {
return "\(type(of: self))"
Expand All @@ -65,12 +79,64 @@ public class TracingSubscriber<Input, Failure: Error>: Subscriber, CustomStringC

public var customMirror: Mirror {
return Mirror(self, children: [
"receiveSubscriptionBody": receiveSubscriptionBody as Any,
"receiveValueBody": receiveValueBody as Any,
"receiveCompletionBody": receiveCompletionBody as Any,
"lock": lock,
"_rcvSubscription": _rcvSubscription as Any,
"_rcvValue": _rcvValue as Any,
"_rcvCompletion": _rcvCompletion as Any,
"_lock": _lock,
"_subscription": _subscription as Any,
"_events": _events,
])
}
}

// MARK: - Event

extension TracingSubscriber.Event: Equatable where Input: Equatable, Failure: Equatable {}

extension TracingSubscriber.Event: Hashable where Input: Hashable, Failure: Hashable {}

extension TracingSubscriber.Event: CustomStringConvertible {

public var description: String {
switch self {
case let .subscription(s):
return "subscription \(s)"
case let .value(v):
return "value \(v)"
case let .completion(c):
return "completion \(c)"
}
}
}

public extension TracingSubscriber.Event {

var value: Input? {
switch self {
case .value(let v):
return v
case .subscription, .completion:
return nil
}
}

var completion: Subscribers.Completion<Failure>? {
switch self {
case let .completion(c):
return c
case .subscription, .value:
return nil
}
}

func mapError<NewFailure: Error>(_ transform: (Failure) -> NewFailure) -> TracingSubscriber<Input, NewFailure>.Event {
switch self {
case let .subscription(s):
return .subscription(s)
case let .value(i):
return .value(i)
case let .completion(c):
return .completion(c.mapError(transform))
}
}
}
91 changes: 0 additions & 91 deletions Sources/CXTest/TracingSubscriberEvent.swift

This file was deleted.

51 changes: 51 additions & 0 deletions Sources/CXTestUtility/TestSubscriber.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,54 @@ public extension Publisher {
return sub
}
}

public extension TracingSubscriber {

var eventsWithoutSubscription: [Event] {
return self.events.filter { !$0.isSubscription }
}
}

public extension TracingSubscriber.Event {

var isSubscription: Bool {
switch self {
case .subscription:
return true
case .value, .completion:
return false
}
}
}

public typealias TracingSubscriberEvent<Input, Failure: Error> = TracingSubscriber<Input, Failure>.Event

public protocol TestEventProtocol {
associatedtype Input
associatedtype Failure: Error

var testEvent: TracingSubscriber<Input, Failure>.Event {
get set
}
}

extension TracingSubscriber.Event: TestEventProtocol {

public var testEvent: TracingSubscriber<Input, Failure>.Event {
get {
return self
}
set {
self = newValue
}
}
}

extension Collection where Element: TestEventProtocol {

public func mapError<NewFailure: Error>(_ transform: (Element.Failure) -> NewFailure) -> [TracingSubscriber<Element.Input, NewFailure>.Event] {
return self.map {
$0.testEvent.mapError(transform)
}
}
}
4 changes: 2 additions & 2 deletions Tests/CXFoundationTests/NotificationCenterSpec.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ class NotificationCenterSpec: QuickSpec {
NotificationCenter.default.post(name: name, object: nil)
NotificationCenter.default.post(name: name, object: nil)

expect(sub.events).toEventually(haveCount(3))
expect(sub.eventsWithoutSubscription).toEventually(haveCount(3))
}

// MARK: 1.2 should stop sending values after cancel
Expand All @@ -39,7 +39,7 @@ class NotificationCenterSpec: QuickSpec {
NotificationCenter.default.post(name: name, object: nil)
NotificationCenter.default.post(name: name, object: nil)

expect(sub.events).toEventually(beEmpty())
expect(sub.eventsWithoutSubscription).toEventually(beEmpty())
}
}
}
8 changes: 4 additions & 4 deletions Tests/CXFoundationTests/TimerSpec.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class TimerSpec: QuickSpec {
waitUntil(timeout: 3) { done in
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
done()
expect(sub.events).to(beEmpty())
expect(sub.eventsWithoutSubscription).to(beEmpty())
}
}
}
Expand All @@ -34,7 +34,7 @@ class TimerSpec: QuickSpec {

let connection = pub.connect()

expect(sub.events).toEventually(haveCount(4))
expect(sub.eventsWithoutSubscription).toEventually(haveCount(4))

_ = connection
}
Expand All @@ -51,8 +51,8 @@ class TimerSpec: QuickSpec {

RunLoop.current.run(until: Date().addingTimeInterval(1))

expect(sub1.events.count) == 3
expect(sub2.events.count) == 3
expect(sub1.eventsWithoutSubscription.count) == 3
expect(sub2.eventsWithoutSubscription.count) == 3

connection.cancel()
}
Expand Down
4 changes: 2 additions & 2 deletions Tests/CXInconsistentTests/Fixed/FixedSpec.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ class FixedSpec: QuickSpec {

(0...10).forEach(pub.send)

expect(sub.events).to(beEmpty())
expect(sub.events).toEventually(equal([.value(10)]))
expect(sub.eventsWithoutSubscription).to(beEmpty())
expect(sub.eventsWithoutSubscription).toEventually(equal([.value(10)]))
}
}
}
Loading

0 comments on commit 7968602

Please sign in to comment.