toolkit/crashreporter/google-breakpad/src/common/mac/testing/GTMSenTestCase.m

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

     1 //
     2 //  GTMSenTestCase.m
     3 //
     4 //  Copyright 2007-2008 Google Inc.
     5 //
     6 //  Licensed under the Apache License, Version 2.0 (the "License"); you may not
     7 //  use this file except in compliance with the License.  You may obtain a copy
     8 //  of the License at
     9 //
    10 //  http://www.apache.org/licenses/LICENSE-2.0
    11 //
    12 //  Unless required by applicable law or agreed to in writing, software
    13 //  distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    14 //  WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
    15 //  License for the specific language governing permissions and limitations under
    16 //  the License.
    17 //
    19 #import "GTMSenTestCase.h"
    21 #import <unistd.h>
    22 #if GTM_IPHONE_SIMULATOR
    23 #import <objc/message.h>
    24 #endif
    26 #import "GTMObjC2Runtime.h"
    27 #import "GTMUnitTestDevLog.h"
    29 #if !GTM_IPHONE_SDK
    30 #import "GTMGarbageCollection.h"
    31 #endif  // !GTM_IPHONE_SDK
    33 #if GTM_IPHONE_SDK && !GTM_IPHONE_USE_SENTEST
    34 #import <stdarg.h>
    36 @interface NSException (GTMSenTestPrivateAdditions)
    37 + (NSException *)failureInFile:(NSString *)filename
    38                         atLine:(int)lineNumber
    39                         reason:(NSString *)reason;
    40 @end
    42 @implementation NSException (GTMSenTestPrivateAdditions)
    43 + (NSException *)failureInFile:(NSString *)filename
    44                         atLine:(int)lineNumber
    45                         reason:(NSString *)reason {
    46   NSDictionary *userInfo =
    47     [NSDictionary dictionaryWithObjectsAndKeys:
    48      [NSNumber numberWithInteger:lineNumber], SenTestLineNumberKey,
    49      filename, SenTestFilenameKey,
    50      nil];
    52   return [self exceptionWithName:SenTestFailureException
    53                           reason:reason
    54                         userInfo:userInfo];
    55 }
    56 @end
    58 @implementation NSException (GTMSenTestAdditions)
    60 + (NSException *)failureInFile:(NSString *)filename
    61                         atLine:(int)lineNumber
    62                withDescription:(NSString *)formatString, ... {
    64   NSString *testDescription = @"";
    65   if (formatString) {
    66     va_list vl;
    67     va_start(vl, formatString);
    68     testDescription =
    69       [[[NSString alloc] initWithFormat:formatString arguments:vl] autorelease];
    70     va_end(vl);
    71   }
    73   NSString *reason = testDescription;
    75   return [self failureInFile:filename atLine:lineNumber reason:reason];
    76 }
    78 + (NSException *)failureInCondition:(NSString *)condition
    79                              isTrue:(BOOL)isTrue
    80                              inFile:(NSString *)filename
    81                              atLine:(int)lineNumber
    82                     withDescription:(NSString *)formatString, ... {
    84   NSString *testDescription = @"";
    85   if (formatString) {
    86     va_list vl;
    87     va_start(vl, formatString);
    88     testDescription =
    89       [[[NSString alloc] initWithFormat:formatString arguments:vl] autorelease];
    90     va_end(vl);
    91   }
    93   NSString *reason = [NSString stringWithFormat:@"'%@' should be %s. %@",
    94                       condition, isTrue ? "false" : "true", testDescription];
    96   return [self failureInFile:filename atLine:lineNumber reason:reason];
    97 }
    99 + (NSException *)failureInEqualityBetweenObject:(id)left
   100                                       andObject:(id)right
   101                                          inFile:(NSString *)filename
   102                                          atLine:(int)lineNumber
   103                                 withDescription:(NSString *)formatString, ... {
   105   NSString *testDescription = @"";
   106   if (formatString) {
   107     va_list vl;
   108     va_start(vl, formatString);
   109     testDescription =
   110       [[[NSString alloc] initWithFormat:formatString arguments:vl] autorelease];
   111     va_end(vl);
   112   }
   114   NSString *reason =
   115     [NSString stringWithFormat:@"'%@' should be equal to '%@'. %@",
   116      [left description], [right description], testDescription];
   118   return [self failureInFile:filename atLine:lineNumber reason:reason];
   119 }
   121 + (NSException *)failureInEqualityBetweenValue:(NSValue *)left
   122                                       andValue:(NSValue *)right
   123                                   withAccuracy:(NSValue *)accuracy
   124                                         inFile:(NSString *)filename
   125                                         atLine:(int)lineNumber
   126                                withDescription:(NSString *)formatString, ... {
   128   NSString *testDescription = @"";
   129   if (formatString) {
   130     va_list vl;
   131     va_start(vl, formatString);
   132     testDescription =
   133       [[[NSString alloc] initWithFormat:formatString arguments:vl] autorelease];
   134     va_end(vl);
   135   }
   137   NSString *reason;
   138   if (accuracy) {
   139     reason =
   140       [NSString stringWithFormat:@"'%@' should be equal to '%@'. %@",
   141        left, right, testDescription];
   142   } else {
   143     reason =
   144       [NSString stringWithFormat:@"'%@' should be equal to '%@' +/-'%@'. %@",
   145        left, right, accuracy, testDescription];
   146   }
   148   return [self failureInFile:filename atLine:lineNumber reason:reason];
   149 }
   151 + (NSException *)failureInRaise:(NSString *)expression
   152                          inFile:(NSString *)filename
   153                          atLine:(int)lineNumber
   154                 withDescription:(NSString *)formatString, ... {
   156   NSString *testDescription = @"";
   157   if (formatString) {
   158     va_list vl;
   159     va_start(vl, formatString);
   160     testDescription =
   161       [[[NSString alloc] initWithFormat:formatString arguments:vl] autorelease];
   162     va_end(vl);
   163   }
   165   NSString *reason = [NSString stringWithFormat:@"'%@' should raise. %@",
   166                       expression, testDescription];
   168   return [self failureInFile:filename atLine:lineNumber reason:reason];
   169 }
   171 + (NSException *)failureInRaise:(NSString *)expression
   172                       exception:(NSException *)exception
   173                          inFile:(NSString *)filename
   174                          atLine:(int)lineNumber
   175                 withDescription:(NSString *)formatString, ... {
   177   NSString *testDescription = @"";
   178   if (formatString) {
   179     va_list vl;
   180     va_start(vl, formatString);
   181     testDescription =
   182       [[[NSString alloc] initWithFormat:formatString arguments:vl] autorelease];
   183     va_end(vl);
   184   }
   186   NSString *reason;
   187   if ([[exception name] isEqualToString:SenTestFailureException]) {
   188     // it's our exception, assume it has the right description on it.
   189     reason = [exception reason];
   190   } else {
   191     // not one of our exception, use the exceptions reason and our description
   192     reason = [NSString stringWithFormat:@"'%@' raised '%@'. %@",
   193               expression, [exception reason], testDescription];
   194   }
   196   return [self failureInFile:filename atLine:lineNumber reason:reason];
   197 }
   199 @end
   201 NSString *STComposeString(NSString *formatString, ...) {
   202   NSString *reason = @"";
   203   if (formatString) {
   204     va_list vl;
   205     va_start(vl, formatString);
   206     reason =
   207       [[[NSString alloc] initWithFormat:formatString arguments:vl] autorelease];
   208     va_end(vl);
   209   }
   210   return reason;
   211 }
   213 NSString *const SenTestFailureException = @"SenTestFailureException";
   214 NSString *const SenTestFilenameKey = @"SenTestFilenameKey";
   215 NSString *const SenTestLineNumberKey = @"SenTestLineNumberKey";
   217 @interface SenTestCase (SenTestCasePrivate)
   218 // our method of logging errors
   219 + (void)printException:(NSException *)exception fromTestName:(NSString *)name;
   220 @end
   222 @implementation SenTestCase
   223 + (id)testCaseWithInvocation:(NSInvocation *)anInvocation {
   224   return [[[self alloc] initWithInvocation:anInvocation] autorelease];
   225 }
   227 - (id)initWithInvocation:(NSInvocation *)anInvocation {
   228   if ((self = [super init])) {
   229     invocation_ = [anInvocation retain];
   230   }
   231   return self;
   232 }
   234 - (void)dealloc {
   235   [invocation_ release];
   236   [super dealloc];
   237 }
   239 - (void)failWithException:(NSException*)exception {
   240   [exception raise];
   241 }
   243 - (void)setUp {
   244 }
   246 - (void)performTest {
   247   @try {
   248     [self invokeTest];
   249   } @catch (NSException *exception) {
   250     [[self class] printException:exception
   251                     fromTestName:NSStringFromSelector([self selector])];
   252     [exception raise];
   253   }
   254 }
   256 - (NSInvocation *)invocation {
   257   return invocation_;
   258 }
   260 - (SEL)selector {
   261   return [invocation_ selector];
   262 }
   264 + (void)printException:(NSException *)exception fromTestName:(NSString *)name {
   265   NSDictionary *userInfo = [exception userInfo];
   266   NSString *filename = [userInfo objectForKey:SenTestFilenameKey];
   267   NSNumber *lineNumber = [userInfo objectForKey:SenTestLineNumberKey];
   268   NSString *className = NSStringFromClass([self class]);
   269   if ([filename length] == 0) {
   270     filename = @"Unknown.m";
   271   }
   272   fprintf(stderr, "%s:%ld: error: -[%s %s] : %s\n",
   273           [filename UTF8String],
   274           (long)[lineNumber integerValue],
   275           [className UTF8String],
   276           [name UTF8String],
   277           [[exception reason] UTF8String]);
   278   fflush(stderr);
   279 }
   281 - (void)invokeTest {
   282   NSException *e = nil;
   283   @try {
   284     // Wrap things in autorelease pools because they may
   285     // have an STMacro in their dealloc which may get called
   286     // when the pool is cleaned up
   287     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
   288     // We don't log exceptions here, instead we let the person that called
   289     // this log the exception.  This ensures they are only logged once but the
   290     // outer layers get the exceptions to report counts, etc.
   291     @try {
   292       [self setUp];
   293       @try {
   294         NSInvocation *invocation = [self invocation];
   295 #if GTM_IPHONE_SIMULATOR
   296         // We don't call [invocation invokeWithTarget:self]; because of
   297         // Radar 8081169: NSInvalidArgumentException can't be caught
   298         // It turns out that on iOS4 (and 3.2) exceptions thrown inside an
   299         // [invocation invoke] on the simulator cannot be caught.
   300         // http://openradar.appspot.com/8081169
   301         objc_msgSend(self, [invocation selector]);
   302 #else
   303         [invocation invokeWithTarget:self];
   304 #endif
   305       } @catch (NSException *exception) {
   306         e = [exception retain];
   307       }
   308       [self tearDown];
   309     } @catch (NSException *exception) {
   310       e = [exception retain];
   311     }
   312     [pool release];
   313   } @catch (NSException *exception) {
   314     e = [exception retain];
   315   }
   316   if (e) {
   317     [e autorelease];
   318     [e raise];
   319   }
   320 }
   322 - (void)tearDown {
   323 }
   325 - (NSString *)description {
   326   // This matches the description OCUnit would return to you
   327   return [NSString stringWithFormat:@"-[%@ %@]", [self class],
   328           NSStringFromSelector([self selector])];
   329 }
   331 // Used for sorting methods below
   332 static int MethodSort(id a, id b, void *context) {
   333   NSInvocation *invocationA = a;
   334   NSInvocation *invocationB = b;
   335   const char *nameA = sel_getName([invocationA selector]);
   336   const char *nameB = sel_getName([invocationB selector]);
   337   return strcmp(nameA, nameB);
   338 }
   341 + (NSArray *)testInvocations {
   342   NSMutableArray *invocations = nil;
   343   // Need to walk all the way up the parent classes collecting methods (in case
   344   // a test is a subclass of another test).
   345   Class senTestCaseClass = [SenTestCase class];
   346   for (Class currentClass = self;
   347        currentClass && (currentClass != senTestCaseClass);
   348        currentClass = class_getSuperclass(currentClass)) {
   349     unsigned int methodCount;
   350     Method *methods = class_copyMethodList(currentClass, &methodCount);
   351     if (methods) {
   352       // This handles disposing of methods for us even if an exception should fly.
   353       [NSData dataWithBytesNoCopy:methods
   354                            length:sizeof(Method) * methodCount];
   355       if (!invocations) {
   356         invocations = [NSMutableArray arrayWithCapacity:methodCount];
   357       }
   358       for (size_t i = 0; i < methodCount; ++i) {
   359         Method currMethod = methods[i];
   360         SEL sel = method_getName(currMethod);
   361         char *returnType = NULL;
   362         const char *name = sel_getName(sel);
   363         // If it starts with test, takes 2 args (target and sel) and returns
   364         // void run it.
   365         if (strstr(name, "test") == name) {
   366           returnType = method_copyReturnType(currMethod);
   367           if (returnType) {
   368             // This handles disposing of returnType for us even if an
   369             // exception should fly. Length +1 for the terminator, not that
   370             // the length really matters here, as we never reference inside
   371             // the data block.
   372             [NSData dataWithBytesNoCopy:returnType
   373                                  length:strlen(returnType) + 1];
   374           }
   375         }
   376         // TODO: If a test class is a subclass of another, and they reuse the
   377         // same selector name (ie-subclass overrides it), this current loop
   378         // and test here will cause cause it to get invoked twice.  To fix this
   379         // the selector would have to be checked against all the ones already
   380         // added, so it only gets done once.
   381         if (returnType  // True if name starts with "test"
   382             && strcmp(returnType, @encode(void)) == 0
   383             && method_getNumberOfArguments(currMethod) == 2) {
   384           NSMethodSignature *sig = [self instanceMethodSignatureForSelector:sel];
   385           NSInvocation *invocation
   386             = [NSInvocation invocationWithMethodSignature:sig];
   387           [invocation setSelector:sel];
   388           [invocations addObject:invocation];
   389         }
   390       }
   391     }
   392   }
   393   // Match SenTestKit and run everything in alphbetical order.
   394   [invocations sortUsingFunction:MethodSort context:nil];
   395   return invocations;
   396 }
   398 @end
   400 #endif  // GTM_IPHONE_SDK && !GTM_IPHONE_USE_SENTEST
   402 @implementation GTMTestCase : SenTestCase
   403 - (void)invokeTest {
   404   NSAutoreleasePool *localPool = [[NSAutoreleasePool alloc] init];
   405   Class devLogClass = NSClassFromString(@"GTMUnitTestDevLog");
   406   if (devLogClass) {
   407     [devLogClass performSelector:@selector(enableTracking)];
   408     [devLogClass performSelector:@selector(verifyNoMoreLogsExpected)];
   410   }
   411   [super invokeTest];
   412   if (devLogClass) {
   413     [devLogClass performSelector:@selector(verifyNoMoreLogsExpected)];
   414     [devLogClass performSelector:@selector(disableTracking)];
   415   }
   416   [localPool drain];
   417 }
   419 + (BOOL)isAbstractTestCase {
   420   NSString *name = NSStringFromClass(self);
   421   return [name rangeOfString:@"AbstractTest"].location != NSNotFound;
   422 }
   424 + (NSArray *)testInvocations {
   425   NSArray *invocations = nil;
   426   if (![self isAbstractTestCase]) {
   427     invocations = [super testInvocations];
   428   }
   429   return invocations;
   430 }
   432 @end
   434 // Leak detection
   435 #if !GTM_IPHONE_DEVICE && !GTM_SUPPRESS_RUN_LEAKS_HOOK
   436 // Don't want to get leaks on the iPhone Device as the device doesn't
   437 // have 'leaks'. The simulator does though.
   439 // COV_NF_START
   440 // We don't have leak checking on by default, so this won't be hit.
   441 static void _GTMRunLeaks(void) {
   442   // This is an atexit handler. It runs leaks for us to check if we are
   443   // leaking anything in our tests.
   444   const char* cExclusionsEnv = getenv("GTM_LEAKS_SYMBOLS_TO_IGNORE");
   445   NSMutableString *exclusions = [NSMutableString string];
   446   if (cExclusionsEnv) {
   447     NSString *exclusionsEnv = [NSString stringWithUTF8String:cExclusionsEnv];
   448     NSArray *exclusionsArray = [exclusionsEnv componentsSeparatedByString:@","];
   449     NSString *exclusion;
   450     NSCharacterSet *wcSet = [NSCharacterSet whitespaceCharacterSet];
   451     GTM_FOREACH_OBJECT(exclusion, exclusionsArray) {
   452       exclusion = [exclusion stringByTrimmingCharactersInSet:wcSet];
   453       [exclusions appendFormat:@"-exclude \"%@\" ", exclusion];
   454     }
   455   }
   456   // Clearing out DYLD_ROOT_PATH because iPhone Simulator framework libraries
   457   // are different from regular OS X libraries and leaks will fail to run
   458   // because of missing symbols. Also capturing the output of leaks and then
   459   // pipe rather than a direct pipe, because otherwise if leaks failed,
   460   // the system() call will still be successful. Bug:
   461   // http://code.google.com/p/google-toolbox-for-mac/issues/detail?id=56
   462   NSString *string
   463     = [NSString stringWithFormat:
   464        @"LeakOut=`DYLD_ROOT_PATH='' /usr/bin/leaks %@%d` &&"
   465        @"echo \"$LeakOut\"|/usr/bin/sed -e 's/Leak: /Leaks:0: warning: Leak /'",
   466        exclusions, getpid()];
   467   int ret = system([string UTF8String]);
   468   if (ret) {
   469     fprintf(stderr,
   470             "%s:%d: Error: Unable to run leaks. 'system' returned: %d\n",
   471             __FILE__, __LINE__, ret);
   472     fflush(stderr);
   473   }
   474 }
   475 // COV_NF_END
   477 static __attribute__((constructor)) void _GTMInstallLeaks(void) {
   478   BOOL checkLeaks = YES;
   479 #if !GTM_IPHONE_SDK
   480   checkLeaks = GTMIsGarbageCollectionEnabled() ? NO : YES;
   481 #endif  // !GTM_IPHONE_SDK
   482   if (checkLeaks) {
   483     checkLeaks = getenv("GTM_ENABLE_LEAKS") ? YES : NO;
   484     if (checkLeaks) {
   485       // COV_NF_START
   486       // We don't have leak checking on by default, so this won't be hit.
   487       fprintf(stderr, "Leak Checking Enabled\n");
   488       fflush(stderr);
   489       int ret = atexit(&_GTMRunLeaks);
   490       // To avoid unused variable warning when _GTMDevAssert is stripped.
   491       (void)ret;
   492       _GTMDevAssert(ret == 0,
   493                     @"Unable to install _GTMRunLeaks as an atexit handler (%d)",
   494                     errno);
   495       // COV_NF_END
   496     }
   497   }
   498 }
   500 #endif   // !GTM_IPHONE_DEVICE && !GTM_SUPPRESS_RUN_LEAKS_HOOK

mercurial