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