Browse files

Dropped UISpec in favor of slimmed down UISpecRunner extraction. Kill…

…ed off the submodule to simplify testing.
  • Loading branch information...
1 parent f1b0815 commit 205f1140af105aa083ac1a2419b239cb11196ded @blakewatters blakewatters committed Jul 21, 2011
View
3 .gitmodules
@@ -1,6 +1,3 @@
-[submodule "Specs/Runner/UISpec"]
- path = Specs/Runner/UISpec
- url = git://github.com/twotoasters/UISpec.git
[submodule "Examples/RKCatalog/Server"]
path = Examples/RKCatalog/Server
url = git://github.com/twotoasters/RKCatalog-Server.git
View
3 Docs/TESTING.md
@@ -5,13 +5,12 @@ RestKit ships with a testing infrastructure built around UISpec and
an associated Ruby Gem called UISpecRunner. To be able to run the
tests, you need to do a little bit of setup:
-1. Initialize the UISpec submodule: `git submodule init && git submodule update`
1. Install the Ruby Bundler Gem (if necessary): `sudo gem install bundler`
1. Install the other required Gems via Bundler: `bundle`
1. Start the spec server: `rake uispec:server`
1. Verify the configuration by running the specs: `uispec -v`
-If the project builds the UISpec target correctly and executes the suite, then
+If the project builds the Specs target correctly and executes the suite, then
you are all set. If there are any issues, you may need to reach out to the mailing
list for help debugging.
View
2 Specs/Runner/RKSpecEnvironment.h
@@ -7,8 +7,6 @@
//
#import "UISpec.h"
-#import "UIBug.h"
-#import "UIQuery.h"
#import "UIExpectation.h"
#import <OCMock/OCMock.h>
1 Specs/Runner/UISpec
@@ -1 +0,0 @@
-Subproject commit 5b0c81f99d90ebd052bef6385abf62ea2e4c041a
View
22 Specs/Runner/UISpecRunner/NSNumberCreator.h
@@ -0,0 +1,22 @@
+// This code is in the public domain, use it as you like.
+// Created by Martin Häcker
+
+/*
+ Use like this:
+ id dict = [NSDictionary dictionary];
+ NSNumber *ten = NMMakeNumber(10.0f);
+ [dict setObject:ten forKey:@"numberOfFingers"];
+ [dict setObject:NMMakeNumber(23) forKey:@"fnord"];
+ [dict setObject:NMMakeNumber(YES) forKey:@"wantFries"];
+ */
+
+#define N(aValue) ({ \
+/* The stack var is here, so this macro can accept constants directly. */ \
+__typeof__(aValue) __aValue = (aValue); \
+[NSNumberCreator numberWithValue:&__aValue objCType:@encode(__typeof__(aValue))]; \
+}) \
+
+@interface NSNumberCreator : NSObject
++ numberWithValue:(const void *)aValue objCType:(const char *)aTypeDescription;
+- initWithValue:(const void *)aValue objCType:(const char *)aTypeDescription;
+@end
View
103 Specs/Runner/UISpecRunner/NSNumberCreator.m
@@ -0,0 +1,103 @@
+#import "NSNumberCreator.h"
+
+@implementation NSNumberCreator
++ numberWithValue:(const void *)aValue objCType:(const char *)aTypeDescription;
+{
+ return [[[self alloc] initWithValue:aValue objCType:aTypeDescription] autorelease];
+}
+
+/// For the constants see: <http://developer.apple.com/documentation/Cocoa/Conceptual/ObjectiveC/Articles/chapter_14_section_9.html>
+- initWithValue:(const void *)aValue objCType:(const char *)aTypeDescription;
+{
+ if ('^' == *aTypeDescription
+ && nil == *(id *)aValue) return nil; // nil should stay nil, even if it's technically a (void *)
+ id number = [NSNumber alloc];
+ switch (*aTypeDescription)
+ {
+ case 'c': // BOOL, char
+ return [number initWithChar:*(char *)aValue];
+ case 'C': return [number initWithUnsignedChar:*(unsigned char *)aValue];
+ case 's': return [number initWithShort:*(short *)aValue];
+ case 'S': return [number initWithUnsignedShort:*(unsigned short *)aValue];
+ case 'i': return [number initWithInt:*(int *)aValue];
+ case 'I': return [number initWithUnsignedInt:*(unsigned *)aValue];
+ case 'l': return [number initWithLong:*(long *)aValue];
+ case 'L': return [number initWithUnsignedLong:*(unsigned long *)aValue];
+ case 'q': return [number initWithLongLong:*(long long *)aValue];
+ case 'Q': return [number initWithUnsignedLongLong:*(unsigned long long *)aValue];
+ case 'f': return [number initWithFloat:*(float *)aValue];
+ case 'd': return [number initWithDouble:*(double *)aValue];
+ case '@': return *(id *)aValue;
+ case '^': // pointer, no string stuff supported right now
+ case '{': // struct, only simple ones containing only basic types right now
+ case '[': // array, of whatever, just gets the address
+ return [[NSValue alloc] initWithBytes:aValue objCType:aTypeDescription];
+ }
+
+ return [[NSValue alloc] initWithBytes:aValue objCType:aTypeDescription];
+}
+@end
+
+// The ... is just to silence compiler warnings about multiple arguments if commas are used
+//#define test(result...) ({ \
+//if ( ! (result)) \
+//printf("%s:%d: %s\n", __FILE__, __LINE__, #result); \
+//})
+//
+//int main (int argc, const char * argv[]) {
+// NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
+//
+// // Creating NSValues
+// void *voidPointer = main;
+// test(main == [NMMakeNumber(voidPointer) pointerValue]);
+//
+// NSPoint pointVal = NSMakePoint(2,3);
+// NSPoint boxedPoint = [NMMakeNumber(pointVal) pointValue];
+// test(pointVal.x == boxedPoint.x);
+// test(pointVal.y == boxedPoint.y);
+//
+// NSRange rangeVal = NSMakeRange(2, 3);
+// NSRange boxedRange = [NMMakeNumber(rangeVal) rangeValue];
+// test(rangeVal.location == boxedRange.location);
+// test(rangeVal.length == boxedRange.length);
+//
+// NSRect rectVal = NSMakeRect(1, 2, 3, 4);
+// NSRect boxedRect = [NMMakeNumber(rectVal) rectValue];
+// test(rectVal.origin.x == boxedRect.origin.x);
+//
+// NSSize sizeVal = NSMakeSize(2, 3);
+// NSSize boxedSize = [NMMakeNumber(sizeVal) sizeValue];
+// test(sizeVal.width == boxedSize.width);
+//
+// // Creating NSNumbers
+// test( YES == [NMMakeNumber((BOOL)YES) boolValue]);
+// test( nil != NMMakeNumber((BOOL)NO));
+// test( nil != NMMakeNumber(NO));
+// test( -23 == [NMMakeNumber((char)-23) charValue]);
+// test( -23 == [NMMakeNumber((int)-23) intValue]);
+// test( -23 == [NMMakeNumber((NSInteger)-23) integerValue]);
+// test( -23 == [NMMakeNumber((long)-23) longValue]);
+// test( -23 == [NMMakeNumber((long long)-23) longLongValue]);
+// test( 23 == [NMMakeNumber((short)23) shortValue]);
+// test( 23 == [NMMakeNumber((unsigned char)23) unsignedCharValue]);
+// test( 23 == [NMMakeNumber((unsigned short)23) unsignedShortValue]);
+// test( 23 == [NMMakeNumber((unsigned int)23) unsignedIntValue]);
+// test( 23 == [NMMakeNumber((NSUInteger)23) unsignedIntegerValue]);
+// test( 23 == [NMMakeNumber((unsigned long)23) unsignedLongValue]);
+// test( 23 == [NMMakeNumber((unsigned long long)23) unsignedLongLongValue]);
+//
+// test( 23.1f == [NMMakeNumber((float)23.1) floatValue]);
+// test( 23.1F == [NMMakeNumber((double)23.1F) doubleValue]);
+//
+// test(nil == NMMakeNumber(nil));
+// test([@"fnord" isEqual:NMMakeNumber(@"fnord")]);
+// // REFACT: consider changing cStrings to objCStrings. Not sure if I want this though
+//
+// test( 23 == [NMMakeNumber(23.1F) intValue]);
+// test( -23.0f == [NMMakeNumber(-23) floatValue]);
+// // test( 23 == [NMMakeNumber((int)-23) unsignedIntValue]);
+// // TODO: propper conversion of NSNumbers so the test above doesn't fail anymore
+//
+// [pool drain];
+// return 0;
+//}
View
16 Specs/Runner/UISpecRunner/UIConsoleLog.h
@@ -0,0 +1,16 @@
+
+#import "UILog.h"
+
+@interface UIConsoleLog : NSObject <UILog> {
+ NSDate *start;
+ NSMutableArray *errors;
+ NSString *currentExample;
+ NSString *currentSpec;
+
+ BOOL _exitOnFinish;
+}
+
+// When YES, the application will terminate after specs finish running
+@property (nonatomic, assign) BOOL exitOnFinish;
+
+@end
View
93 Specs/Runner/UISpecRunner/UIConsoleLog.m
@@ -0,0 +1,93 @@
+#import "UIConsoleLog.h"
+#import "UISpec.h"
+
+@implementation UIConsoleLog
+
+@synthesize exitOnFinish = _exitOnFinish;
+
+- (id)init {
+ self = [super init];
+ if (self) {
+ _exitOnFinish = NO;
+ }
+
+ return self;
+}
+
+
+-(void)onStart {
+ errors = [[NSMutableArray array] retain];
+ start = [[NSDate date] retain];
+}
+
+-(void)onSpec:(UISpec *)spec {
+ currentSpec = NSStringFromClass([spec class]);
+ NSLog(@"\n%@", currentSpec);
+}
+
+-(void)onBeforeAll{}
+
+-(void)onBeforeAllException:(NSException *)exception {
+ NSString *error = [NSString stringWithFormat:@"%@ in %@ beforeAll \n %@", exception.name, currentSpec, exception.reason];
+ [errors addObject:error];
+}
+
+-(void)onBefore:(NSString *)example {
+ currentExample = example;
+}
+
+-(void)onBeforeException:(NSException *)exception {
+ NSString *error = [NSString stringWithFormat:@"%@ in %@ before %@ \n %@", exception.name, currentSpec, currentExample, exception.reason];
+ [errors addObject:error];
+}
+
+-(void)onExample:(NSString *)example {
+ currentExample = example;
+ NSLog(@"\n- %@", currentExample);
+}
+
+-(void)onExampleException:(NSException *)exception {
+ NSString *error = [NSString stringWithFormat:@"%@ %@ FAILED \n%@", currentSpec, currentExample, exception.reason];
+ [errors addObject:error];
+}
+
+-(void)onAfter:(NSString *)example {
+ currentExample = example;
+}
+
+-(void)onAfterException:(NSException *)exception {
+ NSString *error = [NSString stringWithFormat:@"%@ in %@ after %@ \n %@", exception.name, currentSpec, currentExample, exception.reason];
+ [errors addObject:error];
+}
+
+-(void)onAfterAll{}
+
+-(void)onAfterAllException:(NSException *)exception {
+ NSString *error = [NSString stringWithFormat:@"%@ in %@ afterAll \n %@", exception.name, currentSpec, exception.reason];
+ [errors addObject:error];
+}
+
+-(void)afterSpec:(UISpec *)spec { }
+
+-(void)onFinish:(int)count {
+ NSMutableString *log = [NSMutableString string];
+ if (errors.count > 0) {
+ int num = 0;
+ for (NSString *error in errors) {
+ [log appendFormat:@"\n\n%i)", ++num];
+ [log appendFormat:@"\n%@", error];
+ }
+ }
+ [log appendFormat:@"\n\nFinished in %f seconds", fabsf([start timeIntervalSinceNow])];
+
+ [log appendFormat:@"\n\n%i examples %d failures", count, errors.count];
+ NSLog(@"%@", log);
+
+ if (self.exitOnFinish) {
+ int exitStatus = [errors count] > 0 ? EXIT_FAILURE : EXIT_SUCCESS;
+ NSLog(@"Exiting with status code: %d", exitStatus);
+ exit(exitStatus);
+ }
+}
+
+@end
View
59 Specs/Runner/UISpecRunner/UIExpectation.h
@@ -0,0 +1,59 @@
+//
+// UIExpection.h
+// UISpec
+//
+// Created by Brian Knorr <btknorr@gmail.com>
+// Copyright(c) 2009 StarterStep, Inc., Some rights reserved.
+//
+
+#define expectThat(aValue) ({ \
+/* The stack var is here, so this macro can accept constants directly. */ \
+__typeof__(aValue) __aValue = (aValue); \
+[UIExpectation withValue:&__aValue objCType:@encode(__typeof__(aValue)) file:__FILE__ line:__LINE__ isFailureTest:NO]; \
+}) \
+
+#define expectFailureWhen(aValue) ({ \
+/* The stack var is here, so this macro can accept constants directly. */ \
+__typeof__(aValue) __aValue = (aValue); \
+[UIExpectation withValue:&__aValue objCType:@encode(__typeof__(aValue)) file:__FILE__ line:__LINE__ isFailureTest:YES]; \
+}) \
+
+#define be(aValue) ({ \
+__typeof__(aValue) __aValue = (aValue); \
+[UIMatcher withValue:&__aValue objCType:@encode(__typeof__(aValue)) matchSelector:@selector(be:)]; \
+}) \
+
+
+#import "UIMatcher.h"
+
+@interface UIExpectation : NSObject {
+ BOOL exist, isNot, isHave, isBe, isFailureTest;
+ UIExpectation *not, *have, *be, *should, *shouldNot;
+ id value;
+ const char * typeDescription;
+ const char * file;
+ int line;
+}
+
+@property(nonatomic, readonly) UIExpectation *not, *have, *be, *should, *shouldNot;
+@property(nonatomic, readonly) BOOL exist;
+
++(id)withValue:(const void *)aValue objCType:(const char *)aTypeDescription file:(const char *)aFile line:(int)aLine isFailureTest:(BOOL)failureTest;
++(SEL)makeIsSelector:(SEL)aSelector;
+
+-(id)initWithValue:(const void *)aValue objCType:(const char *)aTypeDescription file:(const char *)aFile line:(int)aLine isFailureTest:(BOOL)failureTest;
+-(UIExpectation *)not;
+-(UIExpectation *)should;
+-(UIExpectation *)shouldNot;
+-(UIExpectation *)have;
+-(UIExpectation *)be;
+-(void)should:(UIMatcher *)matcher;
+-(void)shouldNot:(UIMatcher *)matcher;
+-(void)not:(UIMatcher *)matcher;
+-(void)be:(SEL)sel;
+-(void)have:(NSInvocation *)invocation;
+-(NSString *)valueAsString;
+-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
+-(void)forwardInvocation:(NSInvocation *)anInvocation;
+
+@end
View
184 Specs/Runner/UISpecRunner/UIExpectation.m
@@ -0,0 +1,184 @@
+
+#import "UIExpectation.h"
+// #import "UIRedoer.h"
+// #import "UIQueryExpectation.h"
+#import "NSNumberCreator.h"
+
+@implementation UIExpectation
+
++(id)withValue:(const void *)aValue objCType:(const char *)aTypeDescription file:(const char *)aFile line:(int)aLine isFailureTest:(BOOL)failureTest{
+ // if (*aTypeDescription == '@' && ([*(id *)aValue isKindOfClass:[UIQuery class]] || [*(id *)aValue isKindOfClass:[UIRedoer class]])) {
+ // return [[[UIQueryExpectation alloc] initWithValue:aValue objCType:aTypeDescription file:aFile line:aLine isFailureTest:failureTest] autorelease];
+ // }
+ return [[[self alloc] initWithValue:aValue objCType:aTypeDescription file:aFile line:aLine isFailureTest:failureTest] autorelease];
+}
+
+-(id)initWithValue:(const void *)aValue objCType:(const char *)aTypeDescription file:(const char *)aFile line:(int)aLine isFailureTest:(BOOL)failureTest{
+ if (self = [super init]) {
+ //NSLog(@" UIExpectation initWithValue %s, %s, %d", aTypeDescription, aFile, aLine);
+ typeDescription = aTypeDescription;
+ value = [[NSNumberCreator numberWithValue:aValue objCType:aTypeDescription] retain];
+ file = aFile;
+ line = aLine;
+ isFailureTest = failureTest;
+ }
+ return self;
+}
+
+-(UIExpectation *)not {
+ isNot = YES;
+ return self;
+}
+
+-(UIExpectation *)should {
+ return self;
+}
+
+-(UIExpectation *)shouldNot {
+ return [self not];
+}
+
+-(UIExpectation *)have {
+ isHave = YES;
+ isBe = NO;
+ return self;
+}
+
+-(UIExpectation *)be {
+ isBe = YES;
+ isHave = NO;
+ return self;
+}
+
+-(void)should:(UIMatcher *)matcher {
+ if (![matcher matches:value]) {
+ if (!isFailureTest) {
+ [NSException raise:nil format:@"%@\n%s:%d", matcher.errorMessage, file, line];
+ }
+ } else if (isFailureTest) {
+ [NSException raise:nil format:@"%@\n%s:%d", @"expected: Failure, got: Success", file, line];\
+ }
+}
+
+-(void)shouldNot:(UIMatcher *)matcher {
+ if ([matcher matches:value]) {
+ if (!isFailureTest) {
+ [NSException raise:nil format:@"not %@\n%s:%d", matcher.errorMessage, file, line];
+ }
+ } else if (isFailureTest) {
+ [NSException raise:nil format:@"%@\n%s:%d", @"expected: Failure, got: Success", file, line];\
+ }
+}
+
+-(void)not:(UIMatcher *)matcher {
+ [self shouldNot:matcher];
+}
+
+-(void)be:(SEL)sel {
+ NSString *origSelector = NSStringFromSelector(sel);
+ if (![value respondsToSelector:sel] && [value respondsToSelector:[UIExpectation makeIsSelector:sel]]) {
+ sel = [UIExpectation makeIsSelector:sel];
+ }
+ BOOL result = [value performSelector:sel];
+ if ((result == YES && isNot) || (result == NO && !isNot)) {
+ if (!isFailureTest) {
+ [NSException raise:nil format:@"%@ did not pass condition: [%@ be %@]\n%s:%d", [self valueAsString], (isNot ? @"should not" : @"should"), origSelector, file, line];
+ }
+ } else if (isFailureTest) {
+ [NSException raise:nil format:@"%@\n%s:%d", @"expected: Failure, got: Success", file, line];\
+ }
+}
+
+-(void)have:(NSInvocation *)invocation {
+ NSMutableString *selector = [NSMutableString stringWithString:NSStringFromSelector([invocation selector])];
+ NSArray *selectors = [selector componentsSeparatedByString:@":"];
+ BOOL foundErrors = NO;
+ NSMutableArray *errors = [NSMutableArray array];
+ int i = 2;
+ const void * expected = nil;
+ for (NSString *key in selectors) {
+ if (![key isEqualToString:@""]) {
+ SEL selector = NSSelectorFromString(key);
+ if (![value respondsToSelector:selector]) {
+ [errors addObject:[NSString stringWithFormat:@"%@ doesn't respond to %@", [self valueAsString], key]];
+ foundErrors = YES;
+ continue;
+ }
+ [invocation getArgument:&expected atIndex:i];
+ NSString *returnType = [NSString stringWithFormat:@"%s", [[value methodSignatureForSelector:selector] methodReturnType]];
+ if ([returnType isEqualToString:@"@"]) {
+ if ([expected isKindOfClass:[NSString class]]) {
+ if ([[value performSelector:selector] rangeOfString:expected].length == 0) {
+ [errors addObject:[NSString stringWithFormat:@"%@ : '%@' doesn't contain '%@'", key, [value performSelector:selector], expected]];
+ foundErrors = YES;
+ continue;
+ }
+ } else if (![[value performSelector:selector] isEqual:expected]) {
+ [errors addObject:[NSString stringWithFormat:@"%@ : %@ is not equal to %@", key, [value performSelector:selector], expected]];
+ foundErrors = YES;
+ continue;
+ }
+ } else if ([value performSelector:selector] != expected) {
+ [errors addObject:[NSString stringWithFormat:@"%@ is not equal to value", key]];
+ foundErrors = YES;
+ continue;
+ }
+ }
+ }
+ if (foundErrors) {
+ if (!isFailureTest) {
+ [NSException raise:nil format:@"%@ should have %@ but %@\n%s:%d\n", [self valueAsString], selector, errors, file, line];
+ }
+ } else if (isFailureTest) {
+ [NSException raise:nil format:@"%@\n%s:%d", @"expected: Failure, got: Success", file, line];\
+ }
+}
+
+-(NSString *)valueAsString {
+ return [value description];
+}
+
+- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
+ NSMutableString *selector = NSStringFromSelector(aSelector);
+ if (isBe) {
+ if (![value respondsToSelector:aSelector] && [value respondsToSelector:[UIExpectation makeIsSelector:aSelector]]) {
+ aSelector = [UIExpectation makeIsSelector:aSelector];
+ }
+ //NSLog(@"isBe value = %@, selecotr = %@", value, NSStringFromSelector(aSelector));
+ return [value methodSignatureForSelector:aSelector];
+ }else if (isHave) {
+ if (isNot) {
+ [NSException raise:nil format:@"not isn't supported yet for something like [should.not.have foo:1]"];
+ }
+ NSString *selector = NSStringFromSelector(aSelector);
+ NSRange whereIsSet = [selector rangeOfString:@":"];
+ if (whereIsSet.length != 0) {
+ NSArray *selectors = [NSStringFromSelector(aSelector) componentsSeparatedByString:@":"];
+ NSMutableString *signature = [NSMutableString stringWithString:@"@@:"];
+ for (NSString *selector in selectors) {
+ if ([selector length] > 0) {
+ [signature appendString:@"@"];
+ }
+ }
+ //NSLog(@"signature = %@", signature);
+ return [NSMethodSignature signatureWithObjCTypes:[signature cStringUsingEncoding:NSUTF8StringEncoding]];
+ }
+ }
+ return [super methodSignatureForSelector:aSelector];
+}
+
+- (void)forwardInvocation:(NSInvocation *)anInvocation {
+ NSMutableString *selector = [NSMutableString stringWithString:NSStringFromSelector([anInvocation selector])];
+ if (isBe) {
+ [self be:[anInvocation selector]];
+ } else if (isHave) {
+ [self have:anInvocation];
+ }
+}
+
++(SEL)makeIsSelector:(SEL)aSelector {
+ NSString *selector = NSStringFromSelector(aSelector);
+ return NSSelectorFromString([NSString stringWithFormat:@"is%@", [selector stringByReplacingCharactersInRange:NSMakeRange(0,1) withString:[[selector substringWithRange:NSMakeRange(0,1)] uppercaseString]]]);
+}
+
+@end
View
20 Specs/Runner/UISpecRunner/UILog.h
@@ -0,0 +1,20 @@
+@class UISpec;
+
+@protocol UILog
+
+-(void)onStart;
+-(void)onSpec:(UISpec *)spec;
+-(void)onBeforeAll;
+-(void)onBeforeAllException:(NSException *)exception;
+-(void)onBefore:(NSString *)example;
+-(void)onBeforeException:(NSException *)exception;
+-(void)onExample:(NSString *)example;
+-(void)onExampleException:(NSException *)exception;
+-(void)onAfter:(NSString *)example;
+-(void)onAfterException:(NSException *)exception;
+-(void)onAfterAll;
+-(void)onAfterAllException:(NSException *)exception;
+-(void)afterSpec:(UISpec *)spec;
+-(void)onFinish:(int)count;
+
+@end
View
25 Specs/Runner/UISpecRunner/UIMatcher.h
@@ -0,0 +1,25 @@
+
+@protocol UIMatcher
+
+-(BOOL)matches:(const void *)value objCType:(const char *)typeDescription;
+
+@end
+
+
+@interface UIMatcher : NSObject <UIMatcher>{
+ id expectedValue;
+ const char * expectedTypeDescription;
+ SEL matchSelector;
+ NSString *errorMessage;
+}
+
+@property(nonatomic, retain) NSString *errorMessage;
+
+-(id)initWithValue:(const void *)aValue objCType:(const char *)aTypeDescription matchSelector:(SEL)aMatchSelector;
+-(BOOL)matches:(id)value;
+-(BOOL)be:(id)value;
+
++(id)withValue:(const void *)aValue objCType:(const char *)aTypeDescription matchSelector:(SEL)aMatchSelector;
++(NSString *)valueAsString:(const void *)value objCType:(const char *)typeDescription;
+
+@end
View
47 Specs/Runner/UISpecRunner/UIMatcher.m
@@ -0,0 +1,47 @@
+
+#import "UIMatcher.h"
+#import "NSNumberCreator.h"
+
+@implementation UIMatcher
+
+@synthesize errorMessage;
+
++(id)withValue:(const void *)aValue objCType:(const char *)aTypeDescription matchSelector:(SEL)aMatchSelector {
+ return [[[self alloc] initWithValue:aValue objCType:aTypeDescription matchSelector:(SEL)aMatchSelector] autorelease];
+}
+
+-(id)initWithValue:(const void *)aValue objCType:(const char *)aTypeDescription matchSelector:(SEL)aMatchSelector {
+ if (self = [super init]) {
+ expectedTypeDescription = aTypeDescription;
+ expectedValue = [NSNumberCreator numberWithValue:aValue objCType:aTypeDescription];
+ matchSelector = aMatchSelector;
+ }
+ return self;
+}
+
+-(BOOL)matches:(id)value {
+ return [self performSelector:matchSelector withObject:value];
+}
+
+-(BOOL)be:(id)value {
+ self.errorMessage = [NSString stringWithFormat:@"expected: %@, got: %@", expectedValue==nil?@"nil":expectedValue, value==nil?@"nil":value];
+ return (expectedValue == value || [expectedValue isEqual:value]);
+}
+
++(NSString *)valueAsString:(const void *)value objCType:(const char *)typeDescription {
+ if ('^' == *typeDescription) {
+ return @"nil";
+ }
+ if ('@' == *typeDescription) {
+ return [NSString stringWithFormat:@"%@", *(id *)value];
+ }
+ return [[NSNumber numberWithValue:value objCType:typeDescription] stringValue];
+}
+
+- (void)dealloc {
+ self.errorMessage = nil;
+ [super dealloc];
+}
+
+
+@end
View
50 Specs/Runner/UISpecRunner/UISpec.h
@@ -0,0 +1,50 @@
+//
+// UISpec.h
+// UISpec
+//
+// Created by Brian Knorr <btknorr@gmail.com>
+// Copyright(c) 2009 StarterStep, Inc., Some rights reserved.
+//
+
+@class UILog;
+
+@interface UISpec : NSObject {
+
+}
+
++(void)initialize;
++(void)runSpecsAfterDelay:(int)seconds;
++(void)runSpec:(NSString *)specName afterDelay:(int)seconds;
++(void)runSpec:(NSString *)specName example:(NSString *)exampleName afterDelay:(int)seconds;
++(void)runSpecs;
++(void)runSpec:(NSTimer *)timer;
++(void)runSpecExample:(NSTimer *)timer;
++(void)runSpecClasses:(NSArray *)specClasses;
++(void)runExamples:(NSArray *)examples onSpec:(Class *)class;
++(void)setLog:(UILog *)log;
++(NSDictionary *)specsAndExamples;
+
+/**
+ * Run all UISpec classes conforming to a given protocol
+ */
++(void)runSpecsConformingToProtocol:(Protocol *)protocol afterDelay:(NSTimeInterval)delay;
+
+/**
+ * Run all UISpec classes inheriting from a given base class
+ */
++(void)runSpecsInheritingFromClass:(Class)class afterDelay:(NSTimeInterval)delay;
+
+/**
+ * Infers which set of UISpec classes to run from the following environment variables:
+ * UISPEC_PROTOCOL - Specifies a protocol to run
+ * UISPEC_SPEC - Specifies a spec class to run
+ * UISPEC_METHOD - Specifies an example to run (requires UISPEC_SPEC to be set)
+ * UISPEC_EXIT_ON_FINISH - When YES, instructs UISpecRunner to terminate the application when specs run is complete
+ */
++(void)runSpecsFromEnvironmentAfterDelay:(int)seconds;
+
+@end
+
+@protocol UISpec
+@end
+
View
282 Specs/Runner/UISpecRunner/UISpec.m
@@ -0,0 +1,282 @@
+#import "UISpec.h"
+#import "objc/runtime.h"
+#import "UIConsoleLog.h"
+
+@implementation UISpec
+
+static UILog *logger = nil;
+
++(void)initialize {
+ logger = [[UIConsoleLog alloc] init];
+}
+
++(void)setLog:(UILog *)log{
+ [logger release];
+ logger = [log retain];
+}
+
++(void)runSpecsAfterDelay:(int)seconds {
+ [NSTimer scheduledTimerWithTimeInterval:seconds target:self selector:@selector(runSpecs) userInfo:nil repeats:NO];
+}
+
++(void)runSpec:(NSString *)specName afterDelay:(int)seconds {
+ [NSTimer scheduledTimerWithTimeInterval:seconds target:self selector:@selector(runSpec:) userInfo:specName repeats:NO];
+}
+
++(void)runSpec:(NSString *)specName example:(NSString *)exampleName afterDelay:(int)seconds {
+ [NSTimer scheduledTimerWithTimeInterval:seconds target:self selector:@selector(runSpecExample:) userInfo:[NSArray arrayWithObjects:specName, exampleName, nil] repeats:NO];
+}
+
++(void)runSpecs {
+ NSArray *specClasses = [self specClasses];
+ [self runSpecClasses:specClasses];
+}
+
++(void)runSpec:(NSTimer *)timer {
+ Class *class = NSClassFromString(timer.userInfo);
+ [self runSpecClasses:[NSArray arrayWithObject:class]];
+}
+
++(void)runSpecExample:(NSTimer *)timer {
+ Class *class = NSClassFromString([timer.userInfo objectAtIndex:0]);
+ NSString *exampleName = [timer.userInfo objectAtIndex:1];
+ [logger onStart];
+ [self runExamples:[NSArray arrayWithObject:exampleName] onSpec:class];
+ [logger onFinish:1];
+}
+
++(void)runSpecClasses:(NSArray *)specClasses {
+ if (specClasses.count == 0) return;
+ int examplesCount = 0;
+ [logger onStart];
+ for (Class class in specClasses) {
+ NSArray *examples = [self examplesForSpecClass:class];
+ if (examples.count == 0) continue;
+ examplesCount = examplesCount + examples.count;
+ [self runExamples:examples onSpec:class];
+ }
+ [logger onFinish:examplesCount];
+}
+
++(void)runExamples:(NSArray *)examples onSpec:(Class *)class {
+ UISpec *spec = [[[class alloc] init] autorelease];
+ [logger onSpec:spec];
+ if ([spec respondsToSelector:@selector(beforeAll)]) {
+ @try {
+ [logger onBeforeAll];
+ [spec beforeAll];
+ } @catch (NSException *exception) {
+ [logger onBeforeAllException:exception];
+ }
+ }
+ for (NSString *exampleName in [examples reverseObjectEnumerator]) {
+ if ([spec respondsToSelector:@selector(before)]) {
+ @try {
+ [logger onBefore:exampleName];
+ [spec before];
+ } @catch (NSException *exception) {
+ [logger onBeforeException:exception];
+ }
+ }
+ @try {
+ [logger onExample:exampleName];
+ [spec performSelector:NSSelectorFromString(exampleName)];
+ } @catch (NSException *exception) {
+ [logger onExampleException:exception];
+ }
+ if ([spec respondsToSelector:@selector(after)]) {
+ @try {
+ [logger onAfter:exampleName];
+ [spec after];
+ } @catch (NSException *exception) {
+ [logger onAfterException:exception];
+ }
+ }
+ }
+ if ([spec respondsToSelector:@selector(afterAll)]) {
+ @try {
+ [logger onAfterAll];
+ [spec afterAll];
+ } @catch (NSException *exception) {
+ [logger onAfterAllException:exception];
+ }
+ }
+ [logger afterSpec:spec];
+}
+
++(NSDictionary *)specsAndExamples {
+ NSArray *specClasses = [self specClasses];
+ NSMutableDictionary *specsAndExamples = [NSMutableDictionary dictionaryWithCapacity:[specClasses count]];
+ for (Class specClass in specClasses) {
+ NSArray *examples = [self examplesForSpecClass:specClass];
+ if ([examples count]) {
+ [specsAndExamples addObject:examples forKey:NSStringFromClass(specClass)];
+ }
+ }
+ return specsAndExamples;
+}
+
++(NSArray *)examplesForSpecClass:(Class *)specClass {
+ NSMutableArray *array = [NSMutableArray array];
+ unsigned int methodCount;
+ Method *methods = class_copyMethodList(specClass, &methodCount);
+ for (size_t i = 0; i < methodCount; ++i) {
+ Method method = methods[i];
+ SEL selector = method_getName(method);
+ NSString *selectorName = NSStringFromSelector(selector);
+ if ([selectorName hasPrefix:@"it"]) {
+ [array addObject:selectorName];
+ }
+ }
+ return array;
+}
+
++(BOOL)isASpec:(Class)class {
+ //Class spec = NSClassFromString(@"UISpec");
+ while (class) {
+ if (class_conformsToProtocol(class, NSProtocolFromString(@"UISpec"))) {
+ return YES;
+ }
+ class = class_getSuperclass(class);
+ }
+ return NO;
+}
+
++(NSArray*)specClasses {
+ NSMutableArray *array = [NSMutableArray array];
+ int numClasses = objc_getClassList(NULL, 0);
+ if (numClasses > 0) {
+ Class *classes = malloc(sizeof(Class) * numClasses);
+ (void) objc_getClassList (classes, numClasses);
+ int i;
+ for (i = 0; i < numClasses; i++) {
+ Class *c = classes[i];
+ if ([self isASpec:c]) {
+ [array addObject:c];
+ }
+ }
+ free(classes);
+ }
+ return array;
+}
+
++(void)swizzleMethodOnClass:(Class)targetClass originalSelector:(SEL)originalSelector fromClass:(Class)fromClass alternateSelector:(SEL)alternateSelector {
+ Method originalMethod = nil, alternateMethod = nil;
+
+ // First, look for the methods
+ originalMethod = class_getInstanceMethod(targetClass, originalSelector);
+ alternateMethod = class_getInstanceMethod(fromClass, alternateSelector);
+
+ // If both are found, swizzle them
+ if (originalMethod != nil && alternateMethod != nil) {
+ IMP originalImplementation = method_getImplementation(originalMethod);
+ IMP alternateImplementation = method_getImplementation(alternateMethod);
+ method_setImplementation(originalMethod, alternateImplementation);
+ method_setImplementation(alternateMethod, originalImplementation);
+ }
+}
+
++(BOOL)class:(Class)class conformsToProtocol:(Protocol *)protocol {
+ while (class) {
+ if (class_conformsToProtocol(class, protocol)) {
+ return YES;
+ }
+ class = class_getSuperclass(class);
+ }
+ return NO;
+}
+
++(NSArray*)specClassesConformingToProtocol:(Protocol *)protocol {
+ NSMutableArray *array = [NSMutableArray array];
+ int numClasses = objc_getClassList(NULL, 0);
+ if (numClasses > 0) {
+ Class *classes = malloc(sizeof(Class) * numClasses);
+ (void) objc_getClassList (classes, numClasses);
+ int i;
+ for (i = 0; i < numClasses; i++) {
+ Class c = classes[i];
+ if ([self isASpec:c] && [self class:c conformsToProtocol:protocol]) {
+ [array addObject:c];
+ }
+ }
+ free(classes);
+ }
+ return array;
+}
+
++(void)runSpecsConformingToProtocol:(Protocol *)protocol afterDelay:(NSTimeInterval)delay {
+ NSArray* specClasses = [self specClassesConformingToProtocol:protocol];
+ [self performSelector:@selector(runSpecClasses:) withObject:specClasses afterDelay:delay];
+}
+
++(NSArray*)specClassesInheritingFromClass:(Class)parentClass {
+ int numClasses = objc_getClassList(NULL, 0);
+ Class *classes = NULL;
+
+ classes = malloc(sizeof(Class) * numClasses);
+ numClasses = objc_getClassList(classes, numClasses);
+
+ NSMutableArray *result = [NSMutableArray arrayWithObject:parentClass];
+ for (NSInteger i = 0; i < numClasses; i++)
+ {
+ Class superClass = classes[i];
+ do
+ {
+ superClass = class_getSuperclass(superClass);
+ } while(superClass && superClass != parentClass);
+
+ if (superClass == nil)
+ {
+ continue;
+ }
+
+ if ([self isASpec:classes[i]]) {
+ [result addObject:classes[i]];
+ }
+ }
+
+ free(classes);
+
+ return result;
+}
+
++(void)runSpecsInheritingFromClass:(Class)class afterDelay:(NSTimeInterval)delay {
+ NSArray* specClasses = [self specClassesInheritingFromClass:class];
+ NSLog(@"Executing Specs: %@", specClasses);
+ [self performSelector:@selector(runSpecClasses:) withObject:specClasses afterDelay:delay];
+}
+
++(void)runSpecsFromEnvironmentAfterDelay:(int)seconds {
+ char* protocolName = getenv("UISPEC_PROTOCOL");
+ char* specName = getenv("UISPEC_SPEC");
+ char* exampleName = getenv("UISPEC_EXAMPLE");
+ char* exitOnFinish = getenv("UISPEC_EXIT_ON_FINISH");
+
+ UIConsoleLog* log = [[UIConsoleLog alloc] init];
+ [UISpec setLog:(UILog*)log];
+
+ if (NULL == exitOnFinish || [[NSString stringWithUTF8String:exitOnFinish] isEqualToString:@"YES"]) {
+ log.exitOnFinish = YES;
+ }
+
+ if (protocolName) {
+ Protocol* protocol = NSProtocolFromString([NSString stringWithUTF8String:protocolName]);
+ NSLog(@"[UISpecRunner] Running Specs conforming to Protocol: %@", [NSString stringWithUTF8String:protocolName]);
+ [UISpec runSpecsConformingToProtocol:protocol afterDelay:seconds];
+ } else if (exampleName) {
+ if (nil == specName) {
+ [NSException raise:nil format:@"UISPEC_EXAMPLE cannot be specified without providing UISPEC_SPEC"];
+ }
+ NSLog(@"[UISpecRunner] Running Examples %s on Spec %s", exampleName, specName);
+ [UISpec runSpec:[NSString stringWithUTF8String:specName] example:[NSString stringWithUTF8String:exampleName] afterDelay:seconds];
+ } else if (specName) {
+ NSLog(@"[UISpecRunner] Running Spec classes inheriting from %s", specName);
+ Class class = NSClassFromString([NSString stringWithUTF8String:specName]);
+ [UISpec runSpecsInheritingFromClass:class afterDelay:seconds];
+ } else {
+ [UISpec runSpecsAfterDelay:seconds];
+ }
+}
+
+
+@end

0 comments on commit 205f114

Please sign in to comment.