PageRenderTime 84ms CodeModel.GetById 12ms app.highlight 67ms RepoModel.GetById 1ms app.codeStats 0ms

/MapView/GTM/GTMSenTestCase.m

http://github.com/route-me/route-me
Objective C | 458 lines | 350 code | 60 blank | 48 comment | 34 complexity | a7238257d4eaad9b6e99842d0e762d89 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#import <unistd.h>
 21#import "GTMObjC2Runtime.h"
 22
 23#if !GTM_IPHONE_SDK
 24#import "GTMGarbageCollection.h"
 25#endif  // !GTM_IPHONE_SDK
 26
 27#if GTM_IPHONE_SDK
 28#import <stdarg.h>
 29
 30@interface NSException (GTMSenTestPrivateAdditions)
 31+ (NSException *)failureInFile:(NSString *)filename
 32                        atLine:(int)lineNumber
 33                        reason:(NSString *)reason;
 34@end
 35
 36@implementation NSException (GTMSenTestPrivateAdditions)
 37+ (NSException *)failureInFile:(NSString *)filename
 38                        atLine:(int)lineNumber
 39                        reason:(NSString *)reason {
 40  NSDictionary *userInfo =
 41    [NSDictionary dictionaryWithObjectsAndKeys:
 42     [NSNumber numberWithInteger:lineNumber], SenTestLineNumberKey,
 43     filename, SenTestFilenameKey,
 44     nil];
 45
 46  return [self exceptionWithName:SenTestFailureException
 47                          reason:reason
 48                        userInfo:userInfo];
 49}
 50@end
 51
 52@implementation NSException (GTMSenTestAdditions)
 53
 54+ (NSException *)failureInFile:(NSString *)filename
 55                        atLine:(int)lineNumber
 56               withDescription:(NSString *)formatString, ... {
 57
 58  NSString *testDescription = @"";
 59  if (formatString) {
 60    va_list vl;
 61    va_start(vl, formatString);
 62    testDescription =
 63      [[[NSString alloc] initWithFormat:formatString arguments:vl] autorelease];
 64    va_end(vl);
 65  }
 66
 67  NSString *reason = testDescription;
 68
 69  return [self failureInFile:filename atLine:lineNumber reason:reason];
 70}
 71
 72+ (NSException *)failureInCondition:(NSString *)condition
 73                             isTrue:(BOOL)isTrue
 74                             inFile:(NSString *)filename
 75                             atLine:(int)lineNumber
 76                    withDescription:(NSString *)formatString, ... {
 77
 78  NSString *testDescription = @"";
 79  if (formatString) {
 80    va_list vl;
 81    va_start(vl, formatString);
 82    testDescription =
 83      [[[NSString alloc] initWithFormat:formatString arguments:vl] autorelease];
 84    va_end(vl);
 85  }
 86
 87  NSString *reason = [NSString stringWithFormat:@"'%@' should be %s. %@",
 88                      condition, isTrue ? "TRUE" : "FALSE", testDescription];
 89
 90  return [self failureInFile:filename atLine:lineNumber reason:reason];
 91}
 92
 93+ (NSException *)failureInEqualityBetweenObject:(id)left
 94                                      andObject:(id)right
 95                                         inFile:(NSString *)filename
 96                                         atLine:(int)lineNumber
 97                                withDescription:(NSString *)formatString, ... {
 98
 99  NSString *testDescription = @"";
100  if (formatString) {
101    va_list vl;
102    va_start(vl, formatString);
103    testDescription =
104      [[[NSString alloc] initWithFormat:formatString arguments:vl] autorelease];
105    va_end(vl);
106  }
107
108  NSString *reason =
109    [NSString stringWithFormat:@"'%@' should be equal to '%@'. %@",
110     [left description], [right description], testDescription];
111
112  return [self failureInFile:filename atLine:lineNumber reason:reason];
113}
114
115+ (NSException *)failureInEqualityBetweenValue:(NSValue *)left
116                                      andValue:(NSValue *)right
117                                  withAccuracy:(NSValue *)accuracy
118                                        inFile:(NSString *)filename
119                                        atLine:(int)lineNumber
120                               withDescription:(NSString *)formatString, ... {
121
122  NSString *testDescription = @"";
123  if (formatString) {
124    va_list vl;
125    va_start(vl, formatString);
126    testDescription =
127      [[[NSString alloc] initWithFormat:formatString arguments:vl] autorelease];
128    va_end(vl);
129  }
130
131  NSString *reason;
132  if (accuracy) {
133    reason =
134      [NSString stringWithFormat:@"'%@' should be equal to '%@'. %@",
135       left, right, testDescription];
136  } else {
137    reason =
138      [NSString stringWithFormat:@"'%@' should be equal to '%@' +/-'%@'. %@",
139       left, right, accuracy, testDescription];
140  }
141
142  return [self failureInFile:filename atLine:lineNumber reason:reason];
143}
144
145+ (NSException *)failureInRaise:(NSString *)expression
146                         inFile:(NSString *)filename
147                         atLine:(int)lineNumber
148                withDescription:(NSString *)formatString, ... {
149
150  NSString *testDescription = @"";
151  if (formatString) {
152    va_list vl;
153    va_start(vl, formatString);
154    testDescription =
155      [[[NSString alloc] initWithFormat:formatString arguments:vl] autorelease];
156    va_end(vl);
157  }
158
159  NSString *reason = [NSString stringWithFormat:@"'%@' should raise. %@",
160                      expression, testDescription];
161
162  return [self failureInFile:filename atLine:lineNumber reason:reason];
163}
164
165+ (NSException *)failureInRaise:(NSString *)expression
166                      exception:(NSException *)exception
167                         inFile:(NSString *)filename
168                         atLine:(int)lineNumber
169                withDescription:(NSString *)formatString, ... {
170
171  NSString *testDescription = @"";
172  if (formatString) {
173    va_list vl;
174    va_start(vl, formatString);
175    testDescription =
176      [[[NSString alloc] initWithFormat:formatString arguments:vl] autorelease];
177    va_end(vl);
178  }
179
180  NSString *reason;
181  if ([[exception name] isEqualToString:SenTestFailureException]) {
182    // it's our exception, assume it has the right description on it.
183    reason = [exception reason];
184  } else {
185    // not one of our exception, use the exceptions reason and our description
186    reason = [NSString stringWithFormat:@"'%@' raised '%@'. %@",
187              expression, [exception reason], testDescription];
188  }
189
190  return [self failureInFile:filename atLine:lineNumber reason:reason];
191}
192
193@end
194
195NSString *STComposeString(NSString *formatString, ...) {
196  NSString *reason = @"";
197  if (formatString) {
198    va_list vl;
199    va_start(vl, formatString);
200    reason =
201      [[[NSString alloc] initWithFormat:formatString arguments:vl] autorelease];
202    va_end(vl);
203  }
204  return reason;
205}
206
207NSString *const SenTestFailureException = @"SenTestFailureException";
208NSString *const SenTestFilenameKey = @"SenTestFilenameKey";
209NSString *const SenTestLineNumberKey = @"SenTestLineNumberKey";
210
211@interface SenTestCase (SenTestCasePrivate)
212// our method of logging errors
213+ (void)printException:(NSException *)exception fromTestName:(NSString *)name;
214@end
215
216@implementation SenTestCase
217+ (id)testCaseWithInvocation:(NSInvocation *)anInvocation {
218  return [[[[self class] alloc] initWithInvocation:anInvocation] autorelease];
219}
220
221- (id)initWithInvocation:(NSInvocation *)anInvocation {
222  if ((self = [super init])) {
223    invocation_ = [anInvocation retain];
224  }
225  return self;
226}
227
228- (void)dealloc {
229  [invocation_ release];
230  [super dealloc];
231}
232
233- (void)failWithException:(NSException*)exception {
234  [exception raise];
235}
236
237- (void)setUp {
238}
239
240- (void)performTest {
241  @try {
242    [self invokeTest];
243  } @catch (NSException *exception) {
244    [[self class] printException:exception
245                    fromTestName:NSStringFromSelector([self selector])];
246    [exception raise];
247  }
248}
249
250- (NSInvocation *)invocation {
251  return invocation_;
252}
253
254- (SEL)selector {
255  return [invocation_ selector];
256}
257
258+ (void)printException:(NSException *)exception fromTestName:(NSString *)name {
259  NSDictionary *userInfo = [exception userInfo];
260  NSString *filename = [userInfo objectForKey:SenTestFilenameKey];
261  NSNumber *lineNumber = [userInfo objectForKey:SenTestLineNumberKey];
262  NSString *className = NSStringFromClass([self class]);
263  if ([filename length] == 0) {
264    filename = @"Unknown.m";
265  }
266  fprintf(stderr, "%s:%ld: error: -[%s %s] : %s\n",
267          [filename UTF8String],
268          (long)[lineNumber integerValue],
269          [className UTF8String],
270          [name UTF8String],
271          [[exception reason] UTF8String]);
272  fflush(stderr);
273}
274
275- (void)invokeTest {
276  NSException *e = nil;
277  @try {
278    // Wrap things in autorelease pools because they may
279    // have an STMacro in their dealloc which may get called
280    // when the pool is cleaned up
281    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
282    // We don't log exceptions here, instead we let the person that called
283    // this log the exception.  This ensures they are only logged once but the
284    // outer layers get the exceptions to report counts, etc.
285    @try {
286      [self setUp];
287      @try {
288        NSInvocation *invocation = [self invocation];
289        [invocation setTarget:self];
290        [invocation invoke];
291      } @catch (NSException *exception) {
292        e = [exception retain];
293      }
294      [self tearDown];
295    } @catch (NSException *exception) {
296      e = [exception retain];
297    }
298    [pool release];
299  } @catch (NSException *exception) {
300    e = [exception retain];
301  }
302  if (e) {
303    [e autorelease];
304    [e raise];
305  }
306}
307
308- (void)tearDown {
309}
310
311- (NSString *)description {
312  // This matches the description OCUnit would return to you
313  return [NSString stringWithFormat:@"-[%@ %@]", [self class], 
314          NSStringFromSelector([self selector])];
315}
316
317// Used for sorting methods below
318static int MethodSort(const void *a, const void *b) {
319  const char *nameA = sel_getName(method_getName(*(Method*)a));
320  const char *nameB = sel_getName(method_getName(*(Method*)b));
321  return strcmp(nameA, nameB);
322}
323
324+ (NSArray *)testInvocations {
325  NSMutableArray *invocations = nil;
326  unsigned int methodCount;
327  Method *methods = class_copyMethodList(self, &methodCount);
328  if (methods) {
329    // This handles disposing of methods for us even if an exception should fly.
330    [NSData dataWithBytesNoCopy:methods
331                         length:sizeof(Method) * methodCount];
332    // Sort our methods so they are called in Alphabetical order just
333    // because we can.
334    qsort(methods, methodCount, sizeof(Method), MethodSort);
335    invocations = [NSMutableArray arrayWithCapacity:methodCount];
336    for (size_t i = 0; i < methodCount; ++i) {
337      Method currMethod = methods[i];
338      SEL sel = method_getName(currMethod);
339      char *returnType = NULL;
340      const char *name = sel_getName(sel);
341      // If it starts with test, takes 2 args (target and sel) and returns
342      // void run it.
343      if (strstr(name, "test") == name) {
344        returnType = method_copyReturnType(currMethod);
345        if (returnType) {
346          // This handles disposing of returnType for us even if an
347          // exception should fly. Length +1 for the terminator, not that
348          // the length really matters here, as we never reference inside
349          // the data block.
350          [NSData dataWithBytesNoCopy:returnType
351                               length:strlen(returnType) + 1];
352        }
353      }
354      if (returnType  // True if name starts with "test"
355          && strcmp(returnType, @encode(void)) == 0
356          && method_getNumberOfArguments(currMethod) == 2) {
357        NSMethodSignature *sig = [self instanceMethodSignatureForSelector:sel];
358        NSInvocation *invocation 
359          = [NSInvocation invocationWithMethodSignature:sig];
360        [invocation setSelector:sel];
361        [invocations addObject:invocation];
362      }
363    }
364  }
365  return invocations;
366}
367
368@end
369
370#endif  // GTM_IPHONE_SDK
371
372@implementation GTMTestCase : SenTestCase
373- (void)invokeTest {
374  Class devLogClass = NSClassFromString(@"GTMUnitTestDevLog");
375  if (devLogClass) {
376    [devLogClass performSelector:@selector(enableTracking)];
377    [devLogClass performSelector:@selector(verifyNoMoreLogsExpected)];
378
379  }
380  [super invokeTest];
381  if (devLogClass) {
382    [devLogClass performSelector:@selector(verifyNoMoreLogsExpected)];
383    [devLogClass performSelector:@selector(disableTracking)];
384  }
385}
386
387+ (BOOL)isAbstractTestCase {
388  NSString *name = NSStringFromClass(self);
389  return [name rangeOfString:@"AbstractTest"].location != NSNotFound;
390}
391
392+ (NSArray *)testInvocations {
393  NSArray *invocations = nil;
394  if (![self isAbstractTestCase]) {
395    invocations = [super testInvocations];
396  }
397  return invocations;
398}
399
400@end
401
402// Leak detection
403#if !GTM_IPHONE_DEVICE
404// Don't want to get leaks on the iPhone Device as the device doesn't
405// have 'leaks'. The simulator does though.
406
407// COV_NF_START
408// We don't have leak checking on by default, so this won't be hit.
409static void _GTMRunLeaks(void) {
410  // This is an atexit handler. It runs leaks for us to check if we are 
411  // leaking anything in our tests. 
412  const char* cExclusionsEnv = getenv("GTM_LEAKS_SYMBOLS_TO_IGNORE");
413  NSMutableString *exclusions = [NSMutableString string];
414  if (cExclusionsEnv) {
415    NSString *exclusionsEnv = [NSString stringWithUTF8String:cExclusionsEnv];
416    NSArray *exclusionsArray = [exclusionsEnv componentsSeparatedByString:@","];
417    NSString *exclusion;
418    NSCharacterSet *wcSet = [NSCharacterSet whitespaceCharacterSet];
419    GTM_FOREACH_OBJECT(exclusion, exclusionsArray) {
420      exclusion = [exclusion stringByTrimmingCharactersInSet:wcSet];
421      [exclusions appendFormat:@"-exclude \"%@\" ", exclusion];
422    }
423  }
424  NSString *string 
425    = [NSString stringWithFormat:@"/usr/bin/leaks %@%d"
426       @"| /usr/bin/sed -e 's/Leak: /Leaks:0: warning: Leak /'", 
427       exclusions, getpid()];
428  int ret = system([string UTF8String]);
429  if (ret) {
430    fprintf(stderr, "%s:%d: Error: Unable to run leaks. 'system' returned: %d", 
431            __FILE__, __LINE__, ret);
432    fflush(stderr);
433  }
434}
435// COV_NF_END
436
437static __attribute__((constructor)) void _GTMInstallLeaks(void) {
438  BOOL checkLeaks = YES;
439#if !GTM_IPHONE_SDK
440  checkLeaks = GTMIsGarbageCollectionEnabled() ? NO : YES;
441#endif  // !GTM_IPHONE_SDK
442  if (checkLeaks) {
443    checkLeaks = getenv("GTM_ENABLE_LEAKS") ? YES : NO;
444    if (checkLeaks) {
445      // COV_NF_START
446      // We don't have leak checking on by default, so this won't be hit.
447      fprintf(stderr, "Leak Checking Enabled\n");
448      fflush(stderr);
449      int ret = atexit(&_GTMRunLeaks);
450      _GTMDevAssert(ret == 0, 
451                    @"Unable to install _GTMRunLeaks as an atexit handler (%d)", 
452                    errno);
453      // COV_NF_END
454    }  
455  }
456}
457
458#endif   // !GTM_IPHONE_DEVICE