Index: third_party/ocmock/OCMock/OCClassMockObject.m |
diff --git a/third_party/ocmock/OCMock/OCClassMockObject.m b/third_party/ocmock/OCMock/OCClassMockObject.m |
index 65b8fdf02d52317d7b617a5628d49dc622e784f6..2e2bc1ec28f852ca86b9a9f3b239ac5bc0fc474b 100644 |
--- a/third_party/ocmock/OCMock/OCClassMockObject.m |
+++ b/third_party/ocmock/OCMock/OCClassMockObject.m |
@@ -1,10 +1,24 @@ |
-//--------------------------------------------------------------------------------------- |
-// $Id$ |
-// Copyright (c) 2005-2008 by Mulle Kybernetik. See License file for details. |
-//--------------------------------------------------------------------------------------- |
+/* |
+ * Copyright (c) 2005-2015 Erik Doernenburg and contributors |
+ * |
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may |
+ * not use these files except in compliance with the License. You may obtain |
+ * a copy of the License at |
+ * |
+ * http://www.apache.org/licenses/LICENSE-2.0 |
+ * |
+ * Unless required by applicable law or agreed to in writing, software |
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
+ * License for the specific language governing permissions and limitations |
+ * under the License. |
+ */ |
+#import <objc/runtime.h> |
#import "OCClassMockObject.h" |
- |
+#import "NSObject+OCMAdditions.h" |
+#import "OCMFunctions.h" |
+#import "OCMInvocationStub.h" |
@implementation OCClassMockObject |
@@ -12,14 +26,22 @@ |
- (id)initWithClass:(Class)aClass |
{ |
+ NSParameterAssert(aClass != nil); |
[super init]; |
mockedClass = aClass; |
+ [self prepareClassForClassMethodMocking]; |
return self; |
} |
+- (void)dealloc |
+{ |
+ [self stopMocking]; |
+ [super dealloc]; |
+} |
+ |
- (NSString *)description |
{ |
- return [NSString stringWithFormat:@"OCMockObject[%@]", NSStringFromClass(mockedClass)]; |
+ return [NSString stringWithFormat:@"OCMockObject(%@)", NSStringFromClass(mockedClass)]; |
} |
- (Class)mockedClass |
@@ -27,12 +49,140 @@ |
return mockedClass; |
} |
+#pragma mark Extending/overriding superclass behaviour |
+ |
+- (void)stopMocking |
+{ |
+ if(originalMetaClass != nil) |
+ [self restoreMetaClass]; |
+ [super stopMocking]; |
+} |
+ |
+- (void)restoreMetaClass |
+{ |
+ OCMSetAssociatedMockForClass(nil, mockedClass); |
+ OCMSetIsa(mockedClass, originalMetaClass); |
+ originalMetaClass = nil; |
+} |
+ |
+- (void)addStub:(OCMInvocationStub *)aStub |
+{ |
+ [super addStub:aStub]; |
+ if([aStub recordedAsClassMethod]) |
+ [self setupForwarderForClassMethodSelector:[[aStub recordedInvocation] selector]]; |
+} |
+ |
+ |
+#pragma mark Class method mocking |
+ |
+- (void)prepareClassForClassMethodMocking |
+{ |
+ /* haven't figured out how to work around runtime dependencies on NSString, so exclude it for now */ |
+ /* also weird: [[NSString class] isKindOfClass:[NSString class]] is false, hence the additional clause */ |
+ if([[mockedClass class] isKindOfClass:[NSString class]] || (mockedClass == [NSString class])) |
+ return; |
+ |
+ /* if there is another mock for this exact class, stop it */ |
+ id otherMock = OCMGetAssociatedMockForClass(mockedClass, NO); |
+ if(otherMock != nil) |
+ [otherMock restoreMetaClass]; |
+ |
+ OCMSetAssociatedMockForClass(self, mockedClass); |
+ |
+ /* dynamically create a subclass and use its meta class as the meta class for the mocked class */ |
+ Class subclass = OCMCreateSubclass(mockedClass, mockedClass); |
+ originalMetaClass = object_getClass(mockedClass); |
+ id newMetaClass = object_getClass(subclass); |
+ OCMSetIsa(mockedClass, OCMGetIsa(subclass)); |
+ |
+ /* point forwardInvocation: of the object to the implementation in the mock */ |
+ Method myForwardMethod = class_getInstanceMethod([self mockObjectClass], @selector(forwardInvocationForClassObject:)); |
+ IMP myForwardIMP = method_getImplementation(myForwardMethod); |
+ class_addMethod(newMetaClass, @selector(forwardInvocation:), myForwardIMP, method_getTypeEncoding(myForwardMethod)); |
+ |
+ /* create a dummy initialize method */ |
+ Method myDummyInitializeMethod = class_getInstanceMethod([self mockObjectClass], @selector(initializeForClassObject)); |
+ const char *initializeTypes = method_getTypeEncoding(myDummyInitializeMethod); |
+ IMP myDummyInitializeIMP = method_getImplementation(myDummyInitializeMethod); |
+ class_addMethod(newMetaClass, @selector(initialize), myDummyInitializeIMP, initializeTypes); |
+ |
+ /* adding forwarder for most class methods (instance methods on meta class) to allow for verify after run */ |
+ NSArray *methodBlackList = @[@"class", @"forwardingTargetForSelector:", @"methodSignatureForSelector:", @"forwardInvocation:", @"isBlock", |
+ @"instanceMethodForwarderForSelector:", @"instanceMethodSignatureForSelector:"]; |
+ [NSObject enumerateMethodsInClass:originalMetaClass usingBlock:^(Class cls, SEL sel) { |
+ if((cls == object_getClass([NSObject class])) || (cls == [NSObject class]) || (cls == object_getClass(cls))) |
+ return; |
+ NSString *className = NSStringFromClass(cls); |
+ NSString *selName = NSStringFromSelector(sel); |
+ if(([className hasPrefix:@"NS"] || [className hasPrefix:@"UI"]) && |
+ ([selName hasPrefix:@"_"] || [selName hasSuffix:@"_"])) |
+ return; |
+ if([methodBlackList containsObject:selName]) |
+ return; |
+ @try |
+ { |
+ [self setupForwarderForClassMethodSelector:sel]; |
+ } |
+ @catch(NSException *e) |
+ { |
+ // ignore for now |
+ } |
+ }]; |
+} |
+ |
+- (void)setupForwarderForClassMethodSelector:(SEL)selector |
+{ |
+ SEL aliasSelector = OCMAliasForOriginalSelector(selector); |
+ if(class_getClassMethod(mockedClass, aliasSelector) != NULL) |
+ return; |
+ |
+ Method originalMethod = class_getClassMethod(mockedClass, selector); |
+ IMP originalIMP = method_getImplementation(originalMethod); |
+ const char *types = method_getTypeEncoding(originalMethod); |
+ |
+ Class metaClass = object_getClass(mockedClass); |
+ IMP forwarderIMP = [originalMetaClass instanceMethodForwarderForSelector:selector]; |
+ class_replaceMethod(metaClass, selector, forwarderIMP, types); |
+ class_addMethod(metaClass, aliasSelector, originalIMP, types); |
+} |
+ |
+ |
+- (void)forwardInvocationForClassObject:(NSInvocation *)anInvocation |
+{ |
+ // in here "self" is a reference to the real class, not the mock |
+ OCClassMockObject *mock = OCMGetAssociatedMockForClass((Class) self, YES); |
+ if(mock == nil) |
+ { |
+ [NSException raise:NSInternalInconsistencyException format:@"No mock for class %@", NSStringFromClass((Class)self)]; |
+ } |
+ if([mock handleInvocation:anInvocation] == NO) |
+ { |
+ [anInvocation setSelector:OCMAliasForOriginalSelector([anInvocation selector])]; |
+ [anInvocation invoke]; |
+ } |
+} |
+ |
+- (void)initializeForClassObject |
+{ |
+ // we really just want to have an implementation so that the superclass's is not called |
+} |
+ |
#pragma mark Proxy API |
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector |
{ |
- return [mockedClass instanceMethodSignatureForSelector:aSelector]; |
+ return [mockedClass instanceMethodSignatureForSelector:aSelector]; |
+} |
+ |
+- (Class)mockObjectClass |
+{ |
+ return [super class]; |
+} |
+ |
+- (Class)class |
+{ |
+ return mockedClass; |
} |
- (BOOL)respondsToSelector:(SEL)selector |
@@ -40,4 +190,88 @@ |
return [mockedClass instancesRespondToSelector:selector]; |
} |
+- (BOOL)isKindOfClass:(Class)aClass |
+{ |
+ return [mockedClass isSubclassOfClass:aClass]; |
+} |
+ |
+- (BOOL)conformsToProtocol:(Protocol *)aProtocol |
+{ |
+ return class_conformsToProtocol(mockedClass, aProtocol); |
+} |
+ |
+@end |
+ |
+ |
+#pragma mark - |
+ |
+/** |
+ taken from: |
+ `class-dump -f isNS /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator7.0.sdk/System/Library/Frameworks/CoreFoundation.framework` |
+ |
+ @interface NSObject (__NSIsKinds) |
+ - (_Bool)isNSValue__; |
+ - (_Bool)isNSTimeZone__; |
+ - (_Bool)isNSString__; |
+ - (_Bool)isNSSet__; |
+ - (_Bool)isNSOrderedSet__; |
+ - (_Bool)isNSNumber__; |
+ - (_Bool)isNSDictionary__; |
+ - (_Bool)isNSDate__; |
+ - (_Bool)isNSData__; |
+ - (_Bool)isNSArray__; |
+ */ |
+ |
+@implementation OCClassMockObject(NSIsKindsImplementation) |
+ |
+- (BOOL)isNSValue__ |
+{ |
+ return [mockedClass isKindOfClass:[NSValue class]]; |
+} |
+ |
+- (BOOL)isNSTimeZone__ |
+{ |
+ return [mockedClass isKindOfClass:[NSTimeZone class]]; |
+} |
+ |
+- (BOOL)isNSSet__ |
+{ |
+ return [mockedClass isKindOfClass:[NSSet class]]; |
+} |
+ |
+- (BOOL)isNSOrderedSet__ |
+{ |
+ return [mockedClass isKindOfClass:[NSOrderedSet class]]; |
+} |
+ |
+- (BOOL)isNSNumber__ |
+{ |
+ return [mockedClass isKindOfClass:[NSNumber class]]; |
+} |
+ |
+- (BOOL)isNSDate__ |
+{ |
+ return [mockedClass isKindOfClass:[NSDate class]]; |
+} |
+ |
+- (BOOL)isNSString__ |
+{ |
+ return [mockedClass isKindOfClass:[NSString class]]; |
+} |
+ |
+- (BOOL)isNSDictionary__ |
+{ |
+ return [mockedClass isKindOfClass:[NSDictionary class]]; |
+} |
+ |
+- (BOOL)isNSData__ |
+{ |
+ return [mockedClass isKindOfClass:[NSData class]]; |
+} |
+ |
+- (BOOL)isNSArray__ |
+{ |
+ return [mockedClass isKindOfClass:[NSArray class]]; |
+} |
+ |
@end |