Skip to content

Commit 680f873

Browse files
Use direct method interception via the runtime instead of OCMock
1 parent 16a06a9 commit 680f873

File tree

1 file changed

+55
-17
lines changed

1 file changed

+55
-17
lines changed

src/darwin/Framework/CHIPTests/TestHelpers/MTRMockCB.m

+55-17
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@
1717
#import "MTRMockCB.h"
1818

1919
#import <CoreBluetooth/CoreBluetooth.h>
20-
#import <OCMock/OCMock.h>
2120
#import <XCTest/XCTest.h>
21+
#import <objc/runtime.h>
2222

2323
NS_ASSUME_NONNULL_BEGIN
2424

@@ -116,13 +116,49 @@ - (void)openL2CAPChannel:(CBL2CAPPSM)PSM;
116116

117117
@implementation MTRMockCB {
118118
@package
119-
id _classMock;
119+
os_block_t _invalidate;
120120
dispatch_queue_t _queue;
121121
NSHashTable<MTRMockCBCentralManager *> * _managers;
122122
NSMutableDictionary<NSUUID *, MTRMockCBPeripheralDetails *> * _peripherals;
123123
CBManagerState _state;
124124
}
125125

126+
static void InterceptClassMethod(__strong os_block_t * inOutCleanup, Class cls, SEL sel, id block)
127+
{
128+
Method method = class_getClassMethod(cls, sel); // may return an inherited method
129+
if (!method) {
130+
NSString * reason = [NSString stringWithFormat:@"+[%@ %@] does not exist",
131+
NSStringFromClass(cls), NSStringFromSelector(sel)];
132+
@throw [NSException exceptionWithName:NSInternalInconsistencyException reason:reason userInfo:nil];
133+
}
134+
IMP originalImp = method_getImplementation(method);
135+
if (imp_getBlock(originalImp)) {
136+
NSString * reason = [NSString stringWithFormat:@"+[%@ %@] was already intercepted",
137+
NSStringFromClass(cls), NSStringFromSelector(sel)];
138+
@throw [NSException exceptionWithName:NSInternalInconsistencyException reason:reason userInfo:nil];
139+
}
140+
141+
// Try to add the method first, in case it came from a super class.
142+
// Note we need to pass the meta-class to class_addMethod().
143+
IMP newImp = imp_implementationWithBlock(block);
144+
if (class_addMethod(object_getClass(cls), sel, newImp, method_getTypeEncoding(method))) {
145+
method = class_getClassMethod(cls, sel); // look up again so we clean up the method we added
146+
} else {
147+
method_setImplementation(method, newImp);
148+
}
149+
150+
os_block_t nextCleanup = *inOutCleanup;
151+
*inOutCleanup = ^{
152+
// This isn't 100% correct if we added an override of a super-class method, because
153+
// there is no API for removing a method. Instead we directly point it at the
154+
// inherited implementation; this is good enough for our purposes here.
155+
method_setImplementation(method, originalImp);
156+
if (nextCleanup) {
157+
nextCleanup();
158+
}
159+
};
160+
}
161+
126162
- (instancetype)init
127163
{
128164
self = [super init];
@@ -131,29 +167,30 @@ - (instancetype)init
131167
_peripherals = [[NSMutableDictionary alloc] init];
132168
_state = CBManagerStatePoweredOn;
133169

134-
_classMock = OCMClassMock(CBCentralManager.class);
135-
OCMStub(ClassMethod([_classMock alloc]))
136-
.andCall(self, @selector(allocMockManager));
137-
OCMStub(ClassMethod([_classMock authorization]))
138-
.andCall(MTRMockCBCentralManager.class, @selector(authorization));
139-
OCMStub(ClassMethod([_classMock supportsFeatures:0])).ignoringNonObjectArgs().andCall(MTRMockCBCentralManager.class, @selector(supportsFeatures:));
170+
// Replace implementations of class methods we need to mock. We don't need to intercept
171+
// any instance methods directly, because we're returning a mock object from `alloc`.
172+
InterceptClassMethod(&_invalidate, CBCentralManager.class, @selector(alloc), ^id NS_RETURNS_RETAINED(void) {
173+
return [[MTRMockCBCentralManager alloc] _initWithMock:self];
174+
});
175+
InterceptClassMethod(&_invalidate, CBCentralManager.class, @selector(supportsFeatures:), ^BOOL(CBCentralManagerFeature features) {
176+
return [MTRMockCBCentralManager supportsFeatures:features];
177+
});
178+
InterceptClassMethod(&_invalidate, CBManager.class, @selector(authorization), ^CBManagerAuthorization(void) {
179+
return [MTRMockCBCentralManager authorization];
180+
});
181+
140182
return self;
141183
}
142184

143185
- (BOOL)isValid
144186
{
145187
__block BOOL valid;
146188
dispatch_sync(_queue, ^{
147-
valid = (_classMock != nil);
189+
valid = (_invalidate != nil);
148190
});
149191
return valid;
150192
}
151193

152-
- (id)allocMockManager
153-
{
154-
return [[MTRMockCBCentralManager alloc] _initWithMock:self];
155-
}
156-
157194
- (void)reset
158195
{
159196
dispatch_sync(_queue, ^{
@@ -166,10 +203,11 @@ - (void)reset
166203
- (void)stopMocking
167204
{
168205
dispatch_sync(_queue, ^{
169-
if (_classMock) {
206+
if (_invalidate) {
207+
_invalidate();
208+
_invalidate = nil;
209+
170210
NSArray<MTRMockCBCentralManager *> * managers = [_managers allObjects];
171-
[_classMock stopMocking];
172-
_classMock = nil;
173211
_managers = nil;
174212
_peripherals = nil;
175213

0 commit comments

Comments
 (0)