Test.m
author Jens Alfke <jens@mooseyard.com>
Mon Apr 14 13:58:48 2008 -0700 (2008-04-14)
changeset 4 64823cdde6a5
parent 0 d84d25d6cdbb
child 10 82a37ccf6b8c
permissions -rw-r--r--
Minor improvements.
     1 //
     2 //  Test.m
     3 //  MYUtilities
     4 //
     5 //  Created by Jens Alfke on 1/5/08.
     6 //  Copyright 2008 Jens Alfke. All rights reserved.
     7 //
     8 
     9 #import "Test.h"
    10 #import <unistd.h>
    11 
    12 #if DEBUG
    13 
    14 BOOL gRunningTestCase;
    15 
    16 struct TestCaseLink *gAllTestCases;
    17 static int sPassed, sFailed;
    18 
    19 static BOOL RunTestCase( struct TestCaseLink *test )
    20 {
    21     BOOL oldLogging = EnableLog(YES);
    22     gRunningTestCase = YES;
    23     if( test->testptr ) {
    24         NSAutoreleasePool *pool = [NSAutoreleasePool new];
    25         Log(@"=== Testing %s ...",test->name);
    26         @try{
    27             test->testptr();
    28             Log(@"√√√ %s passed\n\n",test->name);
    29             test->passed = YES;
    30             sPassed++;
    31         }@catch( NSException *x ) {
    32             if( [x.name isEqualToString: @"TestCaseSkipped"] )
    33                 Log(@"... skipping test %s since %@\n\n", test->name, x.reason);
    34             else {
    35                 Log(@"XXX FAILED test case '%s' due to:\nException: %@\n%@\n\n", 
    36                       test->name,x,x.my_callStack);
    37                 sFailed++;
    38             }
    39         }@finally{
    40             [pool drain];
    41             test->testptr = NULL;       // prevents test from being run again
    42         }
    43     }
    44     gRunningTestCase = NO;
    45     EnableLog(oldLogging);
    46     return test->passed;
    47 }
    48 
    49 
    50 static BOOL RunTestCaseNamed( const char *name )
    51 {
    52     for( struct TestCaseLink *test = gAllTestCases; test; test=test->next )
    53         if( strcmp(name,test->name)==0 ) {
    54             return RunTestCase(test);
    55         }
    56     Log(@"... WARNING: Could not find test case named '%s'\n\n",name);
    57     return NO;
    58 }
    59 
    60 
    61 void _RequireTestCase( const char *name )
    62 {
    63     if( ! RunTestCaseNamed(name) ) {
    64         [NSException raise: @"TestCaseSkipped" 
    65                     format: @"prerequisite %s failed", name];
    66     }
    67 }
    68 
    69 
    70 void RunTestCases( int argc, const char **argv )
    71 {
    72     sPassed = sFailed = 0;
    73     BOOL stopAfterTests = NO;
    74     for( int i=1; i<argc; i++ ) {
    75         const char *arg = argv[i];
    76         if( strncmp(arg,"Test_",5)==0 ) {
    77             arg += 5;
    78             if( strcmp(arg,"Only")==0 )
    79                 stopAfterTests = YES;
    80             else if( strcmp(arg,"All") == 0 ) {
    81                 for( struct TestCaseLink *link = gAllTestCases; link; link=link->next )
    82                     RunTestCase(link);
    83             } else {
    84                 RunTestCaseNamed(arg);
    85             }
    86         }
    87     }
    88     if( sPassed>0 || sFailed>0 || stopAfterTests ) {
    89         if( sFailed==0 )
    90             Log(@"√√√√√√ ALL %i TESTS PASSED √√√√√√", sPassed);
    91         else {
    92             Log(@"****** %i TESTS FAILED, %i PASSED ******", sFailed,sPassed);
    93             exit(1);
    94         }
    95         if( stopAfterTests ) {
    96             Log(@"Stopping after tests ('Test_Only' arg detected)");
    97             exit(0);
    98         }
    99     }
   100 }
   101 
   102 
   103 #endif DEBUG
   104 
   105 
   106 #pragma mark -
   107 #pragma mark ASSERTION FAILURE HANDLER:
   108 
   109 
   110 void _AssertFailed( id rcvr, const char *selOrFn, const char *sourceFile, int sourceLine,
   111                     const char *condString, NSString *message, ... )
   112 {
   113     if( message ) {
   114         va_list args;
   115         va_start(args,message);
   116         message = [[[NSString alloc] initWithFormat: message arguments: args] autorelease];
   117         va_end(args);
   118     } else
   119         message = [NSString stringWithUTF8String: condString];
   120     
   121     if( rcvr )
   122         [[NSAssertionHandler currentHandler] handleFailureInMethod: (SEL)selOrFn
   123                                                             object: rcvr 
   124                                                               file: [NSString stringWithUTF8String: sourceFile]
   125                                                         lineNumber: sourceLine 
   126                                                        description: @"%@", message];
   127     else
   128         [[NSAssertionHandler currentHandler] handleFailureInFunction: [NSString stringWithUTF8String:selOrFn]
   129                                                                 file: [NSString stringWithUTF8String: sourceFile]
   130                                                           lineNumber: sourceLine 
   131                                                          description: @"%@", message];
   132     abort(); // unreachable, but appeases compiler
   133 }
   134 
   135 
   136 #pragma mark -
   137 #pragma mark EXCEPTION BACKTRACES:
   138 
   139 
   140 @implementation NSException (MooseyardUtil)
   141 
   142 
   143 - (NSArray*) my_callStackReturnAddresses
   144 {
   145     // On 10.5 or later, can get the backtrace:
   146     if( [self respondsToSelector: @selector(callStackReturnAddresses)] )
   147         return [self valueForKey: @"callStackReturnAddresses"];
   148     else
   149         return nil;
   150 }
   151 
   152 - (NSArray*) my_callStackReturnAddressesSkipping: (unsigned)skip limit: (unsigned)limit
   153 {
   154     NSArray *addresses = [self my_callStackReturnAddresses];
   155     if( addresses ) {
   156         unsigned n = [addresses count];
   157         skip = MIN(skip,n);
   158         limit = MIN(limit,n-skip);
   159         addresses = [addresses subarrayWithRange: NSMakeRange(skip,limit)];
   160     }
   161     return addresses;
   162 }
   163 
   164 
   165 - (NSString*) my_callStack
   166 {
   167     NSArray *addresses = [self my_callStackReturnAddressesSkipping: 2 limit: 15];
   168     if (!addresses)
   169         return nil;
   170     
   171     // We pipe the hex return addresses through the 'atos' tool to get symbolic names:
   172     // Adapted from <http://paste.lisp.org/display/47196>:
   173     NSMutableString *cmd = [NSMutableString stringWithFormat: @"/usr/bin/atos -p %d", getpid()];
   174     NSValue *addr;
   175     foreach(addr,addresses) {
   176         [cmd appendFormat: @" %p", [addr pointerValue]];
   177     }
   178     FILE *file = popen( [cmd UTF8String], "r" );
   179     if( ! file )
   180         return nil;
   181     
   182     NSMutableData *output = [NSMutableData data];
   183     char buffer[512];
   184     size_t length;
   185     while ((length = fread( buffer, 1, sizeof( buffer ), file ) ))
   186         [output appendBytes: buffer length: length];
   187     pclose( file );
   188     NSString *outStr = [[[NSString alloc] initWithData: output encoding: NSUTF8StringEncoding] autorelease];
   189     
   190     NSMutableString *result = [NSMutableString string];
   191     NSString *line;
   192     foreach( line, [outStr componentsSeparatedByString: @"\n"] ) {
   193         // Skip  frames that are part of the exception/assertion handling itself:
   194         if( [line hasPrefix: @"-[NSAssertionHandler"] || [line hasPrefix: @"+[NSException"] 
   195                 || [line hasPrefix: @"-[NSException"] || [line hasPrefix: @"_AssertFailed"] )
   196             continue;
   197         if( result.length )
   198             [result appendString: @"\n"];
   199         [result appendString: @"\t"];
   200         [result appendString: line];
   201         // Don't show the "__start" frame below "main":
   202         if( [line hasPrefix: @"main "] )
   203             break;
   204     }
   205     return result;
   206 }
   207 
   208 @end