RxSwift/Tests/RxCocoaTests/SentMessageTest.swift

1095 lines
46 KiB
Swift

//
// SentMessageTest.swift
// RxTests
//
// Created by Krunoslav Zaher on 11/21/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
import Foundation
import XCTest
import RxSwift
import RxCocoa
import RxBlocking
class SentMessageTest : RxTest {
var testClosure: () -> () = { }
func dynamicClassName(_ baseClassName: String) -> String {
return "_RX_namespace_" + baseClassName
}
}
// MARK: Observing dealloc
extension SentMessageTest {
func testDealloc_baseClass_subClass_dont_interact1() {
// swizzle normal class
ensureGlobalRuntimeChangesAreCached(
createNormalInstance(),
observeIt: { (target: SentMessageTestBase_dealloc) in
return [target.rx.sentMessage(NSSelectorFromString("dealloc"))]
},
objectActingClassChange: [
.implementationAdded(forSelector: NSSelectorFromString("dealloc")),
],
objectRealClassChange: [
.implementationAdded(forSelector: NSSelectorFromString("dealloc")),
],
runtimeChange: RxObjCRuntimeChange.changes(interceptedClasses: 1, methodsSwizzled: 1),
useIt: { _ in return [[[]]]})
// swizzle normal class
ensureGlobalRuntimeChangesAreCached(
createNormalInstance(),
observeIt: { (target: SentMessageTest_dealloc) in
return [target.rx.sentMessage(NSSelectorFromString("dealloc"))]
},
objectActingClassChange: [
.implementationAdded(forSelector: NSSelectorFromString("dealloc")),
],
objectRealClassChange: [
.implementationAdded(forSelector: NSSelectorFromString("dealloc")),
],
runtimeChange: RxObjCRuntimeChange.changes(interceptedClasses: 1, methodsSwizzled: 1),
useIt: { _ in return [[[]]]})
}
func testDealloc_baseClass_subClass_dont_interact2() {
// swizzle normal class
ensureGlobalRuntimeChangesAreCached(
createNormalInstance(),
observeIt: { (target: SentMessageTestBase_dealloc2) in
return [target.rx.deallocating.map { _ in [] }]
},
objectActingClassChange: [
.implementationAdded(forSelector: NSSelectorFromString("dealloc")),
],
objectRealClassChange: [
.implementationAdded(forSelector: NSSelectorFromString("dealloc")),
],
runtimeChange: RxObjCRuntimeChange.changes(interceptedClasses: 1, methodsSwizzled: 1),
useIt: { _ in return [[[]]]})
// swizzle normal class
ensureGlobalRuntimeChangesAreCached(
createNormalInstance(),
observeIt: { (target: SentMessageTest_dealloc2) in
return [target.rx.deallocating.map { _ in [] }]
},
objectActingClassChange: [
.implementationAdded(forSelector: NSSelectorFromString("dealloc")),
],
objectRealClassChange: [
.implementationAdded(forSelector: NSSelectorFromString("dealloc")),
],
runtimeChange: RxObjCRuntimeChange.changes(interceptedClasses: 1, methodsSwizzled: 1),
useIt: { _ in return [[[]]]})
}
func testDealloc_baseClass_subClass_dont_interact_base_implements() {
// swizzle normal class
ensureGlobalRuntimeChangesAreCached(
createNormalInstance(),
observeIt: { (target: SentMessageTestBase_dealloc_base) in
return [target.rx.sentMessage(NSSelectorFromString("dealloc"))]
},
objectActingClassChange: [
.implementationChanged(forSelector: NSSelectorFromString("dealloc")),
],
objectRealClassChange: [
.implementationChanged(forSelector: NSSelectorFromString("dealloc")),
],
runtimeChange: RxObjCRuntimeChange.changes(interceptedClasses: 1, methodsSwizzled: 1),
useIt: { _ in return [[[]]]})
// swizzle normal class
ensureGlobalRuntimeChangesAreCached(
createNormalInstance(),
observeIt: { (target: SentMessageTest_dealloc_base) in
return [target.rx.sentMessage(NSSelectorFromString("dealloc"))]
},
objectActingClassChange: [
.implementationAdded(forSelector: NSSelectorFromString("dealloc")),
],
objectRealClassChange: [
.implementationAdded(forSelector: NSSelectorFromString("dealloc")),
],
runtimeChange: RxObjCRuntimeChange.changes(interceptedClasses: 1, methodsSwizzled: 1),
useIt: { _ in return [[[]]]})
}
func testDealloc_baseClass_subClass_dont_interact_subclass_implements() {
// swizzle normal class
ensureGlobalRuntimeChangesAreCached(
createNormalInstance(),
observeIt: { (target: SentMessageTestBase_dealloc_subclass) in
return [target.rx.sentMessage(NSSelectorFromString("dealloc"))]
},
objectActingClassChange: [
.implementationAdded(forSelector: NSSelectorFromString("dealloc")),
],
objectRealClassChange: [
.implementationAdded(forSelector: NSSelectorFromString("dealloc")),
],
runtimeChange: RxObjCRuntimeChange.changes(interceptedClasses: 1, methodsSwizzled: 1),
useIt: { _ in return [[[]]]})
// swizzle normal class
ensureGlobalRuntimeChangesAreCached(
createNormalInstance(),
observeIt: { (target: SentMessageTest_dealloc_subclass) in
return [target.rx.sentMessage(NSSelectorFromString("dealloc"))]
},
objectActingClassChange: [
.implementationChanged(forSelector: NSSelectorFromString("dealloc")),
],
objectRealClassChange: [
.implementationChanged(forSelector: NSSelectorFromString("dealloc")),
],
runtimeChange: RxObjCRuntimeChange.changes(interceptedClasses: 1, methodsSwizzled: 1),
useIt: { _ in return [[[]]]})
}
func testDealloc_baseClass_subClass_dont_interact_base_subclass_implements() {
// swizzle normal class
ensureGlobalRuntimeChangesAreCached(
createNormalInstance(),
observeIt: { (target: SentMessageTestBase_dealloc_base_subclass) in
return [target.rx.sentMessage(NSSelectorFromString("dealloc"))]
},
objectActingClassChange: [
.implementationChanged(forSelector: NSSelectorFromString("dealloc")),
],
objectRealClassChange: [
.implementationChanged(forSelector: NSSelectorFromString("dealloc")),
],
runtimeChange: RxObjCRuntimeChange.changes(interceptedClasses: 1, methodsSwizzled: 1),
useIt: { _ in return [[[]]]})
// swizzle normal class
ensureGlobalRuntimeChangesAreCached(
createNormalInstance(),
observeIt: { (target: SentMessageTest_dealloc_base_subclass) in
return [target.rx.sentMessage(NSSelectorFromString("dealloc"))]
},
objectActingClassChange: [
.implementationChanged(forSelector: NSSelectorFromString("dealloc")),
],
objectRealClassChange: [
.implementationChanged(forSelector: NSSelectorFromString("dealloc")),
],
runtimeChange: RxObjCRuntimeChange.changes(interceptedClasses: 1, methodsSwizzled: 1),
useIt: { _ in return [[[]]]})
}
}
// MARK: Observing by forwarding
extension SentMessageTest {
func testBaseClass_subClass_dont_interact_for_forwarding() {
// first forwarding with normal first
ensureGlobalRuntimeChangesAreCached(
createNormalInstance(SentMessageTest_interact_forwarding.self),
observeIt: { target in
return [target.rx.sentMessage(#selector(SentMessageTestBase_shared.justCalledObject(toSay:)))]
},
objectActingClassChange: [
],
objectRealClassChange: [
ObjectRuntimeChange.ClassChangedToDynamic("SentMessageTest_interact_forwarding", andImplementsTheseSelectors: [
NSSelectorFromString("class"),
NSSelectorFromString("respondsToSelector:"),
NSSelectorFromString("methodSignatureForSelector:"),
NSSelectorFromString("forwardInvocation:"),
#selector(SentMessageTestBase_shared.justCalledObject(toSay:)),
NSSelectorFromString("_RX_namespace_justCalledObjectToSay:"),
])
],
runtimeChange: RxObjCRuntimeChange.changes(dynamicSubclasses:1, swizzledForwardClasses: 1, methodsForwarded: 1)
) { target in
let o = NSObject()
target.justCalledObject(toSay: o)
return [[[o]]]
}
// then forwarding base class
ensureGlobalRuntimeChangesAreCached(
createNormalInstance(SentMessageTestBase_interact_forwarding.self),
observeIt: { target in
return [target.rx.sentMessage(#selector(SentMessageTestBase_shared.justCalledObject(toSay:)))]
},
objectActingClassChange: [
],
objectRealClassChange: [
ObjectRuntimeChange.ClassChangedToDynamic("SentMessageTestBase_interact_forwarding", andImplementsTheseSelectors: [
NSSelectorFromString("class"),
NSSelectorFromString("respondsToSelector:"),
NSSelectorFromString("methodSignatureForSelector:"),
NSSelectorFromString("forwardInvocation:"),
#selector(SentMessageTestBase_shared.justCalledObject(toSay:)),
NSSelectorFromString("_RX_namespace_justCalledObjectToSay:"),
])
],
runtimeChange: RxObjCRuntimeChange.changes(dynamicSubclasses:1, swizzledForwardClasses: 1, methodsForwarded: 1)
) { target in
let o = NSObject()
target.justCalledObject(toSay: o)
return [[[o]]]
}
// then normal again, no changes
ensureGlobalRuntimeChangesAreCached(
createNormalInstance(SentMessageTest_interact_forwarding.self),
observeIt: { target in
return [target.rx.sentMessage(#selector(SentMessageTestBase_shared.justCalledObject(toSay:)))]
},
objectActingClassChange: [
],
objectRealClassChange: [
ObjectRuntimeChange.ClassChangedToDynamic("SentMessageTest_interact_forwarding", andImplementsTheseSelectors: [
NSSelectorFromString("class"),
NSSelectorFromString("respondsToSelector:"),
NSSelectorFromString("methodSignatureForSelector:"),
NSSelectorFromString("forwardInvocation:"),
#selector(SentMessageTestBase_shared.justCalledObject(toSay:)),
NSSelectorFromString("_RX_namespace_justCalledObjectToSay:"),
])
],
runtimeChange: RxObjCRuntimeChange.changes()
) { target in
let o = NSObject()
target.justCalledObject(toSay: o)
return [[[o]]]
}
// then dynamic again, no changes
ensureGlobalRuntimeChangesAreCached(
createNormalInstance(SentMessageTestBase_interact_forwarding.self),
observeIt: { target in
return [target.rx.sentMessage(#selector(SentMessageTestBase_shared.justCalledObject(toSay:)))]
},
objectActingClassChange: [
],
objectRealClassChange: [
ObjectRuntimeChange.ClassChangedToDynamic("SentMessageTestBase_interact_forwarding", andImplementsTheseSelectors: [
NSSelectorFromString("class"),
NSSelectorFromString("respondsToSelector:"),
NSSelectorFromString("methodSignatureForSelector:"),
NSSelectorFromString("forwardInvocation:"),
#selector(SentMessageTestBase_shared.justCalledObject(toSay:)),
NSSelectorFromString("_RX_namespace_justCalledObjectToSay:"),
])
],
runtimeChange: RxObjCRuntimeChange.changes()
) { target in
let o = NSObject()
target.justCalledObject(toSay: o)
return [[[o]]]
}
}
}
// MARK: Optimized observers don't interfere between class/baseclass
extension SentMessageTest {
func testBaseClass_subClass_dont_interact_for_optimized_version_void() {
_baseClass_subClass_dont_interact_for_optimized_version(
SentMessageTestBase_optimized_void.self,
SentMessageTest_optimized_void.self,
#selector(SentMessageTestBase_shared.voidJustCalledVoidToSay)) { target in
target.voidJustCalledVoidToSay()
return [[[]]]
}
}
func testBaseClass_subClass_dont_interact_for_optimized_version_id() {
_baseClass_subClass_dont_interact_for_optimized_version(
SentMessageTestBase_optimized_id.self,
SentMessageTest_optimized_id.self,
#selector(SentMessageTestBase_shared.voidJustCalledObject(toSay:))) { target in
let o = NSObject()
target.voidJustCalledObject(toSay: o)
return [[[o]]]
}
}
func testBaseClass_subClass_dont_interact_for_optimized_version_int() {
_baseClass_subClass_dont_interact_for_optimized_version(
SentMessageTestBase_optimized_int.self,
SentMessageTest_optimized_int.self,
#selector(SentMessageTestBase_shared.voidJustCalledInt(toSay:))) { target in
target.voidJustCalledInt(toSay: 3)
return [[[NSNumber(value: 3)]]]
}
}
func testBaseClass_subClass_dont_interact_for_optimized_version_long() {
_baseClass_subClass_dont_interact_for_optimized_version(
SentMessageTestBase_optimized_long.self,
SentMessageTest_optimized_long.self,
#selector(SentMessageTestBase_shared.voidJustCalledLong(toSay:))) { target in
target.voidJustCalledLong(toSay: 3)
return [[[NSNumber(value: 3)]]]
}
}
func testBaseClass_subClass_dont_interact_for_optimized_version_char() {
_baseClass_subClass_dont_interact_for_optimized_version(
SentMessageTestBase_optimized_char.self,
SentMessageTest_optimized_char.self,
#selector(SentMessageTestBase_shared.voidJustCalledChar(toSay:))) { target in
target.voidJustCalledChar(toSay: 3)
return [[[NSNumber(value: 3)]]]
}
}
func testBaseClass_subClass_dont_interact_for_optimized_version_id_id() {
_baseClass_subClass_dont_interact_for_optimized_version(
SentMessageTestBase_optimized_id_id.self,
SentMessageTest_optimized_id_id.self,
#selector(SentMessageTestBase_shared.voidJustCalledObject(toSay:object:))) { target in
let o = NSObject()
let o1 = NSObject()
target.voidJustCalledObject(toSay: o, object: o1)
return [[[o, o1]]]
}
}
func _baseClass_subClass_dont_interact_for_optimized_version
<
BaseClass: SentMessageTestClassCreationProtocol & NSObjectProtocol,
TargetClass: SentMessageTestClassCreationProtocol & NSObjectProtocol
>(_ baseClass: BaseClass.Type, _ targetClass: TargetClass.Type, _ method: Selector, _ invoke: @escaping (BaseClass) -> [[MethodParameters]]) {
// now force forwarding mechanism for normal class
ensureGlobalRuntimeChangesAreCached(
createNormalInstance(),
observeIt: { target in
return [(target as! NSObject).rx.sentMessage(method)]
},
objectActingClassChange: [
],
objectRealClassChange: [
ObjectRuntimeChange.ClassChangedToDynamic("\(targetClass)", andImplementsTheseSelectors: [method, NSSelectorFromString("class")])
],
runtimeChange: RxObjCRuntimeChange.changes(dynamicSubclasses: 1, methodsSwizzled: 1)) { (target: TargetClass) in
return invoke(target as! BaseClass)
}
// first force base class forwarding
ensureGlobalRuntimeChangesAreCached(
createNormalInstance(),
observeIt: { (target: BaseClass) in
return [(target as! NSObject).rx.sentMessage(method)]
},
objectActingClassChange: [
],
objectRealClassChange: [
ObjectRuntimeChange.ClassChangedToDynamic("\(baseClass)", andImplementsTheseSelectors: [method, NSSelectorFromString("class")])
],
runtimeChange: RxObjCRuntimeChange.changes(dynamicSubclasses: 1, methodsSwizzled: 1),
useIt: invoke)
// now force forwarding mechanism for normal class again
ensureGlobalRuntimeChangesAreCached(
createNormalInstance(),
observeIt: { target in
return [(target as! NSObject).rx.sentMessage(method)]
},
objectActingClassChange: [
],
objectRealClassChange: [
ObjectRuntimeChange.ClassChangedToDynamic("\(targetClass)", andImplementsTheseSelectors: [method, NSSelectorFromString("class")])
],
runtimeChange: RxObjCRuntimeChange.changes()) { (target: TargetClass) in
return invoke(target as! BaseClass)
}
// forwarding for base class again
ensureGlobalRuntimeChangesAreCached(
createNormalInstance(),
observeIt: { (target: BaseClass) in
return [(target as! NSObject).rx.sentMessage(method)]
},
objectActingClassChange: [
],
objectRealClassChange: [
ObjectRuntimeChange.ClassChangedToDynamic("\(baseClass)", andImplementsTheseSelectors: [method, NSSelectorFromString("class")])
],
runtimeChange: RxObjCRuntimeChange.changes(),
useIt: invoke)
}
}
// MARK: Optimized observers don't interfere between class/baseclass depending on who is implementing the method
extension SentMessageTest {
func testBaseClass_subClass_dont_interact_for_optimized_version_int_base_implements() {
let argument = NSObject()
_baseClass_subClass_dont_interact_for_optimized_version(
SentMessageTestBase_optimized_int_base.self,
SentMessageTest_optimized_int_base.self,
#selector(SentMessageTestBase_optimized_int_base.optimized(_:))) { target in
target.optimized(argument)
return [[[argument]]]
}
}
}
// MARK: Basic observing by forwarding cases
extension SentMessageTest {
func testBasicForwardingCase() {
let target = SentMessageTest_forwarding_basic()
var messages = [[Any]]()
let d = target.rx.sentMessage(#selector(SentMessageTestBase_shared.message_allSupportedParameters(_:p2:p3:p4:p5:p6:p7:p8:p9:p10:p11:p12:p13:p14:p15:p16:))).subscribe(onNext: { n in
messages.append(n)
}, onError: { e in
XCTFail("Errors out \(e)")
})
let objectParam = NSObject()
let str: UnsafePointer<Int8> = ("123" as NSString).utf8String!
let unsafeStr: UnsafeMutablePointer<Int8> = UnsafeMutablePointer(mutating: str)
let largeStruct = some_insanely_large_struct(a: (0, 1, 2, 3, 4, 5, 6, 7), some_large_text: nil, next: nil)
target.message_allSupportedParameters(objectParam, p2: type(of: target), p3: { x in x}, p4: -2, p5: -3, p6: -4, p7: -5,
p8: 1, p9: 2, p10: 3, p11: 4, p12: 1.0, p13: 2.0, p14: str, p15: unsafeStr, p16: largeStruct)
d.dispose()
XCTAssertEqualAnyObjectArrayOfArrays(target.messages as [[AnyObject]], messages)
}
}
// MARK: Test failures
extension SentMessageTest {
func testFailsInCaseObservingUnknownSelector() {
let target = SentMessageTest_shared()
do {
_ = try target.rx.sentMessage(NSSelectorFromString("unknownSelector:"))
.toBlocking()
.first()
XCTFail()
}
catch let e {
guard case .selectorNotImplemented(let targetInError) = e as! RxCocoaObjCRuntimeError else {
XCTFail()
return
}
XCTAssertEqual(targetInError as? SentMessageTest_shared, target)
}
}
func testFailsInCaseObjectIsAlreadyBeingInterceptedWithKVO() {
let target = SentMessageTest_shared()
let disposeBag = DisposeBag()
target.rx.observe(NSArray.self, "messages")
.subscribe(onNext: { _ in
})
.addDisposableTo(disposeBag)
do {
_ = try target.rx.sentMessage(#selector(SentMessageTestBase_shared.justCalledBool(toSay:)))
.toBlocking()
.first()
XCTFail()
}
catch let e {
guard case .objectMessagesAlreadyBeingIntercepted(let targetInError, let mechanism) = e as! RxCocoaObjCRuntimeError else {
XCTFail()
return
}
XCTAssertEqual(targetInError as? SentMessageTest_shared, target)
XCTAssertEqual(mechanism, RxCocoaInterceptionMechanism.kvo)
}
}
func testFailsInCaseObjectIsAlreadyBeingInterceptedWithSomeOtherMechanism() {
let target = SentMessageTest_shared()
object_setClass(target, SentMessageTest_shared_mock_interceptor.self)
do {
_ = try target.rx.sentMessage(#selector(SentMessageTestBase_shared.justCalledBool(toSay:)))
.toBlocking()
.first()
XCTFail()
}
catch let e {
guard case .objectMessagesAlreadyBeingIntercepted(let targetInError, let mechanism) = e as! RxCocoaObjCRuntimeError else {
XCTFail()
return
}
XCTAssertEqual(targetInError as? SentMessageTest_shared, target)
XCTAssertEqual(mechanism, RxCocoaInterceptionMechanism.kvo)
}
}
/*
func testFailsInCaseObjectIsCF() {
autoreleasepool {
let target = "\(Date())"
do {
_ = try target.rx.sentMessage(#selector(_NSStringCoreType.length))
.toBlocking()
.first()
XCTFail()
}
catch let e {
guard case .cantInterceptCoreFoundationTollFreeBridgedObjects(let targetInError) = e as! RxCocoaObjCRuntimeError else {
XCTFail()
return
}
XCTAssertEqual(targetInError as? NSString, target)
}
}
}
*/
}
// MARK: Test interaction with KVO
extension SentMessageTest {
func testWorksWithKVOInCaseKVORegisteredAfter() {
let target = SentMessageTest_shared()
let messages = target.rx.sentMessage(#selector(SentMessageTestBase_shared.justCalledBool(toSay:)))
let kvo = target.rx.observe(NSArray.self, "messages")
.subscribe(onNext: { _ in
})
var recordedMessages = [MethodParameters]()
let methodObserving = messages.subscribe(onNext: { n in
recordedMessages.append(n)
})
target.justCalledBool(toSay: true)
kvo.dispose()
target.justCalledBool(toSay: false)
XCTAssertEqualAnyObjectArrayOfArrays(recordedMessages, [[NSNumber(value: true)], [NSNumber(value: false)]])
methodObserving.dispose()
}
}
// MARK: Test subjects
extension SentMessageTest {
func testMessageSentSubjectHasPublishBehavior() {
var messages: Observable<MethodParameters>!
var recordedMessages = [MethodParameters]()
var completed = false
let disposeBag = DisposeBag()
autoreleasepool {
let target = SentMessageTest_shared()
messages = target.rx.sentMessage(#selector(SentMessageTestBase_shared.justCalledBool(toSay:)))
target.justCalledBool(toSay: true)
messages.subscribe(onNext: { n in
recordedMessages.append(n)
}, onCompleted: {
completed = true
}).addDisposableTo(disposeBag)
target.justCalledBool(toSay: true)
}
XCTAssertEqualAnyObjectArrayOfArrays(recordedMessages, [[NSNumber(value: true)]])
XCTAssertTrue(completed)
}
func testDeallocSubjectHasReplayBehavior1() {
var deallocSequence: Observable<MethodParameters>!
autoreleasepool {
let target = SentMessageTest_shared()
deallocSequence = target.rx.sentMessage(NSSelectorFromString("dealloc"))
}
var called = false
var completed = false
_ = deallocSequence.subscribe(onNext: { n in
called = true
}, onCompleted: {
completed = true
})
XCTAssertTrue(called)
XCTAssertTrue(completed)
}
func testDeallocSubjectHasReplayBehavior2() {
var deallocSequence: Observable<()>!
autoreleasepool {
let target = SentMessageTest_shared()
deallocSequence = target.rx.deallocating
}
var called = false
var completed = false
_ = deallocSequence.subscribe(onNext: { n in
called = true
}, onCompleted: {
completed = true
})
XCTAssertTrue(called)
XCTAssertTrue(completed)
}
}
// MARK: Test observing of special methods fail
extension SentMessageTest {
func testObserve_special_class() {
let target = SentMessageTest_shared()
do {
_ = try target.rx.sentMessage(NSSelectorFromString("class"))
.toBlocking()
.first()
XCTFail()
}
catch let e {
guard case .observingPerformanceSensitiveMessages(let targetInError) = e as! RxCocoaObjCRuntimeError else {
XCTFail()
return
}
XCTAssertEqual(targetInError as? SentMessageTest_shared, target)
}
}
func testObserve_special_forwardingTargetForSelector() {
let target = SentMessageTest_shared()
do {
_ = try target.rx.sentMessage(NSSelectorFromString("forwardingTargetForSelector:"))
.toBlocking()
.first()
XCTFail()
}
catch let e {
guard case .observingPerformanceSensitiveMessages(let targetInError) = e as! RxCocoaObjCRuntimeError else {
XCTFail()
return
}
XCTAssertEqual(targetInError as? SentMessageTest_shared, target)
}
}
func testObserve_special_methodSignatureForSelector() {
let target = SentMessageTest_shared()
do {
_ = try target.rx.sentMessage(NSSelectorFromString("methodSignatureForSelector:"))
.toBlocking()
.first()
XCTFail()
}
catch let e {
guard case .observingPerformanceSensitiveMessages(let targetInError) = e as! RxCocoaObjCRuntimeError else {
XCTFail()
return
}
XCTAssertEqual(targetInError as? SentMessageTest_shared, target)
}
}
func testObserve_special_respondsToSelector() {
let target = SentMessageTest_shared()
do {
_ = try target.rx.sentMessage(NSSelectorFromString("respondsToSelector:"))
.toBlocking()
.first()
XCTFail()
}
catch let e {
guard case .observingPerformanceSensitiveMessages(let targetInError) = e as! RxCocoaObjCRuntimeError else {
XCTFail()
return
}
XCTAssertEqual(targetInError as? SentMessageTest_shared, target)
}
}
}
// MARK: Test return value check
extension SentMessageTest {
func testObserve_largeStructReturnValueFails() {
let target = SentMessageTest_shared()
do {
_ = try target.rx.sentMessage(#selector(SentMessageTestBase_shared.hugeResult))
.toBlocking()
.first()
XCTFail()
}
catch let e {
guard case .observingMessagesWithUnsupportedReturnType(let targetInError) = e as! RxCocoaObjCRuntimeError else {
XCTFail()
return
}
XCTAssertEqual(targetInError as? SentMessageTest_shared, target)
}
}
}
// MARK: Ensure all types are covered
extension SentMessageTest {
func testObservingForAllTypes() {
let object = SentMessageTest_all_supported_types()
let closure: () -> () = { }
let constChar = ("you better be listening" as NSString).utf8String!
let largeStruct = some_insanely_large_struct(a: (0, 1, 2, 3, 4, 5, 6, 7), some_large_text: nil, next: nil)
let startRuntimeState = RxObjCRuntimeState()
_testMessageRecordedAndAllCallsAreMade(#selector(SentMessageTestBase_shared.justCalledObject(toSay:)),
sendMessage: { x in NSValue(nonretainedObject: x.justCalledObject(toSay: object)) },
expectedResult: NSValue(nonretainedObject: object))
_testMessageRecordedAndAllCallsAreMade(#selector(SentMessageTestBase_shared.justCalledClass(toSay:)), sendMessage: { x in NSValue(nonretainedObject: x.justCalledClass(toSay: type(of: object))) }, expectedResult: NSValue(nonretainedObject: type(of: object)))
_testMessageRecordedAndAllCallsAreMade(#selector(SentMessageTestBase_shared.justCalledClosure(toSay:)),
sendMessage: { x in "\(x.justCalledClosure(toSay: closure))" },
expectedResult: "\(closure)")
_testMessageRecordedAndAllCallsAreMade(#selector(SentMessageTestBase_shared.justCalledChar(toSay:)),
sendMessage: { x in x.justCalledChar(toSay: 3)},
expectedResult: 3)
_testMessageRecordedAndAllCallsAreMade(#selector(SentMessageTestBase_shared.justCalledShort(toSay:)),
sendMessage: { x in x.justCalledShort(toSay: 4) },
expectedResult: 4)
_testMessageRecordedAndAllCallsAreMade(#selector(SentMessageTestBase_shared.justCalledInt(toSay:)),
sendMessage: { x in x.justCalledInt(toSay: 5) },
expectedResult: 5)
_testMessageRecordedAndAllCallsAreMade(#selector(SentMessageTestBase_shared.justCalledLong(toSay:)),
sendMessage: { x in x.justCalledLong(toSay: 6) },
expectedResult: 6)
_testMessageRecordedAndAllCallsAreMade(#selector(SentMessageTestBase_shared.justCalledLongLong(toSay:)),
sendMessage: { x in x.justCalledLongLong(toSay: 7) },
expectedResult: 7)
_testMessageRecordedAndAllCallsAreMade(#selector(SentMessageTestBase_shared.justCalledUnsignedChar(toSay:)),
sendMessage: { x in x.justCalledUnsignedChar(toSay: 8) },
expectedResult: 8)
_testMessageRecordedAndAllCallsAreMade(#selector(SentMessageTestBase_shared.justCalledUnsignedShort(toSay:)),
sendMessage: { x in x.justCalledUnsignedShort(toSay: 9) },
expectedResult: 9)
_testMessageRecordedAndAllCallsAreMade(#selector(SentMessageTestBase_shared.justCalledUnsignedInt(toSay:)),
sendMessage: { x in x.justCalledUnsignedInt(toSay: 10) },
expectedResult: 10)
_testMessageRecordedAndAllCallsAreMade(#selector(SentMessageTestBase_shared.justCalledUnsignedLong(toSay:)),
sendMessage: { x in x.justCalledUnsignedLong(toSay: 11) },
expectedResult: 11)
_testMessageRecordedAndAllCallsAreMade(#selector(SentMessageTestBase_shared.justCalledUnsignedLongLong(toSay:)),
sendMessage: { x in x.justCalledUnsignedLongLong(toSay: 12) },
expectedResult: 12)
_testMessageRecordedAndAllCallsAreMade(#selector(SentMessageTestBase_shared.justCalledFloat(toSay:)),
sendMessage: { x in x.justCalledFloat(toSay: 13) },
expectedResult: 13)
_testMessageRecordedAndAllCallsAreMade(#selector(SentMessageTestBase_shared.justCalledDouble(toSay:)),
sendMessage: { x in x.justCalledDouble(toSay: 13) },
expectedResult: 13)
_testMessageRecordedAndAllCallsAreMade(#selector(SentMessageTestBase_shared.justCalledBool(toSay:)),
sendMessage: { x in x.justCalledBool(toSay: true) },
expectedResult: true)
_testMessageRecordedAndAllCallsAreMade(#selector(SentMessageTestBase_shared.justCalledConstChar(toSay:)),
sendMessage: { x in x.justCalledConstChar(toSay: constChar) },
expectedResult: constChar)
_testMessageRecordedAndAllCallsAreMade(#selector(SentMessageTestBase_shared.justCalledLarge(toSay:)),
sendMessage: { x in x.justCalledLarge(toSay: largeStruct) },
expectedResult: 28)
let middleRuntimeState = RxObjCRuntimeState()
let middleChanges = RxObjCRuntimeChange.changes(dynamicSubclasses: 1, swizzledForwardClasses: 1, methodsForwarded: 18)
middleRuntimeState.assertAfterThisMoment(startRuntimeState, changed:middleChanges)
_testMessageRecordedAndAllCallsAreMade(#selector(SentMessageTestBase_shared.voidJustCalledObject(toSay:)), sendMessage: { x in x.voidJustCalledObject(toSay: object); return NSValue(nonretainedObject: object) }, expectedResult: NSValue(nonretainedObject: object))
_testMessageRecordedAndAllCallsAreMade(#selector(SentMessageTestBase_shared.voidJustCalledClosure(toSay:)),
sendMessage: { x in x.voidJustCalledClosure(toSay: closure); return "\(closure)" },
expectedResult: "\(closure)")
_testMessageRecordedAndAllCallsAreMade(#selector(SentMessageTestBase_shared.voidJustCalledChar(toSay:)),
sendMessage: { x in x.voidJustCalledChar(toSay: 3); return 3 },
expectedResult: 3)
_testMessageRecordedAndAllCallsAreMade(#selector(SentMessageTestBase_shared.voidJustCalledShort(toSay:)),
sendMessage: { x in x.voidJustCalledShort(toSay: 4); return 4 },
expectedResult: 4)
_testMessageRecordedAndAllCallsAreMade(#selector(SentMessageTestBase_shared.voidJustCalledInt(toSay:)),
sendMessage: { x in x.voidJustCalledInt(toSay: 5); return 5 },
expectedResult: 5)
_testMessageRecordedAndAllCallsAreMade(#selector(SentMessageTestBase_shared.voidJustCalledLong(toSay:)),
sendMessage: { x in x.voidJustCalledLong(toSay: 6); return 6 },
expectedResult: 6)
_testMessageRecordedAndAllCallsAreMade(#selector(SentMessageTestBase_shared.voidJustCalledUnsignedChar(toSay:)),
sendMessage: { x in x.voidJustCalledUnsignedChar(toSay: 8); return 8 },
expectedResult: 8)
_testMessageRecordedAndAllCallsAreMade(#selector(SentMessageTestBase_shared.voidJustCalledUnsignedShort(toSay:)),
sendMessage: { x in x.voidJustCalledUnsignedShort(toSay: 9); return 9 },
expectedResult: 9)
_testMessageRecordedAndAllCallsAreMade(#selector(SentMessageTestBase_shared.voidJustCalledUnsignedInt(toSay:)),
sendMessage: { x in x.voidJustCalledUnsignedInt(toSay: 10); return 10 },
expectedResult: 10)
_testMessageRecordedAndAllCallsAreMade(#selector(SentMessageTestBase_shared.voidJustCalledUnsignedLong(toSay:)),
sendMessage: { x in x.voidJustCalledUnsignedLong(toSay: 11); return 11 },
expectedResult: 11)
_testMessageRecordedAndAllCallsAreMade(#selector(SentMessageTestBase_shared.voidJustCalledDouble(toSay:)),
sendMessage: { x in x.voidJustCalledDouble(toSay: 13); return 13 },
expectedResult: 13)
_testMessageRecordedAndAllCallsAreMade(#selector(SentMessageTestBase_shared.voidJustCalledFloat(toSay:)), sendMessage: { x in x.voidJustCalledFloat(toSay: 13); return 13 }, expectedResult: 13)
let endRuntimeState = RxObjCRuntimeState()
endRuntimeState.assertAfterThisMoment(middleRuntimeState, changed: RxObjCRuntimeChange.changes(methodsSwizzled: 12))
}
func _testMessageRecordedAndAllCallsAreMade<Result: Equatable>(_ selector: Selector, sendMessage: @escaping (SentMessageTest_all_supported_types) -> Result, expectedResult: Result) {
var observedMessages = [[Any]]()
var receivedDerivedClassMessage = [[Any]]()
var receivedBaseClassMessage = [[Any]]()
var completed = false
var result: Result! = nil
let action: () -> Disposable = { () -> Disposable in
let target = SentMessageTest_all_supported_types()
let d = target.rx.sentMessage(selector).subscribe(onNext: { n in
observedMessages.append(n)
}, onError: { e in
XCTFail("Errors out \(e)")
}, onCompleted: {
completed = true
})
result = sendMessage(target)
receivedDerivedClassMessage = target.messages
receivedBaseClassMessage = target.baseMessages
return d
}
action().dispose()
XCTAssertEqual(result, expectedResult)
XCTAssertTrue(completed)
XCTAssert(observedMessages.count == 1)
XCTAssertEqualAnyObjectArrayOfArrays(observedMessages, receivedDerivedClassMessage)
XCTAssertEqualAnyObjectArrayOfArrays(observedMessages, receivedBaseClassMessage)
}
}
extension SentMessageTest {
/**
Repeats action twice and makes sure there is no global leaks. Observing mechanism is lazy loaded so not caching
results properly can cause serious memory leaks.
*/
func ensureGlobalRuntimeChangesAreCached<T: SentMessageTestClassCreationProtocol & NSObjectProtocol>(
_ createIt: @escaping () -> T,
observeIt: @escaping (T) -> [Observable<MethodParameters>],
objectActingClassChange: [ObjectRuntimeChange],
objectRealClassChange: [ObjectRuntimeChange],
runtimeChange: RxObjCRuntimeChange,
useIt: @escaping (T) -> [[MethodParameters]]
) {
// First run normal experiment
_ensureGlobalRuntimeChangesAreCached(createIt,
observeIt: observeIt,
expectedActingClassChanges: objectActingClassChange,
expectedRealClassChanges: objectRealClassChange,
runtimeChange: runtimeChange,
useIt: useIt
)
// The second run of the same experiment shouldn't cause any changes in global runtime.
// Cached methods should be used.
// If second experiment causes some change in runtime, that means there is a bug.
_ensureGlobalRuntimeChangesAreCached(createIt,
observeIt: observeIt,
expectedActingClassChanges: [], // acting class can't change second time, because that would mean that on every observe attempt we would inject new methods in runtime
expectedRealClassChanges: objectRealClassChange.filter { $0.isClassChange }, // only class can change to the same class it changed originally
runtimeChange: RxObjCRuntimeChange.changes(),
useIt: useIt
)
}
func _ensureGlobalRuntimeChangesAreCached<T: SentMessageTestClassCreationProtocol & NSObjectProtocol> (
_ createIt: () -> T,
observeIt: (T) -> [Observable<MethodParameters>],
expectedActingClassChanges: [ObjectRuntimeChange],
expectedRealClassChanges: [ObjectRuntimeChange],
runtimeChange: RxObjCRuntimeChange,
useIt: (T) -> [[MethodParameters]]
) {
let originalRuntimeState = RxObjCRuntimeState()
var createdObject: T! = nil
var disposables = [Disposable]()
var nCompleted = 0
var recordedParameters = [[MethodParameters]]()
var observables: [Observable<MethodParameters>] = []
autoreleasepool {
createdObject = createIt()
let afterCreateState = RxObjCRuntimeState()
afterCreateState.assertAfterThisMoment(originalRuntimeState, changed: RxObjCRuntimeChange.changes())
}
let originalObjectRuntimeState = ObjectRuntimeState(target: createdObject)
autoreleasepool {
observables = observeIt(createdObject)
}
let afterObserveObjectRuntimeState = ObjectRuntimeState(target: createdObject)
let afterObserveRuntimeState = RxObjCRuntimeState()
afterObserveObjectRuntimeState.assertChangesFrom(originalObjectRuntimeState,
expectedActingClassChanges: expectedActingClassChanges,
expectedRealClassChanges: expectedRealClassChanges
)
afterObserveRuntimeState.assertAfterThisMoment(originalRuntimeState, changed: runtimeChange)
autoreleasepool {
var i = 0
for o in observables {
let index = i
i += 1
recordedParameters.append([])
_ = o.subscribe(onNext: { n in
recordedParameters[index].append(n)
}, onError: { e in
XCTFail("Error happened \(e)")
}, onCompleted: { () -> Void in
nCompleted += 1
})
}
}
let expectedParameters = useIt(createdObject)
let finalObjectRuntimeState = ObjectRuntimeState(target: createdObject)
finalObjectRuntimeState.assertChangesFrom(afterObserveObjectRuntimeState,
expectedActingClassChanges: [],
expectedRealClassChanges: []
)
autoreleasepool {
for d in disposables {
d.dispose()
}
disposables = []
// tiny second test to make sure runtime stayed intact
createdObject = nil
}
// ensure all observables are completed on object dispose
XCTAssertTrue(nCompleted == observables.count)
XCTAssertEqual(recordedParameters.count, expectedParameters.count)
for (lhs, rhs) in zip(recordedParameters, expectedParameters) {
XCTAssertEqualAnyObjectArrayOfArrays(lhs, rhs)
}
// nothing is changed after requesting the observables
let endRuntimeState = RxObjCRuntimeState()
endRuntimeState.assertAfterThisMoment(afterObserveRuntimeState, changed: RxObjCRuntimeChange.changes())
}
}
// MARK: Convenience
extension SentMessageTest {
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
}
func createKVODynamicSubclassed<T: SentMessageTestClassCreationProtocol & NSObjectProtocol>(_ type: T.Type = T.self) -> () -> (T, [Disposable]) {
return {
let t = T.createInstance()
//let disposable = (t as! NSObject).rx.observe(NSArray.self, "messages").publish().connect()
(t as! NSObject).addObserver(self, forKeyPath: "messages", options: [], context: nil)
return (t, [Disposables.create { (t as! NSObject).removeObserver(self, forKeyPath: "messages") }])
}
}
func createNormalInstance<T: SentMessageTestClassCreationProtocol & NSObjectProtocol>(_ type: T.Type = T.self) -> () -> T {
return {
return T.createInstance()
}
}
}
extension some_insanely_large_struct : Equatable {
}
public func ==(lhs: some_insanely_large_struct, rhs: some_insanely_large_struct) -> Bool {
if lhs.a.0 != rhs.a.0 { return false }
if lhs.a.1 != rhs.a.1 { return false }
if lhs.a.2 != rhs.a.2 { return false }
if lhs.a.3 != rhs.a.3 { return false }
if lhs.a.4 != rhs.a.4 { return false }
if lhs.a.5 != rhs.a.5 { return false }
if lhs.a.6 != rhs.a.6 { return false }
if lhs.a.7 != rhs.a.7 { return false }
return lhs.some_large_text == rhs.some_large_text && lhs.next == rhs.next
}
typealias MethodParameters = [AnyObject]