Skip to content

Commit

Permalink
Allow injection of macro state class into OCM macros
Browse files Browse the repository at this point in the history
We have a need to inject a replacement for `OCMMacroState` for when we are doing cross
process testing using a library like https://github.com/google/EarlGrey/tree/earlgrey2.

This simple expansion to the basic macros will give us the flexibility we need to allow
us to use OCMock v3 syntax instead of falling back to OCMock v2 syntax when working
with EarlGrey tests.

The modification to andReturn macro allows us to move objects across the process boundary
instead of "hiding" an object instead of an NSValue. The "hack" of using NSArray as a
tuple could be replaced by a real object, or an NSDictionary, but I went with the simple
stupid approach to start.
  • Loading branch information
dmaclach committed Jan 27, 2021
1 parent 6358799 commit af39699
Show file tree
Hide file tree
Showing 3 changed files with 49 additions and 44 deletions.
15 changes: 7 additions & 8 deletions Source/OCMock/OCMStubRecorder.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,14 @@

@interface OCMStubRecorder (Properties)

#define andReturn(aValue) _andReturn(({ \
__typeof__(aValue) _val = (aValue); \
NSValue *_nsval = [NSValue value:&_val withObjCType:@encode(__typeof__(_val))]; \
if (OCMIsObjectType(@encode(__typeof(_val)))) { \
objc_setAssociatedObject(_nsval, "OCMAssociatedBoxedValue", *(__unsafe_unretained id *) (void *) &_val, OBJC_ASSOCIATION_RETAIN); \
} \
_nsval; \
#define andReturn(aValue) _andReturn(({ \
__typeof__(aValue) _val = (aValue); \
const char *_encoding = @encode(__typeof(_val)); \
BOOL _boxed = !OCMIsObjectType(_encoding); \
id _retVal = _boxed ? [NSValue value:&_val withObjCType:_encoding] : *(const id*)(void *)(&_val); \
@[_retVal, @(_boxed)]; \
}))
@property (nonatomic, readonly) OCMStubRecorder *(^ _andReturn)(NSValue *);
@property (nonatomic, readonly) OCMStubRecorder *(^ _andReturn)(NSArray<id> *);

#define andThrow(anException) _andThrow(anException)
@property (nonatomic, readonly) OCMStubRecorder *(^ _andThrow)(NSException *);
Expand Down
21 changes: 7 additions & 14 deletions Source/OCMock/OCMStubRecorder.m
Original file line number Diff line number Diff line change
Expand Up @@ -126,21 +126,14 @@ @implementation OCMStubRecorder (Properties)

@dynamic _andReturn;

- (OCMStubRecorder * (^)(NSValue *))_andReturn
{
id (^theBlock)(id) = ^(NSValue *aValue) {
if(OCMIsObjectType([aValue objCType]))
{
id objValue = nil;
[aValue getValue:&objValue]; // TODO: deprecated but replacement available in 10.13 only
return [self andReturn:objValue];
}
else
{
return [self andReturnValue:aValue];
}
- (OCMStubRecorder *(^)(NSArray<id> *))_andReturn
{
id (^theBlock)(NSArray<id> *) = ^ (NSArray<id> *aTuple)
{
id value = aTuple[0];
return [aTuple[1] boolValue] ? [self andReturnValue:value] : [self andReturn:value];
};
return (id)[[theBlock copy] autorelease];
return [[theBlock copy] autorelease];
}


Expand Down
57 changes: 35 additions & 22 deletions Source/OCMock/OCMockMacros.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,68 +34,76 @@
#define OCMObserverMock() [OCMockObject observerMock]


#define OCMStub(invocation) \
#define OCMStubWithStateClass(MacroStateClass, invocation) \
({ \
_OCMSilenceWarnings( \
[OCMMacroState beginStubMacro]; \
[MacroStateClass beginStubMacro]; \
OCMStubRecorder *recorder = nil; \
@try{ \
invocation; \
}@catch(...){ \
[[OCMMacroState globalState] setInvocationDidThrow:YES]; \
[[MacroStateClass globalState] setInvocationDidThrow:YES]; \
/* NOLINTNEXTLINE(google-objc-avoid-throwing-exception) */ \
@throw; \
}@finally{ \
recorder = [OCMMacroState endStubMacro]; \
recorder = [MacroStateClass endStubMacro]; \
} \
recorder; \
); \
})

#define OCMExpect(invocation) \
#define OCMStub(invocation) OCMStubWithStateClass(OCMMacroState, invocation)

#define OCMExpectWithStateClass(MacroStateClass, invocation) \
({ \
_OCMSilenceWarnings( \
[OCMMacroState beginExpectMacro]; \
[MacroStateClass beginExpectMacro]; \
OCMStubRecorder *recorder = nil; \
@try{ \
invocation; \
}@catch(...){ \
[[OCMMacroState globalState] setInvocationDidThrow:YES]; \
[[MacroStateClass globalState] setInvocationDidThrow:YES]; \
/* NOLINTNEXTLINE(google-objc-avoid-throwing-exception) */ \
@throw; \
}@finally{ \
recorder = [OCMMacroState endExpectMacro]; \
recorder = [MacroStateClass endExpectMacro]; \
} \
recorder; \
); \
})

#define OCMReject(invocation) \
#define OCMExpect(invocation) OCMExpectWithStateClass(OCMMacroState, invocation)

#define OCMRejectWithStateClass(MacroStateClass, invocation) \
({ \
_OCMSilenceWarnings( \
[OCMMacroState beginRejectMacro]; \
[MacroStateClass beginRejectMacro]; \
OCMStubRecorder *recorder = nil; \
@try{ \
invocation; \
}@catch(...){ \
[[OCMMacroState globalState] setInvocationDidThrow:YES]; \
[[MacroStateClass globalState] setInvocationDidThrow:YES]; \
/* NOLINTNEXTLINE(google-objc-avoid-throwing-exception) */ \
@throw; \
}@finally{ \
recorder = [OCMMacroState endRejectMacro]; \
recorder = [MacroStateClass endRejectMacro]; \
} \
recorder; \
); \
})

#define OCMReject(invocation) OCMRejectWithStateClass(OCMMacroState, invocation)


#define OCMClassMethod(invocation) \

#define OCMClassMethodWithStateClass(MacroStateClass, invocation) \
_OCMSilenceWarnings( \
[[OCMMacroState globalState] switchToClassMethod]; \
[[MacroStateClass globalState] switchToClassMethod]; \
invocation; \
);

#define OCMClassMethod(invocation) OCMClassMethodWithStateClass(OCMMacroState, invocation)


#ifndef OCM_DISABLE_SHORT_SYNTAX
#define ClassMethod(invocation) OCMClassMethod(invocation)
Expand All @@ -106,38 +114,43 @@

#define OCMVerifyAllWithDelay(mock, delay) [(OCMockObject *)mock verifyWithDelay:delay atLocation:OCMMakeLocation(self, __FILE__, __LINE__)]

#define _OCMVerify(invocation) \
#define _OCMVerifyWithStateClass(MacroStateClass, invocation) \
({ \
_OCMSilenceWarnings( \
[OCMMacroState beginVerifyMacroAtLocation:OCMMakeLocation(self, __FILE__, __LINE__)]; \
[MacroStateClass beginVerifyMacroAtLocation:OCMMakeLocation(self, __FILE__, __LINE__)]; \
@try{ \
invocation; \
}@catch(...){ \
[[OCMMacroState globalState] setInvocationDidThrow:YES]; \
[[MacroStateClass globalState] setInvocationDidThrow:YES]; \
/* NOLINTNEXTLINE(google-objc-avoid-throwing-exception) */ \
@throw; \
}@finally{ \
[OCMMacroState endVerifyMacro]; \
[MacroStateClass endVerifyMacro]; \
} \
); \
})

#define _OCMVerifyWithQuantifier(quantifier, invocation) \
#define _OCMVerify(invocation) _OCMVerifyWithStateClass(OCMMacroState, invocation)


#define _OCMVerifyWithQuantifierAndStateClass(MacroStateClass, quantifier, invocation) \
({ \
_OCMSilenceWarnings( \
[OCMMacroState beginVerifyMacroAtLocation:OCMMakeLocation(self, __FILE__, __LINE__) withQuantifier:quantifier]; \
[MacroStateClass beginVerifyMacroAtLocation:OCMMakeLocation(self, __FILE__, __LINE__) withQuantifier:quantifier]; \
@try{ \
invocation; \
}@catch(...){ \
[[OCMMacroState globalState] setInvocationDidThrow:YES]; \
[[MacroStateClass globalState] setInvocationDidThrow:YES]; \
/* NOLINTNEXTLINE(google-objc-avoid-throwing-exception) */ \
@throw; \
}@finally{ \
[OCMMacroState endVerifyMacro]; \
[MacroStateClass endVerifyMacro]; \
} \
); \
})

#define _OCMVerifyWithQuantifier(quantifier, invocation) _OCMVerifyWithQuantifierAndStateClass(OCMMacroState, quantifier, invocation)

// explanation for macros below here: https://stackoverflow.com/questions/3046889/optional-parameters-with-c-macros

#define _OCMVerify_1(A) _OCMVerify(A)
Expand Down

0 comments on commit af39699

Please sign in to comment.