PageRenderTime 125ms CodeModel.GetById 11ms app.highlight 107ms RepoModel.GetById 2ms app.codeStats 0ms

/core/externals/update-engine/externals/google-toolbox-for-mac/UnitTesting/GTMSenTestCase.m

http://macfuse.googlecode.com/
Objective C | 505 lines | 374 code | 65 blank | 66 comment | 39 complexity | 2855ced15c9e5b1daf5db5e92d998216 MD5 | raw file
  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//
 18
 19#import "GTMSenTestCase.h"
 20
 21#import <unistd.h>
 22#if GTM_IPHONE_SIMULATOR
 23#import <objc/message.h>
 24#endif
 25
 26#import "GTMObjC2Runtime.h"
 27#import "GTMUnitTestDevLog.h"
 28
 29#if GTM_IPHONE_SDK
 30#import <UIKit/UIKit.h>
 31#endif  // GTM_IPHONE_SDK
 32
 33#if GTM_IPHONE_SDK && !GTM_IPHONE_USE_SENTEST
 34#import <stdarg.h>
 35
 36@interface NSException (GTMSenTestPrivateAdditions)
 37+ (NSException *)failureInFile:(NSString *)filename
 38                        atLine:(int)lineNumber
 39                        reason:(NSString *)reason;
 40@end
 41
 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];
 51
 52  return [self exceptionWithName:SenTestFailureException
 53                          reason:reason
 54                        userInfo:userInfo];
 55}
 56@end
 57
 58@implementation NSException (GTMSenTestAdditions)
 59
 60+ (NSException *)failureInFile:(NSString *)filename
 61                        atLine:(int)lineNumber
 62               withDescription:(NSString *)formatString, ... {
 63
 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  }
 72
 73  NSString *reason = testDescription;
 74
 75  return [self failureInFile:filename atLine:lineNumber reason:reason];
 76}
 77
 78+ (NSException *)failureInCondition:(NSString *)condition
 79                             isTrue:(BOOL)isTrue
 80                             inFile:(NSString *)filename
 81                             atLine:(int)lineNumber
 82                    withDescription:(NSString *)formatString, ... {
 83
 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  }
 92
 93  NSString *reason = [NSString stringWithFormat:@"'%@' should be %s. %@",
 94                      condition, isTrue ? "false" : "true", testDescription];
 95
 96  return [self failureInFile:filename atLine:lineNumber reason:reason];
 97}
 98
 99+ (NSException *)failureInEqualityBetweenObject:(id)left
100                                      andObject:(id)right
101                                         inFile:(NSString *)filename
102                                         atLine:(int)lineNumber
103                                withDescription:(NSString *)formatString, ... {
104
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  }
113
114  NSString *reason =
115    [NSString stringWithFormat:@"'%@' should be equal to '%@'. %@",
116     [left description], [right description], testDescription];
117
118  return [self failureInFile:filename atLine:lineNumber reason:reason];
119}
120
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, ... {
127
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  }
136
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  }
147
148  return [self failureInFile:filename atLine:lineNumber reason:reason];
149}
150
151+ (NSException *)failureInRaise:(NSString *)expression
152                         inFile:(NSString *)filename
153                         atLine:(int)lineNumber
154                withDescription:(NSString *)formatString, ... {
155
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  }
164
165  NSString *reason = [NSString stringWithFormat:@"'%@' should raise. %@",
166                      expression, testDescription];
167
168  return [self failureInFile:filename atLine:lineNumber reason:reason];
169}
170
171+ (NSException *)failureInRaise:(NSString *)expression
172                      exception:(NSException *)exception
173                         inFile:(NSString *)filename
174                         atLine:(int)lineNumber
175                withDescription:(NSString *)formatString, ... {
176
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  }
185
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  }
195
196  return [self failureInFile:filename atLine:lineNumber reason:reason];
197}
198
199@end
200
201NSString *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}
212
213NSString *const SenTestFailureException = @"SenTestFailureException";
214NSString *const SenTestFilenameKey = @"SenTestFilenameKey";
215NSString *const SenTestLineNumberKey = @"SenTestLineNumberKey";
216
217@interface SenTestCase (SenTestCasePrivate)
218// our method of logging errors
219+ (void)printException:(NSException *)exception fromTestName:(NSString *)name;
220@end
221
222@implementation SenTestCase
223+ (id)testCaseWithInvocation:(NSInvocation *)anInvocation {
224  return [[[self alloc] initWithInvocation:anInvocation] autorelease];
225}
226
227- (id)initWithInvocation:(NSInvocation *)anInvocation {
228  if ((self = [super init])) {
229    invocation_ = [anInvocation retain];
230  }
231  return self;
232}
233
234- (void)dealloc {
235  [invocation_ release];
236  [super dealloc];
237}
238
239- (void)failWithException:(NSException*)exception {
240  [exception raise];
241}
242
243- (void)setUp {
244}
245
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}
255
256- (NSInvocation *)invocation {
257  return invocation_;
258}
259
260- (SEL)selector {
261  return [invocation_ selector];
262}
263
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}
280
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}
321
322- (void)tearDown {
323}
324
325- (NSString *)description {
326  // This matches the description OCUnit would return to you
327  return [NSString stringWithFormat:@"-[%@ %@]", [self class],
328          NSStringFromSelector([self selector])];
329}
330
331// Used for sorting methods below
332static NSInteger 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}
339
340
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}
397
398@end
399
400#endif  // GTM_IPHONE_SDK && !GTM_IPHONE_USE_SENTEST
401
402@implementation GTMTestCase
403
404- (void)invokeTest {
405  NSAutoreleasePool *localPool = [[NSAutoreleasePool alloc] init];
406  Class devLogClass = NSClassFromString(@"GTMUnitTestDevLog");
407  if (devLogClass) {
408    [devLogClass performSelector:@selector(enableTracking)];
409    [devLogClass performSelector:@selector(verifyNoMoreLogsExpected)];
410
411  }
412  [super invokeTest];
413  if (devLogClass) {
414    [devLogClass performSelector:@selector(verifyNoMoreLogsExpected)];
415    [devLogClass performSelector:@selector(disableTracking)];
416  }
417  [localPool drain];
418}
419
420+ (BOOL)isAbstractTestCase {
421  NSString *name = NSStringFromClass(self);
422  return [name rangeOfString:@"AbstractTest"].location != NSNotFound;
423}
424
425#if GTM_IPHONE_SDK
426- (UIImage *)imageFromResource:(NSString *)resource {
427  NSBundle *bundle = [NSBundle bundleForClass:[self class]];
428  NSString *path = [bundle pathForResource:resource ofType:nil];
429  UIImage *image = [UIImage imageWithContentsOfFile:path];
430  STAssertNotNil(image, @"Could not load image from resource: %@", path);
431  return image;
432}
433#endif
434
435+ (NSArray *)testInvocations {
436  NSArray *invocations = nil;
437  if (![self isAbstractTestCase]) {
438    invocations = [super testInvocations];
439  }
440  return invocations;
441}
442
443@end
444
445// Leak detection
446#if !GTM_IPHONE_DEVICE && !GTM_SUPPRESS_RUN_LEAKS_HOOK
447// Don't want to get leaks on the iPhone Device as the device doesn't
448// have 'leaks'. The simulator does though.
449
450// COV_NF_START
451// We don't have leak checking on by default, so this won't be hit.
452static void _GTMRunLeaks(void) {
453  // This is an atexit handler. It runs leaks for us to check if we are
454  // leaking anything in our tests.
455  const char* cExclusionsEnv = getenv("GTM_LEAKS_SYMBOLS_TO_IGNORE");
456  NSMutableString *exclusions = [NSMutableString string];
457  if (cExclusionsEnv) {
458    NSString *exclusionsEnv = [NSString stringWithUTF8String:cExclusionsEnv];
459    NSArray *exclusionsArray = [exclusionsEnv componentsSeparatedByString:@","];
460    NSString *exclusion;
461    NSCharacterSet *wcSet = [NSCharacterSet whitespaceCharacterSet];
462    GTM_FOREACH_OBJECT(exclusion, exclusionsArray) {
463      exclusion = [exclusion stringByTrimmingCharactersInSet:wcSet];
464      [exclusions appendFormat:@"-exclude \"%@\" ", exclusion];
465    }
466  }
467  // Clearing out DYLD_ROOT_PATH because iPhone Simulator framework libraries
468  // are different from regular OS X libraries and leaks will fail to run
469  // because of missing symbols. Also capturing the output of leaks and then
470  // pipe rather than a direct pipe, because otherwise if leaks failed,
471  // the system() call will still be successful. Bug:
472  // http://code.google.com/p/google-toolbox-for-mac/issues/detail?id=56
473  NSString *string
474    = [NSString stringWithFormat:
475       @"LeakOut=`DYLD_ROOT_PATH='' /usr/bin/leaks %@%d` &&"
476       @"echo \"$LeakOut\"|/usr/bin/sed -e 's/Leak: /Leaks:0: warning: Leak /'",
477       exclusions, getpid()];
478  int ret = system([string UTF8String]);
479  if (ret) {
480    fprintf(stderr,
481            "%s:%d: Error: Unable to run leaks. 'system' returned: %d\n",
482            __FILE__, __LINE__, ret);
483    fflush(stderr);
484  }
485}
486// COV_NF_END
487
488static __attribute__((constructor)) void _GTMInstallLeaks(void) {
489  BOOL checkLeaks = getenv("GTM_ENABLE_LEAKS") ? YES : NO;
490  if (checkLeaks) {
491    // COV_NF_START
492    // We don't have leak checking on by default, so this won't be hit.
493    fprintf(stderr, "Leak Checking Enabled\n");
494    fflush(stderr);
495    int ret = atexit(&_GTMRunLeaks);
496    // To avoid unused variable warning when _GTMDevAssert is stripped.
497    (void)ret;
498    _GTMDevAssert(ret == 0,
499                  @"Unable to install _GTMRunLeaks as an atexit handler (%d)",
500                  errno);
501    // COV_NF_END
502  }
503}
504
505#endif   // !GTM_IPHONE_DEVICE && !GTM_SUPPRESS_RUN_LEAKS_HOOK