diff options
-rw-r--r-- | Foundation/GTMObjC2Runtime.h | 1 | ||||
-rw-r--r-- | Foundation/GTMObjC2Runtime.m | 4 | ||||
-rw-r--r-- | Foundation/GTMObjC2RuntimeTest.m | 9 | ||||
-rw-r--r-- | UnitTesting/GTMSenTestCase.h | 23 | ||||
-rw-r--r-- | UnitTesting/GTMSenTestCase.m | 31 | ||||
-rw-r--r-- | UnitTesting/GTMUnitTestingTest.m | 13 |
6 files changed, 80 insertions, 1 deletions
diff --git a/Foundation/GTMObjC2Runtime.h b/Foundation/GTMObjC2Runtime.h index 4f569a5..9f5afa0 100644 --- a/Foundation/GTMObjC2Runtime.h +++ b/Foundation/GTMObjC2Runtime.h @@ -55,6 +55,7 @@ OBJC_EXPORT Class object_getClass(id obj); OBJC_EXPORT const char *class_getName(Class cls); OBJC_EXPORT BOOL class_conformsToProtocol(Class cls, Protocol *protocol); +OBJC_EXPORT BOOL class_respondsToSelector(Class cls, SEL sel); OBJC_EXPORT Class class_getSuperclass(Class cls); OBJC_EXPORT Method *class_copyMethodList(Class cls, unsigned int *outCount); OBJC_EXPORT SEL method_getName(Method m); diff --git a/Foundation/GTMObjC2Runtime.m b/Foundation/GTMObjC2Runtime.m index 9835654..2eb5dc3 100644 --- a/Foundation/GTMObjC2Runtime.m +++ b/Foundation/GTMObjC2Runtime.m @@ -58,6 +58,10 @@ Class class_getSuperclass(Class cls) { return cls->super_class; } +BOOL class_respondsToSelector(Class cls, SEL sel) { + return class_getInstanceMethod(cls, sel) != nil; +} + Method *class_copyMethodList(Class cls, unsigned int *outCount) { if (!cls) return NULL; diff --git a/Foundation/GTMObjC2RuntimeTest.m b/Foundation/GTMObjC2RuntimeTest.m index 7344a0f..7a88f23 100644 --- a/Foundation/GTMObjC2RuntimeTest.m +++ b/Foundation/GTMObjC2RuntimeTest.m @@ -160,6 +160,15 @@ AT_REQUIRED nil); } +- (void)test_class_respondsToSelector { + // Nil Checks + STAssertFalse(class_respondsToSelector(cls_, @selector(setUp)), nil); + STAssertFalse(class_respondsToSelector(cls_, nil), nil); + + // Standard use check + STAssertTrue(class_respondsToSelector(cls_, @selector(kwyjibo)), nil); +} + - (void)test_class_getSuperclass { // Nil Checks STAssertNil(class_getSuperclass(nil), nil); diff --git a/UnitTesting/GTMSenTestCase.h b/UnitTesting/GTMSenTestCase.h index 9837b2b..58c0594 100644 --- a/UnitTesting/GTMSenTestCase.h +++ b/UnitTesting/GTMSenTestCase.h @@ -1008,4 +1008,27 @@ GTM_EXTERN NSString *const SenTestLineNumberKey; // to set up our logging system correctly to verify logging calls. // See GTMUnitTestDevLog.h for details @interface GTMTestCase : SenTestCase + +// Returns YES if this is an abstract testCase class as opposed to a concrete +// testCase class that you want tests run against. SenTestCase is not designed +// out of the box to handle an abstract class hierarchy descending from it with +// some concrete subclasses. In some cases we want all the "concrete" +// subclasses of an abstract subclass of SenTestCase to run a test, but we don't +// want that test to be run against an instance of an abstract subclass itself. +// By returning "YES" here, the tests defined by this class won't be run against +// an instance of this class. As an example class hierarchy: +// +// FooExtensionTestCase +// GTMTestCase <- ExtensionTestCase < +// BarExtensionTestCase +// +// So FooExtensionTestCase and BarExtensionTestCase inherit from +// ExtensionTestCase (and probably FooExtension and BarExtension inherit from a +// class named Extension). We want the tests in ExtensionTestCase to be run as +// part of FooExtensionTestCase and BarExtensionTestCase, but we don't want them +// run against ExtensionTestCase. The default implementation of +// isAbstractTestCase returns NO if self (being a class) has no subclasses and +// YES otherwise. ++ (BOOL)isAbstractTestCase; + @end diff --git a/UnitTesting/GTMSenTestCase.m b/UnitTesting/GTMSenTestCase.m index 99b9db0..ec5ef62 100644 --- a/UnitTesting/GTMSenTestCase.m +++ b/UnitTesting/GTMSenTestCase.m @@ -18,6 +18,7 @@ #import "GTMSenTestCase.h" #import <unistd.h> +#import "GTMObjC2Runtime.h" #if !GTM_IPHONE_SDK #import "GTMGarbageCollection.h" @@ -305,6 +306,36 @@ NSString *const SenTestLineNumberKey = @"SenTestLineNumberKey"; [devLogClass performSelector:@selector(disableTracking)]; } } + ++ (BOOL)isAbstractTestCase { + int numClasses = objc_getClassList(NULL, 0); + BOOL isAbstract = NO; + if (numClasses > 0) { + Class *classes = malloc(sizeof(Class) * numClasses); + numClasses = objc_getClassList(classes, numClasses); + for (int i = 0; i < numClasses && !isAbstract; ++i) { + Class cls = classes[i]; + if (class_respondsToSelector(cls, @selector(superclass))) { + Class superClass = [cls superclass]; + if ([self isEqualTo:superClass]) { + isAbstract = YES; + } + } + } + free(classes); + } + return isAbstract; +} + + ++ (NSArray *)testInvocations { + NSArray *invocations = nil; + if (![self isAbstractTestCase]) { + invocations = [super testInvocations]; + } + return invocations; +} + @end // Leak detection diff --git a/UnitTesting/GTMUnitTestingTest.m b/UnitTesting/GTMUnitTestingTest.m index 72e6880..cd00b3e 100644 --- a/UnitTesting/GTMUnitTestingTest.m +++ b/UnitTesting/GTMUnitTestingTest.m @@ -23,7 +23,10 @@ NSString *const kGTMWindowNibName = @"GTMUnitTestingTest"; NSString *const kGTMWindowSaveFileName = @"GTMUnitTestingWindow"; -@interface GTMUnitTestingTest : GTMTestCase { +@interface GTMAbstractUnitTestingTest : GTMTestCase +@end + +@interface GTMUnitTestingTest : GTMAbstractUnitTestingTest { int expectedFailureCount_; } @end @@ -44,6 +47,14 @@ NSString *const kGTMWindowSaveFileName = @"GTMUnitTestingWindow"; @interface GTMUnitTestingProxyTest : NSProxy @end +@implementation GTMAbstractUnitTestingTest +- (void)testAbstractUnitTest { + static int testCount = 0; + testCount += 1; + STAssertEquals(testCount, 1, @"testAbstractUnitTest should only fire once"); +} +@end + @implementation GTMUnitTestingTest // Brings up the window defined in the nib and takes a snapshot of it. |