Skip to content

Commit

Permalink
Merge pull request #157 from pivotal/multiple_protocol_fake
Browse files Browse the repository at this point in the history
Adds the ability to create a protocol fake for multiple protocols.
  • Loading branch information
jeffh committed Jan 11, 2014
2 parents cbe824d + 71c7cc9 commit e6b5d3b
Show file tree
Hide file tree
Showing 10 changed files with 225 additions and 49 deletions.
2 changes: 2 additions & 0 deletions Cedar.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -780,6 +780,7 @@
AEF33016145B6222002F93BB /* BeLessThan.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BeLessThan.h; sourceTree = "<group>"; };
AEF3301D145B68D7002F93BB /* BeLTESpec.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = BeLTESpec.mm; sourceTree = "<group>"; };
AEF33020145B69DE002F93BB /* BeLTE.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BeLTE.h; sourceTree = "<group>"; };
AEF52C26187F236D00C8D2D8 /* SimpleMultiplier.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SimpleMultiplier.h; sourceTree = "<group>"; };
AEF72F7713EC730700786282 /* CedarComparators.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CedarComparators.h; sourceTree = "<group>"; };
AEF72F7A13EC734000786282 /* CedarStringifiers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CedarStringifiers.h; sourceTree = "<group>"; };
AEF72FFB13ECC21E00786282 /* Base.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Base.h; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1521,6 +1522,7 @@
AE3E8F36184FEEE000633740 /* ObjectWithCollections.m */,
AE807889183C71950078C608 /* SimpleIncrementer.h */,
AE80788A183C71950078C608 /* SimpleIncrementer.m */,
AEF52C26187F236D00C8D2D8 /* SimpleMultiplier.h */,
AE80788B183C71950078C608 /* SimpleKeyValueObserver.h */,
AE80788C183C71950078C608 /* SimpleKeyValueObserver.m */,
AE8F1D901850F6C00059E840 /* CedarObservedObject.h */,
Expand Down
12 changes: 11 additions & 1 deletion Source/Doubles/CDRClassFake.mm
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,16 @@ - (void)setValue:(id)value forUndefinedKey:(NSString *)key {

@end

id CDR_fake_for(Class klass, BOOL require_explicit_stubs /*= YES */) {
id CDR_fake_for(BOOL require_explicit_stubs, Class klass, ...) {
va_list args;
va_start(args, klass);
id other_klass = va_arg(args, Class);
if (other_klass) {
[[NSException exceptionWithName:NSInternalInconsistencyException
reason:@"Can't create a fake for multiple classes."
userInfo:nil] raise];
}
va_end(args);

return [[[CDRClassFake alloc] initWithClass:klass requireExplicitStubs:require_explicit_stubs] autorelease];
}
101 changes: 76 additions & 25 deletions Source/Doubles/CDRProtocolFake.mm
Original file line number Diff line number Diff line change
Expand Up @@ -8,77 +8,128 @@ static bool protocol_hasSelector(Protocol *protocol, SEL selector, BOOL is_requi
return method_description.name && method_description.types;
}

static bool CDR_protocol_hasSelector(Protocol *protocol, SEL selector) {
return (protocol_hasSelector(protocol, selector, true, true)
|| protocol_hasSelector(protocol, selector, true, false)
|| protocol_hasSelector(protocol, selector, false, true)
|| protocol_hasSelector(protocol, selector, false, false));
}

@interface CDRProtocolFake ()
@property (strong, nonatomic) Protocol *protocol;
@property (retain, nonatomic) NSArray *protocols;
@end

@implementation CDRProtocolFake

@synthesize protocol = protocol_;
@synthesize protocols = protocols_;

- (id)initWithClass:(Class)klass forProtocol:(Protocol *)protocol requireExplicitStubs:(BOOL)requireExplicitStubs {
- (id)initWithClass:(Class)klass forProtocols:(NSArray *)protocols requireExplicitStubs:(BOOL)requireExplicitStubs; {
if (self = [super initWithClass:klass requireExplicitStubs:requireExplicitStubs]) {
protocol_ = protocol;
self.protocols = protocols;
}
return self;
}

- (void)dealloc {
protocol_ = nil;
self.protocols = nil;
[super dealloc];
}

- (BOOL)can_stub:(SEL)selector {
return class_respondsToSelector(self.klass, selector)
|| protocol_hasSelector(protocol_, selector, true, true)
|| protocol_hasSelector(protocol_, selector, true, false)
|| protocol_hasSelector(protocol_, selector, false, true)
|| protocol_hasSelector(protocol_, selector, false, false);
if (class_respondsToSelector(self.klass, selector)) {
return YES;
}
for (Protocol *protocol in self.protocols) {
if (CDR_protocol_hasSelector(protocol, selector)) {
return YES;
}
}
return NO;
}

- (BOOL)respondsToSelector:(SEL)selector {
return class_respondsToSelector(self.klass, selector)
|| protocol_hasSelector(protocol_, selector, true, true)
|| protocol_hasSelector(protocol_, selector, true, false)
|| (!self.requiresExplicitStubs && protocol_hasSelector(protocol_, selector, false, true))
|| (self.requiresExplicitStubs && [self has_stubbed_method_for:selector]);
if (class_respondsToSelector(self.klass, selector)) {
return YES;
}

for (Protocol *protocol in self.protocols) {
if ([self respondsToSelector:selector fromProtocol:protocol]) {
return YES;
}
}
return NO;
}

- (BOOL)conformsToProtocol:(Protocol *)aProtocol {
return protocol_conformsToProtocol(protocol_, aProtocol);
for (Protocol *protocol in self.protocols) {
if (protocol_conformsToProtocol(protocol, aProtocol)) {
return YES;
}
}
return NO;
}

- (NSString *)description {
return [NSString stringWithFormat:@"Fake implementation of %s protocol", protocol_getName(protocol_)];
NSMutableString *mutableDescription = [NSMutableString stringWithFormat:@"Fake implementation of %s", protocol_getName([self.protocols objectAtIndex:0])];
NSUInteger protocolCount = [self.protocols count];
for (int i = 1; i < protocolCount; i++) {
[mutableDescription appendFormat:@", %s", protocol_getName([self.protocols objectAtIndex:i])];
}
[mutableDescription appendString:@" protocol(s)"];
return [NSString stringWithString:mutableDescription];
}

- (BOOL)respondsToSelector:(SEL)selector fromProtocol:(Protocol *)protocol {
return protocol_hasSelector(protocol, selector, true, true)
|| protocol_hasSelector(protocol, selector, true, false)
|| (!self.requiresExplicitStubs && protocol_hasSelector(protocol, selector, false, true))
|| (self.requiresExplicitStubs && [self has_stubbed_method_for:selector]);
}

@end


id CDR_fake_for(Protocol *protocol, BOOL require_explicit_stubs /*= YES */) {
id CDR_fake_for(BOOL require_explicit_stubs, Protocol *protocol, ...) {
static size_t protocol_dummy_class_id = 0;

NSMutableArray *protocolArray = [NSMutableArray arrayWithObject:protocol];

va_list args;
va_start(args, protocol);
Protocol *p = nil;
while ((p = va_arg(args, Protocol *))) {
[protocolArray addObject:p];
}
va_end(args);

//TODO: emit all names
const char * protocol_name = protocol_getName(protocol);
std::stringstream class_name_emitter;
class_name_emitter << "fake for Protocol " << protocol_name << " #" << protocol_dummy_class_id++;

Class klass = objc_allocateClassPair([CDRProtocolFake class], class_name_emitter.str().c_str(), 0);
if(!klass) {

if (!klass) {
[[NSException exceptionWithName:NSInternalInconsistencyException
reason:[NSString stringWithFormat:@"Failed to create class when faking protocol %s", protocol_name]
userInfo:nil] raise];
}
if(!class_addProtocol(klass, @protocol(CedarDouble))) {

if (!class_addProtocol(klass, @protocol(CedarDouble))) {
[[NSException exceptionWithName:NSInternalInconsistencyException
reason:[NSString stringWithFormat:@"Failed to add CedarDouble protocol class when faking protocol %s", protocol_name]
userInfo:nil] raise];
}
if (!class_addProtocol(klass, protocol)) {
[[NSException exceptionWithName:NSInternalInconsistencyException
reason:[NSString stringWithFormat:@"Failed to fake protocol %s, unable to add protocol to fake object", protocol_name]
userInfo:nil] raise];

for (Protocol *proto in protocolArray) {
if (!class_addProtocol(klass, proto)) {
[[NSException exceptionWithName:NSInternalInconsistencyException
reason:[NSString stringWithFormat:@"Failed to fake protocol %s, unable to add protocol to fake object", protocol_getName(proto)]
userInfo:nil] raise];
}
}

objc_registerClassPair(klass);

return [[[CDRProtocolFake alloc] initWithClass:klass forProtocol:protocol requireExplicitStubs:require_explicit_stubs] autorelease];
return [[[CDRProtocolFake alloc] initWithClass:klass forProtocols:protocolArray requireExplicitStubs:require_explicit_stubs] autorelease];
}
2 changes: 1 addition & 1 deletion Source/Headers/Doubles/CDRClassFake.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@

@end

id CDR_fake_for(Class klass, BOOL require_explicit_stubs = YES);
id CDR_fake_for(BOOL require_explicit_stubs, Class klass, ...);
4 changes: 2 additions & 2 deletions Source/Headers/Doubles/CDRFake.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,6 @@
@end

#ifndef CEDAR_DOUBLES_COMPATIBILITY_MODE
#define fake_for(x) CDR_fake_for((x))
#define nice_fake_for(x) CDR_fake_for((x), NO)
#define fake_for(...) CDR_fake_for(YES, __VA_ARGS__, nil)
#define nice_fake_for(...) CDR_fake_for(NO, __VA_ARGS__, nil)
#endif
4 changes: 2 additions & 2 deletions Source/Headers/Doubles/CDRProtocolFake.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@

@interface CDRProtocolFake : CDRFake

- (id)initWithClass:(Class)klass forProtocol:(Protocol *)protocol requireExplicitStubs:(BOOL)requireExplicitStubs;
- (id)initWithClass:(Class)klass forProtocols:(NSArray *)protocols requireExplicitStubs:(BOOL)requireExplicitStubs;

@end

id CDR_fake_for(Protocol *protocol, BOOL require_explicit_stubs = YES);
id CDR_fake_for(BOOL require_explicit_stubs, Protocol *protocol, ...);
6 changes: 6 additions & 0 deletions Spec/Doubles/CDRClassFakeSpec.mm
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,12 @@
});
});
});

describe(@"trying to create a fake for multiple classes", ^{
it(@"should fail with a reasonable message", ^{
^{ nice_fake_for([SimpleIncrementer class], [NSValue class]); } should raise_exception.with_reason(@"Can't create a fake for multiple classes.");
});
});
});

SPEC_END
Loading

0 comments on commit e6b5d3b

Please sign in to comment.