Skip to content

Commit 861bfc8

Browse files
committed
Allow injection of macro state class into OCM macros
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.
1 parent 6358799 commit 861bfc8

File tree

3 files changed

+68
-44
lines changed

3 files changed

+68
-44
lines changed

Source/OCMock/OCMStubRecorder.h

+26-8
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#import <OCMock/OCMRecorder.h>
1919

2020
#import <objc/runtime.h>
21+
#import <string.h>
2122

2223
#if !TARGET_OS_WATCH
2324
@class XCTestExpectation;
@@ -42,15 +43,32 @@
4243

4344
@interface OCMStubRecorder (Properties)
4445

45-
#define andReturn(aValue) _andReturn(({ \
46-
__typeof__(aValue) _val = (aValue); \
47-
NSValue *_nsval = [NSValue value:&_val withObjCType:@encode(__typeof__(_val))]; \
48-
if (OCMIsObjectType(@encode(__typeof(_val)))) { \
49-
objc_setAssociatedObject(_nsval, "OCMAssociatedBoxedValue", *(__unsafe_unretained id *) (void *) &_val, OBJC_ASSOCIATION_RETAIN); \
50-
} \
51-
_nsval; \
46+
// Used to autoselect `return` vs `returnValue` based on type.
47+
// Gets complicated because NSValue cannot be NSCoded if it contains a type
48+
// of void*, and typeof(nil) is void* in ObjC (but not ObjC++) code.
49+
// Also we want to avoid undefined behaviours by casting _val into an id
50+
// if it isn't a pointer type.
51+
#define andReturn(aValue) _andReturn(({ \
52+
__typeof__(aValue) _val = (aValue); \
53+
const char *_encoding = @encode(__typeof(aValue)); \
54+
const void *_nilPtr = nil; \
55+
BOOL _objectOrNil = OCMIsObjectType(_encoding) || \
56+
(strcmp(_encoding, @encode(void *)) == 0 && \
57+
memcmp((void*)&_val, &_nilPtr, sizeof(void*)) == 0); \
58+
id _retVal; \
59+
if(_objectOrNil) \
60+
{ \
61+
__unsafe_unretained id _unsafeId; \
62+
memcpy(&_unsafeId, (void*)&_val, sizeof(id)); \
63+
_retVal = _unsafeId; \
64+
} \
65+
else \
66+
{ \
67+
_retVal = [NSValue valueWithBytes:&_val objCType:_encoding]; \
68+
} \
69+
[NSArray arrayWithObjects:@(_objectOrNil), _retVal, nil]; \
5270
}))
53-
@property (nonatomic, readonly) OCMStubRecorder *(^ _andReturn)(NSValue *);
71+
@property (nonatomic, readonly) OCMStubRecorder *(^ _andReturn)(NSArray<id> *);
5472

5573
#define andThrow(anException) _andThrow(anException)
5674
@property (nonatomic, readonly) OCMStubRecorder *(^ _andThrow)(NSException *);

Source/OCMock/OCMStubRecorder.m

+7-14
Original file line numberDiff line numberDiff line change
@@ -126,21 +126,14 @@ @implementation OCMStubRecorder (Properties)
126126

127127
@dynamic _andReturn;
128128

129-
- (OCMStubRecorder * (^)(NSValue *))_andReturn
130-
{
131-
id (^theBlock)(id) = ^(NSValue *aValue) {
132-
if(OCMIsObjectType([aValue objCType]))
133-
{
134-
id objValue = nil;
135-
[aValue getValue:&objValue]; // TODO: deprecated but replacement available in 10.13 only
136-
return [self andReturn:objValue];
137-
}
138-
else
139-
{
140-
return [self andReturnValue:aValue];
141-
}
129+
- (OCMStubRecorder *(^)(NSArray<id> *))_andReturn
130+
{
131+
id (^theBlock)(NSArray<id> *) = ^ (NSArray<id> *aTuple)
132+
{
133+
id value = (aTuple.count == 1) ? nil : aTuple[1];
134+
return [aTuple[0] boolValue] ? [self andReturn:value] : [self andReturnValue:value];
142135
};
143-
return (id)[[theBlock copy] autorelease];
136+
return [[theBlock copy] autorelease];
144137
}
145138

146139

Source/OCMock/OCMockMacros.h

+35-22
Original file line numberDiff line numberDiff line change
@@ -34,68 +34,76 @@
3434
#define OCMObserverMock() [OCMockObject observerMock]
3535

3636

37-
#define OCMStub(invocation) \
37+
#define OCMStubWithStateClass(MacroStateClass, invocation) \
3838
({ \
3939
_OCMSilenceWarnings( \
40-
[OCMMacroState beginStubMacro]; \
40+
[MacroStateClass beginStubMacro]; \
4141
OCMStubRecorder *recorder = nil; \
4242
@try{ \
4343
invocation; \
4444
}@catch(...){ \
45-
[[OCMMacroState globalState] setInvocationDidThrow:YES]; \
45+
[[MacroStateClass globalState] setInvocationDidThrow:YES]; \
4646
/* NOLINTNEXTLINE(google-objc-avoid-throwing-exception) */ \
4747
@throw; \
4848
}@finally{ \
49-
recorder = [OCMMacroState endStubMacro]; \
49+
recorder = [MacroStateClass endStubMacro]; \
5050
} \
5151
recorder; \
5252
); \
5353
})
5454

55-
#define OCMExpect(invocation) \
55+
#define OCMStub(invocation) OCMStubWithStateClass(OCMMacroState, invocation)
56+
57+
#define OCMExpectWithStateClass(MacroStateClass, invocation) \
5658
({ \
5759
_OCMSilenceWarnings( \
58-
[OCMMacroState beginExpectMacro]; \
60+
[MacroStateClass beginExpectMacro]; \
5961
OCMStubRecorder *recorder = nil; \
6062
@try{ \
6163
invocation; \
6264
}@catch(...){ \
63-
[[OCMMacroState globalState] setInvocationDidThrow:YES]; \
65+
[[MacroStateClass globalState] setInvocationDidThrow:YES]; \
6466
/* NOLINTNEXTLINE(google-objc-avoid-throwing-exception) */ \
6567
@throw; \
6668
}@finally{ \
67-
recorder = [OCMMacroState endExpectMacro]; \
69+
recorder = [MacroStateClass endExpectMacro]; \
6870
} \
6971
recorder; \
7072
); \
7173
})
7274

73-
#define OCMReject(invocation) \
75+
#define OCMExpect(invocation) OCMExpectWithStateClass(OCMMacroState, invocation)
76+
77+
#define OCMRejectWithStateClass(MacroStateClass, invocation) \
7478
({ \
7579
_OCMSilenceWarnings( \
76-
[OCMMacroState beginRejectMacro]; \
80+
[MacroStateClass beginRejectMacro]; \
7781
OCMStubRecorder *recorder = nil; \
7882
@try{ \
7983
invocation; \
8084
}@catch(...){ \
81-
[[OCMMacroState globalState] setInvocationDidThrow:YES]; \
85+
[[MacroStateClass globalState] setInvocationDidThrow:YES]; \
8286
/* NOLINTNEXTLINE(google-objc-avoid-throwing-exception) */ \
8387
@throw; \
8488
}@finally{ \
85-
recorder = [OCMMacroState endRejectMacro]; \
89+
recorder = [MacroStateClass endRejectMacro]; \
8690
} \
8791
recorder; \
8892
); \
8993
})
9094

95+
#define OCMReject(invocation) OCMRejectWithStateClass(OCMMacroState, invocation)
9196

9297

93-
#define OCMClassMethod(invocation) \
98+
99+
#define OCMClassMethodWithStateClass(MacroStateClass, invocation) \
94100
_OCMSilenceWarnings( \
95-
[[OCMMacroState globalState] switchToClassMethod]; \
101+
[[MacroStateClass globalState] switchToClassMethod]; \
96102
invocation; \
97103
);
98104

105+
#define OCMClassMethod(invocation) OCMClassMethodWithStateClass(OCMMacroState, invocation)
106+
99107

100108
#ifndef OCM_DISABLE_SHORT_SYNTAX
101109
#define ClassMethod(invocation) OCMClassMethod(invocation)
@@ -106,38 +114,43 @@
106114

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

109-
#define _OCMVerify(invocation) \
117+
#define _OCMVerifyWithStateClass(MacroStateClass, invocation) \
110118
({ \
111119
_OCMSilenceWarnings( \
112-
[OCMMacroState beginVerifyMacroAtLocation:OCMMakeLocation(self, __FILE__, __LINE__)]; \
120+
[MacroStateClass beginVerifyMacroAtLocation:OCMMakeLocation(self, __FILE__, __LINE__)]; \
113121
@try{ \
114122
invocation; \
115123
}@catch(...){ \
116-
[[OCMMacroState globalState] setInvocationDidThrow:YES]; \
124+
[[MacroStateClass globalState] setInvocationDidThrow:YES]; \
117125
/* NOLINTNEXTLINE(google-objc-avoid-throwing-exception) */ \
118126
@throw; \
119127
}@finally{ \
120-
[OCMMacroState endVerifyMacro]; \
128+
[MacroStateClass endVerifyMacro]; \
121129
} \
122130
); \
123131
})
124132

125-
#define _OCMVerifyWithQuantifier(quantifier, invocation) \
133+
#define _OCMVerify(invocation) _OCMVerifyWithStateClass(OCMMacroState, invocation)
134+
135+
136+
#define _OCMVerifyWithQuantifierAndStateClass(MacroStateClass, quantifier, invocation) \
126137
({ \
127138
_OCMSilenceWarnings( \
128-
[OCMMacroState beginVerifyMacroAtLocation:OCMMakeLocation(self, __FILE__, __LINE__) withQuantifier:quantifier]; \
139+
[MacroStateClass beginVerifyMacroAtLocation:OCMMakeLocation(self, __FILE__, __LINE__) withQuantifier:quantifier]; \
129140
@try{ \
130141
invocation; \
131142
}@catch(...){ \
132-
[[OCMMacroState globalState] setInvocationDidThrow:YES]; \
143+
[[MacroStateClass globalState] setInvocationDidThrow:YES]; \
133144
/* NOLINTNEXTLINE(google-objc-avoid-throwing-exception) */ \
134145
@throw; \
135146
}@finally{ \
136-
[OCMMacroState endVerifyMacro]; \
147+
[MacroStateClass endVerifyMacro]; \
137148
} \
138149
); \
139150
})
140151

152+
#define _OCMVerifyWithQuantifier(quantifier, invocation) _OCMVerifyWithQuantifierAndStateClass(OCMMacroState, quantifier, invocation)
153+
141154
// explanation for macros below here: https://stackoverflow.com/questions/3046889/optional-parameters-with-c-macros
142155

143156
#define _OCMVerify_1(A) _OCMVerify(A)

0 commit comments

Comments
 (0)