diff options
author | gtm.daemon <gtm.daemon@7dc7ac4e-7543-0410-b95c-c1676fc8e2a3> | 2009-09-16 20:30:39 +0000 |
---|---|---|
committer | gtm.daemon <gtm.daemon@7dc7ac4e-7543-0410-b95c-c1676fc8e2a3> | 2009-09-16 20:30:39 +0000 |
commit | a6af758d94658190533c50e1d25c0d34997b5561 (patch) | |
tree | f7c2ec20316a0fc87120d6c67d58d36cf750a269 /UnitTesting | |
parent | 4d9b07dbd57a18008eb239a33021dbf868ceef28 (diff) |
[Author: dmaclach]
Fix up the unit testing stack that I broke on iPhone. This makes the unittesting stuff on iPhone
much closer to the original SenTestCase design.
R=thomasvl,altse
DELTA=180 (96 added, 61 deleted, 23 changed)
Diffstat (limited to 'UnitTesting')
-rw-r--r-- | UnitTesting/GTMIPhoneUnitTestDelegate.m | 89 | ||||
-rw-r--r-- | UnitTesting/GTMSenTestCase.h | 18 | ||||
-rw-r--r-- | UnitTesting/GTMSenTestCase.m | 94 |
3 files changed, 118 insertions, 83 deletions
diff --git a/UnitTesting/GTMIPhoneUnitTestDelegate.m b/UnitTesting/GTMIPhoneUnitTestDelegate.m index 39de1f5..037357b 100644 --- a/UnitTesting/GTMIPhoneUnitTestDelegate.m +++ b/UnitTesting/GTMIPhoneUnitTestDelegate.m @@ -27,26 +27,6 @@ #import <UIKit/UIKit.h> #import "GTMSenTestCase.h" -// Used for sorting methods below -static int MethodSort(const void *a, const void *b) { - const char *nameA = sel_getName(method_getName(*(Method*)a)); - const char *nameB = sel_getName(method_getName(*(Method*)b)); - return strcmp(nameA, nameB); -} - -// Return YES if class is subclass (1 or more generations) of SenTestCase -static BOOL IsTestFixture(Class aClass) { - BOOL iscase = NO; - Class testCaseClass = [SenTestCase class]; - Class superclass; - for (superclass = aClass; - !iscase && superclass; - superclass = class_getSuperclass(superclass)) { - iscase = superclass == testCaseClass ? YES : NO; - } - return iscase; -} - @implementation GTMIPhoneUnitTestDelegate // Run through all the registered classes and run test methods on any @@ -83,8 +63,10 @@ static BOOL IsTestFixture(Class aClass) { fputs([suiteStartString UTF8String], stderr); fflush(stderr); for (int i = 0; i < count; ++i) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; Class currClass = classes[i]; - if (IsTestFixture(currClass)) { + if (class_respondsToSelector(currClass, @selector(conformsToProtocol:)) && + [currClass conformsToProtocol:@protocol(SenTestCase)]) { NSDate *fixtureStartDate = [NSDate date]; NSString *fixtureName = NSStringFromClass(currClass); NSString *fixtureStartString @@ -94,53 +76,16 @@ static BOOL IsTestFixture(Class aClass) { int fixtureFailures = 0; fputs([fixtureStartString UTF8String], stderr); fflush(stderr); - id testcase = [[currClass alloc] init]; - _GTMDevAssert(testcase, @"Unable to instantiate Test Suite: '%@'\n", - fixtureName); - unsigned int methodCount; - Method *methods = class_copyMethodList(currClass, &methodCount); - if (!methods) { - // If the class contains no methods, head on to the next class - NSString *output = [NSString stringWithFormat:@"Test Suite '%@' " - @"finished at %@.\nExecuted 0 tests, with 0 " - @"failures (0 unexpected) in 0 (0) seconds\n", - fixtureName, fixtureStartDate]; - - fputs([output UTF8String], stderr); - continue; - } - // This handles disposing of methods for us even if an - // exception should fly. - [NSData dataWithBytesNoCopy:methods - length:sizeof(Method) * methodCount]; - // Sort our methods so they are called in Alphabetical order just - // because we can. - qsort(methods, methodCount, sizeof(Method), MethodSort); - for (size_t j = 0; j < methodCount; ++j) { - Method currMethod = methods[j]; - SEL sel = method_getName(currMethod); - char *returnType = NULL; - const char *name = sel_getName(sel); - // If it starts with test, takes 2 args (target and sel) and returns - // void run it. - if (strstr(name, "test") == name) { - returnType = method_copyReturnType(currMethod); - if (returnType) { - // This handles disposing of returnType for us even if an - // exception should fly. Length +1 for the terminator, not that - // the length really matters here, as we never reference inside - // the data block. - [NSData dataWithBytesNoCopy:returnType - length:strlen(returnType) + 1]; - } - } - if (returnType // True if name starts with "test" - && strcmp(returnType, @encode(void)) == 0 - && method_getNumberOfArguments(currMethod) == 2) { + NSArray *invocations = [currClass testInvocations]; + if ([invocations count]) { + NSInvocation *invocation; + GTM_FOREACH_OBJECT(invocation, invocations) { + GTMTestCase *testCase + = [[currClass alloc] initWithInvocation:invocation]; BOOL failed = NO; NSDate *caseStartDate = [NSDate date]; @try { - [testcase performTest:sel]; + [testCase performTest]; } @catch (NSException *exception) { failed = YES; } @@ -151,17 +96,18 @@ static BOOL IsTestFixture(Class aClass) { } NSTimeInterval caseEndTime = [[NSDate date] timeIntervalSinceDate:caseStartDate]; + NSString *selectorName = NSStringFromSelector([invocation selector]); NSString *caseEndString - = [NSString stringWithFormat:@"Test Case '-[%@ %s]' %@ (%0.3f " - @"seconds).\n", - fixtureName, name, - failed ? @"failed" : @"passed", - caseEndTime]; + = [NSString stringWithFormat:@"Test Case '-[%@ %@]' %@ (%0.3f " + @"seconds).\n", + fixtureName, selectorName, + failed ? @"failed" : @"passed", + caseEndTime]; fputs([caseEndString UTF8String], stderr); fflush(stderr); + [testCase release]; } } - [testcase release]; NSDate *fixtureEndDate = [NSDate date]; NSTimeInterval fixtureEndTime = [fixtureEndDate timeIntervalSinceDate:fixtureStartDate]; @@ -179,6 +125,7 @@ static BOOL IsTestFixture(Class aClass) { totalSuccesses_ += fixtureSuccesses; totalFailures_ += fixtureFailures; } + [pool release]; } NSDate *suiteEndDate = [NSDate date]; NSTimeInterval suiteEndTime diff --git a/UnitTesting/GTMSenTestCase.h b/UnitTesting/GTMSenTestCase.h index c45146e..4f30a08 100644 --- a/UnitTesting/GTMSenTestCase.h +++ b/UnitTesting/GTMSenTestCase.h @@ -986,15 +986,23 @@ do { \ // SENTE_END -@interface SenTestCase : NSObject { - SEL currentSelector_; -} - +@protocol SenTestCase ++ (id)testCaseWithInvocation:(NSInvocation *)anInvocation; +- (id)initWithInvocation:(NSInvocation *)anInvocation; - (void)setUp; - (void)invokeTest; - (void)tearDown; -- (void)performTest:(SEL)sel; +- (void)performTest; - (void)failWithException:(NSException*)exception; +- (NSInvocation *)invocation; +- (SEL)selector; ++ (NSArray *)testInvocations; +@end + +@interface SenTestCase : NSObject<SenTestCase> { + @private + NSInvocation *invocation_; +} @end GTM_EXTERN NSString *const SenTestFailureException; diff --git a/UnitTesting/GTMSenTestCase.m b/UnitTesting/GTMSenTestCase.m index ec5ef62..a6d8aeb 100644 --- a/UnitTesting/GTMSenTestCase.m +++ b/UnitTesting/GTMSenTestCase.m @@ -214,6 +214,22 @@ NSString *const SenTestLineNumberKey = @"SenTestLineNumberKey"; @end @implementation SenTestCase ++ (id)testCaseWithInvocation:(NSInvocation *)anInvocation { + return [[[[self class] alloc] initWithInvocation:anInvocation] autorelease]; +} + +- (id)initWithInvocation:(NSInvocation *)anInvocation { + if ((self = [super init])) { + invocation_ = [anInvocation retain]; + } + return self; +} + +- (void)dealloc { + [invocation_ release]; + [super dealloc]; +} + - (void)failWithException:(NSException*)exception { [exception raise]; } @@ -221,17 +237,24 @@ NSString *const SenTestLineNumberKey = @"SenTestLineNumberKey"; - (void)setUp { } -- (void)performTest:(SEL)sel { - currentSelector_ = sel; +- (void)performTest { @try { [self invokeTest]; } @catch (NSException *exception) { [[self class] printException:exception - fromTestName:NSStringFromSelector(sel)]; + fromTestName:NSStringFromSelector([self selector])]; [exception raise]; } } +- (NSInvocation *)invocation { + return invocation_; +} + +- (SEL)selector { + return [invocation_ selector]; +} + + (void)printException:(NSException *)exception fromTestName:(NSString *)name { NSDictionary *userInfo = [exception userInfo]; NSString *filename = [userInfo objectForKey:SenTestFilenameKey]; @@ -262,7 +285,9 @@ NSString *const SenTestLineNumberKey = @"SenTestLineNumberKey"; @try { [self setUp]; @try { - [self performSelector:currentSelector_]; + NSInvocation *invocation = [self invocation]; + [invocation setTarget:self]; + [invocation invoke]; } @catch (NSException *exception) { e = [exception retain]; } @@ -286,8 +311,60 @@ NSString *const SenTestLineNumberKey = @"SenTestLineNumberKey"; - (NSString *)description { // This matches the description OCUnit would return to you return [NSString stringWithFormat:@"-[%@ %@]", [self class], - NSStringFromSelector(currentSelector_)]; + NSStringFromSelector([self selector])]; } + +// Used for sorting methods below +static int MethodSort(const void *a, const void *b) { + const char *nameA = sel_getName(method_getName(*(Method*)a)); + const char *nameB = sel_getName(method_getName(*(Method*)b)); + return strcmp(nameA, nameB); +} + ++ (NSArray *)testInvocations { + NSMutableArray *invocations = nil; + unsigned int methodCount; + Method *methods = class_copyMethodList(self, &methodCount); + if (methods) { + // This handles disposing of methods for us even if an exception should fly. + [NSData dataWithBytesNoCopy:methods + length:sizeof(Method) * methodCount]; + // Sort our methods so they are called in Alphabetical order just + // because we can. + qsort(methods, methodCount, sizeof(Method), MethodSort); + invocations = [NSMutableArray arrayWithCapacity:methodCount]; + for (size_t i = 0; i < methodCount; ++i) { + Method currMethod = methods[i]; + SEL sel = method_getName(currMethod); + char *returnType = NULL; + const char *name = sel_getName(sel); + // If it starts with test, takes 2 args (target and sel) and returns + // void run it. + if (strstr(name, "test") == name) { + returnType = method_copyReturnType(currMethod); + if (returnType) { + // This handles disposing of returnType for us even if an + // exception should fly. Length +1 for the terminator, not that + // the length really matters here, as we never reference inside + // the data block. + [NSData dataWithBytesNoCopy:returnType + length:strlen(returnType) + 1]; + } + } + if (returnType // True if name starts with "test" + && strcmp(returnType, @encode(void)) == 0 + && method_getNumberOfArguments(currMethod) == 2) { + NSMethodSignature *sig = [self instanceMethodSignatureForSelector:sel]; + NSInvocation *invocation + = [NSInvocation invocationWithMethodSignature:sig]; + [invocation setSelector:sel]; + [invocations addObject:invocation]; + } + } + } + return invocations; +} + @end #endif // GTM_IPHONE_SDK @@ -311,7 +388,11 @@ NSString *const SenTestLineNumberKey = @"SenTestLineNumberKey"; int numClasses = objc_getClassList(NULL, 0); BOOL isAbstract = NO; if (numClasses > 0) { - Class *classes = malloc(sizeof(Class) * numClasses); + size_t size = sizeof(Class) * numClasses; + Class *classes = malloc(size); + // This handles disposing of classes for us even if an exception should fly. + [NSData dataWithBytesNoCopy:classes + length:size]; numClasses = objc_getClassList(classes, numClasses); for (int i = 0; i < numClasses && !isAbstract; ++i) { Class cls = classes[i]; @@ -322,7 +403,6 @@ NSString *const SenTestLineNumberKey = @"SenTestLineNumberKey"; } } } - free(classes); } return isAbstract; } |