|
@@ -0,0 +1,188 @@
|
|
|
|
+#import "ExpectaSupport.h"
|
|
|
|
+#import "NSValue+Expecta.h"
|
|
|
|
+#import "NSObject+Expecta.h"
|
|
|
|
+#import "EXPUnsupportedObject.h"
|
|
|
|
+#import "EXPFloatTuple.h"
|
|
|
|
+#import "EXPDoubleTuple.h"
|
|
|
|
+#import "EXPDefines.h"
|
|
|
|
+#import <objc/runtime.h>
|
|
|
|
+
|
|
|
|
+@interface NSException (ExpectaSenTestFailure)
|
|
|
|
+
|
|
|
|
++ (NSException *)failureInFile:(NSString *)filename atLine:(int)lineNumber withDescription:(NSString *)formatString, ...;
|
|
|
|
+
|
|
|
|
+@end
|
|
|
|
+
|
|
|
|
+@interface NSObject (ExpectaXCTestRecordFailure)
|
|
|
|
+
|
|
|
|
+// suppress warning
|
|
|
|
+- (void)recordFailureWithDescription:(NSString *)description inFile:(NSString *)filename atLine:(NSUInteger)lineNumber expected:(BOOL)expected;
|
|
|
|
+
|
|
|
|
+@end
|
|
|
|
+
|
|
|
|
+id _EXPObjectify(const char *type, ...) {
|
|
|
|
+ va_list v;
|
|
|
|
+ va_start(v, type);
|
|
|
|
+ id obj = nil;
|
|
|
|
+ if(strcmp(type, @encode(char)) == 0) {
|
|
|
|
+ char actual = (char)va_arg(v, int);
|
|
|
|
+ obj = [NSNumber numberWithChar:actual];
|
|
|
|
+ } else if(strcmp(type, @encode(_Bool)) == 0) {
|
|
|
|
+ _Static_assert(sizeof(_Bool) <= sizeof(int), "Expected _Bool to be subject to vararg type promotion");
|
|
|
|
+ _Bool actual = (_Bool)va_arg(v, int);
|
|
|
|
+ obj = [NSNumber numberWithBool:actual];
|
|
|
|
+ } else if(strcmp(type, @encode(double)) == 0) {
|
|
|
|
+ double actual = (double)va_arg(v, double);
|
|
|
|
+ obj = [NSNumber numberWithDouble:actual];
|
|
|
|
+ } else if(strcmp(type, @encode(float)) == 0) {
|
|
|
|
+ float actual = (float)va_arg(v, double);
|
|
|
|
+ obj = [NSNumber numberWithFloat:actual];
|
|
|
|
+ } else if(strcmp(type, @encode(int)) == 0) {
|
|
|
|
+ int actual = (int)va_arg(v, int);
|
|
|
|
+ obj = [NSNumber numberWithInt:actual];
|
|
|
|
+ } else if(strcmp(type, @encode(long)) == 0) {
|
|
|
|
+ long actual = (long)va_arg(v, long);
|
|
|
|
+ obj = [NSNumber numberWithLong:actual];
|
|
|
|
+ } else if(strcmp(type, @encode(long long)) == 0) {
|
|
|
|
+ long long actual = (long long)va_arg(v, long long);
|
|
|
|
+ obj = [NSNumber numberWithLongLong:actual];
|
|
|
|
+ } else if(strcmp(type, @encode(short)) == 0) {
|
|
|
|
+ short actual = (short)va_arg(v, int);
|
|
|
|
+ obj = [NSNumber numberWithShort:actual];
|
|
|
|
+ } else if(strcmp(type, @encode(unsigned char)) == 0) {
|
|
|
|
+ unsigned char actual = (unsigned char)va_arg(v, unsigned int);
|
|
|
|
+ obj = [NSNumber numberWithUnsignedChar:actual];
|
|
|
|
+ } else if(strcmp(type, @encode(unsigned int)) == 0) {
|
|
|
|
+ unsigned int actual = (int)va_arg(v, unsigned int);
|
|
|
|
+ obj = [NSNumber numberWithUnsignedInt:actual];
|
|
|
|
+ } else if(strcmp(type, @encode(unsigned long)) == 0) {
|
|
|
|
+ unsigned long actual = (unsigned long)va_arg(v, unsigned long);
|
|
|
|
+ obj = [NSNumber numberWithUnsignedLong:actual];
|
|
|
|
+ } else if(strcmp(type, @encode(unsigned long long)) == 0) {
|
|
|
|
+ unsigned long long actual = (unsigned long long)va_arg(v, unsigned long long);
|
|
|
|
+ obj = [NSNumber numberWithUnsignedLongLong:actual];
|
|
|
|
+ } else if(strcmp(type, @encode(unsigned short)) == 0) {
|
|
|
|
+ unsigned short actual = (unsigned short)va_arg(v, unsigned int);
|
|
|
|
+ obj = [NSNumber numberWithUnsignedShort:actual];
|
|
|
|
+ } else if(strstr(type, @encode(EXPBasicBlock)) != NULL) {
|
|
|
|
+ // @encode(EXPBasicBlock) returns @? as of clang 4.1.
|
|
|
|
+ // This condition must occur before the test for id/class type,
|
|
|
|
+ // otherwise blocks will be treated as vanilla objects.
|
|
|
|
+ id actual = va_arg(v, EXPBasicBlock);
|
|
|
|
+ obj = [[actual copy] autorelease];
|
|
|
|
+ } else if((strstr(type, @encode(id)) != NULL) || (strstr(type, @encode(Class)) != 0)) {
|
|
|
|
+ id actual = va_arg(v, id);
|
|
|
|
+ obj = actual;
|
|
|
|
+ } else if(strcmp(type, @encode(__typeof__(nil))) == 0) {
|
|
|
|
+ obj = nil;
|
|
|
|
+ } else if(strstr(type, "ff}{") != NULL) { //TODO: of course this only works for a 2x2 e.g. CGRect
|
|
|
|
+ obj = [[[EXPFloatTuple alloc] initWithFloatValues:(float *)va_arg(v, float[4]) size:4] autorelease];
|
|
|
|
+ } else if(strstr(type, "=ff}") != NULL) {
|
|
|
|
+ obj = [[[EXPFloatTuple alloc] initWithFloatValues:(float *)va_arg(v, float[2]) size:2] autorelease];
|
|
|
|
+ } else if(strstr(type, "=ffff}") != NULL) {
|
|
|
|
+ obj = [[[EXPFloatTuple alloc] initWithFloatValues:(float *)va_arg(v, float[4]) size:4] autorelease];
|
|
|
|
+ } else if(strstr(type, "dd}{") != NULL) { //TODO: same here
|
|
|
|
+ obj = [[[EXPDoubleTuple alloc] initWithDoubleValues:(double *)va_arg(v, double[4]) size:4] autorelease];
|
|
|
|
+ } else if(strstr(type, "=dd}") != NULL) {
|
|
|
|
+ obj = [[[EXPDoubleTuple alloc] initWithDoubleValues:(double *)va_arg(v, double[2]) size:2] autorelease];
|
|
|
|
+ } else if(strstr(type, "=dddd}") != NULL) {
|
|
|
|
+ obj = [[[EXPDoubleTuple alloc] initWithDoubleValues:(double *)va_arg(v, double[4]) size:4] autorelease];
|
|
|
|
+ } else if(type[0] == '{') {
|
|
|
|
+ EXPUnsupportedObject *actual = [[[EXPUnsupportedObject alloc] initWithType:@"struct"] autorelease];
|
|
|
|
+ obj = actual;
|
|
|
|
+ } else if(type[0] == '(') {
|
|
|
|
+ EXPUnsupportedObject *actual = [[[EXPUnsupportedObject alloc] initWithType:@"union"] autorelease];
|
|
|
|
+ obj = actual;
|
|
|
|
+ } else {
|
|
|
|
+ void *actual = va_arg(v, void *);
|
|
|
|
+ obj = (actual == NULL ? nil :[NSValue valueWithPointer:actual]);
|
|
|
|
+ }
|
|
|
|
+ if([obj isKindOfClass:[NSValue class]] && ![obj isKindOfClass:[NSNumber class]]) {
|
|
|
|
+ [(NSValue *)obj set_EXP_objCType:type];
|
|
|
|
+ }
|
|
|
|
+ va_end(v);
|
|
|
|
+ return obj;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+EXPExpect *_EXP_expect(id testCase, int lineNumber, const char *fileName, EXPIdBlock actualBlock) {
|
|
|
|
+ return [EXPExpect expectWithActualBlock:actualBlock testCase:testCase lineNumber:lineNumber fileName:fileName];
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+void EXPFail(id testCase, int lineNumber, const char *fileName, NSString *message) {
|
|
|
|
+ NSLog(@"%s:%d %@", fileName, lineNumber, message);
|
|
|
|
+ NSString *reason = [NSString stringWithFormat:@"%s:%d %@", fileName, lineNumber, message];
|
|
|
|
+ NSException *exception = [NSException exceptionWithName:@"Expecta Error" reason:reason userInfo:nil];
|
|
|
|
+
|
|
|
|
+ if(testCase && [testCase respondsToSelector:@selector(failWithException:)]) {
|
|
|
|
+ if([[(Class)objc_getMetaClass("NSException") class] instancesRespondToSelector:@selector(failureInFile:atLine:withDescription:)]) {
|
|
|
|
+ exception = [NSException failureInFile:[NSString stringWithUTF8String:fileName] atLine:lineNumber withDescription:message];
|
|
|
|
+ }
|
|
|
|
+ [testCase failWithException:exception];
|
|
|
|
+ } else if(testCase && [testCase respondsToSelector:@selector(recordFailureWithDescription:inFile:atLine:expected:)]){
|
|
|
|
+ [testCase recordFailureWithDescription:message
|
|
|
|
+ inFile:[NSString stringWithUTF8String:fileName]
|
|
|
|
+ atLine:lineNumber
|
|
|
|
+ expected:NO];
|
|
|
|
+ } else {
|
|
|
|
+ [exception raise];
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+NSString *EXPDescribeObject(id obj) {
|
|
|
|
+ if(obj == nil) {
|
|
|
|
+ return @"nil/null";
|
|
|
|
+ } else if([obj isKindOfClass:[NSValue class]]) {
|
|
|
|
+ if([obj isKindOfClass:[NSValue class]] && ![obj isKindOfClass:[NSNumber class]]) {
|
|
|
|
+ void *pointerValue = [obj pointerValue];
|
|
|
|
+ const char *type = [(NSValue *)obj _EXP_objCType];
|
|
|
|
+ if(type) {
|
|
|
|
+ if(strcmp(type, @encode(SEL)) == 0) {
|
|
|
|
+ return [NSString stringWithFormat:@"@selector(%@)", NSStringFromSelector([obj pointerValue])];
|
|
|
|
+ } else if(strcmp(type, @encode(Class)) == 0) {
|
|
|
|
+ return NSStringFromClass(pointerValue);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ NSString *description = [obj description];
|
|
|
|
+ if([obj isKindOfClass:[NSArray class]]) {
|
|
|
|
+ NSMutableArray *arr = [NSMutableArray arrayWithCapacity:[obj count]];
|
|
|
|
+ for(id o in obj) {
|
|
|
|
+ [arr addObject:EXPDescribeObject(o)];
|
|
|
|
+ }
|
|
|
|
+ description = [NSString stringWithFormat:@"(%@)", [arr componentsJoinedByString:@", "]];
|
|
|
|
+ } else if([obj isKindOfClass:[NSSet class]] || [obj isKindOfClass:[NSOrderedSet class]]) {
|
|
|
|
+ NSMutableArray *arr = [NSMutableArray arrayWithCapacity:[obj count]];
|
|
|
|
+ for(id o in obj) {
|
|
|
|
+ [arr addObject:EXPDescribeObject(o)];
|
|
|
|
+ }
|
|
|
|
+ description = [NSString stringWithFormat:@"{(%@)}", [arr componentsJoinedByString:@", "]];
|
|
|
|
+ } else if([obj isKindOfClass:[NSDictionary class]]) {
|
|
|
|
+ NSMutableArray *arr = [NSMutableArray arrayWithCapacity:[obj count]];
|
|
|
|
+ for(id k in obj) {
|
|
|
|
+ id v = [obj objectForKey:k];
|
|
|
|
+ [arr addObject:[NSString stringWithFormat:@"%@ = %@;",EXPDescribeObject(k), EXPDescribeObject(v)]];
|
|
|
|
+ }
|
|
|
|
+ description = [NSString stringWithFormat:@"{%@}", [arr componentsJoinedByString:@" "]];
|
|
|
|
+ } else {
|
|
|
|
+ description = [description stringByReplacingOccurrencesOfString:@"\n" withString:@"\\n"];
|
|
|
|
+ }
|
|
|
|
+ return description;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+void EXP_prerequisite(EXPBoolBlock block) {
|
|
|
|
+ [[[[NSThread currentThread] threadDictionary] objectForKey:@"EXP_currentMatcher"] setPrerequisiteBlock:block];
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+void EXP_match(EXPBoolBlock block) {
|
|
|
|
+ [[[[NSThread currentThread] threadDictionary] objectForKey:@"EXP_currentMatcher"] setMatchBlock:block];
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+void EXP_failureMessageForTo(EXPStringBlock block) {
|
|
|
|
+ [[[[NSThread currentThread] threadDictionary] objectForKey:@"EXP_currentMatcher"] setFailureMessageForToBlock:block];
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+void EXP_failureMessageForNotTo(EXPStringBlock block) {
|
|
|
|
+ [[[[NSThread currentThread] threadDictionary] objectForKey:@"EXP_currentMatcher"] setFailureMessageForNotToBlock:block];
|
|
|
|
+}
|
|
|
|
+
|