222 lines
5.5 KiB
Objective-C
222 lines
5.5 KiB
Objective-C
#import "EXPExpect.h"
|
|
#import "NSObject+Expecta.h"
|
|
#import "Expecta.h"
|
|
#import "EXPUnsupportedObject.h"
|
|
#import "EXPMatcher.h"
|
|
#import "EXPBlockDefinedMatcher.h"
|
|
#import <libkern/OSAtomic.h>
|
|
|
|
#pragma clang diagnostic push
|
|
#pragma clang diagnostic ignored "-Wobjc-designated-initializers"
|
|
@implementation EXPExpect
|
|
#pragma clang diagnostic pop
|
|
|
|
@dynamic
|
|
actual,
|
|
to,
|
|
toNot,
|
|
notTo,
|
|
will,
|
|
willNot,
|
|
after;
|
|
|
|
@synthesize
|
|
actualBlock=_actualBlock,
|
|
testCase=_testCase,
|
|
negative=_negative,
|
|
asynchronous=_asynchronous,
|
|
timeout=_timeout,
|
|
lineNumber=_lineNumber,
|
|
fileName=_fileName;
|
|
|
|
- (instancetype)initWithActualBlock:(id)actualBlock testCase:(id)testCase lineNumber:(int)lineNumber fileName:(const char *)fileName {
|
|
self = [super init];
|
|
if(self) {
|
|
self.actualBlock = actualBlock;
|
|
self.testCase = testCase;
|
|
self.negative = NO;
|
|
self.asynchronous = NO;
|
|
self.timeout = [Expecta asynchronousTestTimeout];
|
|
self.lineNumber = lineNumber;
|
|
self.fileName = fileName;
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (void)dealloc
|
|
{
|
|
[_actualBlock release];
|
|
_actualBlock = nil;
|
|
[super dealloc];
|
|
}
|
|
|
|
+ (EXPExpect *)expectWithActualBlock:(id)actualBlock testCase:(id)testCase lineNumber:(int)lineNumber fileName:(const char *)fileName {
|
|
return [[[EXPExpect alloc] initWithActualBlock:actualBlock testCase:(id)testCase lineNumber:lineNumber fileName:fileName] autorelease];
|
|
}
|
|
|
|
#pragma mark -
|
|
|
|
- (EXPExpect *)to {
|
|
return self;
|
|
}
|
|
|
|
- (EXPExpect *)toNot {
|
|
self.negative = !self.negative;
|
|
return self;
|
|
}
|
|
|
|
- (EXPExpect *)notTo {
|
|
return [self toNot];
|
|
}
|
|
|
|
- (EXPExpect *)will {
|
|
self.asynchronous = YES;
|
|
return self;
|
|
}
|
|
|
|
- (EXPExpect *)willNot {
|
|
return self.will.toNot;
|
|
}
|
|
|
|
- (EXPExpect *(^)(NSTimeInterval))after
|
|
{
|
|
EXPExpect * (^block)(NSTimeInterval) = [^EXPExpect *(NSTimeInterval timeout) {
|
|
self.asynchronous = YES;
|
|
self.timeout = timeout;
|
|
return self;
|
|
} copy];
|
|
|
|
return [block autorelease];
|
|
}
|
|
|
|
#pragma mark -
|
|
|
|
- (id)actual {
|
|
if(self.actualBlock) {
|
|
return self.actualBlock();
|
|
}
|
|
return nil;
|
|
}
|
|
|
|
- (void)applyMatcher:(id<EXPMatcher>)matcher
|
|
{
|
|
id actual = [self actual];
|
|
[self applyMatcher:matcher to:&actual];
|
|
}
|
|
|
|
- (void)applyMatcher:(id<EXPMatcher>)matcher to:(NSObject **)actual {
|
|
if([*actual isKindOfClass:[EXPUnsupportedObject class]]) {
|
|
EXPFail(self.testCase, self.lineNumber, self.fileName,
|
|
[NSString stringWithFormat:@"expecting a %@ is not supported", ((EXPUnsupportedObject *)*actual).type]);
|
|
} else {
|
|
BOOL failed = NO;
|
|
if([matcher respondsToSelector:@selector(meetsPrerequesiteFor:)] &&
|
|
![matcher meetsPrerequesiteFor:*actual]) {
|
|
failed = YES;
|
|
} else {
|
|
BOOL matchResult = NO;
|
|
if(self.asynchronous) {
|
|
NSTimeInterval timeOut = self.timeout;
|
|
NSDate *expiryDate = [NSDate dateWithTimeIntervalSinceNow:timeOut];
|
|
while(1) {
|
|
matchResult = [matcher matches:*actual];
|
|
failed = self.negative ? matchResult : !matchResult;
|
|
if(!failed || ([(NSDate *)[NSDate date] compare:expiryDate] == NSOrderedDescending)) {
|
|
break;
|
|
}
|
|
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.01]];
|
|
OSMemoryBarrier();
|
|
*actual = self.actual;
|
|
}
|
|
} else {
|
|
matchResult = [matcher matches:*actual];
|
|
}
|
|
failed = self.negative ? matchResult : !matchResult;
|
|
}
|
|
if(failed) {
|
|
NSString *message = nil;
|
|
|
|
if(self.negative) {
|
|
if ([matcher respondsToSelector:@selector(failureMessageForNotTo:)]) {
|
|
message = [matcher failureMessageForNotTo:*actual];
|
|
}
|
|
} else {
|
|
if ([matcher respondsToSelector:@selector(failureMessageForTo:)]) {
|
|
message = [matcher failureMessageForTo:*actual];
|
|
}
|
|
}
|
|
if (message == nil) {
|
|
message = @"Match Failed.";
|
|
}
|
|
|
|
EXPFail(self.testCase, self.lineNumber, self.fileName, message);
|
|
}
|
|
}
|
|
self.negative = NO;
|
|
}
|
|
|
|
#pragma mark - Dynamic predicate dispatch
|
|
|
|
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
|
|
{
|
|
if ([self.actual respondsToSelector:aSelector]) {
|
|
return [self.actual methodSignatureForSelector:aSelector];
|
|
}
|
|
return [super methodSignatureForSelector:aSelector];
|
|
}
|
|
|
|
- (void)forwardInvocation:(NSInvocation *)anInvocation
|
|
{
|
|
if ([self.actual respondsToSelector:anInvocation.selector]) {
|
|
EXPDynamicPredicateMatcher *matcher = [[EXPDynamicPredicateMatcher alloc] initWithExpectation:self selector:anInvocation.selector];
|
|
[anInvocation setSelector:@selector(dispatch)];
|
|
[anInvocation invokeWithTarget:matcher];
|
|
[matcher release];
|
|
}
|
|
else {
|
|
[super forwardInvocation:anInvocation];
|
|
}
|
|
}
|
|
|
|
@end
|
|
|
|
#pragma clang diagnostic push
|
|
#pragma clang diagnostic ignored "-Wobjc-designated-initializers"
|
|
@implementation EXPDynamicPredicateMatcher
|
|
#pragma clang diagnostic pop
|
|
|
|
- (instancetype)initWithExpectation:(EXPExpect *)expectation selector:(SEL)selector
|
|
{
|
|
if ((self = [super init])) {
|
|
_expectation = expectation;
|
|
_selector = selector;
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (BOOL)matches:(id)actual
|
|
{
|
|
return (BOOL)[actual performSelector:_selector];
|
|
}
|
|
|
|
- (NSString *)failureMessageForTo:(id)actual
|
|
{
|
|
return [NSString stringWithFormat:@"expected %@ to be true", NSStringFromSelector(_selector)];
|
|
}
|
|
|
|
- (NSString *)failureMessageForNotTo:(id)actual
|
|
{
|
|
return [NSString stringWithFormat:@"expected %@ to be false", NSStringFromSelector(_selector)];
|
|
}
|
|
|
|
- (void (^)(void))dispatch
|
|
{
|
|
__block id blockExpectation = _expectation;
|
|
|
|
return [[^{
|
|
[blockExpectation applyMatcher:self];
|
|
} copy] autorelease];
|
|
}
|
|
|
|
@end
|