Skip to content

Commit

Permalink
Add support for controlling retain/copy semantics for arguments to st…
Browse files Browse the repository at this point in the history
…ubs.

Allows marking an argument in a stub as having various semantics:
  - is not retained by invocations
    Object arguments are retained by default in OCMock. In some cases to avoid retain
    loops you need to mark an argument as unretained.
  - is not retained by stub
    Stub arguments are retained by default in OCMock. In some specialized cases you
    do not want the stub arguments retained
  - is copied by invocation
    Some arguments have copy semantics and we need the invocation to copy the argument
    instead of retain it.
  • Loading branch information
dmaclach committed Jun 27, 2020
1 parent a41d9df commit 7156c9e
Show file tree
Hide file tree
Showing 16 changed files with 309 additions and 343 deletions.
24 changes: 0 additions & 24 deletions Source/OCMock.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -281,16 +281,6 @@
817EB1661BD7674D0047E85A /* OCMFunctionsPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = 03F370CA1BAA1DE800CAD3E8 /* OCMFunctionsPrivate.h */; };
8BF73E53246CA75E00B9A52C /* OCMNoEscapeBlockTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8BF73E52246CA75E00B9A52C /* OCMNoEscapeBlockTests.m */; settings = {COMPILER_FLAGS = "-Xclang -fexperimental-optimized-noescape"; }; };
8BF73E54246CA75E00B9A52C /* OCMNoEscapeBlockTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8BF73E52246CA75E00B9A52C /* OCMNoEscapeBlockTests.m */; settings = {COMPILER_FLAGS = "-Xclang -fexperimental-optimized-noescape"; }; };
8BF740142476E4B400B9A52C /* OCMUnretainedArgument.h in Headers */ = {isa = PBXBuildFile; fileRef = 8BF740122476E4B300B9A52C /* OCMUnretainedArgument.h */; };
8BF740152476E4B400B9A52C /* OCMUnretainedArgument.m in Sources */ = {isa = PBXBuildFile; fileRef = 8BF740132476E4B400B9A52C /* OCMUnretainedArgument.m */; };
8BF740162476E59A00B9A52C /* OCMUnretainedArgument.m in Sources */ = {isa = PBXBuildFile; fileRef = 8BF740132476E4B400B9A52C /* OCMUnretainedArgument.m */; };
8BF740172476E59A00B9A52C /* OCMUnretainedArgument.m in Sources */ = {isa = PBXBuildFile; fileRef = 8BF740132476E4B400B9A52C /* OCMUnretainedArgument.m */; };
8BF740182476E59B00B9A52C /* OCMUnretainedArgument.m in Sources */ = {isa = PBXBuildFile; fileRef = 8BF740132476E4B400B9A52C /* OCMUnretainedArgument.m */; };
8BF740192476E59C00B9A52C /* OCMUnretainedArgument.m in Sources */ = {isa = PBXBuildFile; fileRef = 8BF740132476E4B400B9A52C /* OCMUnretainedArgument.m */; };
8BF7401A24771FD600B9A52C /* OCMUnretainedArgument.h in Headers */ = {isa = PBXBuildFile; fileRef = 8BF740122476E4B300B9A52C /* OCMUnretainedArgument.h */; };
8BF7401B24771FD700B9A52C /* OCMUnretainedArgument.h in Headers */ = {isa = PBXBuildFile; fileRef = 8BF740122476E4B300B9A52C /* OCMUnretainedArgument.h */; };
8BF7401C24771FD700B9A52C /* OCMUnretainedArgument.h in Headers */ = {isa = PBXBuildFile; fileRef = 8BF740122476E4B300B9A52C /* OCMUnretainedArgument.h */; };
8BF7401D24771FD800B9A52C /* OCMUnretainedArgument.h in Headers */ = {isa = PBXBuildFile; fileRef = 8BF740122476E4B300B9A52C /* OCMUnretainedArgument.h */; };
8DE97C5522B43EE60098C63F /* OCMockObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 03B3159E146333BF0052CD09 /* OCMockObject.m */; };
8DE97C5622B43EE60098C63F /* OCClassMockObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 03B3158C146333BF0052CD09 /* OCClassMockObject.m */; };
8DE97C5722B43EE60098C63F /* OCPartialMockObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 03B315AA146333BF0052CD09 /* OCPartialMockObject.m */; };
Expand Down Expand Up @@ -582,8 +572,6 @@
3CFBDD761BB3DB200050D9C5 /* TestClassWithCustomReferenceCounting.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TestClassWithCustomReferenceCounting.m; sourceTree = "<group>"; };
817EB1621BD765130047E85A /* OCMock.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = OCMock.framework; sourceTree = BUILT_PRODUCTS_DIR; };
8BF73E52246CA75E00B9A52C /* OCMNoEscapeBlockTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OCMNoEscapeBlockTests.m; sourceTree = "<group>"; };
8BF740122476E4B300B9A52C /* OCMUnretainedArgument.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OCMUnretainedArgument.h; sourceTree = "<group>"; };
8BF740132476E4B400B9A52C /* OCMUnretainedArgument.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OCMUnretainedArgument.m; sourceTree = "<group>"; };
8DE97CA022B43EE60098C63F /* OCMock.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = OCMock.framework; sourceTree = BUILT_PRODUCTS_DIR; };
A02926811CA0725A00594AAF /* TestObjects.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = TestObjects.xcdatamodel; sourceTree = "<group>"; };
D31108AD1828DB8700737925 /* OCMockLibTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = OCMockLibTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
Expand Down Expand Up @@ -883,8 +871,6 @@
03B315A2146333BF0052CD09 /* OCMPassByRefSetter.m */,
2FA2891034E7B73AA3511D17 /* OCMBlockArgCaller.h */,
2FA283D58AA7569D8A5B0C57 /* OCMBlockArgCaller.m */,
8BF740122476E4B300B9A52C /* OCMUnretainedArgument.h */,
8BF740132476E4B400B9A52C /* OCMUnretainedArgument.m */,
);
name = "Argument Constraints and Actions";
sourceTree = "<group>";
Expand Down Expand Up @@ -975,7 +961,6 @@
03B315F5146333C00052CD09 /* OCMPassByRefSetter.h in Headers */,
03B315FA146333C00052CD09 /* OCMRealObjectForwarder.h in Headers */,
03B315FF146333C00052CD09 /* OCMObjectReturnValueProvider.h in Headers */,
8BF740142476E4B400B9A52C /* OCMUnretainedArgument.h in Headers */,
03B31604146333C00052CD09 /* OCObserverMockObject.h in Headers */,
03B31609146333C00052CD09 /* OCPartialMockObject.h in Headers */,
0368656D1D357317005E6BEE /* OCMQuantifier.h in Headers */,
Expand Down Expand Up @@ -1025,7 +1010,6 @@
817EB1661BD7674D0047E85A /* OCMFunctionsPrivate.h in Headers */,
03B31605146333C00052CD09 /* OCObserverMockObject.h in Headers */,
03B3160A146333C00052CD09 /* OCPartialMockObject.h in Headers */,
8BF7401A24771FD600B9A52C /* OCMUnretainedArgument.h in Headers */,
03B31614146333C00052CD09 /* OCProtocolMockObject.h in Headers */,
2FA28E1EB6B8536785258DF5 /* OCMInvocationMatcher.h in Headers */,
0322DA6A19118B4600CACAF1 /* OCMVerifier.h in Headers */,
Expand Down Expand Up @@ -1077,7 +1061,6 @@
817EB1591BD765130047E85A /* NSObject+OCMAdditions.h in Headers */,
817EB15A1BD765130047E85A /* NSValue+OCMAdditions.h in Headers */,
817EB15B1BD765130047E85A /* OCMFunctions.h in Headers */,
8BF7401C24771FD700B9A52C /* OCMUnretainedArgument.h in Headers */,
817EB15C1BD765130047E85A /* OCMBlockArgCaller.h in Headers */,
817EB15D1BD765130047E85A /* OCMArgAction.h in Headers */,
2FA28806443827E286F12F6F /* OCMNonRetainingObjectReturnValueProvider.h in Headers */,
Expand Down Expand Up @@ -1110,7 +1093,6 @@
8DE97C8C22B43EE60098C63F /* OCMBoxedReturnValueProvider.h in Headers */,
8DE97C8D22B43EE60098C63F /* OCMExceptionReturnValueProvider.h in Headers */,
8DE97C8E22B43EE60098C63F /* OCMIndirectReturnValueProvider.h in Headers */,
8BF7401D24771FD800B9A52C /* OCMUnretainedArgument.h in Headers */,
8DE97C8F22B43EE60098C63F /* OCMNotificationPoster.h in Headers */,
8DE97C9022B43EE60098C63F /* OCMObjectReturnValueProvider.h in Headers */,
8DE97C9122B43EE60098C63F /* OCMFunctionsPrivate.h in Headers */,
Expand Down Expand Up @@ -1168,7 +1150,6 @@
F0B951481B00810C00942C38 /* NSObject+OCMAdditions.h in Headers */,
F0B951491B00810C00942C38 /* NSValue+OCMAdditions.h in Headers */,
F0B9514A1B00810C00942C38 /* OCMFunctions.h in Headers */,
8BF7401B24771FD700B9A52C /* OCMUnretainedArgument.h in Headers */,
2FA28B7BDB3319A499E90525 /* OCMBlockArgCaller.h in Headers */,
2FA280E60213BA09F007C173 /* OCMArgAction.h in Headers */,
2FA28AFBD67EAB9DD1F23BF5 /* OCMNonRetainingObjectReturnValueProvider.h in Headers */,
Expand Down Expand Up @@ -1432,7 +1413,6 @@
03B315CA146333BF0052CD09 /* OCMBlockCaller.m in Sources */,
036865681D3572ED005E6BEE /* OCMQuantifier.m in Sources */,
03B315CF146333BF0052CD09 /* OCMBoxedReturnValueProvider.m in Sources */,
8BF740152476E4B400B9A52C /* OCMUnretainedArgument.m in Sources */,
03B315D4146333BF0052CD09 /* OCMConstraint.m in Sources */,
03B315D9146333BF0052CD09 /* OCMExceptionReturnValueProvider.m in Sources */,
03B315DE146333BF0052CD09 /* OCMIndirectReturnValueProvider.m in Sources */,
Expand Down Expand Up @@ -1475,7 +1455,6 @@
03B315CC146333BF0052CD09 /* OCMBlockCaller.m in Sources */,
036865691D3572ED005E6BEE /* OCMQuantifier.m in Sources */,
03B315D1146333BF0052CD09 /* OCMBoxedReturnValueProvider.m in Sources */,
8BF740162476E59A00B9A52C /* OCMUnretainedArgument.m in Sources */,
03DCED6D183406BC0059089E /* NSObject+OCMAdditions.m in Sources */,
03B315D6146333BF0052CD09 /* OCMConstraint.m in Sources */,
03B315DB146333BF0052CD09 /* OCMExceptionReturnValueProvider.m in Sources */,
Expand Down Expand Up @@ -1547,7 +1526,6 @@
817EB11F1BD765130047E85A /* OCMInvocationMatcher.m in Sources */,
0368656B1D3572ED005E6BEE /* OCMQuantifier.m in Sources */,
817EB1201BD765130047E85A /* OCMInvocationStub.m in Sources */,
8BF740182476E59B00B9A52C /* OCMUnretainedArgument.m in Sources */,
817EB1211BD765130047E85A /* OCMInvocationExpectation.m in Sources */,
817EB1221BD765130047E85A /* OCMRealObjectForwarder.m in Sources */,
817EB1231BD765130047E85A /* OCMBlockCaller.m in Sources */,
Expand Down Expand Up @@ -1590,7 +1568,6 @@
8DE97C5C22B43EE60098C63F /* OCMVerifier.m in Sources */,
8DE97C5D22B43EE60098C63F /* OCMInvocationMatcher.m in Sources */,
8DE97C5E22B43EE60098C63F /* OCMInvocationStub.m in Sources */,
8BF740192476E59C00B9A52C /* OCMUnretainedArgument.m in Sources */,
8DE97C5F22B43EE60098C63F /* OCMInvocationExpectation.m in Sources */,
8DE97C6022B43EE60098C63F /* OCMRealObjectForwarder.m in Sources */,
8DE97C6122B43EE60098C63F /* OCMBlockCaller.m in Sources */,
Expand Down Expand Up @@ -1662,7 +1639,6 @@
F0B951141B0080EC00942C38 /* OCMInvocationMatcher.m in Sources */,
0368656A1D3572ED005E6BEE /* OCMQuantifier.m in Sources */,
F0B951151B0080EC00942C38 /* OCMInvocationStub.m in Sources */,
8BF740172476E59A00B9A52C /* OCMUnretainedArgument.m in Sources */,
F0B951161B0080EC00942C38 /* OCMInvocationExpectation.m in Sources */,
F0B951171B0080EC00942C38 /* OCMRealObjectForwarder.m in Sources */,
F0B951181B0080EC00942C38 /* OCMBlockCaller.m in Sources */,
Expand Down
3 changes: 1 addition & 2 deletions Source/OCMock/NSInvocation+OCMAdditions.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,7 @@

+ (NSInvocation *)invocationForBlock:(id)block withArguments:(NSArray *)arguments;

- (void)retainObjectArgumentsExcludingObject:(id)objectToExclude excludingObjectsAtIndexes:(NSIndexSet *)indexes;

- (void)applyConstraintOptionsFromStubInvocation:(NSInvocation *)stubInvocation excludingObject:(id)objectToExclude;
- (id)getArgumentAtIndexAsObject:(NSInteger)argIndex;

- (NSString *)invocationDescription;
Expand Down
40 changes: 28 additions & 12 deletions Source/OCMock/NSInvocation+OCMAdditions.m
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,20 @@ + (NSInvocation *)invocationForBlock:(id)block withArguments:(NSArray *)argument
}


- (OCMConstraintOptions)getArgumentContraintOptionsForArgumentAtIndex:(NSUInteger)index
{
id argument;
[self getArgument:&argument atIndex:index];
if(![argument isProxy] && [argument isKindOfClass:[OCMConstraint class]])
{
return [(OCMConstraint *)argument constraintOptions];
}
return OCMConstraintDefaultOptions;
}

static NSString *const OCMRetainedObjectArgumentsKey = @"OCMRetainedObjectArgumentsKey";

- (void)retainObjectArgumentsExcludingObject:(id)objectToExclude excludingObjectsAtIndexes:(NSIndexSet *)indexes;
- (void)applyConstraintOptionsFromStubInvocation:(NSInvocation *)stubInvocation excludingObject:(id)objectToExclude
{
if(objc_getAssociatedObject(self, OCMRetainedObjectArgumentsKey) != nil)
{
Expand All @@ -80,16 +91,7 @@ - (void)retainObjectArgumentsExcludingObject:(id)objectToExclude excludingObject
for(NSUInteger index = 2; index < numberOfArguments; index++)
{
const char *argumentType = [[self methodSignature] getArgumentTypeAtIndex:index];
BOOL isObjectType = OCMIsObjectType(argumentType);
if ([indexes containsIndex:index])
{
if (!isObjectType)
{
[NSException raise:NSInternalInconsistencyException format:@"Argument at %d is not an object", (int)index];
}
continue;
}
if (isObjectType)
if (OCMIsObjectType(argumentType))
{
id argument;
[self getArgument:&argument atIndex:index];
Expand Down Expand Up @@ -118,7 +120,21 @@ - (void)retainObjectArgumentsExcludingObject:(id)objectToExclude excludingObject
}
else
{
[retainedArguments addObject:argument];
// Conform to the constraintOptions in the stub (if any).
OCMConstraintOptions constraintOptions = [stubInvocation getArgumentContraintOptionsForArgumentAtIndex:index];
if((constraintOptions & OCMConstraintCopyInvocationArg))
{
// Copy not only retains the copy in our array
// but updates the arg in the invocation that we store.
id argCopy = [argument copy];
[retainedArguments addObject:argCopy];
[self setArgument:&argCopy atIndex:index];
[argCopy release];
}
else if(!(constraintOptions & OCMConstraintDoNotRetainInvocationArg))
{
[retainedArguments addObject:argument];
}
}
}
}
Expand Down
58 changes: 35 additions & 23 deletions Source/OCMock/OCMArg.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,37 @@

#import <Foundation/Foundation.h>

// Options for controlling how OCMArgs function.
typedef NS_OPTIONS(NSUInteger, OCMArgOptions) {
// The OCMArg will retain/release the value passed to it, and invocations on a stub that has
// arguments that the OCMArg is constraining will retain the values passed to them for the
// arguments being constrained by the OCMArg.
OCMArgDefaultOptions = 0UL,

// The OCMArg will not retain/release the value passed to it. Is only applicable for
// `isEqual:options:` and `isNotEqual:options`. The caller is responsible for making sure that the
// arg is valid for the required lifetime. Note that unless `OCMArgDoNotRetainInvocationArg` is
// also specified, invocations of the stub that the OCMArg arg is constraining will retain values
// passed to them for the arguments being constrained by the OCMArg. `OCMArgNeverRetainArg` is
// usually what you want to use.
OCMArgDoNotRetainStubArg = (1UL << 0),

// Invocations on a stub that has arguments that the OCMArg is constraining will retain/release
// the values passed to them for the arguments being constrained by the OCMArg.
OCMArgDoNotRetainInvocationArg = (1UL << 1),

// Invocations on a stub that has arguments that the OCMArg is constraining will copy/release
// the values passed to them for the arguments being constrained by the OCMArg.
OCMArgCopyInvocationArg = (1UL << 2),

OCMArgNeverRetainArg = OCMArgDoNotRetainStubArg | OCMArgDoNotRetainInvocationArg,
};

@interface OCMArg : NSObject

// constraining arguments

// constrain using OCMArgDefaultOptions
+ (id)any;
+ (SEL)anySelector;
+ (void *)anyPointer;
Expand All @@ -32,21 +59,14 @@
+ (id)checkWithSelector:(SEL)selector onObject:(id)anObject;
+ (id)checkWithBlock:(BOOL (^)(id obj))block;

// Unretained object arguments are not retained by invocations on the mock, but are retained by the
// stub itself. A use case for this is when you are stubbing an argument to a method that does not
// retain its argument using an `OCMArg` variant that you do not want to keep a reference to.
// See `OCMOCK_ANY_UNRETAINED`.
+ (id)unretainedObject:(id)anObject;

// Unsafe unretained object arguments are not retained by invocations on the mock or by the stub.
// A potential use case for this is when you are stubbing methods that do not retain their
// arguments and you want to verify dealloc conditions. An example of this would be verifying
// KVO registration/deregistration that occurs in the init/dealloc of an object. If the object were
// retained by the mocking system in any way you would never see the deregistration.
// Note that you *must* keep a reference to anObject outside this call or you will crash.
// Something like `[OCMArg unsafeUnretainedObject:[[Foo alloc] init]]` under ARC is a guaranteed
// dangling pointer problem.
+ (id)unsafeUnretainedObject:(id)anObject;
+ (id)anyWithOptions:(OCMArgOptions)options;
+ (id)isNilWithOptions:(OCMArgOptions)options;
+ (id)isNotNilWithOptions:(OCMArgOptions)options;
+ (id)isEqual:(id)value options:(OCMArgOptions)options;
+ (id)isNotEqual:(id)value options:(OCMArgOptions)options;
+ (id)isKindOfClass:(Class)cls options:(OCMArgOptions)options;
+ (id)checkWithSelector:(SEL)selector onObject:(id)anObject options:(OCMArgOptions)options;
+ (id)checkWithOptions:(OCMArgOptions)options withBlock:(BOOL (^)(id obj))block;

// manipulating arguments

Expand All @@ -61,18 +81,10 @@

+ (id)resolveSpecialValues:(NSValue *)value;

// Return YES if `object` is either an unretained or an unsafe unretained object.
+ (BOOL)isUnretained:(id)object;

@end

#define OCMOCK_ANY [OCMArg any]

// See comments on [OCMArg unretainedObject] and [OCMArg unsafeUnretainedObject].
#define OCMOCK_UNSAFE_UNRETAINED(x) [OCMArg unsafeUnretainedObject:(x)]
#define OCMOCK_UNRETAINED(x) [OCMArg unretainedObject:(x)]
#define OCMOCK_ANY_UNRETAINED OCMOCK_UNRETAINED(OCMOCK_ANY)

#if defined(__GNUC__) && !defined(__STRICT_ANSI__)
#define OCMOCK_VALUE(variable) \
({ __typeof__(variable) __v = (variable); [NSValue value:&__v withObjCType:@encode(__typeof__(__v))]; })
Expand Down
Loading

0 comments on commit 7156c9e

Please sign in to comment.