OCMFunctions.m 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373
  1. /*
  2. * Copyright (c) 2014-2015 Erik Doernenburg and contributors
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License"); you may
  5. * not use these files except in compliance with the License. You may obtain
  6. * a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  12. * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  13. * License for the specific language governing permissions and limitations
  14. * under the License.
  15. */
  16. #import <objc/runtime.h>
  17. #import "OCMFunctions.h"
  18. #import "OCMLocation.h"
  19. #import "OCClassMockObject.h"
  20. #import "OCPartialMockObject.h"
  21. #pragma mark Known private API
  22. @interface NSException(OCMKnownExceptionMethods)
  23. + (NSException *)failureInFile:(NSString *)file atLine:(int)line withDescription:(NSString *)formatString, ...;
  24. @end
  25. @interface NSObject(OCMKnownTestCaseMethods)
  26. - (void)recordFailureWithDescription:(NSString *)description inFile:(NSString *)file atLine:(NSUInteger)line expected:(BOOL)expected;
  27. - (void)failWithException:(NSException *)exception;
  28. @end
  29. #pragma mark Functions related to ObjC type system
  30. BOOL OCMIsObjectType(const char *objCType)
  31. {
  32. objCType = OCMTypeWithoutQualifiers(objCType);
  33. if(strcmp(objCType, @encode(id)) == 0 || strcmp(objCType, @encode(Class)) == 0)
  34. return YES;
  35. // if the returnType is a typedef to an object, it has the form ^{OriginClass=#}
  36. NSString *regexString = @"^\\^\\{(.*)=#.*\\}";
  37. NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:regexString options:0 error:NULL];
  38. NSString *type = [NSString stringWithCString:objCType encoding:NSASCIIStringEncoding];
  39. if([regex numberOfMatchesInString:type options:0 range:NSMakeRange(0, type.length)] > 0)
  40. return YES;
  41. // if the return type is a block we treat it like an object
  42. // TODO: if the runtime were to encode the block's argument and/or return types, this test would not be sufficient
  43. if(strcmp(objCType, @encode(void(^)())) == 0)
  44. return YES;
  45. return NO;
  46. }
  47. const char *OCMTypeWithoutQualifiers(const char *objCType)
  48. {
  49. while(strchr("rnNoORV", objCType[0]) != NULL)
  50. objCType += 1;
  51. return objCType;
  52. }
  53. static BOOL ParseStructType(const char *type, const char **typeEnd, const char **typeNameEnd, const char **typeEqualSign)
  54. {
  55. if (type[0] != '{' && type[0] != '(')
  56. return NO;
  57. *typeNameEnd = NULL;
  58. *typeEqualSign = NULL;
  59. const char endChar = type[0] == '{' ? '}' : ')';
  60. for (const char* ptr = type + 1; *ptr; ++ptr) {
  61. switch (*ptr) {
  62. case '(':
  63. case '{':
  64. {
  65. const char *subTypeEnd;
  66. const char *subTypeNameEnd;
  67. const char *subTypeEqualSign;
  68. if (!ParseStructType(ptr, &subTypeEnd, &subTypeNameEnd, &subTypeEqualSign))
  69. return NO;
  70. ptr = subTypeEnd;
  71. break;
  72. }
  73. case '=':
  74. {
  75. if (!*typeEqualSign) {
  76. *typeNameEnd = ptr;
  77. *typeEqualSign = ptr;
  78. }
  79. break;
  80. }
  81. case ')':
  82. case '}':
  83. {
  84. if (*ptr == endChar) {
  85. *typeEnd = ptr;
  86. if (!*typeNameEnd)
  87. *typeNameEnd = ptr;
  88. return YES;
  89. }
  90. break;
  91. }
  92. default:
  93. break;
  94. }
  95. }
  96. return NO;
  97. }
  98. /*
  99. * Sometimes an external type is an opaque struct (which will have an @encode of "{structName}"
  100. * or "{structName=}") but the actual method return type, or property type, will know the contents
  101. * of the struct (so will have an objcType of say "{structName=iiSS}". This function will determine
  102. * those are equal provided they have the same structure name, otherwise everything else will be
  103. * compared textually. This can happen particularly for pointers to such structures, which still
  104. * encode what is being pointed to.
  105. *
  106. * In addition, this funtion will consider structures with unknown names, encoded as "{?=}, equal to
  107. * structures with any name. This means that "{?=dd}" and "{foo=dd}", and even "{?=}" and "{foo=dd}",
  108. * are considered equal.
  109. *
  110. * For some types some runtime functions throw exceptions, which is why we wrap this in an
  111. * exception handler just below.
  112. */
  113. static BOOL OCMEqualTypesAllowingOpaqueStructsInternal(const char *type1, const char *type2)
  114. {
  115. type1 = OCMTypeWithoutQualifiers(type1);
  116. type2 = OCMTypeWithoutQualifiers(type2);
  117. switch (type1[0])
  118. {
  119. case '{':
  120. case '(':
  121. {
  122. if (type2[0] != type1[0])
  123. return NO;
  124. const char *type1End;
  125. const char *type1NameEnd;
  126. const char *type1EqualSign;
  127. if (!ParseStructType(type1, &type1End, &type1NameEnd, &type1EqualSign))
  128. return NO;
  129. const char *type2End;
  130. const char *type2NameEnd;
  131. const char *type2EqualSign;
  132. if (!ParseStructType(type2, &type2End, &type2NameEnd, &type2EqualSign))
  133. return NO;
  134. /* Opaque types either don't have an equals sign (just the name and the end brace), or
  135. * empty content after the equals sign.
  136. * We want that to compare the same as a type of the same name but with the content.
  137. */
  138. BOOL type1Opaque = (type1EqualSign == NULL || type1EqualSign + 1 == type1End);
  139. BOOL type2Opaque = (type2EqualSign == NULL || type2EqualSign + 2 == type2End);
  140. intptr_t type1NameLen = type1NameEnd - type1;
  141. intptr_t type2NameLen = type2NameEnd - type2;
  142. /* If the names are not equal and neither of the names is a question mark, return NO */
  143. if ((type1NameLen != type2NameLen || strncmp(type1, type2, type1NameLen)) &&
  144. !((type1NameLen == 2) && (type1[1] == '?')) && !((type2NameLen == 2) && (type2[1] == '?')) &&
  145. !(type1NameLen == 1 || type2NameLen == 1))
  146. return NO;
  147. /* If the same name, and at least one is opaque, that is close enough. */
  148. if (type1Opaque || type2Opaque)
  149. return YES;
  150. /* Otherwise, compare all the elements. Use NSGetSizeAndAlignment to walk through the struct elements. */
  151. type1 = type1EqualSign + 1;
  152. type2 = type2EqualSign + 1;
  153. while (type1 != type1End && *type1)
  154. {
  155. if (!OCMEqualTypesAllowingOpaqueStructs(type1, type2))
  156. return NO;
  157. if (*type1 != '{' && *type1 != '(') {
  158. type1 = NSGetSizeAndAlignment(type1, NULL, NULL);
  159. type2 = NSGetSizeAndAlignment(type2, NULL, NULL);
  160. } else {
  161. const char *subType1End;
  162. const char *subType1NameEnd;
  163. const char *subType1EqualSign;
  164. if (!ParseStructType(type1, &subType1End, &subType1NameEnd, &subType1EqualSign))
  165. return NO;
  166. const char *subType2End;
  167. const char *subType2NameEnd;
  168. const char *subType2EqualSign;
  169. if (!ParseStructType(type2, &subType2End, &subType2NameEnd, &subType2EqualSign))
  170. return NO;
  171. type1 = subType1End + 1;
  172. type2 = subType2End + 1;
  173. }
  174. }
  175. return YES;
  176. }
  177. case '^':
  178. /* for a pointer, make sure the other is a pointer, then recursively compare the rest */
  179. if (type2[0] != type1[0])
  180. return NO;
  181. return OCMEqualTypesAllowingOpaqueStructs(type1 + 1, type2 + 1);
  182. case '?':
  183. return type2[0] == '?';
  184. case '\0':
  185. return type2[0] == '\0';
  186. default:
  187. {
  188. // Move the type pointers past the current types, then compare that region
  189. const char *afterType1 = NSGetSizeAndAlignment(type1, NULL, NULL);
  190. const char *afterType2 = NSGetSizeAndAlignment(type2, NULL, NULL);
  191. intptr_t type1Len = afterType1 - type1;
  192. intptr_t type2Len = afterType2 - type2;
  193. return (type1Len == type2Len && (strncmp(type1, type2, type1Len) == 0));
  194. }
  195. }
  196. }
  197. BOOL OCMEqualTypesAllowingOpaqueStructs(const char *type1, const char *type2)
  198. {
  199. @try
  200. {
  201. return OCMEqualTypesAllowingOpaqueStructsInternal(type1, type2);
  202. }
  203. @catch (NSException *e)
  204. {
  205. /* Probably a bitfield or something that NSGetSizeAndAlignment chokes on, oh well */
  206. return NO;
  207. }
  208. }
  209. #pragma mark Creating classes
  210. Class OCMCreateSubclass(Class class, void *ref)
  211. {
  212. const char *className = [[NSString stringWithFormat:@"%@-%p-%u", NSStringFromClass(class), ref, arc4random()] UTF8String];
  213. Class subclass = objc_allocateClassPair(class, className, 0);
  214. objc_registerClassPair(subclass);
  215. return subclass;
  216. }
  217. #pragma mark Directly manipulating the isa pointer (look away)
  218. void OCMSetIsa(id object, Class class)
  219. {
  220. *((Class *)object) = class;
  221. }
  222. Class OCMGetIsa(id object)
  223. {
  224. return *((Class *)object);
  225. }
  226. #pragma mark Alias for renaming real methods
  227. static NSString *const OCMRealMethodAliasPrefix = @"ocmock_replaced_";
  228. static const char *const OCMRealMethodAliasPrefixCString = "ocmock_replaced_";
  229. BOOL OCMIsAliasSelector(SEL selector)
  230. {
  231. return [NSStringFromSelector(selector) hasPrefix:OCMRealMethodAliasPrefix];
  232. }
  233. SEL OCMAliasForOriginalSelector(SEL selector)
  234. {
  235. char aliasName[2048];
  236. const char *originalName = sel_getName(selector);
  237. strlcpy(aliasName, OCMRealMethodAliasPrefixCString, sizeof(aliasName));
  238. strlcat(aliasName, originalName, sizeof(aliasName));
  239. return sel_registerName(aliasName);
  240. }
  241. SEL OCMOriginalSelectorForAlias(SEL selector)
  242. {
  243. if(!OCMIsAliasSelector(selector))
  244. [NSException raise:NSInvalidArgumentException format:@"Not an alias selector; found %@", NSStringFromSelector(selector)];
  245. NSString *string = NSStringFromSelector(selector);
  246. return NSSelectorFromString([string substringFromIndex:[OCMRealMethodAliasPrefix length]]);
  247. }
  248. #pragma mark Wrappers around associative references
  249. static NSString *const OCMClassMethodMockObjectKey = @"OCMClassMethodMockObjectKey";
  250. void OCMSetAssociatedMockForClass(OCClassMockObject *mock, Class aClass)
  251. {
  252. if((mock != nil) && (objc_getAssociatedObject(aClass, OCMClassMethodMockObjectKey) != nil))
  253. [NSException raise:NSInternalInconsistencyException format:@"Another mock is already associated with class %@", NSStringFromClass(aClass)];
  254. objc_setAssociatedObject(aClass, OCMClassMethodMockObjectKey, mock, OBJC_ASSOCIATION_ASSIGN);
  255. }
  256. OCClassMockObject *OCMGetAssociatedMockForClass(Class aClass, BOOL includeSuperclasses)
  257. {
  258. OCClassMockObject *mock = nil;
  259. do
  260. {
  261. mock = objc_getAssociatedObject(aClass, OCMClassMethodMockObjectKey);
  262. aClass = class_getSuperclass(aClass);
  263. }
  264. while((mock == nil) && (aClass != nil) && includeSuperclasses);
  265. return mock;
  266. }
  267. static NSString *const OCMPartialMockObjectKey = @"OCMPartialMockObjectKey";
  268. void OCMSetAssociatedMockForObject(OCClassMockObject *mock, id anObject)
  269. {
  270. if((mock != nil) && (objc_getAssociatedObject(anObject, OCMPartialMockObjectKey) != nil))
  271. [NSException raise:NSInternalInconsistencyException format:@"Another mock is already associated with object %@", anObject];
  272. objc_setAssociatedObject(anObject, OCMPartialMockObjectKey, mock, OBJC_ASSOCIATION_ASSIGN);
  273. }
  274. OCPartialMockObject *OCMGetAssociatedMockForObject(id anObject)
  275. {
  276. return objc_getAssociatedObject(anObject, OCMPartialMockObjectKey);
  277. }
  278. #pragma mark Functions related to IDE error reporting
  279. void OCMReportFailure(OCMLocation *loc, NSString *description)
  280. {
  281. id testCase = [loc testCase];
  282. if((testCase != nil) && [testCase respondsToSelector:@selector(recordFailureWithDescription:inFile:atLine:expected:)])
  283. {
  284. [testCase recordFailureWithDescription:description inFile:[loc file] atLine:[loc line] expected:NO];
  285. }
  286. else if((testCase != nil) && [testCase respondsToSelector:@selector(failWithException:)])
  287. {
  288. NSException *exception = nil;
  289. if([NSException instancesRespondToSelector:@selector(failureInFile:atLine:withDescription:)])
  290. {
  291. exception = [NSException failureInFile:[loc file] atLine:(int)[loc line] withDescription:description];
  292. }
  293. else
  294. {
  295. NSString *reason = [NSString stringWithFormat:@"%@:%lu %@", [loc file], (unsigned long)[loc line], description];
  296. exception = [NSException exceptionWithName:@"OCMockTestFailure" reason:reason userInfo:nil];
  297. }
  298. [testCase failWithException:exception];
  299. }
  300. else if(loc != nil)
  301. {
  302. NSLog(@"%@:%lu %@", [loc file], (unsigned long)[loc line], description);
  303. NSString *reason = [NSString stringWithFormat:@"%@:%lu %@", [loc file], (unsigned long)[loc line], description];
  304. [[NSException exceptionWithName:@"OCMockTestFailure" reason:reason userInfo:nil] raise];
  305. }
  306. else
  307. {
  308. NSLog(@"%@", description);
  309. [[NSException exceptionWithName:@"OCMockTestFailure" reason:description userInfo:nil] raise];
  310. }
  311. }