Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for unretained and unsafeunretained arguments for stubs. #419

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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;

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

- (NSString *)invocationDescription;
Expand Down
30 changes: 28 additions & 2 deletions Source/OCMock/NSInvocation+OCMAdditions.m
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#import "NSInvocation+OCMAdditions.h"
#import "NSMethodSignature+OCMAdditions.h"
#import "OCMArg.h"
#import "OCMConstraint.h"
#import "OCMFunctionsPrivate.h"

#if(TARGET_OS_OSX && (!defined(__MAC_10_10) || __MAC_OS_X_VERSION_MIN_REQUIRED < __MAC_10_10)) || \
Expand Down Expand Up @@ -53,9 +54,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
- (void)applyConstraintOptionsFromStubInvocation:(NSInvocation *)stubInvocation excludingObject:(id)objectToExclude
{
if(objc_getAssociatedObject(self, OCMRetainedObjectArgumentsKey) != nil)
{
Expand Down Expand Up @@ -111,7 +123,21 @@ - (void)retainObjectArgumentsExcludingObject:(id)objectToExclude
}
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
37 changes: 36 additions & 1 deletion Source/OCMock/OCMArg.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,36 @@

#import <Foundation/Foundation.h>

@interface OCMArg : NSObject
// 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,6 +58,15 @@
+ (id)checkWithSelector:(SEL)selector onObject:(id)anObject;
+ (id)checkWithBlock:(BOOL (^)(id obj))block;

+ (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

+ (id *)setTo:(id)value;
Expand Down
72 changes: 60 additions & 12 deletions Source/OCMock/OCMArg.m
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ @implementation OCMArg

+ (id)any
{
return [OCMAnyConstraint constraint];
return [self anyWithOptions:OCMArgDefaultOptions];
}

+ (void *)anyPointer
Expand All @@ -45,41 +45,80 @@ + (SEL)anySelector

+ (id)isNil
{
return [OCMIsNilConstraint constraint];

return [self isNilWithOptions:OCMArgDefaultOptions];
}

+ (id)isNotNil
{
return [OCMIsNotNilConstraint constraint];
return [self isNotNilWithOptions:OCMArgDefaultOptions];
}

+ (id)isEqual:(id)value
{
return value;
return [self isEqual:value options:OCMArgDefaultOptions];
}

+ (id)isNotEqual:(id)value
{
OCMIsNotEqualConstraint *constraint = [OCMIsNotEqualConstraint constraint];
constraint->testValue = value;
return constraint;
return [self isNotEqual:value options:OCMArgDefaultOptions];
}

+ (id)isKindOfClass:(Class)cls
{
return [[[OCMBlockConstraint alloc] initWithConstraintBlock:^BOOL(id obj) {
return [obj isKindOfClass:cls];
}] autorelease];
return [self isKindOfClass:cls options:OCMArgDefaultOptions];
}

+ (id)checkWithSelector:(SEL)selector onObject:(id)anObject
{
return [OCMConstraint constraintWithSelector:selector onObject:anObject];
return [self checkWithSelector:selector onObject:anObject options:OCMArgDefaultOptions];
}

+ (id)checkWithBlock:(BOOL (^)(id))block
{
return [[[OCMBlockConstraint alloc] initWithConstraintBlock:block] autorelease];
return [self checkWithOptions:OCMArgDefaultOptions withBlock:block];
}

+ (id)anyWithOptions:(OCMArgOptions)options
{
return [[[OCMAnyConstraint alloc] initWithOptions:[self constraintOptionsFromArgOptions:options]] autorelease];
}

+ (id)isNilWithOptions:(OCMArgOptions)options
{
return [[[OCMIsEqualConstraint alloc] initWithTestValue:nil options:[self constraintOptionsFromArgOptions:options]] autorelease];
}

+ (id)isNotNilWithOptions:(OCMArgOptions)options
{
return [[[OCMIsNotEqualConstraint alloc] initWithTestValue:nil options:[self constraintOptionsFromArgOptions:options]] autorelease];
}

+ (id)isEqual:(id)value options:(OCMArgOptions)options
{
return [[[OCMIsEqualConstraint alloc] initWithTestValue:value options:[self constraintOptionsFromArgOptions:options]] autorelease];
}

+ (id)isNotEqual:(id)value options:(OCMArgOptions)options
{
return [[[OCMIsNotEqualConstraint alloc] initWithTestValue:value options:[self constraintOptionsFromArgOptions:options]] autorelease];
}

+ (id)isKindOfClass:(Class)cls options:(OCMArgOptions)options
{
return [[[OCMBlockConstraint alloc] initWithOptions:[self constraintOptionsFromArgOptions:options] block:^BOOL(id obj) {
return [obj isKindOfClass:cls];
}] autorelease];
}

+ (id)checkWithSelector:(SEL)selector onObject:(id)anObject options:(OCMArgOptions)options
{
return [OCMConstraint constraintWithSelector:selector onObject:anObject options:[self constraintOptionsFromArgOptions:options]];
}

+ (id)checkWithOptions:(OCMArgOptions)options withBlock:(BOOL (^)(id obj))block
{
return [[[OCMBlockConstraint alloc] initWithOptions:[self constraintOptionsFromArgOptions:options] block:block] autorelease];
}

+ (id *)setTo:(id)value
Expand Down Expand Up @@ -142,4 +181,13 @@ + (id)resolveSpecialValues:(NSValue *)value
return value;
}

+ (OCMConstraintOptions)constraintOptionsFromArgOptions:(OCMArgOptions)argOptions
{
OCMConstraintOptions constraintOptions = 0;
if(argOptions & OCMArgDoNotRetainStubArg) constraintOptions |= OCMConstraintDoNotRetainStubArg;
if(argOptions & OCMArgDoNotRetainInvocationArg) constraintOptions |= OCMConstraintDoNotRetainInvocationArg;
if(argOptions & OCMArgCopyInvocationArg) constraintOptions |= OCMConstraintCopyInvocationArg;
return constraintOptions;
}

@end
36 changes: 31 additions & 5 deletions Source/OCMock/OCMConstraint.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,22 @@

#import <Foundation/Foundation.h>

// See OCMArgOptions for documentation on options.
typedef NS_OPTIONS(NSUInteger, OCMConstraintOptions) {
OCMConstraintDefaultOptions = 0UL,
OCMConstraintDoNotRetainStubArg = (1UL << 0),
OCMConstraintDoNotRetainInvocationArg = (1UL << 1),
OCMConstraintCopyInvocationArg = (1UL << 2),
OCMConstraintNeverRetainArg = OCMConstraintDoNotRetainStubArg | OCMConstraintDoNotRetainInvocationArg,
};

@interface OCMConstraint : NSObject

+ (instancetype)constraint;
@property (readonly) OCMConstraintOptions constraintOptions;

- (instancetype)initWithOptions:(OCMConstraintOptions)options NS_DESIGNATED_INITIALIZER;
- (instancetype)init NS_UNAVAILABLE;

- (BOOL)evaluate:(id)value;

// if you are looking for any, isNil, etc, they have moved to OCMArg
Expand All @@ -28,6 +41,8 @@
+ (instancetype)constraintWithSelector:(SEL)aSelector onObject:(id)anObject;
+ (instancetype)constraintWithSelector:(SEL)aSelector onObject:(id)anObject withValue:(id)aValue;

+ (instancetype)constraintWithSelector:(SEL)aSelector onObject:(id)anObject options:(OCMConstraintOptions)options;
+ (instancetype)constraintWithSelector:(SEL)aSelector onObject:(id)anObject withValue:(id)aValue options:(OCMConstraintOptions)options;

@end

Expand All @@ -40,28 +55,39 @@
@interface OCMIsNotNilConstraint : OCMConstraint
@end

@interface OCMIsNotEqualConstraint : OCMConstraint
@interface OCMEqualityConstraint : OCMConstraint
{
@public
id testValue;
}

- (instancetype)initWithTestValue:(id)testValue options:(OCMConstraintOptions)options NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithOptions:(OCMConstraintOptions)options NS_UNAVAILABLE;

@end

@interface OCMIsEqualConstraint : OCMEqualityConstraint
@end

@interface OCMIsNotEqualConstraint : OCMEqualityConstraint
@end

@interface OCMInvocationConstraint : OCMConstraint
{
@public
NSInvocation *invocation;
}

- (instancetype)initWithInvocation:(NSInvocation *)invocation options:(OCMConstraintOptions)options NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithOptions:(OCMConstraintOptions)options NS_UNAVAILABLE;

@end

@interface OCMBlockConstraint : OCMConstraint
{
BOOL (^block)(id);
}

- (instancetype)initWithConstraintBlock:(BOOL (^)(id))block;
- (instancetype)initWithOptions:(OCMConstraintOptions)options block:(BOOL (^)(id))block NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithOptions:(OCMConstraintOptions)options NS_UNAVAILABLE;

@end

Expand Down