RxSwift/Tests/RxCocoaTests/RuntimeStateSnapshot.swift

160 lines
6.1 KiB
Swift

//
// RuntimeStateSnapshot.swift
// RxTests
//
// Created by Krunoslav Zaher on 11/27/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
import Foundation
import XCTest
class ObjectRuntimeState {
let real: ClassRuntimeState
let actingAs: ClassRuntimeState
init(target: AnyObject) {
assert(object_getClass(target) == type(of: target))
real = ClassRuntimeState(object_getClass(target))
actingAs = ClassRuntimeState(RXObjCTestRuntime.objCClass(target))
}
fileprivate static func changesFrom(_ from: ClassRuntimeState, to: ClassRuntimeState) -> [ObjectRuntimeChange] {
if from.targetClass == to.targetClass {
var changes = [ObjectRuntimeChange]()
for (selector, implementation) in to.implementations {
if let originalImplementation = from.implementations[selector] {
if originalImplementation != implementation {
if RXObjCTestRuntime.isForwardingIMP(implementation) {
changes.append(.implementationChangedToForwarding(forSelector: selector))
}
else {
changes.append(.implementationChanged(forSelector: selector))
}
}
}
else {
if RXObjCTestRuntime.isForwardingIMP(implementation) {
changes.append(.forwardImplementationAdded(forSelector: selector))
}
else {
changes.append(.implementationAdded(forSelector: selector))
}
}
}
for (oldSelector, _) in from.implementations {
if to.implementations[oldSelector] == nil {
changes.append(.implementationDeleted(forSelector: oldSelector))
}
}
return changes
}
else {
return [.classChanged(from: NSStringFromClass(from.targetClass), to: NSStringFromClass(to.targetClass), andImplementsTheseSelectors: Array(to.implementations.keys))]
}
}
func changesFrom(_ initialState: ObjectRuntimeState) -> (real: [ObjectRuntimeChange], actingAs: [ObjectRuntimeChange]) {
return (
real: ObjectRuntimeState.changesFrom(initialState.real, to: self.real),
actingAs: ObjectRuntimeState.changesFrom(initialState.actingAs, to: self.actingAs)
)
}
func assertChangesFrom(_ initialState: ObjectRuntimeState, expectedActingClassChanges: [ObjectRuntimeChange], expectedRealClassChanges: [ObjectRuntimeChange]) {
let changes = self.changesFrom(initialState)
XCTAssertEqual(Set(changes.actingAs), Set(expectedActingClassChanges))
if (Set(changes.actingAs) != Set(expectedActingClassChanges)) {
print("Changes in actingAs class\nreal:\n\(changes.actingAs)\nexpected:\n\(expectedActingClassChanges)\n\n")
}
XCTAssertEqual(Set(changes.real), Set(expectedRealClassChanges))
if (Set(changes.real) != Set(expectedRealClassChanges)) {
print("Changes in actual class\nreal:\n\(changes.real)\nexpected:\n\(expectedRealClassChanges)\n\n")
}
}
}
enum ObjectRuntimeChange : Hashable {
static func ClassChangedToDynamic(_ from: String, andImplementsTheseSelectors: [Selector]) -> ObjectRuntimeChange {
return .classChanged(from: from, to: "_RX_namespace_" + from, andImplementsTheseSelectors: andImplementsTheseSelectors)
}
case classChanged(from: String, to: String, andImplementsTheseSelectors: [Selector])
case implementationChanged(forSelector: Selector)
case implementationChangedToForwarding(forSelector: Selector)
case implementationAdded(forSelector: Selector)
case implementationDeleted(forSelector: Selector)
case forwardImplementationAdded(forSelector: Selector)
}
extension ObjectRuntimeChange {
var hashValue: Int {
// who cares, this is not performance critical
return 0
}
var isClassChange: Bool {
if case .classChanged = self {
return true
}
return false
}
}
func ==(lhs: ObjectRuntimeChange, rhs: ObjectRuntimeChange) -> Bool {
switch (lhs, rhs) {
case let (.classChanged(lFrom, lTo, lImplementations), .classChanged(rFrom, rTo, rImplementations)):
return (lFrom == rFrom && lTo == rTo) && Set(lImplementations) == Set(rImplementations)
case let (.implementationChanged(lSelector), .implementationChanged(rSelector)):
return lSelector == rSelector
case let (.implementationChangedToForwarding(lSelector), .implementationChangedToForwarding(rSelector)):
return lSelector == rSelector
case let (.implementationAdded(lSelector), .implementationAdded(rSelector)):
return lSelector == rSelector
case let (.implementationDeleted(lSelector), .implementationDeleted(rSelector)):
return lSelector == rSelector
case let (.forwardImplementationAdded(lSelector), .forwardImplementationAdded(rSelector)):
return lSelector == rSelector
default:
return false
}
}
extension Sequence where Iterator.Element == ObjectRuntimeChange {
var classChanged: Bool {
return self.filter { x in
if case .classChanged = x {
return true
} else {
return false
}
}.count > 0
}
}
struct ClassRuntimeState {
let targetClass: AnyClass
let implementations: [Selector: IMP]
init(_ targetClass: AnyClass) {
self.targetClass = targetClass
self.implementations = ClassRuntimeState.implementationsBySelector(targetClass)
}
static func implementationsBySelector(_ klass: AnyClass) -> [Selector: IMP] {
var count: UInt32 = 0
let methods = class_copyMethodList(klass, &count)
var result = [Selector: IMP]()
for i in 0 ..< count {
let method: Method = methods!.advanced(by: Int(i)).pointee!
result[method_getName(method)] = method_getImplementation(method)
}
return result
}
}