diff --git a/Source/OCMock/OCClassMockObject.h b/Source/OCMock/OCClassMockObject.h index 6f1ccffd..48533067 100644 --- a/Source/OCMock/OCClassMockObject.h +++ b/Source/OCMock/OCClassMockObject.h @@ -16,12 +16,7 @@ #import "OCMockObject.h" -@interface OCClassMockObject : OCMockObject -{ - Class mockedClass; - Class originalMetaClass; - Class classCreatedForNewMetaClass; -} +@interface OCClassMockObject : OCMockObject - (id)initWithClass:(Class)aClass; @@ -30,4 +25,6 @@ - (void)assertClassIsSupported:(Class)aClass; +- (void)scribbleOnMemory:(void *)start ofSize:(size_t)size; +- (void)verifyScribbleAt:(void *)start ofSize:(size_t)size; @end diff --git a/Source/OCMock/OCClassMockObject.m b/Source/OCMock/OCClassMockObject.m index b7cbb71f..6409d5ab 100644 --- a/Source/OCMock/OCClassMockObject.m +++ b/Source/OCMock/OCClassMockObject.m @@ -26,15 +26,53 @@ + (BOOL)supportsMocking:(NSString **)reason; @end +@interface OCClassMockObjectInstanceVars : NSObject +@property (nonatomic) Class mockedClass; +@property (nonatomic) Class originalMetaClass; +@property (nonatomic) Class classCreatedForNewMetaClass; +@property (nonatomic) void *classScribbleStart; +@property (nonatomic) size_t classScribbleSize; +@end + +@implementation OCClassMockObjectInstanceVars +@end + +@interface OCClassMockObject () +@property (nonatomic) Class mockedClass; +@property (nonatomic) Class originalMetaClass; +@property (nonatomic) Class classCreatedForNewMetaClass; +@property (nonatomic) void *classScribbleStart; +@property (nonatomic) size_t classScribbleSize; +@end + +static const char *OCClassMockObjectInstanceVarsKey = "OCClassMockObjectInstanceVarsKey"; + @implementation OCClassMockObject -#pragma mark Initialisers, description, accessors, etc. +#pragma mark Initialisers, description, etc. - (id)initWithClass:(Class)aClass { [self assertClassIsSupported:aClass]; - [super init]; - mockedClass = aClass; + + size_t allocedSize = class_getInstanceSize(aClass); + Class selfClass = object_getClass(self); + size_t selfSize = class_getInstanceSize(selfClass); + if(allocedSize > selfSize) + { + self = realloc(self, allocedSize); + } + self = [super init]; + + OCClassMockObjectInstanceVars *vars = [[OCClassMockObjectInstanceVars alloc] init]; + objc_setAssociatedObject(self, OCClassMockObjectInstanceVarsKey, vars, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + [vars release]; + + self.classScribbleSize = allocedSize - selfSize; + self.classScribbleStart = (void *)self + selfSize; + [self scribbleOnMemory:self.classScribbleStart ofSize:self.classScribbleSize]; + + self.mockedClass = aClass; [self prepareClassForClassMethodMocking]; return self; } @@ -47,12 +85,64 @@ - (void)dealloc - (NSString *)description { - return [NSString stringWithFormat:@"OCClassMockObject(%@)", NSStringFromClass(mockedClass)]; + return [NSString stringWithFormat:@"OCClassMockObject(%@)", NSStringFromClass(self.mockedClass)]; +} + +- (void)scribbleOnMemory:(void *)start ofSize:(size_t)size +{ + bzero(start, size); +} + +- (void)verifyScribbleAt:(void *)start ofSize:(size_t)size +{ + // Default version does no verification +} + +#pragma mark Setters/Getters + +- (OCClassMockObjectInstanceVars *)classMockObjectInstanceVars +{ + return objc_getAssociatedObject(self, OCClassMockObjectInstanceVarsKey); } - (Class)mockedClass { - return mockedClass; + return self.classMockObjectInstanceVars.mockedClass; +} + +- (Class)classCreatedForNewMetaClass +{ + return self.classMockObjectInstanceVars.classCreatedForNewMetaClass; +} + +- (Class)originalMetaClass +{ + return self.classMockObjectInstanceVars.originalMetaClass; +} + +- (void *)classScribbleStart +{ + return self.classMockObjectInstanceVars.classScribbleStart; +} + +- (size_t)classScribbleSize +{ + return self.classMockObjectInstanceVars.classScribbleSize; +} + +- (void)setMockedClass:(Class)mockedClass +{ + self.classMockObjectInstanceVars.mockedClass = mockedClass; +} + +- (void)setClassCreatedForNewMetaClass:(Class)classCreatedForNewMetaClass +{ + self.classMockObjectInstanceVars.classCreatedForNewMetaClass = classCreatedForNewMetaClass; +} + +- (void)setOriginalMetaClass:(Class)originalMetaClass +{ + self.classMockObjectInstanceVars.originalMetaClass = originalMetaClass; } - (void)assertClassIsSupported:(Class)aClass @@ -68,28 +158,39 @@ - (void)assertClassIsSupported:(Class)aClass } } +- (void)setClassScribbleSize:(size_t)classScribbleSize +{ + self.classMockObjectInstanceVars.classScribbleSize = classScribbleSize; +} + +- (void)setClassScribbleStart:(void *)classScribbleStart +{ + self.classMockObjectInstanceVars.classScribbleStart = classScribbleStart; +} + #pragma mark Extending/overriding superclass behaviour - (void)stopMocking { - if(originalMetaClass != nil) + if(self.originalMetaClass != nil) { [self stopMockingClassMethods]; } - if(classCreatedForNewMetaClass != nil) + if(self.classCreatedForNewMetaClass != nil) { - OCMDisposeSubclass(classCreatedForNewMetaClass); - classCreatedForNewMetaClass = nil; + OCMDisposeSubclass(self.classCreatedForNewMetaClass); + self.classCreatedForNewMetaClass = nil; } [super stopMocking]; + [self verifyScribbleAt:self.classScribbleStart ofSize:self.classScribbleSize]; } - (void)stopMockingClassMethods { - OCMSetAssociatedMockForClass(nil, mockedClass); - object_setClass(mockedClass, originalMetaClass); - originalMetaClass = nil; + OCMSetAssociatedMockForClass(nil, self.mockedClass); + object_setClass(self.mockedClass, self.originalMetaClass); + self.originalMetaClass = nil; /* created meta class will be disposed later because partial mocks create another subclass depending on it */ } @@ -107,24 +208,24 @@ - (void)addStub:(OCMInvocationStub *)aStub - (void)prepareClassForClassMethodMocking { /* the runtime and OCMock depend on string and array; we don't intercept methods on them to avoid endless loops */ - if([[mockedClass class] isSubclassOfClass:[NSString class]] || [[mockedClass class] isSubclassOfClass:[NSArray class]]) + if([[self.mockedClass class] isSubclassOfClass:[NSString class]] || [[self.mockedClass class] isSubclassOfClass:[NSArray class]]) return; /* trying to replace class methods on NSManagedObject and subclasses of it doesn't work; see #339 */ - if([mockedClass isSubclassOfClass:objc_getClass("NSManagedObject")]) + if([self.mockedClass isSubclassOfClass:objc_getClass("NSManagedObject")]) return; /* if there is another mock for this exact class, stop it */ - id otherMock = OCMGetAssociatedMockForClass(mockedClass, NO); + id otherMock = OCMGetAssociatedMockForClass(self.mockedClass, NO); if(otherMock != nil) [otherMock stopMockingClassMethods]; - OCMSetAssociatedMockForClass(self, mockedClass); + OCMSetAssociatedMockForClass(self, self.mockedClass); /* dynamically create a subclass and use its meta class as the meta class for the mocked class */ - classCreatedForNewMetaClass = OCMCreateSubclass(mockedClass, mockedClass); - originalMetaClass = object_getClass(mockedClass); - id newMetaClass = object_getClass(classCreatedForNewMetaClass); + self.classCreatedForNewMetaClass = OCMCreateSubclass(self.mockedClass, self.mockedClass); + self.originalMetaClass = object_getClass(self.mockedClass); + id newMetaClass = object_getClass(self.classCreatedForNewMetaClass); /* create a dummy initialize method */ Method myDummyInitializeMethod = class_getInstanceMethod([self mockObjectClass], @selector(initializeForClassObject)); @@ -132,7 +233,7 @@ - (void)prepareClassForClassMethodMocking IMP myDummyInitializeIMP = method_getImplementation(myDummyInitializeMethod); class_addMethod(newMetaClass, @selector(initialize), myDummyInitializeIMP, initializeTypes); - object_setClass(mockedClass, newMetaClass); // only after dummy initialize is installed (iOS9) + object_setClass(self.mockedClass, newMetaClass); // only after dummy initialize is installed (iOS9) /* point forwardInvocation: of the object to the implementation in the mock */ Method myForwardMethod = class_getInstanceMethod([self mockObjectClass], @selector(forwardInvocationForClassObject:)); @@ -160,22 +261,22 @@ - (void)prepareClassForClassMethodMocking // ignore for now } }; - [NSObject enumerateMethodsInClass:originalMetaClass usingBlock:setupForwarderFiltered]; + [NSObject enumerateMethodsInClass:self.originalMetaClass usingBlock:setupForwarderFiltered]; } - (void)setupForwarderForClassMethodSelector:(SEL)selector { SEL aliasSelector = OCMAliasForOriginalSelector(selector); - if(class_getClassMethod(mockedClass, aliasSelector) != NULL) + if(class_getClassMethod(self.mockedClass, aliasSelector) != NULL) return; - Method originalMethod = class_getClassMethod(mockedClass, selector); + Method originalMethod = class_getClassMethod(self.mockedClass, selector); IMP originalIMP = method_getImplementation(originalMethod); const char *types = method_getTypeEncoding(originalMethod); - Class metaClass = object_getClass(mockedClass); - IMP forwarderIMP = [originalMetaClass instanceMethodForwarderForSelector:selector]; + Class metaClass = object_getClass(self.mockedClass); + IMP forwarderIMP = [self.originalMetaClass instanceMethodForwarderForSelector:selector]; class_addMethod(metaClass, aliasSelector, originalIMP, types); class_replaceMethod(metaClass, selector, forwarderIMP, types); } @@ -206,10 +307,10 @@ - (void)initializeForClassObject - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector { - NSMethodSignature *signature = [mockedClass instanceMethodSignatureForSelector:aSelector]; + NSMethodSignature *signature = [self.mockedClass instanceMethodSignatureForSelector:aSelector]; if(signature == nil) { - signature = [NSMethodSignature signatureForDynamicPropertyAccessedWithSelector:aSelector inClass:mockedClass]; + signature = [NSMethodSignature signatureForDynamicPropertyAccessedWithSelector:aSelector inClass:self.mockedClass]; } return signature; } @@ -221,25 +322,25 @@ - (Class)mockObjectClass - (Class)class { - return mockedClass; + return self.mockedClass; } - (BOOL)respondsToSelector:(SEL)selector { - return [mockedClass instancesRespondToSelector:selector]; + return [self.mockedClass instancesRespondToSelector:selector]; } - (BOOL)isKindOfClass:(Class)aClass { - return [mockedClass isSubclassOfClass:aClass]; + return [self.mockedClass isSubclassOfClass:aClass]; } - (BOOL)conformsToProtocol:(Protocol *)aProtocol { - Class clazz = mockedClass; + Class clazz = self.mockedClass; while(clazz != nil) { - if(class_conformsToProtocol(clazz, aProtocol)) + if (class_conformsToProtocol(clazz, aProtocol)) { return YES; } @@ -274,52 +375,52 @@ @implementation OCClassMockObject(NSIsKindsImplementation) - (BOOL)isNSValue__ { - return [mockedClass isSubclassOfClass:[NSValue class]]; + return [self.mockedClass isSubclassOfClass:[NSValue class]]; } - (BOOL)isNSTimeZone__ { - return [mockedClass isSubclassOfClass:[NSTimeZone class]]; + return [self.mockedClass isSubclassOfClass:[NSTimeZone class]]; } - (BOOL)isNSSet__ { - return [mockedClass isSubclassOfClass:[NSSet class]]; + return [self.mockedClass isSubclassOfClass:[NSSet class]]; } - (BOOL)isNSOrderedSet__ { - return [mockedClass isSubclassOfClass:[NSOrderedSet class]]; + return [self.mockedClass isSubclassOfClass:[NSOrderedSet class]]; } - (BOOL)isNSNumber__ { - return [mockedClass isSubclassOfClass:[NSNumber class]]; + return [self.mockedClass isSubclassOfClass:[NSNumber class]]; } - (BOOL)isNSDate__ { - return [mockedClass isSubclassOfClass:[NSDate class]]; + return [self.mockedClass isSubclassOfClass:[NSDate class]]; } - (BOOL)isNSString__ { - return [mockedClass isSubclassOfClass:[NSString class]]; + return [self.mockedClass isSubclassOfClass:[NSString class]]; } - (BOOL)isNSDictionary__ { - return [mockedClass isSubclassOfClass:[NSDictionary class]]; + return [self.mockedClass isSubclassOfClass:[NSDictionary class]]; } - (BOOL)isNSData__ { - return [mockedClass isSubclassOfClass:[NSData class]]; + return [self.mockedClass isSubclassOfClass:[NSData class]]; } - (BOOL)isNSArray__ { - return [mockedClass isSubclassOfClass:[NSArray class]]; + return [self.mockedClass isSubclassOfClass:[NSArray class]]; } @end diff --git a/Source/OCMock/OCMockObject.h b/Source/OCMock/OCMockObject.h index f5739716..73d1f082 100644 --- a/Source/OCMock/OCMockObject.h +++ b/Source/OCMock/OCMockObject.h @@ -25,15 +25,6 @@ @interface OCMockObject : NSProxy -{ - BOOL isNice; - BOOL expectationOrderMatters; - NSMutableArray *stubs; - NSMutableArray *expectations; - NSMutableArray *exceptions; - NSMutableArray *invocations; -} - + (id)mockForClass:(Class)aClass; + (id)mockForProtocol:(Protocol *)aProtocol; + (id)partialMockForObject:(NSObject *)anObject; diff --git a/Source/OCMock/OCMockObject.m b/Source/OCMock/OCMockObject.m index ec652b5d..cdabef00 100644 --- a/Source/OCMock/OCMockObject.m +++ b/Source/OCMock/OCMockObject.m @@ -29,6 +29,28 @@ #import "OCPartialMockObject.h" #import "OCProtocolMockObject.h" +@interface OCMockObjectInstanceVars : NSObject +@property (nonatomic) BOOL isNice; +@property (nonatomic) BOOL expectationOrderMatters; +@property (nonatomic, assign) NSMutableArray *stubs; +@property (nonatomic, assign) NSMutableArray *expectations; +@property (nonatomic, assign) NSMutableArray *exceptions; +@property (nonatomic, assign) NSMutableArray *invocations; +@end + +@implementation OCMockObjectInstanceVars +@end + +static const char *OCMockObjectInstanceVarsKey = "OCMockObjectInstanceVarsKey"; + +@interface OCMockObject () +@property (nonatomic) BOOL isNice; +@property (nonatomic) BOOL expectationOrderMatters; +@property (nonatomic) NSMutableArray *stubs; +@property (nonatomic) NSMutableArray *expectations; +@property (nonatomic) NSMutableArray *exceptions; +@property (nonatomic) NSMutableArray *invocations; +@end @implementation OCMockObject @@ -72,7 +94,7 @@ + (id)niceMockForProtocol:(Protocol *)aProtocol + (id)_makeNice:(OCMockObject *)mock { - mock->isNice = YES; + mock.isNice = YES; return mock; } @@ -95,8 +117,8 @@ - (instancetype)init return (id)[recorder init]; } - // skip initialisation when init is called again, which can happen when stubbing alloc/init - if(stubs != nil) + // skip initialisation when init is called again, which can happen when stubbing alloc/init + if(self.mockObjectInstanceVars != nil) { return self; } @@ -106,21 +128,25 @@ - (instancetype)init [NSException raise:NSInternalInconsistencyException format:@"*** Cannot create instances of OCMockObject. Please use one of the subclasses."]; } + OCMockObjectInstanceVars *vars = [[OCMockObjectInstanceVars alloc] init]; + objc_setAssociatedObject(self, OCMockObjectInstanceVarsKey, vars, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + [vars release]; + // no [super init], we're inheriting from NSProxy - expectationOrderMatters = NO; - stubs = [[NSMutableArray alloc] init]; - expectations = [[NSMutableArray alloc] init]; - exceptions = [[NSMutableArray alloc] init]; - invocations = [[NSMutableArray alloc] init]; + self.expectationOrderMatters = NO; + self.stubs = [[NSMutableArray alloc] init]; + self.expectations = [[NSMutableArray alloc] init]; + self.exceptions = [[NSMutableArray alloc] init]; + self.invocations = [[NSMutableArray alloc] init]; return self; } - (void)dealloc { - [stubs release]; - [expectations release]; - [exceptions release]; - [invocations release]; + [self.stubs release]; + [self.expectations release]; + [self.exceptions release]; + [self.invocations release]; [super dealloc]; } @@ -132,17 +158,17 @@ - (NSString *)description - (void)addStub:(OCMInvocationStub *)aStub { [self assertInvocationsArrayIsPresent]; - @synchronized(stubs) + @synchronized(self.stubs) { - [stubs addObject:aStub]; + [self.stubs addObject:aStub]; } } - (OCMInvocationStub *)stubForInvocation:(NSInvocation *)anInvocation { - @synchronized(stubs) + @synchronized(self.stubs) { - for(OCMInvocationStub *stub in stubs) + for(OCMInvocationStub *stub in self.stubs) if([stub matchesInvocation:anInvocation]) return stub; return nil; @@ -151,15 +177,15 @@ - (OCMInvocationStub *)stubForInvocation:(NSInvocation *)anInvocation - (void)addExpectation:(OCMInvocationExpectation *)anExpectation { - @synchronized(expectations) + @synchronized(self.expectations) { - [expectations addObject:anExpectation]; + [self.expectations addObject:anExpectation]; } } - (void)assertInvocationsArrayIsPresent { - if(invocations == nil) + if(self.invocations == nil) { [NSException raise:NSInternalInconsistencyException format:@"** Cannot use mock object %@ at %p. This error usually occurs when a mock object is used after stopMocking has been called on it. In most cases it is not necessary to call stopMocking. If you know you have to, please make sure that the mock object is not used afterwards.", [self description], (void *)self]; } @@ -167,7 +193,7 @@ - (void)assertInvocationsArrayIsPresent - (void)addInvocation:(NSInvocation *)anInvocation { - @synchronized(invocations) + @synchronized(self.invocations) { // We can't do a normal retain arguments on anInvocation because its target/arguments/return // value could be self. That would produce a retain cycle self->invocations->anInvocation->self. @@ -176,33 +202,92 @@ - (void)addInvocation:(NSInvocation *)anInvocation // This still doesn't completely prevent retain cycles since any of the arguments could have a // strong reference to self. Those will have to be broken with manual calls to -stopMocking. [anInvocation retainObjectArgumentsExcludingObject:self]; - [invocations addObject:anInvocation]; + [self.invocations addObject:anInvocation]; } } +# pragma mark Getters/Setters +- (OCMockObjectInstanceVars *)mockObjectInstanceVars +{ + return objc_getAssociatedObject(self, OCMockObjectInstanceVarsKey); +} + +- (BOOL)isNice +{ + return self.mockObjectInstanceVars.isNice; +} -#pragma mark Public API +- (BOOL)expectationOrderMatters +{ + return self.mockObjectInstanceVars.expectationOrderMatters; +} + +- (NSMutableArray *)stubs +{ + return self.mockObjectInstanceVars.stubs; +} + +- (NSMutableArray *)expectations +{ + return self.mockObjectInstanceVars.expectations; +} + +- (NSMutableArray *)exceptions +{ + return self.mockObjectInstanceVars.exceptions; +} -- (void)setExpectationOrderMatters:(BOOL)flag +- (NSMutableArray *)invocations { - expectationOrderMatters = flag; + return self.mockObjectInstanceVars.invocations; } +- (void)setIsNice:(BOOL)isNice +{ + self.mockObjectInstanceVars.isNice = isNice; +} + +- (void)setExpectationOrderMatters:(BOOL)expectationOrderMatters +{ + self.mockObjectInstanceVars.expectationOrderMatters = expectationOrderMatters; +} + +- (void)setStubs:(NSMutableArray *)stubs +{ + self.mockObjectInstanceVars.stubs = stubs; +} + +- (void)setExpectations:(NSMutableArray *)expectations +{ + self.mockObjectInstanceVars.expectations = expectations; +} + +- (void)setExceptions:(NSMutableArray *)exceptions +{ + self.mockObjectInstanceVars.exceptions = exceptions; +} + +- (void)setInvocations:(NSMutableArray *)invocations +{ + self.mockObjectInstanceVars.invocations = invocations; +} + +#pragma mark Public API + - (void)stopMocking { // invocations can contain objects that clients expect to be deallocated by now, // and they can also have a strong reference to self, creating a retain cycle. Get // rid of all of the invocations to hopefully let their objects deallocate, and to // break any retain cycles involving self. - @synchronized(invocations) + @synchronized(self.invocations) { - [invocations removeAllObjects]; - [invocations autorelease]; - invocations = nil; + [self.invocations removeAllObjects]; + [self.invocations release]; + self.invocations = nil; } } - - (id)stub { return [[[OCMStubRecorder alloc] initWithMockObject:self] autorelease]; @@ -227,9 +312,9 @@ - (id)verify - (id)verifyAtLocation:(OCMLocation *)location { NSMutableArray *unsatisfiedExpectations = [NSMutableArray array]; - @synchronized(expectations) + @synchronized(self.expectations) { - for(OCMInvocationExpectation *e in expectations) + for(OCMInvocationExpectation *e in self.expectations) { if(![e isSatisfied]) [unsatisfiedExpectations addObject:e]; @@ -250,9 +335,9 @@ - (id)verifyAtLocation:(OCMLocation *)location } OCMInvocationExpectation *firstException = nil; - @synchronized(exceptions) + @synchronized(self.exceptions) { - firstException = [exceptions.firstObject retain]; + firstException = [self.exceptions.firstObject retain]; } if(firstException) { @@ -276,10 +361,10 @@ - (void)verifyWithDelay:(NSTimeInterval)delay atLocation:(OCMLocation *)location NSTimeInterval step = 0.01; while(delay > 0) { - @synchronized(expectations) + @synchronized(self.expectations) { BOOL allExpectationsAreMatchAndReject = YES; - for(OCMInvocationExpectation *expectation in expectations) + for(OCMInvocationExpectation *expectation in self.expectations) { if(![expectation isMatchAndReject]) { @@ -314,9 +399,9 @@ - (void)verifyInvocation:(OCMInvocationMatcher *)matcher withQuantifier:(OCMQuan { NSUInteger count = 0; [self assertInvocationsArrayIsPresent]; - @synchronized(invocations) + @synchronized(self.invocations) { - for(NSInvocation *invocation in invocations) + for(NSInvocation *invocation in self.invocations) { if([matcher matchesInvocation:invocation]) count += 1; @@ -367,9 +452,9 @@ - (id)forwardingTargetForSelector:(SEL)aSelector - (BOOL)handleSelector:(SEL)sel { - @synchronized(stubs) + @synchronized(self.stubs) { - for(OCMInvocationStub *recorder in stubs) + for(OCMInvocationStub *recorder in self.stubs) if([recorder matchesSelector:sel]) return YES; } @@ -392,9 +477,9 @@ - (void)forwardInvocation:(NSInvocation *)anInvocation else { // add non-stubbed method to list of exceptions to be re-raised in verify - @synchronized(exceptions) + @synchronized(self.exceptions) { - [exceptions addObject:e]; + [self.exceptions addObject:e]; } } [e raise]; @@ -404,9 +489,11 @@ - (void)forwardInvocation:(NSInvocation *)anInvocation - (BOOL)handleInvocation:(NSInvocation *)anInvocation { [self assertInvocationsArrayIsPresent]; + [self addInvocation:anInvocation]; OCMInvocationStub *stub = [self stubForInvocation:anInvocation]; + if(stub == nil) return NO; @@ -414,16 +501,16 @@ - (BOOL)handleInvocation:(NSInvocation *)anInvocation [stub retain]; BOOL removeStub = NO; - @synchronized(expectations) + @synchronized(self.expectations) { - if([expectations containsObject:stub]) + if([self.expectations containsObject:stub]) { OCMInvocationExpectation *expectation = [self _nextExpectedInvocation]; - if(expectationOrderMatters && (expectation != stub)) + if(self.expectationOrderMatters && (expectation != stub)) { [NSException raise:NSInternalInconsistencyException format:@"%@: unexpected method invoked: %@\n\texpected:\t%@", - [self description], [stub description], [[expectations objectAtIndex:0] description]]; + [self description], [stub description], [[self.expectations objectAtIndex:0] description]]; } // We can't check isSatisfied yet, since the stub won't be satisfied until we call @@ -432,16 +519,16 @@ - (BOOL)handleInvocation:(NSInvocation *)anInvocation // expected methods to be called yet if(![(OCMInvocationExpectation *)stub isMatchAndReject]) { - [expectations removeObject:stub]; + [self.expectations removeObject:stub]; removeStub = YES; } } } if(removeStub) { - @synchronized(stubs) + @synchronized(self.stubs) { - [stubs removeObject:stub]; + [self.stubs removeObject:stub]; } } @@ -460,7 +547,7 @@ - (BOOL)handleInvocation:(NSInvocation *)anInvocation // Must be synchronized on expectations when calling this method. - (OCMInvocationExpectation *)_nextExpectedInvocation { - for(OCMInvocationExpectation *expectation in expectations) + for(OCMInvocationExpectation *expectation in self.expectations) if(![expectation isMatchAndReject]) return expectation; return nil; @@ -468,7 +555,7 @@ - (OCMInvocationExpectation *)_nextExpectedInvocation - (void)handleUnRecordedInvocation:(NSInvocation *)anInvocation { - if(isNice == NO) + if(self.isNice == NO) { [NSException raise:NSInternalInconsistencyException format:@"%@: unexpected method invoked: %@ %@", @@ -500,16 +587,16 @@ - (NSString *)_stubDescriptions:(BOOL)onlyExpectations { NSMutableString *outputString = [NSMutableString string]; NSArray *stubsCopy = nil; - @synchronized(stubs) + @synchronized(self.stubs) { - stubsCopy = [stubs copy]; + stubsCopy = [self.stubs copy]; } for(OCMStubRecorder *stub in stubsCopy) { BOOL expectationsContainStub = NO; - @synchronized(expectations) + @synchronized(self.expectations) { - expectationsContainStub = [expectations containsObject:stub]; + expectationsContainStub = [self.expectations containsObject:stub]; } NSString *prefix = @""; diff --git a/Source/OCMock/OCPartialMockObject.h b/Source/OCMock/OCPartialMockObject.h index afc3bb08..412bb50a 100644 --- a/Source/OCMock/OCPartialMockObject.h +++ b/Source/OCMock/OCPartialMockObject.h @@ -17,10 +17,6 @@ #import "OCClassMockObject.h" @interface OCPartialMockObject : OCClassMockObject -{ - NSObject *realObject; - NSInvocation *invocationFromMock; -} - (id)initWithObject:(NSObject *)anObject; diff --git a/Source/OCMock/OCPartialMockObject.m b/Source/OCMock/OCPartialMockObject.m index c482e4f2..a989d819 100644 --- a/Source/OCMock/OCPartialMockObject.m +++ b/Source/OCMock/OCPartialMockObject.m @@ -22,6 +22,25 @@ #import "OCMFunctionsPrivate.h" #import "OCMInvocationStub.h" +@interface OCPartialMockObjectInstanceVars : NSObject +@property (nonatomic, assign) NSObject *realObject; +@property (nonatomic, assign) NSInvocation *invocationFromMock; +@end + +@implementation OCPartialMockObjectInstanceVars +@end + +static const char *OCPartialMockObjectInstanceVarsKey = "OCPartialMockObjectInstanceVarsKey"; + +// 0xEB chosen intentionally to try and force crashes. +// It has both the high and low bit set, and 0xEBEBEBEBEB..etc +// should be recognizable in a debugger as a bad value. +static uint8_t OCScribbleByte = 0xEB; + +@interface OCPartialMockObject () +@property (nonatomic) NSObject *realObject; +@property (nonatomic) NSInvocation *invocationFromMock; +@end @implementation OCPartialMockObject @@ -32,23 +51,68 @@ - (id)initWithObject:(NSObject *)anObject if(anObject == nil) [NSException raise:NSInvalidArgumentException format:@"Object cannot be nil."]; Class const class = [self classToSubclassForObject:anObject]; - [super initWithClass:class]; - realObject = [anObject retain]; + [self assertClassIsSupported:class]; + self = [super initWithClass:class]; + OCPartialMockObjectInstanceVars *vars = [[OCPartialMockObjectInstanceVars alloc] init]; + objc_setAssociatedObject(self, OCPartialMockObjectInstanceVarsKey, vars, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + [vars release]; + self.realObject = [anObject retain]; [self prepareObjectForInstanceMethodMocking]; return self; } - (NSString *)description { - return [NSString stringWithFormat:@"OCPartialMockObject(%@)", NSStringFromClass(mockedClass)]; + return [NSString stringWithFormat:@"OCPartialMockObject(%@)", NSStringFromClass(self.mockedClass)]; +} + +- (void)scribbleOnMemory:(void *)start ofSize:(size_t)size; +{ + for(size_t i = 0; i < size; ++i) + { + ((uint8_t*)start)[i] = OCScribbleByte; + } +} + +- (void)verifyScribbleAt:(void *)start ofSize:(size_t)size; +{ + for(size_t i = 0; i < size; ++i) + { + if(((uint8_t*)start)[i] != OCScribbleByte) + { + [NSException raise:NSInternalInconsistencyException format:@"The class that partial mock `%@` does internal direct ivar accesses. You must use the real object instead of the mock for all uses other than setting/verifying stubs/expectations etc.", self]; + } + } +} + +#pragma mark Setters/Getters + +- (OCPartialMockObjectInstanceVars *)partialMockObjectInstanceVars +{ + return objc_getAssociatedObject(self, OCPartialMockObjectInstanceVarsKey); } - (NSObject *)realObject { - return realObject; + return self.partialMockObjectInstanceVars.realObject; +} + +- (void)setRealObject:(NSObject *)realObject +{ + self.partialMockObjectInstanceVars.realObject = realObject; +} + +- (NSInvocation *)invocationFromMock +{ + return self.partialMockObjectInstanceVars.invocationFromMock; +} + +- (void)setInvocationFromMock:(NSInvocation *)invocationFromMock +{ + self.partialMockObjectInstanceVars.invocationFromMock = invocationFromMock; } -#pragma mark Helper methods +#pragma mark Helper methods - (void)assertClassIsSupported:(Class)class { @@ -84,13 +148,13 @@ - (Class)classToSubclassForObject:(id)object - (void)stopMocking { - if(realObject != nil) + if(self.realObject != nil) { - Class partialMockClass = object_getClass(realObject); - OCMSetAssociatedMockForObject(nil, realObject); - object_setClass(realObject, [self mockedClass]); - [realObject release]; - realObject = nil; + Class partialMockClass = object_getClass(self.realObject); + OCMSetAssociatedMockForObject(nil, self.realObject); + object_setClass(self.realObject, self.mockedClass); + [self.realObject release]; + self.realObject = nil; OCMDisposeSubclass(partialMockClass); } [super stopMocking]; @@ -107,7 +171,7 @@ - (void)addInvocation:(NSInvocation *)anInvocation { // If the mock invokes a method on the real object we end up here a second time, but because // the mock has added the invocation already we do not want to add it again. - if((invocationFromMock == nil) || ([anInvocation selector] != [invocationFromMock selector])) + if((self.invocationFromMock == nil) || ([anInvocation selector] != [self.invocationFromMock selector])) [super addInvocation:anInvocation]; } @@ -122,21 +186,21 @@ - (void)handleUnRecordedInvocation:(NSInvocation *)anInvocation if([anInvocation methodIsInInitFamily]) { targetReceivingInit = [anInvocation target]; - [realObject retain]; + [self.realObject retain]; } - invocationFromMock = anInvocation; - [anInvocation invokeWithTarget:realObject]; - invocationFromMock = nil; + self.invocationFromMock = anInvocation; + [anInvocation invokeWithTarget:self.realObject]; + self.invocationFromMock = nil; if(targetReceivingInit) { id returnVal; [anInvocation getReturnValue:&returnVal]; - if(returnVal == realObject) + if(returnVal == self.realObject) { [anInvocation setReturnValue:&self]; - [realObject release]; + [self.realObject release]; [self retain]; } [targetReceivingInit release]; @@ -148,11 +212,11 @@ - (void)handleUnRecordedInvocation:(NSInvocation *)anInvocation - (void)prepareObjectForInstanceMethodMocking { - OCMSetAssociatedMockForObject(self, realObject); + OCMSetAssociatedMockForObject(self, self.realObject); /* dynamically create a subclass and set it as the class of the object */ - Class subclass = OCMCreateSubclass(mockedClass, realObject); - object_setClass(realObject, subclass); + Class subclass = OCMCreateSubclass(self.mockedClass, self.realObject); + object_setClass(self.realObject, subclass); /* point forwardInvocation: of the object to the implementation in the mock */ Method myForwardMethod = class_getInstanceMethod([self mockObjectClass], @selector(forwardInvocationForRealObject:)); @@ -162,7 +226,7 @@ - (void)prepareObjectForInstanceMethodMocking /* 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:)]; + IMP originalForwardingTargetIMP = [self.mockedClass instanceMethodForSelector:@selector(forwardingTargetForSelector:)]; class_addMethod(subclass, @selector(forwardingTargetForSelector:), myForwardingTargetIMP, method_getTypeEncoding(myForwardingTargetMethod)); class_addMethod(subclass, @selector(ocmock_replaced_forwardingTargetForSelector:), originalForwardingTargetIMP, method_getTypeEncoding(myForwardingTargetMethod)); @@ -189,25 +253,25 @@ - (void)prepareObjectForInstanceMethodMocking // ignore for now } }; - [NSObject enumerateMethodsInClass:mockedClass usingBlock:setupForwarderFiltered]; + [NSObject enumerateMethodsInClass:self.mockedClass usingBlock:setupForwarderFiltered]; } - (void)setupForwarderForSelector:(SEL)sel { SEL aliasSelector = OCMAliasForOriginalSelector(sel); - if(class_getInstanceMethod(object_getClass(realObject), aliasSelector) != NULL) + if(class_getInstanceMethod(object_getClass(self.realObject), aliasSelector) != NULL) return; - Method originalMethod = class_getInstanceMethod(mockedClass, sel); + Method originalMethod = class_getInstanceMethod(self.mockedClass, sel); /* Might be NULL if the selector is forwarded to another class */ IMP originalIMP = (originalMethod != NULL) ? method_getImplementation(originalMethod) : NULL; const char *types = (originalMethod != NULL) ? method_getTypeEncoding(originalMethod) : NULL; // TODO: check the fallback implementation is actually sufficient if(types == NULL) - types = ([[mockedClass instanceMethodSignatureForSelector:sel] fullObjCTypes]); + types = ([[self.mockedClass instanceMethodSignatureForSelector:sel] fullObjCTypes]); Class subclass = object_getClass([self realObject]); - IMP forwarderIMP = [mockedClass instanceMethodForwarderForSelector:sel]; + IMP forwarderIMP = [self.mockedClass instanceMethodForwarderForSelector:sel]; class_replaceMethod(subclass, sel, forwarderIMP, types); class_addMethod(subclass, aliasSelector, originalIMP, types); } @@ -264,7 +328,7 @@ - (NSString *)descriptionForVerificationFailureWithMatcher:(OCMInvocationMatcher { SEL matcherSel = [[matcher recordedInvocation] selector]; __block BOOL stubbingMightHelp = NO; - [NSObject enumerateMethodsInClass:mockedClass usingBlock:^(Class cls, SEL sel) { + [NSObject enumerateMethodsInClass:self.mockedClass usingBlock:^(Class cls, SEL sel) { if(sel == matcherSel) stubbingMightHelp = OCMIsAppleBaseClass(cls) || OCMIsApplePrivateMethod(cls, sel); }]; diff --git a/Source/OCMock/OCProtocolMockObject.h b/Source/OCMock/OCProtocolMockObject.h index a6bac51f..abe71813 100644 --- a/Source/OCMock/OCProtocolMockObject.h +++ b/Source/OCMock/OCProtocolMockObject.h @@ -16,10 +16,7 @@ #import "OCMockObject.h" -@interface OCProtocolMockObject : OCMockObject -{ - Protocol *mockedProtocol; -} +@interface OCProtocolMockObject : OCMockObject - (id)initWithProtocol:(Protocol *)aProtocol; diff --git a/Source/OCMock/OCProtocolMockObject.m b/Source/OCMock/OCProtocolMockObject.m index 76f44f15..85619ab9 100644 --- a/Source/OCMock/OCProtocolMockObject.m +++ b/Source/OCMock/OCProtocolMockObject.m @@ -19,6 +19,9 @@ @implementation OCProtocolMockObject +{ + Protocol *mockedProtocol; +} #pragma mark Initialisers, description, accessors, etc. @@ -27,7 +30,7 @@ - (id)initWithProtocol:(Protocol *)aProtocol if(aProtocol == nil) [NSException raise:NSInvalidArgumentException format:@"Protocol cannot be nil."]; - [super init]; + self = [super init]; mockedProtocol = aProtocol; return self; } diff --git a/Source/OCMockTests/OCMockObjectPartialMocksTests.m b/Source/OCMockTests/OCMockObjectPartialMocksTests.m index 8e15b2d5..96e0680a 100644 --- a/Source/OCMockTests/OCMockObjectPartialMocksTests.m +++ b/Source/OCMockTests/OCMockObjectPartialMocksTests.m @@ -205,6 +205,7 @@ @implementation OCTestManagedObject @interface OCPartialMockObject (AccessToInvocationsForTesting) +- (NSArray *)invocations; - (NSArray *)invocationsExcludingInitialize; @end @@ -214,10 +215,9 @@ @implementation OCPartialMockObject (AccessToInvocationsForTesting) - (NSArray *)invocationsExcludingInitialize { NSMutableArray *filteredInvocations = [[NSMutableArray alloc] init]; - for(NSInvocation *i in invocations) + for(NSInvocation *i in [self invocations]) if([NSStringFromSelector([i selector]) hasSuffix:@"initialize"] == NO) [filteredInvocations addObject:i]; - return filteredInvocations; } diff --git a/Source/OCMockTests/OCMockObjectTests.m b/Source/OCMockTests/OCMockObjectTests.m index 27543673..33c23c0f 100644 --- a/Source/OCMockTests/OCMockObjectTests.m +++ b/Source/OCMockTests/OCMockObjectTests.m @@ -16,7 +16,9 @@ #import #import "OCMock/OCMock.h" - +#import "OCMBoxedReturnValueProvider.h" +#import "OCPartialMockObject.h" +#import "OCClassMockObject.h" #pragma mark Helper classes and protocols for testing @@ -214,6 +216,23 @@ + (BOOL)supportsMocking:(NSString **)reasonPtr @end +@interface TestClassLargeClass : NSObject +{ + int foo[4096]; +} +@end + +@implementation TestClassLargeClass + +- (void)dirtyInstanceVariables:(TestClassLargeClass *)cls +{ + for(int i = 0; i < 4096; ++i) { + cls->foo[i] = i; + } +} + +@end + static NSString *TestNotification = @"TestNotification"; @@ -1157,5 +1176,33 @@ - (void)testTryingToCreateAnInstanceOfOCMockObjectRaisesAnException XCTAssertThrows([[OCMockObject alloc] init]); } +- (void)testMockObjectsHaveNoInstanceVariables +{ + XCTAssertEqual(class_getInstanceSize([NSProxy class]), class_getInstanceSize([OCMockObject class])); + XCTAssertEqual(class_getInstanceSize([NSProxy class]), class_getInstanceSize([OCPartialMockObject class])); + XCTAssertEqual(class_getInstanceSize([NSProxy class]), class_getInstanceSize([OCClassMockObject class])); +} + +- (void)testClassMockAllowsDirectMemoryAccess +{ + TestClassLargeClass *one = [[TestClassLargeClass alloc] init]; + id mockOne = OCMClassMock([TestClassLargeClass class]); + [one dirtyInstanceVariables:mockOne]; +} + +- (void)performDirectMemoryAccess +{ + @autoreleasepool { + TestClassLargeClass *one = [[TestClassLargeClass alloc] init]; + TestClassLargeClass *two = [[TestClassLargeClass alloc] init]; + id mockTwo = OCMPartialMock(two); + [one dirtyInstanceVariables:mockTwo]; + } +} + +- (void)testPartialClassMockDoesNotAllowDirectMemoryAccess +{ + XCTAssertThrowsSpecificNamed([self performDirectMemoryAccess], NSException, NSInternalInconsistencyException); +} @end