1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/toolkit/crashreporter/google-breakpad/src/common/mac/testing/GTMSenTestCase.m Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,500 @@ 1.4 +// 1.5 +// GTMSenTestCase.m 1.6 +// 1.7 +// Copyright 2007-2008 Google Inc. 1.8 +// 1.9 +// Licensed under the Apache License, Version 2.0 (the "License"); you may not 1.10 +// use this file except in compliance with the License. You may obtain a copy 1.11 +// of the License at 1.12 +// 1.13 +// http://www.apache.org/licenses/LICENSE-2.0 1.14 +// 1.15 +// Unless required by applicable law or agreed to in writing, software 1.16 +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 1.17 +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 1.18 +// License for the specific language governing permissions and limitations under 1.19 +// the License. 1.20 +// 1.21 + 1.22 +#import "GTMSenTestCase.h" 1.23 + 1.24 +#import <unistd.h> 1.25 +#if GTM_IPHONE_SIMULATOR 1.26 +#import <objc/message.h> 1.27 +#endif 1.28 + 1.29 +#import "GTMObjC2Runtime.h" 1.30 +#import "GTMUnitTestDevLog.h" 1.31 + 1.32 +#if !GTM_IPHONE_SDK 1.33 +#import "GTMGarbageCollection.h" 1.34 +#endif // !GTM_IPHONE_SDK 1.35 + 1.36 +#if GTM_IPHONE_SDK && !GTM_IPHONE_USE_SENTEST 1.37 +#import <stdarg.h> 1.38 + 1.39 +@interface NSException (GTMSenTestPrivateAdditions) 1.40 ++ (NSException *)failureInFile:(NSString *)filename 1.41 + atLine:(int)lineNumber 1.42 + reason:(NSString *)reason; 1.43 +@end 1.44 + 1.45 +@implementation NSException (GTMSenTestPrivateAdditions) 1.46 ++ (NSException *)failureInFile:(NSString *)filename 1.47 + atLine:(int)lineNumber 1.48 + reason:(NSString *)reason { 1.49 + NSDictionary *userInfo = 1.50 + [NSDictionary dictionaryWithObjectsAndKeys: 1.51 + [NSNumber numberWithInteger:lineNumber], SenTestLineNumberKey, 1.52 + filename, SenTestFilenameKey, 1.53 + nil]; 1.54 + 1.55 + return [self exceptionWithName:SenTestFailureException 1.56 + reason:reason 1.57 + userInfo:userInfo]; 1.58 +} 1.59 +@end 1.60 + 1.61 +@implementation NSException (GTMSenTestAdditions) 1.62 + 1.63 ++ (NSException *)failureInFile:(NSString *)filename 1.64 + atLine:(int)lineNumber 1.65 + withDescription:(NSString *)formatString, ... { 1.66 + 1.67 + NSString *testDescription = @""; 1.68 + if (formatString) { 1.69 + va_list vl; 1.70 + va_start(vl, formatString); 1.71 + testDescription = 1.72 + [[[NSString alloc] initWithFormat:formatString arguments:vl] autorelease]; 1.73 + va_end(vl); 1.74 + } 1.75 + 1.76 + NSString *reason = testDescription; 1.77 + 1.78 + return [self failureInFile:filename atLine:lineNumber reason:reason]; 1.79 +} 1.80 + 1.81 ++ (NSException *)failureInCondition:(NSString *)condition 1.82 + isTrue:(BOOL)isTrue 1.83 + inFile:(NSString *)filename 1.84 + atLine:(int)lineNumber 1.85 + withDescription:(NSString *)formatString, ... { 1.86 + 1.87 + NSString *testDescription = @""; 1.88 + if (formatString) { 1.89 + va_list vl; 1.90 + va_start(vl, formatString); 1.91 + testDescription = 1.92 + [[[NSString alloc] initWithFormat:formatString arguments:vl] autorelease]; 1.93 + va_end(vl); 1.94 + } 1.95 + 1.96 + NSString *reason = [NSString stringWithFormat:@"'%@' should be %s. %@", 1.97 + condition, isTrue ? "false" : "true", testDescription]; 1.98 + 1.99 + return [self failureInFile:filename atLine:lineNumber reason:reason]; 1.100 +} 1.101 + 1.102 ++ (NSException *)failureInEqualityBetweenObject:(id)left 1.103 + andObject:(id)right 1.104 + inFile:(NSString *)filename 1.105 + atLine:(int)lineNumber 1.106 + withDescription:(NSString *)formatString, ... { 1.107 + 1.108 + NSString *testDescription = @""; 1.109 + if (formatString) { 1.110 + va_list vl; 1.111 + va_start(vl, formatString); 1.112 + testDescription = 1.113 + [[[NSString alloc] initWithFormat:formatString arguments:vl] autorelease]; 1.114 + va_end(vl); 1.115 + } 1.116 + 1.117 + NSString *reason = 1.118 + [NSString stringWithFormat:@"'%@' should be equal to '%@'. %@", 1.119 + [left description], [right description], testDescription]; 1.120 + 1.121 + return [self failureInFile:filename atLine:lineNumber reason:reason]; 1.122 +} 1.123 + 1.124 ++ (NSException *)failureInEqualityBetweenValue:(NSValue *)left 1.125 + andValue:(NSValue *)right 1.126 + withAccuracy:(NSValue *)accuracy 1.127 + inFile:(NSString *)filename 1.128 + atLine:(int)lineNumber 1.129 + withDescription:(NSString *)formatString, ... { 1.130 + 1.131 + NSString *testDescription = @""; 1.132 + if (formatString) { 1.133 + va_list vl; 1.134 + va_start(vl, formatString); 1.135 + testDescription = 1.136 + [[[NSString alloc] initWithFormat:formatString arguments:vl] autorelease]; 1.137 + va_end(vl); 1.138 + } 1.139 + 1.140 + NSString *reason; 1.141 + if (accuracy) { 1.142 + reason = 1.143 + [NSString stringWithFormat:@"'%@' should be equal to '%@'. %@", 1.144 + left, right, testDescription]; 1.145 + } else { 1.146 + reason = 1.147 + [NSString stringWithFormat:@"'%@' should be equal to '%@' +/-'%@'. %@", 1.148 + left, right, accuracy, testDescription]; 1.149 + } 1.150 + 1.151 + return [self failureInFile:filename atLine:lineNumber reason:reason]; 1.152 +} 1.153 + 1.154 ++ (NSException *)failureInRaise:(NSString *)expression 1.155 + inFile:(NSString *)filename 1.156 + atLine:(int)lineNumber 1.157 + withDescription:(NSString *)formatString, ... { 1.158 + 1.159 + NSString *testDescription = @""; 1.160 + if (formatString) { 1.161 + va_list vl; 1.162 + va_start(vl, formatString); 1.163 + testDescription = 1.164 + [[[NSString alloc] initWithFormat:formatString arguments:vl] autorelease]; 1.165 + va_end(vl); 1.166 + } 1.167 + 1.168 + NSString *reason = [NSString stringWithFormat:@"'%@' should raise. %@", 1.169 + expression, testDescription]; 1.170 + 1.171 + return [self failureInFile:filename atLine:lineNumber reason:reason]; 1.172 +} 1.173 + 1.174 ++ (NSException *)failureInRaise:(NSString *)expression 1.175 + exception:(NSException *)exception 1.176 + inFile:(NSString *)filename 1.177 + atLine:(int)lineNumber 1.178 + withDescription:(NSString *)formatString, ... { 1.179 + 1.180 + NSString *testDescription = @""; 1.181 + if (formatString) { 1.182 + va_list vl; 1.183 + va_start(vl, formatString); 1.184 + testDescription = 1.185 + [[[NSString alloc] initWithFormat:formatString arguments:vl] autorelease]; 1.186 + va_end(vl); 1.187 + } 1.188 + 1.189 + NSString *reason; 1.190 + if ([[exception name] isEqualToString:SenTestFailureException]) { 1.191 + // it's our exception, assume it has the right description on it. 1.192 + reason = [exception reason]; 1.193 + } else { 1.194 + // not one of our exception, use the exceptions reason and our description 1.195 + reason = [NSString stringWithFormat:@"'%@' raised '%@'. %@", 1.196 + expression, [exception reason], testDescription]; 1.197 + } 1.198 + 1.199 + return [self failureInFile:filename atLine:lineNumber reason:reason]; 1.200 +} 1.201 + 1.202 +@end 1.203 + 1.204 +NSString *STComposeString(NSString *formatString, ...) { 1.205 + NSString *reason = @""; 1.206 + if (formatString) { 1.207 + va_list vl; 1.208 + va_start(vl, formatString); 1.209 + reason = 1.210 + [[[NSString alloc] initWithFormat:formatString arguments:vl] autorelease]; 1.211 + va_end(vl); 1.212 + } 1.213 + return reason; 1.214 +} 1.215 + 1.216 +NSString *const SenTestFailureException = @"SenTestFailureException"; 1.217 +NSString *const SenTestFilenameKey = @"SenTestFilenameKey"; 1.218 +NSString *const SenTestLineNumberKey = @"SenTestLineNumberKey"; 1.219 + 1.220 +@interface SenTestCase (SenTestCasePrivate) 1.221 +// our method of logging errors 1.222 ++ (void)printException:(NSException *)exception fromTestName:(NSString *)name; 1.223 +@end 1.224 + 1.225 +@implementation SenTestCase 1.226 ++ (id)testCaseWithInvocation:(NSInvocation *)anInvocation { 1.227 + return [[[self alloc] initWithInvocation:anInvocation] autorelease]; 1.228 +} 1.229 + 1.230 +- (id)initWithInvocation:(NSInvocation *)anInvocation { 1.231 + if ((self = [super init])) { 1.232 + invocation_ = [anInvocation retain]; 1.233 + } 1.234 + return self; 1.235 +} 1.236 + 1.237 +- (void)dealloc { 1.238 + [invocation_ release]; 1.239 + [super dealloc]; 1.240 +} 1.241 + 1.242 +- (void)failWithException:(NSException*)exception { 1.243 + [exception raise]; 1.244 +} 1.245 + 1.246 +- (void)setUp { 1.247 +} 1.248 + 1.249 +- (void)performTest { 1.250 + @try { 1.251 + [self invokeTest]; 1.252 + } @catch (NSException *exception) { 1.253 + [[self class] printException:exception 1.254 + fromTestName:NSStringFromSelector([self selector])]; 1.255 + [exception raise]; 1.256 + } 1.257 +} 1.258 + 1.259 +- (NSInvocation *)invocation { 1.260 + return invocation_; 1.261 +} 1.262 + 1.263 +- (SEL)selector { 1.264 + return [invocation_ selector]; 1.265 +} 1.266 + 1.267 ++ (void)printException:(NSException *)exception fromTestName:(NSString *)name { 1.268 + NSDictionary *userInfo = [exception userInfo]; 1.269 + NSString *filename = [userInfo objectForKey:SenTestFilenameKey]; 1.270 + NSNumber *lineNumber = [userInfo objectForKey:SenTestLineNumberKey]; 1.271 + NSString *className = NSStringFromClass([self class]); 1.272 + if ([filename length] == 0) { 1.273 + filename = @"Unknown.m"; 1.274 + } 1.275 + fprintf(stderr, "%s:%ld: error: -[%s %s] : %s\n", 1.276 + [filename UTF8String], 1.277 + (long)[lineNumber integerValue], 1.278 + [className UTF8String], 1.279 + [name UTF8String], 1.280 + [[exception reason] UTF8String]); 1.281 + fflush(stderr); 1.282 +} 1.283 + 1.284 +- (void)invokeTest { 1.285 + NSException *e = nil; 1.286 + @try { 1.287 + // Wrap things in autorelease pools because they may 1.288 + // have an STMacro in their dealloc which may get called 1.289 + // when the pool is cleaned up 1.290 + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; 1.291 + // We don't log exceptions here, instead we let the person that called 1.292 + // this log the exception. This ensures they are only logged once but the 1.293 + // outer layers get the exceptions to report counts, etc. 1.294 + @try { 1.295 + [self setUp]; 1.296 + @try { 1.297 + NSInvocation *invocation = [self invocation]; 1.298 +#if GTM_IPHONE_SIMULATOR 1.299 + // We don't call [invocation invokeWithTarget:self]; because of 1.300 + // Radar 8081169: NSInvalidArgumentException can't be caught 1.301 + // It turns out that on iOS4 (and 3.2) exceptions thrown inside an 1.302 + // [invocation invoke] on the simulator cannot be caught. 1.303 + // http://openradar.appspot.com/8081169 1.304 + objc_msgSend(self, [invocation selector]); 1.305 +#else 1.306 + [invocation invokeWithTarget:self]; 1.307 +#endif 1.308 + } @catch (NSException *exception) { 1.309 + e = [exception retain]; 1.310 + } 1.311 + [self tearDown]; 1.312 + } @catch (NSException *exception) { 1.313 + e = [exception retain]; 1.314 + } 1.315 + [pool release]; 1.316 + } @catch (NSException *exception) { 1.317 + e = [exception retain]; 1.318 + } 1.319 + if (e) { 1.320 + [e autorelease]; 1.321 + [e raise]; 1.322 + } 1.323 +} 1.324 + 1.325 +- (void)tearDown { 1.326 +} 1.327 + 1.328 +- (NSString *)description { 1.329 + // This matches the description OCUnit would return to you 1.330 + return [NSString stringWithFormat:@"-[%@ %@]", [self class], 1.331 + NSStringFromSelector([self selector])]; 1.332 +} 1.333 + 1.334 +// Used for sorting methods below 1.335 +static int MethodSort(id a, id b, void *context) { 1.336 + NSInvocation *invocationA = a; 1.337 + NSInvocation *invocationB = b; 1.338 + const char *nameA = sel_getName([invocationA selector]); 1.339 + const char *nameB = sel_getName([invocationB selector]); 1.340 + return strcmp(nameA, nameB); 1.341 +} 1.342 + 1.343 + 1.344 ++ (NSArray *)testInvocations { 1.345 + NSMutableArray *invocations = nil; 1.346 + // Need to walk all the way up the parent classes collecting methods (in case 1.347 + // a test is a subclass of another test). 1.348 + Class senTestCaseClass = [SenTestCase class]; 1.349 + for (Class currentClass = self; 1.350 + currentClass && (currentClass != senTestCaseClass); 1.351 + currentClass = class_getSuperclass(currentClass)) { 1.352 + unsigned int methodCount; 1.353 + Method *methods = class_copyMethodList(currentClass, &methodCount); 1.354 + if (methods) { 1.355 + // This handles disposing of methods for us even if an exception should fly. 1.356 + [NSData dataWithBytesNoCopy:methods 1.357 + length:sizeof(Method) * methodCount]; 1.358 + if (!invocations) { 1.359 + invocations = [NSMutableArray arrayWithCapacity:methodCount]; 1.360 + } 1.361 + for (size_t i = 0; i < methodCount; ++i) { 1.362 + Method currMethod = methods[i]; 1.363 + SEL sel = method_getName(currMethod); 1.364 + char *returnType = NULL; 1.365 + const char *name = sel_getName(sel); 1.366 + // If it starts with test, takes 2 args (target and sel) and returns 1.367 + // void run it. 1.368 + if (strstr(name, "test") == name) { 1.369 + returnType = method_copyReturnType(currMethod); 1.370 + if (returnType) { 1.371 + // This handles disposing of returnType for us even if an 1.372 + // exception should fly. Length +1 for the terminator, not that 1.373 + // the length really matters here, as we never reference inside 1.374 + // the data block. 1.375 + [NSData dataWithBytesNoCopy:returnType 1.376 + length:strlen(returnType) + 1]; 1.377 + } 1.378 + } 1.379 + // TODO: If a test class is a subclass of another, and they reuse the 1.380 + // same selector name (ie-subclass overrides it), this current loop 1.381 + // and test here will cause cause it to get invoked twice. To fix this 1.382 + // the selector would have to be checked against all the ones already 1.383 + // added, so it only gets done once. 1.384 + if (returnType // True if name starts with "test" 1.385 + && strcmp(returnType, @encode(void)) == 0 1.386 + && method_getNumberOfArguments(currMethod) == 2) { 1.387 + NSMethodSignature *sig = [self instanceMethodSignatureForSelector:sel]; 1.388 + NSInvocation *invocation 1.389 + = [NSInvocation invocationWithMethodSignature:sig]; 1.390 + [invocation setSelector:sel]; 1.391 + [invocations addObject:invocation]; 1.392 + } 1.393 + } 1.394 + } 1.395 + } 1.396 + // Match SenTestKit and run everything in alphbetical order. 1.397 + [invocations sortUsingFunction:MethodSort context:nil]; 1.398 + return invocations; 1.399 +} 1.400 + 1.401 +@end 1.402 + 1.403 +#endif // GTM_IPHONE_SDK && !GTM_IPHONE_USE_SENTEST 1.404 + 1.405 +@implementation GTMTestCase : SenTestCase 1.406 +- (void)invokeTest { 1.407 + NSAutoreleasePool *localPool = [[NSAutoreleasePool alloc] init]; 1.408 + Class devLogClass = NSClassFromString(@"GTMUnitTestDevLog"); 1.409 + if (devLogClass) { 1.410 + [devLogClass performSelector:@selector(enableTracking)]; 1.411 + [devLogClass performSelector:@selector(verifyNoMoreLogsExpected)]; 1.412 + 1.413 + } 1.414 + [super invokeTest]; 1.415 + if (devLogClass) { 1.416 + [devLogClass performSelector:@selector(verifyNoMoreLogsExpected)]; 1.417 + [devLogClass performSelector:@selector(disableTracking)]; 1.418 + } 1.419 + [localPool drain]; 1.420 +} 1.421 + 1.422 ++ (BOOL)isAbstractTestCase { 1.423 + NSString *name = NSStringFromClass(self); 1.424 + return [name rangeOfString:@"AbstractTest"].location != NSNotFound; 1.425 +} 1.426 + 1.427 ++ (NSArray *)testInvocations { 1.428 + NSArray *invocations = nil; 1.429 + if (![self isAbstractTestCase]) { 1.430 + invocations = [super testInvocations]; 1.431 + } 1.432 + return invocations; 1.433 +} 1.434 + 1.435 +@end 1.436 + 1.437 +// Leak detection 1.438 +#if !GTM_IPHONE_DEVICE && !GTM_SUPPRESS_RUN_LEAKS_HOOK 1.439 +// Don't want to get leaks on the iPhone Device as the device doesn't 1.440 +// have 'leaks'. The simulator does though. 1.441 + 1.442 +// COV_NF_START 1.443 +// We don't have leak checking on by default, so this won't be hit. 1.444 +static void _GTMRunLeaks(void) { 1.445 + // This is an atexit handler. It runs leaks for us to check if we are 1.446 + // leaking anything in our tests. 1.447 + const char* cExclusionsEnv = getenv("GTM_LEAKS_SYMBOLS_TO_IGNORE"); 1.448 + NSMutableString *exclusions = [NSMutableString string]; 1.449 + if (cExclusionsEnv) { 1.450 + NSString *exclusionsEnv = [NSString stringWithUTF8String:cExclusionsEnv]; 1.451 + NSArray *exclusionsArray = [exclusionsEnv componentsSeparatedByString:@","]; 1.452 + NSString *exclusion; 1.453 + NSCharacterSet *wcSet = [NSCharacterSet whitespaceCharacterSet]; 1.454 + GTM_FOREACH_OBJECT(exclusion, exclusionsArray) { 1.455 + exclusion = [exclusion stringByTrimmingCharactersInSet:wcSet]; 1.456 + [exclusions appendFormat:@"-exclude \"%@\" ", exclusion]; 1.457 + } 1.458 + } 1.459 + // Clearing out DYLD_ROOT_PATH because iPhone Simulator framework libraries 1.460 + // are different from regular OS X libraries and leaks will fail to run 1.461 + // because of missing symbols. Also capturing the output of leaks and then 1.462 + // pipe rather than a direct pipe, because otherwise if leaks failed, 1.463 + // the system() call will still be successful. Bug: 1.464 + // http://code.google.com/p/google-toolbox-for-mac/issues/detail?id=56 1.465 + NSString *string 1.466 + = [NSString stringWithFormat: 1.467 + @"LeakOut=`DYLD_ROOT_PATH='' /usr/bin/leaks %@%d` &&" 1.468 + @"echo \"$LeakOut\"|/usr/bin/sed -e 's/Leak: /Leaks:0: warning: Leak /'", 1.469 + exclusions, getpid()]; 1.470 + int ret = system([string UTF8String]); 1.471 + if (ret) { 1.472 + fprintf(stderr, 1.473 + "%s:%d: Error: Unable to run leaks. 'system' returned: %d\n", 1.474 + __FILE__, __LINE__, ret); 1.475 + fflush(stderr); 1.476 + } 1.477 +} 1.478 +// COV_NF_END 1.479 + 1.480 +static __attribute__((constructor)) void _GTMInstallLeaks(void) { 1.481 + BOOL checkLeaks = YES; 1.482 +#if !GTM_IPHONE_SDK 1.483 + checkLeaks = GTMIsGarbageCollectionEnabled() ? NO : YES; 1.484 +#endif // !GTM_IPHONE_SDK 1.485 + if (checkLeaks) { 1.486 + checkLeaks = getenv("GTM_ENABLE_LEAKS") ? YES : NO; 1.487 + if (checkLeaks) { 1.488 + // COV_NF_START 1.489 + // We don't have leak checking on by default, so this won't be hit. 1.490 + fprintf(stderr, "Leak Checking Enabled\n"); 1.491 + fflush(stderr); 1.492 + int ret = atexit(&_GTMRunLeaks); 1.493 + // To avoid unused variable warning when _GTMDevAssert is stripped. 1.494 + (void)ret; 1.495 + _GTMDevAssert(ret == 0, 1.496 + @"Unable to install _GTMRunLeaks as an atexit handler (%d)", 1.497 + errno); 1.498 + // COV_NF_END 1.499 + } 1.500 + } 1.501 +} 1.502 + 1.503 +#endif // !GTM_IPHONE_DEVICE && !GTM_SUPPRESS_RUN_LEAKS_HOOK