aboutsummaryrefslogtreecommitdiff
path: root/UnitTesting
diff options
context:
space:
mode:
authorGravatar gtm.daemon <gtm.daemon@7dc7ac4e-7543-0410-b95c-c1676fc8e2a3>2009-09-16 20:30:39 +0000
committerGravatar gtm.daemon <gtm.daemon@7dc7ac4e-7543-0410-b95c-c1676fc8e2a3>2009-09-16 20:30:39 +0000
commita6af758d94658190533c50e1d25c0d34997b5561 (patch)
treef7c2ec20316a0fc87120d6c67d58d36cf750a269 /UnitTesting
parent4d9b07dbd57a18008eb239a33021dbf868ceef28 (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.m89
-rw-r--r--UnitTesting/GTMSenTestCase.h18
-rw-r--r--UnitTesting/GTMSenTestCase.m94
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;
}