jens@0: // jens@0: // Test.m jens@0: // MYUtilities jens@0: // jens@0: // Created by Jens Alfke on 1/5/08. jens@0: // Copyright 2008 Jens Alfke. All rights reserved. jens@0: // jens@0: jens@0: #import "Test.h" jens@0: #import jens@0: jens@0: #if DEBUG jens@0: jens@0: struct TestCaseLink *gAllTestCases; jens@0: static int sPassed, sFailed; jens@0: jens@0: static BOOL RunTestCase( struct TestCaseLink *test ) jens@0: { jens@0: if( test->testptr ) { jens@0: NSAutoreleasePool *pool = [NSAutoreleasePool new]; jens@0: Log(@"=== Testing %s ...",test->name); jens@0: @try{ jens@0: test->testptr(); jens@0: Log(@"√√√ %s passed\n\n",test->name); jens@0: test->passed = YES; jens@0: sPassed++; jens@0: }@catch( NSException *x ) { jens@0: if( [x.name isEqualToString: @"TestCaseSkipped"] ) jens@0: Log(@"... skipping test %s since %@\n\n", test->name, x.reason); jens@0: else { jens@0: Log(@"XXX FAILED test case '%s' due to:\nException: %@\n%@\n\n", jens@0: test->name,x,x.my_callStack); jens@0: sFailed++; jens@0: } jens@0: }@finally{ jens@0: [pool drain]; jens@0: test->testptr = NULL; // prevents test from being run again jens@0: } jens@0: } jens@0: return test->passed; jens@0: } jens@0: jens@0: jens@0: static BOOL RunTestCaseNamed( const char *name ) jens@0: { jens@0: for( struct TestCaseLink *test = gAllTestCases; test; test=test->next ) jens@0: if( strcmp(name,test->name)==0 ) { jens@0: return RunTestCase(test); jens@0: } jens@0: Log(@"... WARNING: Could not find test case named '%s'\n\n",name); jens@0: return NO; jens@0: } jens@0: jens@0: jens@0: void _RequireTestCase( const char *name ) jens@0: { jens@0: if( ! RunTestCaseNamed(name) ) { jens@0: [NSException raise: @"TestCaseSkipped" jens@0: format: @"prerequisite %s failed", name]; jens@0: } jens@0: } jens@0: jens@0: jens@0: void RunTestCases( int argc, const char **argv ) jens@0: { jens@0: gShouldLog = YES; jens@0: sPassed = sFailed = 0; jens@0: BOOL stopAfterTests = NO; jens@0: for( int i=1; inext ) jens@0: RunTestCase(link); jens@0: } else { jens@0: RunTestCaseNamed(arg); jens@0: } jens@0: } jens@0: } jens@0: if( sPassed>0 || sFailed>0 || stopAfterTests ) { jens@0: if( sFailed==0 ) jens@0: Log(@"√√√√√√ ALL %i TESTS PASSED √√√√√√", sPassed); jens@0: else { jens@0: Log(@"****** %i TESTS FAILED, %i PASSED ******", sFailed,sPassed); jens@0: exit(1); jens@0: } jens@0: if( stopAfterTests ) { jens@0: Log(@"Stopping after tests ('Test_Only' arg detected)"); jens@0: exit(0); jens@0: } jens@0: } jens@0: } jens@0: jens@0: jens@0: #endif DEBUG jens@0: jens@0: jens@0: #pragma mark - jens@0: #pragma mark ASSERTION FAILURE HANDLER: jens@0: jens@0: jens@0: void _AssertFailed( id rcvr, const char *selOrFn, const char *sourceFile, int sourceLine, jens@0: const char *condString, NSString *message, ... ) jens@0: { jens@0: if( message ) { jens@0: va_list args; jens@0: va_start(args,message); jens@0: message = [[[NSString alloc] initWithFormat: message arguments: args] autorelease]; jens@0: va_end(args); jens@0: } else jens@0: message = [NSString stringWithUTF8String: condString]; jens@0: jens@0: if( rcvr ) jens@0: [[NSAssertionHandler currentHandler] handleFailureInMethod: (SEL)selOrFn jens@0: object: rcvr jens@0: file: [NSString stringWithUTF8String: sourceFile] jens@0: lineNumber: sourceLine jens@0: description: @"%@", message]; jens@0: else jens@0: [[NSAssertionHandler currentHandler] handleFailureInFunction: [NSString stringWithUTF8String:selOrFn] jens@0: file: [NSString stringWithUTF8String: sourceFile] jens@0: lineNumber: sourceLine jens@0: description: @"%@", message]; jens@0: abort(); // unreachable, but appeases compiler jens@0: } jens@0: jens@0: jens@0: #pragma mark - jens@0: #pragma mark EXCEPTION BACKTRACES: jens@0: jens@0: jens@0: @implementation NSException (MooseyardUtil) jens@0: jens@0: jens@0: - (NSArray*) my_callStackReturnAddresses jens@0: { jens@0: // On 10.5 or later, can get the backtrace: jens@0: if( [self respondsToSelector: @selector(callStackReturnAddresses)] ) jens@0: return [self valueForKey: @"callStackReturnAddresses"]; jens@0: else jens@0: return nil; jens@0: } jens@0: jens@0: - (NSArray*) my_callStackReturnAddressesSkipping: (unsigned)skip limit: (unsigned)limit jens@0: { jens@0: NSArray *addresses = [self my_callStackReturnAddresses]; jens@0: if( addresses ) { jens@0: unsigned n = [addresses count]; jens@0: skip = MIN(skip,n); jens@0: limit = MIN(limit,n-skip); jens@0: addresses = [addresses subarrayWithRange: NSMakeRange(skip,limit)]; jens@0: } jens@0: return addresses; jens@0: } jens@0: jens@0: jens@0: - (NSString*) my_callStack jens@0: { jens@0: NSArray *addresses = [self my_callStackReturnAddressesSkipping: 2 limit: 15]; jens@0: if (!addresses) jens@0: return nil; jens@0: jens@0: // We pipe the hex return addresses through the 'atos' tool to get symbolic names: jens@0: // Adapted from : jens@0: NSMutableString *cmd = [NSMutableString stringWithFormat: @"/usr/bin/atos -p %d", getpid()]; jens@0: NSValue *addr; jens@0: foreach(addr,addresses) { jens@0: [cmd appendFormat: @" %p", [addr pointerValue]]; jens@0: } jens@0: FILE *file = popen( [cmd UTF8String], "r" ); jens@0: if( ! file ) jens@0: return nil; jens@0: jens@0: NSMutableData *output = [NSMutableData data]; jens@0: char buffer[512]; jens@0: size_t length; jens@0: while ((length = fread( buffer, 1, sizeof( buffer ), file ) )) jens@0: [output appendBytes: buffer length: length]; jens@0: pclose( file ); jens@0: NSString *outStr = [[[NSString alloc] initWithData: output encoding: NSUTF8StringEncoding] autorelease]; jens@0: jens@0: NSMutableString *result = [NSMutableString string]; jens@0: NSString *line; jens@0: foreach( line, [outStr componentsSeparatedByString: @"\n"] ) { jens@0: // Skip frames that are part of the exception/assertion handling itself: jens@0: if( [line hasPrefix: @"-[NSAssertionHandler"] || [line hasPrefix: @"+[NSException"] jens@0: || [line hasPrefix: @"-[NSException"] || [line hasPrefix: @"_AssertFailed"] ) jens@0: continue; jens@0: if( result.length ) jens@0: [result appendString: @"\n"]; jens@0: [result appendString: @"\t"]; jens@0: [result appendString: line]; jens@0: // Don't show the "__start" frame below "main": jens@0: if( [line hasPrefix: @"main "] ) jens@0: break; jens@0: } jens@0: return result; jens@0: } jens@0: jens@0: @end