| Index: third_party/ocmock/OCMock/OCPartialMockObject.m
|
| diff --git a/third_party/ocmock/OCMock/OCPartialMockObject.m b/third_party/ocmock/OCMock/OCPartialMockObject.m
|
| old mode 100644
|
| new mode 100755
|
| index fb0b8bd592bc6298b58e8a7491860d509d622c41..c82db8227c01cd3c06e7f64232631366da28c1ac
|
| --- a/third_party/ocmock/OCMock/OCPartialMockObject.m
|
| +++ b/third_party/ocmock/OCMock/OCPartialMockObject.m
|
| @@ -1,74 +1,52 @@
|
| -//---------------------------------------------------------------------------------------
|
| -// $Id$
|
| -// Copyright (c) 2009 by Mulle Kybernetik. See License file for details.
|
| -//---------------------------------------------------------------------------------------
|
| +/*
|
| + * Copyright (c) 2009-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 "OCPartialMockRecorder.h"
|
| +#import "OCMockObject.h"
|
| #import "OCPartialMockObject.h"
|
| +#import "NSMethodSignature+OCMAdditions.h"
|
| +#import "NSObject+OCMAdditions.h"
|
| +#import "OCMFunctionsPrivate.h"
|
| +#import "OCMInvocationStub.h"
|
|
|
|
|
| -@interface OCPartialMockObject (Private)
|
| -- (void)forwardInvocationForRealObject:(NSInvocation *)anInvocation;
|
| -@end
|
| -
|
| -
|
| -NSString *OCMRealMethodAliasPrefix = @"ocmock_replaced_";
|
| -
|
| @implementation OCPartialMockObject
|
|
|
| -
|
| -#pragma mark Mock table
|
| -
|
| -static NSMutableDictionary *mockTable;
|
| -
|
| -+ (void)initialize
|
| -{
|
| - if(self == [OCPartialMockObject class])
|
| - mockTable = [[NSMutableDictionary alloc] init];
|
| -}
|
| -
|
| -+ (void)rememberPartialMock:(OCPartialMockObject *)mock forObject:(id)anObject
|
| -{
|
| - [mockTable setObject:[NSValue valueWithNonretainedObject:mock] forKey:[NSValue valueWithNonretainedObject:anObject]];
|
| -}
|
| -
|
| -+ (void)forgetPartialMockForObject:(id)anObject
|
| -{
|
| - [mockTable removeObjectForKey:[NSValue valueWithNonretainedObject:anObject]];
|
| -}
|
| -
|
| -+ (OCPartialMockObject *)existingPartialMockForObject:(id)anObject
|
| -{
|
| - OCPartialMockObject *mock = [[mockTable objectForKey:[NSValue valueWithNonretainedObject:anObject]] nonretainedObjectValue];
|
| - if(mock == nil)
|
| - [NSException raise:NSInternalInconsistencyException format:@"No partial mock for object %p", anObject];
|
| - return mock;
|
| -}
|
| -
|
| -
|
| -
|
| #pragma mark Initialisers, description, accessors, etc.
|
|
|
| - (id)initWithObject:(NSObject *)anObject
|
| {
|
| + NSParameterAssert(anObject != nil);
|
| + [self assertClassIsSupported:[anObject class]];
|
| [super initWithClass:[anObject class]];
|
| realObject = [anObject retain];
|
| - [[self class] rememberPartialMock:self forObject:anObject];
|
| - [self setupSubclassForObject:realObject];
|
| + [self prepareObjectForInstanceMethodMocking];
|
| return self;
|
| }
|
|
|
| - (void)dealloc
|
| {
|
| - if(realObject != nil)
|
| - [self stop];
|
| + [self stopMocking];
|
| + [realObject release];
|
| [super dealloc];
|
| }
|
|
|
| - (NSString *)description
|
| {
|
| - return [NSString stringWithFormat:@"OCPartialMockObject[%@]", NSStringFromClass(mockedClass)];
|
| + return [NSString stringWithFormat:@"OCPartialMockObject(%@)", NSStringFromClass(mockedClass)];
|
| }
|
|
|
| - (NSObject *)realObject
|
| @@ -76,66 +54,164 @@ static NSMutableDictionary *mockTable;
|
| return realObject;
|
| }
|
|
|
| -- (void)stop
|
| +#pragma mark Helper methods
|
| +
|
| +- (void)assertClassIsSupported:(Class)class
|
| {
|
| - object_setClass(realObject, [self mockedClass]);
|
| - [realObject release];
|
| - [[self class] forgetPartialMockForObject:realObject];
|
| - realObject = nil;
|
| + NSString *classname = NSStringFromClass(class);
|
| + NSString *reason = nil;
|
| + if([classname hasPrefix:@"__NSTagged"] || [classname hasPrefix:@"NSTagged"])
|
| + reason = [NSString stringWithFormat:@"OCMock does not support partially mocking tagged classes; got %@", classname];
|
| + else if([classname hasPrefix:@"__NSCF"])
|
| + reason = [NSString stringWithFormat:@"OCMock does not support partially mocking toll-free bridged classes; got %@", classname];
|
| +
|
| + if(reason != nil)
|
| + [[NSException exceptionWithName:NSInvalidArgumentException reason:reason userInfo:nil] raise];
|
| }
|
|
|
|
|
| -#pragma mark Subclass management
|
| +#pragma mark Extending/overriding superclass behaviour
|
| +
|
| +- (void)stopMocking
|
| +{
|
| + if(realObject != nil)
|
| + {
|
| + OCMSetAssociatedMockForObject(nil, realObject);
|
| + object_setClass(realObject, [self mockedClass]);
|
| + [realObject release];
|
| + realObject = nil;
|
| + }
|
| + [super stopMocking];
|
| +}
|
|
|
| -- (void)setupSubclassForObject:(id)anObject
|
| +- (void)addStub:(OCMInvocationStub *)aStub
|
| {
|
| - Class realClass = [anObject class];
|
| - double timestamp = [NSDate timeIntervalSinceReferenceDate];
|
| - const char *className = [[NSString stringWithFormat:@"%@-%p-%f", realClass, anObject, timestamp] UTF8String];
|
| - Class subclass = objc_allocateClassPair(realClass, className, 0);
|
| - objc_registerClassPair(subclass);
|
| - object_setClass(anObject, subclass);
|
| -
|
| - Method forwardInvocationMethod = class_getInstanceMethod([self class], @selector(forwardInvocationForRealObject:));
|
| - IMP forwardInvocationImp = method_getImplementation(forwardInvocationMethod);
|
| - const char *forwardInvocationTypes = method_getTypeEncoding(forwardInvocationMethod);
|
| - class_addMethod(subclass, @selector(forwardInvocation:), forwardInvocationImp, forwardInvocationTypes);
|
| + [super addStub:aStub];
|
| + if(![aStub recordedAsClassMethod])
|
| + [self setupForwarderForSelector:[[aStub recordedInvocation] selector]];
|
| }
|
|
|
| -- (void)setupForwarderForSelector:(SEL)selector
|
| +- (void)handleUnRecordedInvocation:(NSInvocation *)anInvocation
|
| {
|
| - Class subclass = [[self realObject] class];
|
| - Method originalMethod = class_getInstanceMethod([subclass superclass], selector);
|
| - IMP originalImp = method_getImplementation(originalMethod);
|
| + [anInvocation invokeWithTarget:realObject];
|
| +}
|
| +
|
|
|
| - IMP forwarderImp = [subclass instanceMethodForSelector:@selector(aMethodThatMustNotExist)];
|
| - class_addMethod(subclass, method_getName(originalMethod), forwarderImp, method_getTypeEncoding(originalMethod));
|
| +#pragma mark Subclass management
|
|
|
| - SEL aliasSelector = NSSelectorFromString([OCMRealMethodAliasPrefix stringByAppendingString:NSStringFromSelector(selector)]);
|
| - class_addMethod(subclass, aliasSelector, originalImp, method_getTypeEncoding(originalMethod));
|
| +- (void)prepareObjectForInstanceMethodMocking
|
| +{
|
| + OCMSetAssociatedMockForObject(self, realObject);
|
| +
|
| + /* dynamically create a subclass and set it as the class of the object */
|
| + Class subclass = OCMCreateSubclass(mockedClass, realObject);
|
| + object_setClass(realObject, subclass);
|
| +
|
| + /* point forwardInvocation: of the object to the implementation in the mock */
|
| + Method myForwardMethod = class_getInstanceMethod([self mockObjectClass], @selector(forwardInvocationForRealObject:));
|
| + IMP myForwardIMP = method_getImplementation(myForwardMethod);
|
| + class_addMethod(subclass, @selector(forwardInvocation:), myForwardIMP, method_getTypeEncoding(myForwardMethod));
|
| +
|
| + /* do the same for forwardingTargetForSelector, remember existing imp with alias selector */
|
| + Method myForwardingTargetMethod = class_getInstanceMethod([self mockObjectClass], @selector(forwardingTargetForSelectorForRealObject:));
|
| + IMP myForwardingTargetIMP = method_getImplementation(myForwardingTargetMethod);
|
| + IMP originalForwardingTargetIMP = [mockedClass instanceMethodForSelector:@selector(forwardingTargetForSelector:)];
|
| + class_addMethod(subclass, @selector(forwardingTargetForSelector:), myForwardingTargetIMP, method_getTypeEncoding(myForwardingTargetMethod));
|
| + class_addMethod(subclass, @selector(ocmock_replaced_forwardingTargetForSelector:), originalForwardingTargetIMP, method_getTypeEncoding(myForwardingTargetMethod));
|
| +
|
| + /* We also override the -class method to return the original class */
|
| + Method myObjectClassMethod = class_getInstanceMethod([self mockObjectClass], @selector(classForRealObject));
|
| + const char *objectClassTypes = method_getTypeEncoding(myObjectClassMethod);
|
| + IMP myObjectClassImp = method_getImplementation(myObjectClassMethod);
|
| + class_addMethod(subclass, @selector(class), myObjectClassImp, objectClassTypes);
|
| +
|
| + /* Adding forwarder for most instance methods to allow for verify after run */
|
| + NSArray *methodBlackList = @[@"class", @"forwardingTargetForSelector:", @"methodSignatureForSelector:", @"forwardInvocation:",
|
| + @"allowsWeakReference", @"retainWeakReference", @"isBlock"];
|
| + [NSObject enumerateMethodsInClass:mockedClass usingBlock:^(Class cls, SEL sel) {
|
| + if((cls == [NSObject class]) || (cls == [NSProxy class]))
|
| + 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 setupForwarderForSelector:sel];
|
| + }
|
| + @catch(NSException *e)
|
| + {
|
| + // ignore for now
|
| + }
|
| + }];
|
| }
|
|
|
| -- (void)forwardInvocationForRealObject:(NSInvocation *)anInvocation
|
| +- (void)setupForwarderForSelector:(SEL)sel
|
| {
|
| - // in here "self" is a reference to the real object, not the mock
|
| - OCPartialMockObject *mock = [OCPartialMockObject existingPartialMockForObject:self];
|
| - if([mock handleInvocation:anInvocation] == NO)
|
| - [NSException raise:NSInternalInconsistencyException format:@"Ended up in subclass forwarder for %@ with unstubbed method %@",
|
| - [self class], NSStringFromSelector([anInvocation selector])];
|
| + SEL aliasSelector = OCMAliasForOriginalSelector(sel);
|
| + if(class_getInstanceMethod(object_getClass(realObject), aliasSelector) != NULL)
|
| + return;
|
| +
|
| + Method originalMethod = class_getInstanceMethod(mockedClass, sel);
|
| + IMP originalIMP = method_getImplementation(originalMethod);
|
| + const char *types = method_getTypeEncoding(originalMethod);
|
| + /* Might be NULL if the selector is forwarded to another class */
|
| + // TODO: check the fallback implementation is actually sufficient
|
| + if(types == NULL)
|
| + types = ([[mockedClass instanceMethodSignatureForSelector:sel] fullObjCTypes]);
|
| +
|
| + Class subclass = object_getClass([self realObject]);
|
| + IMP forwarderIMP = [mockedClass instanceMethodForwarderForSelector:sel];
|
| + class_replaceMethod(subclass, sel, forwarderIMP, types);
|
| + class_addMethod(subclass, aliasSelector, originalIMP, types);
|
| }
|
|
|
|
|
| +// Implementation of the -class method; return the Class that was reported with [realObject class] prior to mocking
|
| +- (Class)classForRealObject
|
| +{
|
| + // in here "self" is a reference to the real object, not the mock
|
| + OCPartialMockObject *mock = OCMGetAssociatedMockForObject(self);
|
| + if(mock == nil)
|
| + [NSException raise:NSInternalInconsistencyException format:@"No partial mock for object %p", self];
|
| + return [mock mockedClass];
|
| +}
|
|
|
| -#pragma mark Overrides
|
|
|
| -- (id)getNewRecorder
|
| +- (id)forwardingTargetForSelectorForRealObject:(SEL)sel
|
| {
|
| - return [[[OCPartialMockRecorder alloc] initWithSignatureResolver:self] autorelease];
|
| + // in here "self" is a reference to the real object, not the mock
|
| + OCPartialMockObject *mock = OCMGetAssociatedMockForObject(self);
|
| + if(mock == nil)
|
| + [NSException raise:NSInternalInconsistencyException format:@"No partial mock for object %p", self];
|
| + if([mock handleSelector:sel])
|
| + return self;
|
| +
|
| + return [self ocmock_replaced_forwardingTargetForSelector:sel];
|
| }
|
|
|
| -- (void)handleUnRecordedInvocation:(NSInvocation *)anInvocation
|
| +// Make the compiler happy in -forwardingTargetForSelectorForRealObject: because it can't find the messageā¦
|
| +- (id)ocmock_replaced_forwardingTargetForSelector:(SEL)sel
|
| {
|
| - [anInvocation invokeWithTarget:realObject];
|
| + return nil;
|
| +}
|
| +
|
| +
|
| +- (void)forwardInvocationForRealObject:(NSInvocation *)anInvocation
|
| +{
|
| + // in here "self" is a reference to the real object, not the mock
|
| + OCPartialMockObject *mock = OCMGetAssociatedMockForObject(self);
|
| + if(mock == nil)
|
| + [NSException raise:NSInternalInconsistencyException format:@"No partial mock for object %p", self];
|
| +
|
| + if([mock handleInvocation:anInvocation] == NO)
|
| + {
|
| + [anInvocation setSelector:OCMAliasForOriginalSelector([anInvocation selector])];
|
| + [anInvocation invoke];
|
| + }
|
| }
|
|
|
|
|
|
|