1/* 2 * Copyright (c) 2011 Apple Inc. All rights reserved. 3 * 4 * @APPLE_APACHE_LICENSE_HEADER_START@ 5 * 6 * Licensed under the Apache License, Version 2.0 (the "License"); 7 * you may not use this file except in compliance with the License. 8 * You may obtain a copy 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, 14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 * See the License for the specific language governing permissions and 16 * limitations under the License. 17 * 18 * @APPLE_APACHE_LICENSE_HEADER_END@ 19 */ 20// 21// TestCase.m 22// Copyright (c) 2009-2011 Apple Inc. All rights reserved. 23// 24 25#import "TestCase.h" 26#import "Environment.h" 27#import <objc/objc-auto.h> 28#import <objc/objc-class.h> 29#import <unistd.h> 30 31#define assertTestQueue() do { \ 32if (dispatch_get_current_queue() != _testQueue) { \ 33 NSLog(@"%@ invoked on incorrect dispatch queue", NSStringFromSelector(_cmd)); abort(); \ 34}} while (0) 35 36@implementation TestFinalizer : NSObject 37 38- (id)init 39{ 40 self = [super init]; 41 if (self) { 42 _allocatingTest = [TestCase currentTestCase]; 43 } 44 return self; 45} 46 47- (void)finalize 48{ 49 TestCase *tc = [TestCase currentTestCase]; 50 // Here is a convenient place to test objc_is_finalized() 51 if (!objc_is_finalized(self)) { 52 [tc fail:[NSString stringWithFormat:@"objc_is_finalized(self) returns false during -finalize. self = %@, allocating = %@, running = %@", [self className], [_allocatingTest className], [tc className]]]; 53 } 54 if (tc == _allocatingTest) { 55 [tc didFinalize:self]; 56 } 57 [super finalize]; 58} 59@end 60 61@implementation TestCase 62 63static TestCase *_currentTestCase; 64 65+ (id)currentTestCase 66{ 67 return _currentTestCase; 68} 69 70+ (BOOL)isConcreteTestCase 71{ 72 Class tc = [TestCase class]; 73 IMP startImp = [tc instanceMethodForSelector:@selector(performTest)]; 74 if ([self instanceMethodForSelector:@selector(performTest)] != startImp) { 75 return YES; 76 } 77 return NO; 78} 79 80static NSInteger sortClassesByName(id cls1, id cls2, void* context) { 81 return strcmp(class_getName(cls1), class_getName(cls2)); 82} 83 84+ (NSArray *)testClasses 85{ 86 static NSArray *_testClasses = nil; 87 // Dynamically find all the subclasses of AutoTestScript 88 if (_testClasses == nil) { 89 NSMutableArray *testClasses = [[NSMutableArray alloc] init]; 90 int numClasses; 91 Class * classes = NULL; 92 Class tc = [TestCase class]; 93 IMP startImp = [tc instanceMethodForSelector:@selector(performTest)]; 94 95 classes = NULL; 96 numClasses = objc_getClassList(NULL, 0); 97 98 if (numClasses > 0 ) 99 { 100 classes = (Class *)malloc(sizeof(Class) * numClasses); 101 numClasses = objc_getClassList(classes, numClasses); 102 int i; 103 for (i=0; i<numClasses; i++) { 104 // Some fumkiness to weed out weird clases like NSZombie and Object without actually messaging them. 105 Class sc = class_getSuperclass(classes[i]); 106 if (sc) { 107 Class ssc = class_getSuperclass(sc); 108 if (ssc) { 109 if ([classes[i] isSubclassOfClass:tc]) { 110 if ([classes[i] isConcreteTestCase]) 111 [testClasses addObject:classes[i]]; 112 } 113 } 114 } 115 } 116 free(classes); 117 } 118 _testClasses = [testClasses sortedArrayUsingFunction:sortClassesByName context:NULL]; 119 } 120 return _testClasses; 121} 122 123+ (void)initialize 124{ 125 if (self == [TestCase class]) { 126 Auto::Environment::initialize(); 127 } 128} 129 130+ (NSString *)resultStringForResult:(TestResult)resultCode 131{ 132 NSString *result; 133 switch (resultCode) { 134 case PASSED: 135 result = @"PASSED"; 136 break; 137 case FAILED: 138 result = @"FAILED"; 139 break; 140 case SKIPPED: 141 result = @"SKIPPED"; 142 break; 143 default: 144 result = @"UNKNOWN"; 145 break; 146 } 147 return result; 148} 149 150 151- (id)init 152{ 153 self = [super init]; 154 if (self) { 155 NSString *label = [NSString stringWithFormat:@"Test Case: %@", [self className]]; 156 _testQueue = dispatch_queue_create([label UTF8String], NULL); 157 _result = PENDING; 158 pthread_mutex_init(&_mutex, NULL); 159 pthread_cond_init(&_cond, NULL); 160 _orig_fd = -1; 161 } 162 return self; 163} 164 165- (void)finalize 166{ 167 dispatch_release(_testQueue); 168 [super finalize]; 169} 170 171 172- (void)setCompletionCallback:(void (^)(void))completionCallback 173{ 174 _completionCallback = Block_copy(completionCallback); 175 _completionCallbackQueue = dispatch_get_current_queue(); 176 dispatch_retain(_completionCallbackQueue); 177} 178 179- (void)invokeCompletionCallback 180{ 181 assertTestQueue(); 182 // safe to call this multiple times 183 // do not really do it until we are really done 184 if (_completionCallback && !_testThread && _outputCompleteCalled && _result != IN_PROGRESS) { 185 if (_completionCallback) { 186 _currentTestCase = nil; 187 dispatch_async(_completionCallbackQueue, _completionCallback); 188 Block_release(_completionCallback); 189 dispatch_release(_completionCallbackQueue); 190 _completionCallback = nil; 191 } 192 } 193} 194 195- (TestResult)result 196{ 197 return _result; 198} 199 200- (NSString *)resultString 201{ 202 TestResult r = [self result]; 203 NSString *result; 204 if (_resultMessage) 205 result = [NSString stringWithFormat:@"%@: %@", [TestCase resultStringForResult:r], _resultMessage]; 206 else 207 result = [TestCase resultStringForResult:r]; 208 return result; 209} 210 211- (NSString *)testOutput 212{ 213 return _testOutput; 214} 215 216- (void)setTestResult:(TestResult)result message:(NSString *)msg 217{ 218 BOOL badTransition = NO; 219 // sanity check that the result only changes in expected ways 220 switch (_result) { 221 case PENDING: 222 if (result != IN_PROGRESS && result != SKIPPED) 223 badTransition = YES; 224 break; 225 case IN_PROGRESS: 226 if (result != PASSED && result != FAILED) 227 badTransition = YES; 228 break; 229 case PASSED: 230 // allow the result to change from passed->failed to simplify post-test output handling 231 if (result != FAILED) 232 badTransition = YES; 233 break; 234 case SKIPPED: 235 badTransition = YES; 236 break; 237 case FAILED: 238 if (result != FAILED) 239 badTransition = YES; 240 break; 241 default: 242 badTransition = YES; 243 } 244 if (badTransition) { 245 // we might not be able to allocate here 246 // peg the result to failed to get attention 247 result = FAILED; 248 msg = @"Bogus test result transition"; 249 } 250 _result = result; 251 if (_resultMessage == nil) 252 _resultMessage = msg; 253} 254 255- (BOOL)resurrectionIsFatal 256{ 257 return Auto::Environment::resurrection_is_fatal ? YES : NO; 258} 259 260- (void)monitorOutput:(id)unused 261{ 262 dispatch_async(_testQueue, ^{[self performTest];}); 263 char buf[512]; 264 NSMutableString *stringBuf = [NSMutableString string]; 265 int bytes; 266 do { 267 bytes = read(_fds[0], buf, sizeof(buf)-1); 268 if (bytes > 0 && ![self failed]) { 269 buf[bytes] = 0; 270 [stringBuf appendFormat:@"%s", buf]; 271 if (!_testOutput) 272 _testOutput = [NSMutableString string]; 273 [_testOutput appendFormat:@"%s", buf]; 274 NSRange newline; 275 do { 276 newline = [stringBuf rangeOfString:@"\n"]; 277 if (newline.location != NSNotFound) { 278 NSString *line = [stringBuf substringWithRange:NSMakeRange(0, newline.location)]; 279 [stringBuf deleteCharactersInRange:NSMakeRange(0, newline.location+1)]; 280 if ([line length] > 0) { 281 dispatch_async(_testQueue, ^{if (![self failed]) [self processOutputLine:line];}); 282 } 283 } 284 } while (newline.location != NSNotFound); 285 } 286 } while (bytes > 0); 287 if ([stringBuf length] > 0 && ![self failed]) { 288 dispatch_async(_testQueue, ^{[self processOutputLine:stringBuf];}); 289 } 290 dispatch_async(_testQueue, ^{ 291 [self outputComplete]; 292 }); 293} 294 295- (BOOL)shouldMonitorOutput 296{ 297 return YES; 298} 299 300- (void)beginMonitoringOutput 301{ 302 assertTestQueue(); 303 304 if ([self shouldMonitorOutput]) { 305 // take over stderr 306 if (pipe(_fds) == -1) { 307 abort(); 308 } 309 310 _orig_fd = dup(2); 311 if (_orig_fd == -1) { 312 abort(); 313 } 314 if (dup2(_fds[1], 2) == -1) { 315 abort(); 316 } 317 318 [NSThread detachNewThreadSelector:@selector(monitorOutput:) toTarget:self withObject:nil]; 319 } else { 320 dispatch_async(_testQueue, ^{[self performTest];}); 321 } 322} 323 324- (void)endMonitoringOutput 325{ 326 assertTestQueue(); 327 // restore file descriptors 328 if (_orig_fd != -1) { 329 close(_fds[0]); 330 close(_fds[1]); 331 if (dup2(_orig_fd, 2)==-1) 332 abort(); 333 _orig_fd = -1; 334 _fds[0] = -1; 335 _fds[1] = -1; 336 } else { 337 dispatch_async(_testQueue, ^{ 338 [self outputComplete]; 339 }); 340 } 341} 342 343- (void)flushStderr 344{ 345 assertTestQueue(); 346 [self endMonitoringOutput]; 347} 348 349- (void)outputComplete; 350{ 351 assertTestQueue(); 352 _outputCompleteCalled = YES; 353 [self invokeCompletionCallback]; 354} 355 356 357- (void)startTest 358{ 359 NSLog(@"Running: %@", [self className]); 360 _currentTestCase = self; 361 [self setTestResult:IN_PROGRESS message:nil]; 362 [self runThreadLocalCollection]; 363 dispatch_async(_testQueue, ^{[self beginMonitoringOutput];}); 364} 365 366- (void)performTest 367{ 368 [self fail:[NSString stringWithFormat:@"%@ not overridden in test class: %@", NSStringFromSelector(_cmd), [self className]]]; 369} 370 371- (NSString *)shouldSkip 372{ 373 return nil; 374} 375 376- (void)processOutputLine:(NSString *)line 377{ 378 NSString *collectionLog = @"[scan + freeze + finalize + reclaim]"; 379 NSRange r = [line rangeOfString:collectionLog]; 380 if (r.location != NSNotFound) { 381 NSFileHandle *err; 382 if (_orig_fd != -1) 383 err = [[NSFileHandle alloc] initWithFileDescriptor:_orig_fd closeOnDealloc:NO]; 384 else 385 err = [NSFileHandle fileHandleWithStandardError]; 386 [err writeData:[[line stringByAppendingString:@"\n"] dataUsingEncoding:NSMacOSRomanStringEncoding]]; 387 } else { 388 [self fail:[NSString stringWithFormat:@"Unexpected console output: %@", line]]; 389 } 390} 391 392- (void)testFinished 393{ 394 assertTestQueue(); 395 [self stopTestThread]; 396 [self endMonitoringOutput]; 397 [self invokeCompletionCallback]; 398} 399 400- (void)passed 401{ 402 if (![self failed]) { 403 // resumptively set result to passed. may still change to failed based on output processing 404 [self setTestResult:PASSED message:nil]; 405 } 406} 407 408- (void)fail:(NSString *)msg 409{ 410 [self setTestResult:FAILED message:msg]; 411} 412 413 414/* Misc convenience methods */ 415 416- (BOOL)failed 417{ 418 return [self result] == FAILED; 419} 420 421- (void)collectWithOptions:(auto_zone_options_t)options 422{ 423 auto_zone_collect([self auto_zone], options); 424} 425 426- (void)collectWithOptions:(auto_zone_options_t)options completionCallback:(void (^)(void))callback 427{ 428 auto_zone_collect_and_notify([self auto_zone], options, _testQueue, callback); 429} 430 431- (void)runThreadLocalCollection 432{ 433 [self collectWithOptions:AUTO_ZONE_COLLECT_LOCAL_COLLECTION]; 434} 435 436- (void)requestAdvisoryCollectionWithCompletionCallback:(void (^)(void))callback; 437{ 438 [self collectWithOptions:AUTO_ZONE_COLLECT_NO_OPTIONS completionCallback:callback]; 439} 440 441- (void)requestGenerationalCollectionWithCompletionCallback:(void (^)(void))callback; 442{ 443 [self collectWithOptions:AUTO_ZONE_COLLECT_GENERATIONAL_COLLECTION completionCallback:callback]; 444} 445 446- (void)requestFullCollectionWithCompletionCallback:(void (^)(void))callback; 447{ 448 [self collectWithOptions:AUTO_ZONE_COLLECT_FULL_COLLECTION completionCallback:callback]; 449} 450 451- (void)requestExhaustiveCollectionWithCompletionCallback:(void (^)(void))callback; 452{ 453 [self collectWithOptions:AUTO_ZONE_COLLECT_EXHAUSTIVE_COLLECTION completionCallback:callback]; 454} 455 456- (void)requestCompactionWithCompletionCallback:(void (^)(void))callback { 457 auto_zone_compact([self auto_zone], AUTO_ZONE_COMPACT_NO_OPTIONS, _testQueue, callback); 458} 459 460- (void)requestCompactionAnalysisWithCompletionCallback:(void (^)(void))callback { 461 auto_zone_compact([self auto_zone], AUTO_ZONE_COMPACT_ANALYZE, _testQueue, callback); 462} 463 464- (void)clearStack 465{ 466 objc_clear_stack(0); 467} 468 469- (void)didFinalize:(TestFinalizer *)finalizer 470{ 471} 472 473- (vm_address_t)disguise:(void *)pointer; 474{ 475 return (vm_address_t)((intptr_t)pointer + 1); 476} 477 478 479- (void *)undisguise:(vm_address_t)disguisedPointer; 480{ 481 return (void *)((intptr_t)disguisedPointer - 1); 482} 483 484- (BOOL)block:(void *)block isInList:(void **)blockList count:(uint32_t)count 485{ 486 BOOL found = NO; 487 uint32_t i; 488 for (i = 0; i < count && !found; i++) 489 found = (blockList[i] == block); 490 return found; 491} 492 493- (auto_zone_t *)auto_zone 494{ 495 return objc_collectableZone(); 496} 497 498 499 500 501 502 503 504/* Test thread support */ 505 506 507#define STACK_BUFFER_SIZE 16 508- (void)testThread 509{ 510 pthread_mutex_lock(&_mutex); 511 _testThread = pthread_self(); 512 SEL s; 513 id stackBuffer[STACK_BUFFER_SIZE]; 514 bzero(stackBuffer, sizeof(stackBuffer)); 515 _disguisedStackBuffer = [self disguise:&stackBuffer[0]]; 516 do { 517 while (_selector == nil) 518 pthread_cond_wait(&_cond, &_mutex); 519 s = _selector; 520 if (s != (SEL)-1) { 521 [self performSelector:s]; 522 [self clearStack]; 523 } 524 _selector = nil; 525 pthread_cond_signal(&_cond); 526 } while (s != (SEL)-1); 527 _disguisedStackBuffer = 0; 528 _testThread = NULL; 529 pthread_mutex_unlock(&_mutex); 530} 531 532void *test_thread_loop(void *arg) { 533 objc_registerThreadWithCollector(); 534 TestCase *testCase = (TestCase *)arg; 535 CFRelease(testCase); 536 [testCase testThread]; 537 return NULL; 538} 539 540- (void)startTestThread 541{ 542 if (_testThread != NULL) { 543 NSLog(@"%@: attempted to start test thread twice", [self className]); 544 exit(-1); 545 } 546 CFRetain(self); 547 pthread_mutex_lock(&_mutex); 548 if (pthread_create(&_testThread, NULL, test_thread_loop, self)) { 549 NSLog(@"pthread_create() failed"); 550 exit(-1); 551 } 552 _selector = @selector(self); 553 while (_selector != nil) 554 pthread_cond_wait(&_cond, &_mutex); 555 pthread_mutex_unlock(&_mutex); 556} 557 558- (void)stopTestThread 559{ 560 pthread_mutex_lock(&_mutex); 561 if (_testThread != NULL) { 562 while (_selector != nil) 563 pthread_cond_wait(&_cond, &_mutex); 564 _selector = (SEL)-1; 565 pthread_cond_signal(&_cond); 566 while (_selector != nil) 567 pthread_cond_wait(&_cond, &_mutex); 568 } 569 pthread_mutex_unlock(&_mutex); 570} 571 572- (void)performSelectorOnTestThread:(SEL)cmd 573{ 574 if (!_testThread) 575 [self startTestThread]; 576 pthread_mutex_lock(&_mutex); 577 while (_selector != nil) 578 pthread_cond_wait(&_cond, &_mutex); 579 _selector = cmd; 580 pthread_cond_signal(&_cond); 581 while (_selector != nil) 582 pthread_cond_wait(&_cond, &_mutex); 583 pthread_mutex_unlock(&_mutex); 584} 585 586- (id *)testThreadStackBuffer 587{ 588 if (pthread_self() != _testThread) { 589 NSLog(@"%@ can only be called on the test thread", self); 590 __builtin_trap(); 591 } 592 return (id *)[self undisguise:_disguisedStackBuffer]; 593} 594 595- (uintptr_t)testThreadStackBufferSize 596{ 597 return STACK_BUFFER_SIZE; 598} 599 600@end 601