1// 2// GTMSenTestCase.m 3// 4// Copyright 2007-2008 Google Inc. 5// 6// Licensed under the Apache License, Version 2.0 (the "License"); you may not 7// use this file except in compliance with the License. You may obtain a copy 8// of the License at 9// 10// http://www.apache.org/licenses/LICENSE-2.0 11// 12// Unless required by applicable law or agreed to in writing, software 13// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 14// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 15// License for the specific language governing permissions and limitations under 16// the License. 17// 18 19#import "GTMSenTestCase.h" 20#import <unistd.h> 21 22#if !GTM_IPHONE_SDK 23#import "GTMGarbageCollection.h" 24#endif // !GTM_IPHONE_SDK 25 26#if GTM_IPHONE_SDK 27#import <stdarg.h> 28 29@interface NSException (GTMSenTestPrivateAdditions) 30+ (NSException *)failureInFile:(NSString *)filename 31 atLine:(int)lineNumber 32 reason:(NSString *)reason; 33@end 34 35@implementation NSException (GTMSenTestPrivateAdditions) 36+ (NSException *)failureInFile:(NSString *)filename 37 atLine:(int)lineNumber 38 reason:(NSString *)reason { 39 NSDictionary *userInfo = 40 [NSDictionary dictionaryWithObjectsAndKeys: 41 [NSNumber numberWithInteger:lineNumber], SenTestLineNumberKey, 42 filename, SenTestFilenameKey, 43 nil]; 44 45 return [self exceptionWithName:SenTestFailureException 46 reason:reason 47 userInfo:userInfo]; 48} 49@end 50 51@implementation NSException (GTMSenTestAdditions) 52 53+ (NSException *)failureInFile:(NSString *)filename 54 atLine:(int)lineNumber 55 withDescription:(NSString *)formatString, ... { 56 57 NSString *testDescription = @""; 58 if (formatString) { 59 va_list vl; 60 va_start(vl, formatString); 61 testDescription = 62 [[[NSString alloc] initWithFormat:formatString arguments:vl] autorelease]; 63 va_end(vl); 64 } 65 66 NSString *reason = testDescription; 67 68 return [self failureInFile:filename atLine:lineNumber reason:reason]; 69} 70 71+ (NSException *)failureInCondition:(NSString *)condition 72 isTrue:(BOOL)isTrue 73 inFile:(NSString *)filename 74 atLine:(int)lineNumber 75 withDescription:(NSString *)formatString, ... { 76 77 NSString *testDescription = @""; 78 if (formatString) { 79 va_list vl; 80 va_start(vl, formatString); 81 testDescription = 82 [[[NSString alloc] initWithFormat:formatString arguments:vl] autorelease]; 83 va_end(vl); 84 } 85 86 NSString *reason = [NSString stringWithFormat:@"'%@' should be %s. %@", 87 condition, isTrue ? "TRUE" : "FALSE", testDescription]; 88 89 return [self failureInFile:filename atLine:lineNumber reason:reason]; 90} 91 92+ (NSException *)failureInEqualityBetweenObject:(id)left 93 andObject:(id)right 94 inFile:(NSString *)filename 95 atLine:(int)lineNumber 96 withDescription:(NSString *)formatString, ... { 97 98 NSString *testDescription = @""; 99 if (formatString) { 100 va_list vl; 101 va_start(vl, formatString); 102 testDescription = 103 [[[NSString alloc] initWithFormat:formatString arguments:vl] autorelease]; 104 va_end(vl); 105 } 106 107 NSString *reason = 108 [NSString stringWithFormat:@"'%@' should be equal to '%@'. %@", 109 [left description], [right description], testDescription]; 110 111 return [self failureInFile:filename atLine:lineNumber reason:reason]; 112} 113 114+ (NSException *)failureInEqualityBetweenValue:(NSValue *)left 115 andValue:(NSValue *)right 116 withAccuracy:(NSValue *)accuracy 117 inFile:(NSString *)filename 118 atLine:(int)lineNumber 119 withDescription:(NSString *)formatString, ... { 120 121 NSString *testDescription = @""; 122 if (formatString) { 123 va_list vl; 124 va_start(vl, formatString); 125 testDescription = 126 [[[NSString alloc] initWithFormat:formatString arguments:vl] autorelease]; 127 va_end(vl); 128 } 129 130 NSString *reason; 131 if (accuracy) { 132 reason = 133 [NSString stringWithFormat:@"'%@' should be equal to '%@'. %@", 134 left, right, testDescription]; 135 } else { 136 reason = 137 [NSString stringWithFormat:@"'%@' should be equal to '%@' +/-'%@'. %@", 138 left, right, accuracy, testDescription]; 139 } 140 141 return [self failureInFile:filename atLine:lineNumber reason:reason]; 142} 143 144+ (NSException *)failureInRaise:(NSString *)expression 145 inFile:(NSString *)filename 146 atLine:(int)lineNumber 147 withDescription:(NSString *)formatString, ... { 148 149 NSString *testDescription = @""; 150 if (formatString) { 151 va_list vl; 152 va_start(vl, formatString); 153 testDescription = 154 [[[NSString alloc] initWithFormat:formatString arguments:vl] autorelease]; 155 va_end(vl); 156 } 157 158 NSString *reason = [NSString stringWithFormat:@"'%@' should raise. %@", 159 expression, testDescription]; 160 161 return [self failureInFile:filename atLine:lineNumber reason:reason]; 162} 163 164+ (NSException *)failureInRaise:(NSString *)expression 165 exception:(NSException *)exception 166 inFile:(NSString *)filename 167 atLine:(int)lineNumber 168 withDescription:(NSString *)formatString, ... { 169 170 NSString *testDescription = @""; 171 if (formatString) { 172 va_list vl; 173 va_start(vl, formatString); 174 testDescription = 175 [[[NSString alloc] initWithFormat:formatString arguments:vl] autorelease]; 176 va_end(vl); 177 } 178 179 NSString *reason; 180 if ([[exception name] isEqualToString:SenTestFailureException]) { 181 // it's our exception, assume it has the right description on it. 182 reason = [exception reason]; 183 } else { 184 // not one of our exception, use the exceptions reason and our description 185 reason = [NSString stringWithFormat:@"'%@' raised '%@'. %@", 186 expression, [exception reason], testDescription]; 187 } 188 189 return [self failureInFile:filename atLine:lineNumber reason:reason]; 190} 191 192@end 193 194NSString *STComposeString(NSString *formatString, ...) { 195 NSString *reason = @""; 196 if (formatString) { 197 va_list vl; 198 va_start(vl, formatString); 199 reason = 200 [[[NSString alloc] initWithFormat:formatString arguments:vl] autorelease]; 201 va_end(vl); 202 } 203 return reason; 204} 205 206NSString *const SenTestFailureException = @"SenTestFailureException"; 207NSString *const SenTestFilenameKey = @"SenTestFilenameKey"; 208NSString *const SenTestLineNumberKey = @"SenTestLineNumberKey"; 209 210@interface SenTestCase (SenTestCasePrivate) 211// our method of logging errors 212+ (void)printException:(NSException *)exception fromTestName:(NSString *)name; 213@end 214 215@implementation SenTestCase 216- (void)failWithException:(NSException*)exception { 217 [exception raise]; 218} 219 220- (void)setUp { 221} 222 223- (void)performTest:(SEL)sel { 224 currentSelector_ = sel; 225 @try { 226 [self invokeTest]; 227 } @catch (NSException *exception) { 228 [[self class] printException:exception 229 fromTestName:NSStringFromSelector(sel)]; 230 [exception raise]; 231 } 232} 233 234+ (void)printException:(NSException *)exception fromTestName:(NSString *)name { 235 NSDictionary *userInfo = [exception userInfo]; 236 NSString *filename = [userInfo objectForKey:SenTestFilenameKey]; 237 NSNumber *lineNumber = [userInfo objectForKey:SenTestLineNumberKey]; 238 NSString *className = NSStringFromClass([self class]); 239 if ([filename length] == 0) { 240 filename = @"Unknown.m"; 241 } 242 fprintf(stderr, "%s:%ld: error: -[%s %s] : %s\n", 243 [filename UTF8String], 244 (long)[lineNumber integerValue], 245 [className UTF8String], 246 [name UTF8String], 247 [[exception reason] UTF8String]); 248 fflush(stderr); 249} 250 251- (void)invokeTest { 252 NSException *e = nil; 253 @try { 254 // Wrap things in autorelease pools because they may 255 // have an STMacro in their dealloc which may get called 256 // when the pool is cleaned up 257 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; 258 // We don't log exceptions here, instead we let the person that called 259 // this log the exception. This ensures they are only logged once but the 260 // outer layers get the exceptions to report counts, etc. 261 @try { 262 [self setUp]; 263 @try { 264 [self performSelector:currentSelector_]; 265 } @catch (NSException *exception) { 266 e = [exception retain]; 267 } 268 [self tearDown]; 269 } @catch (NSException *exception) { 270 e = [exception retain]; 271 } 272 [pool release]; 273 } @catch (NSException *exception) { 274 e = [exception retain]; 275 } 276 if (e) { 277 [e autorelease]; 278 [e raise]; 279 } 280} 281 282- (void)tearDown { 283} 284 285- (NSString *)description { 286 // This matches the description OCUnit would return to you 287 return [NSString stringWithFormat:@"-[%@ %@]", [self class], 288 NSStringFromSelector(currentSelector_)]; 289} 290@end 291 292#endif // GTM_IPHONE_SDK 293 294@implementation GTMTestCase : SenTestCase 295- (void)invokeTest { 296 Class devLogClass = NSClassFromString(@"GTMUnitTestDevLog"); 297 if (devLogClass) { 298 [devLogClass performSelector:@selector(enableTracking)]; 299 [devLogClass performSelector:@selector(verifyNoMoreLogsExpected)]; 300 301 } 302 [super invokeTest]; 303 if (devLogClass) { 304 [devLogClass performSelector:@selector(verifyNoMoreLogsExpected)]; 305 [devLogClass performSelector:@selector(disableTracking)]; 306 } 307} 308@end 309 310// Leak detection 311#if !GTM_IPHONE_DEVICE 312// Don't want to get leaks on the iPhone Device as the device doesn't 313// have 'leaks'. The simulator does though. 314 315// COV_NF_START 316// We don't have leak checking on by default, so this won't be hit. 317static void _GTMRunLeaks(void) { 318 // This is an atexit handler. It runs leaks for us to check if we are 319 // leaking anything in our tests. 320 const char* cExclusionsEnv = getenv("GTM_LEAKS_SYMBOLS_TO_IGNORE"); 321 NSMutableString *exclusions = [NSMutableString string]; 322 if (cExclusionsEnv) { 323 NSString *exclusionsEnv = [NSString stringWithUTF8String:cExclusionsEnv]; 324 NSArray *exclusionsArray = [exclusionsEnv componentsSeparatedByString:@","]; 325 NSString *exclusion; 326 NSCharacterSet *wcSet = [NSCharacterSet whitespaceCharacterSet]; 327 GTM_FOREACH_OBJECT(exclusion, exclusionsArray) { 328 exclusion = [exclusion stringByTrimmingCharactersInSet:wcSet]; 329 [exclusions appendFormat:@"-exclude \"%@\" ", exclusion]; 330 } 331 } 332 NSString *string 333 = [NSString stringWithFormat:@"/usr/bin/leaks %@%d" 334 @"| /usr/bin/sed -e 's/Leak: /Leaks:0: warning: Leak /'", 335 exclusions, getpid()]; 336 int ret = system([string UTF8String]); 337 if (ret) { 338 fprintf(stderr, "%s:%d: Error: Unable to run leaks. 'system' returned: %d", 339 __FILE__, __LINE__, ret); 340 fflush(stderr); 341 } 342} 343// COV_NF_END 344 345static __attribute__((constructor)) void _GTMInstallLeaks(void) { 346 BOOL checkLeaks = YES; 347#if !GTM_IPHONE_SDK 348 checkLeaks = GTMIsGarbageCollectionEnabled() ? NO : YES; 349#endif // !GTM_IPHONE_SDK 350 if (checkLeaks) { 351 checkLeaks = getenv("GTM_ENABLE_LEAKS") ? YES : NO; 352 if (checkLeaks) { 353 // COV_NF_START 354 // We don't have leak checking on by default, so this won't be hit. 355 fprintf(stderr, "Leak Checking Enabled\n"); 356 fflush(stderr); 357 int ret = atexit(&_GTMRunLeaks); 358 _GTMDevAssert(ret == 0, 359 @"Unable to install _GTMRunLeaks as an atexit handler (%d)", 360 errno); 361 // COV_NF_END 362 } 363 } 364} 365 366#endif // !GTM_IPHONE_DEVICE 367