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