Split ExceptionUtils out of Test.
1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1.2 +++ b/ExceptionUtils.h Tue May 20 17:40:28 2008 -0700
1.3 @@ -0,0 +1,41 @@
1.4 +//
1.5 +// ExceptionUtils.h
1.6 +// MYUtilities
1.7 +//
1.8 +// Created by Jens Alfke on 1/5/08.
1.9 +// Copyright 2008 Jens Alfke. All rights reserved.
1.10 +// See BSD license at bottom of ExceptionUtils.m.
1.11 +//
1.12 +
1.13 +#import <Cocoa/Cocoa.h>
1.14 +
1.15 +
1.16 +/** Edit your Info.plist to make this your app's principal class,
1.17 + and most exceptions will be reported via a modal alert.
1.18 + This includes exceptions caught by AppKit (i.e. uncaught ones from event handlers)
1.19 + and ones you report yourself via MYReportException and @catchAndReport. */
1.20 +@interface MYExceptionReportingApplication : NSApplication
1.21 +@end
1.22 +
1.23 +
1.24 +/** A useful macro to use in code where you absolutely cannot allow an exception to
1.25 + go uncaught because it would crash (e.g. in a C callback or at the top level of a thread.)
1.26 + It catches the exception but makes sure it gets reported. */
1.27 +#define catchAndReport(MSG...) @catch(NSException *x) {MYReportException(x,MSG);}
1.28 +
1.29 +
1.30 +/** Report an exception that's being caught and consumed.
1.31 + Logs a warning to the console, and calls the current MYReportException target if any. */
1.32 +void MYReportException( NSException *x, NSString *where, ... );
1.33 +
1.34 +
1.35 +/** Sets a callback to be invoked when MYReportException is called.
1.36 + In a GUI app, the callback would typically call [NSApp reportException: theException].
1.37 + The ExceptionReportingApplication class, below, sets this up automatically. */
1.38 +void MYSetExceptionReporter( void (*reporter)(NSException*) );
1.39 +
1.40 +
1.41 +@interface NSException (MYUtilities)
1.42 +/** Returns a textual, human-readable backtrace of the point where the exception was thrown. */
1.43 +- (NSString*) my_callStack;
1.44 +@end
2.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2.2 +++ b/ExceptionUtils.m Tue May 20 17:40:28 2008 -0700
2.3 @@ -0,0 +1,174 @@
2.4 +//
2.5 +// ExceptionUtils.m
2.6 +// MYUtilities
2.7 +//
2.8 +// Created by Jens Alfke on 1/5/08.
2.9 +// Copyright 2008 Jens Alfke. All rights reserved.
2.10 +// See BSD license at bottom of file.
2.11 +//
2.12 +
2.13 +#import "ExceptionUtils.h"
2.14 +#import <unistd.h>
2.15 +
2.16 +
2.17 +#ifndef Warn
2.18 +#define Warn NSLog
2.19 +#endif
2.20 +
2.21 +
2.22 +static void (*sExceptionReporter)(NSException*);
2.23 +
2.24 +void MYSetExceptionReporter( void (*reporter)(NSException*) )
2.25 +{
2.26 + sExceptionReporter = reporter;
2.27 +}
2.28 +
2.29 +void MYReportException( NSException *x, NSString *where, ... )
2.30 +{
2.31 + va_list args;
2.32 + va_start(args,where);
2.33 + where = [[NSString alloc] initWithFormat: where arguments: args];
2.34 + va_end(args);
2.35 + if( sExceptionReporter ) {
2.36 + Warn(@"Exception caught in %@:\n\t%@",where,x);
2.37 + sExceptionReporter(x);
2.38 + }else
2.39 + Warn(@"Exception caught in %@:\n\t%@\n%@",where,x,x.my_callStack);
2.40 + [where release];
2.41 +}
2.42 +
2.43 +
2.44 +@implementation NSException (MooseyardUtil)
2.45 +
2.46 +
2.47 +- (NSArray*) my_callStackReturnAddresses
2.48 +{
2.49 + // On 10.5 or later, can get the backtrace:
2.50 + if( [self respondsToSelector: @selector(callStackReturnAddresses)] )
2.51 + return [self valueForKey: @"callStackReturnAddresses"];
2.52 + else
2.53 + return nil;
2.54 +}
2.55 +
2.56 +- (NSArray*) my_callStackReturnAddressesSkipping: (unsigned)skip limit: (unsigned)limit
2.57 +{
2.58 + NSArray *addresses = [self my_callStackReturnAddresses];
2.59 + if( addresses ) {
2.60 + unsigned n = [addresses count];
2.61 + skip = MIN(skip,n);
2.62 + limit = MIN(limit,n-skip);
2.63 + addresses = [addresses subarrayWithRange: NSMakeRange(skip,limit)];
2.64 + }
2.65 + return addresses;
2.66 +}
2.67 +
2.68 +
2.69 +- (NSString*) my_callStack
2.70 +{
2.71 + NSArray *addresses = [self my_callStackReturnAddressesSkipping: 2 limit: 15];
2.72 + if (!addresses)
2.73 + return nil;
2.74 +
2.75 + // We pipe the hex return addresses through the 'atos' tool to get symbolic names:
2.76 + // Adapted from <http://paste.lisp.org/display/47196>:
2.77 + NSMutableString *cmd = [NSMutableString stringWithFormat: @"/usr/bin/atos -p %d", getpid()];
2.78 + NSValue *addr;
2.79 + foreach(addr,addresses) {
2.80 + [cmd appendFormat: @" %p", [addr pointerValue]];
2.81 + }
2.82 + FILE *file = popen( [cmd UTF8String], "r" );
2.83 + if( ! file )
2.84 + return nil;
2.85 +
2.86 + NSMutableData *output = [NSMutableData data];
2.87 + char buffer[512];
2.88 + size_t length;
2.89 + while ((length = fread( buffer, 1, sizeof( buffer ), file ) ))
2.90 + [output appendBytes: buffer length: length];
2.91 + pclose( file );
2.92 + NSString *outStr = [[[NSString alloc] initWithData: output encoding: NSUTF8StringEncoding] autorelease];
2.93 +
2.94 + NSMutableString *result = [NSMutableString string];
2.95 + NSString *line;
2.96 + foreach( line, [outStr componentsSeparatedByString: @"\n"] ) {
2.97 + // Skip frames that are part of the exception/assertion handling itself:
2.98 + if( [line hasPrefix: @"-[NSAssertionHandler"] || [line hasPrefix: @"+[NSException"]
2.99 + || [line hasPrefix: @"-[NSException"] || [line hasPrefix: @"_AssertFailed"] )
2.100 + continue;
2.101 + if( result.length )
2.102 + [result appendString: @"\n"];
2.103 + [result appendString: @"\t"];
2.104 + [result appendString: line];
2.105 + // Don't show the "__start" frame below "main":
2.106 + if( [line hasPrefix: @"main "] )
2.107 + break;
2.108 + }
2.109 + return result;
2.110 +}
2.111 +
2.112 +@end
2.113 +
2.114 +
2.115 +
2.116 +
2.117 +@implementation MYExceptionReportingApplication
2.118 +
2.119 +
2.120 +static void report( NSException *x ) {
2.121 + [NSApp reportException: x];
2.122 +}
2.123 +
2.124 +- (id) init
2.125 +{
2.126 + self = [super init];
2.127 + if (self != nil) {
2.128 + MYSetExceptionReporter(&report);
2.129 + }
2.130 + return self;
2.131 +}
2.132 +
2.133 +
2.134 +- (void)reportException:(NSException *)x
2.135 +{
2.136 + [super reportException: x];
2.137 + [self performSelector: @selector(_showExceptionAlert:) withObject: x afterDelay: 0.0];
2.138 + MYSetExceptionReporter(NULL); // ignore further exceptions till alert is dismissed
2.139 +}
2.140 +
2.141 +- (void) _showExceptionAlert: (NSException*)x
2.142 +{
2.143 + NSString *stack = [x my_callStack] ?:@"";
2.144 + int r = NSRunCriticalAlertPanel( @"Cloudy Internal Error!",
2.145 + [NSString stringWithFormat: @"Uncaught exception: %@\n%@\n\n%@\n\n"
2.146 + "Please report this bug to jens@mooseyard.com (you can copy & paste the text).",
2.147 + [x name], [x reason], stack],
2.148 + @"Sorry",@"Quit",nil);
2.149 + if( r == NSAlertAlternateReturn )
2.150 + exit(1);
2.151 + MYSetExceptionReporter(&report);
2.152 +}
2.153 +
2.154 +@end
2.155 +
2.156 +
2.157 +/*
2.158 + Copyright (c) 2008, Jens Alfke. All rights reserved.
2.159 +
2.160 + Redistribution and use in source and binary forms, with or without modification, are permitted
2.161 + provided that the following conditions are met:
2.162 +
2.163 + * Redistributions of source code must retain the above copyright notice, this list of conditions
2.164 + and the following disclaimer.
2.165 + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
2.166 + and the following disclaimer in the documentation and/or other materials provided with the
2.167 + distribution.
2.168 +
2.169 + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
2.170 + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
2.171 + FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
2.172 + BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
2.173 + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
2.174 + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
2.175 + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
2.176 + THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
2.177 + */
3.1 --- a/Test.h Sat May 17 13:14:48 2008 -0700
3.2 +++ b/Test.h Tue May 20 17:40:28 2008 -0700
3.3 @@ -88,16 +88,6 @@
3.4 }while(0)
3.5
3.6
3.7 -#pragma mark -
3.8 -#pragma mark EXCEPTION UTILITIES:
3.9 -
3.10 -@interface NSException (MooseyardUtil)
3.11 -/** Returns a textual, human-readable backtrace of the point where the exception was thrown. */
3.12 -- (NSString*) my_callStack;
3.13 -@end
3.14 -
3.15 -
3.16 -
3.17 // Nasty internals ...
3.18 #if DEBUG
3.19 void _RunTestCase( void (*testptr)(), const char *name );
4.1 --- a/Test.m Sat May 17 13:14:48 2008 -0700
4.2 +++ b/Test.m Tue May 20 17:40:28 2008 -0700
4.3 @@ -7,7 +7,7 @@
4.4 //
4.5
4.6 #import "Test.h"
4.7 -#import <unistd.h>
4.8 +
4.9
4.10 #if DEBUG
4.11
4.12 @@ -15,6 +15,13 @@
4.13
4.14 struct TestCaseLink *gAllTestCases;
4.15 static int sPassed, sFailed;
4.16 +static int sCurTestCaseExceptions;
4.17 +
4.18 +
4.19 +static void TestCaseExceptionReporter( NSException *x ) {
4.20 + sCurTestCaseExceptions++;
4.21 + Log(@"XXX FAILED test case -- backtrace:\n%@\n\n", x.my_callStack);
4.22 +}
4.23
4.24 static BOOL RunTestCase( struct TestCaseLink *test )
4.25 {
4.26 @@ -24,10 +31,20 @@
4.27 NSAutoreleasePool *pool = [NSAutoreleasePool new];
4.28 Log(@"=== Testing %s ...",test->name);
4.29 @try{
4.30 - test->testptr();
4.31 - Log(@"√√√ %s passed\n\n",test->name);
4.32 - test->passed = YES;
4.33 - sPassed++;
4.34 + sCurTestCaseExceptions = 0;
4.35 + MYSetExceptionReporter(&TestCaseExceptionReporter);
4.36 +
4.37 + test->testptr(); //SHAZAM!
4.38 +
4.39 + if( sCurTestCaseExceptions == 0 ) {
4.40 + Log(@"√√√ %s passed\n\n",test->name);
4.41 + test->passed = YES;
4.42 + sPassed++;
4.43 + } else {
4.44 + Log(@"XXX FAILED test case '%s' due to %i exception(s) already reported above",
4.45 + test->name,sCurTestCaseExceptions);
4.46 + sFailed++;
4.47 + }
4.48 }@catch( NSException *x ) {
4.49 if( [x.name isEqualToString: @"TestCaseSkipped"] )
4.50 Log(@"... skipping test %s since %@\n\n", test->name, x.reason);
4.51 @@ -131,78 +148,3 @@
4.52 description: @"%@", message];
4.53 abort(); // unreachable, but appeases compiler
4.54 }
4.55 -
4.56 -
4.57 -#pragma mark -
4.58 -#pragma mark EXCEPTION BACKTRACES:
4.59 -
4.60 -
4.61 -@implementation NSException (MooseyardUtil)
4.62 -
4.63 -
4.64 -- (NSArray*) my_callStackReturnAddresses
4.65 -{
4.66 - // On 10.5 or later, can get the backtrace:
4.67 - if( [self respondsToSelector: @selector(callStackReturnAddresses)] )
4.68 - return [self valueForKey: @"callStackReturnAddresses"];
4.69 - else
4.70 - return nil;
4.71 -}
4.72 -
4.73 -- (NSArray*) my_callStackReturnAddressesSkipping: (unsigned)skip limit: (unsigned)limit
4.74 -{
4.75 - NSArray *addresses = [self my_callStackReturnAddresses];
4.76 - if( addresses ) {
4.77 - unsigned n = [addresses count];
4.78 - skip = MIN(skip,n);
4.79 - limit = MIN(limit,n-skip);
4.80 - addresses = [addresses subarrayWithRange: NSMakeRange(skip,limit)];
4.81 - }
4.82 - return addresses;
4.83 -}
4.84 -
4.85 -
4.86 -- (NSString*) my_callStack
4.87 -{
4.88 - NSArray *addresses = [self my_callStackReturnAddressesSkipping: 2 limit: 15];
4.89 - if (!addresses)
4.90 - return nil;
4.91 -
4.92 - // We pipe the hex return addresses through the 'atos' tool to get symbolic names:
4.93 - // Adapted from <http://paste.lisp.org/display/47196>:
4.94 - NSMutableString *cmd = [NSMutableString stringWithFormat: @"/usr/bin/atos -p %d", getpid()];
4.95 - NSValue *addr;
4.96 - foreach(addr,addresses) {
4.97 - [cmd appendFormat: @" %p", [addr pointerValue]];
4.98 - }
4.99 - FILE *file = popen( [cmd UTF8String], "r" );
4.100 - if( ! file )
4.101 - return nil;
4.102 -
4.103 - NSMutableData *output = [NSMutableData data];
4.104 - char buffer[512];
4.105 - size_t length;
4.106 - while ((length = fread( buffer, 1, sizeof( buffer ), file ) ))
4.107 - [output appendBytes: buffer length: length];
4.108 - pclose( file );
4.109 - NSString *outStr = [[[NSString alloc] initWithData: output encoding: NSUTF8StringEncoding] autorelease];
4.110 -
4.111 - NSMutableString *result = [NSMutableString string];
4.112 - NSString *line;
4.113 - foreach( line, [outStr componentsSeparatedByString: @"\n"] ) {
4.114 - // Skip frames that are part of the exception/assertion handling itself:
4.115 - if( [line hasPrefix: @"-[NSAssertionHandler"] || [line hasPrefix: @"+[NSException"]
4.116 - || [line hasPrefix: @"-[NSException"] || [line hasPrefix: @"_AssertFailed"] )
4.117 - continue;
4.118 - if( result.length )
4.119 - [result appendString: @"\n"];
4.120 - [result appendString: @"\t"];
4.121 - [result appendString: line];
4.122 - // Don't show the "__start" frame below "main":
4.123 - if( [line hasPrefix: @"main "] )
4.124 - break;
4.125 - }
4.126 - return result;
4.127 -}
4.128 -
4.129 -@end