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