From c83cd11615dc48188882a6cbe7c15c05c172d66e Mon Sep 17 00:00:00 2001 From: "qsr@chromium.org" Date: Fri, 21 Sep 2012 07:55:17 +0000 Subject: Update GTM files to latest from http://google-toolbox-for-mac.googlecode.com/svn/trunk/ Patch by: jakerr@google.com Review: https://breakpad.appspot.com/452002/ git-svn-id: http://google-breakpad.googlecode.com/svn/trunk@1045 4c0a9323-5329-0410-9bdc-e9ce6186880e --- src/common/mac/testing/GTMSenTestCase.m | 174 ++++++++++++++++++++++++++++---- 1 file changed, 154 insertions(+), 20 deletions(-) (limited to 'src/common/mac/testing/GTMSenTestCase.m') diff --git a/src/common/mac/testing/GTMSenTestCase.m b/src/common/mac/testing/GTMSenTestCase.m index 99b9db07..5607c316 100644 --- a/src/common/mac/testing/GTMSenTestCase.m +++ b/src/common/mac/testing/GTMSenTestCase.m @@ -17,13 +17,20 @@ // #import "GTMSenTestCase.h" + #import +#if GTM_IPHONE_SIMULATOR +#import +#endif + +#import "GTMObjC2Runtime.h" +#import "GTMUnitTestDevLog.h" #if !GTM_IPHONE_SDK #import "GTMGarbageCollection.h" #endif // !GTM_IPHONE_SDK -#if GTM_IPHONE_SDK +#if GTM_IPHONE_SDK && !GTM_IPHONE_USE_SENTEST #import @interface NSException (GTMSenTestPrivateAdditions) @@ -84,7 +91,7 @@ } NSString *reason = [NSString stringWithFormat:@"'%@' should be %s. %@", - condition, isTrue ? "TRUE" : "FALSE", testDescription]; + condition, isTrue ? "false" : "true", testDescription]; return [self failureInFile:filename atLine:lineNumber reason:reason]; } @@ -213,6 +220,22 @@ NSString *const SenTestLineNumberKey = @"SenTestLineNumberKey"; @end @implementation SenTestCase ++ (id)testCaseWithInvocation:(NSInvocation *)anInvocation { + return [[[self 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]; } @@ -220,17 +243,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]; @@ -261,7 +291,17 @@ NSString *const SenTestLineNumberKey = @"SenTestLineNumberKey"; @try { [self setUp]; @try { - [self performSelector:currentSelector_]; + NSInvocation *invocation = [self invocation]; +#if GTM_IPHONE_SIMULATOR + // We don't call [invocation invokeWithTarget:self]; because of + // Radar 8081169: NSInvalidArgumentException can't be caught + // It turns out that on iOS4 (and 3.2) exceptions thrown inside an + // [invocation invoke] on the simulator cannot be caught. + // http://openradar.appspot.com/8081169 + objc_msgSend(self, [invocation selector]); +#else + [invocation invokeWithTarget:self]; +#endif } @catch (NSException *exception) { e = [exception retain]; } @@ -284,15 +324,84 @@ NSString *const SenTestLineNumberKey = @"SenTestLineNumberKey"; - (NSString *)description { // This matches the description OCUnit would return to you - return [NSString stringWithFormat:@"-[%@ %@]", [self class], - NSStringFromSelector(currentSelector_)]; + return [NSString stringWithFormat:@"-[%@ %@]", [self class], + NSStringFromSelector([self selector])]; +} + +// Used for sorting methods below +static int MethodSort(id a, id b, void *context) { + NSInvocation *invocationA = a; + NSInvocation *invocationB = b; + const char *nameA = sel_getName([invocationA selector]); + const char *nameB = sel_getName([invocationB selector]); + return strcmp(nameA, nameB); } + + ++ (NSArray *)testInvocations { + NSMutableArray *invocations = nil; + // Need to walk all the way up the parent classes collecting methods (in case + // a test is a subclass of another test). + Class senTestCaseClass = [SenTestCase class]; + for (Class currentClass = self; + currentClass && (currentClass != senTestCaseClass); + currentClass = class_getSuperclass(currentClass)) { + unsigned int methodCount; + Method *methods = class_copyMethodList(currentClass, &methodCount); + if (methods) { + // This handles disposing of methods for us even if an exception should fly. + [NSData dataWithBytesNoCopy:methods + length:sizeof(Method) * methodCount]; + if (!invocations) { + 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]; + } + } + // TODO: If a test class is a subclass of another, and they reuse the + // same selector name (ie-subclass overrides it), this current loop + // and test here will cause cause it to get invoked twice. To fix this + // the selector would have to be checked against all the ones already + // added, so it only gets done once. + 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]; + } + } + } + } + // Match SenTestKit and run everything in alphbetical order. + [invocations sortUsingFunction:MethodSort context:nil]; + return invocations; +} + @end -#endif // GTM_IPHONE_SDK +#endif // GTM_IPHONE_SDK && !GTM_IPHONE_USE_SENTEST @implementation GTMTestCase : SenTestCase - (void)invokeTest { + NSAutoreleasePool *localPool = [[NSAutoreleasePool alloc] init]; Class devLogClass = NSClassFromString(@"GTMUnitTestDevLog"); if (devLogClass) { [devLogClass performSelector:@selector(enableTracking)]; @@ -304,19 +413,34 @@ NSString *const SenTestLineNumberKey = @"SenTestLineNumberKey"; [devLogClass performSelector:@selector(verifyNoMoreLogsExpected)]; [devLogClass performSelector:@selector(disableTracking)]; } + [localPool drain]; } + ++ (BOOL)isAbstractTestCase { + NSString *name = NSStringFromClass(self); + return [name rangeOfString:@"AbstractTest"].location != NSNotFound; +} + ++ (NSArray *)testInvocations { + NSArray *invocations = nil; + if (![self isAbstractTestCase]) { + invocations = [super testInvocations]; + } + return invocations; +} + @end // Leak detection -#if !GTM_IPHONE_DEVICE +#if !GTM_IPHONE_DEVICE && !GTM_SUPPRESS_RUN_LEAKS_HOOK // Don't want to get leaks on the iPhone Device as the device doesn't // have 'leaks'. The simulator does though. // COV_NF_START // We don't have leak checking on by default, so this won't be hit. static void _GTMRunLeaks(void) { - // This is an atexit handler. It runs leaks for us to check if we are - // leaking anything in our tests. + // This is an atexit handler. It runs leaks for us to check if we are + // leaking anything in our tests. const char* cExclusionsEnv = getenv("GTM_LEAKS_SYMBOLS_TO_IGNORE"); NSMutableString *exclusions = [NSMutableString string]; if (cExclusionsEnv) { @@ -329,13 +453,21 @@ static void _GTMRunLeaks(void) { [exclusions appendFormat:@"-exclude \"%@\" ", exclusion]; } } - NSString *string - = [NSString stringWithFormat:@"/usr/bin/leaks %@%d" - @"| /usr/bin/sed -e 's/Leak: /Leaks:0: warning: Leak /'", + // Clearing out DYLD_ROOT_PATH because iPhone Simulator framework libraries + // are different from regular OS X libraries and leaks will fail to run + // because of missing symbols. Also capturing the output of leaks and then + // pipe rather than a direct pipe, because otherwise if leaks failed, + // the system() call will still be successful. Bug: + // http://code.google.com/p/google-toolbox-for-mac/issues/detail?id=56 + NSString *string + = [NSString stringWithFormat: + @"LeakOut=`DYLD_ROOT_PATH='' /usr/bin/leaks %@%d` &&" + @"echo \"$LeakOut\"|/usr/bin/sed -e 's/Leak: /Leaks:0: warning: Leak /'", exclusions, getpid()]; int ret = system([string UTF8String]); if (ret) { - fprintf(stderr, "%s:%d: Error: Unable to run leaks. 'system' returned: %d", + fprintf(stderr, + "%s:%d: Error: Unable to run leaks. 'system' returned: %d\n", __FILE__, __LINE__, ret); fflush(stderr); } @@ -355,12 +487,14 @@ static __attribute__((constructor)) void _GTMInstallLeaks(void) { fprintf(stderr, "Leak Checking Enabled\n"); fflush(stderr); int ret = atexit(&_GTMRunLeaks); - _GTMDevAssert(ret == 0, - @"Unable to install _GTMRunLeaks as an atexit handler (%d)", + // To avoid unused variable warning when _GTMDevAssert is stripped. + (void)ret; + _GTMDevAssert(ret == 0, + @"Unable to install _GTMRunLeaks as an atexit handler (%d)", errno); // COV_NF_END - } + } } } -#endif // !GTM_IPHONE_DEVICE +#endif // !GTM_IPHONE_DEVICE && !GTM_SUPPRESS_RUN_LEAKS_HOOK -- cgit v1.2.1